@modootoday/datalab-extension-mcp 1.8.9 → 1.8.11
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.en.md +11 -11
- package/README.md +11 -11
- package/dist/{browsers-DRGNGPSI.js → browsers-EMS5V4M4.js} +19 -5
- package/dist/{chunk-WBMZLPXC.js → chunk-65FDP6LF.js} +17 -4
- package/dist/{chunk-GCRJQLTV.js → chunk-Q6SOGGKF.js} +249 -168
- package/dist/{chunk-5S7RFXIU.js → chunk-S6D7YVBB.js} +1 -1
- package/dist/cli.js +7 -7
- package/dist/{dist-JICXADYR.js → dist-63JYX33Z.js} +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -55,10 +55,10 @@ Avoid sharing a screenshot or the copied setup. If it may have leaked, press **[
|
|
|
55
55
|
One command finds **every** AI app on your computer and connects them automatically. Nothing to do per app.
|
|
56
56
|
|
|
57
57
|
1. **Open a terminal** — on Windows type `cmd` in the search box → "Command Prompt"; on Mac press `⌘+Space` and type `Terminal`; on Linux open your terminal app (often `Ctrl+Alt+T`).
|
|
58
|
-
2. **Paste this command and press Enter.** (`@1.8.
|
|
58
|
+
2. **Paste this command and press Enter.** (`@1.8.11` is the pinned version, so the connector never changes behind your back.)
|
|
59
59
|
|
|
60
60
|
```
|
|
61
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
61
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 install
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Lines of English scrolling by is normal. This only downloads and runs the connector — it touches nothing else.
|
|
@@ -130,10 +130,10 @@ Apps it **only prints instructions for** (follow the on-screen guide): VS Code,
|
|
|
130
130
|
Installing also places the bundled **writing skills**. An AI app reads the name and description of every installed skill on every start, so the more are installed the less room the conversation has. The default therefore places only the ones that work right away.
|
|
131
131
|
|
|
132
132
|
```
|
|
133
|
-
npx -y @modootoday/datalab-extension-mcp skills list
|
|
134
|
-
npx -y @modootoday/datalab-extension-mcp skills status
|
|
135
|
-
npx -y @modootoday/datalab-extension-mcp skills attach <name>
|
|
136
|
-
npx -y @modootoday/datalab-extension-mcp skills detach <name>
|
|
133
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills list
|
|
134
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills status
|
|
135
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills attach <name>
|
|
136
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills detach <name>
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
- `list` shows everything in this version, name and description.
|
|
@@ -152,9 +152,9 @@ The **workbench** is this computer, where the connector runs. If you use more th
|
|
|
152
152
|
Make the code from the panel with [등록 코드 만들기], or from the terminal.
|
|
153
153
|
|
|
154
154
|
```
|
|
155
|
-
npx -y @modootoday/datalab-extension-mcp browsers add "Work Chrome"
|
|
156
|
-
npx -y @modootoday/datalab-extension-mcp browsers list
|
|
157
|
-
npx -y @modootoday/datalab-extension-mcp browsers revoke <handle>
|
|
155
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers add "Work Chrome"
|
|
156
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers list
|
|
157
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers revoke <handle>
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
`add` **asks you to paste the pairing token** — the same value you used to
|
|
@@ -252,7 +252,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
|
|
|
252
252
|
- **When the connection disappears** — run the very command you used at the start once more; it safely re-registers. (Some AI apps clean up their settings file and drop the connection.)
|
|
253
253
|
|
|
254
254
|
```
|
|
255
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
255
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 install
|
|
256
256
|
```
|
|
257
257
|
|
|
258
258
|
- **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
|
|
@@ -282,7 +282,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
|
|
|
282
282
|
1. Run this in a terminal (it removes the entries registered in your AI apps).
|
|
283
283
|
|
|
284
284
|
```
|
|
285
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
285
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 uninstall
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
2. Turn **MCP 연결** off in the extension settings. The panel is the bridge, so with it off a leftover config line can look nothing up.
|
package/README.md
CHANGED
|
@@ -49,10 +49,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
|
|
|
49
49
|
명령 한 줄이면 컴퓨터에 있는 AI 앱을 **모두 자동으로** 찾아 연결해 줘요. 앱마다 따로 할 일은 없어요.
|
|
50
50
|
|
|
51
51
|
1. **터미널을 열어요** — Windows는 검색창에 `cmd` → "명령 프롬프트", Mac은 `⌘+스페이스`로 `터미널`, Linux는 터미널 앱(보통 `Ctrl+Alt+T`)이에요.
|
|
52
|
-
2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.8.
|
|
52
|
+
2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.8.11`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
|
|
53
53
|
|
|
54
54
|
```
|
|
55
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
55
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 install
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
|
|
@@ -118,10 +118,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
|
|
|
118
118
|
설치할 때 **글쓰기 도움말(스킬)** 도 함께 놓입니다. AI 앱은 켜질 때마다 설치된 도움말의 이름과 설명을 전부 읽기 때문에, 많이 깔수록 대화에 쓸 자리가 줄어들어요. 그래서 기본은 **바로 쓸 수 있는 것만** 놓습니다.
|
|
119
119
|
|
|
120
120
|
```
|
|
121
|
-
npx -y @modootoday/datalab-extension-mcp skills list
|
|
122
|
-
npx -y @modootoday/datalab-extension-mcp skills status
|
|
123
|
-
npx -y @modootoday/datalab-extension-mcp skills attach <이름>
|
|
124
|
-
npx -y @modootoday/datalab-extension-mcp skills detach <이름>
|
|
121
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills list
|
|
122
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills status
|
|
123
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills attach <이름>
|
|
124
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 skills detach <이름>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
- `list` 는 이 버전에 들어 있는 전부를 이름과 설명으로 보여 줘요.
|
|
@@ -140,9 +140,9 @@ npx -y @modootoday/datalab-extension-mcp skills detach <이름>
|
|
|
140
140
|
패널에서 [등록 코드 만들기]를 눌러도 되고, 터미널을 쓰셔도 돼요.
|
|
141
141
|
|
|
142
142
|
```
|
|
143
|
-
npx -y @modootoday/datalab-extension-mcp browsers add "회사 크롬"
|
|
144
|
-
npx -y @modootoday/datalab-extension-mcp browsers list
|
|
145
|
-
npx -y @modootoday/datalab-extension-mcp browsers revoke <handle>
|
|
143
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers add "회사 크롬"
|
|
144
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers list
|
|
145
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 browsers revoke <handle>
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
`add` 를 실행하면 **연결 토큰을 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 패널의 `복사` 버튼으로 얻습니다. 그러면 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
|
|
@@ -220,7 +220,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
|
|
|
220
220
|
- **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
|
|
221
221
|
|
|
222
222
|
```
|
|
223
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
223
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 install
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
- **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
|
|
@@ -250,7 +250,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
|
|
|
250
250
|
1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
|
|
251
251
|
|
|
252
252
|
```
|
|
253
|
-
npx -y @modootoday/datalab-extension-mcp@1.8.
|
|
253
|
+
npx -y @modootoday/datalab-extension-mcp@1.8.11 uninstall
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_HOST,
|
|
3
3
|
DEFAULT_PORT
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Q6SOGGKF.js";
|
|
5
5
|
import {
|
|
6
6
|
TOKEN_PROMPT_GUIDE,
|
|
7
7
|
TOKEN_PROMPT_QUESTION,
|
|
8
8
|
TOKEN_REQUIRED_NON_INTERACTIVE
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-S6D7YVBB.js";
|
|
10
10
|
import "./chunk-MLKGABMK.js";
|
|
11
11
|
|
|
12
12
|
// src/browsers.ts
|
|
@@ -40,9 +40,9 @@ async function runBrowsers(argv, deps) {
|
|
|
40
40
|
const send = deps.fetchImpl ?? globalThis.fetch;
|
|
41
41
|
const rest = withoutFlag(argv, "--token");
|
|
42
42
|
const action = rest[0] ?? "list";
|
|
43
|
-
if (action !== "add" && action !== "list" && action !== "revoke") {
|
|
43
|
+
if (action !== "add" && action !== "list" && action !== "revoke" && action !== "repair") {
|
|
44
44
|
deps.out(
|
|
45
|
-
`usage: browsers [add <label>|list|revoke <handle
|
|
45
|
+
`usage: browsers [add <label>|list|revoke <handle>|repair] [--token <t>]`
|
|
46
46
|
);
|
|
47
47
|
return 2;
|
|
48
48
|
}
|
|
@@ -93,12 +93,19 @@ async function runBrowsers(argv, deps) {
|
|
|
93
93
|
}
|
|
94
94
|
if (action === "list") {
|
|
95
95
|
const rows = parsed["browsers"] ?? [];
|
|
96
|
+
const registryStatus = parsed["registryStatus"];
|
|
97
|
+
if (registryStatus?.state === "degraded") {
|
|
98
|
+
deps.out(
|
|
99
|
+
`Registry warning: ${String(registryStatus.error ?? "degraded")}.`
|
|
100
|
+
);
|
|
101
|
+
deps.out("Run: datalab-extension-mcp browsers repair");
|
|
102
|
+
}
|
|
96
103
|
if (rows.length === 0) {
|
|
97
104
|
deps.out("No browsers registered.");
|
|
98
105
|
return 0;
|
|
99
106
|
}
|
|
100
107
|
for (const row of rows) {
|
|
101
|
-
const state = row.browserId === null ? "waiting" : "connected";
|
|
108
|
+
const state = row.browserId === null ? "waiting" : row.connected === true ? "connected" : "detached";
|
|
102
109
|
deps.out(`${row.handle} ${state.padEnd(9)} ${row.label}`);
|
|
103
110
|
}
|
|
104
111
|
return 0;
|
|
@@ -109,6 +116,13 @@ async function runBrowsers(argv, deps) {
|
|
|
109
116
|
);
|
|
110
117
|
return parsed["removed"] === true ? 0 : 1;
|
|
111
118
|
}
|
|
119
|
+
if (action === "repair") {
|
|
120
|
+
const removed = Array.isArray(parsed["removed"]) ? parsed["removed"].length : 0;
|
|
121
|
+
deps.out(
|
|
122
|
+
removed > 0 ? `Repaired ${String(removed)} conflicting browser registration.` : "No conflicting browser registrations found."
|
|
123
|
+
);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
112
126
|
deps.out(`Registered ${String(parsed["handle"])}.`);
|
|
113
127
|
deps.out(`Paste this into the browser's panel \u2014 it is shown only now:`);
|
|
114
128
|
deps.out(String(parsed["token"]));
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
mediaToolDescriptors,
|
|
5
5
|
spawnDaemon,
|
|
6
6
|
tryConnect
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Q6SOGGKF.js";
|
|
8
8
|
import {
|
|
9
9
|
__export
|
|
10
10
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -14583,6 +14583,14 @@ var HelloSchema = external_exports.object({
|
|
|
14583
14583
|
*/
|
|
14584
14584
|
capabilities: external_exports.array(external_exports.string()).default([])
|
|
14585
14585
|
});
|
|
14586
|
+
var BRIDGE_AUTH_CAPABILITY = "bridge-auth-bearer";
|
|
14587
|
+
var BROWSER_ROUTING_CAPABILITY = "browser-routing-v1";
|
|
14588
|
+
var LOCAL_FILE_REQUEST_CAPABILITY = "local-file-request-v1";
|
|
14589
|
+
var REQUIRED_BROWSER_CAPABILITIES = Object.freeze([
|
|
14590
|
+
BRIDGE_AUTH_CAPABILITY,
|
|
14591
|
+
BROWSER_ROUTING_CAPABILITY,
|
|
14592
|
+
LOCAL_FILE_REQUEST_CAPABILITY
|
|
14593
|
+
]);
|
|
14586
14594
|
var HelloAckSchema = external_exports.object({
|
|
14587
14595
|
t: external_exports.literal("hello_ack"),
|
|
14588
14596
|
/** The single version both sides will use for this session. */
|
|
@@ -14601,7 +14609,11 @@ var HelloNackReasonSchema = external_exports.enum([
|
|
|
14601
14609
|
/** Origin header absent or not our extension. */
|
|
14602
14610
|
"forbidden_origin",
|
|
14603
14611
|
/** This browser already has an active event stream. */
|
|
14604
|
-
"session_active"
|
|
14612
|
+
"session_active",
|
|
14613
|
+
/** Root authority cannot open a browser session. */
|
|
14614
|
+
"browser_registration_required",
|
|
14615
|
+
/** This panel predates mandatory browser routing and file permits. */
|
|
14616
|
+
"browser_capability_required"
|
|
14605
14617
|
]);
|
|
14606
14618
|
var BrowserRoutingSchema = external_exports.object({
|
|
14607
14619
|
targetBrowserId: external_exports.string(),
|
|
@@ -15031,6 +15043,7 @@ var REASON_POLICY = Object.freeze({
|
|
|
15031
15043
|
// --- The request itself must change ---
|
|
15032
15044
|
unknown_tool: Object.freeze({ retryable: false }),
|
|
15033
15045
|
invalid_args: Object.freeze({ retryable: false }),
|
|
15046
|
+
browser_required: Object.freeze({ retryable: false }),
|
|
15034
15047
|
tool_closed: Object.freeze({ retryable: false }),
|
|
15035
15048
|
missing_in_build: Object.freeze({ retryable: false }),
|
|
15036
15049
|
// --- A human must act; re-calling only nags them ---
|
|
@@ -15444,7 +15457,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
|
|
|
15444
15457
|
panelClosed: "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC774 \uB2EB\uD600 \uC788\uC5B4\uC694. \uD06C\uB86C\uC744 \uC5F4\uACE0 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC744 \uC5F4\uC5B4 \uB450\uBA74 \uBC14\uB85C \uB3D9\uC791\uD574\uC694.",
|
|
15445
15458
|
/** The panel accepted the call but never answered inside the deadline. */
|
|
15446
15459
|
toolTimeout: "\uBE0C\uB77C\uC6B0\uC800 \uC751\uB2F5\uC774 \uB2A6\uC5B4\uC9C0\uACE0 \uC788\uC5B4\uC694. \uD06C\uB86C\uC5D0\uC11C \uB124\uC774\uBC84\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
|
|
15447
|
-
/** The
|
|
15460
|
+
/** The daemon request budget is empty. */
|
|
15448
15461
|
rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
|
|
15449
15462
|
/**
|
|
15450
15463
|
* The local connector accepted nothing back inside its ceiling. Distinct
|
|
@@ -15456,7 +15469,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
|
|
|
15456
15469
|
var BRIDGE_AGENT_HINT = Object.freeze({
|
|
15457
15470
|
/** Panel closed / not connected — the panel must be open for any tool to run. */
|
|
15458
15471
|
notConnected: "The Datalab extension side panel is not open, so this tool cannot run right now. Ask the user to open the extension side panel in their browser, then call this tool again.",
|
|
15459
|
-
/**
|
|
15472
|
+
/** Daemon-wide rate limit tripped — transient. */
|
|
15460
15473
|
rateLimited: "This tool is temporarily rate-limited. Wait a few seconds, then call it again.",
|
|
15461
15474
|
/** The local connector did not answer at all — retrying immediately will not help. */
|
|
15462
15475
|
connectorStuck: "The local connector stopped responding, so this tool cannot run right now. Do not retry immediately; ask the user to turn the MCP connection off and on again in the extension side panel."
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../mcp-server/dist/chunk-
|
|
1
|
+
// ../mcp-server/dist/chunk-377F4BGN.js
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
3
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
4
4
|
import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
@@ -24,8 +24,8 @@ import { spawn } from "child_process";
|
|
|
24
24
|
import { closeSync } from "fs";
|
|
25
25
|
import { randomBytes } from "crypto";
|
|
26
26
|
import { readFileSync } from "fs";
|
|
27
|
-
import { rename, writeFile as writeFile2 } from "fs/promises";
|
|
28
|
-
import { join as join3 } from "path";
|
|
27
|
+
import { mkdir as mkdir2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
28
|
+
import { dirname, join as join3 } from "path";
|
|
29
29
|
import { createRequire } from "module";
|
|
30
30
|
import { join as join4 } from "path";
|
|
31
31
|
var __defProp = Object.defineProperty;
|
|
@@ -14439,6 +14439,11 @@ var HelloSchema = external_exports.object({
|
|
|
14439
14439
|
var BRIDGE_AUTH_CAPABILITY = "bridge-auth-bearer";
|
|
14440
14440
|
var BROWSER_ROUTING_CAPABILITY = "browser-routing-v1";
|
|
14441
14441
|
var LOCAL_FILE_REQUEST_CAPABILITY = "local-file-request-v1";
|
|
14442
|
+
var REQUIRED_BROWSER_CAPABILITIES = Object.freeze([
|
|
14443
|
+
BRIDGE_AUTH_CAPABILITY,
|
|
14444
|
+
BROWSER_ROUTING_CAPABILITY,
|
|
14445
|
+
LOCAL_FILE_REQUEST_CAPABILITY
|
|
14446
|
+
]);
|
|
14442
14447
|
function bearerToken(header) {
|
|
14443
14448
|
if (typeof header !== "string") return null;
|
|
14444
14449
|
const match = /^Bearer (.+)$/i.exec(header.trim());
|
|
@@ -14462,7 +14467,11 @@ var HelloNackReasonSchema = external_exports.enum([
|
|
|
14462
14467
|
/** Origin header absent or not our extension. */
|
|
14463
14468
|
"forbidden_origin",
|
|
14464
14469
|
/** This browser already has an active event stream. */
|
|
14465
|
-
"session_active"
|
|
14470
|
+
"session_active",
|
|
14471
|
+
/** Root authority cannot open a browser session. */
|
|
14472
|
+
"browser_registration_required",
|
|
14473
|
+
/** This panel predates mandatory browser routing and file permits. */
|
|
14474
|
+
"browser_capability_required"
|
|
14466
14475
|
]);
|
|
14467
14476
|
var BrowserRoutingSchema = external_exports.object({
|
|
14468
14477
|
targetBrowserId: external_exports.string(),
|
|
@@ -14652,6 +14661,14 @@ var REFUSED = {
|
|
|
14652
14661
|
message: "Pairing token missing or invalid."
|
|
14653
14662
|
};
|
|
14654
14663
|
function resolveCredential(presented, known) {
|
|
14664
|
+
const root = checkToken(presented.token, known.rootToken);
|
|
14665
|
+
if (root.ok) {
|
|
14666
|
+
return {
|
|
14667
|
+
kind: "refused",
|
|
14668
|
+
reason: "browser_registration_required",
|
|
14669
|
+
message: "The root token is management-only. Register this browser with its own token."
|
|
14670
|
+
};
|
|
14671
|
+
}
|
|
14655
14672
|
const { browserId } = presented;
|
|
14656
14673
|
if (browserId !== void 0 && browserId.length > 0) {
|
|
14657
14674
|
const expected = known.claimed.get(browserId);
|
|
@@ -14662,8 +14679,7 @@ function resolveCredential(presented, known) {
|
|
|
14662
14679
|
return REFUSED;
|
|
14663
14680
|
}
|
|
14664
14681
|
}
|
|
14665
|
-
|
|
14666
|
-
return root.ok ? { kind: "root" } : REFUSED;
|
|
14682
|
+
return REFUSED;
|
|
14667
14683
|
}
|
|
14668
14684
|
var READ_ONLY_TOOLS = [
|
|
14669
14685
|
"datalab_browsers",
|
|
@@ -15015,6 +15031,7 @@ var REASON_POLICY = Object.freeze({
|
|
|
15015
15031
|
// --- The request itself must change ---
|
|
15016
15032
|
unknown_tool: Object.freeze({ retryable: false }),
|
|
15017
15033
|
invalid_args: Object.freeze({ retryable: false }),
|
|
15034
|
+
browser_required: Object.freeze({ retryable: false }),
|
|
15018
15035
|
tool_closed: Object.freeze({ retryable: false }),
|
|
15019
15036
|
missing_in_build: Object.freeze({ retryable: false }),
|
|
15020
15037
|
// --- A human must act; re-calling only nags them ---
|
|
@@ -15483,7 +15500,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
|
|
|
15483
15500
|
panelClosed: "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC774 \uB2EB\uD600 \uC788\uC5B4\uC694. \uD06C\uB86C\uC744 \uC5F4\uACE0 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC744 \uC5F4\uC5B4 \uB450\uBA74 \uBC14\uB85C \uB3D9\uC791\uD574\uC694.",
|
|
15484
15501
|
/** The panel accepted the call but never answered inside the deadline. */
|
|
15485
15502
|
toolTimeout: "\uBE0C\uB77C\uC6B0\uC800 \uC751\uB2F5\uC774 \uB2A6\uC5B4\uC9C0\uACE0 \uC788\uC5B4\uC694. \uD06C\uB86C\uC5D0\uC11C \uB124\uC774\uBC84\uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
|
|
15486
|
-
/** The
|
|
15503
|
+
/** The daemon request budget is empty. */
|
|
15487
15504
|
rateLimited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uC7A6\uC544\uC694. \uC7A0\uAE50 \uAE30\uB2E4\uB838\uB2E4\uAC00 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
|
|
15488
15505
|
/**
|
|
15489
15506
|
* The local connector accepted nothing back inside its ceiling. Distinct
|
|
@@ -15495,7 +15512,7 @@ var BRIDGE_USER_MESSAGES = Object.freeze({
|
|
|
15495
15512
|
var BRIDGE_AGENT_HINT = Object.freeze({
|
|
15496
15513
|
/** Panel closed / not connected — the panel must be open for any tool to run. */
|
|
15497
15514
|
notConnected: "The Datalab extension side panel is not open, so this tool cannot run right now. Ask the user to open the extension side panel in their browser, then call this tool again.",
|
|
15498
|
-
/**
|
|
15515
|
+
/** Daemon-wide rate limit tripped — transient. */
|
|
15499
15516
|
rateLimited: "This tool is temporarily rate-limited. Wait a few seconds, then call it again.",
|
|
15500
15517
|
/** The local connector did not answer at all — retrying immediately will not help. */
|
|
15501
15518
|
connectorStuck: "The local connector stopped responding, so this tool cannot run right now. Do not retry immediately; ask the user to turn the MCP connection off and on again in the extension side panel."
|
|
@@ -15615,10 +15632,6 @@ var PendingRegistry = class {
|
|
|
15615
15632
|
function digestToken(token) {
|
|
15616
15633
|
return createHash("sha256").update(token, "utf8").digest("hex");
|
|
15617
15634
|
}
|
|
15618
|
-
var LEGACY_SESSION_KEY = "legacy";
|
|
15619
|
-
function sessionKeyFor(credential) {
|
|
15620
|
-
return credential.kind === "claimed" ? credential.browserId : LEGACY_SESSION_KEY;
|
|
15621
|
-
}
|
|
15622
15635
|
var DEFAULT_RATE_LIMIT = BRIDGE_LIMITS;
|
|
15623
15636
|
function daemonIdTag() {
|
|
15624
15637
|
return Math.floor(Math.random() * 16777215).toString(36).padStart(4, "0");
|
|
@@ -15629,7 +15642,6 @@ var Bridge = class {
|
|
|
15629
15642
|
#idTag = daemonIdTag();
|
|
15630
15643
|
#sessionSeq = 0;
|
|
15631
15644
|
#defaultKey = null;
|
|
15632
|
-
#retireSession = null;
|
|
15633
15645
|
#bucketTokens = 0;
|
|
15634
15646
|
#bucketRefilledAt = 0;
|
|
15635
15647
|
// Last catalog the panel reported, kept across disconnects: many hosts cache
|
|
@@ -15638,12 +15650,9 @@ var Bridge = class {
|
|
|
15638
15650
|
#cachedTools = [];
|
|
15639
15651
|
constructor(deps) {
|
|
15640
15652
|
this.#deps = deps;
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
throw new Error("bridge transport retirement is already bound");
|
|
15645
|
-
}
|
|
15646
|
-
this.#retireSession = retire;
|
|
15653
|
+
const limit = deps.rateLimit ?? DEFAULT_RATE_LIMIT;
|
|
15654
|
+
this.#bucketTokens = limit.capacity;
|
|
15655
|
+
this.#bucketRefilledAt = (deps.now ?? Date.now)();
|
|
15647
15656
|
}
|
|
15648
15657
|
/**
|
|
15649
15658
|
* The session a request with no browser named goes to. Map preserves
|
|
@@ -15669,13 +15678,16 @@ var Bridge = class {
|
|
|
15669
15678
|
*/
|
|
15670
15679
|
connectedBrowsers() {
|
|
15671
15680
|
const keys = [...this.#sessions.values()].filter((entry) => entry.streamActive).map((entry) => entry.key);
|
|
15672
|
-
const primary = this.#
|
|
15681
|
+
const primary = this.#primary()?.key ?? null;
|
|
15673
15682
|
return keys.map((id) => ({
|
|
15674
15683
|
id,
|
|
15675
15684
|
name: this.#deps.browserLabels.get(id) ?? null,
|
|
15676
15685
|
primary: primary === id
|
|
15677
15686
|
}));
|
|
15678
15687
|
}
|
|
15688
|
+
browserRegistryStatus() {
|
|
15689
|
+
return this.#deps.browserRegistryStatus?.() ?? { state: "ready" };
|
|
15690
|
+
}
|
|
15679
15691
|
/**
|
|
15680
15692
|
* The only place a registry is born, and it is born already in the map.
|
|
15681
15693
|
* One living anywhere else would be missed by `sweep` and by `#closeSession`,
|
|
@@ -15699,7 +15711,6 @@ var Bridge = class {
|
|
|
15699
15711
|
key,
|
|
15700
15712
|
session,
|
|
15701
15713
|
tokenHash: auth.tokenHash,
|
|
15702
|
-
authRequired: auth.authRequired,
|
|
15703
15714
|
routingRequired: auth.routingRequired,
|
|
15704
15715
|
fileRequestBound: auth.fileRequestBound,
|
|
15705
15716
|
streamActive: false,
|
|
@@ -15751,9 +15762,6 @@ var Bridge = class {
|
|
|
15751
15762
|
sessionForKey(key) {
|
|
15752
15763
|
return this.#sessions.get(key)?.session ?? null;
|
|
15753
15764
|
}
|
|
15754
|
-
legacySessionKey() {
|
|
15755
|
-
return this.#sessions.has(LEGACY_SESSION_KEY) ? LEGACY_SESSION_KEY : null;
|
|
15756
|
-
}
|
|
15757
15765
|
/** Is a panel connected and ready to serve tool calls? */
|
|
15758
15766
|
get connected() {
|
|
15759
15767
|
return this.#primary() !== null;
|
|
@@ -15800,9 +15808,6 @@ var Bridge = class {
|
|
|
15800
15808
|
if (!timingSafeEqual(hash2, entry.tokenHash) || this.#deps.wasRevoked(hash2)) {
|
|
15801
15809
|
return null;
|
|
15802
15810
|
}
|
|
15803
|
-
if (entry.key === LEGACY_SESSION_KEY) {
|
|
15804
|
-
return this.verifyRootToken(presented) ? "root" : null;
|
|
15805
|
-
}
|
|
15806
15811
|
const current = this.#deps.claimedBrowsers.get(entry.key);
|
|
15807
15812
|
return current !== void 0 && timingSafeEqual(hash2, current) ? "claimed_browser" : null;
|
|
15808
15813
|
}
|
|
@@ -15814,33 +15819,6 @@ var Bridge = class {
|
|
|
15814
15819
|
}
|
|
15815
15820
|
return "unknown";
|
|
15816
15821
|
}
|
|
15817
|
-
authorizeLegacyFileCredential(presented) {
|
|
15818
|
-
if (typeof presented !== "string" || presented.length === 0) {
|
|
15819
|
-
return {
|
|
15820
|
-
ok: false,
|
|
15821
|
-
reason: "file_session_token_missing",
|
|
15822
|
-
credential: "missing"
|
|
15823
|
-
};
|
|
15824
|
-
}
|
|
15825
|
-
const credential = this.#fileCredentialClass(presented);
|
|
15826
|
-
for (const entry of this.#sessions.values()) {
|
|
15827
|
-
const accepted = this.#fileCredential(entry, presented);
|
|
15828
|
-
if (accepted && !entry.fileRequestBound) {
|
|
15829
|
-
return {
|
|
15830
|
-
ok: true,
|
|
15831
|
-
key: entry.key,
|
|
15832
|
-
sessionId: entry.session.sessionId,
|
|
15833
|
-
credential: accepted,
|
|
15834
|
-
requestBound: false
|
|
15835
|
-
};
|
|
15836
|
-
}
|
|
15837
|
-
}
|
|
15838
|
-
return {
|
|
15839
|
-
ok: false,
|
|
15840
|
-
reason: "file_session_token_mismatch",
|
|
15841
|
-
credential
|
|
15842
|
-
};
|
|
15843
|
-
}
|
|
15844
15822
|
authorizeFileRequest(input) {
|
|
15845
15823
|
const entry = [...this.#sessions.values()].find(
|
|
15846
15824
|
(candidate) => candidate.session.sessionId === input.sessionId
|
|
@@ -15859,7 +15837,7 @@ var Bridge = class {
|
|
|
15859
15837
|
credential: "missing",
|
|
15860
15838
|
key: entry.key,
|
|
15861
15839
|
sessionId: entry.session.sessionId,
|
|
15862
|
-
|
|
15840
|
+
requestId: input.requestId
|
|
15863
15841
|
};
|
|
15864
15842
|
}
|
|
15865
15843
|
const credential = this.#fileCredential(entry, input.token);
|
|
@@ -15870,32 +15848,23 @@ var Bridge = class {
|
|
|
15870
15848
|
credential: this.#fileCredentialClass(input.token),
|
|
15871
15849
|
key: entry.key,
|
|
15872
15850
|
sessionId: entry.session.sessionId,
|
|
15873
|
-
|
|
15851
|
+
requestId: input.requestId
|
|
15874
15852
|
};
|
|
15875
15853
|
}
|
|
15876
15854
|
if (!entry.fileRequestBound) {
|
|
15877
15855
|
return {
|
|
15878
|
-
ok:
|
|
15856
|
+
ok: false,
|
|
15857
|
+
reason: "file_request_expired",
|
|
15858
|
+
credential,
|
|
15879
15859
|
key: entry.key,
|
|
15880
15860
|
sessionId: entry.session.sessionId,
|
|
15881
|
-
|
|
15882
|
-
credential,
|
|
15883
|
-
requestBound: false
|
|
15861
|
+
requestId: input.requestId
|
|
15884
15862
|
};
|
|
15885
15863
|
}
|
|
15886
15864
|
const now = (this.#deps.now ?? Date.now)();
|
|
15887
15865
|
for (const [requestId, permit2] of entry.filePermits) {
|
|
15888
15866
|
if (permit2.expiresAt <= now) entry.filePermits.delete(requestId);
|
|
15889
15867
|
}
|
|
15890
|
-
if (input.requestId === void 0) {
|
|
15891
|
-
return {
|
|
15892
|
-
ok: false,
|
|
15893
|
-
reason: "file_request_expired",
|
|
15894
|
-
credential,
|
|
15895
|
-
key: entry.key,
|
|
15896
|
-
sessionId: entry.session.sessionId
|
|
15897
|
-
};
|
|
15898
|
-
}
|
|
15899
15868
|
const permit = entry.filePermits.get(input.requestId);
|
|
15900
15869
|
if (!permit) {
|
|
15901
15870
|
return {
|
|
@@ -15981,6 +15950,32 @@ var Bridge = class {
|
|
|
15981
15950
|
message: idCheck.message
|
|
15982
15951
|
};
|
|
15983
15952
|
}
|
|
15953
|
+
const negotiated = negotiateProtocol(parsed.data.protocolVersions);
|
|
15954
|
+
if (!negotiated.ok) {
|
|
15955
|
+
return {
|
|
15956
|
+
t: "hello_nack",
|
|
15957
|
+
reason: negotiated.reason,
|
|
15958
|
+
supported: negotiated.supported,
|
|
15959
|
+
message: negotiated.message
|
|
15960
|
+
};
|
|
15961
|
+
}
|
|
15962
|
+
const bearerRequired = parsed.data.capabilities.includes(
|
|
15963
|
+
BRIDGE_AUTH_CAPABILITY
|
|
15964
|
+
);
|
|
15965
|
+
const routingRequired = parsed.data.capabilities.includes(
|
|
15966
|
+
BROWSER_ROUTING_CAPABILITY
|
|
15967
|
+
);
|
|
15968
|
+
const fileRequestBound = parsed.data.capabilities.includes(
|
|
15969
|
+
LOCAL_FILE_REQUEST_CAPABILITY
|
|
15970
|
+
);
|
|
15971
|
+
if (!bearerRequired || !routingRequired || !fileRequestBound) {
|
|
15972
|
+
return {
|
|
15973
|
+
t: "hello_nack",
|
|
15974
|
+
reason: "browser_capability_required",
|
|
15975
|
+
supported: [],
|
|
15976
|
+
message: "This browser panel is outdated. Close and reopen the side panel before reconnecting."
|
|
15977
|
+
};
|
|
15978
|
+
}
|
|
15984
15979
|
const tokenHash = digestToken(parsed.data.token);
|
|
15985
15980
|
const browserId = parsed.data.browserId;
|
|
15986
15981
|
if (browserId !== void 0 && browserId.length > 0 && !this.#deps.claimedBrowsers.has(browserId)) {
|
|
@@ -16001,19 +15996,23 @@ var Bridge = class {
|
|
|
16001
15996
|
message: credential.message
|
|
16002
15997
|
};
|
|
16003
15998
|
}
|
|
16004
|
-
|
|
16005
|
-
if (!negotiated.ok) {
|
|
15999
|
+
if (credential.kind !== "claimed") {
|
|
16006
16000
|
return {
|
|
16007
16001
|
t: "hello_nack",
|
|
16008
|
-
reason:
|
|
16009
|
-
supported:
|
|
16010
|
-
message:
|
|
16002
|
+
reason: "browser_registration_required",
|
|
16003
|
+
supported: [],
|
|
16004
|
+
message: "The root token is management-only. Register this browser with its own token."
|
|
16011
16005
|
};
|
|
16012
16006
|
}
|
|
16013
|
-
const key =
|
|
16007
|
+
const key = credential.browserId;
|
|
16014
16008
|
const held = this.#sessions.get(key);
|
|
16015
16009
|
if (held?.streamActive) {
|
|
16016
|
-
|
|
16010
|
+
return {
|
|
16011
|
+
t: "hello_nack",
|
|
16012
|
+
reason: "session_active",
|
|
16013
|
+
supported: [],
|
|
16014
|
+
message: "This browser already has an active side-panel session."
|
|
16015
|
+
};
|
|
16017
16016
|
}
|
|
16018
16017
|
const session = {
|
|
16019
16018
|
// A counter, not a timestamp: a panel reload produces two handshakes
|
|
@@ -16023,34 +16022,17 @@ var Bridge = class {
|
|
|
16023
16022
|
protocolVersion: negotiated.version,
|
|
16024
16023
|
extensionVersion: parsed.data.extensionVersion
|
|
16025
16024
|
};
|
|
16026
|
-
const authRequired = parsed.data.capabilities.includes(
|
|
16027
|
-
BRIDGE_AUTH_CAPABILITY
|
|
16028
|
-
);
|
|
16029
|
-
const routingRequired = credential.kind === "claimed" && parsed.data.capabilities.includes(BROWSER_ROUTING_CAPABILITY);
|
|
16030
|
-
const fileRequestBound = parsed.data.capabilities.includes(
|
|
16031
|
-
LOCAL_FILE_REQUEST_CAPABILITY
|
|
16032
|
-
);
|
|
16033
16025
|
this.#installSession(key, session, {
|
|
16034
16026
|
tokenHash,
|
|
16035
|
-
authRequired,
|
|
16036
16027
|
routingRequired,
|
|
16037
16028
|
fileRequestBound
|
|
16038
16029
|
});
|
|
16039
|
-
const limit = this.#deps.rateLimit ?? DEFAULT_RATE_LIMIT;
|
|
16040
|
-
this.#bucketTokens = limit.capacity;
|
|
16041
|
-
this.#bucketRefilledAt = (this.#deps.now ?? Date.now)();
|
|
16042
16030
|
return {
|
|
16043
16031
|
t: "hello_ack",
|
|
16044
16032
|
protocolVersion: negotiated.version,
|
|
16045
16033
|
serverVersion: this.#deps.serverVersion,
|
|
16046
16034
|
sessionId: session.sessionId,
|
|
16047
|
-
|
|
16048
|
-
// is holding it to the header from one that predates the gate entirely.
|
|
16049
|
-
capabilities: [
|
|
16050
|
-
...authRequired ? [BRIDGE_AUTH_CAPABILITY] : [],
|
|
16051
|
-
...routingRequired ? [BROWSER_ROUTING_CAPABILITY] : [],
|
|
16052
|
-
...fileRequestBound ? [LOCAL_FILE_REQUEST_CAPABILITY] : []
|
|
16053
|
-
]
|
|
16035
|
+
capabilities: [...REQUIRED_BROWSER_CAPABILITIES]
|
|
16054
16036
|
};
|
|
16055
16037
|
}
|
|
16056
16038
|
/**
|
|
@@ -16068,15 +16050,13 @@ var Bridge = class {
|
|
|
16068
16050
|
return this.#mayAct(entry, presented);
|
|
16069
16051
|
}
|
|
16070
16052
|
#mayAct(entry, presented) {
|
|
16071
|
-
if (!entry.authRequired) return true;
|
|
16072
16053
|
if (typeof presented !== "string" || presented.length === 0) return false;
|
|
16073
16054
|
return timingSafeEqual(digestToken(presented), entry.tokenHash);
|
|
16074
16055
|
}
|
|
16075
16056
|
/**
|
|
16076
16057
|
* Route a frame from the panel to whoever is waiting for it.
|
|
16077
16058
|
*
|
|
16078
|
-
* `presented` is the credential the poster carried.
|
|
16079
|
-
* be held to it refuses anything else; one that did not is unaffected.
|
|
16059
|
+
* `presented` is the credential the poster carried.
|
|
16080
16060
|
*/
|
|
16081
16061
|
receive(raw, presented) {
|
|
16082
16062
|
const classified = classifyFrame(
|
|
@@ -16244,7 +16224,7 @@ var Bridge = class {
|
|
|
16244
16224
|
};
|
|
16245
16225
|
}
|
|
16246
16226
|
/**
|
|
16247
|
-
* Refill-then-take on the
|
|
16227
|
+
* Refill-then-take on the daemon's token bucket. The refill is computed
|
|
16248
16228
|
* lazily on each take, so there is no timer to leak and an injected clock
|
|
16249
16229
|
* keeps it testable without waiting real seconds.
|
|
16250
16230
|
*/
|
|
@@ -16268,7 +16248,7 @@ var Bridge = class {
|
|
|
16268
16248
|
if (browser === void 0) {
|
|
16269
16249
|
entry = this.#primary();
|
|
16270
16250
|
} else {
|
|
16271
|
-
const known = this.#deps.claimedBrowsers.has(browser)
|
|
16251
|
+
const known = this.#deps.claimedBrowsers.has(browser);
|
|
16272
16252
|
if (!known) {
|
|
16273
16253
|
return {
|
|
16274
16254
|
ok: false,
|
|
@@ -16857,14 +16837,22 @@ async function handleMcpRequest(bridge, body, accept, stage) {
|
|
|
16857
16837
|
delete args["browser"];
|
|
16858
16838
|
}
|
|
16859
16839
|
if (params.name === BROWSERS_TOOL) {
|
|
16840
|
+
const registryStatus = bridge.browserRegistryStatus();
|
|
16841
|
+
const { state: registryState, ...registryDetail } = registryStatus;
|
|
16842
|
+
const payload = {
|
|
16843
|
+
browsers: bridge.connectedBrowsers(),
|
|
16844
|
+
registryStatus: registryState,
|
|
16845
|
+
...registryState === "ready" ? {} : registryDetail
|
|
16846
|
+
};
|
|
16860
16847
|
return frameMcpResponse(
|
|
16861
16848
|
rpcResult(id, {
|
|
16862
16849
|
content: [
|
|
16863
16850
|
{
|
|
16864
16851
|
type: "text",
|
|
16865
|
-
text: JSON.stringify(
|
|
16852
|
+
text: JSON.stringify(payload)
|
|
16866
16853
|
}
|
|
16867
|
-
]
|
|
16854
|
+
],
|
|
16855
|
+
...registryState === "disabled" ? { isError: true } : {}
|
|
16868
16856
|
}),
|
|
16869
16857
|
accept
|
|
16870
16858
|
);
|
|
@@ -17164,13 +17152,6 @@ function createHttpBridge(opts) {
|
|
|
17164
17152
|
opts.bridge.disconnect(key);
|
|
17165
17153
|
if (wasPrimary) scheduleToolsDrop();
|
|
17166
17154
|
};
|
|
17167
|
-
opts.bridge.bindRetireSession((key, sessionId) => {
|
|
17168
|
-
const stream = streams.get(key);
|
|
17169
|
-
if (!stream || stream.sessionId !== sessionId) return;
|
|
17170
|
-
const wasPrimary = key === opts.bridge.sessionKey;
|
|
17171
|
-
removeStream(key)?.end();
|
|
17172
|
-
if (wasPrimary) scheduleToolsDrop();
|
|
17173
|
-
});
|
|
17174
17155
|
const server = createServer((req, res) => {
|
|
17175
17156
|
const url2 = new URL(req.url ?? "/", `http://${host}`);
|
|
17176
17157
|
const origin = req.headers.origin;
|
|
@@ -17240,7 +17221,11 @@ function createHttpBridge(opts) {
|
|
|
17240
17221
|
}
|
|
17241
17222
|
if (req.method === "GET" && url2.pathname === "/bridge/events") {
|
|
17242
17223
|
const claimed = url2.searchParams.get("session");
|
|
17243
|
-
|
|
17224
|
+
if (claimed === null || claimed.length === 0) {
|
|
17225
|
+
json2(res, 400, { error: "session_required" });
|
|
17226
|
+
return;
|
|
17227
|
+
}
|
|
17228
|
+
const key = claimed === null ? null : opts.bridge.keyForSession(claimed);
|
|
17244
17229
|
if (key === null) {
|
|
17245
17230
|
opts.log?.("refused a stream for an unknown session");
|
|
17246
17231
|
res.writeHead(200, { "content-type": "text/event-stream" });
|
|
@@ -17267,13 +17252,8 @@ function createHttpBridge(opts) {
|
|
|
17267
17252
|
// by nature.
|
|
17268
17253
|
"x-accel-buffering": "no"
|
|
17269
17254
|
});
|
|
17270
|
-
const sessionId = claimed ?? opts.bridge.sessionForKey(key)?.sessionId ?? "";
|
|
17271
|
-
if (!sessionId) {
|
|
17272
|
-
res.end();
|
|
17273
|
-
return;
|
|
17274
|
-
}
|
|
17275
17255
|
try {
|
|
17276
|
-
installStream(key,
|
|
17256
|
+
installStream(key, claimed, res);
|
|
17277
17257
|
} catch {
|
|
17278
17258
|
res.end();
|
|
17279
17259
|
return;
|
|
@@ -17314,7 +17294,18 @@ function createHttpBridge(opts) {
|
|
|
17314
17294
|
}
|
|
17315
17295
|
const action = b?.["action"];
|
|
17316
17296
|
if (action === "list") {
|
|
17317
|
-
|
|
17297
|
+
const active = new Set(
|
|
17298
|
+
opts.bridge.connectedBrowsers().map((row) => row.id)
|
|
17299
|
+
);
|
|
17300
|
+
const browsers = (await registry2.list()).map((row) => ({
|
|
17301
|
+
...row,
|
|
17302
|
+
connected: row.browserId !== null && active.has(row.browserId)
|
|
17303
|
+
}));
|
|
17304
|
+
json2(res, 200, {
|
|
17305
|
+
ok: true,
|
|
17306
|
+
browsers,
|
|
17307
|
+
registryStatus: registry2.status()
|
|
17308
|
+
});
|
|
17318
17309
|
return;
|
|
17319
17310
|
}
|
|
17320
17311
|
if (action === "add") {
|
|
@@ -17330,6 +17321,10 @@ function createHttpBridge(opts) {
|
|
|
17330
17321
|
});
|
|
17331
17322
|
return;
|
|
17332
17323
|
}
|
|
17324
|
+
if (action === "repair") {
|
|
17325
|
+
json2(res, 200, { ok: true, ...await registry2.repair() });
|
|
17326
|
+
return;
|
|
17327
|
+
}
|
|
17333
17328
|
json2(res, 400, { error: "unknown action" });
|
|
17334
17329
|
}).catch((err) => {
|
|
17335
17330
|
const why = err instanceof Error ? err.message : String(err);
|
|
@@ -17347,20 +17342,20 @@ function createHttpBridge(opts) {
|
|
|
17347
17342
|
const hasRequestId = b !== null && Object.hasOwn(b, "requestId");
|
|
17348
17343
|
const invalidSessionId = hasSessionId && (typeof b?.sessionId !== "string" || b.sessionId.length === 0);
|
|
17349
17344
|
const invalidRequestId = hasRequestId && (typeof b?.requestId !== "string" || b.requestId.length === 0);
|
|
17350
|
-
if (invalidSessionId || invalidRequestId) {
|
|
17345
|
+
if (!hasSessionId || !hasRequestId || invalidSessionId || invalidRequestId) {
|
|
17351
17346
|
json2(res, 403, {
|
|
17352
17347
|
error: "unauthorized",
|
|
17353
17348
|
message: "\uC0AC\uC9C4 \uC694\uCCAD\uC758 \uC5F0\uACB0 \uC815\uBCF4\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC5B4\uC694."
|
|
17354
17349
|
});
|
|
17355
17350
|
return;
|
|
17356
17351
|
}
|
|
17357
|
-
const auth =
|
|
17352
|
+
const auth = opts.bridge.authorizeFileRequest({
|
|
17358
17353
|
sessionId: b.sessionId,
|
|
17359
|
-
|
|
17354
|
+
requestId: b.requestId,
|
|
17360
17355
|
token,
|
|
17361
17356
|
path,
|
|
17362
17357
|
...typeof b?.tool === "string" ? { tool: b.tool } : {}
|
|
17363
|
-
})
|
|
17358
|
+
});
|
|
17364
17359
|
if (!auth.ok) {
|
|
17365
17360
|
opts.log?.(
|
|
17366
17361
|
`file request refused: reason=${auth.reason} credential=${auth.credential} tokenPresent=${String(token.length > 0)} sessionMatch=${String(auth.key !== void 0)} open=not-entered`
|
|
@@ -17383,7 +17378,7 @@ function createHttpBridge(opts) {
|
|
|
17383
17378
|
return;
|
|
17384
17379
|
}
|
|
17385
17380
|
opts.log?.(
|
|
17386
|
-
`file request authorised: browser=${correlationValue(auth.key, 8)} session=${correlationValue(auth.sessionId, 32)} request=${
|
|
17381
|
+
`file request authorised: browser=${correlationValue(auth.key, 8)} session=${correlationValue(auth.sessionId, 32)} request=${correlationValue(auth.requestId, 96)} credential=${auth.credential} registryMatch=${String(auth.credential === "claimed_browser")} open=entered`
|
|
17387
17382
|
);
|
|
17388
17383
|
let source = null;
|
|
17389
17384
|
let owner = null;
|
|
@@ -17842,6 +17837,7 @@ async function writeLedger(path, claims) {
|
|
|
17842
17837
|
var writing = /* @__PURE__ */ new Map();
|
|
17843
17838
|
async function writeOnce(path, claims) {
|
|
17844
17839
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
17840
|
+
await mkdir2(dirname(path), { recursive: true, mode: 448 });
|
|
17845
17841
|
await writeFile2(tmp, `${JSON.stringify(claims, null, 2)}
|
|
17846
17842
|
`, {
|
|
17847
17843
|
mode: 384
|
|
@@ -17873,16 +17869,53 @@ function matches(claim, handle) {
|
|
|
17873
17869
|
function createBrowserRegistry(opts) {
|
|
17874
17870
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
17875
17871
|
const revoked = /* @__PURE__ */ new Set();
|
|
17876
|
-
const
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
if (
|
|
17883
|
-
throw new Error(
|
|
17872
|
+
const initial = readLedger(opts.path);
|
|
17873
|
+
const unreadable = initial.state === "unreadable" ? initial.reason : null;
|
|
17874
|
+
let rows = initial.state === "ok" ? initial.rows.map((row) => ({ ...row })) : [];
|
|
17875
|
+
let mutationTail = Promise.resolve();
|
|
17876
|
+
let pendingMutations = 0;
|
|
17877
|
+
const view = (source = rows) => {
|
|
17878
|
+
if (unreadable !== null) {
|
|
17879
|
+
throw new Error(`browser registry is unreadable: ${unreadable}`);
|
|
17880
|
+
}
|
|
17881
|
+
if (!opts.forbiddenTokenHash) return { rows: [...source], quarantined: [] };
|
|
17882
|
+
const quarantined = source.filter(
|
|
17883
|
+
(row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
17884
|
+
);
|
|
17885
|
+
return {
|
|
17886
|
+
rows: source.filter(
|
|
17887
|
+
(row) => !timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
17888
|
+
),
|
|
17889
|
+
quarantined
|
|
17890
|
+
};
|
|
17891
|
+
};
|
|
17892
|
+
const current = () => view().rows;
|
|
17893
|
+
const serialize = (work) => {
|
|
17894
|
+
pendingMutations += 1;
|
|
17895
|
+
const result = mutationTail.then(work, work);
|
|
17896
|
+
mutationTail = result.then(
|
|
17897
|
+
() => {
|
|
17898
|
+
pendingMutations -= 1;
|
|
17899
|
+
},
|
|
17900
|
+
() => {
|
|
17901
|
+
pendingMutations -= 1;
|
|
17902
|
+
}
|
|
17903
|
+
);
|
|
17904
|
+
return result;
|
|
17905
|
+
};
|
|
17906
|
+
const transact = (propose) => {
|
|
17907
|
+
if (unreadable !== null) {
|
|
17908
|
+
return Promise.reject(
|
|
17909
|
+
new Error(`browser registry is unreadable: ${unreadable}`)
|
|
17910
|
+
);
|
|
17884
17911
|
}
|
|
17885
|
-
return
|
|
17912
|
+
return serialize(async () => {
|
|
17913
|
+
const proposal = propose(rows.map((row) => ({ ...row })));
|
|
17914
|
+
await writeLedger(opts.path, proposal.next);
|
|
17915
|
+
rows = proposal.next;
|
|
17916
|
+
proposal.commit?.();
|
|
17917
|
+
return proposal.result;
|
|
17918
|
+
});
|
|
17886
17919
|
};
|
|
17887
17920
|
return {
|
|
17888
17921
|
async add(label) {
|
|
@@ -17893,8 +17926,10 @@ function createBrowserRegistry(opts) {
|
|
|
17893
17926
|
label,
|
|
17894
17927
|
createdAt: now().toISOString()
|
|
17895
17928
|
};
|
|
17896
|
-
|
|
17897
|
-
|
|
17929
|
+
return transact((snapshot) => ({
|
|
17930
|
+
next: [...snapshot, claim],
|
|
17931
|
+
result: { handle: handleOf(claim), token }
|
|
17932
|
+
}));
|
|
17898
17933
|
},
|
|
17899
17934
|
async list() {
|
|
17900
17935
|
return current().map((claim) => ({
|
|
@@ -17905,6 +17940,7 @@ function createBrowserRegistry(opts) {
|
|
|
17905
17940
|
}));
|
|
17906
17941
|
},
|
|
17907
17942
|
claim(browserId, tokenHash) {
|
|
17943
|
+
if (pendingMutations > 0) return false;
|
|
17908
17944
|
if (opts.claimed.has(browserId)) return false;
|
|
17909
17945
|
for (const held of opts.claimed.values()) {
|
|
17910
17946
|
if (timingSafeEqual(held, tokenHash)) return false;
|
|
@@ -17919,26 +17955,81 @@ function createBrowserRegistry(opts) {
|
|
|
17919
17955
|
(claim) => claim.browserId === null && timingSafeEqual(claim.tokenHash, tokenHash)
|
|
17920
17956
|
);
|
|
17921
17957
|
if (!row) return false;
|
|
17922
|
-
|
|
17958
|
+
const stored = rows.find((claim) => claim.tokenHash === row.tokenHash);
|
|
17959
|
+
if (!stored) return false;
|
|
17960
|
+
stored.browserId = browserId;
|
|
17923
17961
|
opts.claimed.set(browserId, row.tokenHash);
|
|
17924
|
-
|
|
17925
|
-
});
|
|
17962
|
+
opts.onClaim?.(browserId, stored.label);
|
|
17963
|
+
const snapshot = rows.map((entry) => ({ ...entry }));
|
|
17964
|
+
void serialize(async () => {
|
|
17965
|
+
try {
|
|
17966
|
+
await writeLedger(opts.path, snapshot);
|
|
17967
|
+
} catch (error51) {
|
|
17968
|
+
if (stored.browserId === browserId) stored.browserId = null;
|
|
17969
|
+
if (opts.claimed.get(browserId) === row.tokenHash) {
|
|
17970
|
+
opts.claimed.delete(browserId);
|
|
17971
|
+
opts.onRevoke?.(browserId);
|
|
17972
|
+
}
|
|
17973
|
+
throw error51;
|
|
17974
|
+
}
|
|
17975
|
+
}).catch(() => void 0);
|
|
17926
17976
|
return true;
|
|
17927
17977
|
},
|
|
17928
17978
|
async revoke(handle) {
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
if (!matches(claim, handle)) continue;
|
|
17935
|
-
revoked.add(claim.tokenHash);
|
|
17936
|
-
if (claim.browserId !== null) {
|
|
17937
|
-
opts.claimed.delete(claim.browserId);
|
|
17938
|
-
opts.onRevoke?.(claim.browserId);
|
|
17979
|
+
return transact((snapshot) => {
|
|
17980
|
+
const claims = view(snapshot).rows;
|
|
17981
|
+
const removed = claims.filter((claim) => matches(claim, handle));
|
|
17982
|
+
if (removed.length === 0) {
|
|
17983
|
+
return { next: snapshot, result: false };
|
|
17939
17984
|
}
|
|
17985
|
+
const removedHashes = new Set(removed.map((claim) => claim.tokenHash));
|
|
17986
|
+
return {
|
|
17987
|
+
next: snapshot.filter((claim) => !removedHashes.has(claim.tokenHash)),
|
|
17988
|
+
result: true,
|
|
17989
|
+
commit: () => {
|
|
17990
|
+
for (const claim of removed) {
|
|
17991
|
+
revoked.add(claim.tokenHash);
|
|
17992
|
+
if (claim.browserId !== null) {
|
|
17993
|
+
opts.claimed.delete(claim.browserId);
|
|
17994
|
+
opts.onRevoke?.(claim.browserId);
|
|
17995
|
+
}
|
|
17996
|
+
}
|
|
17997
|
+
}
|
|
17998
|
+
};
|
|
17999
|
+
});
|
|
18000
|
+
},
|
|
18001
|
+
async repair() {
|
|
18002
|
+
return transact((snapshot) => {
|
|
18003
|
+
const currentView = view(snapshot);
|
|
18004
|
+
const removed = currentView.quarantined.map(handleOf);
|
|
18005
|
+
const removedHashes = new Set(
|
|
18006
|
+
currentView.quarantined.map((row) => row.tokenHash)
|
|
18007
|
+
);
|
|
18008
|
+
return {
|
|
18009
|
+
next: snapshot.filter((row) => !removedHashes.has(row.tokenHash)),
|
|
18010
|
+
result: { removed }
|
|
18011
|
+
};
|
|
18012
|
+
});
|
|
18013
|
+
},
|
|
18014
|
+
status() {
|
|
18015
|
+
if (unreadable !== null) {
|
|
18016
|
+
return {
|
|
18017
|
+
state: "disabled",
|
|
18018
|
+
error: "browser_registry_unreadable",
|
|
18019
|
+
detail: unreadable
|
|
18020
|
+
};
|
|
17940
18021
|
}
|
|
17941
|
-
|
|
18022
|
+
if (opts.forbiddenTokenHash && ledgerOverlapsToken(rows, opts.forbiddenTokenHash)) {
|
|
18023
|
+
return {
|
|
18024
|
+
state: "degraded",
|
|
18025
|
+
error: "browser_registry_root_authority_overlap",
|
|
18026
|
+
quarantinedHandles: rows.filter(
|
|
18027
|
+
(row) => timingSafeEqual(row.tokenHash, opts.forbiddenTokenHash)
|
|
18028
|
+
).map(handleOf),
|
|
18029
|
+
recovery: "browsers repair"
|
|
18030
|
+
};
|
|
18031
|
+
}
|
|
18032
|
+
return { state: "ready" };
|
|
17942
18033
|
},
|
|
17943
18034
|
wasRevoked(tokenHash) {
|
|
17944
18035
|
return revoked.has(tokenHash);
|
|
@@ -17972,12 +18063,12 @@ function runDaemon(env, deps = {}) {
|
|
|
17972
18063
|
const browserLabels = /* @__PURE__ */ new Map();
|
|
17973
18064
|
const ledger = readLedger(ledgerPath(deps.home));
|
|
17974
18065
|
const rootTokenHash = digestToken(token);
|
|
17975
|
-
const
|
|
17976
|
-
if (ledger.state === "ok"
|
|
17977
|
-
for (const [id, hash2] of claimedDigestsFrom(
|
|
18066
|
+
const safeLedgerRows = ledger.state === "ok" ? ledger.rows.filter((row) => row.tokenHash !== rootTokenHash) : [];
|
|
18067
|
+
if (ledger.state === "ok") {
|
|
18068
|
+
for (const [id, hash2] of claimedDigestsFrom(safeLedgerRows)) {
|
|
17978
18069
|
claimedBrowsers.set(id, hash2);
|
|
17979
18070
|
}
|
|
17980
|
-
for (const row of
|
|
18071
|
+
for (const row of safeLedgerRows) {
|
|
17981
18072
|
if (row.browserId !== null) browserLabels.set(row.browserId, row.label);
|
|
17982
18073
|
}
|
|
17983
18074
|
}
|
|
@@ -17986,8 +18077,10 @@ function runDaemon(env, deps = {}) {
|
|
|
17986
18077
|
`browser registry is unreadable (${ledger.reason}); registering is off`
|
|
17987
18078
|
);
|
|
17988
18079
|
}
|
|
17989
|
-
if (
|
|
17990
|
-
log(
|
|
18080
|
+
if (ledger.state === "ok" && ledgerOverlapsToken(ledger.rows, rootTokenHash)) {
|
|
18081
|
+
log(
|
|
18082
|
+
"browser registry overlaps the root authority; conflicting rows are quarantined"
|
|
18083
|
+
);
|
|
17991
18084
|
}
|
|
17992
18085
|
const bridge = new Bridge({
|
|
17993
18086
|
send: (frame, key) => http.send(frame, key),
|
|
@@ -17995,24 +18088,11 @@ function runDaemon(env, deps = {}) {
|
|
|
17995
18088
|
extensionIds,
|
|
17996
18089
|
serverVersion: version2,
|
|
17997
18090
|
rateLimit: deps.rateLimit,
|
|
17998
|
-
// Empty until a browser is registered
|
|
17999
|
-
// yet — so every panel still shares the one slot it always had.
|
|
18091
|
+
// Empty until a browser is registered.
|
|
18000
18092
|
claimedBrowsers,
|
|
18001
18093
|
browserLabels,
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
if (took) {
|
|
18005
|
-
const now = readLedger(ledgerPath(deps.home));
|
|
18006
|
-
if (now.state === "ok") {
|
|
18007
|
-
for (const row of now.rows) {
|
|
18008
|
-
if (row.browserId === browserId) {
|
|
18009
|
-
browserLabels.set(browserId, row.label);
|
|
18010
|
-
}
|
|
18011
|
-
}
|
|
18012
|
-
}
|
|
18013
|
-
}
|
|
18014
|
-
return took;
|
|
18015
|
-
},
|
|
18094
|
+
browserRegistryStatus: () => registry2.status(),
|
|
18095
|
+
claimBrowser: (browserId, tokenHash) => registry2.claim(browserId, tokenHash),
|
|
18016
18096
|
wasRevoked: (tokenHash) => registry2.wasRevoked(tokenHash),
|
|
18017
18097
|
log
|
|
18018
18098
|
});
|
|
@@ -18021,6 +18101,7 @@ function runDaemon(env, deps = {}) {
|
|
|
18021
18101
|
const registry2 = createBrowserRegistry({
|
|
18022
18102
|
path: ledgerPath(deps.home),
|
|
18023
18103
|
claimed: claimedBrowsers,
|
|
18104
|
+
onClaim: (browserId, label) => browserLabels.set(browserId, label),
|
|
18024
18105
|
forbiddenTokenHash: rootTokenHash,
|
|
18025
18106
|
onRevoke: (browserId) => http.disconnect(browserId)
|
|
18026
18107
|
});
|
|
@@ -1109,7 +1109,7 @@ async function removeSkills(io, detectedIds, slugs) {
|
|
|
1109
1109
|
var RESTART_NOTICE = "\uB9C8\uC9C0\uB9C9 \uD55C \uB2E8\uACC4: AI \uC571\uC744 \uC644\uC804\uD788 \uC885\uB8CC\uD588\uB2E4\uAC00 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.\n(Windows: \uC791\uC5C5 \uD45C\uC2DC\uC904 \uD2B8\uB808\uC774 \uC544\uC774\uCF58\uC5D0\uC11C \uC885\uB8CC)";
|
|
1110
1110
|
var NOTHING_CHANGED = "\uC544\uBB34\uAC83\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC5B4\uC694.";
|
|
1111
1111
|
var UNINSTALL_DONE = "\uC815\uB9AC\uAC00 \uB05D\uB0AC\uC5B4\uC694.";
|
|
1112
|
-
var UNINSTALL_TOKEN_REMINDER = "\uC5F0\uACB0 \
|
|
1112
|
+
var UNINSTALL_TOKEN_REMINDER = "\uC774 \uBE0C\uB77C\uC6B0\uC800\uC758 \uB85C\uCEEC \uC5F0\uACB0 \uC815\uBCF4\uB3C4 \uC9C0\uC6B0\uB824\uBA74 \uB370\uC774\uD130\uB7A9\uD234\uC988 \uD328\uB110\uC5D0\uC11C '\uC774 \uBE0C\uB77C\uC6B0\uC800 \uC5F0\uACB0 \uB044\uAE30'\uB97C \uB20C\uB7EC \uC8FC\uC138\uC694. \uC6CC\uD06C\uBCA4\uCE58 \uB4F1\uB85D\uC740 \uBE0C\uB77C\uC6B0\uC800 \uBAA9\uB85D\uC5D0\uC11C \uBCC4\uB3C4\uB85C \uB0B4\uB9B4 \uC218 \uC788\uC5B4\uC694.";
|
|
1113
1113
|
var PERMISSION_DENIED_HINT = "\uD30C\uC77C \uAD8C\uD55C \uB54C\uBB38\uC5D0 \uC124\uC815\uC744 \uC4F8 \uC218 \uC5C6\uC5C8\uC5B4\uC694. \uD30C\uC77C \uAD8C\uD55C\uC744 \uD655\uC778\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
|
|
1114
1114
|
var NO_HOSTS_DETECTED = "\uC5F0\uACB0\uD560 \uC218 \uC788\uB294 AI \uD504\uB85C\uADF8\uB7A8\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC5B4\uC694.";
|
|
1115
1115
|
var SUPPORTED_APPS_HEADER = "\uC9C0\uC6D0\uD558\uB294 \uD504\uB85C\uADF8\uB7A8\uACFC \uB2E4\uC6B4\uB85C\uB4DC \uC8FC\uC18C\uC608\uC694:";
|
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
dispatchCli,
|
|
4
4
|
runAdapter
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-65FDP6LF.js";
|
|
6
6
|
import {
|
|
7
7
|
runDaemon
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-Q6SOGGKF.js";
|
|
9
9
|
import "./chunk-MLKGABMK.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
@@ -17,7 +17,7 @@ function log(message) {
|
|
|
17
17
|
`);
|
|
18
18
|
}
|
|
19
19
|
async function runInstaller(sub, argv) {
|
|
20
|
-
const { createNodeIo, runInstall, runUninstall } = await import("./dist-
|
|
20
|
+
const { createNodeIo, runInstall, runUninstall } = await import("./dist-63JYX33Z.js");
|
|
21
21
|
const opts = { version: VERSION };
|
|
22
22
|
for (let i = 0; i < argv.length; i += 1) {
|
|
23
23
|
const flag = argv[i];
|
|
@@ -62,8 +62,8 @@ async function main() {
|
|
|
62
62
|
// Loaded lazily, like the installer: the adapter path is what an MCP host
|
|
63
63
|
// spawns and must not pay for a command only an operator types.
|
|
64
64
|
browsers: async (argv) => {
|
|
65
|
-
const { runBrowsers } = await import("./browsers-
|
|
66
|
-
const { createNodeIo } = await import("./dist-
|
|
65
|
+
const { runBrowsers } = await import("./browsers-EMS5V4M4.js");
|
|
66
|
+
const { createNodeIo } = await import("./dist-63JYX33Z.js");
|
|
67
67
|
const io = createNodeIo();
|
|
68
68
|
const code = await runBrowsers(argv, {
|
|
69
69
|
env: process.env,
|
|
@@ -88,7 +88,7 @@ async function main() {
|
|
|
88
88
|
attachSkill,
|
|
89
89
|
detachSkill,
|
|
90
90
|
skillsPayloadRoot
|
|
91
|
-
} = await import("./dist-
|
|
91
|
+
} = await import("./dist-63JYX33Z.js");
|
|
92
92
|
const io = createNodeIo();
|
|
93
93
|
const root = skillsPayloadRoot();
|
|
94
94
|
const code = await runSkills(argv, {
|
|
@@ -109,7 +109,7 @@ async function main() {
|
|
|
109
109
|
unknown: (sub) => {
|
|
110
110
|
log(`unknown command "${sub}".`);
|
|
111
111
|
log(`usage: ${NAME} [install|uninstall|serve|browsers|skills]`);
|
|
112
|
-
log(` browsers [add <label>|list|revoke <handle
|
|
112
|
+
log(` browsers [add <label>|list|revoke <handle>|repair] [--token <t>]`);
|
|
113
113
|
log(` skills [list|status|attach <slug>|detach <slug>]`);
|
|
114
114
|
log(`run with no command to serve an MCP host over stdio.`);
|
|
115
115
|
process.exit(2);
|
package/dist/index.js
CHANGED