toolcraft 0.0.93 → 0.0.95
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/README.md +74 -4
- package/composition.json +1 -1
- package/dist/agent-defs.d.ts +1 -0
- package/dist/agent-defs.js +1 -0
- package/dist/agent-human-in-loop.d.ts +1 -0
- package/dist/agent-human-in-loop.js +1 -0
- package/dist/agent-mcp-config.d.ts +1 -0
- package/dist/agent-mcp-config.js +1 -0
- package/dist/auth-store.d.ts +1 -0
- package/dist/auth-store.js +1 -0
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +24 -65
- package/dist/composition.json +1 -1
- package/dist/config-mutations.d.ts +1 -0
- package/dist/config-mutations.js +1 -0
- package/dist/frontmatter.d.ts +1 -0
- package/dist/frontmatter.js +1 -0
- package/dist/human-in-loop/gate.d.ts +5 -1
- package/dist/human-in-loop/gate.js +10 -8
- package/dist/human-in-loop/runner.js +1 -28
- package/dist/mcp.d.ts +4 -1
- package/dist/mcp.js +8 -51
- package/dist/process-runner.d.ts +1 -0
- package/dist/process-runner.js +1 -0
- package/dist/runtime/io.d.ts +5 -0
- package/dist/runtime/io.js +50 -0
- package/dist/sdk.d.ts +12 -8
- package/dist/sdk.js +7 -52
- package/dist/task-list.d.ts +1 -0
- package/dist/task-list.js +1 -0
- package/dist/testing/fakes.d.ts +20 -0
- package/dist/testing/fakes.js +83 -0
- package/dist/testing/fixtures.d.ts +8 -0
- package/dist/testing/fixtures.js +248 -0
- package/dist/testing/harness.d.ts +76 -0
- package/dist/testing/harness.js +391 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/memory-fs.d.ts +11 -0
- package/dist/testing/memory-fs.js +61 -0
- package/dist/testing/parity.d.ts +25 -0
- package/dist/testing/parity.js +384 -0
- package/dist/testing/render-capture.d.ts +6 -0
- package/dist/testing/render-capture.js +54 -0
- package/dist/tiny-mcp-client.d.ts +1 -0
- package/dist/tiny-mcp-client.js +1 -0
- package/node_modules/@poe-code/agent-defs/package.json +2 -0
- package/node_modules/@poe-code/agent-human-in-loop/README.md +12 -2
- package/node_modules/@poe-code/agent-human-in-loop/package.json +2 -0
- package/node_modules/@poe-code/agent-mcp-config/README.md +7 -7
- package/node_modules/@poe-code/agent-mcp-config/package.json +2 -0
- package/node_modules/@poe-code/config-mutations/README.md +8 -8
- package/node_modules/@poe-code/config-mutations/package.json +2 -0
- package/node_modules/@poe-code/frontmatter/README.md +2 -2
- package/node_modules/@poe-code/frontmatter/package.json +2 -0
- package/node_modules/@poe-code/process-runner/README.md +1 -1
- package/node_modules/@poe-code/process-runner/package.json +2 -0
- package/node_modules/@poe-code/task-list/README.md +15 -3
- package/node_modules/@poe-code/task-list/package.json +2 -0
- package/node_modules/auth-store/README.md +15 -0
- package/node_modules/auth-store/package.json +2 -0
- package/node_modules/tiny-mcp-client/README.md +36 -36
- package/node_modules/tiny-mcp-client/package.json +2 -0
- package/node_modules/toolcraft-design/README.md +8 -4
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +48 -6
- package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/explorer/actions.js +1 -0
- package/node_modules/toolcraft-design/dist/explorer/events.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/explorer/reducer.js +52 -4
- package/node_modules/toolcraft-design/dist/explorer/render/list.js +47 -20
- package/node_modules/toolcraft-design/dist/explorer/render/modal.js +13 -1
- package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +1 -1
- package/node_modules/toolcraft-design/dist/explorer/runtime.js +3 -0
- package/node_modules/toolcraft-design/dist/explorer/state.d.ts +9 -0
- package/node_modules/toolcraft-design/package.json +5 -0
- package/package.json +42 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Define a command once. Get a typed CLI, an MCP server, and a typed SDK from the
|
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
|
-
You have
|
|
9
|
+
You have one-off scripts and MCP servers. Each one re-derives argument parsing, env handling, and help text. Running them from a chatbot needs another adapter. Calling them from another script means subprocessing.
|
|
10
10
|
|
|
11
11
|
`toolcraft` is the consolidation step. You write each operation as one `defineCommand`, group them, and pick which surfaces to expose:
|
|
12
12
|
|
|
@@ -30,7 +30,7 @@ Before writing toolcraft code, make a small tool map. For each script or MCP too
|
|
|
30
30
|
- **Surfaces** — where it should appear: CLI, MCP, SDK, or all three.
|
|
31
31
|
- **Safety** — whether it needs approval, auth, or another precondition.
|
|
32
32
|
|
|
33
|
-
Keep the first migration
|
|
33
|
+
Keep the first migration direct:
|
|
34
34
|
|
|
35
35
|
1. Wrap existing scripts as thin `defineCommand` handlers.
|
|
36
36
|
2. Proxy existing MCP servers with `defineGroup({ mcp })` when you do not want to rewrite them yet.
|
|
@@ -38,7 +38,7 @@ Keep the first migration boring:
|
|
|
38
38
|
4. Add MCP scope only to tools that are safe and useful for agents.
|
|
39
39
|
5. Document exposed env vars and config options in the package README.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
After the tool map exists, add commands to `root`, expose the same tree through CLI, MCP, and SDK, and remove old entrypoints when they are no longer needed.
|
|
42
42
|
|
|
43
43
|
## Install
|
|
44
44
|
|
|
@@ -255,6 +255,63 @@ defineCommand<Services>({
|
|
|
255
255
|
|
|
256
256
|
Services are merged into the handler context alongside the built-ins (`fetch`, `fs`, `env`, `progress`).
|
|
257
257
|
|
|
258
|
+
## Testing commands
|
|
259
|
+
|
|
260
|
+
Use `createCommandTestHarness` to exercise the same command pipeline as the SDK without assembling a handler context by hand:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { expect, it } from "vitest";
|
|
264
|
+
import { createCommandTestHarness, fakeService } from "toolcraft/testing";
|
|
265
|
+
import { root } from "./root.js";
|
|
266
|
+
|
|
267
|
+
it("creates a deployment", async () => {
|
|
268
|
+
const deployments = fakeService({
|
|
269
|
+
create: async (name: string) => ({ id: "dep-1", name })
|
|
270
|
+
});
|
|
271
|
+
const harness = createCommandTestHarness(root, {
|
|
272
|
+
services: { deployments },
|
|
273
|
+
env: { HOME: "/test/home" },
|
|
274
|
+
secrets: { apiKey: "test-token" },
|
|
275
|
+
fs: { "/test/input.json": "{}" },
|
|
276
|
+
fetch: [{ method: "POST", url: "https://api.example.com/deployments", json: { ok: true } }],
|
|
277
|
+
confirmations: "approve",
|
|
278
|
+
apiVersion: "1.2.0",
|
|
279
|
+
logLevel: "debug"
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const result = await harness.run<{ id: string }>(["deploy", "create"], {
|
|
283
|
+
name: "production"
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result).toMatchObject({ ok: true, value: { id: "dep-1" } });
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Harness options provide injected `services`, an explicit `env`, named `secrets`, an initial file map or `HandlerFs`, a fetch implementation or `fakeFetch` routes, confirmation behavior (`"approve"`, `"decline"`, or a callback), `apiVersion`, and `logLevel`. `fakeService`, `fakeFetch`, and `createMemoryFs` are also exported for standalone tests.
|
|
291
|
+
|
|
292
|
+
Each `run()` returns a `RunResult` instead of throwing. Assert `ok`, `value`, or `error`, and use `failedAt` to identify the pipeline stage: `resolve`, `secrets`, `requirements`, `params`, `confirm`, `handler`, or `render`. The result also captures `pending`, diagnostic `logs`, `progress`, `confirmations`, the ordered effect `timeline`, `fsChanges`, and rendered rich, Markdown, and JSON output. Pre-handler validation can therefore be asserted directly:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
const result = await harness.run(["deploy", "create"], { name: 42 });
|
|
296
|
+
|
|
297
|
+
expect(result.ok).toBe(false);
|
|
298
|
+
expect(result.failedAt).toBe("params");
|
|
299
|
+
expect(result.timeline).toEqual([]);
|
|
300
|
+
expect(deployments.calls).toEqual([]);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The harness is hermetic by default: it never falls back to `process.env`, starts with an in-memory filesystem, blocks unmatched network requests, records injected service and effect calls, and uses deterministic timestamp-free pending approval data. Provide every external input through the harness options.
|
|
304
|
+
|
|
305
|
+
Use `parity()` when a command must behave identically through the real SDK, MCP, and CLI adapters:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const parity = await harness.parity(["deploy", "create"], { name: "production" });
|
|
309
|
+
|
|
310
|
+
expect(parity.agree, parity.diff).toBe(true);
|
|
311
|
+
expect(parity.sdk.value).toEqual(parity.mcp.value);
|
|
312
|
+
expect(parity.mcp.value).toEqual(parity.cli.value);
|
|
313
|
+
```
|
|
314
|
+
|
|
258
315
|
## Output rendering
|
|
259
316
|
|
|
260
317
|
Handlers return raw values. Add `result:` when a command returns structured data and may be exposed over MCP; Toolcraft turns that schema into MCP `outputSchema`, validates the returned object, returns it as `structuredContent`, and keeps a JSON text backstop for older MCP clients.
|
|
@@ -471,12 +528,16 @@ Import the command definitions from `toolcraft` and the CLI runner from `toolcra
|
|
|
471
528
|
|
|
472
529
|
esbuild records dependency paths relative to the build layout. A standalone install can produce `node_modules/toolcraft/...`, while a hoisted workspace can produce `../../node_modules/toolcraft/...` in module-label comments and source-map `sources`. For layout-independent artifacts, canonicalize every dependency path by keeping the substring from the first `node_modules/` segment onward. Parse the source map as JSON and apply the same operation to each string in `sources`; do not modify application source paths. Serialize the map, then hash or publish the canonical bundle and map.
|
|
473
530
|
|
|
474
|
-
## Environment
|
|
531
|
+
## Environment Variables
|
|
475
532
|
|
|
476
533
|
- `TOOLCRAFT_MCP_REFRESH` — MCP proxy cache refresh (`unset` = use cache, `1`/`true` = refresh all, comma-separated names = refresh those).
|
|
477
534
|
- `TOOLCRAFT_ERROR_REPORTS=1` — enables structured error report files for CLI, MCP, and SDK surfaces that wire `errorReports`.
|
|
478
535
|
- Per-command `secrets` declarations name additional env vars. They are read at command run time and passed to the handler.
|
|
479
536
|
|
|
537
|
+
## Configuration Options
|
|
538
|
+
|
|
539
|
+
Toolcraft configuration is code-first. Use `defineCommand(config)` and `defineGroup(config)` for the command tree, then pass runtime options to `runCLI`, `createSDK`, `createMCPServer`, or `runMCP`. MCP proxy schemas are cached under `.toolcraft/mcp`, and optional human-in-loop state is configured with `HumanInLoopRuntimeOptions`.
|
|
540
|
+
|
|
480
541
|
## API reference
|
|
481
542
|
|
|
482
543
|
### `defineCommand(config)`
|
|
@@ -573,3 +634,12 @@ Subpath imports:
|
|
|
573
634
|
- `toolcraft/mcp` — `runMCP`, `createMCPServer`
|
|
574
635
|
- `toolcraft/human-in-loop` — provider helpers
|
|
575
636
|
- `toolcraft/mcp-proxy` — proxy internals
|
|
637
|
+
|
|
638
|
+
## Manual QA
|
|
639
|
+
|
|
640
|
+
Use these package-local walkthroughs after behavior changes:
|
|
641
|
+
|
|
642
|
+
- [Help output](QA-help-output.md)
|
|
643
|
+
- [Error UX](QA-error-ux.md)
|
|
644
|
+
- [Human-in-loop](QA-human-in-loop.md)
|
|
645
|
+
- [MCP proxy](QA-mcp-proxy.md)
|
package/composition.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-defs";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-defs";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-human-in-loop";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-human-in-loop";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-mcp-config";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/agent-mcp-config";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "auth-store";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "auth-store";
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./node-require-shim.js";
|
|
2
2
|
import { configureTheme } from "toolcraft-design";
|
|
3
|
-
import type { Group, LogLevel, RuntimeLoggerInput } from "./index.js";
|
|
3
|
+
import type { Group, HandlerFs, LogLevel, RuntimeLoggerInput } from "./index.js";
|
|
4
4
|
import { type ErrorReportsOption } from "./error-report.js";
|
|
5
5
|
import type { HumanInLoopRuntimeOptions } from "./human-in-loop/types.js";
|
|
6
6
|
export { renderErrorReport } from "./error-report.js";
|
|
@@ -20,10 +20,13 @@ export interface RunCLIOptions<TServices extends object = Record<string, unknown
|
|
|
20
20
|
argv?: readonly string[];
|
|
21
21
|
casing?: Casing;
|
|
22
22
|
controls?: CLIControls;
|
|
23
|
+
env?: Record<string, string>;
|
|
23
24
|
fetch?: typeof globalThis.fetch;
|
|
25
|
+
fs?: HandlerFs;
|
|
24
26
|
humanInLoop?: HumanInLoopRuntimeOptions;
|
|
25
27
|
logLevel?: LogLevel;
|
|
26
28
|
logger?: RuntimeLoggerInput;
|
|
29
|
+
outputEmitter?: (entry: string) => void;
|
|
27
30
|
projectRoot?: string;
|
|
28
31
|
rootDisplayName?: string;
|
|
29
32
|
rootUsageName?: string;
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./node-require-shim.js";
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
5
5
|
import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
|
|
@@ -16,21 +16,10 @@ import { renderSourceSnippet } from "./source-snippet.js";
|
|
|
16
16
|
import { enableSourceMaps, formatDebugStack } from "./stack-trim.js";
|
|
17
17
|
import { suggest } from "./suggest.js";
|
|
18
18
|
import { throwValidationErrors } from "./validation-errors.js";
|
|
19
|
+
import { RESERVED_SERVICE_NAMES, createEnv, createFs, validateServices } from "./runtime/io.js";
|
|
19
20
|
export { renderErrorReport } from "./error-report.js";
|
|
20
21
|
configureTheme({ brand: "blue", label: "Toolcraft" });
|
|
21
22
|
export { configureTheme };
|
|
22
|
-
const RESERVED_SERVICE_NAMES = new Set([
|
|
23
|
-
"params",
|
|
24
|
-
"secrets",
|
|
25
|
-
"fetch",
|
|
26
|
-
"fs",
|
|
27
|
-
"env",
|
|
28
|
-
"diagnostics",
|
|
29
|
-
"progress",
|
|
30
|
-
"runtimeOptions",
|
|
31
|
-
"root"
|
|
32
|
-
]);
|
|
33
|
-
const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, diagnostics, progress, runtimeOptions, root.";
|
|
34
23
|
const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
|
|
35
24
|
const optionalModulePaths = {
|
|
36
25
|
approvals: "./human-in-loop/approvals-commands.js",
|
|
@@ -350,7 +339,7 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
|
|
|
350
339
|
longAliases: [...(childSchema.cliAliases ?? [])].map((alias) => alias.startsWith("--") ? alias : `--${alias}`),
|
|
351
340
|
shortFlag: childSchema.short,
|
|
352
341
|
schema: childSchema,
|
|
353
|
-
description: childSchema.description,
|
|
342
|
+
description: childSchema.cliDescription ?? childSchema.description,
|
|
354
343
|
optional: runtimeOptional,
|
|
355
344
|
hasDefault: childSchema.default !== undefined,
|
|
356
345
|
defaultValue: childSchema.default,
|
|
@@ -1889,33 +1878,6 @@ async function withOutputFormat(output, fn) {
|
|
|
1889
1878
|
resetOutputFormatCache();
|
|
1890
1879
|
}
|
|
1891
1880
|
}
|
|
1892
|
-
function createFs() {
|
|
1893
|
-
return {
|
|
1894
|
-
readFile: async (path, encoding = "utf8") => readFile(path, { encoding }),
|
|
1895
|
-
writeFile: async (path, contents, options) => {
|
|
1896
|
-
await writeFile(path, contents, options);
|
|
1897
|
-
},
|
|
1898
|
-
exists: async (path) => {
|
|
1899
|
-
try {
|
|
1900
|
-
await access(path);
|
|
1901
|
-
return true;
|
|
1902
|
-
}
|
|
1903
|
-
catch {
|
|
1904
|
-
return false;
|
|
1905
|
-
}
|
|
1906
|
-
},
|
|
1907
|
-
lstat: async (path) => lstat(path),
|
|
1908
|
-
rename: async (fromPath, toPath) => rename(fromPath, toPath),
|
|
1909
|
-
unlink: async (path) => unlink(path)
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
function createEnv(values = process.env) {
|
|
1913
|
-
return {
|
|
1914
|
-
get(key) {
|
|
1915
|
-
return values[key];
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
1881
|
function isPlainObject(value) {
|
|
1920
1882
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1921
1883
|
}
|
|
@@ -2317,16 +2279,16 @@ function createFixtureEnvValues(command) {
|
|
|
2317
2279
|
}
|
|
2318
2280
|
return values;
|
|
2319
2281
|
}
|
|
2320
|
-
async function resolveFixtureRuntime(command, services, requirementOptions, runtimeFetch) {
|
|
2282
|
+
async function resolveFixtureRuntime(command, services, requirementOptions, runtimeFetch, runtimeEnv, runtimeFs) {
|
|
2321
2283
|
const selector = process.env.TOOLCRAFT_FIXTURE;
|
|
2322
2284
|
if (selector === undefined || selector.length === 0) {
|
|
2323
2285
|
return {
|
|
2324
|
-
env: createEnv(),
|
|
2286
|
+
env: createEnv(runtimeEnv),
|
|
2325
2287
|
fetch: runtimeFetch,
|
|
2326
|
-
fs: createFs(),
|
|
2288
|
+
fs: createFs(runtimeFs),
|
|
2327
2289
|
isFixture: false,
|
|
2328
2290
|
requirementOptions,
|
|
2329
|
-
secrets: resolveCommandSecrets(command),
|
|
2291
|
+
secrets: resolveCommandSecrets(command, runtimeEnv),
|
|
2330
2292
|
services
|
|
2331
2293
|
};
|
|
2332
2294
|
}
|
|
@@ -2373,8 +2335,8 @@ function renderApprovalDeclined(error) {
|
|
|
2373
2335
|
logger.error(error.message);
|
|
2374
2336
|
process.exitCode = 1;
|
|
2375
2337
|
}
|
|
2376
|
-
function renderCliErrorPattern(pattern) {
|
|
2377
|
-
const logger = createLogger();
|
|
2338
|
+
function renderCliErrorPattern(pattern, outputEmitter) {
|
|
2339
|
+
const logger = createLogger(outputEmitter);
|
|
2378
2340
|
if (pattern.kind === "usage") {
|
|
2379
2341
|
logger.error(appendUsagePointer(pattern.message, {
|
|
2380
2342
|
rootUsageName: pattern.rootUsageName,
|
|
@@ -2417,13 +2379,6 @@ function renderCliErrorPattern(pattern) {
|
|
|
2417
2379
|
}
|
|
2418
2380
|
process.exitCode = 1;
|
|
2419
2381
|
}
|
|
2420
|
-
function validateServices(services) {
|
|
2421
|
-
for (const name of Object.keys(services)) {
|
|
2422
|
-
if (RESERVED_SERVICE_NAMES.has(name)) {
|
|
2423
|
-
throw new Error(`Service name "${name}" is reserved. Choose a different name. ${RESERVED_SERVICE_NAMES_MESSAGE}`);
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
2382
|
function getNestedValue(target, path) {
|
|
2428
2383
|
return path.reduce((current, segment) => current !== null && typeof current === "object"
|
|
2429
2384
|
? current[segment]
|
|
@@ -2981,8 +2936,8 @@ function getResolvedFlags(command) {
|
|
|
2981
2936
|
const flags = command.optsWithGlobals();
|
|
2982
2937
|
return flags;
|
|
2983
2938
|
}
|
|
2984
|
-
async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, diagnosticsOptions, onErrorReportContext) {
|
|
2985
|
-
const logger = createLogger();
|
|
2939
|
+
async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, runtimeEnv, runtimeFs, outputEmitter, diagnosticsOptions, onErrorReportContext) {
|
|
2940
|
+
const logger = createLogger(outputEmitter);
|
|
2986
2941
|
const primitives = {
|
|
2987
2942
|
logger,
|
|
2988
2943
|
renderTable,
|
|
@@ -3000,7 +2955,7 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
|
|
|
3000
2955
|
logger: diagnosticsOptions.logger ?? writeCLIDiagnosticEvent
|
|
3001
2956
|
});
|
|
3002
2957
|
const shouldPrompt = !resolvedFlags.yes && Boolean(process.stdin.isTTY);
|
|
3003
|
-
const runtime = await resolveFixtureRuntime(state.command, services, requirementOptions, runtimeFetch);
|
|
2958
|
+
const runtime = await resolveFixtureRuntime(state.command, services, requirementOptions, runtimeFetch, runtimeEnv, runtimeFs);
|
|
3004
2959
|
const preflightContext = {
|
|
3005
2960
|
...runtime.services,
|
|
3006
2961
|
secrets: runtime.secrets,
|
|
@@ -3059,7 +3014,9 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
|
|
|
3059
3014
|
renderHumanInLoopPending(result);
|
|
3060
3015
|
return;
|
|
3061
3016
|
}
|
|
3062
|
-
const renderStatus = renderResult(state.command, result, output, primitives
|
|
3017
|
+
const renderStatus = renderResult(state.command, result, output, primitives, outputEmitter === undefined
|
|
3018
|
+
? undefined
|
|
3019
|
+
: (chunk) => outputEmitter(chunk.endsWith("\n") ? chunk.slice(0, -1) : chunk));
|
|
3063
3020
|
if (renderStatus.mcpError) {
|
|
3064
3021
|
process.exitCode = 1;
|
|
3065
3022
|
}
|
|
@@ -3270,7 +3227,7 @@ function renderHttpError(error, options) {
|
|
|
3270
3227
|
}
|
|
3271
3228
|
}
|
|
3272
3229
|
async function handleRunError(error, options) {
|
|
3273
|
-
const logger = createLogger();
|
|
3230
|
+
const logger = createLogger(options.outputEmitter);
|
|
3274
3231
|
await withOutputFormat(options.output, async () => {
|
|
3275
3232
|
if (error instanceof UserError) {
|
|
3276
3233
|
renderCliErrorPattern(options.userErrorPattern === "definition"
|
|
@@ -3289,7 +3246,7 @@ async function handleRunError(error, options) {
|
|
|
3289
3246
|
: {
|
|
3290
3247
|
kind: "runtime-user",
|
|
3291
3248
|
message: error.message
|
|
3292
|
-
});
|
|
3249
|
+
}, options.outputEmitter);
|
|
3293
3250
|
return;
|
|
3294
3251
|
}
|
|
3295
3252
|
if (error instanceof Error && error.name === "ToolcraftBugError") {
|
|
@@ -3297,7 +3254,7 @@ async function handleRunError(error, options) {
|
|
|
3297
3254
|
kind: "toolcraft-bug",
|
|
3298
3255
|
error,
|
|
3299
3256
|
debugStackMode: options.debugStackMode
|
|
3300
|
-
});
|
|
3257
|
+
}, options.outputEmitter);
|
|
3301
3258
|
return;
|
|
3302
3259
|
}
|
|
3303
3260
|
if (error instanceof CommanderError) {
|
|
@@ -3341,7 +3298,7 @@ async function handleRunError(error, options) {
|
|
|
3341
3298
|
message,
|
|
3342
3299
|
stack: error instanceof Error ? error.stack : undefined,
|
|
3343
3300
|
debugStackMode: options.debugStackMode
|
|
3344
|
-
});
|
|
3301
|
+
}, options.outputEmitter);
|
|
3345
3302
|
});
|
|
3346
3303
|
}
|
|
3347
3304
|
function formatCommanderErrorMessage(error) {
|
|
@@ -3560,7 +3517,7 @@ function isCommandNameCharacter(character) {
|
|
|
3560
3517
|
const isLowercaseLetter = code >= 97 && code <= 122;
|
|
3561
3518
|
const isUppercaseLetter = code >= 65 && code <= 90;
|
|
3562
3519
|
const isDigit = code >= 48 && code <= 57;
|
|
3563
|
-
return isLowercaseLetter || isUppercaseLetter || isDigit || character === "-" || character === "_";
|
|
3520
|
+
return (isLowercaseLetter || isUppercaseLetter || isDigit || character === "-" || character === "_");
|
|
3564
3521
|
}
|
|
3565
3522
|
function getDefaultCommanderCommandName(command) {
|
|
3566
3523
|
const candidate = command;
|
|
@@ -3612,7 +3569,8 @@ export async function runCLI(roots, options = {}) {
|
|
|
3612
3569
|
root
|
|
3613
3570
|
};
|
|
3614
3571
|
const requirementOptions = {
|
|
3615
|
-
apiVersion: options.apiVersion
|
|
3572
|
+
apiVersion: options.apiVersion,
|
|
3573
|
+
env: options.env
|
|
3616
3574
|
};
|
|
3617
3575
|
validateServices(services);
|
|
3618
3576
|
if (hasHelpFlag(argv)) {
|
|
@@ -3642,7 +3600,7 @@ export async function runCLI(roots, options = {}) {
|
|
|
3642
3600
|
const execute = async (state) => {
|
|
3643
3601
|
lastActionCommand = state.actionCommand;
|
|
3644
3602
|
resolvedCommandPath = formatCliCommandPath(state.commandPath);
|
|
3645
|
-
await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, {
|
|
3603
|
+
await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, options.env, options.fs, options.outputEmitter, {
|
|
3646
3604
|
logLevel: options.logLevel,
|
|
3647
3605
|
logger: options.logger,
|
|
3648
3606
|
verboseControlEnabled: controls.verbose
|
|
@@ -3707,6 +3665,7 @@ export async function runCLI(roots, options = {}) {
|
|
|
3707
3665
|
argv,
|
|
3708
3666
|
rootUsageName,
|
|
3709
3667
|
commandPath: resolvedCommandPath,
|
|
3668
|
+
outputEmitter: options.outputEmitter,
|
|
3710
3669
|
userErrorPattern: errorReportContext?.params === undefined ? userErrorPattern : "runtime-user"
|
|
3711
3670
|
});
|
|
3712
3671
|
}
|
package/dist/composition.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/config-mutations";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/config-mutations";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/frontmatter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/frontmatter";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Command, HandlerContext } from "../index.js";
|
|
2
|
+
import { enqueueApproval } from "./approval-tasks.js";
|
|
2
3
|
import type { HumanInLoopPending, HumanInLoopProvider, HumanInLoopRuntimeOptions } from "./types.js";
|
|
3
4
|
export declare function resolveProvider(runtimeOptions: HumanInLoopRuntimeOptions | undefined): HumanInLoopProvider;
|
|
4
|
-
export declare function invokeWithHumanInLoop<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, runtimeOptions: HumanInLoopRuntimeOptions | undefined, commandPath: string
|
|
5
|
+
export declare function invokeWithHumanInLoop<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, runtimeOptions: HumanInLoopRuntimeOptions | undefined, commandPath: string, options?: {
|
|
6
|
+
enqueueApproval?: typeof enqueueApproval;
|
|
7
|
+
spawnRunner?: boolean;
|
|
8
|
+
}): Promise<T | HumanInLoopPending>;
|
|
@@ -20,37 +20,39 @@ export function resolveProvider(runtimeOptions) {
|
|
|
20
20
|
providersByRuntime.set(runtimeOptions, provider);
|
|
21
21
|
return provider;
|
|
22
22
|
}
|
|
23
|
-
export async function invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPath) {
|
|
23
|
+
export async function invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPath, options = {}) {
|
|
24
24
|
if (!node.humanInLoop) {
|
|
25
25
|
return node.handler(ctx);
|
|
26
26
|
}
|
|
27
27
|
const message = node.humanInLoop.message({
|
|
28
28
|
params: ctx.params,
|
|
29
|
-
commandPath
|
|
29
|
+
commandPath
|
|
30
30
|
});
|
|
31
31
|
if (node.humanInLoop.mode === "async") {
|
|
32
32
|
const { tasks } = await ensureApprovalList(runtimeOptions);
|
|
33
|
-
const { approvalId, pending } = await enqueueApproval({
|
|
33
|
+
const { approvalId, pending } = await (options.enqueueApproval ?? enqueueApproval)({
|
|
34
34
|
tasks,
|
|
35
35
|
payload: {
|
|
36
36
|
commandPath,
|
|
37
37
|
params: ctx.params,
|
|
38
38
|
message,
|
|
39
|
-
declineInputPrompt: node.humanInLoop.declineInputPrompt
|
|
40
|
-
}
|
|
39
|
+
declineInputPrompt: node.humanInLoop.declineInputPrompt
|
|
40
|
+
}
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
if (options.spawnRunner !== false) {
|
|
43
|
+
spawnApprovalRunner(approvalId, runtimeOptions);
|
|
44
|
+
}
|
|
43
45
|
return pending;
|
|
44
46
|
}
|
|
45
47
|
const provider = resolveProvider(runtimeOptions);
|
|
46
48
|
const result = await provider.requestApproval({
|
|
47
49
|
message,
|
|
48
|
-
declineInputPrompt: node.humanInLoop.declineInputPrompt
|
|
50
|
+
declineInputPrompt: node.humanInLoop.declineInputPrompt
|
|
49
51
|
});
|
|
50
52
|
if (result.outcome === "declined") {
|
|
51
53
|
throw new ApprovalDeclinedError({
|
|
52
54
|
reason: result.reason,
|
|
53
|
-
commandPath
|
|
55
|
+
commandPath
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
58
|
return node.handler(ctx);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { access, lstat, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
1
|
import { InvalidTransitionError } from "@poe-code/task-list";
|
|
3
2
|
import { UserError, resolveCommandSecrets } from "../index.js";
|
|
3
|
+
import { createEnv, createFs } from "../runtime/io.js";
|
|
4
4
|
import { ensureApprovalList } from "./approval-tasks.js";
|
|
5
5
|
import { resolveProvider } from "./gate.js";
|
|
6
6
|
import { createRuntimeLogger } from "../runtime-logging.js";
|
|
@@ -184,33 +184,6 @@ function createHandlerContext(command, params) {
|
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
-
function createFs() {
|
|
188
|
-
return {
|
|
189
|
-
readFile: async (path, encoding = "utf8") => readFile(path, { encoding }),
|
|
190
|
-
writeFile: async (path, contents, options) => {
|
|
191
|
-
await writeFile(path, contents, options);
|
|
192
|
-
},
|
|
193
|
-
exists: async (path) => {
|
|
194
|
-
try {
|
|
195
|
-
await access(path);
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
lstat: async (path) => lstat(path),
|
|
203
|
-
rename: async (fromPath, toPath) => rename(fromPath, toPath),
|
|
204
|
-
unlink: async (path) => unlink(path)
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
function createEnv(values = process.env) {
|
|
208
|
-
return {
|
|
209
|
-
get(key) {
|
|
210
|
-
return values[key];
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
187
|
function serializeJsonResult(value) {
|
|
215
188
|
try {
|
|
216
189
|
const serialized = JSON.stringify(value);
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type SDKTransport, type Server as TinyServer } from "tiny-stdio-mcp-server";
|
|
2
|
-
import type { Group, LogLevel, RuntimeLoggerInput } from "./index.js";
|
|
2
|
+
import type { Group, HandlerFs, LogLevel, RuntimeLoggerInput } from "./index.js";
|
|
3
3
|
import { type ErrorReportsOption } from "./error-report.js";
|
|
4
4
|
import { type HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
|
|
5
5
|
type Casing = "snake" | "camel";
|
|
@@ -7,8 +7,11 @@ type CmdkitServer = Omit<TinyServer, "connect"> & {
|
|
|
7
7
|
connect(transport: SDKTransport): Promise<void>;
|
|
8
8
|
};
|
|
9
9
|
export interface RunMCPOptions<TServices extends object = Record<string, unknown>> {
|
|
10
|
+
apiVersion?: string;
|
|
10
11
|
approvals?: boolean;
|
|
12
|
+
env?: Record<string, string>;
|
|
11
13
|
fetch?: typeof globalThis.fetch;
|
|
14
|
+
fs?: HandlerFs;
|
|
12
15
|
name: string;
|
|
13
16
|
version?: string;
|
|
14
17
|
humanInLoop?: HumanInLoopRuntimeOptions;
|
package/dist/mcp.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { access, lstat, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
1
|
import { createServer, JSON_RPC_ERROR_CODES, ToolError } from "tiny-stdio-mcp-server";
|
|
3
2
|
import { toJsonSchema } from "toolcraft-schema";
|
|
4
3
|
import { createHttpErrorEnvelope, isHttpErrorLike } from "./api-error-summary.js";
|
|
@@ -14,18 +13,7 @@ import { enableSourceMaps } from "./stack-trim.js";
|
|
|
14
13
|
import { suggest } from "./suggest.js";
|
|
15
14
|
import { throwValidationErrors } from "./validation-errors.js";
|
|
16
15
|
import { createRuntimeLogger } from "./runtime-logging.js";
|
|
17
|
-
|
|
18
|
-
"params",
|
|
19
|
-
"secrets",
|
|
20
|
-
"fetch",
|
|
21
|
-
"fs",
|
|
22
|
-
"env",
|
|
23
|
-
"diagnostics",
|
|
24
|
-
"progress",
|
|
25
|
-
"runtimeOptions",
|
|
26
|
-
"root"
|
|
27
|
-
]);
|
|
28
|
-
const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, diagnostics, progress, runtimeOptions, root.";
|
|
16
|
+
import { createEnv, createFs, validateServices } from "./runtime/io.js";
|
|
29
17
|
function normalizeRoots(roots) {
|
|
30
18
|
if (!Array.isArray(roots)) {
|
|
31
19
|
return roots;
|
|
@@ -93,40 +81,6 @@ function isOptional(schema) {
|
|
|
93
81
|
function isPlainObject(value) {
|
|
94
82
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
95
83
|
}
|
|
96
|
-
function createFs() {
|
|
97
|
-
return {
|
|
98
|
-
readFile: async (path, encoding = "utf8") => readFile(path, { encoding }),
|
|
99
|
-
writeFile: async (path, contents, options) => {
|
|
100
|
-
await writeFile(path, contents, options);
|
|
101
|
-
},
|
|
102
|
-
exists: async (path) => {
|
|
103
|
-
try {
|
|
104
|
-
await access(path);
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
lstat: async (path) => lstat(path),
|
|
112
|
-
rename: async (fromPath, toPath) => rename(fromPath, toPath),
|
|
113
|
-
unlink: async (path) => unlink(path)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function createEnv(values = process.env) {
|
|
117
|
-
return {
|
|
118
|
-
get(key) {
|
|
119
|
-
return values[key];
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
function validateServices(services) {
|
|
124
|
-
for (const name of Object.keys(services)) {
|
|
125
|
-
if (RESERVED_SERVICE_NAMES.has(name)) {
|
|
126
|
-
throw new Error(`Service name "${name}" is reserved. Choose a different name. ${RESERVED_SERVICE_NAMES_MESSAGE}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
84
|
function formatAvailableList(values) {
|
|
131
85
|
return `Available: ${[...values].sort().join(", ")}.`;
|
|
132
86
|
}
|
|
@@ -779,19 +733,22 @@ function createResolvedMCPServer(root, options) {
|
|
|
779
733
|
let params;
|
|
780
734
|
let secrets;
|
|
781
735
|
try {
|
|
782
|
-
secrets = resolveCommandSecrets(tool.command);
|
|
736
|
+
secrets = resolveCommandSecrets(tool.command, options.env);
|
|
783
737
|
const baseContext = {
|
|
784
738
|
...servicesWithBuiltIns,
|
|
785
739
|
secrets,
|
|
786
740
|
fetch: runtimeFetch,
|
|
787
|
-
fs: createFs(),
|
|
788
|
-
env: createEnv(),
|
|
741
|
+
fs: createFs(options.fs),
|
|
742
|
+
env: createEnv(options.env),
|
|
789
743
|
diagnostics,
|
|
790
744
|
progress(message) {
|
|
791
745
|
diagnostics.emit({ level: "info", message, category: "progress" });
|
|
792
746
|
}
|
|
793
747
|
};
|
|
794
|
-
await assertCommandRequirements(tool.command, { ...baseContext, params: undefined }
|
|
748
|
+
await assertCommandRequirements(tool.command, { ...baseContext, params: undefined }, {
|
|
749
|
+
apiVersion: options.apiVersion,
|
|
750
|
+
env: options.env
|
|
751
|
+
});
|
|
795
752
|
params = validateToolArguments(tool.command.params, argumentsValue, casing);
|
|
796
753
|
const result = await invokeWithHumanInLoop(tool.command, {
|
|
797
754
|
...baseContext,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/process-runner";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@poe-code/process-runner";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HandlerEnv, HandlerFs } from "../index.js";
|
|
2
|
+
export declare const RESERVED_SERVICE_NAMES: Set<string>;
|
|
3
|
+
export declare function createFs(fs?: HandlerFs): HandlerFs;
|
|
4
|
+
export declare function createEnv(values?: Record<string, string | undefined>): HandlerEnv;
|
|
5
|
+
export declare function validateServices(services: object): void;
|