@mycadet/cli 0.12.9 → 0.12.11

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 (55) hide show
  1. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  2. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  3. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  4. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  5. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  6. package/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  7. package/package.json +4 -3
  8. package/src/__tests__/client-web-config.test.ts +0 -1
  9. package/src/__tests__/cloud-login-poll.test.ts +69 -0
  10. package/src/__tests__/devices.test.ts +62 -0
  11. package/src/__tests__/ensure-binary.test.ts +52 -0
  12. package/src/__tests__/hatch-next-steps.test.ts +38 -0
  13. package/src/__tests__/multi-local.test.ts +3 -3
  14. package/src/__tests__/nginx-ingress.test.ts +58 -26
  15. package/src/__tests__/pair.test.ts +149 -78
  16. package/src/__tests__/ps-cloud-status.test.ts +6 -4
  17. package/src/__tests__/setup.test.ts +14 -4
  18. package/src/__tests__/teleport.test.ts +0 -15
  19. package/src/__tests__/tunnel-startup-summary.test.ts +110 -0
  20. package/src/__tests__/tunnel-stop.test.ts +226 -0
  21. package/src/__tests__/tunnel.test.ts +265 -70
  22. package/src/__tests__/uninstall.test.ts +140 -8
  23. package/src/commands/client.ts +4 -6
  24. package/src/commands/devices.ts +66 -30
  25. package/src/commands/hatch.ts +14 -53
  26. package/src/commands/pair.ts +147 -38
  27. package/src/commands/ps.ts +10 -20
  28. package/src/commands/teleport.ts +2 -21
  29. package/src/commands/tunnel.ts +67 -28
  30. package/src/commands/uninstall.ts +106 -71
  31. package/src/commands/upgrade.ts +6 -1
  32. package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
  33. package/src/lib/assistant-config.ts +20 -5
  34. package/src/lib/cloud-client.ts +0 -3
  35. package/src/lib/cloud-login-poll.ts +75 -0
  36. package/src/lib/cloud-login.ts +3 -2
  37. package/src/lib/color.ts +4 -0
  38. package/src/lib/docker.ts +46 -70
  39. package/src/lib/ensure-binary.ts +65 -6
  40. package/src/lib/environments/__tests__/paths.test.ts +27 -1
  41. package/src/lib/environments/paths.ts +16 -8
  42. package/src/lib/environments/resolve.ts +2 -4
  43. package/src/lib/hatch-local.ts +16 -59
  44. package/src/lib/hatch-next-steps.ts +18 -9
  45. package/src/lib/live-voice/tunnel.ts +2 -2
  46. package/src/lib/local.ts +42 -9
  47. package/src/lib/nginx-ingress.ts +124 -24
  48. package/src/lib/step-runner.ts +6 -1
  49. package/src/lib/tunnel.ts +86 -34
  50. package/src/lib/tunnels/cadet.ts +12 -1
  51. package/src/lib/tunnels/cloudflare.ts +36 -1
  52. package/src/lib/tunnels/ngrok.ts +16 -2
  53. package/src/lib/tunnels/process-lifecycle.ts +26 -0
  54. package/src/lib/tunnels/tailscale.ts +79 -1
  55. package/src/lib/xdg-log.ts +11 -0
@@ -0,0 +1,26 @@
1
+ import { clearIngressUrl } from "../ingress-config.js";
2
+ import { stopProcess } from "../process.js";
3
+ import type { ProviderStopOptions } from "../tunnel.js";
4
+
5
+ /** Stop only an identified provider process and retain state on termination failure. */
6
+ export async function stopOwnedTunnelAgent(
7
+ opts: ProviderStopOptions,
8
+ matches: (pid: number) => boolean,
9
+ label: string,
10
+ ): Promise<boolean> {
11
+ let stopped = false;
12
+ if (matches(opts.record.pid)) {
13
+ stopped = await stopProcess(opts.record.pid, "tunnel");
14
+ if (!stopped && matches(opts.record.pid)) {
15
+ throw new Error(
16
+ `Could not stop ${label} process ${opts.record.pid}; saved tunnel state was retained.`,
17
+ );
18
+ }
19
+ }
20
+ if (opts.workspaceDir)
21
+ clearIngressUrl(
22
+ opts.workspaceDir,
23
+ opts.assistantId ?? opts.record.assistantId,
24
+ );
25
+ return stopped;
26
+ }
@@ -1,4 +1,16 @@
1
- import type { ProviderMeta, ProviderTunnelResult } from "../tunnel.js";
1
+ import { z } from "zod";
2
+ import {
3
+ clearRootProcess,
4
+ readRootProcesses,
5
+ stampRootProcess,
6
+ } from "../assistant-config.js";
7
+ import { readProcessCommandLine, stopProcess } from "../process.js";
8
+ import type {
9
+ TunnelProvider,
10
+ ProviderStopOptions,
11
+ ProviderMeta,
12
+ ProviderTunnelResult,
13
+ } from "../tunnel.js";
2
14
 
3
15
  import { spawnSync } from "node:child_process";
4
16
  import { win32 } from "node:path";
@@ -7,6 +19,7 @@ import { GATEWAY_PORT } from "../constants.js";
7
19
  import { ensureBinaryInstalled } from "../ensure-binary.js";
8
20
  import {
9
21
  clearIngressUrl,
22
+ loadRawConfig,
10
23
  getDefaultWorkspaceDir,
11
24
  readLockfileIngressUrl,
12
25
  saveIngressUrl,
@@ -418,6 +431,16 @@ export async function runTailscaleTunnel(
418
431
  );
419
432
  console.log("or stop it later with: tailscale serve --https=443 off");
420
433
 
434
+ const previous = opts.preserveIngressUrl
435
+ ? readRootProcesses().tunnel
436
+ : undefined;
437
+ stampRootProcess("tunnel", {
438
+ ...(previous?.provider !== "tailscale" ? { previous } : {}),
439
+ pid: process.pid,
440
+ provider: "tailscale",
441
+ assistantId: opts.assistantId,
442
+ startedAt: new Date().toISOString(),
443
+ });
421
444
  const teardown = (): void => {
422
445
  console.log(
423
446
  `\nStopping tailscale serve and clearing the ${publishedLabel}...`,
@@ -441,6 +464,7 @@ export async function runTailscaleTunnel(
441
464
  );
442
465
  }
443
466
  if (shouldClearIngressUrl(result)) {
467
+ clearRootProcess("tunnel", process.pid);
444
468
  retractServeUrl(opts, workspaceDir, previousLockfileIngressUrl);
445
469
  } else {
446
470
  console.error(
@@ -475,3 +499,57 @@ export const TAILSCALE_TUNNEL_META: ProviderMeta = {
475
499
  ],
476
500
  supportsReservedDomain: false,
477
501
  };
502
+
503
+ const stoppedTailscaleStateSchema = z.object({
504
+ ingress: z
505
+ .object({
506
+ publicBaseUrl: z.string().optional(),
507
+ pairingTunnel: z.object({ provider: z.string() }).optional(),
508
+ })
509
+ .optional(),
510
+ });
511
+
512
+ export async function stopTailscaleTunnel(
513
+ opts: ProviderStopOptions,
514
+ deps: TailscaleDeps = {
515
+ findBinary: async () => probeTailscaleBinary(),
516
+ run: realRun,
517
+ },
518
+ ): Promise<boolean> {
519
+ const binary = await deps.findBinary();
520
+ if (!binary) throw new Error(getTailscaleInstallMessage());
521
+ const result = stopTailscaleServe(binary, deps);
522
+ if (result.status !== 0)
523
+ throw new Error(`Could not stop tailscale serve: ${joinOutput(result)}`);
524
+ // The serve belongs to tailscaled; only the Cadet supervisor may be signaled.
525
+ let supervisor = false;
526
+ try {
527
+ const command = readProcessCommandLine(opts.record.pid);
528
+ supervisor =
529
+ /cadet/i.test(command) && /(?:^|\s)tunnel(?:\s|$)/.test(command);
530
+ } catch {
531
+ /* The supervisor may already have exited. */
532
+ }
533
+ if (supervisor && opts.record.pid !== process.pid)
534
+ await stopProcess(opts.record.pid, "tunnel supervisor");
535
+ if (opts.workspaceDir) {
536
+ const state = stoppedTailscaleStateSchema.parse(
537
+ loadRawConfig(opts.workspaceDir),
538
+ ).ingress;
539
+ const assistantId = opts.assistantId ?? opts.record.assistantId;
540
+ retractServeUrl(
541
+ {
542
+ assistantId,
543
+ preserveIngressUrl: state?.pairingTunnel?.provider === "tailscale",
544
+ },
545
+ opts.workspaceDir,
546
+ state?.publicBaseUrl ?? null,
547
+ );
548
+ }
549
+ return true;
550
+ }
551
+
552
+ export const TAILSCALE_PROVIDER: TunnelProvider = {
553
+ run: runTailscaleTunnel,
554
+ stop: stopTailscaleTunnel,
555
+ };
@@ -142,3 +142,14 @@ export function pipeToLogFile(
142
142
  stream.on("error", onDone);
143
143
  }
144
144
  }
145
+
146
+ /** Runtime output belongs to the instance, alongside its service state. */
147
+ export function openRuntimeLogFile(instanceDir: string, service: "assistant" | "gateway"): number | "ignore" {
148
+ try {
149
+ const dir = join(instanceDir, "logs");
150
+ mkdirSync(dir, { recursive: true });
151
+ return openSync(join(dir, `${service}-stdout.log`), "a", 0o600);
152
+ } catch {
153
+ return "ignore";
154
+ }
155
+ }