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