apcore-cli 0.3.1 → 0.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/CHANGELOG.md +17 -0
- package/README.md +7 -4
- package/dist/bin/apcore-cli.js +16 -6
- package/dist/bin/apcore-cli.js.map +1 -1
- package/dist/index.d.ts +30 -3
- package/dist/index.js +175 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to apcore-cli (TypeScript SDK) will be documented in this fi
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-03-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Verbose help mode** — Built-in apcore options (`--input`, `--yes`, `--large-input`, `--format`, `--sandbox`) are now hidden from `--help` output by default. Pass `--help --verbose` to display the full option list including built-in options.
|
|
12
|
+
- **Universal man page generation** — `buildProgramManPage()` generates a complete roff man page covering all registered commands. `configureManHelp()` adds `--help --man` support to any Commander program, enabling downstream projects to get man pages for free.
|
|
13
|
+
- **Documentation URL support** — `setDocsUrl()` sets a base URL for online docs. Per-command help shows `Docs: {url}/commands/{name}`, man page SEE ALSO includes `Full documentation at {url}`. No default — disabled when not set.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `buildModuleCommand()` accepts optional `verboseHelp` parameter to control built-in option visibility in help.
|
|
17
|
+
- `--sandbox` is now always hidden from help (not yet implemented). Only four built-in options (`--input`, `--yes`, `--large-input`, `--format`) toggle with `--verbose`.
|
|
18
|
+
- Improved built-in option descriptions for clarity (e.g., `--input` now reads "Read JSON input from a file path, or use '-' to read from stdin pipe").
|
|
19
|
+
|
|
20
|
+
## [0.3.2] - 2026-03-28
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Handle missing `package.json` for version retrieval in bundled environments (e.g., Bun compile).
|
|
24
|
+
|
|
8
25
|
## [0.3.1] - 2026-03-28
|
|
9
26
|
|
|
10
27
|
### Changed
|
package/README.md
CHANGED
|
@@ -148,6 +148,8 @@ apcore-cli [OPTIONS] COMMAND [ARGS]
|
|
|
148
148
|
| `--log-level` | `WARNING` | Logging: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
149
149
|
| `--version` | | Show version and exit |
|
|
150
150
|
| `--help` | | Show help and exit |
|
|
151
|
+
| `--verbose` | | Show all options in help (including built-in apcore options) |
|
|
152
|
+
| `--man` | | Output man page in roff format (use with `--help`) |
|
|
151
153
|
|
|
152
154
|
### Built-in Commands
|
|
153
155
|
|
|
@@ -161,7 +163,7 @@ apcore-cli [OPTIONS] COMMAND [ARGS]
|
|
|
161
163
|
|
|
162
164
|
### Module Execution Options
|
|
163
165
|
|
|
164
|
-
When executing a module (e.g. `apcore-cli math.add`), these built-in options are
|
|
166
|
+
When executing a module (e.g. `apcore-cli math.add`), these built-in options are available (hidden by default; use `--verbose` to show in `--help`):
|
|
165
167
|
|
|
166
168
|
| Option | Description |
|
|
167
169
|
|--------|-------------|
|
|
@@ -169,7 +171,7 @@ When executing a module (e.g. `apcore-cli math.add`), these built-in options are
|
|
|
169
171
|
| `--yes` / `-y` | Bypass approval prompts |
|
|
170
172
|
| `--large-input` | Allow STDIN input larger than 10MB |
|
|
171
173
|
| `--format` | Output format: `json` or `table` |
|
|
172
|
-
| `--sandbox` | Run module in subprocess sandbox |
|
|
174
|
+
| `--sandbox` | Run module in subprocess sandbox (not yet implemented — always hidden) |
|
|
173
175
|
|
|
174
176
|
Schema-generated flags (e.g. `--a`, `--b`) are added automatically from the module's `input_schema`.
|
|
175
177
|
|
|
@@ -234,7 +236,8 @@ cli:
|
|
|
234
236
|
- **Schema validation** -- inputs validated against JSON Schema before execution, with `$ref`/`allOf`/`anyOf`/`oneOf` resolution
|
|
235
237
|
- **Security** -- API key auth (keyring + AES-256-GCM), append-only audit logging, subprocess sandboxing
|
|
236
238
|
- **Shell completions** -- `apcore-cli completion bash|zsh|fish` generates completion scripts with dynamic module ID completion
|
|
237
|
-
- **Man pages** -- `apcore-cli man <command>`
|
|
239
|
+
- **Man pages** -- `apcore-cli man <command>` for single commands, or `--help --man` for a complete program man page. `configureManHelp()` provides one-line integration for downstream projects
|
|
240
|
+
- **Documentation URL** -- `setDocsUrl()` adds doc links to help footers and man pages
|
|
238
241
|
- **Audit logging** -- all executions logged to `~/.apcore-cli/audit.jsonl` with SHA-256 input hashing
|
|
239
242
|
|
|
240
243
|
## How It Works
|
|
@@ -274,7 +277,7 @@ apcore Registry + Executor (your modules, unchanged)
|
|
|
274
277
|
|
|
275
278
|
**Classes:** `LazyModuleGroup`, `ConfigResolver`, `AuthProvider`, `ConfigEncryptor`, `AuditLogger`, `Sandbox`
|
|
276
279
|
|
|
277
|
-
**Functions:** `createCli`, `main`, `buildModuleCommand`, `validateModuleId`, `collectInput`, `schemaToCliOptions`, `reconvertEnumValues`, `resolveRefs`, `checkApproval`, `resolveFormat`, `formatModuleList`, `formatModuleDetail`, `formatExecResult`, `registerDiscoveryCommands`, `registerShellCommands`, `setAuditLogger`, `getAuditLogger`, `exitCodeForError`, `mapType`, `extractHelp`, `truncate`
|
|
280
|
+
**Functions:** `createCli`, `main`, `buildModuleCommand`, `validateModuleId`, `collectInput`, `schemaToCliOptions`, `reconvertEnumValues`, `resolveRefs`, `checkApproval`, `resolveFormat`, `formatModuleList`, `formatModuleDetail`, `formatExecResult`, `registerDiscoveryCommands`, `registerShellCommands`, `setAuditLogger`, `getAuditLogger`, `setVerboseHelp`, `setDocsUrl`, `buildProgramManPage`, `configureManHelp`, `exitCodeForError`, `mapType`, `extractHelp`, `truncate`
|
|
278
281
|
|
|
279
282
|
**Errors:** `ApprovalTimeoutError`, `ApprovalDeniedError`, `AuthenticationError`, `ConfigDecryptionError`, `ModuleExecutionError`, `ModuleNotFoundError`, `SchemaValidationError`
|
|
280
283
|
|
package/dist/bin/apcore-cli.js
CHANGED
|
@@ -134,7 +134,7 @@ init_errors();
|
|
|
134
134
|
import { readFileSync } from "fs";
|
|
135
135
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
136
136
|
import * as path3 from "path";
|
|
137
|
-
import { Command, CommanderError } from "commander";
|
|
137
|
+
import { Command, CommanderError, Option } from "commander";
|
|
138
138
|
|
|
139
139
|
// src/ref-resolver.ts
|
|
140
140
|
init_esm_shims();
|
|
@@ -311,13 +311,22 @@ init_esm_shims();
|
|
|
311
311
|
|
|
312
312
|
// src/main.ts
|
|
313
313
|
var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
|
|
314
|
-
var
|
|
315
|
-
|
|
316
|
-
|
|
314
|
+
var verboseHelp = false;
|
|
315
|
+
function hasVerboseFlag() {
|
|
316
|
+
return process.argv.includes("--verbose");
|
|
317
|
+
}
|
|
318
|
+
var VERSION = "0.0.0";
|
|
319
|
+
try {
|
|
320
|
+
const pkg = JSON.parse(readFileSync(path3.resolve(__dirname2, "../package.json"), "utf-8"));
|
|
321
|
+
VERSION = pkg.version;
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
function createCli(extensionsDir, progName, verbose = false) {
|
|
325
|
+
verboseHelp = verbose;
|
|
317
326
|
const resolvedProgName = progName ?? path3.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
|
|
318
327
|
const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
|
|
319
328
|
setLogLevel(cliLogLevel);
|
|
320
|
-
const program = new Command(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--commands-dir <path>", "Path to convention-based commands directory").option("--binding <path>", "Path to binding.yaml for display overlay").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING");
|
|
329
|
+
const program = new Command(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--commands-dir <path>", "Path to convention-based commands directory").option("--binding <path>", "Path to binding.yaml for display overlay").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
|
|
321
330
|
const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
|
|
322
331
|
void resolvedExtDir;
|
|
323
332
|
registerInitCommand(program);
|
|
@@ -351,7 +360,8 @@ async function applyToolkitIntegration(commandsDir, bindingPath) {
|
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
362
|
function main(progName) {
|
|
354
|
-
|
|
363
|
+
verboseHelp = hasVerboseFlag();
|
|
364
|
+
const program = createCli(void 0, progName, verboseHelp);
|
|
355
365
|
try {
|
|
356
366
|
program.parse(process.argv);
|
|
357
367
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js","../../src/errors.ts","../../bin/apcore-cli.ts","../../src/main.ts","../../src/ref-resolver.ts","../../src/schema-parser.ts","../../src/approval.ts","../../src/output.ts","../../src/logger.ts","../../src/init-cmd.ts","../../src/display-helpers.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","/**\n * Error classes and exit code mapping for apcore-cli.\n *\n * Protocol spec: Error handling & exit codes\n */\n\n// ---------------------------------------------------------------------------\n// Error classes\n// ---------------------------------------------------------------------------\n\n/** Thrown when the user does not approve module execution within the timeout. */\nexport class ApprovalTimeoutError extends Error {\n constructor(message = \"Approval timed out\") {\n super(message);\n this.name = \"ApprovalTimeoutError\";\n }\n}\n\n/** Thrown when API key authentication fails or is missing. */\nexport class AuthenticationError extends Error {\n constructor(message = \"Authentication failed\") {\n super(message);\n this.name = \"AuthenticationError\";\n }\n}\n\n/** Thrown when encrypted config cannot be decrypted. */\nexport class ConfigDecryptionError extends Error {\n constructor(message = \"Config decryption failed\") {\n super(message);\n this.name = \"ConfigDecryptionError\";\n }\n}\n\n/** Thrown when module execution fails. */\nexport class ModuleExecutionError extends Error {\n constructor(message = \"Module execution failed\") {\n super(message);\n this.name = \"ModuleExecutionError\";\n }\n}\n\n/** Thrown when approval is denied by the user. */\nexport class ApprovalDeniedError extends Error {\n constructor(message = \"Approval denied\") {\n super(message);\n this.name = \"ApprovalDeniedError\";\n }\n}\n\n/** Thrown when schema validation fails. */\nexport class SchemaValidationError extends Error {\n constructor(message = \"Schema validation failed\") {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\n/** Thrown when a module is not found. */\nexport class ModuleNotFoundError extends Error {\n constructor(message = \"Module not found\") {\n super(message);\n this.name = \"ModuleNotFoundError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Exit code map\n// ---------------------------------------------------------------------------\n\nexport const EXIT_CODES = {\n SUCCESS: 0,\n MODULE_EXECUTE_ERROR: 1,\n MODULE_TIMEOUT: 1,\n INVALID_CLI_INPUT: 2,\n MODULE_NOT_FOUND: 44,\n MODULE_LOAD_ERROR: 44,\n MODULE_DISABLED: 44,\n SCHEMA_VALIDATION_ERROR: 45,\n APPROVAL_DENIED: 46,\n APPROVAL_TIMEOUT: 46,\n CONFIG_NOT_FOUND: 47,\n CONFIG_INVALID: 47,\n SCHEMA_CIRCULAR_REF: 48,\n ACL_DENIED: 77,\n KEYBOARD_INTERRUPT: 130,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map a caught error to the appropriate process exit code.\n * Also checks for apcore error codes on the error object.\n */\nexport function exitCodeForError(error: unknown): ExitCode {\n if (error instanceof ApprovalTimeoutError) {\n return EXIT_CODES.APPROVAL_TIMEOUT;\n }\n if (error instanceof ApprovalDeniedError) {\n return EXIT_CODES.APPROVAL_DENIED;\n }\n if (error instanceof AuthenticationError) {\n return EXIT_CODES.ACL_DENIED;\n }\n if (error instanceof ConfigDecryptionError) {\n return EXIT_CODES.CONFIG_INVALID;\n }\n if (error instanceof SchemaValidationError) {\n return EXIT_CODES.SCHEMA_VALIDATION_ERROR;\n }\n if (error instanceof ModuleNotFoundError) {\n return EXIT_CODES.MODULE_NOT_FOUND;\n }\n if (error instanceof ModuleExecutionError) {\n return EXIT_CODES.MODULE_EXECUTE_ERROR;\n }\n\n // Check for apcore error codes on the error object\n if (error instanceof Error) {\n const code = (error as unknown as Record<string, unknown>).code as string | undefined;\n const codeMap: Record<string, ExitCode> = {\n MODULE_NOT_FOUND: EXIT_CODES.MODULE_NOT_FOUND,\n MODULE_LOAD_ERROR: EXIT_CODES.MODULE_LOAD_ERROR,\n MODULE_DISABLED: EXIT_CODES.MODULE_DISABLED,\n SCHEMA_VALIDATION_ERROR: EXIT_CODES.SCHEMA_VALIDATION_ERROR,\n SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,\n APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,\n APPROVAL_TIMEOUT: EXIT_CODES.APPROVAL_TIMEOUT,\n CONFIG_NOT_FOUND: EXIT_CODES.CONFIG_NOT_FOUND,\n CONFIG_INVALID: EXIT_CODES.CONFIG_INVALID,\n MODULE_EXECUTE_ERROR: EXIT_CODES.MODULE_EXECUTE_ERROR,\n MODULE_TIMEOUT: EXIT_CODES.MODULE_TIMEOUT,\n ACL_DENIED: EXIT_CODES.ACL_DENIED,\n };\n if (code && code in codeMap) {\n return codeMap[code];\n }\n }\n\n return EXIT_CODES.MODULE_EXECUTE_ERROR;\n}\n","#!/usr/bin/env node\n/**\n * apcore-cli — Shebang entry point.\n *\n * This file is the bin target. It bootstraps the CLI and delegates to main().\n */\n\nimport { main } from \"../src/main.js\";\n\nmain(\"apcore-cli\");\n","/**\n * CLI entry point — createCli / main equivalents.\n *\n * Protocol spec: CLI bootstrapping & command registration\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport * as path from \"node:path\";\nimport { Command, CommanderError } from \"commander\";\nimport { EXIT_CODES, exitCodeForError } from \"./errors.js\";\nimport { resolveRefs } from \"./ref-resolver.js\";\nimport { schemaToCliOptions } from \"./schema-parser.js\";\nimport { checkApproval } from \"./approval.js\";\nimport { formatExecResult } from \"./output.js\";\nimport { setLogLevel } from \"./logger.js\";\nimport { registerInitCommand } from \"./init-cmd.js\";\nimport { getDisplay } from \"./display-helpers.js\";\nimport type { Executor, ModuleDescriptor } from \"./cli.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(path.resolve(__dirname, \"../package.json\"), \"utf-8\"));\nconst VERSION: string = pkg.version;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Configuration for a single Commander option derived from a JSON Schema property. */\nexport interface OptionConfig {\n /** The property name from the schema. */\n name: string;\n /** Commander flags string (e.g. \"--my-flag <value>\" or \"--flag, --no-flag\"). */\n flags: string;\n /** Help text for the option. */\n description: string;\n /** Default value. */\n defaultValue?: unknown;\n /** Whether the field is required (for display only). */\n required: boolean;\n /** Enum choices (string values). */\n choices?: string[];\n /** Whether this is a boolean flag pair (--flag/--no-flag). */\n isBooleanFlag?: boolean;\n /** Maps string enum value → original type name (\"int\", \"float\", \"bool\"). */\n enumOriginalTypes?: Record<string, string>;\n /** Parser function for Commander (e.g. parseInt, parseFloat). */\n parseArg?: (value: string) => unknown;\n}\n\n// ---------------------------------------------------------------------------\n// createCli\n// ---------------------------------------------------------------------------\n\n/**\n * Build and return the top-level Commander program.\n *\n * @param extensionsDir Path to the extensions directory (default: ./extensions)\n * @param progName Program name shown in help (default: apcore-cli)\n */\nexport function createCli(\n extensionsDir?: string,\n progName?: string,\n): Command {\n // Resolve program name\n const resolvedProgName = progName ?? path.basename(process.argv[1] ?? \"apcore-cli\") ?? \"apcore-cli\";\n\n // Resolve log level\n const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? \"WARNING\";\n setLogLevel(cliLogLevel);\n\n const program = new Command(resolvedProgName)\n .exitOverride()\n .version(VERSION, \"--version\", `Show ${resolvedProgName} version`)\n .description(\"apcore CLI — execute apcore modules from the command line\")\n .option(\"--extensions-dir <path>\", \"Path to extensions directory\")\n .option(\"--commands-dir <path>\", \"Path to convention-based commands directory\")\n .option(\"--binding <path>\", \"Path to binding.yaml for display overlay\")\n .option(\"--log-level <level>\", \"Logging level (DEBUG|INFO|WARNING|ERROR)\", \"WARNING\");\n\n // NOTE: Full registry/executor wiring requires apcore-js to be available.\n // For now, extensions-dir is accepted but not wired to a real registry.\n const resolvedExtDir = extensionsDir\n ?? process.env.APCORE_EXTENSIONS_ROOT\n ?? \"./extensions\";\n void resolvedExtDir; // Will be used when apcore-js registry is wired\n\n // Register init command for scaffolding\n registerInitCommand(program);\n\n // Hook to apply optional toolkit integration before command execution.\n // Commander actions are async, so we can set up toolkit state lazily.\n program.hook(\"preAction\", async (thisCommand) => {\n const opts = thisCommand.opts();\n const commandsDir = opts.commandsDir as string | undefined;\n const bindingPath = opts.binding as string | undefined;\n await applyToolkitIntegration(commandsDir, bindingPath);\n });\n\n return program;\n}\n\n// ---------------------------------------------------------------------------\n// applyToolkitIntegration\n// ---------------------------------------------------------------------------\n\n/**\n * Optionally apply apcore-toolkit features (DisplayResolver, RegistryWriter).\n *\n * Uses dynamic import so the dependency remains optional — if apcore-toolkit\n * is not installed, a warning is printed and the CLI continues without it.\n */\nexport async function applyToolkitIntegration(\n commandsDir?: string,\n bindingPath?: string,\n): Promise<void> {\n if (!commandsDir && !bindingPath) {\n return;\n }\n\n try {\n // Use a variable to prevent TypeScript from resolving the module at build time\n const toolkitModule = \"apcore-toolkit\";\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const toolkit = await import(/* @vite-ignore */ toolkitModule);\n\n // ConventionScanner is not yet available in the TypeScript toolkit\n if (commandsDir) {\n console.warn(\"Convention scanning not yet available in TypeScript toolkit\");\n }\n\n // DisplayResolver for binding overlay\n if (bindingPath) {\n const resolver = new toolkit.DisplayResolver();\n // NOTE: Full integration requires registered modules from apcore-js.\n // For now, the resolver is instantiated and ready for when modules\n // are available through the registry.\n void resolver;\n }\n } catch {\n // apcore-toolkit not installed — graceful fallback\n console.warn(\"apcore-toolkit not installed — toolkit features unavailable\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// main\n// ---------------------------------------------------------------------------\n\n/**\n * Parse argv and run the CLI. Handles top-level error catching and exit codes.\n */\nexport function main(progName?: string): void {\n const program = createCli(undefined, progName);\n\n try {\n program.parse(process.argv);\n } catch (error: unknown) {\n if (error instanceof CommanderError) {\n // Commander already printed the error message\n process.exit(error.exitCode);\n }\n const code = exitCodeForError(error);\n if (error instanceof Error) {\n process.stderr.write(`Error: ${error.message}\\n`);\n }\n process.exit(code);\n }\n}\n\n// ---------------------------------------------------------------------------\n// buildModuleCommand\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Commander Command for a single apcore module.\n */\nexport function buildModuleCommand(\n moduleDef: ModuleDescriptor,\n executor: Executor,\n helpTextMaxLength = 1000,\n cmdName?: string,\n): Command {\n const moduleId = moduleDef.id;\n let resolvedSchema: Record<string, unknown> = {};\n let schemaOptions: OptionConfig[] = [];\n\n // Resolve display overlay fields\n const display = getDisplay(moduleDef);\n const cliDisplay = (display.cli && typeof display.cli === \"object\" && !Array.isArray(display.cli))\n ? (display.cli as Record<string, unknown>)\n : {};\n const effectiveCmdName: string = cmdName ?? (cliDisplay.alias as string | undefined) ?? moduleId;\n const cmdHelp: string = (cliDisplay.description as string | undefined) ?? moduleDef.description;\n\n // Resolve schema\n const inputSchema = moduleDef.inputSchema;\n if (inputSchema && typeof inputSchema === \"object\" && inputSchema.properties) {\n try {\n resolvedSchema = resolveRefs(inputSchema, 32, moduleId);\n } catch {\n resolvedSchema = inputSchema;\n }\n schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);\n }\n\n const cmd = new Command(effectiveCmdName).description(cmdHelp);\n\n // Built-in options\n cmd.option(\"--input <source>\", \"Read input from STDIN ('-')\");\n cmd.option(\"-y, --yes\", \"Bypass approval prompts\", false);\n cmd.option(\"--large-input\", \"Allow STDIN input larger than 10MB\", false);\n cmd.option(\"--format <format>\", \"Output format (json|table)\");\n cmd.option(\"--sandbox\", \"Run module in subprocess sandbox\", false);\n\n // Schema-generated options\n for (const opt of schemaOptions) {\n if (opt.parseArg) {\n cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);\n } else {\n cmd.option(opt.flags, opt.description, opt.defaultValue as string | boolean | undefined);\n }\n }\n\n // Action callback\n cmd.action(async (options: Record<string, unknown>) => {\n // Pop built-in options\n const stdinFlag = options.input as string | undefined;\n const autoApprove = options.yes as boolean;\n const largeInput = options.largeInput as boolean;\n const outputFormat = options.format as string | undefined;\n const sandboxEnabled = options.sandbox as boolean;\n\n // Remove built-in keys from options to get schema kwargs\n const schemaKwargs: Record<string, unknown> = {};\n const builtinKeys = new Set([\"input\", \"yes\", \"largeInput\", \"format\", \"sandbox\"]);\n for (const [k, v] of Object.entries(options)) {\n if (!builtinKeys.has(k)) {\n schemaKwargs[k] = v;\n }\n }\n\n try {\n // Collect and merge input\n const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);\n\n // Reconvert enum values\n const reconverted = reconvertEnumValues(merged, schemaOptions);\n\n // Check approval\n await checkApproval(moduleDef, autoApprove);\n\n // Execute with timing\n const { Sandbox } = await import(\"./security/index.js\");\n const sandbox = new Sandbox(sandboxEnabled);\n const startTime = performance.now();\n const result = await sandbox.execute(moduleId, reconverted, executor);\n const durationMs = Math.round(performance.now() - startTime);\n\n // Audit log (success)\n const { getAuditLogger } = await import(\"./security/audit.js\");\n const auditLogger = getAuditLogger();\n if (auditLogger) {\n auditLogger.logExecution(moduleId, reconverted, \"success\", 0, durationMs);\n }\n\n // Format output\n formatExecResult(result, outputFormat);\n } catch (err: unknown) {\n // Audit log (error)\n const { getAuditLogger } = await import(\"./security/audit.js\");\n const auditLogger = getAuditLogger();\n const code = exitCodeForError(err);\n if (auditLogger) {\n auditLogger.logExecution(moduleId, {}, \"error\", code, 0);\n }\n\n if (err instanceof Error) {\n process.stderr.write(`Error: ${err.message}\\n`);\n }\n process.exit(code);\n }\n });\n\n return cmd;\n}\n\n// ---------------------------------------------------------------------------\n// validateModuleId\n// ---------------------------------------------------------------------------\n\n/**\n * Validate that a module ID conforms to the expected format.\n * Pattern: [a-z][a-z0-9_]*(.[a-z][a-z0-9_])* — max 128 chars.\n */\nexport function validateModuleId(moduleId: string): void {\n if (moduleId.length > 128) {\n process.stderr.write(\n `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n if (!/^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {\n process.stderr.write(\n `Error: Invalid module ID format: '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n}\n\n// ---------------------------------------------------------------------------\n// collectInput\n// ---------------------------------------------------------------------------\n\n/**\n * Collect module input from stdin and/or CLI keyword arguments.\n */\nexport async function collectInput(\n stdinFlag?: string,\n cliKwargs: Record<string, unknown> = {},\n largeInput?: boolean,\n): Promise<Record<string, unknown>> {\n // Remove null/undefined values from CLI kwargs\n const cliKwargsNonNull: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(cliKwargs)) {\n if (v !== null && v !== undefined) {\n cliKwargsNonNull[k] = v;\n }\n }\n\n if (!stdinFlag) {\n return cliKwargsNonNull;\n }\n\n if (stdinFlag === \"-\") {\n const raw = await readStdin();\n const rawSize = Buffer.byteLength(raw, \"utf-8\");\n\n if (rawSize > 10_485_760 && !largeInput) {\n process.stderr.write(\n \"Error: STDIN input exceeds 10MB limit. Use --large-input to override.\\n\",\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n if (!raw) {\n return cliKwargsNonNull;\n }\n\n let stdinData: unknown;\n try {\n stdinData = JSON.parse(raw);\n } catch {\n process.stderr.write(\n \"Error: STDIN does not contain valid JSON.\\n\",\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n if (typeof stdinData !== \"object\" || stdinData === null || Array.isArray(stdinData)) {\n process.stderr.write(\n `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? \"array\" : typeof stdinData}.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n // CLI flags override STDIN for duplicate keys\n return { ...(stdinData as Record<string, unknown>), ...cliKwargsNonNull };\n }\n\n return cliKwargsNonNull;\n}\n\n/**\n * Read all data from stdin with proper cleanup.\n */\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n const onData = (chunk: Buffer) => chunks.push(chunk);\n const onEnd = () => {\n cleanup();\n resolve(Buffer.concat(chunks).toString(\"utf-8\"));\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const cleanup = () => {\n process.stdin.removeListener(\"data\", onData);\n process.stdin.removeListener(\"end\", onEnd);\n process.stdin.removeListener(\"error\", onError);\n };\n process.stdin.on(\"data\", onData);\n process.stdin.on(\"end\", onEnd);\n process.stdin.on(\"error\", onError);\n process.stdin.resume();\n });\n}\n\n// ---------------------------------------------------------------------------\n// reconvertEnumValues\n// ---------------------------------------------------------------------------\n\n/**\n * Re-convert CLI string values back to their schema-typed equivalents\n * based on the option configs.\n */\nexport function reconvertEnumValues(\n kwargs: Record<string, unknown>,\n options: OptionConfig[],\n): Record<string, unknown> {\n const result = { ...kwargs };\n for (const opt of options) {\n if (!opt.enumOriginalTypes) continue;\n const paramName = opt.name;\n if (!(paramName in result) || result[paramName] === null || result[paramName] === undefined) {\n continue;\n }\n const strVal = String(result[paramName]);\n const origType = opt.enumOriginalTypes[strVal];\n if (origType === \"int\") {\n result[paramName] = parseInt(strVal, 10);\n } else if (origType === \"float\") {\n result[paramName] = parseFloat(strVal);\n } else if (origType === \"bool\") {\n result[paramName] = strVal.toLowerCase() === \"true\";\n }\n }\n return result;\n}\n","/**\n * JSON Schema $ref resolver.\n *\n * Protocol spec: Schema resolution & $ref handling\n */\n\nimport { EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// resolveRefs\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve all $ref references in a JSON Schema.\n * Returns a fully inlined schema with $defs/definitions removed.\n */\nexport function resolveRefs(\n schema: Record<string, unknown>,\n maxDepth = 32,\n moduleId = \"\",\n): Record<string, unknown> {\n const cloned = structuredClone(schema);\n const defs = (cloned.$defs ?? cloned.definitions ?? {}) as Record<\n string,\n unknown\n >;\n const result = resolveNode(\n cloned,\n defs,\n new Set<string>(),\n 0,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n\n // Remove definition keys\n delete result.$defs;\n delete result.definitions;\n return result;\n}\n\nfunction resolveNode(\n node: unknown,\n defs: Record<string, unknown>,\n visited: Set<string>,\n depth: number,\n maxDepth: number,\n moduleId: string,\n): unknown {\n if (typeof node !== \"object\" || node === null || Array.isArray(node)) {\n return node;\n }\n\n const obj = node as Record<string, unknown>;\n\n // Handle $ref\n if (\"$ref\" in obj) {\n const refPath = obj.$ref as string;\n\n if (depth >= maxDepth) {\n process.stderr.write(\n `Error: $ref resolution depth exceeded maximum of ${maxDepth} for module '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);\n }\n\n if (visited.has(refPath)) {\n process.stderr.write(\n `Error: Circular $ref detected in schema for module '${moduleId}' at path '${refPath}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);\n }\n\n // Parse ref target: extract key from \"#/$defs/Address\" → \"Address\"\n const parts = refPath.split(\"/\");\n const key = parts[parts.length - 1];\n\n if (!(key in defs)) {\n process.stderr.write(\n `Error: Unresolvable $ref '${refPath}' in schema for module '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);\n }\n\n const newVisited = new Set(visited);\n newVisited.add(refPath);\n return resolveNode(defs[key], defs, newVisited, depth + 1, maxDepth, moduleId);\n }\n\n // Handle allOf\n if (\"allOf\" in obj && Array.isArray(obj.allOf)) {\n const merged: Record<string, unknown> = {\n properties: {},\n required: [] as string[],\n };\n for (const subSchema of obj.allOf as unknown[]) {\n const resolved = resolveNode(\n subSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n if (resolved.properties) {\n Object.assign(\n merged.properties as Record<string, unknown>,\n resolved.properties,\n );\n }\n if (Array.isArray(resolved.required)) {\n (merged.required as string[]).push(...resolved.required);\n }\n }\n // Deduplicate required\n merged.required = [...new Set(merged.required as string[])];\n // Copy non-composition keys\n for (const [k, v] of Object.entries(obj)) {\n if (k !== \"allOf\" && !(k in merged)) {\n merged[k] = v;\n }\n }\n return merged;\n }\n\n // Handle anyOf / oneOf\n for (const keyword of [\"anyOf\", \"oneOf\"]) {\n if (keyword in obj && Array.isArray(obj[keyword])) {\n const merged: Record<string, unknown> = {\n properties: {},\n required: [] as string[],\n };\n const allRequiredSets: Set<string>[] = [];\n for (const subSchema of obj[keyword] as unknown[]) {\n const resolved = resolveNode(\n subSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n if (resolved.properties) {\n Object.assign(\n merged.properties as Record<string, unknown>,\n resolved.properties,\n );\n }\n if (Array.isArray(resolved.required)) {\n allRequiredSets.push(new Set(resolved.required as string[]));\n }\n }\n // Required = intersection of all branches\n if (allRequiredSets.length > 0) {\n let intersection = allRequiredSets[0];\n for (let i = 1; i < allRequiredSets.length; i++) {\n intersection = new Set(\n [...intersection].filter((x) => allRequiredSets[i].has(x)),\n );\n }\n merged.required = [...intersection];\n } else {\n merged.required = [];\n }\n // Copy non-composition keys\n for (const [k, v] of Object.entries(obj)) {\n if (k !== keyword && !(k in merged)) {\n merged[k] = v;\n }\n }\n return merged;\n }\n }\n\n // Recursively process nested properties\n if (\"properties\" in obj && typeof obj.properties === \"object\" && obj.properties !== null) {\n const props = obj.properties as Record<string, unknown>;\n for (const [propName, propSchema] of Object.entries(props)) {\n props[propName] = resolveNode(\n propSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n );\n }\n }\n\n return obj;\n}\n","/**\n * JSON Schema -> Commander options mapping.\n *\n * Protocol spec: Schema-driven argument parsing\n */\n\nimport type { OptionConfig } from \"./main.js\";\nimport { EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Sentinel type marker for boolean flags. */\nconst BOOLEAN_FLAG = Symbol(\"BOOLEAN_FLAG\");\n\ntype TypeResult = \"string\" | \"int\" | \"float\" | typeof BOOLEAN_FLAG | \"file\";\n\n/**\n * Map JSON Schema type to a type identifier.\n */\nexport function mapType(propName: string, propSchema: Record<string, unknown>): TypeResult {\n const schemaType = propSchema.type as string | undefined;\n\n // Check file convention\n if (\n schemaType === \"string\" &&\n (propName.endsWith(\"_file\") || propSchema[\"x-cli-file\"] === true)\n ) {\n return \"file\";\n }\n\n const typeMap: Record<string, TypeResult> = {\n string: \"string\",\n integer: \"int\",\n number: \"float\",\n boolean: BOOLEAN_FLAG,\n object: \"string\",\n array: \"string\",\n };\n\n if (!schemaType) {\n return \"string\";\n }\n\n return typeMap[schemaType] ?? \"string\";\n}\n\n/**\n * Extract help text from schema property, preferring x-llm-description.\n */\nexport function extractHelp(propSchema: Record<string, unknown>, maxLength = 1000): string | undefined {\n let text = propSchema[\"x-llm-description\"] as string | undefined;\n if (!text) {\n text = propSchema.description as string | undefined;\n }\n if (!text) {\n return undefined;\n }\n if (maxLength > 0 && text.length > maxLength) {\n return text.slice(0, maxLength - 3) + \"...\";\n }\n return text;\n}\n\n// ---------------------------------------------------------------------------\n// schemaToCliOptions\n// ---------------------------------------------------------------------------\n\n/** Reserved CLI option names that cannot be used by schema properties. */\nconst RESERVED_NAMES = new Set([\"input\", \"yes\", \"large_input\", \"format\", \"sandbox\"]);\n\n/**\n * Convert a JSON Schema `properties` object into an array of\n * Commander option configurations.\n */\nexport function schemaToCliOptions(\n schema: Record<string, unknown>,\n maxHelpLength = 1000,\n): OptionConfig[] {\n const properties = (schema.properties ?? {}) as Record<\n string,\n Record<string, unknown>\n >;\n const requiredList = (schema.required ?? []) as string[];\n const options: OptionConfig[] = [];\n const flagNames: Record<string, string> = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const flagName = \"--\" + propName.replace(/_/g, \"-\");\n\n // Collision detection\n if (flagName in flagNames) {\n process.stderr.write(\n `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n flagNames[flagName] = propName;\n\n // Reserved name check\n if (RESERVED_NAMES.has(propName)) {\n process.stderr.write(\n `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n const typeResult = mapType(propName, propSchema);\n const isRequired = requiredList.includes(propName);\n const helpBase = extractHelp(propSchema, maxHelpLength);\n const helpText = isRequired\n ? (helpBase ? helpBase + \" \" : \"\") + \"[required]\"\n : helpBase ?? \"\";\n const defaultValue = propSchema.default as unknown;\n\n if (typeResult === BOOLEAN_FLAG) {\n // Boolean flag pair: --flag/--no-flag\n const flagBase = propName.replace(/_/g, \"-\");\n const defaultVal = (propSchema.default as boolean) ?? false;\n options.push({\n name: propName,\n flags: `--${flagBase}, --no-${flagBase}`,\n description: helpText,\n defaultValue: defaultVal,\n required: false,\n isBooleanFlag: true,\n });\n } else if (\"enum\" in propSchema && Array.isArray(propSchema.enum)) {\n const enumValues = propSchema.enum as unknown[];\n if (enumValues.length === 0) {\n // Empty enum — fall back to plain string option\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue,\n required: false,\n });\n } else {\n const stringValues = enumValues.map(String);\n const enumOriginalTypes: Record<string, string> = {};\n for (const v of enumValues) {\n if (typeof v === \"number\" && Number.isInteger(v)) {\n enumOriginalTypes[String(v)] = \"int\";\n } else if (typeof v === \"number\") {\n enumOriginalTypes[String(v)] = \"float\";\n } else if (typeof v === \"boolean\") {\n enumOriginalTypes[String(v)] = \"bool\";\n }\n }\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue:\n defaultValue !== undefined ? String(defaultValue) : undefined,\n required: false,\n choices: stringValues,\n enumOriginalTypes:\n Object.keys(enumOriginalTypes).length > 0\n ? enumOriginalTypes\n : undefined,\n });\n }\n } else {\n // Standard option\n let parseArg: ((value: string) => unknown) | undefined;\n if (typeResult === \"int\") {\n parseArg = (v: string) => {\n const n = parseInt(v, 10);\n if (isNaN(n)) throw new Error(`Invalid integer: ${v}`);\n return n;\n };\n } else if (typeResult === \"float\") {\n parseArg = (v: string) => {\n const n = parseFloat(v);\n if (isNaN(n)) throw new Error(`Invalid number: ${v}`);\n return n;\n };\n }\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue,\n required: false,\n parseArg,\n });\n }\n }\n\n return options;\n}\n","/**\n * Interactive approval prompts with timeout.\n *\n * Protocol spec: Approval workflow\n */\n\nimport * as readline from \"node:readline\";\nimport type { ModuleDescriptor } from \"./cli.js\";\nimport { ApprovalTimeoutError, EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get an annotation value from either a dict or an object.\n */\nfunction getAnnotation(\n annotations: unknown,\n key: string,\n defaultValue: unknown = undefined,\n): unknown {\n if (!annotations || typeof annotations !== \"object\") return defaultValue;\n const ann = annotations as Record<string, unknown>;\n return key in ann ? ann[key] : defaultValue;\n}\n\n// ---------------------------------------------------------------------------\n// checkApproval\n// ---------------------------------------------------------------------------\n\n/**\n * Check if module requires approval and handle accordingly.\n * Returns normally if approved (or approval not required).\n * Calls process.exit(46) if denied/timed out/non-TTY.\n */\nexport async function checkApproval(\n moduleDef: ModuleDescriptor,\n autoApprove: boolean,\n): Promise<void> {\n const annotations = moduleDef.annotations;\n\n // Check if approval is required\n let requiresApproval: boolean;\n if (moduleDef.requiresApproval !== undefined) {\n requiresApproval = moduleDef.requiresApproval;\n } else if (annotations) {\n requiresApproval = getAnnotation(annotations, \"requires_approval\", false) === true;\n } else {\n return; // No annotations, no approval needed\n }\n\n if (!requiresApproval) {\n return;\n }\n\n const moduleId = moduleDef.id;\n\n // Bypass: autoApprove flag (highest priority)\n if (autoApprove) {\n return;\n }\n\n // Bypass: APCORE_CLI_AUTO_APPROVE env var\n const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? \"\";\n if (envVal === \"1\") {\n return;\n }\n if (envVal !== \"\" && envVal !== \"1\") {\n process.stderr.write(\n `Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.\\n`,\n );\n }\n\n // Non-TTY check\n if (!process.stdin.isTTY) {\n process.stderr.write(\n `Error: Module '${moduleId}' requires approval but no interactive ` +\n \"terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 \" +\n \"to bypass.\\n\",\n );\n process.exit(EXIT_CODES.APPROVAL_DENIED);\n }\n\n // TTY prompt\n await promptWithTimeout(moduleDef, 60);\n}\n\n/**\n * Display approval prompt with timeout.\n */\nasync function promptWithTimeout(\n moduleDef: ModuleDescriptor,\n timeout: number,\n): Promise<void> {\n // Clamp timeout\n timeout = Math.max(1, Math.min(timeout, 3600));\n\n const moduleId = moduleDef.id;\n const annotations = moduleDef.annotations;\n const message =\n (annotations\n ? (getAnnotation(annotations, \"approval_message\") as string | undefined)\n : undefined) ??\n `Module '${moduleId}' requires approval to execute.`;\n\n process.stderr.write(message + \"\\n\");\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n const answer = await Promise.race([\n new Promise<string>((resolve) => {\n rl.question(\"Proceed? [y/N] \", (ans) => resolve(ans));\n }),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new ApprovalTimeoutError(\n `Approval prompt timed out after ${timeout} seconds.`,\n ));\n }, timeout * 1000);\n }),\n ]);\n\n // Clear the timeout — prompt resolved before timeout fired\n if (timer) clearTimeout(timer);\n\n const normalized = answer.trim().toLowerCase();\n if (normalized === \"y\" || normalized === \"yes\") {\n return;\n }\n\n process.stderr.write(\"Error: Approval denied.\\n\");\n process.exit(EXIT_CODES.APPROVAL_DENIED);\n } catch (err) {\n if (timer) clearTimeout(timer);\n if (err instanceof ApprovalTimeoutError) {\n process.stderr.write(\n `Error: Approval prompt timed out after ${timeout} seconds.\\n`,\n );\n process.exit(EXIT_CODES.APPROVAL_TIMEOUT);\n }\n throw err;\n } finally {\n rl.close();\n }\n}\n","/**\n * TTY-adaptive output formatting (table/json).\n *\n * Protocol spec: Output formatting\n */\n\nimport type { ModuleDescriptor } from \"./cli.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve output format with TTY-adaptive default.\n */\nexport function resolveFormat(explicitFormat?: string): string {\n if (explicitFormat !== undefined) {\n return explicitFormat;\n }\n return process.stdout.isTTY ? \"table\" : \"json\";\n}\n\n/**\n * Truncate text to maxLength, appending '...' if needed.\n */\nexport function truncate(text: string, maxLength = 80): string {\n if (text.length <= maxLength) {\n return text;\n }\n return text.slice(0, maxLength - 3) + \"...\";\n}\n\n/**\n * Render a simple plain-text table with column headers.\n */\nfunction formatTable(\n headers: string[],\n rows: string[][],\n): string {\n // Calculate column widths\n const colWidths = headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const sep = colWidths.map((w) => \"-\".repeat(w)).join(\" \");\n const headerLine = headers\n .map((h, i) => h.padEnd(colWidths[i]))\n .join(\" \");\n const dataLines = rows.map((row) =>\n row.map((cell, i) => (cell ?? \"\").padEnd(colWidths[i])).join(\" \"),\n );\n\n return [headerLine, sep, ...dataLines].join(\"\\n\") + \"\\n\";\n}\n\n// ---------------------------------------------------------------------------\n// formatModuleList\n// ---------------------------------------------------------------------------\n\n/**\n * Format and print a list of modules.\n */\nexport function formatModuleList(\n modules: ModuleDescriptor[],\n format: string,\n filterTags?: string[],\n): void {\n if (format === \"table\") {\n if (modules.length === 0 && filterTags && filterTags.length > 0) {\n process.stdout.write(\n `No modules found matching tags: ${filterTags.join(\", \")}.\\n`,\n );\n return;\n }\n if (modules.length === 0) {\n process.stdout.write(\"No modules found.\\n\");\n return;\n }\n\n const headers = [\"ID\", \"Description\", \"Tags\"];\n const rows = modules.map((m) => [\n m.id,\n truncate(m.description, 80),\n (m.tags ?? []).join(\", \"),\n ]);\n process.stdout.write(formatTable(headers, rows));\n } else if (format === \"json\") {\n const result = modules.map((m) => ({\n id: m.id,\n description: m.description,\n tags: m.tags ?? [],\n }));\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// formatModuleDetail\n// ---------------------------------------------------------------------------\n\n/**\n * Convert annotations to a plain dict, filtering out falsy/default values.\n */\nfunction annotationsToDict(\n annotations: unknown,\n): Record<string, unknown> | null {\n if (!annotations) return null;\n if (typeof annotations !== \"object\" || Array.isArray(annotations)) return null;\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(annotations as Record<string, unknown>)) {\n if (v !== null && v !== undefined && v !== false && v !== 0 && !(Array.isArray(v) && v.length === 0)) {\n result[k] = v;\n }\n }\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Format and print full module metadata.\n */\nexport function formatModuleDetail(\n moduleDef: ModuleDescriptor,\n format: string,\n): void {\n if (format === \"table\") {\n process.stdout.write(`\\nModule: ${moduleDef.id}\\n`);\n process.stdout.write(`\\nDescription:\\n ${moduleDef.description}\\n`);\n\n if (moduleDef.inputSchema && Object.keys(moduleDef.inputSchema).length > 0) {\n process.stdout.write(\"\\nInput Schema:\\n\");\n process.stdout.write(JSON.stringify(moduleDef.inputSchema, null, 2) + \"\\n\");\n }\n\n if (moduleDef.outputSchema && Object.keys(moduleDef.outputSchema).length > 0) {\n process.stdout.write(\"\\nOutput Schema:\\n\");\n process.stdout.write(JSON.stringify(moduleDef.outputSchema, null, 2) + \"\\n\");\n }\n\n const annDict = annotationsToDict(\n moduleDef.annotations,\n );\n if (annDict) {\n process.stdout.write(\"\\nAnnotations:\\n\");\n for (const [k, v] of Object.entries(annDict)) {\n process.stdout.write(` ${k}: ${v}\\n`);\n }\n }\n\n // Extension metadata (x- prefixed)\n const metadata = moduleDef.metadata;\n if (metadata) {\n const xFields: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(metadata)) {\n if (k.startsWith(\"x-\") || k.startsWith(\"x_\")) {\n xFields[k] = v;\n }\n }\n if (Object.keys(xFields).length > 0) {\n process.stdout.write(\"\\nExtension Metadata:\\n\");\n for (const [k, v] of Object.entries(xFields)) {\n process.stdout.write(` ${k}: ${v}\\n`);\n }\n }\n }\n\n const tags = moduleDef.tags ?? [];\n if (tags.length > 0) {\n process.stdout.write(`\\nTags: ${tags.join(\", \")}\\n`);\n }\n } else if (format === \"json\") {\n const result: Record<string, unknown> = {\n id: moduleDef.id,\n description: moduleDef.description,\n };\n if (moduleDef.inputSchema) result.input_schema = moduleDef.inputSchema;\n if (moduleDef.outputSchema) result.output_schema = moduleDef.outputSchema;\n\n const annDict = annotationsToDict(\n moduleDef.annotations,\n );\n if (annDict) result.annotations = annDict;\n\n const tags = moduleDef.tags ?? [];\n if (tags.length > 0) result.tags = tags;\n\n // Extension metadata\n const metadata = moduleDef.metadata;\n if (metadata) {\n for (const [k, v] of Object.entries(metadata)) {\n if (k.startsWith(\"x-\") || k.startsWith(\"x_\")) {\n result[k] = v;\n }\n }\n }\n\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// formatExecResult\n// ---------------------------------------------------------------------------\n\n/**\n * Format and print module execution result.\n */\nexport function formatExecResult(\n result: unknown,\n format?: string,\n): void {\n if (result === null || result === undefined) {\n return;\n }\n const effective = resolveFormat(format);\n if (\n effective === \"table\" &&\n typeof result === \"object\" &&\n !Array.isArray(result)\n ) {\n // Key-value table\n const entries = Object.entries(result as Record<string, unknown>);\n const headers = [\"Key\", \"Value\"];\n const rows = entries.map(([k, v]) => [String(k), String(v)]);\n process.stdout.write(formatTable(headers, rows));\n } else if (typeof result === \"object\") {\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n } else if (typeof result === \"string\") {\n process.stdout.write(result + \"\\n\");\n } else {\n process.stdout.write(String(result) + \"\\n\");\n }\n}\n","/**\n * Simple structured logger respecting logging.level config.\n */\n\nconst LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 } as const;\ntype LogLevel = keyof typeof LEVELS;\n\nlet currentLevel: LogLevel = \"WARNING\";\n\nexport function setLogLevel(level: string): void {\n const upper = level.toUpperCase();\n if (upper in LEVELS) {\n currentLevel = upper as LogLevel;\n }\n}\n\nexport function getLogLevel(): LogLevel {\n return currentLevel;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVELS[level] >= LEVELS[currentLevel];\n}\n\nexport function debug(message: string): void {\n if (shouldLog(\"DEBUG\")) process.stderr.write(`DEBUG: ${message}\\n`);\n}\n\nexport function info(message: string): void {\n if (shouldLog(\"INFO\")) process.stderr.write(`INFO: ${message}\\n`);\n}\n\nexport function warn(message: string): void {\n if (shouldLog(\"WARNING\")) process.stderr.write(`WARNING: ${message}\\n`);\n}\n\nexport function error(message: string): void {\n if (shouldLog(\"ERROR\")) process.stderr.write(`ERROR: ${message}\\n`);\n}\n","/**\n * Init command — scaffold new apcore modules (Phase 1).\n */\n\nimport { Command } from \"commander\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst DECORATOR_TEMPLATE = `\\\nimport { module } from \"apcore-js\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport const {varName} = module({\n id: \"{moduleId}\",\n description: \"{description}\",\n inputSchema: Type.Object({}),\n outputSchema: Type.Object({ status: Type.String() }),\n execute: (_inputs) => {\n // TODO: implement\n return { status: \"ok\" };\n },\n});\n`;\n\nconst CONVENTION_TEMPLATE = `\\\n/**\n * {description}\n */\n{cliGroupLine}\nexport function {funcName}(): Record<string, unknown> {\n // TODO: implement\n return { status: \"ok\" };\n}\n`;\n\nconst BINDING_TEMPLATE = `\\\nbindings:\n - module_id: \"{moduleId}\"\n target: \"{target}\"\n description: \"{description}\"\n auto_schema: true\n`;\n\n/**\n * Simple template rendering: replaces {key} with values from the context.\n */\nfunction renderTemplate(template: string, context: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(context)) {\n // Replace all occurrences of {key} with the value\n result = result.split(`{${key}}`).join(value);\n }\n return result;\n}\n\n/**\n * Register the init command group on the CLI program.\n */\nexport function registerInitCommand(cli: Command): void {\n const initGroup = cli.command(\"init\").description(\"Scaffold new apcore modules.\");\n\n initGroup\n .command(\"module <module-id>\")\n .description(\"Create a new module from a template.\\n\\nMODULE_ID is the module identifier (e.g., ops.deploy, user.create).\")\n .option(\n \"--style <style>\",\n \"Module style: decorator (@module), convention (plain function), or binding (YAML).\",\n \"convention\",\n )\n .option(\"--dir <path>\", \"Output directory. Default: extensions/ or commands/.\")\n .option(\"-d, --description <text>\", \"Module description.\", \"TODO: add description\")\n .action((moduleId: string, opts: { style: string; dir?: string; description: string }) => {\n // Parse module_id into parts\n const lastDot = moduleId.lastIndexOf(\".\");\n const prefix = lastDot >= 0 ? moduleId.substring(0, lastDot) : moduleId;\n const funcName = lastDot >= 0 ? moduleId.substring(lastDot + 1) : moduleId;\n\n const style = opts.style;\n const description = opts.description;\n\n // Validate --dir to prevent path traversal\n const dir = opts.dir ?? (style === \"decorator\" ? \"extensions\" : style === \"binding\" ? \"bindings\" : \"commands\");\n if (dir.split(path.sep).includes(\"..\") || dir.split(\"/\").includes(\"..\")) {\n process.stderr.write(`Error: Output directory must not contain '..' path components.\\n`);\n process.exit(2);\n }\n\n switch (style) {\n case \"decorator\":\n createDecoratorModule(moduleId, prefix, funcName, description, dir);\n break;\n case \"convention\":\n createConventionModule(moduleId, prefix, funcName, description, dir);\n break;\n case \"binding\":\n createBindingModule(moduleId, prefix, funcName, description, dir);\n break;\n default:\n process.stderr.write(`Error: Unknown style '${style}'\\n`);\n process.exit(2);\n }\n });\n}\n\nfunction createDecoratorModule(\n moduleId: string,\n _prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n fs.mkdirSync(outputDir, { recursive: true });\n const filename = moduleId.replace(/\\./g, \"_\") + \".ts\";\n const filepath = path.join(outputDir, filename);\n\n const varName = funcName + \"Module\";\n const content = renderTemplate(DECORATOR_TEMPLATE, {\n moduleId,\n varName,\n funcName,\n description,\n });\n fs.writeFileSync(filepath, content);\n process.stdout.write(`Created ${filepath}\\n`);\n}\n\nfunction createConventionModule(\n moduleId: string,\n prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n // If prefix has dots, create subdirectories\n const prefixParts = prefix.split(\".\");\n const dirPath = prefixParts.length > 1\n ? path.join(outputDir, ...prefixParts.slice(0, -1))\n : outputDir;\n fs.mkdirSync(dirPath, { recursive: true });\n\n let filename: string;\n if (prefixParts.length > 1) {\n filename = prefixParts[prefixParts.length - 1] + \".ts\";\n } else {\n filename = prefix + \".ts\";\n }\n // If the file would be the same as the function name, use prefix as filename\n if (prefix === funcName) {\n filename = prefix + \".ts\";\n }\n const filepath = path.join(dirPath, filename);\n\n const cliGroupLine = moduleId.includes(\".\")\n ? `export const CLI_GROUP = \"${prefixParts[0]}\";\\n`\n : \"\";\n\n const content = renderTemplate(CONVENTION_TEMPLATE, {\n funcName,\n description,\n cliGroupLine,\n });\n fs.writeFileSync(filepath, content);\n process.stdout.write(`Created ${filepath}\\n`);\n}\n\nfunction createBindingModule(\n moduleId: string,\n prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n fs.mkdirSync(outputDir, { recursive: true });\n\n const yamlFile = path.join(outputDir, moduleId.replace(/\\./g, \"_\") + \".binding.yaml\");\n const target = `commands.${prefix}:${funcName}`;\n\n const yamlContent = renderTemplate(BINDING_TEMPLATE, {\n moduleId,\n target,\n description,\n });\n fs.writeFileSync(yamlFile, yamlContent);\n process.stdout.write(`Created ${yamlFile}\\n`);\n\n // Also create the target function file\n const baseSrc = \"commands\";\n fs.mkdirSync(baseSrc, { recursive: true });\n const srcFile = path.join(baseSrc, prefix.replace(/\\./g, \"_\") + \".ts\");\n if (!fs.existsSync(srcFile)) {\n const srcContent =\n `export function ${funcName}(): Record<string, unknown> {\\n` +\n ` /** ${description} */\\n` +\n \" // TODO: implement\\n\" +\n ' return { status: \"ok\" };\\n' +\n \"}\\n\";\n fs.writeFileSync(srcFile, srcContent);\n process.stdout.write(`Created ${srcFile}\\n`);\n }\n}\n","/**\n * Display overlay helpers — shared resolution logic for CLI surfaces.\n */\n\nimport type { ModuleDescriptor } from \"./cli.js\";\n\n/**\n * Extract resolved display overlay from a ModuleDescriptor's metadata.\n */\nexport function getDisplay(descriptor: ModuleDescriptor): Record<string, unknown> {\n const metadata = descriptor.metadata ?? {};\n const display = (metadata as Record<string, unknown>).display;\n if (display && typeof display === \"object\" && !Array.isArray(display)) {\n return display as Record<string, unknown>;\n }\n return {};\n}\n\n/**\n * Return [displayName, description, tags] resolved from the display overlay.\n *\n * Falls back to scanner-provided values when no overlay is present.\n */\nexport function getCliDisplayFields(descriptor: ModuleDescriptor): [string, string, string[]] {\n const display = getDisplay(descriptor);\n const cli = (display.cli && typeof display.cli === \"object\" && !Array.isArray(display.cli))\n ? (display.cli as Record<string, unknown>)\n : {};\n const name = (cli.alias as string | undefined)\n ?? (display.alias as string | undefined)\n ?? descriptor.id;\n const desc = (cli.description as string | undefined) ?? descriptor.description;\n const tags = (display.tags as string[] | undefined) ?? descriptor.tags ?? [];\n return [name, desc, tags];\n}\n"],"mappings":"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","names":["fileURLToPath","path","path","__dirname","fileURLToPath"]}
|
|
1
|
+
{"version":3,"sources":["../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js","../../src/errors.ts","../../bin/apcore-cli.ts","../../src/main.ts","../../src/ref-resolver.ts","../../src/schema-parser.ts","../../src/approval.ts","../../src/output.ts","../../src/logger.ts","../../src/init-cmd.ts","../../src/display-helpers.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","/**\n * Error classes and exit code mapping for apcore-cli.\n *\n * Protocol spec: Error handling & exit codes\n */\n\n// ---------------------------------------------------------------------------\n// Error classes\n// ---------------------------------------------------------------------------\n\n/** Thrown when the user does not approve module execution within the timeout. */\nexport class ApprovalTimeoutError extends Error {\n constructor(message = \"Approval timed out\") {\n super(message);\n this.name = \"ApprovalTimeoutError\";\n }\n}\n\n/** Thrown when API key authentication fails or is missing. */\nexport class AuthenticationError extends Error {\n constructor(message = \"Authentication failed\") {\n super(message);\n this.name = \"AuthenticationError\";\n }\n}\n\n/** Thrown when encrypted config cannot be decrypted. */\nexport class ConfigDecryptionError extends Error {\n constructor(message = \"Config decryption failed\") {\n super(message);\n this.name = \"ConfigDecryptionError\";\n }\n}\n\n/** Thrown when module execution fails. */\nexport class ModuleExecutionError extends Error {\n constructor(message = \"Module execution failed\") {\n super(message);\n this.name = \"ModuleExecutionError\";\n }\n}\n\n/** Thrown when approval is denied by the user. */\nexport class ApprovalDeniedError extends Error {\n constructor(message = \"Approval denied\") {\n super(message);\n this.name = \"ApprovalDeniedError\";\n }\n}\n\n/** Thrown when schema validation fails. */\nexport class SchemaValidationError extends Error {\n constructor(message = \"Schema validation failed\") {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\n/** Thrown when a module is not found. */\nexport class ModuleNotFoundError extends Error {\n constructor(message = \"Module not found\") {\n super(message);\n this.name = \"ModuleNotFoundError\";\n }\n}\n\n// ---------------------------------------------------------------------------\n// Exit code map\n// ---------------------------------------------------------------------------\n\nexport const EXIT_CODES = {\n SUCCESS: 0,\n MODULE_EXECUTE_ERROR: 1,\n MODULE_TIMEOUT: 1,\n INVALID_CLI_INPUT: 2,\n MODULE_NOT_FOUND: 44,\n MODULE_LOAD_ERROR: 44,\n MODULE_DISABLED: 44,\n SCHEMA_VALIDATION_ERROR: 45,\n APPROVAL_DENIED: 46,\n APPROVAL_TIMEOUT: 46,\n CONFIG_NOT_FOUND: 47,\n CONFIG_INVALID: 47,\n SCHEMA_CIRCULAR_REF: 48,\n ACL_DENIED: 77,\n KEYBOARD_INTERRUPT: 130,\n} as const;\n\nexport type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Map a caught error to the appropriate process exit code.\n * Also checks for apcore error codes on the error object.\n */\nexport function exitCodeForError(error: unknown): ExitCode {\n if (error instanceof ApprovalTimeoutError) {\n return EXIT_CODES.APPROVAL_TIMEOUT;\n }\n if (error instanceof ApprovalDeniedError) {\n return EXIT_CODES.APPROVAL_DENIED;\n }\n if (error instanceof AuthenticationError) {\n return EXIT_CODES.ACL_DENIED;\n }\n if (error instanceof ConfigDecryptionError) {\n return EXIT_CODES.CONFIG_INVALID;\n }\n if (error instanceof SchemaValidationError) {\n return EXIT_CODES.SCHEMA_VALIDATION_ERROR;\n }\n if (error instanceof ModuleNotFoundError) {\n return EXIT_CODES.MODULE_NOT_FOUND;\n }\n if (error instanceof ModuleExecutionError) {\n return EXIT_CODES.MODULE_EXECUTE_ERROR;\n }\n\n // Check for apcore error codes on the error object\n if (error instanceof Error) {\n const code = (error as unknown as Record<string, unknown>).code as string | undefined;\n const codeMap: Record<string, ExitCode> = {\n MODULE_NOT_FOUND: EXIT_CODES.MODULE_NOT_FOUND,\n MODULE_LOAD_ERROR: EXIT_CODES.MODULE_LOAD_ERROR,\n MODULE_DISABLED: EXIT_CODES.MODULE_DISABLED,\n SCHEMA_VALIDATION_ERROR: EXIT_CODES.SCHEMA_VALIDATION_ERROR,\n SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,\n APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,\n APPROVAL_TIMEOUT: EXIT_CODES.APPROVAL_TIMEOUT,\n CONFIG_NOT_FOUND: EXIT_CODES.CONFIG_NOT_FOUND,\n CONFIG_INVALID: EXIT_CODES.CONFIG_INVALID,\n MODULE_EXECUTE_ERROR: EXIT_CODES.MODULE_EXECUTE_ERROR,\n MODULE_TIMEOUT: EXIT_CODES.MODULE_TIMEOUT,\n ACL_DENIED: EXIT_CODES.ACL_DENIED,\n };\n if (code && code in codeMap) {\n return codeMap[code];\n }\n }\n\n return EXIT_CODES.MODULE_EXECUTE_ERROR;\n}\n","#!/usr/bin/env node\n/**\n * apcore-cli — Shebang entry point.\n *\n * This file is the bin target. It bootstraps the CLI and delegates to main().\n */\n\nimport { main } from \"../src/main.js\";\n\nmain(\"apcore-cli\");\n","/**\n * CLI entry point — createCli / main equivalents.\n *\n * Protocol spec: CLI bootstrapping & command registration\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport * as path from \"node:path\";\nimport { Command, CommanderError, Option } from \"commander\";\nimport { EXIT_CODES, exitCodeForError } from \"./errors.js\";\nimport { resolveRefs } from \"./ref-resolver.js\";\nimport { schemaToCliOptions } from \"./schema-parser.js\";\nimport { checkApproval } from \"./approval.js\";\nimport { formatExecResult } from \"./output.js\";\nimport { setLogLevel } from \"./logger.js\";\nimport { registerInitCommand } from \"./init-cmd.js\";\nimport { getDisplay } from \"./display-helpers.js\";\nimport type { Executor, ModuleDescriptor } from \"./cli.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/** Whether --verbose was passed (controls help detail level). */\nexport let verboseHelp = false;\n\n/** Set the verbose help flag. When false, built-in options are hidden from help. */\nexport function setVerboseHelp(verbose: boolean): void {\n verboseHelp = verbose;\n}\n\n/** Base URL for online documentation. Null means no docs link shown. */\nexport let docsUrl: string | null = null;\n\n/**\n * Set the base URL for online documentation links shown in help and man pages.\n * Pass null to disable. Command-level help appends `/commands/{name}` automatically.\n *\n * @example setDocsUrl(\"https://docs.apcore.dev/cli\");\n */\nexport function setDocsUrl(url: string | null): void {\n docsUrl = url;\n}\n\n/** Check if --verbose is present in process.argv (pre-parse, before Commander). */\nfunction hasVerboseFlag(): boolean {\n return process.argv.includes(\"--verbose\");\n}\n\nlet VERSION = \"0.0.0\";\ntry {\n const pkg = JSON.parse(readFileSync(path.resolve(__dirname, \"../package.json\"), \"utf-8\"));\n VERSION = pkg.version;\n} catch {\n // Bundled environments (e.g., Bun compile) may not have package.json accessible\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Configuration for a single Commander option derived from a JSON Schema property. */\nexport interface OptionConfig {\n /** The property name from the schema. */\n name: string;\n /** Commander flags string (e.g. \"--my-flag <value>\" or \"--flag, --no-flag\"). */\n flags: string;\n /** Help text for the option. */\n description: string;\n /** Default value. */\n defaultValue?: unknown;\n /** Whether the field is required (for display only). */\n required: boolean;\n /** Enum choices (string values). */\n choices?: string[];\n /** Whether this is a boolean flag pair (--flag/--no-flag). */\n isBooleanFlag?: boolean;\n /** Maps string enum value → original type name (\"int\", \"float\", \"bool\"). */\n enumOriginalTypes?: Record<string, string>;\n /** Parser function for Commander (e.g. parseInt, parseFloat). */\n parseArg?: (value: string) => unknown;\n}\n\n// ---------------------------------------------------------------------------\n// createCli\n// ---------------------------------------------------------------------------\n\n/**\n * Build and return the top-level Commander program.\n *\n * @param extensionsDir Path to the extensions directory (default: ./extensions)\n * @param progName Program name shown in help (default: apcore-cli)\n */\nexport function createCli(\n extensionsDir?: string,\n progName?: string,\n verbose = false,\n): Command {\n verboseHelp = verbose;\n // Resolve program name\n const resolvedProgName = progName ?? path.basename(process.argv[1] ?? \"apcore-cli\") ?? \"apcore-cli\";\n\n // Resolve log level\n const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? \"WARNING\";\n setLogLevel(cliLogLevel);\n\n const program = new Command(resolvedProgName)\n .exitOverride()\n .version(VERSION, \"--version\", `Show ${resolvedProgName} version`)\n .description(\"apcore CLI — execute apcore modules from the command line\")\n .option(\"--extensions-dir <path>\", \"Path to extensions directory\")\n .option(\"--commands-dir <path>\", \"Path to convention-based commands directory\")\n .option(\"--binding <path>\", \"Path to binding.yaml for display overlay\")\n .option(\"--log-level <level>\", \"Logging level (DEBUG|INFO|WARNING|ERROR)\", \"WARNING\")\n .option(\"--verbose\", \"Show all options in help output (including built-in apcore options)\");\n\n // NOTE: Full registry/executor wiring requires apcore-js to be available.\n // For now, extensions-dir is accepted but not wired to a real registry.\n const resolvedExtDir = extensionsDir\n ?? process.env.APCORE_EXTENSIONS_ROOT\n ?? \"./extensions\";\n void resolvedExtDir; // Will be used when apcore-js registry is wired\n\n // Register init command for scaffolding\n registerInitCommand(program);\n\n // Hook to apply optional toolkit integration before command execution.\n // Commander actions are async, so we can set up toolkit state lazily.\n program.hook(\"preAction\", async (thisCommand) => {\n const opts = thisCommand.opts();\n const commandsDir = opts.commandsDir as string | undefined;\n const bindingPath = opts.binding as string | undefined;\n await applyToolkitIntegration(commandsDir, bindingPath);\n });\n\n return program;\n}\n\n// ---------------------------------------------------------------------------\n// applyToolkitIntegration\n// ---------------------------------------------------------------------------\n\n/**\n * Optionally apply apcore-toolkit features (DisplayResolver, RegistryWriter).\n *\n * Uses dynamic import so the dependency remains optional — if apcore-toolkit\n * is not installed, a warning is printed and the CLI continues without it.\n */\nexport async function applyToolkitIntegration(\n commandsDir?: string,\n bindingPath?: string,\n): Promise<void> {\n if (!commandsDir && !bindingPath) {\n return;\n }\n\n try {\n // Use a variable to prevent TypeScript from resolving the module at build time\n const toolkitModule = \"apcore-toolkit\";\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const toolkit = await import(/* @vite-ignore */ toolkitModule);\n\n // ConventionScanner is not yet available in the TypeScript toolkit\n if (commandsDir) {\n console.warn(\"Convention scanning not yet available in TypeScript toolkit\");\n }\n\n // DisplayResolver for binding overlay\n if (bindingPath) {\n const resolver = new toolkit.DisplayResolver();\n // NOTE: Full integration requires registered modules from apcore-js.\n // For now, the resolver is instantiated and ready for when modules\n // are available through the registry.\n void resolver;\n }\n } catch {\n // apcore-toolkit not installed — graceful fallback\n console.warn(\"apcore-toolkit not installed — toolkit features unavailable\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// main\n// ---------------------------------------------------------------------------\n\n/**\n * Parse argv and run the CLI. Handles top-level error catching and exit codes.\n */\nexport function main(progName?: string): void {\n verboseHelp = hasVerboseFlag();\n const program = createCli(undefined, progName, verboseHelp);\n\n try {\n program.parse(process.argv);\n } catch (error: unknown) {\n if (error instanceof CommanderError) {\n // Commander already printed the error message\n process.exit(error.exitCode);\n }\n const code = exitCodeForError(error);\n if (error instanceof Error) {\n process.stderr.write(`Error: ${error.message}\\n`);\n }\n process.exit(code);\n }\n}\n\n// ---------------------------------------------------------------------------\n// buildModuleCommand\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Commander Command for a single apcore module.\n */\nexport function buildModuleCommand(\n moduleDef: ModuleDescriptor,\n executor: Executor,\n helpTextMaxLength = 1000,\n cmdName?: string,\n verbose = verboseHelp,\n): Command {\n const moduleId = moduleDef.id;\n let resolvedSchema: Record<string, unknown> = {};\n let schemaOptions: OptionConfig[] = [];\n\n // Resolve display overlay fields\n const display = getDisplay(moduleDef);\n const cliDisplay = (display.cli && typeof display.cli === \"object\" && !Array.isArray(display.cli))\n ? (display.cli as Record<string, unknown>)\n : {};\n const effectiveCmdName: string = cmdName ?? (cliDisplay.alias as string | undefined) ?? moduleId;\n const cmdHelp: string = (cliDisplay.description as string | undefined) ?? moduleDef.description;\n\n // Resolve schema\n const inputSchema = moduleDef.inputSchema;\n if (inputSchema && typeof inputSchema === \"object\" && inputSchema.properties) {\n try {\n resolvedSchema = resolveRefs(inputSchema, 32, moduleId);\n } catch {\n resolvedSchema = inputSchema;\n }\n schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);\n }\n\n const cmd = new Command(effectiveCmdName).description(cmdHelp);\n\n // Built-in options (hidden unless --verbose)\n const inputOpt = new Option(\"--input <source>\", \"Read JSON input from a file path, or use '-' to read from stdin pipe\");\n const yesOpt = new Option(\"-y, --yes\", \"Skip interactive approval prompts (for scripts and CI)\").default(false);\n const largeInputOpt = new Option(\"--large-input\", \"Allow stdin input larger than 10MB (default limit protects against accidental pipes)\").default(false);\n const formatOpt = new Option(\"--format <format>\", \"Set output format: 'json' for machine-readable, 'table' for human-readable\");\n // --sandbox is always hidden (not yet implemented)\n const sandboxOpt = new Option(\"--sandbox\", \"Run module in an isolated subprocess with restricted filesystem and env access\").default(false).hideHelp();\n\n if (!verbose) {\n inputOpt.hideHelp();\n yesOpt.hideHelp();\n largeInputOpt.hideHelp();\n formatOpt.hideHelp();\n }\n\n cmd.addOption(inputOpt);\n cmd.addOption(yesOpt);\n cmd.addOption(largeInputOpt);\n cmd.addOption(formatOpt);\n cmd.addOption(sandboxOpt);\n\n // Help footer: verbose hint + optional docs link\n const footerParts: string[] = [];\n if (!verbose) {\n footerParts.push(\"Use --verbose to show all options (including built-in apcore options).\");\n }\n if (docsUrl) {\n footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);\n }\n if (footerParts.length > 0) {\n cmd.addHelpText(\"after\", \"\\n\" + footerParts.join(\"\\n\") + \"\\n\");\n }\n\n // Schema-generated options\n for (const opt of schemaOptions) {\n if (opt.parseArg) {\n cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);\n } else {\n cmd.option(opt.flags, opt.description, opt.defaultValue as string | boolean | undefined);\n }\n }\n\n // Action callback\n cmd.action(async (options: Record<string, unknown>) => {\n // Pop built-in options\n const stdinFlag = options.input as string | undefined;\n const autoApprove = options.yes as boolean;\n const largeInput = options.largeInput as boolean;\n const outputFormat = options.format as string | undefined;\n const sandboxEnabled = options.sandbox as boolean;\n\n // Remove built-in keys from options to get schema kwargs\n const schemaKwargs: Record<string, unknown> = {};\n const builtinKeys = new Set([\"input\", \"yes\", \"largeInput\", \"format\", \"sandbox\", \"verbose\"]);\n for (const [k, v] of Object.entries(options)) {\n if (!builtinKeys.has(k)) {\n schemaKwargs[k] = v;\n }\n }\n\n try {\n // Collect and merge input\n const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);\n\n // Reconvert enum values\n const reconverted = reconvertEnumValues(merged, schemaOptions);\n\n // Check approval\n await checkApproval(moduleDef, autoApprove);\n\n // Execute with timing\n const { Sandbox } = await import(\"./security/index.js\");\n const sandbox = new Sandbox(sandboxEnabled);\n const startTime = performance.now();\n const result = await sandbox.execute(moduleId, reconverted, executor);\n const durationMs = Math.round(performance.now() - startTime);\n\n // Audit log (success)\n const { getAuditLogger } = await import(\"./security/audit.js\");\n const auditLogger = getAuditLogger();\n if (auditLogger) {\n auditLogger.logExecution(moduleId, reconverted, \"success\", 0, durationMs);\n }\n\n // Format output\n formatExecResult(result, outputFormat);\n } catch (err: unknown) {\n // Audit log (error)\n const { getAuditLogger } = await import(\"./security/audit.js\");\n const auditLogger = getAuditLogger();\n const code = exitCodeForError(err);\n if (auditLogger) {\n auditLogger.logExecution(moduleId, {}, \"error\", code, 0);\n }\n\n if (err instanceof Error) {\n process.stderr.write(`Error: ${err.message}\\n`);\n }\n process.exit(code);\n }\n });\n\n return cmd;\n}\n\n// ---------------------------------------------------------------------------\n// validateModuleId\n// ---------------------------------------------------------------------------\n\n/**\n * Validate that a module ID conforms to the expected format.\n * Pattern: [a-z][a-z0-9_]*(.[a-z][a-z0-9_])* — max 128 chars.\n */\nexport function validateModuleId(moduleId: string): void {\n if (moduleId.length > 128) {\n process.stderr.write(\n `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n if (!/^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {\n process.stderr.write(\n `Error: Invalid module ID format: '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n}\n\n// ---------------------------------------------------------------------------\n// collectInput\n// ---------------------------------------------------------------------------\n\n/**\n * Collect module input from stdin and/or CLI keyword arguments.\n */\nexport async function collectInput(\n stdinFlag?: string,\n cliKwargs: Record<string, unknown> = {},\n largeInput?: boolean,\n): Promise<Record<string, unknown>> {\n // Remove null/undefined values from CLI kwargs\n const cliKwargsNonNull: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(cliKwargs)) {\n if (v !== null && v !== undefined) {\n cliKwargsNonNull[k] = v;\n }\n }\n\n if (!stdinFlag) {\n return cliKwargsNonNull;\n }\n\n if (stdinFlag === \"-\") {\n const raw = await readStdin();\n const rawSize = Buffer.byteLength(raw, \"utf-8\");\n\n if (rawSize > 10_485_760 && !largeInput) {\n process.stderr.write(\n \"Error: STDIN input exceeds 10MB limit. Use --large-input to override.\\n\",\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n if (!raw) {\n return cliKwargsNonNull;\n }\n\n let stdinData: unknown;\n try {\n stdinData = JSON.parse(raw);\n } catch {\n process.stderr.write(\n \"Error: STDIN does not contain valid JSON.\\n\",\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n if (typeof stdinData !== \"object\" || stdinData === null || Array.isArray(stdinData)) {\n process.stderr.write(\n `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? \"array\" : typeof stdinData}.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n // CLI flags override STDIN for duplicate keys\n return { ...(stdinData as Record<string, unknown>), ...cliKwargsNonNull };\n }\n\n return cliKwargsNonNull;\n}\n\n/**\n * Read all data from stdin with proper cleanup.\n */\nfunction readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n const onData = (chunk: Buffer) => chunks.push(chunk);\n const onEnd = () => {\n cleanup();\n resolve(Buffer.concat(chunks).toString(\"utf-8\"));\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const cleanup = () => {\n process.stdin.removeListener(\"data\", onData);\n process.stdin.removeListener(\"end\", onEnd);\n process.stdin.removeListener(\"error\", onError);\n };\n process.stdin.on(\"data\", onData);\n process.stdin.on(\"end\", onEnd);\n process.stdin.on(\"error\", onError);\n process.stdin.resume();\n });\n}\n\n// ---------------------------------------------------------------------------\n// reconvertEnumValues\n// ---------------------------------------------------------------------------\n\n/**\n * Re-convert CLI string values back to their schema-typed equivalents\n * based on the option configs.\n */\nexport function reconvertEnumValues(\n kwargs: Record<string, unknown>,\n options: OptionConfig[],\n): Record<string, unknown> {\n const result = { ...kwargs };\n for (const opt of options) {\n if (!opt.enumOriginalTypes) continue;\n const paramName = opt.name;\n if (!(paramName in result) || result[paramName] === null || result[paramName] === undefined) {\n continue;\n }\n const strVal = String(result[paramName]);\n const origType = opt.enumOriginalTypes[strVal];\n if (origType === \"int\") {\n result[paramName] = parseInt(strVal, 10);\n } else if (origType === \"float\") {\n result[paramName] = parseFloat(strVal);\n } else if (origType === \"bool\") {\n result[paramName] = strVal.toLowerCase() === \"true\";\n }\n }\n return result;\n}\n","/**\n * JSON Schema $ref resolver.\n *\n * Protocol spec: Schema resolution & $ref handling\n */\n\nimport { EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// resolveRefs\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve all $ref references in a JSON Schema.\n * Returns a fully inlined schema with $defs/definitions removed.\n */\nexport function resolveRefs(\n schema: Record<string, unknown>,\n maxDepth = 32,\n moduleId = \"\",\n): Record<string, unknown> {\n const cloned = structuredClone(schema);\n const defs = (cloned.$defs ?? cloned.definitions ?? {}) as Record<\n string,\n unknown\n >;\n const result = resolveNode(\n cloned,\n defs,\n new Set<string>(),\n 0,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n\n // Remove definition keys\n delete result.$defs;\n delete result.definitions;\n return result;\n}\n\nfunction resolveNode(\n node: unknown,\n defs: Record<string, unknown>,\n visited: Set<string>,\n depth: number,\n maxDepth: number,\n moduleId: string,\n): unknown {\n if (typeof node !== \"object\" || node === null || Array.isArray(node)) {\n return node;\n }\n\n const obj = node as Record<string, unknown>;\n\n // Handle $ref\n if (\"$ref\" in obj) {\n const refPath = obj.$ref as string;\n\n if (depth >= maxDepth) {\n process.stderr.write(\n `Error: $ref resolution depth exceeded maximum of ${maxDepth} for module '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);\n }\n\n if (visited.has(refPath)) {\n process.stderr.write(\n `Error: Circular $ref detected in schema for module '${moduleId}' at path '${refPath}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);\n }\n\n // Parse ref target: extract key from \"#/$defs/Address\" → \"Address\"\n const parts = refPath.split(\"/\");\n const key = parts[parts.length - 1];\n\n if (!(key in defs)) {\n process.stderr.write(\n `Error: Unresolvable $ref '${refPath}' in schema for module '${moduleId}'.\\n`,\n );\n process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);\n }\n\n const newVisited = new Set(visited);\n newVisited.add(refPath);\n return resolveNode(defs[key], defs, newVisited, depth + 1, maxDepth, moduleId);\n }\n\n // Handle allOf\n if (\"allOf\" in obj && Array.isArray(obj.allOf)) {\n const merged: Record<string, unknown> = {\n properties: {},\n required: [] as string[],\n };\n for (const subSchema of obj.allOf as unknown[]) {\n const resolved = resolveNode(\n subSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n if (resolved.properties) {\n Object.assign(\n merged.properties as Record<string, unknown>,\n resolved.properties,\n );\n }\n if (Array.isArray(resolved.required)) {\n (merged.required as string[]).push(...resolved.required);\n }\n }\n // Deduplicate required\n merged.required = [...new Set(merged.required as string[])];\n // Copy non-composition keys\n for (const [k, v] of Object.entries(obj)) {\n if (k !== \"allOf\" && !(k in merged)) {\n merged[k] = v;\n }\n }\n return merged;\n }\n\n // Handle anyOf / oneOf\n for (const keyword of [\"anyOf\", \"oneOf\"]) {\n if (keyword in obj && Array.isArray(obj[keyword])) {\n const merged: Record<string, unknown> = {\n properties: {},\n required: [] as string[],\n };\n const allRequiredSets: Set<string>[] = [];\n for (const subSchema of obj[keyword] as unknown[]) {\n const resolved = resolveNode(\n subSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n ) as Record<string, unknown>;\n if (resolved.properties) {\n Object.assign(\n merged.properties as Record<string, unknown>,\n resolved.properties,\n );\n }\n if (Array.isArray(resolved.required)) {\n allRequiredSets.push(new Set(resolved.required as string[]));\n }\n }\n // Required = intersection of all branches\n if (allRequiredSets.length > 0) {\n let intersection = allRequiredSets[0];\n for (let i = 1; i < allRequiredSets.length; i++) {\n intersection = new Set(\n [...intersection].filter((x) => allRequiredSets[i].has(x)),\n );\n }\n merged.required = [...intersection];\n } else {\n merged.required = [];\n }\n // Copy non-composition keys\n for (const [k, v] of Object.entries(obj)) {\n if (k !== keyword && !(k in merged)) {\n merged[k] = v;\n }\n }\n return merged;\n }\n }\n\n // Recursively process nested properties\n if (\"properties\" in obj && typeof obj.properties === \"object\" && obj.properties !== null) {\n const props = obj.properties as Record<string, unknown>;\n for (const [propName, propSchema] of Object.entries(props)) {\n props[propName] = resolveNode(\n propSchema,\n defs,\n visited,\n depth + 1,\n maxDepth,\n moduleId,\n );\n }\n }\n\n return obj;\n}\n","/**\n * JSON Schema -> Commander options mapping.\n *\n * Protocol spec: Schema-driven argument parsing\n */\n\nimport type { OptionConfig } from \"./main.js\";\nimport { EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Sentinel type marker for boolean flags. */\nconst BOOLEAN_FLAG = Symbol(\"BOOLEAN_FLAG\");\n\ntype TypeResult = \"string\" | \"int\" | \"float\" | typeof BOOLEAN_FLAG | \"file\";\n\n/**\n * Map JSON Schema type to a type identifier.\n */\nexport function mapType(propName: string, propSchema: Record<string, unknown>): TypeResult {\n const schemaType = propSchema.type as string | undefined;\n\n // Check file convention\n if (\n schemaType === \"string\" &&\n (propName.endsWith(\"_file\") || propSchema[\"x-cli-file\"] === true)\n ) {\n return \"file\";\n }\n\n const typeMap: Record<string, TypeResult> = {\n string: \"string\",\n integer: \"int\",\n number: \"float\",\n boolean: BOOLEAN_FLAG,\n object: \"string\",\n array: \"string\",\n };\n\n if (!schemaType) {\n return \"string\";\n }\n\n return typeMap[schemaType] ?? \"string\";\n}\n\n/**\n * Extract help text from schema property, preferring x-llm-description.\n */\nexport function extractHelp(propSchema: Record<string, unknown>, maxLength = 1000): string | undefined {\n let text = propSchema[\"x-llm-description\"] as string | undefined;\n if (!text) {\n text = propSchema.description as string | undefined;\n }\n if (!text) {\n return undefined;\n }\n if (maxLength > 0 && text.length > maxLength) {\n return text.slice(0, maxLength - 3) + \"...\";\n }\n return text;\n}\n\n// ---------------------------------------------------------------------------\n// schemaToCliOptions\n// ---------------------------------------------------------------------------\n\n/** Reserved CLI option names that cannot be used by schema properties. */\nconst RESERVED_NAMES = new Set([\"input\", \"yes\", \"large_input\", \"format\", \"sandbox\"]);\n\n/**\n * Convert a JSON Schema `properties` object into an array of\n * Commander option configurations.\n */\nexport function schemaToCliOptions(\n schema: Record<string, unknown>,\n maxHelpLength = 1000,\n): OptionConfig[] {\n const properties = (schema.properties ?? {}) as Record<\n string,\n Record<string, unknown>\n >;\n const requiredList = (schema.required ?? []) as string[];\n const options: OptionConfig[] = [];\n const flagNames: Record<string, string> = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n const flagName = \"--\" + propName.replace(/_/g, \"-\");\n\n // Collision detection\n if (flagName in flagNames) {\n process.stderr.write(\n `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n flagNames[flagName] = propName;\n\n // Reserved name check\n if (RESERVED_NAMES.has(propName)) {\n process.stderr.write(\n `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.\\n`,\n );\n process.exit(EXIT_CODES.INVALID_CLI_INPUT);\n }\n\n const typeResult = mapType(propName, propSchema);\n const isRequired = requiredList.includes(propName);\n const helpBase = extractHelp(propSchema, maxHelpLength);\n const helpText = isRequired\n ? (helpBase ? helpBase + \" \" : \"\") + \"[required]\"\n : helpBase ?? \"\";\n const defaultValue = propSchema.default as unknown;\n\n if (typeResult === BOOLEAN_FLAG) {\n // Boolean flag pair: --flag/--no-flag\n const flagBase = propName.replace(/_/g, \"-\");\n const defaultVal = (propSchema.default as boolean) ?? false;\n options.push({\n name: propName,\n flags: `--${flagBase}, --no-${flagBase}`,\n description: helpText,\n defaultValue: defaultVal,\n required: false,\n isBooleanFlag: true,\n });\n } else if (\"enum\" in propSchema && Array.isArray(propSchema.enum)) {\n const enumValues = propSchema.enum as unknown[];\n if (enumValues.length === 0) {\n // Empty enum — fall back to plain string option\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue,\n required: false,\n });\n } else {\n const stringValues = enumValues.map(String);\n const enumOriginalTypes: Record<string, string> = {};\n for (const v of enumValues) {\n if (typeof v === \"number\" && Number.isInteger(v)) {\n enumOriginalTypes[String(v)] = \"int\";\n } else if (typeof v === \"number\") {\n enumOriginalTypes[String(v)] = \"float\";\n } else if (typeof v === \"boolean\") {\n enumOriginalTypes[String(v)] = \"bool\";\n }\n }\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue:\n defaultValue !== undefined ? String(defaultValue) : undefined,\n required: false,\n choices: stringValues,\n enumOriginalTypes:\n Object.keys(enumOriginalTypes).length > 0\n ? enumOriginalTypes\n : undefined,\n });\n }\n } else {\n // Standard option\n let parseArg: ((value: string) => unknown) | undefined;\n if (typeResult === \"int\") {\n parseArg = (v: string) => {\n const n = parseInt(v, 10);\n if (isNaN(n)) throw new Error(`Invalid integer: ${v}`);\n return n;\n };\n } else if (typeResult === \"float\") {\n parseArg = (v: string) => {\n const n = parseFloat(v);\n if (isNaN(n)) throw new Error(`Invalid number: ${v}`);\n return n;\n };\n }\n options.push({\n name: propName,\n flags: `${flagName} <value>`,\n description: helpText,\n defaultValue,\n required: false,\n parseArg,\n });\n }\n }\n\n return options;\n}\n","/**\n * Interactive approval prompts with timeout.\n *\n * Protocol spec: Approval workflow\n */\n\nimport * as readline from \"node:readline\";\nimport type { ModuleDescriptor } from \"./cli.js\";\nimport { ApprovalTimeoutError, EXIT_CODES } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get an annotation value from either a dict or an object.\n */\nfunction getAnnotation(\n annotations: unknown,\n key: string,\n defaultValue: unknown = undefined,\n): unknown {\n if (!annotations || typeof annotations !== \"object\") return defaultValue;\n const ann = annotations as Record<string, unknown>;\n return key in ann ? ann[key] : defaultValue;\n}\n\n// ---------------------------------------------------------------------------\n// checkApproval\n// ---------------------------------------------------------------------------\n\n/**\n * Check if module requires approval and handle accordingly.\n * Returns normally if approved (or approval not required).\n * Calls process.exit(46) if denied/timed out/non-TTY.\n */\nexport async function checkApproval(\n moduleDef: ModuleDescriptor,\n autoApprove: boolean,\n): Promise<void> {\n const annotations = moduleDef.annotations;\n\n // Check if approval is required\n let requiresApproval: boolean;\n if (moduleDef.requiresApproval !== undefined) {\n requiresApproval = moduleDef.requiresApproval;\n } else if (annotations) {\n requiresApproval = getAnnotation(annotations, \"requires_approval\", false) === true;\n } else {\n return; // No annotations, no approval needed\n }\n\n if (!requiresApproval) {\n return;\n }\n\n const moduleId = moduleDef.id;\n\n // Bypass: autoApprove flag (highest priority)\n if (autoApprove) {\n return;\n }\n\n // Bypass: APCORE_CLI_AUTO_APPROVE env var\n const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? \"\";\n if (envVal === \"1\") {\n return;\n }\n if (envVal !== \"\" && envVal !== \"1\") {\n process.stderr.write(\n `Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.\\n`,\n );\n }\n\n // Non-TTY check\n if (!process.stdin.isTTY) {\n process.stderr.write(\n `Error: Module '${moduleId}' requires approval but no interactive ` +\n \"terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 \" +\n \"to bypass.\\n\",\n );\n process.exit(EXIT_CODES.APPROVAL_DENIED);\n }\n\n // TTY prompt\n await promptWithTimeout(moduleDef, 60);\n}\n\n/**\n * Display approval prompt with timeout.\n */\nasync function promptWithTimeout(\n moduleDef: ModuleDescriptor,\n timeout: number,\n): Promise<void> {\n // Clamp timeout\n timeout = Math.max(1, Math.min(timeout, 3600));\n\n const moduleId = moduleDef.id;\n const annotations = moduleDef.annotations;\n const message =\n (annotations\n ? (getAnnotation(annotations, \"approval_message\") as string | undefined)\n : undefined) ??\n `Module '${moduleId}' requires approval to execute.`;\n\n process.stderr.write(message + \"\\n\");\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n const answer = await Promise.race([\n new Promise<string>((resolve) => {\n rl.question(\"Proceed? [y/N] \", (ans) => resolve(ans));\n }),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new ApprovalTimeoutError(\n `Approval prompt timed out after ${timeout} seconds.`,\n ));\n }, timeout * 1000);\n }),\n ]);\n\n // Clear the timeout — prompt resolved before timeout fired\n if (timer) clearTimeout(timer);\n\n const normalized = answer.trim().toLowerCase();\n if (normalized === \"y\" || normalized === \"yes\") {\n return;\n }\n\n process.stderr.write(\"Error: Approval denied.\\n\");\n process.exit(EXIT_CODES.APPROVAL_DENIED);\n } catch (err) {\n if (timer) clearTimeout(timer);\n if (err instanceof ApprovalTimeoutError) {\n process.stderr.write(\n `Error: Approval prompt timed out after ${timeout} seconds.\\n`,\n );\n process.exit(EXIT_CODES.APPROVAL_TIMEOUT);\n }\n throw err;\n } finally {\n rl.close();\n }\n}\n","/**\n * TTY-adaptive output formatting (table/json).\n *\n * Protocol spec: Output formatting\n */\n\nimport type { ModuleDescriptor } from \"./cli.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve output format with TTY-adaptive default.\n */\nexport function resolveFormat(explicitFormat?: string): string {\n if (explicitFormat !== undefined) {\n return explicitFormat;\n }\n return process.stdout.isTTY ? \"table\" : \"json\";\n}\n\n/**\n * Truncate text to maxLength, appending '...' if needed.\n */\nexport function truncate(text: string, maxLength = 80): string {\n if (text.length <= maxLength) {\n return text;\n }\n return text.slice(0, maxLength - 3) + \"...\";\n}\n\n/**\n * Render a simple plain-text table with column headers.\n */\nfunction formatTable(\n headers: string[],\n rows: string[][],\n): string {\n // Calculate column widths\n const colWidths = headers.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => (r[i] ?? \"\").length)),\n );\n\n const sep = colWidths.map((w) => \"-\".repeat(w)).join(\" \");\n const headerLine = headers\n .map((h, i) => h.padEnd(colWidths[i]))\n .join(\" \");\n const dataLines = rows.map((row) =>\n row.map((cell, i) => (cell ?? \"\").padEnd(colWidths[i])).join(\" \"),\n );\n\n return [headerLine, sep, ...dataLines].join(\"\\n\") + \"\\n\";\n}\n\n// ---------------------------------------------------------------------------\n// formatModuleList\n// ---------------------------------------------------------------------------\n\n/**\n * Format and print a list of modules.\n */\nexport function formatModuleList(\n modules: ModuleDescriptor[],\n format: string,\n filterTags?: string[],\n): void {\n if (format === \"table\") {\n if (modules.length === 0 && filterTags && filterTags.length > 0) {\n process.stdout.write(\n `No modules found matching tags: ${filterTags.join(\", \")}.\\n`,\n );\n return;\n }\n if (modules.length === 0) {\n process.stdout.write(\"No modules found.\\n\");\n return;\n }\n\n const headers = [\"ID\", \"Description\", \"Tags\"];\n const rows = modules.map((m) => [\n m.id,\n truncate(m.description, 80),\n (m.tags ?? []).join(\", \"),\n ]);\n process.stdout.write(formatTable(headers, rows));\n } else if (format === \"json\") {\n const result = modules.map((m) => ({\n id: m.id,\n description: m.description,\n tags: m.tags ?? [],\n }));\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// formatModuleDetail\n// ---------------------------------------------------------------------------\n\n/**\n * Convert annotations to a plain dict, filtering out falsy/default values.\n */\nfunction annotationsToDict(\n annotations: unknown,\n): Record<string, unknown> | null {\n if (!annotations) return null;\n if (typeof annotations !== \"object\" || Array.isArray(annotations)) return null;\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(annotations as Record<string, unknown>)) {\n if (v !== null && v !== undefined && v !== false && v !== 0 && !(Array.isArray(v) && v.length === 0)) {\n result[k] = v;\n }\n }\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Format and print full module metadata.\n */\nexport function formatModuleDetail(\n moduleDef: ModuleDescriptor,\n format: string,\n): void {\n if (format === \"table\") {\n process.stdout.write(`\\nModule: ${moduleDef.id}\\n`);\n process.stdout.write(`\\nDescription:\\n ${moduleDef.description}\\n`);\n\n if (moduleDef.inputSchema && Object.keys(moduleDef.inputSchema).length > 0) {\n process.stdout.write(\"\\nInput Schema:\\n\");\n process.stdout.write(JSON.stringify(moduleDef.inputSchema, null, 2) + \"\\n\");\n }\n\n if (moduleDef.outputSchema && Object.keys(moduleDef.outputSchema).length > 0) {\n process.stdout.write(\"\\nOutput Schema:\\n\");\n process.stdout.write(JSON.stringify(moduleDef.outputSchema, null, 2) + \"\\n\");\n }\n\n const annDict = annotationsToDict(\n moduleDef.annotations,\n );\n if (annDict) {\n process.stdout.write(\"\\nAnnotations:\\n\");\n for (const [k, v] of Object.entries(annDict)) {\n process.stdout.write(` ${k}: ${v}\\n`);\n }\n }\n\n // Extension metadata (x- prefixed)\n const metadata = moduleDef.metadata;\n if (metadata) {\n const xFields: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(metadata)) {\n if (k.startsWith(\"x-\") || k.startsWith(\"x_\")) {\n xFields[k] = v;\n }\n }\n if (Object.keys(xFields).length > 0) {\n process.stdout.write(\"\\nExtension Metadata:\\n\");\n for (const [k, v] of Object.entries(xFields)) {\n process.stdout.write(` ${k}: ${v}\\n`);\n }\n }\n }\n\n const tags = moduleDef.tags ?? [];\n if (tags.length > 0) {\n process.stdout.write(`\\nTags: ${tags.join(\", \")}\\n`);\n }\n } else if (format === \"json\") {\n const result: Record<string, unknown> = {\n id: moduleDef.id,\n description: moduleDef.description,\n };\n if (moduleDef.inputSchema) result.input_schema = moduleDef.inputSchema;\n if (moduleDef.outputSchema) result.output_schema = moduleDef.outputSchema;\n\n const annDict = annotationsToDict(\n moduleDef.annotations,\n );\n if (annDict) result.annotations = annDict;\n\n const tags = moduleDef.tags ?? [];\n if (tags.length > 0) result.tags = tags;\n\n // Extension metadata\n const metadata = moduleDef.metadata;\n if (metadata) {\n for (const [k, v] of Object.entries(metadata)) {\n if (k.startsWith(\"x-\") || k.startsWith(\"x_\")) {\n result[k] = v;\n }\n }\n }\n\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// formatExecResult\n// ---------------------------------------------------------------------------\n\n/**\n * Format and print module execution result.\n */\nexport function formatExecResult(\n result: unknown,\n format?: string,\n): void {\n if (result === null || result === undefined) {\n return;\n }\n const effective = resolveFormat(format);\n if (\n effective === \"table\" &&\n typeof result === \"object\" &&\n !Array.isArray(result)\n ) {\n // Key-value table\n const entries = Object.entries(result as Record<string, unknown>);\n const headers = [\"Key\", \"Value\"];\n const rows = entries.map(([k, v]) => [String(k), String(v)]);\n process.stdout.write(formatTable(headers, rows));\n } else if (typeof result === \"object\") {\n process.stdout.write(JSON.stringify(result, null, 2) + \"\\n\");\n } else if (typeof result === \"string\") {\n process.stdout.write(result + \"\\n\");\n } else {\n process.stdout.write(String(result) + \"\\n\");\n }\n}\n","/**\n * Simple structured logger respecting logging.level config.\n */\n\nconst LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 } as const;\ntype LogLevel = keyof typeof LEVELS;\n\nlet currentLevel: LogLevel = \"WARNING\";\n\nexport function setLogLevel(level: string): void {\n const upper = level.toUpperCase();\n if (upper in LEVELS) {\n currentLevel = upper as LogLevel;\n }\n}\n\nexport function getLogLevel(): LogLevel {\n return currentLevel;\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVELS[level] >= LEVELS[currentLevel];\n}\n\nexport function debug(message: string): void {\n if (shouldLog(\"DEBUG\")) process.stderr.write(`DEBUG: ${message}\\n`);\n}\n\nexport function info(message: string): void {\n if (shouldLog(\"INFO\")) process.stderr.write(`INFO: ${message}\\n`);\n}\n\nexport function warn(message: string): void {\n if (shouldLog(\"WARNING\")) process.stderr.write(`WARNING: ${message}\\n`);\n}\n\nexport function error(message: string): void {\n if (shouldLog(\"ERROR\")) process.stderr.write(`ERROR: ${message}\\n`);\n}\n","/**\n * Init command — scaffold new apcore modules (Phase 1).\n */\n\nimport { Command } from \"commander\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst DECORATOR_TEMPLATE = `\\\nimport { module } from \"apcore-js\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport const {varName} = module({\n id: \"{moduleId}\",\n description: \"{description}\",\n inputSchema: Type.Object({}),\n outputSchema: Type.Object({ status: Type.String() }),\n execute: (_inputs) => {\n // TODO: implement\n return { status: \"ok\" };\n },\n});\n`;\n\nconst CONVENTION_TEMPLATE = `\\\n/**\n * {description}\n */\n{cliGroupLine}\nexport function {funcName}(): Record<string, unknown> {\n // TODO: implement\n return { status: \"ok\" };\n}\n`;\n\nconst BINDING_TEMPLATE = `\\\nbindings:\n - module_id: \"{moduleId}\"\n target: \"{target}\"\n description: \"{description}\"\n auto_schema: true\n`;\n\n/**\n * Simple template rendering: replaces {key} with values from the context.\n */\nfunction renderTemplate(template: string, context: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(context)) {\n // Replace all occurrences of {key} with the value\n result = result.split(`{${key}}`).join(value);\n }\n return result;\n}\n\n/**\n * Register the init command group on the CLI program.\n */\nexport function registerInitCommand(cli: Command): void {\n const initGroup = cli.command(\"init\").description(\"Scaffold new apcore modules.\");\n\n initGroup\n .command(\"module <module-id>\")\n .description(\"Create a new module from a template.\\n\\nMODULE_ID is the module identifier (e.g., ops.deploy, user.create).\")\n .option(\n \"--style <style>\",\n \"Module style: decorator (@module), convention (plain function), or binding (YAML).\",\n \"convention\",\n )\n .option(\"--dir <path>\", \"Output directory. Default: extensions/ or commands/.\")\n .option(\"-d, --description <text>\", \"Module description.\", \"TODO: add description\")\n .action((moduleId: string, opts: { style: string; dir?: string; description: string }) => {\n // Parse module_id into parts\n const lastDot = moduleId.lastIndexOf(\".\");\n const prefix = lastDot >= 0 ? moduleId.substring(0, lastDot) : moduleId;\n const funcName = lastDot >= 0 ? moduleId.substring(lastDot + 1) : moduleId;\n\n const style = opts.style;\n const description = opts.description;\n\n // Validate --dir to prevent path traversal\n const dir = opts.dir ?? (style === \"decorator\" ? \"extensions\" : style === \"binding\" ? \"bindings\" : \"commands\");\n if (dir.split(path.sep).includes(\"..\") || dir.split(\"/\").includes(\"..\")) {\n process.stderr.write(`Error: Output directory must not contain '..' path components.\\n`);\n process.exit(2);\n }\n\n switch (style) {\n case \"decorator\":\n createDecoratorModule(moduleId, prefix, funcName, description, dir);\n break;\n case \"convention\":\n createConventionModule(moduleId, prefix, funcName, description, dir);\n break;\n case \"binding\":\n createBindingModule(moduleId, prefix, funcName, description, dir);\n break;\n default:\n process.stderr.write(`Error: Unknown style '${style}'\\n`);\n process.exit(2);\n }\n });\n}\n\nfunction createDecoratorModule(\n moduleId: string,\n _prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n fs.mkdirSync(outputDir, { recursive: true });\n const filename = moduleId.replace(/\\./g, \"_\") + \".ts\";\n const filepath = path.join(outputDir, filename);\n\n const varName = funcName + \"Module\";\n const content = renderTemplate(DECORATOR_TEMPLATE, {\n moduleId,\n varName,\n funcName,\n description,\n });\n fs.writeFileSync(filepath, content);\n process.stdout.write(`Created ${filepath}\\n`);\n}\n\nfunction createConventionModule(\n moduleId: string,\n prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n // If prefix has dots, create subdirectories\n const prefixParts = prefix.split(\".\");\n const dirPath = prefixParts.length > 1\n ? path.join(outputDir, ...prefixParts.slice(0, -1))\n : outputDir;\n fs.mkdirSync(dirPath, { recursive: true });\n\n let filename: string;\n if (prefixParts.length > 1) {\n filename = prefixParts[prefixParts.length - 1] + \".ts\";\n } else {\n filename = prefix + \".ts\";\n }\n // If the file would be the same as the function name, use prefix as filename\n if (prefix === funcName) {\n filename = prefix + \".ts\";\n }\n const filepath = path.join(dirPath, filename);\n\n const cliGroupLine = moduleId.includes(\".\")\n ? `export const CLI_GROUP = \"${prefixParts[0]}\";\\n`\n : \"\";\n\n const content = renderTemplate(CONVENTION_TEMPLATE, {\n funcName,\n description,\n cliGroupLine,\n });\n fs.writeFileSync(filepath, content);\n process.stdout.write(`Created ${filepath}\\n`);\n}\n\nfunction createBindingModule(\n moduleId: string,\n prefix: string,\n funcName: string,\n description: string,\n outputDir: string,\n): void {\n fs.mkdirSync(outputDir, { recursive: true });\n\n const yamlFile = path.join(outputDir, moduleId.replace(/\\./g, \"_\") + \".binding.yaml\");\n const target = `commands.${prefix}:${funcName}`;\n\n const yamlContent = renderTemplate(BINDING_TEMPLATE, {\n moduleId,\n target,\n description,\n });\n fs.writeFileSync(yamlFile, yamlContent);\n process.stdout.write(`Created ${yamlFile}\\n`);\n\n // Also create the target function file\n const baseSrc = \"commands\";\n fs.mkdirSync(baseSrc, { recursive: true });\n const srcFile = path.join(baseSrc, prefix.replace(/\\./g, \"_\") + \".ts\");\n if (!fs.existsSync(srcFile)) {\n const srcContent =\n `export function ${funcName}(): Record<string, unknown> {\\n` +\n ` /** ${description} */\\n` +\n \" // TODO: implement\\n\" +\n ' return { status: \"ok\" };\\n' +\n \"}\\n\";\n fs.writeFileSync(srcFile, srcContent);\n process.stdout.write(`Created ${srcFile}\\n`);\n }\n}\n","/**\n * Display overlay helpers — shared resolution logic for CLI surfaces.\n */\n\nimport type { ModuleDescriptor } from \"./cli.js\";\n\n/**\n * Extract resolved display overlay from a ModuleDescriptor's metadata.\n */\nexport function getDisplay(descriptor: ModuleDescriptor): Record<string, unknown> {\n const metadata = descriptor.metadata ?? {};\n const display = (metadata as Record<string, unknown>).display;\n if (display && typeof display === \"object\" && !Array.isArray(display)) {\n return display as Record<string, unknown>;\n }\n return {};\n}\n\n/**\n * Return [displayName, description, tags] resolved from the display overlay.\n *\n * Falls back to scanner-provided values when no overlay is present.\n */\nexport function getCliDisplayFields(descriptor: ModuleDescriptor): [string, string, string[]] {\n const display = getDisplay(descriptor);\n const cli = (display.cli && typeof display.cli === \"object\" && !Array.isArray(display.cli))\n ? (display.cli as Record<string, unknown>)\n : {};\n const name = (cli.alias as string | undefined)\n ?? (display.alias as string | undefined)\n ?? descriptor.id;\n const desc = (cli.description as string | undefined) ?? descriptor.description;\n const tags = (display.tags as string[] | undefined) ?? descriptor.tags ?? [];\n return [name, desc, tags];\n}\n"],"mappings":"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","names":["fileURLToPath","path","path","__dirname","fileURLToPath","__dirname"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,19 @@ declare class GroupedModuleGroup extends LazyModuleGroup {
|
|
|
117
117
|
* Protocol spec: CLI bootstrapping & command registration
|
|
118
118
|
*/
|
|
119
119
|
|
|
120
|
+
/** Whether --verbose was passed (controls help detail level). */
|
|
121
|
+
declare let verboseHelp: boolean;
|
|
122
|
+
/** Set the verbose help flag. When false, built-in options are hidden from help. */
|
|
123
|
+
declare function setVerboseHelp(verbose: boolean): void;
|
|
124
|
+
/** Base URL for online documentation. Null means no docs link shown. */
|
|
125
|
+
declare let docsUrl: string | null;
|
|
126
|
+
/**
|
|
127
|
+
* Set the base URL for online documentation links shown in help and man pages.
|
|
128
|
+
* Pass null to disable. Command-level help appends `/commands/{name}` automatically.
|
|
129
|
+
*
|
|
130
|
+
* @example setDocsUrl("https://docs.apcore.dev/cli");
|
|
131
|
+
*/
|
|
132
|
+
declare function setDocsUrl(url: string | null): void;
|
|
120
133
|
/** Configuration for a single Commander option derived from a JSON Schema property. */
|
|
121
134
|
interface OptionConfig {
|
|
122
135
|
/** The property name from the schema. */
|
|
@@ -144,7 +157,7 @@ interface OptionConfig {
|
|
|
144
157
|
* @param extensionsDir Path to the extensions directory (default: ./extensions)
|
|
145
158
|
* @param progName Program name shown in help (default: apcore-cli)
|
|
146
159
|
*/
|
|
147
|
-
declare function createCli(extensionsDir?: string, progName?: string): Command;
|
|
160
|
+
declare function createCli(extensionsDir?: string, progName?: string, verbose?: boolean): Command;
|
|
148
161
|
/**
|
|
149
162
|
* Optionally apply apcore-toolkit features (DisplayResolver, RegistryWriter).
|
|
150
163
|
*
|
|
@@ -159,7 +172,7 @@ declare function main(progName?: string): void;
|
|
|
159
172
|
/**
|
|
160
173
|
* Build a Commander Command for a single apcore module.
|
|
161
174
|
*/
|
|
162
|
-
declare function buildModuleCommand(moduleDef: ModuleDescriptor, executor: Executor, helpTextMaxLength?: number, cmdName?: string): Command;
|
|
175
|
+
declare function buildModuleCommand(moduleDef: ModuleDescriptor, executor: Executor, helpTextMaxLength?: number, cmdName?: string, verbose?: boolean): Command;
|
|
163
176
|
/**
|
|
164
177
|
* Validate that a module ID conforms to the expected format.
|
|
165
178
|
* Pattern: [a-z][a-z0-9_]*(.[a-z][a-z0-9_])* — max 128 chars.
|
|
@@ -328,6 +341,20 @@ declare function checkApproval(moduleDef: ModuleDescriptor, autoApprove: boolean
|
|
|
328
341
|
* Protocol spec: Shell integration
|
|
329
342
|
*/
|
|
330
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Build a complete roff man page for the entire program.
|
|
346
|
+
* Covers all registered commands including downstream business commands.
|
|
347
|
+
*/
|
|
348
|
+
declare function buildProgramManPage(program: Command, progName: string, version: string, description?: string, docsUrl?: string): string;
|
|
349
|
+
/**
|
|
350
|
+
* Configure --help --man support on a Commander program.
|
|
351
|
+
* When --man is passed with --help, outputs a complete roff man page
|
|
352
|
+
* covering all registered commands (including downstream business commands).
|
|
353
|
+
*
|
|
354
|
+
* Usage in downstream projects:
|
|
355
|
+
* configureManHelp(program, 'reach', '0.2.0', 'ReachForge: The Social Influence Engine', 'https://reachforge.dev/docs');
|
|
356
|
+
*/
|
|
357
|
+
declare function configureManHelp(program: Command, progName: string, version: string, description?: string, docsUrl?: string): void;
|
|
331
358
|
/**
|
|
332
359
|
* Register completion and man commands.
|
|
333
360
|
*/
|
|
@@ -505,4 +532,4 @@ declare class Sandbox {
|
|
|
505
532
|
private sandboxedExecute;
|
|
506
533
|
}
|
|
507
534
|
|
|
508
|
-
export { ApprovalDeniedError, ApprovalTimeoutError, AuditLogger, AuthProvider, AuthenticationError, BUILTIN_COMMANDS, ConfigDecryptionError, ConfigEncryptor, ConfigResolver, DEFAULTS, EXIT_CODES, type Executor, type ExitCode, GroupedModuleGroup, LazyGroup, LazyModuleGroup, type ModuleDescriptor, ModuleExecutionError, ModuleNotFoundError, type OptionConfig, type Registry, Sandbox, SchemaValidationError, applyToolkitIntegration, buildModuleCommand, checkApproval, collectInput, createCli, debug, error, exitCodeForError, extractHelp, formatExecResult, formatModuleDetail, formatModuleList, getAuditLogger, getCliDisplayFields, getDisplay, getLogLevel, info, main, mapType, reconvertEnumValues, registerDiscoveryCommands, registerInitCommand, registerShellCommands, resolveFormat, resolveRefs, schemaToCliOptions, setAuditLogger, setLogLevel, truncate, validateModuleId, warn };
|
|
535
|
+
export { ApprovalDeniedError, ApprovalTimeoutError, AuditLogger, AuthProvider, AuthenticationError, BUILTIN_COMMANDS, ConfigDecryptionError, ConfigEncryptor, ConfigResolver, DEFAULTS, EXIT_CODES, type Executor, type ExitCode, GroupedModuleGroup, LazyGroup, LazyModuleGroup, type ModuleDescriptor, ModuleExecutionError, ModuleNotFoundError, type OptionConfig, type Registry, Sandbox, SchemaValidationError, applyToolkitIntegration, buildModuleCommand, buildProgramManPage, checkApproval, collectInput, configureManHelp, createCli, debug, docsUrl, error, exitCodeForError, extractHelp, formatExecResult, formatModuleDetail, formatModuleList, getAuditLogger, getCliDisplayFields, getDisplay, getLogLevel, info, main, mapType, reconvertEnumValues, registerDiscoveryCommands, registerInitCommand, registerShellCommands, resolveFormat, resolveRefs, schemaToCliOptions, setAuditLogger, setDocsUrl, setLogLevel, setVerboseHelp, truncate, validateModuleId, verboseHelp, warn };
|