chrome-relay 0.5.2 → 0.5.4
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/cli.js +114 -27
- package/dist/index.js +1 -1
- package/dist/native-host.js +68 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,15 +4,6 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { writeFileSync } from "fs";
|
|
6
6
|
|
|
7
|
-
// src/index.ts
|
|
8
|
-
var CHROME_RELAY_VERSION = true ? "0.5.2" : "0.0.0-dev";
|
|
9
|
-
|
|
10
|
-
// src/install/install.ts
|
|
11
|
-
import os from "os";
|
|
12
|
-
import path from "path";
|
|
13
|
-
import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
|
|
16
7
|
// ../protocol/dist/index.js
|
|
17
8
|
var NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
|
|
18
9
|
var DEFAULT_HTTP_PORT = 12122;
|
|
@@ -24,8 +15,41 @@ var DEFAULT_EXTENSION_IDS = [
|
|
|
24
15
|
LEGACY_DEV_EXTENSION_ID,
|
|
25
16
|
LOCAL_UNPACKED_EXTENSION_ID
|
|
26
17
|
];
|
|
18
|
+
var RelayError = class extends Error {
|
|
19
|
+
code;
|
|
20
|
+
tool;
|
|
21
|
+
phase;
|
|
22
|
+
details;
|
|
23
|
+
retryable;
|
|
24
|
+
constructor(spec) {
|
|
25
|
+
super(spec.message);
|
|
26
|
+
this.name = "RelayError";
|
|
27
|
+
this.code = spec.code;
|
|
28
|
+
this.tool = spec.tool;
|
|
29
|
+
this.phase = spec.phase;
|
|
30
|
+
this.details = spec.details;
|
|
31
|
+
this.retryable = spec.retryable;
|
|
32
|
+
}
|
|
33
|
+
toBridgeError() {
|
|
34
|
+
return {
|
|
35
|
+
code: this.code,
|
|
36
|
+
message: this.message,
|
|
37
|
+
...this.tool ? { tool: this.tool } : {},
|
|
38
|
+
...this.phase ? { phase: this.phase } : {},
|
|
39
|
+
...this.details ? { details: this.details } : {},
|
|
40
|
+
...this.retryable !== void 0 ? { retryable: this.retryable } : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/index.ts
|
|
46
|
+
var CHROME_RELAY_VERSION = true ? "0.5.4" : "0.0.0-dev";
|
|
27
47
|
|
|
28
48
|
// src/install/install.ts
|
|
49
|
+
import os from "os";
|
|
50
|
+
import path from "path";
|
|
51
|
+
import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
52
|
+
import { fileURLToPath } from "url";
|
|
29
53
|
var APP_DIR = path.join(os.homedir(), ".chrome-relay");
|
|
30
54
|
var KNOWN_EXTENSION_IDS = [
|
|
31
55
|
["Chrome Web Store", CHROME_WEB_STORE_EXTENSION_ID],
|
|
@@ -140,25 +164,30 @@ function emitNoticeOnce(notice) {
|
|
|
140
164
|
async function callToolWithMeta(name, args) {
|
|
141
165
|
const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/call`, {
|
|
142
166
|
method: "POST",
|
|
143
|
-
headers: {
|
|
144
|
-
"content-type": "application/json"
|
|
145
|
-
},
|
|
167
|
+
headers: { "content-type": "application/json" },
|
|
146
168
|
body: JSON.stringify({
|
|
147
169
|
name,
|
|
148
170
|
args
|
|
149
171
|
})
|
|
150
172
|
});
|
|
151
173
|
const payload = await response.json().catch(() => null);
|
|
174
|
+
const noticeString = payload?.notice ?? payload?.notices?.[0]?.message;
|
|
152
175
|
if (!response.ok) {
|
|
153
|
-
if (
|
|
154
|
-
throw
|
|
176
|
+
if (noticeString) emitNoticeOnce(noticeString);
|
|
177
|
+
throw rebuildError(payload, `Bridge request failed with ${response.status}`);
|
|
155
178
|
}
|
|
156
179
|
if (!payload?.ok) {
|
|
157
|
-
if (
|
|
158
|
-
throw
|
|
180
|
+
if (noticeString) emitNoticeOnce(noticeString);
|
|
181
|
+
throw rebuildError(payload, "Bridge call failed.");
|
|
159
182
|
}
|
|
160
|
-
if (
|
|
161
|
-
return { data: payload.data, notice: payload.notice };
|
|
183
|
+
if (noticeString) emitNoticeOnce(noticeString);
|
|
184
|
+
return { data: payload.data, notice: payload.notice, notices: payload.notices };
|
|
185
|
+
}
|
|
186
|
+
function rebuildError(payload, fallbackMessage) {
|
|
187
|
+
if (payload?.errorDetails) {
|
|
188
|
+
return new RelayError(payload.errorDetails);
|
|
189
|
+
}
|
|
190
|
+
return new Error(payload?.error || fallbackMessage);
|
|
162
191
|
}
|
|
163
192
|
async function callTool(name, args) {
|
|
164
193
|
const { data } = await callToolWithMeta(name, args);
|
|
@@ -167,6 +196,22 @@ async function callTool(name, args) {
|
|
|
167
196
|
|
|
168
197
|
// src/release-notes.ts
|
|
169
198
|
var RELEASE_NOTES = {
|
|
199
|
+
"0.5.4": [
|
|
200
|
+
"Strict target routing (code-quality-hardening PR 2). Within a single scope, --tab / --workspace / --group are mutually exclusive \u2014 passing more than one on the same subcommand (or both at the program level) now fails with `target_conflict` and exit code 2.",
|
|
201
|
+
"Cross-scope override is still allowed but visible: `chrome-relay --workspace W <cmd> --workspace W2` works, but stderr prints a `target_overridden: workspace W \u2192 W2` notice so the agent (or user) knows what happened.",
|
|
202
|
+
"Fixed silent drops: `viewport set` and `console` previously hand-rolled their args and ignored global --workspace/--group. They now route through baseArgs() like every other targetable command.",
|
|
203
|
+
"New target-routing test matrix (55 tests) proves every targetable subcommand forwards --tab, --workspace, and --group correctly \u2014 and that the strict-conflict + override behavior holds. If you add a new targetable command to the CLI, add it to TARGETABLE_COMMANDS in packages/cli/test/target-routing.test.ts.",
|
|
204
|
+
"New TargetSelector type in @chrome-relay/protocol (future-proofing). Wire still carries the three loose fields; a future PR migrates the extension to read a single structured `target` field."
|
|
205
|
+
],
|
|
206
|
+
"0.5.3": [
|
|
207
|
+
"Structured errors and notices (code-quality-hardening PR 1). New `BridgeError` and `BridgeNotice` types in @chrome-relay/protocol carry a code, tool, phase, and details \u2014 agents can branch on `errorDetails.code === 'invalid_arguments'` instead of regex-matching message strings.",
|
|
208
|
+
"Tool result JSON now carries BOTH the legacy fields (`error: string`, `notice: string`) AND the new structured fields (`errorDetails: BridgeError`, `notices: BridgeNotice[]`). Old consumers keep working; new consumers prefer the structured shape.",
|
|
209
|
+
"The cli-outdated notice is now a `BridgeNotice` with `code:'cli_outdated'`, `details.currentVersion`, `details.expectedVersion`, and an `action.command` field.",
|
|
210
|
+
"Every strict parser (PR 0 strict enums) now throws `RelayError` with `code:'invalid_arguments'`, the offending tool, the phase that failed, and the list of valid choices.",
|
|
211
|
+
"All action-validator throws (chrome_console, chrome_network, chrome_viewport, chrome_workspace, chrome_group, chrome_screencast) carry the same structured shape.",
|
|
212
|
+
"Unknown tool dispatch now returns `code:'unsupported_tool'`.",
|
|
213
|
+
"CLI: when a RelayError flows back, stderr prints the human message AND a `relayError` JSON object so agents can parse the structured details from stderr without needing a separate flag."
|
|
214
|
+
],
|
|
170
215
|
"0.5.2": [
|
|
171
216
|
"Strict input parsers (code-quality-hardening PR 0). Invalid console levels, network status filters, tab-group colors, and tab-id lists now throw instead of being silently dropped \u2014 an agent that asks for `errors` (typo of `error`) gets a precise error rather than all levels back.",
|
|
172
217
|
"Affected tools: chrome_console (levels), chrome_network (status), chrome_group (color, tabIds), chrome_screencast (format, action), chrome_network (action).",
|
|
@@ -280,9 +325,14 @@ Notes:
|
|
|
280
325
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
281
326
|
}
|
|
282
327
|
} catch (error) {
|
|
283
|
-
|
|
284
|
-
(error
|
|
285
|
-
|
|
328
|
+
if (error instanceof RelayError) {
|
|
329
|
+
process.stderr.write(error.message + "\n");
|
|
330
|
+
process.stderr.write(JSON.stringify({ relayError: error.toBridgeError() }, null, 2) + "\n");
|
|
331
|
+
} else {
|
|
332
|
+
process.stderr.write(
|
|
333
|
+
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
286
336
|
process.exit(1);
|
|
287
337
|
}
|
|
288
338
|
}
|
|
@@ -290,15 +340,53 @@ Notes:
|
|
|
290
340
|
return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v)).option("--workspace <name>", "target the active tab in a named workspace window (see `chrome-relay workspace`)").option("--group <name>", "target the active tab in a named tab-group (see `chrome-relay group`)");
|
|
291
341
|
}
|
|
292
342
|
function baseArgs(opts) {
|
|
343
|
+
const parentOpts = program.opts();
|
|
344
|
+
rejectIntraScopeConflict("subcommand", {
|
|
345
|
+
tab: opts.tab,
|
|
346
|
+
workspace: opts.workspace,
|
|
347
|
+
group: opts.group
|
|
348
|
+
});
|
|
349
|
+
rejectIntraScopeConflict("program-level", {
|
|
350
|
+
workspace: parentOpts.workspace,
|
|
351
|
+
group: parentOpts.group
|
|
352
|
+
});
|
|
353
|
+
if (opts.workspace && parentOpts.workspace && opts.workspace !== parentOpts.workspace) {
|
|
354
|
+
emitTargetOverride("workspace", parentOpts.workspace, opts.workspace);
|
|
355
|
+
}
|
|
356
|
+
if (opts.group && parentOpts.group && opts.group !== parentOpts.group) {
|
|
357
|
+
emitTargetOverride("group", parentOpts.group, opts.group);
|
|
358
|
+
}
|
|
359
|
+
if (opts.tab !== void 0 && (parentOpts.workspace || parentOpts.group)) {
|
|
360
|
+
const prior = parentOpts.workspace ? `workspace=${parentOpts.workspace}` : `group=${parentOpts.group}`;
|
|
361
|
+
emitTargetOverride("tab", prior, String(opts.tab));
|
|
362
|
+
}
|
|
293
363
|
const args = {};
|
|
294
364
|
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
295
|
-
const parentOpts = program.opts();
|
|
296
365
|
const effectiveWorkspace = opts.workspace ?? parentOpts.workspace;
|
|
297
366
|
const effectiveGroup = opts.group ?? parentOpts.group;
|
|
298
|
-
if (effectiveWorkspace) args.workspaceName = effectiveWorkspace;
|
|
299
|
-
if (effectiveGroup) args.groupName = effectiveGroup;
|
|
367
|
+
if (opts.tab === void 0 && effectiveWorkspace) args.workspaceName = effectiveWorkspace;
|
|
368
|
+
if (opts.tab === void 0 && effectiveGroup) args.groupName = effectiveGroup;
|
|
300
369
|
return args;
|
|
301
370
|
}
|
|
371
|
+
function rejectIntraScopeConflict(scope, fields) {
|
|
372
|
+
const present = [];
|
|
373
|
+
if (fields.tab !== void 0) present.push("--tab");
|
|
374
|
+
if (fields.workspace) present.push("--workspace");
|
|
375
|
+
if (fields.group) present.push("--group");
|
|
376
|
+
if (present.length > 1) {
|
|
377
|
+
process.stderr.write(
|
|
378
|
+
`[chrome-relay] target_conflict: ${scope} flags ${present.join(" + ")} are mutually exclusive. Pass exactly one of --tab, --workspace, or --group on the same ${scope}.
|
|
379
|
+
`
|
|
380
|
+
);
|
|
381
|
+
process.exit(2);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function emitTargetOverride(kind, from, to) {
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
`[chrome-relay] target_overridden: ${kind} ${from} \u2192 ${to} (subcommand-level overrides program-level)
|
|
387
|
+
`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
302
390
|
program.command("tabs [verb]").description("List open Chrome windows and tabs. (verb 'list' is accepted as alias)").action(async (verb) => {
|
|
303
391
|
if (verb && verb !== "list") {
|
|
304
392
|
process.stderr.write(`unknown tabs verb: ${verb}. Use 'tabs' or 'tabs list'.
|
|
@@ -494,7 +582,7 @@ Notes:
|
|
|
494
582
|
viewport.command("set").description("Apply explicit viewport dimensions.").requiredOption("--width <px>", "viewport width in CSS pixels", (v) => Number(v)).requiredOption("--height <px>", "viewport height in CSS pixels", (v) => Number(v)).option("--dpr <ratio>", "device pixel ratio (1, 2, 3...)", (v) => Number(v)).option("--mobile", "set the mobile flag (affects meta viewport interpretation)").option("--touch", "enable touch event emulation").option("--user-agent <ua>", "override the User-Agent header")
|
|
495
583
|
).action(async (opts) => {
|
|
496
584
|
const args = { action: "set", width: opts.width, height: opts.height };
|
|
497
|
-
|
|
585
|
+
Object.assign(args, baseArgs(opts));
|
|
498
586
|
if (opts.dpr !== void 0) args.dpr = opts.dpr;
|
|
499
587
|
if (opts.mobile) args.mobile = true;
|
|
500
588
|
if (opts.touch) args.hasTouch = true;
|
|
@@ -726,8 +814,7 @@ Notes:
|
|
|
726
814
|
`
|
|
727
815
|
)
|
|
728
816
|
).action(async (opts) => {
|
|
729
|
-
const args =
|
|
730
|
-
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
817
|
+
const args = baseArgs(opts);
|
|
731
818
|
if (opts.clear) args.action = "clear";
|
|
732
819
|
if (opts.level) args.levels = opts.level;
|
|
733
820
|
if (typeof opts.since === "number") args.since = opts.since;
|
package/dist/index.js
CHANGED
package/dist/native-host.js
CHANGED
|
@@ -8,9 +8,47 @@ import Fastify from "fastify";
|
|
|
8
8
|
|
|
9
9
|
// ../protocol/dist/index.js
|
|
10
10
|
var DEFAULT_HTTP_PORT = 12122;
|
|
11
|
+
var RelayError = class extends Error {
|
|
12
|
+
code;
|
|
13
|
+
tool;
|
|
14
|
+
phase;
|
|
15
|
+
details;
|
|
16
|
+
retryable;
|
|
17
|
+
constructor(spec) {
|
|
18
|
+
super(spec.message);
|
|
19
|
+
this.name = "RelayError";
|
|
20
|
+
this.code = spec.code;
|
|
21
|
+
this.tool = spec.tool;
|
|
22
|
+
this.phase = spec.phase;
|
|
23
|
+
this.details = spec.details;
|
|
24
|
+
this.retryable = spec.retryable;
|
|
25
|
+
}
|
|
26
|
+
toBridgeError() {
|
|
27
|
+
return {
|
|
28
|
+
code: this.code,
|
|
29
|
+
message: this.message,
|
|
30
|
+
...this.tool ? { tool: this.tool } : {},
|
|
31
|
+
...this.phase ? { phase: this.phase } : {},
|
|
32
|
+
...this.details ? { details: this.details } : {},
|
|
33
|
+
...this.retryable !== void 0 ? { retryable: this.retryable } : {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function toBridgeError(unknownErr, fallbackTool) {
|
|
38
|
+
if (unknownErr instanceof RelayError) {
|
|
39
|
+
const e = unknownErr.toBridgeError();
|
|
40
|
+
return fallbackTool && !e.tool ? { ...e, tool: fallbackTool } : e;
|
|
41
|
+
}
|
|
42
|
+
const message = unknownErr instanceof Error ? unknownErr.message : String(unknownErr);
|
|
43
|
+
return {
|
|
44
|
+
code: "internal_error",
|
|
45
|
+
message,
|
|
46
|
+
...fallbackTool ? { tool: fallbackTool } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
11
49
|
|
|
12
50
|
// src/index.ts
|
|
13
|
-
var CHROME_RELAY_VERSION = true ? "0.5.
|
|
51
|
+
var CHROME_RELAY_VERSION = true ? "0.5.4" : "0.0.0-dev";
|
|
14
52
|
|
|
15
53
|
// src/release-notes.ts
|
|
16
54
|
function compareSemver(a, b) {
|
|
@@ -30,7 +68,20 @@ function buildOutdatedNotice(bridge2) {
|
|
|
30
68
|
const extVersion = bridge2.getExtensionVersion();
|
|
31
69
|
if (!extVersion) return void 0;
|
|
32
70
|
if (compareSemver(CHROME_RELAY_VERSION, extVersion) >= 0) return void 0;
|
|
33
|
-
return
|
|
71
|
+
return {
|
|
72
|
+
code: "cli_outdated",
|
|
73
|
+
message: `cli-outdated: ${CHROME_RELAY_VERSION} < extension ${extVersion}; run \`chrome-relay update\``,
|
|
74
|
+
details: {
|
|
75
|
+
currentVersion: CHROME_RELAY_VERSION,
|
|
76
|
+
expectedVersion: extVersion
|
|
77
|
+
},
|
|
78
|
+
action: { command: "chrome-relay update" }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function attachNotices(payload, notice) {
|
|
82
|
+
if (!notice) return;
|
|
83
|
+
payload.notice = notice.message;
|
|
84
|
+
payload.notices = [notice];
|
|
34
85
|
}
|
|
35
86
|
var RelayHttpServer = class {
|
|
36
87
|
constructor(bridge2, port = DEFAULT_HTTP_PORT) {
|
|
@@ -63,15 +114,19 @@ var RelayHttpServer = class {
|
|
|
63
114
|
body.args ?? {}
|
|
64
115
|
);
|
|
65
116
|
const notice = buildOutdatedNotice(this.bridge);
|
|
66
|
-
|
|
117
|
+
const payload = { ok: true, data };
|
|
118
|
+
attachNotices(payload, notice);
|
|
119
|
+
reply.send(payload);
|
|
67
120
|
} catch (error) {
|
|
68
121
|
const notice = buildOutdatedNotice(this.bridge);
|
|
69
|
-
const
|
|
122
|
+
const errorDetails = error instanceof RelayError ? error.toBridgeError() : toBridgeError(error, body.name);
|
|
123
|
+
const payload = {
|
|
70
124
|
ok: false,
|
|
71
|
-
error:
|
|
125
|
+
error: errorDetails.message,
|
|
126
|
+
errorDetails
|
|
72
127
|
};
|
|
73
|
-
|
|
74
|
-
reply.code(500).send(
|
|
128
|
+
attachNotices(payload, notice);
|
|
129
|
+
reply.code(500).send(payload);
|
|
75
130
|
}
|
|
76
131
|
});
|
|
77
132
|
await this.app.listen({ port: this.port, host: "127.0.0.1" });
|
|
@@ -135,7 +190,12 @@ var ExtensionBridge = class {
|
|
|
135
190
|
pending.resolve(message.payload.data);
|
|
136
191
|
return;
|
|
137
192
|
}
|
|
138
|
-
|
|
193
|
+
const details = message.payload.errorDetails;
|
|
194
|
+
if (details) {
|
|
195
|
+
pending.reject(new RelayError(details));
|
|
196
|
+
} else {
|
|
197
|
+
pending.reject(new Error(message.payload.error));
|
|
198
|
+
}
|
|
139
199
|
}
|
|
140
200
|
async waitUntilReady(timeoutMs = 15e3) {
|
|
141
201
|
if (this.ready) {
|