sf-docs-agent 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -260
- package/agents/sf-docs.md +15 -9
- package/fetch-docs.mjs +10 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,312 +1,113 @@
|
|
|
1
1
|
# sf-docs-agent
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
A Claude Code subagent that answers Salesforce questions from the official docs and hands back quotes, not pages.
|
|
3
|
+
**Read the docs, keep your context.** A Claude Code agent that reads the official Salesforce docs for you and returns just the lines that answer, each with its official URL.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
lightningdesignsystem.com. The model picks the lines and a script copies them from the cached page,
|
|
9
|
-
each with the official URL it came from, so you can check any quote with one grep.
|
|
5
|
+
[](https://www.npmjs.com/package/sf-docs-agent)
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
**84% fewer tokens** added to your session than Salesforce's own docs skill · **117 of 117** cited lines still on today's page · **every line** checkable with one `grep`
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
[Install](#install) · [Use](#use) · [How it works](#how-it-works) · [How it compares](#how-it-compares) · [Limitations](#limitations) · [Benchmarks](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/benchmarks.md)
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
the lines you asked for.
|
|
11
|
+
---
|
|
17
12
|
|
|
18
|
-
Salesforce's
|
|
19
|
-
|
|
20
|
-
it
|
|
21
|
-
JS-heavy, shell-rendered, or hard to extract with naive fetching."
|
|
13
|
+
| Salesforce's platform-docs-get skill · 4,578 tokens | sf-docs-agent · 303 tokens |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Every page it read lands in your session | Only the lines that answer, each with its official URL |
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
*"How many versions can a flow have?"* Same right answer. 4,578 tokens become 303.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| Runtime | Python, `playwright`, `playwright-stealth` | Node, `playwright-core` |
|
|
28
|
-
| Bot checks | spoofs a desktop user agent, optional stealth | neither |
|
|
29
|
-
| developer, architect and LDS pages | renders and scrapes the HTML | reads the `.md` copy where one exists |
|
|
30
|
-
| Finding pages | you give the URL | `llms.txt` indexes, sitemaps and link lists |
|
|
31
|
-
| What lands in your context | every page | a report of quotes |
|
|
32
|
-
| Checking an answer | re-read the page | `grep` one line |
|
|
19
|
+
The agent reads the pages in its own context. The model picks the lines and a script copies them word for word from the cached page, so no quote is written from the model's memory.
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
copies the lines. Every quote is copied word for word from the page, not written from the model's memory, and you can
|
|
36
|
-
check it with one `grep` against the cached copy, which saves curly quotes as straight ones and drops image links and
|
|
37
|
-
the padding in table rows.
|
|
21
|
+
I built this because every docs lookup was dumping whole pages into my Claude Code session. I only wanted the lines that answer my question.
|
|
38
22
|
|
|
39
|
-
##
|
|
23
|
+
## Install
|
|
40
24
|
|
|
41
|
-
|
|
42
|
-
added 84% fewer tokens to your context and used 63% fewer tokens over the whole session, with the right answer on
|
|
43
|
-
10 of 10.
|
|
25
|
+
Needs Node 22+ and Claude Code.
|
|
44
26
|
|
|
45
|
-
|
|
27
|
+
```
|
|
28
|
+
npx sf-docs-agent
|
|
29
|
+
```
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
|---|---|---|---|---|---|
|
|
49
|
-
| 1. Scratch org editions | developer | 2,178 → **289** | **86.7%** | 338,852 → **119,355** | **64.8%** |
|
|
50
|
-
| 2. Platform event allocation | developer | 4,107 → **490** | **88.1%** | 387,598 → **120,396** | **68.9%** |
|
|
51
|
-
| 3. QueryLocator batch limits | developer | 2,568 → **581** | **77.4%** | 406,878 → **113,611** | **72.1%** |
|
|
52
|
-
| 4. Integration patterns | architect | 1,104 → **484** | **56.2%** | 154,068 → **125,540** | **18.5%** |
|
|
53
|
-
| 5. Salesforce Connect editions | help | 1,948 → **322** | **83.5%** | 382,751 → **157,746** | **58.8%** |
|
|
54
|
-
| 6. Flow version limits | help | 4,578 → **290** | **93.7%** | 339,464 → **164,888** | **51.4%** |
|
|
55
|
-
| 7. Headless 360 | admin | 3,772 → **380** | **89.9%** | 427,542 → **117,258** | **72.6%** |
|
|
56
|
-
| 8. SLDS tooltips | lightningdesignsystem | 3,876 → **414** | **89.3%** | 340,914 → **112,252** | **67.1%** |
|
|
57
|
-
| 9. Bulk API batch size | developer | 2,134 → **411** | **80.7%** | 506,340 → **125,870** | **75.1%** |
|
|
58
|
-
| 10. Pub/Sub fetch limit | developer | 1,199 → **716** | **40.3%** | 236,766 → **130,110** | **45.0%** |
|
|
59
|
-
| **All** | | 27,464 → **4,377** | **84.1%** | 3,521,173 → **1,287,026** | **63.4%** |
|
|
31
|
+
Then start a new Claude Code session. The agent is listed as `sf-docs`. Add `--project` to install it for the current project only.
|
|
60
32
|
|
|
61
33
|
<details>
|
|
62
|
-
<summary>
|
|
63
|
-
|
|
64
|
-
1. What are the valid values for edition in a scratch org definition file?
|
|
65
|
-
2. What is the hourly platform event publishing allocation for Enterprise Edition and for Developer Edition?
|
|
66
|
-
3. With a QueryLocator start method, what is the maximum batch scope, and how many records can the QueryLocator return?
|
|
67
|
-
4. Which integration patterns does the architect Integration Patterns guide list?
|
|
68
|
-
5. Which editions include the Salesforce Connect cross-org adapter?
|
|
69
|
-
6. How many versions can a flow have, and how many active flows per flow type in Enterprise vs Professional?
|
|
70
|
-
7. What three technologies is Salesforce Headless 360 built on, and does it replace the Lightning UI?
|
|
71
|
-
8. Should you put an SLDS tooltip on a non-interactive element?
|
|
72
|
-
9. How many records fit in one Bulk API batch, and in one sObject Collections create request?
|
|
73
|
-
10. How many events can a Pub/Sub API client request across all FetchRequests in one Subscribe call?
|
|
34
|
+
<summary>Headless browser, Linux, update, remove, cache</summary>
|
|
74
35
|
|
|
75
|
-
|
|
36
|
+
- **Headless browser:** some sites have no `.md` copy of their pages, so the agent opens them in a headless browser. The installer asks before it downloads Chromium (about 200 MB). Pass `--yes` to skip the question.
|
|
37
|
+
- **Linux:** also run `node "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sf-docs/node_modules/playwright-core/cli.js" install-deps chromium` once.
|
|
38
|
+
- **Update:** `npx sf-docs-agent@latest`
|
|
39
|
+
- **Remove:** delete `~/.claude/agents/sf-docs.md` (or `.claude/agents/sf-docs.md` in the project), then `~/.claude/sf-docs/` once no project uses it. Chromium stays in Playwright's cache (`~/Library/Caches/ms-playwright` on macOS, `~/.cache/ms-playwright` on Linux); delete that too if nothing else uses Playwright.
|
|
40
|
+
- **Cache:** pages stay cached for 24 hours. Set `SF_DOCS_CACHE_HOURS` in your Claude Code `settings.json` to change it (`0` fetches every time).
|
|
76
41
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
The agent reads the `.md` copy of a page where the site publishes one. Not every Salesforce docs site does yet.
|
|
80
|
-
|
|
81
|
-
Give the script an `.html` or bare URL for a new developer docs, architect or LDS page and it fetches the `.md` copy
|
|
82
|
-
instead. The same goes for an atlas URL that now redirects to the new docs.
|
|
83
|
-
|
|
84
|
-
The agent finds pages through the site's `llms.txt` index or its sitemaps.
|
|
85
|
-
|
|
86
|
-
| Site | How | Where it finds pages |
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| developer.salesforce.com, new docs | the `.md` copy Salesforce publishes | `/docs/llms.txt` and one index per product |
|
|
89
|
-
| developer.salesforce.com, atlas docs (Apex, Apex Reference, Metadata API, Object Reference) | renders the page, code samples included | `/docs/llms-product-docs.txt` |
|
|
90
|
-
| developer.salesforce.com, API reference pages (`?meta=<operation>`) | renders the page | the product's index |
|
|
91
|
-
| architect.salesforce.com | the `.md` copy | `/llms.txt` |
|
|
92
|
-
| help.salesforce.com, articles and knowledge articles | renders the page | links in other docs and articles, and Help's product sitemaps |
|
|
93
|
-
| admin.salesforce.com | renders the page | `/sitemap_index.xml` and its post sitemaps |
|
|
94
|
-
| lightningdesignsystem.com (SLDS 2) | the `.md` copy | the home page's link list |
|
|
95
|
-
| v1.lightningdesignsystem.com (SLDS 1) | renders the page | the home page's link list |
|
|
42
|
+
</details>
|
|
96
43
|
|
|
97
44
|
## Use
|
|
98
45
|
|
|
99
|
-
Ask
|
|
100
|
-
Salesforce docs link and ask. It picks up the intent either way.
|
|
46
|
+
Ask in your own words. Name the site or product if you know it, or paste a Salesforce docs link and ask about it.
|
|
101
47
|
|
|
102
48
|
```
|
|
103
49
|
Go scan the developer.salesforce.com docs on the current release's governors and limits
|
|
104
50
|
```
|
|
105
51
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
The sample prompt is broad on purpose: I wanted to see the agent decide what "governors and limits" covers, and whether it could find everything in a broad question.
|
|
109
|
-
|
|
110
|
-
Here are the results of that prompt, one run each on Opus 5.5 and Sonnet 5.5 at effort high. Each session called the sf-docs agent, which returned quotes with their
|
|
111
|
-
official URLs, and the session turned them into the answer.
|
|
112
|
-
|
|
113
|
-
<details>
|
|
114
|
-
<summary><b>Sonnet 5.5, effort high, through the agent</b>: 86 seconds, 34k agent tokens</summary>
|
|
115
|
-
<br>
|
|
116
|
-
|
|
117
|
-
> The `sf-docs` agent finished scanning the Apex governors and limits pages. All limit values below are quoted from the cached concept page, `apex_gov_limits.htm`. The release version is an inference, not a stated fact.
|
|
118
|
-
>
|
|
119
|
-
> ## Which release
|
|
120
|
-
> - The cached pages are the unversioned "current" docs, and they don't name a release.
|
|
121
|
-
> - The version dropdown lists **Summer '26 (API 67.0, path `262.0`)** as the newest versioned link.
|
|
122
|
-
> - The page's own sidebar links point to `264.0` paths. That is most likely **Winter '27 (API 68.0)**.
|
|
123
|
-
> - The agent did not fetch the 262 or 264 versioned pages to confirm.
|
|
124
|
-
>
|
|
125
|
-
> ## Per-transaction Apex limits
|
|
126
|
-
> Limits are listed as synchronous / asynchronous.
|
|
127
|
-
>
|
|
128
|
-
> | Limit | Sync | Async |
|
|
129
|
-
> |---|---|---|
|
|
130
|
-
> | SOQL queries | 100 | 200 |
|
|
131
|
-
> | SOQL rows retrieved | 50,000 | 50,000 |
|
|
132
|
-
> | DML statements | 150 | 150 |
|
|
133
|
-
> | DML rows | 10,000 | 10,000 |
|
|
134
|
-
> | Callouts | 100 | 100 |
|
|
135
|
-
> | `@future` methods per invocation | 50 | 0 in batch and future contexts, 50 in queueable |
|
|
136
|
-
> | `System.enqueueJob` | 50 | 1 |
|
|
137
|
-
> | Heap | 10 MB | 25 MB |
|
|
138
|
-
> | CPU time | 10,000 ms | 60,000 ms |
|
|
139
|
-
> | Execution time | 10 minutes | 10 minutes |
|
|
140
|
-
>
|
|
141
|
-
> - **Enforcement:** "the Apex runtime engine strictly enforces limits", and exceeding a limit raises a runtime exception "that can't be handled."
|
|
142
|
-
> - **Reset and scope:** For Batch Apex, limits reset for each `execute` call. Synchronous limits apply to scheduled Apex. Bulk API transactions get the higher of the sync and async limits.
|
|
143
|
-
> - **Heap:** Exceeding it gives an uncatchable `System.LimitException`. Email services get a 50 MB heap.
|
|
144
|
-
>
|
|
145
|
-
> ## Certified managed packages
|
|
146
|
-
> - Each certified namespace gets its own per-transaction limits.
|
|
147
|
-
> - The cumulative limit across namespaces is 11 times the per-namespace limit. That gives 1,100 SOQL queries and 1,650 DML statements.
|
|
148
|
-
> - Heap, CPU, execution time and the unique-namespace count are not counted separately per package.
|
|
149
|
-
>
|
|
150
|
-
> ## Platform, static and size limits
|
|
151
|
-
> - **Async executions per 24 hours:** 250,000, or licenses × 200, whichever is greater.
|
|
152
|
-
> - **Concurrent long-running transactions (over 5 seconds):** one per 100 licenses.
|
|
153
|
-
> - **Scheduled classes:** 100 (5 in Developer Edition).
|
|
154
|
-
> - **Batch jobs queued or active:** 5.
|
|
155
|
-
> - **Callout timeout:** 10 seconds by default.
|
|
156
|
-
> - **Callout request or response size:** 6 MB synchronous, 12 MB asynchronous.
|
|
157
|
-
> - **Apex code per org:** 6 MB.
|
|
158
|
-
> - **Method size:** 65,535 bytecode instructions.
|
|
159
|
-
> - **Single email:** 5,000 external addresses per day per org (GMT).
|
|
160
|
-
> - **Push notifications:** 20,000 iOS and 10,000 Android per hour.
|
|
161
|
-
> - **Governor limit email warnings:** The default threshold is 50% of allocated limits, and only per-request limits are checked.
|
|
162
|
-
>
|
|
163
|
-
> ## Gaps
|
|
164
|
-
> - **Not fetched:** The agent did not read the "Elastic Limits for Asynchronous Apex Jobs (Beta)" page or `apex_limits_tips.htm`.
|
|
165
|
-
> - **Not quoted:** Several rows on the same page weren't quoted:
|
|
166
|
-
> - push notification calls per transaction
|
|
167
|
-
> - Apex cursor limits
|
|
168
|
-
> - inbound email limits
|
|
169
|
-
> - the 450,000 `MAX_DML_ROWS` limit in tests
|
|
170
|
-
> - **"Lightweight limits":** This isn't a section name in the docs.
|
|
171
|
-
>
|
|
172
|
-
> ## Sources
|
|
173
|
-
> - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
|
|
174
|
-
> - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits_emails.htm
|
|
175
|
-
>
|
|
176
|
-
> The pages are cached locally, so you can check any quote with `grep -c -F '<quote>' <cache path>`. The cache is under `~/.claude/sf-docs/cache/developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/`.
|
|
177
|
-
>
|
|
178
|
-
> I can have the agent fetch the 262 and 264 versioned pages to pin down the release, or cover the elastic limits page.
|
|
179
|
-
|
|
180
|
-
</details>
|
|
181
|
-
|
|
182
|
-
<details>
|
|
183
|
-
<summary><b>Opus 5.5, effort high, through the agent</b>: 89 seconds, 39k agent tokens</summary>
|
|
184
|
-
<br>
|
|
52
|
+
Part of what came back (Opus 5.5, 89 seconds):
|
|
185
53
|
|
|
186
54
|
> I read the Apex Developer Guide page **Execution Governors and Limits**. I spot-checked several of the quotes against the cached copy of the page and they match.
|
|
187
55
|
>
|
|
188
|
-
> **Which release:** the page doesn't name its release. Its links point to the 264.0 doc set, and its list of older versions starts at Summer '26 (API 67.0). So this is most likely **Winter '27 (API 68.0)**, but that's my inference, not something the page says. The page also doesn't mark any limit as new or changed in this release.
|
|
189
|
-
>
|
|
190
|
-
> ### Per-transaction Apex limits (synchronous / asynchronous)
|
|
191
56
|
> | Limit | Sync | Async |
|
|
192
57
|
> |---|---|---|
|
|
193
58
|
> | SOQL queries | 100 | 200 |
|
|
194
|
-
> | SOQL rows retrieved | 50,000 | 50,000 |
|
|
195
|
-
> | `Database.getQueryLocator` rows | 10,000 | 10,000 |
|
|
196
|
-
> | SOSL queries / rows per query | 20 / 2,000 | 20 / 2,000 |
|
|
197
59
|
> | DML statements / rows | 150 / 10,000 | 150 / 10,000 |
|
|
198
|
-
> | Trigger recursion stack depth | 16 | 16 |
|
|
199
|
-
> | Callouts / cumulative callout timeout | 100 / 120 s | 100 / 120 s |
|
|
200
|
-
> | `@future` calls | 50 | 0 in batch and future; 50 in queueable |
|
|
201
|
-
> | `System.enqueueJob` | 50 | 1 |
|
|
202
|
-
> | `sendEmail` calls | 10 | 10 |
|
|
203
60
|
> | Heap | 10 MB | 25 MB |
|
|
204
61
|
> | CPU time | 10,000 ms | 60,000 ms |
|
|
205
|
-
> | Maximum execution time | 10 min | 10 min |
|
|
206
|
-
> | `EventBus.publish` calls (publish immediately) | 150 | 150 |
|
|
207
|
-
> | Apex cursor rows / `Cursor.fetch` calls | 50M / 100 | 50M / 100 |
|
|
208
|
-
> | Pagination cursors: rows / instances / rows per page | 100,000 / 50 / 2,000 | same |
|
|
209
|
-
>
|
|
210
|
-
> **Notes that change how these apply:**
|
|
211
|
-
> - **Scheduled Apex** runs under the synchronous limits.
|
|
212
|
-
> - **Bulk API** transactions get the higher of the sync and async values.
|
|
213
|
-
> - **Parent-child subqueries** each count as an extra query.
|
|
214
|
-
> - **Custom metadata queries** don't count toward the SOQL limit.
|
|
215
|
-
> - **Email services** get a 50 MB heap.
|
|
216
|
-
> - **CPU time** leaves out time spent waiting on the database and on callouts.
|
|
217
|
-
> - **Batch Apex** limits reset for each `execute` chunk.
|
|
218
|
-
>
|
|
219
|
-
> ### Org-wide Platform limits
|
|
220
|
-
> - **Async executions per 24 hours:** 250,000 or 200 × licenses, whichever is greater. Batch Apex checks up front that the whole job fits under this limit before it starts.
|
|
221
|
-
> - **Elastic async executions (beta)** can add up to 2 million more per day.
|
|
222
|
-
> - **Cursors per 24 hours:** 10,000 cursor instances, 100M cursor rows, and 200,000 pagination cursors.
|
|
223
|
-
> - **Long-running synchronous transactions (over 5 s):** 1 per 100 licenses, minimum 10 and maximum 50.
|
|
224
|
-
> - **Scheduled classes:** 100 (5 in Developer Edition).
|
|
225
|
-
> - **Batch Apex:**
|
|
226
|
-
> - Flex queue jobs in Holding status: 100
|
|
227
|
-
> - Jobs queued or active: 5
|
|
228
|
-
> - Concurrent `start` methods: 1
|
|
229
|
-
> - **Test classes queued per 24 hours:** the greater of 500 or 10× your test classes in production (20× in sandbox and Developer Edition).
|
|
230
62
|
>
|
|
231
|
-
>
|
|
232
|
-
> - **Callouts:** 10 s default timeout; request or response up to 6 MB sync or 12 MB async, and this counts toward heap.
|
|
233
|
-
> - **SOQL run time:** 120 s.
|
|
234
|
-
> - **Deployments:** 7,500 code units each.
|
|
235
|
-
> - **Trigger batch size:** 200, or 2,000 for platform events and Change Data Capture.
|
|
236
|
-
> - **`QueryLocator`:** 50M records.
|
|
237
|
-
> - **Code size:**
|
|
238
|
-
> - 1M characters per class or trigger.
|
|
239
|
-
> - 6 MB of Apex per org (10 MB in scratch orgs). Support can raise it, and managed-package and `@isTest` code don't count.
|
|
240
|
-
> - 65,535 bytecode instructions per method.
|
|
241
|
-
>
|
|
242
|
-
> ### Certified managed packages
|
|
243
|
-
> - Each certified namespace gets its own copy of the per-transaction limits.
|
|
244
|
-
> - The total across namespaces is capped at 11× the per-namespace limit: for example, 1,100 SOQL queries or 1,650 DML statements.
|
|
245
|
-
> - Heap, CPU time, execution time and the number of namespaces are shared across the whole transaction.
|
|
246
|
-
> - Code in non-certified packages counts against your org's own limits.
|
|
247
|
-
>
|
|
248
|
-
> ### Other limits on the page
|
|
249
|
-
> - **Single email:** 5,000 external addresses per day and 150 recipients per message.
|
|
250
|
-
> - **Push notifications:** 20,000 iOS and 10,000 Android per hour per org.
|
|
251
|
-
> - **Tests:** 450,000 DML rows per synchronous test run.
|
|
252
|
-
> - **ConnectApi:** each write costs one DML statement.
|
|
253
|
-
> - **Data.com Clean:** 200 SOQL queries and 10 future calls per batch.
|
|
254
|
-
>
|
|
255
|
-
> **Not covered:** I didn't read the Asynchronous Callout Limits page or the Winter '27 release notes. If you need to know what changed since Summer '26, the release notes are the next place to look.
|
|
256
|
-
>
|
|
257
|
-
> **Source:** https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm
|
|
63
|
+
> **Not covered:** I didn't read the Asynchronous Callout Limits page or the Winter '27 release notes.
|
|
258
64
|
|
|
259
|
-
|
|
65
|
+
It tells you what it did not read. A gap you can see beats a guess you can't.
|
|
260
66
|
|
|
261
|
-
|
|
67
|
+
It covers developer.salesforce.com, architect.salesforce.com, help.salesforce.com, admin.salesforce.com and lightningdesignsystem.com. Full outputs on Opus and Sonnet: [docs/examples.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/examples.md)
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
262
70
|
|
|
263
|
-
|
|
264
|
-
|
|
71
|
+
1. **Finds the page.** It searches the site's `llms.txt` index or sitemaps, or starts from the link you give it.
|
|
72
|
+
2. **Reads the cleanest copy.** Where Salesforce publishes a `.md` copy of a page (the new developer docs, architect, SLDS 2), it reads that. Help, admin and some older guides have none, so it renders them in a headless browser. No fake user agent, no stealth plugin.
|
|
73
|
+
3. **Picks lines, not prose.** The model reads the cached page in the agent's own context and picks line numbers. A script copies those lines, each with its official URL.
|
|
74
|
+
4. **Names what it missed.** A page it did not read, or a link it did not follow, comes back as a gap.
|
|
75
|
+
|
|
76
|
+
To check a line, replace `<cache path>` (the agent's Sources name it) and `<quote>` below. Apostrophes are safe, and the cache saves curly quotes as straight ones, so a quote you retype still matches. Keep one quote on one line with no blank line around it, since grep counts a blank line as matching every line. Any count above 0 means the line is on the page.
|
|
265
77
|
|
|
266
78
|
```
|
|
267
|
-
|
|
268
|
-
|
|
79
|
+
grep -c -F -f - <cache path> <<'EOF'
|
|
80
|
+
<quote>
|
|
81
|
+
EOF
|
|
269
82
|
```
|
|
270
83
|
|
|
271
|
-
|
|
272
|
-
`--yes` to skip the question in scripts. If you say no, it prints the command to run before first use. On Linux, also
|
|
273
|
-
run `node "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sf-docs/node_modules/playwright-core/cli.js" install-deps chromium` once
|
|
274
|
-
for the system libraries. It asks for your password itself.
|
|
84
|
+
Every site, and how it reads each one: [docs/how-it-works.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/how-it-works.md)
|
|
275
85
|
|
|
276
|
-
|
|
86
|
+
## How it compares
|
|
277
87
|
|
|
278
|
-
|
|
279
|
-
current project.
|
|
88
|
+
Salesforce has two official options for the same job. I ran both on the same 10 questions.
|
|
280
89
|
|
|
281
|
-
**
|
|
282
|
-
live in `~/.claude/sf-docs/` for every scope, so delete that folder last, once no project uses the agent. If
|
|
283
|
-
`CLAUDE_CONFIG_DIR` is set, the installer uses that instead of `~/.claude`.
|
|
90
|
+
**[platform-docs-get](https://github.com/forcedotcom/sf-skills/tree/main/skills/platform-docs-get)**, a skill in Salesforce's sf-skills library, pulls every page it reads into your session. Against it, sf-docs-agent added 84% fewer tokens to your session and used 34% fewer tokens overall. Both got 39 of 40 runs right.
|
|
284
91
|
|
|
285
|
-
**
|
|
286
|
-
`SF_DOCS_CACHE_HOURS` in your Claude Code `settings.json` (`0` fetches every time):
|
|
92
|
+
**[Salesforce Docs MCP](https://labs.agentforce.com/docs/salesforce-docs-mcp)** searches a closed index with no dates on its results. It is the same trade-off you accept with [Context7](https://github.com/upstash/context7), whose README says its parsing and crawling engines are private: you can't see what the index holds or when it was taken. In my runs it returned no page from admin.salesforce.com or SLDS 2. In a subagent it got 24 of 30 right against sf-docs-agent's 29 of 30, and only 135 of its 177 quotes were on today's page, against 117 of 117. Called from the main session it got 28 of 30, but added 10 times as many tokens to your session.
|
|
287
93
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
94
|
+
**Where sf-docs-agent loses:** the MCP in a subagent is faster and cheaper: 218 seconds against 346 for all 10 questions, and 29% fewer tokens. If you are fine trusting the index, it is a good choice. I wanted something I control and can check against the live sites.
|
|
95
|
+
|
|
96
|
+
My numbers are from a rerun on 2 November 2026, after the agent started following two links. The other tools ran on 30 and 31 October 2026 and were not rerun, and the docs changed in between.
|
|
97
|
+
|
|
98
|
+
Every number, per question, with the method: [docs/benchmarks.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/benchmarks.md)
|
|
291
99
|
|
|
292
100
|
## Limitations
|
|
293
101
|
|
|
294
|
-
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
-
|
|
298
|
-
|
|
299
|
-
-
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Every quote it gives is still right; the missing row is the problem.
|
|
304
|
-
- If a site starts blocking headless browsers, the agent stops and reports it. It does not try to get around the block.
|
|
305
|
-
- What leaves your machine: the pages the agent fetches from those sites (and each site's `robots.txt`, which raw
|
|
306
|
-
fetches load first). No telemetry. Installing downloads `playwright-core` from npm and, if you agree, Chromium from
|
|
307
|
-
Playwright's servers.
|
|
308
|
-
- The agent has Bash. Its brief limits it to the script and the cache, but that is an instruction to the model, not a
|
|
309
|
-
sandbox.
|
|
102
|
+
- Help, admin and some older developer guides have no `.md` copy, so those pages are slower. If Salesforce changes their layout, the agent may read only part of an article. A daily check in this repo flags it.
|
|
103
|
+
- No Help search, because Help's `robots.txt` disallows it.
|
|
104
|
+
- From a page it found, it follows at most two more links in a row, and only toward a missing part of the answer. Anything further comes back as a gap.
|
|
105
|
+
- On a long list it sometimes stops a row early. The quotes it gives are still right.
|
|
106
|
+
- If a site blocks headless browsers, it stops and tells you. It does not try to get around the block.
|
|
107
|
+
- The agent has Bash. Its instructions limit it to its script and cache, but that is an instruction to the model, not a sandbox.
|
|
108
|
+
- No telemetry. Only the docs pages it fetches leave your machine.
|
|
109
|
+
|
|
110
|
+
How it reads each site, and the full list: [docs/how-it-works.md](https://github.com/drsaavedra/sf-docs-agent/blob/main/docs/how-it-works.md)
|
|
310
111
|
|
|
311
112
|
## Develop
|
|
312
113
|
|
|
@@ -317,8 +118,9 @@ sed "s|/path/to/sf-docs-agent|$PWD|g" agents/sf-docs.md > "${CLAUDE_CONFIG_DIR:-
|
|
|
317
118
|
npm test
|
|
318
119
|
```
|
|
319
120
|
|
|
320
|
-
`agents/sf-docs.md` names its script and cache as `/path/to/sf-docs-agent`. The `sed` line writes your copy with your
|
|
321
|
-
|
|
121
|
+
`agents/sf-docs.md` names its script and cache as `/path/to/sf-docs-agent`. The `sed` line writes your copy with your clone's path. Run it again after you edit the agent.
|
|
122
|
+
|
|
123
|
+
**Release:** open a PR that only bumps the version (`npm version patch --no-git-tag-version`, which changes `package.json` and `package-lock.json`). Merging it runs [`release.yml`](.github/workflows/release.yml): the tests, an install of the packed tarball, `npm publish` through npm trusted publishing (no token), a check that npm holds the tarball that was tested, then the `v<version>` tag and GitHub Release.
|
|
322
124
|
|
|
323
125
|
## License
|
|
324
126
|
|
package/agents/sf-docs.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sf-docs
|
|
3
|
-
description: Answers a question from official Salesforce docs (developer, architect, help, admin and lightningdesignsystem.com) with verbatim quotes the script copies from the cached page, each tied to its official URL and line. Use it proactively whenever the user wants something read, scanned, checked or looked up in Salesforce documentation, even without naming this agent ("what does Help say about X", "check the Apex guide for Y", "scan the docs on Z"), and when you do not know which page holds the answer. Brief it with the question, the site or product if known, and the width ("the one concept page" or "the whole doc set"). Delegate even for one page you already know, with its URL in the brief: a page is thousands of tokens in your context and the agent's report is about 400. A URL on one of those sites in the question is for this agent too; do not WebFetch or curl it, those get a 403
|
|
3
|
+
description: Answers a question from official Salesforce docs (developer, architect, help, admin and lightningdesignsystem.com) with verbatim quotes the script copies from the cached page, each tied to its official URL and line. Use it proactively whenever the user wants something read, scanned, checked or looked up in Salesforce documentation, even without naming this agent ("what does Help say about X", "check the Apex guide for Y", "scan the docs on Z"), and when you do not know which page holds the answer. Brief it with the question, the site or product if known, and the width ("the one concept page" or "the whole doc set"). Delegate even for one page you already know, with its URL in the brief: a page is thousands of tokens in your context and the agent's report is about 400. A URL on one of those sites in the question is for this agent too; do not WebFetch or curl it, those get a 403, an empty JavaScript shell or a raw HTML page. Show the user its Answer and Sources. To check a quote, run `grep -c -F -f - <cache path>` with the quote on stdin through a quoted heredoc (`<<'EOF'`), so an apostrophe in it cannot break the shell: one quote on one line, no blank line, since grep counts a blank line as matching every line. Reads docs only. Not for org state.
|
|
4
4
|
model: claude-sonnet-5-5
|
|
5
5
|
tools: Bash, Read
|
|
6
6
|
---
|
|
@@ -43,8 +43,8 @@ that, the printed output is the limit, not time.
|
|
|
43
43
|
`duplicate-of <url>` means the body matches an earlier URL: read only the first copy. `same <url> as <url>` means
|
|
44
44
|
two arguments are one page, fetched once. `(cached)` means it came from disk. Status `0` carries the error.
|
|
45
45
|
`[redirected <url>]` means the page moved and the Source line names where: check that it is the page you asked for,
|
|
46
|
-
since a mistyped atlas topic redirects to its guide's landing page, which is not evidence
|
|
47
|
-
line that adds `[twin <path>]` (the redirected page read as its `.md`) or `[twin failed: <reason>]` (its rendered
|
|
46
|
+
since a mistyped atlas topic redirects to its guide's landing page, which is not evidence; in the Apex Developer
|
|
47
|
+
Guide, which moved to the new docs, it answers 404 and nothing is saved. That rule also decides a line that adds `[twin <path>]` (the redirected page read as its `.md`) or `[twin failed: <reason>]` (its rendered
|
|
48
48
|
text kept); a cached run shows `(cached) [redirected <url>]` and no `[twin]`. `not saved` means a shell, login wall or missing page (Help
|
|
49
49
|
answers 200 for a missing article), or `[api docs did not load]` on an API reference page: not evidence, but a rerun
|
|
50
50
|
may work. `not saved: <code>` means the page loaded but the cache write failed: rerun. `not saved: left the doc
|
|
@@ -85,13 +85,17 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
85
85
|
next page.
|
|
86
86
|
3. **Look.** To see the lines around a match, use Read with `offset` and `limit`, or `grep -n -C3 -e '<term>' <file>`:
|
|
87
87
|
both number lines as `--quote` counts them. `sed -n` and `head` do not, so never take a line number from them.
|
|
88
|
+
Leave quote marks out of a grep term and search the words on either side: a `'` ends the shell quote, and a
|
|
89
|
+
curly one from the question misses a page saved with straight ones.
|
|
88
90
|
Never read a page whole. When the answer is a list or a table (patterns, editions, values), Read from its first
|
|
89
91
|
item to the next heading before you quote: a `--terms` match list stops at its cap, and the last rows may not
|
|
90
92
|
hold your term.
|
|
91
|
-
4. **
|
|
92
|
-
part of the answer (a child article, a "Limitations for ..." heading with no text under it, an index entry
|
|
93
|
-
title matches the question, another post), fetch it now with `--terms`.
|
|
94
|
-
|
|
93
|
+
4. **Two hops at most.** If a matched line, a `.links.txt` match or a sentence you will quote names another page that
|
|
94
|
+
holds part of the answer (a child article, a "Limitations for ..." heading with no text under it, an index entry
|
|
95
|
+
whose title matches the question, another post), fetch it now with `--terms`. From that page, follow one more link
|
|
96
|
+
the same way when that link names the part still missing. A page named by the page the second hop reached stays a
|
|
97
|
+
Gap. Follow a link only for a missing part of the answer. A parent Help article often lists no editions or
|
|
98
|
+
permissions, and its child article does.
|
|
95
99
|
5. **Quote.** Pick the line numbers that carry the answer, from the matches or the Read. Then let the script copy them:
|
|
96
100
|
`node "/path/to/sf-docs-agent/fetch-docs.mjs" --quote <cache file>:<line>,<line> [<cache file>:<line> ...]`
|
|
97
101
|
(cache file relative to the cache dir or absolute). It prints the Evidence and Sources blocks verbatim, with the
|
|
@@ -102,7 +106,8 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
102
106
|
|
|
103
107
|
- Run the script as written. Do not add a stealth plugin, a spoofed user agent or any other bot-check bypass:
|
|
104
108
|
plain headless Chromium gets 200 today. If the site answers 403 or a challenge page, stop and report
|
|
105
|
-
`BLOCKED: <status>`; do not try another route. WebFetch and curl get 403
|
|
109
|
+
`BLOCKED: <status>`; do not try another route. WebFetch and curl are not routes: they get a 403, an empty
|
|
110
|
+
JavaScript shell on Help, or raw HTML with no Source line for `--quote`.
|
|
106
111
|
- If the script prints a `BLOCKED:` line, or Node cannot find `playwright-core`, stop and report the error lines as
|
|
107
112
|
printed (after `BLOCKED:` they include the install command). Do not install anything yourself.
|
|
108
113
|
- Bash is for running the script and reading the cache dir. Write nothing anywhere else.
|
|
@@ -114,7 +119,8 @@ Every tool call costs you the whole conversation again, so use as few Bash calls
|
|
|
114
119
|
## Reading discipline
|
|
115
120
|
|
|
116
121
|
- Go to the exact concept page. A landing, overview or index page that only links onward is not evidence. The
|
|
117
|
-
caller's width ("the one concept page") still allows the
|
|
122
|
+
caller's width ("the one concept page") still allows the linked pages, up to two hops (step 4), that hold what the
|
|
123
|
+
question asks for.
|
|
118
124
|
- Quote verbatim. Carry every option an enumerating sentence lists, and quote any sentence that limits the scope
|
|
119
125
|
of the one you rely on ("X is intended only for Y").
|
|
120
126
|
- Two lines that disagree, on two pages or on one: quote both, name the files, do not pick one.
|
package/fetch-docs.mjs
CHANGED
|
@@ -74,8 +74,9 @@ const SOURCE = /^Source: [^\n]*\n/;
|
|
|
74
74
|
const isShell = b => /^\s*<(!doctype|html)/i.test(b);
|
|
75
75
|
// A twin is missing (render instead, and remember it) on a 404, or a 200 whose body is an HTML shell or empty.
|
|
76
76
|
const missingTwin = (status, body) => status === 404 || (status === 200 && (isShell(body) || !body.trim()));
|
|
77
|
-
// One page, whatever the #anchor or trailing slash; a Help article is
|
|
78
|
-
|
|
77
|
+
// One page, whatever the scheme (0.1.2 saved http:// Source lines), #anchor or trailing slash; a Help article is
|
|
78
|
+
// told apart by its ?id=.
|
|
79
|
+
const sameDoc = (a, b) => a.host === b.host && a.pathname.replace(/\/$/, '') === b.pathname.replace(/\/$/, '')
|
|
79
80
|
&& a.searchParams.get('id') === b.searchParams.get('id');
|
|
80
81
|
// Paths the agent will paste into Git Bash on Windows: forward slashes, which Node accepts too.
|
|
81
82
|
const slash = p => p.split(sep).join('/');
|
|
@@ -119,8 +120,9 @@ const grep = (file, t) => {
|
|
|
119
120
|
if (args[0] === '--quote') {
|
|
120
121
|
const files = [], ev = [];
|
|
121
122
|
for (const spec of args.slice(1)) {
|
|
122
|
-
const
|
|
123
|
-
|
|
123
|
+
const m = spec.match(/^(.+):(\d+(?:,\d+)*)$/); // greedy: a Windows C:\... colon stays in the path
|
|
124
|
+
if (!m) { console.error(`not <cache file>:<line>[,<line>...]: ${spec}`); continue; }
|
|
125
|
+
const file = isAbsolute(m[1]) ? m[1] : join(out, m[1]);
|
|
124
126
|
// Only cached official pages: inside the cache dir and starting with their Source: line.
|
|
125
127
|
const inside = relative(out, file);
|
|
126
128
|
let lines;
|
|
@@ -128,7 +130,7 @@ if (args[0] === '--quote') {
|
|
|
128
130
|
catch { lines = []; }
|
|
129
131
|
if (!lines[0]?.startsWith('Source: ')) { console.error(`not a cached page: ${slash(file)}`); continue; }
|
|
130
132
|
if (!files.includes(file)) files.push(file);
|
|
131
|
-
for (const n of
|
|
133
|
+
for (const n of m[2].split(',').map(Number)) {
|
|
132
134
|
const text = (lines[n - 1] || '').trim();
|
|
133
135
|
if (text && n > 1) ev.push(`- "${text}" [${files.indexOf(file) + 1}:${n}]`);
|
|
134
136
|
else console.error(`skipped ${slash(relative(out, file))}:${n}: empty, out of range or the Source line`);
|
|
@@ -186,7 +188,7 @@ const parse = a => {
|
|
|
186
188
|
const given = [], refused = [];
|
|
187
189
|
for (const a of args) {
|
|
188
190
|
const u = parse(a);
|
|
189
|
-
if (u && SITES.includes(u.host)) given.push(u);
|
|
191
|
+
if (u && SITES.includes(u.host)) { u.protocol = 'https:'; given.push(u); } // all https: one key, one Source line
|
|
190
192
|
else refused.push(u ? `${u.href}: not an official doc site` : `${a}: not a URL`);
|
|
191
193
|
}
|
|
192
194
|
// New developer docs, architect and LDS pages have a .md twin: fetch it raw rather than render the HTML page.
|
|
@@ -451,8 +453,8 @@ try {
|
|
|
451
453
|
if (got.sel === 'body') got = await page.evaluate(extract, [CONTENT, true])
|
|
452
454
|
.catch(e => { console.error(`fallback failed on ${u.href}: ${e.message.split('\n')[0]}`); return got; });
|
|
453
455
|
const { sel, text, links, hold } = got;
|
|
454
|
-
// A redirect is shown and cited: a mistyped atlas topic lands on its guide's landing page with status 200
|
|
455
|
-
// Help article can move to another ?id= on the same path.
|
|
456
|
+
// A redirect is shown and cited: a mistyped atlas topic lands on its guide's landing page with status 200 (a 404 in
|
|
457
|
+
// a guide that moved to the new docs), and a Help article can move to another ?id= on the same path.
|
|
456
458
|
const to = new URL(page.url()), hop = sameDoc(to, u) ? '' : ` [redirected ${to.href}]`;
|
|
457
459
|
// Old atlas URLs (and the LDS home) redirect to pages that have a .md twin: keep the rendered links, read the twin.
|
|
458
460
|
const moved = hop && twinOf(to);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sf-docs-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code subagent that answers from official Salesforce docs (developer, architect, help, admin, lightningdesignsystem.com) with grep-checked quotes",
|
|
6
6
|
"devDependencies": {
|