apcore-cli 0.8.1 → 0.9.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 +52 -0
- package/README.md +16 -12
- package/dist/bin/apcore-cli.js +135 -81
- package/dist/bin/apcore-cli.js.map +1 -1
- package/dist/index.d.ts +55 -6
- package/dist/index.js +136 -81
- package/dist/index.js.map +1 -1
- package/package.json +3 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,58 @@ 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.9.0] - 2026-05-13
|
|
9
|
+
|
|
10
|
+
### Fixed (2026-05-13 — cross-SDK audit D10/D11/D1)
|
|
11
|
+
|
|
12
|
+
- **`ConfigEncryptor` LOGNAME key-derivation chain** (D10-001) — PBKDF2 username fallback was `USER → USERNAME → "unknown"` (3-tier); now `USER → LOGNAME → USERNAME → "unknown"` (4-tier) matching the spec and Rust. `src/security/config-encryptor.ts:183, 219`.
|
|
13
|
+
- **Sandbox stdin write lacks `'error'` listener** (D11-008) — `child.stdin.on('error', () => {})` added before `write()` so an EPIPE event from a child that exits early no longer surfaces as an uncaught exception. `src/security/sandbox.ts:153`.
|
|
14
|
+
- **`buildSandboxEnv` drops explicitly-empty env values** (D11-009) — `if (process.env[key])` changed to `if (process.env[key] !== undefined)` so `PATH=""` is forwarded uniformly with Python and Rust. `src/security/sandbox.ts:264`.
|
|
15
|
+
- **`exec --trace` flag ignored when used without `--strategy`** (D11-011) — condition `if (opts.strategy && executor.callWithTrace)` changed to `if ((opts.trace || opts.strategy) && executor.callWithTrace)`. `--trace` alone now routes through `callWithTrace` matching Python. `src/discovery.ts:345`.
|
|
16
|
+
- **CLI brand string in auth error messages** (D11-006) — remediation strings now say `apcli config set auth.api_key` (canonical FE-13 name). `src/security/auth.ts:46`.
|
|
17
|
+
- **`requestApproval` missing `requires_approval=false` short-circuit** (D11-014) — returns `approved/not_required` when the request explicitly carries `requires_approval: false`, matching Rust. `src/approval.ts:63`.
|
|
18
|
+
- **`AuthProvider` missing `config.encryptor` peer-attribute fallback** (D11-005) — `getEncryptor()` now walks explicit constructor arg → `config.encryptor` peer attribute → fresh instance, matching Python's three-tier chain. `src/security/auth.ts:22`.
|
|
19
|
+
- **`APCLI_SUBCOMMAND_NAMES` and `DEFAULT_BUILTIN_GROUP_NAME` not re-exported** (D1 re-audit) — both constants added to `src/index.ts:27` export block. Python and Rust already re-exported both.
|
|
20
|
+
- **Standalone bin entrypoint used deprecated `verbose:` field internally** (D9 re-audit) — `src/main.ts:848` `createCli` call now passes canonical `allOptions: verboseHelp`.
|
|
21
|
+
- **Stale `cli.ts` placeholder-type TODO** (D9-W2) — TODO comment updated to document the actual `apcore-js` Registry/ModuleDescriptor shape gap (method names diverge: `listModules`/`getModule` vs `list`/`getDefinition`/`moduleId`), replacing the generic "until available" wording.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`CreateCliOptions.allOptions` field** (D1-W5) — canonical successor to the deprecated `verbose` field. Embedders should migrate `createCli({ verbose: true })` → `createCli({ allOptions: true })`. `verbose` remains for backward compat through v0.9 and will be removed in v0.10. `src/main.ts:251`.
|
|
26
|
+
- **`setLogLevel` / `getLogLevel` documented as intentionally TS-only** (D1-W4) — `src/index.ts:96-102` now carries a cross-SDK parity note explaining that Python and Rust delegate to their native logging channels.
|
|
27
|
+
- **`getAuditLogger` documented as intentionally TS-only** (D1 re-audit) — `src/index.ts:106` now carries a note. Only the setter (`setAuditLogger`) is the canonical cross-SDK API.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **CSV `--format csv` heterogeneous-keys data loss** — `formatExecResult` previously derived CSV headers from `Object.keys(rows[0])` only, silently dropping fields that first appeared in later rows. Surfaced via aisee-cli's `summarizeAction()` which emits optional `description` / `solution` fields. The header is now the **union of keys across all rows** in insertion-order. `src/output.ts:340-357`.
|
|
32
|
+
- **CSV line terminator** — now `\r\n` per RFC 4180 (was `\n`). Existing Excel + downstream-parser compatibility improves significantly.
|
|
33
|
+
- **CSV nested-value serialization** — now goes through the toolkit's canonical JSON encoder (compact, insertion-order, unicode-preserved). Behavior was already correct via `JSON.stringify`, but the contract is now enforced at the toolkit layer.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **User-visible help/man/completion/error text no longer leaks the `apcore` / `apcore-js` framework name** to end users of downstream CLIs built on apcore-cli. Affected strings: footer hint (`Use --verbose to show all options (including built-in apcore options)` → `… (including built-in options)`, `src/main.ts:947`), `init` group description (`Scaffold new apcore modules` → `Scaffold new modules`, `src/init-cmd.ts:82`), top-level CLI description (`… execute apcore modules from the command line` → `… execute modules from the command line`, `src/main.ts:835`), standalone unwired-registry error message (`Error: no apcore-js registry wired.` → `Error: no module registry wired.`, `src/main.ts:619`), and man-page `ENVIRONMENT` text (`Path to the apcore extensions directory.` → `Path to the extensions directory.`, `src/shell.ts:302`). README's `--verbose` row updated to match. Two `tests/main.test.ts` assertions (`:891`, `:916`) updated to the new error string. Logger names, source comments, type comments, and environment-variable identifiers (`APCORE_*`) are unchanged — only descriptive copy that appears in `--help`, shell completion, `man` output, or user-facing error messages. Cross-SDK parity with Python 0.8.1 and Rust 0.8.1.
|
|
38
|
+
|
|
39
|
+
### Changed (breaking CLI surface)
|
|
40
|
+
|
|
41
|
+
- **Global `--verbose` flag renamed to `--all-options`** — The help-display flag is now `--all-options`; use `apcore-cli module --help --all-options` to reveal hidden built-in options. `verbose` is removed from the reserved schema property names set — module schemas may now freely define `verbose: boolean` for runtime output control. Tracked in [apcore-cli#21](https://github.com/aiperceivable/apcore-cli/issues/21).
|
|
42
|
+
|
|
43
|
+
### Changed (breaking peer-dep semantics)
|
|
44
|
+
|
|
45
|
+
- **`apcore-toolkit` promoted from optional to REQUIRED peer dependency** (`>=0.7.0`). All `--format` operations now go through the toolkit's reference implementation for csv/jsonl/markdown/skill (was only markdown/skill). Consumers that did not install the optional peer must add it. `package.json` peer-dependency-meta `optional: true` removed.
|
|
46
|
+
|
|
47
|
+
### Removed
|
|
48
|
+
|
|
49
|
+
- `csvCellString` and `escapeCsvField` private helpers — replaced by `apcore_toolkit.formatCsv()` and the toolkit's RFC 4180 internals.
|
|
50
|
+
|
|
51
|
+
### Why
|
|
52
|
+
|
|
53
|
+
Per-SDK CSV reimplementations had accumulated divergence: Python emitted Python repr `{'k': 'v'}`, TS dropped heterogeneous keys, Rust used `\n` not CRLF. The spec MUST language couldn't enforce conformance on downstream consumers (e.g. aisee-cli) that reimplemented. See ADR-09 in `apcore-cli/docs/tech-design.md` for the byte-equivalent vs SDK-native tier split.
|
|
54
|
+
|
|
55
|
+
### Migration
|
|
56
|
+
|
|
57
|
+
Downstream consumers using only `json` / `table` formats are unaffected at runtime but need `apcore-toolkit@^0.7` installed alongside `apcore-cli@^0.9` (previously optional). aisee-cli and similar adapters get the CSV bug fix automatically on upgrade.
|
|
58
|
+
|
|
59
|
+
|
|
8
60
|
## [0.8.1] - 2026-05-09
|
|
9
61
|
|
|
10
62
|
### Fixed
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Terminal adapter for apcore. Execute AI-Perceivable modules from the command lin
|
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
|
-
[]()
|
|
12
12
|
|
|
13
13
|
| | |
|
|
14
14
|
|---|---|
|
|
@@ -46,16 +46,15 @@ Terminal adapter for apcore. Execute AI-Perceivable modules from the command lin
|
|
|
46
46
|
## Installation
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
pnpm add apcore-cli apcore-js
|
|
49
|
+
pnpm add apcore-cli apcore-js apcore-toolkit
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Requires Node.js 18
|
|
52
|
+
Requires Node.js 18+, `apcore-js >= 0.21.0`, and `apcore-toolkit >= 0.7.0` (required peer dep as of v0.9.0).
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**v0.9.0 breaking change:** `apcore-toolkit` is now a **required** peer dependency (previously optional). All `--format` operations route through the toolkit's byte-equivalent reference implementations for csv / jsonl / markdown / skill. See [tech-design ADR-09](https://github.com/aiperceivable/apcore-cli/blob/main/docs/tech-design.md) for the rationale and migration notes.
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
pnpm add apcore-cli apcore-js
|
|
58
|
-
pnpm add -D apcore-toolkit # optional, for display overlay / registry writer
|
|
57
|
+
pnpm add apcore-cli apcore-js apcore-toolkit
|
|
59
58
|
```
|
|
60
59
|
|
|
61
60
|
## Quick Start
|
|
@@ -151,7 +150,7 @@ your-project/
|
|
|
151
150
|
No changes to your project. Just install and run:
|
|
152
151
|
|
|
153
152
|
```bash
|
|
154
|
-
pnpm add apcore-cli apcore-js
|
|
153
|
+
pnpm add apcore-cli apcore-js apcore-toolkit
|
|
155
154
|
apcore-cli --extensions-dir ./extensions list
|
|
156
155
|
apcore-cli --extensions-dir ./extensions math.add --a 5 --b 10
|
|
157
156
|
```
|
|
@@ -185,7 +184,7 @@ apcore-cli [OPTIONS] COMMAND [ARGS]
|
|
|
185
184
|
| `--log-level` | `WARNING` | Logging: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
186
185
|
| `--version` | | Show version and exit |
|
|
187
186
|
| `--help` | | Show help and exit |
|
|
188
|
-
| `--
|
|
187
|
+
| `--all-options` | | Show all options in help (including built-in options) |
|
|
189
188
|
| `--man` | | Output man page in roff format (use with `--help`) |
|
|
190
189
|
|
|
191
190
|
### Built-in Commands (the `apcli` group)
|
|
@@ -235,12 +234,17 @@ The canonical 13 `apcli` subcommands:
|
|
|
235
234
|
| `apcli init module <id>` | Scaffold a new module (TS/JS/YAML binding) into the extensions or commands directory (see `registerInitCommand` in `src/init-cmd.ts`) |
|
|
236
235
|
| `apcli validate` | Validate modules and configuration against JSON Schema (see `registerValidateCommand` in `src/discovery.ts`) |
|
|
237
236
|
|
|
238
|
-
**Shell integration**
|
|
237
|
+
**Shell integration** (under `apcli` group)
|
|
239
238
|
|
|
240
239
|
| Command | Description |
|
|
241
240
|
|---------|-------------|
|
|
242
241
|
| `apcli completion <shell>` | Generate shell completion script for bash / zsh / fish (see `registerCompletionCommand` in `src/shell.ts`) |
|
|
243
|
-
|
|
242
|
+
|
|
243
|
+
**Root meta-commands** (NOT under `apcli` — invoked directly on the host CLI)
|
|
244
|
+
|
|
245
|
+
| Command | Description |
|
|
246
|
+
|---------|-------------|
|
|
247
|
+
| `<cli> --help --man [command]` | Generate a man page in roff format for a single command or the whole program (see `configureManHelp` in `src/shell.ts`). This is a root-level option, not an `apcli` subcommand. |
|
|
244
248
|
|
|
245
249
|
#### Standalone vs. embedded surfaces
|
|
246
250
|
|
|
@@ -251,14 +255,14 @@ The canonical 13 `apcli` subcommands:
|
|
|
251
255
|
|
|
252
256
|
### Module Execution Options
|
|
253
257
|
|
|
254
|
-
When executing a module (e.g. `apcore-cli math.add`), these built-in options are available (hidden by default; use `--
|
|
258
|
+
When executing a module (e.g. `apcore-cli math.add`), these built-in options are available (hidden by default; use `--all-options` to show in `--help`):
|
|
255
259
|
|
|
256
260
|
| Option | Description |
|
|
257
261
|
|--------|-------------|
|
|
258
262
|
| `--input -` | Read JSON input from STDIN |
|
|
259
263
|
| `--yes` / `-y` | Bypass approval prompts |
|
|
260
264
|
| `--large-input` | Allow STDIN input larger than 10MB |
|
|
261
|
-
| `--format <fmt>` | Output format: `json`, `table`, `csv`, `yaml`,
|
|
265
|
+
| `--format <fmt>` | Output format: `json`, `table`, `csv`, `yaml`, `jsonl`, `markdown`, `skill`. **v0.9.0:** `csv` / `jsonl` are byte-identical across SDKs via `apcore-toolkit.formatCsv` / `formatJsonl`. Fixes the prior heterogeneous-keys data-loss bug (header was derived from first row only). |
|
|
262
266
|
| `--sandbox` | Run module in a subprocess sandbox (re-exec with stripped env; 64MiB stdout/stderr cap; 300s default timeout). Hidden by default — set `APCORE_CLI_SANDBOX=1` to enable globally. |
|
|
263
267
|
| `--dry-run` | Run preflight checks (schema, ACL, approval) without executing (FE-11) |
|
|
264
268
|
| `--trace` | Emit execution pipeline trace (strategy, hooks, middleware timings) |
|
package/dist/bin/apcore-cli.js
CHANGED
|
@@ -35,6 +35,12 @@ function exitCodeForError(error) {
|
|
|
35
35
|
if (error instanceof SchemaValidationError) {
|
|
36
36
|
return EXIT_CODES.SCHEMA_VALIDATION_ERROR;
|
|
37
37
|
}
|
|
38
|
+
if (error instanceof MaxDepthExceededError || error instanceof CircularRefError) {
|
|
39
|
+
return EXIT_CODES.SCHEMA_CIRCULAR_REF;
|
|
40
|
+
}
|
|
41
|
+
if (error instanceof UnresolvableRefError) {
|
|
42
|
+
return EXIT_CODES.SCHEMA_VALIDATION_ERROR;
|
|
43
|
+
}
|
|
38
44
|
if (error instanceof ModuleNotFoundError) {
|
|
39
45
|
return EXIT_CODES.MODULE_NOT_FOUND;
|
|
40
46
|
}
|
|
@@ -74,7 +80,7 @@ function exitCodeForError(error) {
|
|
|
74
80
|
}
|
|
75
81
|
return EXIT_CODES.MODULE_EXECUTE_ERROR;
|
|
76
82
|
}
|
|
77
|
-
var ApprovalTimeoutError, AuthenticationError, ConfigDecryptionError, ModuleExecutionError, ApprovalDeniedError, SchemaValidationError, ModuleNotFoundError, EXIT_CODES;
|
|
83
|
+
var ApprovalTimeoutError, AuthenticationError, ConfigDecryptionError, ModuleExecutionError, ApprovalDeniedError, SchemaValidationError, MaxDepthExceededError, CircularRefError, UnresolvableRefError, ModuleNotFoundError, EXIT_CODES;
|
|
78
84
|
var init_errors = __esm({
|
|
79
85
|
"src/errors.ts"() {
|
|
80
86
|
"use strict";
|
|
@@ -115,6 +121,24 @@ var init_errors = __esm({
|
|
|
115
121
|
this.name = "SchemaValidationError";
|
|
116
122
|
}
|
|
117
123
|
};
|
|
124
|
+
MaxDepthExceededError = class extends Error {
|
|
125
|
+
constructor(message = "Schema $ref resolution depth exceeded") {
|
|
126
|
+
super(message);
|
|
127
|
+
this.name = "MaxDepthExceededError";
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
CircularRefError = class extends Error {
|
|
131
|
+
constructor(message = "Circular $ref detected in schema") {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "CircularRefError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
UnresolvableRefError = class extends Error {
|
|
137
|
+
constructor(message = "Unresolvable $ref in schema") {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "UnresolvableRefError";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
118
142
|
ModuleNotFoundError = class extends Error {
|
|
119
143
|
constructor(message = "Module not found") {
|
|
120
144
|
super(message);
|
|
@@ -155,6 +179,7 @@ var init_errors = __esm({
|
|
|
155
179
|
var sandbox_exports = {};
|
|
156
180
|
__export(sandbox_exports, {
|
|
157
181
|
Sandbox: () => Sandbox,
|
|
182
|
+
_buildSandboxEnvForTesting: () => _buildSandboxEnvForTesting,
|
|
158
183
|
runSandboxRunner: () => runSandboxRunner
|
|
159
184
|
});
|
|
160
185
|
import { spawn } from "child_process";
|
|
@@ -195,10 +220,13 @@ async function runSandboxRunner(moduleId) {
|
|
|
195
220
|
process.exit(1);
|
|
196
221
|
}
|
|
197
222
|
}
|
|
223
|
+
function _buildSandboxEnvForTesting(tmpDir) {
|
|
224
|
+
return buildSandboxEnv(tmpDir);
|
|
225
|
+
}
|
|
198
226
|
function buildSandboxEnv(tmpDir) {
|
|
199
227
|
const env = {};
|
|
200
228
|
for (const key of SANDBOX_ALLOW_KEYS) {
|
|
201
|
-
if (process.env[key]) env[key] = process.env[key];
|
|
229
|
+
if (process.env[key] !== void 0) env[key] = process.env[key];
|
|
202
230
|
}
|
|
203
231
|
for (const [key, val] of Object.entries(process.env)) {
|
|
204
232
|
if (key.startsWith(SANDBOX_ALLOW_PREFIX) && !key.startsWith(SANDBOX_DENY_PREFIX) && !SANDBOX_DENY_KEYS.includes(key)) {
|
|
@@ -303,11 +331,20 @@ var init_sandbox = __esm({
|
|
|
303
331
|
}
|
|
304
332
|
stderr += chunk.toString();
|
|
305
333
|
});
|
|
334
|
+
child.stdin.on("error", () => {
|
|
335
|
+
});
|
|
306
336
|
child.stdin.write(JSON.stringify(inputData));
|
|
307
337
|
child.stdin.end();
|
|
308
338
|
return new Promise((resolve2, reject) => {
|
|
339
|
+
const cleanup = () => {
|
|
340
|
+
try {
|
|
341
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
};
|
|
309
345
|
const timer = setTimeout(() => {
|
|
310
346
|
child.kill("SIGKILL");
|
|
347
|
+
cleanup();
|
|
311
348
|
reject(
|
|
312
349
|
new ModuleExecutionError(
|
|
313
350
|
`Sandbox module '${moduleId}' timed out after ${this.timeoutSeconds}s.`
|
|
@@ -316,10 +353,7 @@ var init_sandbox = __esm({
|
|
|
316
353
|
}, this.timeoutSeconds * 1e3);
|
|
317
354
|
child.on("close", (code) => {
|
|
318
355
|
clearTimeout(timer);
|
|
319
|
-
|
|
320
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
321
|
-
} catch {
|
|
322
|
-
}
|
|
356
|
+
cleanup();
|
|
323
357
|
if (sizeExceeded) {
|
|
324
358
|
const limitMiB = Math.floor(outputCap / (1024 * 1024));
|
|
325
359
|
reject(new ModuleExecutionError(
|
|
@@ -343,6 +377,7 @@ var init_sandbox = __esm({
|
|
|
343
377
|
});
|
|
344
378
|
child.on("error", (err) => {
|
|
345
379
|
clearTimeout(timer);
|
|
380
|
+
cleanup();
|
|
346
381
|
reject(new ModuleExecutionError(`Failed to spawn sandbox process: ${err.message}`));
|
|
347
382
|
});
|
|
348
383
|
});
|
|
@@ -375,27 +410,21 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
|
|
|
375
410
|
if ("$ref" in obj) {
|
|
376
411
|
const refPath = obj.$ref;
|
|
377
412
|
if (depth >= maxDepth) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
`
|
|
413
|
+
throw new MaxDepthExceededError(
|
|
414
|
+
`$ref resolution depth exceeded maximum of ${maxDepth} for module '${moduleId}'.`
|
|
381
415
|
);
|
|
382
|
-
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
383
416
|
}
|
|
384
417
|
if (visited.has(refPath)) {
|
|
385
|
-
|
|
386
|
-
`
|
|
387
|
-
`
|
|
418
|
+
throw new CircularRefError(
|
|
419
|
+
`Circular $ref detected in schema for module '${moduleId}' at path '${refPath}'.`
|
|
388
420
|
);
|
|
389
|
-
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
390
421
|
}
|
|
391
422
|
const parts = refPath.split("/");
|
|
392
423
|
const key = parts[parts.length - 1];
|
|
393
424
|
if (!(key in defs)) {
|
|
394
|
-
|
|
395
|
-
`
|
|
396
|
-
`
|
|
425
|
+
throw new UnresolvableRefError(
|
|
426
|
+
`Unresolvable $ref '${refPath}' in schema for module '${moduleId}'.`
|
|
397
427
|
);
|
|
398
|
-
process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);
|
|
399
428
|
}
|
|
400
429
|
const newVisited = new Set(visited);
|
|
401
430
|
newVisited.add(refPath);
|
|
@@ -496,17 +525,12 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
|
|
|
496
525
|
return merged;
|
|
497
526
|
}
|
|
498
527
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
visited,
|
|
506
|
-
depth,
|
|
507
|
-
maxDepth,
|
|
508
|
-
moduleId
|
|
509
|
-
);
|
|
528
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
529
|
+
if (k === "allOf" || k === "anyOf" || k === "oneOf" || k === "$ref") {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
533
|
+
obj[k] = resolveNode(v, defs, visited, depth, maxDepth, moduleId);
|
|
510
534
|
}
|
|
511
535
|
}
|
|
512
536
|
return obj;
|
|
@@ -703,7 +727,7 @@ var init_schema_parser = __esm({
|
|
|
703
727
|
"format",
|
|
704
728
|
"fields",
|
|
705
729
|
"sandbox",
|
|
706
|
-
"
|
|
730
|
+
"all_options",
|
|
707
731
|
"dry_run",
|
|
708
732
|
"trace",
|
|
709
733
|
"stream",
|
|
@@ -816,6 +840,14 @@ var init_approval = __esm({
|
|
|
816
840
|
}
|
|
817
841
|
async requestApproval(request) {
|
|
818
842
|
const moduleId = request.module_id ?? "unknown";
|
|
843
|
+
if (request.requires_approval === false) {
|
|
844
|
+
return { status: "approved", approved_by: "not_required" };
|
|
845
|
+
}
|
|
846
|
+
const moduleDef = request.module_def;
|
|
847
|
+
const annotationsForCheck = moduleDef?.annotations;
|
|
848
|
+
if (annotationsForCheck && annotationsForCheck.requires_approval === false) {
|
|
849
|
+
return { status: "approved", approved_by: "not_required" };
|
|
850
|
+
}
|
|
819
851
|
if (this.autoApprove) {
|
|
820
852
|
return { status: "approved", approved_by: "auto_approve" };
|
|
821
853
|
}
|
|
@@ -852,6 +884,7 @@ var init_approval = __esm({
|
|
|
852
884
|
|
|
853
885
|
// src/output.ts
|
|
854
886
|
import yaml from "js-yaml";
|
|
887
|
+
import { formatCsv, formatJsonl } from "apcore-toolkit";
|
|
855
888
|
function descriptorToScanned(m) {
|
|
856
889
|
const metadata = m.metadata ?? {};
|
|
857
890
|
const display = metadata["display"] ?? null;
|
|
@@ -872,11 +905,6 @@ function descriptorToScanned(m) {
|
|
|
872
905
|
warnings: []
|
|
873
906
|
};
|
|
874
907
|
}
|
|
875
|
-
function csvCellString(value) {
|
|
876
|
-
if (value === null || value === void 0) return "";
|
|
877
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
878
|
-
return String(value);
|
|
879
|
-
}
|
|
880
908
|
function resolveFormat(explicitFormat) {
|
|
881
909
|
if (explicitFormat !== void 0) {
|
|
882
910
|
return explicitFormat;
|
|
@@ -1079,30 +1107,18 @@ function formatExecResult(result, format, fields) {
|
|
|
1079
1107
|
}
|
|
1080
1108
|
const effective = resolveFormat(format);
|
|
1081
1109
|
if (effective === "csv") {
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const header = keys.map(escapeCsvField).join(",");
|
|
1086
|
-
const row = keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
|
|
1087
|
-
process.stdout.write(header + "\n" + row + "\n");
|
|
1088
|
-
} else if (Array.isArray(effective_result) && effective_result.length > 0 && typeof effective_result[0] === "object") {
|
|
1089
|
-
const keys = Object.keys(effective_result[0]);
|
|
1090
|
-
const header = keys.map(escapeCsvField).join(",");
|
|
1091
|
-
const rows = effective_result.map((item) => {
|
|
1092
|
-
const obj = item;
|
|
1093
|
-
return keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
|
|
1094
|
-
});
|
|
1095
|
-
process.stdout.write(header + "\n" + rows.join("\n") + "\n");
|
|
1110
|
+
const rows = toRowsForTabular(effective_result);
|
|
1111
|
+
if (rows !== null) {
|
|
1112
|
+
process.stdout.write(formatCsv(rows));
|
|
1096
1113
|
} else {
|
|
1097
1114
|
process.stdout.write(JSON.stringify(effective_result) + "\n");
|
|
1098
1115
|
}
|
|
1099
1116
|
} else if (effective === "yaml") {
|
|
1100
1117
|
process.stdout.write(yaml.dump(effective_result, { lineWidth: -1 }));
|
|
1101
1118
|
} else if (effective === "jsonl") {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
}
|
|
1119
|
+
const rows = toRowsForTabular(effective_result);
|
|
1120
|
+
if (rows !== null) {
|
|
1121
|
+
process.stdout.write(formatJsonl(rows));
|
|
1106
1122
|
} else {
|
|
1107
1123
|
process.stdout.write(JSON.stringify(effective_result) + "\n");
|
|
1108
1124
|
}
|
|
@@ -1119,11 +1135,19 @@ function formatExecResult(result, format, fields) {
|
|
|
1119
1135
|
process.stdout.write(String(effective_result) + "\n");
|
|
1120
1136
|
}
|
|
1121
1137
|
}
|
|
1122
|
-
function
|
|
1123
|
-
if (value
|
|
1124
|
-
|
|
1138
|
+
function toRowsForTabular(value) {
|
|
1139
|
+
if (value === null || value === void 0) return null;
|
|
1140
|
+
if (Array.isArray(value)) {
|
|
1141
|
+
if (value.length === 0) return null;
|
|
1142
|
+
if (!value.every((item) => typeof item === "object" && item !== null && !Array.isArray(item))) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
return value;
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof value === "object") {
|
|
1148
|
+
return [value];
|
|
1125
1149
|
}
|
|
1126
|
-
return
|
|
1150
|
+
return null;
|
|
1127
1151
|
}
|
|
1128
1152
|
function formatPreflightResult(result, format) {
|
|
1129
1153
|
const resolved = resolveFormat(format);
|
|
@@ -1204,7 +1228,7 @@ var init_output = __esm({
|
|
|
1204
1228
|
"use strict";
|
|
1205
1229
|
init_esm_shims();
|
|
1206
1230
|
init_errors();
|
|
1207
|
-
TOOLKIT_MISSING_HINT = "The 'markdown' and 'skill' output formats require the apcore-toolkit peer dependency. Install with: npm install apcore-toolkit@^0.
|
|
1231
|
+
TOOLKIT_MISSING_HINT = "The 'markdown' and 'skill' output formats require the apcore-toolkit peer dependency. Install with: npm install apcore-toolkit@^0.7";
|
|
1208
1232
|
}
|
|
1209
1233
|
});
|
|
1210
1234
|
|
|
@@ -1235,7 +1259,7 @@ function renderTemplate(template, context) {
|
|
|
1235
1259
|
return result;
|
|
1236
1260
|
}
|
|
1237
1261
|
function registerInitCommand(cli) {
|
|
1238
|
-
const initGroup = cli.command("init").description("Scaffold new
|
|
1262
|
+
const initGroup = cli.command("init").description("Scaffold new modules.");
|
|
1239
1263
|
initGroup.command("module <module-id>").description("Create a new module from a template.\n\nMODULE_ID is the module identifier (e.g., ops.deploy, user.create).").option(
|
|
1240
1264
|
"--style <style>",
|
|
1241
1265
|
"Module style: decorator (@module), convention (plain function), or binding (YAML).",
|
|
@@ -1811,7 +1835,7 @@ function buildProgramManPage(program, progName, version, description, docsUrl2)
|
|
|
1811
1835
|
s.push(".SH ENVIRONMENT");
|
|
1812
1836
|
s.push(".TP");
|
|
1813
1837
|
s.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
|
|
1814
|
-
s.push("Path to the
|
|
1838
|
+
s.push("Path to the extensions directory.");
|
|
1815
1839
|
s.push(".TP");
|
|
1816
1840
|
s.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
|
|
1817
1841
|
s.push("Set to \\fB1\\fR to bypass approval prompts.");
|
|
@@ -1836,7 +1860,7 @@ function buildProgramManPage(program, progName, version, description, docsUrl2)
|
|
|
1836
1860
|
${meaning}`);
|
|
1837
1861
|
}
|
|
1838
1862
|
s.push(".SH SEE ALSO");
|
|
1839
|
-
s.push(`\\fB${progName} \\-\\-help \\-\\-
|
|
1863
|
+
s.push(`\\fB${progName} \\-\\-help \\-\\-all\\-options\\fR for full option list.`);
|
|
1840
1864
|
if (docsUrl2) {
|
|
1841
1865
|
s.push(`.PP
|
|
1842
1866
|
Full documentation at \\fI${roffEscape(docsUrl2)}\\fR`);
|
|
@@ -2243,7 +2267,7 @@ var init_config_encryptor = __esm({
|
|
|
2243
2267
|
_ConfigEncryptor.weakFallbackWarned = true;
|
|
2244
2268
|
}
|
|
2245
2269
|
const hostname2 = os3.hostname();
|
|
2246
|
-
const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
2270
|
+
const username = process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "unknown";
|
|
2247
2271
|
const material = `${hostname2}:${username}`;
|
|
2248
2272
|
return crypto2.pbkdf2Sync(material, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
2249
2273
|
}
|
|
@@ -2275,7 +2299,7 @@ var init_config_encryptor = __esm({
|
|
|
2275
2299
|
const tag = data.subarray(12, 28);
|
|
2276
2300
|
const ct = data.subarray(28);
|
|
2277
2301
|
const hostname2 = os3.hostname();
|
|
2278
|
-
const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
2302
|
+
const username = process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "unknown";
|
|
2279
2303
|
const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
|
|
2280
2304
|
const materials = passphrase ? [passphrase, `${hostname2}:${username}`] : [`${hostname2}:${username}`];
|
|
2281
2305
|
for (const material of materials) {
|
|
@@ -2305,10 +2329,26 @@ var init_auth = __esm({
|
|
|
2305
2329
|
init_config_encryptor();
|
|
2306
2330
|
AuthProvider = class {
|
|
2307
2331
|
config;
|
|
2308
|
-
|
|
2332
|
+
_encryptor;
|
|
2309
2333
|
constructor(config, encryptor) {
|
|
2310
2334
|
this.config = config;
|
|
2311
|
-
this.
|
|
2335
|
+
this._encryptor = encryptor;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Resolve the active ConfigEncryptor instance.
|
|
2339
|
+
*
|
|
2340
|
+
* D11-005 (2026-05-12): three-tier fallback chain matching Python's
|
|
2341
|
+
* `_get_encryptor` (auth.py:33): explicit constructor arg > peer attribute
|
|
2342
|
+
* `config.encryptor` (set by embedders injecting forced-AES test fixtures
|
|
2343
|
+
* or shared instances) > fresh `new ConfigEncryptor()`. Previously TS
|
|
2344
|
+
* skipped the peer-attribute tier, silently giving embedders a different
|
|
2345
|
+
* encryptor than the one they wired on the config.
|
|
2346
|
+
*/
|
|
2347
|
+
getEncryptor() {
|
|
2348
|
+
if (this._encryptor) return this._encryptor;
|
|
2349
|
+
const fromConfig = this.config.encryptor;
|
|
2350
|
+
if (fromConfig) return fromConfig;
|
|
2351
|
+
return new ConfigEncryptor();
|
|
2312
2352
|
}
|
|
2313
2353
|
/**
|
|
2314
2354
|
* Retrieve the API key from the configured sources.
|
|
@@ -2326,11 +2366,11 @@ var init_auth = __esm({
|
|
|
2326
2366
|
const strResult = String(result);
|
|
2327
2367
|
if (strResult.startsWith("keyring:") || strResult.startsWith("enc:")) {
|
|
2328
2368
|
try {
|
|
2329
|
-
return await this.
|
|
2369
|
+
return await this.getEncryptor().retrieve(strResult, "auth.api_key");
|
|
2330
2370
|
} catch (err) {
|
|
2331
2371
|
if (err instanceof ConfigDecryptionError) {
|
|
2332
2372
|
throw new AuthenticationError(
|
|
2333
|
-
"Failed to decrypt stored API key. Re-
|
|
2373
|
+
"Failed to decrypt stored API key. Re-store with 'apcli config set auth.api_key'."
|
|
2334
2374
|
);
|
|
2335
2375
|
}
|
|
2336
2376
|
throw err;
|
|
@@ -2359,7 +2399,7 @@ var init_auth = __esm({
|
|
|
2359
2399
|
}
|
|
2360
2400
|
if (/[\r\n]/.test(key)) {
|
|
2361
2401
|
throw new AuthenticationError(
|
|
2362
|
-
"Malformed API key: contains invalid characters (CR/LF). Re-
|
|
2402
|
+
"Malformed API key: contains invalid characters (CR/LF). Re-store with 'apcli config set auth.api_key'."
|
|
2363
2403
|
);
|
|
2364
2404
|
}
|
|
2365
2405
|
headers.Authorization = `Bearer ${key.trim()}`;
|
|
@@ -2581,7 +2621,7 @@ function registerExecCommand(apcliGroup, registry, executor) {
|
|
|
2581
2621
|
return;
|
|
2582
2622
|
}
|
|
2583
2623
|
let result;
|
|
2584
|
-
if (opts.strategy && executor.callWithTrace) {
|
|
2624
|
+
if ((opts.trace || opts.strategy) && executor.callWithTrace) {
|
|
2585
2625
|
const [res] = await executor.callWithTrace(moduleId, merged, { strategy: opts.strategy });
|
|
2586
2626
|
result = res;
|
|
2587
2627
|
} else {
|
|
@@ -3697,6 +3737,7 @@ __export(main_exports, {
|
|
|
3697
3737
|
reconvertEnumValues: () => reconvertEnumValues,
|
|
3698
3738
|
resolveIntOption: () => resolveIntOption,
|
|
3699
3739
|
resolveStringOption: () => resolveStringOption,
|
|
3740
|
+
setAllOptionsHelp: () => setAllOptionsHelp,
|
|
3700
3741
|
setDocsUrl: () => setDocsUrl,
|
|
3701
3742
|
setVerboseHelp: () => setVerboseHelp,
|
|
3702
3743
|
validateModuleId: () => validateModuleId
|
|
@@ -3705,14 +3746,17 @@ import { readFileSync as readFileSync3 } from "fs";
|
|
|
3705
3746
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3706
3747
|
import * as path5 from "path";
|
|
3707
3748
|
import { Command as Command5, CommanderError, Option as Option4 } from "commander";
|
|
3749
|
+
function setAllOptionsHelp(allOptions) {
|
|
3750
|
+
verboseHelp = allOptions;
|
|
3751
|
+
}
|
|
3708
3752
|
function setVerboseHelp(verbose) {
|
|
3709
|
-
|
|
3753
|
+
setAllOptionsHelp(verbose);
|
|
3710
3754
|
}
|
|
3711
3755
|
function setDocsUrl(url) {
|
|
3712
3756
|
docsUrl = url;
|
|
3713
3757
|
}
|
|
3714
3758
|
function hasVerboseFlag() {
|
|
3715
|
-
return process.argv.includes("--
|
|
3759
|
+
return process.argv.includes("--all-options");
|
|
3716
3760
|
}
|
|
3717
3761
|
function resolveIntOption(cliValue, envValue, defaultValue) {
|
|
3718
3762
|
if (cliValue !== void 0 && Number.isFinite(cliValue) && cliValue > 0) {
|
|
@@ -3787,7 +3831,7 @@ function emitErrorTty(e, exitCode) {
|
|
|
3787
3831
|
Exit code: ${exitCode}
|
|
3788
3832
|
`);
|
|
3789
3833
|
}
|
|
3790
|
-
function createCli(extensionsDirOrOpts, progName,
|
|
3834
|
+
function createCli(extensionsDirOrOpts, progName, allOptions = false) {
|
|
3791
3835
|
let extensionsDir;
|
|
3792
3836
|
let registry;
|
|
3793
3837
|
let executor;
|
|
@@ -3802,7 +3846,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
3802
3846
|
if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
|
|
3803
3847
|
extensionsDir = extensionsDirOrOpts.extensionsDir;
|
|
3804
3848
|
progName = extensionsDirOrOpts.progName ?? progName;
|
|
3805
|
-
|
|
3849
|
+
allOptions = extensionsDirOrOpts.allOptions ?? extensionsDirOrOpts.verbose ?? allOptions;
|
|
3806
3850
|
app = extensionsDirOrOpts.app;
|
|
3807
3851
|
registry = extensionsDirOrOpts.registry;
|
|
3808
3852
|
executor = extensionsDirOrOpts.executor;
|
|
@@ -3816,7 +3860,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
3816
3860
|
} else {
|
|
3817
3861
|
extensionsDir = extensionsDirOrOpts;
|
|
3818
3862
|
}
|
|
3819
|
-
verboseHelp =
|
|
3863
|
+
verboseHelp = allOptions;
|
|
3820
3864
|
registerConfigNamespace();
|
|
3821
3865
|
try {
|
|
3822
3866
|
const auditLogger = new AuditLogger();
|
|
@@ -3849,7 +3893,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
3849
3893
|
}
|
|
3850
3894
|
}
|
|
3851
3895
|
const registryInjected = registry !== void 0;
|
|
3852
|
-
const program = new Command5(resolvedProgName).exitOverride().helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description(appDescription ?? `${resolvedProgName} CLI`).option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--
|
|
3896
|
+
const program = new Command5(resolvedProgName).exitOverride().helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description(appDescription ?? `${resolvedProgName} CLI`).option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--all-options", "Show all options in help output (including built-in options)");
|
|
3853
3897
|
if (appVersion) {
|
|
3854
3898
|
program.version(appVersion, "-V, --version", "Print version");
|
|
3855
3899
|
}
|
|
@@ -3920,7 +3964,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
3920
3964
|
_registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
|
|
3921
3965
|
program.addHelpText("after", [
|
|
3922
3966
|
"",
|
|
3923
|
-
"Use --help --
|
|
3967
|
+
"Use --help --all-options to show all options (including built-in options).",
|
|
3924
3968
|
"Use --help --man to display a formatted man page."
|
|
3925
3969
|
].join("\n"));
|
|
3926
3970
|
configureManHelp(program, resolvedProgName, appVersion ?? VERSION);
|
|
@@ -3957,7 +4001,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
3957
4001
|
function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter) {
|
|
3958
4002
|
const emitUnwiredError = () => {
|
|
3959
4003
|
process.stderr.write(
|
|
3960
|
-
"Error: no
|
|
4004
|
+
"Error: no module registry wired. In standalone mode, pass --extensions-dir <path> to enable module discovery.\n"
|
|
3961
4005
|
);
|
|
3962
4006
|
process.exit(EXIT_CODES.CONFIG_INVALID);
|
|
3963
4007
|
};
|
|
@@ -4071,9 +4115,9 @@ function main(progName) {
|
|
|
4071
4115
|
verboseHelp = hasVerboseFlag();
|
|
4072
4116
|
const program = createCli({
|
|
4073
4117
|
progName,
|
|
4074
|
-
|
|
4118
|
+
allOptions: verboseHelp,
|
|
4075
4119
|
version: VERSION,
|
|
4076
|
-
description: `${progName ?? "apcore-cli"} \u2014 execute
|
|
4120
|
+
description: `${progName ?? "apcore-cli"} \u2014 execute modules from the command line`
|
|
4077
4121
|
});
|
|
4078
4122
|
try {
|
|
4079
4123
|
program.parse(process.argv);
|
|
@@ -4101,7 +4145,17 @@ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdNam
|
|
|
4101
4145
|
if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
|
|
4102
4146
|
try {
|
|
4103
4147
|
resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
|
|
4104
|
-
} catch {
|
|
4148
|
+
} catch (err) {
|
|
4149
|
+
if (err instanceof MaxDepthExceededError || err instanceof CircularRefError) {
|
|
4150
|
+
process.stderr.write(`Error: ${err.message}
|
|
4151
|
+
`);
|
|
4152
|
+
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
4153
|
+
}
|
|
4154
|
+
if (err instanceof UnresolvableRefError) {
|
|
4155
|
+
process.stderr.write(`Error: ${err.message}
|
|
4156
|
+
`);
|
|
4157
|
+
process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);
|
|
4158
|
+
}
|
|
4105
4159
|
resolvedSchema = inputSchema;
|
|
4106
4160
|
}
|
|
4107
4161
|
schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
|
|
@@ -4146,7 +4200,7 @@ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdNam
|
|
|
4146
4200
|
cmd.addOption(approvalTokenOpt);
|
|
4147
4201
|
const footerParts = [];
|
|
4148
4202
|
if (!verbose) {
|
|
4149
|
-
footerParts.push("Use --
|
|
4203
|
+
footerParts.push("Use --all-options to show all options (including built-in options).");
|
|
4150
4204
|
}
|
|
4151
4205
|
if (docsUrl) {
|
|
4152
4206
|
footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
|