Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/commands/fund.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
17 changes: 14 additions & 3 deletions lib/selat-pay.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion test/selat-pay-resolve.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down