linkshell-cli 0.6.1 → 0.6.3

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 (108) hide show
  1. package/README.md +47 -132
  2. package/dist/cli/src/auth.js +1 -1
  3. package/dist/cli/src/auth.js.map +1 -1
  4. package/dist/cli/src/commands/doctor.d.ts +1 -1
  5. package/dist/cli/src/commands/doctor.js +76 -36
  6. package/dist/cli/src/commands/doctor.js.map +1 -1
  7. package/dist/cli/src/commands/host.d.ts +15 -0
  8. package/dist/cli/src/commands/host.js +101 -20
  9. package/dist/cli/src/commands/host.js.map +1 -1
  10. package/dist/cli/src/commands/logout.d.ts +1 -1
  11. package/dist/cli/src/commands/logout.js +10 -2
  12. package/dist/cli/src/commands/logout.js.map +1 -1
  13. package/dist/cli/src/commands/upgrade.js +11 -0
  14. package/dist/cli/src/commands/upgrade.js.map +1 -1
  15. package/dist/cli/src/config.js +1 -1
  16. package/dist/cli/src/config.js.map +1 -1
  17. package/dist/cli/src/index.js +105 -120
  18. package/dist/cli/src/index.js.map +1 -1
  19. package/dist/cli/src/utils/daemon.d.ts +2 -0
  20. package/dist/cli/src/utils/daemon.js +4 -0
  21. package/dist/cli/src/utils/daemon.js.map +1 -1
  22. package/dist/cli/tsconfig.tsbuildinfo +1 -1
  23. package/dist/host/src/carry.d.ts +15 -0
  24. package/dist/host/src/carry.js +83 -0
  25. package/dist/host/src/carry.js.map +1 -0
  26. package/dist/host/src/drivers/acp/driver.d.ts +21 -3
  27. package/dist/host/src/drivers/acp/driver.js +99 -13
  28. package/dist/host/src/drivers/acp/driver.js.map +1 -1
  29. package/dist/host/src/drivers/acp/mapper.d.ts +5 -1
  30. package/dist/host/src/drivers/acp/mapper.js +58 -6
  31. package/dist/host/src/drivers/acp/mapper.js.map +1 -1
  32. package/dist/host/src/drivers/claude/driver.d.ts +76 -8
  33. package/dist/host/src/drivers/claude/driver.js +510 -29
  34. package/dist/host/src/drivers/claude/driver.js.map +1 -1
  35. package/dist/host/src/drivers/claude/holders.d.ts +9 -0
  36. package/dist/host/src/drivers/claude/holders.js +61 -0
  37. package/dist/host/src/drivers/claude/holders.js.map +1 -0
  38. package/dist/host/src/drivers/claude/transcript.d.ts +105 -61
  39. package/dist/host/src/drivers/claude/transcript.js +260 -22
  40. package/dist/host/src/drivers/claude/transcript.js.map +1 -1
  41. package/dist/host/src/drivers/codex/commands.d.ts +27 -0
  42. package/dist/host/src/drivers/codex/commands.js +23 -0
  43. package/dist/host/src/drivers/codex/commands.js.map +1 -0
  44. package/dist/host/src/drivers/codex/driver.d.ts +21 -1
  45. package/dist/host/src/drivers/codex/driver.js +140 -1
  46. package/dist/host/src/drivers/codex/driver.js.map +1 -1
  47. package/dist/host/src/drivers/codex/mapper.d.ts +4 -0
  48. package/dist/host/src/drivers/codex/mapper.js +29 -5
  49. package/dist/host/src/drivers/codex/mapper.js.map +1 -1
  50. package/dist/host/src/drivers/types.d.ts +45 -4
  51. package/dist/host/src/fs.d.ts +26 -4
  52. package/dist/host/src/fs.js +71 -17
  53. package/dist/host/src/fs.js.map +1 -1
  54. package/dist/host/src/gateway.d.ts +3 -0
  55. package/dist/host/src/gateway.js +10 -1
  56. package/dist/host/src/gateway.js.map +1 -1
  57. package/dist/host/src/host.d.ts +7 -3
  58. package/dist/host/src/host.js +53 -21
  59. package/dist/host/src/host.js.map +1 -1
  60. package/dist/host/src/hub.d.ts +98 -14
  61. package/dist/host/src/hub.js +639 -27
  62. package/dist/host/src/hub.js.map +1 -1
  63. package/dist/host/src/index.d.ts +1 -1
  64. package/dist/host/src/index.js +1 -1
  65. package/dist/host/src/index.js.map +1 -1
  66. package/dist/host/src/legacy.d.ts +7 -0
  67. package/dist/host/src/legacy.js +36 -0
  68. package/dist/host/src/legacy.js.map +1 -0
  69. package/dist/host/src/rpc/server.d.ts +4 -2
  70. package/dist/host/src/rpc/server.js +62 -5
  71. package/dist/host/src/rpc/server.js.map +1 -1
  72. package/dist/host/src/screen.d.ts +11 -0
  73. package/dist/host/src/screen.js +54 -2
  74. package/dist/host/src/screen.js.map +1 -1
  75. package/dist/host/src/shell-env.d.ts +6 -0
  76. package/dist/host/src/shell-env.js +48 -0
  77. package/dist/host/src/shell-env.js.map +1 -1
  78. package/dist/host/src/slim.d.ts +17 -0
  79. package/dist/host/src/slim.js +66 -0
  80. package/dist/host/src/slim.js.map +1 -0
  81. package/dist/host/src/store.d.ts +81 -3
  82. package/dist/host/src/store.js +249 -16
  83. package/dist/host/src/store.js.map +1 -1
  84. package/dist/host/src/terminals.d.ts +4 -4
  85. package/dist/host/src/terminals.js +19 -4
  86. package/dist/host/src/terminals.js.map +1 -1
  87. package/dist/host/src/worktrees.d.ts +36 -0
  88. package/dist/host/src/worktrees.js +96 -0
  89. package/dist/host/src/worktrees.js.map +1 -0
  90. package/dist/wire/src/model.d.ts +334 -139
  91. package/dist/wire/src/model.js +68 -0
  92. package/dist/wire/src/model.js.map +1 -1
  93. package/dist/wire/src/relay-client.d.ts +2 -0
  94. package/dist/wire/src/relay-client.js +12 -0
  95. package/dist/wire/src/relay-client.js.map +1 -1
  96. package/dist/wire/src/rpc.d.ts +7273 -890
  97. package/dist/wire/src/rpc.js +154 -6
  98. package/dist/wire/src/rpc.js.map +1 -1
  99. package/dist/wire/src/updates.d.ts +498 -498
  100. package/package.json +5 -5
  101. package/src/auth.ts +1 -1
  102. package/src/commands/doctor.ts +76 -34
  103. package/src/commands/host.ts +109 -19
  104. package/src/commands/logout.ts +10 -2
  105. package/src/commands/upgrade.ts +11 -0
  106. package/src/config.ts +1 -1
  107. package/src/index.ts +104 -137
  108. package/src/utils/daemon.ts +5 -0
package/src/index.ts CHANGED
@@ -51,7 +51,7 @@ const program = new Command();
51
51
  program
52
52
  .name("linkshell")
53
53
  .description(
54
- "Bridge a local terminal session to a remote gateway. Launch any CLI (Claude/Codex/etc.) manually inside the shell.",
54
+ "Your coding agents and terminals, on your phone. Run `linkshell host --daemon`, then `linkshell pair` (or `linkshell login` for the official gateway).",
55
55
  )
56
56
  .version(pkg.version);
57
57
 
@@ -65,8 +65,10 @@ const hostCmd = program
65
65
  .option("--gateway <url>", "Reach this computer through a v2 gateway (saved; 'off' to disable, 'default' for the official one when logged in)")
66
66
  .option("--_foreground-host", undefined) // internal
67
67
  .action(async (options) => {
68
- const { runHostForeground, ensureHostRunning, readHostConfig, writeHostConfig, assertHostRuntime } = await import("./commands/host.js");
68
+ const { runHostForeground, ensureHostRunning, readHostConfig, writeHostConfig, assertHostRuntime, silenceSqliteWarning, withRunningHost, refreshGateway, describeGateway } =
69
+ await import("./commands/host.js");
69
70
  assertHostRuntime();
71
+ silenceSqliteWarning();
70
72
  if (options.devPort) process.env.LINKSHELL_DEV_PORT = String(options.devPort);
71
73
  if (options.gateway) {
72
74
  const { defaultHome } = await import("@linkshell/host");
@@ -74,15 +76,30 @@ const hostCmd = program
74
76
  // "off" is remembered, so being logged in doesn't turn the official gateway back on.
75
77
  writeHostConfig(home, { ...readHostConfig(home), gateway: options.gateway === "default" ? undefined : options.gateway });
76
78
  }
79
+ const running = options._foregroundHost ? undefined : await withRunningHost((client) => client.call("machine.info", {}));
77
80
  if (options.daemon && !options._foregroundHost) {
78
81
  const socket = await ensureHostRunning();
79
82
  const daemon = await import("./utils/daemon.js");
80
83
  process.stderr.write(`\n LinkShell host running (${socket})\n`);
84
+ if (running && running.hostVersion !== pkg.version) {
85
+ process.stderr.write(` It is still ${running.hostVersion}; this CLI is ${pkg.version}. To update it (stops running agent turns):\n`);
86
+ process.stderr.write(` linkshell host stop && linkshell host --daemon\n`);
87
+ }
88
+ // A gateway given to a host that was already running applies now.
89
+ if (options.gateway && running) {
90
+ const gateway = await refreshGateway(10_000);
91
+ if (gateway) process.stderr.write(` Gateway: ${describeGateway(gateway)}\n`);
92
+ }
81
93
  process.stderr.write(` Log: ${daemon.getLogFile("host")}\n`);
82
94
  process.stderr.write(` Status: linkshell host status\n`);
83
95
  process.stderr.write(` Stop: linkshell host stop\n\n`);
84
96
  return;
85
97
  }
98
+ if (running && options.gateway) {
99
+ const gateway = await refreshGateway(10_000);
100
+ if (gateway) process.stderr.write(`\n Gateway: ${describeGateway(gateway)}\n\n`);
101
+ return;
102
+ }
86
103
  await runHostForeground(pkg.version);
87
104
  });
88
105
 
@@ -110,6 +127,57 @@ program
110
127
  await runPair();
111
128
  });
112
129
 
130
+ const devicesCmd = program
131
+ .command("devices")
132
+ .description("List the phones paired with this computer")
133
+ .action(async () => {
134
+ const { withRunningHost, assertHostRuntime } = await import("./commands/host.js");
135
+ assertHostRuntime();
136
+ const gateway = await withRunningHost((client) => client.call("gateway.status", {}));
137
+ if (!gateway) {
138
+ process.stderr.write(" LinkShell host is not running. Start it with: linkshell host --daemon\n");
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ if (gateway.devices.length === 0) {
143
+ process.stdout.write(" No paired devices. (Devices signed in to your account don't need pairing.)\n");
144
+ return;
145
+ }
146
+ for (const device of gateway.devices) {
147
+ const paired = new Date(device.pairedAt).toLocaleDateString();
148
+ process.stdout.write(` ${device.online ? "●" : "○"} ${device.name} paired ${paired} ${device.id.slice(0, 8)}\n`);
149
+ }
150
+ process.stdout.write("\n Remove one with: linkshell devices remove <name or id>\n");
151
+ });
152
+
153
+ devicesCmd
154
+ .command("remove <device>")
155
+ .description("Unpair a phone: it can no longer reach this computer")
156
+ .action(async (wanted: string) => {
157
+ const { withRunningHost, assertHostRuntime } = await import("./commands/host.js");
158
+ assertHostRuntime();
159
+ const result = await withRunningHost(async (client) => {
160
+ const { devices } = await client.call("gateway.status", {});
161
+ const matches = devices.filter((device) => device.id.startsWith(wanted) || device.name.toLowerCase() === wanted.toLowerCase());
162
+ if (matches.length !== 1) return { matches };
163
+ await client.call("devices.revoke", { deviceId: matches[0]!.id });
164
+ return { removed: matches[0]! };
165
+ });
166
+ if (!result) {
167
+ process.stderr.write(" LinkShell host is not running. Start it with: linkshell host --daemon\n");
168
+ process.exitCode = 1;
169
+ } else if (result.removed) {
170
+ process.stdout.write(` Unpaired ${result.removed.name}.\n`);
171
+ } else {
172
+ process.stderr.write(
173
+ result.matches.length === 0
174
+ ? ` No paired device named "${wanted}". See: linkshell devices\n`
175
+ : ` "${wanted}" matches ${result.matches.length} devices; use the id shown by: linkshell devices\n`,
176
+ );
177
+ process.exitCode = 1;
178
+ }
179
+ });
180
+
113
181
  // `linkshell codex …` / `linkshell claude …` start the agent's own TUI attached
114
182
  // to the host, so the session is live here and on every paired device.
115
183
  // All arguments go to the agent.
@@ -133,7 +201,7 @@ for (const [agent, description] of [
133
201
 
134
202
  program
135
203
  .command("start")
136
- .description("Start a bridge session (with built-in or remote gateway)")
204
+ .description("Start a v1 bridge session (for the v1 app; with built-in or remote gateway)")
137
205
  .option(
138
206
  "--gateway <url>",
139
207
  "Gateway websocket URL (omit to start built-in gateway)",
@@ -411,20 +479,25 @@ const gatewayCmd = program
411
479
  const { startEmbeddedGateway } =
412
480
  await import("@linkshell/gateway/embedded");
413
481
  const port = Number(options.port);
482
+ const { join } = await import("node:path");
483
+ (await import("./commands/host.js")).silenceSqliteWarning();
414
484
  const gw = await startEmbeddedGateway({
415
485
  port,
416
486
  logLevel: options.logLevel,
417
487
  silent: false,
488
+ // v2 pairings live here; keep this file to keep phones paired.
489
+ relayDataPath: join(daemon.linkshellDir(), "relay.db"),
418
490
  });
419
491
 
420
492
  daemon.savePid("gateway", process.pid);
421
493
 
422
- process.stderr.write(`\n LinkShell Gateway v0.1.8\n`);
494
+ process.stderr.write(`\n LinkShell Gateway ${pkg.version}\n`);
423
495
  process.stderr.write(` Listening on http://0.0.0.0:${gw.port}\n`);
424
496
  process.stderr.write(` PID: ${process.pid}\n`);
425
497
  process.stderr.write(` Log level: ${options.logLevel}\n\n`);
498
+ process.stderr.write(` Computers (v2): linkshell host --gateway ws://this-server:${gw.port}\n`);
426
499
  process.stderr.write(
427
- ` Clients connect via: ws://your-server:${gw.port}/ws\n`,
500
+ ` v1 clients: ws://this-server:${gw.port}/ws\n`,
428
501
  );
429
502
  process.stderr.write(
430
503
  ` Health check: curl http://your-server:${gw.port}/healthz\n\n`,
@@ -476,11 +549,13 @@ program
476
549
  .description("Stop all running LinkShell processes")
477
550
  .action(async () => {
478
551
  const { stopDaemon } = await import("./utils/daemon.js");
552
+ const hostStopped = stopDaemon("host");
479
553
  const bridgeStopped = stopDaemon("bridge");
480
554
  const gatewayStopped = stopDaemon("gateway");
481
- if (bridgeStopped) process.stderr.write(" Bridge stopped.\n");
482
- if (gatewayStopped) process.stderr.write(" Gateway stopped.\n");
483
- if (!bridgeStopped && !gatewayStopped) {
555
+ if (hostStopped) process.stderr.write(" Host stopped.\n");
556
+ if (bridgeStopped) process.stderr.write(" v1 bridge stopped.\n");
557
+ if (gatewayStopped) process.stderr.write(" Gateway server stopped.\n");
558
+ if (!hostStopped && !bridgeStopped && !gatewayStopped) {
484
559
  process.stderr.write(" No running processes found.\n");
485
560
  }
486
561
  });
@@ -489,47 +564,36 @@ program
489
564
 
490
565
  program
491
566
  .command("status")
492
- .description("Show status of all LinkShell processes")
567
+ .description("Show the host, its gateway connection, and anything else LinkShell runs here")
493
568
  .action(async () => {
494
- const { readPid, getLogFile, readMetadata } = await import(
495
- "./utils/daemon.js"
496
- );
497
- const bridgePid = readPid("bridge");
569
+ const { readPid, getLogFile, readMetadata } = await import("./utils/daemon.js");
570
+ const { printHostStatus, assertHostRuntime } = await import("./commands/host.js");
571
+ assertHostRuntime();
572
+ process.stdout.write("\n");
573
+ await printHostStatus();
498
574
  const gatewayPid = readPid("gateway");
499
- const bridgeMeta = bridgePid ? readMetadata("bridge") : null;
500
-
501
- process.stderr.write("\n");
575
+ if (gatewayPid) process.stdout.write(` Gateway server: running (PID ${gatewayPid}), log ${getLogFile("gateway")}\n`);
576
+ const bridgePid = readPid("bridge");
502
577
  if (bridgePid) {
503
- process.stderr.write(` Bridge: running (PID ${bridgePid})\n`);
504
- process.stderr.write(
505
- ` Keep awake: ${bridgeMeta?.keepAwake ? "enabled" : "disabled"}\n`,
506
- );
507
- process.stderr.write(` Log: ${getLogFile("bridge")}\n`);
508
- } else {
509
- process.stderr.write(" Bridge: not running\n");
510
- }
511
- if (gatewayPid) {
512
- process.stderr.write(` Gateway: running (PID ${gatewayPid})\n`);
513
- process.stderr.write(` Log: ${getLogFile("gateway")}\n`);
514
- } else {
515
- process.stderr.write(" Gateway: not running\n");
578
+ const keepAwake = readMetadata("bridge")?.keepAwake ? ", keeps this computer awake" : "";
579
+ process.stdout.write(` v1 bridge: running (PID ${bridgePid}${keepAwake}), log ${getLogFile("bridge")}\n`);
516
580
  }
517
- process.stderr.write("\n");
581
+ process.stdout.write("\n");
518
582
  });
519
583
 
520
584
  // ── doctor / setup ──────────────────────────────────────────────────
521
585
 
522
586
  program
523
587
  .command("doctor")
524
- .description("Check your environment and connectivity")
525
- .option("--gateway <url>", "Gateway URL to test", config.gateway)
588
+ .description("Check that this computer is ready: Node, agents, the host and its gateway")
589
+ .option("--gateway <url>", "Also check that this gateway answers")
526
590
  .action(async (options) => {
527
- await runDoctor(options.gateway);
591
+ await runDoctor(pkg.version, options.gateway);
528
592
  });
529
593
 
530
594
  program
531
595
  .command("setup")
532
- .description("Interactive setup wizard")
596
+ .description("Configure the v1 bridge (`linkshell start`)")
533
597
  .action(async () => {
534
598
  await runSetup();
535
599
  });
@@ -543,123 +607,26 @@ program
543
607
 
544
608
  program
545
609
  .command("login")
546
- .description("Log in to LinkShell (enables premium gateway)")
610
+ .description("Log in to LinkShell (Pro: the official gateway, no pairing needed)")
547
611
  .action(async () => {
548
612
  const result = await runLogin();
549
613
  if (!result) return;
550
614
 
551
- if (result.plan !== "pro") {
552
- process.stderr.write(
553
- " Upgrade to Pro for official gateway access: https://itool.tech\n\n",
554
- );
555
- return;
556
- }
557
-
558
- // Pro user — fetch official gateways and offer to connect
559
- const { SUPABASE_URL, SUPABASE_ANON_KEY } = await import("./auth.js");
560
- let gateways: { url: string; name: string; region: string | null }[] = [];
561
- try {
562
- const res = await fetch(
563
- `${SUPABASE_URL}/rest/v1/linkshell_official_gateways?enabled=eq.true&select=url,name,region`,
564
- {
565
- headers: {
566
- Authorization: `Bearer ${result.accessToken}`,
567
- apikey: SUPABASE_ANON_KEY,
568
- },
569
- signal: AbortSignal.timeout(10_000),
570
- },
571
- );
572
- if (res.ok) {
573
- gateways = (await res.json()) as typeof gateways;
574
- }
575
- } catch {}
576
-
577
- if (gateways.length === 0) {
578
- process.stderr.write(" No official gateways available yet.\n\n");
579
- return;
580
- }
581
-
582
- process.stderr.write(" Available gateways:\n\n");
583
- for (let i = 0; i < gateways.length; i++) {
584
- const gw = gateways[i]!;
585
- const label = gw.region ? `${gw.name} (${gw.region})` : gw.name;
586
- process.stderr.write(` [${i + 1}] ${label} ${gw.url}\n`);
587
- }
588
- process.stderr.write(` [0] Skip\n\n`);
589
-
590
- const { createInterface } = await import("node:readline");
591
- const rl = createInterface({ input: process.stdin, output: process.stderr });
592
- const answer = await new Promise<string>((res) => {
593
- rl.question(" Connect to gateway [1]: ", (ans) => {
594
- rl.close();
595
- res(ans.trim());
596
- });
597
- });
598
-
599
- if (answer === "0") return;
600
-
601
- const idx = (answer === "" ? 1 : Number(answer)) - 1;
602
- if (idx < 0 || idx >= gateways.length || Number.isNaN(idx)) {
603
- process.stderr.write(" Invalid choice.\n\n");
604
- return;
605
- }
606
-
607
- const chosen = gateways[idx]!;
608
- const gwWsUrl = chosen.url.replace(/\/$/, "").replace(/^https:/, "wss:").replace(/^http:/, "ws:") + "/ws";
609
-
610
- process.stderr.write(`\n Connecting to ${chosen.name}...\n`);
611
-
612
- // Start daemon with the chosen gateway
613
- const daemon = await import("./utils/daemon.js");
614
- const keepAwake = shouldKeepAwake(undefined);
615
- const existingPid = daemon.readPid("bridge");
616
- if (existingPid) {
617
- process.stderr.write(
618
- ` Bridge already running (PID ${existingPid}). Run: linkshell stop\n\n`,
619
- );
620
- return;
621
- }
622
-
623
- const childArgs = [
624
- "start",
625
- "--_foreground-bridge",
626
- "--gateway", gwWsUrl,
627
- "--provider", "custom",
628
- "--client-name", config.clientName ?? "local-cli",
629
- "--cols", String(config.cols ?? 120),
630
- "--rows", String(config.rows ?? 36),
631
- "--screen",
632
- "--agent-ui",
633
- ];
634
- if (config.command) childArgs.push("--command", config.command);
635
- if (config.hostname) childArgs.push("--hostname", config.hostname);
636
- if (!keepAwake) childArgs.push("--no-keep-awake");
637
-
638
- const pid = daemon.spawnDaemon("bridge", childArgs);
639
- daemon.saveMetadata("bridge", { keepAwake, startedAt: Date.now() });
640
- process.stderr.write(`\n \x1b[32m✓\x1b[0m Bridge started in background (PID ${pid})\n`);
641
- process.stderr.write(` Gateway: ${chosen.name}\n`);
642
- process.stderr.write(` Provider: custom (shell)\n`);
643
- process.stderr.write(` Keep awake: ${keepAwake ? "enabled" : "disabled"}\n`);
644
- process.stderr.write(` Desktop: enabled\n`);
645
- process.stderr.write(` Agent GUI: enabled (Codex / Claude auto-detect)\n`);
646
- process.stderr.write(` Open the LinkShell app on your phone to connect.\n\n`);
647
- process.stderr.write(` Stop: linkshell stop\n`);
648
- process.stderr.write(` Status: linkshell status\n`);
649
- process.stderr.write(` Logs: tail -f ${daemon.getLogFile("bridge")}\n\n`);
615
+ const { joinGatewayAfterLogin } = await import("./commands/host.js");
616
+ await joinGatewayAfterLogin(result.plan);
650
617
  process.exit(0);
651
618
  });
652
619
 
653
620
  program
654
621
  .command("logout")
655
622
  .description("Log out of LinkShell")
656
- .action(() => {
657
- runLogout();
623
+ .action(async () => {
624
+ await runLogout();
658
625
  });
659
626
 
660
627
  program
661
628
  .command("list")
662
- .description("List your sessions on official gateways")
629
+ .description("List your v1 sessions on official gateways")
663
630
  .action(async () => {
664
631
  const { getValidToken, loadAuth, SUPABASE_URL, SUPABASE_ANON_KEY } =
665
632
  await import("./auth.js");
@@ -24,6 +24,11 @@ function metadataFile(service: ServiceName): string {
24
24
  return join(LINKSHELL_DIR, `${service}.json`);
25
25
  }
26
26
 
27
+ /** ~/.linkshell, or LINKSHELL_HOME. */
28
+ export function linkshellDir(): string {
29
+ return LINKSHELL_DIR;
30
+ }
31
+
27
32
  export function savePid(service: ServiceName, pid: number): void {
28
33
  mkdirSync(LINKSHELL_DIR, { recursive: true });
29
34
  writeFileSync(pidFile(service), String(pid), "utf8");