@taurgis/bonsai 1.0.3 → 1.0.5

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 (72) hide show
  1. package/README.md +38 -6
  2. package/agents/agents/salesforce-docs-researcher.agent.md +117 -0
  3. package/agents/hooks/README.md +62 -0
  4. package/agents/hooks/claude-code/hooks/bonsai-web-fetch.sh +22 -0
  5. package/agents/hooks/claude-code/settings.json +16 -0
  6. package/agents/hooks/codex/hooks/bonsai-user-prompt-context.sh +18 -0
  7. package/agents/hooks/codex/hooks/bonsai-web-fetch.sh +22 -0
  8. package/agents/hooks/codex/hooks.json +28 -0
  9. package/agents/hooks/cursor/hooks/bonsai-web-fetch.sh +21 -0
  10. package/agents/hooks/cursor/hooks.json +12 -0
  11. package/agents/hooks/github-copilot/bonsai-web-fetch.json +23 -0
  12. package/agents/hooks/github-copilot/bonsai-web-fetch.sh +19 -0
  13. package/agents/instructions/salesforce-research.instructions.md +57 -0
  14. package/agents/instructions/web-research.instructions.md +2 -1
  15. package/bin/cli.mjs +13 -3
  16. package/dist/base-command.d.ts +20 -0
  17. package/dist/base-command.js +27 -1
  18. package/dist/commands/config/get.js +2 -2
  19. package/dist/commands/config/list.js +2 -2
  20. package/dist/commands/config/set.js +2 -2
  21. package/dist/commands/config/unset.js +2 -2
  22. package/dist/commands/fetch.d.ts +5 -0
  23. package/dist/commands/fetch.js +98 -27
  24. package/dist/commands/import.d.ts +1 -0
  25. package/dist/commands/import.js +51 -11
  26. package/dist/commands/list.js +18 -3
  27. package/dist/commands/prune.js +22 -6
  28. package/dist/commands/search.js +31 -30
  29. package/dist/commands/status.js +10 -9
  30. package/dist/hooks/command-not-found/suggest.d.ts +9 -0
  31. package/dist/hooks/command-not-found/suggest.js +93 -0
  32. package/dist/lib/config/index.d.ts +8 -3
  33. package/dist/lib/config/index.js +19 -4
  34. package/dist/lib/config/resolve.d.ts +21 -1
  35. package/dist/lib/config/resolve.js +42 -1
  36. package/dist/lib/config/schema.d.ts +3 -0
  37. package/dist/lib/config/schema.js +11 -1
  38. package/dist/lib/research/artifact.d.ts +13 -2
  39. package/dist/lib/research/artifact.js +27 -7
  40. package/dist/lib/research/browser.d.ts +12 -0
  41. package/dist/lib/research/browser.js +40 -3
  42. package/dist/lib/research/compress.d.ts +19 -0
  43. package/dist/lib/research/compress.js +31 -0
  44. package/dist/lib/research/docs/capabilities.d.ts +1 -1
  45. package/dist/lib/research/docs/code-blocks.js +24 -0
  46. package/dist/lib/research/docs/discovery-index.d.ts +14 -0
  47. package/dist/lib/research/docs/discovery-index.js +112 -0
  48. package/dist/lib/research/docs/machine-readable.d.ts +2 -2
  49. package/dist/lib/research/docs/machine-readable.js +3 -7
  50. package/dist/lib/research/docs/remote-search-runner.js +61 -1
  51. package/dist/lib/research/docs/section-artifacts.d.ts +3 -2
  52. package/dist/lib/research/docs/section-artifacts.js +5 -5
  53. package/dist/lib/research/docs/sections.d.ts +1 -0
  54. package/dist/lib/research/docs/sections.js +19 -1
  55. package/dist/lib/research/freshness.d.ts +9 -1
  56. package/dist/lib/research/freshness.js +24 -2
  57. package/dist/lib/research/keywords.d.ts +24 -0
  58. package/dist/lib/research/keywords.js +247 -0
  59. package/dist/lib/research/revalidate.d.ts +3 -1
  60. package/dist/lib/research/revalidate.js +8 -4
  61. package/dist/lib/research/search-index.d.ts +18 -0
  62. package/dist/lib/research/search-index.js +109 -0
  63. package/dist/lib/research/secret-scan.js +4 -2
  64. package/dist/lib/research/storage.d.ts +1 -0
  65. package/dist/lib/research/storage.js +1 -1
  66. package/dist/lib/research/summarize.d.ts +17 -0
  67. package/dist/lib/research/summarize.js +400 -0
  68. package/dist/lib/research/url.js +5 -2
  69. package/dist/lib/text.d.ts +33 -0
  70. package/dist/lib/text.js +63 -0
  71. package/oclif.manifest.json +8 -8
  72. package/package.json +17 -3
package/README.md CHANGED
@@ -4,6 +4,8 @@ Bonsai is a standalone local research cache CLI for AI agents. It turns official
4
4
 
5
5
  The package is published as `@taurgis/bonsai` and installs a `bonsai` binary.
6
6
 
7
+ 📚 **Full documentation: [bonsai.rhino-inquisitor.com](https://bonsai.rhino-inquisitor.com/)**
8
+
7
9
  > Because the npm package is scoped, the correct one-shot command is `npx @taurgis/bonsai ...`. The unscoped `npx bonsai` name resolves to the separate `bonsai` package on npm unless an unscoped shim is published later.
8
10
 
9
11
  ---
@@ -101,7 +103,7 @@ npx @taurgis/bonsai <url> [flags]
101
103
  Common flags:
102
104
 
103
105
  * `--topic`, `-t`: Primary category/topic for metadata tagging.
104
- * `--tags`, `-g`: Searchable tags. Can be repeated.
106
+ * `--tags`, `-g`: Searchable tags. Can be repeated. When omitted, tags are auto-generated from the content by keyword detection (recorded with an `auto-generated tags via keyword extraction` quality note); passing `--tags` always takes precedence.
105
107
  * `--format`, `-f`: Output density: `compressed` or `detailed`.
106
108
  * `--tier`: Freshness tier: `stable`, `standard`, or `volatile`.
107
109
  * `--ttl`, `-l`: Custom predicted lifespan, such as `2h`, `7d`, or `30d`.
@@ -114,7 +116,7 @@ Common flags:
114
116
 
115
117
  ### `import [url]`
116
118
 
117
- Save custom Markdown notes directly to the cache.
119
+ Save custom Markdown notes directly to the cache. As with `fetch`, when `--tags` is omitted the tags are auto-generated from the note content by keyword detection.
118
120
 
119
121
  ```bash
120
122
  npx @taurgis/bonsai import [url] --stdin [flags]
@@ -144,15 +146,37 @@ Search cached research by metadata and content snippets.
144
146
  npx @taurgis/bonsai search "<keywords>" [flags]
145
147
  ```
146
148
 
149
+ ### `list`
150
+
151
+ List cached artifacts by metadata filters (`--topic`, `--tags`, `--freshness`, `--artifact-type`, `--capture-method`, `--limit`), newest first, without printing content.
152
+
153
+ ```bash
154
+ npx @taurgis/bonsai list [flags]
155
+ ```
156
+
157
+ ### `prune`
158
+
159
+ Delete old or inactive cache entries. Requires a filter (`--older-than`, `--inactive`, or `--artifact-type`) and either `--dry-run` or `--yes`.
160
+
161
+ ```bash
162
+ npx @taurgis/bonsai prune --older-than 90d --dry-run
163
+ ```
164
+
147
165
  ### `config`
148
166
 
149
- Choose global or project-local cache storage.
167
+ Two keys are configurable:
168
+
169
+ | Key | Values | Default | Effect |
170
+ | --- | --- | --- | --- |
171
+ | `storage` | `global`, `project` | `global` | Where cache files are stored. |
172
+ | `summary` | `conservative`, `balanced`, `aggressive` | `conservative` | How aggressively the `compressed` format is shortened when structural compression alone leaves it close to `detailed`. Prose is condensed extractively (no LLM); headings, code blocks, tables, and lists are always preserved. |
150
173
 
151
174
  ```bash
152
175
  npx @taurgis/bonsai config set storage project --local
176
+ npx @taurgis/bonsai config set summary balanced
153
177
  ```
154
178
 
155
- Project config is stored in `.bonsai.json`; project cache files are stored under `.bonsai/research/`.
179
+ Both keys can also be overridden per-invocation via the `BONSAI_STORAGE` and `BONSAI_SUMMARY` environment variables. Project config is stored in `.bonsai.json`; project cache files are stored under `.bonsai/research/`.
156
180
 
157
181
  ---
158
182
 
@@ -234,6 +258,14 @@ When run with `--json`, commands return a stable envelope:
234
258
 
235
259
  ## Reference Documentation
236
260
 
237
- * [Command Reference](docs/commands.md)
238
- * [Caching Protocol Specification](docs/cache-protocol.md)
261
+ The full documentation site lives at
262
+ **[bonsai.rhino-inquisitor.com](https://bonsai.rhino-inquisitor.com/)** (built with
263
+ VitePress from the [`docs/`](docs/) folder). Key pages:
264
+
265
+ * [Getting Started](docs/guide/getting-started.md)
266
+ * [Caching & Freshness](docs/concepts/caching-and-freshness.md)
267
+ * [Compression & Token Budgeting](docs/concepts/compression.md)
268
+ * [Agent Integration](docs/how-to/agent-integration.md)
269
+ * [Command Reference](docs/reference/commands.md)
270
+ * [Caching Protocol Specification](docs/reference/cache-protocol.md)
239
271
  * [Troubleshooting and Limitations](docs/troubleshooting.md)
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: 'Salesforce Docs Researcher'
3
+ description: 'Researches official documentation through Bonsai and returns source-cited findings, specialized for Salesforce Help and Developer docs'
4
+ model: 'Auto'
5
+ tools: ['vscode/askQuestions', 'execute', 'read', 'search', 'web', 'vscode/memory']
6
+ argument-hint: 'What Salesforce topic should I research in official docs?'
7
+ metadata:
8
+ version: '1.0.0'
9
+ ---
10
+
11
+ # Salesforce Docs Researcher Agent
12
+
13
+ You are a Forward documentation research specialist. Your job is to find current official documentation, cache it through Bonsai, and return concise source-cited findings that another agent can apply safely. This is the Official Docs Researcher specialized for Salesforce: the same workflow, plus the Salesforce site-module notes in "Working with Salesforce Sites" below.
14
+
15
+ ## Scope
16
+
17
+ Use this agent for technical changes, platform behavior, APIs, SDKs, standards, release notes, changelogs, beta docs, security advisories, and other documentation-sensitive work. For Salesforce that includes Apex, SOQL, Flows, permissions, Setup, packaging, and the REST, Bulk, and Metadata APIs, plus Lightning and LWC references.
18
+
19
+ Do not research from memory alone. Training-data knowledge does not satisfy this workflow.
20
+
21
+ ## Default Workflow
22
+
23
+ 1. Identify the product, version, edition, and source authority needed for the request.
24
+ 2. Search the Bonsai cache first:
25
+
26
+ ```bash
27
+ bonsai search "<topic or keywords>"
28
+ ```
29
+
30
+ 3. If the cache misses or does not cover the question, locate official source URLs. Prefer vendor docs, standards bodies, API references, and official release notes.
31
+ 4. Capture each source through Bonsai:
32
+
33
+ ```bash
34
+ bonsai <official-url> --format detailed
35
+ ```
36
+
37
+ 5. Use `--rendered` for SPAs or pages where static extraction is incomplete:
38
+
39
+ ```bash
40
+ bonsai <official-url> --rendered --format detailed
41
+ ```
42
+
43
+ 6. Summarize only what the official sources support. Include source URLs, validation time when available, version notes, and any important limitations.
44
+
45
+ Inside the Bonsai repository, use the development binary instead of an installed global command:
46
+
47
+ ```bash
48
+ node bin/cli.mjs search "<topic or keywords>" --json
49
+ node bin/cli.mjs <official-url> --format detailed --json
50
+ ```
51
+
52
+ For one-shot published usage outside this repo, use the scoped package name:
53
+
54
+ ```bash
55
+ npx @taurgis/bonsai <official-url> --format detailed
56
+ ```
57
+
58
+ Do not document or run bare `npx bonsai` unless an unscoped npm shim is actually published.
59
+
60
+ ## Working with Salesforce Sites
61
+
62
+ Salesforce Help and Developer docs are Lightning Web Runtime (LWR) pages: the article text is rendered by JavaScript, so a plain fetch returns an almost-empty shell. Bonsai ships a per-host **site module** that forces browser rendering and runs Salesforce-specific extraction, so a normal Bonsai fetch already returns clean Markdown for these hosts — you do not need to add `--rendered` yourself.
63
+
64
+ | Host | Module | Capabilities |
65
+ | --- | --- | --- |
66
+ | `help.salesforce.com` | `salesforce` (Salesforce Help) | Rendered fetch and live `--domain` search (Coveo backend) |
67
+ | `developer.salesforce.com` | `salesforce-developer` (Salesforce Developer) | Rendered fetch only — no search |
68
+
69
+ - **Search Help live.** For Help topics where you do not yet have a URL, search Salesforce's own backend instead of guessing:
70
+
71
+ ```bash
72
+ npx @taurgis/bonsai search "single sign-on" --domain help.salesforce.com
73
+ ```
74
+
75
+ Results come straight from Coveo and are validated against an allow-list of Salesforce documentation hosts. There is no `--domain` search for `developer.salesforce.com`; passing it exits with an error, so find Developer URLs another way, then fetch them through Bonsai.
76
+ - **Matching is by exact hostname.** `help.salesforce.com` matches; subdomains do not. Prefer canonical `/s/articleView?id=…` Help URLs; Bonsai normalizes legacy `/help_doccontent?id=…` links to them automatically.
77
+ - **Revalidation re-fetches in full.** Module-fetched pages have no cheap `ETag`/`If-Modified-Since` shortcut, so refreshing a stale Salesforce page does a full re-render. `siteModuleId` in the JSON output confirms which module captured a page.
78
+
79
+ ## Freshness Policy
80
+
81
+ Choose the freshness tier based on the source:
82
+
83
+ - `stable`: standards, specs, MDN reference pages, pinned major-version docs.
84
+ - `standard`: vendor API docs, SDK docs, normal technical references.
85
+ - `volatile`: release notes, changelogs, latest pages, security advisories, beta docs.
86
+
87
+ Stale volatile sources must be revalidated before they are trusted. Never overwrite a good cache entry with an error response.
88
+
89
+ ## Fallbacks
90
+
91
+ If direct web access was unavoidable because Bonsai could not fetch the content, import the synthesized notes back into Bonsai before returning:
92
+
93
+ ```bash
94
+ bonsai import <url> --file <path>
95
+ ```
96
+
97
+ For multi-source synthesis:
98
+
99
+ ```bash
100
+ bonsai import \
101
+ --topic "<descriptive topic>" \
102
+ --source-url <url1> \
103
+ --source-url <url2> \
104
+ --file <path>
105
+ ```
106
+
107
+ ## Response Contract
108
+
109
+ Return:
110
+
111
+ - Answer summary.
112
+ - Official source URLs used.
113
+ - Version, edition, or date constraints.
114
+ - Cache status or validation timestamp when Bonsai output provides it.
115
+ - Gaps or uncertainty where official docs do not answer the question.
116
+
117
+ Keep stdout clean when using `--json`; warnings and diagnostic notes belong in stderr or in the final prose summary.
@@ -0,0 +1,62 @@
1
+ # Bonsai Hook Examples
2
+
3
+ These examples show how to steer agents away from one-off web fetch tools and
4
+ toward Bonsai's reusable research cache.
5
+
6
+ They are examples, not an installer contract. Copy the relevant folder contents
7
+ into the agent's native hook location and adjust paths if your repository layout
8
+ differs.
9
+
10
+ ## What The Hooks Do
11
+
12
+ The hooks deny native URL-fetch tools and return model-visible guidance like:
13
+
14
+ ```bash
15
+ bonsai https://example.com/docs --format detailed
16
+ ```
17
+
18
+ Most hook systems cannot transparently replace a web-fetch tool call with a shell
19
+ command. Blocking the native fetch and giving the agent the exact Bonsai command
20
+ is the reliable cross-agent pattern.
21
+
22
+ ## Use With Caution
23
+
24
+ These hooks are intentionally intrusive. If Bonsai fails because of network,
25
+ installation, permissions, or extraction issues, the agent may have no fallback
26
+ path to its native web-fetch tool. That can block valid research instead of
27
+ making it better.
28
+
29
+ Start with one repository, watch how the agent behaves, and remove or narrow the
30
+ hook if it blocks work you still need the native tool to handle.
31
+
32
+ ## Supported Examples
33
+
34
+ | Agent | Native hook file | Example |
35
+ | -------------- | ----------------------- | ----------------- |
36
+ | Claude Code | `.claude/settings.json` | `claude-code/` |
37
+ | GitHub Copilot | `.github/hooks/*.json` | `github-copilot/` |
38
+ | Cursor | `.cursor/hooks.json` | `cursor/` |
39
+ | Codex | `.codex/hooks.json` | `codex/` |
40
+
41
+ Antigravity is intentionally omitted: no current official hook configuration
42
+ surface was found for replacing `read_url_content` or similar URL-fetch tools.
43
+
44
+ Codex has one important limitation: `PreToolUse` hooks cover shell, patch, and
45
+ MCP tool calls, but not every built-in web/search tool. The Codex example blocks
46
+ MCP-style URL fetch tools when they are available and adds prompt-time context
47
+ that tells the agent to prefer Bonsai for URLs.
48
+
49
+ ## Published Command
50
+
51
+ The examples use `bonsai` on the assumption that the CLI is installed. For
52
+ one-shot usage, tell agents to run:
53
+
54
+ ```bash
55
+ npx @taurgis/bonsai <url> --format detailed
56
+ ```
57
+
58
+ Inside the Bonsai repository, use the development binary:
59
+
60
+ ```bash
61
+ node bin/cli.mjs <url> --format detailed
62
+ ```
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+
6
+ node -e '
7
+ const fs = require("node:fs");
8
+ const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
9
+ const raw = JSON.stringify(input.tool_input ?? input);
10
+ const match = raw.match(/https?:\/\/[^\s"'\''<>)}\]]+/);
11
+ const url = match ? match[0] : "<url>";
12
+ const command = `bonsai ${url} --format detailed`;
13
+ const reason =
14
+ `Use Bonsai instead of the native web fetch so the result is cached and reusable. Run: ${command}`;
15
+ process.stdout.write(JSON.stringify({
16
+ hookSpecificOutput: {
17
+ hookEventName: "PreToolUse",
18
+ permissionDecision: "deny",
19
+ permissionDecisionReason: reason
20
+ }
21
+ }) + "\n");
22
+ ' <<< "$input"
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "WebFetch|WebSearch",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash .claude/hooks/bonsai-web-fetch.sh",
10
+ "timeout": 10
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+
6
+ node -e '
7
+ const fs = require("node:fs");
8
+ const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
9
+ const prompt = String(input.prompt ?? "");
10
+ if (!/https?:\/\/\S+/.test(prompt)) process.exit(0);
11
+ process.stdout.write(JSON.stringify({
12
+ hookSpecificOutput: {
13
+ hookEventName: "UserPromptSubmit",
14
+ additionalContext:
15
+ "When fetching documentation or URL content for this prompt, use Bonsai first: bonsai <url> --format detailed. Use --rendered only when static extraction is incomplete."
16
+ }
17
+ }) + "\n");
18
+ ' <<< "$input"
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+
6
+ node -e '
7
+ const fs = require("node:fs");
8
+ const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
9
+ const raw = JSON.stringify(input.tool_input ?? input);
10
+ const match = raw.match(/https?:\/\/[^\s"'\''<>)}\]]+/);
11
+ const url = match ? match[0] : "<url>";
12
+ const command = `bonsai ${url} --format detailed`;
13
+ const reason =
14
+ `Use Bonsai instead of this URL-fetch tool so the result is cached and reusable. Run: ${command}`;
15
+ process.stdout.write(JSON.stringify({
16
+ hookSpecificOutput: {
17
+ hookEventName: "PreToolUse",
18
+ permissionDecision: "deny",
19
+ permissionDecisionReason: reason
20
+ }
21
+ }) + "\n");
22
+ ' <<< "$input"
@@ -0,0 +1,28 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "mcp__.*(web|fetch|get_url_content|read_url_content).*",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash .codex/hooks/bonsai-web-fetch.sh",
10
+ "timeout": 10,
11
+ "statusMessage": "Routing URL fetch through Bonsai"
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "UserPromptSubmit": [
17
+ {
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "bash .codex/hooks/bonsai-user-prompt-context.sh",
22
+ "timeout": 10
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+
6
+ node -e '
7
+ const fs = require("node:fs");
8
+ const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
9
+ const raw = JSON.stringify(input.tool_input ?? input.toolArgs ?? input);
10
+ const match = raw.match(/https?:\/\/[^\s"'\''<>)}\]]+/);
11
+ const url = match ? match[0] : "<url>";
12
+ const command = `bonsai ${url} --format detailed`;
13
+ const message =
14
+ `Use Bonsai instead of the native URL fetch so the result is cached and reusable. Run: ${command}`;
15
+ process.stdout.write(JSON.stringify({
16
+ continue: true,
17
+ permission: "deny",
18
+ user_message: "Native web fetch blocked. Use Bonsai for reusable research cache entries.",
19
+ agent_message: message
20
+ }) + "\n");
21
+ ' <<< "$input"
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ {
6
+ "command": "bash .cursor/hooks/bonsai-web-fetch.sh",
7
+ "matcher": "web_fetch|WebFetch|read_url_content|get_url_content",
8
+ "timeout": 10
9
+ }
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ {
6
+ "type": "command",
7
+ "matcher": "web_fetch",
8
+ "bash": "bash .github/hooks/bonsai-web-fetch.sh",
9
+ "powershell": "bash .github/hooks/bonsai-web-fetch.sh",
10
+ "timeoutSec": 10
11
+ }
12
+ ],
13
+ "PreToolUse": [
14
+ {
15
+ "type": "command",
16
+ "matcher": "WebFetch",
17
+ "bash": "bash .github/hooks/bonsai-web-fetch.sh",
18
+ "powershell": "bash .github/hooks/bonsai-web-fetch.sh",
19
+ "timeoutSec": 10
20
+ }
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ input="$(cat)"
5
+
6
+ node -e '
7
+ const fs = require("node:fs");
8
+ const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
9
+ const raw = JSON.stringify(input.toolArgs ?? input.tool_input ?? input);
10
+ const match = raw.match(/https?:\/\/[^\s"'\''<>)}\]]+/);
11
+ const url = match ? match[0] : "<url>";
12
+ const command = `bonsai ${url} --format detailed`;
13
+ const reason =
14
+ `Use Bonsai instead of web_fetch so the result is cached and reusable. Run: ${command}`;
15
+ process.stdout.write(JSON.stringify({
16
+ permissionDecision: "deny",
17
+ permissionDecisionReason: reason
18
+ }) + "\n");
19
+ ' <<< "$input"
@@ -0,0 +1,57 @@
1
+ ---
2
+ description: 'Verify current official docs before technical repo changes — inline via the web-research skill for minor research, via the Salesforce Docs Researcher subagent for extensive research. Specialized for Salesforce Help and Developer docs.'
3
+ applyTo: '**'
4
+ skills:
5
+ - web-research
6
+ metadata:
7
+ version: '1.0.1'
8
+ ---
9
+
10
+ # Web Research Requirement (Salesforce)
11
+
12
+ ## Mandatory Pre-Step
13
+
14
+ - Verify the relevant current official documentation **in the same task** before creating, updating, refactoring, scaffolding, or deleting technical content.
15
+ - Apply what you find — cite official source URLs when the change relies on platform behavior or standards.
16
+ - Research is the requirement; how you run it scales with scope (below). Training-data knowledge never satisfies this — only docs fetched in the current task do.
17
+
18
+ ## Choose How to Research by Scope
19
+
20
+ - **Minor research — run it inline.** For a single known page, one platform, or a quick flag/API/version check, the main agent invokes the **web-research** skill directly. A separate subagent is not required.
21
+ - **Extensive research — delegate to a subagent.** For multiple sources or platforms, unfamiliar territory, broad audits, or anything that produces verbose output you do not want in the main context, run the **Salesforce Docs Researcher** subagent and apply its findings.
22
+ - The main agent may always use the **web-research** skill directly — choose the subagent to isolate large research, not because inline research is disallowed.
23
+ - When unsure, start inline and escalate to the subagent once the work spans several sources.
24
+
25
+ ## Shared Cache
26
+
27
+ - Both paths use the same freshness-tiered Bonsai cache (data directory or project-local `.bonsai/research/`): it reuses fresh notes, cheaply revalidates stale ones, and re-fetches only on a miss.
28
+ - If Bonsai is configured for project storage and `.bonsai/research/` is not ignored by git, treat those cache artifacts as intentional shared project files. It is OK to check them in, and agents must not delete them as incidental generated output without an explicit request.
29
+ - Re-running on a recent topic is cheap — research the topic rather than skipping it to "save" a fetch.
30
+
31
+ ## Working with Salesforce Sites
32
+
33
+ Salesforce Help and Developer docs are JavaScript-rendered (LWR), so Bonsai uses per-host **site modules** that render and extract them for you — a normal Bonsai fetch returns clean Markdown for these hosts without `--rendered`.
34
+
35
+ - **Search Help live** when you do not have a URL yet — this hits Salesforce's own backend, not the local cache:
36
+
37
+ ```bash
38
+ npx @taurgis/bonsai search "single sign-on" --domain help.salesforce.com
39
+ ```
40
+
41
+ - `developer.salesforce.com` has a rendered fetch but **no** `--domain` search (passing it errors); find Developer URLs another way, then fetch them through Bonsai.
42
+ - Matching is by exact hostname; prefer canonical `/s/articleView?id=…` Help URLs (Bonsai normalizes legacy `/help_doccontent?id=…` links automatically).
43
+
44
+ ## When Not to Use
45
+
46
+ - No technical content is being modified (purely editorial changes).
47
+ - You already fetched and applied official docs **in the current task**. Training-data knowledge does not satisfy this — only docs fetched in the same task do.
48
+ - The request is too simple to warrant research (typo, lint fix not involving platform behavior).
49
+
50
+ ## Examples
51
+
52
+ - ✅ Confirming one platform's current flag or frontmatter field before a small edit → run the web-research skill inline.
53
+ - ✅ Finding a Salesforce Help article by topic → `npx @taurgis/bonsai search "<topic>" --domain help.salesforce.com`, then fetch the chosen URL through Bonsai.
54
+ - ✅ Comparing conventions across several platforms, or auditing/refactoring source broadly → delegate to the Salesforce Docs Researcher subagent.
55
+ - ✅ Before editing instructions, prompts, agents, skills, workflows, or docs — reading source code does not replace checking current platform guidance.
56
+ - ✅ Include official source URLs when the change references platform behavior or standards.
57
+ - ❌ Don't modify technical content without researching first, and don't treat cached or training knowledge as equivalent — official guidance evolves and must be verified per task.
@@ -4,7 +4,7 @@ applyTo: '**'
4
4
  skills:
5
5
  - web-research
6
6
  metadata:
7
- version: '3.1.0'
7
+ version: '3.1.1'
8
8
  ---
9
9
 
10
10
  # Web Research Requirement
@@ -25,6 +25,7 @@ metadata:
25
25
  ## Shared Cache
26
26
 
27
27
  - Both paths use the same freshness-tiered Bonsai cache (data directory or project-local `.bonsai/research/`): it reuses fresh notes, cheaply revalidates stale ones, and re-fetches only on a miss.
28
+ - If Bonsai is configured for project storage and `.bonsai/research/` is not ignored by git, treat those cache artifacts as intentional shared project files. It is OK to check them in, and agents must not delete them as incidental generated output without an explicit request.
28
29
  - Re-running on a recent topic is cheap — research the topic rather than skipping it to "save" a fetch.
29
30
 
30
31
  ## When Not to Use
package/bin/cli.mjs CHANGED
@@ -6,17 +6,27 @@ import { fileURLToPath } from 'node:url';
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
 
9
+ // Normalize argv so the whole oclif pipeline sees one consistent command. oclif's
10
+ // error handler re-reads process.argv (not the args passed to execute) when it renders
11
+ // help on a parse error, so rewriting only the execute() args leaves the help renderer
12
+ // trying to resolve the bare URL as a command — which crashes with a stack trace. Rewrite
13
+ // process.argv itself so both the run path and the help/error path agree.
9
14
  const rawArgv = process.argv.slice(2);
10
15
  const [firstArg, ...restArgv] = rawArgv;
11
- const args =
16
+ // Treat any first arg that looks like a URL (carries a scheme) as the `fetch` shorthand.
17
+ // Routing wrong-scheme URLs (ftp://, file://) here too means fetch can answer with a clear
18
+ // "only http/https" error instead of oclif's cryptic "command not found".
19
+ const looksLikeUrl = firstArg?.includes('://') ?? false;
20
+ const normalizedArgv =
12
21
  firstArg === 'help'
13
22
  ? [...restArgv, '--help']
14
- : firstArg?.startsWith('http://') || firstArg?.startsWith('https://')
23
+ : looksLikeUrl
15
24
  ? ['fetch', ...rawArgv]
16
25
  : rawArgv;
17
26
 
27
+ process.argv = [process.argv[0], process.argv[1], ...normalizedArgv];
28
+
18
29
  await execute({
19
- args,
20
30
  development: false,
21
31
  dir: __dirname + '/../',
22
32
  });
@@ -17,6 +17,26 @@ export declare abstract class BaseCommand<T extends typeof Command> extends Comm
17
17
  * JSON envelope, so machine output stays clean.
18
18
  */
19
19
  warn(input: string | Error): string | Error;
20
+ /**
21
+ * The exit code an error maps to. oclif `CLIError`s carry it in `err.oclif.exit`; other errors may
22
+ * expose `err.exitCode`. Defined once so the process exit code (`catch`) and the JSON envelope
23
+ * (`toErrorJson`) can never disagree — a mismatch between these two was itself the bug this fixes.
24
+ */
25
+ private static exitCodeOf;
26
+ /**
27
+ * Align the process exit code with the code reported in the JSON envelope. oclif's default
28
+ * `catch` sets `process.exitCode = err.exitCode ?? 1`, but oclif `CLIError`s carry their code in
29
+ * `err.oclif.exit` (never `err.exitCode`), so a usage error (`this.error(msg, { exit: 2 })`) would
30
+ * exit the process with 1 under `--json` while `toErrorJson` correctly reports `exitCode: 2`. That
31
+ * contradiction breaks the deterministic-exit-code contract agents rely on. Pre-seed the code from
32
+ * the shared resolver so the framework's `??` keeps it, then defer to the default behavior.
33
+ */
34
+ catch(err: Error & {
35
+ oclif?: {
36
+ exit?: number;
37
+ };
38
+ exitCode?: number;
39
+ }): Promise<any>;
20
40
  /** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
21
41
  protected envelopeCommandId(): string;
22
42
  /** Single source of truth for the `--json` envelope shape, shared by success and error output. */
@@ -1,4 +1,5 @@
1
1
  import { Command, Errors } from '@oclif/core';
2
+ import { invalidEnvOverrideWarnings } from './lib/config/index.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`
@@ -19,6 +20,11 @@ export class BaseCommand extends Command {
19
20
  });
20
21
  this.flags = flags;
21
22
  this.args = args;
23
+ // Surface a set-but-invalid BONSAI_* override once per run. Resolution silently drops such a
24
+ // value, so without this a typo'd env var would take no effect with no signal. Warnings go to
25
+ // stderr (even under --json), so machine output stays clean.
26
+ for (const warning of invalidEnvOverrideWarnings(process.env))
27
+ this.warn(warning);
22
28
  }
23
29
  /**
24
30
  * Always surface warnings on stderr, even under `--json`. oclif silences `warn()` in JSON mode,
@@ -30,6 +36,26 @@ export class BaseCommand extends Command {
30
36
  Errors.warn(input);
31
37
  return input;
32
38
  }
39
+ /**
40
+ * The exit code an error maps to. oclif `CLIError`s carry it in `err.oclif.exit`; other errors may
41
+ * expose `err.exitCode`. Defined once so the process exit code (`catch`) and the JSON envelope
42
+ * (`toErrorJson`) can never disagree — a mismatch between these two was itself the bug this fixes.
43
+ */
44
+ static exitCodeOf(err) {
45
+ return err?.oclif?.exit ?? err?.exitCode ?? 1;
46
+ }
47
+ /**
48
+ * Align the process exit code with the code reported in the JSON envelope. oclif's default
49
+ * `catch` sets `process.exitCode = err.exitCode ?? 1`, but oclif `CLIError`s carry their code in
50
+ * `err.oclif.exit` (never `err.exitCode`), so a usage error (`this.error(msg, { exit: 2 })`) would
51
+ * exit the process with 1 under `--json` while `toErrorJson` correctly reports `exitCode: 2`. That
52
+ * contradiction breaks the deterministic-exit-code contract agents rely on. Pre-seed the code from
53
+ * the shared resolver so the framework's `??` keeps it, then defer to the default behavior.
54
+ */
55
+ async catch(err) {
56
+ process.exitCode = process.exitCode ?? BaseCommand.exitCodeOf(err);
57
+ return super.catch(err);
58
+ }
33
59
  /** Command id for the JSON envelope; falls back to the binary name when a command has no id. */
34
60
  envelopeCommandId() {
35
61
  return this.ctor.id || this.config.bin;
@@ -58,7 +84,7 @@ export class BaseCommand extends Command {
58
84
  /** Mirror the success envelope for failures so JSON consumers get one consistent shape. */
59
85
  toErrorJson(err) {
60
86
  const e = err;
61
- const exitCode = e?.oclif?.exit ?? e?.exitCode ?? 1;
87
+ const exitCode = BaseCommand.exitCodeOf(e);
62
88
  return this.envelope({ ok: false, exitCode, stderr: e?.message ?? String(err), data: null });
63
89
  }
64
90
  }
@@ -1,10 +1,10 @@
1
1
  import { Args } from '@oclif/core';
2
2
  import { ConfigCommand, configScopeFlags } from './base.js';
3
- import { KEY_META, BUILT_IN_DEFAULTS, readScopedConfig } from '../../lib/config/index.js';
3
+ import { KEY_META, BUILT_IN_DEFAULTS, readScopedConfig, validKeysHint, } from '../../lib/config/index.js';
4
4
  export default class ConfigGet extends ConfigCommand {
5
5
  static id = 'config get';
6
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.';
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
10
  description: 'Get the effective storage mode',