@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.
- package/eslint.config.mjs +7 -0
- package/node_modules/@mycadet/local-mode/src/__tests__/environment.test.ts +1 -4
- package/node_modules/@mycadet/local-mode/src/config.ts +14 -19
- package/node_modules/@mycadet/local-mode/src/index.ts +5 -0
- package/node_modules/@mycadet/local-mode/src/local-signing-key.ts +73 -0
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +18 -3
- package/package.json +4 -3
- package/src/__tests__/assistant-config.test.ts +30 -28
- package/src/__tests__/debug.test.ts +8 -8
- package/src/__tests__/default-main-screen-formatting.test.ts +5 -4
- package/src/__tests__/flags.test.ts +2 -2
- package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
- package/src/__tests__/ingress-config.test.ts +2 -2
- package/src/__tests__/login-loopback.test.ts +1 -1
- package/src/__tests__/nginx-ingress.test.ts +68 -76
- package/src/__tests__/pair.test.ts +135 -121
- package/src/__tests__/platform-client.test.ts +2 -2
- package/src/__tests__/recover.test.ts +96 -3
- package/src/__tests__/retire.test.ts +5 -3
- package/src/__tests__/signing-key-continuity.test.ts +261 -0
- package/src/__tests__/sleep.test.ts +189 -6
- package/src/__tests__/tailscale-tunnel.test.ts +1 -1
- package/src/__tests__/uninstall.test.ts +171 -0
- package/src/__tests__/upgrade-local.test.ts +16 -3
- package/src/__tests__/use.test.ts +2 -2
- package/src/__tests__/wake-error.test.ts +76 -0
- package/src/__tests__/wake.test.ts +309 -36
- package/src/commands/backup.ts +6 -2
- package/src/commands/clean.ts +6 -2
- package/src/commands/client.ts +91 -41
- package/src/commands/confirm.ts +6 -2
- package/src/commands/connect.ts +6 -2
- package/src/commands/debug.ts +6 -2
- package/src/commands/devices.ts +6 -2
- package/src/commands/events.ts +6 -2
- package/src/commands/exec.ts +50 -12
- package/src/commands/flags.ts +6 -2
- package/src/commands/gateway.ts +6 -2
- package/src/commands/hatch.ts +6 -2
- package/src/commands/login.ts +26 -356
- package/src/commands/logs.ts +6 -2
- package/src/commands/message.ts +6 -2
- package/src/commands/pair.ts +6 -2
- package/src/commands/ps.ts +7 -2
- package/src/commands/recover.ts +21 -16
- package/src/commands/restore.ts +6 -2
- package/src/commands/retire.ts +6 -2
- package/src/commands/roadmap.ts +6 -2
- package/src/commands/rollback.ts +6 -2
- package/src/commands/setup.ts +76 -9
- package/src/commands/sleep.ts +131 -31
- package/src/commands/ssh.ts +6 -2
- package/src/commands/teleport.ts +6 -2
- package/src/commands/terminal.ts +6 -2
- package/src/commands/tunnel.ts +6 -2
- package/src/commands/uninstall.ts +228 -0
- package/src/commands/unpair.ts +6 -2
- package/src/commands/upgrade.ts +13 -8
- package/src/commands/use.ts +6 -2
- package/src/commands/voice.ts +6 -2
- package/src/commands/wake.ts +69 -37
- package/src/commands/workflows.ts +6 -2
- package/src/components/DefaultMainScreen.tsx +250 -36
- package/src/index.ts +23 -55
- package/src/lib/assistant-config.ts +27 -13
- package/src/lib/backup-ops.ts +3 -4
- package/src/lib/cli-error.ts +5 -0
- package/src/lib/client-identity.ts +8 -10
- package/src/lib/cloud-login.ts +422 -0
- package/src/lib/color.ts +16 -0
- package/src/lib/default-main-screen-formatting.ts +32 -8
- package/src/lib/environments/__tests__/paths.test.ts +9 -11
- package/src/lib/environments/paths.ts +2 -20
- package/src/lib/guardian-token.ts +72 -18
- package/src/lib/hatch-local.ts +52 -1
- package/src/lib/health-check.ts +24 -15
- package/src/lib/lifecycle-reporter.ts +9 -1
- package/src/lib/local-hatch-lock.ts +19 -27
- package/src/lib/local-runtime-client.ts +25 -14
- package/src/lib/local.ts +17 -43
- package/src/lib/ngrok.ts +48 -16
- package/src/lib/platform-client.ts +141 -72
- package/src/lib/retire-archive.ts +4 -4
- package/src/lib/retire-local.ts +2 -25
- package/src/lib/teleport-backup.ts +23 -9
- package/src/lib/terminal-client.ts +21 -9
- package/src/lib/tunnel-edge.ts +11 -13
- package/src/lib/upgrade-lifecycle.ts +16 -4
- 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
|
-
|
|
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
|
|
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
|
|
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" | "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1840
|
-
|
|
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
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
}
|