@mycadet/cli 0.12.6-dev.202610082233.54244ee → 0.12.7

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 (89) hide show
  1. package/eslint.config.mjs +7 -0
  2. package/node_modules/@mycadet/local-mode/src/__tests__/environment.test.ts +1 -4
  3. package/node_modules/@mycadet/local-mode/src/config.ts +14 -19
  4. package/node_modules/@mycadet/local-mode/src/index.ts +5 -0
  5. package/node_modules/@mycadet/local-mode/src/local-signing-key.ts +73 -0
  6. package/node_modules/@mycadet/local-mode/src/lockfile.ts +18 -3
  7. package/package.json +4 -3
  8. package/src/__tests__/assistant-config.test.ts +30 -28
  9. package/src/__tests__/debug.test.ts +8 -8
  10. package/src/__tests__/default-main-screen-formatting.test.ts +5 -4
  11. package/src/__tests__/flags.test.ts +2 -2
  12. package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
  13. package/src/__tests__/ingress-config.test.ts +2 -2
  14. package/src/__tests__/login-loopback.test.ts +1 -1
  15. package/src/__tests__/nginx-ingress.test.ts +68 -76
  16. package/src/__tests__/pair.test.ts +135 -121
  17. package/src/__tests__/platform-client.test.ts +2 -2
  18. package/src/__tests__/recover.test.ts +96 -3
  19. package/src/__tests__/retire.test.ts +5 -3
  20. package/src/__tests__/signing-key-continuity.test.ts +261 -0
  21. package/src/__tests__/sleep.test.ts +189 -6
  22. package/src/__tests__/tailscale-tunnel.test.ts +1 -1
  23. package/src/__tests__/uninstall.test.ts +171 -0
  24. package/src/__tests__/upgrade-local.test.ts +16 -3
  25. package/src/__tests__/use.test.ts +2 -2
  26. package/src/__tests__/wake-error.test.ts +76 -0
  27. package/src/__tests__/wake.test.ts +309 -36
  28. package/src/commands/backup.ts +6 -2
  29. package/src/commands/clean.ts +6 -2
  30. package/src/commands/client.ts +91 -41
  31. package/src/commands/confirm.ts +6 -2
  32. package/src/commands/connect.ts +6 -2
  33. package/src/commands/debug.ts +6 -2
  34. package/src/commands/devices.ts +6 -2
  35. package/src/commands/events.ts +6 -2
  36. package/src/commands/exec.ts +50 -12
  37. package/src/commands/flags.ts +6 -2
  38. package/src/commands/gateway.ts +6 -2
  39. package/src/commands/hatch.ts +6 -2
  40. package/src/commands/login.ts +26 -356
  41. package/src/commands/logs.ts +6 -2
  42. package/src/commands/message.ts +6 -2
  43. package/src/commands/pair.ts +6 -2
  44. package/src/commands/ps.ts +7 -2
  45. package/src/commands/recover.ts +21 -16
  46. package/src/commands/restore.ts +6 -2
  47. package/src/commands/retire.ts +6 -2
  48. package/src/commands/roadmap.ts +6 -2
  49. package/src/commands/rollback.ts +6 -2
  50. package/src/commands/setup.ts +76 -9
  51. package/src/commands/sleep.ts +131 -31
  52. package/src/commands/ssh.ts +6 -2
  53. package/src/commands/teleport.ts +6 -2
  54. package/src/commands/terminal.ts +6 -2
  55. package/src/commands/tunnel.ts +6 -2
  56. package/src/commands/uninstall.ts +228 -0
  57. package/src/commands/unpair.ts +6 -2
  58. package/src/commands/upgrade.ts +13 -8
  59. package/src/commands/use.ts +6 -2
  60. package/src/commands/voice.ts +6 -2
  61. package/src/commands/wake.ts +69 -37
  62. package/src/commands/workflows.ts +6 -2
  63. package/src/components/DefaultMainScreen.tsx +250 -36
  64. package/src/index.ts +23 -55
  65. package/src/lib/assistant-config.ts +27 -13
  66. package/src/lib/backup-ops.ts +3 -4
  67. package/src/lib/cli-error.ts +5 -0
  68. package/src/lib/client-identity.ts +8 -10
  69. package/src/lib/cloud-login.ts +422 -0
  70. package/src/lib/color.ts +16 -0
  71. package/src/lib/default-main-screen-formatting.ts +32 -8
  72. package/src/lib/environments/__tests__/paths.test.ts +9 -11
  73. package/src/lib/environments/paths.ts +2 -20
  74. package/src/lib/guardian-token.ts +72 -18
  75. package/src/lib/hatch-local.ts +52 -1
  76. package/src/lib/health-check.ts +24 -15
  77. package/src/lib/lifecycle-reporter.ts +9 -1
  78. package/src/lib/local-hatch-lock.ts +19 -27
  79. package/src/lib/local-runtime-client.ts +25 -14
  80. package/src/lib/local.ts +17 -43
  81. package/src/lib/ngrok.ts +48 -16
  82. package/src/lib/platform-client.ts +141 -72
  83. package/src/lib/retire-archive.ts +4 -4
  84. package/src/lib/retire-local.ts +2 -25
  85. package/src/lib/teleport-backup.ts +23 -9
  86. package/src/lib/terminal-client.ts +21 -9
  87. package/src/lib/tunnel-edge.ts +11 -13
  88. package/src/lib/upgrade-lifecycle.ts +16 -4
  89. package/src/lib/workos-pkce.ts +52 -21
@@ -10,6 +10,7 @@ import {
10
10
  getActiveAssistant,
11
11
  lookupAssistantByIdentifier,
12
12
  resolveAssistant,
13
+ resolveConversationKey,
13
14
  saveAssistantEntry,
14
15
  type AssistantEntry,
15
16
  } from "../lib/assistant-config";
@@ -76,9 +77,47 @@ import { relaunchDetached } from "../lib/detached-process.js";
76
77
  import { probePort } from "../lib/port-probe.js";
77
78
  import { openBrowser } from "../lib/open-browser";
78
79
  import { findWebDistDir } from "../lib/web-dist.js";
80
+ import { z } from "zod";
81
+
82
+ const AssistantIdBodySchema = z.object({
83
+ assistantId: z.string().optional(),
84
+ });
85
+
86
+ const TokenBodySchema = z.object({ token: z.unknown() }).nullable();
87
+
88
+ const SyncPlatformBodySchema = z.object({
89
+ syncPlatform: z.unknown().optional(),
90
+ platformAssistants: z.array(z.record(z.string(), z.unknown())).optional(),
91
+ organizationId: z.string().optional(),
92
+ rename: z
93
+ .object({ assistantId: z.string(), name: z.string() })
94
+ .optional(),
95
+ assistant: z.record(z.string(), z.unknown()).optional(),
96
+ activeAssistant: z.string().optional(),
97
+ });
98
+
99
+ const RemoteBodySchema = z.object({ remote: z.string().optional() });
100
+
101
+ const UnpairBodySchema = z.object({
102
+ assistantId: z.unknown().optional(),
103
+ hashedDeviceId: z.unknown().optional(),
104
+ });
105
+
106
+ const DeviceBodySchema = z.object({
107
+ address: z.unknown().optional(),
108
+ handle: z.unknown().optional(),
109
+ name: z.unknown().optional(),
110
+ });
111
+
112
+ function errnoCode(error: unknown): string | undefined {
113
+ if (!(error instanceof Error)) return undefined;
114
+ if (!("code" in error) || typeof error.code !== "string") return undefined;
115
+ return error.code;
116
+ }
79
117
 
80
118
  const SUPPORTED_INTERFACES = ["cli", "web"] as const;
81
119
  type SupportedInterface = (typeof SUPPORTED_INTERFACES)[number];
120
+ const SupportedInterfaceSchema = z.enum(SUPPORTED_INTERFACES);
82
121
 
83
122
  const ANSI = {
84
123
  reset: "\x1b[0m",
@@ -298,13 +337,14 @@ export function parseArgs(): ParsedArgs {
298
337
  assistantName = undefined;
299
338
  } else if ((flag === "--interface" || flag === "-i") && flagArgs[i + 1]) {
300
339
  const value = flagArgs[++i];
301
- if (!(SUPPORTED_INTERFACES as readonly string[]).includes(value)) {
340
+ const parsedInterface = SupportedInterfaceSchema.safeParse(value);
341
+ if (!parsedInterface.success) {
302
342
  console.error(
303
343
  `Unknown interface '${value}'. Supported: ${SUPPORTED_INTERFACES.join(", ")}.`,
304
344
  );
305
345
  process.exit(1);
306
346
  }
307
- interfaceId = value as SupportedInterface;
347
+ interfaceId = parsedInterface.data;
308
348
  }
309
349
  }
310
350
 
@@ -364,10 +404,6 @@ ${ANSI.bold}OPTIONS:${ANSI.reset}
364
404
  --disable-platform Suppress all outbound platform API calls
365
405
  -h, --help Show this help message
366
406
 
367
- ${ANSI.bold}DEFAULTS:${ANSI.reset}
368
- Reads from ~/.cadet.lock.json (created by cadet hatch).
369
- Override with flags above.
370
-
371
407
  ${ANSI.bold}EXAMPLES:${ANSI.reset}
372
408
  cadet client
373
409
  cadet client mycadet-foo
@@ -551,10 +587,11 @@ async function handleLocalEndpoints(
551
587
  return Response.json({ ok: true });
552
588
  }
553
589
  if (req.method === "POST") {
554
- const body = (await req.json().catch(() => null)) as {
555
- token?: unknown;
556
- } | null;
557
- const token = body?.token;
590
+ const parsed = TokenBodySchema.safeParse(
591
+ await req.json().catch(() => null),
592
+ );
593
+ const token =
594
+ parsed.success && parsed.data ? parsed.data.token : undefined;
558
595
  if (typeof token !== "string" || !/^[A-Za-z0-9]+$/.test(token)) {
559
596
  return Response.json(
560
597
  { ok: false, error: "Invalid token" },
@@ -578,9 +615,11 @@ async function handleLocalEndpoints(
578
615
  return new Response(null, { status: result.status });
579
616
  }
580
617
  if (req.method === "POST") {
581
- let body: Record<string, unknown>;
618
+ let body: z.infer<typeof SyncPlatformBodySchema>;
582
619
  try {
583
- body = (await req.json()) as Record<string, unknown>;
620
+ const parsed = SyncPlatformBodySchema.safeParse(await req.json());
621
+ if (!parsed.success) throw new Error("invalid body");
622
+ body = parsed.data;
584
623
  } catch {
585
624
  return Response.json(
586
625
  { ok: false, error: "Invalid JSON body" },
@@ -588,24 +627,23 @@ async function handleLocalEndpoints(
588
627
  );
589
628
  }
590
629
  let result;
591
- if (body.syncPlatform && Array.isArray(body.platformAssistants)) {
630
+ if (body.syncPlatform && body.platformAssistants) {
592
631
  result = replacePlatformAssistants(
593
632
  lockfilePaths,
594
- body.platformAssistants as Array<Record<string, unknown>>,
595
- body.organizationId as string | undefined,
633
+ body.platformAssistants,
634
+ body.organizationId,
596
635
  );
597
- } else if (body.rename && typeof body.rename === "object") {
598
- const rename = body.rename as Record<string, unknown>;
636
+ } else if (body.rename) {
599
637
  result = renameLockfileAssistantIfPresent(
600
638
  lockfilePaths,
601
- rename.assistantId as string,
602
- rename.name as string,
639
+ body.rename.assistantId,
640
+ body.rename.name,
603
641
  );
604
642
  } else {
605
643
  result = upsertRendererLockfileAssistant(
606
644
  lockfilePaths,
607
- body.assistant as Record<string, unknown>,
608
- body.activeAssistant as string | undefined,
645
+ body.assistant ?? {},
646
+ body.activeAssistant,
609
647
  );
610
648
  }
611
649
  return Response.json(result, { status: result.ok ? 200 : result.status });
@@ -621,10 +659,8 @@ async function handleLocalEndpoints(
621
659
  const contentType = req.headers.get("content-type") ?? "";
622
660
  if (contentType.includes("json")) {
623
661
  try {
624
- const body = (await req.json()) as {
625
- remote?: string;
626
- };
627
- if (body.remote) remote = body.remote;
662
+ const parsed = RemoteBodySchema.safeParse(await req.json());
663
+ if (parsed.success && parsed.data.remote) remote = parsed.data.remote;
628
664
  } catch {
629
665
  return Response.json(
630
666
  { ok: false, error: "Invalid JSON body" },
@@ -659,8 +695,9 @@ async function handleLocalEndpoints(
659
695
 
660
696
  let assistantId: string | undefined;
661
697
  try {
662
- const body = (await req.json()) as { assistantId?: string };
663
- assistantId = body.assistantId;
698
+ const parsed = AssistantIdBodySchema.safeParse(await req.json());
699
+ if (!parsed.success) throw new Error("invalid body");
700
+ assistantId = parsed.data.assistantId;
664
701
  } catch {
665
702
  return Response.json(
666
703
  { ok: false, error: "Invalid JSON body" },
@@ -710,8 +747,9 @@ async function handleLocalEndpoints(
710
747
 
711
748
  let assistantId: string | undefined;
712
749
  try {
713
- const body = (await req.json()) as { assistantId?: string };
714
- assistantId = body.assistantId;
750
+ const parsed = AssistantIdBodySchema.safeParse(await req.json());
751
+ if (!parsed.success) throw new Error("invalid body");
752
+ assistantId = parsed.data.assistantId;
715
753
  } catch {
716
754
  return Response.json(
717
755
  { ok: false, error: "Invalid JSON body" },
@@ -746,7 +784,9 @@ async function handleLocalEndpoints(
746
784
 
747
785
  let body: { assistantId?: unknown; hashedDeviceId?: unknown };
748
786
  try {
749
- body = (await req.json()) as typeof body;
787
+ const parsed = UnpairBodySchema.safeParse(await req.json());
788
+ if (!parsed.success) throw new Error("invalid body");
789
+ body = parsed.data;
750
790
  } catch {
751
791
  return Response.json(
752
792
  { ok: false, error: "Invalid JSON body" },
@@ -805,11 +845,9 @@ async function handleLocalEndpoints(
805
845
 
806
846
  let body: { address?: unknown; handle?: unknown; name?: unknown };
807
847
  try {
808
- body = (await req.json()) as {
809
- address?: unknown;
810
- handle?: unknown;
811
- name?: unknown;
812
- };
848
+ const parsed = DeviceBodySchema.safeParse(await req.json());
849
+ if (!parsed.success) throw new Error("invalid body");
850
+ body = parsed.data;
813
851
  } catch {
814
852
  return Response.json(
815
853
  { ok: false, error: "Invalid JSON body" },
@@ -1047,10 +1085,11 @@ type WebFetchHandler = (
1047
1085
  ) => Promise<Response>;
1048
1086
 
1049
1087
  function isAddrInUse(err: unknown): boolean {
1050
- const e = err as { code?: string; message?: string } | undefined;
1088
+ const code = errnoCode(err);
1089
+ const message = err instanceof Error ? err.message : undefined;
1051
1090
  return (
1052
- e?.code === "EADDRINUSE" ||
1053
- /EADDRINUSE|address already in use/i.test(e?.message ?? "")
1091
+ code === "EADDRINUSE" ||
1092
+ /EADDRINUSE|address already in use/i.test(message ?? "")
1054
1093
  );
1055
1094
  }
1056
1095
 
@@ -1590,7 +1629,8 @@ export async function resolveFreshBearerToken(
1590
1629
  return refreshed?.accessToken ?? bearerToken;
1591
1630
  }
1592
1631
 
1593
- export async function client(): Promise<void> {
1632
+ export const client = Object.assign(
1633
+ async function client(): Promise<void> {
1594
1634
  const {
1595
1635
  runtimeUrl,
1596
1636
  assistantId,
@@ -1673,6 +1713,13 @@ export async function client(): Promise<void> {
1673
1713
  };
1674
1714
  }
1675
1715
 
1716
+ // The CLI addresses its chat conversation by a client-minted uuid key
1717
+ // (web's temp-client-id pattern) — resolve-or-mint once, persisted on the
1718
+ // entry, so every session shares one conversation.
1719
+ const lookup = lookupAssistantByIdentifier(assistantId);
1720
+ const conversationKey =
1721
+ lookup.status === "found" ? resolveConversationKey(lookup.entry) : undefined;
1722
+
1676
1723
  const { renderChatApp } = await import("../components/DefaultMainScreen");
1677
1724
 
1678
1725
  process.stdout.write("\x1b[2J\x1b[H");
@@ -1688,6 +1735,9 @@ export async function client(): Promise<void> {
1688
1735
  console.log(`${ANSI.dim}Disconnected.${ANSI.reset}`);
1689
1736
  process.exit(0);
1690
1737
  },
1691
- { auth, assistantName, workspaceRoot },
1738
+ { auth, assistantName, workspaceRoot, conversationKey },
1692
1739
  );
1693
- }
1740
+ },
1741
+ { description: "Connect to a hatched assistant" },
1742
+ );
1743
+
@@ -103,7 +103,8 @@ function exitWithUsage(error: string): never {
103
103
  process.exit(1);
104
104
  }
105
105
 
106
- export async function confirm(): Promise<void> {
106
+ export const confirm = Object.assign(
107
+ async function confirm(): Promise<void> {
107
108
  const rawArgs = process.argv.slice(3);
108
109
 
109
110
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
@@ -141,4 +142,7 @@ export async function confirm(): Promise<void> {
141
142
  : `Confirmation not accepted`,
142
143
  );
143
144
  }
144
- }
145
+ },
146
+ { description: "Resolve a pending tool confirmation on an assistant" },
147
+ );
148
+
@@ -11,7 +11,8 @@ function printUsage(): void {
11
11
  );
12
12
  }
13
13
 
14
- export async function connect(): Promise<void> {
14
+ export const connect = Object.assign(
15
+ async function connect(): Promise<void> {
15
16
  const args = process.argv.slice(3);
16
17
  const subcommand = args[0];
17
18
 
@@ -28,4 +29,7 @@ export async function connect(): Promise<void> {
28
29
  console.error(`Unknown subcommand: ${subcommand}`);
29
30
  printUsage();
30
31
  process.exit(1);
31
- }
32
+ },
33
+ { description: "Pair with an assistant running on another machine [beta]" },
34
+ );
35
+
@@ -78,7 +78,8 @@ Examples:
78
78
  `);
79
79
  }
80
80
 
81
- export async function debugBundle(): Promise<void> {
81
+ export const debug = Object.assign(
82
+ async function debug(): Promise<void> {
82
83
  const args = process.argv.slice(3);
83
84
  if (args.includes("--help") || args.includes("-h")) {
84
85
  printHelp();
@@ -182,7 +183,10 @@ export async function debugBundle(): Promise<void> {
182
183
  console.log(
183
184
  "Debug bundle sent to Cadet. Staff can open it for the next 7 days.",
184
185
  );
185
- }
186
+ },
187
+ { description: "Send Cadet staff a debug bundle of a self-hosted assistant" },
188
+ );
189
+
186
190
 
187
191
  async function daemonAccessToken(
188
192
  entry: {
@@ -279,7 +279,8 @@ async function revokeDevice(
279
279
  );
280
280
  }
281
281
 
282
- export async function devices(): Promise<void> {
282
+ export const devices = Object.assign(
283
+ async function devices(): Promise<void> {
283
284
  const rawArgs = process.argv.slice(3);
284
285
 
285
286
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
@@ -311,4 +312,7 @@ export async function devices(): Promise<void> {
311
312
  const entry = resolveTargetAssistant(nameArg);
312
313
  const base = resolveLoopbackBase(entry);
313
314
  await listDevices(entry, base, jsonOutput);
314
- }
315
+ },
316
+ { description: "List or revoke devices paired to a local assistant [beta]" },
317
+ );
318
+
@@ -113,7 +113,8 @@ export function renderMarkdown(event: AssistantEvent): void {
113
113
  }
114
114
  }
115
115
 
116
- export async function events(): Promise<void> {
116
+ export const events = Object.assign(
117
+ async function events(): Promise<void> {
117
118
  const rawArgs = process.argv.slice(3);
118
119
 
119
120
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
@@ -157,4 +158,7 @@ export async function events(): Promise<void> {
157
158
  renderMarkdown(event);
158
159
  }
159
160
  }
160
- }
161
+ },
162
+ { description: "Stream events from a running assistant" },
163
+ );
164
+
@@ -1,5 +1,9 @@
1
+ import { existsSync } from "fs";
2
+ import { join } from "path";
3
+
1
4
  import { isManagedCloud } from "../lib/managed-cloud.js";
2
5
  import { spawn } from "child_process";
6
+ import { red } from "../lib/color";
3
7
 
4
8
  import { resolveAssistant } from "../lib/assistant-config";
5
9
  import { dockerResourceNames } from "../lib/docker";
@@ -26,7 +30,7 @@ function normalizeService(raw: string): ServiceName {
26
30
  const normalized = SERVICE_ALIASES[raw];
27
31
  if (!normalized) {
28
32
  console.error(
29
- `Unknown service '${raw}'. Valid services: assistant, gateway, credential-executor`,
33
+ red(`Unknown service '${raw}'. Valid services: assistant, gateway, credential-executor`),
30
34
  );
31
35
  process.exit(1);
32
36
  }
@@ -48,7 +52,8 @@ function resolveDockerContainer(
48
52
  }
49
53
  }
50
54
 
51
- export async function exec(): Promise<void> {
55
+ export const exec = Object.assign(
56
+ async function exec(): Promise<void> {
52
57
  const rawArgs = process.argv.slice(3);
53
58
 
54
59
  // Only check for help flags before the -- separator so that
@@ -103,8 +108,10 @@ export async function exec(): Promise<void> {
103
108
 
104
109
  if (dashDashIndex === -1) {
105
110
  console.error(
106
- "Error: missing '--' separator before command.\n" +
107
- "Usage: cadet exec [<name>] -- <command...>",
111
+ red(
112
+ "Error: missing '--' separator before command.\n" +
113
+ "Usage: cadet exec [<name>] -- <command...>",
114
+ ),
108
115
  );
109
116
  process.exit(1);
110
117
  }
@@ -112,7 +119,7 @@ export async function exec(): Promise<void> {
112
119
  const command = rawArgs.slice(dashDashIndex + 1);
113
120
 
114
121
  if (command.length === 0) {
115
- console.error("Error: no command specified after '--'.");
122
+ console.error(red("Error: no command specified after '--'."));
116
123
  process.exit(1);
117
124
  }
118
125
 
@@ -130,7 +137,7 @@ export async function exec(): Promise<void> {
130
137
  } else if (preArgs[i] === "--timeout" && preArgs[i + 1]) {
131
138
  const secs = Number(preArgs[++i]);
132
139
  if (!Number.isFinite(secs) || secs < 0) {
133
- console.error("Error: --timeout must be a non-negative number.");
140
+ console.error(red("Error: --timeout must be a non-negative number."));
134
141
  process.exit(1);
135
142
  }
136
143
  timeoutMs = secs === 0 ? 0 : secs * 1000;
@@ -147,9 +154,9 @@ export async function exec(): Promise<void> {
147
154
 
148
155
  if (!entry) {
149
156
  if (nameArg) {
150
- console.error(`No assistant instance found with name '${nameArg}'.`);
157
+ console.error(red(`No assistant instance found with name '${nameArg}'.`));
151
158
  } else {
152
- console.error("No assistant instance found. Run `cadet hatch` first.");
159
+ console.error(red("No assistant instance found. Run `cadet hatch` first."));
153
160
  }
154
161
  process.exit(1);
155
162
  }
@@ -157,9 +164,37 @@ export async function exec(): Promise<void> {
157
164
  const cloud = entry.cloud;
158
165
 
159
166
  if (cloud === "local") {
167
+ const instanceDir = entry.resources?.instanceDir;
168
+ if (!instanceDir) {
169
+ console.error(
170
+ red(
171
+ `Error: assistant '${entry.assistantId}' has no local instance directory recorded.`,
172
+ ),
173
+ );
174
+ process.exit(1);
175
+ }
176
+
177
+ const workspaceDir = join(instanceDir, "workspace");
178
+ const socketPath = join(workspaceDir, "assistant.sock");
179
+ if (!existsSync(socketPath)) {
180
+ console.error(
181
+ red(
182
+ `Error: the assistant runtime is not reachable (no socket at ${socketPath}).`,
183
+ ),
184
+ );
185
+ console.error(
186
+ red("Start it with `cadet wake`, or check `cadet ps` for its status."),
187
+ );
188
+ process.exit(1);
189
+ }
190
+
160
191
  const child = spawn(command[0], command.slice(1), {
161
192
  stdio: "inherit",
162
193
  windowsHide: true,
194
+ env: {
195
+ ...process.env,
196
+ CADET_WORKSPACE_DIR: workspaceDir,
197
+ },
163
198
  });
164
199
  await new Promise<void>((resolve) => {
165
200
  child.on("close", (code) => {
@@ -167,7 +202,7 @@ export async function exec(): Promise<void> {
167
202
  resolve();
168
203
  });
169
204
  child.on("error", (err) => {
170
- console.error(`Error: ${err.message}`);
205
+ console.error(red(`Error: ${err.message}`));
171
206
  process.exitCode = 1;
172
207
  resolve();
173
208
  });
@@ -212,7 +247,7 @@ export async function exec(): Promise<void> {
212
247
  const token = readPlatformToken();
213
248
  if (!token) {
214
249
  console.error(
215
- "Not logged in. Run `cadet login` first to authenticate with Cadet Cloud.",
250
+ red("Not logged in. Run `cadet login` first to authenticate with Cadet Cloud."),
216
251
  );
217
252
  process.exit(1);
218
253
  }
@@ -244,6 +279,9 @@ export async function exec(): Promise<void> {
244
279
  return;
245
280
  }
246
281
 
247
- console.error(`Error: 'cadet exec' is not supported for ${cloud} instances.`);
282
+ console.error(red(`Error: 'cadet exec' is not supported for ${cloud} instances.`));
248
283
  process.exit(1);
249
- }
284
+ },
285
+ { description: "Execute a command inside an assistant's container" },
286
+ );
287
+
@@ -203,7 +203,8 @@ async function setFlag(
203
203
  console.log(`Flag "${key}" set to ${value}`);
204
204
  }
205
205
 
206
- export async function flags(): Promise<void> {
206
+ export const flags = Object.assign(
207
+ async function flags(): Promise<void> {
207
208
  const args = process.argv.slice(3);
208
209
 
209
210
  if (args.includes("--help") || args.includes("-h")) {
@@ -248,4 +249,7 @@ export async function flags(): Promise<void> {
248
249
  console.error(`Unknown subcommand: ${subcommand}`);
249
250
  printHelp();
250
251
  process.exit(1);
251
- }
252
+ },
253
+ { description: "Show and toggle feature flags" },
254
+ );
255
+
@@ -9,7 +9,8 @@ function printUsage(): void {
9
9
  console.log(" token Manage gateway authentication tokens");
10
10
  }
11
11
 
12
- export async function gateway(): Promise<void> {
12
+ export const gateway = Object.assign(
13
+ async function gateway(): Promise<void> {
13
14
  const args = process.argv.slice(3);
14
15
  const subcommand = args[0];
15
16
 
@@ -26,4 +27,7 @@ export async function gateway(): Promise<void> {
26
27
  console.error(`Unknown subcommand: ${subcommand}`);
27
28
  printUsage();
28
29
  process.exit(1);
29
- }
30
+ },
31
+ { description: "Gateway management commands" },
32
+ );
33
+
@@ -222,7 +222,8 @@ function getCliVersion(): string {
222
222
  return cliPkg.version ?? "unknown";
223
223
  }
224
224
 
225
- export async function hatch(): Promise<void> {
225
+ export const hatch = Object.assign(
226
+ async function hatch(): Promise<void> {
226
227
  const cliVersion = getCliVersion();
227
228
  console.log(`@mycadet/cli v${cliVersion}`);
228
229
 
@@ -314,7 +315,10 @@ export async function hatch(): Promise<void> {
314
315
 
315
316
  console.error(`Error: Remote host '${remote}' is not yet supported.`);
316
317
  process.exit(1);
317
- }
318
+ },
319
+ { description: "Create a new assistant instance" },
320
+ );
321
+
318
322
 
319
323
  async function hatchCadetPlatform(): Promise<void> {
320
324
  const token = readPlatformToken();