@taurgis/bonsai 3.1.0 → 3.2.2
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 +21 -4
- package/agents/agents/salesforce-docs-researcher.agent.md +14 -1
- package/agents/instructions/salesforce-research.instructions.md +2 -1
- package/bin/cli.mjs +9 -17
- package/dist/base-command.d.ts +37 -10
- package/dist/base-command.js +113 -46
- package/dist/commands/config/base.js +11 -2
- package/dist/commands/config/get.js +8 -12
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/list.js +10 -16
- package/dist/commands/config/set.d.ts +2 -1
- package/dist/commands/config/set.js +34 -27
- package/dist/commands/config/unset.d.ts +2 -1
- package/dist/commands/config/unset.js +25 -24
- package/dist/commands/fetch.d.ts +7 -15
- package/dist/commands/fetch.js +58 -401
- package/dist/commands/import.d.ts +4 -9
- package/dist/commands/import.js +56 -280
- package/dist/commands/inspect.d.ts +7 -1
- package/dist/commands/inspect.js +103 -52
- package/dist/commands/list.d.ts +13 -1
- package/dist/commands/list.js +79 -32
- package/dist/commands/prune.d.ts +4 -1
- package/dist/commands/prune.js +56 -65
- package/dist/commands/status.d.ts +3 -2
- package/dist/commands/status.js +52 -79
- package/dist/hooks/command-not-found/suggest.d.ts +1 -0
- package/dist/hooks/command-not-found/suggest.js +66 -39
- package/dist/lib/argv.d.ts +24 -3
- package/dist/lib/argv.js +124 -53
- 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 +5 -0
- package/dist/lib/cli-flag-manifest.js +8 -0
- package/dist/lib/cli-presentation.d.ts +39 -0
- package/dist/lib/cli-presentation.js +50 -0
- package/dist/lib/cli-result-types.d.ts +88 -0
- package/dist/lib/cli-result-types.js +2 -0
- package/dist/lib/config/index.d.ts +22 -4
- package/dist/lib/config/index.js +59 -4
- package/dist/lib/config/resolve.d.ts +21 -0
- package/dist/lib/config/resolve.js +55 -6
- 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 +147 -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 +28 -0
- package/dist/lib/prune-flags.js +74 -0
- package/dist/lib/research/artifact-index.d.ts +11 -2
- package/dist/lib/research/artifact-index.js +7 -4
- package/dist/lib/research/browser.d.ts +41 -2
- package/dist/lib/research/browser.js +139 -50
- 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 +33 -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 +310 -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 +58 -30
- 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 +381 -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/persist-artifact.d.ts +23 -0
- package/dist/lib/research/persist-artifact.js +27 -0
- package/dist/lib/research/prompt-injection.d.ts +8 -0
- package/dist/lib/research/prompt-injection.js +42 -13
- package/dist/lib/research/proxy.d.ts +14 -8
- package/dist/lib/research/proxy.js +14 -10
- 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 +11 -1
- package/dist/lib/research/secure-write.js +38 -7
- package/dist/lib/research/storage.d.ts +5 -1
- package/dist/lib/research/storage.js +8 -3
- 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 +7 -2
- package/dist/lib/text.js +13 -2
- 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 +192 -87
- package/package.json +19 -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,8 +110,9 @@ 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
|
+
* `--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.
|
|
115
116
|
* `--json`: Return structured machine-readable output.
|
|
116
117
|
|
|
117
118
|
### `import [url]`
|
|
@@ -148,7 +149,7 @@ npx @taurgis/bonsai list [flags]
|
|
|
148
149
|
|
|
149
150
|
### `prune`
|
|
150
151
|
|
|
151
|
-
Delete old or inactive cache entries. Requires a filter (`--older-than`, `--inactive`, `--artifact-type`, or `--url`) and exactly one of `--dry-run` or `--yes` (passing both is a usage error).
|
|
152
|
+
Delete old or inactive cache entries. Requires a filter (`--older-than`, `--inactive`, `--artifact-type`, or `--url`) 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.
|
|
152
153
|
|
|
153
154
|
```bash
|
|
154
155
|
npx @taurgis/bonsai prune --older-than 90d --dry-run
|
|
@@ -170,6 +171,18 @@ npx @taurgis/bonsai config set summary balanced
|
|
|
170
171
|
|
|
171
172
|
Both keys can also be overridden per-invocation via the `BONSAI_STORAGE` and `BONSAI_SUMMARY` environment variables. Project config is stored in `.bonsai.json`; project cache files are stored under `.bonsai/research/`.
|
|
172
173
|
|
|
174
|
+
### Read-only / plan mode
|
|
175
|
+
|
|
176
|
+
Every command accepts a global `--read-only` flag (alias `--plan`) that blocks all filesystem writes and deletes — cache persistence, config file writes, `prune` deletions — while still allowing network fetches to run. This is for agent harnesses (Claude Code and others) that enter a read-only "plan mode": set it once and every `bonsai` invocation for the rest of that session automatically skips writes, without needing the flag on every call.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
BONSAI_READ_ONLY=1 npx @taurgis/bonsai https://example.com/docs
|
|
180
|
+
npx @taurgis/bonsai import https://example.com/docs --file notes.md --read-only
|
|
181
|
+
npx @taurgis/bonsai prune --older-than 90d --plan
|
|
182
|
+
```
|
|
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 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
|
+
|
|
173
186
|
---
|
|
174
187
|
|
|
175
188
|
## Shell Autocomplete
|
|
@@ -222,7 +235,10 @@ If no `--ttl` is specified, freshness is computed from the tier:
|
|
|
222
235
|
| `standard` | 30 days | 14 days | General API docs and developer guides |
|
|
223
236
|
| `stable` | 180 days | 60 days | RFCs, standards, long-lived references |
|
|
224
237
|
|
|
225
|
-
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.
|
|
226
242
|
|
|
227
243
|
---
|
|
228
244
|
|
|
@@ -273,6 +289,7 @@ When run with `--json`, commands return a stable envelope:
|
|
|
273
289
|
| --- | --- |
|
|
274
290
|
| `BONSAI_STORAGE` | Override the default storage location per-invocation (`global` or `project`). |
|
|
275
291
|
| `BONSAI_SUMMARY` | Override the summary compression level per-invocation (`conservative`, `balanced`, or `aggressive`). |
|
|
292
|
+
| `BONSAI_READ_ONLY` / `BONSAI_PLAN_MODE` | Set to `1`/`true`/`yes` to enable [read-only/plan mode](#read-only--plan-mode) for every command in the session, equivalent to passing `--read-only` (`--plan`) each time. Either variable is sufficient; there is no way to override it back off from a single invocation. |
|
|
276
293
|
| `NO_COLOR` | Set to any non-empty value to disable all ANSI color output. An empty `NO_COLOR` is treated as unset. |
|
|
277
294
|
| `FORCE_COLOR` | Set to any value other than an empty string, `0`, or `false` to force ANSI color on, even in non-TTY environments. |
|
|
278
295
|
| `TERM=dumb` | Disables ANSI color output when the terminal cannot render escape sequences. |
|
|
@@ -289,7 +306,7 @@ When run with `--json`, commands return a stable envelope:
|
|
|
289
306
|
| `0` | Success. |
|
|
290
307
|
| `1` | Runtime failure (network error, fetch failed, file read error). |
|
|
291
308
|
| `2` | Usage error (invalid flag, bad URL, missing required argument). |
|
|
292
|
-
| `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. |
|
|
293
310
|
|
|
294
311
|
---
|
|
295
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.1.0'
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Salesforce Docs Researcher Agent
|
|
@@ -22,6 +22,8 @@ Do not research from memory alone. Training-data knowledge does not satisfy this
|
|
|
22
22
|
|
|
23
23
|
Run Bonsai as `npx @taurgis/bonsai ...`. Add `--json` when you need structured output.
|
|
24
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.
|
|
26
|
+
|
|
25
27
|
## Default Workflow
|
|
26
28
|
|
|
27
29
|
1. Identify the product, version, edition, and source authority needed for the request.
|
|
@@ -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:
|
|
@@ -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 } 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,35 +23,25 @@ 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, { valueTakingFlags: VALUE_TAKING_FLAG_TOKENS });
|
|
25
27
|
const root = __dirname + '/../';
|
|
26
28
|
|
|
27
|
-
if (result.
|
|
28
|
-
|
|
29
|
-
console.log(JSON.stringify(result.exitWithJson.envelope, null, 2));
|
|
30
|
-
process.exit();
|
|
29
|
+
if (result.earlyExit) {
|
|
30
|
+
exitWithPreflight(result.earlyExit);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const unknownHelp = await tryUnknownHelpOutput(result.argv, root);
|
|
34
34
|
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();
|
|
35
|
+
exitWithPreflight(unknownHelp);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
const jsonMeta = await tryJsonMetaOutput(result.argv, root);
|
|
45
39
|
if (jsonMeta) {
|
|
46
|
-
|
|
47
|
-
console.log(JSON.stringify(jsonMeta.envelope, null, 2));
|
|
48
|
-
process.exit();
|
|
40
|
+
exitWithPreflight({ ...jsonMeta, json: true });
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
// Only notify after the
|
|
52
|
-
// a
|
|
43
|
+
// Only notify after the earlyExit fast-path so the notifier never fires before
|
|
44
|
+
// a usage-error envelope exits. Also skip under --json so agent callers never see stderr noise.
|
|
53
45
|
if (!process.argv.includes('--json')) {
|
|
54
46
|
updateNotifier({ pkg }).notify();
|
|
55
47
|
}
|
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`
|
|
@@ -8,6 +9,15 @@ import { type ResolveResearchTargetOptions, type ResolvedResearchTarget } from '
|
|
|
8
9
|
*/
|
|
9
10
|
export declare abstract class BaseCommand<T extends typeof Command> extends Command {
|
|
10
11
|
static enableJsonFlag: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Flags shared by every command. Passed as `baseFlags` to `this.parse()` in `init()` below, which
|
|
14
|
+
* is oclif's own mechanism for merging them into `this.ctor.flags` at both parse time and
|
|
15
|
+
* `--help`/manifest generation — no per-command `static flags` changes are needed to expose these.
|
|
16
|
+
* `--plan` mirrors agent-harness "plan mode" terminology; `--read-only` is the canonical name.
|
|
17
|
+
*/
|
|
18
|
+
static baseFlags: {
|
|
19
|
+
'read-only': Interfaces.BooleanFlag<boolean>;
|
|
20
|
+
};
|
|
11
21
|
flags: Interfaces.InferredFlags<T['flags']>;
|
|
12
22
|
args: Interfaces.InferredArgs<T['args']>;
|
|
13
23
|
parsedArgv: string[];
|
|
@@ -19,12 +29,14 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
19
29
|
* JSON envelope, so machine output stays clean.
|
|
20
30
|
*/
|
|
21
31
|
warn(input: string | Error): string | Error;
|
|
32
|
+
/** Whether read-only/plan mode is active for this invocation (flag OR either env var). */
|
|
33
|
+
protected get readOnly(): boolean;
|
|
22
34
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
35
|
+
* Combines a command's own explicit dry-run intent with global read-only mode. Warns once (on
|
|
36
|
+
* stderr, so it never pollutes `--json`) when read-only mode is what's actually suppressing the
|
|
37
|
+
* write, so a skipped write is never a silent surprise to the caller.
|
|
26
38
|
*/
|
|
27
|
-
|
|
39
|
+
protected effectiveDryRun(explicitDryRun: boolean): boolean;
|
|
28
40
|
/**
|
|
29
41
|
* Align the process exit code with the code reported in the JSON envelope. oclif's default
|
|
30
42
|
* `catch` sets `process.exitCode = err.exitCode ?? 1`, but oclif `CLIError`s carry their code in
|
|
@@ -39,6 +51,7 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
39
51
|
};
|
|
40
52
|
exitCode?: number;
|
|
41
53
|
code?: string;
|
|
54
|
+
suggestions?: string[];
|
|
42
55
|
}): Promise<any>;
|
|
43
56
|
/** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
|
|
44
57
|
protected envelopeCommandId(): string;
|
|
@@ -49,16 +62,30 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
|
|
|
49
62
|
* stays INVALID_URL.
|
|
50
63
|
*/
|
|
51
64
|
protected resolveResearchTargetOrFail(url: string, extra?: Pick<ResolveResearchTargetOptions, 'flagOverride' | 'lookup'>): ResolvedResearchTarget;
|
|
52
|
-
/**
|
|
53
|
-
* Single exit point for a URL that failed normalization, shared by every URL-accepting command.
|
|
54
|
-
* The scheme-less message is already self-contained and actionable, so it is surfaced as-is;
|
|
55
|
-
* other parse failures keep the "Invalid URL:" prefix for context.
|
|
56
|
-
*/
|
|
65
|
+
/** Exit for a URL that failed normalization; see {@link failInvalidUrl} for the code contract. */
|
|
57
66
|
protected failInvalidUrl(url: string, message: string): never;
|
|
67
|
+
/** Command context handed to command services (fetch/import) so they stay oclif-free. */
|
|
68
|
+
protected cliIo(): CliIo;
|
|
58
69
|
/** Single source of truth for the `--json` envelope shape, shared by success and error output. */
|
|
59
70
|
private envelope;
|
|
71
|
+
/**
|
|
72
|
+
* Base success envelope from `process.exitCode` (including exit 5 = served stale / ok).
|
|
73
|
+
* Subclasses that overlay batch failures must call this — not `toSuccessJson` — to avoid
|
|
74
|
+
* recursing through their own override.
|
|
75
|
+
*/
|
|
76
|
+
protected baseSuccessJson(data: unknown): Record<string, unknown>;
|
|
60
77
|
/** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
|
|
61
78
|
protected toSuccessJson(data: unknown): Record<string, unknown>;
|
|
79
|
+
/**
|
|
80
|
+
* Success overlay for multi-URL read commands (`status`, `inspect`). Apply CACHE_MISS first,
|
|
81
|
+
* then row `.error` so validation failures win when both appear in one batch.
|
|
82
|
+
*/
|
|
83
|
+
protected batchReadSuccessJson(data: unknown): Record<string, unknown>;
|
|
84
|
+
/**
|
|
85
|
+
* Map each URL through `fn`. In a multi-URL batch, per-URL CLIErrors become failure rows so
|
|
86
|
+
* prior hits are kept — matching fetch's INVALID_URL / MISSING_URL_SCHEME batch contract.
|
|
87
|
+
*/
|
|
88
|
+
protected mapUrlsAllowingBatchErrors<T, F>(urls: string[], fn: (url: string) => T, failureRow: (url: string, err: InstanceType<typeof Errors.CLIError>) => F): Array<T | F>;
|
|
62
89
|
/** Mirror the success envelope for failures so JSON consumers get one consistent shape. */
|
|
63
90
|
protected toErrorJson(err: unknown): Record<string, unknown>;
|
|
64
91
|
}
|
package/dist/base-command.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { Command, Errors, toConfiguredId } from '@oclif/core';
|
|
2
|
-
import { invalidEnvOverrideWarnings } from './lib/config/index.js';
|
|
3
|
-
import {
|
|
1
|
+
import { Command, Errors, Flags, toConfiguredId } from '@oclif/core';
|
|
2
|
+
import { invalidEnvOverrideWarnings, resolveReadOnly } from './lib/config/index.js';
|
|
3
|
+
import { CLI_FLAG_DESCRIPTIONS } from './lib/cli-presentation.js';
|
|
4
|
+
import { enrichErrorForDisplay, resolveExitCode, prepareCliError, EXIT_OK, EXIT_STALE_SERVED, } from './lib/cli-error-policy.js';
|
|
5
|
+
import { buildEnvelope, enrichCacheMissEnvelope, enrichRowErrorEnvelope, formatErrorForJson, } from './lib/envelope.js';
|
|
6
|
+
import { enrichParseError } from './lib/parse-error-ux.js';
|
|
4
7
|
import { resolveResearchTarget, } from './lib/research/resolve-target.js';
|
|
5
|
-
import {
|
|
8
|
+
import { failInvalidUrl } from './lib/research/cli-io.js';
|
|
6
9
|
/**
|
|
7
10
|
* Shared base for every Bonsai command. Enables oclif's native `--json` flag,
|
|
8
11
|
* parses args/flags once in `init()` so commands read `this.args`/`this.flags`
|
|
@@ -11,6 +14,19 @@ import { looksLikeSchemelessUrl } from './lib/research/url.js';
|
|
|
11
14
|
*/
|
|
12
15
|
export class BaseCommand extends Command {
|
|
13
16
|
static enableJsonFlag = true;
|
|
17
|
+
/**
|
|
18
|
+
* Flags shared by every command. Passed as `baseFlags` to `this.parse()` in `init()` below, which
|
|
19
|
+
* is oclif's own mechanism for merging them into `this.ctor.flags` at both parse time and
|
|
20
|
+
* `--help`/manifest generation — no per-command `static flags` changes are needed to expose these.
|
|
21
|
+
* `--plan` mirrors agent-harness "plan mode" terminology; `--read-only` is the canonical name.
|
|
22
|
+
*/
|
|
23
|
+
static baseFlags = {
|
|
24
|
+
'read-only': Flags.boolean({
|
|
25
|
+
aliases: ['plan'],
|
|
26
|
+
default: false,
|
|
27
|
+
description: CLI_FLAG_DESCRIPTIONS.readOnly,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
14
30
|
flags;
|
|
15
31
|
args;
|
|
16
32
|
parsedArgv;
|
|
@@ -18,6 +34,7 @@ export class BaseCommand extends Command {
|
|
|
18
34
|
await super.init();
|
|
19
35
|
const { args, flags, argv } = await this.parse({
|
|
20
36
|
flags: this.ctor.flags,
|
|
37
|
+
baseFlags: this.ctor.baseFlags,
|
|
21
38
|
enableJsonFlag: this.ctor.enableJsonFlag,
|
|
22
39
|
args: this.ctor.args,
|
|
23
40
|
strict: this.ctor.strict,
|
|
@@ -41,13 +58,20 @@ export class BaseCommand extends Command {
|
|
|
41
58
|
Errors.warn(input);
|
|
42
59
|
return input;
|
|
43
60
|
}
|
|
61
|
+
/** Whether read-only/plan mode is active for this invocation (flag OR either env var). */
|
|
62
|
+
get readOnly() {
|
|
63
|
+
return resolveReadOnly({ flag: Boolean(this.flags?.['read-only']), env: process.env });
|
|
64
|
+
}
|
|
44
65
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
66
|
+
* Combines a command's own explicit dry-run intent with global read-only mode. Warns once (on
|
|
67
|
+
* stderr, so it never pollutes `--json`) when read-only mode is what's actually suppressing the
|
|
68
|
+
* write, so a skipped write is never a silent surprise to the caller.
|
|
48
69
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
effectiveDryRun(explicitDryRun) {
|
|
71
|
+
if (this.readOnly && !explicitDryRun) {
|
|
72
|
+
this.warn('Read-only mode active (--read-only/--plan or BONSAI_READ_ONLY/BONSAI_PLAN_MODE): skipping filesystem writes.');
|
|
73
|
+
}
|
|
74
|
+
return explicitDryRun || this.readOnly;
|
|
51
75
|
}
|
|
52
76
|
/**
|
|
53
77
|
* Align the process exit code with the code reported in the JSON envelope. oclif's default
|
|
@@ -62,16 +86,10 @@ export class BaseCommand extends Command {
|
|
|
62
86
|
// [UnparsedCommand] warning to stderr even under `--json`. CLIParseError subclasses carry `parse`.
|
|
63
87
|
if (err && typeof err === 'object' && 'parse' in err)
|
|
64
88
|
this.parsed = true;
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (err && typeof err === 'object' && !err.code) {
|
|
70
|
-
const code = stableErrorCodeFrom(err);
|
|
71
|
-
if (code)
|
|
72
|
-
err.code = code;
|
|
73
|
-
}
|
|
74
|
-
process.exitCode = process.exitCode ?? BaseCommand.exitCodeOf(err);
|
|
89
|
+
// Unwrap / fuzzy tips are no-ops when not applicable (including non-Error throws with no message).
|
|
90
|
+
enrichParseError(err);
|
|
91
|
+
enrichErrorForDisplay(err, { bin: this.config.bin, command: this.envelopeCommandId() });
|
|
92
|
+
process.exitCode = process.exitCode ?? resolveExitCode(err);
|
|
75
93
|
return super.catch(err);
|
|
76
94
|
}
|
|
77
95
|
/** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
|
|
@@ -98,16 +116,22 @@ export class BaseCommand extends Command {
|
|
|
98
116
|
this.failInvalidUrl(url, err.message);
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Single exit point for a URL that failed normalization, shared by every URL-accepting command.
|
|
103
|
-
* The scheme-less message is already self-contained and actionable, so it is surfaced as-is;
|
|
104
|
-
* other parse failures keep the "Invalid URL:" prefix for context.
|
|
105
|
-
*/
|
|
119
|
+
/** Exit for a URL that failed normalization; see {@link failInvalidUrl} for the code contract. */
|
|
106
120
|
failInvalidUrl(url, message) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
failInvalidUrl((msg, opts) => this.error(msg, opts), url, message);
|
|
122
|
+
}
|
|
123
|
+
/** Command context handed to command services (fetch/import) so they stay oclif-free. */
|
|
124
|
+
cliIo() {
|
|
125
|
+
return {
|
|
126
|
+
bin: this.config.bin,
|
|
127
|
+
configDir: this.config.configDir,
|
|
128
|
+
dataDir: this.config.dataDir,
|
|
129
|
+
cwd: process.cwd(),
|
|
130
|
+
json: this.jsonEnabled(),
|
|
131
|
+
warn: (msg) => void this.warn(msg),
|
|
132
|
+
log: (msg) => this.log(msg),
|
|
133
|
+
error: (msg, opts) => this.error(msg, opts),
|
|
134
|
+
};
|
|
111
135
|
}
|
|
112
136
|
/** Single source of truth for the `--json` envelope shape, shared by success and error output. */
|
|
113
137
|
envelope(parts) {
|
|
@@ -119,33 +143,76 @@ export class BaseCommand extends Command {
|
|
|
119
143
|
data: parts.data,
|
|
120
144
|
});
|
|
121
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Base success envelope from `process.exitCode` (including exit 5 = served stale / ok).
|
|
148
|
+
* Subclasses that overlay batch failures must call this — not `toSuccessJson` — to avoid
|
|
149
|
+
* recursing through their own override.
|
|
150
|
+
*/
|
|
151
|
+
baseSuccessJson(data) {
|
|
152
|
+
// ponytail: the stale-serve path signals EXIT_STALE_SERVED by setting process.exitCode inside
|
|
153
|
+
// run() (e.g. fetch's handleStaleRevalidationResult); this reads it back to mark the envelope.
|
|
154
|
+
// If that signalling moves off process.exitCode, update here too. Number() normalizes Node's
|
|
155
|
+
// string codes.
|
|
156
|
+
const exitCode = Number(process.exitCode ?? EXIT_OK);
|
|
157
|
+
// EXIT_STALE_SERVED means "served stale" — a successful, usable result, so it reports ok.
|
|
158
|
+
return this.envelope({
|
|
159
|
+
ok: exitCode === EXIT_OK || exitCode === EXIT_STALE_SERVED,
|
|
160
|
+
exitCode,
|
|
161
|
+
stderr: '',
|
|
162
|
+
data,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
122
165
|
/** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
|
|
123
166
|
toSuccessJson(data) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
return this.baseSuccessJson(data);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Success overlay for multi-URL read commands (`status`, `inspect`). Apply CACHE_MISS first,
|
|
171
|
+
* then row `.error` so validation failures win when both appear in one batch.
|
|
172
|
+
*/
|
|
173
|
+
batchReadSuccessJson(data) {
|
|
174
|
+
return enrichRowErrorEnvelope(enrichCacheMissEnvelope(this.baseSuccessJson(data), data, this.config.bin), data);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Map each URL through `fn`. In a multi-URL batch, per-URL CLIErrors become failure rows so
|
|
178
|
+
* prior hits are kept — matching fetch's INVALID_URL / MISSING_URL_SCHEME batch contract.
|
|
179
|
+
*/
|
|
180
|
+
mapUrlsAllowingBatchErrors(urls, fn, failureRow) {
|
|
181
|
+
const batch = urls.length > 1;
|
|
182
|
+
const results = [];
|
|
183
|
+
for (const url of urls) {
|
|
184
|
+
try {
|
|
185
|
+
results.push(fn(url));
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (!batch || !(err instanceof Errors.CLIError))
|
|
189
|
+
throw err;
|
|
190
|
+
// Mirror the single-URL error format (message + Code: + Try this:) rather than a bare
|
|
191
|
+
// message, so a row failure in a batch is exactly as actionable as it is standalone.
|
|
192
|
+
if (!this.jsonEnabled())
|
|
193
|
+
this.warn(formatErrorForJson(err));
|
|
194
|
+
results.push(failureRow(url, err));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return results;
|
|
130
198
|
}
|
|
131
199
|
/** Mirror the success envelope for failures so JSON consumers get one consistent shape. */
|
|
132
200
|
toErrorJson(err) {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
: String(err);
|
|
201
|
+
const prepared = prepareCliError(err, {
|
|
202
|
+
bin: this.config.bin,
|
|
203
|
+
command: this.envelopeCommandId(),
|
|
204
|
+
});
|
|
205
|
+
// oclif prints the returned object to stdout; error text lives in envelope.stderr only
|
|
206
|
+
// so process stderr stays clean under `--json` (CACHE_MISS / batch overlays + oclif JSON log suppression).
|
|
140
207
|
return buildEnvelope({
|
|
141
208
|
command: this.envelopeCommandId(),
|
|
142
209
|
ok: false,
|
|
143
|
-
exitCode,
|
|
144
|
-
stderr,
|
|
210
|
+
exitCode: prepared.exitCode,
|
|
211
|
+
stderr: prepared.stderr,
|
|
145
212
|
data: null,
|
|
146
|
-
code,
|
|
147
|
-
suggestions:
|
|
148
|
-
ref:
|
|
213
|
+
code: prepared.code,
|
|
214
|
+
suggestions: prepared.suggestions,
|
|
215
|
+
ref: prepared.ref,
|
|
149
216
|
});
|
|
150
217
|
}
|
|
151
218
|
}
|
|
@@ -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
|
/**
|
|
@@ -30,15 +30,24 @@ export class ConfigCommand extends BaseCommand {
|
|
|
30
30
|
if (!isKnownKey(keyArg)) {
|
|
31
31
|
const suggestion = suggestKey(keyArg);
|
|
32
32
|
const hint = suggestion ? ` Did you mean: ${suggestion}?` : '';
|
|
33
|
+
const command = this.ctor.id ? toConfiguredId(this.ctor.id, this.config) : 'config';
|
|
33
34
|
this.error(`Unknown config key: "${keyArg}".${hint} Valid keys: ${validKeysHint()}.`, {
|
|
34
35
|
exit: 2,
|
|
35
36
|
code: 'UNKNOWN_KEY',
|
|
37
|
+
suggestions: suggestion
|
|
38
|
+
? [`Use "${suggestion}": ${this.config.bin} ${command} ${suggestion}`]
|
|
39
|
+
: [`Use one of: ${validKeysHint()}`],
|
|
36
40
|
});
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
requireConfigKey(key) {
|
|
40
44
|
if (!key) {
|
|
41
|
-
this.
|
|
45
|
+
const command = this.ctor.id ? toConfiguredId(this.ctor.id, this.config) : 'config';
|
|
46
|
+
this.error('Missing required argument: key', {
|
|
47
|
+
exit: 2,
|
|
48
|
+
code: 'MISSING_ARGUMENT',
|
|
49
|
+
suggestions: [`Choose a key: ${this.config.bin} ${command} storage`],
|
|
50
|
+
});
|
|
42
51
|
}
|
|
43
52
|
this.assertKnownKey(key);
|
|
44
53
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
3
|
-
import {
|
|
3
|
+
import { formatConfigEntry, resolveConfigEntry, validKeysHint } from '../../lib/config/index.js';
|
|
4
4
|
export default class ConfigGet extends ConfigCommand {
|
|
5
5
|
static id = 'config get';
|
|
6
|
-
static summary = 'Get
|
|
7
|
-
static description = `Print a config value. Without --global/--local, shows the merged effective value
|
|
6
|
+
static summary = 'Get a research configuration value';
|
|
7
|
+
static description = `Print a config value. Without --global/--local, shows the merged effective value, in precedence order: a per-command override flag (e.g. \`fetch --storage\`) > env var > project file > user file > built-in default.\n\nValid keys: ${validKeysHint()}.`;
|
|
8
8
|
static examples = [
|
|
9
9
|
{
|
|
10
10
|
description: 'get the effective storage mode',
|
|
@@ -20,8 +20,8 @@ export default class ConfigGet extends ConfigCommand {
|
|
|
20
20
|
};
|
|
21
21
|
static flags = {
|
|
22
22
|
...configScopeFlags({
|
|
23
|
-
global: '
|
|
24
|
-
local: '
|
|
23
|
+
global: 'read user-level config only',
|
|
24
|
+
local: 'read project-level config only',
|
|
25
25
|
}),
|
|
26
26
|
};
|
|
27
27
|
static stdoutIsPrimaryData = true;
|
|
@@ -29,13 +29,9 @@ export default class ConfigGet extends ConfigCommand {
|
|
|
29
29
|
const key = this.args.key;
|
|
30
30
|
this.validateConfigKeyAndScope(key, this.flags.global, this.flags.local);
|
|
31
31
|
const scope = this.readScope(this.flags.global, this.flags.local);
|
|
32
|
-
const
|
|
33
|
-
const meta = KEY_META[key];
|
|
34
|
-
const displayValue = value !== undefined ? value : BUILT_IN_DEFAULTS[key];
|
|
35
|
-
const formatted = meta.format(displayValue);
|
|
36
|
-
const suffix = value === undefined ? ' (not configured)' : '';
|
|
32
|
+
const entry = resolveConfigEntry(key, scope, this.config.configDir, process.cwd());
|
|
37
33
|
if (!this.jsonEnabled())
|
|
38
|
-
this.log(
|
|
39
|
-
return
|
|
34
|
+
this.log(formatConfigEntry(entry, scope));
|
|
35
|
+
return entry;
|
|
40
36
|
}
|
|
41
37
|
}
|
|
@@ -2,7 +2,7 @@ import { Help, toConfiguredId } from '@oclif/core';
|
|
|
2
2
|
import { BaseCommand } from '../../base-command.js';
|
|
3
3
|
export default class ConfigIndex extends BaseCommand {
|
|
4
4
|
static id = 'config';
|
|
5
|
-
static summary = 'Manage research cache
|
|
5
|
+
static summary = 'Manage research cache configuration';
|
|
6
6
|
static description = 'Show config subcommands for reading and writing Bonsai research cache configuration.';
|
|
7
7
|
static examples = [
|
|
8
8
|
{
|
|
@@ -10,7 +10,7 @@ export default class ConfigIndex extends BaseCommand {
|
|
|
10
10
|
command: '<%= config.bin %> <%= command.id %> get storage',
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
|
-
description: 'set
|
|
13
|
+
description: 'set project storage mode',
|
|
14
14
|
command: '<%= config.bin %> <%= command.id %> set storage project --local',
|
|
15
15
|
},
|
|
16
16
|
];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
2
|
-
import {
|
|
2
|
+
import { formatConfigEntry, resolveConfigEntries, validKeysHint } from '../../lib/config/index.js';
|
|
3
3
|
export default class ConfigList extends ConfigCommand {
|
|
4
4
|
static id = 'config list';
|
|
5
|
-
static summary = 'List
|
|
5
|
+
static summary = 'List research configuration values';
|
|
6
6
|
static description = `Show every configuration key with its current value. Use --global/--local to show a single scope.\n\nValid keys: ${validKeysHint()}.`;
|
|
7
7
|
static examples = [
|
|
8
8
|
{
|
|
@@ -16,28 +16,22 @@ export default class ConfigList extends ConfigCommand {
|
|
|
16
16
|
];
|
|
17
17
|
static flags = {
|
|
18
18
|
...configScopeFlags({
|
|
19
|
-
global: '
|
|
20
|
-
local: '
|
|
19
|
+
global: 'show user-level config only',
|
|
20
|
+
local: 'show project-level config only',
|
|
21
21
|
}),
|
|
22
22
|
};
|
|
23
23
|
static stdoutIsPrimaryData = true;
|
|
24
24
|
async run() {
|
|
25
25
|
this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
|
|
26
26
|
const scope = this.readScope(this.flags.global, this.flags.local);
|
|
27
|
-
const
|
|
27
|
+
const entries = resolveConfigEntries(scope, this.config.configDir, process.cwd());
|
|
28
28
|
if (!this.jsonEnabled()) {
|
|
29
|
-
const width = Math.max(...
|
|
30
|
-
for (const
|
|
31
|
-
|
|
32
|
-
const formatted = raw !== undefined ? KEY_META[key].format(raw) : '(not configured)';
|
|
33
|
-
this.log(`${key.padEnd(width)}${formatted}`);
|
|
29
|
+
const width = Math.max(...entries.map((e) => e.key.length)) + 2;
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
this.log(`${entry.key.padEnd(width)}${formatConfigEntry(entry, scope)}`);
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const raw = scoped[key];
|
|
39
|
-
values[key] = raw !== undefined ? raw : BUILT_IN_DEFAULTS[key];
|
|
40
|
-
}
|
|
41
|
-
return values;
|
|
34
|
+
// Bare array — same shape as `list`'s data payload; no `{ entries }` wrapper.
|
|
35
|
+
return entries;
|
|
42
36
|
}
|
|
43
37
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigCommand } from './base.js';
|
|
2
|
+
import type { ConfigWriteResult } from '../../lib/cli-result-types.js';
|
|
2
3
|
export default class ConfigSet extends ConfigCommand<typeof ConfigSet> {
|
|
3
4
|
static id: string;
|
|
4
5
|
static summary: string;
|
|
@@ -17,5 +18,5 @@ export default class ConfigSet extends ConfigCommand<typeof ConfigSet> {
|
|
|
17
18
|
local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
19
|
};
|
|
19
20
|
static stdoutIsPrimaryData: boolean;
|
|
20
|
-
run(): Promise<
|
|
21
|
+
run(): Promise<ConfigWriteResult>;
|
|
21
22
|
}
|