@taurgis/bonsai 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/agents/agents/salesforce-docs-researcher.agent.md +17 -4
- package/agents/instructions/salesforce-research.instructions.md +2 -1
- package/bin/cli.mjs +13 -18
- package/dist/base-command.d.ts +59 -12
- package/dist/base-command.js +179 -51
- package/dist/commands/config/base.js +12 -2
- package/dist/commands/config/get.js +8 -12
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/list.js +11 -17
- package/dist/commands/config/set.d.ts +2 -1
- package/dist/commands/config/set.js +35 -28
- package/dist/commands/config/unset.d.ts +2 -1
- package/dist/commands/config/unset.js +26 -25
- package/dist/commands/fetch.d.ts +7 -15
- package/dist/commands/fetch.js +61 -401
- package/dist/commands/import.d.ts +4 -9
- package/dist/commands/import.js +56 -284
- package/dist/commands/inspect.d.ts +7 -1
- package/dist/commands/inspect.js +112 -52
- package/dist/commands/list.d.ts +18 -3
- package/dist/commands/list.js +121 -46
- package/dist/commands/prune.d.ts +6 -1
- package/dist/commands/prune.js +89 -78
- package/dist/commands/status.d.ts +3 -2
- package/dist/commands/status.js +62 -79
- package/dist/hooks/command-not-found/suggest.d.ts +2 -1
- package/dist/hooks/command-not-found/suggest.js +78 -41
- package/dist/lib/argv.d.ts +29 -3
- package/dist/lib/argv.js +147 -54
- package/dist/lib/batch.d.ts +41 -0
- package/dist/lib/batch.js +39 -0
- package/dist/lib/cache-view.d.ts +21 -0
- package/dist/lib/cache-view.js +30 -0
- package/dist/lib/cli-emit.d.ts +28 -0
- package/dist/lib/cli-emit.js +44 -0
- package/dist/lib/cli-error-policy.d.ts +57 -0
- package/dist/lib/cli-error-policy.js +74 -0
- package/dist/lib/cli-flag-manifest.d.ts +12 -0
- package/dist/lib/cli-flag-manifest.js +15 -0
- package/dist/lib/cli-presentation.d.ts +41 -0
- package/dist/lib/cli-presentation.js +53 -0
- package/dist/lib/cli-result-types.d.ts +112 -0
- package/dist/lib/cli-result-types.js +2 -0
- package/dist/lib/config/index.d.ts +23 -5
- package/dist/lib/config/index.js +60 -5
- package/dist/lib/config/io.d.ts +9 -0
- package/dist/lib/config/io.js +71 -21
- package/dist/lib/config/schema.d.ts +2 -0
- package/dist/lib/config/schema.js +7 -0
- package/dist/lib/config-persist.d.ts +28 -0
- package/dist/lib/config-persist.js +29 -0
- package/dist/lib/envelope.d.ts +41 -3
- package/dist/lib/envelope.js +154 -4
- package/dist/lib/help-preflight.d.ts +5 -8
- package/dist/lib/help-preflight.js +13 -32
- package/dist/lib/json-meta.d.ts +0 -2
- package/dist/lib/json-meta.js +21 -25
- package/dist/lib/limit-flag.d.ts +11 -1
- package/dist/lib/limit-flag.js +19 -6
- package/dist/lib/parse-error-ux.d.ts +5 -0
- package/dist/lib/parse-error-ux.js +73 -0
- package/dist/lib/prune-flags.d.ts +30 -0
- package/dist/lib/prune-flags.js +86 -0
- package/dist/lib/research/artifact-index.d.ts +11 -2
- package/dist/lib/research/artifact-index.js +14 -5
- package/dist/lib/research/artifact.js +20 -2
- package/dist/lib/research/browser.d.ts +53 -2
- package/dist/lib/research/browser.js +202 -58
- package/dist/lib/research/capture.d.ts +24 -2
- package/dist/lib/research/capture.js +24 -4
- package/dist/lib/research/cli-io.d.ts +40 -0
- package/dist/lib/research/cli-io.js +27 -0
- package/dist/lib/research/compress.d.ts +7 -0
- package/dist/lib/research/compress.js +7 -3
- package/dist/lib/research/docs/capabilities.d.ts +15 -0
- package/dist/lib/research/docs/capabilities.js +5 -0
- package/dist/lib/research/docs/clean-dom.d.ts +2 -0
- package/dist/lib/research/docs/clean-dom.js +2 -0
- package/dist/lib/research/docs/code-blocks.d.ts +2 -0
- package/dist/lib/research/docs/code-blocks.js +2 -0
- package/dist/lib/research/docs/detect.d.ts +4 -0
- package/dist/lib/research/docs/detect.js +4 -0
- package/dist/lib/research/docs/machine-readable.d.ts +18 -0
- package/dist/lib/research/docs/machine-readable.js +10 -0
- package/dist/lib/research/docs/markdown-source.d.ts +20 -0
- package/dist/lib/research/docs/markdown-source.js +19 -2
- package/dist/lib/research/docs/quality-gates.d.ts +5 -0
- package/dist/lib/research/docs/quality-gates.js +4 -0
- package/dist/lib/research/docs/rsc.d.ts +9 -2
- package/dist/lib/research/docs/rsc.js +5 -2
- package/dist/lib/research/docs/section-artifacts.d.ts +12 -0
- package/dist/lib/research/docs/section-artifacts.js +12 -0
- package/dist/lib/research/docs/sections.d.ts +18 -0
- package/dist/lib/research/docs/sections.js +17 -1
- package/dist/lib/research/docs/source-map.d.ts +28 -0
- package/dist/lib/research/docs/source-map.js +27 -0
- package/dist/lib/research/docs/validate.d.ts +35 -2
- package/dist/lib/research/docs/validate.js +47 -2
- package/dist/lib/research/extract.d.ts +16 -0
- package/dist/lib/research/extract.js +12 -1
- package/dist/lib/research/fetch-command-service.d.ts +36 -0
- package/dist/lib/research/fetch-command-service.js +367 -0
- package/dist/lib/research/fetch-result.d.ts +114 -0
- package/dist/lib/research/fetch-result.js +212 -0
- package/dist/lib/research/fetcher.d.ts +46 -8
- package/dist/lib/research/fetcher.js +61 -31
- package/dist/lib/research/freshness.d.ts +54 -11
- package/dist/lib/research/freshness.js +91 -31
- package/dist/lib/research/import-command-service.d.ts +56 -0
- package/dist/lib/research/import-command-service.js +402 -0
- package/dist/lib/research/keywords.js +5 -1
- package/dist/lib/research/markdown.d.ts +12 -1
- package/dist/lib/research/markdown.js +13 -5
- package/dist/lib/research/metadata-filters.d.ts +18 -0
- package/dist/lib/research/metadata-filters.js +37 -0
- package/dist/lib/research/metadata-flags.d.ts +34 -0
- package/dist/lib/research/metadata-flags.js +45 -0
- package/dist/lib/research/persist-artifact.d.ts +23 -0
- package/dist/lib/research/persist-artifact.js +27 -0
- package/dist/lib/research/prompt-injection.d.ts +9 -0
- package/dist/lib/research/prompt-injection.js +59 -14
- package/dist/lib/research/proxy.d.ts +14 -8
- package/dist/lib/research/proxy.js +14 -10
- package/dist/lib/research/resolve-target.d.ts +2 -0
- package/dist/lib/research/resolve-target.js +1 -1
- package/dist/lib/research/revalidate.d.ts +49 -20
- package/dist/lib/research/revalidate.js +80 -15
- package/dist/lib/research/schema.d.ts +7 -0
- package/dist/lib/research/schema.js +2 -0
- package/dist/lib/research/secret-scan.d.ts +6 -0
- package/dist/lib/research/secret-scan.js +8 -0
- package/dist/lib/research/secure-write.d.ts +4 -0
- package/dist/lib/research/secure-write.js +31 -2
- package/dist/lib/research/storage.d.ts +9 -8
- package/dist/lib/research/storage.js +34 -28
- package/dist/lib/research/summarize.d.ts +15 -0
- package/dist/lib/research/summarize.js +11 -0
- package/dist/lib/research/url.d.ts +9 -6
- package/dist/lib/research/url.js +33 -21
- package/dist/lib/text.d.ts +19 -8
- package/dist/lib/text.js +32 -9
- package/dist/lib/value-flags.d.ts +14 -0
- package/dist/lib/value-flags.js +28 -0
- package/dist/sites/coveo.d.ts +10 -0
- package/dist/sites/coveo.js +10 -0
- package/dist/sites/index.d.ts +16 -0
- package/dist/sites/index.js +16 -4
- package/dist/sites/markdown-twin.d.ts +31 -0
- package/dist/sites/markdown-twin.js +56 -0
- package/dist/sites/salesforce/fetch-page.d.ts +8 -2
- package/dist/sites/salesforce/fetch-page.js +14 -3
- package/dist/sites/salesforce/help-tooling-markdown.d.ts +30 -0
- package/dist/sites/salesforce/help-tooling-markdown.js +74 -0
- package/dist/sites/salesforce/index.d.ts +1 -0
- package/dist/sites/salesforce/index.js +1 -0
- package/dist/sites/salesforce-developer/fetch-page.d.ts +6 -1
- package/dist/sites/salesforce-developer/fetch-page.js +6 -1
- package/dist/sites/salesforce-developer/index.d.ts +1 -0
- package/dist/sites/salesforce-developer/index.js +1 -0
- package/dist/sites/salesforce-developer/route-markdown.d.ts +18 -6
- package/dist/sites/salesforce-developer/route-markdown.js +29 -67
- package/dist/sites/salesforce-doc-fetch.d.ts +39 -5
- package/dist/sites/salesforce-doc-fetch.js +52 -7
- package/dist/sites/salesforce-dom-probe.d.ts +20 -2
- package/dist/sites/salesforce-dom-probe.js +20 -2
- package/dist/sites/types.d.ts +14 -8
- package/dist/sites/types.js +3 -0
- package/oclif.manifest.json +216 -102
- package/package.json +20 -7
- package/agents/agents/official-docs-researcher.agent.md +0 -87
- package/agents/instructions/web-research.instructions.md +0 -44
- package/agents/skills/web-research/SKILL.md +0 -118
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ Common flags:
|
|
|
110
110
|
* `--max-age`: Read-time freshness threshold.
|
|
111
111
|
* `--force`: Force a fresh fetch.
|
|
112
112
|
* `--dry-run`: Scrape and validate without writing to cache.
|
|
113
|
-
* `--allow-stale`:
|
|
113
|
+
* `--allow-stale`: Suppress the exit `5` signal when a within-grace stale entry is served after failed revalidation (see [Freshness and Cache Rules](#freshness-and-cache-rules) below). Stale content is served within the grace window regardless of this flag; it does not extend serving beyond the grace window.
|
|
114
114
|
* `--rendered`: Use browser-rendered extraction for SPA pages.
|
|
115
115
|
* `--read-only` (alias `--plan`): Block all filesystem writes/deletes across every command; network fetches still run. See [Read-only / plan mode](#read-only--plan-mode) below.
|
|
116
116
|
* `--json`: Return structured machine-readable output.
|
|
@@ -141,7 +141,7 @@ npx @taurgis/bonsai inspect <url>
|
|
|
141
141
|
|
|
142
142
|
### `list`
|
|
143
143
|
|
|
144
|
-
List cached artifacts by metadata filters (`--topic`, `--tags`, `--freshness`, `--artifact-type`, `--capture-method`, `--limit`), newest first, without printing content.
|
|
144
|
+
List cached artifacts by metadata filters (`--topic`, `--tags`, `--url`, `--freshness`, `--artifact-type`, `--capture-method`, `--limit`), newest first, without printing content. The default `--json`/`--toon` row is a minimal 4-field shape (`sourceUrls`, `topic`, `freshness`, `tokenEstimate`); pass `--full` for every metadata field. Running `bonsai` with no arguments at all is shorthand for `bonsai list`.
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
147
|
npx @taurgis/bonsai list [flags]
|
|
@@ -149,7 +149,7 @@ npx @taurgis/bonsai list [flags]
|
|
|
149
149
|
|
|
150
150
|
### `prune`
|
|
151
151
|
|
|
152
|
-
Delete old or inactive cache entries. Requires a filter (`--older-than`, `--inactive`, `--artifact-type`, or `--
|
|
152
|
+
Delete old or inactive cache entries. Requires a filter (`--older-than`, `--inactive`, `--artifact-type`, `--url`, `--topic`, or `--tags`) and exactly one of `--dry-run` or `--yes` (passing both is a usage error). In [read-only mode](#read-only--plan-mode), `--yes` is rejected and the safety check is implicitly satisfied by a preview.
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
155
|
npx @taurgis/bonsai prune --older-than 90d --dry-run
|
|
@@ -181,7 +181,7 @@ npx @taurgis/bonsai import https://example.com/docs --file notes.md --read-only
|
|
|
181
181
|
npx @taurgis/bonsai prune --older-than 90d --plan
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
It can also be set via the `BONSAI_READ_ONLY` or `BONSAI_PLAN_MODE` environment variables (both are equivalent). Composition is OR, not override: read-only mode is active if *either* env var or the flag is set, and there is no way to force writes back on once one of them is — this is a safety gate, not a preference. Commands that
|
|
184
|
+
It can also be set via the `BONSAI_READ_ONLY` or `BONSAI_PLAN_MODE` environment variables (both are equivalent). Composition is OR, not override: read-only mode is active if *either* env var or the flag is set, and there is no way to force writes back on once one of them is — this is a safety gate, not a preference. Commands that expose `--dry-run` (`fetch`, `import`, `prune`, `config set`/`unset`) treat global read-only the same as an explicit `--dry-run` and report `dryRun: true` (with `would_*` cache statuses where applicable) instead of persisting.
|
|
185
185
|
|
|
186
186
|
---
|
|
187
187
|
|
|
@@ -235,7 +235,10 @@ If no `--ttl` is specified, freshness is computed from the tier:
|
|
|
235
235
|
| `standard` | 30 days | 14 days | General API docs and developer guides |
|
|
236
236
|
| `stable` | 180 days | 60 days | RFCs, standards, long-lived references |
|
|
237
237
|
|
|
238
|
-
When stale entries still have `ETag` or `Last-Modified` metadata, Bonsai attempts cheap revalidation before a full refetch. If revalidation fails
|
|
238
|
+
When stale entries still have `ETag` or `Last-Modified` metadata, Bonsai attempts cheap revalidation before a full refetch. If revalidation fails:
|
|
239
|
+
|
|
240
|
+
* **Within the grace window**, the stale content is served either way. By default this exits with code `5` so a caller can detect "this result may be outdated"; `--allow-stale` suppresses that signal and exits `0` instead, since the caller has already said outdated content is acceptable.
|
|
241
|
+
* **Past the grace window**, the entry is expired and revalidation failure is always a hard error (exit `1`) — `--allow-stale` has no effect here, since there is no fresher fallback to serve.
|
|
239
242
|
|
|
240
243
|
---
|
|
241
244
|
|
|
@@ -303,7 +306,7 @@ When run with `--json`, commands return a stable envelope:
|
|
|
303
306
|
| `0` | Success. |
|
|
304
307
|
| `1` | Runtime failure (network error, fetch failed, file read error). |
|
|
305
308
|
| `2` | Usage error (invalid flag, bad URL, missing required argument). |
|
|
306
|
-
| `5` | Stale content served —
|
|
309
|
+
| `5` | Stale content served — revalidation failed within the grace window and `--allow-stale` was omitted. The returned content is still usable. |
|
|
307
310
|
|
|
308
311
|
---
|
|
309
312
|
|
|
@@ -5,7 +5,7 @@ model: 'Auto'
|
|
|
5
5
|
tools: ['vscode/askQuestions', 'execute', 'read', 'search', 'web', 'vscode/memory']
|
|
6
6
|
argument-hint: 'What Salesforce topic should I research in official docs?'
|
|
7
7
|
metadata:
|
|
8
|
-
version: '2.
|
|
8
|
+
version: '2.2.0'
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Salesforce Docs Researcher Agent
|
|
@@ -20,7 +20,9 @@ Do not research from memory alone. Training-data knowledge does not satisfy this
|
|
|
20
20
|
|
|
21
21
|
## Invocation
|
|
22
22
|
|
|
23
|
-
Run Bonsai as `npx @taurgis/bonsai ...`. Add `--
|
|
23
|
+
Run Bonsai as `npx @taurgis/bonsai ...`. Add `--toon` when you need structured output at a lower token cost; `--json` is also available if you specifically need JSON (mutually exclusive with `--toon`).
|
|
24
|
+
|
|
25
|
+
If you are operating in a read-only/plan mode (no filesystem writes allowed), add `--read-only` (alias `--plan`) to every Bonsai call, or export `BONSAI_READ_ONLY=1` (or `BONSAI_PLAN_MODE=1`) once for the session — fetches still run, but nothing is persisted to the local cache or config. See "Read-only / Plan Mode" below.
|
|
24
26
|
|
|
25
27
|
## Default Workflow
|
|
26
28
|
|
|
@@ -43,7 +45,7 @@ Run Bonsai as `npx @taurgis/bonsai ...`. Add `--json` when you need structured o
|
|
|
43
45
|
For structured output:
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
|
-
npx @taurgis/bonsai <official-url> --format detailed --
|
|
48
|
+
npx @taurgis/bonsai <official-url> --format detailed --toon
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
## Working with Salesforce Sites
|
|
@@ -68,6 +70,17 @@ Choose the freshness tier based on the source:
|
|
|
68
70
|
|
|
69
71
|
Stale volatile sources must be revalidated before they are trusted. Never overwrite a good cache entry with an error response.
|
|
70
72
|
|
|
73
|
+
## Read-only / Plan Mode
|
|
74
|
+
|
|
75
|
+
Bonsai supports a global `--read-only` flag (alias `--plan`), also honored via the `BONSAI_READ_ONLY`/`BONSAI_PLAN_MODE` environment variables, that blocks all filesystem writes and deletes (cache persistence, config writes, `prune` deletions) while still allowing network fetches to run. Use it whenever you are invoked under a read-only/plan-mode harness so this research workflow stays non-destructive:
|
|
76
|
+
|
|
77
|
+
- Research fetches (`npx @taurgis/bonsai <url>`), including the rendered Salesforce site-module fetches above, still hit the network and return content normally; they just skip writing the result to the local cache.
|
|
78
|
+
- `import` previews the write and reports `dryRun: true` instead of persisting.
|
|
79
|
+
- `config set` / `config unset` preview the write and report `would_set` / `would_unset`.
|
|
80
|
+
- `prune` treats `--read-only` like an implicit dry run; combining it with `--yes` is rejected.
|
|
81
|
+
|
|
82
|
+
Composition is OR: once read-only mode is active (flag or either env var), there is no per-call way to force writes back on.
|
|
83
|
+
|
|
71
84
|
## Fallbacks
|
|
72
85
|
|
|
73
86
|
If direct web access was unavoidable because Bonsai could not fetch the content, import the synthesized notes back into Bonsai before returning:
|
|
@@ -96,4 +109,4 @@ Return:
|
|
|
96
109
|
- Cache status or validation timestamp when Bonsai output provides it.
|
|
97
110
|
- Gaps or uncertainty where official docs do not answer the question.
|
|
98
111
|
|
|
99
|
-
Keep stdout clean when using `--json`; warnings and diagnostic notes belong in stderr or in the final prose summary.
|
|
112
|
+
Keep stdout clean when using `--toon`/`--json`; warnings and diagnostic notes belong in stderr or in the final prose summary.
|
|
@@ -4,7 +4,7 @@ applyTo: '**'
|
|
|
4
4
|
skills:
|
|
5
5
|
- web-research
|
|
6
6
|
metadata:
|
|
7
|
-
version: '2.
|
|
7
|
+
version: '2.1.0'
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Web Research Requirement (Salesforce)
|
|
@@ -29,6 +29,7 @@ metadata:
|
|
|
29
29
|
- **Browse cached entries**: Use `npx @taurgis/bonsai list` when you need to see what is already cached by topic, tags, or freshness.
|
|
30
30
|
- If Bonsai is configured for project storage and `.bonsai/research/` is not ignored by git, treat those cache artifacts as intentional shared project files. It is OK to check them in, and agents must not delete them as incidental generated output without an explicit request.
|
|
31
31
|
- Re-running on a recent topic is cheap — research the topic rather than skipping it to "save" a fetch.
|
|
32
|
+
- If you are operating under a read-only/plan-mode harness, add `--read-only` (alias `--plan`) to Bonsai calls, or rely on `BONSAI_READ_ONLY`/`BONSAI_PLAN_MODE` if the harness sets it — fetches still run, but nothing is persisted to the local cache or config; `config set` / `config unset` preview with `would_set` / `would_unset`.
|
|
32
33
|
|
|
33
34
|
## Working with Salesforce Sites
|
|
34
35
|
|
package/bin/cli.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import updateNotifier from 'update-notifier';
|
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
8
8
|
|
|
9
9
|
import { normalizeArgv } from '../dist/lib/argv.js';
|
|
10
|
+
import { VALUE_TAKING_FLAG_TOKENS, KNOWN_COMMAND_ROOT_TOKENS } from '../dist/lib/cli-flag-manifest.js';
|
|
11
|
+
import { exitWithPreflight } from '../dist/lib/cli-emit.js';
|
|
10
12
|
import { tryUnknownHelpOutput } from '../dist/lib/help-preflight.js';
|
|
11
13
|
import { tryJsonMetaOutput } from '../dist/lib/json-meta.js';
|
|
12
14
|
|
|
@@ -21,36 +23,29 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
21
23
|
// trying to resolve the bare URL as a command — which crashes with a stack trace. Rewrite
|
|
22
24
|
// process.argv itself so both the run path and the help/error path agree.
|
|
23
25
|
const rawArgv = process.argv.slice(2);
|
|
24
|
-
const result = normalizeArgv(rawArgv
|
|
26
|
+
const result = normalizeArgv(rawArgv, {
|
|
27
|
+
valueTakingFlags: VALUE_TAKING_FLAG_TOKENS,
|
|
28
|
+
knownCommandRoots: KNOWN_COMMAND_ROOT_TOKENS,
|
|
29
|
+
});
|
|
25
30
|
const root = __dirname + '/../';
|
|
26
31
|
|
|
27
|
-
if (result.
|
|
28
|
-
|
|
29
|
-
console.log(JSON.stringify(result.exitWithJson.envelope, null, 2));
|
|
30
|
-
process.exit();
|
|
32
|
+
if (result.earlyExit) {
|
|
33
|
+
exitWithPreflight(result.earlyExit);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
const unknownHelp = await tryUnknownHelpOutput(result.argv, root);
|
|
34
37
|
if (unknownHelp) {
|
|
35
|
-
|
|
36
|
-
if (unknownHelp.kind === 'json') {
|
|
37
|
-
console.log(JSON.stringify(unknownHelp.envelope, null, 2));
|
|
38
|
-
} else {
|
|
39
|
-
console.error(` › Error: ${unknownHelp.message.replaceAll('\n', '\n › ')}`);
|
|
40
|
-
}
|
|
41
|
-
process.exit();
|
|
38
|
+
exitWithPreflight(unknownHelp);
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
const jsonMeta = await tryJsonMetaOutput(result.argv, root);
|
|
45
42
|
if (jsonMeta) {
|
|
46
|
-
|
|
47
|
-
console.log(JSON.stringify(jsonMeta.envelope, null, 2));
|
|
48
|
-
process.exit();
|
|
43
|
+
exitWithPreflight({ ...jsonMeta, json: true });
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
// Only notify after the
|
|
52
|
-
// a
|
|
53
|
-
if (!process.argv.includes('--json')) {
|
|
46
|
+
// Only notify after the earlyExit fast-path so the notifier never fires before
|
|
47
|
+
// a usage-error envelope exits. Also skip under --json/--toon so agent callers never see stderr noise.
|
|
48
|
+
if (!process.argv.includes('--json') && !process.argv.includes('--toon')) {
|
|
54
49
|
updateNotifier({ pkg }).notify();
|
|
55
50
|
}
|
|
56
51
|
|
package/dist/base-command.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Command, Interfaces } from '@oclif/core';
|
|
1
|
+
import { Command, Errors, Interfaces } from '@oclif/core';
|
|
2
2
|
import { type ResolveResearchTargetOptions, type ResolvedResearchTarget } from './lib/research/resolve-target.js';
|
|
3
|
+
import { type CliIo } from './lib/research/cli-io.js';
|
|
3
4
|
/**
|
|
4
5
|
* Shared base for every Bonsai command. Enables oclif's native `--json` flag,
|
|
5
6
|
* parses args/flags once in `init()` so commands read `this.args`/`this.flags`
|
|
@@ -16,11 +17,24 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
16
17
|
*/
|
|
17
18
|
static baseFlags: {
|
|
18
19
|
'read-only': Interfaces.BooleanFlag<boolean>;
|
|
20
|
+
toon: Interfaces.BooleanFlag<boolean>;
|
|
19
21
|
};
|
|
20
22
|
flags: Interfaces.InferredFlags<T['flags']>;
|
|
21
23
|
args: Interfaces.InferredArgs<T['args']>;
|
|
22
24
|
parsedArgv: string[];
|
|
23
25
|
init(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Treat `--toon` as machine mode too, so every existing `!this.jsonEnabled()` human-output branch
|
|
28
|
+
* across every command (table rendering, tips, empty-state guidance) is suppressed under `--toon`
|
|
29
|
+
* for free, exactly as it already is under `--json`.
|
|
30
|
+
*/
|
|
31
|
+
jsonEnabled(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Real `--json` keeps oclif's own colorized-JSON rendering untouched (zero regression risk).
|
|
34
|
+
* `--toon` (and no real `--json`) re-encodes the identical envelope as TOON instead — same data,
|
|
35
|
+
* ~40% fewer tokens for callers who opted in.
|
|
36
|
+
*/
|
|
37
|
+
protected logJson(json: unknown): void;
|
|
24
38
|
/**
|
|
25
39
|
* Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
|
|
26
40
|
* but our warnings (secret-redirect, stale-serve, prune failures) are
|
|
@@ -28,6 +42,30 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
28
42
|
* JSON envelope, so machine output stays clean.
|
|
29
43
|
*/
|
|
30
44
|
warn(input: string | Error): string | Error;
|
|
45
|
+
/**
|
|
46
|
+
* Sanitize the human-readable error surface before handing off to oclif. Error text throughout
|
|
47
|
+
* this CLI routinely echoes raw user input back (a rejected URL, `--ttl`, an unknown config key)
|
|
48
|
+
* so the message stays actionable, but that value is untrusted per the repo's trust-boundary
|
|
49
|
+
* rules — the same reasoning that already strips ANSI/control bytes from cached content before
|
|
50
|
+
* terminal render (see `sanitizeForTerminal`). Left unsanitized, a value containing raw escape
|
|
51
|
+
* bytes would replay as a terminal-injection attack the moment the rejected command's own error
|
|
52
|
+
* is printed. `--json` is untouched: `JSON.stringify` already escapes control characters, and the
|
|
53
|
+
* raw value there preserves exact input for programmatic callers.
|
|
54
|
+
*/
|
|
55
|
+
error(input: string | Error, options: {
|
|
56
|
+
code?: string;
|
|
57
|
+
exit: false;
|
|
58
|
+
} & Errors.PrettyPrintableError): void;
|
|
59
|
+
error(input: string | Error, options?: {
|
|
60
|
+
code?: string;
|
|
61
|
+
exit?: number;
|
|
62
|
+
} & Errors.PrettyPrintableError): never;
|
|
63
|
+
/**
|
|
64
|
+
* Human-mode-only contextual next-step suggestion after a successful command, mirroring the
|
|
65
|
+
* "Try this:" pattern already used on errors. A no-op under `--json`/`--toon`: the envelope's
|
|
66
|
+
* `data` is self-describing, so machine callers get no prose.
|
|
67
|
+
*/
|
|
68
|
+
protected tip(message: string): void;
|
|
31
69
|
/** Whether read-only/plan mode is active for this invocation (flag OR either env var). */
|
|
32
70
|
protected get readOnly(): boolean;
|
|
33
71
|
/**
|
|
@@ -36,12 +74,6 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
36
74
|
* write, so a skipped write is never a silent surprise to the caller.
|
|
37
75
|
*/
|
|
38
76
|
protected effectiveDryRun(explicitDryRun: boolean): boolean;
|
|
39
|
-
/**
|
|
40
|
-
* The exit code an error maps to. oclif `CLIError`s carry it in `err.oclif.exit`; other errors may
|
|
41
|
-
* expose `err.exitCode`. Defined once so the process exit code (`catch`) and the JSON envelope
|
|
42
|
-
* (`toErrorJson`) can never disagree — a mismatch between these two was itself the bug this fixes.
|
|
43
|
-
*/
|
|
44
|
-
private static exitCodeOf;
|
|
45
77
|
/**
|
|
46
78
|
* Align the process exit code with the code reported in the JSON envelope. oclif's default
|
|
47
79
|
* `catch` sets `process.exitCode = err.exitCode ?? 1`, but oclif `CLIError`s carry their code in
|
|
@@ -56,6 +88,7 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
56
88
|
};
|
|
57
89
|
exitCode?: number;
|
|
58
90
|
code?: string;
|
|
91
|
+
suggestions?: string[];
|
|
59
92
|
}): Promise<any>;
|
|
60
93
|
/** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
|
|
61
94
|
protected envelopeCommandId(): string;
|
|
@@ -66,16 +99,30 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
66
99
|
* stays INVALID_URL.
|
|
67
100
|
*/
|
|
68
101
|
protected resolveResearchTargetOrFail(url: string, extra?: Pick<ResolveResearchTargetOptions, 'flagOverride' | 'lookup'>): ResolvedResearchTarget;
|
|
69
|
-
/**
|
|
70
|
-
* Single exit point for a URL that failed normalization, shared by every URL-accepting command.
|
|
71
|
-
* The scheme-less message is already self-contained and actionable, so it is surfaced as-is;
|
|
72
|
-
* other parse failures keep the "Invalid URL:" prefix for context.
|
|
73
|
-
*/
|
|
102
|
+
/** Exit for a URL that failed normalization; see {@link failInvalidUrl} for the code contract. */
|
|
74
103
|
protected failInvalidUrl(url: string, message: string): never;
|
|
104
|
+
/** Command context handed to command services (fetch/import) so they stay oclif-free. */
|
|
105
|
+
protected cliIo(): CliIo;
|
|
75
106
|
/** Single source of truth for the `--json` envelope shape, shared by success and error output. */
|
|
76
107
|
private envelope;
|
|
108
|
+
/**
|
|
109
|
+
* Base success envelope from `process.exitCode` (including exit 5 = served stale / ok).
|
|
110
|
+
* Subclasses that overlay batch failures must call this — not `toSuccessJson` — to avoid
|
|
111
|
+
* recursing through their own override.
|
|
112
|
+
*/
|
|
113
|
+
protected baseSuccessJson(data: unknown): Record<string, unknown>;
|
|
77
114
|
/** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
|
|
78
115
|
protected toSuccessJson(data: unknown): Record<string, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* Success overlay for multi-URL read commands (`status`, `inspect`). Apply CACHE_MISS first,
|
|
118
|
+
* then row `.error` so validation failures win when both appear in one batch.
|
|
119
|
+
*/
|
|
120
|
+
protected batchReadSuccessJson(data: unknown): Record<string, unknown>;
|
|
121
|
+
/**
|
|
122
|
+
* Map each URL through `fn`. In a multi-URL batch, per-URL CLIErrors become failure rows so
|
|
123
|
+
* prior hits are kept — matching fetch's INVALID_URL / MISSING_URL_SCHEME batch contract.
|
|
124
|
+
*/
|
|
125
|
+
protected mapUrlsAllowingBatchErrors<T, F>(urls: string[], fn: (url: string) => T, failureRow: (url: string, err: InstanceType<typeof Errors.CLIError>) => F): Array<T | F>;
|
|
79
126
|
/** Mirror the success envelope for failures so JSON consumers get one consistent shape. */
|
|
80
127
|
protected toErrorJson(err: unknown): Record<string, unknown>;
|
|
81
128
|
}
|
package/dist/base-command.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import { Command, Errors, Flags, toConfiguredId } from '@oclif/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Command, Errors, Flags, toConfiguredId, ux } from '@oclif/core';
|
|
2
|
+
import { encode } from '@toon-format/toon';
|
|
3
|
+
import { invalidConfigFileWarnings, invalidEnvOverrideWarnings, resolveReadOnly, } from './lib/config/index.js';
|
|
4
|
+
import { CLI_FLAG_DESCRIPTIONS } from './lib/cli-presentation.js';
|
|
5
|
+
import { formatTip, sanitizeForTerminal } from './lib/text.js';
|
|
6
|
+
import { enrichErrorForDisplay, resolveExitCode, prepareCliError, EXIT_OK, EXIT_STALE_SERVED, } from './lib/cli-error-policy.js';
|
|
7
|
+
import { buildEnvelope, enrichCacheMissEnvelope, enrichRowErrorEnvelope } from './lib/envelope.js';
|
|
8
|
+
import { enrichParseError } from './lib/parse-error-ux.js';
|
|
4
9
|
import { resolveResearchTarget, } from './lib/research/resolve-target.js';
|
|
5
|
-
import {
|
|
10
|
+
import { failInvalidUrl } from './lib/research/cli-io.js';
|
|
11
|
+
/**
|
|
12
|
+
* Mirrors oclif's own `--json` argv-scan (respecting a `--` pass-through boundary) so `--toon`
|
|
13
|
+
* gets identical treatment: present in argv, regardless of parse state.
|
|
14
|
+
*/
|
|
15
|
+
function argvHasFlag(argv, flag) {
|
|
16
|
+
const passThroughIndex = argv.indexOf('--');
|
|
17
|
+
const flagIndex = argv.indexOf(flag);
|
|
18
|
+
return passThroughIndex === -1
|
|
19
|
+
? flagIndex !== -1
|
|
20
|
+
: flagIndex !== -1 && flagIndex < passThroughIndex;
|
|
21
|
+
}
|
|
6
22
|
/**
|
|
7
23
|
* Shared base for every Bonsai command. Enables oclif's native `--json` flag,
|
|
8
24
|
* parses args/flags once in `init()` so commands read `this.args`/`this.flags`
|
|
@@ -21,8 +37,11 @@ export class BaseCommand extends Command {
|
|
|
21
37
|
'read-only': Flags.boolean({
|
|
22
38
|
aliases: ['plan'],
|
|
23
39
|
default: false,
|
|
24
|
-
description:
|
|
25
|
-
|
|
40
|
+
description: CLI_FLAG_DESCRIPTIONS.readOnly,
|
|
41
|
+
}),
|
|
42
|
+
toon: Flags.boolean({
|
|
43
|
+
default: false,
|
|
44
|
+
description: CLI_FLAG_DESCRIPTIONS.toon,
|
|
26
45
|
}),
|
|
27
46
|
};
|
|
28
47
|
flags;
|
|
@@ -40,11 +59,52 @@ export class BaseCommand extends Command {
|
|
|
40
59
|
this.flags = flags;
|
|
41
60
|
this.args = args;
|
|
42
61
|
this.parsedArgv = argv;
|
|
62
|
+
// --json and --toon both claim the machine-output seat; picking one silently over the other
|
|
63
|
+
// would surprise a caller who set both by mistake, so reject the combination outright. Reuses
|
|
64
|
+
// the existing CONFLICTING_FLAGS code (not a new one) — the catalog already documents it as the
|
|
65
|
+
// one code for "choose one of the mutually exclusive options", regardless of which flags.
|
|
66
|
+
if (this.flags?.['json'] && this.flags?.['toon']) {
|
|
67
|
+
this.error('Cannot combine --json and --toon: pick one output format.', {
|
|
68
|
+
exit: 2,
|
|
69
|
+
code: 'CONFLICTING_FLAGS',
|
|
70
|
+
suggestions: ['Pass only one of --json or --toon'],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
43
73
|
// Surface a set-but-invalid BONSAI_* override once per run. Resolution silently drops such a
|
|
44
74
|
// value, so without this a typo'd env var would take no effect with no signal. Warnings go to
|
|
45
75
|
// stderr (even under --json), so machine output stays clean.
|
|
46
76
|
for (const warning of invalidEnvOverrideWarnings(process.env))
|
|
47
77
|
this.warn(warning);
|
|
78
|
+
// Same signal for a corrupted or hand-edited config file: parsing silently degrades to `{}`
|
|
79
|
+
// (or drops just the offending key), which would otherwise mask the problem entirely.
|
|
80
|
+
for (const warning of invalidConfigFileWarnings(this.config.configDir, process.cwd(), this.config.bin)) {
|
|
81
|
+
this.warn(warning);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Treat `--toon` as machine mode too, so every existing `!this.jsonEnabled()` human-output branch
|
|
86
|
+
* across every command (table rendering, tips, empty-state guidance) is suppressed under `--toon`
|
|
87
|
+
* for free, exactly as it already is under `--json`.
|
|
88
|
+
*/
|
|
89
|
+
jsonEnabled() {
|
|
90
|
+
// Mirrors oclif's own enableJsonFlag guard (every command in this CLI sets it, but the base
|
|
91
|
+
// class's contract shouldn't silently break if a future command ever opts out).
|
|
92
|
+
return (super.jsonEnabled() ||
|
|
93
|
+
(Boolean(this.ctor?.enableJsonFlag) && argvHasFlag(this.argv, '--toon')));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Real `--json` keeps oclif's own colorized-JSON rendering untouched (zero regression risk).
|
|
97
|
+
* `--toon` (and no real `--json`) re-encodes the identical envelope as TOON instead — same data,
|
|
98
|
+
* ~40% fewer tokens for callers who opted in.
|
|
99
|
+
*/
|
|
100
|
+
logJson(json) {
|
|
101
|
+
if (super.jsonEnabled()) {
|
|
102
|
+
super.logJson(json);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// this.log() no-ops whenever jsonEnabled() is true (by design, everywhere else in this CLI) —
|
|
106
|
+
// ux.stdout writes directly, matching how oclif's own default logJson bypasses that guard too.
|
|
107
|
+
ux.stdout(encode(json));
|
|
48
108
|
}
|
|
49
109
|
/**
|
|
50
110
|
* Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
|
|
@@ -56,6 +116,35 @@ export class BaseCommand extends Command {
|
|
|
56
116
|
Errors.warn(input);
|
|
57
117
|
return input;
|
|
58
118
|
}
|
|
119
|
+
error(input, options = {}) {
|
|
120
|
+
if (!this.jsonEnabled()) {
|
|
121
|
+
if (typeof input === 'string')
|
|
122
|
+
input = sanitizeForTerminal(input);
|
|
123
|
+
if (options.suggestions) {
|
|
124
|
+
options = { ...options, suggestions: options.suggestions.map(sanitizeForTerminal) };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// `exit: false` is oclif's non-throwing error render (see `errorRow` on the CliIo port and
|
|
128
|
+
// `mapUrlsAllowingBatchErrors`/prune's per-row failures, all of which route through here for the
|
|
129
|
+
// same terminal-injection sanitization above). Destructure `exit` into its own binding so
|
|
130
|
+
// narrowing it to the `false` literal actually narrows the object passed to `super.error()` into
|
|
131
|
+
// each of oclif's two overloads — narrowing a property read alone (`options.exit === false`)
|
|
132
|
+
// doesn't narrow the enclosing object's type, only a local variable's.
|
|
133
|
+
const { exit, ...rest } = options;
|
|
134
|
+
if (exit === false)
|
|
135
|
+
return super.error(input, { ...rest, exit });
|
|
136
|
+
return super.error(input, { ...rest, exit });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Human-mode-only contextual next-step suggestion after a successful command, mirroring the
|
|
140
|
+
* "Try this:" pattern already used on errors. A no-op under `--json`/`--toon`: the envelope's
|
|
141
|
+
* `data` is self-describing, so machine callers get no prose.
|
|
142
|
+
*/
|
|
143
|
+
tip(message) {
|
|
144
|
+
if (this.jsonEnabled())
|
|
145
|
+
return;
|
|
146
|
+
this.warn(formatTip(message));
|
|
147
|
+
}
|
|
59
148
|
/** Whether read-only/plan mode is active for this invocation (flag OR either env var). */
|
|
60
149
|
get readOnly() {
|
|
61
150
|
return resolveReadOnly({ flag: Boolean(this.flags?.['read-only']), env: process.env });
|
|
@@ -71,14 +160,6 @@ export class BaseCommand extends Command {
|
|
|
71
160
|
}
|
|
72
161
|
return explicitDryRun || this.readOnly;
|
|
73
162
|
}
|
|
74
|
-
/**
|
|
75
|
-
* The exit code an error maps to. oclif `CLIError`s carry it in `err.oclif.exit`; other errors may
|
|
76
|
-
* expose `err.exitCode`. Defined once so the process exit code (`catch`) and the JSON envelope
|
|
77
|
-
* (`toErrorJson`) can never disagree — a mismatch between these two was itself the bug this fixes.
|
|
78
|
-
*/
|
|
79
|
-
static exitCodeOf(err) {
|
|
80
|
-
return err?.oclif?.exit ?? err?.exitCode ?? 1;
|
|
81
|
-
}
|
|
82
163
|
/**
|
|
83
164
|
* Align the process exit code with the code reported in the JSON envelope. oclif's default
|
|
84
165
|
* `catch` sets `process.exitCode = err.exitCode ?? 1`, but oclif `CLIError`s carry their code in
|
|
@@ -92,16 +173,10 @@ export class BaseCommand extends Command {
|
|
|
92
173
|
// [UnparsedCommand] warning to stderr even under `--json`. CLIParseError subclasses carry `parse`.
|
|
93
174
|
if (err && typeof err === 'object' && 'parse' in err)
|
|
94
175
|
this.parsed = true;
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (err && typeof err === 'object' && !err.code) {
|
|
100
|
-
const code = stableErrorCodeFrom(err);
|
|
101
|
-
if (code)
|
|
102
|
-
err.code = code;
|
|
103
|
-
}
|
|
104
|
-
process.exitCode = process.exitCode ?? BaseCommand.exitCodeOf(err);
|
|
176
|
+
// Unwrap / fuzzy tips are no-ops when not applicable (including non-Error throws with no message).
|
|
177
|
+
enrichParseError(err);
|
|
178
|
+
enrichErrorForDisplay(err, { bin: this.config.bin, command: this.envelopeCommandId() });
|
|
179
|
+
process.exitCode = process.exitCode ?? resolveExitCode(err);
|
|
105
180
|
return super.catch(err);
|
|
106
181
|
}
|
|
107
182
|
/** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
|
|
@@ -120,6 +195,7 @@ export class BaseCommand extends Command {
|
|
|
120
195
|
configDir: this.config.configDir,
|
|
121
196
|
cwd: process.cwd(),
|
|
122
197
|
dataDir: this.config.dataDir,
|
|
198
|
+
readOnly: this.readOnly,
|
|
123
199
|
url,
|
|
124
200
|
...extra,
|
|
125
201
|
});
|
|
@@ -128,16 +204,23 @@ export class BaseCommand extends Command {
|
|
|
128
204
|
this.failInvalidUrl(url, err.message);
|
|
129
205
|
}
|
|
130
206
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Single exit point for a URL that failed normalization, shared by every URL-accepting command.
|
|
133
|
-
* The scheme-less message is already self-contained and actionable, so it is surfaced as-is;
|
|
134
|
-
* other parse failures keep the "Invalid URL:" prefix for context.
|
|
135
|
-
*/
|
|
207
|
+
/** Exit for a URL that failed normalization; see {@link failInvalidUrl} for the code contract. */
|
|
136
208
|
failInvalidUrl(url, message) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
209
|
+
failInvalidUrl((msg, opts) => this.error(msg, opts), url, message);
|
|
210
|
+
}
|
|
211
|
+
/** Command context handed to command services (fetch/import) so they stay oclif-free. */
|
|
212
|
+
cliIo() {
|
|
213
|
+
return {
|
|
214
|
+
bin: this.config.bin,
|
|
215
|
+
configDir: this.config.configDir,
|
|
216
|
+
dataDir: this.config.dataDir,
|
|
217
|
+
cwd: process.cwd(),
|
|
218
|
+
json: this.jsonEnabled(),
|
|
219
|
+
warn: (msg) => void this.warn(msg),
|
|
220
|
+
log: (msg) => this.log(msg),
|
|
221
|
+
error: (msg, opts) => this.error(msg, opts),
|
|
222
|
+
errorRow: (msg, opts) => this.error(msg, { ...opts, exit: false }),
|
|
223
|
+
};
|
|
141
224
|
}
|
|
142
225
|
/** Single source of truth for the `--json` envelope shape, shared by success and error output. */
|
|
143
226
|
envelope(parts) {
|
|
@@ -149,33 +232,78 @@ export class BaseCommand extends Command {
|
|
|
149
232
|
data: parts.data,
|
|
150
233
|
});
|
|
151
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Base success envelope from `process.exitCode` (including exit 5 = served stale / ok).
|
|
237
|
+
* Subclasses that overlay batch failures must call this — not `toSuccessJson` — to avoid
|
|
238
|
+
* recursing through their own override.
|
|
239
|
+
*/
|
|
240
|
+
baseSuccessJson(data) {
|
|
241
|
+
// ponytail: the stale-serve path signals EXIT_STALE_SERVED by setting process.exitCode inside
|
|
242
|
+
// run() (e.g. fetch's handleStaleRevalidationResult); this reads it back to mark the envelope.
|
|
243
|
+
// If that signalling moves off process.exitCode, update here too. Number() normalizes Node's
|
|
244
|
+
// string codes.
|
|
245
|
+
const exitCode = Number(process.exitCode ?? EXIT_OK);
|
|
246
|
+
// EXIT_STALE_SERVED means "served stale" — a successful, usable result, so it reports ok.
|
|
247
|
+
return this.envelope({
|
|
248
|
+
ok: exitCode === EXIT_OK || exitCode === EXIT_STALE_SERVED,
|
|
249
|
+
exitCode,
|
|
250
|
+
stderr: '',
|
|
251
|
+
data,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
152
254
|
/** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
|
|
153
255
|
toSuccessJson(data) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
256
|
+
return this.baseSuccessJson(data);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Success overlay for multi-URL read commands (`status`, `inspect`). Apply CACHE_MISS first,
|
|
260
|
+
* then row `.error` so validation failures win when both appear in one batch.
|
|
261
|
+
*/
|
|
262
|
+
batchReadSuccessJson(data) {
|
|
263
|
+
return enrichRowErrorEnvelope(enrichCacheMissEnvelope(this.baseSuccessJson(data), data, this.config.bin), data);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Map each URL through `fn`. In a multi-URL batch, per-URL CLIErrors become failure rows so
|
|
267
|
+
* prior hits are kept — matching fetch's INVALID_URL / MISSING_URL_SCHEME batch contract.
|
|
268
|
+
*/
|
|
269
|
+
mapUrlsAllowingBatchErrors(urls, fn, failureRow) {
|
|
270
|
+
const batch = urls.length > 1;
|
|
271
|
+
const results = [];
|
|
272
|
+
for (const url of urls) {
|
|
273
|
+
try {
|
|
274
|
+
results.push(fn(url));
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
if (!batch || !(err instanceof Errors.CLIError))
|
|
278
|
+
throw err;
|
|
279
|
+
// Render with the same "Error:" formatting as a single-URL failure (this.error's exit:false
|
|
280
|
+
// form prints the CLIError's own message/Code/Try this and returns instead of throwing) rather
|
|
281
|
+
// than this.warn, since a row failure still fails the command overall (status/inspect batch
|
|
282
|
+
// exit code, JSON ok:false) and should never read as merely a warning.
|
|
283
|
+
if (!this.jsonEnabled())
|
|
284
|
+
this.error(err, { exit: false });
|
|
285
|
+
results.push(failureRow(url, err));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return results;
|
|
160
289
|
}
|
|
161
290
|
/** Mirror the success envelope for failures so JSON consumers get one consistent shape. */
|
|
162
291
|
toErrorJson(err) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
: String(err);
|
|
292
|
+
const prepared = prepareCliError(err, {
|
|
293
|
+
bin: this.config.bin,
|
|
294
|
+
command: this.envelopeCommandId(),
|
|
295
|
+
});
|
|
296
|
+
// oclif prints the returned object to stdout; error text lives in envelope.stderr only
|
|
297
|
+
// so process stderr stays clean under `--json` (CACHE_MISS / batch overlays + oclif JSON log suppression).
|
|
170
298
|
return buildEnvelope({
|
|
171
299
|
command: this.envelopeCommandId(),
|
|
172
300
|
ok: false,
|
|
173
|
-
exitCode,
|
|
174
|
-
stderr,
|
|
301
|
+
exitCode: prepared.exitCode,
|
|
302
|
+
stderr: prepared.stderr,
|
|
175
303
|
data: null,
|
|
176
|
-
code,
|
|
177
|
-
suggestions:
|
|
178
|
-
ref:
|
|
304
|
+
code: prepared.code,
|
|
305
|
+
suggestions: prepared.suggestions,
|
|
306
|
+
ref: prepared.ref,
|
|
179
307
|
});
|
|
180
308
|
}
|
|
181
309
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Flags } from '@oclif/core';
|
|
1
|
+
import { Flags, toConfiguredId } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../base-command.js';
|
|
3
3
|
import { isKnownKey, suggestKey, validKeysHint } from '../../lib/config/index.js';
|
|
4
4
|
/**
|
|
@@ -11,6 +11,7 @@ export class ConfigCommand extends BaseCommand {
|
|
|
11
11
|
this.error('--global and --local are mutually exclusive.', {
|
|
12
12
|
exit: 2,
|
|
13
13
|
code: 'CONFLICTING_FLAGS',
|
|
14
|
+
suggestions: ['Pass --global or --local, not both.'],
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -30,15 +31,24 @@ export class ConfigCommand extends BaseCommand {
|
|
|
30
31
|
if (!isKnownKey(keyArg)) {
|
|
31
32
|
const suggestion = suggestKey(keyArg);
|
|
32
33
|
const hint = suggestion ? ` Did you mean: ${suggestion}?` : '';
|
|
34
|
+
const command = this.ctor.id ? toConfiguredId(this.ctor.id, this.config) : 'config';
|
|
33
35
|
this.error(`Unknown config key: "${keyArg}".${hint} Valid keys: ${validKeysHint()}.`, {
|
|
34
36
|
exit: 2,
|
|
35
37
|
code: 'UNKNOWN_KEY',
|
|
38
|
+
suggestions: suggestion
|
|
39
|
+
? [`Use "${suggestion}": ${this.config.bin} ${command} ${suggestion}`]
|
|
40
|
+
: [`Use one of: ${validKeysHint()}`],
|
|
36
41
|
});
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
requireConfigKey(key) {
|
|
40
45
|
if (!key) {
|
|
41
|
-
this.
|
|
46
|
+
const command = this.ctor.id ? toConfiguredId(this.ctor.id, this.config) : 'config';
|
|
47
|
+
this.error('Missing required argument: key', {
|
|
48
|
+
exit: 2,
|
|
49
|
+
code: 'MISSING_ARGUMENT',
|
|
50
|
+
suggestions: [`Choose a key: ${this.config.bin} ${command} storage`],
|
|
51
|
+
});
|
|
42
52
|
}
|
|
43
53
|
this.assertKnownKey(key);
|
|
44
54
|
}
|