@procedures/mark 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/mark",
4
- "version": "0.7.0",
4
+ "version": "0.8.1",
5
5
  "description": "mark: one CLI for every procedure package. It finds the procedures, parses flags by their Zod types, runs local or through a fast warm server, and asks for missing arguments.",
6
6
  "keywords": [
7
7
  "cli",
@@ -52,16 +52,16 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@clack/prompts": "^0.7.0",
55
- "@procedures/cli-run": "0.7.0",
56
- "@procedures/client": "0.7.0",
57
- "@procedures/lib": "0.7.0",
58
- "@procedures/mcp-dev-tools": "0.7.0",
59
- "@procedures/peer": "0.7.0",
60
- "@procedures/pnpm": "0.7.0",
61
- "@procedures/procedure": "0.7.0",
62
- "@procedures/server": "0.7.0",
63
- "@procedures/shell": "0.7.0",
64
- "@procedures/vitest": "0.7.0",
55
+ "@procedures/cli-run": "0.8.1",
56
+ "@procedures/client": "0.8.1",
57
+ "@procedures/lib": "0.8.1",
58
+ "@procedures/mcp-dev-tools": "0.8.1",
59
+ "@procedures/peer": "0.8.1",
60
+ "@procedures/pnpm": "0.8.1",
61
+ "@procedures/procedure": "0.8.1",
62
+ "@procedures/server": "0.8.1",
63
+ "@procedures/shell": "0.8.1",
64
+ "@procedures/vitest": "0.8.1",
65
65
  "chalk": "^5.3.0",
66
66
  "cli-table3": "^0.6.5",
67
67
  "ora": "^8.0.0",
@@ -70,44 +70,44 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@mark1russell7/cue": "^1.0.0",
73
- "@procedures/archive": "0.7.0",
74
- "@procedures/bundle-assistant": "0.7.0",
75
- "@procedures/bundle-dev": "0.7.0",
76
- "@procedures/bundle-mcp": "0.7.0",
77
- "@procedures/claude-cli": "0.7.0",
78
- "@procedures/compiler": "0.7.0",
79
- "@procedures/cue": "0.7.0",
80
- "@procedures/dag": "0.7.0",
81
- "@procedures/docker": "0.7.0",
82
- "@procedures/env": "0.7.0",
83
- "@procedures/exif": "0.7.0",
84
- "@procedures/fs": "0.7.0",
85
- "@procedures/geo": "0.7.0",
86
- "@procedures/gh": "0.7.0",
87
- "@procedures/git": "0.7.0",
88
- "@procedures/http": "0.7.0",
89
- "@procedures/ics": "0.7.0",
90
- "@procedures/ios-backup": "0.7.0",
91
- "@procedures/json": "0.7.0",
92
- "@procedures/llm": "0.7.0",
93
- "@procedures/mail": "0.7.0",
94
- "@procedures/mongo": "0.7.0",
95
- "@procedures/node": "0.7.0",
96
- "@procedures/notify": "0.7.0",
97
- "@procedures/npm": "0.7.0",
98
- "@procedures/pdf": "0.7.0",
99
- "@procedures/privacy-scan": "0.7.0",
100
- "@procedures/restic": "0.7.0",
101
- "@procedures/s3": "0.7.0",
102
- "@procedures/schedule": "0.7.0",
103
- "@procedures/snapshot": "0.7.0",
104
- "@procedures/splay": "0.7.0",
105
- "@procedures/sqlite": "0.7.0",
106
- "@procedures/store": "0.7.0",
107
- "@procedures/systems": "0.7.0",
108
- "@procedures/tailscale": "0.7.0",
109
- "@procedures/vite": "0.7.0",
110
- "@procedures/weather": "0.7.0",
73
+ "@procedures/archive": "0.8.1",
74
+ "@procedures/bundle-assistant": "0.8.1",
75
+ "@procedures/bundle-dev": "0.8.1",
76
+ "@procedures/bundle-mcp": "0.8.1",
77
+ "@procedures/claude-cli": "0.8.1",
78
+ "@procedures/compiler": "0.8.1",
79
+ "@procedures/cue": "0.8.1",
80
+ "@procedures/dag": "0.8.1",
81
+ "@procedures/docker": "0.8.1",
82
+ "@procedures/env": "0.8.1",
83
+ "@procedures/exif": "0.8.1",
84
+ "@procedures/fs": "0.8.1",
85
+ "@procedures/geo": "0.8.1",
86
+ "@procedures/gh": "0.8.1",
87
+ "@procedures/git": "0.8.1",
88
+ "@procedures/http": "0.8.1",
89
+ "@procedures/ics": "0.8.1",
90
+ "@procedures/ios-backup": "0.8.1",
91
+ "@procedures/json": "0.8.1",
92
+ "@procedures/llm": "0.8.1",
93
+ "@procedures/mail": "0.8.1",
94
+ "@procedures/mongo": "0.8.1",
95
+ "@procedures/node": "0.8.1",
96
+ "@procedures/notify": "0.8.1",
97
+ "@procedures/npm": "0.8.1",
98
+ "@procedures/pdf": "0.8.1",
99
+ "@procedures/privacy-scan": "0.8.1",
100
+ "@procedures/restic": "0.8.1",
101
+ "@procedures/s3": "0.8.1",
102
+ "@procedures/schedule": "0.8.1",
103
+ "@procedures/snapshot": "0.8.1",
104
+ "@procedures/splay": "0.8.1",
105
+ "@procedures/sqlite": "0.8.1",
106
+ "@procedures/store": "0.8.1",
107
+ "@procedures/systems": "0.8.1",
108
+ "@procedures/tailscale": "0.8.1",
109
+ "@procedures/vite": "0.8.1",
110
+ "@procedures/weather": "0.8.1",
111
111
  "@types/node": "^22.0.0",
112
112
  "fast-check": "^4.10.2",
113
113
  "typescript": "^7.0.2",
package/src/args.ts CHANGED
@@ -413,7 +413,9 @@ export function parseCommandLine<T extends CommandLike>(argv: readonly string[],
413
413
  return;
414
414
  }
415
415
  let value: unknown;
416
- if (many && texts.length > 1) {
416
+ // Each argument of a last array field is one item, also when there is only one. Before, one
417
+ // argument was converted as the whole array, so `[]` gave no item and `["a","b"]` two.
418
+ if (many) {
417
419
  const items: unknown[] = [];
418
420
  for (const text of texts) {
419
421
  const converted = convertValue(text, type.item ?? ANY, field);
package/src/cli.ts CHANGED
@@ -142,7 +142,8 @@ function showRootHelp(procedures: readonly CommandLike[]): void {
142
142
  print.info("Hosts: 'mark serve [--procedures pkgs] [--port N] [--expose patterns] [--events topics]' (the procedure server),");
143
143
  print.info(" 'mark mcp' (the dev-tools MCP server over stdio).");
144
144
  print.info("Modes: 'mark --server [--port N] [--host H] [--transport http|websocket|both] [--events topics]' and 'mark -i' (the REPL).");
145
- print.info("Root: 'mark --fs-root <folder> <command>' confines each path of fs.* to the folder, also in each mode and host.");
145
+ print.info("Root: 'mark --fs-root <folder> <command>' keeps each path of each procedure in the folder, also in each mode and host.");
146
+ print.info(" It refuses the procedures that it cannot confine: git, pnpm, docker run, a program (FORBIDDEN, outside-root).");
146
147
  }
147
148
 
148
149
  /** The global flags that only `--server` takes. */
@@ -207,7 +208,7 @@ function interruptSignal(): { signal: AbortSignal; release: () => void } {
207
208
  /**
208
209
  * Initialize the CLI context (load ecosystem, create client)
209
210
  * This is the expensive part (~4s). Call once, reuse many times. With `fsRoot` (`--fs-root`),
210
- * the `fs.*` procedures confine each path to the root, also in the nested calls.
211
+ * each procedure keeps its paths in the root, also in the nested calls, or the root refuses it.
211
212
  */
212
213
  export async function initCli(verbose = false, fsRoot?: string): Promise<CliContext> {
213
214
  const clientModule = await import("@procedures/client");
@@ -485,8 +486,9 @@ Options:
485
486
  --http <port> Serve over Streamable HTTP on this port, not over stdio (MCP_HTTP_PORT)
486
487
  --host <host> The address of --http (default: 127.0.0.1, only this computer)
487
488
  --token <token> A secret that each HTTP request must send as a bearer token
488
- --fs-root <folder> Confine each path of fs.* to this folder, also the nested fs calls
489
- of the tools (default: MCP_FS_ROOT, or no root). The tools stay the same.
489
+ --fs-root <folder> Keep each path of each tool in this folder, also in the nested calls,
490
+ and refuse each tool that the folder cannot confine (default:
491
+ MCP_FS_ROOT, or no root). The list of the tools stays the same.
490
492
  --debug Write debug lines to stderr
491
493
  -h, --help Show this help
492
494
 
package/src/ecosystem.ts CHANGED
@@ -209,36 +209,43 @@ const allCoreModule: ProcedureModule = defineModule("@procedures/client (all cor
209
209
  export interface EcosystemRegistry {
210
210
  registry: ProcedureRegistry;
211
211
  loaded: string[];
212
- /** The absolute path of the root of the `fs.*` procedures (`--fs-root`), or undefined. */
212
+ /** The absolute path of the root of the files (`--fs-root`), or undefined. */
213
213
  fsRoot?: string | undefined;
214
+ /** The keys of the procedures that the root refuses, or undefined without a root. */
215
+ refusedUnderRoot?: string[] | undefined;
214
216
  }
215
217
 
216
218
  /** The options of `loadEcosystemRegistry()`. */
217
219
  export interface EcosystemOptions {
218
220
  /**
219
- * The folder that confines each path of the `fs.*` procedures (`--fs-root`, roadmap R33). The
220
- * nested `fs.*` calls of the other procedures are confined too.
221
+ * The root of the files (`--fs-root`, roadmap R33). Each procedure keeps its paths in the root,
222
+ * also in the nested calls, or the root refuses it (`confineToRoot()` of `@procedures/fs`).
221
223
  */
222
224
  fsRoot?: string | undefined;
223
225
  }
224
226
 
225
- /** The function of `@procedures/fs` that confines the `fs.*` procedures of a registry to a root. */
226
- type ConfineFs = (registry: ProcedureRegistry, root: string) => { root: string; procedures: string[] };
227
+ /** The function of `@procedures/fs` that confines each procedure of a registry to a root. */
228
+ type ConfineToRoot = (registry: ProcedureRegistry, root: string) => { root: string; refused: string[] };
227
229
 
228
230
  /**
229
- * This function confines the `fs.*` procedures of the registry to a root, with
230
- * `confineFsProcedures()` of the workspace package `@procedures/fs`. Without that package, it
231
- * throws: a root that does not apply is an error, not a silent no-op.
231
+ * This function confines each procedure of the registry to a root, with `confineToRoot()` of the
232
+ * workspace package `@procedures/fs`. Without that package, it throws: a root that does not apply
233
+ * is an error, not a silent no-op.
232
234
  */
233
- async function confineFs(registry: ProcedureRegistry, root: string, discovered: readonly DiscoveredPackage[]): Promise<string> {
235
+ async function confineRoot(
236
+ registry: ProcedureRegistry,
237
+ root: string,
238
+ discovered: readonly DiscoveredPackage[],
239
+ ): Promise<{ root: string; refused: string[] }> {
234
240
  const fsPackage = discovered.find((pkg) => pkg.name === "@procedures/fs");
235
241
  const file = fsPackage?.modulePath ? path.join(fsPackage.path, fsPackage.modulePath) : undefined;
236
242
  if (!file || !fs.existsSync(file)) {
237
243
  throw new Error("--fs-root needs the package @procedures/fs of the workspace: build packages/client-fs");
238
244
  }
239
- const { confineFsProcedures } = (await import(pathToFileURL(file).href)) as { confineFsProcedures?: ConfineFs };
240
- if (typeof confineFsProcedures !== "function") throw new Error("--fs-root needs a build of @procedures/fs with confineFsProcedures");
241
- return confineFsProcedures(registry, root).root;
245
+ const { confineToRoot } = (await import(pathToFileURL(file).href)) as { confineToRoot?: ConfineToRoot };
246
+ if (typeof confineToRoot !== "function") throw new Error("--fs-root needs a build of @procedures/fs with confineToRoot");
247
+ const { root: folder, refused } = confineToRoot(registry, root);
248
+ return { root: folder, refused };
242
249
  }
243
250
 
244
251
  /**
@@ -265,8 +272,8 @@ async function moduleOfPackage(pkg: DiscoveredPackage): Promise<ProcedureModule
265
272
  * The registry of `mark` (roadmap 3.1): a registry of its own, built from the module of each
266
273
  * workspace package that declares `client.procedures`, with all the core procedures. The global
267
274
  * registry does not change for a package with a module entry. A package that fails to load gives
268
- * one warning line on stderr, as with `loadEcosystemProcedures()`. With `options.fsRoot`, the
269
- * `fs.*` procedures confine each path to the root.
275
+ * one warning line on stderr, as with `loadEcosystemProcedures()`. With `options.fsRoot`, each
276
+ * procedure keeps its paths in the root, or the root refuses it (`confineToRoot()`).
270
277
  */
271
278
  export async function loadEcosystemRegistry(verbose = false, warn = true, options: EcosystemOptions = {}): Promise<EcosystemRegistry> {
272
279
  const registry = createRegistry(allCoreModule);
@@ -289,7 +296,7 @@ export async function loadEcosystemRegistry(verbose = false, warn = true, option
289
296
  }
290
297
  }
291
298
  if (options.fsRoot === undefined) return { registry, loaded };
292
- // After the last package: each fs.* procedure of the registry gets the handler of the root
293
- const fsRoot = await confineFs(registry, options.fsRoot, discovered);
294
- return { registry, loaded, fsRoot };
299
+ // After the last package: each procedure of the registry gets the rules of the root
300
+ const { root: fsRoot, refused } = await confineRoot(registry, options.fsRoot, discovered);
301
+ return { registry, loaded, fsRoot, refusedUnderRoot: refused };
295
302
  }
@@ -4,7 +4,7 @@
4
4
  * Runs the CLI as a persistent server, exposing all ecosystem procedures
5
5
  * via HTTP. The server has the registry of mark (the module of each package, roadmap 3.1) and
6
6
  * the host policy of the core (roadmap 2.3): the token in the lockfile, and the topics of
7
- * `--events`. With `--fs-root`, the `fs.*` procedures confine each path to the root.
7
+ * `--events`. With `--fs-root`, each procedure keeps its paths in the root, or the root refuses it.
8
8
  */
9
9
 
10
10
  import { print } from "./print.js";
@@ -83,7 +83,7 @@ export async function startServerMode(options: ServerModeOptions): Promise<void>
83
83
  if (verbose) {
84
84
  print.info("Loading ecosystem procedures...");
85
85
  }
86
- const { registry, fsRoot } = await loadEcosystemRegistry(verbose, true, { fsRoot: options.fsRoot });
86
+ const { registry, fsRoot, refusedUnderRoot } = await loadEcosystemRegistry(verbose, true, { fsRoot: options.fsRoot });
87
87
 
88
88
  // Build transport config
89
89
  const transports = buildTransports(options);
@@ -145,7 +145,8 @@ export async function startServerMode(options: ServerModeOptions): Promise<void>
145
145
  }
146
146
  print.info(events.length > 0 ? `\nEvent topics (events.subscribe): ${events.join(", ")}` : "\nEvent topics: none (--events adds them)");
147
147
  if (fsRoot !== undefined) {
148
- print.info(`File root: ${fsRoot} (fs.* refuses each path outside it)`);
148
+ print.info(`File root: ${fsRoot} (each procedure refuses a path outside it)`);
149
+ print.info(` Refused under the root: ${refusedUnderRoot?.length ?? 0} procedures that the root cannot confine`);
149
150
  }
150
151
  print.info("\nPress Ctrl+C to stop.");
151
152