aicomputer 0.1.14 → 0.1.16
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/README.md +9 -2
- package/dist/chunk-5IEWKH52.js +883 -0
- package/dist/chunk-KXLTHWW3.js +184 -0
- package/dist/chunk-OWK5N76S.js +70 -0
- package/dist/index.js +735 -644
- package/dist/lib/mount-config.d.ts +72 -0
- package/dist/lib/mount-config.js +40 -0
- package/dist/lib/mount-host.d.ts +14 -0
- package/dist/lib/mount-host.js +10 -0
- package/dist/lib/mount-reconcile.d.ts +44 -0
- package/dist/lib/mount-reconcile.js +13 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,400 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatMountHostInstallGuidance,
|
|
4
|
+
getMountHostValidationIssues
|
|
5
|
+
} from "./chunk-OWK5N76S.js";
|
|
6
|
+
import {
|
|
7
|
+
ApiError,
|
|
8
|
+
api,
|
|
9
|
+
apiWithKey,
|
|
10
|
+
clearAPIKey,
|
|
11
|
+
createBrowserAccess,
|
|
12
|
+
createComputer,
|
|
13
|
+
deleteComputer,
|
|
14
|
+
deletePublishedPort,
|
|
15
|
+
formatStatus,
|
|
16
|
+
getAPIKey,
|
|
17
|
+
getBaseURL,
|
|
18
|
+
getComputerByID,
|
|
19
|
+
getConnectionInfo,
|
|
20
|
+
getFilesystemSettings,
|
|
21
|
+
getStoredAPIKey,
|
|
22
|
+
getWebURL,
|
|
23
|
+
hasEnvAPIKey,
|
|
24
|
+
listComputers,
|
|
25
|
+
listPublishedPorts,
|
|
26
|
+
padEnd,
|
|
27
|
+
promptForSSHComputer,
|
|
28
|
+
publishPort,
|
|
29
|
+
reconcileMounts,
|
|
30
|
+
resolveComputer,
|
|
31
|
+
setAPIKey,
|
|
32
|
+
teardownManagedSessions,
|
|
33
|
+
timeAgo,
|
|
34
|
+
vncURL,
|
|
35
|
+
webURL
|
|
36
|
+
} from "./chunk-5IEWKH52.js";
|
|
37
|
+
import {
|
|
38
|
+
defaultMountServiceConfig,
|
|
39
|
+
ensureMountDirectories,
|
|
40
|
+
getMountPaths,
|
|
41
|
+
readMountConfig,
|
|
42
|
+
readMountControllerLock,
|
|
43
|
+
readMountStatusSnapshot,
|
|
44
|
+
removeMountControllerLock,
|
|
45
|
+
writeMountConfig,
|
|
46
|
+
writeMountControllerLock,
|
|
47
|
+
writeMountStatusSnapshot
|
|
48
|
+
} from "./chunk-KXLTHWW3.js";
|
|
2
49
|
|
|
3
50
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import
|
|
6
|
-
import { readFileSync as
|
|
51
|
+
import { Command as Command14 } from "commander";
|
|
52
|
+
import chalk12 from "chalk";
|
|
53
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
7
54
|
import { basename as basename2 } from "path";
|
|
8
55
|
|
|
9
56
|
// src/commands/access.ts
|
|
10
57
|
import { Command } from "commander";
|
|
11
|
-
import chalk3 from "chalk";
|
|
12
|
-
import ora from "ora";
|
|
13
|
-
|
|
14
|
-
// src/lib/config.ts
|
|
15
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
16
|
-
import { homedir } from "os";
|
|
17
|
-
import { join } from "path";
|
|
18
|
-
var CONFIG_DIR = join(homedir(), ".computer");
|
|
19
|
-
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
20
|
-
function ensureConfigDir() {
|
|
21
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
22
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function readConfig() {
|
|
26
|
-
ensureConfigDir();
|
|
27
|
-
if (!existsSync(CONFIG_FILE)) {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
32
|
-
} catch {
|
|
33
|
-
return {};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function writeConfig(config) {
|
|
37
|
-
ensureConfigDir();
|
|
38
|
-
const tempFile = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
39
|
-
writeFileSync(tempFile, JSON.stringify(config, null, 2), { mode: 384 });
|
|
40
|
-
renameSync(tempFile, CONFIG_FILE);
|
|
41
|
-
}
|
|
42
|
-
function getAPIKey() {
|
|
43
|
-
const envValue = process.env.COMPUTER_API_KEY ?? process.env.AGENTCOMPUTER_API_KEY;
|
|
44
|
-
if (envValue) {
|
|
45
|
-
return envValue.trim();
|
|
46
|
-
}
|
|
47
|
-
return getStoredAPIKey();
|
|
48
|
-
}
|
|
49
|
-
function getStoredAPIKey() {
|
|
50
|
-
return readConfig().auth?.apiKey?.trim() || null;
|
|
51
|
-
}
|
|
52
|
-
function hasEnvAPIKey() {
|
|
53
|
-
return Boolean(process.env.COMPUTER_API_KEY ?? process.env.AGENTCOMPUTER_API_KEY);
|
|
54
|
-
}
|
|
55
|
-
function setAPIKey(apiKey) {
|
|
56
|
-
const config = readConfig();
|
|
57
|
-
config.auth = { apiKey: apiKey.trim() };
|
|
58
|
-
writeConfig(config);
|
|
59
|
-
}
|
|
60
|
-
function clearAPIKey() {
|
|
61
|
-
const config = readConfig();
|
|
62
|
-
delete config.auth;
|
|
63
|
-
writeConfig(config);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// src/lib/api.ts
|
|
67
|
-
var BASE_URL = process.env.COMPUTER_API_URL ?? process.env.AGENTCOMPUTER_API_URL ?? "https://api.computer.agentcomputer.ai";
|
|
68
|
-
var WEB_URL = process.env.COMPUTER_WEB_URL ?? process.env.AGENTCOMPUTER_WEB_URL ?? resolveDefaultWebURL(BASE_URL);
|
|
69
|
-
var ApiError = class extends Error {
|
|
70
|
-
constructor(status, message) {
|
|
71
|
-
super(message);
|
|
72
|
-
this.status = status;
|
|
73
|
-
this.name = "ApiError";
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
function getBaseURL() {
|
|
77
|
-
return BASE_URL;
|
|
78
|
-
}
|
|
79
|
-
function getWebURL() {
|
|
80
|
-
return WEB_URL;
|
|
81
|
-
}
|
|
82
|
-
async function api(path, options = {}) {
|
|
83
|
-
const apiKey = getAPIKey();
|
|
84
|
-
if (!apiKey) {
|
|
85
|
-
throw new ApiError(401, "not logged in; run 'computer login' first");
|
|
86
|
-
}
|
|
87
|
-
return requestWithKey(apiKey, path, options);
|
|
88
|
-
}
|
|
89
|
-
function resolveDefaultWebURL(apiURL) {
|
|
90
|
-
try {
|
|
91
|
-
const parsed = new URL(apiURL);
|
|
92
|
-
if (parsed.hostname === "api.computer.agentcomputer.ai") {
|
|
93
|
-
return "https://agentcomputer.ai";
|
|
94
|
-
}
|
|
95
|
-
if (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1") {
|
|
96
|
-
return `${parsed.protocol}//${parsed.hostname}:3000`;
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
return "https://agentcomputer.ai";
|
|
100
|
-
}
|
|
101
|
-
return "https://agentcomputer.ai";
|
|
102
|
-
}
|
|
103
|
-
async function apiWithKey(apiKey, path, options = {}) {
|
|
104
|
-
return requestWithKey(apiKey, path, options);
|
|
105
|
-
}
|
|
106
|
-
async function requestWithKey(apiKey, path, options) {
|
|
107
|
-
const headers = {
|
|
108
|
-
Accept: "application/json",
|
|
109
|
-
...options.headers ?? {}
|
|
110
|
-
};
|
|
111
|
-
if (options.body !== void 0) {
|
|
112
|
-
headers["Content-Type"] = "application/json";
|
|
113
|
-
}
|
|
114
|
-
if (apiKey) {
|
|
115
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
116
|
-
}
|
|
117
|
-
const response = await fetch(`${BASE_URL}${path}`, {
|
|
118
|
-
...options,
|
|
119
|
-
headers
|
|
120
|
-
});
|
|
121
|
-
if (!response.ok) {
|
|
122
|
-
throw new ApiError(response.status, await readErrorMessage(response));
|
|
123
|
-
}
|
|
124
|
-
if (response.status === 204) {
|
|
125
|
-
return void 0;
|
|
126
|
-
}
|
|
127
|
-
return await response.json();
|
|
128
|
-
}
|
|
129
|
-
async function readErrorMessage(response) {
|
|
130
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
131
|
-
if (contentType.includes("application/json")) {
|
|
132
|
-
try {
|
|
133
|
-
const payload = await response.json();
|
|
134
|
-
if (payload.error) {
|
|
135
|
-
return payload.error;
|
|
136
|
-
}
|
|
137
|
-
return JSON.stringify(payload);
|
|
138
|
-
} catch {
|
|
139
|
-
return response.statusText || "request failed";
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const body = await response.text();
|
|
143
|
-
return body || response.statusText || "request failed";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// src/lib/computers.ts
|
|
147
|
-
async function listComputers() {
|
|
148
|
-
const response = await api("/v1/computers");
|
|
149
|
-
return response.computers;
|
|
150
|
-
}
|
|
151
|
-
async function getComputerByID(id) {
|
|
152
|
-
return api(`/v1/computers/${id}`);
|
|
153
|
-
}
|
|
154
|
-
async function createComputer(input) {
|
|
155
|
-
return api("/v1/computers", {
|
|
156
|
-
method: "POST",
|
|
157
|
-
body: JSON.stringify(input)
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
async function deleteComputer(computerID) {
|
|
161
|
-
return api(`/v1/computers/${computerID}`, {
|
|
162
|
-
method: "DELETE"
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
async function getFilesystemSettings() {
|
|
166
|
-
return api("/v1/me/filesystem");
|
|
167
|
-
}
|
|
168
|
-
async function getConnectionInfo(computerID) {
|
|
169
|
-
return api(`/v1/computers/${computerID}/connection`);
|
|
170
|
-
}
|
|
171
|
-
async function createBrowserAccess(computerID) {
|
|
172
|
-
return api(`/v1/computers/${computerID}/access/browser`, {
|
|
173
|
-
method: "POST"
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
async function createTerminalAccess(computerID) {
|
|
177
|
-
return api(`/v1/computers/${computerID}/access/terminal`, {
|
|
178
|
-
method: "POST"
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
async function listPublishedPorts(computerID) {
|
|
182
|
-
const response = await api(`/v1/computers/${computerID}/ports`);
|
|
183
|
-
return response.ports;
|
|
184
|
-
}
|
|
185
|
-
async function publishPort(computerID, input) {
|
|
186
|
-
return api(`/v1/computers/${computerID}/ports`, {
|
|
187
|
-
method: "POST",
|
|
188
|
-
body: JSON.stringify(input)
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
async function deletePublishedPort(computerID, targetPort) {
|
|
192
|
-
return api(`/v1/computers/${computerID}/ports/${targetPort}`, {
|
|
193
|
-
method: "DELETE"
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
async function resolveComputer(identifier) {
|
|
197
|
-
try {
|
|
198
|
-
return await getComputerByID(identifier);
|
|
199
|
-
} catch (error) {
|
|
200
|
-
if (!(error instanceof Error) || !("status" in error)) {
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
203
|
-
const status = Reflect.get(error, "status");
|
|
204
|
-
if (status !== 404) {
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const computers = await listComputers();
|
|
209
|
-
const exact = computers.find(
|
|
210
|
-
(computer) => computer.handle === identifier || computer.id === identifier
|
|
211
|
-
);
|
|
212
|
-
if (exact) {
|
|
213
|
-
return exact;
|
|
214
|
-
}
|
|
215
|
-
throw new Error(`computer '${identifier}' not found`);
|
|
216
|
-
}
|
|
217
|
-
function webURL(computer) {
|
|
218
|
-
return `https://${computer.primary_web_host}${normalizePrimaryPath(computer.primary_path)}`;
|
|
219
|
-
}
|
|
220
|
-
function vncURL(computer) {
|
|
221
|
-
if (!computer.vnc_enabled) {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
|
|
225
|
-
return `https://6080--${computer.handle}.${domain}`;
|
|
226
|
-
}
|
|
227
|
-
function terminalURL(computer) {
|
|
228
|
-
if (computer.runtime_family !== "managed-worker") {
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
|
|
232
|
-
return `https://8788--${computer.handle}.${domain}`;
|
|
233
|
-
}
|
|
234
|
-
function normalizePrimaryPath(primaryPath) {
|
|
235
|
-
const trimmed = primaryPath?.trim();
|
|
236
|
-
if (!trimmed) {
|
|
237
|
-
return "/";
|
|
238
|
-
}
|
|
239
|
-
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// src/lib/computer-picker.ts
|
|
243
|
-
import { select } from "@inquirer/prompts";
|
|
244
|
-
import chalk2 from "chalk";
|
|
245
|
-
|
|
246
|
-
// src/lib/format.ts
|
|
247
58
|
import chalk from "chalk";
|
|
248
|
-
|
|
249
|
-
const visible = str.replace(/\u001b\[[0-9;]*m/g, "");
|
|
250
|
-
return str + " ".repeat(Math.max(0, len - visible.length));
|
|
251
|
-
}
|
|
252
|
-
function timeAgo(dateStr) {
|
|
253
|
-
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1e3);
|
|
254
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
255
|
-
const minutes = Math.floor(seconds / 60);
|
|
256
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
257
|
-
const hours = Math.floor(minutes / 60);
|
|
258
|
-
if (hours < 24) return `${hours}h ago`;
|
|
259
|
-
const days = Math.floor(hours / 24);
|
|
260
|
-
return `${days}d ago`;
|
|
261
|
-
}
|
|
262
|
-
function formatStatus(status) {
|
|
263
|
-
switch (status) {
|
|
264
|
-
case "running":
|
|
265
|
-
return chalk.green(status);
|
|
266
|
-
case "pending":
|
|
267
|
-
case "provisioning":
|
|
268
|
-
case "starting":
|
|
269
|
-
return chalk.yellow(status);
|
|
270
|
-
case "stopping":
|
|
271
|
-
case "stopped":
|
|
272
|
-
case "deleted":
|
|
273
|
-
return chalk.gray(status);
|
|
274
|
-
case "error":
|
|
275
|
-
return chalk.red(status);
|
|
276
|
-
default:
|
|
277
|
-
return status;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/lib/computer-picker.ts
|
|
282
|
-
async function promptForSSHComputer(computers, message) {
|
|
283
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
284
|
-
throw new Error("computer id or handle is required when not running interactively");
|
|
285
|
-
}
|
|
286
|
-
const available = computers.filter(isSSHSelectable);
|
|
287
|
-
if (available.length === 0) {
|
|
288
|
-
if (computers.length === 0) {
|
|
289
|
-
throw new Error("no computers found");
|
|
290
|
-
}
|
|
291
|
-
throw new Error("no running computers with SSH enabled");
|
|
292
|
-
}
|
|
293
|
-
const handleWidth = Math.max(6, ...available.map((computer) => computer.handle.length));
|
|
294
|
-
const selectedID = await select({
|
|
295
|
-
message,
|
|
296
|
-
pageSize: Math.min(available.length, 10),
|
|
297
|
-
choices: available.map((computer) => ({
|
|
298
|
-
name: `${padEnd(chalk2.white(computer.handle), handleWidth + 2)}${padEnd(formatStatus(computer.status), 12)}${chalk2.dim(describeSSHChoice(computer))}`,
|
|
299
|
-
value: computer.id
|
|
300
|
-
}))
|
|
301
|
-
});
|
|
302
|
-
return available.find((computer) => computer.id === selectedID) ?? available[0];
|
|
303
|
-
}
|
|
304
|
-
function isSSHSelectable(computer) {
|
|
305
|
-
return computer.ssh_enabled && computer.status === "running";
|
|
306
|
-
}
|
|
307
|
-
function describeSSHChoice(computer) {
|
|
308
|
-
const displayName = computer.display_name.trim();
|
|
309
|
-
if (displayName && displayName !== computer.handle) {
|
|
310
|
-
return `${displayName} ${timeAgo(computer.updated_at)}`;
|
|
311
|
-
}
|
|
312
|
-
return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
|
|
313
|
-
}
|
|
59
|
+
import ora from "ora";
|
|
314
60
|
|
|
315
|
-
// src/lib/ssh-
|
|
316
|
-
import {
|
|
317
|
-
import { join as join2 } from "path";
|
|
318
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
319
|
-
var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
|
|
320
|
-
var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
|
|
321
|
-
async function ensureSSHAliasConfig(options) {
|
|
322
|
-
const sshDir = join2(homedir2(), ".ssh");
|
|
323
|
-
const configPath = join2(sshDir, "config");
|
|
324
|
-
await mkdir(sshDir, { recursive: true, mode: 448 });
|
|
325
|
-
let existing = "";
|
|
326
|
-
try {
|
|
327
|
-
existing = await readFile(configPath, "utf8");
|
|
328
|
-
} catch (error) {
|
|
329
|
-
if (error.code !== "ENOENT") {
|
|
330
|
-
throw error;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
const nextBlock = renderManagedBlock(options);
|
|
334
|
-
const managedBlockPattern = new RegExp(
|
|
335
|
-
`${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
|
|
336
|
-
"m"
|
|
337
|
-
);
|
|
338
|
-
let nextContents;
|
|
339
|
-
if (managedBlockPattern.test(existing)) {
|
|
340
|
-
nextContents = existing.replace(managedBlockPattern, nextBlock);
|
|
341
|
-
} else {
|
|
342
|
-
const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
|
|
343
|
-
`;
|
|
344
|
-
nextContents = normalized.length === 0 ? nextBlock : `${normalized}
|
|
345
|
-
${nextBlock}`;
|
|
346
|
-
}
|
|
347
|
-
const changed = nextContents !== existing;
|
|
348
|
-
if (changed) {
|
|
349
|
-
await writeFile(configPath, nextContents, { mode: 384 });
|
|
350
|
-
}
|
|
351
|
-
return {
|
|
352
|
-
configPath,
|
|
353
|
-
changed
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
function renderManagedBlock(options) {
|
|
357
|
-
const user = options.user?.trim() || "agentcomputer";
|
|
358
|
-
const identityFile = formatIdentityFilePath(options.identityFilePath);
|
|
359
|
-
return `${MANAGED_BLOCK_START}
|
|
360
|
-
Host ${options.alias}
|
|
361
|
-
HostName ${options.host}
|
|
362
|
-
Port ${options.port}
|
|
363
|
-
User ${user}
|
|
364
|
-
IdentityFile ${identityFile}
|
|
365
|
-
IdentitiesOnly yes
|
|
366
|
-
ServerAliveInterval 30
|
|
367
|
-
ServerAliveCountMax 4
|
|
368
|
-
${MANAGED_BLOCK_END}
|
|
369
|
-
`;
|
|
370
|
-
}
|
|
371
|
-
function formatIdentityFilePath(path) {
|
|
372
|
-
const normalized = path.trim();
|
|
373
|
-
const homePath = `${homedir2()}/`;
|
|
374
|
-
if (normalized.startsWith(homePath)) {
|
|
375
|
-
return `~/${normalized.slice(homePath.length)}`;
|
|
376
|
-
}
|
|
377
|
-
return normalized.replaceAll(" ", "\\ ");
|
|
378
|
-
}
|
|
379
|
-
function escapeRegex(value) {
|
|
380
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
381
|
-
}
|
|
61
|
+
// src/lib/ssh-access.ts
|
|
62
|
+
import { spawn } from "child_process";
|
|
382
63
|
|
|
383
64
|
// src/lib/ssh-keys.ts
|
|
384
65
|
import { basename } from "path";
|
|
385
|
-
import { homedir
|
|
386
|
-
import { readFile
|
|
66
|
+
import { homedir } from "os";
|
|
67
|
+
import { readFile, mkdir } from "fs/promises";
|
|
387
68
|
import { execFileSync } from "child_process";
|
|
388
|
-
import { existsSync
|
|
69
|
+
import { existsSync } from "fs";
|
|
389
70
|
var DEFAULT_PUBLIC_KEY_PATHS = [
|
|
390
|
-
`${
|
|
391
|
-
`${
|
|
392
|
-
`${
|
|
71
|
+
`${homedir()}/.ssh/id_ed25519.pub`,
|
|
72
|
+
`${homedir()}/.ssh/id_ecdsa.pub`,
|
|
73
|
+
`${homedir()}/.ssh/id_rsa.pub`
|
|
393
74
|
];
|
|
394
75
|
async function ensureDefaultSSHKeyRegistered() {
|
|
395
76
|
for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
|
|
396
77
|
try {
|
|
397
|
-
const publicKey2 = (await
|
|
78
|
+
const publicKey2 = (await readFile(path, "utf8")).trim();
|
|
398
79
|
if (!publicKey2) {
|
|
399
80
|
continue;
|
|
400
81
|
}
|
|
@@ -418,7 +99,7 @@ async function ensureDefaultSSHKeyRegistered() {
|
|
|
418
99
|
}
|
|
419
100
|
}
|
|
420
101
|
const generated = await generateSSHKey();
|
|
421
|
-
const publicKey = (await
|
|
102
|
+
const publicKey = (await readFile(generated.publicKeyPath, "utf8")).trim();
|
|
422
103
|
const key = await api("/v1/ssh-keys", {
|
|
423
104
|
method: "POST",
|
|
424
105
|
body: JSON.stringify({
|
|
@@ -433,9 +114,9 @@ async function ensureDefaultSSHKeyRegistered() {
|
|
|
433
114
|
};
|
|
434
115
|
}
|
|
435
116
|
async function generateSSHKey() {
|
|
436
|
-
const sshDir = `${
|
|
437
|
-
if (!
|
|
438
|
-
await
|
|
117
|
+
const sshDir = `${homedir()}/.ssh`;
|
|
118
|
+
if (!existsSync(sshDir)) {
|
|
119
|
+
await mkdir(sshDir, { mode: 448 });
|
|
439
120
|
}
|
|
440
121
|
const privateKeyPath = `${sshDir}/id_ed25519`;
|
|
441
122
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
@@ -447,7 +128,6 @@ async function generateSSHKey() {
|
|
|
447
128
|
}
|
|
448
129
|
|
|
449
130
|
// src/lib/ssh-access.ts
|
|
450
|
-
import { spawn } from "child_process";
|
|
451
131
|
async function prepareSSHConnection(computer) {
|
|
452
132
|
const registered = await ensureDefaultSSHKeyRegistered();
|
|
453
133
|
const info = await getConnectionInfo(computer.id);
|
|
@@ -504,6 +184,11 @@ import { constants } from "fs";
|
|
|
504
184
|
import { access } from "fs/promises";
|
|
505
185
|
import open from "open";
|
|
506
186
|
var IMAGE_BROWSER_LAUNCHER = "/usr/local/bin/browser-launcher";
|
|
187
|
+
var IMAGE_BROWSER_CANDIDATES = [
|
|
188
|
+
"/usr/local/bin/google-chrome",
|
|
189
|
+
"/usr/local/bin/chromium",
|
|
190
|
+
IMAGE_BROWSER_LAUNCHER
|
|
191
|
+
];
|
|
507
192
|
async function isExecutable(path) {
|
|
508
193
|
try {
|
|
509
194
|
await access(path, constants.X_OK);
|
|
@@ -518,6 +203,8 @@ function hasBrokenChromeBrowserEnv() {
|
|
|
518
203
|
case "chrome":
|
|
519
204
|
case "google-chrome":
|
|
520
205
|
case "google chrome":
|
|
206
|
+
case "browser":
|
|
207
|
+
case "browser-launcher":
|
|
521
208
|
return true;
|
|
522
209
|
default:
|
|
523
210
|
return false;
|
|
@@ -531,9 +218,12 @@ async function openBrowserURL(url) {
|
|
|
531
218
|
if (hasBrokenChromeBrowserEnv()) {
|
|
532
219
|
delete process.env.BROWSER;
|
|
533
220
|
}
|
|
534
|
-
|
|
221
|
+
for (const browserPath of IMAGE_BROWSER_CANDIDATES) {
|
|
222
|
+
if (!await isExecutable(browserPath)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
535
225
|
try {
|
|
536
|
-
await open(url, { app: { name:
|
|
226
|
+
await open(url, { app: { name: browserPath } });
|
|
537
227
|
return;
|
|
538
228
|
} catch {
|
|
539
229
|
}
|
|
@@ -546,39 +236,146 @@ async function openBrowserURL(url) {
|
|
|
546
236
|
await open(url);
|
|
547
237
|
}
|
|
548
238
|
|
|
239
|
+
// src/lib/ssh-config.ts
|
|
240
|
+
import { homedir as homedir2 } from "os";
|
|
241
|
+
import { join } from "path";
|
|
242
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
|
|
243
|
+
var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
|
|
244
|
+
var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
|
|
245
|
+
async function ensureSSHAliasConfig(options) {
|
|
246
|
+
const sshDir = join(homedir2(), ".ssh");
|
|
247
|
+
const configPath = join(sshDir, "config");
|
|
248
|
+
await mkdir2(sshDir, { recursive: true, mode: 448 });
|
|
249
|
+
let existing = "";
|
|
250
|
+
try {
|
|
251
|
+
existing = await readFile2(configPath, "utf8");
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error.code !== "ENOENT") {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const nextBlock = renderManagedBlock(options);
|
|
258
|
+
const managedBlockPattern = new RegExp(
|
|
259
|
+
`${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
|
|
260
|
+
"m"
|
|
261
|
+
);
|
|
262
|
+
let nextContents;
|
|
263
|
+
if (managedBlockPattern.test(existing)) {
|
|
264
|
+
nextContents = existing.replace(managedBlockPattern, nextBlock);
|
|
265
|
+
} else {
|
|
266
|
+
const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
|
|
267
|
+
`;
|
|
268
|
+
nextContents = normalized.length === 0 ? nextBlock : `${normalized}
|
|
269
|
+
${nextBlock}`;
|
|
270
|
+
}
|
|
271
|
+
const changed = nextContents !== existing;
|
|
272
|
+
if (changed) {
|
|
273
|
+
await writeFile(configPath, nextContents, { mode: 384 });
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
configPath,
|
|
277
|
+
changed
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function renderManagedBlock(options) {
|
|
281
|
+
const user = options.user?.trim() || "agentcomputer";
|
|
282
|
+
const identityFile = formatIdentityFilePath(options.identityFilePath);
|
|
283
|
+
return `${MANAGED_BLOCK_START}
|
|
284
|
+
Host ${options.alias}
|
|
285
|
+
HostName ${options.host}
|
|
286
|
+
Port ${options.port}
|
|
287
|
+
User ${user}
|
|
288
|
+
IdentityFile ${identityFile}
|
|
289
|
+
IdentitiesOnly yes
|
|
290
|
+
ServerAliveInterval 30
|
|
291
|
+
ServerAliveCountMax 4
|
|
292
|
+
${MANAGED_BLOCK_END}
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
function formatIdentityFilePath(path) {
|
|
296
|
+
const normalized = path.trim();
|
|
297
|
+
const homePath = `${homedir2()}/`;
|
|
298
|
+
if (normalized.startsWith(homePath)) {
|
|
299
|
+
return `~/${normalized.slice(homePath.length)}`;
|
|
300
|
+
}
|
|
301
|
+
return normalized.replaceAll(" ", "\\ ");
|
|
302
|
+
}
|
|
303
|
+
function escapeRegex(value) {
|
|
304
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/lib/ssh-setup.ts
|
|
308
|
+
async function ensureSSHAccessConfigured(options = {}) {
|
|
309
|
+
const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
|
|
310
|
+
const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
|
|
311
|
+
const port = typeof options.port === "number" ? parseSSHPort(String(options.port)) : parseSSHPort(options.port ?? "443");
|
|
312
|
+
const registered = await ensureDefaultSSHKeyRegistered();
|
|
313
|
+
const configResult = await ensureSSHAliasConfig({
|
|
314
|
+
alias,
|
|
315
|
+
host,
|
|
316
|
+
port,
|
|
317
|
+
user: "agentcomputer",
|
|
318
|
+
identityFilePath: registered.privateKeyPath
|
|
319
|
+
});
|
|
320
|
+
return {
|
|
321
|
+
alias,
|
|
322
|
+
host,
|
|
323
|
+
port,
|
|
324
|
+
configPath: configResult.configPath,
|
|
325
|
+
identityFilePath: registered.privateKeyPath,
|
|
326
|
+
changed: configResult.changed
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function normalizeSSHAlias(value) {
|
|
330
|
+
const alias = value.trim();
|
|
331
|
+
if (!alias) {
|
|
332
|
+
throw new Error("ssh alias cannot be empty");
|
|
333
|
+
}
|
|
334
|
+
if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"ssh alias may contain only letters, numbers, dot, dash, or underscore"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return alias;
|
|
340
|
+
}
|
|
341
|
+
function normalizeSSHHost(value) {
|
|
342
|
+
const host = value.trim();
|
|
343
|
+
if (!host) {
|
|
344
|
+
throw new Error("ssh host cannot be empty");
|
|
345
|
+
}
|
|
346
|
+
if (host.includes(" ")) {
|
|
347
|
+
throw new Error("ssh host cannot contain spaces");
|
|
348
|
+
}
|
|
349
|
+
return host;
|
|
350
|
+
}
|
|
351
|
+
function parseSSHPort(value) {
|
|
352
|
+
const parsed = Number.parseInt(value, 10);
|
|
353
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
354
|
+
throw new Error("ssh port must be between 1 and 65535");
|
|
355
|
+
}
|
|
356
|
+
return parsed;
|
|
357
|
+
}
|
|
358
|
+
|
|
549
359
|
// src/commands/access.ts
|
|
550
|
-
var openCommand = new Command("open").description("Open a computer in your browser").argument("<id-or-handle>", "Computer id or handle").option("--vnc", "Open VNC desktop instead of gateway").
|
|
360
|
+
var openCommand = new Command("open").description("Open a computer in your browser").argument("<id-or-handle>", "Computer id or handle").option("--vnc", "Open VNC desktop instead of gateway").action(async (identifier, options) => {
|
|
551
361
|
const spinner = ora("Preparing access...").start();
|
|
552
362
|
try {
|
|
553
363
|
const computer = await resolveComputer(identifier);
|
|
554
364
|
const info = await getConnectionInfo(computer.id);
|
|
555
|
-
if (options.vnc && options.terminal) {
|
|
556
|
-
throw new Error("choose either --vnc or --terminal");
|
|
557
|
-
}
|
|
558
365
|
if (options.vnc) {
|
|
559
366
|
if (!info.connection.vnc_available) {
|
|
560
367
|
throw new Error("VNC is not available for this computer");
|
|
561
368
|
}
|
|
562
369
|
const url = info.connection.vnc_url;
|
|
563
|
-
spinner.succeed(`Opening VNC for ${
|
|
370
|
+
spinner.succeed(`Opening VNC for ${chalk.bold(computer.handle)}`);
|
|
564
371
|
await openBrowserURL(url);
|
|
565
|
-
console.log(
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (options.terminal) {
|
|
569
|
-
if (!info.connection.terminal_available) {
|
|
570
|
-
throw new Error("Terminal access is not available for this computer");
|
|
571
|
-
}
|
|
572
|
-
const access3 = await createTerminalAccess(computer.id);
|
|
573
|
-
spinner.succeed(`Opening terminal for ${chalk3.bold(computer.handle)}`);
|
|
574
|
-
await openBrowserURL(access3.access_url);
|
|
575
|
-
console.log(chalk3.dim(` ${access3.access_url}`));
|
|
372
|
+
console.log(chalk.dim(` ${url}`));
|
|
576
373
|
return;
|
|
577
374
|
}
|
|
578
375
|
const access2 = await createBrowserAccess(computer.id);
|
|
579
|
-
spinner.succeed(`Opening ${
|
|
376
|
+
spinner.succeed(`Opening ${chalk.bold(computer.handle)}`);
|
|
580
377
|
await openBrowserURL(access2.access_url);
|
|
581
|
-
console.log(
|
|
378
|
+
console.log(chalk.dim(` ${access2.access_url}`));
|
|
582
379
|
} catch (error) {
|
|
583
380
|
spinner.fail(
|
|
584
381
|
error instanceof Error ? error.message : "Failed to open computer"
|
|
@@ -598,8 +395,8 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
|
|
|
598
395
|
try {
|
|
599
396
|
const computer = await resolveSSHComputer(identifier, spinner);
|
|
600
397
|
const connection = await prepareSSHConnection(computer);
|
|
601
|
-
spinner.succeed(`Connecting to ${
|
|
602
|
-
console.log(
|
|
398
|
+
spinner.succeed(`Connecting to ${chalk.bold(computer.handle)}`);
|
|
399
|
+
console.log(chalk.dim(` ${connection.command}`));
|
|
603
400
|
console.log();
|
|
604
401
|
await openSSHConnection(connection);
|
|
605
402
|
} catch (error) {
|
|
@@ -621,24 +418,24 @@ portsCommand.command("ls").description("List published ports for a computer").ar
|
|
|
621
418
|
spinner.stop();
|
|
622
419
|
if (ports.length === 0) {
|
|
623
420
|
console.log();
|
|
624
|
-
console.log(
|
|
421
|
+
console.log(chalk.dim(" No published ports."));
|
|
625
422
|
console.log();
|
|
626
423
|
return;
|
|
627
424
|
}
|
|
628
425
|
const subWidth = Math.max(10, ...ports.map((p) => p.subdomain.length));
|
|
629
426
|
console.log();
|
|
630
427
|
console.log(
|
|
631
|
-
` ${
|
|
428
|
+
` ${chalk.dim(padEnd("Subdomain", subWidth + 2))}${chalk.dim(padEnd("Port", 8))}${chalk.dim("Protocol")}`
|
|
632
429
|
);
|
|
633
430
|
console.log(
|
|
634
|
-
` ${
|
|
431
|
+
` ${chalk.dim("-".repeat(subWidth + 2))}${chalk.dim("-".repeat(8))}${chalk.dim("-".repeat(8))}`
|
|
635
432
|
);
|
|
636
433
|
for (const port of ports) {
|
|
637
434
|
const url = `https://${port.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
|
|
638
435
|
console.log(
|
|
639
436
|
` ${padEnd(port.subdomain, subWidth + 2)}${padEnd(String(port.target_port), 8)}${port.protocol}`
|
|
640
437
|
);
|
|
641
|
-
console.log(` ${
|
|
438
|
+
console.log(` ${chalk.dim(url)}`);
|
|
642
439
|
}
|
|
643
440
|
console.log();
|
|
644
441
|
} catch (error) {
|
|
@@ -666,9 +463,9 @@ portsCommand.command("publish").description("Publish an HTTP app port").argument
|
|
|
666
463
|
});
|
|
667
464
|
const url = `https://${published.subdomain}--${computer.handle}.computer.agentcomputer.ai`;
|
|
668
465
|
spinner.succeed(
|
|
669
|
-
`Published port ${
|
|
466
|
+
`Published port ${chalk.bold(String(published.target_port))}`
|
|
670
467
|
);
|
|
671
|
-
console.log(
|
|
468
|
+
console.log(chalk.dim(` ${url}`));
|
|
672
469
|
} catch (error) {
|
|
673
470
|
spinner.fail(
|
|
674
471
|
error instanceof Error ? error.message : "Failed to publish port"
|
|
@@ -685,7 +482,7 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
|
|
|
685
482
|
}
|
|
686
483
|
const computer = await resolveComputer(identifier);
|
|
687
484
|
await deletePublishedPort(computer.id, targetPort);
|
|
688
|
-
spinner.succeed(`Removed port ${
|
|
485
|
+
spinner.succeed(`Removed port ${chalk.bold(String(targetPort))}`);
|
|
689
486
|
} catch (error) {
|
|
690
487
|
spinner.fail(
|
|
691
488
|
error instanceof Error ? error.message : "Failed to remove port"
|
|
@@ -696,24 +493,14 @@ portsCommand.command("rm").description("Unpublish an app port").argument("<id-or
|
|
|
696
493
|
async function setupSSHAlias(options) {
|
|
697
494
|
const spinner = ora("Configuring global SSH access...").start();
|
|
698
495
|
try {
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
const port = parseSSHPort(options.port ?? "443");
|
|
702
|
-
const registered = await ensureDefaultSSHKeyRegistered();
|
|
703
|
-
const configResult = await ensureSSHAliasConfig({
|
|
704
|
-
alias,
|
|
705
|
-
host,
|
|
706
|
-
port,
|
|
707
|
-
user: "agentcomputer",
|
|
708
|
-
identityFilePath: registered.privateKeyPath
|
|
709
|
-
});
|
|
710
|
-
spinner.succeed(`SSH alias '${alias}' is ready`);
|
|
496
|
+
const setup = await ensureSSHAccessConfigured(options);
|
|
497
|
+
spinner.succeed(`SSH alias '${setup.alias}' is ready`);
|
|
711
498
|
console.log();
|
|
712
|
-
console.log(
|
|
713
|
-
console.log(
|
|
499
|
+
console.log(chalk.dim(` SSH config: ${setup.configPath}`));
|
|
500
|
+
console.log(chalk.dim(` Identity: ${setup.identityFilePath}`));
|
|
714
501
|
console.log();
|
|
715
|
-
console.log(` ${
|
|
716
|
-
console.log(` ${
|
|
502
|
+
console.log(` ${chalk.bold("Shell:")} ssh ${setup.alias}`);
|
|
503
|
+
console.log(` ${chalk.bold("Direct:")} ssh <handle>@${setup.alias}`);
|
|
717
504
|
console.log();
|
|
718
505
|
} catch (error) {
|
|
719
506
|
spinner.fail(
|
|
@@ -722,35 +509,6 @@ async function setupSSHAlias(options) {
|
|
|
722
509
|
process.exit(1);
|
|
723
510
|
}
|
|
724
511
|
}
|
|
725
|
-
function normalizeSSHAlias(value) {
|
|
726
|
-
const alias = value.trim();
|
|
727
|
-
if (!alias) {
|
|
728
|
-
throw new Error("ssh alias cannot be empty");
|
|
729
|
-
}
|
|
730
|
-
if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
|
|
731
|
-
throw new Error(
|
|
732
|
-
"ssh alias may contain only letters, numbers, dot, dash, or underscore"
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
return alias;
|
|
736
|
-
}
|
|
737
|
-
function normalizeSSHHost(value) {
|
|
738
|
-
const host = value.trim();
|
|
739
|
-
if (!host) {
|
|
740
|
-
throw new Error("ssh host cannot be empty");
|
|
741
|
-
}
|
|
742
|
-
if (host.includes(" ")) {
|
|
743
|
-
throw new Error("ssh host cannot contain spaces");
|
|
744
|
-
}
|
|
745
|
-
return host;
|
|
746
|
-
}
|
|
747
|
-
function parseSSHPort(value) {
|
|
748
|
-
const parsed = Number.parseInt(value, 10);
|
|
749
|
-
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
750
|
-
throw new Error("ssh port must be between 1 and 65535");
|
|
751
|
-
}
|
|
752
|
-
return parsed;
|
|
753
|
-
}
|
|
754
512
|
async function resolveSSHComputer(identifier, spinner) {
|
|
755
513
|
const trimmed = identifier?.trim();
|
|
756
514
|
if (trimmed) {
|
|
@@ -769,7 +527,7 @@ async function resolveSSHComputer(identifier, spinner) {
|
|
|
769
527
|
}
|
|
770
528
|
|
|
771
529
|
// src/commands/acp.ts
|
|
772
|
-
import { readFileSync
|
|
530
|
+
import { readFileSync } from "fs";
|
|
773
531
|
import readline from "readline";
|
|
774
532
|
import { Command as Command2 } from "commander";
|
|
775
533
|
|
|
@@ -845,7 +603,7 @@ async function openAgentSessionEventsStream(computerID, sessionID, options = {})
|
|
|
845
603
|
signal: options.signal
|
|
846
604
|
});
|
|
847
605
|
if (!response.ok) {
|
|
848
|
-
const message = await
|
|
606
|
+
const message = await readErrorMessage(response);
|
|
849
607
|
throw new ApiError(response.status, message);
|
|
850
608
|
}
|
|
851
609
|
return response;
|
|
@@ -865,7 +623,7 @@ async function waitForSessionToSettle(computerID, sessionID, promptID, options =
|
|
|
865
623
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
866
624
|
}
|
|
867
625
|
}
|
|
868
|
-
async function
|
|
626
|
+
async function readErrorMessage(response) {
|
|
869
627
|
const contentType = response.headers.get("content-type") ?? "";
|
|
870
628
|
if (contentType.includes("application/json")) {
|
|
871
629
|
try {
|
|
@@ -881,7 +639,7 @@ async function readErrorMessage2(response) {
|
|
|
881
639
|
|
|
882
640
|
// src/commands/acp.ts
|
|
883
641
|
var pkg = JSON.parse(
|
|
884
|
-
|
|
642
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
885
643
|
);
|
|
886
644
|
function emit(message) {
|
|
887
645
|
process.stdout.write(`${JSON.stringify(message)}
|
|
@@ -1112,22 +870,22 @@ acpCommand.command("serve").description("Serve a local stdio ACP bridge backed b
|
|
|
1112
870
|
|
|
1113
871
|
// src/commands/agent.ts
|
|
1114
872
|
import { Command as Command3 } from "commander";
|
|
1115
|
-
import
|
|
873
|
+
import chalk2 from "chalk";
|
|
1116
874
|
import ora2 from "ora";
|
|
1117
875
|
function formatAgentSessionStatus(status) {
|
|
1118
876
|
switch (status) {
|
|
1119
877
|
case "idle":
|
|
1120
|
-
return
|
|
878
|
+
return chalk2.green(status);
|
|
1121
879
|
case "running":
|
|
1122
|
-
return
|
|
880
|
+
return chalk2.blue(status);
|
|
1123
881
|
case "cancelling":
|
|
1124
|
-
return
|
|
882
|
+
return chalk2.yellow(status);
|
|
1125
883
|
case "interrupted":
|
|
1126
|
-
return
|
|
884
|
+
return chalk2.yellow(status);
|
|
1127
885
|
case "failed":
|
|
1128
|
-
return
|
|
886
|
+
return chalk2.red(status);
|
|
1129
887
|
case "closed":
|
|
1130
|
-
return
|
|
888
|
+
return chalk2.gray(status);
|
|
1131
889
|
default:
|
|
1132
890
|
return status;
|
|
1133
891
|
}
|
|
@@ -1136,14 +894,14 @@ function printAgents(agents) {
|
|
|
1136
894
|
const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
|
|
1137
895
|
console.log();
|
|
1138
896
|
console.log(
|
|
1139
|
-
` ${
|
|
897
|
+
` ${chalk2.dim(padEnd("Agent", idWidth + 2))}${chalk2.dim(padEnd("Installed", 12))}${chalk2.dim(padEnd("Creds", 8))}${chalk2.dim("Version")}`
|
|
1140
898
|
);
|
|
1141
899
|
console.log(
|
|
1142
|
-
` ${
|
|
900
|
+
` ${chalk2.dim("-".repeat(idWidth + 2))}${chalk2.dim("-".repeat(12))}${chalk2.dim("-".repeat(8))}${chalk2.dim("-".repeat(12))}`
|
|
1143
901
|
);
|
|
1144
902
|
for (const agent of agents) {
|
|
1145
903
|
console.log(
|
|
1146
|
-
` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ?
|
|
904
|
+
` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk2.green("yes") : chalk2.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk2.green("yes") : chalk2.yellow("no"), 8)}${agent.version ?? chalk2.dim("unknown")}`
|
|
1147
905
|
);
|
|
1148
906
|
}
|
|
1149
907
|
console.log();
|
|
@@ -1151,7 +909,7 @@ function printAgents(agents) {
|
|
|
1151
909
|
function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
|
|
1152
910
|
if (sessions.length === 0) {
|
|
1153
911
|
console.log();
|
|
1154
|
-
console.log(
|
|
912
|
+
console.log(chalk2.dim(" No agent sessions found."));
|
|
1155
913
|
console.log();
|
|
1156
914
|
return;
|
|
1157
915
|
}
|
|
@@ -1160,19 +918,19 @@ function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map())
|
|
|
1160
918
|
const statusWidth = 13;
|
|
1161
919
|
console.log();
|
|
1162
920
|
console.log(
|
|
1163
|
-
` ${
|
|
921
|
+
` ${chalk2.dim(padEnd("Session", 14))}${chalk2.dim(padEnd("Name", nameWidth + 2))}${chalk2.dim(padEnd("Agent", agentWidth + 2))}${chalk2.dim(padEnd("Status", statusWidth + 2))}${chalk2.dim("Location")}`
|
|
1164
922
|
);
|
|
1165
923
|
console.log(
|
|
1166
|
-
` ${
|
|
924
|
+
` ${chalk2.dim("-".repeat(14))}${chalk2.dim("-".repeat(nameWidth + 2))}${chalk2.dim("-".repeat(agentWidth + 2))}${chalk2.dim("-".repeat(statusWidth + 2))}${chalk2.dim("-".repeat(20))}`
|
|
1167
925
|
);
|
|
1168
926
|
for (const session of sessions) {
|
|
1169
927
|
const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
|
|
1170
928
|
console.log(
|
|
1171
929
|
` ${padEnd(session.id.slice(0, 12), 14)}${padEnd(session.name || "default", nameWidth + 2)}${padEnd(session.agent, agentWidth + 2)}${padEnd(formatAgentSessionStatus(session.status), statusWidth + 2)}${location}`
|
|
1172
930
|
);
|
|
1173
|
-
console.log(` ${
|
|
931
|
+
console.log(` ${chalk2.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
|
|
1174
932
|
if (session.last_stop_reason || session.last_error) {
|
|
1175
|
-
console.log(` ${
|
|
933
|
+
console.log(` ${chalk2.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
|
|
1176
934
|
}
|
|
1177
935
|
}
|
|
1178
936
|
console.log();
|
|
@@ -1186,7 +944,7 @@ var StreamPrinter = class {
|
|
|
1186
944
|
}
|
|
1187
945
|
writeDim(text) {
|
|
1188
946
|
this.ensureBreak();
|
|
1189
|
-
process.stdout.write(
|
|
947
|
+
process.stdout.write(chalk2.dim(text));
|
|
1190
948
|
this.inlineOpen = !text.endsWith("\n");
|
|
1191
949
|
}
|
|
1192
950
|
writeLine(text) {
|
|
@@ -1254,7 +1012,7 @@ function renderSSEChunk(chunk, printer, asJson) {
|
|
|
1254
1012
|
try {
|
|
1255
1013
|
envelope = JSON.parse(payload);
|
|
1256
1014
|
} catch {
|
|
1257
|
-
printer.writeLine(
|
|
1015
|
+
printer.writeLine(chalk2.dim(payload));
|
|
1258
1016
|
return;
|
|
1259
1017
|
}
|
|
1260
1018
|
const method = typeof envelope.method === "string" ? envelope.method : "";
|
|
@@ -1281,14 +1039,14 @@ function renderSSEChunk(chunk, printer, asJson) {
|
|
|
1281
1039
|
case "tool_call_update": {
|
|
1282
1040
|
const title = typeof update?.title === "string" && update.title ? update.title : "tool";
|
|
1283
1041
|
const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
|
|
1284
|
-
printer.writeLine(
|
|
1042
|
+
printer.writeLine(chalk2.dim(`[tool:${status}] ${title}`));
|
|
1285
1043
|
return;
|
|
1286
1044
|
}
|
|
1287
1045
|
case "plan": {
|
|
1288
1046
|
const entries = Array.isArray(update?.entries) ? update.entries : [];
|
|
1289
1047
|
const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
|
|
1290
1048
|
if (detail) {
|
|
1291
|
-
printer.writeLine(
|
|
1049
|
+
printer.writeLine(chalk2.dim(`Plan
|
|
1292
1050
|
${detail}`));
|
|
1293
1051
|
}
|
|
1294
1052
|
return;
|
|
@@ -1298,7 +1056,7 @@ ${detail}`));
|
|
|
1298
1056
|
}
|
|
1299
1057
|
}
|
|
1300
1058
|
if (method === "session/request_permission") {
|
|
1301
|
-
printer.writeLine(
|
|
1059
|
+
printer.writeLine(chalk2.yellow("Permission requested by remote agent"));
|
|
1302
1060
|
return;
|
|
1303
1061
|
}
|
|
1304
1062
|
}
|
|
@@ -1426,12 +1184,12 @@ agentCommand.command("prompt").description("Send a prompt to a machine agent ses
|
|
|
1426
1184
|
return;
|
|
1427
1185
|
}
|
|
1428
1186
|
console.log();
|
|
1429
|
-
console.log(` ${
|
|
1187
|
+
console.log(` ${chalk2.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
|
|
1430
1188
|
if (settled.last_stop_reason) {
|
|
1431
|
-
console.log(
|
|
1189
|
+
console.log(chalk2.dim(` stop_reason=${settled.last_stop_reason}`));
|
|
1432
1190
|
}
|
|
1433
1191
|
if (settled.last_error) {
|
|
1434
|
-
console.log(
|
|
1192
|
+
console.log(chalk2.red(` error=${settled.last_error}`));
|
|
1435
1193
|
}
|
|
1436
1194
|
console.log();
|
|
1437
1195
|
} catch (error) {
|
|
@@ -1542,7 +1300,7 @@ agentCommand.command("close").description("Close and delete a machine agent sess
|
|
|
1542
1300
|
import { randomBytes as randomBytes2, createHash } from "crypto";
|
|
1543
1301
|
import { input as textInput } from "@inquirer/prompts";
|
|
1544
1302
|
import { Command as Command4 } from "commander";
|
|
1545
|
-
import
|
|
1303
|
+
import chalk3 from "chalk";
|
|
1546
1304
|
import ora4 from "ora";
|
|
1547
1305
|
|
|
1548
1306
|
// src/lib/remote-auth.ts
|
|
@@ -1755,7 +1513,7 @@ var claudeLoginCommand = new Command4("claude-login").alias("claude-auth").descr
|
|
|
1755
1513
|
await runClaudeLogin(options);
|
|
1756
1514
|
} catch (error) {
|
|
1757
1515
|
const message = error instanceof Error ? error.message : "Failed to authenticate Claude";
|
|
1758
|
-
console.error(
|
|
1516
|
+
console.error(chalk3.red(`
|
|
1759
1517
|
${message}`));
|
|
1760
1518
|
process.exit(1);
|
|
1761
1519
|
}
|
|
@@ -1768,7 +1526,7 @@ async function runClaudeLogin(options) {
|
|
|
1768
1526
|
let activeTodoID = "target";
|
|
1769
1527
|
let failureMessage = null;
|
|
1770
1528
|
console.log();
|
|
1771
|
-
console.log(
|
|
1529
|
+
console.log(chalk3.cyan("Authenticating with Claude Code...\n"));
|
|
1772
1530
|
try {
|
|
1773
1531
|
const prepared = await prepareTargetMachine(options);
|
|
1774
1532
|
target = prepared.computer;
|
|
@@ -1853,7 +1611,7 @@ async function runClaudeLogin(options) {
|
|
|
1853
1611
|
}
|
|
1854
1612
|
if (target) {
|
|
1855
1613
|
console.log(
|
|
1856
|
-
|
|
1614
|
+
chalk3.green(`Claude login installed on ${chalk3.bold(target.handle)}.`)
|
|
1857
1615
|
);
|
|
1858
1616
|
console.log();
|
|
1859
1617
|
}
|
|
@@ -1894,11 +1652,11 @@ function markVerificationTodo(items, id, result, successDetail) {
|
|
|
1894
1652
|
}
|
|
1895
1653
|
function printTodoList(items) {
|
|
1896
1654
|
console.log();
|
|
1897
|
-
console.log(
|
|
1655
|
+
console.log(chalk3.dim("TODO"));
|
|
1898
1656
|
console.log();
|
|
1899
1657
|
for (const item of items) {
|
|
1900
|
-
const marker = item.state === "done" ?
|
|
1901
|
-
const detail = item.detail ?
|
|
1658
|
+
const marker = item.state === "done" ? chalk3.green("[x]") : item.state === "skipped" ? chalk3.yellow("[-]") : item.state === "failed" ? chalk3.red("[!]") : chalk3.dim("[ ]");
|
|
1659
|
+
const detail = item.detail ? chalk3.dim(` ${item.detail}`) : "";
|
|
1902
1660
|
console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
|
|
1903
1661
|
}
|
|
1904
1662
|
console.log();
|
|
@@ -1926,7 +1684,7 @@ async function runManualOAuthFlow() {
|
|
|
1926
1684
|
try {
|
|
1927
1685
|
await openBrowserURL(url);
|
|
1928
1686
|
} catch {
|
|
1929
|
-
console.log(
|
|
1687
|
+
console.log(chalk3.yellow("Unable to open the browser automatically."));
|
|
1930
1688
|
}
|
|
1931
1689
|
console.log(
|
|
1932
1690
|
"After completing authentication, copy the code shown on the success page."
|
|
@@ -2017,7 +1775,7 @@ function parseAuthorizationInput(value, expectedState) {
|
|
|
2017
1775
|
}
|
|
2018
1776
|
async function installClaudeAuth(target, oauth) {
|
|
2019
1777
|
const spinner = ora4(
|
|
2020
|
-
`Installing Claude auth on ${
|
|
1778
|
+
`Installing Claude auth on ${chalk3.bold(target.handle)}...`
|
|
2021
1779
|
).start();
|
|
2022
1780
|
try {
|
|
2023
1781
|
const installScript = buildInstallScript(oauth.refreshToken, oauth.scope);
|
|
@@ -2027,10 +1785,10 @@ async function installClaudeAuth(target, oauth) {
|
|
|
2027
1785
|
installScript
|
|
2028
1786
|
);
|
|
2029
1787
|
if (result.stdout.trim()) {
|
|
2030
|
-
spinner.succeed(`Installed Claude auth on ${
|
|
1788
|
+
spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
|
|
2031
1789
|
return;
|
|
2032
1790
|
}
|
|
2033
|
-
spinner.succeed(`Installed Claude auth on ${
|
|
1791
|
+
spinner.succeed(`Installed Claude auth on ${chalk3.bold(target.handle)}`);
|
|
2034
1792
|
} catch (error) {
|
|
2035
1793
|
spinner.fail(
|
|
2036
1794
|
error instanceof Error ? error.message : `Failed to install Claude auth on ${target.handle}`
|
|
@@ -2040,11 +1798,11 @@ async function installClaudeAuth(target, oauth) {
|
|
|
2040
1798
|
}
|
|
2041
1799
|
async function verifyTargetMachine(handle, target) {
|
|
2042
1800
|
const spinner = ora4(
|
|
2043
|
-
`Verifying Claude login on ${
|
|
1801
|
+
`Verifying Claude login on ${chalk3.bold(handle)}...`
|
|
2044
1802
|
).start();
|
|
2045
1803
|
const result = await verifyStoredAuth(target);
|
|
2046
1804
|
if (result.status === "verified") {
|
|
2047
|
-
spinner.succeed(`Verified Claude login on ${
|
|
1805
|
+
spinner.succeed(`Verified Claude login on ${chalk3.bold(handle)}`);
|
|
2048
1806
|
return result;
|
|
2049
1807
|
}
|
|
2050
1808
|
spinner.warn(result.detail);
|
|
@@ -2052,7 +1810,7 @@ async function verifyTargetMachine(handle, target) {
|
|
|
2052
1810
|
}
|
|
2053
1811
|
async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
|
|
2054
1812
|
const spinner = ora4(
|
|
2055
|
-
`Verifying shared-home Claude login from ${
|
|
1813
|
+
`Verifying shared-home Claude login from ${chalk3.bold(primaryHandle)}...`
|
|
2056
1814
|
).start();
|
|
2057
1815
|
const result = await verifySecondaryMachine(
|
|
2058
1816
|
primaryComputerID,
|
|
@@ -2062,7 +1820,7 @@ async function verifySharedInstall(primaryHandle, primaryComputerID, sharedInsta
|
|
|
2062
1820
|
);
|
|
2063
1821
|
if (result.status === "verified") {
|
|
2064
1822
|
spinner.succeed(
|
|
2065
|
-
`Verified shared-home Claude login on ${
|
|
1823
|
+
`Verified shared-home Claude login on ${chalk3.bold(result.handle)}`
|
|
2066
1824
|
);
|
|
2067
1825
|
return result;
|
|
2068
1826
|
}
|
|
@@ -2146,9 +1904,9 @@ function randomSuffix2(length) {
|
|
|
2146
1904
|
|
|
2147
1905
|
// src/commands/computers.ts
|
|
2148
1906
|
import { Command as Command5 } from "commander";
|
|
2149
|
-
import
|
|
1907
|
+
import chalk4 from "chalk";
|
|
2150
1908
|
import ora5 from "ora";
|
|
2151
|
-
import { select
|
|
1909
|
+
import { select, input as textInput2, confirm } from "@inquirer/prompts";
|
|
2152
1910
|
|
|
2153
1911
|
// src/lib/machine-sources.ts
|
|
2154
1912
|
async function getMachineSourceSettings() {
|
|
@@ -2193,48 +1951,90 @@ function summarizeMachineSource(source) {
|
|
|
2193
1951
|
return parts.join(" | ");
|
|
2194
1952
|
}
|
|
2195
1953
|
|
|
1954
|
+
// src/lib/mount-control.ts
|
|
1955
|
+
import { unlinkSync } from "fs";
|
|
1956
|
+
import net from "net";
|
|
1957
|
+
async function notifyMountDaemon(paths) {
|
|
1958
|
+
return new Promise((resolve) => {
|
|
1959
|
+
const socket = net.createConnection(paths.socketPath);
|
|
1960
|
+
socket.on("connect", () => {
|
|
1961
|
+
socket.end("reconcile\n");
|
|
1962
|
+
resolve(true);
|
|
1963
|
+
});
|
|
1964
|
+
socket.on("error", () => {
|
|
1965
|
+
resolve(false);
|
|
1966
|
+
});
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
function createMountControlServer(paths, onReconcile) {
|
|
1970
|
+
try {
|
|
1971
|
+
unlinkSync(paths.socketPath);
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
const server = net.createServer((socket) => {
|
|
1975
|
+
socket.setEncoding("utf8");
|
|
1976
|
+
let buffer = "";
|
|
1977
|
+
socket.on("data", (chunk) => {
|
|
1978
|
+
buffer += chunk;
|
|
1979
|
+
if (buffer.includes("\n")) {
|
|
1980
|
+
void Promise.resolve(onReconcile()).finally(() => {
|
|
1981
|
+
socket.end("ok\n");
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
socket.on("error", () => {
|
|
1986
|
+
socket.destroy();
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1989
|
+
server.on("close", () => {
|
|
1990
|
+
try {
|
|
1991
|
+
unlinkSync(paths.socketPath);
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
return server;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
2196
1998
|
// src/commands/computers.ts
|
|
2197
1999
|
function isInternalCondition(value) {
|
|
2198
2000
|
return /^[A-Z][a-zA-Z]+$/.test(value);
|
|
2199
2001
|
}
|
|
2200
2002
|
function printComputer(computer) {
|
|
2201
2003
|
const vnc = vncURL(computer);
|
|
2202
|
-
const terminal = terminalURL(computer);
|
|
2203
2004
|
const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
|
|
2204
2005
|
const isCustom = computer.runtime_family === "custom-machine";
|
|
2205
2006
|
console.log();
|
|
2206
|
-
console.log(` ${
|
|
2007
|
+
console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
|
|
2207
2008
|
console.log();
|
|
2208
|
-
console.log(` ${
|
|
2209
|
-
console.log(` ${
|
|
2009
|
+
console.log(` ${chalk4.dim("ID")} ${computer.id}`);
|
|
2010
|
+
console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
|
|
2210
2011
|
if (isCustom) {
|
|
2211
|
-
console.log(` ${
|
|
2212
|
-
console.log(` ${
|
|
2213
|
-
console.log(` ${
|
|
2012
|
+
console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
|
|
2013
|
+
console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
|
|
2014
|
+
console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
|
|
2214
2015
|
} else {
|
|
2215
|
-
console.log(` ${
|
|
2216
|
-
console.log(` ${
|
|
2016
|
+
console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
|
|
2017
|
+
console.log(` ${chalk4.dim("Launch")} ${formatManagedWorkerLaunchSource(computer)}`);
|
|
2217
2018
|
if (computer.user_source_id && computer.resolved_image_ref) {
|
|
2218
|
-
console.log(` ${
|
|
2019
|
+
console.log(` ${chalk4.dim("Image")} ${computer.resolved_image_ref}`);
|
|
2219
2020
|
}
|
|
2220
2021
|
}
|
|
2221
|
-
console.log(` ${
|
|
2222
|
-
console.log(` ${
|
|
2022
|
+
console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
|
|
2023
|
+
console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
|
|
2223
2024
|
console.log();
|
|
2224
|
-
console.log(` ${
|
|
2225
|
-
console.log(` ${
|
|
2226
|
-
console.log(` ${
|
|
2227
|
-
console.log(` ${chalk6.dim("SSH")} ${computer.ssh_enabled ? chalk6.white(ssh) : chalk6.dim(ssh)}`);
|
|
2025
|
+
console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
|
|
2026
|
+
console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
|
|
2027
|
+
console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
|
|
2228
2028
|
if (computer.last_error) {
|
|
2229
2029
|
console.log();
|
|
2230
2030
|
if (isInternalCondition(computer.last_error)) {
|
|
2231
|
-
console.log(` ${
|
|
2031
|
+
console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
|
|
2232
2032
|
} else {
|
|
2233
|
-
console.log(` ${
|
|
2033
|
+
console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
|
|
2234
2034
|
}
|
|
2235
2035
|
}
|
|
2236
2036
|
console.log();
|
|
2237
|
-
console.log(` ${
|
|
2037
|
+
console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
|
|
2238
2038
|
console.log();
|
|
2239
2039
|
}
|
|
2240
2040
|
function formatSSHCommand2(user, host, port) {
|
|
@@ -2271,16 +2071,16 @@ function printComputerTable(computers) {
|
|
|
2271
2071
|
const createdWidth = 10;
|
|
2272
2072
|
console.log();
|
|
2273
2073
|
console.log(
|
|
2274
|
-
` ${
|
|
2074
|
+
` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
|
|
2275
2075
|
);
|
|
2276
2076
|
console.log(
|
|
2277
|
-
` ${
|
|
2077
|
+
` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
|
|
2278
2078
|
);
|
|
2279
2079
|
for (const computer of computers) {
|
|
2280
2080
|
const status = formatStatus(computer.status);
|
|
2281
|
-
const created =
|
|
2081
|
+
const created = chalk4.dim(timeAgo(computer.created_at));
|
|
2282
2082
|
console.log(
|
|
2283
|
-
` ${
|
|
2083
|
+
` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
|
|
2284
2084
|
);
|
|
2285
2085
|
}
|
|
2286
2086
|
console.log();
|
|
@@ -2290,23 +2090,19 @@ function printComputerTableVerbose(computers) {
|
|
|
2290
2090
|
const statusWidth = 10;
|
|
2291
2091
|
console.log();
|
|
2292
2092
|
console.log(
|
|
2293
|
-
` ${
|
|
2093
|
+
` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
|
|
2294
2094
|
);
|
|
2295
2095
|
console.log(
|
|
2296
|
-
` ${
|
|
2096
|
+
` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
|
|
2297
2097
|
);
|
|
2298
2098
|
for (const computer of computers) {
|
|
2299
2099
|
const status = formatStatus(computer.status);
|
|
2300
2100
|
const vnc = vncURL(computer);
|
|
2301
|
-
const terminal = terminalURL(computer);
|
|
2302
|
-
console.log(
|
|
2303
|
-
` ${chalk6.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk6.cyan(webURL(computer))}`
|
|
2304
|
-
);
|
|
2305
2101
|
console.log(
|
|
2306
|
-
` ${padEnd(
|
|
2102
|
+
` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
|
|
2307
2103
|
);
|
|
2308
2104
|
console.log(
|
|
2309
|
-
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${
|
|
2105
|
+
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
|
|
2310
2106
|
);
|
|
2311
2107
|
}
|
|
2312
2108
|
console.log();
|
|
@@ -2322,7 +2118,7 @@ var lsCommand = new Command5("ls").description("List computers").option("--json"
|
|
|
2322
2118
|
}
|
|
2323
2119
|
if (computers.length === 0) {
|
|
2324
2120
|
console.log();
|
|
2325
|
-
console.log(
|
|
2121
|
+
console.log(chalk4.dim(" No computers found."));
|
|
2326
2122
|
console.log();
|
|
2327
2123
|
return;
|
|
2328
2124
|
}
|
|
@@ -2378,11 +2174,11 @@ var createCommand = new Command5("create").description("Create a computer").argu
|
|
|
2378
2174
|
Boolean(selectedOptions.usePlatformDefault)
|
|
2379
2175
|
);
|
|
2380
2176
|
if (machineSourceNote) {
|
|
2381
|
-
console.log(
|
|
2177
|
+
console.log(chalk4.dim(machineSourceNote));
|
|
2382
2178
|
}
|
|
2383
2179
|
const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
|
|
2384
2180
|
if (provisioningNote) {
|
|
2385
|
-
console.log(
|
|
2181
|
+
console.log(chalk4.dim(provisioningNote));
|
|
2386
2182
|
}
|
|
2387
2183
|
spinner = ora5(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
|
|
2388
2184
|
startTime = Date.now();
|
|
@@ -2420,7 +2216,12 @@ var createCommand = new Command5("create").description("Create a computer").argu
|
|
|
2420
2216
|
}
|
|
2421
2217
|
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2422
2218
|
spinner.succeed(
|
|
2423
|
-
|
|
2219
|
+
chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
|
|
2220
|
+
);
|
|
2221
|
+
await notifyMountDaemon(
|
|
2222
|
+
getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
|
|
2223
|
+
).catch(
|
|
2224
|
+
() => false
|
|
2424
2225
|
);
|
|
2425
2226
|
printComputer(computer);
|
|
2426
2227
|
} catch (error) {
|
|
@@ -2429,10 +2230,10 @@ var createCommand = new Command5("create").description("Create a computer").argu
|
|
|
2429
2230
|
}
|
|
2430
2231
|
const message = error instanceof Error ? error.message : "Failed to create computer";
|
|
2431
2232
|
if (spinner) {
|
|
2432
|
-
const suffix = startTime ? ` ${
|
|
2233
|
+
const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
|
|
2433
2234
|
spinner.fail(`${message}${suffix}`);
|
|
2434
2235
|
} else {
|
|
2435
|
-
console.error(
|
|
2236
|
+
console.error(chalk4.red(message));
|
|
2436
2237
|
}
|
|
2437
2238
|
process.exit(1);
|
|
2438
2239
|
}
|
|
@@ -2444,11 +2245,11 @@ async function resolveCreateOptions(options) {
|
|
|
2444
2245
|
validateCreateOptions(selectedOptions);
|
|
2445
2246
|
return selectedOptions;
|
|
2446
2247
|
}
|
|
2447
|
-
const runtimeChoice = await
|
|
2248
|
+
const runtimeChoice = await select({
|
|
2448
2249
|
message: "Select runtime",
|
|
2449
2250
|
choices: [
|
|
2450
2251
|
{
|
|
2451
|
-
name: "managed-worker - default Ubuntu desktop, SSH, VNC
|
|
2252
|
+
name: "managed-worker - default Ubuntu desktop, SSH, and VNC",
|
|
2452
2253
|
value: "managed-worker"
|
|
2453
2254
|
},
|
|
2454
2255
|
{
|
|
@@ -2464,7 +2265,7 @@ async function resolveCreateOptions(options) {
|
|
|
2464
2265
|
selectedOptions.imageRef = (await textInput2({ message: "OCI image ref (required):" })).trim();
|
|
2465
2266
|
selectedOptions.primaryPort = (await textInput2({ message: "Primary port:", default: "3000" })).trim();
|
|
2466
2267
|
selectedOptions.primaryPath = (await textInput2({ message: "Primary path:", default: "/" })).trim();
|
|
2467
|
-
selectedOptions.healthcheckType = await
|
|
2268
|
+
selectedOptions.healthcheckType = await select({
|
|
2468
2269
|
message: "Healthcheck type",
|
|
2469
2270
|
choices: [
|
|
2470
2271
|
{ name: "tcp", value: "tcp" },
|
|
@@ -2495,11 +2296,11 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
|
|
|
2495
2296
|
spinner.stop();
|
|
2496
2297
|
if (!skipConfirm && process.stdin.isTTY) {
|
|
2497
2298
|
const confirmed = await confirm({
|
|
2498
|
-
message: `Delete computer ${
|
|
2299
|
+
message: `Delete computer ${chalk4.bold(computer.handle)}?`,
|
|
2499
2300
|
default: false
|
|
2500
2301
|
});
|
|
2501
2302
|
if (!confirmed) {
|
|
2502
|
-
console.log(
|
|
2303
|
+
console.log(chalk4.dim(" Cancelled."));
|
|
2503
2304
|
return;
|
|
2504
2305
|
}
|
|
2505
2306
|
}
|
|
@@ -2507,7 +2308,12 @@ var removeCommand = new Command5("rm").description("Delete a computer").argument
|
|
|
2507
2308
|
await api(`/v1/computers/${computer.id}`, {
|
|
2508
2309
|
method: "DELETE"
|
|
2509
2310
|
});
|
|
2510
|
-
deleteSpinner.succeed(
|
|
2311
|
+
deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
|
|
2312
|
+
await notifyMountDaemon(
|
|
2313
|
+
getMountPaths((readMountConfig() ?? defaultMountServiceConfig()).rootPath)
|
|
2314
|
+
).catch(
|
|
2315
|
+
() => false
|
|
2316
|
+
);
|
|
2511
2317
|
} catch (error) {
|
|
2512
2318
|
spinner.fail(
|
|
2513
2319
|
error instanceof Error ? error.message : "Failed to delete computer"
|
|
@@ -2563,9 +2369,9 @@ function createMachineSourceNote(runtimeFamily, machineSourceSettings, usePlatfo
|
|
|
2563
2369
|
return `Using managed-worker image source: ${summarizeMachineSourceSelection(machineSourceSettings)}.`;
|
|
2564
2370
|
}
|
|
2565
2371
|
function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
|
|
2566
|
-
const elapsedLabel =
|
|
2372
|
+
const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
|
|
2567
2373
|
if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
|
|
2568
|
-
return `Creating computer... ${elapsedLabel} ${
|
|
2374
|
+
return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
|
|
2569
2375
|
}
|
|
2570
2376
|
return `Creating computer... ${elapsedLabel}`;
|
|
2571
2377
|
}
|
|
@@ -2654,6 +2460,7 @@ _computer() {
|
|
|
2654
2460
|
'open:Open in browser'
|
|
2655
2461
|
'ssh:SSH into a computer'
|
|
2656
2462
|
'ports:Manage published ports'
|
|
2463
|
+
'mount:Mirror SSH-ready machine homes into ~/agentcomputer while running'
|
|
2657
2464
|
'agent:Manage cloud agent sessions'
|
|
2658
2465
|
'acp:Run a local ACP bridge for remote agent sessions'
|
|
2659
2466
|
'rm:Delete a computer'
|
|
@@ -2677,6 +2484,11 @@ _computer() {
|
|
|
2677
2484
|
'rm:Delete a machine image source'
|
|
2678
2485
|
)
|
|
2679
2486
|
|
|
2487
|
+
local -a mount_commands
|
|
2488
|
+
mount_commands=(
|
|
2489
|
+
'status:Show machine mount controller status'
|
|
2490
|
+
)
|
|
2491
|
+
|
|
2680
2492
|
_arguments -C \\
|
|
2681
2493
|
'(-h --help)'{-h,--help}'[Display help]' \\
|
|
2682
2494
|
'(-V --version)'{-V,--version}'[Show version]' \\
|
|
@@ -2781,12 +2593,33 @@ _computer() {
|
|
|
2781
2593
|
open)
|
|
2782
2594
|
_arguments \\
|
|
2783
2595
|
'--vnc[Open VNC desktop]' \\
|
|
2784
|
-
'--terminal[Open terminal]' \\
|
|
2785
2596
|
'1:computer:_computer_handles'
|
|
2786
2597
|
;;
|
|
2787
2598
|
ssh)
|
|
2788
2599
|
_arguments '1:computer:_computer_handles'
|
|
2789
2600
|
;;
|
|
2601
|
+
mount)
|
|
2602
|
+
_arguments -C \\
|
|
2603
|
+
'--alias[SSH host alias]:alias:' \\
|
|
2604
|
+
'--host[SSH gateway host]:host:' \\
|
|
2605
|
+
'--port[SSH gateway port]:port:' \\
|
|
2606
|
+
'--poll-interval[Reconcile interval in milliseconds]:ms:' \\
|
|
2607
|
+
'--connect-timeout[SSH connect timeout for Mutagen]:seconds:' \\
|
|
2608
|
+
'1:command:->mount_command' \\
|
|
2609
|
+
'*::arg:->mount_args'
|
|
2610
|
+
case "$state" in
|
|
2611
|
+
mount_command)
|
|
2612
|
+
_describe -t commands 'mount command' mount_commands
|
|
2613
|
+
;;
|
|
2614
|
+
mount_args)
|
|
2615
|
+
case "$words[2]" in
|
|
2616
|
+
status)
|
|
2617
|
+
_arguments
|
|
2618
|
+
;;
|
|
2619
|
+
esac
|
|
2620
|
+
;;
|
|
2621
|
+
esac
|
|
2622
|
+
;;
|
|
2790
2623
|
rm)
|
|
2791
2624
|
_arguments \\
|
|
2792
2625
|
'(-y --yes)'{-y,--yes}'[Skip confirmation]' \\
|
|
@@ -2850,9 +2683,10 @@ var BASH_SCRIPT = `_computer() {
|
|
|
2850
2683
|
local cur prev words cword
|
|
2851
2684
|
_init_completion || return
|
|
2852
2685
|
|
|
2853
|
-
local commands="login upgrade logout whoami claude-login claude-auth codex-login codex-auth create ls get image open ssh ports agent acp rm completion help"
|
|
2686
|
+
local commands="login upgrade logout whoami claude-login claude-auth codex-login codex-auth create ls get image open ssh ports mount agent acp rm completion help"
|
|
2854
2687
|
local ports_commands="ls publish rm"
|
|
2855
2688
|
local image_commands="ls save default rebuild rm"
|
|
2689
|
+
local mount_commands="status"
|
|
2856
2690
|
|
|
2857
2691
|
if [[ $cword -eq 1 ]]; then
|
|
2858
2692
|
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
@@ -2930,11 +2764,18 @@ var BASH_SCRIPT = `_computer() {
|
|
|
2930
2764
|
else
|
|
2931
2765
|
case "$cmd" in
|
|
2932
2766
|
get) COMPREPLY=($(compgen -W "--json" -- "$cur")) ;;
|
|
2933
|
-
open) COMPREPLY=($(compgen -W "--vnc
|
|
2767
|
+
open) COMPREPLY=($(compgen -W "--vnc" -- "$cur")) ;;
|
|
2934
2768
|
rm) COMPREPLY=($(compgen -W "--yes -y" -- "$cur")) ;;
|
|
2935
2769
|
esac
|
|
2936
2770
|
fi
|
|
2937
2771
|
;;
|
|
2772
|
+
mount)
|
|
2773
|
+
if [[ $cword -eq 2 ]]; then
|
|
2774
|
+
COMPREPLY=($(compgen -W "$mount_commands" -- "$cur"))
|
|
2775
|
+
elif [[ $cword -ge 3 ]]; then
|
|
2776
|
+
COMPREPLY=($(compgen -W "--alias --host --port --poll-interval --connect-timeout" -- "$cur"))
|
|
2777
|
+
fi
|
|
2778
|
+
;;
|
|
2938
2779
|
ports)
|
|
2939
2780
|
if [[ $cword -eq 2 ]]; then
|
|
2940
2781
|
COMPREPLY=($(compgen -W "$ports_commands" -- "$cur"))
|
|
@@ -2970,17 +2811,17 @@ var completionCommand = new Command6("completion").description("Generate shell c
|
|
|
2970
2811
|
// src/commands/codex-login.ts
|
|
2971
2812
|
import { spawn as spawn3 } from "child_process";
|
|
2972
2813
|
import { readFile as readFile3 } from "fs/promises";
|
|
2973
|
-
import { homedir as
|
|
2974
|
-
import { join as
|
|
2814
|
+
import { homedir as homedir3 } from "os";
|
|
2815
|
+
import { join as join2 } from "path";
|
|
2975
2816
|
import { Command as Command7 } from "commander";
|
|
2976
|
-
import
|
|
2817
|
+
import chalk5 from "chalk";
|
|
2977
2818
|
import ora6 from "ora";
|
|
2978
2819
|
var codexLoginCommand = new Command7("codex-login").alias("codex-auth").description("Authenticate Codex on a computer").option("--machine <id-or-handle>", "Use a specific computer").option("--keep-helper", "Keep a temporary helper machine if one is created").option("--skip-cross-check", "Skip verification on a second shared machine").option("--verbose", "Show step-by-step auth diagnostics").action(async (options) => {
|
|
2979
2820
|
try {
|
|
2980
2821
|
await runCodexLogin(options);
|
|
2981
2822
|
} catch (error) {
|
|
2982
2823
|
const message = error instanceof Error ? error.message : "Failed to authenticate Codex";
|
|
2983
|
-
console.error(
|
|
2824
|
+
console.error(chalk5.red(`
|
|
2984
2825
|
${message}`));
|
|
2985
2826
|
process.exit(1);
|
|
2986
2827
|
}
|
|
@@ -2993,7 +2834,7 @@ async function runCodexLogin(options) {
|
|
|
2993
2834
|
let activeTodoID = "target";
|
|
2994
2835
|
let failureMessage = null;
|
|
2995
2836
|
console.log();
|
|
2996
|
-
console.log(
|
|
2837
|
+
console.log(chalk5.cyan("Authenticating with Codex...\n"));
|
|
2997
2838
|
try {
|
|
2998
2839
|
const prepared = await prepareTargetMachine2(options);
|
|
2999
2840
|
target = prepared.computer;
|
|
@@ -3078,7 +2919,7 @@ async function runCodexLogin(options) {
|
|
|
3078
2919
|
}
|
|
3079
2920
|
if (target) {
|
|
3080
2921
|
console.log(
|
|
3081
|
-
|
|
2922
|
+
chalk5.green(`Codex login installed on ${chalk5.bold(target.handle)}.`)
|
|
3082
2923
|
);
|
|
3083
2924
|
console.log();
|
|
3084
2925
|
}
|
|
@@ -3119,11 +2960,11 @@ function markVerificationTodo2(items, id, result, successDetail) {
|
|
|
3119
2960
|
}
|
|
3120
2961
|
function printTodoList2(items) {
|
|
3121
2962
|
console.log();
|
|
3122
|
-
console.log(
|
|
2963
|
+
console.log(chalk5.dim("TODO"));
|
|
3123
2964
|
console.log();
|
|
3124
2965
|
for (const item of items) {
|
|
3125
|
-
const marker = item.state === "done" ?
|
|
3126
|
-
const detail = item.detail ?
|
|
2966
|
+
const marker = item.state === "done" ? chalk5.green("[x]") : item.state === "skipped" ? chalk5.yellow("[-]") : item.state === "failed" ? chalk5.red("[!]") : chalk5.dim("[ ]");
|
|
2967
|
+
const detail = item.detail ? chalk5.dim(` ${item.detail}`) : "";
|
|
3127
2968
|
console.log(` ${marker} ${item.label}${detail ? ` ${detail}` : ""}`);
|
|
3128
2969
|
}
|
|
3129
2970
|
console.log();
|
|
@@ -3171,7 +3012,7 @@ async function getLocalCodexStatus() {
|
|
|
3171
3012
|
return parseCodexStatusOutput(result.stdout, result.stderr);
|
|
3172
3013
|
}
|
|
3173
3014
|
async function readLocalCodexAuthFile() {
|
|
3174
|
-
const authPath =
|
|
3015
|
+
const authPath = join2(homedir3(), ".codex", "auth.json");
|
|
3175
3016
|
let raw;
|
|
3176
3017
|
try {
|
|
3177
3018
|
raw = await readFile3(authPath, "utf8");
|
|
@@ -3234,12 +3075,12 @@ async function captureLocalCommand(command, args) {
|
|
|
3234
3075
|
}
|
|
3235
3076
|
async function installCodexAuth(target, authJSON) {
|
|
3236
3077
|
const spinner = ora6(
|
|
3237
|
-
`Installing Codex login on ${
|
|
3078
|
+
`Installing Codex login on ${chalk5.bold(target.handle)}...`
|
|
3238
3079
|
).start();
|
|
3239
3080
|
try {
|
|
3240
3081
|
const installScript = buildInstallScript2(authJSON);
|
|
3241
3082
|
await runRemoteCommand(target, ["bash", "-s"], installScript);
|
|
3242
|
-
spinner.succeed(`Installed Codex login on ${
|
|
3083
|
+
spinner.succeed(`Installed Codex login on ${chalk5.bold(target.handle)}`);
|
|
3243
3084
|
} catch (error) {
|
|
3244
3085
|
spinner.fail(
|
|
3245
3086
|
error instanceof Error ? error.message : `Failed to install Codex login on ${target.handle}`
|
|
@@ -3249,11 +3090,11 @@ async function installCodexAuth(target, authJSON) {
|
|
|
3249
3090
|
}
|
|
3250
3091
|
async function verifyTargetMachine2(handle, target) {
|
|
3251
3092
|
const spinner = ora6(
|
|
3252
|
-
`Verifying Codex login on ${
|
|
3093
|
+
`Verifying Codex login on ${chalk5.bold(handle)}...`
|
|
3253
3094
|
).start();
|
|
3254
3095
|
const result = await verifyStoredCodexAuth(target);
|
|
3255
3096
|
if (result.status === "verified") {
|
|
3256
|
-
spinner.succeed(`Verified Codex login on ${
|
|
3097
|
+
spinner.succeed(`Verified Codex login on ${chalk5.bold(handle)}`);
|
|
3257
3098
|
return result;
|
|
3258
3099
|
}
|
|
3259
3100
|
spinner.warn(result.detail);
|
|
@@ -3261,7 +3102,7 @@ async function verifyTargetMachine2(handle, target) {
|
|
|
3261
3102
|
}
|
|
3262
3103
|
async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInstall, skip, verify) {
|
|
3263
3104
|
const spinner = ora6(
|
|
3264
|
-
`Verifying shared-home Codex login from ${
|
|
3105
|
+
`Verifying shared-home Codex login from ${chalk5.bold(primaryHandle)}...`
|
|
3265
3106
|
).start();
|
|
3266
3107
|
const result = await verifySecondaryMachine(
|
|
3267
3108
|
primaryComputerID,
|
|
@@ -3271,7 +3112,7 @@ async function verifySharedInstall2(primaryHandle, primaryComputerID, sharedInst
|
|
|
3271
3112
|
);
|
|
3272
3113
|
if (result.status === "verified") {
|
|
3273
3114
|
spinner.succeed(
|
|
3274
|
-
`Verified shared-home Codex login on ${
|
|
3115
|
+
`Verified shared-home Codex login on ${chalk5.bold(result.handle)}`
|
|
3275
3116
|
);
|
|
3276
3117
|
return result;
|
|
3277
3118
|
}
|
|
@@ -3329,9 +3170,9 @@ function firstStatusLine2(value) {
|
|
|
3329
3170
|
}
|
|
3330
3171
|
|
|
3331
3172
|
// src/commands/images.ts
|
|
3332
|
-
import { confirm as confirm2, input as textInput3, select as
|
|
3173
|
+
import { confirm as confirm2, input as textInput3, select as select2 } from "@inquirer/prompts";
|
|
3333
3174
|
import { Command as Command8 } from "commander";
|
|
3334
|
-
import
|
|
3175
|
+
import chalk6 from "chalk";
|
|
3335
3176
|
import ora7 from "ora";
|
|
3336
3177
|
var imageCommand = new Command8("image").description("Manage machine image sources");
|
|
3337
3178
|
imageCommand.command("ls").description("List machine image sources").option("--json", "Print raw JSON").action(async (options) => {
|
|
@@ -3364,7 +3205,7 @@ imageCommand.command("save").description("Create or update a machine image sourc
|
|
|
3364
3205
|
return;
|
|
3365
3206
|
}
|
|
3366
3207
|
console.log();
|
|
3367
|
-
console.log(
|
|
3208
|
+
console.log(chalk6.green("Saved machine image source."));
|
|
3368
3209
|
printMachineSourceSettings(settings);
|
|
3369
3210
|
} catch (error) {
|
|
3370
3211
|
if (spinner) {
|
|
@@ -3403,9 +3244,9 @@ imageCommand.command("default").description("Set the default machine image sourc
|
|
|
3403
3244
|
if (!usePlatformDefault) {
|
|
3404
3245
|
const selected = settings.default_machine_source ?? void 0;
|
|
3405
3246
|
const label = selected ? summarizeMachineSource(selected) : sourceID;
|
|
3406
|
-
console.log(
|
|
3247
|
+
console.log(chalk6.green(`Selected ${chalk6.bold(label)} as the default machine image.`));
|
|
3407
3248
|
} else {
|
|
3408
|
-
console.log(
|
|
3249
|
+
console.log(chalk6.green("Using the AgentComputer platform default image."));
|
|
3409
3250
|
}
|
|
3410
3251
|
printMachineSourceSettings(settings);
|
|
3411
3252
|
} catch (error) {
|
|
@@ -3427,7 +3268,7 @@ imageCommand.command("rebuild").description("Rebuild a machine image source").ar
|
|
|
3427
3268
|
return;
|
|
3428
3269
|
}
|
|
3429
3270
|
console.log();
|
|
3430
|
-
console.log(
|
|
3271
|
+
console.log(chalk6.green(`Queued rebuild for ${chalk6.bold(sourceID)}.`));
|
|
3431
3272
|
printMachineSourceSettings(settings);
|
|
3432
3273
|
} catch (error) {
|
|
3433
3274
|
if (spinner) {
|
|
@@ -3446,7 +3287,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
|
|
|
3446
3287
|
if (!skipConfirm && process.stdin.isTTY) {
|
|
3447
3288
|
const confirmed = await confirmDeletion(sourceID);
|
|
3448
3289
|
if (!confirmed) {
|
|
3449
|
-
console.log(
|
|
3290
|
+
console.log(chalk6.dim(" Cancelled."));
|
|
3450
3291
|
return;
|
|
3451
3292
|
}
|
|
3452
3293
|
}
|
|
@@ -3458,7 +3299,7 @@ imageCommand.command("rm").description("Delete a machine image source").argument
|
|
|
3458
3299
|
return;
|
|
3459
3300
|
}
|
|
3460
3301
|
console.log();
|
|
3461
|
-
console.log(
|
|
3302
|
+
console.log(chalk6.green(`Deleted machine image source ${chalk6.bold(sourceID)}.`));
|
|
3462
3303
|
printMachineSourceSettings(settings);
|
|
3463
3304
|
} catch (error) {
|
|
3464
3305
|
if (spinner) {
|
|
@@ -3470,14 +3311,14 @@ imageCommand.command("rm").description("Delete a machine image source").argument
|
|
|
3470
3311
|
}
|
|
3471
3312
|
});
|
|
3472
3313
|
function printMachineSourceSettings(settings) {
|
|
3473
|
-
console.log(` ${
|
|
3314
|
+
console.log(` ${chalk6.dim("Default")} ${chalk6.white(summarizeDefaultMachineSource(settings))}`);
|
|
3474
3315
|
console.log();
|
|
3475
3316
|
if (settings.sources.length === 0) {
|
|
3476
|
-
console.log(
|
|
3317
|
+
console.log(chalk6.dim(" No custom machine images configured yet."));
|
|
3477
3318
|
console.log();
|
|
3478
3319
|
return;
|
|
3479
3320
|
}
|
|
3480
|
-
console.log(` ${
|
|
3321
|
+
console.log(` ${chalk6.dim("Custom")} ${chalk6.white(formatMachineSourceCounts(settings.sources))}`);
|
|
3481
3322
|
console.log();
|
|
3482
3323
|
for (const source of sortMachineSources(settings.sources, settings.default_machine_source_id)) {
|
|
3483
3324
|
printMachineSourceCard(source, settings.default_machine_source_id === source.id);
|
|
@@ -3492,17 +3333,17 @@ function printMachineSourceCard(source, isDefault) {
|
|
|
3492
3333
|
];
|
|
3493
3334
|
const extra = machineSourceExtraParts(source);
|
|
3494
3335
|
console.log(
|
|
3495
|
-
` ${statusLabel}${
|
|
3336
|
+
` ${statusLabel}${chalk6.bold(machineSourceTitle(source))}${isDefault ? chalk6.green(" default") : ""}`
|
|
3496
3337
|
);
|
|
3497
|
-
console.log(` ${
|
|
3498
|
-
console.log(` ${
|
|
3338
|
+
console.log(` ${chalk6.dim(meta.concat(extra).join(" | "))}`);
|
|
3339
|
+
console.log(` ${chalk6.dim(machineSourceStatusSummary(source))}`);
|
|
3499
3340
|
if (source.resolved_image_ref) {
|
|
3500
|
-
console.log(` ${
|
|
3341
|
+
console.log(` ${chalk6.dim("resolved")} ${source.resolved_image_ref}`);
|
|
3501
3342
|
} else if (source.last_good_resolved_image_ref) {
|
|
3502
|
-
console.log(` ${
|
|
3343
|
+
console.log(` ${chalk6.dim("last good")} ${source.last_good_resolved_image_ref}`);
|
|
3503
3344
|
}
|
|
3504
3345
|
if (source.error) {
|
|
3505
|
-
console.log(` ${
|
|
3346
|
+
console.log(` ${chalk6.red(source.error)}`);
|
|
3506
3347
|
}
|
|
3507
3348
|
console.log();
|
|
3508
3349
|
}
|
|
@@ -3572,13 +3413,13 @@ function formatMachineSourceStatus(status) {
|
|
|
3572
3413
|
const text = status.toUpperCase();
|
|
3573
3414
|
switch (status) {
|
|
3574
3415
|
case "ready":
|
|
3575
|
-
return
|
|
3416
|
+
return chalk6.green(text);
|
|
3576
3417
|
case "failed":
|
|
3577
|
-
return
|
|
3418
|
+
return chalk6.red(text);
|
|
3578
3419
|
case "pending":
|
|
3579
3420
|
case "resolving":
|
|
3580
3421
|
case "building":
|
|
3581
|
-
return
|
|
3422
|
+
return chalk6.yellow(text);
|
|
3582
3423
|
default:
|
|
3583
3424
|
return text;
|
|
3584
3425
|
}
|
|
@@ -3687,7 +3528,7 @@ async function resolveSaveInput(options) {
|
|
|
3687
3528
|
return input;
|
|
3688
3529
|
}
|
|
3689
3530
|
async function selectMachineSourceKind() {
|
|
3690
|
-
const kind = await
|
|
3531
|
+
const kind = await select2({
|
|
3691
3532
|
message: "Select machine image kind",
|
|
3692
3533
|
choices: [
|
|
3693
3534
|
{ name: "oci-image - resolve an OCI image digest", value: "oci-image" },
|
|
@@ -3721,7 +3562,7 @@ async function confirmDeletion(sourceID) {
|
|
|
3721
3562
|
|
|
3722
3563
|
// src/commands/login.ts
|
|
3723
3564
|
import { Command as Command9 } from "commander";
|
|
3724
|
-
import
|
|
3565
|
+
import chalk7 from "chalk";
|
|
3725
3566
|
import ora8 from "ora";
|
|
3726
3567
|
|
|
3727
3568
|
// src/lib/browser-login.ts
|
|
@@ -3994,7 +3835,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
|
|
|
3994
3835
|
if (existingKey && !options.force) {
|
|
3995
3836
|
console.log();
|
|
3996
3837
|
console.log(
|
|
3997
|
-
|
|
3838
|
+
chalk7.yellow(" Already logged in. Use --force to overwrite.")
|
|
3998
3839
|
);
|
|
3999
3840
|
console.log();
|
|
4000
3841
|
return;
|
|
@@ -4003,8 +3844,8 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
|
|
|
4003
3844
|
const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
|
|
4004
3845
|
if (!apiKey && wantsManualLogin) {
|
|
4005
3846
|
console.log();
|
|
4006
|
-
console.log(
|
|
4007
|
-
console.log(
|
|
3847
|
+
console.log(chalk7.dim(" Usage: computer login --api-key <ac_live_...>"));
|
|
3848
|
+
console.log(chalk7.dim(` API: ${getBaseURL()}`));
|
|
4008
3849
|
console.log();
|
|
4009
3850
|
process.exit(1);
|
|
4010
3851
|
}
|
|
@@ -4014,7 +3855,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
|
|
|
4014
3855
|
}
|
|
4015
3856
|
if (!apiKey.startsWith("ac_live_")) {
|
|
4016
3857
|
console.log();
|
|
4017
|
-
console.log(
|
|
3858
|
+
console.log(chalk7.red(" API key must start with ac_live_"));
|
|
4018
3859
|
console.log();
|
|
4019
3860
|
process.exit(1);
|
|
4020
3861
|
}
|
|
@@ -4022,7 +3863,7 @@ var loginCommand = new Command9("login").description("Authenticate the CLI").opt
|
|
|
4022
3863
|
try {
|
|
4023
3864
|
const me = await apiWithKey(apiKey, "/v1/me");
|
|
4024
3865
|
setAPIKey(apiKey);
|
|
4025
|
-
spinner.succeed(`Logged in as ${
|
|
3866
|
+
spinner.succeed(`Logged in as ${chalk7.bold(me.user.email)}`);
|
|
4026
3867
|
} catch (error) {
|
|
4027
3868
|
spinner.fail(
|
|
4028
3869
|
error instanceof Error ? error.message : "Failed to validate API key"
|
|
@@ -4042,15 +3883,15 @@ async function runBrowserLogin() {
|
|
|
4042
3883
|
spinner.stop();
|
|
4043
3884
|
console.log();
|
|
4044
3885
|
console.log(
|
|
4045
|
-
|
|
3886
|
+
chalk7.yellow(" Browser auto-open failed. Open this URL to continue:")
|
|
4046
3887
|
);
|
|
4047
|
-
console.log(
|
|
3888
|
+
console.log(chalk7.dim(` ${attempt.loginURL}`));
|
|
4048
3889
|
console.log();
|
|
4049
3890
|
spinner.start("Waiting for browser login...");
|
|
4050
3891
|
}
|
|
4051
3892
|
spinner.text = "Waiting for browser login...";
|
|
4052
3893
|
const result = await attempt.waitForResult();
|
|
4053
|
-
spinner.succeed(`Logged in as ${
|
|
3894
|
+
spinner.succeed(`Logged in as ${chalk7.bold(result.me.user.email)}`);
|
|
4054
3895
|
await continueFirstLoginFlow(result);
|
|
4055
3896
|
} catch (error) {
|
|
4056
3897
|
spinner.fail(
|
|
@@ -4084,8 +3925,8 @@ async function continueFirstLoginFlow(result) {
|
|
|
4084
3925
|
}
|
|
4085
3926
|
console.log();
|
|
4086
3927
|
console.log(
|
|
4087
|
-
|
|
4088
|
-
`Continuing first-time setup for ${
|
|
3928
|
+
chalk7.cyan(
|
|
3929
|
+
`Continuing first-time setup for ${chalk7.bold(machineHandle)}...
|
|
4089
3930
|
`
|
|
4090
3931
|
)
|
|
4091
3932
|
);
|
|
@@ -4098,8 +3939,8 @@ async function continueFirstLoginFlow(result) {
|
|
|
4098
3939
|
const spinner = ora8(`Preparing SSH access for ${machineHandle}...`).start();
|
|
4099
3940
|
try {
|
|
4100
3941
|
const connection = await prepareSSHConnectionByIdentifier(machineHandle);
|
|
4101
|
-
spinner.succeed(`Connecting to ${
|
|
4102
|
-
console.log(
|
|
3942
|
+
spinner.succeed(`Connecting to ${chalk7.bold(machineHandle)}`);
|
|
3943
|
+
console.log(chalk7.dim(` ${connection.command}`));
|
|
4103
3944
|
console.log();
|
|
4104
3945
|
await openSSHConnection(connection);
|
|
4105
3946
|
} catch (error) {
|
|
@@ -4110,19 +3951,19 @@ async function continueFirstLoginFlow(result) {
|
|
|
4110
3951
|
}
|
|
4111
3952
|
} catch (error) {
|
|
4112
3953
|
const message = error instanceof Error ? error.message : "Failed to finish first-time setup";
|
|
4113
|
-
console.error(
|
|
3954
|
+
console.error(chalk7.red(`
|
|
4114
3955
|
${message}`));
|
|
4115
3956
|
console.log();
|
|
4116
3957
|
if (result.provider === "claude") {
|
|
4117
3958
|
console.log(
|
|
4118
|
-
|
|
3959
|
+
chalk7.dim(` computer claude-login --machine ${machineHandle}`)
|
|
4119
3960
|
);
|
|
4120
3961
|
} else if (result.provider === "codex") {
|
|
4121
3962
|
console.log(
|
|
4122
|
-
|
|
3963
|
+
chalk7.dim(` computer codex-login --machine ${machineHandle}`)
|
|
4123
3964
|
);
|
|
4124
3965
|
}
|
|
4125
|
-
console.log(
|
|
3966
|
+
console.log(chalk7.dim(` computer ssh ${machineHandle}`));
|
|
4126
3967
|
console.log();
|
|
4127
3968
|
process.exit(1);
|
|
4128
3969
|
}
|
|
@@ -4136,45 +3977,291 @@ async function runSelectedProvider(provider, machineHandle) {
|
|
|
4136
3977
|
await runCodexLogin({ machine: machineHandle });
|
|
4137
3978
|
return;
|
|
4138
3979
|
}
|
|
4139
|
-
console.log(
|
|
3980
|
+
console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
|
|
4140
3981
|
console.log();
|
|
4141
3982
|
}
|
|
4142
3983
|
function printNextStep(machineHandle) {
|
|
4143
|
-
console.log(
|
|
4144
|
-
console.log(
|
|
3984
|
+
console.log(chalk7.green(`Sandbox ${chalk7.bold(machineHandle)} is ready.`));
|
|
3985
|
+
console.log(chalk7.dim(` computer ssh ${machineHandle}`));
|
|
4145
3986
|
console.log();
|
|
4146
3987
|
}
|
|
4147
3988
|
|
|
4148
|
-
// src/commands/
|
|
3989
|
+
// src/commands/mount.ts
|
|
4149
3990
|
import { Command as Command10 } from "commander";
|
|
4150
|
-
import
|
|
4151
|
-
|
|
3991
|
+
import chalk8 from "chalk";
|
|
3992
|
+
import ora9 from "ora";
|
|
3993
|
+
|
|
3994
|
+
// src/lib/mount-daemon.ts
|
|
3995
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
3996
|
+
function getMountControllerState(rootPath = defaultMountServiceConfig().rootPath) {
|
|
3997
|
+
const lock = readMountControllerLock(rootPath);
|
|
3998
|
+
if (!lock) {
|
|
3999
|
+
return { running: false };
|
|
4000
|
+
}
|
|
4001
|
+
if (!processExists(lock.pid)) {
|
|
4002
|
+
removeMountControllerLock(rootPath);
|
|
4003
|
+
return { running: false };
|
|
4004
|
+
}
|
|
4005
|
+
return { running: true, pid: lock.pid };
|
|
4006
|
+
}
|
|
4007
|
+
async function runMountDaemon(config) {
|
|
4008
|
+
const paths = getMountPaths(config.rootPath);
|
|
4009
|
+
ensureMountDirectories(paths);
|
|
4010
|
+
await mkdir3(paths.rootPath, { recursive: true });
|
|
4011
|
+
await acquireControllerLock(config.rootPath);
|
|
4012
|
+
writeMountStatusSnapshot(
|
|
4013
|
+
{
|
|
4014
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4015
|
+
controllerPid: process.pid,
|
|
4016
|
+
running: true,
|
|
4017
|
+
mounts: []
|
|
4018
|
+
},
|
|
4019
|
+
config.rootPath
|
|
4020
|
+
);
|
|
4021
|
+
await teardownManagedSessions(config, paths);
|
|
4022
|
+
let running = false;
|
|
4023
|
+
let queued = false;
|
|
4024
|
+
let shuttingDown = false;
|
|
4025
|
+
let activeRun = null;
|
|
4026
|
+
const runOnce = async () => {
|
|
4027
|
+
if (shuttingDown) {
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
if (running) {
|
|
4031
|
+
queued = true;
|
|
4032
|
+
return activeRun ?? void 0;
|
|
4033
|
+
}
|
|
4034
|
+
activeRun = (async () => {
|
|
4035
|
+
running = true;
|
|
4036
|
+
try {
|
|
4037
|
+
await reconcileMounts(config, paths, process.pid);
|
|
4038
|
+
} catch (error) {
|
|
4039
|
+
if (shuttingDown) {
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
const previous = readMountStatusSnapshot(config.rootPath);
|
|
4043
|
+
writeMountStatusSnapshot(
|
|
4044
|
+
{
|
|
4045
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4046
|
+
controllerPid: process.pid,
|
|
4047
|
+
running: true,
|
|
4048
|
+
lastHealthySyncAt: previous?.lastHealthySyncAt,
|
|
4049
|
+
lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
|
|
4050
|
+
lastIssueAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4051
|
+
lastIssue: error instanceof Error ? error.message : "mount reconcile failed",
|
|
4052
|
+
lastError: error instanceof Error ? error.message : "mount reconcile failed",
|
|
4053
|
+
mounts: previous?.mounts ?? []
|
|
4054
|
+
},
|
|
4055
|
+
config.rootPath
|
|
4056
|
+
);
|
|
4057
|
+
console.error(
|
|
4058
|
+
error instanceof Error ? error.message : "mount reconcile failed"
|
|
4059
|
+
);
|
|
4060
|
+
} finally {
|
|
4061
|
+
running = false;
|
|
4062
|
+
activeRun = null;
|
|
4063
|
+
if (queued && !shuttingDown) {
|
|
4064
|
+
queued = false;
|
|
4065
|
+
void runOnce();
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
})();
|
|
4069
|
+
return activeRun;
|
|
4070
|
+
};
|
|
4071
|
+
const server = createMountControlServer(paths, async () => {
|
|
4072
|
+
await runOnce();
|
|
4073
|
+
});
|
|
4074
|
+
await new Promise((resolve, reject) => {
|
|
4075
|
+
server.once("error", reject);
|
|
4076
|
+
server.listen(paths.socketPath, () => resolve());
|
|
4077
|
+
});
|
|
4078
|
+
const interval = setInterval(() => {
|
|
4079
|
+
void runOnce();
|
|
4080
|
+
}, config.pollIntervalMs);
|
|
4081
|
+
const shutdown = async () => {
|
|
4082
|
+
if (shuttingDown) {
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
shuttingDown = true;
|
|
4086
|
+
clearInterval(interval);
|
|
4087
|
+
server.close();
|
|
4088
|
+
if (activeRun) {
|
|
4089
|
+
await activeRun.catch(() => {
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
await teardownManagedSessions(config, paths);
|
|
4093
|
+
const previous = readMountStatusSnapshot(config.rootPath);
|
|
4094
|
+
writeMountStatusSnapshot(
|
|
4095
|
+
{
|
|
4096
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4097
|
+
controllerPid: previous?.controllerPid,
|
|
4098
|
+
running: false,
|
|
4099
|
+
lastHealthySyncAt: previous?.lastHealthySyncAt,
|
|
4100
|
+
lastSuccessfulSyncAt: previous?.lastSuccessfulSyncAt,
|
|
4101
|
+
lastIssueAt: previous?.lastIssueAt,
|
|
4102
|
+
lastIssue: void 0,
|
|
4103
|
+
lastError: void 0,
|
|
4104
|
+
mounts: []
|
|
4105
|
+
},
|
|
4106
|
+
config.rootPath
|
|
4107
|
+
);
|
|
4108
|
+
removeMountControllerLock(config.rootPath);
|
|
4109
|
+
process.exit(0);
|
|
4110
|
+
};
|
|
4111
|
+
process.on("SIGINT", () => {
|
|
4112
|
+
void shutdown();
|
|
4113
|
+
});
|
|
4114
|
+
process.on("SIGTERM", () => {
|
|
4115
|
+
void shutdown();
|
|
4116
|
+
});
|
|
4117
|
+
await runOnce();
|
|
4118
|
+
await new Promise(() => {
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
async function acquireControllerLock(rootPath) {
|
|
4122
|
+
const existing = readMountControllerLock(rootPath);
|
|
4123
|
+
if (existing && processExists(existing.pid)) {
|
|
4124
|
+
throw new Error(`computer mount is already running (pid ${existing.pid})`);
|
|
4125
|
+
}
|
|
4126
|
+
removeMountControllerLock(rootPath);
|
|
4127
|
+
writeMountControllerLock(
|
|
4128
|
+
{
|
|
4129
|
+
pid: process.pid,
|
|
4130
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4131
|
+
},
|
|
4132
|
+
rootPath
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
function processExists(pid) {
|
|
4136
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
4137
|
+
return false;
|
|
4138
|
+
}
|
|
4139
|
+
try {
|
|
4140
|
+
process.kill(pid, 0);
|
|
4141
|
+
return true;
|
|
4142
|
+
} catch {
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
// src/commands/mount.ts
|
|
4148
|
+
var mountCommand = new Command10("mount").description("Mirror SSH-ready machines under ~/agentcomputer while this command is running").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "443").option("--poll-interval <ms>", "Reconcile interval in milliseconds", "5000").option("--connect-timeout <seconds>", "SSH connect timeout for Mutagen", "5").action(async (options) => {
|
|
4149
|
+
const spinner = ora9("Starting machine mount controller...").start();
|
|
4150
|
+
try {
|
|
4151
|
+
const issues = getMountHostValidationIssues();
|
|
4152
|
+
if (issues.length > 0) {
|
|
4153
|
+
throw new Error(
|
|
4154
|
+
[
|
|
4155
|
+
...issues.map((issue) => issue.message),
|
|
4156
|
+
...formatMountHostInstallGuidance(issues)
|
|
4157
|
+
].join("\n")
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
const sshSetup = await ensureSSHAccessConfigured(options);
|
|
4161
|
+
const config = {
|
|
4162
|
+
...defaultMountServiceConfig(),
|
|
4163
|
+
alias: sshSetup.alias,
|
|
4164
|
+
host: sshSetup.host,
|
|
4165
|
+
port: sshSetup.port,
|
|
4166
|
+
pollIntervalMs: parsePositiveInt(options.pollInterval, "poll interval"),
|
|
4167
|
+
connectTimeoutSeconds: parsePositiveInt(
|
|
4168
|
+
options.connectTimeout,
|
|
4169
|
+
"connect timeout"
|
|
4170
|
+
)
|
|
4171
|
+
};
|
|
4172
|
+
writeMountConfig(config);
|
|
4173
|
+
spinner.succeed("Machine mount controller running");
|
|
4174
|
+
console.log();
|
|
4175
|
+
console.log(chalk8.dim(` Root: ${config.rootPath}`));
|
|
4176
|
+
console.log(chalk8.dim(` SSH alias: ${config.alias}`));
|
|
4177
|
+
console.log(chalk8.dim(` Poll: ${config.pollIntervalMs}ms`));
|
|
4178
|
+
console.log();
|
|
4179
|
+
console.log(chalk8.dim(" Press Ctrl-C to stop syncing."));
|
|
4180
|
+
console.log();
|
|
4181
|
+
await runMountDaemon(config);
|
|
4182
|
+
} catch (error) {
|
|
4183
|
+
spinner.fail(
|
|
4184
|
+
error instanceof Error ? error.message : "Failed to start machine mount controller"
|
|
4185
|
+
);
|
|
4186
|
+
process.exit(1);
|
|
4187
|
+
}
|
|
4188
|
+
}).addCommand(
|
|
4189
|
+
new Command10("status").description("Show machine mount controller status").action(() => {
|
|
4190
|
+
const config = readMountConfig() ?? defaultMountServiceConfig();
|
|
4191
|
+
const controller = getMountControllerState(config.rootPath);
|
|
4192
|
+
const snapshot = readMountStatusSnapshot(config.rootPath);
|
|
4193
|
+
console.log();
|
|
4194
|
+
console.log(` ${chalk8.bold("Machine Mounts")}`);
|
|
4195
|
+
console.log();
|
|
4196
|
+
console.log(
|
|
4197
|
+
` ${chalk8.dim("Running")} ${controller.running ? chalk8.green("yes") : chalk8.dim("no")}`
|
|
4198
|
+
);
|
|
4199
|
+
if (controller.pid) {
|
|
4200
|
+
console.log(` ${chalk8.dim("PID")} ${controller.pid}`);
|
|
4201
|
+
}
|
|
4202
|
+
console.log(` ${chalk8.dim("Root")} ${config.rootPath}`);
|
|
4203
|
+
console.log(` ${chalk8.dim("Alias")} ${config.alias}`);
|
|
4204
|
+
console.log(
|
|
4205
|
+
` ${chalk8.dim("Updated")} ${snapshot?.updatedAt ? timeAgo(snapshot.updatedAt) : chalk8.dim("never")}`
|
|
4206
|
+
);
|
|
4207
|
+
console.log(
|
|
4208
|
+
` ${chalk8.dim("Healthy")} ${snapshot?.lastHealthySyncAt ? timeAgo(snapshot.lastHealthySyncAt) : chalk8.dim("never")}`
|
|
4209
|
+
);
|
|
4210
|
+
if (controller.running && snapshot?.mounts.length) {
|
|
4211
|
+
console.log();
|
|
4212
|
+
for (const mount of snapshot.mounts) {
|
|
4213
|
+
const state = mount.state === "mounted" ? chalk8.green(mount.state) : mount.state === "reconnecting" ? chalk8.cyan(mount.state) : mount.state === "degraded" ? chalk8.yellow(mount.state) : mount.state === "pending" ? chalk8.yellow(mount.state) : chalk8.red(mount.state);
|
|
4214
|
+
console.log(` ${chalk8.white(mount.handle)} ${state} ${chalk8.dim(mount.mountPath)}`);
|
|
4215
|
+
if (mount.message) {
|
|
4216
|
+
console.log(` ${chalk8.dim(mount.message)}`);
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
if (snapshot?.lastIssue) {
|
|
4221
|
+
console.log();
|
|
4222
|
+
console.log(` ${chalk8.dim("Last issue")} ${chalk8.yellow(snapshot.lastIssue)}`);
|
|
4223
|
+
}
|
|
4224
|
+
console.log();
|
|
4225
|
+
})
|
|
4226
|
+
);
|
|
4227
|
+
function parsePositiveInt(raw, label) {
|
|
4228
|
+
const value = Number(raw);
|
|
4229
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
4230
|
+
throw new Error(`${label} must be a positive number`);
|
|
4231
|
+
}
|
|
4232
|
+
return Math.round(value);
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
// src/commands/logout.ts
|
|
4236
|
+
import { Command as Command11 } from "commander";
|
|
4237
|
+
import chalk9 from "chalk";
|
|
4238
|
+
var logoutCommand = new Command11("logout").description("Remove stored API key").action(() => {
|
|
4152
4239
|
if (!getStoredAPIKey()) {
|
|
4153
4240
|
console.log();
|
|
4154
|
-
console.log(
|
|
4241
|
+
console.log(chalk9.dim(" Not logged in."));
|
|
4155
4242
|
if (hasEnvAPIKey()) {
|
|
4156
|
-
console.log(
|
|
4243
|
+
console.log(chalk9.dim(" Environment API key is still active in this shell."));
|
|
4157
4244
|
}
|
|
4158
4245
|
console.log();
|
|
4159
4246
|
return;
|
|
4160
4247
|
}
|
|
4161
4248
|
clearAPIKey();
|
|
4162
4249
|
console.log();
|
|
4163
|
-
console.log(
|
|
4250
|
+
console.log(chalk9.green(" Logged out."));
|
|
4164
4251
|
if (hasEnvAPIKey()) {
|
|
4165
|
-
console.log(
|
|
4252
|
+
console.log(chalk9.dim(" Environment API key is still active in this shell."));
|
|
4166
4253
|
}
|
|
4167
4254
|
console.log();
|
|
4168
4255
|
});
|
|
4169
4256
|
|
|
4170
4257
|
// src/commands/upgrade.ts
|
|
4171
4258
|
import { spawnSync } from "child_process";
|
|
4172
|
-
import { readFileSync as
|
|
4173
|
-
import { Command as
|
|
4174
|
-
import
|
|
4175
|
-
import
|
|
4259
|
+
import { readFileSync as readFileSync2, realpathSync } from "fs";
|
|
4260
|
+
import { Command as Command12 } from "commander";
|
|
4261
|
+
import chalk10 from "chalk";
|
|
4262
|
+
import ora10 from "ora";
|
|
4176
4263
|
var pkg2 = JSON.parse(
|
|
4177
|
-
|
|
4264
|
+
readFileSync2(new URL("../package.json", import.meta.url), "utf8")
|
|
4178
4265
|
);
|
|
4179
4266
|
function normalizeVersion(version) {
|
|
4180
4267
|
return version.split("-")[0].split(".").map((part) => Number.parseInt(part, 10)).map((part) => Number.isNaN(part) ? 0 : part);
|
|
@@ -4286,10 +4373,10 @@ async function getLatestVersion(packageName) {
|
|
|
4286
4373
|
}
|
|
4287
4374
|
return payload.version;
|
|
4288
4375
|
}
|
|
4289
|
-
var upgradeCommand = new
|
|
4376
|
+
var upgradeCommand = new Command12("upgrade").description("Update the CLI to the latest version").action(async () => {
|
|
4290
4377
|
const currentVersion = pkg2.version ?? "0.0.0";
|
|
4291
4378
|
const packageName = pkg2.name ?? "aicomputer";
|
|
4292
|
-
const spinner =
|
|
4379
|
+
const spinner = ora10("Checking for updates...").start();
|
|
4293
4380
|
let latestVersion;
|
|
4294
4381
|
try {
|
|
4295
4382
|
latestVersion = await getLatestVersion(packageName);
|
|
@@ -4319,16 +4406,16 @@ var upgradeCommand = new Command11("upgrade").description("Update the CLI to the
|
|
|
4319
4406
|
spinner.stop();
|
|
4320
4407
|
console.log();
|
|
4321
4408
|
console.log(
|
|
4322
|
-
|
|
4409
|
+
chalk10.dim(` Updating ${chalk10.bold(`v${currentVersion}`)} -> ${chalk10.bold(`v${latestVersion}`)}`)
|
|
4323
4410
|
);
|
|
4324
|
-
console.log(
|
|
4411
|
+
console.log(chalk10.dim(` ${upgrade.label}`));
|
|
4325
4412
|
console.log();
|
|
4326
4413
|
const result = spawnSync(upgrade.command, upgrade.args, {
|
|
4327
4414
|
stdio: "inherit"
|
|
4328
4415
|
});
|
|
4329
4416
|
if (result.status === 0) {
|
|
4330
4417
|
console.log();
|
|
4331
|
-
console.log(
|
|
4418
|
+
console.log(chalk10.green(` Updated to v${latestVersion}.`));
|
|
4332
4419
|
console.log();
|
|
4333
4420
|
return;
|
|
4334
4421
|
}
|
|
@@ -4336,11 +4423,11 @@ var upgradeCommand = new Command11("upgrade").description("Update the CLI to the
|
|
|
4336
4423
|
});
|
|
4337
4424
|
|
|
4338
4425
|
// src/commands/whoami.ts
|
|
4339
|
-
import { Command as
|
|
4340
|
-
import
|
|
4341
|
-
import
|
|
4342
|
-
var whoamiCommand = new
|
|
4343
|
-
const spinner = options.json ? null :
|
|
4426
|
+
import { Command as Command13 } from "commander";
|
|
4427
|
+
import chalk11 from "chalk";
|
|
4428
|
+
import ora11 from "ora";
|
|
4429
|
+
var whoamiCommand = new Command13("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
|
|
4430
|
+
const spinner = options.json ? null : ora11("Loading user...").start();
|
|
4344
4431
|
try {
|
|
4345
4432
|
const me = await api("/v1/me");
|
|
4346
4433
|
spinner?.stop();
|
|
@@ -4349,14 +4436,14 @@ var whoamiCommand = new Command12("whoami").description("Show current user").opt
|
|
|
4349
4436
|
return;
|
|
4350
4437
|
}
|
|
4351
4438
|
console.log();
|
|
4352
|
-
console.log(` ${
|
|
4439
|
+
console.log(` ${chalk11.bold.white(me.user.display_name || me.user.email)}`);
|
|
4353
4440
|
if (me.user.display_name) {
|
|
4354
|
-
console.log(` ${
|
|
4441
|
+
console.log(` ${chalk11.dim(me.user.email)}`);
|
|
4355
4442
|
}
|
|
4356
4443
|
if (me.api_key.name) {
|
|
4357
|
-
console.log(` ${
|
|
4444
|
+
console.log(` ${chalk11.dim("Key:")} ${me.api_key.name}`);
|
|
4358
4445
|
}
|
|
4359
|
-
console.log(` ${
|
|
4446
|
+
console.log(` ${chalk11.dim("API:")} ${chalk11.dim(getBaseURL())}`);
|
|
4360
4447
|
console.log();
|
|
4361
4448
|
} catch (error) {
|
|
4362
4449
|
if (spinner) {
|
|
@@ -4370,18 +4457,18 @@ var whoamiCommand = new Command12("whoami").description("Show current user").opt
|
|
|
4370
4457
|
|
|
4371
4458
|
// src/index.ts
|
|
4372
4459
|
var pkg3 = JSON.parse(
|
|
4373
|
-
|
|
4460
|
+
readFileSync3(new URL("../package.json", import.meta.url), "utf8")
|
|
4374
4461
|
);
|
|
4375
4462
|
var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
|
|
4376
|
-
var program = new
|
|
4463
|
+
var program = new Command14();
|
|
4377
4464
|
function appendTextSection(lines, title, values) {
|
|
4378
4465
|
if (values.length === 0) {
|
|
4379
4466
|
return;
|
|
4380
4467
|
}
|
|
4381
|
-
lines.push(` ${
|
|
4468
|
+
lines.push(` ${chalk12.dim(title)}`);
|
|
4382
4469
|
lines.push("");
|
|
4383
4470
|
for (const value of values) {
|
|
4384
|
-
lines.push(` ${
|
|
4471
|
+
lines.push(` ${chalk12.white(value)}`);
|
|
4385
4472
|
}
|
|
4386
4473
|
lines.push("");
|
|
4387
4474
|
}
|
|
@@ -4390,10 +4477,10 @@ function appendTableSection(lines, title, entries) {
|
|
|
4390
4477
|
return;
|
|
4391
4478
|
}
|
|
4392
4479
|
const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
|
|
4393
|
-
lines.push(` ${
|
|
4480
|
+
lines.push(` ${chalk12.dim(title)}`);
|
|
4394
4481
|
lines.push("");
|
|
4395
4482
|
for (const entry of entries) {
|
|
4396
|
-
lines.push(` ${
|
|
4483
|
+
lines.push(` ${chalk12.white(padEnd(entry.term, width))}${chalk12.dim(entry.desc)}`);
|
|
4397
4484
|
}
|
|
4398
4485
|
lines.push("");
|
|
4399
4486
|
}
|
|
@@ -4414,14 +4501,15 @@ function formatRootHelp(cmd) {
|
|
|
4414
4501
|
["Computers", []],
|
|
4415
4502
|
["Images", []],
|
|
4416
4503
|
["Access", []],
|
|
4504
|
+
["Mounts", []],
|
|
4417
4505
|
["Agents", []],
|
|
4418
4506
|
["Other", []]
|
|
4419
4507
|
];
|
|
4420
4508
|
const otherGroup = groups.find(([name]) => name === "Other")[1];
|
|
4421
|
-
lines.push(`${
|
|
4509
|
+
lines.push(`${chalk12.bold(cliName)} ${chalk12.dim(`v${version}`)}`);
|
|
4422
4510
|
lines.push("");
|
|
4423
4511
|
if (cmd.description()) {
|
|
4424
|
-
lines.push(` ${
|
|
4512
|
+
lines.push(` ${chalk12.dim(cmd.description())}`);
|
|
4425
4513
|
lines.push("");
|
|
4426
4514
|
}
|
|
4427
4515
|
appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
|
|
@@ -4436,8 +4524,10 @@ function formatRootHelp(cmd) {
|
|
|
4436
4524
|
groups[2][1].push(entry);
|
|
4437
4525
|
} else if (["open", "ssh", "ports"].includes(name)) {
|
|
4438
4526
|
groups[3][1].push(entry);
|
|
4439
|
-
} else if (
|
|
4527
|
+
} else if (name === "mount") {
|
|
4440
4528
|
groups[4][1].push(entry);
|
|
4529
|
+
} else if (["agent", "acp"].includes(name)) {
|
|
4530
|
+
groups[5][1].push(entry);
|
|
4441
4531
|
} else {
|
|
4442
4532
|
otherGroup.push(entry);
|
|
4443
4533
|
}
|
|
@@ -4472,10 +4562,10 @@ function formatSubcommandHelp(cmd, helper) {
|
|
|
4472
4562
|
term: helper.optionTerm(option),
|
|
4473
4563
|
desc: helper.optionDescription(option)
|
|
4474
4564
|
}));
|
|
4475
|
-
lines.push(
|
|
4565
|
+
lines.push(chalk12.bold(commandPath(cmd)));
|
|
4476
4566
|
lines.push("");
|
|
4477
4567
|
if (description) {
|
|
4478
|
-
lines.push(` ${
|
|
4568
|
+
lines.push(` ${chalk12.dim(description)}`);
|
|
4479
4569
|
lines.push("");
|
|
4480
4570
|
}
|
|
4481
4571
|
appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
|
|
@@ -4514,6 +4604,7 @@ program.addCommand(acpCommand);
|
|
|
4514
4604
|
program.addCommand(openCommand);
|
|
4515
4605
|
program.addCommand(sshCommand);
|
|
4516
4606
|
program.addCommand(portsCommand);
|
|
4607
|
+
program.addCommand(mountCommand);
|
|
4517
4608
|
program.addCommand(removeCommand);
|
|
4518
4609
|
program.addCommand(completionCommand);
|
|
4519
4610
|
applyHelpFormatting(program);
|