@taurgis/bonsai 1.0.5 → 3.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.
Files changed (126) hide show
  1. package/README.md +59 -12
  2. package/agents/agents/official-docs-researcher.agent.md +15 -26
  3. package/agents/agents/salesforce-docs-researcher.agent.md +17 -35
  4. package/agents/hooks/README.md +2 -9
  5. package/agents/hooks/claude-code/hooks/bonsai-web-fetch.sh +1 -1
  6. package/agents/hooks/codex/hooks/bonsai-user-prompt-context.sh +1 -1
  7. package/agents/hooks/codex/hooks/bonsai-web-fetch.sh +1 -1
  8. package/agents/hooks/cursor/hooks/bonsai-web-fetch.sh +1 -1
  9. package/agents/hooks/github-copilot/bonsai-web-fetch.sh +1 -1
  10. package/agents/instructions/salesforce-research.instructions.md +7 -6
  11. package/agents/instructions/web-research.instructions.md +3 -2
  12. package/agents/skills/web-research/SKILL.md +24 -32
  13. package/bin/cli.mjs +44 -14
  14. package/dist/base-command.d.ts +17 -1
  15. package/dist/base-command.js +70 -9
  16. package/dist/commands/config/base.d.ts +2 -0
  17. package/dist/commands/config/base.js +15 -1
  18. package/dist/commands/config/get.d.ts +1 -1
  19. package/dist/commands/config/get.js +4 -5
  20. package/dist/commands/config/index.d.ts +12 -0
  21. package/dist/commands/config/index.js +34 -0
  22. package/dist/commands/config/list.js +2 -2
  23. package/dist/commands/config/set.js +18 -12
  24. package/dist/commands/config/unset.d.ts +1 -1
  25. package/dist/commands/config/unset.js +9 -6
  26. package/dist/commands/fetch.d.ts +2 -0
  27. package/dist/commands/fetch.js +69 -45
  28. package/dist/commands/import.d.ts +3 -0
  29. package/dist/commands/import.js +91 -53
  30. package/dist/commands/inspect.d.ts +8 -0
  31. package/dist/commands/inspect.js +59 -30
  32. package/dist/commands/list.d.ts +5 -3
  33. package/dist/commands/list.js +60 -35
  34. package/dist/commands/prune.d.ts +2 -1
  35. package/dist/commands/prune.js +62 -19
  36. package/dist/commands/status.d.ts +8 -0
  37. package/dist/commands/status.js +84 -26
  38. package/dist/commands.d.ts +2 -2
  39. package/dist/commands.js +2 -2
  40. package/dist/hooks/command-not-found/suggest.d.ts +7 -1
  41. package/dist/hooks/command-not-found/suggest.js +100 -46
  42. package/dist/lib/argv.d.ts +10 -0
  43. package/dist/lib/argv.js +97 -0
  44. package/dist/lib/color.d.ts +20 -0
  45. package/dist/lib/color.js +36 -0
  46. package/dist/lib/envelope.d.ts +36 -0
  47. package/dist/lib/envelope.js +82 -0
  48. package/dist/lib/help-preflight.d.ts +15 -0
  49. package/dist/lib/help-preflight.js +81 -0
  50. package/dist/lib/json-meta.d.ts +10 -0
  51. package/dist/lib/json-meta.js +98 -0
  52. package/dist/lib/limit-flag.d.ts +1 -0
  53. package/dist/lib/limit-flag.js +24 -0
  54. package/dist/lib/research/{search-index.d.ts → artifact-index.d.ts} +5 -5
  55. package/dist/lib/research/{search-index.js → artifact-index.js} +10 -10
  56. package/dist/lib/research/browser.d.ts +0 -8
  57. package/dist/lib/research/browser.js +20 -43
  58. package/dist/lib/research/regression.d.ts +18 -0
  59. package/dist/lib/research/regression.js +47 -0
  60. package/dist/lib/research/revalidate.js +3 -1
  61. package/dist/lib/research/schema.d.ts +5 -2
  62. package/dist/lib/research/schema.js +12 -1
  63. package/dist/lib/research/storage.d.ts +0 -1
  64. package/dist/lib/research/storage.js +15 -13
  65. package/dist/lib/research/url.d.ts +22 -0
  66. package/dist/lib/research/url.js +63 -1
  67. package/dist/lib/text.d.ts +5 -1
  68. package/dist/lib/text.js +11 -1
  69. package/dist/sites/coveo.d.ts +7 -0
  70. package/dist/sites/coveo.js +33 -0
  71. package/dist/sites/index.js +1 -2
  72. package/dist/sites/salesforce/fetch-page.js +1 -1
  73. package/dist/sites/salesforce/index.js +3 -8
  74. package/dist/sites/salesforce-developer/fetch-page.js +3 -1
  75. package/dist/sites/salesforce-developer/index.js +1 -2
  76. package/dist/sites/salesforce-doc-fetch.d.ts +11 -0
  77. package/dist/sites/salesforce-doc-fetch.js +117 -76
  78. package/dist/sites/salesforce-dom-probe.d.ts +16 -0
  79. package/dist/sites/salesforce-dom-probe.js +114 -0
  80. package/dist/sites/types.d.ts +0 -6
  81. package/oclif.manifest.json +153 -155
  82. package/package.json +38 -25
  83. package/dist/commands/index.d.ts +0 -35
  84. package/dist/commands/index.js +0 -306
  85. package/dist/commands/research/config/base.d.ts +0 -23
  86. package/dist/commands/research/config/base.js +0 -46
  87. package/dist/commands/research/config/get.d.ts +0 -20
  88. package/dist/commands/research/config/get.js +0 -45
  89. package/dist/commands/research/config/list.d.ts +0 -17
  90. package/dist/commands/research/config/list.js +0 -42
  91. package/dist/commands/research/config/set.d.ts +0 -31
  92. package/dist/commands/research/config/set.js +0 -94
  93. package/dist/commands/research/config/unset.d.ts +0 -21
  94. package/dist/commands/research/config/unset.js +0 -59
  95. package/dist/commands/research/import.d.ts +0 -38
  96. package/dist/commands/research/import.js +0 -313
  97. package/dist/commands/research/inspect.d.ts +0 -13
  98. package/dist/commands/research/inspect.js +0 -86
  99. package/dist/commands/research/list.d.ts +0 -25
  100. package/dist/commands/research/list.js +0 -143
  101. package/dist/commands/research/prune.d.ts +0 -24
  102. package/dist/commands/research/prune.js +0 -141
  103. package/dist/commands/research/search.d.ts +0 -32
  104. package/dist/commands/research/search.js +0 -334
  105. package/dist/commands/research/status.d.ts +0 -17
  106. package/dist/commands/research/status.js +0 -108
  107. package/dist/commands/research.d.ts +0 -36
  108. package/dist/commands/research.js +0 -304
  109. package/dist/commands/search.d.ts +0 -31
  110. package/dist/commands/search.js +0 -329
  111. package/dist/lib/research/docs/__fixtures__/sphinx-searchindex.d.ts +0 -1
  112. package/dist/lib/research/docs/__fixtures__/sphinx-searchindex.js +0 -2
  113. package/dist/lib/research/docs/discovery-index.d.ts +0 -14
  114. package/dist/lib/research/docs/discovery-index.js +0 -112
  115. package/dist/lib/research/docs/remote-search-runner.d.ts +0 -21
  116. package/dist/lib/research/docs/remote-search-runner.js +0 -114
  117. package/dist/lib/research/docs/remote-search.d.ts +0 -14
  118. package/dist/lib/research/docs/remote-search.js +0 -101
  119. package/dist/lib/research/docs/search-index.d.ts +0 -13
  120. package/dist/lib/research/docs/search-index.js +0 -176
  121. package/dist/sites/salesforce/browser-search.d.ts +0 -7
  122. package/dist/sites/salesforce/browser-search.js +0 -82
  123. package/dist/sites/salesforce/coveo.d.ts +0 -23
  124. package/dist/sites/salesforce/coveo.js +0 -161
  125. package/dist/sites/salesforce/token.d.ts +0 -26
  126. package/dist/sites/salesforce/token.js +0 -154
@@ -1,4 +1,5 @@
1
1
  import { Command, Interfaces } from '@oclif/core';
2
+ import { type ResolveResearchTargetOptions, type ResolvedResearchTarget } from './lib/research/resolve-target.js';
2
3
  /**
3
4
  * Shared base for every Bonsai command. Enables oclif's native `--json` flag,
4
5
  * parses args/flags once in `init()` so commands read `this.args`/`this.flags`
@@ -9,10 +10,11 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
9
10
  static enableJsonFlag: boolean;
10
11
  flags: Interfaces.InferredFlags<T['flags']>;
11
12
  args: Interfaces.InferredArgs<T['args']>;
13
+ parsedArgv: string[];
12
14
  init(): Promise<void>;
13
15
  /**
14
16
  * Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
15
- * but our warnings (secret-redirect, stale-serve, remote-search fallback, prune failures) are
17
+ * but our warnings (secret-redirect, stale-serve, prune failures) are
16
18
  * security- and freshness-relevant side effects users must see. stderr never pollutes the stdout
17
19
  * JSON envelope, so machine output stays clean.
18
20
  */
@@ -36,9 +38,23 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
36
38
  exit?: number;
37
39
  };
38
40
  exitCode?: number;
41
+ code?: string;
39
42
  }): Promise<any>;
40
43
  /** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
41
44
  protected envelopeCommandId(): string;
45
+ /**
46
+ * Resolve a URL against the research cache, exiting on a normalization failure. A scheme-less but
47
+ * domain-shaped input reports MISSING_URL_SCHEME — the same code the root `bonsai <url>` shorthand
48
+ * uses — so an agent sees one stable code for "forgot https://" everywhere; truly unparseable input
49
+ * stays INVALID_URL.
50
+ */
51
+ 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
+ */
57
+ protected failInvalidUrl(url: string, message: string): never;
42
58
  /** Single source of truth for the `--json` envelope shape, shared by success and error output. */
43
59
  private envelope;
44
60
  /** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
@@ -1,5 +1,8 @@
1
- import { Command, Errors } from '@oclif/core';
1
+ import { Command, Errors, toConfiguredId } from '@oclif/core';
2
2
  import { invalidEnvOverrideWarnings } from './lib/config/index.js';
3
+ import { buildEnvelope, formatErrorForJson, normalizeCliErrorMessage, stableErrorCodeFrom, } from './lib/envelope.js';
4
+ import { resolveResearchTarget, } from './lib/research/resolve-target.js';
5
+ import { looksLikeSchemelessUrl } from './lib/research/url.js';
3
6
  /**
4
7
  * Shared base for every Bonsai command. Enables oclif's native `--json` flag,
5
8
  * parses args/flags once in `init()` so commands read `this.args`/`this.flags`
@@ -10,9 +13,10 @@ export class BaseCommand extends Command {
10
13
  static enableJsonFlag = true;
11
14
  flags;
12
15
  args;
16
+ parsedArgv;
13
17
  async init() {
14
18
  await super.init();
15
- const { args, flags } = await this.parse({
19
+ const { args, flags, argv } = await this.parse({
16
20
  flags: this.ctor.flags,
17
21
  enableJsonFlag: this.ctor.enableJsonFlag,
18
22
  args: this.ctor.args,
@@ -20,6 +24,7 @@ export class BaseCommand extends Command {
20
24
  });
21
25
  this.flags = flags;
22
26
  this.args = args;
27
+ this.parsedArgv = argv;
23
28
  // Surface a set-but-invalid BONSAI_* override once per run. Resolution silently drops such a
24
29
  // value, so without this a typo'd env var would take no effect with no signal. Warnings go to
25
30
  // stderr (even under --json), so machine output stays clean.
@@ -28,7 +33,7 @@ export class BaseCommand extends Command {
28
33
  }
29
34
  /**
30
35
  * Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
31
- * but our warnings (secret-redirect, stale-serve, remote-search fallback, prune failures) are
36
+ * but our warnings (secret-redirect, stale-serve, prune failures) are
32
37
  * security- and freshness-relevant side effects users must see. stderr never pollutes the stdout
33
38
  * JSON envelope, so machine output stays clean.
34
39
  */
@@ -53,24 +58,66 @@ export class BaseCommand extends Command {
53
58
  * the shared resolver so the framework's `??` keeps it, then defer to the default behavior.
54
59
  */
55
60
  async catch(err) {
61
+ // Parse failures throw before `this.parse()` sets `parsed`, which makes oclif emit a spurious
62
+ // [UnparsedCommand] warning to stderr even under `--json`. CLIParseError subclasses carry `parse`.
63
+ if (err && typeof err === 'object' && 'parse' in err)
64
+ this.parsed = true;
65
+ // Attach the stable code to the error itself so oclif's human pretty-print renders the same
66
+ // `Code:` line that `--json` already reports via stableErrorCodeFrom. Without this, built-in
67
+ // oclif parse errors (invalid enum value, missing arg, unknown flag) printed a code under
68
+ // --json but not for humans — the two output modes disagreed on whether an error had a code.
69
+ if (err && typeof err === 'object' && !err.code) {
70
+ const code = stableErrorCodeFrom(err);
71
+ if (code)
72
+ err.code = code;
73
+ }
56
74
  process.exitCode = process.exitCode ?? BaseCommand.exitCodeOf(err);
57
75
  return super.catch(err);
58
76
  }
59
77
  /** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
60
78
  envelopeCommandId() {
61
- return this.ctor.id || this.config.bin;
79
+ return this.ctor.id ? toConfiguredId(this.ctor.id, this.config) : this.config.bin;
80
+ }
81
+ /**
82
+ * Resolve a URL against the research cache, exiting on a normalization failure. A scheme-less but
83
+ * domain-shaped input reports MISSING_URL_SCHEME — the same code the root `bonsai <url>` shorthand
84
+ * uses — so an agent sees one stable code for "forgot https://" everywhere; truly unparseable input
85
+ * stays INVALID_URL.
86
+ */
87
+ resolveResearchTargetOrFail(url, extra) {
88
+ try {
89
+ return resolveResearchTarget({
90
+ configDir: this.config.configDir,
91
+ cwd: process.cwd(),
92
+ dataDir: this.config.dataDir,
93
+ url,
94
+ ...extra,
95
+ });
96
+ }
97
+ catch (err) {
98
+ this.failInvalidUrl(url, err.message);
99
+ }
100
+ }
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
+ */
106
+ failInvalidUrl(url, message) {
107
+ if (looksLikeSchemelessUrl(url)) {
108
+ this.error(message, { exit: 2, code: 'MISSING_URL_SCHEME' });
109
+ }
110
+ this.error(`Invalid URL: ${message}`, { exit: 2, code: 'INVALID_URL' });
62
111
  }
63
112
  /** Single source of truth for the `--json` envelope shape, shared by success and error output. */
64
113
  envelope(parts) {
65
- return {
66
- schemaVersion: 1,
114
+ return buildEnvelope({
67
115
  command: this.envelopeCommandId(),
68
116
  ok: parts.ok,
69
117
  exitCode: parts.exitCode,
70
- stdout: '',
71
118
  stderr: parts.stderr,
72
119
  data: parts.data,
73
- };
120
+ });
74
121
  }
75
122
  /** Wrap a command's return value in the machine-readable envelope emitted under `--json`. */
76
123
  toSuccessJson(data) {
@@ -85,6 +132,20 @@ export class BaseCommand extends Command {
85
132
  toErrorJson(err) {
86
133
  const e = err;
87
134
  const exitCode = BaseCommand.exitCodeOf(e);
88
- return this.envelope({ ok: false, exitCode, stderr: e?.message ?? String(err), data: null });
135
+ const code = stableErrorCodeFrom(e);
136
+ const message = typeof e?.message === 'string' ? normalizeCliErrorMessage(e.message) : undefined;
137
+ const stderr = message || code || e?.suggestions?.length || e?.ref
138
+ ? formatErrorForJson({ ...e, message, code })
139
+ : String(err);
140
+ return buildEnvelope({
141
+ command: this.envelopeCommandId(),
142
+ ok: false,
143
+ exitCode,
144
+ stderr,
145
+ data: null,
146
+ code,
147
+ suggestions: e.suggestions?.length ? e.suggestions : undefined,
148
+ ref: e.ref,
149
+ });
89
150
  }
90
151
  }
@@ -12,6 +12,8 @@ export declare abstract class ConfigCommand<T extends typeof Command> extends Ba
12
12
  /** Read scope for get/list: an explicit `--global`/`--local`, else the merged effective view. */
13
13
  protected readScope(global?: boolean, local?: boolean): ConfigScope;
14
14
  protected assertKnownKey(keyArg: string): asserts keyArg is ConfigKey;
15
+ protected requireConfigKey(key: string | undefined): asserts key is ConfigKey;
16
+ protected validateConfigKeyAndScope(key: string | undefined, global?: boolean, local?: boolean): asserts key is ConfigKey;
15
17
  }
16
18
  /** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
17
19
  export declare function configScopeFlags(descriptions: {
@@ -8,7 +8,10 @@ import { isKnownKey, suggestKey, validKeysHint } from '../../lib/config/index.js
8
8
  export class ConfigCommand extends BaseCommand {
9
9
  assertScopeFlagsExclusive(global, local) {
10
10
  if (global && local) {
11
- this.error('--global and --local are mutually exclusive.', { exit: 2 });
11
+ this.error('--global and --local are mutually exclusive.', {
12
+ exit: 2,
13
+ code: 'CONFLICTING_FLAGS',
14
+ });
12
15
  }
13
16
  }
14
17
  /** `--local` writes/reads the project file; otherwise the user-level file. */
@@ -29,9 +32,20 @@ export class ConfigCommand extends BaseCommand {
29
32
  const hint = suggestion ? ` Did you mean: ${suggestion}?` : '';
30
33
  this.error(`Unknown config key: "${keyArg}".${hint} Valid keys: ${validKeysHint()}.`, {
31
34
  exit: 2,
35
+ code: 'UNKNOWN_KEY',
32
36
  });
33
37
  }
34
38
  }
39
+ requireConfigKey(key) {
40
+ if (!key) {
41
+ this.error('Missing required argument: key', { exit: 2, code: 'MISSING_ARGUMENT' });
42
+ }
43
+ this.assertKnownKey(key);
44
+ }
45
+ validateConfigKeyAndScope(key, global, local) {
46
+ this.requireConfigKey(key);
47
+ this.assertScopeFlagsExclusive(global, local);
48
+ }
35
49
  }
36
50
  /** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
37
51
  export function configScopeFlags(descriptions) {
@@ -8,7 +8,7 @@ export default class ConfigGet extends ConfigCommand<typeof ConfigGet> {
8
8
  command: string;
9
9
  }[];
10
10
  static args: {
11
- key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
11
+ key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
12
12
  };
13
13
  static flags: {
14
14
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -7,16 +7,16 @@ export default class ConfigGet extends ConfigCommand {
7
7
  static description = `Print a config value. Without --global/--local, shows the merged effective value (flag > env > project > user > default).\n\nValid keys: ${validKeysHint()}.`;
8
8
  static examples = [
9
9
  {
10
- description: 'Get the effective storage mode',
10
+ description: 'get the effective storage mode',
11
11
  command: '<%= config.bin %> config get storage',
12
12
  },
13
13
  {
14
- description: 'Read only the project-level value',
14
+ description: 'read only the project-level value',
15
15
  command: '<%= config.bin %> config get storage --local',
16
16
  },
17
17
  ];
18
18
  static args = {
19
- key: Args.string({ required: true, description: 'The configuration key to read.' }),
19
+ key: Args.string({ required: false, description: 'the configuration key to read' }),
20
20
  };
21
21
  static flags = {
22
22
  ...configScopeFlags({
@@ -27,8 +27,7 @@ export default class ConfigGet extends ConfigCommand {
27
27
  static stdoutIsPrimaryData = true;
28
28
  async run() {
29
29
  const key = this.args.key;
30
- this.assertKnownKey(key);
31
- this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
30
+ this.validateConfigKeyAndScope(key, this.flags.global, this.flags.local);
32
31
  const scope = this.readScope(this.flags.global, this.flags.local);
33
32
  const value = readScopedConfig(scope, this.config.configDir, process.cwd())[key];
34
33
  const meta = KEY_META[key];
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class ConfigIndex extends BaseCommand<typeof ConfigIndex> {
3
+ static id: string;
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
10
+ static stdoutIsPrimaryData: boolean;
11
+ run(): Promise<unknown>;
12
+ }
@@ -0,0 +1,34 @@
1
+ import { Help, toConfiguredId } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ export default class ConfigIndex extends BaseCommand {
4
+ static id = 'config';
5
+ static summary = 'Manage research cache storage configuration (global vs project).';
6
+ static description = 'Show config subcommands for reading and writing Bonsai research cache configuration.';
7
+ static examples = [
8
+ {
9
+ description: 'get a configuration value',
10
+ command: '<%= config.bin %> <%= command.id %> get storage',
11
+ },
12
+ {
13
+ description: 'set a configuration value for the current project',
14
+ command: '<%= config.bin %> <%= command.id %> set storage project --local',
15
+ },
16
+ ];
17
+ static stdoutIsPrimaryData = true;
18
+ async run() {
19
+ if (!this.jsonEnabled()) {
20
+ await new Help(this.config, this.config.pjson.oclif.helpOptions ?? {}).showHelp(['config']);
21
+ }
22
+ const commands = this.config.commands
23
+ .filter((c) => c.id.startsWith('config:') && !c.hidden)
24
+ .map((c) => ({
25
+ id: toConfiguredId(c.id, this.config),
26
+ summary: c.summary || c.description || '',
27
+ }))
28
+ .sort((a, b) => a.id.localeCompare(b.id));
29
+ return {
30
+ topic: 'config',
31
+ commands,
32
+ };
33
+ }
34
+ }
@@ -6,11 +6,11 @@ export default class ConfigList extends ConfigCommand {
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
  {
9
- description: 'Show all effective config values',
9
+ description: 'show all effective config values',
10
10
  command: '<%= config.bin %> config list',
11
11
  },
12
12
  {
13
- description: 'Show only project-level config',
13
+ description: 'show only project-level config',
14
14
  command: '<%= config.bin %> config list --local',
15
15
  },
16
16
  ];
@@ -8,17 +8,17 @@ export default class ConfigSet extends ConfigCommand {
8
8
  `Valid keys: ${validKeysHint()}. The inline form \`<key>=<value>\` is also accepted.`;
9
9
  static examples = [
10
10
  {
11
- description: 'Store research cache inside the current project',
11
+ description: 'store research cache inside the current project',
12
12
  command: '<%= config.bin %> config set storage project --local',
13
13
  },
14
14
  {
15
- description: 'Set the user-level default using the inline key=value form',
15
+ description: 'set the user-level default using the inline key=value form',
16
16
  command: '<%= config.bin %> config set storage=global',
17
17
  },
18
18
  ];
19
19
  static args = {
20
- key: Args.string({ required: false, description: 'The configuration key to set.' }),
21
- value: Args.string({ required: false, description: 'The value to assign.' }),
20
+ key: Args.string({ required: false, description: 'the configuration key to set' }),
21
+ value: Args.string({ required: false, description: 'the value to assign' }),
22
22
  };
23
23
  static flags = {
24
24
  ...configScopeFlags({
@@ -26,26 +26,28 @@ export default class ConfigSet extends ConfigCommand {
26
26
  local: 'Write to project-level config (.bonsai.json in cwd).',
27
27
  }),
28
28
  'dry-run': Flags.boolean({
29
- description: 'Show what would be written without saving.',
29
+ description: 'show what would be written without saving',
30
30
  default: false,
31
31
  }),
32
32
  };
33
33
  static stdoutIsPrimaryData = true;
34
34
  async run() {
35
35
  const { keyArg, valueArg } = splitInlineKeyValue(this.args.key, this.args.value);
36
- if (!keyArg) {
37
- this.error('Missing required argument: key', { exit: 2 });
38
- }
39
- this.assertKnownKey(keyArg);
40
- this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
36
+ this.validateConfigKeyAndScope(keyArg, this.flags.global, this.flags.local);
41
37
  const meta = KEY_META[keyArg];
42
38
  if (valueArg === undefined) {
43
- this.error(`Missing required argument: value for key "${keyArg}"`, { exit: 2 });
39
+ this.error(`Missing required argument: value for key "${keyArg}"`, {
40
+ exit: 2,
41
+ code: 'MISSING_ARGUMENT',
42
+ });
44
43
  }
45
44
  const parsed = meta.parseValue(valueArg);
46
45
  if (!meta.isValid(parsed)) {
47
46
  const guidance = meta.values ? `Valid values: ${meta.values.join(', ')}.` : meta.description;
48
- this.error(`Invalid value "${valueArg}" for "${keyArg}". ${guidance}`, { exit: 2 });
47
+ this.error(`Invalid value "${valueArg}" for "${keyArg}". ${guidance}`, {
48
+ exit: 2,
49
+ code: 'INVALID_VALUE',
50
+ });
49
51
  }
50
52
  const scope = this.writeScope(this.flags.local);
51
53
  const formatted = meta.format(parsed);
@@ -63,6 +65,10 @@ export default class ConfigSet extends ConfigCommand {
63
65
  if (!configDir) {
64
66
  this.error('Could not determine user config directory. Use --local to write project config.', {
65
67
  exit: 1,
68
+ code: 'CONFIG_DIR_UNAVAILABLE',
69
+ suggestions: [
70
+ `Write project config instead: ${this.config.bin} config set <key> <value> --local`,
71
+ ],
66
72
  });
67
73
  }
68
74
  writeUserConfig(configDir, patch);
@@ -8,7 +8,7 @@ export default class ConfigUnset extends ConfigCommand<typeof ConfigUnset> {
8
8
  command: string;
9
9
  }[];
10
10
  static args: {
11
- key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
11
+ key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
12
12
  };
13
13
  static flags: {
14
14
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -7,16 +7,16 @@ export default class ConfigUnset extends ConfigCommand {
7
7
  static description = `Delete a key from user-level config (default) or project-level config (--local), restoring the built-in default.\n\nValid keys: ${validKeysHint()}.`;
8
8
  static examples = [
9
9
  {
10
- description: 'Remove the user-level storage setting',
10
+ description: 'remove the user-level storage setting',
11
11
  command: '<%= config.bin %> config unset storage',
12
12
  },
13
13
  {
14
- description: 'Remove the project-level storage setting',
14
+ description: 'remove the project-level storage setting',
15
15
  command: '<%= config.bin %> config unset storage --local',
16
16
  },
17
17
  ];
18
18
  static args = {
19
- key: Args.string({ required: true, description: 'The configuration key to remove.' }),
19
+ key: Args.string({ required: false, description: 'the configuration key to remove' }),
20
20
  };
21
21
  static flags = {
22
22
  ...configScopeFlags({
@@ -24,15 +24,14 @@ export default class ConfigUnset extends ConfigCommand {
24
24
  local: 'Remove from project-level config (.bonsai.json in cwd).',
25
25
  }),
26
26
  'dry-run': Flags.boolean({
27
- description: 'Show what would be removed without saving.',
27
+ description: 'show what would be removed without saving',
28
28
  default: false,
29
29
  }),
30
30
  };
31
31
  static stdoutIsPrimaryData = true;
32
32
  async run() {
33
33
  const key = this.args.key;
34
- this.assertKnownKey(key);
35
- this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
34
+ this.validateConfigKeyAndScope(key, this.flags.global, this.flags.local);
36
35
  const scope = this.writeScope(this.flags.local);
37
36
  if (this.flags['dry-run']) {
38
37
  if (!this.jsonEnabled())
@@ -48,6 +47,10 @@ export default class ConfigUnset extends ConfigCommand {
48
47
  if (!configDir) {
49
48
  this.error('Could not determine user config directory. Use --local to remove project config.', {
50
49
  exit: 1,
50
+ code: 'CONFIG_DIR_UNAVAILABLE',
51
+ suggestions: [
52
+ `Remove project config instead: ${this.config.bin} config unset ${key} --local`,
53
+ ],
51
54
  });
52
55
  }
53
56
  writeUserConfig(configDir, patch);
@@ -8,6 +8,7 @@ export default class FetchCommand extends BaseCommand<typeof FetchCommand> {
8
8
  description: string;
9
9
  command: string;
10
10
  }[];
11
+ static strict: boolean;
11
12
  static args: {
12
13
  url: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
13
14
  };
@@ -35,6 +36,7 @@ export default class FetchCommand extends BaseCommand<typeof FetchCommand> {
35
36
  private persistFreshArtifact;
36
37
  private persistSectionsIfFresh;
37
38
  run(): Promise<unknown>;
39
+ private fetchSingleTarget;
38
40
  }
39
41
  export declare function fetchFailureGuidance(message: string, url: string): {
40
42
  suggestions: string[];