Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions architecture.md

Large diffs are not rendered by default.

21 changes: 19 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { resolve } from "node:path";
import { parseWorkspaceMounts } from "./workspace-mounts.ts";
/**
* CLI — run an executable markdown document.
*
Expand Down Expand Up @@ -320,6 +322,10 @@ const REPL_DESCRIPTION = "Run and reconstruct one XMD entry in an interactive te
* their entries is not the same choice as who their entries talk to.
*/
const replConfig = object({
mount: {
description: "external SOURCE:TARGET directory; repeat for each explicit TARGET",
...field(z.array(z.string()), field.default([]), field.array()),
},
location: {
description:
"a location this REPL printed earlier — `xmd://repl/<execution>/<surface>...` — to reopen " +
Expand Down Expand Up @@ -598,6 +604,8 @@ const REPL_HELP = [
" Each one starts from the bindings the entries before it published, so a",
" later entry can read and revise what an earlier one left behind.",
"",
" --mount SOURCE:TARGET adds an external directory at a required document path.",
" Repeat it: --mount /work/manuals:/reference --mount /work/fixtures:/fixtures.",
" --include <directory> and --include=<directory> select component directories.",
" Repeat them in search order; explicit values replace the default list [components, .].",
" Relative directories resolve from the invocation working directory.",
Expand Down Expand Up @@ -657,14 +665,15 @@ export interface ReplHostAssembly {
* silence would let them believe they got it.
*/
/**
* The four REPL options that carry a value, by the spelling a caller writes.
* The five REPL options that carry a value, by the spelling a caller writes.
*
* Exported because the documentation check reads this command's own examples,
* and a token after one of these is that option's value rather than a location.
* A second roster beside this one would be a reader that disagreed with the
* command about what it had been handed.
*/
export const REPL_VALUES: readonly string[] = [
"--mount",
"--include",
"--agent-provider",
"--default-agent",
Expand Down Expand Up @@ -722,7 +731,7 @@ export function replGrammarError(
return (
`unrecognized option for xmd repl: ${name} — xmd repl takes one optional location, ` +
"the agent options --agent-provider, --default-agent, --approve-all, --approve-reads " +
"and --deny-all, --include for component directories, and --sidekick-agent for the Sidekick's own conversations"
"and --deny-all, --include for component directories, --mount SOURCE:TARGET for workspace directories, and --sidekick-agent for the Sidekick's own conversations"
);
}
if (positional.length > 1) {
Expand Down Expand Up @@ -2943,6 +2952,13 @@ function* dispatch(
yield* exit(1);
break;
}
const mountCwd = yield* cwd();
const mounts = parseWorkspaceMounts(command.config.mount, mountCwd, { resolve });
if (!mounts.ok) {
console.error(mounts.error.message);
yield* exit(1);
break;
}
// The Agent configuration, settled before a terminal exists: mutual
// exclusion, an unknown provider and `DEFAULT_AGENT_NAME` precedence are
// all decided here, so an invocation nobody could run refuses without
Expand Down Expand Up @@ -3015,6 +3031,7 @@ function* dispatch(
[testAgentChildDeclaration()],
);
},
mounts: mounts.value,
includes: command.config.include,
...(command.config.sidekickAgent === undefined
? {}
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/repl-entry-installation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WorkspaceMountTable } from "./workspace-mounts.ts";
/**
* The entry capability this host carries, as inert code.
*
Expand Down Expand Up @@ -45,7 +46,11 @@ import type { ExecutionInstallation } from "@executablemd/core/host";
*/
export type ReplEntryInstallation = (
owner: string,
placement?: { readonly projectCwd?: string; readonly workspace?: string },
placement?: {
readonly projectCwd?: string;
readonly workspace?: string;
readonly mounts?: WorkspaceMountTable;
},
) => Operation<ExecutionInstallation>;

/** The factory this host runs REPL entries with, or absence. */
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/repl-profile.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WorkspaceMountTable } from "./workspace-mounts.ts";
/**
* Capture command selections once, then finalize one execution's component paths.
*
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface ReplProfileSeams {
* parameter: a caller states what it knows and leaves out the rest.
*/
export interface ReplProfileFacts extends ReplProfileSeams {
readonly mounts?: WorkspaceMountTable;
readonly testingHarness?: (includes: readonly string[]) => Operation<ExecutionInstallation>;
readonly includes?: readonly string[];
/**
Expand Down Expand Up @@ -97,6 +99,7 @@ export interface ReplProfileAssembly {

/** One finalized execution profile, shared by validation, descriptions and entries. */
export interface ReplExecutionProfile {
readonly mounts?: WorkspaceMountTable;
readonly testing?: ReplTesting;
readonly projectCwd?: string;
/** Where a declared name is looked for, outermost first. */
Expand Down Expand Up @@ -202,6 +205,7 @@ export function* assembleReplProfile(
facts: ReplProfileFacts = {},
): Operation<ReplProfileAssembly> {
const projectCwd = yield* cwd();
const mounts = facts.mounts;
const configuredIncludes = Object.freeze(
(facts.includes ?? REPL_INCLUDES).map((path) => resolve(projectCwd, path)),
);
Expand Down Expand Up @@ -258,6 +262,7 @@ export function* assembleReplProfile(
});

const profile = Object.freeze({
mounts,
includes,
projectCwd,
vocabulary: entryVocabulary(includes, plugins),
Expand Down
Loading
Loading