Reads go through the read-only CLI first: bun ~/.claude/LIFEOS/TOOLS/StripeRead.ts promos | coupons | products | prices | subs | customer <email> | balance | get <v1 path> [k=v…] (--account <alias> for an additional account). It is a single plain command with no write path, so it runs wherever a shell command can, including unattended sessions. The inline cd … && bun --env-file … -e script below is arbitrary code, and a harness that asks before running such code will stop on it when nobody is there to approve, so use it only for writes and anything the CLI can't express, such as the computed reports in each intent. Two limits of the CLI, from its code: products and prices return active objects only, and every list stops at 1,000 rows, so a count of exactly 1,000 is a floor and needs fetchAll below.
Writes and computed reports run inside this preamble. Fill its two <…> values from Config.md before running it: the SDK folder (STRIPE_SDK_DIR, a directory where the stripe npm package is installed, with a leading ~ written as $HOME) and the API version (STRIPE_API_VERSION). bun -e resolves the SDK from that folder. If Config.md lacks either, stop and point at Setup.md. Each intent's Tool contract gives the body that replaces the last two lines.
cd "<STRIPE_SDK_DIR from Config.md>" && bun --env-file ~/.claude/.env -e "
import Stripe from 'stripe';
const API_VERSION = '<STRIPE_API_VERSION from Config.md>';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: API_VERSION });
async function fetchAll(listFn, params = {}) {
const items = [];
let hasMore = true;
let startingAfter;
while (hasMore) {
const batch = await listFn({ limit: 100, ...params, ...(startingAfter ? { starting_after: startingAfter } : {}) });
items.push(...batch.data);
hasMore = batch.has_more;
if (batch.data.length > 0) startingAfter = batch.data[batch.data.length - 1].id;
}
return items;
}
const result = await stripe.METHOD;
console.log(JSON.stringify(result, null, 2));
"
For an additional account the key line becomes const stripe = new Stripe(process.env['<ENV VAR>'], { apiVersion: API_VERSION });, with <ENV VAR> taken from that alias's row in Config.md's Accounts table, never built from the alias by hand. The read-only CLIs (StripeRead.ts, InvoiceWatch.ts) resolve --account <alias> to STRIPE_SECRET_KEY_<ALIAS in capitals>, so name each additional key that way. Bun's --env-file loads the configured key into the process without reading or printing the secret in shell. A restricted key (rk_live_*) allows only the scopes granted on it: read operations work; write operations (create/refund/cancel) fail unless that scope was granted.