stikfix 1.2.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.
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // bin/stikfix.ts
5
+ var import_node_fs2 = require("node:fs");
6
+ var import_node_path3 = require("node:path");
7
+ var import_node_os2 = require("node:os");
8
+ var import_node_util = require("node:util");
9
+
10
+ // host/src/bootstrap/register.ts
11
+ var import_node_fs = require("node:fs");
12
+ var import_node_path2 = require("node:path");
13
+ var import_node_os = require("node:os");
14
+ var import_node_child_process = require("node:child_process");
15
+
16
+ // host/src/security.ts
17
+ var import_node_crypto = require("node:crypto");
18
+ var import_node_path = require("node:path");
19
+ var MAX_BODY = 12 * 1024 * 1024;
20
+
21
+ // host/src/bootstrap/register.ts
22
+ var NATIVE_HOST_NAME = "com.stikfix.host";
23
+ var MANIFEST_FILENAME = `${NATIVE_HOST_NAME}.json`;
24
+ var FIREFOX_MANIFEST_FILENAME = `${NATIVE_HOST_NAME}.firefox.json`;
25
+ var NATIVE_WRAPPER_WIN = `${NATIVE_HOST_NAME}.bat`;
26
+ var NATIVE_WRAPPER_NIX = `${NATIVE_HOST_NAME}.sh`;
27
+ var NATIVE_WRAPPER_WIN_FIREFOX = `${NATIVE_HOST_NAME}.firefox.bat`;
28
+ var NATIVE_WRAPPER_NIX_FIREFOX = `${NATIVE_HOST_NAME}.firefox.sh`;
29
+ var REG_CHROME_KEY = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`;
30
+ var REG_EDGE_KEY = `HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`;
31
+ var REG_FIREFOX_KEY = `HKCU\\Software\\Mozilla\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`;
32
+ var DEFAULT_GECKO_ID = "stikfix@stikfix.com";
33
+ var LAUNCHER_BATCH_FILENAME = "stikfix-host.bat";
34
+ var LAUNCHER_VBS_FILENAME = "stikfix-host.vbs";
35
+ var LAUNCHER_LNK_FILENAME = "Stikfix Host.lnk";
36
+ var LAUNCHER_COMMAND_FILENAME = "stikfix-host.command";
37
+ var LAUNCHER_DESKTOP_FILENAME = "stikfix-host.desktop";
38
+ var LAUNCHER_SH_FILENAME = "stikfix-host.sh";
39
+ function nativeManifestPath(plat = process.platform, home = (0, import_node_os.homedir)(), browser = "chrome") {
40
+ if (browser === "firefox") {
41
+ switch (plat) {
42
+ case "darwin":
43
+ return (0, import_node_path2.join)(
44
+ home,
45
+ "Library",
46
+ "Application Support",
47
+ "Mozilla",
48
+ "NativeMessagingHosts",
49
+ MANIFEST_FILENAME
50
+ );
51
+ case "linux":
52
+ return (0, import_node_path2.join)(home, ".mozilla", "native-messaging-hosts", MANIFEST_FILENAME);
53
+ case "win32":
54
+ return (0, import_node_path2.join)(home, ".local", "share", "stikfix", FIREFOX_MANIFEST_FILENAME);
55
+ default:
56
+ throw new Error(`Unsupported platform: ${plat}`);
57
+ }
58
+ }
59
+ switch (plat) {
60
+ case "darwin":
61
+ return (0, import_node_path2.join)(
62
+ home,
63
+ "Library",
64
+ "Application Support",
65
+ "Google",
66
+ "Chrome",
67
+ "NativeMessagingHosts",
68
+ MANIFEST_FILENAME
69
+ );
70
+ case "linux":
71
+ return (0, import_node_path2.join)(home, ".config", "google-chrome", "NativeMessagingHosts", MANIFEST_FILENAME);
72
+ case "win32":
73
+ return (0, import_node_path2.join)(home, ".local", "share", "stikfix", MANIFEST_FILENAME);
74
+ default:
75
+ throw new Error(`Unsupported platform: ${plat}`);
76
+ }
77
+ }
78
+ function nativeWrapperPath(plat = process.platform, home = (0, import_node_os.homedir)(), browser = "chrome") {
79
+ const dir = (0, import_node_path2.dirname)(nativeManifestPath(plat, home, browser));
80
+ const winName = browser === "firefox" ? NATIVE_WRAPPER_WIN_FIREFOX : NATIVE_WRAPPER_WIN;
81
+ const nixName = browser === "firefox" ? NATIVE_WRAPPER_NIX_FIREFOX : NATIVE_WRAPPER_NIX;
82
+ return (0, import_node_path2.join)(dir, plat === "win32" ? winName : nixName);
83
+ }
84
+ function writeNativeWrapper(hostBinPath, plat = process.platform, home = (0, import_node_os.homedir)(), browser = "chrome") {
85
+ const wrapperPath = nativeWrapperPath(plat, home, browser);
86
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(wrapperPath), { recursive: true });
87
+ const abs = (0, import_node_path2.resolve)(hostBinPath);
88
+ if (plat === "win32") {
89
+ const content = `@echo off\r
90
+ "node" "${abs}" %*\r
91
+ `;
92
+ (0, import_node_fs.writeFileSync)(wrapperPath, content, { encoding: "utf8" });
93
+ } else {
94
+ const content = `#!/bin/sh
95
+ exec node "${abs}" "$@"
96
+ `;
97
+ (0, import_node_fs.writeFileSync)(wrapperPath, content, { encoding: "utf8", mode: 493 });
98
+ }
99
+ return wrapperPath;
100
+ }
101
+ function launcherDir(plat = process.platform, home = (0, import_node_os.homedir)()) {
102
+ if (plat === "win32") {
103
+ return (0, import_node_path2.join)(home, ".local", "share", "stikfix");
104
+ }
105
+ return (0, import_node_path2.join)(home, ".config", "stikfix");
106
+ }
107
+ function getLauncherPaths(plat = process.platform, home = (0, import_node_os.homedir)()) {
108
+ const dir = launcherDir(plat, home);
109
+ if (plat === "win32") {
110
+ return {
111
+ launcher: (0, import_node_path2.join)(dir, LAUNCHER_BATCH_FILENAME),
112
+ shortcut: (0, import_node_path2.join)(home, "Desktop", LAUNCHER_LNK_FILENAME),
113
+ desktopEntry: null
114
+ };
115
+ }
116
+ if (plat === "darwin") {
117
+ return {
118
+ launcher: (0, import_node_path2.join)(dir, LAUNCHER_COMMAND_FILENAME),
119
+ shortcut: null,
120
+ desktopEntry: null
121
+ };
122
+ }
123
+ return {
124
+ launcher: (0, import_node_path2.join)(dir, LAUNCHER_SH_FILENAME),
125
+ shortcut: null,
126
+ desktopEntry: (0, import_node_path2.join)(home, ".local", "share", "applications", LAUNCHER_DESKTOP_FILENAME)
127
+ };
128
+ }
129
+ var EXT_ID_RE = /^[a-p]{32}$/;
130
+ var GECKO_ID_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
131
+ function buildManifest(extensionId, hostBinPath, browser = "chrome") {
132
+ const absPath = (0, import_node_path2.resolve)(hostBinPath);
133
+ if (browser === "firefox") {
134
+ if (!GECKO_ID_RE.test(extensionId)) {
135
+ throw new Error(
136
+ `Invalid Firefox add-on id "${extensionId}": must be a gecko id like "name@domain.tld".`
137
+ );
138
+ }
139
+ return {
140
+ name: NATIVE_HOST_NAME,
141
+ description: "stikfix native messaging host",
142
+ path: absPath,
143
+ type: "stdio",
144
+ allowed_extensions: [extensionId]
145
+ };
146
+ }
147
+ if (!EXT_ID_RE.test(extensionId)) {
148
+ throw new Error(
149
+ `Invalid extension ID "${extensionId}": must be exactly 32 lowercase a-p characters.`
150
+ );
151
+ }
152
+ return {
153
+ name: NATIVE_HOST_NAME,
154
+ description: "stikfix native messaging host",
155
+ path: absPath,
156
+ type: "stdio",
157
+ allowed_origins: [`chrome-extension://${extensionId}/`]
158
+ };
159
+ }
160
+ function writeManifest(manifest, manifestPath) {
161
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(manifestPath), { recursive: true });
162
+ (0, import_node_fs.writeFileSync)(manifestPath, JSON.stringify(manifest, null, 2), {
163
+ encoding: "utf8",
164
+ mode: 420
165
+ });
166
+ }
167
+ function createLauncherFiles(opts) {
168
+ const plat = opts.plat ?? process.platform;
169
+ const home = opts.home ?? (0, import_node_os.homedir)();
170
+ const written = [];
171
+ const warnings = [];
172
+ const paths = getLauncherPaths(plat, home);
173
+ const launcherDirPath = launcherDir(plat, home);
174
+ (0, import_node_fs.mkdirSync)(launcherDirPath, { recursive: true });
175
+ const nodeCmd = "node";
176
+ const hostEntry = opts.hostEntryPath;
177
+ const rootArg = opts.root;
178
+ const portArg = opts.port !== void 0 ? ` --port ${opts.port}` : "";
179
+ if (plat === "win32") {
180
+ const batchContent = [
181
+ "@echo off",
182
+ `rem Stikfix HTTP host launcher \u2014 double-click to start the backend`,
183
+ `rem Generated by: npx stikfix init`,
184
+ `rem Root: ${rootArg}`,
185
+ ``,
186
+ `"${nodeCmd}" "${hostEntry}" --root "${rootArg}"${portArg}`,
187
+ `if %ERRORLEVEL% NEQ 0 pause`
188
+ ].join("\r\n");
189
+ const batchPath = paths.launcher;
190
+ (0, import_node_fs.writeFileSync)(batchPath, batchContent, { encoding: "utf8" });
191
+ written.push(batchPath);
192
+ const vq = (s) => s.replace(/"/g, '""');
193
+ const vbsHostEntry = vq(hostEntry);
194
+ const vbsRoot = vq(rootArg);
195
+ const vbsPortArg = opts.port !== void 0 ? ` --port ${opts.port}` : "";
196
+ const vbsContent = [
197
+ "Option Explicit",
198
+ "Dim sh, fso, hostEntry, root, portFile, msg, port, f",
199
+ 'Set sh = CreateObject("WScript.Shell")',
200
+ 'Set fso = CreateObject("Scripting.FileSystemObject")',
201
+ `hostEntry = "${vbsHostEntry}"`,
202
+ `root = "${vbsRoot}"`,
203
+ "' Launch the host hidden (window style 0), do not wait",
204
+ `sh.Run "cmd /c node """ & hostEntry & """ --root """ & root & """${vbsPortArg}", 0, False`,
205
+ "' Give it a moment to bind and write the port file",
206
+ "WScript.Sleep 1800",
207
+ 'msg = "Stikfix host is running." & vbCrLf & vbCrLf & "You can start dropping notes."',
208
+ 'portFile = root & "\\.stikfix-port"',
209
+ "If fso.FileExists(portFile) Then",
210
+ " Set f = fso.OpenTextFile(portFile, 1)",
211
+ " port = Trim(f.ReadAll)",
212
+ " f.Close",
213
+ ' If Len(port) > 0 Then msg = "Stikfix host is running on port " & port & "." & vbCrLf & vbCrLf & "You can start dropping notes."',
214
+ "End If",
215
+ 'sh.Popup msg, 5, "Stikfix", 64'
216
+ ].join("\r\n");
217
+ const vbsPath = (0, import_node_path2.join)(launcherDir(plat, home), LAUNCHER_VBS_FILENAME);
218
+ (0, import_node_fs.writeFileSync)(vbsPath, vbsContent, { encoding: "utf8" });
219
+ written.push(vbsPath);
220
+ if (paths.shortcut) {
221
+ const lnkPath = paths.shortcut;
222
+ const iconPath = opts.iconPath ?? "";
223
+ const safeLnkPath = lnkPath.replace(/'/g, "''");
224
+ const safeIconPath = iconPath.replace(/'/g, "''");
225
+ const wscriptPath = (0, import_node_path2.join)(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "wscript.exe");
226
+ const safeWscript = wscriptPath.replace(/'/g, "''");
227
+ const safeVbsPath = vbsPath.replace(/'/g, "''");
228
+ const psScript = `$ws = New-Object -ComObject WScript.Shell;$s = $ws.CreateShortcut('${safeLnkPath}');$s.TargetPath = '${safeWscript}';$s.Arguments = '"${safeVbsPath}"';$s.Description = 'Start the Stikfix HTTP backend host';` + (safeIconPath ? `$s.IconLocation = '${safeIconPath},0';` : "") + `$s.Save()`;
229
+ try {
230
+ (0, import_node_child_process.execFileSync)(
231
+ "powershell.exe",
232
+ ["-NoProfile", "-NonInteractive", "-Command", psScript],
233
+ { timeout: 15e3, stdio: "ignore" }
234
+ );
235
+ written.push(lnkPath);
236
+ } catch (err) {
237
+ warnings.push(
238
+ `Desktop shortcut creation failed (non-fatal): ${String(err.message)}. The batch file at ${batchPath} can be used directly.`
239
+ );
240
+ }
241
+ }
242
+ } else if (plat === "darwin") {
243
+ const commandContent = [
244
+ "#!/bin/bash",
245
+ `# Stikfix HTTP host launcher \u2014 double-click in Finder or drag to Dock`,
246
+ `# Generated by: npx stikfix init`,
247
+ ``,
248
+ `exec "${nodeCmd}" "${hostEntry}" --root "${rootArg}"${portArg}`
249
+ ].join("\n");
250
+ const commandPath = paths.launcher;
251
+ (0, import_node_fs.writeFileSync)(commandPath, commandContent, { encoding: "utf8", mode: 493 });
252
+ written.push(commandPath);
253
+ } else {
254
+ const shContent = [
255
+ "#!/bin/sh",
256
+ `# Stikfix HTTP host launcher`,
257
+ `# Generated by: npx stikfix init`,
258
+ ``,
259
+ `exec "${nodeCmd}" "${hostEntry}" --root "${rootArg}"${portArg}`
260
+ ].join("\n");
261
+ const shPath = paths.launcher;
262
+ (0, import_node_fs.writeFileSync)(shPath, shContent, { encoding: "utf8" });
263
+ try {
264
+ (0, import_node_fs.chmodSync)(shPath, 493);
265
+ } catch {
266
+ warnings.push(`Could not chmod 755 ${shPath} \u2014 run: chmod +x "${shPath}"`);
267
+ }
268
+ written.push(shPath);
269
+ if (paths.desktopEntry) {
270
+ const desktopDir = (0, import_node_path2.dirname)(paths.desktopEntry);
271
+ (0, import_node_fs.mkdirSync)(desktopDir, { recursive: true });
272
+ const iconLine = opts.iconPath ? `Icon=${opts.iconPath}` : "Icon=utilities-terminal";
273
+ const desktopContent = [
274
+ "[Desktop Entry]",
275
+ "Version=1.0",
276
+ "Type=Application",
277
+ "Name=Stikfix Host",
278
+ "Comment=Start the Stikfix HTTP backend host",
279
+ `Exec=${shPath}`,
280
+ iconLine,
281
+ "Terminal=true",
282
+ "Categories=Development;"
283
+ ].join("\n");
284
+ (0, import_node_fs.writeFileSync)(paths.desktopEntry, desktopContent, { encoding: "utf8", mode: 420 });
285
+ written.push(paths.desktopEntry);
286
+ }
287
+ }
288
+ return { written, warnings };
289
+ }
290
+ function registerNativeHost(opts) {
291
+ const plat = opts.plat ?? process.platform;
292
+ const home = opts.home ?? (0, import_node_os.homedir)();
293
+ const browser = opts.browser ?? "chrome";
294
+ const manifestPath = nativeManifestPath(plat, home, browser);
295
+ const wrapperPath = writeNativeWrapper(opts.hostBinPath, plat, home, browser);
296
+ const manifest = buildManifest(opts.extensionId, wrapperPath, browser);
297
+ writeManifest(manifest, manifestPath);
298
+ if (plat === "win32") {
299
+ const execReg = opts.execReg ?? ((args) => {
300
+ (0, import_node_child_process.execFileSync)("reg", args);
301
+ });
302
+ if (browser === "firefox") {
303
+ execReg(["ADD", REG_FIREFOX_KEY, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"]);
304
+ } else {
305
+ execReg(["ADD", REG_CHROME_KEY, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"]);
306
+ execReg(["ADD", REG_EDGE_KEY, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"]);
307
+ }
308
+ }
309
+ }
310
+ function unregisterNativeHost(opts = {}) {
311
+ const plat = opts.plat ?? process.platform;
312
+ const home = opts.home ?? (0, import_node_os.homedir)();
313
+ const browser = opts.browser ?? "chrome";
314
+ const manifestPath = opts.manifestPath ?? nativeManifestPath(plat, home, browser);
315
+ (0, import_node_fs.rmSync)(manifestPath, { force: true });
316
+ (0, import_node_fs.rmSync)(nativeWrapperPath(plat, home, browser), { force: true });
317
+ const defaultPaths = getLauncherPaths(plat, home);
318
+ const lPaths = {
319
+ launcher: opts.launcherPaths?.launcher ?? defaultPaths.launcher,
320
+ shortcut: opts.launcherPaths?.shortcut !== void 0 ? opts.launcherPaths.shortcut : defaultPaths.shortcut,
321
+ desktopEntry: opts.launcherPaths?.desktopEntry !== void 0 ? opts.launcherPaths.desktopEntry : defaultPaths.desktopEntry
322
+ };
323
+ (0, import_node_fs.rmSync)(lPaths.launcher, { force: true });
324
+ if (lPaths.shortcut) (0, import_node_fs.rmSync)(lPaths.shortcut, { force: true });
325
+ if (lPaths.desktopEntry) (0, import_node_fs.rmSync)(lPaths.desktopEntry, { force: true });
326
+ if (plat === "win32") {
327
+ (0, import_node_fs.rmSync)((0, import_node_path2.join)(launcherDir(plat, home), LAUNCHER_VBS_FILENAME), { force: true });
328
+ }
329
+ if (plat === "win32") {
330
+ if (browser === "firefox") {
331
+ try {
332
+ (0, import_node_child_process.execFileSync)("reg", ["DELETE", REG_FIREFOX_KEY, "/f"]);
333
+ } catch {
334
+ }
335
+ } else {
336
+ try {
337
+ (0, import_node_child_process.execFileSync)("reg", ["DELETE", REG_CHROME_KEY, "/f"]);
338
+ } catch {
339
+ }
340
+ try {
341
+ (0, import_node_child_process.execFileSync)("reg", ["DELETE", REG_EDGE_KEY, "/f"]);
342
+ } catch {
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ // host/src/extension-id.ts
349
+ var import_node_crypto2 = require("node:crypto");
350
+ var STABLE_EXTENSION_ID = "ccdfmbhdcafhmnnnfjpbhgebfkfgjgca";
351
+
352
+ // bin/stikfix.ts
353
+ var { values, positionals } = (0, import_node_util.parseArgs)({
354
+ allowPositionals: true,
355
+ options: {
356
+ root: { type: "string" },
357
+ "extension-id": { type: "string" },
358
+ port: { type: "string" },
359
+ browser: { type: "string" }
360
+ },
361
+ strict: false
362
+ });
363
+ var [subcommand] = positionals;
364
+ function resolveBrowser(raw) {
365
+ if (raw === void 0) return "chrome";
366
+ if (typeof raw !== "string" || !["chrome", "firefox"].includes(raw.toLowerCase())) {
367
+ console.error(`stikfix: unknown --browser "${String(raw)}" (expected chrome or firefox)`);
368
+ process.exit(1);
369
+ }
370
+ return raw.toLowerCase() === "firefox" ? "firefox" : "chrome";
371
+ }
372
+ var CONFIG_DIR = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".config", "stikfix");
373
+ var CONFIG_PATH = (0, import_node_path3.join)(CONFIG_DIR, "config.json");
374
+ if (subcommand === "init") {
375
+ const rawRoot = values["root"];
376
+ const rawExtId = values["extension-id"];
377
+ const rawPort = values["port"];
378
+ const rawBrowser = values["browser"];
379
+ if (!rawRoot || typeof rawRoot !== "string") {
380
+ console.error("stikfix init: --root is required");
381
+ console.error("Usage: npx stikfix init --root <project-dir> [--browser <chrome|firefox>] [--extension-id <id>] [--port <port>]");
382
+ process.exit(1);
383
+ }
384
+ const browser = resolveBrowser(rawBrowser);
385
+ const isFirefox = browser === "firefox";
386
+ const extensionId = rawExtId && typeof rawExtId === "string" ? rawExtId : isFirefox ? DEFAULT_GECKO_ID : STABLE_EXTENSION_ID;
387
+ const port = rawPort ? parseInt(rawPort, 10) : void 0;
388
+ const root = (0, import_node_path3.resolve)(rawRoot);
389
+ const name = (0, import_node_path3.basename)(root);
390
+ const notesDir = (0, import_node_path3.join)(root, "notes");
391
+ (0, import_node_fs2.mkdirSync)(CONFIG_DIR, { recursive: true });
392
+ const config = { root, name, notesDir };
393
+ (0, import_node_fs2.writeFileSync)(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf8", mode: 384 });
394
+ const hostBinPath = (0, import_node_path3.resolve)((0, import_node_path3.join)(__dirname, "stikfix-native.cjs"));
395
+ try {
396
+ registerNativeHost({ extensionId, hostBinPath, browser });
397
+ } catch (err) {
398
+ console.error("stikfix init: failed to register native host:", String(err));
399
+ process.exit(1);
400
+ }
401
+ const hostEntryPath = (0, import_node_path3.resolve)((0, import_node_path3.join)(__dirname, "src", "index.js"));
402
+ const projectRoot = (0, import_node_path3.resolve)((0, import_node_path3.join)(__dirname, "..", ".."));
403
+ const firefoxOutputCandidates = [
404
+ (0, import_node_path3.join)(projectRoot, ".output", "firefox-mv2"),
405
+ (0, import_node_path3.join)(projectRoot, ".output", "firefox-mv3")
406
+ ];
407
+ const chromeOutputDir = (0, import_node_path3.join)(projectRoot, ".output", "chrome-mv3");
408
+ const browserOutputDir = isFirefox ? firefoxOutputCandidates.find((d) => (0, import_node_fs2.existsSync)(d)) ?? firefoxOutputCandidates[1] : chromeOutputDir;
409
+ const icoCandidates = [
410
+ (0, import_node_path3.join)(projectRoot, "public", "icon", "stikfix.ico"),
411
+ (0, import_node_path3.join)(browserOutputDir, "icon", "stikfix.ico")
412
+ ];
413
+ const pngCandidates = [
414
+ (0, import_node_path3.join)(browserOutputDir, "icon", "128.png"),
415
+ (0, import_node_path3.join)(projectRoot, "public", "icon", "128.png"),
416
+ (0, import_node_path3.join)(projectRoot, "assets", "icon", "128.png")
417
+ ];
418
+ const iconCandidates = process.platform === "win32" ? [...icoCandidates, ...pngCandidates] : pngCandidates;
419
+ const iconPath = iconCandidates.find((p) => (0, import_node_fs2.existsSync)(p));
420
+ const launcherResult = createLauncherFiles({
421
+ hostEntryPath,
422
+ root,
423
+ port,
424
+ iconPath
425
+ });
426
+ for (const warn of launcherResult.warnings) {
427
+ console.warn(" [warn] " + warn);
428
+ }
429
+ console.log("");
430
+ console.log("stikfix: native host registered successfully.");
431
+ console.log("");
432
+ console.log(" Extension ID: " + extensionId);
433
+ console.log(" Root: " + root);
434
+ console.log(" Notes dir: " + notesDir);
435
+ console.log("");
436
+ if (extensionId === STABLE_EXTENSION_ID) {
437
+ console.log(" Using stable extension ID (derived from the committed public key).");
438
+ console.log(" The manifest key in wxt.config.ts pins this ID across machines.");
439
+ } else {
440
+ console.log(" Using custom extension ID (override via --extension-id).");
441
+ }
442
+ console.log("");
443
+ console.log("--- Next steps (no terminal required after these) ---");
444
+ console.log("");
445
+ if (isFirefox) {
446
+ console.log(" 1. Load the extension (temporary add-on):");
447
+ console.log(" about:debugging#/runtime/this-firefox \u2192 Load Temporary Add-on\u2026");
448
+ console.log(" Pick: " + (0, import_node_path3.resolve)((0, import_node_path3.join)(browserOutputDir, "manifest.json")));
449
+ } else {
450
+ console.log(" 1. Load the extension (unpacked):");
451
+ console.log(" chrome://extensions \u2192 Developer mode ON \u2192 Load unpacked");
452
+ console.log(" Folder: " + (0, import_node_path3.resolve)(browserOutputDir));
453
+ }
454
+ console.log("");
455
+ console.log(" 2. Start the backend \u2014 double-click the desktop launcher:");
456
+ if (process.platform === "win32") {
457
+ const lnkPath = (0, import_node_path3.join)((0, import_node_os2.homedir)(), "Desktop", "Stikfix Host.lnk");
458
+ const batchPath = launcherResult.written.find((p) => p.endsWith(".bat")) ?? "";
459
+ if ((0, import_node_fs2.existsSync)(lnkPath)) {
460
+ console.log(' Desktop shortcut: "Stikfix Host" (icon on your Desktop)');
461
+ } else if (batchPath) {
462
+ console.log(" Batch file: " + batchPath);
463
+ console.log(" (Desktop shortcut creation is in progress or was skipped \u2014 use the batch file above)");
464
+ }
465
+ } else if (process.platform === "darwin") {
466
+ const commandPath = launcherResult.written[0] ?? "";
467
+ console.log(" " + commandPath);
468
+ console.log(" (Double-click in Finder, or drag to the Dock for quick access)");
469
+ } else {
470
+ const shPath = launcherResult.written.find((p) => p.endsWith(".sh")) ?? "";
471
+ console.log(" " + shPath);
472
+ console.log(" (Or use the .desktop shortcut in your Applications menu)");
473
+ }
474
+ console.log("");
475
+ console.log(' 3. Open the extension popup and click "Pair with host".');
476
+ console.log(" The token is delivered automatically \u2014 no copy-paste needed.");
477
+ console.log("");
478
+ console.log(" Extension ID: " + extensionId);
479
+ console.log("");
480
+ console.log("To keep the host up-to-date:");
481
+ console.log(" npx --yes stikfix@latest init --root " + root);
482
+ } else if (subcommand === "uninstall") {
483
+ const browser = resolveBrowser(values["browser"]);
484
+ try {
485
+ unregisterNativeHost({ browser });
486
+ } catch (err) {
487
+ console.error("stikfix uninstall: error removing native-host manifest:", String(err));
488
+ }
489
+ (0, import_node_fs2.rmSync)(CONFIG_PATH, { force: true });
490
+ console.log("stikfix: native host unregistered.");
491
+ console.log(" manifest removed");
492
+ console.log(" launcher files removed");
493
+ console.log(" config removed");
494
+ } else {
495
+ console.error("Usage: npx stikfix <init|uninstall> [--root <dir>] [--browser <chrome|firefox>] [--extension-id <id>] [--port <port>]");
496
+ console.error("");
497
+ console.error(" init Register the native host and write config");
498
+ console.error(" uninstall Remove the native host manifest, launchers, and config");
499
+ process.exit(1);
500
+ }