Fix desktop CLI passthrough tty handling (#791)

* Fix desktop CLI passthrough tty handling

* Fix desktop daemon manager test drift

* Update daemon stop test after main merge

* test(cli): isolate npx cache per CLI test
This commit is contained in:
Mohamed Boudra
2026-05-07 15:14:18 +08:00
committed by GitHub
parent 6fb2fe2283
commit faf664d803
20 changed files with 868 additions and 461 deletions
+4 -24
View File
@@ -1,26 +1,6 @@
import { createCli } from "./cli.js";
import { classifyInvocation } from "./classify.js";
import { openDesktopWithProject } from "./commands/open.js";
import { runCli } from "./run.js";
const program = createCli();
const knownCommands = new Set(program.commands.map((command) => command.name()));
const invocation = classifyInvocation({
argv: process.argv.slice(2),
knownCommands,
cwd: process.cwd(),
const exitCode = await runCli(process.argv.slice(2), {
nodeArgv: [process.argv[0] ?? "node", process.argv[1] ?? "paseo"],
});
switch (invocation.kind) {
case "cli": {
const argv = [...process.argv.slice(0, 2), ...invocation.argv];
if (invocation.argv.length === 0) {
argv.push("onboard");
}
program.parse(argv, { from: "node" });
break;
}
case "open-project":
await openDesktopWithProject(invocation.resolvedPath);
break;
}
process.exitCode = exitCode;
+48
View File
@@ -0,0 +1,48 @@
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { createCliParseArgv } from "./run";
describe("runCli", () => {
it("defaults an empty CLI invocation to onboard", () => {
expect(
createCliParseArgv({
argv: [],
cwd: process.cwd(),
nodeArgv: ["node", "paseo"],
}),
).toEqual(["node", "paseo", "onboard"]);
});
it("preserves known CLI command argv", () => {
expect(
createCliParseArgv({
argv: ["daemon", "set-password"],
cwd: process.cwd(),
nodeArgv: ["node", "paseo"],
}),
).toEqual(["node", "paseo", "daemon", "set-password"]);
});
it("classifies existing unknown directories as open-project invocations", () => {
const root = mkdtempSync(path.join(tmpdir(), "paseo-cli-run-"));
const project = path.join(root, "project");
mkdirSync(project);
try {
expect(
createCliParseArgv({
argv: ["project"],
cwd: root,
nodeArgv: ["node", "paseo"],
}),
).toEqual({
kind: "open-project",
resolvedPath: project,
});
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});
+47
View File
@@ -0,0 +1,47 @@
import { createCli } from "./cli.js";
import { classifyInvocation } from "./classify.js";
import { openDesktopWithProject } from "./commands/open.js";
export interface RunCliOptions {
cwd?: string;
nodeArgv?: [string, string];
}
export function createCliParseArgv(input: {
argv: string[];
cwd: string;
nodeArgv?: [string, string];
}): string[] | { kind: "open-project"; resolvedPath: string } {
const program = createCli();
const knownCommands = new Set(program.commands.map((command) => command.name()));
const invocation = classifyInvocation({
argv: input.argv,
knownCommands,
cwd: input.cwd,
});
if (invocation.kind === "open-project") {
return invocation;
}
const nodeArgv = input.nodeArgv ?? ["paseo", "paseo"];
const cliArgv = invocation.argv.length === 0 ? ["onboard"] : invocation.argv;
return [...nodeArgv, ...cliArgv];
}
export async function runCli(argv: string[], options: RunCliOptions = {}): Promise<number> {
const parseArgv = createCliParseArgv({
argv,
cwd: options.cwd ?? process.cwd(),
nodeArgv: options.nodeArgv,
});
if (!Array.isArray(parseArgv)) {
await openDesktopWithProject(parseArgv.resolvedPath);
return typeof process.exitCode === "number" ? process.exitCode : 0;
}
const program = createCli();
await program.parseAsync(parseArgv, { from: "node" });
return typeof process.exitCode === "number" ? process.exitCode : 0;
}