@pascalcouturier/skill-builder-mcp 3.0.0 → 3.1.1
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/CHANGELOG.md +30 -0
- package/README.md +49 -45
- package/SECURITY.md +14 -8
- package/dist/cli.js +205 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,36 @@ The install line names the major version (`@3`), so restarting your AI tool or i
|
|
|
9
9
|
fetches the newest release of that major (see **Updating** in the
|
|
10
10
|
[README](./README.md#updating)).
|
|
11
11
|
|
|
12
|
+
## 3.1.1
|
|
13
|
+
|
|
14
|
+
Security update. Same protocol, same tools, same install line (`@3`): restarting your AI tool
|
|
15
|
+
picks it up.
|
|
16
|
+
|
|
17
|
+
- Updates the bundled MCP SDK from 1.31.0 to 1.32.1, which fixes two published advisories
|
|
18
|
+
(tasks not tied to the session that created them, and cross-origin redirects that re-sent
|
|
19
|
+
headers and bodies). The connector only talks to Skill Builder over a local connection, so the
|
|
20
|
+
exposure was low, but it ships that code.
|
|
21
|
+
|
|
22
|
+
## 3.1.0
|
|
23
|
+
|
|
24
|
+
Same protocol and tools; the install line stays `@3`, so restarting your AI tool picks it up.
|
|
25
|
+
The tool definitions it offers before Skill Builder connects now match the app's:
|
|
26
|
+
|
|
27
|
+
- `validate` takes `documentId` (check one skill or agent) and `since` (the revision of your
|
|
28
|
+
last result: only what changed comes back), for loops that check after every round.
|
|
29
|
+
- `add_document` adds a whole workflow when `templateId` is a workflow id from
|
|
30
|
+
`list_templates`.
|
|
31
|
+
- `get_standards` has the topic `improve`, the `/improve-until` routine, for AI tools that do
|
|
32
|
+
not show slash commands.
|
|
33
|
+
- `apply_fix` accepts `set-loop-limit`, the fix for a loop with no limit.
|
|
34
|
+
- **A refresh keeps the pairing.** After you pair, the connector hands Skill Builder a
|
|
35
|
+
one-use resume secret (kept in memory only). If you refresh the page within two minutes, Skill
|
|
36
|
+
Builder proves it holds the secret and the AI tool stays connected, with no new code. Closing
|
|
37
|
+
the tab, a new window or tab, restoring tabs after a browser restart, or restarting the AI
|
|
38
|
+
tool still asks for a new code. Older tabs and connectors are unaffected: the messages are
|
|
39
|
+
only used between 3.1.0 and an app that knows them. Disconnecting or denying in Skill Builder,
|
|
40
|
+
or turning Connect AI off, closes with code 4000 and the connector forgets the secret at once.
|
|
41
|
+
|
|
12
42
|
## 3.0.0
|
|
13
43
|
|
|
14
44
|
Fewer, grouped tools, so AI tools read about a third less with every request and choose tools
|
package/README.md
CHANGED
|
@@ -155,7 +155,10 @@ The code is made by the connector and shown only in your AI tool; it never trave
|
|
|
155
155
|
connection. Both sides prove they know it, so no other program on your computer can pose as
|
|
156
156
|
Skill Builder or as the connector. Three wrong codes close the connection; ten in one run
|
|
157
157
|
turn pairing off until you restart the AI tool. You pair again after restarting the AI tool
|
|
158
|
-
or
|
|
158
|
+
or in a new Skill Builder window; the code is new every time and you never need to keep it.
|
|
159
|
+
Refreshing the page keeps the pairing (within two minutes, and until the AI tool restarts): the
|
|
160
|
+
connector gives Skill Builder a one-use secret that it keeps as a key no script can read, and
|
|
161
|
+
Skill Builder proves it holds it, so there is no new code.
|
|
159
162
|
|
|
160
163
|
## What you can ask
|
|
161
164
|
|
|
@@ -210,49 +213,49 @@ The AI can do what you can do in the app, except anything to do with the cloud (
|
|
|
210
213
|
sync, the AI check, the community library). Tools marked **asks** wait for your confirmation
|
|
211
214
|
in Skill Builder.
|
|
212
215
|
|
|
213
|
-
| Tool | What it does
|
|
214
|
-
| ---------------------------- |
|
|
215
|
-
| `get_status` | What is open: project, skill or agent, file, unsaved changes
|
|
216
|
-
| `list_sessions` | Search, filter, sort and page projects
|
|
217
|
-
| `get_session` | A project's skills and agents, files and links; with `canvas`, the canvas graph
|
|
218
|
-
| `get_document` | A skill or agent with every block (uids, types, values); with `fileId`, the markdown a file produces or a script's text
|
|
219
|
-
| `list_block_types` | Block types and their fields
|
|
220
|
-
| `list_templates` | Templates for new skills, agents and rules, with best practice for writing them
|
|
221
|
-
| `list_library` | Built-in skills and your library items
|
|
222
|
-
| `get_library_item` | One library item with its files, scan result and versions
|
|
223
|
-
| `validate` | The checks, with one-click fixes, each with why it matters and its source; with `files`, checks skill or agent files from your repository (nothing is stored)
|
|
224
|
-
| `analyze_project` | The project report's analysis: scorecard, findings with evidence, context cost per AI tool, competition, portability, tests; returns a baseline id
|
|
225
|
-
| `compare_analysis` | Before and after a baseline, computed by Skill Builder (characters, findings, tests)
|
|
226
|
-
| `get_standards` | Best practice per topic and per AI tool, with sources and the date each was checked
|
|
227
|
-
| `use_skill` | Use a skill, agent, rule or instructions you shared: main file, then files on demand
|
|
228
|
-
| `preview_export` | The files the zip would contain (ready for a project, or plain folders)
|
|
229
|
-
| `versions` | Saved versions (`list`), or line changes between versions or against the working copy (`diff`)
|
|
230
|
-
| `create_session` | New project
|
|
231
|
-
| `open_session` | Show a project, skill or agent, or file in the app
|
|
232
|
-
| `update_session` | Rename, change author or description, star
|
|
233
|
-
| `duplicate` | Copy a project (`session`) or a skill or agent (`document`)
|
|
234
|
-
| `delete` | Delete a project and its versions, a skill or agent, a file, a custom block type or a library item
|
|
235
|
-
| `add_document` | Add a skill, agent or tool file, optionally from a template, or a copy of a library item (`fromLibrary`)
|
|
236
|
-
| `update_document` | Set name, description, licence, allowed tools, model…
|
|
237
|
-
| `write_project_instructions` | Create or update AGENTS.md and CLAUDE.md from your repository (`/init-agents-md`)
|
|
238
|
-
| `repository_import` | Import your repository's AI files into a new project: `start`, `add_files` (up to 50 per call), `finish` (`/import-project`)
|
|
239
|
-
| `add_block` | Add a block with values at a position
|
|
240
|
-
| `update_block` | Change block values, collapse or expand
|
|
241
|
-
| `block_action` | `move` (up, down or to another file), `duplicate`, `remove`, `convert` to Custom markdown, `break_down` into built-in blocks, `extract` to a new reference file
|
|
242
|
-
| `add_files` | Add several supporting files by path (scanned like uploads)
|
|
243
|
-
| `file_action` | `add` a reference, script or asset, `write` a script or data file (scanned, never run), `move` it to another folder (`scripts/test`), `rename` a reference
|
|
244
|
-
| `link` | `connect` an agent to a skill, or point a reference or Resource link block; `disconnect` removes the link
|
|
245
|
-
| `update_evaluation` | Save tests for a skill or agent, or the results of running them (`/evaluate-skill`)
|
|
246
|
-
| `apply_fix` | Apply a fix from `validate
|
|
247
|
-
| `save_version` | Save a version, only when you ask (bump suggested from the changes)
|
|
248
|
-
| `restore_version` | Replace the working copy with a version
|
|
249
|
-
| `download` | Download the zip (refused if the scan finds a secret) or the project report as HTML or Markdown (`project`), or a backup (`backup`)
|
|
250
|
-
| `tidy_canvas` | Tidy the canvas layout
|
|
251
|
-
| `undo_ai_changes` | Same as **Undo AI changes**
|
|
252
|
-
| `save_block_type` | Create or edit a custom block type
|
|
253
|
-
| `library_item` | Your library: `add` a skill, agent or rule from markdown, `update` it (a new version), `restore_version`, `copy_builtin` (an editable copy of a built-in skill), `set_hidden`
|
|
254
|
-
| `import_from_github` | List and import skills and agents from a public GitHub repo
|
|
255
|
-
| `set_preferences` | Default author, Blocks or Canvas view, block matching on import
|
|
216
|
+
| Tool | What it does | Asks |
|
|
217
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
|
218
|
+
| `get_status` | What is open: project, skill or agent, file, unsaved changes | |
|
|
219
|
+
| `list_sessions` | Search, filter, sort and page projects | |
|
|
220
|
+
| `get_session` | A project's skills and agents, files and links; with `canvas`, the canvas graph | |
|
|
221
|
+
| `get_document` | A skill or agent with every block (uids, types, values); with `fileId`, the markdown a file produces or a script's text | |
|
|
222
|
+
| `list_block_types` | Block types and their fields | |
|
|
223
|
+
| `list_templates` | Templates for new skills, agents and rules, with best practice for writing them | |
|
|
224
|
+
| `list_library` | Built-in skills, agents and rules, and your library items | |
|
|
225
|
+
| `get_library_item` | One library item with its files, scan result and versions | |
|
|
226
|
+
| `validate` | The checks, with one-click fixes, each with why it matters and its source; with `files`, checks skill or agent files from your repository (nothing is stored); `documentId` checks one item and `since` returns only what changed since your last result | |
|
|
227
|
+
| `analyze_project` | The project report's analysis: scorecard, findings with evidence, context cost per AI tool, competition, portability, tests; returns a baseline id | |
|
|
228
|
+
| `compare_analysis` | Before and after a baseline, computed by Skill Builder (characters, findings, tests) | |
|
|
229
|
+
| `get_standards` | Best practice per topic and per AI tool, with sources and the date each was checked; topic `improve` is the `/improve-until` routine | |
|
|
230
|
+
| `use_skill` | Use a skill, agent, rule or instructions you shared: main file, then files on demand | first use |
|
|
231
|
+
| `preview_export` | The files the zip would contain (ready for a project, or plain folders) | |
|
|
232
|
+
| `versions` | Saved versions (`list`), or line changes between versions or against the working copy (`diff`) | |
|
|
233
|
+
| `create_session` | New project | |
|
|
234
|
+
| `open_session` | Show a project, skill or agent, or file in the app | |
|
|
235
|
+
| `update_session` | Rename, change author or description, star | |
|
|
236
|
+
| `duplicate` | Copy a project (`session`) or a skill or agent (`document`) | |
|
|
237
|
+
| `delete` | Delete a project and its versions, a skill or agent, a file, a custom block type or a library item | asks |
|
|
238
|
+
| `add_document` | Add a skill, agent or tool file, optionally from a template or a whole workflow (a workflow id from `list_templates`), or a copy of a library item (`fromLibrary`) | |
|
|
239
|
+
| `update_document` | Set name, description, licence, allowed tools, model… | |
|
|
240
|
+
| `write_project_instructions` | Create or update AGENTS.md and CLAUDE.md from your repository (`/init-agents-md`) | |
|
|
241
|
+
| `repository_import` | Import your repository's AI files into a new project: `start`, `add_files` (up to 50 per call), `finish` (`/import-project`) | |
|
|
242
|
+
| `add_block` | Add a block with values at a position | |
|
|
243
|
+
| `update_block` | Change block values, collapse or expand | |
|
|
244
|
+
| `block_action` | `move` (up, down or to another file), `duplicate`, `remove`, `convert` to Custom markdown, `break_down` into built-in blocks, `extract` to a new reference file | |
|
|
245
|
+
| `add_files` | Add several supporting files by path (scanned like uploads) | |
|
|
246
|
+
| `file_action` | `add` a reference, script or asset, `write` a script or data file (scanned, never run), `move` it to another folder (`scripts/test`), `rename` a reference | |
|
|
247
|
+
| `link` | `connect` an agent to a skill, or point a reference or Resource link block; `disconnect` removes the link | |
|
|
248
|
+
| `update_evaluation` | Save tests for a skill or agent, or the results of running them (`/evaluate-skill`) | |
|
|
249
|
+
| `apply_fix` | Apply a fix from `validate`, such as adding or limiting a Completion goal | |
|
|
250
|
+
| `save_version` | Save a version, only when you ask (bump suggested from the changes) | asks |
|
|
251
|
+
| `restore_version` | Replace the working copy with a version | asks |
|
|
252
|
+
| `download` | Download the zip (refused if the scan finds a secret) or the project report as HTML or Markdown (`project`), or a backup (`backup`) | asks |
|
|
253
|
+
| `tidy_canvas` | Tidy the canvas layout | |
|
|
254
|
+
| `undo_ai_changes` | Same as **Undo AI changes** | if you edited |
|
|
255
|
+
| `save_block_type` | Create or edit a custom block type | |
|
|
256
|
+
| `library_item` | Your library: `add` a skill, agent or rule from markdown, `update` it (a new version), `restore_version`, `copy_builtin` (an editable copy of a built-in skill), `set_hidden` | if warnings |
|
|
257
|
+
| `import_from_github` | List and import skills and agents from a public GitHub repo | if warnings |
|
|
258
|
+
| `set_preferences` | Default author, Blocks or Canvas view, block matching on import | |
|
|
256
259
|
|
|
257
260
|
Edits carry the project's revision. If you changed the project since the AI last read it,
|
|
258
261
|
the edit is refused and the AI reads it again, so it never overwrites your work by accident.
|
|
@@ -434,7 +437,8 @@ Nothing else is installed, and the connector leaves no files behind.
|
|
|
434
437
|
- **ChatGPT** (web and desktop) only supports remote MCP servers, so it can not use this
|
|
435
438
|
connector.
|
|
436
439
|
- **Safari** is not supported.
|
|
437
|
-
- **Pair on each start:** pairing is not remembered
|
|
440
|
+
- **Pair on each start:** pairing is not remembered after the AI tool restarts, or in a new
|
|
441
|
+
window or tab. A refresh keeps it.
|
|
438
442
|
- **Cloud features** (sign-in, sync, the AI check, the community library) are not available
|
|
439
443
|
to AI tools.
|
|
440
444
|
|
package/SECURITY.md
CHANGED
|
@@ -23,7 +23,11 @@ it does and does not do, how that is enforced, and how to check it yourself.
|
|
|
23
23
|
- **No code from outside.** It never evaluates code it receives, and it does not generate
|
|
24
24
|
code at runtime (`eval` and `new Function` are never called).
|
|
25
25
|
- **No telemetry, no analytics, no accounts, no cloud.**
|
|
26
|
-
- **No secrets stored.** Keys are made fresh for each connection and kept in memory only.
|
|
26
|
+
- **No secrets stored.** Keys are made fresh for each connection and kept in memory only. The
|
|
27
|
+
one exception is the resume secret that lets a refreshed Skill Builder tab reconnect without a
|
|
28
|
+
new code: it is random, used once, kept in the connector's memory only (never written
|
|
29
|
+
anywhere), accepted for two minutes after the tab left, and gone when the connector exits or when you
|
|
30
|
+
disconnect or turn Connect AI off in Skill Builder.
|
|
27
31
|
|
|
28
32
|
These are checked by an automated test (`src/sandbox.test.ts`) that runs the exact file
|
|
29
33
|
published to npm under Node's permission model (`--permission`, with read access to the
|
|
@@ -54,7 +58,8 @@ name, program start, file write or runtime code generation.
|
|
|
54
58
|
`use_skill`): only items marked **Available to AI tools** in the project open in the tab, only
|
|
55
59
|
after the person allows the first use in each connection, scanned each time (blocked content
|
|
56
60
|
is never sent), script code only when allowed per item, and framed as guidance that never
|
|
57
|
-
overrides the person.
|
|
61
|
+
overrides the person. An approval holds only for the text that was shown: a changed item asks
|
|
62
|
+
again.
|
|
58
63
|
|
|
59
64
|
## Secrets
|
|
60
65
|
|
|
@@ -65,8 +70,9 @@ name, program start, file write or runtime code generation.
|
|
|
65
70
|
records every file and variable it touches; another checks the file loads nothing beyond
|
|
66
71
|
networking, crypto and streams.
|
|
67
72
|
- **Skill Builder refuses secrets in what the AI writes:** credentials by format (AWS, GitHub,
|
|
68
|
-
Anthropic, OpenAI, Stripe, Google, npm, Slack tokens, JWTs, private keys),
|
|
69
|
-
with a random-looking value
|
|
73
|
+
Anthropic, OpenAI, Stripe, Google, GitLab, npm, Slack tokens, JWTs, private keys), `Bearer`
|
|
74
|
+
tokens, `.env`-style lines with a random-looking value (also when the value is on the next
|
|
75
|
+
line), passwords in connection strings, and files named like secrets
|
|
70
76
|
(`.env`, `*.pem`, `id_rsa`, `credentials.json`). Placeholders such as `YOUR_API_KEY` are fine.
|
|
71
77
|
The AI is told never to look for, repeat or store a secret.
|
|
72
78
|
- **Your AI tool is what reads your project**, and only its own settings can limit that. For
|
|
@@ -122,9 +128,9 @@ commands, edit files). Treat any tool's output as untrusted, including this one'
|
|
|
122
128
|
|
|
123
129
|
```bash
|
|
124
130
|
# What it contains, and its checksum
|
|
125
|
-
npm pack @pascalcouturier/skill-builder-mcp@3.
|
|
126
|
-
tar -tzf pascalcouturier-skill-builder-mcp-
|
|
127
|
-
npm view @pascalcouturier/skill-builder-mcp@3.
|
|
131
|
+
npm pack @pascalcouturier/skill-builder-mcp@3.1.1
|
|
132
|
+
tar -tzf pascalcouturier-skill-builder-mcp-3.1.1.tgz
|
|
133
|
+
npm view @pascalcouturier/skill-builder-mcp@3.1.1 dist.integrity
|
|
128
134
|
|
|
129
135
|
# That the registry signed it (in a project that installs it)
|
|
130
136
|
npm audit signatures
|
|
@@ -146,7 +152,7 @@ the approved GitHub Actions workflow (no npm token exists).
|
|
|
146
152
|
|
|
147
153
|
This means a release reaches you without you reviewing it first. If you want to review each one
|
|
148
154
|
(for example in a company), put an exact version after the `@` in your AI tool's settings, such as
|
|
149
|
-
`@3.
|
|
155
|
+
`@3.1.1`, and update it yourself; Skill Builder still shows when your connector is out of date.
|
|
150
156
|
Use the commands above to look at any release before you run it.
|
|
151
157
|
|
|
152
158
|
## Reporting a problem
|
package/dist/cli.js
CHANGED
|
@@ -7845,7 +7845,7 @@ var StdioServerTransport = class {
|
|
|
7845
7845
|
};
|
|
7846
7846
|
var package_default = {
|
|
7847
7847
|
name: "@pascalcouturier/skill-builder-mcp",
|
|
7848
|
-
version: "3.
|
|
7848
|
+
version: "3.1.1",
|
|
7849
7849
|
description: "Connect AI tools (GitHub Copilot, Claude Code, Codex, Cursor, Claude Desktop) to Skill Builder open in your browser, over MCP.",
|
|
7850
7850
|
license: "MIT",
|
|
7851
7851
|
author: "Pascal Couturier (https://pascalcouturier.com)",
|
|
@@ -11532,6 +11532,13 @@ function isAllowedHost(host, port) {
|
|
|
11532
11532
|
* the connector forwards calls, the tab answers with results.
|
|
11533
11533
|
* 4. The tab also sends its prompt list (slash commands for the skills and
|
|
11534
11534
|
* agents the person shared), and the connector asks for a prompt with `prompt`.
|
|
11535
|
+
* 5. Resuming (connector 3.1.0 and later): a page refresh loses the connection, but not the
|
|
11536
|
+
* pairing. The connector hands the tab a one-use resume secret after pairing (`paired.grant`,
|
|
11537
|
+
* asked for with `pair.resume`). The tab keeps it as a key it can use but never read, and
|
|
11538
|
+
* after a refresh proves it holds it (`resume`) instead of asking for a code. Each resume
|
|
11539
|
+
* replaces the secret, and the connector only accepts it for a short time after the tab left.
|
|
11540
|
+
* Nothing here changes what an older tab or connector can read: the tab only uses these
|
|
11541
|
+
* messages with a connector that says, by its version, that it understands them.
|
|
11535
11542
|
*/
|
|
11536
11543
|
config({ jitless: true });
|
|
11537
11544
|
/**
|
|
@@ -11540,6 +11547,11 @@ config({ jitless: true });
|
|
|
11540
11547
|
* install line (`outdatedConnectorText` in install.ts), rather than keeping two tool lists.
|
|
11541
11548
|
*/
|
|
11542
11549
|
var SUPPORTED_PROTOCOLS = [3];
|
|
11550
|
+
/**
|
|
11551
|
+
* The close code a tab sends when the person disconnected or denied on purpose (not a refresh or a
|
|
11552
|
+
* closed window). The connector forgets the way back at once instead of waiting out the window.
|
|
11553
|
+
*/
|
|
11554
|
+
var CLOSE_LEFT_ON_PURPOSE = 4e3;
|
|
11543
11555
|
var BRIDGE_LIMITS = {
|
|
11544
11556
|
/** Largest frame either side accepts. */
|
|
11545
11557
|
maxFrameBytes: 1048576,
|
|
@@ -11553,6 +11565,8 @@ var BRIDGE_LIMITS = {
|
|
|
11553
11565
|
pairingTimeoutMs: 3e5,
|
|
11554
11566
|
/** A socket that has not said hello by then is closed. */
|
|
11555
11567
|
handshakeTimeoutMs: 1e4,
|
|
11568
|
+
/** How long after a tab leaves it may resume without a code: long enough for a refresh. */
|
|
11569
|
+
resumeWindowMs: 12e4,
|
|
11556
11570
|
/** Wrong pairing codes allowed per connection. */
|
|
11557
11571
|
pairingAttempts: 3,
|
|
11558
11572
|
/** Wrong pairing codes allowed per connector run; then it stops pairing until restarted. */
|
|
@@ -11666,12 +11680,36 @@ var promptResult = strictObject({
|
|
|
11666
11680
|
text: string().max(9e5)
|
|
11667
11681
|
})).min(1).max(10)
|
|
11668
11682
|
});
|
|
11683
|
+
/** What lets a tab resume after a refresh: an id, and the secret it is the id of (base64, 32 bytes). */
|
|
11684
|
+
var resumeGrant = strictObject({
|
|
11685
|
+
id: string().min(16).max(64),
|
|
11686
|
+
secret: string().min(40).max(64)
|
|
11687
|
+
});
|
|
11669
11688
|
/** Messages after pairing, by type. Bodies are validated on receipt. */
|
|
11670
11689
|
var signedBodies = {
|
|
11671
|
-
/**
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11690
|
+
/**
|
|
11691
|
+
* Tab: proof that the user typed the pairing code the connector showed in the AI tool.
|
|
11692
|
+
* `resume` asks for a resume grant; only sent to a connector that understands it.
|
|
11693
|
+
*/
|
|
11694
|
+
pair: strictObject({
|
|
11695
|
+
proof: string().min(40).max(64),
|
|
11696
|
+
resume: boolean().optional()
|
|
11697
|
+
}),
|
|
11698
|
+
/**
|
|
11699
|
+
* Connector: the proof was right; here is the connector's own proof. `grant` answers `resume`
|
|
11700
|
+
* (after a code or after a resume): the secret to use next time.
|
|
11701
|
+
*/
|
|
11702
|
+
paired: strictObject({
|
|
11703
|
+
proof: string().min(40).max(64),
|
|
11704
|
+
grant: resumeGrant.optional()
|
|
11705
|
+
}),
|
|
11706
|
+
/** Tab: proof that it holds the resume secret of `id`, bound to this connection. */
|
|
11707
|
+
resume: strictObject({
|
|
11708
|
+
id: string().min(16).max(64),
|
|
11709
|
+
proof: string().min(40).max(64)
|
|
11710
|
+
}),
|
|
11711
|
+
/** Connector: can not resume (unknown, used or expired). A code still works on this connection. */
|
|
11712
|
+
resumeFailed: strictObject({}),
|
|
11675
11713
|
/** Connector: the code was wrong. The connection closes when no attempts are left. */
|
|
11676
11714
|
pairFailed: strictObject({ attemptsLeft: number().int().min(0).max(10) }),
|
|
11677
11715
|
/** Tab: its tool list. Sent after approval and again whenever it changes. */
|
|
@@ -11784,6 +11822,16 @@ var SOURCES = {
|
|
|
11784
11822
|
url: "https://github.com/microsoft/vscode/blob/c480e9e49e2a116c458518ecbdec56d50e4af3d2/extensions/copilot/src/platform/configuration/common/configurationService.ts",
|
|
11785
11823
|
checked: "2026-10-12"
|
|
11786
11824
|
},
|
|
11825
|
+
"anthropic-building-agents": {
|
|
11826
|
+
title: "Anthropic: building effective agents",
|
|
11827
|
+
url: "https://www.anthropic.com/engineering/building-effective-agents",
|
|
11828
|
+
checked: "2026-10-13"
|
|
11829
|
+
},
|
|
11830
|
+
"claude-code-hooks": {
|
|
11831
|
+
title: "Claude Code: hooks",
|
|
11832
|
+
url: "https://code.claude.com/docs/en/hooks",
|
|
11833
|
+
checked: "2026-10-13"
|
|
11834
|
+
},
|
|
11787
11835
|
"skill-creator": {
|
|
11788
11836
|
title: "Anthropic skill-creator: testing skills",
|
|
11789
11837
|
url: "https://github.com/anthropics/skills/tree/main/skills/skill-creator",
|
|
@@ -11796,7 +11844,7 @@ var cite = (...ids) => ids.map((id) => ({
|
|
|
11796
11844
|
title: SOURCES[id].title,
|
|
11797
11845
|
url: SOURCES[id].url
|
|
11798
11846
|
}));
|
|
11799
|
-
cite("agent-skills-spec", "anthropic-best-practices", "claude-code-skills", "vscode-skills"), cite("claude-code-subagents", "vscode-agents"), cite("agents-md", "claude-code-memory", "vscode-instructions"), cite("vscode-instructions", "claude-code-memory", "cursor-rules"), cite("skill-creator", "anthropic-best-practices");
|
|
11847
|
+
cite("agent-skills-spec", "anthropic-best-practices", "anthropic-building-agents", "claude-code-skills", "vscode-skills"), cite("claude-code-subagents", "vscode-agents", "anthropic-building-agents"), cite("agents-md", "claude-code-memory", "vscode-instructions"), cite("vscode-instructions", "claude-code-memory", "cursor-rules"), cite("skill-creator", "anthropic-best-practices");
|
|
11800
11848
|
[
|
|
11801
11849
|
"Context cost, from what is paid most often to least:",
|
|
11802
11850
|
"1. Every request: skill and agent names and descriptions, AGENTS.md / CLAUDE.md, rules without paths. Keep descriptions to the main use case and trigger words; move procedures out of AGENTS.md into skills; give rules paths.",
|
|
@@ -11814,6 +11862,11 @@ cite("agent-skills-spec", "anthropic-best-practices", "claude-code-skills", "vsc
|
|
|
11814
11862
|
"5. Report what compare_analysis returned, numbers as they are: characters saved per AI tool, findings cleared and added, test results before and after. Token counts are estimates. Never compute or estimate a change yourself.",
|
|
11815
11863
|
"6. Do not save a version unless the person asks."
|
|
11816
11864
|
].join("\n");
|
|
11865
|
+
/** Rounds `/improve-until` takes by default, and at most. */
|
|
11866
|
+
var IMPROVE_ROUNDS = {
|
|
11867
|
+
default: 3,
|
|
11868
|
+
max: 10
|
|
11869
|
+
};
|
|
11817
11870
|
[
|
|
11818
11871
|
"To make one skill work in Claude Code, GitHub Copilot and Codex:",
|
|
11819
11872
|
"- Use the fields every tool reads: name (lowercase, hyphens, the folder name) and description (under 1,024 characters, main use case first).",
|
|
@@ -11855,6 +11908,7 @@ function reviewPrompt(name) {
|
|
|
11855
11908
|
" - The description, in the third person, says what it does and then when to use it (\"Use when the user asks to…\"), main use case first.",
|
|
11856
11909
|
" - Skills: a short overview, when to use it (and when not), numbered steps, rules, the output, how to check the result, and one example. Keep SKILL.md under 500 lines; move detail into references/ files linked straight from SKILL.md, each with when to read it. Mention every script and data file and whether to run or read it.",
|
|
11857
11910
|
" - Agents: a clear role, responsibilities, boundaries (always, ask first, never), only the tools the job needs, what to hand back (an Output block) and a definition of done. Link only skills it needs every time; refer to the others with a condition.",
|
|
11911
|
+
" - Work that repeats: when a step runs a check (tests, lint, a build) or says to repeat, add a Completion goal (the goal, the check each round, at most a few rounds, then ask). validate reports these as loop-opportunity or loop-implied with a one-click fix.",
|
|
11858
11912
|
" - Plain, specific language; no filler, no dated text, forward slashes in paths; nothing the agent can not act on.",
|
|
11859
11913
|
" - Each finding from validate says why it matters and where the rule comes from: use that to decide, and explain it in your summary.",
|
|
11860
11914
|
"4. AI review: validate also returns aiReview, a rubric for what the coded checks can not judge (whether the description would be picked for the right requests and not near-misses, clear steps, the body matching the description, contradictions, missing inputs or outputs, overlap with other skills, safety). Apply each criterion to the files, fix what it finds the same way, and list the rest for the person with the evidence.",
|
|
@@ -11864,6 +11918,7 @@ function reviewPrompt(name) {
|
|
|
11864
11918
|
}]
|
|
11865
11919
|
};
|
|
11866
11920
|
}
|
|
11921
|
+
`${IMPROVE_ROUNDS.max}${IMPROVE_ROUNDS.default}`;
|
|
11867
11922
|
//#endregion
|
|
11868
11923
|
//#region ../../src/core/bridge/tools.ts
|
|
11869
11924
|
/**
|
|
@@ -11979,6 +12034,17 @@ var issueAction = discriminatedUnion("kind", [
|
|
|
11979
12034
|
strictObject({
|
|
11980
12035
|
kind: literal("preload-linked"),
|
|
11981
12036
|
documentId: id
|
|
12037
|
+
}),
|
|
12038
|
+
strictObject({
|
|
12039
|
+
kind: literal("set-loop-limit"),
|
|
12040
|
+
documentId: id,
|
|
12041
|
+
blockUid: id,
|
|
12042
|
+
value: string().regex(/^\d{1,2}$/)
|
|
12043
|
+
}),
|
|
12044
|
+
strictObject({
|
|
12045
|
+
kind: literal("add-loop-goal"),
|
|
12046
|
+
documentId: id,
|
|
12047
|
+
check: string().max(300)
|
|
11982
12048
|
})
|
|
11983
12049
|
]);
|
|
11984
12050
|
var endpoint = strictObject({
|
|
@@ -12383,7 +12449,7 @@ var bridgeTools = {
|
|
|
12383
12449
|
name: "list_library",
|
|
12384
12450
|
title: "List library",
|
|
12385
12451
|
kind: "read",
|
|
12386
|
-
description: "Skills, agents and rules (Copilot path instructions, Cursor rules) in the library: built-in skills and your own items (imported, uploaded or copied). Items that failed the security scan are never listed.",
|
|
12452
|
+
description: "Skills, agents and rules (Copilot path instructions, Cursor rules) in the library: built-in skills, agents and rules, and your own items (imported, uploaded or copied). Built-in rules are added as path rules (mode claude-rule), which Claude Code, Copilot and Codex all follow. Items that failed the security scan are never listed.",
|
|
12387
12453
|
input: strictObject({
|
|
12388
12454
|
kind: libraryKind.optional(),
|
|
12389
12455
|
source: _enum([
|
|
@@ -12409,14 +12475,16 @@ var bridgeTools = {
|
|
|
12409
12475
|
name: "validate",
|
|
12410
12476
|
title: "Check project",
|
|
12411
12477
|
kind: "read",
|
|
12412
|
-
description: "Runs the checks on a project (Agent Skills specification, official authoring guidance, links, long sections, syntax); issues with an `action` can be fixed with apply_fix. With files: checks skill and agent files you send instead, such as a skill folder in the user's repository, without storing anything (adds the security scan); send each file's repository path (e.g. .claude/skills/pdf/SKILL.md) and text, and never secret files (.env, keys). Run it before and after you edit a skill and fix every error. aiReview (on by default) is a rubric for what only a model can judge (clear steps, the description matching the body, contradictions, overlap): apply it yourself and report its findings with the rule ones
|
|
12478
|
+
description: "Runs the checks on a project (Agent Skills specification, official authoring guidance, links, long sections, syntax); issues with an `action` can be fixed with apply_fix. With files: checks skill and agent files you send instead, such as a skill folder in the user's repository, without storing anything (adds the security scan); send each file's repository path (e.g. .claude/skills/pdf/SKILL.md) and text, and never secret files (.env, keys). Run it before and after you edit a skill and fix every error. aiReview (on by default) is a rubric for what only a model can judge (clear steps, the description matching the body, contradictions, overlap): apply it yourself and report its findings with the rule ones. Re-checking in a loop: pass documentId and since (the revision of your last result) to get only what changed.",
|
|
12413
12479
|
input: strictObject({
|
|
12414
12480
|
sessionId: sessionRef,
|
|
12415
12481
|
files: array(strictObject({
|
|
12416
12482
|
path: string().min(1).max(300).describe("Path in the repository."),
|
|
12417
12483
|
content: string().max(INPUT_LIMITS.code)
|
|
12418
12484
|
})).min(1).max(INPUT_LIMITS.files).optional().describe("The files of one or more skills or agents, under 1 MB in total."),
|
|
12419
|
-
aiReview: boolean().default(true)
|
|
12485
|
+
aiReview: boolean().default(true),
|
|
12486
|
+
documentId: id.optional().describe("Only findings about this skill or agent."),
|
|
12487
|
+
since: string().max(100).optional().describe("The revision of your last validate result: return only what changed since.")
|
|
12420
12488
|
})
|
|
12421
12489
|
}),
|
|
12422
12490
|
analyze_project: tool({
|
|
@@ -12465,7 +12533,7 @@ var bridgeTools = {
|
|
|
12465
12533
|
name: "get_standards",
|
|
12466
12534
|
title: "Get best practice",
|
|
12467
12535
|
kind: "read",
|
|
12468
|
-
description: "Best practice for writing skills, agents, AGENTS.md and rules, checked against each AI tool's documentation, with the source and date of every fact. topic: skill, agent, instructions, rule, evaluation (testing), optimize (improving a project with analyze_project and compare_analysis), portability (one file for Claude Code, Copilot and Codex) or context (what costs context). tool: the facts for one AI tool. Call it before you write or rewrite a skill, agent or instructions file.",
|
|
12536
|
+
description: "Best practice for writing skills, agents, AGENTS.md and rules, checked against each AI tool's documentation, with the source and date of every fact. topic: skill, agent, instructions, rule, evaluation (testing), optimize (improving a project with analyze_project and compare_analysis), improve (the /improve-until routine: rounds with a check after each), portability (one file for Claude Code, Copilot and Codex) or context (what costs context). tool: the facts for one AI tool. Call it before you write or rewrite a skill, agent or instructions file.",
|
|
12469
12537
|
input: strictObject({
|
|
12470
12538
|
topic: _enum([
|
|
12471
12539
|
"skill",
|
|
@@ -12474,6 +12542,7 @@ var bridgeTools = {
|
|
|
12474
12542
|
"rule",
|
|
12475
12543
|
"evaluation",
|
|
12476
12544
|
"optimize",
|
|
12545
|
+
"improve",
|
|
12477
12546
|
"portability",
|
|
12478
12547
|
"context"
|
|
12479
12548
|
]),
|
|
@@ -12578,11 +12647,11 @@ var bridgeTools = {
|
|
|
12578
12647
|
name: "add_document",
|
|
12579
12648
|
title: "Add skill, agent or tool file",
|
|
12580
12649
|
kind: "write",
|
|
12581
|
-
description: "Adds a skill, agent or tool file to a project, optionally from a template (call list_templates first for the template and best practice). Tool files: 'copilot-instructions' (one per project), 'copilot-rule' (.github/instructions; set applyTo with update_document), 'cursor-rule' (.cursor/rules) and 'claude-rule' (.claude/rules; set paths with update_document). The description says, in the third person, what it does and when to use it. For AGENTS.md use write_project_instructions. fromLibrary adds a library item (list_library) as an editable copy instead, optionally linked to an agent.",
|
|
12650
|
+
description: "Adds a skill, agent or tool file to a project, optionally from a template (call list_templates first for the template and best practice). Tool files: 'copilot-instructions' (one per project), 'copilot-rule' (.github/instructions; set applyTo with update_document), 'cursor-rule' (.cursor/rules) and 'claude-rule' (.claude/rules; set paths with update_document). The description says, in the third person, what it does and when to use it. For AGENTS.md use write_project_instructions. fromLibrary adds a library item (list_library) as an editable copy instead, optionally linked to an agent. A workflow id from list_templates as templateId adds all its items, already linked.",
|
|
12582
12651
|
input: strictObject({
|
|
12583
12652
|
baseRevision,
|
|
12584
12653
|
sessionId: sessionRef,
|
|
12585
|
-
mode: addableMode.optional().describe("Required unless fromLibrary is given."),
|
|
12654
|
+
mode: addableMode.optional().describe("Required unless fromLibrary or a workflow is given."),
|
|
12586
12655
|
templateId: id.optional(),
|
|
12587
12656
|
name: name.optional(),
|
|
12588
12657
|
description: string().max(1024).optional(),
|
|
@@ -12594,7 +12663,7 @@ var bridgeTools = {
|
|
|
12594
12663
|
name: "update_document",
|
|
12595
12664
|
title: "Edit skill or agent details",
|
|
12596
12665
|
kind: "write",
|
|
12597
|
-
description: "Sets frontmatter fields; lists are arrays of strings. Skills: name, description, license, compatibility, allowed-tools, metadata ('key: value' lines), argument-hint, disable-model-invocation, user-invocable. Agents: name, description, tools, model. Copilot path instructions: name, applyTo, description, excludeAgent. Cursor rules: name, description, globs, alwaysApply.
|
|
12666
|
+
description: "Sets frontmatter fields; lists are arrays of strings. Skills: name, description, license, compatibility, allowed-tools, metadata ('key: value' lines), argument-hint, disable-model-invocation, user-invocable. Agents: name, description, tools, model. Copilot path instructions: name, applyTo, description, excludeAgent. Cursor rules: name, description, globs, alwaysApply. Path rules (claude-rule): name, paths. Fields kept from an imported file are get_document's otherFields: setting one replaces it, removeOtherFields drops them. Descriptions say, in the third person, what it does and when to use it.",
|
|
12598
12667
|
input: strictObject({
|
|
12599
12668
|
baseRevision,
|
|
12600
12669
|
documentId: id,
|
|
@@ -12940,6 +13009,13 @@ async function createKeyPair() {
|
|
|
12940
13009
|
};
|
|
12941
13010
|
}
|
|
12942
13011
|
var INFO = encoder.encode("skill-builder-bridge/1");
|
|
13012
|
+
/** The handshake as text, length-prefixed so parts can not run into each other. */
|
|
13013
|
+
var transcriptText = (transcript) => [
|
|
13014
|
+
transcript.tabKey,
|
|
13015
|
+
transcript.connectorKey,
|
|
13016
|
+
transcript.tabNonce,
|
|
13017
|
+
transcript.connectorNonce
|
|
13018
|
+
].map((part) => part.length + ":" + part).join("|");
|
|
12943
13019
|
/** Derives the MAC key from our private key and the other side's public key. */
|
|
12944
13020
|
async function derivePairing(own, peerPublicKey, transcript) {
|
|
12945
13021
|
const peer = await subtle().importKey("raw", fromBase64(peerPublicKey), {
|
|
@@ -12950,12 +13026,7 @@ async function derivePairing(own, peerPublicKey, transcript) {
|
|
|
12950
13026
|
name: "ECDH",
|
|
12951
13027
|
public: peer
|
|
12952
13028
|
}, own.privateKey, 256);
|
|
12953
|
-
const salt = await subtle().digest("SHA-256", encoder.encode(
|
|
12954
|
-
transcript.tabKey,
|
|
12955
|
-
transcript.connectorKey,
|
|
12956
|
-
transcript.tabNonce,
|
|
12957
|
-
transcript.connectorNonce
|
|
12958
|
-
].map((part) => part.length + ":" + part).join("|")));
|
|
13029
|
+
const salt = await subtle().digest("SHA-256", encoder.encode(transcriptText(transcript)));
|
|
12959
13030
|
const hkdf = await subtle().importKey("raw", shared, "HKDF", false, ["deriveBits"]);
|
|
12960
13031
|
const bits = new Uint8Array(await subtle().deriveBits({
|
|
12961
13032
|
name: "HKDF",
|
|
@@ -13011,6 +13082,39 @@ function pairingProof(key, role, code) {
|
|
|
13011
13082
|
function checkPairingProof(key, role, code, proof) {
|
|
13012
13083
|
return verify(key, proofPayload(role, code), proof);
|
|
13013
13084
|
}
|
|
13085
|
+
/**
|
|
13086
|
+
* A new resume grant: a random id and a 256-bit secret. Made by the connector only, kept in its
|
|
13087
|
+
* memory only, and used once.
|
|
13088
|
+
*/
|
|
13089
|
+
function createResumeGrant() {
|
|
13090
|
+
return {
|
|
13091
|
+
id: randomNonce(16),
|
|
13092
|
+
secret: toBase64(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)))
|
|
13093
|
+
};
|
|
13094
|
+
}
|
|
13095
|
+
/**
|
|
13096
|
+
* The secret as a key. The tab imports it as not extractable, so it can be kept in the browser
|
|
13097
|
+
* and used to sign, but no script can read it back out.
|
|
13098
|
+
*/
|
|
13099
|
+
function importResumeKey(secret, extractable = false) {
|
|
13100
|
+
return subtle().importKey("raw", fromBase64(secret), {
|
|
13101
|
+
name: "HMAC",
|
|
13102
|
+
hash: "SHA-256"
|
|
13103
|
+
}, extractable, ["sign", "verify"]);
|
|
13104
|
+
}
|
|
13105
|
+
var resumePayload = (role, id, transcript) => `skill-builder-resume/1|${role}|${id}|${transcriptText(transcript)}`;
|
|
13106
|
+
/**
|
|
13107
|
+
* Proof that `role` holds the secret of `id`. Bound to this connection's handshake, so a proof
|
|
13108
|
+
* captured on one connection is worth nothing on the next, and different for each role, so one
|
|
13109
|
+
* side's proof can not stand for the other's.
|
|
13110
|
+
*/
|
|
13111
|
+
function resumeProof(key, role, id, transcript) {
|
|
13112
|
+
return sign(key, resumePayload(role, id, transcript));
|
|
13113
|
+
}
|
|
13114
|
+
/** Checks a resume proof in constant time. */
|
|
13115
|
+
function checkResumeProof(key, role, id, transcript, proof) {
|
|
13116
|
+
return verify(key, resumePayload(role, id, transcript), proof);
|
|
13117
|
+
}
|
|
13014
13118
|
//#endregion
|
|
13015
13119
|
//#region ../../src/core/bridge/channel.ts
|
|
13016
13120
|
/**
|
|
@@ -13089,6 +13193,9 @@ var TabLink = class {
|
|
|
13089
13193
|
protocol = 0;
|
|
13090
13194
|
channel = null;
|
|
13091
13195
|
key = null;
|
|
13196
|
+
transcript = null;
|
|
13197
|
+
/** A tab gets one try at resuming per connection; after that it types a code. */
|
|
13198
|
+
resumeTried = false;
|
|
13092
13199
|
attemptsLeft = BRIDGE_LIMITS.pairingAttempts;
|
|
13093
13200
|
handshakeTimer;
|
|
13094
13201
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -13106,7 +13213,7 @@ var TabLink = class {
|
|
|
13106
13213
|
const text = isBinary ? "" : data.toString();
|
|
13107
13214
|
this.queue = this.queue.then(() => this.receive(text)).catch(() => this.close(1008));
|
|
13108
13215
|
});
|
|
13109
|
-
socket.on("close", () => this.closed());
|
|
13216
|
+
socket.on("close", (code) => this.closed(code === CLOSE_LEFT_ON_PURPOSE));
|
|
13110
13217
|
socket.on("error", () => this.close(1011));
|
|
13111
13218
|
this.handshakeTimer = setTimeout(() => {
|
|
13112
13219
|
if (this.state === "handshake") this.close(1008);
|
|
@@ -13127,12 +13234,14 @@ var TabLink = class {
|
|
|
13127
13234
|
if (protocol === null) return this.reject("protocol", hello.protocols.every((version) => version < 3) ? "Skill Builder is older than this connector. Reload the app to update it." : "This connector is older than Skill Builder. Update the version in your AI tool settings (Skill Builder → Settings → Connect AI shows the current one).");
|
|
13128
13235
|
const own = await createKeyPair();
|
|
13129
13236
|
const nonce = randomNonce();
|
|
13130
|
-
const
|
|
13237
|
+
const transcript = {
|
|
13131
13238
|
tabKey: hello.key,
|
|
13132
13239
|
connectorKey: own.publicKey,
|
|
13133
13240
|
tabNonce: hello.nonce,
|
|
13134
13241
|
connectorNonce: nonce
|
|
13135
|
-
}
|
|
13242
|
+
};
|
|
13243
|
+
const pairing = await derivePairing(own, hello.key, transcript);
|
|
13244
|
+
this.transcript = transcript;
|
|
13136
13245
|
this.socket.send(JSON.stringify({
|
|
13137
13246
|
type: "welcome",
|
|
13138
13247
|
protocol,
|
|
@@ -13155,7 +13264,8 @@ var TabLink = class {
|
|
|
13155
13264
|
}
|
|
13156
13265
|
async handle(message) {
|
|
13157
13266
|
switch (message.type) {
|
|
13158
|
-
case "pair": return this.pair(message.body.proof);
|
|
13267
|
+
case "pair": return this.pair(message.body.proof, message.body.resume === true);
|
|
13268
|
+
case "resume": return this.resume(message.body.id, message.body.proof);
|
|
13159
13269
|
case "tools":
|
|
13160
13270
|
if (this.state !== "paired") throw new ChannelError("Tools before pairing.");
|
|
13161
13271
|
this.tools = message.body.tools;
|
|
@@ -13192,13 +13302,17 @@ var TabLink = class {
|
|
|
13192
13302
|
* with our own proof, so the tab knows it reached the real connector. Wrong:
|
|
13193
13303
|
* a few more tries, then the connection closes.
|
|
13194
13304
|
*/
|
|
13195
|
-
async pair(proof) {
|
|
13305
|
+
async pair(proof, wantsResume) {
|
|
13196
13306
|
if (this.state !== "pairing" || !this.key || !this.code) throw new ChannelError("Unexpected pairing message.");
|
|
13197
13307
|
if (await checkPairingProof(this.key, "tab", this.code, proof)) {
|
|
13198
13308
|
clearTimeout(this.pairingTimer);
|
|
13199
13309
|
this.state = "paired";
|
|
13200
|
-
|
|
13201
|
-
this.
|
|
13310
|
+
const grant = wantsResume ? this.newGrant() : void 0;
|
|
13311
|
+
await this.send("paired", {
|
|
13312
|
+
proof: await pairingProof(this.key, "connector", this.code),
|
|
13313
|
+
...grant ? { grant } : {}
|
|
13314
|
+
});
|
|
13315
|
+
this.events.onPaired?.(false);
|
|
13202
13316
|
return;
|
|
13203
13317
|
}
|
|
13204
13318
|
this.attemptsLeft--;
|
|
@@ -13206,6 +13320,37 @@ var TabLink = class {
|
|
|
13206
13320
|
await this.send("pairFailed", { attemptsLeft: Math.max(0, this.attemptsLeft) });
|
|
13207
13321
|
if (this.attemptsLeft <= 0) this.close(1008);
|
|
13208
13322
|
}
|
|
13323
|
+
/**
|
|
13324
|
+
* The tab came back after a refresh and says it holds the resume secret it was given. Right:
|
|
13325
|
+
* the secret is used up, the connector proves it holds it too (so the tab knows it reached the
|
|
13326
|
+
* real connector), and the tab gets a new secret. Anything else: the tab is told, and the code
|
|
13327
|
+
* on this connection still works. A wrong try does not count against the pairing codes, and
|
|
13328
|
+
* does not use the secret up, but each connection gets one try.
|
|
13329
|
+
*/
|
|
13330
|
+
async resume(id, proof) {
|
|
13331
|
+
if (this.state !== "pairing" || !this.key || !this.transcript || this.resumeTried) throw new ChannelError("Unexpected resume message.");
|
|
13332
|
+
this.resumeTried = true;
|
|
13333
|
+
const secret = this.events.lookupResume?.(id) ?? null;
|
|
13334
|
+
const key = secret ? await importResumeKey(secret) : null;
|
|
13335
|
+
const proven = key ? await checkResumeProof(key, "tab", id, this.transcript, proof) : false;
|
|
13336
|
+
if (!key || !proven || this.state !== "pairing" || !this.events.consumeResume?.(id)) {
|
|
13337
|
+
await this.send("resumeFailed", {});
|
|
13338
|
+
return;
|
|
13339
|
+
}
|
|
13340
|
+
clearTimeout(this.pairingTimer);
|
|
13341
|
+
this.state = "paired";
|
|
13342
|
+
const grant = this.newGrant();
|
|
13343
|
+
await this.send("paired", {
|
|
13344
|
+
proof: await resumeProof(key, "connector", id, this.transcript),
|
|
13345
|
+
grant
|
|
13346
|
+
});
|
|
13347
|
+
this.events.onPaired?.(true);
|
|
13348
|
+
}
|
|
13349
|
+
newGrant() {
|
|
13350
|
+
const grant = createResumeGrant();
|
|
13351
|
+
this.events.onGrant?.(grant);
|
|
13352
|
+
return grant;
|
|
13353
|
+
}
|
|
13209
13354
|
/** Forwards a call to the tab and waits for its result. */
|
|
13210
13355
|
call(name, args, timeoutMs) {
|
|
13211
13356
|
return this.request("call", name, args, timeoutMs);
|
|
@@ -13300,7 +13445,7 @@ var TabLink = class {
|
|
|
13300
13445
|
} catch {}
|
|
13301
13446
|
this.closed();
|
|
13302
13447
|
}
|
|
13303
|
-
closed() {
|
|
13448
|
+
closed(onPurpose = false) {
|
|
13304
13449
|
if (this.state === "closed") return;
|
|
13305
13450
|
this.state = "closed";
|
|
13306
13451
|
clearInterval(this.heartbeat);
|
|
@@ -13317,7 +13462,7 @@ var TabLink = class {
|
|
|
13317
13462
|
});
|
|
13318
13463
|
this.pending.delete(callId);
|
|
13319
13464
|
}
|
|
13320
|
-
this.events.onClose?.();
|
|
13465
|
+
this.events.onClose?.(onPurpose);
|
|
13321
13466
|
}
|
|
13322
13467
|
};
|
|
13323
13468
|
//#endregion
|
|
@@ -13335,6 +13480,12 @@ var Bridge = class {
|
|
|
13335
13480
|
/** Wrong pairing codes in this run. Too many stop pairing until a restart. */
|
|
13336
13481
|
failures = 0;
|
|
13337
13482
|
locked = false;
|
|
13483
|
+
/**
|
|
13484
|
+
* The one resume secret that may be in use: handed to the tab that is (or was) connected, in
|
|
13485
|
+
* memory only, used once. `offlineSince` is when that tab left; it can only resume from then
|
|
13486
|
+
* until the window closes. Never written anywhere, and gone when this connector exits.
|
|
13487
|
+
*/
|
|
13488
|
+
resumable = null;
|
|
13338
13489
|
manifest = toolManifest();
|
|
13339
13490
|
toolListeners = /* @__PURE__ */ new Set();
|
|
13340
13491
|
promptListeners = /* @__PURE__ */ new Set();
|
|
@@ -13459,10 +13610,27 @@ var Bridge = class {
|
|
|
13459
13610
|
this.options.log?.("Too many wrong pairing codes: pairing is off until the AI tool restarts.");
|
|
13460
13611
|
}
|
|
13461
13612
|
},
|
|
13462
|
-
onPaired: () => this.options.log?.("Approved. Skill Builder is connected."),
|
|
13613
|
+
onPaired: (resumed) => this.options.log?.(resumed ? "Skill Builder came back after a refresh and is connected again." : "Approved. Skill Builder is connected."),
|
|
13614
|
+
onGrant: (grant) => {
|
|
13615
|
+
this.resumable = {
|
|
13616
|
+
...grant,
|
|
13617
|
+
holder: link,
|
|
13618
|
+
offlineSince: null
|
|
13619
|
+
};
|
|
13620
|
+
},
|
|
13621
|
+
lookupResume: (id) => this.resumableSecret(id),
|
|
13622
|
+
consumeResume: (id) => {
|
|
13623
|
+
if (!this.resumableSecret(id)) return false;
|
|
13624
|
+
this.resumable = null;
|
|
13625
|
+
return true;
|
|
13626
|
+
},
|
|
13463
13627
|
onTools: (tools) => this.setManifest(tools),
|
|
13464
13628
|
onPrompts: () => this.promptsMaybeChanged(),
|
|
13465
|
-
onClose: () => {
|
|
13629
|
+
onClose: (onPurpose) => {
|
|
13630
|
+
if (this.resumable?.holder === link) {
|
|
13631
|
+
if (onPurpose) this.resumable = null;
|
|
13632
|
+
else if (this.resumable.offlineSince === null) this.resumable.offlineSince = Date.now();
|
|
13633
|
+
}
|
|
13466
13634
|
if (this.link !== link) return;
|
|
13467
13635
|
this.link = null;
|
|
13468
13636
|
this.promptsMaybeChanged();
|
|
@@ -13471,6 +13639,13 @@ var Bridge = class {
|
|
|
13471
13639
|
});
|
|
13472
13640
|
this.link = link;
|
|
13473
13641
|
}
|
|
13642
|
+
/** The secret of a resume id that is still good: the tab left, a short time ago, and has not used it. */
|
|
13643
|
+
resumableSecret(id) {
|
|
13644
|
+
const held = this.resumable;
|
|
13645
|
+
const window = this.options.resumeWindowMs ?? BRIDGE_LIMITS.resumeWindowMs;
|
|
13646
|
+
if (!held || held.id !== id || held.offlineSince === null) return null;
|
|
13647
|
+
return Date.now() - held.offlineSince <= window ? held.secret : null;
|
|
13648
|
+
}
|
|
13474
13649
|
/**
|
|
13475
13650
|
* Answers a socket's first message with a refusal and closes it. A socket that stays silent is
|
|
13476
13651
|
* closed too, so idle ones can not use up the connection limit and block the real tab.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascalcouturier/skill-builder-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Connect AI tools (GitHub Copilot, Claude Code, Codex, Cursor, Claude Desktop) to Skill Builder open in your browser, over MCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Pascal Couturier (https://pascalcouturier.com)",
|