aicomputer 0.1.4 → 0.1.6
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 +15 -0
- package/dist/index.js +1201 -145
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
4
|
+
import { Command as Command9 } from "commander";
|
|
5
|
+
import chalk8 from "chalk";
|
|
6
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
7
7
|
import { basename as basename2 } from "path";
|
|
8
8
|
|
|
9
9
|
// src/commands/access.ts
|
|
@@ -159,6 +159,9 @@ async function createComputer(input) {
|
|
|
159
159
|
body: JSON.stringify(input)
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
|
+
async function getFilesystemSettings() {
|
|
163
|
+
return api("/v1/me/filesystem");
|
|
164
|
+
}
|
|
162
165
|
async function getConnectionInfo(computerID) {
|
|
163
166
|
return api(`/v1/computers/${computerID}/connection`);
|
|
164
167
|
}
|
|
@@ -216,7 +219,7 @@ function vncURL(computer) {
|
|
|
216
219
|
return null;
|
|
217
220
|
}
|
|
218
221
|
const domain = computer.primary_web_host.replace(/^[^.]+\./, "");
|
|
219
|
-
return `https://6080--${computer.handle}.${domain}
|
|
222
|
+
return `https://6080--${computer.handle}.${domain}`;
|
|
220
223
|
}
|
|
221
224
|
function terminalURL(computer) {
|
|
222
225
|
if (computer.runtime_family !== "managed-worker") {
|
|
@@ -233,21 +236,89 @@ function normalizePrimaryPath(primaryPath) {
|
|
|
233
236
|
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
// src/lib/ssh-config.ts
|
|
240
|
+
import { homedir as homedir2 } from "os";
|
|
241
|
+
import { join as join2 } from "path";
|
|
242
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
243
|
+
var MANAGED_BLOCK_START = "# >>> agentcomputer ssh setup >>>";
|
|
244
|
+
var MANAGED_BLOCK_END = "# <<< agentcomputer ssh setup <<<";
|
|
245
|
+
async function ensureSSHAliasConfig(options) {
|
|
246
|
+
const sshDir = join2(homedir2(), ".ssh");
|
|
247
|
+
const configPath = join2(sshDir, "config");
|
|
248
|
+
await mkdir(sshDir, { recursive: true, mode: 448 });
|
|
249
|
+
let existing = "";
|
|
250
|
+
try {
|
|
251
|
+
existing = await readFile(configPath, "utf8");
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error.code !== "ENOENT") {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const nextBlock = renderManagedBlock(options);
|
|
258
|
+
const managedBlockPattern = new RegExp(
|
|
259
|
+
`${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
|
|
260
|
+
"m"
|
|
261
|
+
);
|
|
262
|
+
let nextContents;
|
|
263
|
+
if (managedBlockPattern.test(existing)) {
|
|
264
|
+
nextContents = existing.replace(managedBlockPattern, nextBlock);
|
|
265
|
+
} else {
|
|
266
|
+
const normalized = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
|
|
267
|
+
`;
|
|
268
|
+
nextContents = normalized.length === 0 ? nextBlock : `${normalized}
|
|
269
|
+
${nextBlock}`;
|
|
270
|
+
}
|
|
271
|
+
const changed = nextContents !== existing;
|
|
272
|
+
if (changed) {
|
|
273
|
+
await writeFile(configPath, nextContents, { mode: 384 });
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
configPath,
|
|
277
|
+
changed
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function renderManagedBlock(options) {
|
|
281
|
+
const user = options.user?.trim() || "agentcomputer";
|
|
282
|
+
const identityFile = formatIdentityFilePath(options.identityFilePath);
|
|
283
|
+
return `${MANAGED_BLOCK_START}
|
|
284
|
+
Host ${options.alias}
|
|
285
|
+
HostName ${options.host}
|
|
286
|
+
Port ${options.port}
|
|
287
|
+
User ${user}
|
|
288
|
+
IdentityFile ${identityFile}
|
|
289
|
+
IdentitiesOnly yes
|
|
290
|
+
ServerAliveInterval 30
|
|
291
|
+
ServerAliveCountMax 4
|
|
292
|
+
${MANAGED_BLOCK_END}
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
function formatIdentityFilePath(path) {
|
|
296
|
+
const normalized = path.trim();
|
|
297
|
+
const homePath = `${homedir2()}/`;
|
|
298
|
+
if (normalized.startsWith(homePath)) {
|
|
299
|
+
return `~/${normalized.slice(homePath.length)}`;
|
|
300
|
+
}
|
|
301
|
+
return normalized.replaceAll(" ", "\\ ");
|
|
302
|
+
}
|
|
303
|
+
function escapeRegex(value) {
|
|
304
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
305
|
+
}
|
|
306
|
+
|
|
236
307
|
// src/lib/ssh-keys.ts
|
|
237
308
|
import { basename } from "path";
|
|
238
|
-
import { homedir as
|
|
239
|
-
import { readFile, mkdir } from "fs/promises";
|
|
309
|
+
import { homedir as homedir3 } from "os";
|
|
310
|
+
import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
|
|
240
311
|
import { execFileSync } from "child_process";
|
|
241
312
|
import { existsSync as existsSync2 } from "fs";
|
|
242
313
|
var DEFAULT_PUBLIC_KEY_PATHS = [
|
|
243
|
-
`${
|
|
244
|
-
`${
|
|
245
|
-
`${
|
|
314
|
+
`${homedir3()}/.ssh/id_ed25519.pub`,
|
|
315
|
+
`${homedir3()}/.ssh/id_ecdsa.pub`,
|
|
316
|
+
`${homedir3()}/.ssh/id_rsa.pub`
|
|
246
317
|
];
|
|
247
318
|
async function ensureDefaultSSHKeyRegistered() {
|
|
248
319
|
for (const path of DEFAULT_PUBLIC_KEY_PATHS) {
|
|
249
320
|
try {
|
|
250
|
-
const publicKey2 = (await
|
|
321
|
+
const publicKey2 = (await readFile2(path, "utf8")).trim();
|
|
251
322
|
if (!publicKey2) {
|
|
252
323
|
continue;
|
|
253
324
|
}
|
|
@@ -271,7 +342,7 @@ async function ensureDefaultSSHKeyRegistered() {
|
|
|
271
342
|
}
|
|
272
343
|
}
|
|
273
344
|
const generated = await generateSSHKey();
|
|
274
|
-
const publicKey = (await
|
|
345
|
+
const publicKey = (await readFile2(generated.publicKeyPath, "utf8")).trim();
|
|
275
346
|
const key = await api("/v1/ssh-keys", {
|
|
276
347
|
method: "POST",
|
|
277
348
|
body: JSON.stringify({
|
|
@@ -286,9 +357,9 @@ async function ensureDefaultSSHKeyRegistered() {
|
|
|
286
357
|
};
|
|
287
358
|
}
|
|
288
359
|
async function generateSSHKey() {
|
|
289
|
-
const sshDir = `${
|
|
360
|
+
const sshDir = `${homedir3()}/.ssh`;
|
|
290
361
|
if (!existsSync2(sshDir)) {
|
|
291
|
-
await
|
|
362
|
+
await mkdir2(sshDir, { mode: 448 });
|
|
292
363
|
}
|
|
293
364
|
const privateKeyPath = `${sshDir}/id_ed25519`;
|
|
294
365
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
@@ -419,7 +490,11 @@ var openCommand = new Command("open").description("Open a computer in your brows
|
|
|
419
490
|
process.exit(1);
|
|
420
491
|
}
|
|
421
492
|
});
|
|
422
|
-
var sshCommand = new Command("ssh").description("Open an SSH session to a computer").argument("[id-or-handle]", "Computer id or handle").action(async (identifier) => {
|
|
493
|
+
var sshCommand = new Command("ssh").description("Open an SSH session to a computer").argument("[id-or-handle]", "Computer id or handle").option("--setup", "Register key and configure a global SSH alias").option("--alias <alias>", "SSH host alias", "agentcomputer.ai").option("--host <host>", "SSH gateway host", "ssh.agentcomputer.ai").option("--port <port>", "SSH gateway port", "22").action(async (identifier, options) => {
|
|
494
|
+
if (options.setup) {
|
|
495
|
+
await setupSSHAlias(options);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
423
498
|
const spinner = ora(
|
|
424
499
|
identifier ? "Preparing SSH access..." : "Fetching computers..."
|
|
425
500
|
).start();
|
|
@@ -431,9 +506,7 @@ var sshCommand = new Command("ssh").description("Open an SSH session to a comput
|
|
|
431
506
|
}
|
|
432
507
|
const registered = await ensureDefaultSSHKeyRegistered();
|
|
433
508
|
spinner.succeed(`Connecting to ${chalk2.bold(computer.handle)}`);
|
|
434
|
-
console.log(
|
|
435
|
-
chalk2.dim(` ssh -p ${info.connection.ssh_port} ${info.connection.ssh_user}@${info.connection.ssh_host}`)
|
|
436
|
-
);
|
|
509
|
+
console.log(chalk2.dim(` ${formatSSHCommand(info.connection.ssh_user, info.connection.ssh_host, info.connection.ssh_port)}`));
|
|
437
510
|
console.log();
|
|
438
511
|
await runSSH([
|
|
439
512
|
"-i",
|
|
@@ -534,6 +607,69 @@ async function runSSH(args) {
|
|
|
534
607
|
});
|
|
535
608
|
});
|
|
536
609
|
}
|
|
610
|
+
async function setupSSHAlias(options) {
|
|
611
|
+
const spinner = ora("Configuring global SSH access...").start();
|
|
612
|
+
try {
|
|
613
|
+
const alias = normalizeSSHAlias(options.alias ?? "agentcomputer.ai");
|
|
614
|
+
const host = normalizeSSHHost(options.host ?? "ssh.agentcomputer.ai");
|
|
615
|
+
const port = parseSSHPort(options.port ?? "22");
|
|
616
|
+
const registered = await ensureDefaultSSHKeyRegistered();
|
|
617
|
+
const configResult = await ensureSSHAliasConfig({
|
|
618
|
+
alias,
|
|
619
|
+
host,
|
|
620
|
+
port,
|
|
621
|
+
user: "agentcomputer",
|
|
622
|
+
identityFilePath: registered.privateKeyPath
|
|
623
|
+
});
|
|
624
|
+
spinner.succeed(`SSH alias '${alias}' is ready`);
|
|
625
|
+
console.log();
|
|
626
|
+
console.log(chalk2.dim(` SSH config: ${configResult.configPath}`));
|
|
627
|
+
console.log(chalk2.dim(` Identity: ${registered.privateKeyPath}`));
|
|
628
|
+
console.log();
|
|
629
|
+
console.log(` ${chalk2.bold("Shell:")} ssh ${alias}`);
|
|
630
|
+
console.log(` ${chalk2.bold("Direct:")} ssh <handle>@${alias}`);
|
|
631
|
+
console.log();
|
|
632
|
+
} catch (error) {
|
|
633
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to configure SSH alias");
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function normalizeSSHAlias(value) {
|
|
638
|
+
const alias = value.trim();
|
|
639
|
+
if (!alias) {
|
|
640
|
+
throw new Error("ssh alias cannot be empty");
|
|
641
|
+
}
|
|
642
|
+
if (!/^[A-Za-z0-9._-]+$/.test(alias)) {
|
|
643
|
+
throw new Error("ssh alias may contain only letters, numbers, dot, dash, or underscore");
|
|
644
|
+
}
|
|
645
|
+
return alias;
|
|
646
|
+
}
|
|
647
|
+
function normalizeSSHHost(value) {
|
|
648
|
+
const host = value.trim();
|
|
649
|
+
if (!host) {
|
|
650
|
+
throw new Error("ssh host cannot be empty");
|
|
651
|
+
}
|
|
652
|
+
if (host.includes(" ")) {
|
|
653
|
+
throw new Error("ssh host cannot contain spaces");
|
|
654
|
+
}
|
|
655
|
+
return host;
|
|
656
|
+
}
|
|
657
|
+
function parseSSHPort(value) {
|
|
658
|
+
const parsed = Number.parseInt(value, 10);
|
|
659
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
660
|
+
throw new Error("ssh port must be between 1 and 65535");
|
|
661
|
+
}
|
|
662
|
+
return parsed;
|
|
663
|
+
}
|
|
664
|
+
function formatSSHCommand(user, host, port) {
|
|
665
|
+
if (!user.trim() || !host.trim()) {
|
|
666
|
+
return "ssh unavailable";
|
|
667
|
+
}
|
|
668
|
+
if (port <= 0 || port === 22) {
|
|
669
|
+
return `ssh ${user}@${host}`;
|
|
670
|
+
}
|
|
671
|
+
return `ssh -p ${port} ${user}@${host}`;
|
|
672
|
+
}
|
|
537
673
|
async function resolveSSHComputer(identifier, spinner) {
|
|
538
674
|
const trimmed = identifier?.trim();
|
|
539
675
|
if (trimmed) {
|
|
@@ -578,10 +714,807 @@ function describeSSHChoice(computer) {
|
|
|
578
714
|
return `${computer.runtime_family} ${timeAgo(computer.updated_at)}`;
|
|
579
715
|
}
|
|
580
716
|
|
|
581
|
-
// src/commands/
|
|
717
|
+
// src/commands/acp.ts
|
|
718
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
719
|
+
import readline from "readline";
|
|
582
720
|
import { Command as Command2 } from "commander";
|
|
721
|
+
|
|
722
|
+
// src/lib/agents.ts
|
|
723
|
+
async function listComputerAgents(computerID) {
|
|
724
|
+
const response = await api(`/v1/computers/${computerID}/agents`);
|
|
725
|
+
return response.agents;
|
|
726
|
+
}
|
|
727
|
+
async function listAgentSessions(computerID) {
|
|
728
|
+
const response = await api(`/v1/computers/${computerID}/agent-sessions`);
|
|
729
|
+
return response.sessions;
|
|
730
|
+
}
|
|
731
|
+
async function listFleetAgentSessions() {
|
|
732
|
+
const response = await api("/v1/fleet/agent-sessions");
|
|
733
|
+
return response.sessions;
|
|
734
|
+
}
|
|
735
|
+
async function createAgentSession(computerID, input) {
|
|
736
|
+
const response = await api(`/v1/computers/${computerID}/agent-sessions`, {
|
|
737
|
+
method: "POST",
|
|
738
|
+
body: JSON.stringify(input)
|
|
739
|
+
});
|
|
740
|
+
return response.session;
|
|
741
|
+
}
|
|
742
|
+
async function getAgentSession(computerID, sessionID) {
|
|
743
|
+
const response = await api(
|
|
744
|
+
`/v1/computers/${computerID}/agent-sessions/${sessionID}`
|
|
745
|
+
);
|
|
746
|
+
return response.session;
|
|
747
|
+
}
|
|
748
|
+
async function promptAgentSession(computerID, sessionID, input) {
|
|
749
|
+
return api(
|
|
750
|
+
`/v1/computers/${computerID}/agent-sessions/${sessionID}/prompt`,
|
|
751
|
+
{
|
|
752
|
+
method: "POST",
|
|
753
|
+
body: JSON.stringify(input)
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
async function cancelAgentSession(computerID, sessionID) {
|
|
758
|
+
const response = await api(
|
|
759
|
+
`/v1/computers/${computerID}/agent-sessions/${sessionID}/cancel`,
|
|
760
|
+
{
|
|
761
|
+
method: "POST"
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
return response.session;
|
|
765
|
+
}
|
|
766
|
+
async function interruptAgentSession(computerID, sessionID) {
|
|
767
|
+
const response = await api(
|
|
768
|
+
`/v1/computers/${computerID}/agent-sessions/${sessionID}/interrupt`,
|
|
769
|
+
{
|
|
770
|
+
method: "POST"
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
return response.session;
|
|
774
|
+
}
|
|
775
|
+
async function deleteAgentSession(computerID, sessionID) {
|
|
776
|
+
return api(`/v1/computers/${computerID}/agent-sessions/${sessionID}`, {
|
|
777
|
+
method: "DELETE"
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
async function openAgentSessionEventsStream(computerID, sessionID, options = {}) {
|
|
781
|
+
const apiKey = getAPIKey();
|
|
782
|
+
if (!apiKey) {
|
|
783
|
+
throw new ApiError(401, "not logged in; run 'computer login' first");
|
|
784
|
+
}
|
|
785
|
+
const endpoint = new URL(`${getBaseURL()}/v1/computers/${computerID}/agent-sessions/${sessionID}/events`);
|
|
786
|
+
if (options.lastEventId?.trim()) {
|
|
787
|
+
endpoint.searchParams.set("last_event_id", options.lastEventId.trim());
|
|
788
|
+
}
|
|
789
|
+
const response = await fetch(endpoint, {
|
|
790
|
+
method: "GET",
|
|
791
|
+
headers: {
|
|
792
|
+
Accept: "text/event-stream",
|
|
793
|
+
Authorization: `Bearer ${apiKey}`
|
|
794
|
+
},
|
|
795
|
+
signal: options.signal
|
|
796
|
+
});
|
|
797
|
+
if (!response.ok) {
|
|
798
|
+
const message = await readErrorMessage2(response);
|
|
799
|
+
throw new ApiError(response.status, message);
|
|
800
|
+
}
|
|
801
|
+
return response;
|
|
802
|
+
}
|
|
803
|
+
async function waitForSessionToSettle(computerID, sessionID, promptID, options = {}) {
|
|
804
|
+
const intervalMs = options.intervalMs ?? 1e3;
|
|
805
|
+
const timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
|
|
806
|
+
const deadline = Date.now() + timeoutMs;
|
|
807
|
+
while (true) {
|
|
808
|
+
const session = await getAgentSession(computerID, sessionID);
|
|
809
|
+
if (session.status !== "running" && session.status !== "cancelling" && (!promptID || session.active_prompt_id !== promptID)) {
|
|
810
|
+
return session;
|
|
811
|
+
}
|
|
812
|
+
if (Date.now() >= deadline) {
|
|
813
|
+
throw new Error("timed out waiting for agent session to finish");
|
|
814
|
+
}
|
|
815
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function readErrorMessage2(response) {
|
|
819
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
820
|
+
if (contentType.includes("application/json")) {
|
|
821
|
+
try {
|
|
822
|
+
const payload = await response.json();
|
|
823
|
+
return payload.error ?? JSON.stringify(payload);
|
|
824
|
+
} catch {
|
|
825
|
+
return response.statusText || "request failed";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const body = await response.text();
|
|
829
|
+
return body || response.statusText || "request failed";
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/commands/acp.ts
|
|
833
|
+
var pkg = JSON.parse(
|
|
834
|
+
readFileSync2(new URL("../package.json", import.meta.url), "utf8")
|
|
835
|
+
);
|
|
836
|
+
function emit(message) {
|
|
837
|
+
process.stdout.write(`${JSON.stringify(message)}
|
|
838
|
+
`);
|
|
839
|
+
}
|
|
840
|
+
function emitResult(id, result) {
|
|
841
|
+
if (id === void 0 || id === null) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
emit({
|
|
845
|
+
jsonrpc: "2.0",
|
|
846
|
+
id,
|
|
847
|
+
result
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
function emitError(id, code, message) {
|
|
851
|
+
emit({
|
|
852
|
+
jsonrpc: "2.0",
|
|
853
|
+
id: id ?? null,
|
|
854
|
+
error: {
|
|
855
|
+
code,
|
|
856
|
+
message
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
function forwardBridgePayload(payload, publicSessionID, backendSessionID) {
|
|
861
|
+
let message;
|
|
862
|
+
try {
|
|
863
|
+
message = JSON.parse(payload);
|
|
864
|
+
} catch {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const method = typeof message.method === "string" ? message.method : "";
|
|
868
|
+
if (method !== "session/update" && method !== "session/request_permission") {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const params = typeof message.params === "object" && message.params !== null ? { ...message.params } : {};
|
|
872
|
+
const payloadSessionID = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
|
|
873
|
+
if (backendSessionID?.trim() && payloadSessionID && payloadSessionID !== backendSessionID.trim()) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
params.sessionId = publicSessionID;
|
|
877
|
+
process.stdout.write(`${JSON.stringify({ ...message, params })}
|
|
878
|
+
`);
|
|
879
|
+
}
|
|
880
|
+
async function forwardRawEventStream(computerID, publicSessionID, backendSessionID, signal) {
|
|
881
|
+
const response = await openAgentSessionEventsStream(computerID, publicSessionID, { signal });
|
|
882
|
+
if (!response.body) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const decoder = new TextDecoder();
|
|
886
|
+
const reader = response.body.getReader();
|
|
887
|
+
let buffer = "";
|
|
888
|
+
while (true) {
|
|
889
|
+
const { done, value } = await reader.read();
|
|
890
|
+
if (done) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
|
|
894
|
+
while (true) {
|
|
895
|
+
const splitIndex = buffer.indexOf("\n\n");
|
|
896
|
+
if (splitIndex === -1) {
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
const chunk = buffer.slice(0, splitIndex);
|
|
900
|
+
buffer = buffer.slice(splitIndex + 2);
|
|
901
|
+
const payload = chunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n").trim();
|
|
902
|
+
if (!payload || payload === "heartbeat") {
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
forwardBridgePayload(payload, publicSessionID, backendSessionID);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
async function ensureBridgeSession(computerID, agent, baseName, cwd, requestCwd, counter) {
|
|
910
|
+
const resolvedCwd = typeof requestCwd === "string" && requestCwd.trim() ? requestCwd.trim() : cwd;
|
|
911
|
+
return createAgentSession(computerID, {
|
|
912
|
+
agent,
|
|
913
|
+
name: `${baseName}-${counter}`,
|
|
914
|
+
cwd: resolvedCwd,
|
|
915
|
+
resume: false
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
async function handlePromptRequest(computerID, sessionID, id, params) {
|
|
919
|
+
if (id === void 0 || id === null) {
|
|
920
|
+
throw new Error("session/prompt requires a request id");
|
|
921
|
+
}
|
|
922
|
+
const prompt = Array.isArray(params?.prompt) ? params?.prompt : [];
|
|
923
|
+
if (prompt.length === 0) {
|
|
924
|
+
throw new Error("session/prompt requires prompt content");
|
|
925
|
+
}
|
|
926
|
+
const accepted = await promptAgentSession(computerID, sessionID, { prompt });
|
|
927
|
+
const current = await getAgentSession(computerID, sessionID);
|
|
928
|
+
const controller = new AbortController();
|
|
929
|
+
const streamPromise = forwardRawEventStream(
|
|
930
|
+
computerID,
|
|
931
|
+
sessionID,
|
|
932
|
+
current.backend_session_id,
|
|
933
|
+
controller.signal
|
|
934
|
+
).catch((error) => {
|
|
935
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
return;
|
|
939
|
+
});
|
|
940
|
+
try {
|
|
941
|
+
const settled = await waitForSessionToSettle(computerID, sessionID, accepted.prompt_id);
|
|
942
|
+
emitResult(id, {
|
|
943
|
+
stopReason: settled.last_stop_reason || "end_turn"
|
|
944
|
+
});
|
|
945
|
+
} finally {
|
|
946
|
+
controller.abort();
|
|
947
|
+
await streamPromise.catch(() => void 0);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
var acpCommand = new Command2("acp").description("Expose Agent Computer sessions through a local ACP bridge");
|
|
951
|
+
acpCommand.command("serve").description("Serve a local stdio ACP bridge backed by one machine").argument("<machine>", "Computer id or handle").requiredOption("--agent <agent>", "Agent id to use for remote sessions").option("--name <name>", "Base session name prefix", "acp").option("--cwd <cwd>", "Default working directory").action(async (identifier, options) => {
|
|
952
|
+
try {
|
|
953
|
+
const computer = await resolveComputer(identifier);
|
|
954
|
+
const rl = readline.createInterface({
|
|
955
|
+
input: process.stdin,
|
|
956
|
+
crlfDelay: Infinity
|
|
957
|
+
});
|
|
958
|
+
let nextSession = 0;
|
|
959
|
+
for await (const line of rl) {
|
|
960
|
+
const trimmed = line.trim();
|
|
961
|
+
if (!trimmed) {
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
let message;
|
|
965
|
+
try {
|
|
966
|
+
message = JSON.parse(trimmed);
|
|
967
|
+
} catch {
|
|
968
|
+
emitError(null, -32700, "invalid JSON");
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
switch (message.method) {
|
|
973
|
+
case "initialize": {
|
|
974
|
+
emitResult(message.id, {
|
|
975
|
+
protocolVersion: 1,
|
|
976
|
+
agentInfo: {
|
|
977
|
+
name: "computer-acp-bridge",
|
|
978
|
+
version: pkg.version ?? "0.0.0"
|
|
979
|
+
},
|
|
980
|
+
serverInfo: {
|
|
981
|
+
name: "computer-acp-bridge",
|
|
982
|
+
version: pkg.version ?? "0.0.0"
|
|
983
|
+
},
|
|
984
|
+
agentCapabilities: {
|
|
985
|
+
loadSession: true,
|
|
986
|
+
sessionCapabilities: {
|
|
987
|
+
list: {}
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
capabilities: {}
|
|
991
|
+
});
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
case "session/list": {
|
|
995
|
+
const sessions = await listAgentSessions(computer.id);
|
|
996
|
+
const requestedCwd = typeof message.params?.cwd === "string" ? message.params.cwd.trim() : "";
|
|
997
|
+
emitResult(message.id, {
|
|
998
|
+
sessions: sessions.filter((session) => session.agent === options.agent).filter((session) => !requestedCwd || session.cwd === requestedCwd).map((session) => ({
|
|
999
|
+
sessionId: session.id,
|
|
1000
|
+
cwd: session.cwd,
|
|
1001
|
+
title: session.name || void 0,
|
|
1002
|
+
updatedAt: session.updated_at
|
|
1003
|
+
}))
|
|
1004
|
+
});
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
case "session/new": {
|
|
1008
|
+
nextSession += 1;
|
|
1009
|
+
const session = await ensureBridgeSession(
|
|
1010
|
+
computer.id,
|
|
1011
|
+
options.agent,
|
|
1012
|
+
options.name?.trim() || "acp",
|
|
1013
|
+
options.cwd,
|
|
1014
|
+
message.params?.cwd,
|
|
1015
|
+
nextSession
|
|
1016
|
+
);
|
|
1017
|
+
emitResult(message.id, {
|
|
1018
|
+
sessionId: session.id
|
|
1019
|
+
});
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
case "session/load": {
|
|
1023
|
+
if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
|
|
1024
|
+
throw new Error("session/load requires sessionId");
|
|
1025
|
+
}
|
|
1026
|
+
await getAgentSession(computer.id, message.params.sessionId.trim());
|
|
1027
|
+
emitResult(message.id, {});
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
case "session/prompt": {
|
|
1031
|
+
if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
|
|
1032
|
+
throw new Error("session/prompt requires sessionId");
|
|
1033
|
+
}
|
|
1034
|
+
await handlePromptRequest(computer.id, message.params.sessionId.trim(), message.id, message.params);
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
case "session/cancel": {
|
|
1038
|
+
if (typeof message.params?.sessionId !== "string" || !message.params.sessionId.trim()) {
|
|
1039
|
+
throw new Error("session/cancel requires sessionId");
|
|
1040
|
+
}
|
|
1041
|
+
await cancelAgentSession(computer.id, message.params.sessionId.trim());
|
|
1042
|
+
emitResult(message.id, {});
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
default:
|
|
1046
|
+
emitError(message.id, -32601, `method not found: ${message.method ?? "<unknown>"}`);
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
emitError(
|
|
1051
|
+
message.id,
|
|
1052
|
+
-32e3,
|
|
1053
|
+
error instanceof Error ? error.message : "request failed"
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
console.error(error instanceof Error ? error.message : "Failed to start ACP bridge");
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
// src/commands/agent.ts
|
|
1064
|
+
import { Command as Command3 } from "commander";
|
|
583
1065
|
import chalk3 from "chalk";
|
|
584
1066
|
import ora2 from "ora";
|
|
1067
|
+
function formatAgentSessionStatus(status) {
|
|
1068
|
+
switch (status) {
|
|
1069
|
+
case "idle":
|
|
1070
|
+
return chalk3.green(status);
|
|
1071
|
+
case "running":
|
|
1072
|
+
return chalk3.blue(status);
|
|
1073
|
+
case "cancelling":
|
|
1074
|
+
return chalk3.yellow(status);
|
|
1075
|
+
case "interrupted":
|
|
1076
|
+
return chalk3.yellow(status);
|
|
1077
|
+
case "failed":
|
|
1078
|
+
return chalk3.red(status);
|
|
1079
|
+
case "closed":
|
|
1080
|
+
return chalk3.gray(status);
|
|
1081
|
+
default:
|
|
1082
|
+
return status;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function printAgents(agents) {
|
|
1086
|
+
const idWidth = Math.max(5, ...agents.map((agent) => agent.id.length));
|
|
1087
|
+
console.log();
|
|
1088
|
+
console.log(
|
|
1089
|
+
` ${chalk3.dim(padEnd("Agent", idWidth + 2))}${chalk3.dim(padEnd("Installed", 12))}${chalk3.dim(padEnd("Creds", 8))}${chalk3.dim("Version")}`
|
|
1090
|
+
);
|
|
1091
|
+
console.log(
|
|
1092
|
+
` ${chalk3.dim("-".repeat(idWidth + 2))}${chalk3.dim("-".repeat(12))}${chalk3.dim("-".repeat(8))}${chalk3.dim("-".repeat(12))}`
|
|
1093
|
+
);
|
|
1094
|
+
for (const agent of agents) {
|
|
1095
|
+
console.log(
|
|
1096
|
+
` ${padEnd(agent.id, idWidth + 2)}${padEnd(agent.installed ? chalk3.green("yes") : chalk3.gray("no"), 12)}${padEnd(agent.credentialsAvailable ? chalk3.green("yes") : chalk3.yellow("no"), 8)}${agent.version ?? chalk3.dim("unknown")}`
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
console.log();
|
|
1100
|
+
}
|
|
1101
|
+
function printSessions(sessions, handleByComputerID = /* @__PURE__ */ new Map()) {
|
|
1102
|
+
if (sessions.length === 0) {
|
|
1103
|
+
console.log();
|
|
1104
|
+
console.log(chalk3.dim(" No agent sessions found."));
|
|
1105
|
+
console.log();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const nameWidth = Math.max(7, ...sessions.map((session) => (session.name || "default").length));
|
|
1109
|
+
const agentWidth = Math.max(5, ...sessions.map((session) => session.agent.length));
|
|
1110
|
+
const statusWidth = 13;
|
|
1111
|
+
console.log();
|
|
1112
|
+
console.log(
|
|
1113
|
+
` ${chalk3.dim(padEnd("Session", 14))}${chalk3.dim(padEnd("Name", nameWidth + 2))}${chalk3.dim(padEnd("Agent", agentWidth + 2))}${chalk3.dim(padEnd("Status", statusWidth + 2))}${chalk3.dim("Location")}`
|
|
1114
|
+
);
|
|
1115
|
+
console.log(
|
|
1116
|
+
` ${chalk3.dim("-".repeat(14))}${chalk3.dim("-".repeat(nameWidth + 2))}${chalk3.dim("-".repeat(agentWidth + 2))}${chalk3.dim("-".repeat(statusWidth + 2))}${chalk3.dim("-".repeat(20))}`
|
|
1117
|
+
);
|
|
1118
|
+
for (const session of sessions) {
|
|
1119
|
+
const location = handleByComputerID.get(session.computer_id) ?? session.computer_id;
|
|
1120
|
+
console.log(
|
|
1121
|
+
` ${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}`
|
|
1122
|
+
);
|
|
1123
|
+
console.log(` ${chalk3.dim(` cwd=${session.cwd} updated=${timeAgo(session.updated_at)}`)}`);
|
|
1124
|
+
if (session.last_stop_reason || session.last_error) {
|
|
1125
|
+
console.log(` ${chalk3.dim(` ${session.last_error ? `error=${session.last_error}` : `stop=${session.last_stop_reason}`}`)}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
console.log();
|
|
1129
|
+
}
|
|
1130
|
+
var StreamPrinter = class {
|
|
1131
|
+
inlineOpen = false;
|
|
1132
|
+
writeText(text) {
|
|
1133
|
+
if (!text) return;
|
|
1134
|
+
process.stdout.write(text);
|
|
1135
|
+
this.inlineOpen = !text.endsWith("\n");
|
|
1136
|
+
}
|
|
1137
|
+
writeDim(text) {
|
|
1138
|
+
this.ensureBreak();
|
|
1139
|
+
process.stdout.write(chalk3.dim(text));
|
|
1140
|
+
this.inlineOpen = !text.endsWith("\n");
|
|
1141
|
+
}
|
|
1142
|
+
writeLine(text) {
|
|
1143
|
+
this.ensureBreak();
|
|
1144
|
+
console.log(text);
|
|
1145
|
+
this.inlineOpen = false;
|
|
1146
|
+
}
|
|
1147
|
+
ensureBreak() {
|
|
1148
|
+
if (!this.inlineOpen) {
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
process.stdout.write("\n");
|
|
1152
|
+
this.inlineOpen = false;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
async function streamSessionEvents(computerID, sessionID, options = {}) {
|
|
1156
|
+
const response = await openAgentSessionEventsStream(computerID, sessionID, {
|
|
1157
|
+
lastEventId: options.lastEventId?.trim() || void 0,
|
|
1158
|
+
signal: options.signal
|
|
1159
|
+
});
|
|
1160
|
+
if (!response.body) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const decoder = new TextDecoder();
|
|
1164
|
+
const reader = response.body.getReader();
|
|
1165
|
+
const printer = new StreamPrinter();
|
|
1166
|
+
let buffer = "";
|
|
1167
|
+
while (true) {
|
|
1168
|
+
const { done, value } = await reader.read();
|
|
1169
|
+
if (done) {
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
|
|
1173
|
+
while (true) {
|
|
1174
|
+
const splitIndex = buffer.indexOf("\n\n");
|
|
1175
|
+
if (splitIndex === -1) {
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
const chunk = buffer.slice(0, splitIndex);
|
|
1179
|
+
buffer = buffer.slice(splitIndex + 2);
|
|
1180
|
+
renderSSEChunk(chunk, printer, options.json === true);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
printer.ensureBreak();
|
|
1184
|
+
}
|
|
1185
|
+
function renderSSEChunk(chunk, printer, asJson) {
|
|
1186
|
+
const dataLines = chunk.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
|
|
1187
|
+
if (dataLines.length === 0) {
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const payload = dataLines.join("\n").trim();
|
|
1191
|
+
if (!payload || payload === "heartbeat") {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (asJson) {
|
|
1195
|
+
try {
|
|
1196
|
+
const parsed = JSON.parse(payload);
|
|
1197
|
+
console.log(JSON.stringify(parsed));
|
|
1198
|
+
} catch {
|
|
1199
|
+
console.log(payload);
|
|
1200
|
+
}
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
let envelope;
|
|
1204
|
+
try {
|
|
1205
|
+
envelope = JSON.parse(payload);
|
|
1206
|
+
} catch {
|
|
1207
|
+
printer.writeLine(chalk3.dim(payload));
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const method = typeof envelope.method === "string" ? envelope.method : "";
|
|
1211
|
+
if (method === "session/update") {
|
|
1212
|
+
const params = envelope.params;
|
|
1213
|
+
const update = params?.update;
|
|
1214
|
+
const sessionUpdate = typeof update?.sessionUpdate === "string" ? update.sessionUpdate : "";
|
|
1215
|
+
switch (sessionUpdate) {
|
|
1216
|
+
case "agent_message_chunk": {
|
|
1217
|
+
const content = update?.content;
|
|
1218
|
+
if (content?.type === "text" && content.text) {
|
|
1219
|
+
printer.writeText(content.text);
|
|
1220
|
+
}
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
case "agent_thought_chunk": {
|
|
1224
|
+
const content = update?.content;
|
|
1225
|
+
if (content?.type === "text" && content.text) {
|
|
1226
|
+
printer.writeDim(content.text);
|
|
1227
|
+
}
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
case "tool_call":
|
|
1231
|
+
case "tool_call_update": {
|
|
1232
|
+
const title = typeof update?.title === "string" && update.title ? update.title : "tool";
|
|
1233
|
+
const status = typeof update?.status === "string" && update.status ? update.status : "in_progress";
|
|
1234
|
+
printer.writeLine(chalk3.dim(`[tool:${status}] ${title}`));
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
case "plan": {
|
|
1238
|
+
const entries = Array.isArray(update?.entries) ? update.entries : [];
|
|
1239
|
+
const detail = entries.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => `- [${entry.status ?? "pending"}] ${entry.content ?? ""}`).join("\n");
|
|
1240
|
+
if (detail) {
|
|
1241
|
+
printer.writeLine(chalk3.dim(`Plan
|
|
1242
|
+
${detail}`));
|
|
1243
|
+
}
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
default:
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (method === "session/request_permission") {
|
|
1251
|
+
printer.writeLine(chalk3.yellow("Permission requested by remote agent"));
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
async function resolvePromptSession(computerID, options) {
|
|
1256
|
+
if (options.session?.trim()) {
|
|
1257
|
+
return getAgentSession(computerID, options.session.trim());
|
|
1258
|
+
}
|
|
1259
|
+
if (!options.agent?.trim()) {
|
|
1260
|
+
throw new Error("either --session or --agent is required");
|
|
1261
|
+
}
|
|
1262
|
+
return createAgentSession(computerID, {
|
|
1263
|
+
agent: options.agent.trim(),
|
|
1264
|
+
name: options.name?.trim() || "default",
|
|
1265
|
+
cwd: options.cwd?.trim(),
|
|
1266
|
+
resume: true
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
var agentCommand = new Command3("agent").description("Manage cloud agent sessions");
|
|
1270
|
+
agentCommand.command("agents").description("List available agents on a machine").argument("<machine>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1271
|
+
const spinner = options.json ? null : ora2("Fetching machine agents...").start();
|
|
1272
|
+
try {
|
|
1273
|
+
const computer = await resolveComputer(identifier);
|
|
1274
|
+
const agents = await listComputerAgents(computer.id);
|
|
1275
|
+
spinner?.stop();
|
|
1276
|
+
if (options.json) {
|
|
1277
|
+
console.log(JSON.stringify({ agents }, null, 2));
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
printAgents(agents);
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to fetch agents");
|
|
1283
|
+
if (!spinner) {
|
|
1284
|
+
console.error(error instanceof Error ? error.message : "Failed to fetch agents");
|
|
1285
|
+
}
|
|
1286
|
+
process.exit(1);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
var sessionsCommand = new Command3("sessions").description("Manage agent sessions on one machine");
|
|
1290
|
+
sessionsCommand.command("list").description("List agent sessions for a machine").argument("<machine>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1291
|
+
const spinner = options.json ? null : ora2("Fetching agent sessions...").start();
|
|
1292
|
+
try {
|
|
1293
|
+
const computer = await resolveComputer(identifier);
|
|
1294
|
+
const sessions = await listAgentSessions(computer.id);
|
|
1295
|
+
spinner?.stop();
|
|
1296
|
+
if (options.json) {
|
|
1297
|
+
console.log(JSON.stringify({ sessions }, null, 2));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
printSessions(sessions, /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to fetch sessions");
|
|
1303
|
+
if (!spinner) {
|
|
1304
|
+
console.error(error instanceof Error ? error.message : "Failed to fetch sessions");
|
|
1305
|
+
}
|
|
1306
|
+
process.exit(1);
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
sessionsCommand.command("new").description("Create or resume an agent session on a machine").argument("<machine>", "Computer id or handle").requiredOption("--agent <agent>", "Agent id").option("--name <name>", "Session name", "default").option("--cwd <cwd>", "Session working directory").option("--no-resume", "Always create a new session").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1310
|
+
const spinner = options.json ? null : ora2("Creating agent session...").start();
|
|
1311
|
+
try {
|
|
1312
|
+
const computer = await resolveComputer(identifier);
|
|
1313
|
+
const session = await createAgentSession(computer.id, {
|
|
1314
|
+
agent: options.agent,
|
|
1315
|
+
name: options.name,
|
|
1316
|
+
cwd: options.cwd,
|
|
1317
|
+
resume: options.resume
|
|
1318
|
+
});
|
|
1319
|
+
spinner?.stop();
|
|
1320
|
+
if (options.json) {
|
|
1321
|
+
console.log(JSON.stringify({ session }, null, 2));
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to create session");
|
|
1327
|
+
if (!spinner) {
|
|
1328
|
+
console.error(error instanceof Error ? error.message : "Failed to create session");
|
|
1329
|
+
}
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
agentCommand.addCommand(sessionsCommand);
|
|
1334
|
+
agentCommand.command("prompt").description("Send a prompt to a machine agent session").argument("<machine>", "Computer id or handle").argument("<text>", "Prompt text").option("--session <id>", "Existing session id").option("--agent <agent>", "Agent to use when creating/resuming a session").option("--name <name>", "Session name when creating/resuming", "default").option("--cwd <cwd>", "Session working directory when creating/resuming").option("--no-stream", "Do not stream session events while waiting").option("--json", "Print raw JSON").action(async (identifier, text, options) => {
|
|
1335
|
+
const spinner = options.json ? null : ora2("Preparing agent prompt...").start();
|
|
1336
|
+
const controller = new AbortController();
|
|
1337
|
+
let streamPromise;
|
|
1338
|
+
try {
|
|
1339
|
+
const computer = await resolveComputer(identifier);
|
|
1340
|
+
const session = await resolvePromptSession(computer.id, options);
|
|
1341
|
+
spinner?.stop();
|
|
1342
|
+
const canStreamImmediately = !options.noStream && Boolean(session.backend_session_id?.trim());
|
|
1343
|
+
if (canStreamImmediately) {
|
|
1344
|
+
streamPromise = streamSessionEvents(computer.id, session.id, {
|
|
1345
|
+
json: options.json,
|
|
1346
|
+
signal: controller.signal
|
|
1347
|
+
}).catch((error) => {
|
|
1348
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
return;
|
|
1352
|
+
});
|
|
1353
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1354
|
+
}
|
|
1355
|
+
const promptResponse = await promptAgentSession(computer.id, session.id, {
|
|
1356
|
+
prompt: [{ type: "text", text }]
|
|
1357
|
+
});
|
|
1358
|
+
if (!options.noStream && !canStreamImmediately) {
|
|
1359
|
+
streamPromise = streamSessionEvents(computer.id, session.id, {
|
|
1360
|
+
json: options.json,
|
|
1361
|
+
signal: controller.signal
|
|
1362
|
+
}).catch((error) => {
|
|
1363
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
return;
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
const settled = await waitForSessionToSettle(computer.id, session.id, promptResponse.prompt_id);
|
|
1370
|
+
controller.abort();
|
|
1371
|
+
if (streamPromise) {
|
|
1372
|
+
await streamPromise;
|
|
1373
|
+
}
|
|
1374
|
+
if (options.json) {
|
|
1375
|
+
console.log(JSON.stringify({ prompt: promptResponse, session: settled }, null, 2));
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
console.log();
|
|
1379
|
+
console.log(` ${chalk3.bold(session.name || "default")} ${formatAgentSessionStatus(settled.status)}`);
|
|
1380
|
+
if (settled.last_stop_reason) {
|
|
1381
|
+
console.log(chalk3.dim(` stop_reason=${settled.last_stop_reason}`));
|
|
1382
|
+
}
|
|
1383
|
+
if (settled.last_error) {
|
|
1384
|
+
console.log(chalk3.red(` error=${settled.last_error}`));
|
|
1385
|
+
}
|
|
1386
|
+
console.log();
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
controller.abort();
|
|
1389
|
+
if (spinner) {
|
|
1390
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to prompt agent session");
|
|
1391
|
+
} else {
|
|
1392
|
+
console.error(error instanceof Error ? error.message : "Failed to prompt agent session");
|
|
1393
|
+
}
|
|
1394
|
+
process.exit(1);
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
agentCommand.command("watch").description("Watch a machine agent session event stream").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--last-event-id <id>", "Replay buffered events after this event id").option("--json", "Print raw event envelopes").action(async (identifier, options) => {
|
|
1398
|
+
try {
|
|
1399
|
+
const computer = await resolveComputer(identifier);
|
|
1400
|
+
if (options.lastEventId && !/^[1-9]\d*$/.test(options.lastEventId.trim())) {
|
|
1401
|
+
throw new Error("--last-event-id must be a positive integer");
|
|
1402
|
+
}
|
|
1403
|
+
await streamSessionEvents(computer.id, options.session.trim(), {
|
|
1404
|
+
lastEventId: options.lastEventId,
|
|
1405
|
+
json: options.json
|
|
1406
|
+
});
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
console.error(error instanceof Error ? error.message : "Failed to watch session");
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
agentCommand.command("status").description("Show one session or list sessions on a machine").argument("<machine>", "Computer id or handle").option("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1413
|
+
const spinner = options.json ? null : ora2("Fetching session status...").start();
|
|
1414
|
+
try {
|
|
1415
|
+
const computer = await resolveComputer(identifier);
|
|
1416
|
+
if (options.session) {
|
|
1417
|
+
const session = await getAgentSession(computer.id, options.session.trim());
|
|
1418
|
+
spinner?.stop();
|
|
1419
|
+
if (options.json) {
|
|
1420
|
+
console.log(JSON.stringify({ session }, null, 2));
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const sessions = await listAgentSessions(computer.id);
|
|
1427
|
+
spinner?.stop();
|
|
1428
|
+
if (options.json) {
|
|
1429
|
+
console.log(JSON.stringify({ sessions }, null, 2));
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
printSessions(sessions, /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to fetch session status");
|
|
1435
|
+
if (!spinner) {
|
|
1436
|
+
console.error(error instanceof Error ? error.message : "Failed to fetch session status");
|
|
1437
|
+
}
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
agentCommand.command("cancel").description("Send session/cancel to a machine agent session").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1442
|
+
const spinner = options.json ? null : ora2("Cancelling agent session...").start();
|
|
1443
|
+
try {
|
|
1444
|
+
const computer = await resolveComputer(identifier);
|
|
1445
|
+
const session = await cancelAgentSession(computer.id, options.session.trim());
|
|
1446
|
+
spinner?.stop();
|
|
1447
|
+
if (options.json) {
|
|
1448
|
+
console.log(JSON.stringify({ session }, null, 2));
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to cancel session");
|
|
1454
|
+
if (!spinner) {
|
|
1455
|
+
console.error(error instanceof Error ? error.message : "Failed to cancel session");
|
|
1456
|
+
}
|
|
1457
|
+
process.exit(1);
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
agentCommand.command("interrupt").description("Force-stop a machine agent session runtime").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1461
|
+
const spinner = options.json ? null : ora2("Interrupting agent session...").start();
|
|
1462
|
+
try {
|
|
1463
|
+
const computer = await resolveComputer(identifier);
|
|
1464
|
+
const session = await interruptAgentSession(computer.id, options.session.trim());
|
|
1465
|
+
spinner?.stop();
|
|
1466
|
+
if (options.json) {
|
|
1467
|
+
console.log(JSON.stringify({ session }, null, 2));
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
printSessions([session], /* @__PURE__ */ new Map([[computer.id, computer.handle]]));
|
|
1471
|
+
} catch (error) {
|
|
1472
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to interrupt session");
|
|
1473
|
+
if (!spinner) {
|
|
1474
|
+
console.error(error instanceof Error ? error.message : "Failed to interrupt session");
|
|
1475
|
+
}
|
|
1476
|
+
process.exit(1);
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
agentCommand.command("close").description("Close and delete a machine agent session").argument("<machine>", "Computer id or handle").requiredOption("--session <id>", "Session id").action(async (identifier, options) => {
|
|
1480
|
+
const spinner = ora2("Closing agent session...").start();
|
|
1481
|
+
try {
|
|
1482
|
+
const computer = await resolveComputer(identifier);
|
|
1483
|
+
await deleteAgentSession(computer.id, options.session.trim());
|
|
1484
|
+
spinner.succeed("Agent session closed");
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
spinner.fail(error instanceof Error ? error.message : "Failed to close session");
|
|
1487
|
+
process.exit(1);
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
var fleetCommand = new Command3("fleet").description("View agent activity across your fleet");
|
|
1491
|
+
fleetCommand.command("status").description("List open agent sessions across all machines").option("--json", "Print raw JSON").action(async (options) => {
|
|
1492
|
+
const spinner = options.json ? null : ora2("Fetching fleet status...").start();
|
|
1493
|
+
try {
|
|
1494
|
+
const [sessions, computers] = await Promise.all([
|
|
1495
|
+
listFleetAgentSessions(),
|
|
1496
|
+
listComputers()
|
|
1497
|
+
]);
|
|
1498
|
+
spinner?.stop();
|
|
1499
|
+
if (options.json) {
|
|
1500
|
+
console.log(JSON.stringify({ sessions }, null, 2));
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const handleByComputerID = new Map(computers.map((computer) => [computer.id, computer.handle]));
|
|
1504
|
+
printSessions(sessions, handleByComputerID);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
spinner?.fail(error instanceof Error ? error.message : "Failed to fetch fleet status");
|
|
1507
|
+
if (!spinner) {
|
|
1508
|
+
console.error(error instanceof Error ? error.message : "Failed to fetch fleet status");
|
|
1509
|
+
}
|
|
1510
|
+
process.exit(1);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
// src/commands/computers.ts
|
|
1515
|
+
import { Command as Command4 } from "commander";
|
|
1516
|
+
import chalk4 from "chalk";
|
|
1517
|
+
import ora3 from "ora";
|
|
585
1518
|
import { select as select2, input as textInput, confirm } from "@inquirer/prompts";
|
|
586
1519
|
function isInternalCondition(value) {
|
|
587
1520
|
return /^[A-Z][a-zA-Z]+$/.test(value);
|
|
@@ -589,53 +1522,62 @@ function isInternalCondition(value) {
|
|
|
589
1522
|
function printComputer(computer) {
|
|
590
1523
|
const vnc = vncURL(computer);
|
|
591
1524
|
const terminal = terminalURL(computer);
|
|
592
|
-
const ssh = computer.ssh_enabled ?
|
|
1525
|
+
const ssh = computer.ssh_enabled ? formatSSHCommand2(computer.handle, computer.ssh_host, computer.ssh_port) : "disabled";
|
|
593
1526
|
const isCustom = computer.runtime_family === "custom-machine";
|
|
594
1527
|
console.log();
|
|
595
|
-
console.log(` ${
|
|
1528
|
+
console.log(` ${chalk4.bold.white(computer.handle)} ${formatStatus(computer.status)}`);
|
|
596
1529
|
console.log();
|
|
597
|
-
console.log(` ${
|
|
598
|
-
console.log(` ${
|
|
1530
|
+
console.log(` ${chalk4.dim("ID")} ${computer.id}`);
|
|
1531
|
+
console.log(` ${chalk4.dim("Tier")} ${computer.tier}`);
|
|
599
1532
|
if (isCustom) {
|
|
600
|
-
console.log(` ${
|
|
601
|
-
console.log(` ${
|
|
602
|
-
console.log(` ${
|
|
1533
|
+
console.log(` ${chalk4.dim("Runtime")} ${computer.runtime_family}`);
|
|
1534
|
+
console.log(` ${chalk4.dim("Source")} ${computer.source_kind}`);
|
|
1535
|
+
console.log(` ${chalk4.dim("Image")} ${computer.image_family}`);
|
|
603
1536
|
}
|
|
604
|
-
console.log(` ${
|
|
605
|
-
console.log(` ${
|
|
1537
|
+
console.log(` ${chalk4.dim("Primary")} :${computer.primary_port}${computer.primary_path}`);
|
|
1538
|
+
console.log(` ${chalk4.dim("Health")} ${computer.healthcheck_type}${computer.healthcheck_value ? ` (${computer.healthcheck_value})` : ""}`);
|
|
606
1539
|
console.log();
|
|
607
|
-
console.log(` ${
|
|
608
|
-
console.log(` ${
|
|
609
|
-
console.log(` ${
|
|
610
|
-
console.log(` ${
|
|
1540
|
+
console.log(` ${chalk4.dim("Gateway")} ${chalk4.cyan(webURL(computer))}`);
|
|
1541
|
+
console.log(` ${chalk4.dim("VNC")} ${vnc ? chalk4.cyan(vnc) : chalk4.dim("not available")}`);
|
|
1542
|
+
console.log(` ${chalk4.dim("Terminal")} ${terminal ? chalk4.cyan(terminal) : chalk4.dim("not available")}`);
|
|
1543
|
+
console.log(` ${chalk4.dim("SSH")} ${computer.ssh_enabled ? chalk4.white(ssh) : chalk4.dim(ssh)}`);
|
|
611
1544
|
if (computer.last_error) {
|
|
612
1545
|
console.log();
|
|
613
1546
|
if (isInternalCondition(computer.last_error)) {
|
|
614
|
-
console.log(` ${
|
|
1547
|
+
console.log(` ${chalk4.dim("Condition")} ${chalk4.dim(computer.last_error)}`);
|
|
615
1548
|
} else {
|
|
616
|
-
console.log(` ${
|
|
1549
|
+
console.log(` ${chalk4.dim("Error")} ${chalk4.red(computer.last_error)}`);
|
|
617
1550
|
}
|
|
618
1551
|
}
|
|
619
1552
|
console.log();
|
|
620
|
-
console.log(` ${
|
|
1553
|
+
console.log(` ${chalk4.dim("Created")} ${timeAgo(computer.created_at)}`);
|
|
621
1554
|
console.log();
|
|
622
1555
|
}
|
|
1556
|
+
function formatSSHCommand2(user, host, port) {
|
|
1557
|
+
if (!user.trim() || !host.trim()) {
|
|
1558
|
+
return "ssh unavailable";
|
|
1559
|
+
}
|
|
1560
|
+
if (port <= 0 || port === 22) {
|
|
1561
|
+
return `ssh ${user}@${host}`;
|
|
1562
|
+
}
|
|
1563
|
+
return `ssh -p ${port} ${user}@${host}`;
|
|
1564
|
+
}
|
|
623
1565
|
function printComputerTable(computers) {
|
|
624
1566
|
const handleWidth = Math.max(6, ...computers.map((c) => c.handle.length));
|
|
625
1567
|
const statusWidth = 12;
|
|
626
1568
|
const createdWidth = 10;
|
|
627
1569
|
console.log();
|
|
628
1570
|
console.log(
|
|
629
|
-
` ${
|
|
1571
|
+
` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim(padEnd("Created", createdWidth + 2))}${chalk4.dim("URL")}`
|
|
630
1572
|
);
|
|
631
1573
|
console.log(
|
|
632
|
-
` ${
|
|
1574
|
+
` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(createdWidth + 2))}${chalk4.dim("-".repeat(20))}`
|
|
633
1575
|
);
|
|
634
1576
|
for (const computer of computers) {
|
|
635
1577
|
const status = formatStatus(computer.status);
|
|
636
|
-
const created =
|
|
1578
|
+
const created = chalk4.dim(timeAgo(computer.created_at));
|
|
637
1579
|
console.log(
|
|
638
|
-
` ${
|
|
1580
|
+
` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${padEnd(created, createdWidth + 2)}${chalk4.cyan(webURL(computer))}`
|
|
639
1581
|
);
|
|
640
1582
|
}
|
|
641
1583
|
console.log();
|
|
@@ -645,29 +1587,29 @@ function printComputerTableVerbose(computers) {
|
|
|
645
1587
|
const statusWidth = 10;
|
|
646
1588
|
console.log();
|
|
647
1589
|
console.log(
|
|
648
|
-
` ${
|
|
1590
|
+
` ${chalk4.dim(padEnd("Handle", handleWidth + 2))}${chalk4.dim(padEnd("Status", statusWidth + 2))}${chalk4.dim("URLs")}`
|
|
649
1591
|
);
|
|
650
1592
|
console.log(
|
|
651
|
-
` ${
|
|
1593
|
+
` ${chalk4.dim("-".repeat(handleWidth + 2))}${chalk4.dim("-".repeat(statusWidth + 2))}${chalk4.dim("-".repeat(20))}`
|
|
652
1594
|
);
|
|
653
1595
|
for (const computer of computers) {
|
|
654
1596
|
const status = formatStatus(computer.status);
|
|
655
1597
|
const vnc = vncURL(computer);
|
|
656
1598
|
const terminal = terminalURL(computer);
|
|
657
1599
|
console.log(
|
|
658
|
-
` ${
|
|
1600
|
+
` ${chalk4.white(padEnd(computer.handle, handleWidth + 2))}${padEnd(status, statusWidth + 2)}${chalk4.cyan(webURL(computer))}`
|
|
659
1601
|
);
|
|
660
1602
|
console.log(
|
|
661
|
-
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${
|
|
1603
|
+
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(vnc ?? "VNC not available")}`
|
|
662
1604
|
);
|
|
663
1605
|
console.log(
|
|
664
|
-
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${
|
|
1606
|
+
` ${padEnd("", handleWidth + 2)}${padEnd("", statusWidth + 2)}${chalk4.dim(terminal ?? "Terminal not available")}`
|
|
665
1607
|
);
|
|
666
1608
|
}
|
|
667
1609
|
console.log();
|
|
668
1610
|
}
|
|
669
|
-
var lsCommand = new
|
|
670
|
-
const spinner = options.json ? null :
|
|
1611
|
+
var lsCommand = new Command4("ls").description("List computers").option("--json", "Print raw JSON").option("-v, --verbose", "Show all URLs for each computer").action(async (options) => {
|
|
1612
|
+
const spinner = options.json ? null : ora3("Fetching computers...").start();
|
|
671
1613
|
try {
|
|
672
1614
|
const computers = await listComputers();
|
|
673
1615
|
spinner?.stop();
|
|
@@ -677,7 +1619,7 @@ var lsCommand = new Command2("ls").description("List computers").option("--json"
|
|
|
677
1619
|
}
|
|
678
1620
|
if (computers.length === 0) {
|
|
679
1621
|
console.log();
|
|
680
|
-
console.log(
|
|
1622
|
+
console.log(chalk4.dim(" No computers found."));
|
|
681
1623
|
console.log();
|
|
682
1624
|
return;
|
|
683
1625
|
}
|
|
@@ -697,8 +1639,8 @@ var lsCommand = new Command2("ls").description("List computers").option("--json"
|
|
|
697
1639
|
process.exit(1);
|
|
698
1640
|
}
|
|
699
1641
|
});
|
|
700
|
-
var getCommand = new
|
|
701
|
-
const spinner = options.json ? null :
|
|
1642
|
+
var getCommand = new Command4("get").description("Show computer details").argument("<id-or-handle>", "Computer id or handle").option("--json", "Print raw JSON").action(async (identifier, options) => {
|
|
1643
|
+
const spinner = options.json ? null : ora3("Fetching computer...").start();
|
|
702
1644
|
try {
|
|
703
1645
|
const computer = await resolveComputer(identifier);
|
|
704
1646
|
spinner?.stop();
|
|
@@ -718,25 +1660,31 @@ var getCommand = new Command2("get").description("Show computer details").argume
|
|
|
718
1660
|
process.exit(1);
|
|
719
1661
|
}
|
|
720
1662
|
});
|
|
721
|
-
var createCommand = new
|
|
1663
|
+
var createCommand = new Command4("create").description("Create a computer").argument("[handle]", "Optional computer handle").option("--name <display-name>", "Display name").option("--tier <tier>", "Tier override").option("--interactive", "Prompt for runtime choices").option("--runtime-family <runtime-family>", "managed-worker or custom-machine").option("--source-kind <source-kind>", "none or oci-image").option("--image-family <family>", "Image family override").option("--image-ref <image>", "Resolved image override").option("--primary-port <port>", "Primary app port").option("--primary-path <path>", "Primary app path").option("--healthcheck-type <type>", "http or tcp").option("--healthcheck-value <value>", "Health check path or port").option("--ssh-enabled", "Enable SSH access").option("--ssh-disabled", "Disable SSH access").option("--vnc-enabled", "Enable VNC access").option("--vnc-disabled", "Disable VNC access").action(async (handle, options) => {
|
|
722
1664
|
let spinner;
|
|
723
1665
|
let timer;
|
|
724
1666
|
let startTime = 0;
|
|
725
1667
|
try {
|
|
726
1668
|
const selectedOptions = await resolveCreateOptions(options);
|
|
727
|
-
|
|
1669
|
+
const runtimeFamily = effectiveRuntimeFamily(selectedOptions.runtimeFamily);
|
|
1670
|
+
const filesystemSettings = await loadFilesystemSettingsForCreate(runtimeFamily);
|
|
1671
|
+
const provisioningNote = createProvisioningNote(runtimeFamily, filesystemSettings);
|
|
1672
|
+
if (provisioningNote) {
|
|
1673
|
+
console.log(chalk4.dim(provisioningNote));
|
|
1674
|
+
}
|
|
1675
|
+
spinner = ora3(createSpinnerText(runtimeFamily, filesystemSettings, 0)).start();
|
|
728
1676
|
startTime = Date.now();
|
|
729
1677
|
timer = setInterval(() => {
|
|
730
|
-
const elapsed2 = (
|
|
1678
|
+
const elapsed2 = (Date.now() - startTime) / 1e3;
|
|
731
1679
|
if (spinner) {
|
|
732
|
-
spinner.text =
|
|
1680
|
+
spinner.text = createSpinnerText(runtimeFamily, filesystemSettings, elapsed2);
|
|
733
1681
|
}
|
|
734
1682
|
}, 100);
|
|
735
1683
|
const computer = await createComputer({
|
|
736
1684
|
handle,
|
|
737
1685
|
display_name: selectedOptions.name,
|
|
738
1686
|
tier: selectedOptions.tier,
|
|
739
|
-
runtime_family:
|
|
1687
|
+
runtime_family: runtimeFamily,
|
|
740
1688
|
source_kind: parseSourceKindOption(selectedOptions.sourceKind),
|
|
741
1689
|
image_family: selectedOptions.imageFamily,
|
|
742
1690
|
image_ref: selectedOptions.imageRef,
|
|
@@ -760,7 +1708,7 @@ var createCommand = new Command2("create").description("Create a computer").argu
|
|
|
760
1708
|
}
|
|
761
1709
|
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
762
1710
|
spinner.succeed(
|
|
763
|
-
|
|
1711
|
+
chalk4.green(`Created ${chalk4.bold(computer.handle)} ${chalk4.dim(`[${elapsed}s]`)}`)
|
|
764
1712
|
);
|
|
765
1713
|
printComputer(computer);
|
|
766
1714
|
} catch (error) {
|
|
@@ -769,10 +1717,10 @@ var createCommand = new Command2("create").description("Create a computer").argu
|
|
|
769
1717
|
}
|
|
770
1718
|
const message = error instanceof Error ? error.message : "Failed to create computer";
|
|
771
1719
|
if (spinner) {
|
|
772
|
-
const suffix = startTime ? ` ${
|
|
1720
|
+
const suffix = startTime ? ` ${chalk4.dim(`[${((Date.now() - startTime) / 1e3).toFixed(1)}s]`)}` : "";
|
|
773
1721
|
spinner.fail(`${message}${suffix}`);
|
|
774
1722
|
} else {
|
|
775
|
-
console.error(
|
|
1723
|
+
console.error(chalk4.red(message));
|
|
776
1724
|
}
|
|
777
1725
|
process.exit(1);
|
|
778
1726
|
}
|
|
@@ -827,28 +1775,28 @@ async function resolveCreateOptions(options) {
|
|
|
827
1775
|
validateCreateOptions(selectedOptions);
|
|
828
1776
|
return selectedOptions;
|
|
829
1777
|
}
|
|
830
|
-
var removeCommand = new
|
|
1778
|
+
var removeCommand = new Command4("rm").description("Delete a computer").argument("<id-or-handle>", "Computer id or handle").option("-y, --yes", "Skip confirmation prompt").action(async (identifier, options, cmd) => {
|
|
831
1779
|
const globalYes = cmd.parent?.opts()?.yes;
|
|
832
1780
|
const skipConfirm = Boolean(options.yes || globalYes);
|
|
833
|
-
const spinner =
|
|
1781
|
+
const spinner = ora3("Resolving computer...").start();
|
|
834
1782
|
try {
|
|
835
1783
|
const computer = await resolveComputer(identifier);
|
|
836
1784
|
spinner.stop();
|
|
837
1785
|
if (!skipConfirm && process.stdin.isTTY) {
|
|
838
1786
|
const confirmed = await confirm({
|
|
839
|
-
message: `Delete computer ${
|
|
1787
|
+
message: `Delete computer ${chalk4.bold(computer.handle)}?`,
|
|
840
1788
|
default: false
|
|
841
1789
|
});
|
|
842
1790
|
if (!confirmed) {
|
|
843
|
-
console.log(
|
|
1791
|
+
console.log(chalk4.dim(" Cancelled."));
|
|
844
1792
|
return;
|
|
845
1793
|
}
|
|
846
1794
|
}
|
|
847
|
-
const deleteSpinner =
|
|
1795
|
+
const deleteSpinner = ora3("Deleting computer...").start();
|
|
848
1796
|
await api(`/v1/computers/${computer.id}`, {
|
|
849
1797
|
method: "DELETE"
|
|
850
1798
|
});
|
|
851
|
-
deleteSpinner.succeed(
|
|
1799
|
+
deleteSpinner.succeed(chalk4.green(`Deleted ${chalk4.bold(computer.handle)}`));
|
|
852
1800
|
} catch (error) {
|
|
853
1801
|
spinner.fail(
|
|
854
1802
|
error instanceof Error ? error.message : "Failed to delete computer"
|
|
@@ -866,6 +1814,35 @@ function parseRuntimeFamilyOption(value) {
|
|
|
866
1814
|
throw new Error("--runtime-family must be managed-worker or custom-machine");
|
|
867
1815
|
}
|
|
868
1816
|
}
|
|
1817
|
+
function effectiveRuntimeFamily(value) {
|
|
1818
|
+
return parseRuntimeFamilyOption(value) ?? "managed-worker";
|
|
1819
|
+
}
|
|
1820
|
+
async function loadFilesystemSettingsForCreate(runtimeFamily) {
|
|
1821
|
+
if (runtimeFamily !== "managed-worker") {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
try {
|
|
1825
|
+
return await getFilesystemSettings();
|
|
1826
|
+
} catch {
|
|
1827
|
+
return null;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
function createProvisioningNote(runtimeFamily, filesystemSettings) {
|
|
1831
|
+
if (runtimeFamily !== "managed-worker") {
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
if (filesystemSettings?.shared_enabled) {
|
|
1835
|
+
return "Shared home is enabled. Mounting /home/node from EFS can take around 20s.";
|
|
1836
|
+
}
|
|
1837
|
+
return "Using isolated storage for this desktop.";
|
|
1838
|
+
}
|
|
1839
|
+
function createSpinnerText(runtimeFamily, filesystemSettings, elapsedSeconds) {
|
|
1840
|
+
const elapsedLabel = chalk4.dim(`${elapsedSeconds.toFixed(1)}s`);
|
|
1841
|
+
if (runtimeFamily === "managed-worker" && filesystemSettings?.shared_enabled && elapsedSeconds >= 5) {
|
|
1842
|
+
return `Creating computer... ${elapsedLabel} ${chalk4.dim("mounting shared home")}`;
|
|
1843
|
+
}
|
|
1844
|
+
return `Creating computer... ${elapsedLabel}`;
|
|
1845
|
+
}
|
|
869
1846
|
function validateCreateOptions(options) {
|
|
870
1847
|
const runtimeFamily = parseRuntimeFamilyOption(options.runtimeFamily);
|
|
871
1848
|
const sourceKind = parseSourceKindOption(options.sourceKind);
|
|
@@ -923,7 +1900,7 @@ function resolveOptionalToggle(enabled, disabled, label) {
|
|
|
923
1900
|
}
|
|
924
1901
|
|
|
925
1902
|
// src/commands/completion.ts
|
|
926
|
-
import { Command as
|
|
1903
|
+
import { Command as Command5 } from "commander";
|
|
927
1904
|
var ZSH_SCRIPT = `#compdef computer agentcomputer aicomputer
|
|
928
1905
|
|
|
929
1906
|
_computer() {
|
|
@@ -938,6 +1915,9 @@ _computer() {
|
|
|
938
1915
|
'open:Open in browser'
|
|
939
1916
|
'ssh:SSH into a computer'
|
|
940
1917
|
'ports:Manage published ports'
|
|
1918
|
+
'agent:Manage cloud agent sessions'
|
|
1919
|
+
'fleet:View agent activity across your fleet'
|
|
1920
|
+
'acp:Run a local ACP bridge for remote agent sessions'
|
|
941
1921
|
'rm:Delete a computer'
|
|
942
1922
|
'completion:Generate shell completions'
|
|
943
1923
|
'help:Display help'
|
|
@@ -1064,7 +2044,7 @@ var BASH_SCRIPT = `_computer() {
|
|
|
1064
2044
|
local cur prev words cword
|
|
1065
2045
|
_init_completion || return
|
|
1066
2046
|
|
|
1067
|
-
local commands="login logout whoami create ls get open ssh ports rm completion help"
|
|
2047
|
+
local commands="login logout whoami create ls get open ssh ports agent fleet acp rm completion help"
|
|
1068
2048
|
local ports_commands="ls publish rm"
|
|
1069
2049
|
|
|
1070
2050
|
if [[ $cword -eq 1 ]]; then
|
|
@@ -1118,7 +2098,7 @@ var BASH_SCRIPT = `_computer() {
|
|
|
1118
2098
|
}
|
|
1119
2099
|
|
|
1120
2100
|
complete -F _computer computer agentcomputer aicomputer`;
|
|
1121
|
-
var completionCommand = new
|
|
2101
|
+
var completionCommand = new Command5("completion").description("Generate shell completions").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
|
|
1122
2102
|
switch (shell) {
|
|
1123
2103
|
case "zsh":
|
|
1124
2104
|
console.log(ZSH_SCRIPT);
|
|
@@ -1133,9 +2113,9 @@ var completionCommand = new Command3("completion").description("Generate shell c
|
|
|
1133
2113
|
});
|
|
1134
2114
|
|
|
1135
2115
|
// src/commands/login.ts
|
|
1136
|
-
import { Command as
|
|
1137
|
-
import
|
|
1138
|
-
import
|
|
2116
|
+
import { Command as Command6 } from "commander";
|
|
2117
|
+
import chalk5 from "chalk";
|
|
2118
|
+
import ora4 from "ora";
|
|
1139
2119
|
|
|
1140
2120
|
// src/lib/browser-login.ts
|
|
1141
2121
|
import { randomBytes } from "crypto";
|
|
@@ -1363,11 +2343,11 @@ function escapeHTML(value) {
|
|
|
1363
2343
|
}
|
|
1364
2344
|
|
|
1365
2345
|
// src/commands/login.ts
|
|
1366
|
-
var loginCommand = new
|
|
2346
|
+
var loginCommand = new Command6("login").description("Authenticate the CLI").option("--api-key <key>", "API key starting with ac_live_").option("--stdin", "Read the API key from stdin").option("-f, --force", "Overwrite an existing stored API key").action(async (options) => {
|
|
1367
2347
|
const existingKey = getStoredAPIKey();
|
|
1368
2348
|
if (existingKey && !options.force) {
|
|
1369
2349
|
console.log();
|
|
1370
|
-
console.log(
|
|
2350
|
+
console.log(chalk5.yellow(" Already logged in. Use --force to overwrite."));
|
|
1371
2351
|
console.log();
|
|
1372
2352
|
return;
|
|
1373
2353
|
}
|
|
@@ -1375,8 +2355,8 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
|
|
|
1375
2355
|
const apiKey = await resolveAPIKeyInput(options.apiKey, options.stdin);
|
|
1376
2356
|
if (!apiKey && wantsManualLogin) {
|
|
1377
2357
|
console.log();
|
|
1378
|
-
console.log(
|
|
1379
|
-
console.log(
|
|
2358
|
+
console.log(chalk5.dim(" Usage: computer login --api-key <ac_live_...>"));
|
|
2359
|
+
console.log(chalk5.dim(` API: ${getBaseURL()}`));
|
|
1380
2360
|
console.log();
|
|
1381
2361
|
process.exit(1);
|
|
1382
2362
|
}
|
|
@@ -1386,15 +2366,15 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
|
|
|
1386
2366
|
}
|
|
1387
2367
|
if (!apiKey.startsWith("ac_live_")) {
|
|
1388
2368
|
console.log();
|
|
1389
|
-
console.log(
|
|
2369
|
+
console.log(chalk5.red(" API key must start with ac_live_"));
|
|
1390
2370
|
console.log();
|
|
1391
2371
|
process.exit(1);
|
|
1392
2372
|
}
|
|
1393
|
-
const spinner =
|
|
2373
|
+
const spinner = ora4("Authenticating...").start();
|
|
1394
2374
|
try {
|
|
1395
2375
|
const me = await apiWithKey(apiKey, "/v1/me");
|
|
1396
2376
|
setAPIKey(apiKey);
|
|
1397
|
-
spinner.succeed(`Logged in as ${
|
|
2377
|
+
spinner.succeed(`Logged in as ${chalk5.bold(me.user.email)}`);
|
|
1398
2378
|
} catch (error) {
|
|
1399
2379
|
spinner.fail(
|
|
1400
2380
|
error instanceof Error ? error.message : "Failed to validate API key"
|
|
@@ -1403,7 +2383,7 @@ var loginCommand = new Command4("login").description("Authenticate the CLI").opt
|
|
|
1403
2383
|
}
|
|
1404
2384
|
});
|
|
1405
2385
|
async function runBrowserLogin() {
|
|
1406
|
-
const spinner =
|
|
2386
|
+
const spinner = ora4("Starting browser login...").start();
|
|
1407
2387
|
let attempt = null;
|
|
1408
2388
|
try {
|
|
1409
2389
|
attempt = await createBrowserLoginAttempt();
|
|
@@ -1413,14 +2393,14 @@ async function runBrowserLogin() {
|
|
|
1413
2393
|
} catch {
|
|
1414
2394
|
spinner.stop();
|
|
1415
2395
|
console.log();
|
|
1416
|
-
console.log(
|
|
1417
|
-
console.log(
|
|
2396
|
+
console.log(chalk5.yellow(" Browser auto-open failed. Open this URL to continue:"));
|
|
2397
|
+
console.log(chalk5.dim(` ${attempt.loginURL}`));
|
|
1418
2398
|
console.log();
|
|
1419
2399
|
spinner.start("Waiting for browser login...");
|
|
1420
2400
|
}
|
|
1421
2401
|
spinner.text = "Waiting for browser login...";
|
|
1422
2402
|
const result = await attempt.waitForResult();
|
|
1423
|
-
spinner.succeed(`Logged in as ${
|
|
2403
|
+
spinner.succeed(`Logged in as ${chalk5.bold(result.me.user.email)}`);
|
|
1424
2404
|
} catch (error) {
|
|
1425
2405
|
spinner.fail(error instanceof Error ? error.message : "Browser login failed");
|
|
1426
2406
|
process.exit(1);
|
|
@@ -1446,33 +2426,33 @@ async function resolveAPIKeyInput(flagValue, readFromStdin) {
|
|
|
1446
2426
|
}
|
|
1447
2427
|
|
|
1448
2428
|
// src/commands/logout.ts
|
|
1449
|
-
import { Command as
|
|
1450
|
-
import
|
|
1451
|
-
var logoutCommand = new
|
|
2429
|
+
import { Command as Command7 } from "commander";
|
|
2430
|
+
import chalk6 from "chalk";
|
|
2431
|
+
var logoutCommand = new Command7("logout").description("Remove stored API key").action(() => {
|
|
1452
2432
|
if (!getStoredAPIKey()) {
|
|
1453
2433
|
console.log();
|
|
1454
|
-
console.log(
|
|
2434
|
+
console.log(chalk6.dim(" Not logged in."));
|
|
1455
2435
|
if (hasEnvAPIKey()) {
|
|
1456
|
-
console.log(
|
|
2436
|
+
console.log(chalk6.dim(" Environment API key is still active in this shell."));
|
|
1457
2437
|
}
|
|
1458
2438
|
console.log();
|
|
1459
2439
|
return;
|
|
1460
2440
|
}
|
|
1461
2441
|
clearAPIKey();
|
|
1462
2442
|
console.log();
|
|
1463
|
-
console.log(
|
|
2443
|
+
console.log(chalk6.green(" Logged out."));
|
|
1464
2444
|
if (hasEnvAPIKey()) {
|
|
1465
|
-
console.log(
|
|
2445
|
+
console.log(chalk6.dim(" Environment API key is still active in this shell."));
|
|
1466
2446
|
}
|
|
1467
2447
|
console.log();
|
|
1468
2448
|
});
|
|
1469
2449
|
|
|
1470
2450
|
// src/commands/whoami.ts
|
|
1471
|
-
import { Command as
|
|
1472
|
-
import
|
|
1473
|
-
import
|
|
1474
|
-
var whoamiCommand = new
|
|
1475
|
-
const spinner = options.json ? null :
|
|
2451
|
+
import { Command as Command8 } from "commander";
|
|
2452
|
+
import chalk7 from "chalk";
|
|
2453
|
+
import ora5 from "ora";
|
|
2454
|
+
var whoamiCommand = new Command8("whoami").description("Show current user").option("--json", "Print raw JSON").action(async (options) => {
|
|
2455
|
+
const spinner = options.json ? null : ora5("Loading user...").start();
|
|
1476
2456
|
try {
|
|
1477
2457
|
const me = await api("/v1/me");
|
|
1478
2458
|
spinner?.stop();
|
|
@@ -1481,14 +2461,14 @@ var whoamiCommand = new Command6("whoami").description("Show current user").opti
|
|
|
1481
2461
|
return;
|
|
1482
2462
|
}
|
|
1483
2463
|
console.log();
|
|
1484
|
-
console.log(` ${
|
|
2464
|
+
console.log(` ${chalk7.bold.white(me.user.display_name || me.user.email)}`);
|
|
1485
2465
|
if (me.user.display_name) {
|
|
1486
|
-
console.log(` ${
|
|
2466
|
+
console.log(` ${chalk7.dim(me.user.email)}`);
|
|
1487
2467
|
}
|
|
1488
2468
|
if (me.api_key.name) {
|
|
1489
|
-
console.log(` ${
|
|
2469
|
+
console.log(` ${chalk7.dim("Key:")} ${me.api_key.name}`);
|
|
1490
2470
|
}
|
|
1491
|
-
console.log(` ${
|
|
2471
|
+
console.log(` ${chalk7.dim("API:")} ${chalk7.dim(getBaseURL())}`);
|
|
1492
2472
|
console.log();
|
|
1493
2473
|
} catch (error) {
|
|
1494
2474
|
if (spinner) {
|
|
@@ -1501,70 +2481,146 @@ var whoamiCommand = new Command6("whoami").description("Show current user").opti
|
|
|
1501
2481
|
});
|
|
1502
2482
|
|
|
1503
2483
|
// src/index.ts
|
|
1504
|
-
var
|
|
1505
|
-
|
|
2484
|
+
var pkg2 = JSON.parse(
|
|
2485
|
+
readFileSync3(new URL("../package.json", import.meta.url), "utf8")
|
|
2486
|
+
);
|
|
1506
2487
|
var cliName = process.argv[1] ? basename2(process.argv[1]) : "agentcomputer";
|
|
1507
|
-
var program = new
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2488
|
+
var program = new Command9();
|
|
2489
|
+
function appendTextSection(lines, title, values) {
|
|
2490
|
+
if (values.length === 0) {
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
lines.push(` ${chalk8.dim(title)}`);
|
|
2494
|
+
lines.push("");
|
|
2495
|
+
for (const value of values) {
|
|
2496
|
+
lines.push(` ${chalk8.white(value)}`);
|
|
2497
|
+
}
|
|
2498
|
+
lines.push("");
|
|
2499
|
+
}
|
|
2500
|
+
function appendTableSection(lines, title, entries) {
|
|
2501
|
+
if (entries.length === 0) {
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const width = Math.max(...entries.map((entry) => entry.term.length), 0) + 2;
|
|
2505
|
+
lines.push(` ${chalk8.dim(title)}`);
|
|
2506
|
+
lines.push("");
|
|
2507
|
+
for (const entry of entries) {
|
|
2508
|
+
lines.push(` ${chalk8.white(padEnd(entry.term, width))}${chalk8.dim(entry.desc)}`);
|
|
2509
|
+
}
|
|
2510
|
+
lines.push("");
|
|
2511
|
+
}
|
|
2512
|
+
function commandPath(cmd) {
|
|
2513
|
+
const parts = [];
|
|
2514
|
+
let current = cmd;
|
|
2515
|
+
while (current) {
|
|
2516
|
+
parts.unshift(current.name());
|
|
2517
|
+
current = current.parent ?? null;
|
|
2518
|
+
}
|
|
2519
|
+
return parts.join(" ");
|
|
2520
|
+
}
|
|
2521
|
+
function formatRootHelp(cmd) {
|
|
2522
|
+
const version = pkg2.version ?? "0.0.0";
|
|
2523
|
+
const lines = [];
|
|
2524
|
+
const groups = [
|
|
2525
|
+
["Auth", []],
|
|
2526
|
+
["Computers", []],
|
|
2527
|
+
["Access", []],
|
|
2528
|
+
["Agents", []],
|
|
2529
|
+
["Other", []]
|
|
2530
|
+
];
|
|
2531
|
+
const otherGroup = groups.find(([name]) => name === "Other")[1];
|
|
2532
|
+
lines.push(`${chalk8.bold(cliName)} ${chalk8.dim(`v${version}`)}`);
|
|
2533
|
+
lines.push("");
|
|
2534
|
+
if (cmd.description()) {
|
|
2535
|
+
lines.push(` ${chalk8.dim(cmd.description())}`);
|
|
1513
2536
|
lines.push("");
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
} else if (["open", "ssh", "ports"].includes(name)) {
|
|
1530
|
-
groups.Access.push(entry);
|
|
1531
|
-
} else {
|
|
1532
|
-
groups.Other.push(entry);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
for (const [groupName, entries] of Object.entries(groups)) {
|
|
1536
|
-
if (entries.length === 0) continue;
|
|
1537
|
-
lines.push(` ${chalk7.dim(groupName)}`);
|
|
1538
|
-
for (const entry of entries) {
|
|
1539
|
-
const padded = entry.name.padEnd(14);
|
|
1540
|
-
lines.push(` ${chalk7.white(padded)}${chalk7.dim(entry.desc)}`);
|
|
1541
|
-
}
|
|
1542
|
-
lines.push("");
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
const globalOpts = [
|
|
1546
|
-
{ flags: "-y, --yes", desc: "Skip confirmation prompts" },
|
|
1547
|
-
{ flags: "-V, --version", desc: "Show version" },
|
|
1548
|
-
{ flags: "-h, --help", desc: "Show help" }
|
|
1549
|
-
];
|
|
1550
|
-
lines.push(` ${chalk7.dim("Options")}`);
|
|
1551
|
-
for (const opt of globalOpts) {
|
|
1552
|
-
const padded = opt.flags.padEnd(14);
|
|
1553
|
-
lines.push(` ${chalk7.white(padded)}${chalk7.dim(opt.desc)}`);
|
|
2537
|
+
}
|
|
2538
|
+
appendTextSection(lines, "Usage", [`${cliName} <command> [options]`]);
|
|
2539
|
+
for (const sub of cmd.commands) {
|
|
2540
|
+
const name = sub.name();
|
|
2541
|
+
const entry = { term: name, desc: sub.description() };
|
|
2542
|
+
if (["login", "logout", "whoami"].includes(name)) {
|
|
2543
|
+
groups[0][1].push(entry);
|
|
2544
|
+
} else if (["create", "ls", "get", "rm"].includes(name)) {
|
|
2545
|
+
groups[1][1].push(entry);
|
|
2546
|
+
} else if (["open", "ssh", "ports"].includes(name)) {
|
|
2547
|
+
groups[2][1].push(entry);
|
|
2548
|
+
} else if (["agent", "fleet", "acp"].includes(name)) {
|
|
2549
|
+
groups[3][1].push(entry);
|
|
2550
|
+
} else {
|
|
2551
|
+
otherGroup.push(entry);
|
|
1554
2552
|
}
|
|
2553
|
+
}
|
|
2554
|
+
for (const [groupName, entries] of groups) {
|
|
2555
|
+
appendTableSection(
|
|
2556
|
+
lines,
|
|
2557
|
+
groupName,
|
|
2558
|
+
entries
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
appendTableSection(lines, "Options", [
|
|
2562
|
+
{ term: "-y, --yes", desc: "Skip confirmation prompts" },
|
|
2563
|
+
{ term: "-V, --version", desc: "Show version" },
|
|
2564
|
+
{ term: "-h, --help", desc: "Show help" }
|
|
2565
|
+
]);
|
|
2566
|
+
return `${lines.join("\n").trimEnd()}
|
|
2567
|
+
`;
|
|
2568
|
+
}
|
|
2569
|
+
function formatSubcommandHelp(cmd, helper) {
|
|
2570
|
+
const lines = [];
|
|
2571
|
+
const description = helper.commandDescription(cmd);
|
|
2572
|
+
const argumentsList = helper.visibleArguments(cmd).map((argument) => ({
|
|
2573
|
+
term: helper.argumentTerm(argument),
|
|
2574
|
+
desc: helper.argumentDescription(argument)
|
|
2575
|
+
}));
|
|
2576
|
+
const commandList = helper.visibleCommands(cmd).map((subcommand) => ({
|
|
2577
|
+
term: helper.subcommandTerm(subcommand),
|
|
2578
|
+
desc: helper.subcommandDescription(subcommand)
|
|
2579
|
+
}));
|
|
2580
|
+
const optionList = helper.visibleOptions(cmd).map((option) => ({
|
|
2581
|
+
term: helper.optionTerm(option),
|
|
2582
|
+
desc: helper.optionDescription(option)
|
|
2583
|
+
}));
|
|
2584
|
+
lines.push(chalk8.bold(commandPath(cmd)));
|
|
2585
|
+
lines.push("");
|
|
2586
|
+
if (description) {
|
|
2587
|
+
lines.push(` ${chalk8.dim(description)}`);
|
|
1555
2588
|
lines.push("");
|
|
1556
|
-
return lines.join("\n");
|
|
1557
2589
|
}
|
|
1558
|
-
|
|
2590
|
+
appendTextSection(lines, "Usage", [helper.commandUsage(cmd)]);
|
|
2591
|
+
appendTableSection(lines, "Arguments", argumentsList);
|
|
2592
|
+
appendTableSection(lines, "Commands", commandList);
|
|
2593
|
+
appendTableSection(lines, "Options", optionList);
|
|
2594
|
+
return `${lines.join("\n").trimEnd()}
|
|
2595
|
+
`;
|
|
2596
|
+
}
|
|
2597
|
+
function applyHelpFormatting(cmd) {
|
|
2598
|
+
cmd.configureHelp({
|
|
2599
|
+
formatHelp(current, helper) {
|
|
2600
|
+
if (!current.parent) {
|
|
2601
|
+
return formatRootHelp(current);
|
|
2602
|
+
}
|
|
2603
|
+
return formatSubcommandHelp(current, helper);
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2606
|
+
for (const subcommand of cmd.commands) {
|
|
2607
|
+
applyHelpFormatting(subcommand);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
program.name(cliName).description("Agent Computer CLI").version(pkg2.version ?? "0.0.0").option("-y, --yes", "Skip confirmation prompts");
|
|
1559
2611
|
program.addCommand(loginCommand);
|
|
1560
2612
|
program.addCommand(logoutCommand);
|
|
1561
2613
|
program.addCommand(whoamiCommand);
|
|
1562
2614
|
program.addCommand(createCommand);
|
|
1563
2615
|
program.addCommand(lsCommand);
|
|
1564
2616
|
program.addCommand(getCommand);
|
|
2617
|
+
program.addCommand(agentCommand);
|
|
2618
|
+
program.addCommand(fleetCommand);
|
|
2619
|
+
program.addCommand(acpCommand);
|
|
1565
2620
|
program.addCommand(openCommand);
|
|
1566
2621
|
program.addCommand(sshCommand);
|
|
1567
2622
|
program.addCommand(portsCommand);
|
|
1568
2623
|
program.addCommand(removeCommand);
|
|
1569
2624
|
program.addCommand(completionCommand);
|
|
2625
|
+
applyHelpFormatting(program);
|
|
1570
2626
|
program.parse();
|