@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,25 +0,0 @@
1
- import { BaseCommand } from '../../base-command.js';
2
- export default class ResearchList extends BaseCommand<typeof ResearchList> {
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
- topic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
- tags: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- freshness: import("@oclif/core/interfaces").OptionFlag<"fresh" | "stale_grace" | "stale_expired" | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
- 'artifact-type': import("@oclif/core/interfaces").OptionFlag<"source" | "research_note" | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
- 'capture-method': import("@oclif/core/interfaces").OptionFlag<"static_fetch" | "browser_fallback" | "agent_supplied" | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
- limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
17
- };
18
- static stdoutIsPrimaryData: boolean;
19
- init(): Promise<void>;
20
- private validateListFlags;
21
- private matchesFilters;
22
- private scanCacheDirForList;
23
- private logListResults;
24
- execute(): Promise<unknown>;
25
- }
@@ -1,143 +0,0 @@
1
- import { Flags } from '@oclif/core';
2
- import { BaseCommand } from '../../base-command.js';
3
- import { scanCacheDirs } from '../../lib/research/storage.js';
4
- import { loadStoreRoots } from '../../lib/research/store-roots.js';
5
- import { evaluateFreshness } from '../../lib/research/freshness.js';
6
- export default class ResearchList extends BaseCommand {
7
- static id = 'research list';
8
- static summary = 'List cached research artifacts by metadata filters.';
9
- static description = 'Lists cached research artifacts, including metadata details like path, source count, freshness, token estimates, and quality metrics without printing full content.';
10
- static examples = [
11
- {
12
- description: 'List all cached entries',
13
- command: '<%= config.bin %> research list',
14
- },
15
- {
16
- description: 'List cached entries for a specific topic with JSON output',
17
- command: '<%= config.bin %> research list --topic "React Suspense" --json',
18
- },
19
- {
20
- description: 'List only fresh entries filtered by tags',
21
- command: '<%= config.bin %> research list --freshness fresh --tags node --tags url',
22
- },
23
- ];
24
- static flags = {
25
- topic: Flags.string({
26
- char: 't',
27
- description: 'Filter by exact topic (case-insensitive).',
28
- }),
29
- tags: Flags.string({
30
- char: 'g',
31
- description: 'Filter by tags (must match all tags specified).',
32
- multiple: true,
33
- }),
34
- freshness: Flags.option({
35
- description: 'Filter by freshness state.',
36
- options: ['fresh', 'stale_grace', 'stale_expired'],
37
- })(),
38
- 'artifact-type': Flags.option({
39
- description: 'Filter by artifact type.',
40
- options: ['source', 'research_note'],
41
- })(),
42
- 'capture-method': Flags.option({
43
- description: 'Filter by capture method.',
44
- options: ['static_fetch', 'browser_fallback', 'agent_supplied'],
45
- })(),
46
- limit: Flags.integer({
47
- description: 'Maximum number of results to return (default 50, max 100).',
48
- default: 50,
49
- }),
50
- };
51
- static stdoutIsPrimaryData = true;
52
- async init() {
53
- await super.init();
54
- const { flags } = await this.parse(ResearchList);
55
- this.flags = flags;
56
- }
57
- validateListFlags() {
58
- const limit = this.flags.limit;
59
- if (limit !== undefined && (limit < 1 || limit > 100)) {
60
- this.error('Limit must be between 1 and 100.', { exit: 2 });
61
- }
62
- }
63
- matchesFilters(meta, freshness) {
64
- if (this.flags.topic &&
65
- (!meta.topic || meta.topic.trim().toLowerCase() !== this.flags.topic.trim().toLowerCase())) {
66
- return false;
67
- }
68
- if (this.flags.tags && this.flags.tags.length > 0) {
69
- const metaTagsLower = (meta.tags || []).map((t) => t.toLowerCase());
70
- const hasAllTags = this.flags.tags.every((t) => metaTagsLower.includes(t.toLowerCase()));
71
- if (!hasAllTags) {
72
- return false;
73
- }
74
- }
75
- if (this.flags['artifact-type'] && meta.artifact_type !== this.flags['artifact-type']) {
76
- return false;
77
- }
78
- if (this.flags['capture-method'] && meta.capture_method !== this.flags['capture-method']) {
79
- return false;
80
- }
81
- if (this.flags.freshness && freshness !== this.flags.freshness) {
82
- return false;
83
- }
84
- return true;
85
- }
86
- scanCacheDirForList(readRoots, currentTime) {
87
- return scanCacheDirs(readRoots, (artifact, filePath) => {
88
- if (artifact.metadata.status !== 'active')
89
- return null;
90
- const freshness = evaluateFreshness(artifact.metadata, currentTime, null);
91
- if (!this.matchesFilters(artifact.metadata, freshness))
92
- return null;
93
- return {
94
- cacheKey: artifact.metadata.cache_key,
95
- path: filePath,
96
- artifactType: artifact.metadata.artifact_type,
97
- sourceUrls: artifact.metadata.source_urls,
98
- topic: artifact.metadata.topic,
99
- tags: artifact.metadata.tags,
100
- freshness,
101
- captureMethod: artifact.metadata.capture_method,
102
- tokenEstimate: artifact.metadata.token_estimate,
103
- qualityNotes: artifact.metadata.quality_notes,
104
- fetchedAt: artifact.metadata.fetched_at,
105
- validatedAt: artifact.metadata.validated_at,
106
- };
107
- });
108
- }
109
- logListResults(finalResults) {
110
- if (this.requestedJson())
111
- return;
112
- if (finalResults.length === 0) {
113
- this.log('No cached research entries found matching filters.');
114
- return;
115
- }
116
- this.log(`Found ${finalResults.length} cached research entries:\n`);
117
- finalResults.forEach((res, index) => {
118
- this.log(`${index + 1}. [${res.topic || 'No Topic'}] Key: ${res.cacheKey}`);
119
- this.log(` Type: ${res.artifactType} | Freshness: ${res.freshness}`);
120
- this.log(` Tokens: compressed=${res.tokenEstimate?.compressed || 0}, detailed=${res.tokenEstimate?.detailed || 0}`);
121
- this.log(` Source URLs: ${res.sourceUrls.join(', ')}\n`);
122
- });
123
- }
124
- async execute() {
125
- this.validateListFlags();
126
- const roots = loadStoreRoots({
127
- configDir: this.config.configDir,
128
- cwd: process.cwd(),
129
- dataDir: this.config.dataDir,
130
- });
131
- const currentTime = new Date();
132
- const results = this.scanCacheDirForList(roots.readRoots, currentTime);
133
- // Sort by validated_at or fetched_at descending (most recent first)
134
- results.sort((a, b) => {
135
- const timeA = new Date(a.validatedAt || a.fetchedAt || 0).getTime();
136
- const timeB = new Date(b.validatedAt || b.fetchedAt || 0).getTime();
137
- return timeB - timeA;
138
- });
139
- const finalResults = results.slice(0, this.flags.limit);
140
- this.logListResults(finalResults);
141
- return finalResults;
142
- }
143
- }
@@ -1,24 +0,0 @@
1
- import { BaseCommand } from '../../base-command.js';
2
- export default class ResearchPrune extends BaseCommand<typeof ResearchPrune> {
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
- 'older-than': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
- inactive: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- 'artifact-type': import("@oclif/core/interfaces").OptionFlag<"source" | "research_note" | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
- 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
- yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
- };
17
- static stdoutIsPrimaryData: boolean;
18
- init(): Promise<void>;
19
- private validatePruneFlags;
20
- private shouldPrune;
21
- private findPruneCandidates;
22
- private deletePruneCandidates;
23
- execute(): Promise<unknown>;
24
- }
@@ -1,141 +0,0 @@
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 = 'research 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 %> research 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 %> research 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
- }
@@ -1,32 +0,0 @@
1
- import { BaseCommand } from '../../base-command.js';
2
- export default class ResearchSearch extends BaseCommand<typeof ResearchSearch> {
3
- static id: string;
4
- static summary: string;
5
- static description: string;
6
- static args: {
7
- query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
- };
9
- static flags: {
10
- topic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
- tags: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
- 'artifact-type': import("@oclif/core/interfaces").OptionFlag<"source" | "research_note" | "index" | "section" | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
14
- 'include-stale': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
- domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
- remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
- };
18
- static stdoutIsPrimaryData: boolean;
19
- init(): Promise<void>;
20
- private validateSearchFlags;
21
- private getSearchQueryTerms;
22
- private matchesFilters;
23
- private phraseMatchBonus;
24
- private calculateScore;
25
- private makeSnippet;
26
- private scanCacheDirForResults;
27
- private logSearchResults;
28
- private printResults;
29
- private executeSiteSearch;
30
- private executeRemoteSearch;
31
- execute(): Promise<unknown>;
32
- }