From e52b43ad398a3ea1f9dedbf67a51418a60c1458e Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Wed, 15 Jul 2026 21:20:09 +0800 Subject: [PATCH] fix(onboarding): stop skill-install from eating the piped install script; drop false PATH warning + '(~20s)' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three onboarding papercuts surfaced comparing `curl agents.instacloud.com | sh` to Railway's: 1. The 'Get started' guidance never printed under `curl | sh`. `insta setup agent` spawned `npx skills` with stdin='inherit'; since the piped install script IS stdin, the child consumed the rest of it and the shell never ran the trailing guidance. Fixed: stdin='ignore' (with -y there's no prompt to read anyway). Proven via old-vs-new binary contrast — old eats the guidance, new prints it. 2. 'another insta is first on your PATH: /usr/local/bin/insta' was a FALSE warning — that path is the symlink the installer itself created, resolving straight back to our binary. Now silent when readlink matches our install target; still warns for a genuinely different (e.g. npm) insta. 3. Dropped the misleading '(~20s)' label and enriched the next-steps block (incl. 'your agents now know InstaCloud — just ask them'). 75/75 tests green, tsc clean. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GMbAe5K1RfwaAcge5inX7P --- install.sh | 26 ++++++++++++++++++-------- src/commands/setup.ts | 11 +++++++---- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/install.sh b/install.sh index 3637e0f..a8937e5 100644 --- a/install.sh +++ b/install.sh @@ -48,10 +48,17 @@ fi # other insta on PATH shadowing ours? (shells use the first hit) first_hit="$(command -v insta 2>/dev/null || true)" if [ -n "$first_hit" ] && [ "$first_hit" != "$INSTALL_DIR/$BIN" ]; then - echo "! another insta is first on your PATH: $first_hit" - case "$first_hit" in - */node_modules/*|*npm*|*/.nvm/*) echo "! (npm-installed — update it with: npm update -g insta, or remove it to use the binary)" ;; - esac + # Not a conflict if the first hit is just the symlink WE created into an on-PATH dir + # (see the linking step below) — it resolves straight back to our binary. Warn only for a + # genuinely different insta (e.g. an npm-installed one) that would actually shadow ours. + if [ -L "$first_hit" ] && [ "$(readlink "$first_hit" 2>/dev/null)" = "$INSTALL_DIR/$BIN" ]; then + : # our own symlink → the binary; nothing to warn about + else + echo "! another insta is first on your PATH: $first_hit" + case "$first_hit" in + */node_modules/*|*npm*|*/.nvm/*) echo "! (npm-installed — update it with: npm update -g insta, or remove it to use the binary)" ;; + esac + fi fi # ---- detect platform ---- @@ -168,7 +175,10 @@ fi # ---- next steps (the 3-command wow: real infra, then a full isolated clone of it) ---- echo -echo "Get started:" -echo " insta login --oauth github # cloud — or run insta-oss locally and skip this" -echo " insta project create demo && insta deploy . --port 3000" -echo " insta branch create preview # clones db + storage + app into an isolated env" +echo "Next steps:" +echo " insta login --oauth github # connect to the cloud (or run insta-oss locally to skip)" +echo " insta project create demo # postgres + storage + compute, provisioned in one shot" +echo " insta deploy . --port 3000 # ship your app and get a live URL" +echo " insta branch create preview # clone db + storage + app into an isolated env" +echo +echo "Your coding agents now know InstaCloud — you can just ask them to do the above." diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 6ab3c31..b1a08fb 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -73,12 +73,15 @@ export function summarizeInstall(output: string): string { export type Runner = (cmd: string, args: string[]) => Promise<{ ok: boolean; output?: string }> -// Capture stdout+stderr silently (don't stream) so we can print our own clean summary. We keep -// the child's stdin inherited in case the tool ever needs a TTY, but with -y it shouldn't. +// Capture stdout+stderr silently (don't stream) so we can print our own clean summary. +// stdin is 'ignore', NOT 'inherit': under the canonical `curl … | sh` install, stdin is the +// piped install script itself — a child that inherits it (npx/skills reads for keypresses even +// with -y) consumes the rest of the script, so the shell never runs the trailing "Get started" +// guidance. Ignoring stdin keeps the installer's own output intact. (-y means no prompt anyway.) const defaultRunner: Runner = (cmd, args) => new Promise((resolve) => { const env = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'insta', FORCE_COLOR: '0' } - const p = spawn(cmd, args, { stdio: ['inherit', 'pipe', 'pipe'], env }) + const p = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], env }) let output = '' const grab = (chunk: Buffer) => { output += chunk.toString() } p.stdout?.on('data', grab) @@ -95,7 +98,7 @@ export async function setupAgent(opts: { yes?: boolean }, run: Runner = defaultR if (!opts.yes && !process.stdout.isTTY) { info('non-interactive shell — assuming -y') } - info('setting up coding-agent skills … (~20s)') + info('setting up coding-agent skills …') const res = await run('npx', SETUP_ARGS) if (!res.ok) { info(' skill install failed — install manually with:')