@rynx-ai/daemon 0.1.0 → 0.1.10-beta.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/dist/app-browser-host-supervisor.d.ts +97 -0
- package/dist/app-browser-host-supervisor.js +529 -0
- package/dist/browser-artifact-management.d.ts +20 -0
- package/dist/browser-artifact-management.js +61 -0
- package/dist/browser-cli-args.d.ts +20 -0
- package/dist/browser-cli-args.js +100 -0
- package/dist/browser-runner-session-context.d.ts +24 -0
- package/dist/browser-runner-session-context.js +113 -0
- package/dist/cdp-keyboard-input.d.ts +6 -0
- package/dist/cdp-keyboard-input.js +209 -0
- package/dist/channel-store.d.ts +0 -6
- package/dist/channel-store.js +2 -18
- package/dist/chrome-for-testing-release-resolver.d.ts +34 -0
- package/dist/chrome-for-testing-release-resolver.js +214 -0
- package/dist/chrome-for-testing-store.d.ts +85 -0
- package/dist/chrome-for-testing-store.js +836 -0
- package/dist/chrome-inspection-gateway.d.ts +58 -0
- package/dist/chrome-inspection-gateway.js +806 -0
- package/dist/chrome-inspection-manager.d.ts +94 -0
- package/dist/chrome-inspection-manager.js +573 -0
- package/dist/cli.js +478 -30
- package/dist/control-client.d.ts +122 -0
- package/dist/control-client.js +855 -0
- package/dist/control-deps.js +37 -6
- package/dist/control-endpoint.d.ts +14 -0
- package/dist/control-endpoint.js +124 -0
- package/dist/control-link-store.d.ts +16 -0
- package/dist/control-link-store.js +53 -0
- package/dist/daemon-build-status.d.ts +8 -0
- package/dist/daemon-build-status.js +18 -0
- package/dist/daemon-owner.d.ts +14 -0
- package/dist/daemon-owner.js +104 -0
- package/dist/daemon-server.d.ts +29 -2
- package/dist/daemon-server.js +1113 -26
- package/dist/db.js +196 -10
- package/dist/desktop-browser-host-client.d.ts +35 -0
- package/dist/desktop-browser-host-client.js +415 -0
- package/dist/direct-runtime-authenticator.d.ts +13 -0
- package/dist/direct-runtime-authenticator.js +132 -0
- package/dist/direct-runtime-config.d.ts +20 -0
- package/dist/direct-runtime-config.js +101 -0
- package/dist/embedded-browser-profile-cleanup.d.ts +58 -0
- package/dist/embedded-browser-profile-cleanup.js +338 -0
- package/dist/headless-browser-host.d.ts +14 -0
- package/dist/headless-browser-host.js +2173 -0
- package/dist/index-daemon.js +55 -5
- package/dist/installation-id.d.ts +33 -0
- package/dist/installation-id.js +692 -0
- package/dist/maintenance-management.d.ts +11 -0
- package/dist/maintenance-management.js +13 -0
- package/dist/migrations/cleanup-legacy-sessions.d.ts +0 -3
- package/dist/migrations/cleanup-legacy-sessions.js +10 -28
- package/dist/plugin-cli.d.ts +30 -0
- package/dist/plugin-cli.js +270 -0
- package/dist/plugin-host-rpc.d.ts +85 -0
- package/dist/plugin-host-rpc.js +878 -0
- package/dist/plugin-install-restart.d.ts +13 -0
- package/dist/plugin-install-restart.js +24 -0
- package/dist/plugin-installer.d.ts +71 -15
- package/dist/plugin-installer.js +521 -155
- package/dist/plugin-management-service.d.ts +58 -0
- package/dist/plugin-management-service.js +240 -0
- package/dist/plugin-package.d.ts +61 -0
- package/dist/plugin-package.js +732 -0
- package/dist/plugin-runtime-control.d.ts +2 -0
- package/dist/plugin-runtime-control.js +37 -0
- package/dist/plugin-store.d.ts +37 -8
- package/dist/plugin-store.js +80 -15
- package/dist/plugin-supervisor.d.ts +160 -0
- package/dist/plugin-supervisor.js +1143 -0
- package/dist/pm2.d.ts +3 -1
- package/dist/pm2.js +92 -18
- package/dist/provider-cli-service.d.ts +46 -0
- package/dist/provider-cli-service.js +510 -0
- package/dist/registry.d.ts +6 -19
- package/dist/registry.js +22 -87
- package/dist/remote-runtime-access-store.d.ts +62 -0
- package/dist/remote-runtime-access-store.js +393 -0
- package/dist/remote-runtime-admin.d.ts +35 -0
- package/dist/remote-runtime-admin.js +110 -0
- package/dist/remote-runtime-connection-manager.d.ts +103 -0
- package/dist/remote-runtime-connection-manager.js +1575 -0
- package/dist/remote-runtime-credential-store.d.ts +45 -0
- package/dist/remote-runtime-credential-store.js +639 -0
- package/dist/remote-runtime-target-control.d.ts +51 -0
- package/dist/remote-runtime-target-control.js +488 -0
- package/dist/remote-runtime-target-store.d.ts +56 -0
- package/dist/remote-runtime-target-store.js +326 -0
- package/dist/runtime-share-addresses.d.ts +21 -0
- package/dist/runtime-share-addresses.js +50 -0
- package/dist/session-browser-capability-store.d.ts +13 -0
- package/dist/session-browser-capability-store.js +58 -0
- package/dist/session-emulator-binding-store.d.ts +10 -0
- package/dist/session-emulator-binding-store.js +61 -0
- package/dist/session-launch-operations.d.ts +41 -0
- package/dist/session-launch-operations.js +124 -0
- package/dist/session-log-store.js +29 -0
- package/dist/session-meta-store.d.ts +3 -2
- package/dist/session-meta-store.js +59 -7
- package/dist/session-pending-message-store.d.ts +2 -0
- package/dist/session-pending-message-store.js +41 -0
- package/dist/session-resource-store.d.ts +32 -0
- package/dist/session-resource-store.js +700 -0
- package/dist/setup.d.ts +16 -0
- package/dist/setup.js +144 -5
- package/dist/skills-catalog.js +8 -4
- package/dist/update.d.ts +11 -2
- package/dist/update.js +0 -27
- package/package.json +23 -10
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* `rynx` — the daemon CLI.
|
|
4
4
|
*
|
|
5
5
|
* rynx start | stop | restart | status | logs process lifecycle (pm2)
|
|
6
|
-
* rynx plugin
|
|
6
|
+
* rynx plugin manage install|list|uninstall|enable|disable ...
|
|
7
|
+
* rynx plugin <plugin-id> <plugin-command> ...
|
|
7
8
|
* rynx agent list | show <name> | add <name> | rm <name>
|
|
8
9
|
*
|
|
9
10
|
* The CLI owns process lifecycle, plugin install/registry, and the file-based
|
|
@@ -19,14 +20,22 @@ import { createRequire } from "node:module";
|
|
|
19
20
|
import path from "node:path";
|
|
20
21
|
import { confirm, isCancel } from "@clack/prompts";
|
|
21
22
|
import { listAgentSpecs, loadAgentSpec, loadConfig, rynxAgentsDir } from "@rynx-ai/core";
|
|
23
|
+
import { encodePairingCode } from "@rynx-ai/protocol/direct-runtime";
|
|
22
24
|
import { agentSpecPath, removeAgentSpec, scaffoldAgentSpec } from "./agent-file.js";
|
|
25
|
+
import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "./browser-cli-args.js";
|
|
26
|
+
import { resolveChromeForTestingRelease } from "./chrome-for-testing-release-resolver.js";
|
|
27
|
+
import { createChromeForTestingArtifactStore } from "./chrome-for-testing-store.js";
|
|
23
28
|
import { dbPath } from "./db.js";
|
|
24
29
|
import { cleanupLegacySessions } from "./migrations/cleanup-legacy-sessions.js";
|
|
30
|
+
import { PluginCliUsageError, runPluginCliCommand } from "./plugin-cli.js";
|
|
25
31
|
import { installPlugin, uninstallPlugin } from "./plugin-installer.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
32
|
+
import { restartAfterPluginInstall } from "./plugin-install-restart.js";
|
|
33
|
+
import { getPlugin, isRunnablePluginRecord, listPlugins, setPluginEnabled } from "./plugin-store.js";
|
|
34
|
+
import { ensureResidentPluginRuntime, reloadResidentPluginRuntime, } from "./plugin-runtime-control.js";
|
|
35
|
+
import { isPm2DaemonOnline, startDaemon, statusDaemon, stopDaemon, streamLogs } from "./pm2.js";
|
|
28
36
|
import { runDoctor, runSetup } from "./setup.js";
|
|
29
37
|
import { runUpdate } from "./update.js";
|
|
38
|
+
import { createResidentRemoteRuntimePairingOffer, callManagedRuntimeBrowser, callResidentRuntime, forgetResidentRuntimeTarget, getResidentRuntimeLocalBrowserEndpoint, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, revokeResidentRemoteRuntimeClient, readOptionalManagedRuntimeBrowserCredential, testResidentRuntimeTarget, } from "./control-client.js";
|
|
30
39
|
const USAGE = `Usage: rynx <command>
|
|
31
40
|
|
|
32
41
|
Setup:
|
|
@@ -41,15 +50,15 @@ Lifecycle:
|
|
|
41
50
|
logs tail the server logs
|
|
42
51
|
update [version] upgrade @rynx-ai/daemon (npm) + restart (--check: report only)
|
|
43
52
|
|
|
44
|
-
Plugins (
|
|
45
|
-
plugin
|
|
46
|
-
plugin
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
plugin
|
|
50
|
-
|
|
51
|
-
plugin
|
|
52
|
-
(
|
|
53
|
+
Plugins (static manifest + isolated runner):
|
|
54
|
+
plugin manage list
|
|
55
|
+
plugin manage install <npm|path|git|url> [...] [--expect-digest <sha256-...>]
|
|
56
|
+
plugin manage update <id> [...] [--expect-digest <sha256-...>]
|
|
57
|
+
plugin manage uninstall <id>
|
|
58
|
+
plugin manage enable|disable <id>
|
|
59
|
+
manage arbitrary installed plugins
|
|
60
|
+
plugin <id> <command> run a command declared by that plugin
|
|
61
|
+
(\`manage\` is a reserved plugin id)
|
|
53
62
|
|
|
54
63
|
Agents (~/.rynx/agents/<id>/agent.json — dir name is the id):
|
|
55
64
|
agent list list agents (id · runtime · model · [display name])
|
|
@@ -67,7 +76,44 @@ Maintenance:
|
|
|
67
76
|
|
|
68
77
|
Emulator:
|
|
69
78
|
emulator <args…> passthrough to the bundled \`rynx-emulator\` CLI
|
|
70
|
-
(
|
|
79
|
+
(use --runtime <local|daemon-id> for bounded unary
|
|
80
|
+
remote control; run \`rynx emulator\` for its help)
|
|
81
|
+
|
|
82
|
+
Remote Runtime:
|
|
83
|
+
runtime share --address <host|ws-url> [--label <label>] [--json]
|
|
84
|
+
on the remote host, print one revocable pairing link
|
|
85
|
+
runtime pair [...] compatibility alias for runtime share
|
|
86
|
+
runtime add --pairing-code <rynx://...> [--name <name>]
|
|
87
|
+
pair, verify and save a remote daemon
|
|
88
|
+
(stdin remains supported for scripts)
|
|
89
|
+
runtime list [--json] list the synthetic local target and paired daemons
|
|
90
|
+
runtime test <local|daemon-id> [--json]
|
|
91
|
+
authenticate and read the selected daemon status
|
|
92
|
+
runtime forget <daemon-id>
|
|
93
|
+
remove one local target and its client credential
|
|
94
|
+
runtime clients list [--json]
|
|
95
|
+
runtime clients revoke <grant-id>
|
|
96
|
+
inspect/revoke clients authorized by this daemon
|
|
97
|
+
|
|
98
|
+
Browser (one Browser per Runtime Session):
|
|
99
|
+
browser install [--channel <stable|beta|dev|canary> | --version <x.x.x.x>] [--json]
|
|
100
|
+
install and activate managed Chrome for Testing
|
|
101
|
+
browser update [--channel <stable|beta|dev|canary>] [--json]
|
|
102
|
+
resolve and activate the current channel build
|
|
103
|
+
browser version [--json]
|
|
104
|
+
show the active managed Browser engine
|
|
105
|
+
browser clean [--json] remove inactive managed builds while retaining active,
|
|
106
|
+
rollback, and live Headless Browser builds
|
|
107
|
+
browser open [url] [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
108
|
+
open the selected Runtime Session's managed Browser
|
|
109
|
+
browser status [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
110
|
+
show Browser generation, state and active Page
|
|
111
|
+
browser pages [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
112
|
+
list the Browser's opaque Pages
|
|
113
|
+
browser endpoint [--ensure] [--session <local-id>] [--json]
|
|
114
|
+
print Runtime-local CDP; --ensure opens it when absent
|
|
115
|
+
browser close [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
116
|
+
close the current Browser generation
|
|
71
117
|
|
|
72
118
|
Channel instances (a channel + an agent) are configured in the web control
|
|
73
119
|
console (run \`rynx start\`, then open the printed URL).
|
|
@@ -76,9 +122,8 @@ function fail(message) {
|
|
|
76
122
|
console.error(message);
|
|
77
123
|
process.exit(2);
|
|
78
124
|
}
|
|
79
|
-
async function
|
|
125
|
+
async function pluginManageCommand(sub, arg) {
|
|
80
126
|
switch (sub) {
|
|
81
|
-
case "ls":
|
|
82
127
|
case "list": {
|
|
83
128
|
const plugins = listPlugins();
|
|
84
129
|
console.log(`Plugins (db: ${dbPath()})`);
|
|
@@ -87,24 +132,32 @@ async function pluginCommand(sub, arg) {
|
|
|
87
132
|
return;
|
|
88
133
|
}
|
|
89
134
|
for (const p of plugins) {
|
|
90
|
-
const state = p
|
|
135
|
+
const state = !isRunnablePluginRecord(p)
|
|
136
|
+
? "reinstall-required"
|
|
137
|
+
: p.enabled
|
|
138
|
+
? "enabled"
|
|
139
|
+
: "disabled";
|
|
91
140
|
console.log(` ${p.name.padEnd(16)} ${(p.version ?? "-").padEnd(10)} ${p.source.padEnd(6)} ${state}`);
|
|
92
141
|
}
|
|
93
142
|
return;
|
|
94
143
|
}
|
|
95
|
-
case "add":
|
|
96
144
|
case "install": {
|
|
97
145
|
if (!arg)
|
|
98
|
-
fail("plugin
|
|
146
|
+
fail("plugin manage install: missing npm/path/git/url source");
|
|
99
147
|
const force = process.argv.includes("--force");
|
|
148
|
+
const grantAll = process.argv.includes("--grant-all");
|
|
149
|
+
const allowScripts = process.argv.includes("--allow-scripts");
|
|
150
|
+
const expectedDigest = digestOption(process.argv);
|
|
100
151
|
const result = await installPlugin(arg, (line) => console.log(line), {
|
|
152
|
+
allowScripts,
|
|
153
|
+
...(expectedDigest ? { expectedDigest } : {}),
|
|
101
154
|
onConflict: async (existing) => {
|
|
102
155
|
if (force)
|
|
103
156
|
return true;
|
|
104
157
|
const desc = `${existing.source}${existing.version ? ` @${existing.version}` : ""}`;
|
|
105
158
|
if (!process.stdin.isTTY) {
|
|
106
159
|
fail(`plugin "${existing.name}" already installed (${desc}). ` +
|
|
107
|
-
`Re-run with --force, or \`rynx plugin
|
|
160
|
+
`Re-run with --force, or \`rynx plugin manage uninstall ${existing.name}\` first.`);
|
|
108
161
|
}
|
|
109
162
|
const yes = await confirm({
|
|
110
163
|
message: `插件 "${existing.name}" 已存在(${desc}),覆盖吗?`,
|
|
@@ -112,20 +165,54 @@ async function pluginCommand(sub, arg) {
|
|
|
112
165
|
});
|
|
113
166
|
return !isCancel(yes) && yes === true;
|
|
114
167
|
},
|
|
168
|
+
approveCapabilities: (manifest, existing) => approvePluginCapabilities(manifest, existing?.grants ?? [], grantAll),
|
|
115
169
|
});
|
|
116
170
|
if (result.skipped) {
|
|
117
171
|
console.log(`已取消,保留现有插件 "${result.name}"。`);
|
|
118
172
|
}
|
|
119
173
|
else {
|
|
120
|
-
console.log(`Plugin "${result.name}" (${result.source}${result.version ? ` @${result.version}` : ""}) installed
|
|
174
|
+
console.log(`Plugin "${result.name}" (${result.source}${result.version ? ` @${result.version}` : ""}) installed.`);
|
|
175
|
+
await restartAfterPluginInstall({
|
|
176
|
+
restart: process.argv.includes("--restart"),
|
|
177
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
178
|
+
}, {
|
|
179
|
+
isDaemonOnline: isPm2DaemonOnline,
|
|
180
|
+
confirmRestart: async () => {
|
|
181
|
+
const answer = await confirm({
|
|
182
|
+
message: "Restart Rynx now to apply the plugin?",
|
|
183
|
+
initialValue: true,
|
|
184
|
+
});
|
|
185
|
+
return !isCancel(answer) && answer === true;
|
|
186
|
+
},
|
|
187
|
+
restartDaemon: () => startDaemon({ force: true }),
|
|
188
|
+
log: (message) => console.log(message),
|
|
189
|
+
});
|
|
121
190
|
}
|
|
122
191
|
return;
|
|
123
192
|
}
|
|
124
|
-
case "
|
|
125
|
-
|
|
193
|
+
case "update": {
|
|
194
|
+
if (!arg)
|
|
195
|
+
fail("plugin manage update: missing plugin id");
|
|
196
|
+
const existing = getPlugin(arg);
|
|
197
|
+
if (!existing)
|
|
198
|
+
fail(`plugin "${arg}" not found`);
|
|
199
|
+
if (!isRunnablePluginRecord(existing)) {
|
|
200
|
+
fail(`plugin "${arg}" uses the removed legacy format; reinstall it from an explicit source`);
|
|
201
|
+
}
|
|
202
|
+
const expectedDigest = digestOption(process.argv);
|
|
203
|
+
const result = await installPlugin(existing.spec, (line) => console.log(line), {
|
|
204
|
+
expectedId: existing.name,
|
|
205
|
+
allowScripts: process.argv.includes("--allow-scripts"),
|
|
206
|
+
...(expectedDigest ? { expectedDigest } : {}),
|
|
207
|
+
onConflict: () => true,
|
|
208
|
+
approveCapabilities: (manifest, current) => approvePluginCapabilities(manifest, current?.grants ?? [], process.argv.includes("--grant-all")),
|
|
209
|
+
});
|
|
210
|
+
console.log(`Plugin "${result.name}" updated${result.version ? ` to ${result.version}` : ""}. Run \`rynx restart\` to apply.`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
126
213
|
case "uninstall": {
|
|
127
214
|
if (!arg)
|
|
128
|
-
fail("plugin
|
|
215
|
+
fail("plugin manage uninstall: missing plugin id");
|
|
129
216
|
if (!(await uninstallPlugin(arg, (line) => console.log(line))))
|
|
130
217
|
fail(`plugin "${arg}" not found`);
|
|
131
218
|
console.log(`Plugin "${arg}" uninstalled. Run \`rynx restart\` to apply.`);
|
|
@@ -134,9 +221,12 @@ async function pluginCommand(sub, arg) {
|
|
|
134
221
|
case "enable":
|
|
135
222
|
case "disable": {
|
|
136
223
|
if (!arg)
|
|
137
|
-
fail(`plugin ${sub}: missing plugin
|
|
138
|
-
|
|
224
|
+
fail(`plugin manage ${sub}: missing plugin id`);
|
|
225
|
+
const plugin = getPlugin(arg);
|
|
226
|
+
if (!plugin)
|
|
139
227
|
fail(`plugin "${arg}" not found`);
|
|
228
|
+
if (!isRunnablePluginRecord(plugin))
|
|
229
|
+
fail(`plugin "${arg}" must be reinstalled before it can be enabled`);
|
|
140
230
|
setPluginEnabled(arg, sub === "enable");
|
|
141
231
|
console.log(`Plugin "${arg}" ${sub}d. Run \`rynx restart\` to apply.`);
|
|
142
232
|
return;
|
|
@@ -145,6 +235,69 @@ async function pluginCommand(sub, arg) {
|
|
|
145
235
|
fail(USAGE);
|
|
146
236
|
}
|
|
147
237
|
}
|
|
238
|
+
async function pluginCommand(args) {
|
|
239
|
+
const [pluginId, command] = args;
|
|
240
|
+
if (pluginId === "manage") {
|
|
241
|
+
await pluginManageCommand(command, args[2]);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (!pluginId)
|
|
245
|
+
fail(USAGE);
|
|
246
|
+
try {
|
|
247
|
+
const result = await runPluginCliCommand(pluginId, args.slice(1));
|
|
248
|
+
if (result.code !== 0) {
|
|
249
|
+
process.exitCode = result.code;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const reload = result.effects.some((effect) => effect.effect === "runtime.reload");
|
|
253
|
+
const ensure = result.effects.some((effect) => effect.effect === "runtime.ensure");
|
|
254
|
+
if (reload || ensure) {
|
|
255
|
+
try {
|
|
256
|
+
if (reload)
|
|
257
|
+
await reloadResidentPluginRuntime(pluginId);
|
|
258
|
+
else
|
|
259
|
+
await ensureResidentPluginRuntime(pluginId);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
263
|
+
throw new Error(reload
|
|
264
|
+
? `Plugin "${pluginId}" configuration was saved, but its runtime did not become ready: ${detail}`
|
|
265
|
+
: `Plugin "${pluginId}" runtime did not become ready: ${detail}`);
|
|
266
|
+
}
|
|
267
|
+
if (!args.includes("--json"))
|
|
268
|
+
console.log(`Plugin "${pluginId}" runtime is ready.`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (error instanceof PluginCliUsageError) {
|
|
273
|
+
console.log(error.message);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function approvePluginCapabilities(manifest, existing, grantAll) {
|
|
280
|
+
const requested = manifest.requestedCapabilities;
|
|
281
|
+
const existingSet = new Set(existing);
|
|
282
|
+
const newCapabilities = requested.filter((capability) => !existingSet.has(capability));
|
|
283
|
+
if (newCapabilities.length === 0)
|
|
284
|
+
return requested.filter((capability) => existingSet.has(capability));
|
|
285
|
+
if (grantAll)
|
|
286
|
+
return requested;
|
|
287
|
+
console.log(`Plugin "${manifest.id}" requests host capabilities:`);
|
|
288
|
+
for (const capability of newCapabilities)
|
|
289
|
+
console.log(` - ${capability}`);
|
|
290
|
+
if (!process.stdin.isTTY) {
|
|
291
|
+
throw new Error("capability approval required; inspect the list and re-run with --grant-all");
|
|
292
|
+
}
|
|
293
|
+
const approved = await confirm({
|
|
294
|
+
message: `Grant these ${newCapabilities.length} capability/capabilities to "${manifest.id}"?`,
|
|
295
|
+
initialValue: false,
|
|
296
|
+
});
|
|
297
|
+
if (isCancel(approved) || approved !== true)
|
|
298
|
+
throw new Error("plugin installation cancelled");
|
|
299
|
+
return requested;
|
|
300
|
+
}
|
|
148
301
|
async function agentCommand(sub, name) {
|
|
149
302
|
switch (sub) {
|
|
150
303
|
case "list": {
|
|
@@ -219,6 +372,298 @@ function emulatorPassthrough(args) {
|
|
|
219
372
|
process.exit(1);
|
|
220
373
|
});
|
|
221
374
|
}
|
|
375
|
+
async function runtimeCommand(args) {
|
|
376
|
+
const [sub, arg] = args;
|
|
377
|
+
const json = args.includes("--json");
|
|
378
|
+
switch (sub) {
|
|
379
|
+
case "share":
|
|
380
|
+
case "pair": {
|
|
381
|
+
const label = optionValue(args, "--label");
|
|
382
|
+
const address = optionValue(args, "--address");
|
|
383
|
+
if (!address) {
|
|
384
|
+
fail("runtime share: pass --address with a host or ws(s) URL reachable by the other device");
|
|
385
|
+
}
|
|
386
|
+
const offer = await createResidentRemoteRuntimePairingOffer({
|
|
387
|
+
...(label ? { clientLabel: label } : {}),
|
|
388
|
+
address,
|
|
389
|
+
});
|
|
390
|
+
console.log(json ? JSON.stringify(offer, null, 2) : encodePairingCode(offer));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
case "add": {
|
|
394
|
+
const name = optionValue(args, "--name");
|
|
395
|
+
const code = optionValue(args, "--pairing-code");
|
|
396
|
+
if (code === undefined && process.stdin.isTTY) {
|
|
397
|
+
fail("runtime add: pass --pairing-code or pipe a pairing link on stdin");
|
|
398
|
+
}
|
|
399
|
+
const raw = code ?? await readStdinBounded(64 * 1024);
|
|
400
|
+
if (!raw.trim()) {
|
|
401
|
+
fail("runtime add: pass --pairing-code or pipe a pairing link on stdin");
|
|
402
|
+
}
|
|
403
|
+
let offer = raw.trim();
|
|
404
|
+
if (raw.trim().startsWith("{")) {
|
|
405
|
+
try {
|
|
406
|
+
offer = JSON.parse(raw);
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
fail("runtime add: stdin is not valid pairing JSON");
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const result = await pairResidentRuntimeTarget(offer, name);
|
|
413
|
+
if (json)
|
|
414
|
+
console.log(JSON.stringify(result, null, 2));
|
|
415
|
+
else {
|
|
416
|
+
console.log(`Paired ${result.target.displayName} (${result.target.daemonId}) via ${result.target.endpoint}.`);
|
|
417
|
+
console.log(`Remote instance: ${result.status.daemonInstanceId}`);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
case "list": {
|
|
422
|
+
const targets = await listResidentRuntimeTargets();
|
|
423
|
+
if (json) {
|
|
424
|
+
console.log(JSON.stringify({ runtimes: targets }, null, 2));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
console.log("Runtime targets");
|
|
428
|
+
for (const target of targets) {
|
|
429
|
+
const route = target.binding === "local" ? "local" : target.endpoint;
|
|
430
|
+
console.log(` ${target.selector.padEnd(38)} ${target.displayName} ${route}`);
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
case "test": {
|
|
435
|
+
if (!arg || arg.startsWith("--"))
|
|
436
|
+
fail("runtime test: missing local or daemon id");
|
|
437
|
+
const result = await testResidentRuntimeTarget(arg);
|
|
438
|
+
if (json)
|
|
439
|
+
console.log(JSON.stringify(result, null, 2));
|
|
440
|
+
else {
|
|
441
|
+
console.log(`${result.target.displayName}: connected to ${result.status.daemonId} ` +
|
|
442
|
+
`(instance ${result.status.daemonInstanceId})`);
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
case "forget": {
|
|
447
|
+
if (!arg || arg.startsWith("--"))
|
|
448
|
+
fail("runtime forget: missing daemon id");
|
|
449
|
+
await forgetResidentRuntimeTarget(arg);
|
|
450
|
+
console.log(`Forgot Runtime target ${arg}.`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
case "clients": {
|
|
454
|
+
const action = arg;
|
|
455
|
+
if (action === "list") {
|
|
456
|
+
const clients = await listResidentRemoteRuntimeClients();
|
|
457
|
+
if (json) {
|
|
458
|
+
console.log(JSON.stringify({ clients }, null, 2));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.log("Remote Runtime clients");
|
|
462
|
+
if (clients.length === 0)
|
|
463
|
+
console.log(" (none)");
|
|
464
|
+
for (const client of clients) {
|
|
465
|
+
const state = client.revokedAt ? "revoked" : "active";
|
|
466
|
+
console.log(` ${client.grantId.padEnd(38)} ${client.clientLabel ?? client.clientId} ${state}`);
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (action === "revoke") {
|
|
471
|
+
const grantId = args[2];
|
|
472
|
+
if (!grantId || grantId.startsWith("--")) {
|
|
473
|
+
fail("runtime clients revoke: missing grant id");
|
|
474
|
+
}
|
|
475
|
+
const result = await revokeResidentRemoteRuntimeClient(grantId);
|
|
476
|
+
console.log(`Revoked ${grantId}; closed ${result.closedConnections} active connection(s).`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
fail("runtime clients: expected list or revoke <grant-id>");
|
|
480
|
+
}
|
|
481
|
+
default:
|
|
482
|
+
fail(USAGE);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function browserCommand(args) {
|
|
486
|
+
let parsed;
|
|
487
|
+
try {
|
|
488
|
+
parsed = parseBrowserCliArgs(args);
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
492
|
+
}
|
|
493
|
+
const sub = parsed.subcommand;
|
|
494
|
+
const json = parsed.json;
|
|
495
|
+
if (sub === "install" || sub === "update") {
|
|
496
|
+
const version = parsed.version;
|
|
497
|
+
const channelValue = parsed.channel;
|
|
498
|
+
if (version && channelValue) {
|
|
499
|
+
fail("browser install: choose either --version or --channel");
|
|
500
|
+
}
|
|
501
|
+
if (sub === "update" && version) {
|
|
502
|
+
fail("browser update: use --channel, or browser install --version for a pinned build");
|
|
503
|
+
}
|
|
504
|
+
const selector = version
|
|
505
|
+
? { kind: "version", version }
|
|
506
|
+
: { kind: "channel", channel: browserReleaseChannel(channelValue) };
|
|
507
|
+
const resolved = await resolveChromeForTestingRelease(selector);
|
|
508
|
+
const installed = await createChromeForTestingArtifactStore().installResolved(resolved, {
|
|
509
|
+
...(json ? {} : { onProgress: (message) => console.log(message) }),
|
|
510
|
+
});
|
|
511
|
+
if (json)
|
|
512
|
+
console.log(JSON.stringify({ resolved, installed }, null, 2));
|
|
513
|
+
else
|
|
514
|
+
console.log(`Active Browser engine: Chrome for Testing ${installed.version}.`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (sub === "version") {
|
|
518
|
+
const installed = createChromeForTestingArtifactStore().readActive();
|
|
519
|
+
if (json)
|
|
520
|
+
console.log(JSON.stringify({ installed: installed ?? null }, null, 2));
|
|
521
|
+
else if (installed) {
|
|
522
|
+
console.log(`Chrome for Testing ${installed.version} · ${installed.hostPlatform} · ${installed.sha256}`);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
console.log("No managed Chrome for Testing build is installed.");
|
|
526
|
+
}
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (sub === "clean") {
|
|
530
|
+
const result = createChromeForTestingArtifactStore().pruneBuilds();
|
|
531
|
+
if (json)
|
|
532
|
+
console.log(JSON.stringify(result, null, 2));
|
|
533
|
+
else if (result.removedBuilds.length === 0) {
|
|
534
|
+
console.log("No inactive Chrome for Testing builds were eligible for removal.");
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
console.log(`Removed ${result.removedBuilds.length} inactive Chrome for Testing build(s).`);
|
|
538
|
+
}
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (sub === "endpoint") {
|
|
542
|
+
const target = await resolveBrowserTarget(parsed);
|
|
543
|
+
if (parsed.ensure) {
|
|
544
|
+
await callBrowserRuntime(target, "browser.open", { sessionId: target.sessionId });
|
|
545
|
+
}
|
|
546
|
+
const descriptor = await getResidentRuntimeLocalBrowserEndpoint({
|
|
547
|
+
...(target.credential ? {} : { sessionId: target.sessionId }),
|
|
548
|
+
});
|
|
549
|
+
console.log(json ? JSON.stringify(descriptor, null, 2) : descriptor.endpoint);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const target = await resolveBrowserTarget(parsed);
|
|
553
|
+
const sessionId = target.sessionId;
|
|
554
|
+
if (sub === "open") {
|
|
555
|
+
const state = await callBrowserRuntime(target, "browser.open", {
|
|
556
|
+
sessionId,
|
|
557
|
+
...(parsed.url ? { url: parsed.url } : {}),
|
|
558
|
+
});
|
|
559
|
+
printBrowserState(state, json);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const state = await callBrowserRuntime(target, "browser.state.get", { sessionId });
|
|
563
|
+
if (sub === "status") {
|
|
564
|
+
printBrowserState(state, json);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (sub === "pages") {
|
|
568
|
+
if (json)
|
|
569
|
+
console.log(JSON.stringify({ pages: state?.pages ?? [] }, null, 2));
|
|
570
|
+
else
|
|
571
|
+
printBrowserPages(state);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (!state) {
|
|
575
|
+
if (json)
|
|
576
|
+
console.log(JSON.stringify({ closed: false, browser: null }, null, 2));
|
|
577
|
+
else
|
|
578
|
+
console.log(`No Browser is open for Session ${sessionId}.`);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const result = await callBrowserRuntime(target, "browser.close", {
|
|
582
|
+
sessionId,
|
|
583
|
+
browserGeneration: state.browserGeneration,
|
|
584
|
+
});
|
|
585
|
+
console.log(json
|
|
586
|
+
? JSON.stringify(result, null, 2)
|
|
587
|
+
: `Closed Browser generation ${result.browserGeneration} for Session ${sessionId}.`);
|
|
588
|
+
}
|
|
589
|
+
function browserReleaseChannel(value) {
|
|
590
|
+
const normalized = value === undefined
|
|
591
|
+
? "Stable"
|
|
592
|
+
: `${value.slice(0, 1).toUpperCase()}${value.slice(1).toLowerCase()}`;
|
|
593
|
+
if (normalized !== "Stable" &&
|
|
594
|
+
normalized !== "Beta" &&
|
|
595
|
+
normalized !== "Dev" &&
|
|
596
|
+
normalized !== "Canary") {
|
|
597
|
+
fail("browser: --channel must be stable, beta, dev, or canary");
|
|
598
|
+
}
|
|
599
|
+
return normalized;
|
|
600
|
+
}
|
|
601
|
+
async function resolveBrowserTarget(args) {
|
|
602
|
+
const credential = await readOptionalManagedRuntimeBrowserCredential();
|
|
603
|
+
try {
|
|
604
|
+
return resolveBrowserCliTarget(args, credential);
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function callBrowserRuntime(target, method, params) {
|
|
611
|
+
if (target.credential) {
|
|
612
|
+
return callManagedRuntimeBrowser(target.credential, method, params);
|
|
613
|
+
}
|
|
614
|
+
return callResidentRuntime(target.runtimeSelector, method, params);
|
|
615
|
+
}
|
|
616
|
+
function printBrowserState(state, json) {
|
|
617
|
+
if (json) {
|
|
618
|
+
console.log(JSON.stringify({ browser: state }, null, 2));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (!state) {
|
|
622
|
+
console.log("No Browser is open for this Session.");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
console.log(`Browser ${state.status} · ${state.executionBackend} · generation ${state.browserGeneration}`);
|
|
626
|
+
printBrowserPages(state);
|
|
627
|
+
}
|
|
628
|
+
function printBrowserPages(state) {
|
|
629
|
+
if (!state || state.pages.length === 0) {
|
|
630
|
+
console.log(" (no Pages)");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
for (const page of state.pages) {
|
|
634
|
+
const marker = page.pageId === state.activePageId ? "*" : " ";
|
|
635
|
+
const title = page.title ? ` ${page.title}` : "";
|
|
636
|
+
console.log(`${marker} ${page.pageId} ${page.url}${title}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function optionValue(args, option) {
|
|
640
|
+
const index = args.indexOf(option);
|
|
641
|
+
if (index < 0)
|
|
642
|
+
return undefined;
|
|
643
|
+
const value = args[index + 1];
|
|
644
|
+
if (!value || value.startsWith("--"))
|
|
645
|
+
fail(`${option}: missing value`);
|
|
646
|
+
return value;
|
|
647
|
+
}
|
|
648
|
+
function digestOption(args) {
|
|
649
|
+
const expected = optionValue(args, "--expect-digest");
|
|
650
|
+
const legacyAlias = optionValue(args, "--expected-digest");
|
|
651
|
+
if (expected && legacyAlias)
|
|
652
|
+
fail("pass only one digest expectation option");
|
|
653
|
+
return expected ?? legacyAlias;
|
|
654
|
+
}
|
|
655
|
+
async function readStdinBounded(maxBytes) {
|
|
656
|
+
const chunks = [];
|
|
657
|
+
let bytes = 0;
|
|
658
|
+
for await (const chunk of process.stdin) {
|
|
659
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
660
|
+
bytes += value.byteLength;
|
|
661
|
+
if (bytes > maxBytes)
|
|
662
|
+
fail(`stdin exceeds ${maxBytes} bytes`);
|
|
663
|
+
chunks.push(value);
|
|
664
|
+
}
|
|
665
|
+
return Buffer.concat(chunks, bytes).toString("utf8");
|
|
666
|
+
}
|
|
222
667
|
async function main() {
|
|
223
668
|
const [command, sub, arg] = process.argv.slice(2);
|
|
224
669
|
switch (command) {
|
|
@@ -230,14 +675,11 @@ async function main() {
|
|
|
230
675
|
break;
|
|
231
676
|
case "update": {
|
|
232
677
|
const rest = process.argv.slice(3);
|
|
233
|
-
const
|
|
234
|
-
const resultFile = resultIdx >= 0 ? rest[resultIdx + 1] : undefined;
|
|
235
|
-
const version = rest.find((a) => !a.startsWith("--") && a !== resultFile);
|
|
678
|
+
const version = rest.find((a) => !a.startsWith("--"));
|
|
236
679
|
process.exit(await runUpdate({
|
|
237
680
|
check: rest.includes("--check"),
|
|
238
681
|
json: rest.includes("--json"),
|
|
239
682
|
version,
|
|
240
|
-
resultFile,
|
|
241
683
|
}));
|
|
242
684
|
break;
|
|
243
685
|
}
|
|
@@ -257,7 +699,7 @@ async function main() {
|
|
|
257
699
|
streamLogs();
|
|
258
700
|
break;
|
|
259
701
|
case "plugin":
|
|
260
|
-
await pluginCommand(
|
|
702
|
+
await pluginCommand(process.argv.slice(3));
|
|
261
703
|
break;
|
|
262
704
|
case "agent":
|
|
263
705
|
await agentCommand(sub, arg);
|
|
@@ -268,6 +710,12 @@ async function main() {
|
|
|
268
710
|
case "emulator":
|
|
269
711
|
emulatorPassthrough(process.argv.slice(3));
|
|
270
712
|
break;
|
|
713
|
+
case "runtime":
|
|
714
|
+
await runtimeCommand(process.argv.slice(3));
|
|
715
|
+
break;
|
|
716
|
+
case "browser":
|
|
717
|
+
await browserCommand(process.argv.slice(3));
|
|
718
|
+
break;
|
|
271
719
|
case undefined:
|
|
272
720
|
case "help":
|
|
273
721
|
case "-h":
|