Skip to content

Commit 29cb122

Browse files
wddwyccclaude
andcommitted
Take exec's flags in any order (PHANTOM-66)
`phantom vm exec -it <id> -- vi` is what a hand types, having typed `docker exec -it` a thousand times — and it took -it as the VM id, ran the non-interactive path, and answered "VM is not running: -it". The id being positional is no reason to refuse a flag before it: the first argument that is not a flag (or a flag's value) is the id, wherever it sits. Verified through the real CLI under a pty (script -q /dev/null): vi draws, takes keystrokes and writes its file, and an interactive zsh gives a prompt and exits cleanly — both argument orders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent bf02bcf commit 29cb122

2 files changed

Lines changed: 41 additions & 6 deletions

File tree

phantom-cli/src/commands/vm.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,17 +293,30 @@ export async function vmExec(...args: string[]) {
293293
process.exit(1);
294294
}
295295

296-
const vmId = args[0]!; // guaranteed present since dashDashIndex >= 1
296+
// Flags may come before or after the id: `exec -it <id> -- …` is what a hand
297+
// types, having typed `docker exec -it` a thousand times, and the id being
298+
// positional is no reason to make that an error. The first thing that is not
299+
// a flag (or a flag's value) is the id.
300+
let vmId: string | undefined;
297301
let user: string | undefined;
298302
let interactive = false;
299-
for (let i = 1; i < dashDashIndex; i++) {
300-
if (args[i] === "--user" && i + 1 < dashDashIndex) {
301-
user = args[i + 1];
302-
i++;
303-
} else if (["-it", "-ti", "-i", "-t", "--tty"].includes(args[i]!)) {
303+
for (let i = 0; i < dashDashIndex; i++) {
304+
const arg = args[i]!;
305+
if (arg === "--user" && i + 1 < dashDashIndex) {
306+
user = args[++i];
307+
} else if (["-it", "-ti", "-i", "-t", "--tty"].includes(arg)) {
304308
interactive = true;
309+
} else if (!arg.startsWith("-") && !vmId) {
310+
vmId = arg;
305311
}
306312
}
313+
314+
if (!vmId) {
315+
console.error("Error: a VM id is required");
316+
console.error("");
317+
usage("exec");
318+
process.exit(1);
319+
}
307320
const command = args.slice(dashDashIndex + 1).join(" ");
308321

309322
if (!command) {

phantom-cli/src/tmp-vi.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { sendStreamingRequest } from "./lib/api";
2+
const vmId = process.argv[2]!;
3+
let bytes = 0, esc = 0, first = "";
4+
const { exitCode } = await sendStreamingRequest(
5+
{ method: "vm.execStream", params: { vmId, user: "admin", waitForAgent: true, tty: true, rows: 24, cols: 80, term: "xterm-256color", command: "vi /tmp/probe.txt" } },
6+
(c) => {
7+
if (!c.data) return;
8+
const b = c.encoding === "base64" ? Buffer.from(c.data, "base64") : Buffer.from(c.data);
9+
bytes += b.length;
10+
esc += (b.toString("latin1").match(/\x1b/g) ?? []).length;
11+
if (!first) first = JSON.stringify(b.toString("latin1").slice(0, 120));
12+
},
13+
{ timeoutMs: 30_000, onOpen: (send) => {
14+
const key = (s: string) => send({ type: "stdin", data: Buffer.from(s, "latin1").toString("base64") });
15+
setTimeout(() => key("iHELLO"), 1500);
16+
setTimeout(() => key("\x1b"), 2500);
17+
setTimeout(() => key(":wq\n"), 3000);
18+
} }
19+
);
20+
console.log("bytes:", bytes, "escape sequences:", esc);
21+
console.log("first output:", first);
22+
console.log("exitCode:", exitCode);

0 commit comments

Comments
 (0)