nolo-cli 0.71.0 → 0.72.0

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.
Files changed (68) hide show
  1. package/{_executeModel-GOV2OHC7.js → _executeModel-T3FPYFVF.js} +11 -11
  2. package/{agentDeleteCommand-FSIMTL4K.js → agentDeleteCommand-UAMLWTPS.js} +6 -6
  3. package/{agentEmailCommands-YZQ2VVHY.js → agentEmailCommands-65FRADWQ.js} +15 -14
  4. package/{agentListCommands-2YWZYH52.js → agentListCommands-63DOSBY7.js} +7 -7
  5. package/{agentMachineCommands-UCYM5RRQ.js → agentMachineCommands-HMMAULDF.js} +6 -6
  6. package/{agentRecordCommands-TPBYOPMA.js → agentRecordCommands-HX5AEFP7.js} +15 -14
  7. package/{agentRunCommand-HBC2EQVU.js → agentRunCommand-KXPU5X7D.js} +15 -14
  8. package/{agentRuntimeLocal-O6DZDTK4.js → agentRuntimeLocal-XG3IDNOC.js} +4 -4
  9. package/{agentSlice-ZPODGT3P.js → agentSlice-EACXUOWW.js} +1 -1
  10. package/{appDeleteCommands-HIJNWSK7.js → appDeleteCommands-TJXDMOYT.js} +7 -7
  11. package/{appDeployCommands-XULV2ZN6.js → appDeployCommands-ECZJQAAL.js} +7 -7
  12. package/{appDeployStatusCommands-ETY4Z7K6.js → appDeployStatusCommands-DZKVFG3Y.js} +7 -7
  13. package/{appGetCommands-M253LKDA.js → appGetCommands-JIT4OPUD.js} +7 -7
  14. package/{appListCommands-XYFXJMNP.js → appListCommands-NR2HRMDY.js} +7 -7
  15. package/{chatQueueReduxAdapter-Z67OC4WO.js → chatQueueReduxAdapter-XEIJO5WR.js} +2 -2
  16. package/chromeCommands-VOIRNJBL.js +499 -0
  17. package/chromeOpsCommands-M7DPCX2T.js +258 -0
  18. package/{chunk-PWGW5JTK.js → chunk-226XC5JW.js} +2 -2
  19. package/{chunk-SPQ7GSUX.js → chunk-4SUTS5DI.js} +113 -16
  20. package/{chunk-B62OSA77.js → chunk-AFIKMMGC.js} +1 -1
  21. package/{chunk-Q4OT2DC7.js → chunk-AFIWC5NH.js} +1 -1
  22. package/{chunk-RFMPSCHX.js → chunk-AI5QKFXK.js} +5568 -337
  23. package/{chunk-NUTDASGX.js → chunk-ALGK6TIE.js} +2 -2
  24. package/{chunk-VLX45P4L.js → chunk-CDVGNZDA.js} +2 -2
  25. package/{chunk-2QDDKFG5.js → chunk-CQ3WA2NE.js} +77 -0
  26. package/{chunk-CJ23A7U4.js → chunk-CUQ6PG2C.js} +14 -5
  27. package/{chunk-ZIUMSKK4.js → chunk-D74EWXL4.js} +87 -47
  28. package/{chunk-JWO3RQSH.js → chunk-DWPNOYMB.js} +1 -1
  29. package/{chunk-EDKF7RX4.js → chunk-EOARMOVD.js} +2 -0
  30. package/{chunk-T7PQFKJU.js → chunk-FSFQ3A7S.js} +1 -1
  31. package/{chunk-YYLLD6H6.js → chunk-H5I6AJX4.js} +22 -6
  32. package/{chunk-3NVAU4WZ.js → chunk-H7UKKEIS.js} +1 -1
  33. package/{chunk-YCDWJX45.js → chunk-KC5FAHVS.js} +1 -1
  34. package/{chunk-3U3EA3BO.js → chunk-L2Y24GRM.js} +2 -2
  35. package/{chunk-FCCT2LU2.js → chunk-MEVSATIF.js} +61 -19
  36. package/{chunk-BLCUMZNV.js → chunk-P7FA2HMM.js} +66 -120
  37. package/{chunk-AUKZOZFG.js → chunk-QA6YKM3H.js} +1 -1
  38. package/{chunk-HXAINXPQ.js → chunk-QC5LGHYP.js} +1 -1
  39. package/chunk-QCZX4FTW.js +242 -0
  40. package/{chunk-DQAEUZSQ.js → chunk-QRBAZ5PW.js} +2 -2
  41. package/{chunk-F657NADX.js → chunk-QUH3XHL5.js} +3 -3
  42. package/{chunk-TVDZABBT.js → chunk-U6LKJRJB.js} +4 -4
  43. package/{chunk-IDZTNRP2.js → chunk-X7HHHIOZ.js} +10 -2
  44. package/{chunk-AAWBX67U.js → chunk-X7Z7U4Z6.js} +5 -5
  45. package/{contentThunks-VGXYTSKU.js → contentThunks-3SPROFG5.js} +3 -3
  46. package/{createAgentAutomationAction-6YBZFLET.js → createAgentAutomationAction-EIP2EG3O.js} +3 -3
  47. package/{createDialogAction-N7CTNKB5.js → createDialogAction-RLUSTN4D.js} +4 -4
  48. package/{createPageAction-RXOOVKVO.js → createPageAction-4Y52G7WG.js} +3 -3
  49. package/{dialogCommands-PE5FEFJ5.js → dialogCommands-CJCAMML3.js} +7 -7
  50. package/{dialogSlice-NLSKRXBN.js → dialogSlice-THY45JWM.js} +2 -2
  51. package/{docCreateCommands-IPDFZVPG.js → docCreateCommands-H65RSKT4.js} +6 -6
  52. package/{docDeleteCommands-VQ4Y66CM.js → docDeleteCommands-PBYGJ6CZ.js} +7 -7
  53. package/{docReadCommands-PD33VV27.js → docReadCommands-4VRYN5R5.js} +6 -6
  54. package/{docStore-CIAYE5WP.js → docStore-ZQ3ECOPY.js} +1 -1
  55. package/{docUpdateCommands-CGUQ2JY3.js → docUpdateCommands-COZG3ZDB.js} +6 -6
  56. package/{handleSendMessageAction-ENUNMW3Z.js → handleSendMessageAction-CE4M5XIE.js} +2 -2
  57. package/index.js +108 -48
  58. package/{machineCommands-APIJY7UA.js → machineCommands-JE4G4JYB.js} +14 -13
  59. package/{machineWsRunDispatch-42WKRDE7.js → machineWsRunDispatch-FUQOSWD4.js} +14 -13
  60. package/package.json +1 -1
  61. package/{readlineWorkspace-MUV7VRD4.js → readlineWorkspace-Z4V3EB52.js} +191 -57
  62. package/{runAgentClientLoop-A4OXYOCL.js → runAgentClientLoop-NE5EHXSS.js} +9 -9
  63. package/{spaceCommands-Y47DDAQD.js → spaceCommands-WGXHETMT.js} +6 -6
  64. package/{streamAgentChatTurn-YHKNJEFR.js → streamAgentChatTurn-A6L6O22P.js} +11 -11
  65. package/{tools-S5RSWF3R.js → tools-JS475NYO.js} +6 -6
  66. package/{tuiSummaryLlmCaller-J5BEPLW5.js → tuiSummaryLlmCaller-3Z24VUWM.js} +2 -2
  67. package/{updateCommands-AJYSD73Y.js → updateCommands-Y363MC3L.js} +2 -2
  68. package/{updateDialogTitleAction-BU2VPE5B.js → updateDialogTitleAction-GMZUT6RP.js} +3 -3
@@ -0,0 +1,499 @@
1
+ import {
2
+ NOLO_CHROME_CONNECTOR_EXTENSION_ID,
3
+ NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION,
4
+ REQUIRED_CHROME_CONNECTOR_FEATURES,
5
+ createChromeConnectorClient
6
+ } from "./chunk-QCZX4FTW.js";
7
+ import {
8
+ toErrorMessage
9
+ } from "./chunk-HNMXYYJR.js";
10
+ import "./chunk-KUG52IAP.js";
11
+ import "./chunk-4VNS5WPM.js";
12
+
13
+ // packages/cli/chromeCommands.ts
14
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
15
+ import { resolve as resolve2 } from "node:path";
16
+
17
+ // packages/desktop-chrome-connector/nativeHostInstall.mjs
18
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import { createHash, randomBytes } from "node:crypto";
20
+ import { dirname, resolve } from "node:path";
21
+ import { execFileSync } from "node:child_process";
22
+ import { fileURLToPath } from "node:url";
23
+ var DEFAULT_CONNECTOR_ROOT = dirname(fileURLToPath(import.meta.url));
24
+ function extensionIdFromPublicKey(publicKeyBase64) {
25
+ const digest = createHash("sha256").update(Buffer.from(publicKeyBase64, "base64")).digest();
26
+ return Array.from(
27
+ digest.subarray(0, 16),
28
+ (byte) => `${String.fromCharCode(97 + (byte >> 4))}${String.fromCharCode(97 + (byte & 15))}`
29
+ ).join("");
30
+ }
31
+ function resolveNodePath() {
32
+ if (typeof Bun !== "undefined" && typeof Bun.which === "function") {
33
+ const bunResolvedNode = Bun.which("node");
34
+ if (bunResolvedNode) return bunResolvedNode;
35
+ }
36
+ const resolved = execFileSync("/usr/bin/env", ["node", "-p", "process.execPath"], {
37
+ encoding: "utf8"
38
+ }).trim();
39
+ return resolved || "node";
40
+ }
41
+ function nativeMessagingHostsDir({
42
+ home = process.env.HOME || "",
43
+ platform = process.platform
44
+ } = {}) {
45
+ if (platform === "darwin") {
46
+ return resolve(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts");
47
+ }
48
+ if (platform === "linux") {
49
+ return resolve(home, ".config/google-chrome/NativeMessagingHosts");
50
+ }
51
+ throw new Error(
52
+ `Native host installation is not implemented for platform "${platform}". Windows needs a registry value under HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts pointing at the manifest file, plus an executable launcher: Chrome starts the manifest's path as a process, so a shell wrapper will not work there.`
53
+ );
54
+ }
55
+ function resolveNativeHostInstallPaths({
56
+ home = process.env.HOME || "",
57
+ connectorRoot = DEFAULT_CONNECTOR_ROOT,
58
+ platform = process.platform
59
+ } = {}) {
60
+ const supportDir = resolve(home, "Library/Application Support/Nolo/ChromeConnector");
61
+ return {
62
+ connectorRoot,
63
+ extensionManifestPath: resolve(connectorRoot, "extension", "manifest.json"),
64
+ hostPath: resolve(connectorRoot, "native-host", "nolo-chrome-native-host.mjs"),
65
+ templatePath: resolve(connectorRoot, "native-host", "com.nolo.chrome_connector.json"),
66
+ nativeManifestPath: resolve(
67
+ nativeMessagingHostsDir({ home, platform }),
68
+ "com.nolo.chrome_connector.json"
69
+ ),
70
+ supportDir,
71
+ tokenPath: resolve(supportDir, "token"),
72
+ wrapperPath: resolve(supportDir, "nolo-chrome-native-host")
73
+ };
74
+ }
75
+ function installNativeHostManifest({
76
+ home = process.env.HOME || "",
77
+ connectorRoot = DEFAULT_CONNECTOR_ROOT,
78
+ platform = process.platform,
79
+ extensionId,
80
+ nodePath
81
+ } = {}) {
82
+ const paths = resolveNativeHostInstallPaths({ home, connectorRoot, platform });
83
+ const manifest = JSON.parse(readFileSync(paths.templatePath, "utf8"));
84
+ const extensionManifest = JSON.parse(readFileSync(paths.extensionManifestPath, "utf8"));
85
+ const resolvedExtensionId = extensionId || extensionIdFromPublicKey(extensionManifest.key);
86
+ const resolvedNodePath = nodePath || resolveNodePath();
87
+ mkdirSync(paths.supportDir, { recursive: true });
88
+ const existingToken = existsSync(paths.tokenPath) ? readFileSync(paths.tokenPath, "utf8").trim() : "";
89
+ const token = existingToken || randomBytes(32).toString("hex");
90
+ writeFileSync(paths.tokenPath, `${token}
91
+ `, { mode: 384 });
92
+ writeFileSync(
93
+ paths.wrapperPath,
94
+ `#!/bin/sh
95
+ NOLO_CHROME_CONNECTOR_TOKEN=${JSON.stringify(token)} exec ${JSON.stringify(resolvedNodePath)} ${JSON.stringify(paths.hostPath)}
96
+ `
97
+ );
98
+ chmodSync(paths.wrapperPath, 493);
99
+ manifest.path = paths.wrapperPath;
100
+ manifest.allowed_origins = [`chrome-extension://${resolvedExtensionId}/`];
101
+ mkdirSync(dirname(paths.nativeManifestPath), { recursive: true });
102
+ writeFileSync(paths.nativeManifestPath, `${JSON.stringify(manifest, null, 2)}
103
+ `);
104
+ return {
105
+ extensionId: resolvedExtensionId,
106
+ nodePath: resolvedNodePath,
107
+ tokenPath: paths.tokenPath,
108
+ ...paths
109
+ };
110
+ }
111
+
112
+ // packages/cli/chromeCommands.ts
113
+ var CLI_CONNECTOR_ROOT_CANDIDATES = [
114
+ "../desktop-chrome-connector",
115
+ "../../desktop-chrome-connector",
116
+ "../integrations/connector",
117
+ "../../integrations/connector"
118
+ ];
119
+ var FULL_SUPPORT_CHROME_FEATURES = [
120
+ ...new Set(Object.values(REQUIRED_CHROME_CONNECTOR_FEATURES).flat())
121
+ ].sort();
122
+ function pickCliConnectorRoot(candidates) {
123
+ for (const candidate of candidates) {
124
+ if (existsSync2(resolve2(candidate, "extension", "manifest.json"))) return candidate;
125
+ }
126
+ return null;
127
+ }
128
+ function resolveCliConnectorRoot(args = {}) {
129
+ const env = args.env ?? process.env;
130
+ const moduleDir = args.moduleDir ?? import.meta.dir;
131
+ const override = (env.NOLO_CHROME_CONNECTOR_ROOT ?? "").trim();
132
+ if (override) {
133
+ const resolvedOverride = resolve2(override);
134
+ if (!existsSync2(resolve2(resolvedOverride, "extension", "manifest.json"))) {
135
+ throw new Error(
136
+ `NOLO_CHROME_CONNECTOR_ROOT is set to ${resolvedOverride}, but it contains no extension/manifest.json. Unset it or point it at a Nolo Chrome connector checkout.`
137
+ );
138
+ }
139
+ return resolvedOverride;
140
+ }
141
+ const candidates = CLI_CONNECTOR_ROOT_CANDIDATES.map(
142
+ (relative) => resolve2(moduleDir, relative)
143
+ );
144
+ const found = pickCliConnectorRoot(candidates);
145
+ if (!found) {
146
+ throw new Error(
147
+ `Nolo Chrome connector sources not found. Run the CLI from a repo checkout that contains packages/desktop-chrome-connector, or set NOLO_CHROME_CONNECTOR_ROOT. Probed: ${candidates.join(", ")}`
148
+ );
149
+ }
150
+ return found;
151
+ }
152
+ function detectWorktreeCheckout(connectorRoot) {
153
+ const resolvedRoot = resolve2(connectorRoot);
154
+ let dir = resolvedRoot;
155
+ let checkoutRoot = null;
156
+ for (let depth = 0; depth < 8; depth += 1) {
157
+ const gitEntry = resolve2(dir, ".git");
158
+ if (existsSync2(gitEntry)) {
159
+ let isGitFile = null;
160
+ try {
161
+ isGitFile = statSync(gitEntry).isFile();
162
+ } catch {
163
+ }
164
+ if (isGitFile === true) {
165
+ try {
166
+ const match = /^gitdir:\s*(.+)$/m.exec(readFileSync2(gitEntry, "utf8"));
167
+ const gitDir = match ? resolve2(dir, match[1].trim()) : null;
168
+ if (gitDir && gitDir.split(/[\\/]+/).includes("worktrees")) {
169
+ return { worktreeRoot: dir, mainRoot: resolve2(gitDir, "..", "..", "..") };
170
+ }
171
+ } catch {
172
+ }
173
+ } else if (isGitFile === false) {
174
+ checkoutRoot = dir;
175
+ }
176
+ break;
177
+ }
178
+ const parent = resolve2(dir, "..");
179
+ if (parent === dir) break;
180
+ dir = parent;
181
+ }
182
+ const segments = resolvedRoot.split(/[\\/]+/);
183
+ const markerIndex = segments.indexOf(".worktrees");
184
+ if (markerIndex <= 0) return null;
185
+ if (checkoutRoot) {
186
+ const checkoutDepth = resolve2(checkoutRoot).split(/[\\/]+/).length;
187
+ if (markerIndex < checkoutDepth) return null;
188
+ }
189
+ return {
190
+ worktreeRoot: segments.slice(0, markerIndex + 2).join("/"),
191
+ mainRoot: segments.slice(0, markerIndex).join("/") || "/"
192
+ };
193
+ }
194
+ function readExtensionManifest(connectorRoot) {
195
+ try {
196
+ return JSON.parse(
197
+ readFileSync2(resolve2(connectorRoot, "extension", "manifest.json"), "utf8")
198
+ );
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+ function readJsonFile(path) {
204
+ try {
205
+ return JSON.parse(readFileSync2(path, "utf8"));
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ async function probeConnectorRpc(requestChrome) {
211
+ try {
212
+ const connectorInfo = await requestChrome("connector_info", {}) ?? {};
213
+ const features = Array.isArray(connectorInfo.features) ? connectorInfo.features.filter(
214
+ (entry) => typeof entry === "string"
215
+ ) : [];
216
+ return {
217
+ online: true,
218
+ extensionId: typeof connectorInfo.extensionId === "string" ? connectorInfo.extensionId : null,
219
+ protocolVersion: typeof connectorInfo.protocolVersion === "string" ? connectorInfo.protocolVersion : null,
220
+ features
221
+ };
222
+ } catch (error) {
223
+ return {
224
+ online: false,
225
+ extensionId: null,
226
+ protocolVersion: null,
227
+ features: [],
228
+ error: {
229
+ code: error?.code ?? "CHROME_CONNECTOR_ERROR",
230
+ message: toErrorMessage(error)
231
+ }
232
+ };
233
+ }
234
+ }
235
+ function resolveStatusNextStep(report) {
236
+ if (!report.connectorRoot) {
237
+ if (report.rpc.online && report.rpc.protocolVersion === report.rpc.expectedProtocolVersion) {
238
+ return "Chrome connector is online, but this CLI cannot see a connector checkout, so `nolo chrome install` / manifest checks are unavailable here. Set NOLO_CHROME_CONNECTOR_ROOT (or run the CLI from a repo checkout) to enable them.";
239
+ }
240
+ return "Run the CLI from a repo checkout that contains packages/desktop-chrome-connector, or set NOLO_CHROME_CONNECTOR_ROOT to one, then rerun `nolo chrome status`.";
241
+ }
242
+ if (!report.nativeHost.supported) {
243
+ return `This platform has no supported native messaging host install (Chrome looks the host up through a registry value on Windows), so non-interactive install is not available: ${report.nativeHost.platformError ?? "unsupported platform"}`;
244
+ }
245
+ if (!report.nativeHost.installed || !report.token.present) {
246
+ return "nolo chrome install";
247
+ }
248
+ if (!report.nativeHost.wrapperPathMatches || !report.nativeHost.allowedOriginMatches) {
249
+ const installedWrapperPath = report.nativeHost.installedWrapperPath;
250
+ if (!report.nativeHost.wrapperPathMatches && installedWrapperPath && installedWrapperPath !== report.nativeHost.wrapperPath && existsSync2(installedWrapperPath)) {
251
+ return `Native host is installed for a different checkout (${installedWrapperPath}); this CLI is looking at ${report.connectorRoot}. Run \`nolo chrome status\` from the checkout that installed it, or run \`nolo chrome install\` from here only if Chrome should switch to this one.`;
252
+ }
253
+ return "nolo chrome install (the installed manifest does not point at this checkout's wrapper / extension id, which Chrome silently ignores)";
254
+ }
255
+ if (!report.rpc.online) {
256
+ return `Load the unpacked extension from ${report.extension.path} in chrome://extensions (Developer mode) or install it from the Chrome Web Store, then rerun \`nolo chrome status\`.`;
257
+ }
258
+ if (report.rpc.extensionId && report.rpc.extensionId !== report.extension.expectedId) {
259
+ return `The connected extension (${report.rpc.extensionId}) is not the connector at ${report.extension.path}; remove the other copy and rerun \`nolo chrome status\`.`;
260
+ }
261
+ if (report.rpc.protocolVersion !== report.rpc.expectedProtocolVersion) {
262
+ return `nolo chrome reload (the running extension does not speak protocol ${report.rpc.expectedProtocolVersion}; a reload restarts the worker with the files on disk)`;
263
+ }
264
+ if (report.rpc.missingFeatures.length > 0) {
265
+ return `nolo chrome reload (the running extension is missing ${report.rpc.missingFeatures.join(", ")}); if that persists, the checkout's extension is older than the CLI expects.`;
266
+ }
267
+ return "Chrome connector is online: chrome_* tools execute against this Chrome profile.";
268
+ }
269
+ async function buildChromeConnectorStatus(args = {}) {
270
+ const env = args.env ?? process.env;
271
+ const home = args.home ?? env.HOME ?? "";
272
+ const platform = args.platform ?? process.platform;
273
+ let connectorRoot = args.connectorRoot ?? null;
274
+ let rootError = null;
275
+ if (!connectorRoot) {
276
+ try {
277
+ connectorRoot = resolveCliConnectorRoot({ env });
278
+ } catch (error) {
279
+ rootError = toErrorMessage(error);
280
+ }
281
+ }
282
+ const extensionManifest = connectorRoot ? readExtensionManifest(connectorRoot) : null;
283
+ const expectedId = extensionManifest && typeof extensionManifest.key === "string" ? extensionIdFromPublicKey(extensionManifest.key) : NOLO_CHROME_CONNECTOR_EXTENSION_ID;
284
+ const report = {
285
+ ok: false,
286
+ connectorRoot,
287
+ extension: {
288
+ path: connectorRoot ? resolve2(connectorRoot, "extension") : null,
289
+ expectedId
290
+ },
291
+ nativeHost: {
292
+ supported: true,
293
+ installed: false,
294
+ manifestPath: null,
295
+ wrapperPath: null,
296
+ installedWrapperPath: null,
297
+ wrapperPathMatches: false,
298
+ allowedOriginMatches: false
299
+ },
300
+ token: { path: null, present: false },
301
+ rpc: {
302
+ online: false,
303
+ extensionId: null,
304
+ protocolVersion: null,
305
+ features: [],
306
+ missingFeatures: [...FULL_SUPPORT_CHROME_FEATURES],
307
+ expectedProtocolVersion: NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION
308
+ },
309
+ nextStep: ""
310
+ };
311
+ const requestChrome = args.requestChrome ?? createChromeConnectorClient().request;
312
+ const probe = await probeConnectorRpc(requestChrome);
313
+ report.rpc.online = probe.online;
314
+ report.rpc.extensionId = probe.extensionId;
315
+ report.rpc.protocolVersion = probe.protocolVersion;
316
+ report.rpc.features = probe.features;
317
+ report.rpc.missingFeatures = FULL_SUPPORT_CHROME_FEATURES.filter(
318
+ (feature) => !probe.features.includes(feature)
319
+ );
320
+ if (!probe.online) report.rpc.error = probe.error;
321
+ if (!connectorRoot) {
322
+ if (rootError) report.connectorRootError = rootError;
323
+ report.ok = report.rpc.online && probe.extensionId === expectedId && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
324
+ report.nextStep = resolveStatusNextStep(report);
325
+ return report;
326
+ }
327
+ let paths = null;
328
+ try {
329
+ paths = resolveNativeHostInstallPaths({ home, connectorRoot, platform });
330
+ } catch (error) {
331
+ report.nativeHost.supported = false;
332
+ report.nativeHost.platformError = toErrorMessage(error);
333
+ report.nextStep = resolveStatusNextStep(report);
334
+ return report;
335
+ }
336
+ report.nativeHost.manifestPath = paths.nativeManifestPath;
337
+ report.nativeHost.wrapperPath = paths.wrapperPath;
338
+ report.token.path = paths.tokenPath;
339
+ const installedManifest = existsSync2(paths.nativeManifestPath) ? readJsonFile(paths.nativeManifestPath) : null;
340
+ report.nativeHost.installed = installedManifest !== null;
341
+ report.nativeHost.installedWrapperPath = typeof installedManifest?.path === "string" ? installedManifest.path : null;
342
+ report.nativeHost.wrapperPathMatches = report.nativeHost.installedWrapperPath === paths.wrapperPath;
343
+ const expectedOrigin = `chrome-extension://${expectedId}/`;
344
+ report.nativeHost.allowedOriginMatches = Array.isArray(installedManifest?.allowed_origins) && installedManifest.allowed_origins.includes(expectedOrigin);
345
+ if (existsSync2(paths.tokenPath)) {
346
+ try {
347
+ report.token.present = readFileSync2(paths.tokenPath, "utf8").trim().length > 0;
348
+ } catch {
349
+ report.token.present = false;
350
+ }
351
+ }
352
+ report.ok = report.nativeHost.supported && report.nativeHost.installed && report.nativeHost.wrapperPathMatches && report.nativeHost.allowedOriginMatches && report.token.present && probe.online && probe.extensionId === expectedId && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
353
+ report.nextStep = resolveStatusNextStep(report);
354
+ return report;
355
+ }
356
+ function yesNo(value) {
357
+ return value ? "ok" : "mismatch";
358
+ }
359
+ function formatChromeConnectorStatus(report) {
360
+ const lines = ["Nolo Chrome connector"];
361
+ lines.push(
362
+ ` connector source : ${report.connectorRoot ?? "(not found)"}`
363
+ );
364
+ lines.push(
365
+ ` extension : ${report.extension.path ?? "(not found)"} (id ${report.extension.expectedId})`
366
+ );
367
+ lines.push(
368
+ ` native manifest : ${!report.nativeHost.supported ? "(unsupported platform)" : report.nativeHost.manifestPath ? `${report.nativeHost.manifestPath} [${report.nativeHost.installed ? "installed" : "missing"}]` : "(not resolved)"}`
369
+ );
370
+ if (report.nativeHost.supported && report.nativeHost.installed) {
371
+ lines.push(` wrapper path : ${yesNo(report.nativeHost.wrapperPathMatches)} (${report.nativeHost.installedWrapperPath ?? "unreadable"})`);
372
+ lines.push(` allowed origins: ${yesNo(report.nativeHost.allowedOriginMatches)}`);
373
+ }
374
+ lines.push(
375
+ ` token : ${report.token.present ? "present" : "missing"}${report.token.path ? ` ${report.token.path}` : ""}`
376
+ );
377
+ const rpc = report.rpc;
378
+ if (rpc.online) {
379
+ lines.push(
380
+ ` RPC : online extension ${rpc.extensionId ?? "unknown"} protocolVersion ${rpc.protocolVersion ?? "unknown"} features ${rpc.features.length > 0 ? rpc.features.join(", ") : "(none)"}`
381
+ );
382
+ } else {
383
+ lines.push(
384
+ ` RPC : offline ${rpc.error ? `${rpc.error.code}: ${rpc.error.message}` : ""}`.trimEnd()
385
+ );
386
+ }
387
+ lines.push(`Next step: ${report.nextStep}`);
388
+ return `${lines.join("\n")}
389
+ `;
390
+ }
391
+ async function runChromeStatusCommand(args, deps = {}) {
392
+ const output = deps.output ?? process.stdout;
393
+ let report;
394
+ try {
395
+ report = await buildChromeConnectorStatus(deps);
396
+ } catch (error) {
397
+ output.write(`[nolo] Chrome connector status failed: ${toErrorMessage(error)}
398
+ `);
399
+ return 1;
400
+ }
401
+ if (args.includes("--json")) {
402
+ output.write(`${JSON.stringify(report, null, 2)}
403
+ `);
404
+ } else {
405
+ output.write(formatChromeConnectorStatus(report));
406
+ }
407
+ return report.ok ? 0 : 1;
408
+ }
409
+ async function runChromeInstallCommand(args, deps = {}) {
410
+ const env = deps.env ?? process.env;
411
+ const output = deps.output ?? process.stdout;
412
+ const install = deps.installNativeHost ?? installNativeHostManifest;
413
+ let connectorRoot;
414
+ try {
415
+ connectorRoot = deps.connectorRoot ?? resolveCliConnectorRoot({ env });
416
+ } catch (error) {
417
+ output.write(`[nolo] Chrome native host install failed: ${toErrorMessage(error)}
418
+ `);
419
+ return 1;
420
+ }
421
+ const explicitRoot = (env.NOLO_CHROME_CONNECTOR_ROOT ?? "").trim().length > 0;
422
+ const worktree = detectWorktreeCheckout(connectorRoot);
423
+ if (worktree && !explicitRoot && !args.includes("--force")) {
424
+ output.write(
425
+ `[nolo] Chrome native host install failed: refusing to install from a git worktree (${worktree.worktreeRoot}). The wrapper it would write execs the native host inside that worktree, so the connector would stop working as soon as the worktree is removed. Run this from the main checkout${worktree.mainRoot ? ` (${worktree.mainRoot})` : ""}, or re-run with --force / NOLO_CHROME_CONNECTOR_ROOT=<checkout> if Chrome should really use this one.
426
+ `
427
+ );
428
+ return 1;
429
+ }
430
+ let result;
431
+ try {
432
+ result = install({
433
+ home: deps.home ?? env.HOME ?? "",
434
+ connectorRoot,
435
+ ...deps.platform ? { platform: deps.platform } : {},
436
+ ...deps.nodePath ? { nodePath: deps.nodePath } : {}
437
+ });
438
+ } catch (error) {
439
+ output.write(`[nolo] Chrome native host install failed: ${toErrorMessage(error)}
440
+ `);
441
+ return 1;
442
+ }
443
+ if (args.includes("--json")) {
444
+ output.write(`${JSON.stringify({ ok: true, ...result }, null, 2)}
445
+ `);
446
+ } else {
447
+ output.write(
448
+ [
449
+ "Nolo Chrome connector native host installed.",
450
+ ` manifest : ${result.nativeManifestPath}`,
451
+ ` wrapper : ${result.wrapperPath} -> ${result.nodePath}`,
452
+ ` token : ${result.tokenPath} (an existing token is kept, so reruns are idempotent)`,
453
+ ` extension: ${result.extensionId}`,
454
+ `Next step: load the unpacked extension from ${resolve2(connectorRoot, "extension")} in chrome://extensions (Developer mode) or install it from the Chrome Web Store, then run \`nolo chrome status\`.`,
455
+ ""
456
+ ].join("\n")
457
+ );
458
+ }
459
+ return 0;
460
+ }
461
+ async function runChromeReloadCommand(args, deps = {}) {
462
+ const output = deps.output ?? process.stdout;
463
+ const requestChrome = deps.requestChrome ?? createChromeConnectorClient().request;
464
+ try {
465
+ const result = await requestChrome("reload_extension", {});
466
+ if (args.includes("--json")) {
467
+ output.write(`${JSON.stringify({ ok: true, result }, null, 2)}
468
+ `);
469
+ } else {
470
+ output.write(
471
+ `Reload requested: ${JSON.stringify(result ?? {})}
472
+ The connector service worker restarts within ~1s; verify with \`nolo chrome status\`.
473
+ `
474
+ );
475
+ }
476
+ return 0;
477
+ } catch (error) {
478
+ const code = error?.code ?? "CHROME_CONNECTOR_ERROR";
479
+ output.write(`[nolo] Chrome connector reload failed: ${code} ${toErrorMessage(error)}
480
+ `);
481
+ if (code === "CHROME_CONNECTOR_UNAVAILABLE") {
482
+ output.write(
483
+ "The native host is not reachable: load the unpacked connector extension in chrome://extensions (or start Chrome), then rerun `nolo chrome reload`.\n"
484
+ );
485
+ }
486
+ return 1;
487
+ }
488
+ }
489
+ export {
490
+ CLI_CONNECTOR_ROOT_CANDIDATES,
491
+ FULL_SUPPORT_CHROME_FEATURES,
492
+ buildChromeConnectorStatus,
493
+ detectWorktreeCheckout,
494
+ formatChromeConnectorStatus,
495
+ resolveCliConnectorRoot,
496
+ runChromeInstallCommand,
497
+ runChromeReloadCommand,
498
+ runChromeStatusCommand
499
+ };