qz-browser-agent 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -318
- package/package.json +2 -2
- package/src/args.mjs +18 -2
- package/src/bin.mjs +5 -7
- package/src/browser-registry.mjs +101 -0
- package/src/catalog.json +29 -31
- package/src/cli.mjs +97 -19
- package/src/client.mjs +15 -3
- package/src/commands.mjs +8 -5
- package/src/daemon.mjs +18 -3
- package/src/package-info.mjs +1 -0
- package/src/server.mjs +18 -8
- package/src/state.mjs +136 -32
- package/src/task-stream.mjs +5 -3
- package/src/terminal.mjs +38 -16
- package/src/tools.mjs +20 -29
package/src/catalog.json
CHANGED
|
@@ -4,20 +4,18 @@
|
|
|
4
4
|
"description": "查看本地 bridge 和当前在线的轻舟扩展客户端。",
|
|
5
5
|
"inputSchema": {
|
|
6
6
|
"properties": {
|
|
7
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
7
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
8
8
|
},
|
|
9
9
|
"title": "qingzhou_bridge_statusArguments",
|
|
10
10
|
"type": "object"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
"name": "
|
|
15
|
-
"description": "
|
|
14
|
+
"name": "qingzhou_forget_browser",
|
|
15
|
+
"description": "Forget an offline browser registration, freeing its name for explicit pairing. Read qingzhou_bridge_status first and disconnect online browsers in their extension settings.",
|
|
16
16
|
"inputSchema": {
|
|
17
|
-
"properties": {
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
"title": "qingzhou_bridge_enable_snippetArguments",
|
|
17
|
+
"properties": {"clientId": {"type": "string", "minLength": 1, "description": "Registered browser name or UUID."}},
|
|
18
|
+
"required": ["clientId"],
|
|
21
19
|
"type": "object"
|
|
22
20
|
}
|
|
23
21
|
},
|
|
@@ -29,7 +27,7 @@
|
|
|
29
27
|
"method": {"title": "Method", "type": "string"},
|
|
30
28
|
"params": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "default": null, "title": "Params"},
|
|
31
29
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
32
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
30
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
33
31
|
},
|
|
34
32
|
"required": ["method"],
|
|
35
33
|
"title": "qingzhou_callArguments",
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
"inputSchema": {
|
|
43
41
|
"properties": {
|
|
44
42
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
45
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
43
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
46
44
|
},
|
|
47
45
|
"title": "qingzhou_list_capabilitiesArguments",
|
|
48
46
|
"type": "object"
|
|
@@ -56,7 +54,7 @@
|
|
|
56
54
|
"limit": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Limit"},
|
|
57
55
|
"excludeSessionId": {"default": "", "title": "Excludesessionid", "type": "string"},
|
|
58
56
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
59
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
57
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
60
58
|
},
|
|
61
59
|
"title": "qingzhou_list_sessionsArguments",
|
|
62
60
|
"type": "object"
|
|
@@ -74,7 +72,7 @@
|
|
|
74
72
|
"firstMessage": {"default": "", "title": "Firstmessage", "type": "string"},
|
|
75
73
|
"parentSessionId": {"default": "", "title": "Parentsessionid", "type": "string"},
|
|
76
74
|
"timeoutSeconds": {"anyOf": [{"type": "number", "exclusiveMinimum": 0, "maximum": 86400}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
77
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
75
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
78
76
|
},
|
|
79
77
|
"title": "qingzhou_create_sessionArguments",
|
|
80
78
|
"type": "object"
|
|
@@ -95,7 +93,7 @@
|
|
|
95
93
|
"waitForStart": {"default": true, "title": "Waitforstart", "type": "boolean"},
|
|
96
94
|
"waitForStartMs": {"default": 2000, "title": "Waitforstartms", "type": "number"},
|
|
97
95
|
"timeoutSeconds": {"anyOf": [{"type": "number", "exclusiveMinimum": 0, "maximum": 86400}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
98
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
96
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
99
97
|
},
|
|
100
98
|
"required": ["text"],
|
|
101
99
|
"title": "qingzhou_send_messageArguments",
|
|
@@ -112,7 +110,7 @@
|
|
|
112
110
|
"images": {"anyOf": [{"items": {}, "type": "array"}, {"type": "null"}], "default": null, "title": "Images"},
|
|
113
111
|
"clientMessageId": {"default": "", "title": "Clientmessageid", "type": "string"},
|
|
114
112
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
115
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
113
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
116
114
|
},
|
|
117
115
|
"required": ["text"],
|
|
118
116
|
"title": "qingzhou_follow_upArguments",
|
|
@@ -127,7 +125,7 @@
|
|
|
127
125
|
"sessionId": {"title": "Sessionid", "type": "string"},
|
|
128
126
|
"cascade": {"default": true, "title": "Cascade", "type": "boolean"},
|
|
129
127
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
130
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
128
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
131
129
|
},
|
|
132
130
|
"required": ["sessionId"],
|
|
133
131
|
"title": "qingzhou_abort_sessionArguments",
|
|
@@ -176,7 +174,7 @@
|
|
|
176
174
|
"waitId": {"title": "Waitid", "type": "string", "minLength": 1},
|
|
177
175
|
"payload": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "default": null, "title": "Payload"},
|
|
178
176
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
179
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
177
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
180
178
|
},
|
|
181
179
|
"required": ["sessionId", "waitId"],
|
|
182
180
|
"title": "qingzhou_continue_taskArguments",
|
|
@@ -193,7 +191,7 @@
|
|
|
193
191
|
"waitId": {"title": "Waitid", "type": "string", "minLength": 1},
|
|
194
192
|
"reason": {"default": "submitted", "title": "Reason", "type": "string"},
|
|
195
193
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
196
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
194
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
197
195
|
},
|
|
198
196
|
"required": ["sessionId", "answers", "waitId"],
|
|
199
197
|
"title": "qingzhou_submit_wait_formArguments",
|
|
@@ -208,7 +206,7 @@
|
|
|
208
206
|
"sessionId": {"title": "Sessionid", "type": "string"},
|
|
209
207
|
"waitId": {"title": "Waitid", "type": "string", "minLength": 1},
|
|
210
208
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
211
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
209
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
212
210
|
},
|
|
213
211
|
"required": ["sessionId", "waitId"],
|
|
214
212
|
"title": "qingzhou_cancel_waitArguments",
|
|
@@ -222,7 +220,7 @@
|
|
|
222
220
|
"properties": {
|
|
223
221
|
"sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
|
|
224
222
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
225
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
223
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
226
224
|
},
|
|
227
225
|
"title": "qingzhou_get_session_stateArguments",
|
|
228
226
|
"type": "object"
|
|
@@ -242,7 +240,7 @@
|
|
|
242
240
|
"traceLimit": {"default": 40, "title": "Tracelimit", "type": "number"},
|
|
243
241
|
"includeTools": {"default": false, "title": "Includetools", "type": "boolean"},
|
|
244
242
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
245
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
243
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
246
244
|
},
|
|
247
245
|
"title": "qingzhou_get_session_snapshotArguments",
|
|
248
246
|
"type": "object"
|
|
@@ -258,7 +256,7 @@
|
|
|
258
256
|
"tail": {"default": true, "title": "Tail", "type": "boolean"},
|
|
259
257
|
"num": {"default": 50, "title": "Num", "type": "number"},
|
|
260
258
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
261
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
259
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
262
260
|
},
|
|
263
261
|
"title": "qingzhou_get_messagesArguments",
|
|
264
262
|
"type": "object"
|
|
@@ -272,7 +270,7 @@
|
|
|
272
270
|
"sessionId": {"title": "Sessionid", "type": "string"},
|
|
273
271
|
"cascade": {"default": true, "title": "Cascade", "type": "boolean"},
|
|
274
272
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
275
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
273
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
276
274
|
},
|
|
277
275
|
"required": ["sessionId"],
|
|
278
276
|
"title": "qingzhou_stop_sessionArguments",
|
|
@@ -286,7 +284,7 @@
|
|
|
286
284
|
"properties": {
|
|
287
285
|
"sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
|
|
288
286
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
289
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
287
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
290
288
|
},
|
|
291
289
|
"title": "qingzhou_list_toolsArguments",
|
|
292
290
|
"type": "object"
|
|
@@ -300,7 +298,7 @@
|
|
|
300
298
|
"scope": {"default": "all", "title": "Scope", "type": "string"},
|
|
301
299
|
"query": {"default": "", "title": "Query", "type": "string"},
|
|
302
300
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
303
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
301
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
304
302
|
},
|
|
305
303
|
"title": "qingzhou_list_skillsArguments",
|
|
306
304
|
"type": "object"
|
|
@@ -315,7 +313,7 @@
|
|
|
315
313
|
"includeContent": {"default": true, "title": "Includecontent", "type": "boolean"},
|
|
316
314
|
"includeResources": {"default": false, "title": "Includeresources", "type": "boolean"},
|
|
317
315
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
318
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
316
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
319
317
|
},
|
|
320
318
|
"required": ["name"],
|
|
321
319
|
"title": "qingzhou_get_skillArguments",
|
|
@@ -332,7 +330,7 @@
|
|
|
332
330
|
"start": {"default": 0, "title": "Start", "type": "number"},
|
|
333
331
|
"maxChars": {"default": 300000, "title": "Maxchars", "type": "number"},
|
|
334
332
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
335
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
333
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
336
334
|
},
|
|
337
335
|
"required": ["name", "path"],
|
|
338
336
|
"title": "qingzhou_get_skill_resourceArguments",
|
|
@@ -350,7 +348,7 @@
|
|
|
350
348
|
"runId": {"default": "", "title": "Runid", "type": "string"},
|
|
351
349
|
"wait": {"default": true, "title": "Wait", "type": "boolean"},
|
|
352
350
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
353
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
351
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
354
352
|
},
|
|
355
353
|
"required": ["name"],
|
|
356
354
|
"title": "qingzhou_execute_toolArguments",
|
|
@@ -405,7 +403,7 @@
|
|
|
405
403
|
"inputSchema": {
|
|
406
404
|
"properties": {
|
|
407
405
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
408
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
406
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
409
407
|
},
|
|
410
408
|
"title": "qingzhou_get_configArguments",
|
|
411
409
|
"type": "object"
|
|
@@ -418,7 +416,7 @@
|
|
|
418
416
|
"properties": {
|
|
419
417
|
"config": {"additionalProperties": true, "title": "Config", "type": "object"},
|
|
420
418
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
421
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
419
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
422
420
|
},
|
|
423
421
|
"required": ["config"],
|
|
424
422
|
"title": "qingzhou_save_configArguments",
|
|
@@ -432,7 +430,7 @@
|
|
|
432
430
|
"properties": {
|
|
433
431
|
"sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
|
|
434
432
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
435
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
433
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
436
434
|
},
|
|
437
435
|
"title": "qingzhou_list_pluginsArguments",
|
|
438
436
|
"type": "object"
|
|
@@ -446,7 +444,7 @@
|
|
|
446
444
|
"limit": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Limit"},
|
|
447
445
|
"runLimit": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Runlimit"},
|
|
448
446
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
449
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
447
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
450
448
|
},
|
|
451
449
|
"title": "qingzhou_list_automationsArguments",
|
|
452
450
|
"type": "object"
|
|
@@ -458,7 +456,7 @@
|
|
|
458
456
|
"inputSchema": {
|
|
459
457
|
"properties": {
|
|
460
458
|
"timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
|
|
461
|
-
"clientId": {"default": "", "title": "Clientid", "type": "string"}
|
|
459
|
+
"clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
|
|
462
460
|
},
|
|
463
461
|
"title": "qingzhou_get_traceArguments",
|
|
464
462
|
"type": "object"
|
package/src/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { ensureDaemon, findDaemon, stopDaemon } from "./daemon.mjs";
|
|
|
7
7
|
import { openBrowser } from "./open-browser.mjs";
|
|
8
8
|
import { parseCommand } from "./args.mjs";
|
|
9
9
|
import { renderError, renderHelp, renderResult, renderInputNotice, sanitizeTerminalText } from "./terminal.mjs";
|
|
10
|
-
import { waitForTask } from "./task-stream.mjs";
|
|
10
|
+
import { TASK_NOT_FOUND_MESSAGE, waitForTask } from "./task-stream.mjs";
|
|
11
11
|
import { createLiveOutput, outputMode } from "./output.mjs";
|
|
12
12
|
import { resourceRequest, settingsPatch } from "./cli-resources.mjs";
|
|
13
13
|
import { PACKAGE_NAME, PACKAGE_VERSION } from "./package-info.mjs";
|
|
@@ -40,8 +40,28 @@ async function stdinText({ signal } = {}) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const UPGRADE_HINT = "Use the latest CLI. After current tasks finish, run npx --yes qz-browser-agent@latest stop (disconnects all browsers), update and reload the qz extension, then run npx --yes qz-browser-agent@latest connect.";
|
|
44
|
+
|
|
45
|
+
function requirePairingSupport(health) {
|
|
46
|
+
if (health?.features?.browserPairing !== 1) throw new QzError("upgrade_required",
|
|
47
|
+
"This qz service is unsupported. Upgrade the CLI and extension, then run qz stop and qz connect.",
|
|
48
|
+
{ hint: UPGRADE_HINT });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function selectClient(health, selector = "", { allowEmpty = false } = {}) {
|
|
52
|
+
const clients = health.status?.clients || [];
|
|
53
|
+
if (selector) {
|
|
54
|
+
const entry = (health.status?.browsers || []).find(client => client.clientId === selector || client.name === selector);
|
|
55
|
+
const selected = clients.find(client => client.clientId === (entry?.clientId || selector));
|
|
56
|
+
if (selected) return selected.clientId;
|
|
57
|
+
throw new QzError(entry ? "client_offline" : "client_not_found", `Browser ${selector} is ${entry ? "offline" : "not registered"}.`,
|
|
58
|
+
{ hint: "Run qz browsers to inspect names and UUIDs. Reconnect that profile from its extension settings." });
|
|
59
|
+
}
|
|
60
|
+
if (clients.length > 1) throw new QzError("selection_required", "Multiple browsers are connected. Select one with --client NAME|UUID.",
|
|
61
|
+
{ clients: clients.map(({ clientId, name }) => ({ clientId, name })), hint: clients.map(client => `${client.name || client.clientId}: ${client.clientId}`).join("\n") });
|
|
62
|
+
if (clients.length === 1) return clients[0].clientId;
|
|
63
|
+
if (allowEmpty) return "";
|
|
64
|
+
throw connectionRequired();
|
|
45
65
|
}
|
|
46
66
|
|
|
47
67
|
function connectionRequired() {
|
|
@@ -53,11 +73,18 @@ function recoveryCommand(command, sessionId, clientId, stream = false) {
|
|
|
53
73
|
if (clientId && sanitizeTerminalText(clientId) !== String(clientId)) clientId = "CLIENT_ID";
|
|
54
74
|
const client = clientId ? ` --client${String(clientId).startsWith("-") ? "=" : " "}${shellArgument(clientId)}` : "";
|
|
55
75
|
const options = `${client}${stream ? " --stream" : ""}`;
|
|
76
|
+
if (!sessionId) return `qz ${command}${options}`;
|
|
56
77
|
return String(sessionId).startsWith("-")
|
|
57
78
|
? `qz ${command}${options} ${optionTerminator()} ${shellArgument(sessionId)}`
|
|
58
79
|
: `qz ${command} ${shellArgument(sessionId)}${options}`;
|
|
59
80
|
}
|
|
60
81
|
|
|
82
|
+
function missingTaskDetails(sessionId, clientId) {
|
|
83
|
+
return { sessionId, ...(clientId ? { clientId } : {}),
|
|
84
|
+
hint: "Check the task ID and selected browser. Run qz browsers to inspect browser names and UUIDs. List tasks in the selected browser:",
|
|
85
|
+
command: recoveryCommand("tasks", "", clientId) };
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
export async function runCli(argv, dependencies = {}) {
|
|
62
89
|
return executeCommand(parseCommand(argv), dependencies);
|
|
63
90
|
}
|
|
@@ -66,7 +93,12 @@ async function executeCommand(parsed, dependencies) {
|
|
|
66
93
|
if (parsed.command === "version") return parsed.options.json
|
|
67
94
|
? { result: { name: PACKAGE_NAME, version: PACKAGE_VERSION } }
|
|
68
95
|
: { text: `qz ${PACKAGE_VERSION}\n` };
|
|
69
|
-
return withToolClient(callTool =>
|
|
96
|
+
return withToolClient(async callTool => {
|
|
97
|
+
const returned = await dispatchCommand(parsed, { ...dependencies, callTool: dependencies.callTool || callTool });
|
|
98
|
+
const clientId = parsed.presentation?.clientId;
|
|
99
|
+
return clientId && returned.result && typeof returned.result === "object" && !Array.isArray(returned.result)
|
|
100
|
+
? { ...returned, result: { ...returned.result, clientId } } : returned;
|
|
101
|
+
});
|
|
70
102
|
}
|
|
71
103
|
|
|
72
104
|
async function dispatchCommand(parsed, dependencies) {
|
|
@@ -79,11 +111,24 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
79
111
|
if (command === "install") return { result: await open(INSTALL_URL) };
|
|
80
112
|
if (command === "stop") return { result: await (dependencies.stopDaemon || stopDaemon)() };
|
|
81
113
|
if (command === "connect") {
|
|
82
|
-
const running = await (dependencies.ensureDaemon || ensureDaemon)();
|
|
83
|
-
|
|
114
|
+
const running = await (dependencies.ensureDaemon || ensureDaemon)(options.port === undefined ? {} : { preferredPort: options.port });
|
|
115
|
+
requirePairingSupport(running.health);
|
|
116
|
+
if (options.port !== undefined && Number(new URL(running.endpoint).port) !== options.port) throw new QzError("port_mismatch",
|
|
117
|
+
`The local service is already running at ${running.endpoint}.`, { endpoint: running.endpoint,
|
|
118
|
+
hint: "Use its current address, or run qz stop before connecting on a different port." });
|
|
119
|
+
const bindingUrl = new URL("/qz-connect", running.endpoint);
|
|
120
|
+
if (options.name !== undefined) bindingUrl.searchParams.set("name", options.name);
|
|
121
|
+
const connectUrl = bindingUrl.href;
|
|
122
|
+
if (options["no-open"] || options.name !== undefined) {
|
|
123
|
+
dependencies.signal?.throwIfAborted();
|
|
124
|
+
const opened = options["no-open"] ? false : Boolean((await open(connectUrl))?.opened);
|
|
125
|
+
return { result: { endpoint: running.endpoint, url: connectUrl, mcpUrl: `${running.endpoint}/mcp`, opened } };
|
|
126
|
+
}
|
|
84
127
|
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
128
|
+
let selected = selectClient(running.health, options["client-id"], { allowEmpty: true });
|
|
85
129
|
const probe = async health => {
|
|
86
|
-
const
|
|
130
|
+
const chosen = selected || selectClient(health, "", { allowEmpty: true });
|
|
131
|
+
const clients = (health?.status?.clients || []).filter(client => client.clientId === chosen);
|
|
87
132
|
for (const client of clients) {
|
|
88
133
|
dependencies.signal?.throwIfAborted();
|
|
89
134
|
const timeoutMs = Math.min(2000, deadline - Date.now());
|
|
@@ -94,17 +139,19 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
94
139
|
await dependencies.callTool(running.endpoint, "qingzhou_call", {
|
|
95
140
|
method: "ping", clientId: client.clientId, timeoutSeconds: timeoutMs / 1000,
|
|
96
141
|
}, { timeoutMs, signal: dependencies.signal ? AbortSignal.any([dependencies.signal, timeout]) : timeout });
|
|
142
|
+
selected = client.clientId;
|
|
97
143
|
return true;
|
|
98
144
|
} catch (error) {
|
|
99
145
|
dependencies.signal?.throwIfAborted();
|
|
100
146
|
if (error.code === "connection_error" || (!timeout.aborted
|
|
101
|
-
&& !["BRIDGE_NOT_CONNECTED", "BRIDGE_REQUEST_TIMEOUT", -32001].includes(error.code))) throw error;
|
|
147
|
+
&& !["BRIDGE_NOT_CONNECTED", "BRIDGE_CLIENT_OFFLINE", "BRIDGE_CLIENT_DISCONNECTED", "BRIDGE_REQUEST_TIMEOUT", -32001].includes(error.code))) throw error;
|
|
102
148
|
}
|
|
103
149
|
}
|
|
104
150
|
return false;
|
|
105
151
|
};
|
|
106
|
-
const result = health => ({ result: { connected: true, mcpUrl: `${running.endpoint}/mcp`, clients: health.status.clients } });
|
|
152
|
+
const result = health => ({ result: { connected: true, mcpUrl: `${running.endpoint}/mcp`, clients: health.status.clients, clientId: selected } });
|
|
107
153
|
if (await probe(running.health)) return result(running.health);
|
|
154
|
+
if (options["client-id"]) throw new QzError("client_offline", "The selected browser is not responding. Reconnect it from its extension settings.");
|
|
108
155
|
dependencies.signal?.throwIfAborted();
|
|
109
156
|
await open(connectUrl);
|
|
110
157
|
while (Date.now() < deadline) {
|
|
@@ -114,7 +161,7 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
114
161
|
if (health && await probe(health)) return result(health);
|
|
115
162
|
await delay(200, undefined, { signal: dependencies.signal });
|
|
116
163
|
}
|
|
117
|
-
throw new QzError("connection_pending", "qz is waiting for Chrome. Open the connection URL in the Chrome profile with qz installed and
|
|
164
|
+
throw new QzError("connection_pending", "qz is waiting for Chrome. Open the connection URL in the Chrome profile with qz installed and confirm the connection in qz. Then run qz connect again.", { url: connectUrl });
|
|
118
165
|
}
|
|
119
166
|
// Validate JSON before connecting; malformed arguments cannot cause browser actions.
|
|
120
167
|
let args = {};
|
|
@@ -134,14 +181,21 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
134
181
|
dependencies.signal?.throwIfAborted();
|
|
135
182
|
const resource = resourceRequest(parsed, config);
|
|
136
183
|
const running = await find();
|
|
137
|
-
if (command === "status") return { result: running ? { running: true, endpoint: running.endpoint, updateAvailable: running.updateAvailable === true, ...running.health.status } : { running: false, connected: false } };
|
|
184
|
+
if (command === "status") return { result: running ? { running: true, endpoint: running.endpoint, updateAvailable: running.updateAvailable === true, ...(running.health.features?.browserPairing === 1 ? {} : { updateRequired: true, hint: UPGRADE_HINT }), ...running.health.status } : { running: false, connected: false } };
|
|
138
185
|
if (!running) throw connectionRequired();
|
|
139
186
|
if (command === "mcp") return options.json ? { result: { url: `${running.endpoint}/mcp` } } : { text: `${running.endpoint}/mcp\n` };
|
|
140
187
|
const call = dependencies.callTool;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
188
|
+
requirePairingSupport(running.health);
|
|
189
|
+
if (command === "browsers") {
|
|
190
|
+
if (action === "list") return { result: running.health.status };
|
|
191
|
+
const browser = running.health.status?.browsers?.find(browser => browser.clientId === id || browser.name === id);
|
|
192
|
+
if (!browser) throw new QzError("client_not_found", `No registered browser named ${id}. Run qz browsers to inspect names and UUIDs.`);
|
|
193
|
+
return { result: await call(running.endpoint, "qingzhou_forget_browser", { clientId: browser.clientId }, { timeoutMs: timeoutSeconds * 1000, signal: dependencies.signal }) };
|
|
194
|
+
}
|
|
195
|
+
// Resolve names once. Every subsequent operation carries this profile UUID.
|
|
196
|
+
const clientId = selectClient(running.health, options["client-id"]);
|
|
197
|
+
parsed.presentation = { ...parsed.presentation, clientId };
|
|
198
|
+
options["client-id"] = clientId;
|
|
145
199
|
const common = { timeoutSeconds, ...(clientId ? { clientId } : {}) };
|
|
146
200
|
const requestOptions = { timeoutMs: timeoutSeconds * 1000, signal: dependencies.signal };
|
|
147
201
|
const startedAt = Date.now();
|
|
@@ -172,6 +226,7 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
172
226
|
});
|
|
173
227
|
} catch (error) {
|
|
174
228
|
const code = error.name === "TimeoutError" ? "timeout" : error.name === "AbortError" ? "interrupted" : error.code || "task_wait_failed";
|
|
229
|
+
if (code === "task_deleted") throw new QzError(code, error.message, { ...error.details, ...missingTaskDetails(sessionId, clientId) });
|
|
175
230
|
const changed = code === "task_changed";
|
|
176
231
|
throw new QzError(code, code === "timeout"
|
|
177
232
|
? "Stopped watching after the configured --timeout. The browser task may still be running." : error.message,
|
|
@@ -236,6 +291,9 @@ async function dispatchCommand(parsed, dependencies) {
|
|
|
236
291
|
...(action === "list" && options.limit !== undefined ? { limit: Number(options.limit) } : {}) };
|
|
237
292
|
}
|
|
238
293
|
let result = await call(running.endpoint, name, parameters, requestOptions);
|
|
294
|
+
if (command === "tasks" && action === "get" && result?.session === null) {
|
|
295
|
+
throw new QzError("task_not_found", TASK_NOT_FOUND_MESSAGE, missingTaskDetails(id, clientId));
|
|
296
|
+
}
|
|
239
297
|
if (command === "tasks" && action === "create" && options.wait) {
|
|
240
298
|
const sessionId = result?.session?.id || result?.sessionId;
|
|
241
299
|
if (!sessionId) throw new QzError("invalid_response", "The task was created without an ID. Run qz tasks to find it before retrying.");
|
|
@@ -291,7 +349,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
291
349
|
});
|
|
292
350
|
live?.finish();
|
|
293
351
|
stdout.write(result.text ?? (mode.json ? `${JSON.stringify({ ok: true, result: result.result })}\n`
|
|
294
|
-
: renderResult({ ...parsed, presentation: { streamed: live?.streamed } }, result.result, mode)));
|
|
352
|
+
: renderResult({ ...parsed, presentation: { ...parsed.presentation, streamed: live?.streamed } }, result.result, mode)));
|
|
295
353
|
const status = result.result?.snapshot?.status || result.result?.snapshot?.runtime?.status;
|
|
296
354
|
if (result.result?.accepted === false && !result.result?.requiresUserAction) return 1;
|
|
297
355
|
return ["error", "failed", "aborted", "cancelled", "canceled", "interrupted"].includes(status) ? 1 : 0;
|
|
@@ -304,10 +362,30 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
304
362
|
}
|
|
305
363
|
}
|
|
306
364
|
|
|
365
|
+
/** Keep handlers installed while npm forwards duplicate signals and MCP closes. */
|
|
366
|
+
export async function runMain(argv = process.argv.slice(2), dependencies = {}) {
|
|
367
|
+
const controller = new AbortController();
|
|
368
|
+
let interruptedExitCode;
|
|
369
|
+
const interrupt = code => {
|
|
370
|
+
if (controller.signal.aborted) return;
|
|
371
|
+
interruptedExitCode = code;
|
|
372
|
+
controller.abort();
|
|
373
|
+
};
|
|
374
|
+
const onSigint = () => interrupt(130);
|
|
375
|
+
const onSigterm = () => interrupt(143);
|
|
376
|
+
process.on("SIGINT", onSigint);
|
|
377
|
+
process.on("SIGTERM", onSigterm);
|
|
378
|
+
try {
|
|
379
|
+
const code = await main(argv, { ...dependencies, signal: controller.signal });
|
|
380
|
+
return interruptedExitCode ?? code;
|
|
381
|
+
} finally {
|
|
382
|
+
process.removeListener("SIGINT", onSigint);
|
|
383
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
307
387
|
let isEntry = false;
|
|
308
388
|
try { isEntry = realpathSync(process.argv[1]) === fileURLToPath(import.meta.url); } catch {}
|
|
309
389
|
if (isEntry) {
|
|
310
|
-
|
|
311
|
-
process.once("SIGINT", () => controller.abort());
|
|
312
|
-
process.exitCode = await main(process.argv.slice(2), { signal: controller.signal });
|
|
390
|
+
process.exitCode = await runMain();
|
|
313
391
|
}
|
package/src/client.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { request } from "node:http";
|
|
2
|
-
import { Readable } from "node:stream";
|
|
3
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
4
|
import { PACKAGE_VERSION } from "./package-info.mjs";
|
|
6
5
|
|
|
7
6
|
export const GUIDE_URL = "https://github.com/alibaba/qz-browser-agent/blob/main/services/mcp-bridge/README.md";
|
|
8
|
-
export
|
|
7
|
+
export { INSTALL_URL } from "./package-info.mjs";
|
|
9
8
|
export const SERVICE_NAME = "qz-browser-agent";
|
|
10
9
|
export const PROTOCOL_VERSION = 1;
|
|
11
10
|
|
|
@@ -27,6 +26,19 @@ export function localUrl(value) {
|
|
|
27
26
|
return url;
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
function responseBody(response) {
|
|
30
|
+
const iterator = response[Symbol.asyncIterator]();
|
|
31
|
+
return {
|
|
32
|
+
[Symbol.asyncIterator]() { return this; },
|
|
33
|
+
next: () => iterator.next(),
|
|
34
|
+
return() {
|
|
35
|
+
// An iterator return waits behind a pending read. Destroy the HTTP stream first.
|
|
36
|
+
response.destroy();
|
|
37
|
+
return iterator.return();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
// node:http does not inherit proxy environment settings. Every MCP request stays on loopback.
|
|
31
43
|
export async function localFetch(input, init) {
|
|
32
44
|
const prepared = new Request(input, init);
|
|
@@ -42,7 +54,7 @@ export async function localFetch(input, init) {
|
|
|
42
54
|
return;
|
|
43
55
|
}
|
|
44
56
|
try {
|
|
45
|
-
resolve(new Response([204, 304].includes(res.statusCode) ? null :
|
|
57
|
+
resolve(new Response([204, 304].includes(res.statusCode) ? null : responseBody(res), {
|
|
46
58
|
status: res.statusCode, headers: new Headers(Object.entries(res.headers)
|
|
47
59
|
.flatMap(([key, value]) => Array.isArray(value) ? value.map(item => [key, item]) : value === undefined ? [] : [[key, value]])),
|
|
48
60
|
}));
|
package/src/commands.mjs
CHANGED
|
@@ -8,8 +8,8 @@ const route = (tool, args = () => ({}), options = []) => ({ tool, args, options
|
|
|
8
8
|
// CLI syntax, documentation, and direct MCP routes have one declaration per command.
|
|
9
9
|
// Task execution and form responses retain their explicit handlers in cli.mjs.
|
|
10
10
|
export const COMMANDS = {
|
|
11
|
-
connect: { options: request, summary: "Connect the browser",
|
|
12
|
-
help: ["qz connect [--timeout SECONDS]", "
|
|
11
|
+
connect: { options: [...request, "port", "no-open", "name"], summary: "Connect the browser",
|
|
12
|
+
help: ["qz connect [--client NAME|UUID] [--port PORT] [--timeout SECONDS]", "Reuse a connected browser or open the connection page for first pairing.", "qz connect --no-open [--name NAME] --json", "Return a binding URL immediately. Open it in the target browser profile.", "--name assigns or changes that profile's name. Without --no-open, its binding page opens immediately.", "Use --port for a custom local service port. An existing service remains running.", "qz browsers"],
|
|
13
13
|
},
|
|
14
14
|
create: { options: [...waiting, "title", "parent"], summary: "Start a task",
|
|
15
15
|
help: ['qz create "PROMPT" [--title TITLE] [--wait | --stream]', "Start a browser task. The title is optional.",
|
|
@@ -101,17 +101,20 @@ export const COMMANDS = {
|
|
|
101
101
|
run: route("qingzhou_run_automation", ({ id }) => ({ automationId: id })),
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
|
+
browsers: { options: [], summary: "List or forget browser registrations",
|
|
105
|
+
help: ["qz browsers [list]", "List registered browser names, UUIDs, and connection status.", "qz browsers forget NAME|UUID", "Forget an offline registration to reuse its name. Disconnect online browsers in their extension settings first."],
|
|
106
|
+
},
|
|
104
107
|
status: { options: [], summary: "Check the connection",
|
|
105
108
|
help: ["qz status", "Show the local service and browser connection status."],
|
|
106
109
|
},
|
|
107
110
|
mcp: { options: [], summary: "Print the MCP URL",
|
|
108
111
|
help: ["qz mcp [--json]", "Print the running service's MCP URL for an external agent.", "Run qz connect first."],
|
|
109
112
|
},
|
|
110
|
-
install: { options: [], summary: "
|
|
111
|
-
help: ["qz install", "Open
|
|
113
|
+
install: { options: [], summary: "Install the Chrome extension",
|
|
114
|
+
help: ["qz install", "Open qz in the Chrome Web Store.", "Click Add to Chrome, then run qz connect."],
|
|
112
115
|
},
|
|
113
116
|
stop: { options: [], summary: "Stop the local service",
|
|
114
|
-
help: ["qz stop", "Stop the local service and disconnect
|
|
117
|
+
help: ["qz stop", "Stop the local service and disconnect all browsers.", "Use qz cancel TASK_ID to stop a task.", "qz connect"],
|
|
115
118
|
},
|
|
116
119
|
help: { options: [] },
|
|
117
120
|
};
|
package/src/daemon.mjs
CHANGED
|
@@ -83,6 +83,7 @@ async function withLock(paths, operation, { timeoutMs = 15000 } = {}) {
|
|
|
83
83
|
await mkdir(candidate, { mode: 0o700 });
|
|
84
84
|
const deadline = Date.now() + timeoutMs;
|
|
85
85
|
let acquired = false;
|
|
86
|
+
let missingTargetPermissionError;
|
|
86
87
|
try {
|
|
87
88
|
await writeFile(join(candidate, ownerFile), JSON.stringify(owner), { mode: 0o600 });
|
|
88
89
|
while (true) {
|
|
@@ -92,12 +93,25 @@ async function withLock(paths, operation, { timeoutMs = 15000 } = {}) {
|
|
|
92
93
|
acquired = true;
|
|
93
94
|
break;
|
|
94
95
|
} catch (error) {
|
|
95
|
-
const existing = await stat(paths.lock).catch(
|
|
96
|
+
const existing = await stat(paths.lock).catch(error => {
|
|
97
|
+
if (error.code === "ENOENT") return null;
|
|
98
|
+
throw error;
|
|
99
|
+
});
|
|
96
100
|
if (!existing) {
|
|
101
|
+
if (process.platform === "win32" && error.code === "EPERM") {
|
|
102
|
+
// Windows reports directory collisions as EPERM. The owner may have
|
|
103
|
+
// released the lock before stat; try the same atomic publication once.
|
|
104
|
+
// A persistent denial remains EPERM rather than becoming a busy lock.
|
|
105
|
+
if (missingTargetPermissionError || Date.now() >= deadline) throw missingTargetPermissionError || error;
|
|
106
|
+
missingTargetPermissionError = error;
|
|
107
|
+
await delay(50);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
97
110
|
if (!["EEXIST", "ENOTEMPTY", "ENOTDIR"].includes(error.code)) throw error;
|
|
98
111
|
if (Date.now() >= deadline) throw new QzError("connection_busy", "Another qz command is starting the local service. Try qz status shortly.");
|
|
99
112
|
continue;
|
|
100
113
|
}
|
|
114
|
+
missingTargetPermissionError = undefined;
|
|
101
115
|
if (existing.isDirectory()) {
|
|
102
116
|
for (const name of await readdir(paths.lock).catch(() => [])) {
|
|
103
117
|
if (!/^\d+-[\da-f-]+\.json$/.test(name)) continue;
|
|
@@ -236,11 +250,12 @@ export async function stopDaemon(options = {}) {
|
|
|
236
250
|
|
|
237
251
|
export async function startManagedBridge({ stateFile, scopeId, buildId = "", instanceId = randomUUID(), port = 18765, startBridge } = {}) {
|
|
238
252
|
const start = startBridge || (await import("./server.mjs")).startBridge;
|
|
253
|
+
const registryPath = stateFile.replace(/\.json$/, "") + ".browsers.json";
|
|
239
254
|
let running;
|
|
240
|
-
try { running = await start({ port, scopeId, instanceId, buildId }); }
|
|
255
|
+
try { running = await start({ port, scopeId, instanceId, buildId, registryPath }); }
|
|
241
256
|
catch (error) {
|
|
242
257
|
if (error.code !== "EADDRINUSE" || !port) throw error;
|
|
243
|
-
running = await start({ port: 0, scopeId, instanceId, buildId });
|
|
258
|
+
running = await start({ port: 0, scopeId, instanceId, buildId, registryPath });
|
|
244
259
|
}
|
|
245
260
|
const record = { service: SERVICE_NAME, protocolVersion: PROTOCOL_VERSION, endpoint: running.endpoint,
|
|
246
261
|
processId: process.pid, scopeId, instanceId, buildId };
|
package/src/package-info.mjs
CHANGED
|
@@ -4,3 +4,4 @@ const metadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.
|
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_NAME = metadata.name;
|
|
6
6
|
export const PACKAGE_VERSION = metadata.version;
|
|
7
|
+
export const INSTALL_URL = "https://chromewebstore.google.com/detail/dknbdgkgoinlgjpfebapkklbmjjgdblm";
|