@silkweave/cli 4.2.0 → 4.4.0
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 +15 -0
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +33 -3
- package/build/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -79,6 +79,21 @@ Message 2 about weather
|
|
|
79
79
|
|
|
80
80
|
Backpressure is honoured: each chunk write awaits `stdout`'s `drain` event when the buffer is full, so the action throttles itself if a downstream pipe is slow.
|
|
81
81
|
|
|
82
|
+
## Resource Results (binary)
|
|
83
|
+
|
|
84
|
+
An action with a `binary()` output (see [`@silkweave/core`](https://www.npmjs.com/package/@silkweave/core)) - or any action returning a `resource()`, `File`/`Blob`, or bare bytes - delivers its payload terminal-appropriately; binary is **never dumped onto an interactive terminal**:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Piped: raw bytes on stdout (description + status go to stderr)
|
|
88
|
+
$ mytool screenshot --url https://example.com > shot.png
|
|
89
|
+
|
|
90
|
+
# Interactive: written to --output, or a file named after the resource
|
|
91
|
+
$ mytool screenshot --url https://example.com --output shot.png
|
|
92
|
+
Wrote 48213 bytes (image/png) to shot.png
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `-o, --output <path>` flag is added automatically for `binary()` actions (unless the input schema already has an `output` field, which then wins). The command banner moves to stderr for binary actions so a piped stdout stays byte-clean.
|
|
96
|
+
|
|
82
97
|
## See Also
|
|
83
98
|
|
|
84
99
|
- [Silkweave README](https://github.com/silkweave/silkweave) - Full documentation
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/cli.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/cli.ts"],"mappings":";;;cA+Ka,GAAA,EAAK,cAAA"}
|
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { SilkweaveError, createConsoleLogger, isStreamingAction, unwrap } from "@silkweave/core";
|
|
1
|
+
import { SilkweaveError, binarySchemaMeta, createConsoleLogger, isBinarySchema, isStreamingAction, resourceBytes, toActionResource, unwrap } from "@silkweave/core";
|
|
2
2
|
import { camelCase, kebabCase } from "change-case";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { once } from "events";
|
|
5
|
+
import { writeFile } from "fs/promises";
|
|
5
6
|
import z from "zod/v4";
|
|
6
7
|
//#region src/adapter/cli.ts
|
|
7
8
|
function handleCLIError(error) {
|
|
@@ -75,6 +76,27 @@ function addCliOption(command, key, type, defaultValue, isArgument) {
|
|
|
75
76
|
if (parseArg) command.option(`--${flag} ${placeholder}`, description ?? "", parseArg, defaultValue);
|
|
76
77
|
else command.option(`--${flag} ${placeholder}`, description, defaultValue);
|
|
77
78
|
}
|
|
79
|
+
/** File extension derived from a media type's subtype (`image/png` -> `png`). */
|
|
80
|
+
function extensionFromMime(mimeType) {
|
|
81
|
+
return mimeType.split(";")[0].split("/")[1]?.split("+")[0]?.trim() || "bin";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Deliver a resource result terminal-appropriately: raw bytes to stdout when
|
|
85
|
+
* piped (`my-cli screenshot > shot.png`), otherwise written to `--output` or a
|
|
86
|
+
* file named after the resource - binary is never dumped onto an interactive
|
|
87
|
+
* terminal. Status lines go to stderr so a piped stdout stays byte-clean.
|
|
88
|
+
*/
|
|
89
|
+
async function writeResourceResult(res, outputPath) {
|
|
90
|
+
const bytes = resourceBytes(res);
|
|
91
|
+
if (res.description) console.error(res.description);
|
|
92
|
+
if (!outputPath && !process.stdout.isTTY) {
|
|
93
|
+
if (!process.stdout.write(bytes)) await once(process.stdout, "drain");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const target = outputPath ?? res.name ?? `resource.${extensionFromMime(res.mimeType)}`;
|
|
97
|
+
await writeFile(target, bytes);
|
|
98
|
+
console.error(`Wrote ${bytes.length} bytes (${res.mimeType}) to ${target}`);
|
|
99
|
+
}
|
|
78
100
|
async function runStreamingCommand(action, input, context) {
|
|
79
101
|
const streamRun = action.run;
|
|
80
102
|
const iter = streamRun(input, context);
|
|
@@ -88,6 +110,8 @@ function registerCommand(program, action, options, context) {
|
|
|
88
110
|
const shape = action.input.shape;
|
|
89
111
|
const argKeys = action.args ?? [];
|
|
90
112
|
const argSet = new Set(argKeys);
|
|
113
|
+
const binaryOutput = isBinarySchema(action.output) && !("output" in shape);
|
|
114
|
+
if (binaryOutput) command.option("-o, --output <path>", "Write the resource to this file (default: the resource name)");
|
|
91
115
|
for (const key of Object.keys(shape)) {
|
|
92
116
|
if (argSet.has(key)) continue;
|
|
93
117
|
const [type, { defaultValue }] = unwrap(shape[key]);
|
|
@@ -109,9 +133,15 @@ function registerCommand(program, action, options, context) {
|
|
|
109
133
|
runStreamingCommand(action, input, actionContext).catch(handleCLIError);
|
|
110
134
|
return;
|
|
111
135
|
}
|
|
112
|
-
console.
|
|
136
|
+
if (binaryOutput) console.error(`${options.name} - ${action.name}`);
|
|
137
|
+
else console.info(`${options.name} - ${action.name}`);
|
|
113
138
|
const runFn = action.run;
|
|
114
|
-
runFn(input, actionContext).then((result) => {
|
|
139
|
+
runFn(input, actionContext).then(async (result) => {
|
|
140
|
+
const res = await toActionResource(result, binarySchemaMeta(action.output));
|
|
141
|
+
if (res) {
|
|
142
|
+
await writeResourceResult(res, binaryOutput ? command.opts().output : void 0);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
115
145
|
logger.info(JSON.stringify(result, null, 2));
|
|
116
146
|
}).catch(handleCLIError);
|
|
117
147
|
});
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter/cli.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Action, ActionRun, ActionStreamRun, AdapterFactory, createConsoleLogger, isStreamingAction, SilkweaveContext, SilkweaveError, SilkweaveOptions, unwrap } from '@silkweave/core'\nimport { camelCase, kebabCase } from 'change-case'\nimport { Command } from 'commander'\nimport { once } from 'events'\nimport z from 'zod/v4'\n\nfunction handleCLIError(error: unknown) {\n if (error instanceof SilkweaveError) {\n console.error(`[${error.code}] ${error.message}`)\n } else if (error instanceof z.ZodError) {\n console.error('Validation Error')\n for (const issue of error.issues) {\n console.error(`${issue.path}: ${issue.message}`)\n }\n } else if (error instanceof Error) {\n console.error(error.message)\n } else if (typeof error === 'string') {\n console.error(error)\n } else {\n console.error(JSON.stringify(error))\n }\n process.exitCode = 1\n}\n\nfunction parseCLIInput(action: Action, args: any[]) {\n const tmpArgs = args.slice(0, -1)\n const opts = tmpArgs.pop()\n // Options register as kebab-case flags (--action-id) and Commander stores them\n // camelized (actionId) - read back via camelCase(key) so snake_case schema keys\n // (action_id) map too, not just single-word ones.\n const rawInput: Record<string, unknown> = {}\n for (const key of Object.keys(action.input.shape)) {\n const value = opts[camelCase(key)]\n if (value !== undefined) { rawInput[key] = value }\n }\n action.args?.forEach((k, index) => { rawInput[String(k)] = args[index] })\n const { error, data } = action.input.safeParse(rawInput)\n if (error || !data) {\n handleCLIError(error)\n process.exit()\n }\n\n return data\n}\n\n/**\n * Map a Zod option type to its commander placeholder and, when the value needs\n * coercing from commander's raw string, a `parseArg`. Numeric/bigint/json fields\n * MUST carry a parser - without it every z.number() field fails Zod with\n * `expected number, received string`.\n */\nfunction optionSpec(type: z.ZodType): { placeholder: string; parseArg?: (value: string) => unknown } {\n if (type instanceof z.ZodNumber) {\n return { placeholder: '<number>', parseArg: (value) => Number(value) }\n }\n if (type instanceof z.ZodBigInt) {\n return { placeholder: '<bigint>', parseArg: (value) => { try { return BigInt(value) } catch { return value } } }\n }\n if (type instanceof z.ZodString || type instanceof z.ZodEnum) {\n return { placeholder: '<string>' }\n }\n if (type instanceof z.ZodObject || type instanceof z.ZodRecord || type instanceof z.ZodArray) {\n return { placeholder: '<json>', parseArg: JSON.parse }\n }\n throw new Error(`Invalid zod type: ${type.def.type}`)\n}\n\nfunction addCliOption(command: Command, key: string, type: z.ZodType, defaultValue: any, isArgument: boolean) {\n const description = type.description\n if (isArgument) {\n command.argument(`[${camelCase(key)}]`, description, defaultValue)\n return\n }\n const flag = kebabCase(key)\n if (type instanceof z.ZodBoolean) {\n command.option(`--${flag}`, description, defaultValue)\n command.option(`--no-${flag}`)\n return\n }\n const { placeholder, parseArg } = optionSpec(type)\n if (parseArg) {\n command.option(`--${flag} ${placeholder}`, description ?? '', parseArg, defaultValue)\n } else {\n command.option(`--${flag} ${placeholder}`, description, defaultValue)\n }\n}\n\nasync function runStreamingCommand(action: Action, input: object, context: SilkweaveContext) {\n const streamRun = action.run as ActionStreamRun<object, unknown>\n const iter = streamRun(input, context)\n for await (const chunk of iter) {\n const line = JSON.stringify(chunk) + '\\n'\n if (!process.stdout.write(line)) {\n await once(process.stdout, 'drain')\n }\n }\n}\n\nfunction registerCommand(program: Command, action: Action, options: SilkweaveOptions, context: SilkweaveContext) {\n const command = program.command(kebabCase(action.name)).description(action.description)\n const shape = action.input.shape\n const argKeys = action.args ?? []\n const argSet = new Set(argKeys)\n // Options first (order irrelevant), then positional arguments in `action.args`\n // order - not input-shape key order - so commander's positional slots line up\n // with how parseCLIInput reads them back (else the values are cross-assigned).\n for (const key of Object.keys(shape)) {\n if (argSet.has(key)) { continue }\n const [type, { defaultValue }] = unwrap(shape[key])\n addCliOption(command, key, type, defaultValue, false)\n }\n for (const argKey of argKeys) {\n const key = String(argKey)\n const [type, { defaultValue }] = unwrap(shape[key])\n addCliOption(command, key, type, defaultValue, true)\n }\n command.action((...args) => {\n const logger = createConsoleLogger()\n const input = parseCLIInput(action, args)\n const actionContext = context.fork({ logger, command })\n if (isStreamingAction(action)) {\n runStreamingCommand(action, input, actionContext).catch(handleCLIError)\n return\n }\n console.info(`${options.name} - ${action.name}`)\n const runFn = action.run as ActionRun<object, object>\n runFn(input, actionContext).then((result) => {\n logger.info(JSON.stringify(result, null, 2))\n }).catch(handleCLIError)\n })\n}\n\nexport const cli: AdapterFactory = () => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'cli' })\n const program = new Command()\n .name(options.name)\n .description(options.description)\n .version(options.version)\n\n return {\n context,\n start: async (actions) => {\n for (const action of actions) {\n registerCommand(program, action, options, context)\n }\n program.parse()\n },\n stop: async () => { }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter/cli.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Action, ActionRun, ActionStreamRun, AdapterFactory, binarySchemaMeta, createConsoleLogger, isBinarySchema, isStreamingAction, resourceBytes, SilkweaveContext, SilkweaveError, SilkweaveOptions, toActionResource, unwrap, type ActionResource } from '@silkweave/core'\nimport { camelCase, kebabCase } from 'change-case'\nimport { Command } from 'commander'\nimport { once } from 'events'\nimport { writeFile } from 'fs/promises'\nimport z from 'zod/v4'\n\nfunction handleCLIError(error: unknown) {\n if (error instanceof SilkweaveError) {\n console.error(`[${error.code}] ${error.message}`)\n } else if (error instanceof z.ZodError) {\n console.error('Validation Error')\n for (const issue of error.issues) {\n console.error(`${issue.path}: ${issue.message}`)\n }\n } else if (error instanceof Error) {\n console.error(error.message)\n } else if (typeof error === 'string') {\n console.error(error)\n } else {\n console.error(JSON.stringify(error))\n }\n process.exitCode = 1\n}\n\nfunction parseCLIInput(action: Action, args: any[]) {\n const tmpArgs = args.slice(0, -1)\n const opts = tmpArgs.pop()\n // Options register as kebab-case flags (--action-id) and Commander stores them\n // camelized (actionId) - read back via camelCase(key) so snake_case schema keys\n // (action_id) map too, not just single-word ones.\n const rawInput: Record<string, unknown> = {}\n for (const key of Object.keys(action.input.shape)) {\n const value = opts[camelCase(key)]\n if (value !== undefined) { rawInput[key] = value }\n }\n action.args?.forEach((k, index) => { rawInput[String(k)] = args[index] })\n const { error, data } = action.input.safeParse(rawInput)\n if (error || !data) {\n handleCLIError(error)\n process.exit()\n }\n\n return data\n}\n\n/**\n * Map a Zod option type to its commander placeholder and, when the value needs\n * coercing from commander's raw string, a `parseArg`. Numeric/bigint/json fields\n * MUST carry a parser - without it every z.number() field fails Zod with\n * `expected number, received string`.\n */\nfunction optionSpec(type: z.ZodType): { placeholder: string; parseArg?: (value: string) => unknown } {\n if (type instanceof z.ZodNumber) {\n return { placeholder: '<number>', parseArg: (value) => Number(value) }\n }\n if (type instanceof z.ZodBigInt) {\n return { placeholder: '<bigint>', parseArg: (value) => { try { return BigInt(value) } catch { return value } } }\n }\n if (type instanceof z.ZodString || type instanceof z.ZodEnum) {\n return { placeholder: '<string>' }\n }\n if (type instanceof z.ZodObject || type instanceof z.ZodRecord || type instanceof z.ZodArray) {\n return { placeholder: '<json>', parseArg: JSON.parse }\n }\n throw new Error(`Invalid zod type: ${type.def.type}`)\n}\n\nfunction addCliOption(command: Command, key: string, type: z.ZodType, defaultValue: any, isArgument: boolean) {\n const description = type.description\n if (isArgument) {\n command.argument(`[${camelCase(key)}]`, description, defaultValue)\n return\n }\n const flag = kebabCase(key)\n if (type instanceof z.ZodBoolean) {\n command.option(`--${flag}`, description, defaultValue)\n command.option(`--no-${flag}`)\n return\n }\n const { placeholder, parseArg } = optionSpec(type)\n if (parseArg) {\n command.option(`--${flag} ${placeholder}`, description ?? '', parseArg, defaultValue)\n } else {\n command.option(`--${flag} ${placeholder}`, description, defaultValue)\n }\n}\n\n/** File extension derived from a media type's subtype (`image/png` -> `png`). */\nfunction extensionFromMime(mimeType: string): string {\n const subtype = mimeType.split(';')[0].split('/')[1]?.split('+')[0]?.trim()\n return subtype || 'bin'\n}\n\n/**\n * Deliver a resource result terminal-appropriately: raw bytes to stdout when\n * piped (`my-cli screenshot > shot.png`), otherwise written to `--output` or a\n * file named after the resource - binary is never dumped onto an interactive\n * terminal. Status lines go to stderr so a piped stdout stays byte-clean.\n */\nasync function writeResourceResult(res: ActionResource, outputPath: string | undefined) {\n const bytes = resourceBytes(res)\n if (res.description) { console.error(res.description) }\n if (!outputPath && !process.stdout.isTTY) {\n if (!process.stdout.write(bytes)) { await once(process.stdout, 'drain') }\n return\n }\n const target = outputPath ?? res.name ?? `resource.${extensionFromMime(res.mimeType)}`\n await writeFile(target, bytes)\n console.error(`Wrote ${bytes.length} bytes (${res.mimeType}) to ${target}`)\n}\n\nasync function runStreamingCommand(action: Action, input: object, context: SilkweaveContext) {\n const streamRun = action.run as ActionStreamRun<object, unknown>\n const iter = streamRun(input, context)\n for await (const chunk of iter) {\n const line = JSON.stringify(chunk) + '\\n'\n if (!process.stdout.write(line)) {\n await once(process.stdout, 'drain')\n }\n }\n}\n\nfunction registerCommand(program: Command, action: Action, options: SilkweaveOptions, context: SilkweaveContext) {\n const command = program.command(kebabCase(action.name)).description(action.description)\n const shape = action.input.shape\n const argKeys = action.args ?? []\n const argSet = new Set(argKeys)\n // Binary actions get an --output flag - unless the input schema claims the\n // name, in which case the schema field wins and only pipe/TTY behavior applies.\n const binaryOutput = isBinarySchema(action.output) && !('output' in shape)\n if (binaryOutput) {\n command.option('-o, --output <path>', 'Write the resource to this file (default: the resource name)')\n }\n // Options first (order irrelevant), then positional arguments in `action.args`\n // order - not input-shape key order - so commander's positional slots line up\n // with how parseCLIInput reads them back (else the values are cross-assigned).\n for (const key of Object.keys(shape)) {\n if (argSet.has(key)) { continue }\n const [type, { defaultValue }] = unwrap(shape[key])\n addCliOption(command, key, type, defaultValue, false)\n }\n for (const argKey of argKeys) {\n const key = String(argKey)\n const [type, { defaultValue }] = unwrap(shape[key])\n addCliOption(command, key, type, defaultValue, true)\n }\n command.action((...args) => {\n const logger = createConsoleLogger()\n const input = parseCLIInput(action, args)\n const actionContext = context.fork({ logger, command })\n if (isStreamingAction(action)) {\n runStreamingCommand(action, input, actionContext).catch(handleCLIError)\n return\n }\n // Binary actions keep stdout byte-clean (a piped stdout carries the\n // payload), so the banner goes to stderr for them.\n if (binaryOutput) {\n console.error(`${options.name} - ${action.name}`)\n } else {\n console.info(`${options.name} - ${action.name}`)\n }\n const runFn = action.run as ActionRun<object, object>\n runFn(input, actionContext).then(async (result) => {\n const res = await toActionResource(result, binarySchemaMeta(action.output))\n if (res) {\n await writeResourceResult(res, binaryOutput ? command.opts<{ output?: string }>().output : undefined)\n return\n }\n logger.info(JSON.stringify(result, null, 2))\n }).catch(handleCLIError)\n })\n}\n\nexport const cli: AdapterFactory = () => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'cli' })\n const program = new Command()\n .name(options.name)\n .description(options.description)\n .version(options.version)\n\n return {\n context,\n start: async (actions) => {\n for (const action of actions) {\n registerCommand(program, action, options, context)\n }\n program.parse()\n },\n stop: async () => { }\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silkweave/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Silkweave CLI Adapter",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://www.silkweave.dev",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"change-case": "^5.4.4",
|
|
30
30
|
"commander": "^13.1.0",
|
|
31
31
|
"zod": "^3.25.0",
|
|
32
|
-
"@silkweave/core": "4.
|
|
32
|
+
"@silkweave/core": "4.4.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@eslint/js": "^10.0.1",
|