@cydm/happy-elves 0.1.0-beta.65 → 0.1.0-beta.66
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/apps/cli/dist/commands/account.d.ts +4 -0
- package/apps/cli/dist/commands/account.js +275 -6
- package/apps/cli/dist/commands/app.js +12 -21
- package/apps/cli/dist/commands/daemon.js +145 -23
- package/apps/cli/dist/commands/gateway.js +697 -202
- package/apps/cli/dist/commands/interaction.d.ts +2 -0
- package/apps/cli/dist/commands/interaction.js +416 -0
- package/apps/cli/dist/commands/lib/args.js +26 -0
- package/apps/cli/dist/commands/lib/audit.js +2 -1
- package/apps/cli/dist/commands/lib/bootstrap-daemon.js +2 -1
- package/apps/cli/dist/commands/lib/bootstrap.js +3 -2
- package/apps/cli/dist/commands/lib/cli-exit.d.ts +6 -0
- package/apps/cli/dist/commands/lib/cli-exit.js +15 -0
- package/apps/cli/dist/commands/lib/exit.d.ts +1 -0
- package/apps/cli/dist/commands/lib/exit.js +41 -25
- package/apps/cli/dist/commands/lib/index.d.ts +2 -1
- package/apps/cli/dist/commands/lib/index.js +2 -1
- package/apps/cli/dist/commands/lib/json.d.ts +1 -1
- package/apps/cli/dist/commands/lib/json.js +8 -2
- package/apps/cli/dist/commands/lib/local-daemon.d.ts +13 -6
- package/apps/cli/dist/commands/lib/local-daemon.js +41 -86
- package/apps/cli/dist/commands/lib/loop-store.d.ts +4 -0
- package/apps/cli/dist/commands/lib/loop-store.js +29 -18
- package/apps/cli/dist/commands/lib/orchestrator.d.ts +4 -0
- package/apps/cli/dist/commands/lib/orchestrator.js +12 -0
- package/apps/cli/dist/commands/lib/paths.d.ts +1 -3
- package/apps/cli/dist/commands/lib/paths.js +6 -3
- package/apps/cli/dist/commands/lib/scope.d.ts +1 -0
- package/apps/cli/dist/commands/lib/scope.js +10 -2
- package/apps/cli/dist/commands/lib/status.js +28 -2
- package/apps/cli/dist/commands/lib/types.d.ts +34 -4
- package/apps/cli/dist/commands/lib/usage.js +95 -35
- package/apps/cli/dist/commands/lib/version.d.ts +8 -0
- package/apps/cli/dist/commands/lib/version.js +69 -0
- package/apps/cli/dist/commands/lib/workspace.d.ts +2 -1
- package/apps/cli/dist/commands/lib/workspace.js +28 -5
- package/apps/cli/dist/commands/loop.js +191 -149
- package/apps/cli/dist/commands/memory.d.ts +3 -0
- package/apps/cli/dist/commands/memory.js +161 -0
- package/apps/cli/dist/commands/orchestrator.d.ts +19 -0
- package/apps/cli/dist/commands/orchestrator.js +317 -58
- package/apps/cli/dist/commands/relay.js +16 -8
- package/apps/cli/dist/commands/session.d.ts +25 -0
- package/apps/cli/dist/commands/session.js +234 -60
- package/apps/cli/dist/commands/skill.js +29 -34
- package/apps/cli/dist/commands/token.js +9 -1
- package/apps/cli/dist/commands/turn.js +18 -1
- package/apps/cli/dist/index.js +13 -2
- package/apps/cli/dist/skills/registry.d.ts +1 -0
- package/apps/cli/dist/skills/registry.js +6 -3
- package/apps/daemon/dist/agent-interaction-ipc.d.ts +45 -0
- package/apps/daemon/dist/agent-interaction-ipc.js +578 -0
- package/apps/daemon/dist/agent-interaction-mcp.d.ts +6 -0
- package/apps/daemon/dist/agent-interaction-mcp.js +259 -0
- package/apps/daemon/dist/args.js +1 -1
- package/apps/daemon/dist/audit.js +4 -2
- package/apps/daemon/dist/gateway/adapters.d.ts +10 -2
- package/apps/daemon/dist/gateway/feishu.d.ts +16 -1
- package/apps/daemon/dist/gateway/feishu.js +78 -13
- package/apps/daemon/dist/gateway/runtime.d.ts +6 -0
- package/apps/daemon/dist/gateway/runtime.js +1448 -261
- package/apps/daemon/dist/gateway/store.d.ts +21 -69
- package/apps/daemon/dist/gateway/store.js +47 -201
- package/apps/daemon/dist/gateway/telegram.d.ts +9 -0
- package/apps/daemon/dist/gateway/telegram.js +387 -0
- package/apps/daemon/dist/interaction-claim-history.d.ts +4 -0
- package/apps/daemon/dist/interaction-claim-history.js +51 -0
- package/apps/daemon/dist/interaction-claim-journal.d.ts +20 -0
- package/apps/daemon/dist/interaction-claim-journal.js +139 -0
- package/apps/daemon/dist/interaction-local-state.d.ts +3 -0
- package/apps/daemon/dist/interaction-local-state.js +12 -0
- package/apps/daemon/dist/interaction-privacy.d.ts +10 -0
- package/apps/daemon/dist/interaction-privacy.js +23 -0
- package/apps/daemon/dist/interaction-prompt-redaction-cache.d.ts +7 -0
- package/apps/daemon/dist/interaction-prompt-redaction-cache.js +254 -0
- package/apps/daemon/dist/interaction-runtime.d.ts +58 -0
- package/apps/daemon/dist/interaction-runtime.js +674 -0
- package/apps/daemon/dist/loop/handlers.d.ts +5 -0
- package/apps/daemon/dist/loop/handlers.js +406 -0
- package/apps/daemon/dist/loop/runner.js +597 -68
- package/apps/daemon/dist/loop/schedule.d.ts +34 -0
- package/apps/daemon/dist/loop/schedule.js +161 -0
- package/apps/daemon/dist/loop/store.d.ts +11 -2
- package/apps/daemon/dist/loop/store.js +119 -34
- package/apps/daemon/dist/memory/access-lifecycle.d.ts +15 -0
- package/apps/daemon/dist/memory/access-lifecycle.js +85 -0
- package/apps/daemon/dist/memory/commands.d.ts +3 -0
- package/apps/daemon/dist/memory/commands.js +184 -0
- package/apps/daemon/dist/memory/config.d.ts +7 -0
- package/apps/daemon/dist/memory/config.js +24 -0
- package/apps/daemon/dist/memory/flush.d.ts +10 -0
- package/apps/daemon/dist/memory/flush.js +158 -0
- package/apps/daemon/dist/memory/inject.d.ts +23 -0
- package/apps/daemon/dist/memory/inject.js +57 -0
- package/apps/daemon/dist/memory/instructions.d.ts +9 -0
- package/apps/daemon/dist/memory/instructions.js +47 -0
- package/apps/daemon/dist/memory/managers.d.ts +8 -0
- package/apps/daemon/dist/memory/managers.js +68 -0
- package/apps/daemon/dist/paths.d.ts +3 -3
- package/apps/daemon/dist/paths.js +3 -3
- package/apps/daemon/dist/process.d.ts +41 -8
- package/apps/daemon/dist/process.js +491 -56
- package/apps/daemon/dist/relay/connection.d.ts +1 -1
- package/apps/daemon/dist/relay/connection.js +48 -5
- package/apps/daemon/dist/relay/devtools.d.ts +1 -1
- package/apps/daemon/dist/relay/devtools.js +13 -16
- package/apps/daemon/dist/relay/register.js +6 -0
- package/apps/daemon/dist/relay/send.d.ts +4 -3
- package/apps/daemon/dist/relay/send.js +51 -22
- package/apps/daemon/dist/runtime/cancel-settlement.d.ts +19 -0
- package/apps/daemon/dist/runtime/cancel-settlement.js +191 -0
- package/apps/daemon/dist/runtime/errors.d.ts +3 -0
- package/apps/daemon/dist/runtime/errors.js +37 -0
- package/apps/daemon/dist/runtime/event-pump.d.ts +14 -0
- package/apps/daemon/dist/runtime/event-pump.js +38 -0
- package/apps/daemon/dist/runtime/external-provider.js +3 -0
- package/apps/daemon/dist/runtime/selection.d.ts +10 -0
- package/apps/daemon/dist/runtime/selection.js +33 -0
- package/apps/daemon/dist/runtime/session-recovery.d.ts +10 -0
- package/apps/daemon/dist/runtime/session-recovery.js +130 -0
- package/apps/daemon/dist/session/create-session.d.ts +1 -1
- package/apps/daemon/dist/session/create-session.js +9 -2
- package/apps/daemon/dist/session/events.js +40 -6
- package/apps/daemon/dist/session/lifecycle.js +26 -12
- package/apps/daemon/dist/session/metadata.d.ts +3 -1
- package/apps/daemon/dist/session/metadata.js +29 -12
- package/apps/daemon/dist/session/prompt.js +330 -70
- package/apps/daemon/dist/start.js +139 -21
- package/apps/daemon/dist/state.d.ts +3 -0
- package/apps/daemon/dist/turn-coordinator.d.ts +2 -1
- package/apps/daemon/dist/turn-coordinator.js +3 -0
- package/apps/daemon/dist/types.d.ts +78 -12
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/connections.d.ts +21 -1
- package/apps/relay/dist/connections.js +1 -0
- package/apps/relay/dist/controller-handlers.d.ts +66 -5
- package/apps/relay/dist/controller-handlers.js +822 -111
- package/apps/relay/dist/db.js +2 -0
- package/apps/relay/dist/http-routes.js +163 -3
- package/apps/relay/dist/machine-command-result-handlers.js +80 -5
- package/apps/relay/dist/projections.js +13 -0
- package/apps/relay/dist/relay-context.d.ts +28 -4
- package/apps/relay/dist/relay-context.js +145 -10
- package/apps/relay/dist/scope.js +14 -0
- package/apps/relay/dist/session-projection-reducer.js +15 -3
- package/apps/relay/dist/session-record-handlers.d.ts +15 -0
- package/apps/relay/dist/session-record-handlers.js +148 -0
- package/apps/relay/dist/session-record-repository.d.ts +38 -0
- package/apps/relay/dist/session-record-repository.js +312 -0
- package/apps/relay/dist/types.d.ts +3 -1
- package/apps/relay/dist/websocket.js +6 -0
- package/build-identity.json +6 -0
- package/npm-shrinkwrap.json +76 -23
- package/package.json +5 -1
- package/packages/client/dist/client.d.ts +24 -1
- package/packages/client/dist/client.js +291 -13
- package/packages/client/dist/errors.d.ts +14 -1
- package/packages/client/dist/errors.js +14 -1
- package/packages/client/dist/index.d.ts +4 -2
- package/packages/client/dist/index.js +1 -0
- package/packages/client/dist/live-state.js +4 -0
- package/packages/client/dist/live-types.d.ts +29 -5
- package/packages/client/dist/live.d.ts +29 -1
- package/packages/client/dist/live.js +562 -78
- package/packages/client/dist/schedule-presets.d.ts +17 -0
- package/packages/client/dist/schedule-presets.js +33 -0
- package/packages/client/dist/transport.d.ts +10 -0
- package/packages/client/dist/transport.js +279 -62
- package/packages/client/dist/types.d.ts +144 -0
- package/packages/memory/dist/access.d.ts +44 -0
- package/packages/memory/dist/access.js +357 -0
- package/packages/memory/dist/chunk.d.ts +16 -0
- package/packages/memory/dist/chunk.js +51 -0
- package/packages/memory/dist/embeddings.d.ts +10 -0
- package/packages/memory/dist/embeddings.js +77 -0
- package/packages/memory/dist/index.d.ts +6 -0
- package/packages/memory/dist/index.js +6 -0
- package/packages/memory/dist/manager.d.ts +53 -0
- package/packages/memory/dist/manager.js +689 -0
- package/packages/memory/dist/paths.d.ts +11 -0
- package/packages/memory/dist/paths.js +42 -0
- package/packages/memory/dist/store.d.ts +19 -0
- package/packages/memory/dist/store.js +210 -0
- package/packages/memory/dist/types.d.ts +70 -0
- package/packages/memory/dist/types.js +8 -0
- package/packages/pie-provider/dist/index.js +150 -20
- package/packages/provider-protocol/dist/index.d.ts +96 -86
- package/packages/provider-protocol/dist/index.js +49 -42
- package/packages/runtime-cli/dist/claude-json.d.ts +6 -0
- package/packages/runtime-cli/dist/claude-json.js +6 -0
- package/packages/runtime-cli/dist/claude-turn.d.ts +6 -1
- package/packages/runtime-cli/dist/claude-turn.js +130 -16
- package/packages/runtime-cli/dist/codex-app-server.d.ts +25 -10
- package/packages/runtime-cli/dist/codex-app-server.js +582 -128
- package/packages/runtime-cli/dist/codex-json-rpc.d.ts +21 -0
- package/packages/runtime-cli/dist/codex-json-rpc.js +161 -53
- package/packages/runtime-cli/dist/codex-protocol.js +6 -2
- package/packages/runtime-cli/dist/index.d.ts +4 -1
- package/packages/runtime-cli/dist/index.js +146 -32
- package/packages/runtime-cli/dist/runtime-status.d.ts +20 -0
- package/packages/runtime-cli/dist/runtime-status.js +133 -0
- package/packages/runtime-cli/dist/session-environment.d.ts +7 -0
- package/packages/runtime-cli/dist/session-environment.js +24 -0
- package/packages/runtime-cli/dist/session-store.d.ts +10 -2
- package/packages/shared/dist/crypto.d.ts +106 -0
- package/packages/shared/dist/crypto.js +226 -0
- package/packages/shared/dist/diagnostics.d.ts +21 -0
- package/packages/shared/dist/diagnostics.js +156 -0
- package/packages/shared/dist/index.d.ts +2 -0
- package/packages/shared/dist/index.js +2 -0
- package/packages/shared/dist/interaction.d.ts +95 -0
- package/packages/shared/dist/interaction.js +169 -0
- package/packages/shared/dist/node/daemon-process.d.ts +59 -0
- package/packages/shared/dist/node/daemon-process.js +400 -0
- package/packages/shared/dist/node/gateway-store.d.ts +260 -0
- package/packages/shared/dist/node/gateway-store.js +2396 -0
- package/packages/shared/dist/process-command.d.ts +6 -0
- package/packages/shared/dist/process-command.js +113 -0
- package/packages/shared/dist/protocol-schemas.d.ts +23 -2
- package/packages/shared/dist/protocol-schemas.js +11 -4
- package/packages/shared/dist/protocol-types.d.ts +15 -1
- package/packages/shared/dist/protocol.d.ts +147 -1
- package/packages/shared/dist/protocol.js +157 -1
- package/packages/shared/dist/session-record.d.ts +2 -0
- package/packages/shared/dist/session-record.js +4 -0
- package/apps/relay/dist/gateway-handlers.d.ts +0 -23
- package/apps/relay/dist/gateway-handlers.js +0 -394
- package/apps/relay/dist/gateway-repository.d.ts +0 -10
- package/apps/relay/dist/gateway-repository.js +0 -90
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { CommandInput } from "./command.js";
|
|
2
|
+
export declare function confirmReset(flags: Record<string, string | boolean>, testIo?: {
|
|
3
|
+
interactive: boolean;
|
|
4
|
+
question?: () => Promise<string>;
|
|
5
|
+
}): Promise<void>;
|
|
2
6
|
export declare function handleAccount({ domain, action, flags }: CommandInput): Promise<boolean>;
|
|
@@ -1,7 +1,234 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { CliError, configDir, configPath, generateAccountSecret, ok, parsePairingStartResponse, randomId, readRelayJson, requireRelayUrl, stopLocalDaemon, throwRelayHttpError, writeConfig } from "./lib/index.js";
|
|
6
|
+
const daemonConfigPath = path.join(configDir, "daemon.json");
|
|
7
|
+
const accountConfigFiles = ["config.json", "daemon.json"];
|
|
8
|
+
function accountConfigTarget(file) {
|
|
9
|
+
return file === "config.json" ? configPath : daemonConfigPath;
|
|
10
|
+
}
|
|
11
|
+
export async function confirmReset(flags, testIo) {
|
|
12
|
+
if (flags.json === true) {
|
|
13
|
+
if (flags.yes === true)
|
|
14
|
+
return;
|
|
15
|
+
throw new CliError("account reset --json requires --yes", "CONFIRMATION_REQUIRED");
|
|
16
|
+
}
|
|
17
|
+
const interactive = testIo?.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
18
|
+
if (!interactive) {
|
|
19
|
+
if (flags.yes === true)
|
|
20
|
+
return;
|
|
21
|
+
throw new CliError("account reset requires --yes in non-interactive mode", "CONFIRMATION_REQUIRED");
|
|
22
|
+
}
|
|
23
|
+
if (testIo?.question) {
|
|
24
|
+
const answer = await testIo.question();
|
|
25
|
+
if (answer.trim() !== "RESET")
|
|
26
|
+
throw new CliError("Account reset cancelled", "CONFIRMATION_REQUIRED");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
try {
|
|
31
|
+
const answer = await prompt.question("Type RESET to remove this controller and daemon configuration: ");
|
|
32
|
+
if (answer.trim() !== "RESET")
|
|
33
|
+
throw new CliError("Account reset cancelled", "CONFIRMATION_REQUIRED");
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
prompt.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function confirmRestore(flags, requiresConfirmation, testIo) {
|
|
40
|
+
if (!requiresConfirmation)
|
|
41
|
+
return;
|
|
42
|
+
if (flags.json === true) {
|
|
43
|
+
if (flags.yes === true)
|
|
44
|
+
return;
|
|
45
|
+
throw new CliError("account restore --json requires --yes when replacing or removing configuration", "CONFIRMATION_REQUIRED");
|
|
46
|
+
}
|
|
47
|
+
const interactive = testIo?.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
48
|
+
if (!interactive) {
|
|
49
|
+
if (flags.yes === true)
|
|
50
|
+
return;
|
|
51
|
+
throw new CliError("account restore requires --yes in non-interactive mode when replacing or removing configuration", "CONFIRMATION_REQUIRED");
|
|
52
|
+
}
|
|
53
|
+
if (testIo?.question) {
|
|
54
|
+
const answer = await testIo.question();
|
|
55
|
+
if (answer.trim() !== "RESTORE")
|
|
56
|
+
throw new CliError("Account restore cancelled", "CONFIRMATION_REQUIRED");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
+
try {
|
|
61
|
+
const answer = await prompt.question("Type RESTORE to replace the current controller and daemon configuration: ");
|
|
62
|
+
if (answer.trim() !== "RESTORE")
|
|
63
|
+
throw new CliError("Account restore cancelled", "CONFIRMATION_REQUIRED");
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
prompt.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function copyConfigIfPresent(source, target) {
|
|
70
|
+
try {
|
|
71
|
+
const stat = await fs.lstat(source);
|
|
72
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
73
|
+
throw new CliError(`Refusing to copy non-regular config file: ${source}`, "CONFIG_BACKUP_UNSAFE");
|
|
74
|
+
await fs.copyFile(source, target);
|
|
75
|
+
await fs.chmod(target, 0o600);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === "ENOENT")
|
|
80
|
+
return false;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function backupAccountConfig(prefix = "account-reset") {
|
|
85
|
+
const stamp = new Date().toISOString().replace(/[:.]/gu, "-");
|
|
86
|
+
const backupPath = path.join(configDir, "backups", `${prefix}-${stamp}`);
|
|
87
|
+
await fs.mkdir(backupPath, { recursive: true, mode: 0o700 });
|
|
88
|
+
await fs.chmod(backupPath, 0o700);
|
|
89
|
+
const files = [];
|
|
90
|
+
if (await copyConfigIfPresent(configPath, path.join(backupPath, "config.json")))
|
|
91
|
+
files.push("config.json");
|
|
92
|
+
if (await copyConfigIfPresent(daemonConfigPath, path.join(backupPath, "daemon.json")))
|
|
93
|
+
files.push("daemon.json");
|
|
94
|
+
await fs.writeFile(path.join(backupPath, "manifest.json"), JSON.stringify({ version: 1, createdAt: new Date().toISOString(), files }, null, 2) + "\n", { mode: 0o600 });
|
|
95
|
+
return { path: backupPath, files };
|
|
96
|
+
}
|
|
97
|
+
async function requireDaemonStopped() {
|
|
98
|
+
let stop;
|
|
99
|
+
try {
|
|
100
|
+
stop = await stopLocalDaemon();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error instanceof CliError && ["DAEMON_PID_UNVERIFIED", "DAEMON_PID_VERSION_UNSUPPORTED", "DAEMON_PID_INVALID"].includes(error.code))
|
|
104
|
+
throw error;
|
|
105
|
+
throw new CliError(`Could not stop the local daemon: ${error instanceof Error ? error.message : String(error)}`, "DAEMON_STOP_FAILED");
|
|
106
|
+
}
|
|
107
|
+
if (stop.running) {
|
|
108
|
+
throw new CliError(`The local daemon${stop.pid ? ` (pid ${stop.pid})` : ""} did not stop. Configuration was not changed; stop it and retry.`, "DAEMON_STOP_FAILED");
|
|
109
|
+
}
|
|
110
|
+
return stop;
|
|
111
|
+
}
|
|
112
|
+
async function requireRegularBackupFile(filePath) {
|
|
113
|
+
let stat;
|
|
114
|
+
try {
|
|
115
|
+
stat = await fs.lstat(filePath);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error.code === "ENOENT") {
|
|
119
|
+
throw new CliError(`Account backup is missing ${path.basename(filePath)}.`, "ACCOUNT_BACKUP_INVALID");
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
124
|
+
throw new CliError(`Account backup contains an unsafe ${path.basename(filePath)}.`, "ACCOUNT_BACKUP_INVALID");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function stageAccountRestore(backupPath, files) {
|
|
128
|
+
await fs.mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
129
|
+
await fs.chmod(configDir, 0o700);
|
|
130
|
+
const stagingPath = await fs.mkdtemp(path.join(configDir, ".account-restore-"));
|
|
131
|
+
await fs.chmod(stagingPath, 0o700);
|
|
132
|
+
try {
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const source = path.join(backupPath, file);
|
|
135
|
+
await requireRegularBackupFile(source);
|
|
136
|
+
const contents = await fs.readFile(source, "utf8");
|
|
137
|
+
try {
|
|
138
|
+
JSON.parse(contents);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new CliError(`Account backup ${file} is not valid JSON.`, "ACCOUNT_BACKUP_INVALID");
|
|
142
|
+
}
|
|
143
|
+
const staged = path.join(stagingPath, file);
|
|
144
|
+
await fs.writeFile(staged, contents, { mode: 0o600, flag: "wx" });
|
|
145
|
+
await fs.chmod(staged, 0o600);
|
|
146
|
+
}
|
|
147
|
+
return { path: stagingPath, files };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
await fs.rm(stagingPath, { recursive: true, force: true });
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function currentAccountConfigFiles() {
|
|
155
|
+
const existing = [];
|
|
156
|
+
for (const file of accountConfigFiles) {
|
|
157
|
+
const target = accountConfigTarget(file);
|
|
158
|
+
try {
|
|
159
|
+
const stat = await fs.lstat(target);
|
|
160
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
161
|
+
throw new CliError(`Refusing to replace unsafe account config: ${target}`, "ACCOUNT_RESTORE_UNSAFE");
|
|
162
|
+
}
|
|
163
|
+
existing.push(file);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error.code === "ENOENT")
|
|
167
|
+
continue;
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return existing;
|
|
172
|
+
}
|
|
173
|
+
async function applyAccountRestore(staged) {
|
|
174
|
+
const existing = await currentAccountConfigFiles();
|
|
175
|
+
const rollbackPath = await fs.mkdtemp(path.join(configDir, ".account-restore-rollback-"));
|
|
176
|
+
await fs.chmod(rollbackPath, 0o700);
|
|
177
|
+
const moved = [];
|
|
178
|
+
const installed = [];
|
|
179
|
+
let result;
|
|
180
|
+
let preserveRollback = false;
|
|
181
|
+
try {
|
|
182
|
+
for (const file of existing) {
|
|
183
|
+
await fs.rename(accountConfigTarget(file), path.join(rollbackPath, file));
|
|
184
|
+
moved.push(file);
|
|
185
|
+
}
|
|
186
|
+
for (const file of staged.files) {
|
|
187
|
+
const target = accountConfigTarget(file);
|
|
188
|
+
await fs.rename(path.join(staged.path, file), target);
|
|
189
|
+
installed.push(file);
|
|
190
|
+
await fs.chmod(target, 0o600);
|
|
191
|
+
}
|
|
192
|
+
result = {
|
|
193
|
+
restored: staged.files.map(accountConfigTarget),
|
|
194
|
+
removed: existing.filter((file) => !staged.files.includes(file)).map(accountConfigTarget),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const rollbackFailures = [];
|
|
199
|
+
for (const file of [...installed].reverse()) {
|
|
200
|
+
try {
|
|
201
|
+
await fs.rm(accountConfigTarget(file), { force: true });
|
|
202
|
+
}
|
|
203
|
+
catch (rollbackError) {
|
|
204
|
+
rollbackFailures.push(`${file} cleanup: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const file of [...moved].reverse()) {
|
|
208
|
+
try {
|
|
209
|
+
const target = accountConfigTarget(file);
|
|
210
|
+
await fs.rename(path.join(rollbackPath, file), target);
|
|
211
|
+
await fs.chmod(target, 0o600);
|
|
212
|
+
}
|
|
213
|
+
catch (rollbackError) {
|
|
214
|
+
rollbackFailures.push(`${file} rollback: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
218
|
+
if (rollbackFailures.length > 0) {
|
|
219
|
+
preserveRollback = true;
|
|
220
|
+
throw new CliError(`Account restore failed and rollback was incomplete: ${detail}. ${rollbackFailures.join("; ")}. Recovery files remain in ${rollbackPath}`, "ACCOUNT_RESTORE_ROLLBACK_FAILED");
|
|
221
|
+
}
|
|
222
|
+
throw new CliError(`Account restore failed; the previous configuration was restored: ${detail}`, "ACCOUNT_RESTORE_FAILED");
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
if (!preserveRollback)
|
|
226
|
+
await fs.rm(rollbackPath, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
if (!result)
|
|
229
|
+
throw new CliError("Account restore did not produce a result", "ACCOUNT_RESTORE_FAILED");
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
5
232
|
export async function handleAccount({ domain, action, flags }) {
|
|
6
233
|
if (domain === "account" && action === "create") {
|
|
7
234
|
const relayUrl = requireRelayUrl(flags);
|
|
@@ -36,16 +263,58 @@ export async function handleAccount({ domain, action, flags }) {
|
|
|
36
263
|
return true;
|
|
37
264
|
}
|
|
38
265
|
if (domain === "account" && action === "reset") {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
266
|
+
await confirmReset(flags);
|
|
267
|
+
const stop = await requireDaemonStopped();
|
|
268
|
+
const backup = await backupAccountConfig();
|
|
42
269
|
await fs.rm(configPath, { force: true });
|
|
43
|
-
await fs.rm(
|
|
270
|
+
await fs.rm(daemonConfigPath, { force: true });
|
|
44
271
|
ok("account.reset", {
|
|
45
|
-
removed: [configPath,
|
|
272
|
+
removed: [configPath, daemonConfigPath],
|
|
273
|
+
backup,
|
|
274
|
+
restoreCommand: `happy-elves account restore --from ${JSON.stringify(backup.path)} --yes --json`,
|
|
46
275
|
stop,
|
|
47
276
|
});
|
|
48
277
|
return true;
|
|
49
278
|
}
|
|
279
|
+
if (domain === "account" && action === "restore") {
|
|
280
|
+
const backupPath = typeof flags.from === "string" && flags.from.trim() ? path.resolve(flags.from) : undefined;
|
|
281
|
+
if (!backupPath)
|
|
282
|
+
throw new CliError("Missing --from", "MISSING_ARGUMENT");
|
|
283
|
+
const manifestPath = path.join(backupPath, "manifest.json");
|
|
284
|
+
let manifest;
|
|
285
|
+
try {
|
|
286
|
+
await requireRegularBackupFile(manifestPath);
|
|
287
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
throw new CliError(`Invalid account backup: ${error instanceof Error ? error.message : String(error)}`, "ACCOUNT_BACKUP_INVALID");
|
|
291
|
+
}
|
|
292
|
+
if (manifest.version !== 1 ||
|
|
293
|
+
!Array.isArray(manifest.files) ||
|
|
294
|
+
!manifest.files.every((file) => accountConfigFiles.includes(file)) ||
|
|
295
|
+
new Set(manifest.files).size !== manifest.files.length) {
|
|
296
|
+
throw new CliError("Account backup manifest is invalid", "ACCOUNT_BACKUP_INVALID");
|
|
297
|
+
}
|
|
298
|
+
const files = manifest.files;
|
|
299
|
+
const staged = await stageAccountRestore(backupPath, files);
|
|
300
|
+
try {
|
|
301
|
+
const existing = await currentAccountConfigFiles();
|
|
302
|
+
await confirmRestore(flags, files.length === 0 || existing.length > 0);
|
|
303
|
+
const stop = await requireDaemonStopped();
|
|
304
|
+
const preRestoreBackup = await backupAccountConfig("account-pre-restore");
|
|
305
|
+
const restored = await applyAccountRestore({ path: staged.path, files });
|
|
306
|
+
ok("account.restore", {
|
|
307
|
+
backupPath,
|
|
308
|
+
...restored,
|
|
309
|
+
preRestoreBackup,
|
|
310
|
+
restorePreviousCommand: `happy-elves account restore --from ${JSON.stringify(preRestoreBackup.path)} --yes --json`,
|
|
311
|
+
stop,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
await fs.rm(staged.path, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
50
319
|
return false;
|
|
51
320
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { handleAccount } from "./account.js";
|
|
4
2
|
import { handleCollect } from "./collect.js";
|
|
5
3
|
import { handleConfig } from "./config.js";
|
|
@@ -7,6 +5,7 @@ import { handleDaemon } from "./daemon.js";
|
|
|
7
5
|
import { handleGateway } from "./gateway.js";
|
|
8
6
|
import { handleLoop } from "./loop.js";
|
|
9
7
|
import { handleMachine } from "./machine.js";
|
|
8
|
+
import { handleMemory } from "./memory.js";
|
|
10
9
|
import { handleOrchestrator } from "./orchestrator.js";
|
|
11
10
|
import { handleRelay } from "./relay.js";
|
|
12
11
|
import { handleRemote } from "./remote.js";
|
|
@@ -14,7 +13,7 @@ import { handleSession } from "./session.js";
|
|
|
14
13
|
import { handleSkill } from "./skill.js";
|
|
15
14
|
import { handleToken } from "./token.js";
|
|
16
15
|
import { handleTurn } from "./turn.js";
|
|
17
|
-
import { commandUsage, commandUsageText, handleCliError, parseCliFlags, skillText, startBootstrap, topLevelDoctor, topLevelStatus, usage, usageText } from "./lib/index.js";
|
|
16
|
+
import { buildIdentity, cliVersion, commandUsage, commandUsageText, handleCliError, ok, parseCliFlags, skillText, startBootstrap, topLevelDoctor, topLevelStatus, usage, usageText } from "./lib/index.js";
|
|
18
17
|
export { handleCliError };
|
|
19
18
|
const domainHandlers = [
|
|
20
19
|
handleAccount,
|
|
@@ -25,6 +24,7 @@ const domainHandlers = [
|
|
|
25
24
|
handleDaemon,
|
|
26
25
|
handleRelay,
|
|
27
26
|
handleMachine,
|
|
27
|
+
handleMemory,
|
|
28
28
|
handleGateway,
|
|
29
29
|
handleOrchestrator,
|
|
30
30
|
handleSession,
|
|
@@ -46,10 +46,18 @@ export async function main() {
|
|
|
46
46
|
console.log(usageText());
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
if (domain === "--version" || domain === "-v"
|
|
49
|
+
if (domain === "--version" || domain === "-v") {
|
|
50
50
|
console.log(cliVersion());
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
+
if (domain === "version") {
|
|
54
|
+
const { flags } = parseCliFlags(argv.slice(1));
|
|
55
|
+
if (flags.json === true)
|
|
56
|
+
ok("version", buildIdentity());
|
|
57
|
+
else
|
|
58
|
+
console.log(cliVersion());
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
53
61
|
const topLevelHelpRequested = argv[1] === "help" || argv.slice(1).some((arg) => arg === "--help" || arg === "-h");
|
|
54
62
|
if (topLevelHelpRequested && commandUsageText(domain)) {
|
|
55
63
|
console.log(commandUsageText(domain));
|
|
@@ -87,20 +95,3 @@ export async function main() {
|
|
|
87
95
|
}
|
|
88
96
|
usage();
|
|
89
97
|
}
|
|
90
|
-
function cliVersion() {
|
|
91
|
-
for (const candidate of [
|
|
92
|
-
path.resolve(import.meta.dirname, "..", "..", "..", "..", "package.json"),
|
|
93
|
-
path.resolve(import.meta.dirname, "..", "..", "..", "package.json"),
|
|
94
|
-
path.resolve(import.meta.dirname, "..", "..", "package.json"),
|
|
95
|
-
]) {
|
|
96
|
-
try {
|
|
97
|
-
const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
98
|
-
if (typeof parsed.version === "string" && parsed.version.trim())
|
|
99
|
-
return parsed.version.trim();
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// Try the next packaged/source layout.
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return "0.1.0";
|
|
106
|
-
}
|
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, idleSessionStatuses, localDaemonStatus, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, showMachine, spawnDaemonStart, stopLocalDaemon, throwRelayHttpError, wantsJson, writeDaemonConfig } from "./lib/index.js";
|
|
3
|
+
import { assertLocalDaemonVerified, CliError, compactText, ControllerClient, configDir, configPath, daemonPidPath, deriveOrchestration, idleSessionStatuses, localDaemonStatus, ok, parsePairingClaimResponse, randomId, readConfig, readDaemonConfig, readLocalAuditLog, readRelayJson, redactedDaemonConfig, requirePositional, requireRelayUrl, requireString, showMachine, spawnDaemonStart, stopLocalDaemon, throwRelayHttpError, wantsJson, writeDaemonConfig } from "./lib/index.js";
|
|
4
|
+
function positiveLimit(flags, fallback) {
|
|
5
|
+
const value = flags.limit;
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return fallback;
|
|
8
|
+
if (typeof value !== "string")
|
|
9
|
+
throw new CliError("Missing --limit value", "MISSING_ARGUMENT");
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 200)
|
|
12
|
+
throw new CliError(`Invalid --limit: ${value}`, "INVALID_ARGUMENT");
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
async function boundedMap(values, concurrency, map) {
|
|
16
|
+
const result = new Array(values.length);
|
|
17
|
+
let nextIndex = 0;
|
|
18
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
19
|
+
while (nextIndex < values.length) {
|
|
20
|
+
const index = nextIndex;
|
|
21
|
+
nextIndex += 1;
|
|
22
|
+
result[index] = await map(values[index]);
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function overviewUnsupported(error) {
|
|
28
|
+
return error.code === "OVERVIEW_UNSUPPORTED";
|
|
29
|
+
}
|
|
30
|
+
async function daemonOverview(client, input) {
|
|
31
|
+
try {
|
|
32
|
+
const sessions = [];
|
|
33
|
+
let cursor = input.cursor;
|
|
34
|
+
let first;
|
|
35
|
+
let loadNextPage = true;
|
|
36
|
+
while (loadNextPage) {
|
|
37
|
+
const page = await client.overview({
|
|
38
|
+
machineId: input.machineId,
|
|
39
|
+
limit: input.includeSessions ? (input.all ? 200 : input.limit) : 0,
|
|
40
|
+
cursor,
|
|
41
|
+
});
|
|
42
|
+
first ??= page;
|
|
43
|
+
if (input.includeSessions)
|
|
44
|
+
sessions.push(...page.sessions);
|
|
45
|
+
cursor = page.page.nextCursor;
|
|
46
|
+
loadNextPage = input.all && page.page.hasMore && Boolean(cursor);
|
|
47
|
+
}
|
|
48
|
+
const overview = first;
|
|
49
|
+
if (!overview)
|
|
50
|
+
throw new CliError("Overview response is empty", "RELAY_RESPONSE_INVALID");
|
|
51
|
+
return {
|
|
52
|
+
machines: overview.machines,
|
|
53
|
+
sessions,
|
|
54
|
+
counts: overview.sessionCounts,
|
|
55
|
+
page: {
|
|
56
|
+
returned: sessions.length,
|
|
57
|
+
total: overview.page.total,
|
|
58
|
+
hasMore: input.includeSessions && !input.all ? overview.page.hasMore : false,
|
|
59
|
+
...(input.includeSessions && !input.all ? { limit: input.limit } : {}),
|
|
60
|
+
...(!input.all && overview.page.nextCursor ? { nextCursor: overview.page.nextCursor } : {}),
|
|
61
|
+
source: "overview",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (!overviewUnsupported(error))
|
|
67
|
+
throw error;
|
|
68
|
+
if (input.cursor)
|
|
69
|
+
throw new CliError("This relay does not support daemon status cursors; retry without --cursor", "OVERVIEW_CURSOR_UNSUPPORTED");
|
|
70
|
+
const snapshot = await client.snapshot();
|
|
71
|
+
const machines = input.machineId ? snapshot.machines.filter((machine) => machine.id === input.machineId) : snapshot.machines;
|
|
72
|
+
const machineIds = new Set(machines.map((machine) => machine.id));
|
|
73
|
+
const allSessions = snapshot.sessions.filter((session) => machineIds.has(session.machineId));
|
|
74
|
+
const counts = {};
|
|
75
|
+
for (const session of allSessions)
|
|
76
|
+
counts[session.status] = (counts[session.status] ?? 0) + 1;
|
|
77
|
+
const sessions = input.includeSessions ? (input.all ? allSessions : allSessions.slice(0, input.limit)) : [];
|
|
78
|
+
return {
|
|
79
|
+
machines,
|
|
80
|
+
sessions,
|
|
81
|
+
counts,
|
|
82
|
+
page: {
|
|
83
|
+
returned: sessions.length,
|
|
84
|
+
total: allSessions.length,
|
|
85
|
+
hasMore: input.includeSessions && sessions.length < allSessions.length,
|
|
86
|
+
...(input.includeSessions && !input.all ? { limit: input.limit } : {}),
|
|
87
|
+
source: "bootstrap-fallback",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
4
92
|
export async function handleDaemon({ domain, action, flags }) {
|
|
5
93
|
if (domain === "daemon" && action === "pair") {
|
|
6
94
|
const relayUrl = requireRelayUrl(flags);
|
|
@@ -113,6 +201,7 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
113
201
|
}
|
|
114
202
|
if (domain === "daemon" && action === "start") {
|
|
115
203
|
const status = await localDaemonStatus();
|
|
204
|
+
assertLocalDaemonVerified(status);
|
|
116
205
|
if (status.running) {
|
|
117
206
|
ok("daemon.start", { ...status, started: false });
|
|
118
207
|
return true;
|
|
@@ -138,7 +227,7 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
138
227
|
if (domain === "daemon" && action === "status" && flags.local === true) {
|
|
139
228
|
const local = await localDaemonStatus();
|
|
140
229
|
if (!wantsJson(flags)) {
|
|
141
|
-
console.log(local.running ? `Daemon: running${local.pid ? ` pid ${local.pid}` : ""}` : "Daemon: stopped");
|
|
230
|
+
console.log(local.running ? `Daemon: running${local.pid ? ` pid ${local.pid}` : ""}` : local.state === "unverified" ? `Daemon: unverified live pid${local.pid ? ` ${local.pid}` : ""}` : "Daemon: stopped");
|
|
142
231
|
console.log(`Pid file: ${local.pidPath}`);
|
|
143
232
|
return true;
|
|
144
233
|
}
|
|
@@ -150,17 +239,30 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
150
239
|
const client = new ControllerClient(config);
|
|
151
240
|
const local = await localDaemonStatus();
|
|
152
241
|
const response = await fetch(`${config.relayUrl}/health`);
|
|
153
|
-
const snapshot = await client.snapshot();
|
|
154
242
|
const machineFilter = flags.machine === true ? requirePositional(undefined, "machineId") : typeof flags.machine === "string" ? flags.machine : undefined;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
243
|
+
const includeSessions = flags.verbose === true || flags.all === true;
|
|
244
|
+
const all = flags.all === true;
|
|
245
|
+
const cursor = typeof flags.cursor === "string" && flags.cursor.trim() ? flags.cursor.trim() : undefined;
|
|
246
|
+
if (all && flags.limit !== undefined)
|
|
247
|
+
throw new CliError("--all and --limit are mutually exclusive", "INVALID_ARGUMENT");
|
|
248
|
+
if (all && cursor)
|
|
249
|
+
throw new CliError("--all and --cursor are mutually exclusive", "INVALID_ARGUMENT");
|
|
250
|
+
if (cursor && !includeSessions)
|
|
251
|
+
throw new CliError("--cursor requires --verbose", "INVALID_ARGUMENT");
|
|
252
|
+
if (!all && flags.limit !== undefined && flags.verbose !== true) {
|
|
253
|
+
throw new CliError("--limit requires --verbose", "INVALID_ARGUMENT");
|
|
254
|
+
}
|
|
255
|
+
const limit = positiveLimit(flags, 20);
|
|
256
|
+
const overview = await daemonOverview(client, { machineId: machineFilter, includeSessions, all, limit, cursor });
|
|
257
|
+
const machines = overview.machines;
|
|
158
258
|
if (machineFilter && machines.length === 0)
|
|
159
259
|
throw new CliError("Machine not found", "MACHINE_NOT_FOUND");
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
260
|
+
const projectedMachines = includeSessions
|
|
261
|
+
? await Promise.all((await client.snapshot()).machines
|
|
262
|
+
.filter((machine) => machines.some((summary) => summary.id === machine.id))
|
|
263
|
+
.map((machine) => showMachine(client, machine)))
|
|
264
|
+
: [];
|
|
265
|
+
const projectedSessions = includeSessions ? await boundedMap(overview.sessions, 4, async (session) => ({
|
|
164
266
|
sessionId: session.id,
|
|
165
267
|
machineId: session.machineId,
|
|
166
268
|
agent: session.agent,
|
|
@@ -168,46 +270,66 @@ export async function handleDaemon({ domain, action, flags }) {
|
|
|
168
270
|
orchestration: deriveOrchestration(session, await client.history(session.id, 20)),
|
|
169
271
|
updatedAt: session.updatedAt,
|
|
170
272
|
capabilities: session.capabilities,
|
|
171
|
-
}))
|
|
273
|
+
})) : [];
|
|
274
|
+
const sessionCount = overview.page.total;
|
|
275
|
+
const runningSessionCount = overview.counts.running ?? 0;
|
|
276
|
+
const idleSessionCount = idleSessionStatuses.reduce((sum, status) => sum + (overview.counts[status] ?? 0), 0);
|
|
172
277
|
const data = {
|
|
173
278
|
configPath,
|
|
174
279
|
local,
|
|
175
280
|
relayUrl: config.relayUrl,
|
|
176
281
|
relayReachable: response.ok,
|
|
177
282
|
relay: response.ok ? await response.json() : await response.text(),
|
|
178
|
-
machines: projectedMachines.map((machine) => ({
|
|
283
|
+
machines: includeSessions ? projectedMachines.map((machine) => ({
|
|
179
284
|
machineId: machine.id,
|
|
180
285
|
name: machine.metadata.name ?? machine.name,
|
|
181
286
|
online: machine.online,
|
|
182
287
|
lastSeen: machine.lastSeen,
|
|
183
|
-
defaultCwd: machine.defaultCwd,
|
|
184
288
|
daemonVersion: machine.capabilities.daemonVersion,
|
|
289
|
+
defaultCwd: machine.defaultCwd,
|
|
185
290
|
acpxVersion: machine.capabilities.acpxVersion,
|
|
186
291
|
sessionPrimitives: machine.capabilities.sessionPrimitives,
|
|
187
292
|
agents: machine.capabilities.agents,
|
|
188
293
|
metadata: machine.metadata,
|
|
294
|
+
})) : machines.map((machine) => ({
|
|
295
|
+
machineId: machine.id,
|
|
296
|
+
name: machine.name,
|
|
297
|
+
online: machine.online,
|
|
298
|
+
lastSeen: machine.lastSeen,
|
|
189
299
|
})),
|
|
190
|
-
sessions: projectedSessions,
|
|
300
|
+
...(includeSessions ? { sessions: projectedSessions, page: overview.page } : {}),
|
|
191
301
|
summary: {
|
|
192
302
|
machineCount: machines.length,
|
|
193
303
|
onlineMachineCount: machines.filter((machine) => machine.online).length,
|
|
194
|
-
sessionCount
|
|
195
|
-
runningSessionCount
|
|
196
|
-
idleSessionCount
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
304
|
+
sessionCount,
|
|
305
|
+
runningSessionCount,
|
|
306
|
+
idleSessionCount,
|
|
307
|
+
statusCounts: overview.counts,
|
|
308
|
+
...(includeSessions ? {
|
|
309
|
+
blockedSessionCount: projectedSessions.filter((session) => session.orchestration.blocked).length,
|
|
310
|
+
activeSessionCount: projectedSessions.filter((session) => session.orchestration.state === "active").length,
|
|
311
|
+
completedSessionCount: projectedSessions.filter((session) => session.orchestration.completed).length,
|
|
312
|
+
needsUserSessionCount: projectedSessions.filter((session) => session.orchestration.needsUser).length,
|
|
313
|
+
} : {}),
|
|
201
314
|
},
|
|
202
315
|
};
|
|
203
316
|
if (!wantsJson(flags)) {
|
|
204
317
|
console.log(`Relay: ${data.relayUrl} ${data.relayReachable ? "ok" : "unreachable"}`);
|
|
205
318
|
console.log(data.local.running ? `Local daemon: running${data.local.pid ? ` pid ${data.local.pid}` : ""}` : "Local daemon: stopped");
|
|
206
319
|
console.log(`Machines: ${data.summary.onlineMachineCount}/${data.summary.machineCount} online`);
|
|
207
|
-
console.log(`Sessions: ${data.summary.sessionCount} total, ${data.summary.runningSessionCount} running
|
|
320
|
+
console.log(`Sessions: ${data.summary.sessionCount} total, ${data.summary.runningSessionCount} running`);
|
|
208
321
|
for (const machine of data.machines) {
|
|
209
322
|
console.log(`- ${machine.name} (${machine.machineId}) ${machine.online ? "online" : "offline"}`);
|
|
210
|
-
|
|
323
|
+
if ("defaultCwd" in machine)
|
|
324
|
+
console.log(` cwd: ${machine.defaultCwd ?? "-"}`);
|
|
325
|
+
}
|
|
326
|
+
if (includeSessions) {
|
|
327
|
+
for (const session of projectedSessions) {
|
|
328
|
+
console.log(`- ${session.sessionId} ${session.status} ${session.orchestration.state} ${session.agent} machine:${session.machineId}`);
|
|
329
|
+
}
|
|
330
|
+
console.log(`Page: ${overview.page.returned} of ${overview.page.total} sessions${overview.page.hasMore ? "; more available" : "; complete"}`);
|
|
331
|
+
if (overview.page.nextCursor)
|
|
332
|
+
console.log(`Next cursor: ${overview.page.nextCursor}`);
|
|
211
333
|
}
|
|
212
334
|
return true;
|
|
213
335
|
}
|