@silkweave/cli 5.1.0 → 5.2.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 CHANGED
@@ -8,6 +8,8 @@ CLI adapter for [Silkweave](https://github.com/silkweave/silkweave) - turn your
8
8
  pnpm add @silkweave/core @silkweave/cli
9
9
  ```
10
10
 
11
+ Requires **Node.js >= 22.12** (this package bundles `commander` 15).
12
+
11
13
  ## Usage
12
14
 
13
15
  ```typescript
package/build/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AdapterFactory } from "@silkweave/core";
2
-
3
2
  //#region src/adapter/cli.d.ts
4
3
  declare const cli: AdapterFactory;
5
4
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/cli.ts"],"mappings":";;;cA0Na,GAAA,EAAK,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/cli.ts"],"mappings":";;cAyQa,KAAK"}
@@ -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, 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\ninterface OptionSpec { placeholder: string; parseArg?: (value: string) => unknown }\n\n/**\n * Tolerant JSON parse. A union arm is typically a scalar (`number | number[]`),\n * and commander hands scalars over as bare strings that are not valid JSON -\n * `--cost 3` must survive as `\"3\"` for Zod to coerce, while `--cost '[1,2]'`\n * parses. Throwing here would trade a build-time crash for a run-time one.\n */\nfunction parseMaybeJson(value: string): unknown {\n try { return JSON.parse(value) } catch { return value }\n}\n\n/**\n * Render a literal-bearing option as its arms (`<a|b>`) rather than `<json>`,\n * and coerce only when it needs it: all-string literals arrive usable as-is,\n * anything else (numbers, booleans, null) has to be parsed back off the string.\n */\nfunction literalSpec(values: readonly unknown[]): OptionSpec {\n const placeholder = `<${values.map((value) => String(value)).join('|')}>`\n if (values.every((value) => typeof value === 'string')) { return { placeholder } }\n return { placeholder, parseArg: parseMaybeJson }\n}\n\n/** The literal values every arm of a union carries, or undefined if any arm is not a literal. */\nfunction literalUnionValues(options: readonly z.ZodType[]): unknown[] | undefined {\n const values: unknown[] = []\n for (const option of options) {\n if (!(option instanceof z.ZodLiteral)) { return undefined }\n values.push(...option.def.values)\n }\n return values\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 *\n * This runs while the command table is built, so an unsupported type takes the\n * whole binary down (`--help` included) - hence the key in the error message.\n */\nfunction optionSpec(type: z.ZodType, key: string): OptionSpec {\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.ZodLiteral) {\n return literalSpec(type.def.values)\n }\n if (type instanceof z.ZodUnion) {\n const literals = literalUnionValues(type.def.options as readonly z.ZodType[])\n return literals ? literalSpec(literals) : { placeholder: '<json>', parseArg: parseMaybeJson }\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(`option \"${key}\": unsupported 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, key)\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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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter/cli.ts"],"sourcesContent":["import {\n Action,\n ActionRun,\n ActionStreamRun,\n AdapterFactory,\n binarySchemaMeta,\n createConsoleLogger,\n isBinarySchema,\n isStreamingAction,\n resourceBytes,\n SilkweaveContext,\n SilkweaveError,\n SilkweaveOptions,\n toActionResource,\n unwrap,\n type ActionResource\n} 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) {\n rawInput[key] = value\n }\n }\n action.args?.forEach((k, index) => {\n rawInput[String(k)] = args[index]\n })\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\ninterface OptionSpec {\n placeholder: string\n parseArg?: (value: string) => unknown\n}\n\n/**\n * Tolerant JSON parse. A union arm is typically a scalar (`number | number[]`),\n * and commander hands scalars over as bare strings that are not valid JSON -\n * `--cost 3` must survive as `\"3\"` for Zod to coerce, while `--cost '[1,2]'`\n * parses. Throwing here would trade a build-time crash for a run-time one.\n */\nfunction parseMaybeJson(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\n/**\n * Render a literal-bearing option as its arms (`<a|b>`) rather than `<json>`,\n * and coerce only when it needs it: all-string literals arrive usable as-is,\n * anything else (numbers, booleans, null) has to be parsed back off the string.\n */\nfunction literalSpec(values: readonly unknown[]): OptionSpec {\n const placeholder = `<${values.map((value) => String(value)).join('|')}>`\n if (values.every((value) => typeof value === 'string')) {\n return { placeholder }\n }\n return { placeholder, parseArg: parseMaybeJson }\n}\n\n/** The literal values every arm of a union carries, or undefined if any arm is not a literal. */\nfunction literalUnionValues(options: readonly z.ZodType[]): unknown[] | undefined {\n const values: unknown[] = []\n for (const option of options) {\n if (!(option instanceof z.ZodLiteral)) {\n return undefined\n }\n values.push(...option.def.values)\n }\n return values\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 *\n * This runs while the command table is built, so an unsupported type takes the\n * whole binary down (`--help` included) - hence the key in the error message.\n */\nfunction optionSpec(type: z.ZodType, key: string): OptionSpec {\n if (type instanceof z.ZodNumber) {\n return { placeholder: '<number>', parseArg: (value) => Number(value) }\n }\n if (type instanceof z.ZodBigInt) {\n return {\n placeholder: '<bigint>',\n parseArg: (value) => {\n try {\n return BigInt(value)\n } catch {\n return value\n }\n }\n }\n }\n if (type instanceof z.ZodString || type instanceof z.ZodEnum) {\n return { placeholder: '<string>' }\n }\n if (type instanceof z.ZodLiteral) {\n return literalSpec(type.def.values)\n }\n if (type instanceof z.ZodUnion) {\n const literals = literalUnionValues(type.def.options as readonly z.ZodType[])\n return literals ? literalSpec(literals) : { placeholder: '<json>', parseArg: parseMaybeJson }\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(`option \"${key}\": unsupported 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, key)\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) {\n console.error(res.description)\n }\n if (!outputPath && !process.stdout.isTTY) {\n if (!process.stdout.write(bytes)) {\n await once(process.stdout, 'drain')\n }\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)) {\n continue\n }\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)\n .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 })\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().name(options.name).description(options.description).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": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Silkweave CLI Adapter",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.silkweave.dev",
@@ -13,6 +13,9 @@
13
13
  "directory": "packages/cli"
14
14
  },
15
15
  "type": "module",
16
+ "engines": {
17
+ "node": ">=22.12.0"
18
+ },
16
19
  "main": "./build/index.mjs",
17
20
  "types": "./build/index.d.mts",
18
21
  "files": [
@@ -27,28 +30,24 @@
27
30
  },
28
31
  "dependencies": {
29
32
  "change-case": "^5.4.4",
30
- "commander": "^13.1.0",
31
- "zod": "^3.25.0",
32
- "@silkweave/core": "5.1.0"
33
+ "commander": "^15.0.0",
34
+ "zod": "^3.25.0 || ^4.0.0",
35
+ "@silkweave/core": "5.2.0"
33
36
  },
34
37
  "devDependencies": {
35
- "@eslint/js": "^10.0.1",
36
- "@stylistic/eslint-plugin": "^5.10.0",
37
- "@types/node": "^22.0.0",
38
- "eslint": "^10.4.0",
38
+ "@types/node": "^26.2.0",
39
39
  "rimraf": "^6.1.3",
40
- "tsdown": "^0.21.8",
41
- "tsx": "^4.21.0",
42
- "typescript": "^5.9.3",
43
- "typescript-eslint": "^8.56.1",
44
- "vitest": "^4.1.9"
40
+ "tsdown": "^0.22.14",
41
+ "tsx": "^4.23.12",
42
+ "typescript": "^7.0.2",
43
+ "vitest": "^4.1.10"
45
44
  },
46
45
  "scripts": {
47
46
  "clean": "rimraf build",
48
47
  "build": "tsdown",
49
48
  "watch": "tsdown --watch",
50
49
  "typecheck": "tsc --noEmit",
51
- "lint": "eslint",
50
+ "lint": "oxlint",
52
51
  "check": "pnpm lint && pnpm typecheck",
53
52
  "test": "vitest run"
54
53
  }