mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-20 01:11:18 +00:00
fix(ux): deduplicate install messages, add newlines to SSH polling, clarify completion messages (#2900)
- Suppress stdout+stderr from `claude install --force` to prevent duplicate
"successfully installed" messages (was printed up to 4x)
- Make logStepInline fall back to newline-separated output when stderr is not
a TTY, so SSH port polling status is readable in piped/captured contexts
- Consolidate post-install completion messages into a single clear milestone:
"Agent setup complete -- {agent} is ready on {cloud}"
- Bump CLI version to 0.25.16
Fixes #2899
Agent: ux-engineer
Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a96522829b
commit
7aba20e327
5 changed files with 20 additions and 13 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@openrouter/spawn",
|
||||
"version": "0.25.15",
|
||||
"version": "0.25.16",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"spawn": "cli.js"
|
||||
|
|
|
|||
|
|
@ -85,17 +85,19 @@ describe("logging functions", () => {
|
|||
expect(stderrOutput.join("")).toContain("test step");
|
||||
});
|
||||
|
||||
it("logStepInline writes without newline", () => {
|
||||
it("logStepInline writes message (newline-terminated in non-TTY)", () => {
|
||||
logStepInline("inline msg");
|
||||
const output = stderrOutput.join("");
|
||||
expect(output).toContain("inline msg");
|
||||
expect(output).not.toEndWith("\n");
|
||||
// In non-TTY (test environment), output ends with newline instead of \r overwrite
|
||||
expect(output).toEndWith("\n");
|
||||
});
|
||||
|
||||
it("logStepDone clears the line", () => {
|
||||
it("logStepDone is no-op in non-TTY", () => {
|
||||
logStepDone();
|
||||
const output = stderrOutput.join("");
|
||||
expect(output).toContain("\r");
|
||||
// In non-TTY (test environment), logStepDone writes nothing
|
||||
expect(output).toBe("");
|
||||
});
|
||||
|
||||
it("logDebug only outputs when SPAWN_DEBUG=1", () => {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ async function installClaudeCode(runner: CloudRunner): Promise<void> {
|
|||
|
||||
const claudePath = "$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$HOME/.n/bin";
|
||||
const pathSetup = `for rc in ~/.bashrc ~/.profile ~/.bash_profile ~/.zshrc; do grep -q '.claude/local/bin' "$rc" 2>/dev/null || printf '\\n# Claude Code PATH\\nexport PATH="$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH"\\n' >> "$rc"; done`;
|
||||
const finalize = `claude install --force 2>/dev/null || true; ${pathSetup}`;
|
||||
const finalize = `claude install --force >/dev/null 2>&1 || true; ${pathSetup}`;
|
||||
|
||||
const script = [
|
||||
`export PATH="${claudePath}:$PATH"`,
|
||||
|
|
@ -125,7 +125,7 @@ async function installClaudeCode(runner: CloudRunner): Promise<void> {
|
|||
logError("Claude Code installation failed");
|
||||
throw new Error("Claude Code install failed");
|
||||
}
|
||||
logInfo("Claude Code installed");
|
||||
logInfo("Claude Code agent installed successfully");
|
||||
}
|
||||
|
||||
async function setupClaudeCodeConfig(runner: CloudRunner, apiKey: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -532,9 +532,7 @@ async function postInstall(
|
|||
}
|
||||
|
||||
// Launch agent
|
||||
logInfo(`${agent.name} is ready`);
|
||||
process.stderr.write("\n");
|
||||
logInfo(`${cloud.cloudLabel} setup completed successfully!`);
|
||||
logInfo(`Agent setup complete — ${agent.name} is ready on ${cloud.cloudLabel}`);
|
||||
process.stderr.write("\n");
|
||||
|
||||
const launchCmd = agent.launchCmd();
|
||||
|
|
|
|||
|
|
@ -38,14 +38,21 @@ export function logStep(msg: string): void {
|
|||
process.stderr.write(`${CYAN}${msg}${NC}\n`);
|
||||
}
|
||||
|
||||
/** Overwrite the current line with a status message (no newline). Call logStepDone() when finished. */
|
||||
/** Overwrite the current line with a status message (no newline). Call logStepDone() when finished.
|
||||
* Falls back to newline-separated output when stderr is not a TTY (e.g., piped or captured). */
|
||||
export function logStepInline(msg: string): void {
|
||||
process.stderr.write(`\r${CYAN}${msg}${NC}\x1b[K`);
|
||||
if (process.stderr.isTTY) {
|
||||
process.stderr.write(`\r${CYAN}${msg}${NC}\x1b[K`);
|
||||
} else {
|
||||
process.stderr.write(`${CYAN}${msg}${NC}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
/** End an inline status line by moving to the next line. */
|
||||
export function logStepDone(): void {
|
||||
process.stderr.write("\r\x1b[K");
|
||||
if (process.stderr.isTTY) {
|
||||
process.stderr.write("\r\x1b[K");
|
||||
}
|
||||
}
|
||||
|
||||
/** Prompt for a line of user input. Throws if non-interactive.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue