@@ -88,7 +88,7 @@ import type {
8888 RootDocumentSource ,
8989} from "@executablemd/core" ;
9090import { useAcpxProvider } from "@executablemd/acp" ;
91- import { command as hostCommand , cwd } from "@executablemd/runtime" ;
91+ import { command as hostCommand , cwd , Permissions } from "@executablemd/runtime" ;
9292import { runReplProgram } from "./repl/program.ts" ;
9393import { decodeLocation } from "./repl/route.ts" ;
9494import { renderAgentOptions , renderAgentOptionsJson , scanAgentArgs } from "./agent-options.ts" ;
@@ -250,7 +250,20 @@ const agentFields = {
250250 } ,
251251} ;
252252
253+ const absoluteField = {
254+ description : "allow absolute host Dir paths within existing filesystem restrictions" ,
255+ ...field ( z . boolean ( ) , field . default ( false ) ) ,
256+ } ;
257+
258+ function useAbsolutePermission ( allowed : boolean ) : Operation < void > {
259+ return Permissions . around (
260+ { files : ( [ operation ] , next ) => ( operation === "absolute" ? allowed : next ( operation ) ) } ,
261+ allowed ? { at : "min" } : undefined ,
262+ ) ;
263+ }
264+
253265const executionFields = {
266+ allowAbsolute : absoluteField ,
254267 include : {
255268 description : "component search directory" ,
256269 ...field ( z . array ( z . string ( ) ) , field . default ( [ "components" , "." ] ) , field . array ( ) ) ,
@@ -326,6 +339,7 @@ const REPL_DESCRIPTION = "Run and reconstruct one XMD entry in an interactive te
326339 * their entries is not the same choice as who their entries talk to.
327340 */
328341const replConfig = object ( {
342+ allowAbsolute : absoluteField ,
329343 attach : {
330344 description : "attach one interactive UI to this exact live XMD child" ,
331345 ...field ( z . string ( ) . optional ( ) ) ,
@@ -637,8 +651,10 @@ const REPL_HELP = [
637651 " Close/Stop. At narrow sizes the surface bar offers Sessions, Entries and" ,
638652 " Sidekick; only the selected outlet is mounted." ,
639653 "" ,
640- " --mount SOURCE:TARGET adds an external directory at a required document path." ,
641- " Repeat it: --mount /work/manuals:/reference --mount /work/fixtures:/fixtures." ,
654+ " --allow-absolute permits absolute host Dir paths, subject to write and enclosing restrictions." ,
655+ " It is off by default. File, File.Delete and Glob operands remain relative." ,
656+ " --mount SOURCE:TARGET maps a directory; it grants no filesystem permission." ,
657+ " Example: xmd repl --allow-absolute --mount /work/manuals:/reference." ,
642658 " --include <directory> and --include=<directory> select component directories." ,
643659 " Repeat them in search order; explicit values replace the default list [components, .]." ,
644660 " Relative directories resolve from the invocation working directory." ,
@@ -715,8 +731,13 @@ export const REPL_VALUES: readonly string[] = [
715731 "--attach" ,
716732] ;
717733
718- /** The three REPL switches, which carry none. */
719- const REPL_SWITCHES : readonly string [ ] = [ "--approve-all" , "--approve-reads" , "--deny-all" ] ;
734+ /** The REPL switches, which carry no value. */
735+ const REPL_SWITCHES : readonly string [ ] = [
736+ "--approve-all" ,
737+ "--approve-reads" ,
738+ "--deny-all" ,
739+ "--allow-absolute" ,
740+ ] ;
720741
721742export function replGrammarError (
722743 args : readonly string [ ] ,
@@ -2908,6 +2929,7 @@ function* dispatch(
29082929 }
29092930 announceSecretDetection ( config . secretDetection ) ;
29102931 const result = yield * scoped ( function * ( ) : Operation < Result < void > > {
2932+ yield * useAbsolutePermission ( config . allowAbsolute ) ;
29112933 // `<Elicit>` reaches a person through the browser form, and `xmd run`
29122934 // is the command a person is sitting in front of. Composed here, in the
29132935 // scope this profile assembles around its own document, because that is
@@ -3082,6 +3104,7 @@ function* dispatch(
30823104 if ( replHost . select !== undefined ) {
30833105 yield * replHost . select ( ) ;
30843106 }
3107+ yield * useAbsolutePermission ( command . config . allowAbsolute ) ;
30853108 // The Sidekick's own agent selection travels with it, as the name this
30863109 // line carried and nothing more: the profile settles the fallback, and
30873110 // whether anybody answers to that name is the first explicit message's
0 commit comments