@taurgis/bonsai 1.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 (159) hide show
  1. package/README.md +239 -0
  2. package/agents/agents/official-docs-researcher.agent.md +98 -0
  3. package/agents/instructions/web-research.instructions.md +29 -0
  4. package/agents/skills/web-research/SKILL.md +126 -0
  5. package/bin/cli.mjs +22 -0
  6. package/dist/base-command.d.ts +14 -0
  7. package/dist/base-command.js +56 -0
  8. package/dist/commands/config/base.d.ts +23 -0
  9. package/dist/commands/config/base.js +46 -0
  10. package/dist/commands/config/get.d.ts +20 -0
  11. package/dist/commands/config/get.js +48 -0
  12. package/dist/commands/config/list.d.ts +17 -0
  13. package/dist/commands/config/list.js +48 -0
  14. package/dist/commands/config/set.d.ts +31 -0
  15. package/dist/commands/config/set.js +94 -0
  16. package/dist/commands/config/unset.d.ts +21 -0
  17. package/dist/commands/config/unset.js +65 -0
  18. package/dist/commands/fetch.d.ts +38 -0
  19. package/dist/commands/fetch.js +311 -0
  20. package/dist/commands/import.d.ts +38 -0
  21. package/dist/commands/import.js +313 -0
  22. package/dist/commands/index.d.ts +35 -0
  23. package/dist/commands/index.js +306 -0
  24. package/dist/commands/inspect.d.ts +13 -0
  25. package/dist/commands/inspect.js +82 -0
  26. package/dist/commands/list.d.ts +25 -0
  27. package/dist/commands/list.js +143 -0
  28. package/dist/commands/prune.d.ts +24 -0
  29. package/dist/commands/prune.js +141 -0
  30. package/dist/commands/research/config/base.d.ts +23 -0
  31. package/dist/commands/research/config/base.js +46 -0
  32. package/dist/commands/research/config/get.d.ts +20 -0
  33. package/dist/commands/research/config/get.js +45 -0
  34. package/dist/commands/research/config/list.d.ts +17 -0
  35. package/dist/commands/research/config/list.js +42 -0
  36. package/dist/commands/research/config/set.d.ts +31 -0
  37. package/dist/commands/research/config/set.js +94 -0
  38. package/dist/commands/research/config/unset.d.ts +21 -0
  39. package/dist/commands/research/config/unset.js +59 -0
  40. package/dist/commands/research/import.d.ts +38 -0
  41. package/dist/commands/research/import.js +313 -0
  42. package/dist/commands/research/inspect.d.ts +13 -0
  43. package/dist/commands/research/inspect.js +86 -0
  44. package/dist/commands/research/list.d.ts +25 -0
  45. package/dist/commands/research/list.js +143 -0
  46. package/dist/commands/research/prune.d.ts +24 -0
  47. package/dist/commands/research/prune.js +141 -0
  48. package/dist/commands/research/search.d.ts +32 -0
  49. package/dist/commands/research/search.js +334 -0
  50. package/dist/commands/research/status.d.ts +17 -0
  51. package/dist/commands/research/status.js +108 -0
  52. package/dist/commands/research.d.ts +36 -0
  53. package/dist/commands/research.js +304 -0
  54. package/dist/commands/search.d.ts +32 -0
  55. package/dist/commands/search.js +334 -0
  56. package/dist/commands/status.d.ts +17 -0
  57. package/dist/commands/status.js +95 -0
  58. package/dist/commands.d.ts +24 -0
  59. package/dist/commands.js +24 -0
  60. package/dist/lib/config/index.d.ts +18 -0
  61. package/dist/lib/config/index.js +33 -0
  62. package/dist/lib/config/io.d.ts +8 -0
  63. package/dist/lib/config/io.js +75 -0
  64. package/dist/lib/config/resolve.d.ts +15 -0
  65. package/dist/lib/config/resolve.js +20 -0
  66. package/dist/lib/config/schema.d.ts +28 -0
  67. package/dist/lib/config/schema.js +32 -0
  68. package/dist/lib/research/artifact.d.ts +21 -0
  69. package/dist/lib/research/artifact.js +217 -0
  70. package/dist/lib/research/browser.d.ts +74 -0
  71. package/dist/lib/research/browser.js +359 -0
  72. package/dist/lib/research/cache-key.d.ts +5 -0
  73. package/dist/lib/research/cache-key.js +10 -0
  74. package/dist/lib/research/capture.d.ts +29 -0
  75. package/dist/lib/research/capture.js +181 -0
  76. package/dist/lib/research/compress.d.ts +5 -0
  77. package/dist/lib/research/compress.js +17 -0
  78. package/dist/lib/research/docs/__fixtures__/sphinx-searchindex.d.ts +1 -0
  79. package/dist/lib/research/docs/__fixtures__/sphinx-searchindex.js +2 -0
  80. package/dist/lib/research/docs/capabilities.d.ts +43 -0
  81. package/dist/lib/research/docs/capabilities.js +9 -0
  82. package/dist/lib/research/docs/clean-dom.d.ts +4 -0
  83. package/dist/lib/research/docs/clean-dom.js +41 -0
  84. package/dist/lib/research/docs/code-blocks.d.ts +5 -0
  85. package/dist/lib/research/docs/code-blocks.js +69 -0
  86. package/dist/lib/research/docs/detect.d.ts +6 -0
  87. package/dist/lib/research/docs/detect.js +239 -0
  88. package/dist/lib/research/docs/machine-readable.d.ts +21 -0
  89. package/dist/lib/research/docs/machine-readable.js +73 -0
  90. package/dist/lib/research/docs/markdown-source.d.ts +17 -0
  91. package/dist/lib/research/docs/markdown-source.js +194 -0
  92. package/dist/lib/research/docs/quality-gates.d.ts +9 -0
  93. package/dist/lib/research/docs/quality-gates.js +65 -0
  94. package/dist/lib/research/docs/remote-search-runner.d.ts +21 -0
  95. package/dist/lib/research/docs/remote-search-runner.js +54 -0
  96. package/dist/lib/research/docs/remote-search.d.ts +14 -0
  97. package/dist/lib/research/docs/remote-search.js +101 -0
  98. package/dist/lib/research/docs/rsc.d.ts +10 -0
  99. package/dist/lib/research/docs/rsc.js +30 -0
  100. package/dist/lib/research/docs/search-index.d.ts +13 -0
  101. package/dist/lib/research/docs/search-index.js +176 -0
  102. package/dist/lib/research/docs/section-artifacts.d.ts +12 -0
  103. package/dist/lib/research/docs/section-artifacts.js +75 -0
  104. package/dist/lib/research/docs/sections.d.ts +12 -0
  105. package/dist/lib/research/docs/sections.js +67 -0
  106. package/dist/lib/research/docs/source-map.d.ts +19 -0
  107. package/dist/lib/research/docs/source-map.js +87 -0
  108. package/dist/lib/research/docs/validate.d.ts +15 -0
  109. package/dist/lib/research/docs/validate.js +63 -0
  110. package/dist/lib/research/extract.d.ts +11 -0
  111. package/dist/lib/research/extract.js +83 -0
  112. package/dist/lib/research/fetcher.d.ts +29 -0
  113. package/dist/lib/research/fetcher.js +192 -0
  114. package/dist/lib/research/freshness.d.ts +21 -0
  115. package/dist/lib/research/freshness.js +82 -0
  116. package/dist/lib/research/markdown.d.ts +6 -0
  117. package/dist/lib/research/markdown.js +66 -0
  118. package/dist/lib/research/regression.d.ts +23 -0
  119. package/dist/lib/research/regression.js +93 -0
  120. package/dist/lib/research/resolve-target.d.ts +18 -0
  121. package/dist/lib/research/resolve-target.js +20 -0
  122. package/dist/lib/research/revalidate.d.ts +32 -0
  123. package/dist/lib/research/revalidate.js +152 -0
  124. package/dist/lib/research/schema.d.ts +47 -0
  125. package/dist/lib/research/schema.js +1 -0
  126. package/dist/lib/research/secret-scan.d.ts +5 -0
  127. package/dist/lib/research/secret-scan.js +37 -0
  128. package/dist/lib/research/secure-write.d.ts +16 -0
  129. package/dist/lib/research/secure-write.js +17 -0
  130. package/dist/lib/research/storage.d.ts +52 -0
  131. package/dist/lib/research/storage.js +182 -0
  132. package/dist/lib/research/store-roots.d.ts +29 -0
  133. package/dist/lib/research/store-roots.js +28 -0
  134. package/dist/lib/research/token-estimate.d.ts +5 -0
  135. package/dist/lib/research/token-estimate.js +7 -0
  136. package/dist/lib/research/url.d.ts +24 -0
  137. package/dist/lib/research/url.js +121 -0
  138. package/dist/sites/index.d.ts +4 -0
  139. package/dist/sites/index.js +28 -0
  140. package/dist/sites/salesforce/browser-search.d.ts +7 -0
  141. package/dist/sites/salesforce/browser-search.js +82 -0
  142. package/dist/sites/salesforce/coveo.d.ts +23 -0
  143. package/dist/sites/salesforce/coveo.js +161 -0
  144. package/dist/sites/salesforce/fetch-page.d.ts +6 -0
  145. package/dist/sites/salesforce/fetch-page.js +42 -0
  146. package/dist/sites/salesforce/index.d.ts +2 -0
  147. package/dist/sites/salesforce/index.js +16 -0
  148. package/dist/sites/salesforce/token.d.ts +26 -0
  149. package/dist/sites/salesforce/token.js +154 -0
  150. package/dist/sites/salesforce-developer/fetch-page.d.ts +3 -0
  151. package/dist/sites/salesforce-developer/fetch-page.js +18 -0
  152. package/dist/sites/salesforce-developer/index.d.ts +2 -0
  153. package/dist/sites/salesforce-developer/index.js +12 -0
  154. package/dist/sites/salesforce-doc-fetch.d.ts +14 -0
  155. package/dist/sites/salesforce-doc-fetch.js +321 -0
  156. package/dist/sites/types.d.ts +27 -0
  157. package/dist/sites/types.js +1 -0
  158. package/oclif.manifest.json +845 -0
  159. package/package.json +78 -0
@@ -0,0 +1,141 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { unlinkSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { BaseCommand } from '../base-command.js';
5
+ import { scanCacheDir } from '../lib/research/storage.js';
6
+ import { loadStoreRoots } from '../lib/research/store-roots.js';
7
+ import { parseTtlToMs } from '../lib/research/freshness.js';
8
+ export default class ResearchPrune extends BaseCommand {
9
+ static id = 'prune';
10
+ static summary = 'Clean up old or inactive research cache entries.';
11
+ static description = 'Prunes cached research entries based on age, inactivity, or artifact type to free up disk space.';
12
+ static examples = [
13
+ {
14
+ description: 'Perform a dry run of pruning entries older than 90 days',
15
+ command: '<%= config.bin %> prune --older-than 90d --dry-run',
16
+ },
17
+ {
18
+ description: 'Actually prune entries older than 30 days that are source scrapes',
19
+ command: '<%= config.bin %> prune --older-than 30d --artifact-type source --yes',
20
+ },
21
+ ];
22
+ static flags = {
23
+ 'older-than': Flags.string({
24
+ description: 'Prune entries older than duration (e.g. "30d", "90d").',
25
+ }),
26
+ inactive: Flags.string({
27
+ description: 'Prune entries inactive (unvalidated/unfetched) for duration (e.g. "14d", "30d").',
28
+ }),
29
+ 'artifact-type': Flags.option({
30
+ description: 'Filter pruning to specific artifact type.',
31
+ options: ['source', 'research_note'],
32
+ })(),
33
+ 'dry-run': Flags.boolean({
34
+ description: 'List files that would be deleted without actually deleting them.',
35
+ default: false,
36
+ }),
37
+ yes: Flags.boolean({
38
+ char: 'y',
39
+ description: 'Skip confirmation prompt and prune files.',
40
+ default: false,
41
+ }),
42
+ };
43
+ static stdoutIsPrimaryData = true;
44
+ async init() {
45
+ await super.init();
46
+ const { flags } = await this.parse(ResearchPrune);
47
+ this.flags = flags;
48
+ }
49
+ validatePruneFlags() {
50
+ if (!this.flags['older-than'] && !this.flags.inactive && !this.flags['artifact-type']) {
51
+ this.error('Must specify at least one pruning filter: --older-than, --inactive, or --artifact-type.', { exit: 2 });
52
+ }
53
+ if (!this.flags['dry-run'] && !this.flags.yes) {
54
+ this.error('Safety check: Please specify --yes to confirm pruning, or --dry-run to list files that would be deleted.', { exit: 2 });
55
+ }
56
+ }
57
+ shouldPrune(meta, currentTime) {
58
+ const fetched = meta.fetched_at ? new Date(meta.fetched_at).getTime() : 0;
59
+ const validated = meta.validated_at ? new Date(meta.validated_at).getTime() : 0;
60
+ const baseTime = Math.max(fetched, validated);
61
+ const ageMs = currentTime.getTime() - baseTime;
62
+ if (this.flags['artifact-type'] && meta.artifact_type !== this.flags['artifact-type']) {
63
+ return false;
64
+ }
65
+ if (this.flags['older-than']) {
66
+ const olderThanMs = parseTtlToMs(this.flags['older-than']);
67
+ if (ageMs < olderThanMs) {
68
+ return false;
69
+ }
70
+ }
71
+ if (this.flags.inactive) {
72
+ const inactiveMs = parseTtlToMs(this.flags.inactive);
73
+ if (ageMs < inactiveMs) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ }
79
+ // Prune across every read root (project + global). No cross-root dedup: each file is a distinct
80
+ // deletion target, so a cache key present in both locations is pruned in both.
81
+ findPruneCandidates(readRoots, currentTime) {
82
+ return readRoots.flatMap((dataDir) => scanCacheDir(join(dataDir, 'research'), (artifact, filePath) => {
83
+ if (!this.shouldPrune(artifact.metadata, currentTime))
84
+ return null;
85
+ return {
86
+ cacheKey: artifact.metadata.cache_key,
87
+ path: filePath,
88
+ topic: artifact.metadata.topic,
89
+ url: artifact.metadata.source_url,
90
+ };
91
+ }));
92
+ }
93
+ deletePruneCandidates(filesToPrune) {
94
+ let prunedCount = 0;
95
+ for (const f of filesToPrune) {
96
+ try {
97
+ unlinkSync(f.path);
98
+ prunedCount++;
99
+ }
100
+ catch (err) {
101
+ this.warn(`Failed to delete cache file ${f.path}: ${err.message}`);
102
+ }
103
+ }
104
+ return prunedCount;
105
+ }
106
+ async execute() {
107
+ this.validatePruneFlags();
108
+ const roots = loadStoreRoots({
109
+ configDir: this.config.configDir,
110
+ cwd: process.cwd(),
111
+ dataDir: this.config.dataDir,
112
+ });
113
+ const currentTime = new Date();
114
+ const filesToPrune = this.findPruneCandidates(roots.readRoots, currentTime);
115
+ const dryRun = this.flags['dry-run'];
116
+ const count = filesToPrune.length;
117
+ if (dryRun) {
118
+ if (!this.requestedJson()) {
119
+ this.log(`[Dry Run] Found ${count} research cache entries that would be pruned:\n`);
120
+ filesToPrune.forEach((f) => {
121
+ this.log(`- [${f.topic || 'No Topic'}] Key: ${f.cacheKey} (${f.url || 'Imported note'})`);
122
+ });
123
+ }
124
+ }
125
+ else {
126
+ const prunedCount = this.deletePruneCandidates(filesToPrune);
127
+ if (!this.requestedJson()) {
128
+ this.log(`Successfully pruned ${prunedCount} of ${count} research cache entries.`);
129
+ }
130
+ }
131
+ return {
132
+ dryRun,
133
+ prunedCount: dryRun ? 0 : filesToPrune.length,
134
+ candidateCount: filesToPrune.length,
135
+ files: filesToPrune.map((f) => ({
136
+ cacheKey: f.cacheKey,
137
+ path: f.path,
138
+ })),
139
+ };
140
+ }
141
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base-command.js';
3
+ import type { ConfigKey, ConfigScope } from '../../../lib/config/index.js';
4
+ /**
5
+ * Shared base for the four `research config` subcommands: the mutually-exclusive
6
+ * scope-flag guard, the binary write-scope helper, and known-key validation.
7
+ */
8
+ export declare abstract class ConfigCommand<T extends typeof Command> extends BaseCommand<T> {
9
+ protected assertScopeFlagsExclusive(global?: boolean, local?: boolean): void;
10
+ /** `--local` writes/reads the project file; otherwise the user-level file. */
11
+ protected writeScope(local?: boolean): 'user' | 'project';
12
+ /** Read scope for get/list: an explicit `--global`/`--local`, else the merged effective view. */
13
+ protected readScope(global?: boolean, local?: boolean): ConfigScope;
14
+ protected assertKnownKey(keyArg: string): asserts keyArg is ConfigKey;
15
+ }
16
+ /** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
17
+ export declare function configScopeFlags(descriptions: {
18
+ global: string;
19
+ local: string;
20
+ }): {
21
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ };
@@ -0,0 +1,46 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../../base-command.js';
3
+ import { isKnownKey, suggestKey, validKeysHint } from '../../../lib/config/index.js';
4
+ /**
5
+ * Shared base for the four `research config` subcommands: the mutually-exclusive
6
+ * scope-flag guard, the binary write-scope helper, and known-key validation.
7
+ */
8
+ export class ConfigCommand extends BaseCommand {
9
+ assertScopeFlagsExclusive(global, local) {
10
+ if (global && local) {
11
+ this.error('--global and --local are mutually exclusive.', { exit: 2 });
12
+ }
13
+ }
14
+ /** `--local` writes/reads the project file; otherwise the user-level file. */
15
+ writeScope(local) {
16
+ return local ? 'project' : 'user';
17
+ }
18
+ /** Read scope for get/list: an explicit `--global`/`--local`, else the merged effective view. */
19
+ readScope(global, local) {
20
+ if (global)
21
+ return 'global';
22
+ if (local)
23
+ return 'local';
24
+ return 'effective';
25
+ }
26
+ assertKnownKey(keyArg) {
27
+ if (!isKnownKey(keyArg)) {
28
+ const suggestion = suggestKey(keyArg);
29
+ const hint = suggestion ? ` Did you mean: ${suggestion}?` : '';
30
+ this.error(`Unknown config key: "${keyArg}".${hint} Valid keys: ${validKeysHint()}.`, {
31
+ exit: 2,
32
+ });
33
+ }
34
+ }
35
+ }
36
+ /** Build the `--global`/`--local` scope-flag pair with per-command descriptions. */
37
+ export function configScopeFlags(descriptions) {
38
+ return {
39
+ global: Flags.boolean({ char: 'g', description: descriptions.global }),
40
+ local: Flags.boolean({
41
+ description: descriptions.local,
42
+ aliases: ['project'],
43
+ charAliases: ['p'],
44
+ }),
45
+ };
46
+ }
@@ -0,0 +1,20 @@
1
+ import { ConfigCommand } from './base.js';
2
+ export default class ResearchConfigGet extends ConfigCommand<typeof ResearchConfigGet> {
3
+ static id: string;
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
10
+ static args: {
11
+ key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ static stdoutIsPrimaryData: boolean;
18
+ init(): Promise<void>;
19
+ execute(): Promise<unknown>;
20
+ }
@@ -0,0 +1,45 @@
1
+ import { Args } from '@oclif/core';
2
+ import { ConfigCommand, configScopeFlags } from './base.js';
3
+ import { KEY_META, BUILT_IN_DEFAULTS, readScopedConfig } from '../../../lib/config/index.js';
4
+ export default class ResearchConfigGet extends ConfigCommand {
5
+ static id = 'research config get';
6
+ static summary = 'Get the effective value of a research configuration key.';
7
+ static description = 'Print a config value. Without --global/--local, shows the merged effective value (flag > env > project > user > default).\n\nValid keys: storage.';
8
+ static examples = [
9
+ { description: 'Get the effective storage mode', command: '<%= config.bin %> research config get storage' },
10
+ {
11
+ description: 'Read only the project-level value',
12
+ command: '<%= config.bin %> research config get storage --local',
13
+ },
14
+ ];
15
+ static args = {
16
+ key: Args.string({ required: true, description: 'The configuration key to read.' }),
17
+ };
18
+ static flags = {
19
+ ...configScopeFlags({
20
+ global: 'Read from user-level config only.',
21
+ local: 'Read from project-level config only.',
22
+ }),
23
+ };
24
+ static stdoutIsPrimaryData = true;
25
+ async init() {
26
+ await super.init();
27
+ const { args, flags } = await this.parse(ResearchConfigGet);
28
+ this.args = args;
29
+ this.flags = flags;
30
+ }
31
+ async execute() {
32
+ const key = this.args.key;
33
+ this.assertKnownKey(key);
34
+ this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
35
+ const scope = this.readScope(this.flags.global, this.flags.local);
36
+ const value = readScopedConfig(scope, this.config.configDir, process.cwd())[key];
37
+ const meta = KEY_META[key];
38
+ const displayValue = value !== undefined ? value : BUILT_IN_DEFAULTS[key];
39
+ const formatted = meta.format(displayValue);
40
+ const suffix = value === undefined ? ' (not configured)' : '';
41
+ if (!this.requestedJson())
42
+ this.log(formatted + suffix);
43
+ return { key, value: displayValue };
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ import { ConfigCommand } from './base.js';
2
+ export default class ResearchConfigList extends ConfigCommand<typeof ResearchConfigList> {
3
+ static id: string;
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
10
+ static flags: {
11
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ static stdoutIsPrimaryData: boolean;
15
+ init(): Promise<void>;
16
+ execute(): Promise<unknown>;
17
+ }
@@ -0,0 +1,42 @@
1
+ import { ConfigCommand, configScopeFlags } from './base.js';
2
+ import { ALL_KEYS, KEY_META, BUILT_IN_DEFAULTS, readScopedConfig } from '../../../lib/config/index.js';
3
+ export default class ResearchConfigList extends ConfigCommand {
4
+ static id = 'research config list';
5
+ static summary = 'List all research configuration keys and their effective values.';
6
+ static description = 'Show every configuration key with its current value. Use --global/--local to show a single scope.\n\nValid keys: storage.';
7
+ static examples = [
8
+ { description: 'Show all effective config values', command: '<%= config.bin %> research config list' },
9
+ { description: 'Show only project-level config', command: '<%= config.bin %> research config list --local' },
10
+ ];
11
+ static flags = {
12
+ ...configScopeFlags({
13
+ global: 'Show user-level config only.',
14
+ local: 'Show project-level config only.',
15
+ }),
16
+ };
17
+ static stdoutIsPrimaryData = true;
18
+ async init() {
19
+ await super.init();
20
+ const { flags } = await this.parse(ResearchConfigList);
21
+ this.flags = flags;
22
+ }
23
+ async execute() {
24
+ this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
25
+ const scope = this.readScope(this.flags.global, this.flags.local);
26
+ const scoped = readScopedConfig(scope, this.config.configDir, process.cwd());
27
+ if (!this.requestedJson()) {
28
+ const width = Math.max(...ALL_KEYS.map((k) => k.length)) + 2;
29
+ for (const key of ALL_KEYS) {
30
+ const raw = scoped[key];
31
+ const formatted = raw !== undefined ? KEY_META[key].format(raw) : '(not configured)';
32
+ this.log(`${key.padEnd(width)}${formatted}`);
33
+ }
34
+ }
35
+ const values = {};
36
+ for (const key of ALL_KEYS) {
37
+ const raw = scoped[key];
38
+ values[key] = raw !== undefined ? raw : BUILT_IN_DEFAULTS[key];
39
+ }
40
+ return values;
41
+ }
42
+ }
@@ -0,0 +1,31 @@
1
+ import { ConfigCommand } from './base.js';
2
+ export default class ResearchConfigSet extends ConfigCommand<typeof ResearchConfigSet> {
3
+ static id: string;
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
10
+ static args: {
11
+ key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
12
+ value: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
13
+ };
14
+ static flags: {
15
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ static stdoutIsPrimaryData: boolean;
20
+ init(): Promise<void>;
21
+ execute(): Promise<unknown>;
22
+ }
23
+ /**
24
+ * Accept the `key=value` convenience form alongside the canonical `key value`.
25
+ * Splits only when no separate value positional was supplied, and on the FIRST `=`
26
+ * so a value may itself contain `=`.
27
+ */
28
+ export declare function splitInlineKeyValue(rawKey: string | undefined, rawValue: string | undefined): {
29
+ keyArg: string | undefined;
30
+ valueArg: string | undefined;
31
+ };
@@ -0,0 +1,94 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { ConfigCommand, configScopeFlags } from './base.js';
3
+ import { KEY_META, writeUserConfig, writeProjectConfig } from '../../../lib/config/index.js';
4
+ export default class ResearchConfigSet extends ConfigCommand {
5
+ static id = 'research config set';
6
+ static summary = 'Set a research configuration key.';
7
+ static description = 'Persist a configuration value. Writes user-level config by default; pass --local to write the project-level config (.bonsai.json in cwd).\n\n' +
8
+ 'Valid keys: storage. The inline form `<key>=<value>` is also accepted.';
9
+ static examples = [
10
+ {
11
+ description: 'Store research cache inside the current project',
12
+ command: '<%= config.bin %> research config set storage project --local',
13
+ },
14
+ {
15
+ description: 'Set the user-level default using the inline key=value form',
16
+ command: '<%= config.bin %> research config set storage=global',
17
+ },
18
+ ];
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.' }),
22
+ };
23
+ static flags = {
24
+ ...configScopeFlags({
25
+ global: 'Write to user-level config (default).',
26
+ local: 'Write to project-level config (.bonsai.json in cwd).',
27
+ }),
28
+ 'dry-run': Flags.boolean({
29
+ description: 'Show what would be written without saving.',
30
+ default: false,
31
+ }),
32
+ };
33
+ static stdoutIsPrimaryData = true;
34
+ async init() {
35
+ await super.init();
36
+ const { args, flags } = await this.parse(ResearchConfigSet);
37
+ this.args = args;
38
+ this.flags = flags;
39
+ }
40
+ async execute() {
41
+ const { keyArg, valueArg } = splitInlineKeyValue(this.args.key, this.args.value);
42
+ if (!keyArg) {
43
+ this.error('Missing required argument: key', { exit: 2 });
44
+ }
45
+ this.assertKnownKey(keyArg);
46
+ this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
47
+ const meta = KEY_META[keyArg];
48
+ if (valueArg === undefined) {
49
+ this.error(`Missing required argument: value for key "${keyArg}"`, { exit: 2 });
50
+ }
51
+ const parsed = meta.parseValue(valueArg);
52
+ if (!meta.isValid(parsed)) {
53
+ const guidance = meta.values ? `Valid values: ${meta.values.join(', ')}.` : meta.description;
54
+ this.error(`Invalid value "${valueArg}" for "${keyArg}". ${guidance}`, { exit: 2 });
55
+ }
56
+ const scope = this.writeScope(this.flags.local);
57
+ const formatted = meta.format(parsed);
58
+ if (this.flags['dry-run']) {
59
+ if (!this.requestedJson())
60
+ this.log(`[dry-run] Would set ${keyArg} = ${formatted} (${scope})`);
61
+ return { key: keyArg, value: parsed, scope, dryRun: true };
62
+ }
63
+ const patch = { [keyArg]: parsed };
64
+ if (scope === 'project') {
65
+ writeProjectConfig(process.cwd(), patch);
66
+ }
67
+ else {
68
+ const configDir = this.config.configDir;
69
+ if (!configDir) {
70
+ this.error('Could not determine user config directory. Use --local to write project config.', {
71
+ exit: 1,
72
+ });
73
+ }
74
+ writeUserConfig(configDir, patch);
75
+ }
76
+ if (!this.requestedJson())
77
+ this.log(`Set ${keyArg} = ${formatted} (${scope})`);
78
+ return { key: keyArg, value: parsed, scope, dryRun: false };
79
+ }
80
+ }
81
+ /**
82
+ * Accept the `key=value` convenience form alongside the canonical `key value`.
83
+ * Splits only when no separate value positional was supplied, and on the FIRST `=`
84
+ * so a value may itself contain `=`.
85
+ */
86
+ export function splitInlineKeyValue(rawKey, rawValue) {
87
+ if (rawValue === undefined && rawKey !== undefined) {
88
+ const eq = rawKey.indexOf('=');
89
+ if (eq !== -1) {
90
+ return { keyArg: rawKey.slice(0, eq), valueArg: rawKey.slice(eq + 1) };
91
+ }
92
+ }
93
+ return { keyArg: rawKey, valueArg: rawValue };
94
+ }
@@ -0,0 +1,21 @@
1
+ import { ConfigCommand } from './base.js';
2
+ export default class ResearchConfigUnset extends ConfigCommand<typeof ResearchConfigUnset> {
3
+ static id: string;
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
10
+ static args: {
11
+ key: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
12
+ };
13
+ static flags: {
14
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ };
18
+ static stdoutIsPrimaryData: boolean;
19
+ init(): Promise<void>;
20
+ execute(): Promise<unknown>;
21
+ }
@@ -0,0 +1,59 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { ConfigCommand, configScopeFlags } from './base.js';
3
+ import { writeUserConfig, writeProjectConfig } from '../../../lib/config/index.js';
4
+ export default class ResearchConfigUnset extends ConfigCommand {
5
+ static id = 'research config unset';
6
+ static summary = 'Remove a research configuration key.';
7
+ static description = 'Delete a key from user-level config (default) or project-level config (--local), restoring the built-in default.\n\nValid keys: storage.';
8
+ static examples = [
9
+ { description: 'Remove the user-level storage setting', command: '<%= config.bin %> research config unset storage' },
10
+ {
11
+ description: 'Remove the project-level storage setting',
12
+ command: '<%= config.bin %> research config unset storage --local',
13
+ },
14
+ ];
15
+ static args = {
16
+ key: Args.string({ required: true, description: 'The configuration key to remove.' }),
17
+ };
18
+ static flags = {
19
+ ...configScopeFlags({
20
+ global: 'Remove from user-level config (default).',
21
+ local: 'Remove from project-level config (.bonsai.json in cwd).',
22
+ }),
23
+ 'dry-run': Flags.boolean({ description: 'Show what would be removed without saving.', default: false }),
24
+ };
25
+ static stdoutIsPrimaryData = true;
26
+ async init() {
27
+ await super.init();
28
+ const { args, flags } = await this.parse(ResearchConfigUnset);
29
+ this.args = args;
30
+ this.flags = flags;
31
+ }
32
+ async execute() {
33
+ const key = this.args.key;
34
+ this.assertKnownKey(key);
35
+ this.assertScopeFlagsExclusive(this.flags.global, this.flags.local);
36
+ const scope = this.writeScope(this.flags.local);
37
+ if (this.flags['dry-run']) {
38
+ if (!this.requestedJson())
39
+ this.log(`[dry-run] Would unset ${key} (${scope})`);
40
+ return { key, scope, dryRun: true };
41
+ }
42
+ const patch = { [key]: undefined };
43
+ if (scope === 'project') {
44
+ writeProjectConfig(process.cwd(), patch);
45
+ }
46
+ else {
47
+ const configDir = this.config.configDir;
48
+ if (!configDir) {
49
+ this.error('Could not determine user config directory. Use --local to remove project config.', {
50
+ exit: 1,
51
+ });
52
+ }
53
+ writeUserConfig(configDir, patch);
54
+ }
55
+ if (!this.requestedJson())
56
+ this.log(`Unset ${key} (${scope})`);
57
+ return { key, scope, dryRun: false };
58
+ }
59
+ }
@@ -0,0 +1,38 @@
1
+ import * as fs from 'node:fs';
2
+ import { BaseCommand } from '../../base-command.js';
3
+ export default class ResearchImport extends BaseCommand<typeof ResearchImport> {
4
+ static id: string;
5
+ static summary: string;
6
+ static description: string;
7
+ static examples: {
8
+ description: string;
9
+ command: string;
10
+ }[];
11
+ static args: {
12
+ url: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
13
+ };
14
+ static flags: {
15
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'input-format': import("@oclif/core/interfaces").OptionFlag<"compressed" | "detailed", import("@oclif/core/interfaces").CustomOptions>;
18
+ topic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ tags: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ tier: import("@oclif/core/interfaces").OptionFlag<"stable" | "standard" | "volatile", import("@oclif/core/interfaces").CustomOptions>;
21
+ 'source-url': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
+ ttl: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ storage: import("@oclif/core/interfaces").OptionFlag<"global" | "project" | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ };
25
+ static stdoutIsPrimaryData: boolean;
26
+ init(): Promise<void>;
27
+ private readStdin;
28
+ private getSourceUrls;
29
+ private validateSourceMode;
30
+ protected fsExistsSync(filePath: string): boolean;
31
+ protected fsStatSync(filePath: string): fs.Stats;
32
+ protected fsReadFileSync(filePath: string): string;
33
+ private readAndValidateFile;
34
+ private readAndValidateInput;
35
+ private deriveImportCacheKey;
36
+ private buildImportArtifact;
37
+ execute(): Promise<unknown>;
38
+ }