import { spawnSync } from "node:child_process"; import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); const cliPath = join(packageRoot, "dist", "main.js"); function runCli(args: string[], input?: string) { return spawnSync(process.execPath, [cliPath, ...args], { cwd: packageRoot, encoding: "utf8", input, timeout: 60_000, }); } describe("agentos-shell cli", () => { test("--help prints Docker-style run flags without starting a VM", () => { const result = runCli(["--help"]); expect(result.status).toBe(0); expect(result.stdout).toContain("agentos-shell"); expect(result.stdout).toContain("-i, --interactive"); expect(result.stdout).toContain("--no-interactive"); expect(result.stdout).toContain("-t, --tty"); expect(result.stdout).toContain("--no-tty"); expect(result.stdout).toContain("-e, --env "); expect(result.stdout).toContain("-v, --volume "); expect(result.stderr).not.toContain("agent-os shell"); }); test("runs a VM-backed command with guest cwd and env", () => { const result = runCli([ "--workdir", "/tmp", "--env", "SHELL_TEST_ENV=works", "--", "node", "-e", "console.log('SHELL_VM_COMMAND:' + process.cwd() + ':' + process.env.SHELL_TEST_ENV); process.exit(7);", ]); expect(result.status).toBe(7); expect(result.stdout).toContain("SHELL_VM_COMMAND:/tmp:works"); }); test("mounts a host directory with Docker -v syntax", () => { const hostDir = mkdtempSync(join(tmpdir(), "agentos-shell-volume-")); writeFileSync(join(hostDir, "hello.txt"), "mounted\n"); const result = runCli([ "--volume", `${hostDir}:/mnt:ro`, "--", "cat", "/mnt/hello.txt", ]); expect(result.status).toBe(0); expect(result.stdout).toContain("mounted"); }); test("keeps stdin attached by default", () => { const result = runCli(["--", "bash"], "echo hello\n"); expect(result.status).toBe(0); expect(result.stdout).toContain("hello"); }); test("detaches stdin when --no-interactive is set", () => { const result = runCli( ["--no-interactive", "--", "bash"], "echo should-not-run\n", ); expect(result.status).toBe(0); expect(result.stdout).not.toContain("should-not-run"); }); test("runs a command through terminal mode when -t is set", () => { const result = runCli([ "--workdir", "/tmp", "--tty", "--", "bash", "-c", "echo tty-mode", ]); expect(result.status).toBe(0); expect(result.stdout).toContain("tty-mode"); }); test("default terminal mode launches bash instead of the synthetic shell", () => { const result = runCli([], "echo $0\nexit\n"); expect(result.status).toBe(0); expect(result.stdout).toContain("bash"); expect(result.stdout).not.toContain("sh-0.4$"); }); test("reads env files", () => { const dir = mkdtempSync(join(tmpdir(), "agentos-shell-env-")); mkdirSync(join(dir, "nested")); const envFile = join(dir, "nested", "env.list"); writeFileSync(envFile, "FROM_ENV_FILE=yes\n"); const result = runCli([ "--env-file", envFile, "--", "node", "-e", "console.log(process.env.FROM_ENV_FILE)", ]); expect(result.status).toBe(0); expect(result.stdout).toContain("yes"); }); });