@taurgis/bonsai 3.2.2 → 4.1.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 +25 -2
- package/agents/agents/official-docs-researcher.agent.md +102 -0
- package/agents/agents/salesforce-docs-researcher.agent.md +17 -15
- package/agents/hooks/README.md +18 -2
- package/agents/hooks/claude-code/hooks/bonsai-web-fetch.sh +1 -1
- package/agents/hooks/codex/hooks/bonsai-user-prompt-context.sh +1 -1
- package/agents/hooks/codex/hooks/bonsai-web-fetch.sh +1 -1
- package/agents/hooks/cursor/hooks/bonsai-web-fetch.sh +1 -1
- package/agents/hooks/github-copilot/bonsai-web-fetch.sh +1 -1
- package/agents/instructions/salesforce-research.instructions.md +5 -4
- package/agents/instructions/web-research.instructions.md +45 -0
- package/agents/skills/web-research/SKILL.md +139 -0
- package/bin/cli.mjs +7 -4
- package/dist/base-command.d.ts +42 -0
- package/dist/base-command.js +106 -6
- package/dist/commands/config/base.d.ts +2 -7
- package/dist/commands/config/base.js +4 -17
- package/dist/commands/config/get.js +2 -2
- package/dist/commands/config/list.js +2 -2
- package/dist/commands/config/set.js +2 -2
- package/dist/commands/config/unset.js +2 -2
- package/dist/commands/context.d.ts +20 -0
- package/dist/commands/context.js +65 -0
- package/dist/commands/fetch.js +4 -0
- package/dist/commands/inspect.js +11 -2
- package/dist/commands/list.d.ts +23 -11
- package/dist/commands/list.js +110 -121
- package/dist/commands/prune.d.ts +2 -0
- package/dist/commands/prune.js +35 -4
- package/dist/commands/search.d.ts +43 -0
- package/dist/commands/search.js +233 -0
- package/dist/commands/setup.d.ts +27 -0
- package/dist/commands/setup.js +148 -0
- package/dist/commands/status.js +10 -0
- package/dist/commands.d.ts +6 -0
- package/dist/commands.js +6 -0
- package/dist/hooks/command-not-found/suggest.d.ts +1 -1
- package/dist/hooks/command-not-found/suggest.js +12 -2
- package/dist/lib/argv.d.ts +5 -0
- package/dist/lib/argv.js +30 -5
- package/dist/lib/cli-flag-manifest.d.ts +7 -0
- package/dist/lib/cli-flag-manifest.js +7 -0
- package/dist/lib/cli-presentation.d.ts +25 -4
- package/dist/lib/cli-presentation.js +34 -4
- package/dist/lib/cli-result-types.d.ts +84 -1
- package/dist/lib/color.d.ts +2 -0
- package/dist/lib/color.js +6 -0
- package/dist/lib/common-metadata-filter-flags.d.ts +14 -0
- package/dist/lib/common-metadata-filter-flags.js +37 -0
- package/dist/lib/config/index.d.ts +2 -2
- package/dist/lib/config/index.js +2 -2
- 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/envelope.js +11 -4
- package/dist/lib/next-command.d.ts +15 -0
- package/dist/lib/next-command.js +40 -0
- package/dist/lib/prune-flags.d.ts +2 -0
- package/dist/lib/prune-flags.js +18 -6
- package/dist/lib/research/artifact-index.js +7 -1
- package/dist/lib/research/artifact.js +20 -2
- package/dist/lib/research/browser.d.ts +12 -0
- package/dist/lib/research/browser.js +63 -8
- package/dist/lib/research/cli-io.d.ts +7 -0
- package/dist/lib/research/context-dashboard.d.ts +19 -0
- package/dist/lib/research/context-dashboard.js +43 -0
- package/dist/lib/research/fetch-command-service.js +90 -23
- package/dist/lib/research/fetch-result.d.ts +1 -0
- package/dist/lib/research/fetch-result.js +4 -0
- package/dist/lib/research/fetcher.js +3 -1
- package/dist/lib/research/import-command-service.js +22 -1
- package/dist/lib/research/list-row.d.ts +15 -0
- package/dist/lib/research/list-row.js +31 -0
- package/dist/lib/research/metadata-filters.d.ts +39 -0
- package/dist/lib/research/metadata-filters.js +71 -0
- package/dist/lib/research/metadata-flags.d.ts +34 -0
- package/dist/lib/research/metadata-flags.js +45 -0
- package/dist/lib/research/prompt-injection.d.ts +2 -1
- package/dist/lib/research/prompt-injection.js +18 -2
- package/dist/lib/research/resolve-target.d.ts +2 -0
- package/dist/lib/research/resolve-target.js +1 -1
- package/dist/lib/research/schema.d.ts +2 -0
- package/dist/lib/research/schema.js +4 -0
- package/dist/lib/research/search-match.d.ts +39 -0
- package/dist/lib/research/search-match.js +134 -0
- package/dist/lib/research/storage.d.ts +4 -7
- package/dist/lib/research/storage.js +26 -25
- package/dist/lib/scope-flags.d.ts +19 -0
- package/dist/lib/scope-flags.js +27 -0
- package/dist/lib/setup/agents.d.ts +16 -0
- package/dist/lib/setup/agents.js +20 -0
- package/dist/lib/setup/bin-command.d.ts +14 -0
- package/dist/lib/setup/bin-command.js +42 -0
- package/dist/lib/setup/hook-file-io.d.ts +37 -0
- package/dist/lib/setup/hook-file-io.js +80 -0
- package/dist/lib/text.d.ts +20 -8
- package/dist/lib/text.js +32 -9
- package/oclif.manifest.json +406 -13
- package/package.json +2 -1
package/dist/base-command.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { Command, Errors, Flags, toConfiguredId } from '@oclif/core';
|
|
2
|
-
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';
|
|
3
4
|
import { CLI_FLAG_DESCRIPTIONS } from './lib/cli-presentation.js';
|
|
5
|
+
import { colors } from './lib/color.js';
|
|
6
|
+
import { formatTip, sanitizeForTerminal } from './lib/text.js';
|
|
4
7
|
import { enrichErrorForDisplay, resolveExitCode, prepareCliError, EXIT_OK, EXIT_STALE_SERVED, } from './lib/cli-error-policy.js';
|
|
5
|
-
import { buildEnvelope, enrichCacheMissEnvelope, enrichRowErrorEnvelope
|
|
8
|
+
import { buildEnvelope, enrichCacheMissEnvelope, enrichRowErrorEnvelope } from './lib/envelope.js';
|
|
6
9
|
import { enrichParseError } from './lib/parse-error-ux.js';
|
|
7
10
|
import { resolveResearchTarget, } from './lib/research/resolve-target.js';
|
|
8
11
|
import { failInvalidUrl } from './lib/research/cli-io.js';
|
|
12
|
+
/**
|
|
13
|
+
* Mirrors oclif's own `--json` argv-scan (respecting a `--` pass-through boundary) so `--toon`
|
|
14
|
+
* gets identical treatment: present in argv, regardless of parse state.
|
|
15
|
+
*/
|
|
16
|
+
function argvHasFlag(argv, flag) {
|
|
17
|
+
const passThroughIndex = argv.indexOf('--');
|
|
18
|
+
const flagIndex = argv.indexOf(flag);
|
|
19
|
+
return passThroughIndex === -1
|
|
20
|
+
? flagIndex !== -1
|
|
21
|
+
: flagIndex !== -1 && flagIndex < passThroughIndex;
|
|
22
|
+
}
|
|
9
23
|
/**
|
|
10
24
|
* Shared base for every Bonsai command. Enables oclif's native `--json` flag,
|
|
11
25
|
* parses args/flags once in `init()` so commands read `this.args`/`this.flags`
|
|
@@ -26,6 +40,10 @@ export class BaseCommand extends Command {
|
|
|
26
40
|
default: false,
|
|
27
41
|
description: CLI_FLAG_DESCRIPTIONS.readOnly,
|
|
28
42
|
}),
|
|
43
|
+
toon: Flags.boolean({
|
|
44
|
+
default: false,
|
|
45
|
+
description: CLI_FLAG_DESCRIPTIONS.toon,
|
|
46
|
+
}),
|
|
29
47
|
};
|
|
30
48
|
flags;
|
|
31
49
|
args;
|
|
@@ -42,11 +60,52 @@ export class BaseCommand extends Command {
|
|
|
42
60
|
this.flags = flags;
|
|
43
61
|
this.args = args;
|
|
44
62
|
this.parsedArgv = argv;
|
|
63
|
+
// --json and --toon both claim the machine-output seat; picking one silently over the other
|
|
64
|
+
// would surprise a caller who set both by mistake, so reject the combination outright. Reuses
|
|
65
|
+
// the existing CONFLICTING_FLAGS code (not a new one) — the catalog already documents it as the
|
|
66
|
+
// one code for "choose one of the mutually exclusive options", regardless of which flags.
|
|
67
|
+
if (this.flags?.['json'] && this.flags?.['toon']) {
|
|
68
|
+
this.error('Cannot combine --json and --toon: pick one output format.', {
|
|
69
|
+
exit: 2,
|
|
70
|
+
code: 'CONFLICTING_FLAGS',
|
|
71
|
+
suggestions: ['Pass only one of --json or --toon'],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
45
74
|
// Surface a set-but-invalid BONSAI_* override once per run. Resolution silently drops such a
|
|
46
75
|
// value, so without this a typo'd env var would take no effect with no signal. Warnings go to
|
|
47
76
|
// stderr (even under --json), so machine output stays clean.
|
|
48
77
|
for (const warning of invalidEnvOverrideWarnings(process.env))
|
|
49
78
|
this.warn(warning);
|
|
79
|
+
// Same signal for a corrupted or hand-edited config file: parsing silently degrades to `{}`
|
|
80
|
+
// (or drops just the offending key), which would otherwise mask the problem entirely.
|
|
81
|
+
for (const warning of invalidConfigFileWarnings(this.config.configDir, process.cwd(), this.config.bin)) {
|
|
82
|
+
this.warn(warning);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Treat `--toon` as machine mode too, so every existing `!this.jsonEnabled()` human-output branch
|
|
87
|
+
* across every command (table rendering, tips, empty-state guidance) is suppressed under `--toon`
|
|
88
|
+
* for free, exactly as it already is under `--json`.
|
|
89
|
+
*/
|
|
90
|
+
jsonEnabled() {
|
|
91
|
+
// Mirrors oclif's own enableJsonFlag guard (every command in this CLI sets it, but the base
|
|
92
|
+
// class's contract shouldn't silently break if a future command ever opts out).
|
|
93
|
+
return (super.jsonEnabled() ||
|
|
94
|
+
(Boolean(this.ctor?.enableJsonFlag) && argvHasFlag(this.argv, '--toon')));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Real `--json` keeps oclif's own colorized-JSON rendering untouched (zero regression risk).
|
|
98
|
+
* `--toon` (and no real `--json`) re-encodes the identical envelope as TOON instead — same data,
|
|
99
|
+
* ~40% fewer tokens for callers who opted in.
|
|
100
|
+
*/
|
|
101
|
+
logJson(json) {
|
|
102
|
+
if (super.jsonEnabled()) {
|
|
103
|
+
super.logJson(json);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// this.log() no-ops whenever jsonEnabled() is true (by design, everywhere else in this CLI) —
|
|
107
|
+
// ux.stdout writes directly, matching how oclif's own default logJson bypasses that guard too.
|
|
108
|
+
ux.stdout(encode(json));
|
|
50
109
|
}
|
|
51
110
|
/**
|
|
52
111
|
* Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
|
|
@@ -58,6 +117,43 @@ export class BaseCommand extends Command {
|
|
|
58
117
|
Errors.warn(input);
|
|
59
118
|
return input;
|
|
60
119
|
}
|
|
120
|
+
error(input, options = {}) {
|
|
121
|
+
if (!this.jsonEnabled()) {
|
|
122
|
+
if (typeof input === 'string')
|
|
123
|
+
input = sanitizeForTerminal(input);
|
|
124
|
+
if (options.suggestions) {
|
|
125
|
+
options = { ...options, suggestions: options.suggestions.map(sanitizeForTerminal) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// `exit: false` is oclif's non-throwing error render (see `errorRow` on the CliIo port and
|
|
129
|
+
// `mapUrlsAllowingBatchErrors`/prune's per-row failures, all of which route through here for the
|
|
130
|
+
// same terminal-injection sanitization above). Destructure `exit` into its own binding so
|
|
131
|
+
// narrowing it to the `false` literal actually narrows the object passed to `super.error()` into
|
|
132
|
+
// each of oclif's two overloads — narrowing a property read alone (`options.exit === false`)
|
|
133
|
+
// doesn't narrow the enclosing object's type, only a local variable's.
|
|
134
|
+
const { exit, ...rest } = options;
|
|
135
|
+
if (exit === false)
|
|
136
|
+
return super.error(input, { ...rest, exit });
|
|
137
|
+
return super.error(input, { ...rest, exit });
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Human-mode-only contextual next-step suggestion after a successful command, mirroring the
|
|
141
|
+
* "Try this:" pattern already used on errors. A no-op under `--json`/`--toon`: the envelope's
|
|
142
|
+
* `data` is self-describing, so machine callers get no prose.
|
|
143
|
+
*/
|
|
144
|
+
tip(message) {
|
|
145
|
+
if (this.jsonEnabled())
|
|
146
|
+
return;
|
|
147
|
+
this.warn(formatTip(message));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Human-mode tip for a truncated `list`/`search` result: the copy-pasteable command that raises
|
|
151
|
+
* `--limit` (see `next-command.ts`). A no-op when nothing was truncated (`nextCommand` is `null`).
|
|
152
|
+
*/
|
|
153
|
+
tipNextCommand(nextCommand) {
|
|
154
|
+
if (nextCommand)
|
|
155
|
+
this.tip(`see the rest: ${colors.cyan(nextCommand)}`);
|
|
156
|
+
}
|
|
61
157
|
/** Whether read-only/plan mode is active for this invocation (flag OR either env var). */
|
|
62
158
|
get readOnly() {
|
|
63
159
|
return resolveReadOnly({ flag: Boolean(this.flags?.['read-only']), env: process.env });
|
|
@@ -108,6 +204,7 @@ export class BaseCommand extends Command {
|
|
|
108
204
|
configDir: this.config.configDir,
|
|
109
205
|
cwd: process.cwd(),
|
|
110
206
|
dataDir: this.config.dataDir,
|
|
207
|
+
readOnly: this.readOnly,
|
|
111
208
|
url,
|
|
112
209
|
...extra,
|
|
113
210
|
});
|
|
@@ -131,6 +228,7 @@ export class BaseCommand extends Command {
|
|
|
131
228
|
warn: (msg) => void this.warn(msg),
|
|
132
229
|
log: (msg) => this.log(msg),
|
|
133
230
|
error: (msg, opts) => this.error(msg, opts),
|
|
231
|
+
errorRow: (msg, opts) => this.error(msg, { ...opts, exit: false }),
|
|
134
232
|
};
|
|
135
233
|
}
|
|
136
234
|
/** Single source of truth for the `--json` envelope shape, shared by success and error output. */
|
|
@@ -187,10 +285,12 @@ export class BaseCommand extends Command {
|
|
|
187
285
|
catch (err) {
|
|
188
286
|
if (!batch || !(err instanceof Errors.CLIError))
|
|
189
287
|
throw err;
|
|
190
|
-
//
|
|
191
|
-
//
|
|
288
|
+
// Render with the same "Error:" formatting as a single-URL failure (this.error's exit:false
|
|
289
|
+
// form prints the CLIError's own message/Code/Try this and returns instead of throwing) rather
|
|
290
|
+
// than this.warn, since a row failure still fails the command overall (status/inspect batch
|
|
291
|
+
// exit code, JSON ok:false) and should never read as merely a warning.
|
|
192
292
|
if (!this.jsonEnabled())
|
|
193
|
-
this.
|
|
293
|
+
this.error(err, { exit: false });
|
|
194
294
|
results.push(failureRow(url, err));
|
|
195
295
|
}
|
|
196
296
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../base-command.js';
|
|
3
3
|
import type { ConfigKey, ConfigScope } from '../../lib/config/index.js';
|
|
4
|
+
import { scopeFlags } from '../../lib/scope-flags.js';
|
|
4
5
|
/**
|
|
5
6
|
* Shared base for the four `config` subcommands: the mutually-exclusive
|
|
6
7
|
* scope-flag guard, the binary write-scope helper, and known-key validation.
|
|
@@ -16,10 +17,4 @@ export declare abstract class ConfigCommand<T extends typeof Command> extends Ba
|
|
|
16
17
|
protected validateConfigKeyAndScope(key: string | undefined, global?: boolean, local?: boolean): asserts key is ConfigKey;
|
|
17
18
|
}
|
|
18
19
|
/** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
|
|
19
|
-
export declare
|
|
20
|
-
global: string;
|
|
21
|
-
local: string;
|
|
22
|
-
}): {
|
|
23
|
-
global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
|
-
local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
-
};
|
|
20
|
+
export declare const configScopeFlags: typeof scopeFlags;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toConfiguredId } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../base-command.js';
|
|
3
3
|
import { isKnownKey, suggestKey, validKeysHint } from '../../lib/config/index.js';
|
|
4
|
+
import { assertScopeFlagsExclusive, scopeFlags } from '../../lib/scope-flags.js';
|
|
4
5
|
/**
|
|
5
6
|
* Shared base for the four `config` subcommands: the mutually-exclusive
|
|
6
7
|
* scope-flag guard, the binary write-scope helper, and known-key validation.
|
|
7
8
|
*/
|
|
8
9
|
export class ConfigCommand extends BaseCommand {
|
|
9
10
|
assertScopeFlagsExclusive(global, local) {
|
|
10
|
-
|
|
11
|
-
this.error('--global and --local are mutually exclusive.', {
|
|
12
|
-
exit: 2,
|
|
13
|
-
code: 'CONFLICTING_FLAGS',
|
|
14
|
-
});
|
|
15
|
-
}
|
|
11
|
+
assertScopeFlagsExclusive(this.error.bind(this), global, local);
|
|
16
12
|
}
|
|
17
13
|
/** `--local` writes/reads the project file; otherwise the user-level file. */
|
|
18
14
|
writeScope(local) {
|
|
@@ -57,13 +53,4 @@ export class ConfigCommand extends BaseCommand {
|
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
55
|
/** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
|
|
60
|
-
export
|
|
61
|
-
return {
|
|
62
|
-
global: Flags.boolean({ char: 'g', description: descriptions.global }),
|
|
63
|
-
local: Flags.boolean({
|
|
64
|
-
description: descriptions.local,
|
|
65
|
-
aliases: ['project'],
|
|
66
|
-
charAliases: ['p'],
|
|
67
|
-
}),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
56
|
+
export const configScopeFlags = scopeFlags;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
3
|
-
import { formatConfigEntry, resolveConfigEntry,
|
|
3
|
+
import { formatConfigEntry, resolveConfigEntry, keyValuesHint } from '../../lib/config/index.js';
|
|
4
4
|
export default class ConfigGet extends ConfigCommand {
|
|
5
5
|
static id = 'config get';
|
|
6
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: ${
|
|
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: ${keyValuesHint()}.`;
|
|
8
8
|
static examples = [
|
|
9
9
|
{
|
|
10
10
|
description: 'get the effective storage mode',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
2
|
-
import { formatConfigEntry, resolveConfigEntries,
|
|
2
|
+
import { formatConfigEntry, resolveConfigEntries, keyValuesHint } from '../../lib/config/index.js';
|
|
3
3
|
export default class ConfigList extends ConfigCommand {
|
|
4
4
|
static id = 'config list';
|
|
5
5
|
static summary = 'List research configuration values';
|
|
6
|
-
static description = `Show every configuration key with its current value. Use --global/--local to show a single scope.\n\nValid keys: ${
|
|
6
|
+
static description = `Show every configuration key with its current value. Use --global/--local to show a single scope.\n\nValid keys: ${keyValuesHint()}.`;
|
|
7
7
|
static examples = [
|
|
8
8
|
{
|
|
9
9
|
description: 'show all effective config values',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
3
|
-
import { KEY_META,
|
|
3
|
+
import { KEY_META, keyValuesHint } from '../../lib/config/index.js';
|
|
4
4
|
import { persistConfigPatch } from '../../lib/config-persist.js';
|
|
5
5
|
export default class ConfigSet extends ConfigCommand {
|
|
6
6
|
static id = 'config set';
|
|
7
7
|
static summary = 'Set a research configuration key';
|
|
8
8
|
static description = 'Persist a configuration value. Writes user-level config by default; pass --local for project-level config (.bonsai.json in cwd).\n\n' +
|
|
9
|
-
`Valid keys: ${
|
|
9
|
+
`Valid keys: ${keyValuesHint()}. The inline form \`<key>=<value>\` is also accepted.`;
|
|
10
10
|
static examples = [
|
|
11
11
|
{
|
|
12
12
|
description: 'store research cache in the current project',
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { ConfigCommand, configScopeFlags } from './base.js';
|
|
3
|
-
import {
|
|
3
|
+
import { keyValuesHint } from '../../lib/config/index.js';
|
|
4
4
|
import { persistConfigPatch } from '../../lib/config-persist.js';
|
|
5
5
|
export default class ConfigUnset extends ConfigCommand {
|
|
6
6
|
static id = 'config unset';
|
|
7
7
|
static summary = 'Remove a research configuration key';
|
|
8
|
-
static description = `Delete a key from user-level config (default) or project-level config (--local), restoring the built-in default.\n\nValid keys: ${
|
|
8
|
+
static description = `Delete a key from user-level config (default) or project-level config (--local), restoring the built-in default.\n\nValid keys: ${keyValuesHint()}.`;
|
|
9
9
|
static examples = [
|
|
10
10
|
{
|
|
11
11
|
description: 'remove the user-level storage setting',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command.js';
|
|
2
|
+
import type { ContextDashboard } from '../lib/cli-result-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compact, directory-scoped cache summary (AXI principle 7: ambient session-start context).
|
|
5
|
+
* Plain-text human output is deliberately short — it's meant to be piped verbatim into a
|
|
6
|
+
* SessionStart hook's `additionalContext` by `bonsai setup`, so every line here is also a
|
|
7
|
+
* reasonable thing for an agent to read unprompted at the start of a session.
|
|
8
|
+
*/
|
|
9
|
+
export default class Context extends BaseCommand<typeof Context> {
|
|
10
|
+
static id: string;
|
|
11
|
+
static summary: string;
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: {
|
|
14
|
+
description: string;
|
|
15
|
+
command: string;
|
|
16
|
+
}[];
|
|
17
|
+
static stdoutIsPrimaryData: boolean;
|
|
18
|
+
private logDashboard;
|
|
19
|
+
run(): Promise<ContextDashboard>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { BaseCommand } from '../base-command.js';
|
|
2
|
+
import { loadStoreRoots } from '../lib/research/store-roots.js';
|
|
3
|
+
import { buildContextDashboard } from '../lib/research/context-dashboard.js';
|
|
4
|
+
import { NO_TOPIC_LABEL, formatTip, pluralize, sanitizeForTerminal } from '../lib/text.js';
|
|
5
|
+
import { colors, FRESHNESS_COLOR } from '../lib/color.js';
|
|
6
|
+
/**
|
|
7
|
+
* Compact, directory-scoped cache summary (AXI principle 7: ambient session-start context).
|
|
8
|
+
* Plain-text human output is deliberately short — it's meant to be piped verbatim into a
|
|
9
|
+
* SessionStart hook's `additionalContext` by `bonsai setup`, so every line here is also a
|
|
10
|
+
* reasonable thing for an agent to read unprompted at the start of a session.
|
|
11
|
+
*/
|
|
12
|
+
export default class Context extends BaseCommand {
|
|
13
|
+
static id = 'context';
|
|
14
|
+
static summary = 'Show a compact cache dashboard for ambient session context';
|
|
15
|
+
static description = 'Print a short summary of the current research cache: total entries, a freshness ' +
|
|
16
|
+
'breakdown, and the most recently touched pages. This is what `bonsai setup` wires into a ' +
|
|
17
|
+
"SessionStart hook, so a session starts with the cache's state already visible.";
|
|
18
|
+
static examples = [
|
|
19
|
+
{
|
|
20
|
+
description: 'preview what a SessionStart hook would show',
|
|
21
|
+
command: '<%= config.bin %> context',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
description: 'get the dashboard as JSON',
|
|
25
|
+
command: '<%= config.bin %> context --json',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
static stdoutIsPrimaryData = true;
|
|
29
|
+
logDashboard(dashboard) {
|
|
30
|
+
if (this.jsonEnabled())
|
|
31
|
+
return;
|
|
32
|
+
if (dashboard.total === 0) {
|
|
33
|
+
this.log(`${this.config.bin} cache: 0 entries found.`);
|
|
34
|
+
this.log(formatTip(`populate the cache: ${colors.cyan(`${this.config.bin} <url>`)}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const { fresh, stale_grace: staleGrace, stale_expired: staleExpired } = dashboard.byFreshness;
|
|
38
|
+
this.log(`${this.config.bin} cache: ${dashboard.total} ${pluralize(dashboard.total, 'entry', 'entries')} ` +
|
|
39
|
+
`(${fresh} fresh, ${staleGrace} stale_grace, ${staleExpired} stale_expired)`);
|
|
40
|
+
for (const entry of dashboard.entries) {
|
|
41
|
+
const topicStr = entry.topic
|
|
42
|
+
? colors.cyan(sanitizeForTerminal(entry.topic))
|
|
43
|
+
: colors.gray(NO_TOPIC_LABEL);
|
|
44
|
+
const freshnessStr = FRESHNESS_COLOR[entry.freshness](entry.freshness);
|
|
45
|
+
const url = entry.sourceUrls[0] ?? '';
|
|
46
|
+
this.log(`- [${topicStr}] ${freshnessStr} — ${colors.gray(url)}`);
|
|
47
|
+
}
|
|
48
|
+
if (dashboard.total > dashboard.shown) {
|
|
49
|
+
this.log(formatTip(`see all ${dashboard.total} entries: ${colors.cyan(`${this.config.bin} list`)}`));
|
|
50
|
+
}
|
|
51
|
+
this.log(formatTip(`research a new page: ${colors.cyan(`${this.config.bin} <url>`)}`));
|
|
52
|
+
}
|
|
53
|
+
async run() {
|
|
54
|
+
const roots = loadStoreRoots({
|
|
55
|
+
configDir: this.config.configDir,
|
|
56
|
+
cwd: process.cwd(),
|
|
57
|
+
dataDir: this.config.dataDir,
|
|
58
|
+
});
|
|
59
|
+
const dashboard = buildContextDashboard(roots.readRoots, new Date(), {
|
|
60
|
+
persistIndex: !this.readOnly,
|
|
61
|
+
});
|
|
62
|
+
this.logDashboard(dashboard);
|
|
63
|
+
return dashboard;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/commands/fetch.js
CHANGED
|
@@ -18,6 +18,10 @@ export default class FetchCommand extends BaseCommand {
|
|
|
18
18
|
description: 'cache a volatile page with compressed JSON output',
|
|
19
19
|
command: '<%= config.bin %> https://news.ycombinator.com/ --format compressed --ttl 2h --json',
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
description: 'fetch multiple URLs in one batch (space-separated)',
|
|
23
|
+
command: '<%= config.bin %> https://docs.nestjs.com/ https://docs.nestjs.com/first-steps',
|
|
24
|
+
},
|
|
21
25
|
];
|
|
22
26
|
static strict = false;
|
|
23
27
|
static args = {
|
package/dist/commands/inspect.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getArtifactPath, scanCacheDirs } from '../lib/research/storage.js';
|
|
|
6
6
|
import { artifactMatchesUrlFilter } from '../lib/research/url.js';
|
|
7
7
|
import { colors } from '../lib/color.js';
|
|
8
8
|
import { formatHumanField } from '../lib/cli-presentation.js';
|
|
9
|
+
import { sanitizeForTerminal } from '../lib/text.js';
|
|
9
10
|
/** Inspect a cached artifact and its section children. */
|
|
10
11
|
export default class ResearchInspect extends BaseCommand {
|
|
11
12
|
static id = 'inspect';
|
|
@@ -20,6 +21,10 @@ export default class ResearchInspect extends BaseCommand {
|
|
|
20
21
|
description: 'inspect cached metadata as JSON',
|
|
21
22
|
command: '<%= config.bin %> <%= command.id %> https://example.com/docs --json',
|
|
22
23
|
},
|
|
24
|
+
{
|
|
25
|
+
description: 'inspect multiple URLs in one batch (space-separated)',
|
|
26
|
+
command: '<%= config.bin %> <%= command.id %> https://example.com/docs https://example.com/api',
|
|
27
|
+
},
|
|
23
28
|
];
|
|
24
29
|
static strict = false;
|
|
25
30
|
static args = {
|
|
@@ -110,6 +115,7 @@ export default class ResearchInspect extends BaseCommand {
|
|
|
110
115
|
this.logMetadata(cached.metadata);
|
|
111
116
|
if (sections.length)
|
|
112
117
|
this.logSections(sections);
|
|
118
|
+
this.tip(`${this.config.bin} status ${normalizedUrl} to check freshness.`);
|
|
113
119
|
const sep = batchSeparator(this.parsedArgv.length > 1);
|
|
114
120
|
if (sep)
|
|
115
121
|
this.log(sep);
|
|
@@ -130,14 +136,17 @@ export default class ResearchInspect extends BaseCommand {
|
|
|
130
136
|
this.log(formatHumanField(key, colors.bold(JSON.stringify(val))));
|
|
131
137
|
}
|
|
132
138
|
else {
|
|
133
|
-
|
|
139
|
+
const display = typeof val === 'string' ? sanitizeForTerminal(val) : String(val);
|
|
140
|
+
this.log(formatHumanField(key, colors.bold(display)));
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
}
|
|
137
144
|
logSections(sections) {
|
|
138
145
|
this.log(colors.cyan(`--- Sections (${sections.length}) ---`));
|
|
139
146
|
for (const s of sections) {
|
|
140
|
-
|
|
147
|
+
const headingPath = sanitizeForTerminal(s.headingPath || '');
|
|
148
|
+
const anchor = sanitizeForTerminal(s.anchor || '');
|
|
149
|
+
this.log(`${colors.cyan(headingPath)} [${colors.yellow(anchor)}] (${colors.magenta(String(s.tokenEstimate.detailed || 0))} tokens) ${colors.gray(s.cacheKey)}`);
|
|
141
150
|
}
|
|
142
151
|
}
|
|
143
152
|
// Section children link back via parent_cache_key; list the active ones for this page (T-22).
|
package/dist/commands/list.d.ts
CHANGED
|
@@ -1,38 +1,50 @@
|
|
|
1
1
|
import { BaseCommand } from '../base-command.js';
|
|
2
|
-
import type { ListRow } from '../lib/cli-result-types.js';
|
|
2
|
+
import type { ListRow, ListRowMinimal } from '../lib/cli-result-types.js';
|
|
3
3
|
/** List cached research artifacts by metadata filters. */
|
|
4
4
|
export default class ResearchList extends BaseCommand<typeof ResearchList> {
|
|
5
5
|
static id: string;
|
|
6
6
|
static summary: string;
|
|
7
7
|
static description: string;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Aggregate counts (and an explicit empty/truncation signal) attached to the success envelope so
|
|
10
|
+
* agents get a cache-wide summary without a separate round trip (AXI principles: pre-computed
|
|
11
|
+
* aggregates, definitive empty states). Always set by `run()` before it returns.
|
|
11
12
|
*/
|
|
12
|
-
private
|
|
13
|
+
private jsonSummary;
|
|
13
14
|
static examples: {
|
|
14
15
|
description: string;
|
|
15
16
|
command: string;
|
|
16
17
|
}[];
|
|
17
18
|
static flags: {
|
|
19
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
full: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
identity: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
22
|
topic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
23
|
tags: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
24
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
25
|
freshness: import("@oclif/core/interfaces").OptionFlag<"fresh" | "stale_grace" | "stale_expired" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
26
|
'artifact-type': import("@oclif/core/interfaces").OptionFlag<"source" | "research_note" | "index" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
27
|
'capture-method': import("@oclif/core/interfaces").OptionFlag<"static_fetch" | "browser_fallback" | "agent_supplied" | "route_markdown" | "github_source" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
-
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
28
|
};
|
|
26
29
|
static stdoutIsPrimaryData: boolean;
|
|
27
|
-
|
|
28
|
-
private
|
|
30
|
+
/** The shared filter flags as `matchesCommonMetadataFilters`/`hasActiveMetadataFilters` expect them. */
|
|
31
|
+
private metadataFilterFlags;
|
|
29
32
|
private matchesFilters;
|
|
30
33
|
private scanCacheDirForList;
|
|
31
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Identity header for the AXI "home view" (bare `bonsai`, no args): the tool's own bin path and
|
|
36
|
+
* one-sentence description, shown before the live data so an agent orients on what it's looking
|
|
37
|
+
* at without a separate `--help` round trip. Human mode only — the hidden `--identity` flag is
|
|
38
|
+
* only ever set by the argv rewrite for a bare invocation; it is not documented for direct use.
|
|
39
|
+
*/
|
|
40
|
+
private logIdentityHeader;
|
|
32
41
|
private logListResults;
|
|
33
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Empty-cache / no-match tip (human mode only). Under `--json`/`--toon`, `data: []` is paired with
|
|
44
|
+
* `summary.empty: true` instead of prose — see {@link toSuccessJson}.
|
|
45
|
+
*/
|
|
34
46
|
private emitEmptyListGuidance;
|
|
35
|
-
run(): Promise<ListRow[]>;
|
|
36
|
-
/** Attach `
|
|
47
|
+
run(): Promise<ListRow[] | ListRowMinimal[]>;
|
|
48
|
+
/** Attach the aggregate `summary` computed in `run()` (see {@link ListSummary}). */
|
|
37
49
|
protected toSuccessJson(data: unknown): Record<string, unknown>;
|
|
38
50
|
}
|