@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
@@ -25,7 +25,6 @@ import {
25
25
  DEFAULT_TERMINAL_COLUMNS,
26
26
  estimateItemHeight,
27
27
  formatConfirmationPreview,
28
- formatHeaderEyebrow,
29
28
  formatHeaderTitle,
30
29
  formatTimestamp,
31
30
  isRuntimeMessage,
@@ -65,15 +64,37 @@ export const ANSI = {
65
64
  gray: "\x1b[90m",
66
65
  } as const;
67
66
 
67
+ /**
68
+ * The completion catalog: the CLI's own commands plus the web client's set
69
+ * (clients/web slash-command-catalog: /commands /compact /clean /models
70
+ * /status /doctor /btw). /clean /commands /models /status resolve through
71
+ * the daemon's meta-command endpoint; /compact and /doctor run as real
72
+ * turns (same as web); /config is CLI-local (runtime config get/set).
73
+ */
68
74
  export const SLASH_COMMANDS = [
69
75
  "/btw",
76
+ "/clean",
70
77
  "/clear",
78
+ "/commands",
79
+ "/compact",
80
+ "/config",
81
+ "/doctor",
71
82
  "/exit",
72
83
  "/help",
84
+ "/models",
73
85
  "/q",
74
86
  "/quit",
87
+ "/status",
75
88
  ];
76
89
 
90
+ /** Commands the daemon resolves via POST /v1/conversations/{id}/slash. */
91
+ const DAEMON_META_COMMANDS = new Set([
92
+ "/clean",
93
+ "/commands",
94
+ "/models",
95
+ "/status",
96
+ ]);
97
+
77
98
  const SEND_TIMEOUT_MS = 5000;
78
99
  /** Fresh deadline for a request retried after a mid-session token refresh —
79
100
  * the original caller signal may have already timed out during the refresh. */
@@ -82,8 +103,7 @@ const RETRY_TIMEOUT_MS = 30_000;
82
103
  // ── Layout constants ──────────────────────────────────────
83
104
  const MAX_TOTAL_WIDTH = 72;
84
105
  const DEFAULT_TERMINAL_ROWS = 24;
85
- const HEADER_PREFIX_UNICODE = "── Cadet ";
86
- const HEADER_PREFIX_ASCII = "-- Cadet ";
106
+ // The header opens with a plain full-width rule (no word prefix).
87
107
 
88
108
  // Left panel structure: HEADER lines + art + FOOTER lines
89
109
  // Header chrome (borders around panel content)
@@ -260,6 +280,99 @@ async function runtimeRequest<T>(
260
280
  return response.json() as Promise<T>;
261
281
  }
262
282
 
283
+ interface MetaSlashResponse {
284
+ kind: "clean" | "info";
285
+ text: string;
286
+ contextUsage?: { tokens: number; maxTokens: number | null; fillRatio: number | null };
287
+ }
288
+
289
+ /**
290
+ * Resolve a local meta slash command (/clean, /commands, /models, /status)
291
+ * through the daemon — no turn starts, nothing persists. The CLI addresses
292
+ * its conversation by the client-minted conversation key (a uuid, web's
293
+ * temp-client-id pattern).
294
+ */
295
+ async function resolveMetaCommand(
296
+ baseUrl: string,
297
+ assistantId: string,
298
+ conversationKey: string,
299
+ command: string,
300
+ auth?: Record<string, string>,
301
+ ): Promise<MetaSlashResponse> {
302
+ const url = `${baseUrl}/v1/conversations/${encodeURIComponent(conversationKey)}/slash`;
303
+ const response = await fetch(url, {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json", ...auth },
306
+ body: JSON.stringify({ command }),
307
+ signal: AbortSignal.timeout(10_000),
308
+ });
309
+ if (!response.ok) {
310
+ const body = await response.text().catch(() => "");
311
+ throw new Error(friendlyErrorMessage(response.status, body));
312
+ }
313
+ return response.json() as Promise<MetaSlashResponse>;
314
+ }
315
+
316
+ async function fetchRuntimeConfig(
317
+ baseUrl: string,
318
+ auth?: Record<string, string>,
319
+ ): Promise<unknown> {
320
+ const url = `${baseUrl}/v1/config`;
321
+ const response = await fetch(url, {
322
+ headers: { "Content-Type": "application/json", ...auth },
323
+ signal: AbortSignal.timeout(5000),
324
+ });
325
+ if (!response.ok) {
326
+ const body = await response.text().catch(() => "");
327
+ throw new Error(friendlyErrorMessage(response.status, body));
328
+ }
329
+ return response.json();
330
+ }
331
+
332
+ async function setRuntimeConfigValue(
333
+ baseUrl: string,
334
+ path: string,
335
+ value: unknown,
336
+ auth?: Record<string, string>,
337
+ ): Promise<void> {
338
+ const url = `${baseUrl}/v1/config/set`;
339
+ const response = await fetch(url, {
340
+ method: "POST",
341
+ headers: { "Content-Type": "application/json", ...auth },
342
+ body: JSON.stringify({ path, value }),
343
+ signal: AbortSignal.timeout(5000),
344
+ });
345
+ if (!response.ok) {
346
+ const body = await response.text().catch(() => "");
347
+ throw new Error(friendlyErrorMessage(response.status, body));
348
+ }
349
+ }
350
+
351
+ /** Resolve a dotted config path within the fetched config object. */
352
+ function resolveConfigPath(config: unknown, dottedPath: string): unknown {
353
+ let current: unknown = config;
354
+ for (const segment of dottedPath.split(".")) {
355
+ if (
356
+ current === null ||
357
+ typeof current !== "object" ||
358
+ !(segment in (current as Record<string, unknown>))
359
+ ) {
360
+ return undefined;
361
+ }
362
+ current = (current as Record<string, unknown>)[segment];
363
+ }
364
+ return current;
365
+ }
366
+
367
+ /** Parse a /config value: JSON when it parses, plain string otherwise. */
368
+ function parseConfigValue(raw: string): unknown {
369
+ try {
370
+ return JSON.parse(raw);
371
+ } catch {
372
+ return raw;
373
+ }
374
+ }
375
+
263
376
  async function checkHealthRuntime(baseUrl: string): Promise<HealthResponse> {
264
377
  const url = `${baseUrl}/healthz`;
265
378
  const controller = new AbortController();
@@ -282,9 +395,10 @@ async function checkHealthRuntime(baseUrl: string): Promise<HealthResponse> {
282
395
  async function pollMessages(
283
396
  baseUrl: string,
284
397
  assistantId: string,
398
+ conversationKey: string,
285
399
  auth?: Record<string, string>,
286
400
  ): Promise<ListMessagesResponse> {
287
- const params = new URLSearchParams({ conversationKey: assistantId });
401
+ const params = new URLSearchParams({ conversationKey });
288
402
  const response = await runtimeRequest<ListMessagesResponse>(
289
403
  baseUrl,
290
404
  assistantId,
@@ -304,6 +418,7 @@ async function pollMessages(
304
418
  async function sendMessage(
305
419
  baseUrl: string,
306
420
  assistantId: string,
421
+ conversationKey: string,
307
422
  content: string,
308
423
  signal?: AbortSignal,
309
424
  auth?: Record<string, string>,
@@ -315,7 +430,7 @@ async function sendMessage(
315
430
  {
316
431
  method: "POST",
317
432
  body: JSON.stringify({
318
- conversationKey: assistantId,
433
+ conversationKey,
319
434
  content,
320
435
  sourceChannel: "cadet",
321
436
  interface: "cli",
@@ -702,7 +817,7 @@ interface DefaultMainScreenProps {
702
817
 
703
818
  interface StyledLine {
704
819
  text: string;
705
- style: "heading" | "dim" | "normal" | "eyebrow" | "title" | "art";
820
+ style: "heading" | "dim" | "normal" | "title" | "art";
706
821
  }
707
822
 
708
823
  function CompactHeader({
@@ -747,14 +862,12 @@ function DefaultMainScreen({
747
862
  }: DefaultMainScreenProps): ReactElement {
748
863
  const accentColor = "magenta";
749
864
  const caps = getTerminalCapabilities();
750
- const headerPrefix = caps.unicodeSupported
751
- ? HEADER_PREFIX_UNICODE
752
- : HEADER_PREFIX_ASCII;
753
865
  const headerSep = caps.unicodeSupported ? "─" : "-";
754
866
 
755
867
  const { stdout } = useStdout();
756
868
  const terminalColumns = stdout.columns || DEFAULT_TERMINAL_COLUMNS;
757
- const totalWidth = Math.min(MAX_TOTAL_WIDTH, terminalColumns);
869
+ // The header spans the full terminal width (previously capped at 72).
870
+ const totalWidth = terminalColumns;
758
871
  const isCompact = terminalColumns < COMPACT_THRESHOLD;
759
872
 
760
873
  if (isCompact) {
@@ -772,7 +885,6 @@ function DefaultMainScreen({
772
885
 
773
886
  const leftLines: StyledLine[] = [
774
887
  { text: " ", style: "normal" },
775
- { text: formatHeaderEyebrow(), style: "eyebrow" },
776
888
  { text: formatHeaderTitle(assistantName), style: "title" },
777
889
  { text: " ", style: "normal" },
778
890
  ...art.map((line) => ({
@@ -800,25 +912,15 @@ function DefaultMainScreen({
800
912
 
801
913
  return (
802
914
  <Box flexDirection="column" width={totalWidth}>
803
- <Text dimColor>
804
- {headerPrefix +
805
- headerSep.repeat(Math.max(0, totalWidth - headerPrefix.length))}
806
- </Text>
915
+ <Text dimColor>{headerSep.repeat(totalWidth)}</Text>
807
916
  <Box flexDirection="row">
808
917
  <Box flexDirection="column" width={LEFT_PANEL_WIDTH}>
809
918
  {Array.from({ length: maxLines }, (_, i) => {
810
919
  const item = leftLines[i];
811
920
  if (!item) return <Text key={i}> </Text>;
812
- if (item.style === "eyebrow") {
813
- return (
814
- <Text key={i} color={caps.isDumb ? undefined : accentColor}>
815
- {item.text}
816
- </Text>
817
- );
818
- }
819
921
  if (item.style === "title") {
820
922
  return (
821
- <Text key={i} bold>
923
+ <Text key={i} bold color={caps.isDumb ? undefined : accentColor}>
822
924
  {item.text}
823
925
  </Text>
824
926
  );
@@ -1109,6 +1211,8 @@ export interface ChatAppHandle {
1109
1211
  interface ChatAppProps {
1110
1212
  runtimeUrl: string;
1111
1213
  assistantId: string;
1214
+ /** Client-minted conversation key the CLI addresses its conversation by. */
1215
+ conversationKey: string;
1112
1216
  assistantName?: string;
1113
1217
  /** Assistant data root for the header details panel (home-collapsed). */
1114
1218
  workspaceRoot?: string;
@@ -1122,6 +1226,7 @@ interface ChatAppProps {
1122
1226
  function ChatApp({
1123
1227
  runtimeUrl,
1124
1228
  assistantId,
1229
+ conversationKey,
1125
1230
  assistantName,
1126
1231
  workspaceRoot,
1127
1232
  auth,
@@ -1463,6 +1568,7 @@ function ChatApp({
1463
1568
  const historyResponse = await pollMessages(
1464
1569
  runtimeUrl,
1465
1570
  assistantId,
1571
+ conversationKey,
1466
1572
  auth,
1467
1573
  );
1468
1574
  h.hideSpinner();
@@ -1486,7 +1592,7 @@ function ChatApp({
1486
1592
  for await (const event of streamEvents(
1487
1593
  runtimeUrl,
1488
1594
  assistantId,
1489
- assistantId,
1595
+ conversationKey,
1490
1596
  sseAc.signal,
1491
1597
  auth,
1492
1598
  )) {
@@ -1744,6 +1850,105 @@ function ChatApp({
1744
1850
  return;
1745
1851
  }
1746
1852
 
1853
+ // Daemon meta commands (/clean /commands /models /status) — the same
1854
+ // set web resolves through the daemon's slash endpoint. No turn
1855
+ // starts; the answer renders as an ephemeral status line.
1856
+ const metaCommand = trimmed.split(/\s+/)[0]?.toLowerCase();
1857
+ if (metaCommand && DAEMON_META_COMMANDS.has(metaCommand)) {
1858
+ const isConnected = await ensureConnected();
1859
+ if (!isConnected) return;
1860
+
1861
+ h.showSpinner(`Running ${metaCommand}...`);
1862
+ try {
1863
+ const result = await resolveMetaCommand(
1864
+ runtimeUrl,
1865
+ assistantId,
1866
+ conversationKey,
1867
+ trimmed,
1868
+ auth,
1869
+ );
1870
+ h.hideSpinner();
1871
+ h.addStatus(result.text || "(no output)");
1872
+ if (result.contextUsage) {
1873
+ const usage = result.contextUsage;
1874
+ const pct =
1875
+ usage.maxTokens && usage.maxTokens > 0
1876
+ ? ` (${Math.round((usage.tokens / usage.maxTokens) * 100)}% of context)`
1877
+ : "";
1878
+ h.addStatus(
1879
+ `Context: ${usage.tokens.toLocaleString()} tokens${pct}`,
1880
+ "gray",
1881
+ );
1882
+ }
1883
+ } catch (err) {
1884
+ h.hideSpinner();
1885
+ h.showError(
1886
+ `${metaCommand} failed: ${err instanceof Error ? err.message : String(err)}`,
1887
+ );
1888
+ }
1889
+ setInputFocused(true);
1890
+ return;
1891
+ }
1892
+
1893
+ // /config — read and write runtime config values (GET /v1/config,
1894
+ // POST /v1/config/set — the same API `assistant config set` uses).
1895
+ if (metaCommand === "/config") {
1896
+ const argString = trimmed.slice("/config".length).trim();
1897
+ const isConnected = await ensureConnected();
1898
+ if (!isConnected) return;
1899
+
1900
+ h.showSpinner("Reading config...");
1901
+ try {
1902
+ if (!argString) {
1903
+ const config = await fetchRuntimeConfig(runtimeUrl, auth);
1904
+ h.hideSpinner();
1905
+ const printed = JSON.stringify(config, null, 2);
1906
+ const lines = printed.split("\n");
1907
+ h.addStatus(
1908
+ lines.length > 40
1909
+ ? lines.slice(0, 40).join("\n") + `\n... (${lines.length - 40} more lines)`
1910
+ : printed,
1911
+ );
1912
+ } else {
1913
+ // Split "<path>" or "<path> <value>" — the path is dotted and
1914
+ // never contains spaces; the value is everything after it.
1915
+ const spaceIndex = argString.indexOf(" ");
1916
+ const configPath = (
1917
+ spaceIndex === -1 ? argString : argString.slice(0, spaceIndex)
1918
+ ).trim();
1919
+ const rawValue =
1920
+ spaceIndex === -1 ? undefined : argString.slice(spaceIndex + 1).trim();
1921
+
1922
+ if (rawValue === undefined) {
1923
+ const config = await fetchRuntimeConfig(runtimeUrl, auth);
1924
+ h.hideSpinner();
1925
+ const value = resolveConfigPath(config, configPath);
1926
+ h.addStatus(
1927
+ value === undefined
1928
+ ? `config.${configPath} is not set`
1929
+ : `config.${configPath} = ${JSON.stringify(value, null, 2)}`,
1930
+ );
1931
+ } else {
1932
+ await setRuntimeConfigValue(
1933
+ runtimeUrl,
1934
+ configPath,
1935
+ parseConfigValue(rawValue),
1936
+ auth,
1937
+ );
1938
+ h.hideSpinner();
1939
+ h.addStatus(`Set config.${configPath} = ${rawValue}`, "green");
1940
+ }
1941
+ }
1942
+ } catch (err) {
1943
+ h.hideSpinner();
1944
+ h.showError(
1945
+ `/config failed: ${err instanceof Error ? err.message : String(err)}`,
1946
+ );
1947
+ }
1948
+ setInputFocused(true);
1949
+ return;
1950
+ }
1951
+
1747
1952
  // If a connection attempt is already in progress, don't silently drop input
1748
1953
  if (connectingRef.current) {
1749
1954
  h.addStatus(
@@ -1771,7 +1976,7 @@ function ChatApp({
1771
1976
  ...auth,
1772
1977
  },
1773
1978
  body: JSON.stringify({
1774
- conversationKey: assistantId,
1979
+ conversationKey,
1775
1980
  content: question,
1776
1981
  }),
1777
1982
  signal: AbortSignal.timeout(30_000),
@@ -1836,8 +2041,10 @@ function ChatApp({
1836
2041
  }
1837
2042
 
1838
2043
  if (busyRef.current) {
1839
- // /btw is already handled above this block
1840
- if (!trimmed.startsWith("/")) {
2044
+ // /btw and the other local commands are already handled above this
2045
+ // block — anything reaching here (including unknown /commands, which
2046
+ // run as real turns) echoes as a user message.
2047
+ {
1841
2048
  const userMsg: RuntimeMessage = {
1842
2049
  id: "local-user-" + Date.now(),
1843
2050
  role: "user",
@@ -1861,6 +2068,7 @@ function ChatApp({
1861
2068
  const sendResult = await sendMessage(
1862
2069
  runtimeUrl,
1863
2070
  assistantId,
2071
+ conversationKey,
1864
2072
  trimmed,
1865
2073
  controller.signal,
1866
2074
  auth,
@@ -1883,15 +2091,16 @@ function ChatApp({
1883
2091
  return;
1884
2092
  }
1885
2093
 
1886
- if (!trimmed.startsWith("/")) {
1887
- const userMsg: RuntimeMessage = {
1888
- id: "local-user-" + Date.now(),
1889
- role: "user",
1890
- content: trimmed,
1891
- timestamp: new Date().toISOString(),
1892
- };
1893
- h.addMessage(userMsg);
1894
- }
2094
+ // Unknown /commands run as real turns (web-consistent: /compact and
2095
+ // /doctor are turns too) and show up in the transcript like any other
2096
+ // user message.
2097
+ const userMsg: RuntimeMessage = {
2098
+ id: "local-user-" + Date.now(),
2099
+ role: "user",
2100
+ content: trimmed,
2101
+ timestamp: new Date().toISOString(),
2102
+ };
2103
+ h.addMessage(userMsg);
1895
2104
 
1896
2105
  const isConnected = await ensureConnected();
1897
2106
  if (!isConnected) {
@@ -1910,6 +2119,7 @@ function ChatApp({
1910
2119
  const sendResult = await sendMessage(
1911
2120
  runtimeUrl,
1912
2121
  assistantId,
2122
+ conversationKey,
1913
2123
  trimmed,
1914
2124
  controller.signal,
1915
2125
  auth,
@@ -2249,6 +2459,9 @@ export function renderChatApp(
2249
2459
  auth?: Record<string, string>;
2250
2460
  assistantName?: string;
2251
2461
  workspaceRoot?: string;
2462
+ /** Client-minted conversation key (resolved from the assistant entry).
2463
+ * Unregistered targets (--url) get an ephemeral uuid. */
2464
+ conversationKey?: string;
2252
2465
  },
2253
2466
  ): ChatAppInstance {
2254
2467
  let chatHandle: ChatAppHandle | null = null;
@@ -2257,6 +2470,7 @@ export function renderChatApp(
2257
2470
  <ChatApp
2258
2471
  runtimeUrl={runtimeUrl}
2259
2472
  assistantId={assistantId}
2473
+ conversationKey={options?.conversationKey ?? crypto.randomUUID()}
2260
2474
  assistantName={options?.assistantName}
2261
2475
  workspaceRoot={options?.workspaceRoot}
2262
2476
  auth={options?.auth}
package/src/index.ts CHANGED
@@ -3,7 +3,12 @@
3
3
  import { join } from "path";
4
4
  import { readFileSync } from "node:fs";
5
5
 
6
- import { resolveConfigDir } from "@mycadet/local-mode";
6
+ import {
7
+ resolveConfigDir,
8
+ SigningKeyContinuityError,
9
+ } from "@mycadet/local-mode";
10
+
11
+ import { emitCliError } from "./lib/cli-error.js";
7
12
 
8
13
  import cliPkg from "../package.json";
9
14
  import { backup } from "./commands/backup";
@@ -30,27 +35,30 @@ import { rollback } from "./commands/rollback";
30
35
  import { setup } from "./commands/setup";
31
36
  import { sleep } from "./commands/sleep";
32
37
  import { ssh } from "./commands/ssh";
33
- import { debugBundle } from "./commands/debug";
38
+ import { debug } from "./commands/debug";
34
39
  import { teleport } from "./commands/teleport";
35
40
  import { terminal } from "./commands/terminal";
36
41
  import { tunnel } from "./commands/tunnel";
42
+ import { uninstall } from "./commands/uninstall";
37
43
  import { unpair } from "./commands/unpair";
38
44
  import { upgrade } from "./commands/upgrade";
39
45
  import { use } from "./commands/use";
40
46
  import { voice } from "./commands/voice";
41
47
  import { wake } from "./commands/wake";
42
48
  import { workflows } from "./commands/workflows";
49
+ import { red } from "./lib/color";
43
50
  import { resolveAssistant, setActiveAssistant } from "./lib/assistant-config";
44
51
  import { loadGuardianToken } from "./lib/guardian-token";
45
52
  import { checkHealth } from "./lib/health-check";
46
53
 
54
+
47
55
  const commands = {
48
56
  backup,
49
57
  clean,
50
58
  client,
51
59
  confirm,
52
60
  connect,
53
- debug: debugBundle,
61
+ debug,
54
62
  devices,
55
63
  events,
56
64
  exec,
@@ -74,6 +82,7 @@ const commands = {
74
82
  teleport,
75
83
  terminal,
76
84
  tunnel,
85
+ uninstall,
77
86
  unpair,
78
87
  upgrade,
79
88
  use,
@@ -81,7 +90,7 @@ const commands = {
81
90
  wake,
82
91
  whoami,
83
92
  workflows,
84
- } as const;
93
+ };
85
94
 
86
95
  type CommandName = keyof typeof commands;
87
96
 
@@ -89,56 +98,12 @@ function printHelp(): void {
89
98
  console.log("Usage: cadet <command> [options]");
90
99
  console.log("");
91
100
  console.log("Commands:");
92
- console.log(" backup Export a backup of a running assistant");
93
- console.log(" clean Kill orphaned cadet processes");
94
- console.log(" client Connect to a hatched assistant");
95
- console.log(" confirm Resolve a pending tool confirmation on an assistant");
96
- console.log(
97
- " connect Pair with an assistant running on another machine [beta]",
98
- );
99
- console.log(
100
- " debug Send Cadet staff a debug bundle of a self-hosted assistant",
101
- );
102
- console.log(
103
- " devices List or revoke devices paired to a local assistant [beta]",
104
- );
105
- console.log(" events Stream events from a running assistant");
106
- console.log(" exec Execute a command inside an assistant's container");
107
- console.log(" flags Show and toggle feature flags");
108
- console.log(" gateway Gateway management commands");
109
- console.log(" hatch Create a new assistant instance");
110
- console.log(" logs View logs from an assistant instance");
111
- console.log(" login Log in to Cadet Cloud");
112
- console.log(" logout Log out of Cadet Cloud");
113
- console.log(" message Send a message to a running assistant");
114
- console.log(
115
- " pair Print a pairing link and QR code for another device [beta]",
101
+ const nameWidth = Math.max(
102
+ ...Object.keys(commands).map((name) => name.length),
116
103
  );
117
- console.log(
118
- " ps List assistants (or processes for a specific assistant)",
119
- );
120
- console.log(" recover Restore a previously retired local assistant");
121
- console.log(
122
- " restore Restore data (and optionally version) from a .vbundle backup",
123
- );
124
- console.log(" retire Delete an assistant instance");
125
- console.log(" roadmap Manage roadmap items");
126
- console.log(" rollback Roll back an assistant to a previous version");
127
- console.log(" setup Configure API keys interactively");
128
- console.log(" sleep Stop the assistant process");
129
- console.log(" ssh SSH into a remote assistant instance");
130
- console.log(" teleport Transfer assistant data between environments");
131
- console.log(" terminal Open a terminal into a managed assistant container");
132
- console.log(" tunnel Create a tunnel for a locally hosted assistant");
133
- console.log(
134
- " unpair Forget a paired assistant imported from another machine [beta]",
135
- );
136
- console.log(" upgrade Upgrade an assistant to a newer version");
137
- console.log(" use Set the active assistant for commands");
138
- console.log(" voice Type to an assistant and hear it answer out loud");
139
- console.log(" wake Start the assistant and gateway");
140
- console.log(" whoami Show current logged-in user");
141
- console.log(" workflows Inspect and control workflow runs");
104
+ for (const [name, command] of Object.entries(commands)) {
105
+ console.log(` ${name.padEnd(nameWidth)} ${command.description}`);
106
+ }
142
107
  console.log("");
143
108
  console.log("Options:");
144
109
  console.log(
@@ -275,14 +240,17 @@ async function main() {
275
240
  const command = commands[commandName as CommandName];
276
241
 
277
242
  if (!command) {
278
- console.error(`Unknown command: ${commandName}`);
243
+ console.error(red(`Unknown command: ${commandName}`));
279
244
  process.exit(1);
280
245
  }
281
246
 
282
247
  try {
283
248
  await command();
284
249
  } catch (error) {
285
- console.error("Error:", error instanceof Error ? error.message : error);
250
+ if (error instanceof SigningKeyContinuityError) {
251
+ emitCliError("AUTH_IDENTITY_UNAVAILABLE", error.message, error.code);
252
+ }
253
+ console.error(red(`Error: ${error instanceof Error ? error.message : error}`));
286
254
  process.exit(1);
287
255
  }
288
256
  }