@otto-code/cli 0.5.0
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/bin/otto +3 -0
- package/dist/classify.d.ts +19 -0
- package/dist/classify.js +49 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +113 -0
- package/dist/commands/agent/archive.d.ts +18 -0
- package/dist/commands/agent/archive.js +102 -0
- package/dist/commands/agent/attach.d.ts +11 -0
- package/dist/commands/agent/attach.js +159 -0
- package/dist/commands/agent/delete.d.ts +15 -0
- package/dist/commands/agent/delete.js +107 -0
- package/dist/commands/agent/import.d.ts +14 -0
- package/dist/commands/agent/import.js +122 -0
- package/dist/commands/agent/index.d.ts +3 -0
- package/dist/commands/agent/index.js +48 -0
- package/dist/commands/agent/inspect.d.ts +15 -0
- package/dist/commands/agent/inspect.js +209 -0
- package/dist/commands/agent/logs.d.ts +19 -0
- package/dist/commands/agent/logs.js +180 -0
- package/dist/commands/agent/ls.d.ts +44 -0
- package/dist/commands/agent/ls.js +204 -0
- package/dist/commands/agent/mode.d.ts +18 -0
- package/dist/commands/agent/mode.js +100 -0
- package/dist/commands/agent/reload.d.ts +15 -0
- package/dist/commands/agent/reload.js +75 -0
- package/dist/commands/agent/run.d.ts +44 -0
- package/dist/commands/agent/run.js +464 -0
- package/dist/commands/agent/send.d.ts +19 -0
- package/dist/commands/agent/send.js +187 -0
- package/dist/commands/agent/stop.d.ts +17 -0
- package/dist/commands/agent/stop.js +115 -0
- package/dist/commands/agent/update.d.ts +18 -0
- package/dist/commands/agent/update.js +139 -0
- package/dist/commands/agent/wait.d.ts +17 -0
- package/dist/commands/agent/wait.js +158 -0
- package/dist/commands/chat/create.d.ts +9 -0
- package/dist/commands/chat/create.js +23 -0
- package/dist/commands/chat/delete.d.ts +6 -0
- package/dist/commands/chat/delete.js +20 -0
- package/dist/commands/chat/index.d.ts +3 -0
- package/dist/commands/chat/index.js +47 -0
- package/dist/commands/chat/inspect.d.ts +6 -0
- package/dist/commands/chat/inspect.js +20 -0
- package/dist/commands/chat/ls.d.ts +6 -0
- package/dist/commands/chat/ls.js +20 -0
- package/dist/commands/chat/post.d.ts +9 -0
- package/dist/commands/chat/post.js +28 -0
- package/dist/commands/chat/read.d.ts +11 -0
- package/dist/commands/chat/read.js +40 -0
- package/dist/commands/chat/schema.d.ts +36 -0
- package/dist/commands/chat/schema.js +81 -0
- package/dist/commands/chat/shared.d.ts +19 -0
- package/dist/commands/chat/shared.js +118 -0
- package/dist/commands/chat/wait.d.ts +9 -0
- package/dist/commands/chat/wait.js +45 -0
- package/dist/commands/daemon/index.d.ts +3 -0
- package/dist/commands/daemon/index.js +57 -0
- package/dist/commands/daemon/local-daemon.d.ts +78 -0
- package/dist/commands/daemon/local-daemon.js +544 -0
- package/dist/commands/daemon/pair.d.ts +9 -0
- package/dist/commands/daemon/pair.js +76 -0
- package/dist/commands/daemon/restart.d.ts +12 -0
- package/dist/commands/daemon/restart.js +99 -0
- package/dist/commands/daemon/runtime-toolchain.d.ts +6 -0
- package/dist/commands/daemon/runtime-toolchain.js +87 -0
- package/dist/commands/daemon/set-password.d.ts +17 -0
- package/dist/commands/daemon/set-password.js +82 -0
- package/dist/commands/daemon/start.d.ts +6 -0
- package/dist/commands/daemon/start.js +56 -0
- package/dist/commands/daemon/status.d.ts +10 -0
- package/dist/commands/daemon/status.js +338 -0
- package/dist/commands/daemon/stop.d.ts +15 -0
- package/dist/commands/daemon/stop.js +60 -0
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +89 -0
- package/dist/commands/loop/index.d.ts +3 -0
- package/dist/commands/loop/index.js +18 -0
- package/dist/commands/loop/inspect.d.ts +13 -0
- package/dist/commands/loop/inspect.js +100 -0
- package/dist/commands/loop/logs.d.ts +8 -0
- package/dist/commands/loop/logs.js +69 -0
- package/dist/commands/loop/ls.d.ts +18 -0
- package/dist/commands/loop/ls.js +62 -0
- package/dist/commands/loop/run.d.ts +30 -0
- package/dist/commands/loop/run.js +162 -0
- package/dist/commands/loop/stop.d.ts +15 -0
- package/dist/commands/loop/stop.js +56 -0
- package/dist/commands/loop/types.d.ts +138 -0
- package/dist/commands/loop/types.js +2 -0
- package/dist/commands/onboard.d.ts +10 -0
- package/dist/commands/onboard.js +416 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +81 -0
- package/dist/commands/permit/allow.d.ts +20 -0
- package/dist/commands/permit/allow.js +133 -0
- package/dist/commands/permit/deny.d.ts +12 -0
- package/dist/commands/permit/deny.js +105 -0
- package/dist/commands/permit/index.d.ts +3 -0
- package/dist/commands/permit/index.js +27 -0
- package/dist/commands/permit/ls.d.ts +18 -0
- package/dist/commands/permit/ls.js +66 -0
- package/dist/commands/provider/index.d.ts +3 -0
- package/dist/commands/provider/index.js +16 -0
- package/dist/commands/provider/ls.d.ts +19 -0
- package/dist/commands/provider/ls.js +73 -0
- package/dist/commands/provider/models.d.ts +20 -0
- package/dist/commands/provider/models.js +53 -0
- package/dist/commands/schedule/create.d.ts +18 -0
- package/dist/commands/schedule/create.js +40 -0
- package/dist/commands/schedule/delete.d.ts +10 -0
- package/dist/commands/schedule/delete.js +32 -0
- package/dist/commands/schedule/index.d.ts +3 -0
- package/dist/commands/schedule/index.js +66 -0
- package/dist/commands/schedule/inspect.d.ts +6 -0
- package/dist/commands/schedule/inspect.js +24 -0
- package/dist/commands/schedule/logs.d.ts +6 -0
- package/dist/commands/schedule/logs.js +23 -0
- package/dist/commands/schedule/ls.d.ts +5 -0
- package/dist/commands/schedule/ls.js +23 -0
- package/dist/commands/schedule/pause.d.ts +5 -0
- package/dist/commands/schedule/pause.js +23 -0
- package/dist/commands/schedule/resume.d.ts +5 -0
- package/dist/commands/schedule/resume.js +23 -0
- package/dist/commands/schedule/run-once.d.ts +5 -0
- package/dist/commands/schedule/run-once.js +23 -0
- package/dist/commands/schedule/schema.d.ts +21 -0
- package/dist/commands/schedule/schema.js +67 -0
- package/dist/commands/schedule/shared.d.ts +56 -0
- package/dist/commands/schedule/shared.js +346 -0
- package/dist/commands/schedule/types.d.ts +168 -0
- package/dist/commands/schedule/types.js +2 -0
- package/dist/commands/schedule/update.d.ts +21 -0
- package/dist/commands/schedule/update.js +39 -0
- package/dist/commands/speech/index.d.ts +3 -0
- package/dist/commands/speech/index.js +5 -0
- package/dist/commands/terminal/capture.d.ts +10 -0
- package/dist/commands/terminal/capture.js +68 -0
- package/dist/commands/terminal/create.d.ts +10 -0
- package/dist/commands/terminal/create.js +38 -0
- package/dist/commands/terminal/index.d.ts +3 -0
- package/dist/commands/terminal/index.js +43 -0
- package/dist/commands/terminal/kill.d.ts +6 -0
- package/dist/commands/terminal/kill.js +36 -0
- package/dist/commands/terminal/ls.d.ts +10 -0
- package/dist/commands/terminal/ls.js +21 -0
- package/dist/commands/terminal/schema.d.ts +18 -0
- package/dist/commands/terminal/schema.js +23 -0
- package/dist/commands/terminal/send-keys.d.ts +7 -0
- package/dist/commands/terminal/send-keys.js +76 -0
- package/dist/commands/terminal/shared.d.ts +12 -0
- package/dist/commands/terminal/shared.js +61 -0
- package/dist/commands/worktree/archive.d.ts +20 -0
- package/dist/commands/worktree/archive.js +104 -0
- package/dist/commands/worktree/create-input.d.ts +15 -0
- package/dist/commands/worktree/create-input.js +56 -0
- package/dist/commands/worktree/create.d.ts +11 -0
- package/dist/commands/worktree/create.js +58 -0
- package/dist/commands/worktree/index.d.ts +3 -0
- package/dist/commands/worktree/index.js +25 -0
- package/dist/commands/worktree/ls.d.ts +19 -0
- package/dist/commands/worktree/ls.js +97 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/output/index.d.ts +45 -0
- package/dist/output/index.js +47 -0
- package/dist/output/json.d.ts +11 -0
- package/dist/output/json.js +37 -0
- package/dist/output/quiet.d.ts +9 -0
- package/dist/output/quiet.js +22 -0
- package/dist/output/render.d.ts +15 -0
- package/dist/output/render.js +83 -0
- package/dist/output/table.d.ts +13 -0
- package/dist/output/table.js +135 -0
- package/dist/output/types.d.ts +73 -0
- package/dist/output/types.js +8 -0
- package/dist/output/with-output.d.ts +41 -0
- package/dist/output/with-output.js +87 -0
- package/dist/output/yaml.d.ts +11 -0
- package/dist/output/yaml.js +38 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.js +33 -0
- package/dist/utils/client-id.d.ts +2 -0
- package/dist/utils/client-id.js +37 -0
- package/dist/utils/client.d.ts +44 -0
- package/dist/utils/client.js +330 -0
- package/dist/utils/command-options.d.ts +6 -0
- package/dist/utils/command-options.js +17 -0
- package/dist/utils/duration.d.ts +7 -0
- package/dist/utils/duration.js +38 -0
- package/dist/utils/errors.d.ts +5 -0
- package/dist/utils/errors.js +10 -0
- package/dist/utils/paths.d.ts +12 -0
- package/dist/utils/paths.js +22 -0
- package/dist/utils/provider-model.d.ts +11 -0
- package/dist/utils/provider-model.js +49 -0
- package/dist/utils/timeline.d.ts +11 -0
- package/dist/utils/timeline.js +11 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/package.json +48 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { getOrCreateServerId, findExecutable, execCommand } from "@otto-code/server";
|
|
3
|
+
import { connectToDaemon } from "../../utils/client.js";
|
|
4
|
+
import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
|
|
5
|
+
import { resolveNodePathFromPid } from "./runtime-toolchain.js";
|
|
6
|
+
const DAEMON_STATUS_PROBE_TIMEOUT_MS = 1500;
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
function normalizeError(error) {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
return error.message;
|
|
11
|
+
}
|
|
12
|
+
return String(error);
|
|
13
|
+
}
|
|
14
|
+
function shortenMessage(message, max = 120) {
|
|
15
|
+
const normalized = message.replace(/\s+/g, " ").trim();
|
|
16
|
+
if (normalized.length <= max) {
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
return `${normalized.slice(0, max - 3)}...`;
|
|
20
|
+
}
|
|
21
|
+
function appendNote(current, next) {
|
|
22
|
+
if (!next)
|
|
23
|
+
return current;
|
|
24
|
+
if (!current)
|
|
25
|
+
return next;
|
|
26
|
+
return `${current}; ${next}`;
|
|
27
|
+
}
|
|
28
|
+
function resolveCliVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const packageJson = require("../../../package.json");
|
|
31
|
+
if (typeof packageJson.version === "string" && packageJson.version.trim().length > 0) {
|
|
32
|
+
return packageJson.version.trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Fall through.
|
|
37
|
+
}
|
|
38
|
+
return "unknown";
|
|
39
|
+
}
|
|
40
|
+
function createStatusSchema(status) {
|
|
41
|
+
return {
|
|
42
|
+
idField: "key",
|
|
43
|
+
columns: [
|
|
44
|
+
{ header: "KEY", field: "key" },
|
|
45
|
+
{
|
|
46
|
+
header: "VALUE",
|
|
47
|
+
field: "value",
|
|
48
|
+
color: (_, item) => {
|
|
49
|
+
if (item.key === "Local Daemon") {
|
|
50
|
+
if (item.value === "running")
|
|
51
|
+
return "green";
|
|
52
|
+
if (item.value === "unresponsive")
|
|
53
|
+
return "yellow";
|
|
54
|
+
return "red";
|
|
55
|
+
}
|
|
56
|
+
if (item.key === "Connected Daemon") {
|
|
57
|
+
if (item.value === "reachable")
|
|
58
|
+
return "green";
|
|
59
|
+
if (item.value === "not_probed" || item.value === "auth_required")
|
|
60
|
+
return "yellow";
|
|
61
|
+
return "red";
|
|
62
|
+
}
|
|
63
|
+
if (item.key.startsWith(" ")) {
|
|
64
|
+
if (item.value === "not found" || item.value === "not found (daemon)")
|
|
65
|
+
return "red";
|
|
66
|
+
if (item.value.endsWith("(--version failed)"))
|
|
67
|
+
return "yellow";
|
|
68
|
+
return "green";
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
serialize: () => status,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function toStatusRows(status) {
|
|
78
|
+
const rows = [
|
|
79
|
+
{ key: "Server ID", value: status.serverId ?? "-" },
|
|
80
|
+
{ key: "Local Daemon", value: status.localDaemon },
|
|
81
|
+
{ key: "Connected Daemon", value: status.connectedDaemon },
|
|
82
|
+
{ key: "Home", value: status.home },
|
|
83
|
+
{ key: "Listen", value: status.listen },
|
|
84
|
+
{ key: "Relay", value: status.relay },
|
|
85
|
+
{ key: "Hostname", value: status.hostname ?? "-" },
|
|
86
|
+
{ key: "PID", value: status.pid === null ? "-" : String(status.pid) },
|
|
87
|
+
{ key: "Started", value: status.startedAt ?? "-" },
|
|
88
|
+
{ key: "Owner", value: status.owner ?? "-" },
|
|
89
|
+
{ key: "Logs", value: status.logPath },
|
|
90
|
+
{ key: "Daemon Node", value: status.daemonNode },
|
|
91
|
+
{ key: "CLI Node", value: status.cliNode },
|
|
92
|
+
{ key: "CLI", value: status.cliVersion },
|
|
93
|
+
{ key: "Daemon Version", value: status.daemonVersion ?? "-" },
|
|
94
|
+
];
|
|
95
|
+
if (status.note) {
|
|
96
|
+
rows.push({ key: "Note", value: status.note });
|
|
97
|
+
}
|
|
98
|
+
rows.push({ key: "", value: "" });
|
|
99
|
+
rows.push({ key: "Providers", value: "" });
|
|
100
|
+
for (const provider of status.providers) {
|
|
101
|
+
if (provider.source === "daemon") {
|
|
102
|
+
if (!provider.path) {
|
|
103
|
+
rows.push({ key: ` ${provider.label}`, value: "not found (daemon)" });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (daemon)` });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (!provider.path) {
|
|
110
|
+
rows.push({ key: ` ${provider.label}`, value: "not found" });
|
|
111
|
+
}
|
|
112
|
+
else if (!provider.version) {
|
|
113
|
+
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (--version failed)` });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
rows.push({ key: ` ${provider.label}`, value: `${provider.path} (${provider.version})` });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
const PROVIDER_BINARIES = [
|
|
122
|
+
{ label: "Claude", binary: "claude" },
|
|
123
|
+
{ label: "Codex", binary: "codex" },
|
|
124
|
+
{ label: "OpenCode", binary: "opencode" },
|
|
125
|
+
];
|
|
126
|
+
async function checkProviderBinary(binary) {
|
|
127
|
+
const binaryPath = await findExecutable(binary);
|
|
128
|
+
if (!binaryPath) {
|
|
129
|
+
return { path: null, version: null };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const { stdout } = await execCommand(binaryPath, ["--version"], {
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
});
|
|
135
|
+
return { path: binaryPath, version: stdout.trim() || null };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { path: binaryPath, version: null };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function checkProviderBinaries() {
|
|
142
|
+
const results = await Promise.all(PROVIDER_BINARIES.map(async ({ label, binary }) => {
|
|
143
|
+
const result = await checkProviderBinary(binary);
|
|
144
|
+
return Object.assign({ label }, result);
|
|
145
|
+
}));
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
function resolveOwnerLabel(uid, hostname) {
|
|
149
|
+
if (uid === undefined && !hostname) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const uidPart = uid === undefined ? "?" : String(uid);
|
|
153
|
+
const hostPart = hostname ?? "unknown-host";
|
|
154
|
+
return `${uidPart}@${hostPart}`;
|
|
155
|
+
}
|
|
156
|
+
function classifyDaemonAuthProbeFailure(error) {
|
|
157
|
+
if (!(error instanceof Error))
|
|
158
|
+
return null;
|
|
159
|
+
if (error.message === "Password required")
|
|
160
|
+
return "auth_required";
|
|
161
|
+
if (error.message === "Incorrect password")
|
|
162
|
+
return "auth_failed";
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
function describeDaemonAuthProbeFailure(host, failure) {
|
|
166
|
+
if (failure === "auth_required") {
|
|
167
|
+
return `Daemon is reachable at ${host} but requires a password. Set OTTO_PASSWORD and retry.`;
|
|
168
|
+
}
|
|
169
|
+
return `Daemon is reachable at ${host} but the supplied password was rejected. Check OTTO_PASSWORD and retry.`;
|
|
170
|
+
}
|
|
171
|
+
async function probeDaemonOverWebsocket(args) {
|
|
172
|
+
const { host, state } = args;
|
|
173
|
+
let client;
|
|
174
|
+
try {
|
|
175
|
+
client = await connectToDaemon({ host, timeout: 1500 });
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const authFailure = classifyDaemonAuthProbeFailure(error);
|
|
179
|
+
if (authFailure) {
|
|
180
|
+
return {
|
|
181
|
+
connectedDaemon: authFailure,
|
|
182
|
+
note: describeDaemonAuthProbeFailure(host, authFailure),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (state.running) {
|
|
186
|
+
return {
|
|
187
|
+
connectedDaemon: "unreachable",
|
|
188
|
+
localDaemonOverride: "unresponsive",
|
|
189
|
+
note: `Local daemon PID is running but websocket at ${host} is not reachable`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { connectedDaemon: "unreachable" };
|
|
193
|
+
}
|
|
194
|
+
const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
|
|
195
|
+
try {
|
|
196
|
+
const statusPayload = await client.getDaemonStatus({
|
|
197
|
+
timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
|
|
198
|
+
});
|
|
199
|
+
const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
|
|
200
|
+
const daemonProviders = statusPayload.providers.map((p) => ({
|
|
201
|
+
label: labelMap.get(p.provider) ?? p.provider,
|
|
202
|
+
path: p.available ? "available" : null,
|
|
203
|
+
version: p.available ? null : (p.error ?? null),
|
|
204
|
+
source: "daemon",
|
|
205
|
+
}));
|
|
206
|
+
if (!state.running) {
|
|
207
|
+
return {
|
|
208
|
+
connectedDaemon: "reachable",
|
|
209
|
+
daemonVersion: statusPayload.version ?? daemonVersion,
|
|
210
|
+
daemonNodeOverride: statusPayload.nodePath,
|
|
211
|
+
daemonProviders,
|
|
212
|
+
note: state.pidInfo
|
|
213
|
+
? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
|
|
214
|
+
: `Connected daemon is reachable at ${host} but no local daemon PID file was found`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
connectedDaemon: "reachable",
|
|
219
|
+
daemonVersion: statusPayload.version ?? daemonVersion,
|
|
220
|
+
daemonNodeOverride: statusPayload.nodePath,
|
|
221
|
+
daemonProviders,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return {
|
|
226
|
+
connectedDaemon: "reachable",
|
|
227
|
+
daemonVersion,
|
|
228
|
+
note: state.running
|
|
229
|
+
? `Local daemon PID is running but daemon detail request to ${host} failed`
|
|
230
|
+
: `Connected daemon websocket is reachable at ${host} but daemon status request failed`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
await client.close().catch(() => { });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function applyProbeToStatus(input) {
|
|
238
|
+
const { probe } = input;
|
|
239
|
+
return {
|
|
240
|
+
connectedDaemon: probe.connectedDaemon,
|
|
241
|
+
localDaemon: probe.localDaemonOverride ?? input.localDaemon,
|
|
242
|
+
daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
|
|
243
|
+
daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
|
|
244
|
+
daemonProviders: probe.daemonProviders ?? input.daemonProviders,
|
|
245
|
+
note: probe.note ? appendNote(input.note, probe.note) : input.note,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function resolveServerIdSafely(home) {
|
|
249
|
+
try {
|
|
250
|
+
return { serverId: getOrCreateServerId(home), error: null };
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
return {
|
|
254
|
+
serverId: null,
|
|
255
|
+
error: `serverId unavailable: ${shortenMessage(normalizeError(error))}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function resolveDaemonNodeLabel(state) {
|
|
260
|
+
if (!state.running)
|
|
261
|
+
return "-";
|
|
262
|
+
if (!state.pidInfo?.pid)
|
|
263
|
+
return "unknown (no PID available)";
|
|
264
|
+
const fromPid = await resolveNodePathFromPid(state.pidInfo.pid);
|
|
265
|
+
return fromPid.nodePath ?? `unknown (${fromPid.error ?? "could not resolve from PID"})`;
|
|
266
|
+
}
|
|
267
|
+
function formatRelayStatus(state) {
|
|
268
|
+
if (!state.relayEnabled)
|
|
269
|
+
return "disabled";
|
|
270
|
+
const scheme = state.relayPublicUseTls ? "wss" : "ws";
|
|
271
|
+
return `${scheme}://${state.relayEndpoint}`;
|
|
272
|
+
}
|
|
273
|
+
export async function runStatusCommand(options, _command) {
|
|
274
|
+
const home = typeof options.home === "string" ? options.home : undefined;
|
|
275
|
+
const state = resolveLocalDaemonState({ home });
|
|
276
|
+
const host = resolveTcpHostFromListen(state.listen);
|
|
277
|
+
const owner = resolveOwnerLabel(state.pidInfo?.uid, state.pidInfo?.hostname);
|
|
278
|
+
let daemonNode = await resolveDaemonNodeLabel(state);
|
|
279
|
+
const cliNode = process.execPath;
|
|
280
|
+
let localDaemon = state.running ? "running" : "stopped";
|
|
281
|
+
let connectedDaemon = "not_probed";
|
|
282
|
+
let daemonVersion = null;
|
|
283
|
+
let daemonProviders;
|
|
284
|
+
let note;
|
|
285
|
+
if (!state.running && state.stalePidFile && state.pidInfo) {
|
|
286
|
+
localDaemon = "stale_pid";
|
|
287
|
+
note = `Stale PID file found for PID ${state.pidInfo.pid}`;
|
|
288
|
+
}
|
|
289
|
+
if (host) {
|
|
290
|
+
const probe = await probeDaemonOverWebsocket({ host, state });
|
|
291
|
+
({ connectedDaemon, localDaemon, daemonNode, daemonVersion, daemonProviders, note } =
|
|
292
|
+
applyProbeToStatus({
|
|
293
|
+
probe,
|
|
294
|
+
connectedDaemon,
|
|
295
|
+
localDaemon,
|
|
296
|
+
daemonNode,
|
|
297
|
+
daemonVersion,
|
|
298
|
+
daemonProviders,
|
|
299
|
+
note,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
note = appendNote(note, "Daemon is configured for unix socket listen; API probe skipped");
|
|
304
|
+
}
|
|
305
|
+
const cliVersion = resolveCliVersion();
|
|
306
|
+
const serverIdResult = resolveServerIdSafely(state.home);
|
|
307
|
+
const serverId = serverIdResult.serverId;
|
|
308
|
+
if (serverIdResult.error) {
|
|
309
|
+
note = appendNote(note, serverIdResult.error);
|
|
310
|
+
}
|
|
311
|
+
const providers = daemonProviders ?? (await checkProviderBinaries());
|
|
312
|
+
const daemonStatus = {
|
|
313
|
+
serverId,
|
|
314
|
+
localDaemon,
|
|
315
|
+
connectedDaemon,
|
|
316
|
+
home: state.home,
|
|
317
|
+
listen: state.listen,
|
|
318
|
+
relay: formatRelayStatus(state),
|
|
319
|
+
hostname: state.pidInfo?.hostname ?? null,
|
|
320
|
+
pid: state.pidInfo?.pid ?? null,
|
|
321
|
+
startedAt: state.pidInfo?.startedAt ?? null,
|
|
322
|
+
owner,
|
|
323
|
+
logPath: state.logPath,
|
|
324
|
+
daemonNode,
|
|
325
|
+
cliNode,
|
|
326
|
+
cliVersion,
|
|
327
|
+
daemonVersion,
|
|
328
|
+
desktopManaged: state.pidInfo?.desktopManaged === true,
|
|
329
|
+
providers,
|
|
330
|
+
note,
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
type: "list",
|
|
334
|
+
data: toStatusRows(daemonStatus),
|
|
335
|
+
schema: createStatusSchema(daemonStatus),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CommandOptions, SingleResult } from "../../output/index.js";
|
|
3
|
+
interface StopResult {
|
|
4
|
+
action: "stopped" | "not_running";
|
|
5
|
+
home: string;
|
|
6
|
+
pid: string;
|
|
7
|
+
forced: boolean;
|
|
8
|
+
usedLifecycleRpc: boolean;
|
|
9
|
+
reason: "not_running" | "lifecycle_shutdown_rpc" | "owner_pid_signal" | "owner_pid_sigkill";
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export type StopCommandResult = SingleResult<StopResult>;
|
|
13
|
+
export declare function runStopCommand(options: CommandOptions, _command: Command): Promise<StopCommandResult>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=stop.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { stopLocalDaemon, DEFAULT_STOP_TIMEOUT_MS, DEFAULT_KILL_TIMEOUT_MS, } from "./local-daemon.js";
|
|
2
|
+
const stopResultSchema = {
|
|
3
|
+
idField: "action",
|
|
4
|
+
columns: [
|
|
5
|
+
{
|
|
6
|
+
header: "STATUS",
|
|
7
|
+
field: "action",
|
|
8
|
+
color: (value) => (value === "stopped" ? "green" : "yellow"),
|
|
9
|
+
},
|
|
10
|
+
{ header: "HOME", field: "home" },
|
|
11
|
+
{ header: "PID", field: "pid" },
|
|
12
|
+
{ header: "MESSAGE", field: "message" },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
function parseSecondsOption(raw, fallbackMs, label) {
|
|
16
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
17
|
+
return fallbackMs;
|
|
18
|
+
}
|
|
19
|
+
const seconds = Number(raw);
|
|
20
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
21
|
+
const error = {
|
|
22
|
+
code: "INVALID_TIMEOUT",
|
|
23
|
+
message: `Invalid ${label} value: ${raw}`,
|
|
24
|
+
details: `${label} must be a positive number of seconds`,
|
|
25
|
+
};
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
return Math.ceil(seconds * 1000);
|
|
29
|
+
}
|
|
30
|
+
export async function runStopCommand(options, _command) {
|
|
31
|
+
const home = typeof options.home === "string" ? options.home : undefined;
|
|
32
|
+
const force = options.force === true;
|
|
33
|
+
const timeoutMs = parseSecondsOption(options.timeout, DEFAULT_STOP_TIMEOUT_MS, "timeout");
|
|
34
|
+
const killTimeoutMs = parseSecondsOption(options.killTimeout, DEFAULT_KILL_TIMEOUT_MS, "kill-timeout");
|
|
35
|
+
try {
|
|
36
|
+
const result = await stopLocalDaemon({ home, force, timeoutMs, killTimeoutMs });
|
|
37
|
+
return {
|
|
38
|
+
type: "single",
|
|
39
|
+
data: {
|
|
40
|
+
action: result.action,
|
|
41
|
+
home: result.home,
|
|
42
|
+
pid: result.pid === null ? "-" : String(result.pid),
|
|
43
|
+
forced: result.forced,
|
|
44
|
+
usedLifecycleRpc: result.usedLifecycleRpc,
|
|
45
|
+
reason: result.reason,
|
|
46
|
+
message: result.message,
|
|
47
|
+
},
|
|
48
|
+
schema: stopResultSchema,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
const error = {
|
|
54
|
+
code: "STOP_FAILED",
|
|
55
|
+
message: `Failed to stop local daemon: ${message}`,
|
|
56
|
+
};
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=stop.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
interface HookEnvironment {
|
|
3
|
+
OTTO_TERMINAL_ID?: string;
|
|
4
|
+
OTTO_ACTIVITY_TOKEN?: string;
|
|
5
|
+
OTTO_TERMINAL_ACTIVITY_URL?: string;
|
|
6
|
+
}
|
|
7
|
+
interface HookInput {
|
|
8
|
+
[Symbol.asyncIterator](): AsyncIterator<string | Buffer>;
|
|
9
|
+
isTTY?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface HooksRuntime {
|
|
12
|
+
env: HookEnvironment;
|
|
13
|
+
input: HookInput;
|
|
14
|
+
fetch: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
export declare function createHooksCommand(): Command;
|
|
17
|
+
export declare function runHooksCommand(agent: string, event: string, runtime?: HooksRuntime): Promise<void>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolveHookActivity } from "@otto-code/server/agent-hooks";
|
|
3
|
+
export function createHooksCommand() {
|
|
4
|
+
return new Command("hooks")
|
|
5
|
+
.description("Record agent hook activity")
|
|
6
|
+
.argument("<agent>", "Agent hook source")
|
|
7
|
+
.argument("<event>", "Agent hook event")
|
|
8
|
+
.action((agent, event) => runHooksCommand(agent, event));
|
|
9
|
+
}
|
|
10
|
+
export async function runHooksCommand(agent, event, runtime = {
|
|
11
|
+
env: process.env,
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
fetch,
|
|
14
|
+
}) {
|
|
15
|
+
const target = resolveTarget(runtime.env);
|
|
16
|
+
if (!target)
|
|
17
|
+
return;
|
|
18
|
+
const state = await resolveHookActivity({
|
|
19
|
+
provider: agent,
|
|
20
|
+
event,
|
|
21
|
+
input: {
|
|
22
|
+
isTTY: runtime.input.isTTY,
|
|
23
|
+
read: () => readInput(runtime.input),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
if (!state)
|
|
27
|
+
return;
|
|
28
|
+
await postActivity(target, state, runtime.fetch);
|
|
29
|
+
}
|
|
30
|
+
function resolveTarget(env) {
|
|
31
|
+
const terminalId = env.OTTO_TERMINAL_ID;
|
|
32
|
+
const token = env.OTTO_ACTIVITY_TOKEN;
|
|
33
|
+
const url = env.OTTO_TERMINAL_ACTIVITY_URL;
|
|
34
|
+
if (!terminalId || !token || !url)
|
|
35
|
+
return null;
|
|
36
|
+
return { terminalId, token, url };
|
|
37
|
+
}
|
|
38
|
+
async function readInput(input) {
|
|
39
|
+
const iterator = input[Symbol.asyncIterator]();
|
|
40
|
+
const chunks = [];
|
|
41
|
+
while (true) {
|
|
42
|
+
const next = await withTimeout(iterator.next(), 100);
|
|
43
|
+
if (!next) {
|
|
44
|
+
await iterator.return?.();
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (next.done)
|
|
48
|
+
return chunks.join("");
|
|
49
|
+
chunks.push(String(next.value));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function withTimeout(promise, ms) {
|
|
53
|
+
let timeout;
|
|
54
|
+
try {
|
|
55
|
+
return await Promise.race([
|
|
56
|
+
promise,
|
|
57
|
+
new Promise((resolve) => {
|
|
58
|
+
timeout = setTimeout(() => resolve(null), ms);
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (timeout)
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function postActivity(target, state, send) {
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
70
|
+
try {
|
|
71
|
+
await send(target.url, {
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers: { "content-type": "application/json" },
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
terminalId: target.terminalId,
|
|
76
|
+
token: target.token,
|
|
77
|
+
state,
|
|
78
|
+
}),
|
|
79
|
+
signal: controller.signal,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { withOutput } from "../../output/index.js";
|
|
3
|
+
import { addJsonAndDaemonHostOptions, addDaemonHostOption } from "../../utils/command-options.js";
|
|
4
|
+
import { addLoopRunOptions, runLoopRunCommand } from "./run.js";
|
|
5
|
+
import { addLoopLsOptions, runLoopLsCommand } from "./ls.js";
|
|
6
|
+
import { addLoopInspectOptions, runLoopInspectCommand } from "./inspect.js";
|
|
7
|
+
import { addLoopLogsOptions, runLoopLogsCommand } from "./logs.js";
|
|
8
|
+
import { addLoopStopOptions, runLoopStopCommand } from "./stop.js";
|
|
9
|
+
export function createLoopCommand() {
|
|
10
|
+
const loop = new Command("loop").description("Run iterative worker loops");
|
|
11
|
+
addJsonAndDaemonHostOptions(addLoopRunOptions(loop.command("run"))).action(withOutput(runLoopRunCommand));
|
|
12
|
+
addJsonAndDaemonHostOptions(addLoopLsOptions(loop.command("ls"))).action(withOutput(runLoopLsCommand));
|
|
13
|
+
addJsonAndDaemonHostOptions(addLoopInspectOptions(loop.command("inspect"))).action(withOutput(runLoopInspectCommand));
|
|
14
|
+
addDaemonHostOption(addLoopLogsOptions(loop.command("logs"))).action(runLoopLogsCommand);
|
|
15
|
+
addJsonAndDaemonHostOptions(addLoopStopOptions(loop.command("stop"))).action(withOutput(runLoopStopCommand));
|
|
16
|
+
return loop;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CommandOptions, ListResult } from "../../output/index.js";
|
|
3
|
+
interface InspectRow {
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LoopInspectOptions extends CommandOptions {
|
|
8
|
+
}
|
|
9
|
+
export declare function addLoopInspectOptions(command: Command): Command;
|
|
10
|
+
export type LoopInspectResult = ListResult<InspectRow>;
|
|
11
|
+
export declare function runLoopInspectCommand(id: string, options: LoopInspectOptions, _command: Command): Promise<LoopInspectResult>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
2
|
+
export function addLoopInspectOptions(command) {
|
|
3
|
+
return command.description("Show loop details and iteration history").argument("<id>", "Loop ID");
|
|
4
|
+
}
|
|
5
|
+
function createInspectSchema(loop) {
|
|
6
|
+
return {
|
|
7
|
+
idField: "key",
|
|
8
|
+
columns: [
|
|
9
|
+
{ header: "KEY", field: "key", width: 18 },
|
|
10
|
+
{ header: "VALUE", field: "value", width: 80 },
|
|
11
|
+
],
|
|
12
|
+
serialize: () => loop,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function toRows(loop) {
|
|
16
|
+
return [
|
|
17
|
+
{ key: "Id", value: loop.id },
|
|
18
|
+
{ key: "Name", value: loop.name ?? "null" },
|
|
19
|
+
{ key: "Status", value: loop.status },
|
|
20
|
+
{ key: "Cwd", value: loop.cwd },
|
|
21
|
+
{ key: "Provider", value: loop.provider },
|
|
22
|
+
{ key: "Model", value: loop.model ?? "null" },
|
|
23
|
+
{ key: "WorkerProvider", value: loop.workerProvider ?? "null" },
|
|
24
|
+
{ key: "WorkerModel", value: loop.workerModel ?? "null" },
|
|
25
|
+
{ key: "VerifierProvider", value: loop.verifierProvider ?? "null" },
|
|
26
|
+
{ key: "VerifierModel", value: loop.verifierModel ?? "null" },
|
|
27
|
+
{ key: "Prompt", value: loop.prompt },
|
|
28
|
+
{ key: "VerifyPrompt", value: loop.verifyPrompt ?? "null" },
|
|
29
|
+
{
|
|
30
|
+
key: "VerifyChecks",
|
|
31
|
+
value: loop.verifyChecks.length > 0 ? loop.verifyChecks.join(" | ") : "[]",
|
|
32
|
+
},
|
|
33
|
+
{ key: "Archive", value: String(loop.archive) },
|
|
34
|
+
{ key: "SleepMs", value: String(loop.sleepMs) },
|
|
35
|
+
{
|
|
36
|
+
key: "MaxIterations",
|
|
37
|
+
value: loop.maxIterations === null ? "null" : String(loop.maxIterations),
|
|
38
|
+
},
|
|
39
|
+
{ key: "MaxTimeMs", value: loop.maxTimeMs === null ? "null" : String(loop.maxTimeMs) },
|
|
40
|
+
{ key: "CreatedAt", value: loop.createdAt },
|
|
41
|
+
{ key: "UpdatedAt", value: loop.updatedAt },
|
|
42
|
+
{ key: "CompletedAt", value: loop.completedAt ?? "null" },
|
|
43
|
+
{
|
|
44
|
+
key: "Iterations",
|
|
45
|
+
value: loop.iterations.length === 0
|
|
46
|
+
? "[]"
|
|
47
|
+
: loop.iterations
|
|
48
|
+
.map((iteration) => {
|
|
49
|
+
const summary = [
|
|
50
|
+
`#${iteration.index}`,
|
|
51
|
+
iteration.status,
|
|
52
|
+
iteration.workerAgentId ? `worker=${iteration.workerAgentId}` : null,
|
|
53
|
+
iteration.verifierAgentId ? `verifier=${iteration.verifierAgentId}` : null,
|
|
54
|
+
iteration.failureReason ? `reason=${iteration.failureReason}` : null,
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(" ");
|
|
58
|
+
return summary;
|
|
59
|
+
})
|
|
60
|
+
.join(" | "),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
export async function runLoopInspectCommand(id, options, _command) {
|
|
65
|
+
const host = getDaemonHost({ host: options.host });
|
|
66
|
+
let client;
|
|
67
|
+
try {
|
|
68
|
+
client = (await connectToDaemon({
|
|
69
|
+
host: options.host,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
+
throw {
|
|
75
|
+
code: "DAEMON_NOT_RUNNING",
|
|
76
|
+
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
77
|
+
details: "Start the daemon with: otto daemon start",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const payload = await client.loopInspect(id);
|
|
82
|
+
await client.close();
|
|
83
|
+
if (payload.error || !payload.loop) {
|
|
84
|
+
throw new Error(payload.error ?? `Loop not found: ${id}`);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
type: "list",
|
|
88
|
+
data: toRows(payload.loop),
|
|
89
|
+
schema: createInspectSchema(payload.loop),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
await client.close().catch(() => { });
|
|
94
|
+
throw {
|
|
95
|
+
code: "LOOP_INSPECT_FAILED",
|
|
96
|
+
message: error instanceof Error ? error.message : String(error),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CommandOptions } from "../../output/index.js";
|
|
3
|
+
export interface LoopLogsOptions extends CommandOptions {
|
|
4
|
+
pollInterval?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function addLoopLogsOptions(command: Command): Command;
|
|
7
|
+
export declare function runLoopLogsCommand(id: string, options: LoopLogsOptions, _command: Command): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=logs.d.ts.map
|