202 lines
5.1 KiB
TypeScript
202 lines
5.1 KiB
TypeScript
import { confirm as clackConfirm } from "@clack/prompts";
|
|
|
|
import {
|
|
listSandboxBrowsers,
|
|
listSandboxContainers,
|
|
removeSandboxBrowserContainer,
|
|
removeSandboxContainer,
|
|
type SandboxBrowserInfo,
|
|
type SandboxContainerInfo,
|
|
} from "../agents/sandbox.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import {
|
|
displayBrowsers,
|
|
displayContainers,
|
|
displayRecreatePreview,
|
|
displayRecreateResult,
|
|
displaySummary,
|
|
} from "./sandbox-display.js";
|
|
|
|
// --- Types ---
|
|
|
|
type SandboxListOptions = {
|
|
browser: boolean;
|
|
json: boolean;
|
|
};
|
|
|
|
type SandboxRecreateOptions = {
|
|
all: boolean;
|
|
session?: string;
|
|
agent?: string;
|
|
browser: boolean;
|
|
force: boolean;
|
|
};
|
|
|
|
type ContainerItem = SandboxContainerInfo | SandboxBrowserInfo;
|
|
|
|
type FilteredContainers = {
|
|
containers: SandboxContainerInfo[];
|
|
browsers: SandboxBrowserInfo[];
|
|
};
|
|
|
|
// --- List Command ---
|
|
|
|
export async function sandboxListCommand(
|
|
opts: SandboxListOptions,
|
|
runtime: RuntimeEnv,
|
|
): Promise<void> {
|
|
const containers = opts.browser ? [] : await listSandboxContainers().catch(() => []);
|
|
const browsers = opts.browser ? await listSandboxBrowsers().catch(() => []) : [];
|
|
|
|
if (opts.json) {
|
|
runtime.log(JSON.stringify({ containers, browsers }, null, 2));
|
|
return;
|
|
}
|
|
|
|
if (opts.browser) {
|
|
displayBrowsers(browsers, runtime);
|
|
} else {
|
|
displayContainers(containers, runtime);
|
|
}
|
|
|
|
displaySummary(containers, browsers, runtime);
|
|
}
|
|
|
|
// --- Recreate Command ---
|
|
|
|
export async function sandboxRecreateCommand(
|
|
opts: SandboxRecreateOptions,
|
|
runtime: RuntimeEnv,
|
|
): Promise<void> {
|
|
if (!validateRecreateOptions(opts, runtime)) {
|
|
return;
|
|
}
|
|
|
|
const filtered = await fetchAndFilterContainers(opts);
|
|
|
|
if (filtered.containers.length + filtered.browsers.length === 0) {
|
|
runtime.log("No containers found matching the criteria.");
|
|
return;
|
|
}
|
|
|
|
displayRecreatePreview(filtered.containers, filtered.browsers, runtime);
|
|
|
|
if (!opts.force && !(await confirmRecreate())) {
|
|
runtime.log("Cancelled.");
|
|
return;
|
|
}
|
|
|
|
const result = await removeContainers(filtered, runtime);
|
|
displayRecreateResult(result, runtime);
|
|
|
|
if (result.failCount > 0) {
|
|
runtime.exit(1);
|
|
}
|
|
}
|
|
|
|
// --- Validation ---
|
|
|
|
function validateRecreateOptions(opts: SandboxRecreateOptions, runtime: RuntimeEnv): boolean {
|
|
if (!opts.all && !opts.session && !opts.agent) {
|
|
runtime.error("Please specify --all, --session <key>, or --agent <id>");
|
|
runtime.exit(1);
|
|
return false;
|
|
}
|
|
|
|
const exclusiveCount = [opts.all, opts.session, opts.agent].filter(Boolean).length;
|
|
if (exclusiveCount > 1) {
|
|
runtime.error("Please specify only one of: --all, --session, --agent");
|
|
runtime.exit(1);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- Filtering ---
|
|
|
|
async function fetchAndFilterContainers(opts: SandboxRecreateOptions): Promise<FilteredContainers> {
|
|
const allContainers = await listSandboxContainers().catch(() => []);
|
|
const allBrowsers = await listSandboxBrowsers().catch(() => []);
|
|
|
|
let containers = opts.browser ? [] : allContainers;
|
|
let browsers = opts.browser ? allBrowsers : [];
|
|
|
|
if (opts.session) {
|
|
containers = containers.filter((c) => c.sessionKey === opts.session);
|
|
browsers = browsers.filter((b) => b.sessionKey === opts.session);
|
|
} else if (opts.agent) {
|
|
const matchesAgent = createAgentMatcher(opts.agent);
|
|
containers = containers.filter(matchesAgent);
|
|
browsers = browsers.filter(matchesAgent);
|
|
}
|
|
|
|
return { containers, browsers };
|
|
}
|
|
|
|
function createAgentMatcher(agentId: string) {
|
|
const agentPrefix = `agent:${agentId}`;
|
|
return (item: ContainerItem) =>
|
|
item.sessionKey === agentPrefix || item.sessionKey.startsWith(`${agentPrefix}:`);
|
|
}
|
|
|
|
// --- Container Operations ---
|
|
|
|
async function confirmRecreate(): Promise<boolean> {
|
|
const result = await clackConfirm({
|
|
message: "This will stop and remove these containers. Continue?",
|
|
initialValue: false,
|
|
});
|
|
|
|
return result !== false && result !== Symbol.for("clack:cancel");
|
|
}
|
|
|
|
async function removeContainers(
|
|
filtered: FilteredContainers,
|
|
runtime: RuntimeEnv,
|
|
): Promise<{ successCount: number; failCount: number }> {
|
|
runtime.log("\nRemoving containers...\n");
|
|
|
|
let successCount = 0;
|
|
let failCount = 0;
|
|
|
|
for (const container of filtered.containers) {
|
|
const result = await removeContainer(container.containerName, removeSandboxContainer, runtime);
|
|
if (result.success) {
|
|
successCount++;
|
|
} else {
|
|
failCount++;
|
|
}
|
|
}
|
|
|
|
for (const browser of filtered.browsers) {
|
|
const result = await removeContainer(
|
|
browser.containerName,
|
|
removeSandboxBrowserContainer,
|
|
runtime,
|
|
);
|
|
if (result.success) {
|
|
successCount++;
|
|
} else {
|
|
failCount++;
|
|
}
|
|
}
|
|
|
|
return { successCount, failCount };
|
|
}
|
|
|
|
async function removeContainer(
|
|
containerName: string,
|
|
removeFn: (name: string) => Promise<void>,
|
|
runtime: RuntimeEnv,
|
|
): Promise<{ success: boolean }> {
|
|
try {
|
|
await removeFn(containerName);
|
|
runtime.log(`✓ Removed ${containerName}`);
|
|
return { success: true };
|
|
} catch (err) {
|
|
runtime.error(`✗ Failed to remove ${containerName}: ${String(err)}`);
|
|
return { success: false };
|
|
}
|
|
}
|