@hermespilot/link 0.2.0 → 0.2.1

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/index.js CHANGED
@@ -1,31 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ConversationService,
4
3
  LINK_COMMAND,
5
4
  LINK_VERSION,
6
5
  LinkHttpError,
7
6
  clearPairingClaim,
8
- createApp,
9
- createFileLogger,
10
- createRotatingTextLogWriter,
7
+ currentCliScriptPath,
8
+ daemonLogFile,
11
9
  ensureHermesApiServerAvailable,
12
10
  ensureHermesApiServerConfig,
13
11
  ensureIdentity,
14
- getDaemonLogFile,
12
+ getDaemonStatus,
15
13
  getIdentityStatus,
16
14
  getLinkLogFile,
17
15
  hasActiveDevices,
18
16
  loadConfig,
19
17
  loadIdentity,
20
- migrateLinkDatabase,
21
18
  preparePairing,
19
+ probeLocalLinkService,
22
20
  readHermesApiServerConfig,
23
21
  readPairingClaim,
24
22
  resolveHermesConfigPath,
25
23
  resolveHermesProfileDir,
26
24
  resolveRuntimePaths,
27
- syncHermesLinkCronDeliveries
28
- } from "../chunk-TMCXOV6J.js";
25
+ runDaemonSupervisor,
26
+ startDaemonProcess,
27
+ startLinkService,
28
+ stopDaemonProcess
29
+ } from "../chunk-YQX7OQFH.js";
29
30
 
30
31
  // src/cli/index.ts
31
32
  import { Command } from "commander";
@@ -33,535 +34,10 @@ import qrcode from "qrcode-terminal";
33
34
 
34
35
  // src/autostart/autostart.ts
35
36
  import { execFile } from "child_process";
36
- import { mkdir as mkdir3, readFile as readFile2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
37
+ import { mkdir, readFile, rm, writeFile } from "fs/promises";
37
38
  import os from "os";
38
- import path2 from "path";
39
- import { promisify } from "util";
40
-
41
- // src/daemon/process.ts
42
- import { spawn } from "child_process";
43
- import { mkdir as mkdir2, readFile, rm as rm2 } from "fs/promises";
44
39
  import path from "path";
45
-
46
- // src/daemon/service.ts
47
- import { createServer } from "http";
48
- import { mkdir, rm, writeFile } from "fs/promises";
49
-
50
- // src/relay/control-client.ts
51
- import WebSocket from "ws";
52
- function connectRelayControl(options) {
53
- const wsUrl = new URL(`${options.relayBaseUrl.replace(/\/+$/u, "")}/api/v1/relay/link/connect`);
54
- wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
55
- wsUrl.searchParams.set("link_id", options.linkId);
56
- const maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
57
- const backoffBaseMs = options.backoffBaseMs ?? 1e3;
58
- const backoffMaxMs = options.backoffMaxMs ?? 3e4;
59
- let reconnectAttempts = 0;
60
- let closedByUser = false;
61
- let socket = null;
62
- let retryTimer = null;
63
- let abortControllers = /* @__PURE__ */ new Map();
64
- let fatalRelayRejection = null;
65
- const connect = () => {
66
- options.onStatus?.({ state: "connecting", attempt: reconnectAttempts });
67
- fatalRelayRejection = null;
68
- socket = new WebSocket(wsUrl, {
69
- headers: {
70
- "x-hermes-link-version": LINK_VERSION
71
- }
72
- });
73
- socket.on("open", () => {
74
- reconnectAttempts = 0;
75
- options.onStatus?.({ state: "connected", attempt: reconnectAttempts });
76
- });
77
- socket.on("message", (raw) => {
78
- if (!socket || typeof raw !== "string" && !Buffer.isBuffer(raw)) {
79
- return;
80
- }
81
- void handleFrame(socket, String(raw), options.localPort, abortControllers).catch((error) => {
82
- const message = error instanceof Error ? error.message : "Relay request failed";
83
- socket?.send(JSON.stringify({ type: "http.error", id: "unknown", status: 502, message }));
84
- });
85
- });
86
- socket.on("error", (error) => {
87
- const message = error instanceof Error ? error.message : "Relay websocket error";
88
- fatalRelayRejection = resolveFatalRelayRejection(message);
89
- options.onStatus?.({
90
- state: "disconnected",
91
- attempt: reconnectAttempts,
92
- message: fatalRelayRejection ?? message
93
- });
94
- });
95
- socket.on("close", () => {
96
- abortAll(abortControllers);
97
- abortControllers = /* @__PURE__ */ new Map();
98
- if (fatalRelayRejection) {
99
- options.onStatus?.({
100
- state: "failed",
101
- attempt: reconnectAttempts,
102
- message: fatalRelayRejection
103
- });
104
- return;
105
- }
106
- if (closedByUser) {
107
- options.onStatus?.({ state: "disconnected", attempt: reconnectAttempts });
108
- return;
109
- }
110
- if (reconnectAttempts >= maxReconnectAttempts) {
111
- options.onStatus?.({ state: "failed", attempt: reconnectAttempts, message: "Relay reconnect attempts exhausted" });
112
- return;
113
- }
114
- reconnectAttempts += 1;
115
- const delay = computeBackoffMs(reconnectAttempts, backoffBaseMs, backoffMaxMs);
116
- options.onStatus?.({ state: "retrying", attempt: reconnectAttempts, message: `Retrying in ${delay}ms` });
117
- retryTimer = setTimeout(connect, delay);
118
- retryTimer.unref?.();
119
- });
120
- };
121
- connect();
122
- return {
123
- close() {
124
- closedByUser = true;
125
- if (retryTimer) {
126
- clearTimeout(retryTimer);
127
- retryTimer = null;
128
- }
129
- abortAll(abortControllers);
130
- socket?.terminate();
131
- }
132
- };
133
- }
134
- function resolveFatalRelayRejection(message) {
135
- if (!/Unexpected server response:\s*(400|401|403|426)\b/u.test(message)) {
136
- return null;
137
- }
138
- return "Relay refused the Hermes Link connection. Check Link version and pairing state before retrying.";
139
- }
140
- function abortAll(abortControllers) {
141
- for (const controller of abortControllers.values()) {
142
- controller.abort();
143
- }
144
- abortControllers.clear();
145
- }
146
- function computeBackoffMs(attempt, baseMs, maxMs) {
147
- const exponential = Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt - 1));
148
- const jitter = Math.floor(Math.random() * Math.min(1e3, exponential * 0.2));
149
- return exponential + jitter;
150
- }
151
- async function handleFrame(socket, raw, localPort, abortControllers) {
152
- const frame = JSON.parse(raw);
153
- if (frame.type === "http.cancel") {
154
- abortControllers.get(frame.id)?.abort();
155
- abortControllers.delete(frame.id);
156
- return;
157
- }
158
- if (frame.type !== "http.request") {
159
- return;
160
- }
161
- const abortController = new AbortController();
162
- abortControllers.set(frame.id, abortController);
163
- try {
164
- const response = await fetch(`http://127.0.0.1:${localPort}${frame.path}`, {
165
- method: frame.method,
166
- headers: frame.headers ?? {},
167
- body: frame.bodyBase64 ? Buffer.from(frame.bodyBase64, "base64") : void 0,
168
- signal: abortController.signal
169
- });
170
- const headers = Object.fromEntries(response.headers.entries());
171
- const contentType = response.headers.get("content-type") ?? "";
172
- if (response.body && contentType.includes("text/event-stream")) {
173
- socket.send(JSON.stringify({ type: "http.stream.start", id: frame.id, status: response.status, headers }));
174
- const reader = response.body.getReader();
175
- while (true) {
176
- const next = await reader.read();
177
- if (next.done) {
178
- break;
179
- }
180
- socket.send(JSON.stringify({ type: "http.stream.chunk", id: frame.id, bodyBase64: Buffer.from(next.value).toString("base64") }));
181
- }
182
- socket.send(JSON.stringify({ type: "http.stream.end", id: frame.id }));
183
- return;
184
- }
185
- const body = Buffer.from(await response.arrayBuffer()).toString("base64");
186
- socket.send(JSON.stringify({ type: "http.response", id: frame.id, status: response.status, headers, bodyBase64: body }));
187
- } catch (error) {
188
- const message = error instanceof Error ? error.message : "Relay request failed";
189
- socket.send(JSON.stringify({ type: "http.error", id: frame.id, status: 502, message }));
190
- } finally {
191
- abortControllers.delete(frame.id);
192
- }
193
- }
194
-
195
- // src/daemon/scheduler.ts
196
- function startCronDeliveryScheduler(options) {
197
- let running = false;
198
- const syncCronDeliveries = async () => {
199
- if (running) {
200
- return;
201
- }
202
- running = true;
203
- try {
204
- await syncHermesLinkCronDeliveries(
205
- options.paths,
206
- options.conversations,
207
- options.logger
208
- );
209
- } catch (error) {
210
- void options.logger.warn("cron_link_delivery_sync_failed", {
211
- error: error instanceof Error ? error.message : String(error)
212
- });
213
- } finally {
214
- running = false;
215
- }
216
- };
217
- const timer = setInterval(() => {
218
- void syncCronDeliveries();
219
- }, options.intervalMs ?? 3e4);
220
- timer.unref?.();
221
- return {
222
- close() {
223
- clearInterval(timer);
224
- }
225
- };
226
- }
227
-
228
- // src/daemon/service.ts
229
- async function startLinkService(options = {}) {
230
- const paths = options.paths ?? resolveRuntimePaths();
231
- const logger = createFileLogger({ paths });
232
- const [identity, config] = await Promise.all([loadIdentity(paths), loadConfig(paths)]);
233
- await logger.info("service_starting", {
234
- port: config.port,
235
- mode: identity?.link_id ? "paired" : "local-only"
236
- });
237
- const migration = await migrateLinkDatabase(paths);
238
- if (migration.appliedVersions.length > 0) {
239
- await logger.info("database_migrated", {
240
- database_file: migration.databaseFile,
241
- applied_versions: migration.appliedVersions,
242
- current_version: migration.currentVersion
243
- });
244
- }
245
- const conversations = new ConversationService(paths, logger);
246
- await conversations.rebuildStatisticsIndex();
247
- const app = await createApp({
248
- paths,
249
- logger,
250
- conversations,
251
- onPairingClaimed: options.onPairingClaimed
252
- });
253
- const server = createServer(app.callback());
254
- try {
255
- await listenServer(server, config.port);
256
- } catch (error) {
257
- await logger.error("service_start_failed", {
258
- port: config.port,
259
- error: error instanceof Error ? error.message : String(error)
260
- });
261
- await logger.flush();
262
- throw error;
263
- }
264
- server.on("error", (error) => {
265
- void logger.error("service_error", { error: error.message });
266
- });
267
- void logger.info("service_started", {
268
- port: config.port,
269
- link_id: identity?.link_id ?? null
270
- });
271
- const scheduler = startCronDeliveryScheduler({
272
- paths,
273
- conversations,
274
- logger
275
- });
276
- let relay = null;
277
- if (identity?.link_id) {
278
- relay = connectRelayControl({
279
- relayBaseUrl: config.relayBaseUrl,
280
- linkId: identity.link_id,
281
- localPort: config.port,
282
- maxReconnectAttempts: options.relayMaxReconnectAttempts ?? 5,
283
- backoffBaseMs: 1e3,
284
- backoffMaxMs: 3e4,
285
- onStatus: (status) => {
286
- void logger.info("relay_status", status);
287
- }
288
- });
289
- } else {
290
- void logger.info("relay_skipped", { reason: "link_not_paired" });
291
- }
292
- if (options.writePidFile) {
293
- await writePidFile(paths);
294
- }
295
- return {
296
- async close() {
297
- scheduler.close();
298
- relay?.close();
299
- await closeServer(server);
300
- await logger.info("service_stopped");
301
- await logger.flush();
302
- if (options.writePidFile) {
303
- await rm(pidFilePath(paths), { force: true }).catch(() => void 0);
304
- }
305
- }
306
- };
307
- }
308
- function pidFilePath(paths = resolveRuntimePaths()) {
309
- return `${paths.runDir}/hermeslink.pid`;
310
- }
311
- async function writePidFile(paths) {
312
- await mkdir(paths.runDir, { recursive: true, mode: 448 });
313
- await writeFile(pidFilePath(paths), `${process.pid}
314
- `, { mode: 384 });
315
- }
316
- async function closeServer(server) {
317
- await new Promise((resolve, reject) => {
318
- let settled = false;
319
- let forceCloseTimer;
320
- let timeoutTimer;
321
- const settle = (error) => {
322
- if (settled) {
323
- return;
324
- }
325
- settled = true;
326
- clearTimeout(forceCloseTimer);
327
- clearTimeout(timeoutTimer);
328
- if (error) {
329
- reject(error);
330
- return;
331
- }
332
- resolve();
333
- };
334
- forceCloseTimer = setTimeout(() => {
335
- server.closeIdleConnections?.();
336
- server.closeAllConnections?.();
337
- }, 250);
338
- timeoutTimer = setTimeout(() => {
339
- server.closeAllConnections?.();
340
- settle();
341
- }, 5e3);
342
- server.close((error) => {
343
- if (error) {
344
- settle(error);
345
- return;
346
- }
347
- settle();
348
- });
349
- server.closeIdleConnections?.();
350
- });
351
- }
352
- async function listenServer(server, port) {
353
- await new Promise((resolve, reject) => {
354
- const cleanup = () => {
355
- server.off("error", onError);
356
- server.off("listening", onListening);
357
- };
358
- const onError = (error) => {
359
- cleanup();
360
- reject(error);
361
- };
362
- const onListening = () => {
363
- cleanup();
364
- resolve();
365
- };
366
- server.once("error", onError);
367
- server.once("listening", onListening);
368
- server.listen(port);
369
- });
370
- }
371
-
372
- // src/daemon/process.ts
373
- async function startDaemonProcess(paths = resolveRuntimePaths()) {
374
- const config = await loadConfig(paths);
375
- let status = await getDaemonStatus(paths);
376
- if (status.running) {
377
- const probe = await probeLocalLinkService({ port: config.port, timeoutMs: 500 });
378
- if (probe.reachable) {
379
- return status;
380
- }
381
- await stopDaemonProcess(paths);
382
- status = await getDaemonStatus(paths);
383
- if (status.running) {
384
- return status;
385
- }
386
- }
387
- await mkdir2(paths.logsDir, { recursive: true, mode: 448 });
388
- await mkdir2(paths.runDir, { recursive: true, mode: 448 });
389
- const scriptPath = currentCliScriptPath();
390
- const child = spawn(process.execPath, [scriptPath, "daemon-supervisor"], {
391
- detached: true,
392
- stdio: "ignore",
393
- env: process.env
394
- });
395
- child.unref();
396
- for (let index = 0; index < 12; index += 1) {
397
- await wait(250);
398
- const next = await getDaemonStatus(paths);
399
- if (next.running && (await probeLocalLinkService({ port: config.port, timeoutMs: 500 })).reachable) {
400
- return next;
401
- }
402
- }
403
- return await getDaemonStatus(paths);
404
- }
405
- async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
406
- await mkdir2(paths.logsDir, { recursive: true, mode: 448 });
407
- const log = createRotatingTextLogWriter({
408
- paths,
409
- fileName: path.basename(daemonLogFile(paths))
410
- });
411
- const scriptPath = currentCliScriptPath();
412
- const child = spawn(process.execPath, [scriptPath, "daemon", "--foreground"], {
413
- stdio: ["ignore", "pipe", "pipe"],
414
- env: process.env
415
- });
416
- const write = (chunk) => {
417
- void log.write(chunk);
418
- };
419
- write(`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon supervisor started
420
- `);
421
- child.stdout?.on("data", write);
422
- child.stderr?.on("data", write);
423
- const forwardStop = () => {
424
- if (child.pid && isProcessAlive(child.pid)) {
425
- child.kill("SIGTERM");
426
- }
427
- };
428
- process.once("SIGINT", forwardStop);
429
- process.once("SIGTERM", forwardStop);
430
- const result = await new Promise((resolve, reject) => {
431
- child.once("error", reject);
432
- child.once("exit", (code, signal) => resolve({ code, signal }));
433
- }).catch((error) => {
434
- write(`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon supervisor failed: ${error instanceof Error ? error.message : String(error)}
435
- `);
436
- return { code: 1, signal: null };
437
- });
438
- process.off("SIGINT", forwardStop);
439
- process.off("SIGTERM", forwardStop);
440
- write(
441
- `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon supervisor stopped code=${result.code ?? "null"} signal=${result.signal ?? "null"}
442
- `
443
- );
444
- await log.flush();
445
- return result.code ?? (result.signal ? 0 : 1);
446
- }
447
- async function probeLocalLinkService(options) {
448
- const unreachable = {
449
- reachable: false,
450
- reusable: false,
451
- linkId: null,
452
- version: null
453
- };
454
- let response;
455
- try {
456
- response = await fetch(`http://127.0.0.1:${options.port}/api/v1/bootstrap`, {
457
- headers: { accept: "application/json" },
458
- signal: AbortSignal.timeout(options.timeoutMs ?? 1e3)
459
- });
460
- } catch {
461
- return unreachable;
462
- }
463
- if (!response.ok) {
464
- return unreachable;
465
- }
466
- const payload = await response.json().catch(() => null);
467
- if (!payload || payload.api_version !== 1) {
468
- return unreachable;
469
- }
470
- const linkId = typeof payload.link_id === "string" ? payload.link_id : null;
471
- return {
472
- reachable: true,
473
- reusable: options.linkId ? linkId === options.linkId : true,
474
- linkId,
475
- version: typeof payload.version === "string" ? payload.version : null
476
- };
477
- }
478
- async function stopDaemonProcess(paths = resolveRuntimePaths()) {
479
- const status = await getDaemonStatus(paths);
480
- if (!status.running || !status.pid) {
481
- return status;
482
- }
483
- try {
484
- process.kill(status.pid, "SIGTERM");
485
- } catch {
486
- await rm2(pidFilePath(paths), { force: true }).catch(() => void 0);
487
- return await getDaemonStatus(paths);
488
- }
489
- for (let index = 0; index < 20; index += 1) {
490
- await wait(250);
491
- if (!isProcessAlive(status.pid)) {
492
- break;
493
- }
494
- }
495
- if (isProcessAlive(status.pid)) {
496
- try {
497
- process.kill(status.pid, "SIGKILL");
498
- } catch {
499
- }
500
- for (let index = 0; index < 10; index += 1) {
501
- await wait(250);
502
- if (!isProcessAlive(status.pid)) {
503
- break;
504
- }
505
- }
506
- }
507
- if (!isProcessAlive(status.pid) || !await pidBackedServiceIsReachable(paths)) {
508
- await rm2(pidFilePath(paths), { force: true }).catch(() => void 0);
509
- }
510
- return await getDaemonStatus(paths);
511
- }
512
- async function getDaemonStatus(paths = resolveRuntimePaths()) {
513
- const pidFile = pidFilePath(paths);
514
- const pid = await readPid(pidFile);
515
- if (pid && !isProcessAlive(pid)) {
516
- await rm2(pidFile, { force: true }).catch(() => void 0);
517
- return {
518
- running: false,
519
- pid: null,
520
- pidFile,
521
- logFile: daemonLogFile(paths)
522
- };
523
- }
524
- return {
525
- running: Boolean(pid),
526
- pid,
527
- pidFile,
528
- logFile: daemonLogFile(paths)
529
- };
530
- }
531
- function daemonLogFile(paths = resolveRuntimePaths()) {
532
- return getDaemonLogFile(paths);
533
- }
534
- function currentCliScriptPath() {
535
- return process.argv[1];
536
- }
537
- async function readPid(filePath) {
538
- const raw = await readFile(filePath, "utf8").catch(() => null);
539
- if (!raw) {
540
- return null;
541
- }
542
- const pid = Number.parseInt(raw.trim(), 10);
543
- return Number.isInteger(pid) && pid > 0 ? pid : null;
544
- }
545
- function isProcessAlive(pid) {
546
- try {
547
- process.kill(pid, 0);
548
- return true;
549
- } catch {
550
- return false;
551
- }
552
- }
553
- async function pidBackedServiceIsReachable(paths) {
554
- const config = await loadConfig(paths).catch(() => null);
555
- if (!config) {
556
- return false;
557
- }
558
- return (await probeLocalLinkService({ port: config.port, timeoutMs: 500 })).reachable;
559
- }
560
- function wait(ms) {
561
- return new Promise((resolve) => setTimeout(resolve, ms));
562
- }
563
-
564
- // src/autostart/autostart.ts
40
+ import { promisify } from "util";
565
41
  var execFileAsync = promisify(execFile);
566
42
  var MACOS_LABEL = "com.hermespilot.link";
567
43
  async function enableAutostart() {
@@ -569,14 +45,14 @@ async function enableAutostart() {
569
45
  if (!definition) {
570
46
  return unsupportedStatus();
571
47
  }
572
- await mkdir3(path2.dirname(definition.filePath), { recursive: true, mode: 448 });
573
- await writeFile2(definition.filePath, definition.content, { mode: 384 });
48
+ await mkdir(path.dirname(definition.filePath), { recursive: true, mode: 448 });
49
+ await writeFile(definition.filePath, definition.content, { mode: 384 });
574
50
  if (definition.method === "systemd-user") {
575
- await execFileAsync("systemctl", ["--user", "enable", path2.basename(definition.filePath)]).catch(async () => {
576
- await rm3(definition.filePath, { force: true }).catch(() => void 0);
51
+ await execFileAsync("systemctl", ["--user", "enable", path.basename(definition.filePath)]).catch(async () => {
52
+ await rm(definition.filePath, { force: true }).catch(() => void 0);
577
53
  const fallback = xdgAutostartDefinition();
578
- await mkdir3(path2.dirname(fallback.filePath), { recursive: true, mode: 448 });
579
- await writeFile2(fallback.filePath, fallback.content, { mode: 384 });
54
+ await mkdir(path.dirname(fallback.filePath), { recursive: true, mode: 448 });
55
+ await writeFile(fallback.filePath, fallback.content, { mode: 384 });
580
56
  });
581
57
  }
582
58
  return await getAutostartStatus();
@@ -585,9 +61,9 @@ async function disableAutostart() {
585
61
  const definitions = await allAutostartDefinitions();
586
62
  for (const definition of definitions) {
587
63
  if (definition.method === "systemd-user") {
588
- await execFileAsync("systemctl", ["--user", "disable", path2.basename(definition.filePath)]).catch(() => void 0);
64
+ await execFileAsync("systemctl", ["--user", "disable", path.basename(definition.filePath)]).catch(() => void 0);
589
65
  }
590
- await rm3(definition.filePath, { force: true }).catch(() => void 0);
66
+ await rm(definition.filePath, { force: true }).catch(() => void 0);
591
67
  }
592
68
  return await getAutostartStatus();
593
69
  }
@@ -597,7 +73,7 @@ async function getAutostartStatus() {
597
73
  return unsupportedStatus();
598
74
  }
599
75
  for (const definition of definitions) {
600
- const content = await readFile2(definition.filePath, "utf8").catch(() => null);
76
+ const content = await readFile(definition.filePath, "utf8").catch(() => null);
601
77
  if (content !== null) {
602
78
  return {
603
79
  supported: true,
@@ -648,7 +124,7 @@ async function hasSystemctlUser() {
648
124
  }
649
125
  }
650
126
  function launchdDefinition() {
651
- const filePath = path2.join(os.homedir(), "Library", "LaunchAgents", `${MACOS_LABEL}.plist`);
127
+ const filePath = path.join(os.homedir(), "Library", "LaunchAgents", `${MACOS_LABEL}.plist`);
652
128
  return {
653
129
  method: "launchd",
654
130
  filePath,
@@ -674,7 +150,7 @@ function launchdDefinition() {
674
150
  };
675
151
  }
676
152
  function systemdUserDefinition() {
677
- const filePath = path2.join(os.homedir(), ".config", "systemd", "user", "hermeslink.service");
153
+ const filePath = path.join(os.homedir(), ".config", "systemd", "user", "hermeslink.service");
678
154
  return {
679
155
  method: "systemd-user",
680
156
  filePath,
@@ -693,7 +169,7 @@ WantedBy=default.target
693
169
  };
694
170
  }
695
171
  function xdgAutostartDefinition() {
696
- const filePath = path2.join(os.homedir(), ".config", "autostart", "hermeslink.desktop");
172
+ const filePath = path.join(os.homedir(), ".config", "autostart", "hermeslink.desktop");
697
173
  return {
698
174
  method: "xdg-autostart",
699
175
  filePath,
@@ -707,8 +183,8 @@ X-GNOME-Autostart-enabled=true
707
183
  };
708
184
  }
709
185
  function windowsStartupDefinition() {
710
- const appData = process.env.APPDATA ?? path2.join(os.homedir(), "AppData", "Roaming");
711
- const filePath = path2.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "HermesLink.cmd");
186
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
187
+ const filePath = path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "HermesLink.cmd");
712
188
  return {
713
189
  method: "windows-startup",
714
190
  filePath,
@@ -955,12 +431,12 @@ function parseLanguage(value) {
955
431
 
956
432
  // src/pairing/preflight.ts
957
433
  import { access, stat } from "fs/promises";
958
- import path3 from "path";
434
+ import path2 from "path";
959
435
  async function assertPairingPreflightReady(options = {}) {
960
436
  const profileName = normalizeProfileName(options.profileName);
961
437
  const hermesHome = resolveHermesProfileDir(profileName);
962
438
  const configPath = resolveHermesConfigPath(profileName);
963
- const envPath = path3.join(hermesHome, ".env");
439
+ const envPath = path2.join(hermesHome, ".env");
964
440
  const failures = [];
965
441
  if (!await isDirectory(hermesHome)) {
966
442
  failures.push({