From ffbdc038c5302661a4b7fc85bc7cf88801362dcf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 17 Sep 2026 10:54:00 +0000 Subject: [PATCH] fix: fund help stays off bare argv; selat-pay packageRoot ignores ancestor packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare `selat fund` must enter the deposit flow — help matches only exact -h/--help tokens, never empty argv. findPackageRoot only accepts @selat-ai/selat-pay, so a bare SELAT_PAY_BIN under an in-tree temp dir no longer claims selat-cli's version. Closes #191 Closes #192 Co-authored-by: SELAT-DEV --- lib/commands/fund.mjs | 10 +++++++++- lib/selat-pay.mjs | 17 ++++++++++++++--- test/selat-pay-resolve.test.mjs | 8 +++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/commands/fund.mjs b/lib/commands/fund.mjs index bfba013..fa41ad2 100644 --- a/lib/commands/fund.mjs +++ b/lib/commands/fund.mjs @@ -93,11 +93,19 @@ asks you to confirm before depositing. (--onramp is the exception: it only mints a browser link; the purchase happens in Circle's widget.) \`selat freeze\` is a local kill switch: this command refuses while frozen.`; +/** Exact `-h` / `--help` argv tokens only. Empty argv is the deposit flow, not usage. */ +function wantsFundHelp(args) { + if (!Array.isArray(args)) return false; + return args.some((a) => a === "-h" || a === "--help"); +} + export async function fund(args, { interactive = stdinIsInteractive(), run = sh, stdout = process.stdout, stderr = process.stderr } = {}) { // Help is inert: no skill lookup, no prompts, no deposit flow. Previously // `fund --help` fell through and started prompting toward a real deposit — // the defect class #101 fixed for setup-policy, one command over. - if (args.includes("-h") || args.includes("--help")) { + // Bare `selat fund` (no tokens) must still enter the deposit flow — a + // `!args.length` (or substring) match here would swallow it and print Usage. + if (wantsFundHelp(args)) { console.log(FUND_HELP); return 0; } diff --git a/lib/selat-pay.mjs b/lib/selat-pay.mjs index 221098b..00c785a 100644 --- a/lib/selat-pay.mjs +++ b/lib/selat-pay.mjs @@ -170,11 +170,22 @@ export async function selatPayVersion() { } function findPackageRoot(binPath) { - // Walk up from the binary looking for a package.json. Bounded depth so a - // weird symlink can't spin forever. + // Walk up from the binary looking for THIS package's package.json. + // Returning the first package.json we see is wrong: a bare SELAT_PAY_BIN + // under an in-tree TMPDIR (or any nested temp path) would claim selat-cli's + // own package as selat-pay and report its version. Bounded so a weird + // symlink can't spin forever. let dir = dirname(binPath); for (let i = 0; i < 6; i++) { - if (existsSync(join(dir, "package.json"))) return dir; + const pkgPath = join(dir, "package.json"); + if (existsSync(pkgPath)) { + try { + const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); + if (pkg?.name === "@selat-ai/selat-pay") return dir; + } catch { + // unreadable or invalid JSON — keep walking + } + } const parent = dirname(dir); if (parent === dir) break; dir = parent; diff --git a/test/selat-pay-resolve.test.mjs b/test/selat-pay-resolve.test.mjs index 1a2fe66..9092c40 100644 --- a/test/selat-pay-resolve.test.mjs +++ b/test/selat-pay-resolve.test.mjs @@ -88,7 +88,13 @@ test("ensureSelatPayHistoryDir honors XDG_STATE_HOME when no explicit path is se // ── An override that is a bare binary (no package.json, doesn't run) ───────── test("a bare override resolves with a null packageRoot and no version claim", async () => { - const dir = mkdtempSync(join(tmpdir(), "selat-pay-bare-")); + // Nest under an unrelated package.json so a naive "walk until any + // package.json" resolver cannot pass this when TMPDIR is /tmp. In-tree + // temps (and this decoy) used to resolve to that ancestor and claim its + // version as selat-pay. + const decoy = mkdtempSync(join(tmpdir(), "selat-pay-decoy-")); + writeFileSync(join(decoy, "package.json"), JSON.stringify({ name: "@selat-ai/selat-cli", version: "0.0.0" })); + const dir = mkdtempSync(join(decoy, "bare-")); const bin = join(dir, "selat-pay.mjs"); writeFileSync(bin, "process.exit(0);\n"); const restore = setEnv({ SELAT_PAY_BIN: bin });