@getbags/cash 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ bags --help
13
13
 
14
14
  ### Agent skill (prompt paste)
15
15
 
16
- Hosted skill: https://www.getbags.app/wallet/skill.md
16
+ Hosted skill (agents): https://www.getbags.app/wallet/skill.md
17
17
 
18
18
  Paste into Cursor / Claude / Codex / etc.:
19
19
 
@@ -21,8 +21,9 @@ Paste into Cursor / Claude / Codex / etc.:
21
21
  Set up https://www.getbags.app/wallet/skill.md
22
22
  ```
23
23
 
24
+ Human install guide: https://www.getbags.app/wallet/install
24
25
  Human onboard (bonus + same prompt): https://www.getbags.app/wallet/onboard
25
- Staging: use `https://staging.getbags.app/wallet/skill.md` (or set `BAGS_API_URL`).
26
+ Staging skill (agents): use `https://staging.getbags.app/wallet/skill.md` (or set `BAGS_API_URL`).
26
27
 
27
28
  ## Workflow
28
29
 
@@ -30,21 +31,27 @@ Staging: use `https://staging.getbags.app/wallet/skill.md` (or set `BAGS_API_URL
30
31
 
31
32
  ## Commands
32
33
 
33
- | Command | Description |
34
- | --------------------------- | ----------------------------------------------------------- |
35
- | `redeem <code>` | Redeem an invite; saves token to `~/.bags/credentials.json` |
36
- | `balance` | Show balance, daily spent/cap, status |
37
- | `discover [query]` | List BAGS merchant endpoints (BAGS first) |
38
- | `search <query>` | NL search across BAGS + ecosystem index |
39
- | `check <url>` | Probe schema + exact quote **without paying** |
40
- | `fetch <url> [flags]` | Pay and proxy an x402 endpoint |
41
- | `transactions` | Spend / ledger history |
42
- | `policies get\|set` | Read or update spend policies |
43
- | `keys create\|list\|revoke` | Per-agent wallet keys |
44
- | `register <origin>` | Persist an origin into the discover/search index |
45
- | `init` | Write the `bags-cash` agent skill into this repo |
46
-
47
- Fetch / check flags: `-m/--method`, `-b/--body` (JSON string), `--max-price-cents` (fetch), `--format json|pretty`.
34
+ | Command | Description |
35
+ | --------------------------- | --------------------------------------------------------------- |
36
+ | `redeem <code>` | Redeem an invite; saves token to `~/.bags/credentials.json` |
37
+ | `redeem --test` | Mint a sandbox wallet (no invite; Base Sepolia + Solana devnet) |
38
+ | `balance` | Show balance, daily spent/cap, status |
39
+ | `discover [query]` | List BAGS merchant endpoints (BAGS first) |
40
+ | `search <query>` | NL search across BAGS + ecosystem index |
41
+ | `check <url>` | Probe schema + exact quote **without paying** |
42
+ | `fetch <url> [flags]` | Pay and proxy an x402 endpoint |
43
+ | `result <id> [flags]` | Retrieve retained output without another paid call |
44
+ | `transactions` | Spend / ledger history |
45
+ | `policies get\|set` | Read or update spend policies |
46
+ | `keys create\|list\|revoke` | Per-agent wallet keys |
47
+ | `register <origin>` | Persist an origin into the discover/search index |
48
+ | `init [flags]` | Register MCP + skill + routing rule for detected clients |
49
+ | `mcp` | Serve wallet tools over stdio (JSON-RPC on stdout) |
50
+ | `doctor` | Verify token, MCP registration, skill, and routing rule |
51
+
52
+ `init` flags: `--dry-run`, `--client cursor|claude|codex` (force-create even without a marker directory; repeatable or comma-separated), `--scope project|user` (default `user` — ambient on this machine; use `--scope project` for committable repo-local config). After a successful apply, restart the agent client, then run `bags doctor`.
53
+
54
+ Fetch / check flags: `-m/--method`, `-b/--body` (JSON string), `--max-price-cents` and `--payment-protocol x402|mpp|auto` (fetch), `--format json|pretty` (check). Result flags: `--field`, `--offset`, `--limit`.
48
55
 
49
56
  ## Env
50
57
 
@@ -56,13 +63,16 @@ Fetch / check flags: `-m/--method`, `-b/--body` (JSON string), `--max-price-cent
56
63
 
57
64
  ```bash
58
65
  export BAGS_API_URL=https://staging.getbags.app # optional
59
- npx @getbags/cash redeem BAGS-XXXX-XXXX
66
+ npx @getbags/cash redeem --test # sandbox (no invite burn)
67
+ # or: npx @getbags/cash redeem BAGS-XXXX-XXXX
60
68
  npx @getbags/cash balance
61
- npx @getbags/cash search "weather forecast"
62
- npx @getbags/cash check 'https://example.com/paid'
63
- npx @getbags/cash fetch 'https://example.com/paid' -m POST -b '{"q":"hi"}'
69
+ npx @getbags/cash discover # includes $0.01 /api/v1 showcase
70
+ npx @getbags/cash check 'https://www.getbags.app/api/v1'
71
+ npx @getbags/cash fetch 'https://www.getbags.app/api/v1'
64
72
  ```
65
73
 
74
+ After `npm i -g @getbags/cash`, `bags` is an alias for `npx @getbags/cash`.
75
+
66
76
  Payment receipt prints on stderr; the upstream body prints on stdout.
67
77
 
68
78
  ## Auth
@@ -71,34 +81,39 @@ Token at `~/.bags/credentials.json`. Hosted custody — no local private keys. M
71
81
 
72
82
  ## Publishing `@getbags/cash`
73
83
 
74
- ### Local (preferred dry-run)
84
+ Preferred path is CI via OIDC trusted publishing (no long-lived npm token).
85
+
86
+ ### CI (tag or dispatch)
75
87
 
76
- From the repo root:
88
+ GitHub Action: [`.github/workflows/publish-cash.yml`](../../.github/workflows/publish-cash.yml)
89
+
90
+ 1. Bump `version` in `packages/cli/package.json`
91
+ 2. Tag and push: `git tag cash-v0.3.1 && git push origin cash-v0.3.1` (tag must match package version)
92
+ 3. The workflow builds, tests, and `npm publish`s via GitHub Actions OIDC (`id-token: write`). Configure the trusted publisher once on npmjs.com → `@getbags/cash` → Settings → Trusted publisher (repo `getbagsapp/agentic-payments`, workflow `publish-cash.yml`).
93
+ 4. After publish it retries `npm view` until the version resolves (or fails), and reports whether provenance attestations exist.
94
+
95
+ `workflow_dispatch` defaults to dry-run. To publish manually, always pass an explicit ref (dispatch without `--ref` uses the default branch tip):
77
96
 
78
97
  ```bash
79
- bun run publish:cash --dry-run # install + build + test; never publishes
80
- bun run publish:cash # same as dry-run unless NPM_TOKEN is set
81
- bun run publish:cash --publish # requires NPM_TOKEN; runs npm publish --access public
98
+ gh workflow run publish-cash.yml --ref cash-v0.3.1 -f dry_run=false
82
99
  ```
83
100
 
84
- Or manually:
101
+ `NPM_TOKEN` is an optional emergency fallback only; OIDC is the primary auth. A missing token does not dry-run a real publish attempt — the job fails if OIDC fails and no fallback exists.
102
+
103
+ **Provenance:** OIDC trusted publishing authenticates the publisher; provenance attestations are a separate Sigstore statement. npm does not issue provenance for packages built from a **private** source repository, so `@getbags/cash` will not show a Provenance badge while `getbagsapp/agentic-payments` is private — even though trusted publishing works. Do not add `--provenance` until the source repo is public (npm rejects private-source provenance with HTTP 422). After that cutover, look for the Provenance badge on the version page and a non-404 from `https://registry.npmjs.org/-/npm/v1/attestations/@getbags%2Fcash@<version>`.
104
+
105
+ ### Local dry-run
85
106
 
86
107
  ```bash
87
- cd packages/cli
88
- bun install && bun run build && bun run test
89
- # npm publish --access public # only with npm auth for @getbags
108
+ bun run publish:cash --dry-run # install + build + test; never publishes
90
109
  ```
91
110
 
92
111
  `prepublishOnly` runs the build. Version bumps are manual in `packages/cli/package.json`.
93
112
 
94
- ### CI (tag automation)
113
+ ## Precise payments and retained output
95
114
 
96
- GitHub Action: [`.github/workflows/publish-cash.yml`](../../.github/workflows/publish-cash.yml)
97
-
98
- 1. Bump `version` in `packages/cli/package.json`
99
- 2. Tag and push: `git tag cash-v0.2.0 && git push origin cash-v0.2.0` (tag must match package version)
100
- 3. Workflow builds and tests. It publishes only when repo secret `NPM_TOKEN` is set; otherwise the job succeeds as a dry-run (no npm publish).
115
+ Public `search` and `discover` work without credentials. Paid calls still require a wallet token. Receipts preserve six-decimal USDC and fractional cents. Catalog prices are indicative; check the selected operation's schemas, mode and network before execution. Provider success and target-content success are separate facts.
101
116
 
102
- `workflow_dispatch` defaults to dry-run (build/test only). Set `dry_run=false` to attempt publish (still skips cleanly if `NPM_TOKEN` is missing).
117
+ `bags result <id> --field response.items --offset 0 --limit 20` and the `wallet_result` MCP tool read retained output without another provider call or payment. Results are private to the wallet and expire after 24 hours. Authentication fields are redacted; provider asset URLs may expire earlier. The fetch API and MCP accept `result_mode: "summary"`.
103
118
 
104
- **Do not publish** unless `NPM_TOKEN` is available and you intend a release. Scoped packages typically need an automation token with 2FA bypass for CI. Add the secret under GitHub → Settings → Secrets and variables → Actions (`NPM_TOKEN`).
119
+ MPP supports bounded one-shot `tempo:charge` with explicit chain/currency checks. Hosted session opening is disabled until durable close can be confirmed. `bags sessions list` shows existing session reservations. Hosted `sessions close` currently reports unsupported cleanup and keeps the hold; it does not report the channel closed. Existing channels and reservations remain owned when cleanup fails.
package/SKILL.md CHANGED
@@ -3,40 +3,73 @@ name: bags-cash
3
3
  description: |
4
4
  Pay x402-protected APIs with a BAGS hosted agent wallet (USDC credits, server-side policies).
5
5
  Use when the user has a BAGS invite code, wants to call paid APIs via BAGS Cash / BAGS Wallet,
6
- or mentions bags wallet / bags cash / getbags / x402 micropayments.
6
+ or needs web research, scraping, enrichment, social data, or media generation with no suitable configured tool.
7
+ Prefer the user-selected provider and existing dedicated MCP/API tools; discover paid alternatives only within the user’s task and budget.
7
8
 
8
9
  TRIGGERS: x402, paid API, bags wallet, bags cash, getbags, invite code, redeem, micropayment,
9
- agent wallet, check endpoint, wallet fetch
10
+ agent wallet, check endpoint, wallet fetch, research, scrape, enrich, social data, media generation
10
11
  homepage: https://www.getbags.app/wallet/skill.md
11
12
  metadata:
12
- version: 0.2
13
+ version: 0.3
13
14
  ---
14
15
 
15
16
  # BAGS Cash — Hosted Agent Wallet
16
17
 
17
18
  > Hosted skill URL: https://www.getbags.app/wallet/skill.md
19
+ > Human install guide: https://www.getbags.app/wallet/install
18
20
  > Onboard (humans): https://www.getbags.app/wallet/onboard
19
21
  > Staging: replace the host with `https://staging.getbags.app`.
20
22
 
21
- Call x402-protected APIs with a **hosted** BAGS wallet. Redeem an invite, discover services, **check** the exact quote + schema, then `fetch`. No local private keys — the agent holds only a token (`bag_agent_sk_*`).
23
+ Call x402-protected APIs with a **hosted** BAGS wallet. Redeem an invite (or mint a sandbox wallet), discover services, **check** the exact quote + schema, then `fetch`. No local private keys — the agent holds only a token (`bag_agent_sk_*`).
24
+
25
+ CLI examples below use `npx @getbags/cash …`. After a global install (`npm i -g @getbags/cash`), the `bags` binary is the same command.
22
26
 
23
27
  ## Why hosted (vs local-key wallets)
24
28
 
25
29
  Local plaintext / keychain wallets break in ephemeral sandboxes (Claude Cowork, CI, short-lived VMs): the key dies with the machine. BAGS keeps custody + ledger server-side. Your agent only needs the token in `~/.bags/credentials.json` (or `Authorization: Bearer`). Policies (caps, allowlists, kill switch) are enforced on the server — the agent cannot bypass them by editing a local file.
26
30
 
31
+ ## First-time setup (do in order)
32
+
33
+ 1. **Get a token** — sandbox (no invite) or live invite:
34
+
35
+ ```bash
36
+ npx @getbags/cash redeem --test
37
+ # or: npx @getbags/cash redeem <code>
38
+ ```
39
+
40
+ `redeem --test` calls `POST /api/wallet/redeem` with `{"mode":"test"}`. That mints a test wallet with fake **$100,000** promo credit, restricted to **Base Sepolia + Solana devnet**, and burns **no** invite. Use a live invite (`redeem <code>`) only when you need mainnet spend.
41
+
42
+ 2. **Register the wallet with your agent client:**
43
+
44
+ ```bash
45
+ npx @getbags/cash init
46
+ ```
47
+
48
+ Bare `init` writes **user-scope** config under `~` so the wallet is ambient on this machine (Cursor / Claude Code / Codex). Use `--scope project` to write **committable, repo-local** config (`.cursor/mcp.json`, `.mcp.json`, `AGENTS.md`, etc.) for a team. Other flags: `--dry-run`, `--client cursor|claude|codex` (force-create even without a marker directory).
49
+
50
+ 3. **Restart the agent client** (Cursor / Claude Code / Codex) so it loads the new MCP server. MCP configs are not hot-reloaded — skipping this leaves the wallet invisible.
51
+
52
+ 4. **Verify:**
53
+
54
+ ```bash
55
+ npx @getbags/cash doctor
56
+ ```
57
+
27
58
  ## Wallet
28
59
 
29
- | Task | Command |
30
- | ------------------------------ | --------------------------------- |
31
- | Redeem invite (saves token) | `npx @getbags/cash redeem <code>` |
32
- | Check balance + caps | `npx @getbags/cash balance` |
33
- | Install this skill into a repo | `npx @getbags/cash init` |
60
+ | Task | Command |
61
+ | --------------------------- | --------------------------------- |
62
+ | Sandbox wallet (no invite) | `npx @getbags/cash redeem --test` |
63
+ | Redeem invite (saves token) | `npx @getbags/cash redeem <code>` |
64
+ | One-command agent setup | `npx @getbags/cash init` |
65
+ | Verify agent visibility | `npx @getbags/cash doctor` |
66
+ | Check balance + caps | `npx @getbags/cash balance` |
34
67
 
35
- If there is no token yet, redeem first. If balance is too low, ask for another invite.
68
+ If there is no token yet, redeem (test or invite) first. If live balance is too low, ask for another invite.
36
69
 
37
70
  ## Workflow: balance → discover → check → fetch
38
71
 
39
- Match AgentCash’s agent loop; prefer this order every time.
72
+ Use existing dedicated tools first. Public search/discover works before wallet setup. For paid execution: balance → discover → check → fetch. Discovery is not permission to spend.
40
73
 
41
74
  ### 1. Balance
42
75
 
@@ -47,12 +80,12 @@ npx @getbags/cash balance
47
80
  ### 2. Discover or search
48
81
 
49
82
  ```bash
50
- npx @getbags/cash discover # BAGS merchant catalog
83
+ npx @getbags/cash discover # BAGS catalog (includes $0.01 /api/v1 showcase)
51
84
  npx @getbags/cash discover weather forecast # BAGS first, then open x402 index
52
85
  npx @getbags/cash search "send physical mail" # NL search (catalog + ecosystem)
53
86
  ```
54
87
 
55
- Prefer `source: bags` results (refund-guaranteed on BAGS merchants when that guarantee is live). Then `source: bazaar`. Default table columns: `url`, `method`, `price`, `merchant`, `source`. Use `--format json` for the raw payload (includes schemas when present).
88
+ Public catalog/search costs nothing. `GET /api/v1` is an optional paid protocol demonstration; use free `/openapi.json` for the API index. Prefer a useful task such as extracting a requested page or finding current weather for the first purchase. Compare method, required inputs, output, price ceiling, wallet mode and source coverage before selecting a provider. A catalog price is indicative; unknown pricing does not mean free. Use `--format json` to inspect schemas, freshness and source failures. Schema coverage is partial or unknown, never a guarantee of successful input/output conformance.
56
89
 
57
90
  ### 3. Check (exact quote, no payment)
58
91
 
@@ -71,9 +104,11 @@ npx @getbags/cash fetch '<url>' -m POST -b '{"key":"value"}'
71
104
  npx @getbags/cash fetch '<url>' --max-price-cents 25
72
105
  ```
73
106
 
74
- Payment receipt (amount, tx hash, network) → stderr; upstream body → stdout (pipe-friendly).
107
+ Payment receipt (exact amount, tx hash, network) → stderr; upstream body → stdout (pipe-friendly). Receipts retain `amount_atomic` (six-decimal USDC) and fractional `amount_cents`. Network fees are platform-paid and marked unmeasured unless observed; do not infer total on-chain debit from the service amount. MPP supports one-shot `tempo:charge` on permitted USDC.e chains. Hosted MPP sessions are unavailable until durable close can be confirmed.
108
+
109
+ **Latency:** wallet fetch often takes **~30–60 seconds** (paid ~30s; free/SIWX ~40–50s). Set agent-framework HTTP timeouts accordingly — do not treat a slow response as a hang until well past 60s.
75
110
 
76
- **Response envelope:** check top-level `status` / `code` first. `status: "success"` means the fetch completed as intended (including free/SIWX calls with `payment.paid: false`). A paid attempt that did not complete returns `status: "error"` with `PRE_PAYMENT_FAILED` or `NETWORK_NOT_ALLOWED` (nested `data.http_status` / `data.payment` keep the upstream diagnostic). Do not treat HTTP 200 alone as “paid OK” — and do not treat nested `http_status: 402` under `status: "success"` as a thing that happens anymore.
111
+ **Response envelope:** check top-level `status` / `code` first. `status: "success"` means the HTTP fetch completed (including free/SIWX calls with `payment.paid: false`). A paid attempt that did not complete returns `status: "error"` with `PRE_PAYMENT_FAILED` or `NETWORK_NOT_ALLOWED` (nested `data.http_status` / `data.payment` keep the upstream diagnostic). Do not treat HTTP 200 alone as “paid OK” — and do not treat nested `http_status: 402` under `status: "success"` as a thing that happens anymore.
77
112
 
78
113
  ## Other commands
79
114
 
@@ -83,6 +118,8 @@ Payment receipt (amount, tx hash, network) → stderr; upstream body → stdout
83
118
  | `policies get` / `policies set --per-call-cap-cents=50` | Read/update spend caps |
84
119
  | `keys create\|list\|revoke` | Per-agent sub-keys |
85
120
  | `register <origin>` | Register origin into discover index |
121
+ | `mcp` | Serve wallet tools over stdio |
122
+ | `doctor` | Verify wallet visibility to agents |
86
123
 
87
124
  ## Important rules
88
125
 
@@ -91,25 +128,36 @@ Payment receipt (amount, tx hash, network) → stderr; upstream body → stdout
91
128
  - **Prefer `source: bags`**, then bazaar, then any trusted x402 URL via `fetch`.
92
129
  - **Failed pre-payment requests cost nothing.** If payment settled and upstream still errored, read the stderr receipt / `transactions`.
93
130
  - **Do not invent endpoints.** Use `discover` / `search` / `check` or a URL the user provided.
131
+ - **Sandbox first.** Prefer `redeem --test` while integrating; switch to a live invite for mainnet.
94
132
 
95
133
  ## Troubleshooting
96
134
 
97
- | Code | Fix |
98
- | ----------------------------------- | ----------------------------------------------------------------------------- |
99
- | `INSUFFICIENT_BALANCE` | `bags balance` then redeem another invite |
100
- | `INVITE_INVALID` / `INVITE_EXPIRED` | Ask for a new invite code |
101
- | `URL_BLOCKED` | Use a public `https` endpoint (SSRF gate) |
102
- | `CAP_EXCEEDED` | Wait, lower `--max-price-cents`, or `policies set` |
103
- | `WALLET_SUSPENDED` | Dashboard / support |
104
- | `WALLET_FETCH_DISABLED` | Retry later (global kill switch) |
105
- | `VALIDATION_FAILED` | Fix url/method/body JSON |
106
- | `NOT_FOUND` | Route not live on this env — set `BAGS_API_URL` or upgrade |
107
- | `UPSTREAM_ERROR` / `TIMEOUT` | Retry; `bags check <url>` for a fresh quote |
108
- | `NETWORK_NOT_ALLOWED` | Test wallet vs mainnet (or vice versa) — use matching mode |
109
- | `PRE_PAYMENT_FAILED` | Payment did not broadcast; reservation reversed — fix quote / network / funds |
110
- | `AUTH_MISSING_CREDENTIALS` | `bags redeem <code>` |
135
+ | Code | Fix |
136
+ | ----------------------------------- | -------------------------------------------------------------------------------------- |
137
+ | `INSUFFICIENT_BALANCE` | `npx @getbags/cash balance` then redeem another invite (or use `redeem --test` to try) |
138
+ | `INVITE_INVALID` / `INVITE_EXPIRED` | Ask for a new invite code |
139
+ | `URL_BLOCKED` | Use a public `https` endpoint (SSRF gate) |
140
+ | `CAP_EXCEEDED` | Wait, lower `--max-price-cents`, or `policies set` |
141
+ | `WALLET_SUSPENDED` | Dashboard / support |
142
+ | `WALLET_FETCH_DISABLED` | Retry later (global kill switch) |
143
+ | `VALIDATION_FAILED` | Fix url/method/body JSON |
144
+ | `NOT_FOUND` | Route not live on this env — set `BAGS_API_URL` or upgrade |
145
+ | `UPSTREAM_ERROR` / `TIMEOUT` | Retry; `npx @getbags/cash check <url>` for a fresh quote (fetch can take 30–60s) |
146
+ | `NETWORK_NOT_ALLOWED` | Test wallet vs mainnet (or vice versa) — use matching mode |
147
+ | `PRE_PAYMENT_FAILED` | Payment did not broadcast; reservation reversed — fix quote / network / funds |
148
+ | `AUTH_MISSING_CREDENTIALS` | `npx @getbags/cash redeem --test` or `npx @getbags/cash redeem <code>` |
111
149
 
112
150
  Env: `BAGS_API_URL` (default `https://www.getbags.app`; staging example `https://staging.getbags.app`).
113
151
  Token path: `~/.bags/credentials.json`.
114
152
 
115
153
  MCP: wallet tools on `https://www.getbags.app/api/mcp/mcp` — pass `Authorization: Bearer bag_agent_sk_...` or a `wallet_token` argument on each tool.
154
+
155
+ ## Results and safe retries
156
+
157
+ Read a retained result with `bags result <id> --field response.items --offset 0 --limit 20`, or the read-only `wallet_result` MCP tool. Neither performs another paid call.
158
+
159
+ An upstream 200 can contain target-page 404 or unusable output. Inspect `outcomes` and the payload before claiming task success. Provider failures may still be billed. BAGS-network upstream 5xx may receive wallet credit; external provider billing rules apply. Unknown/submitted payment retains the reservation for reconciliation.
160
+
161
+ Reuse `Idempotency-Key` for retries (24-hour replay); use a new key for an intentional new paid call. Fetch supports `result_mode: "summary"` through the API. `data.result.retrieval_url` retrieves retained output for 24 hours without paying again, with optional `field`, `offset` and `limit`. A result handle is not an async provider poll: accepted provider runs require their provider-specific status contract, which may be paid. Do not blindly execute again. Authentication fields are redacted in retained exports; signed asset URLs may expire before result retention ends.
162
+
163
+ For a frequent origin/task, save only a thin pointer: task, origin and the current search/check workflow. Refresh live schemas and prices on use; never copy credentials or freeze endpoint contracts into a reusable skill.
package/dist/api.d.ts CHANGED
@@ -9,6 +9,10 @@ export type RedeemResponse = {
9
9
  expires: null;
10
10
  };
11
11
  export type BalanceResponse = {
12
+ wallet_id?: string;
13
+ mode?: 'live' | 'test';
14
+ credential_scope?: string;
15
+ payment_readiness?: string;
12
16
  balance_cents: number;
13
17
  daily_spent_cents: number;
14
18
  daily_cap_cents: number;
@@ -32,6 +36,7 @@ export type DiscoverEndpoint = {
32
36
  };
33
37
  };
34
38
  export type DiscoverResponse = {
39
+ coverage?: unknown;
35
40
  endpoints: DiscoverEndpoint[];
36
41
  query?: string | null;
37
42
  };
@@ -40,8 +45,16 @@ export type FetchPayment = {
40
45
  amount_cents: number;
41
46
  tx_hash: string;
42
47
  network: string;
48
+ input_schema_declared?: boolean;
49
+ upstream_4xx?: boolean;
43
50
  };
44
51
  export type FetchResponse = {
52
+ result?: unknown;
53
+ outcomes?: unknown;
54
+ billing?: unknown;
55
+ wallet_id?: string;
56
+ mode?: string;
57
+ request_id?: string;
45
58
  status: string;
46
59
  http_status: number;
47
60
  payment: FetchPayment | null;
@@ -104,7 +117,10 @@ export declare function apiRequest(path: string, options?: {
104
117
  body?: unknown;
105
118
  token?: string;
106
119
  }): Promise<Record<string, unknown>>;
107
- export declare function redeem(code: string): Promise<RedeemResponse>;
120
+ export declare function redeem(opts: {
121
+ code?: string;
122
+ mode?: 'live' | 'test';
123
+ }): Promise<RedeemResponse>;
108
124
  export declare function getBalance(token: string): Promise<BalanceResponse>;
109
125
  export declare function discover(token: string, query?: string): Promise<DiscoverResponse>;
110
126
  export declare function search(token: string, query: string): Promise<DiscoverResponse>;
@@ -120,6 +136,7 @@ export declare function walletFetch(token: string, input: {
120
136
  body?: unknown;
121
137
  max_price_cents?: number;
122
138
  payment_protocol?: 'x402' | 'mpp' | 'auto';
139
+ result_mode?: 'inline' | 'summary';
123
140
  }): Promise<FetchResponse>;
124
141
  export type MppSessionSummary = {
125
142
  id: string;
@@ -165,3 +182,10 @@ export declare function registerOrigin(token: string, origin: string, opts?: {
165
182
  label?: string;
166
183
  description?: string;
167
184
  }): Promise<Record<string, unknown>>;
185
+ /** Read an owned result; never starts another provider call or payment. */
186
+ export declare function getResult(token: string, input: {
187
+ id: string;
188
+ field?: string;
189
+ offset?: number;
190
+ limit?: number;
191
+ }): Promise<Record<string, unknown>>;
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { getBaseUrl } from './config.js';
2
- import { formatApiError } from './errors.js';
2
+ import { formatApiError, withUpstreamDetail } from './errors.js';
3
3
  export class CliApiError extends Error {
4
4
  code;
5
5
  httpStatus;
@@ -50,14 +50,17 @@ export async function apiRequest(path, options = {}) {
50
50
  if (!res.ok || json.status === 'error') {
51
51
  const code = typeof json.code === 'string' ? json.code : res.status === 404 ? 'NOT_FOUND' : `HTTP_${res.status}`;
52
52
  const message = typeof json.message === 'string' ? json.message : res.statusText || 'request failed';
53
- throw new CliApiError(code, message, res.status);
53
+ throw new CliApiError(code, withUpstreamDetail(message, json.data), res.status);
54
54
  }
55
55
  return json;
56
56
  }
57
- export async function redeem(code) {
57
+ export async function redeem(opts) {
58
+ const payload = opts.mode === 'test' && !opts.code
59
+ ? { mode: 'test' }
60
+ : { code: opts.code, ...(opts.mode ? { mode: opts.mode } : {}) };
58
61
  const body = await apiRequest('/api/wallet/redeem', {
59
62
  method: 'POST',
60
- body: { code },
63
+ body: payload,
61
64
  });
62
65
  const data = unwrapPayload(body);
63
66
  if (typeof data.token !== 'string') {
@@ -74,6 +77,10 @@ export async function getBalance(token) {
74
77
  ? body.wallet_status
75
78
  : 'unknown';
76
79
  return {
80
+ ...(data.wallet_id ? { wallet_id: data.wallet_id } : {}),
81
+ ...(data.mode ? { mode: data.mode } : {}),
82
+ ...(data.credential_scope ? { credential_scope: data.credential_scope } : {}),
83
+ ...(data.payment_readiness ? { payment_readiness: data.payment_readiness } : {}),
77
84
  balance_cents: Number(data.balance_cents),
78
85
  daily_spent_cents: Number(data.daily_spent_cents),
79
86
  daily_cap_cents: Number(data.daily_cap_cents),
@@ -86,6 +93,7 @@ export async function discover(token, query) {
86
93
  const body = await apiRequest(path, { token });
87
94
  const data = unwrapPayload(body);
88
95
  return {
96
+ ...(data.coverage ? { coverage: data.coverage } : {}),
89
97
  endpoints: Array.isArray(data.endpoints) ? data.endpoints : [],
90
98
  query: data.query,
91
99
  };
@@ -95,6 +103,7 @@ export async function search(token, query) {
95
103
  const body = await apiRequest(path, { token });
96
104
  const data = unwrapPayload(body);
97
105
  return {
106
+ ...(data.coverage ? { coverage: data.coverage } : {}),
98
107
  endpoints: Array.isArray(data.endpoints) ? data.endpoints : [],
99
108
  query: typeof data.query === 'string' ? data.query : query,
100
109
  };
@@ -115,6 +124,7 @@ export async function walletFetch(token, input) {
115
124
  });
116
125
  const data = unwrapPayload(body);
117
126
  return {
127
+ ...data,
118
128
  status: typeof data.status === 'string' ? data.status : 'success',
119
129
  http_status: Number(data.http_status),
120
130
  payment: data.payment ?? null,
@@ -233,3 +243,14 @@ export async function registerOrigin(token, origin, opts = {}) {
233
243
  });
234
244
  return unwrapPayload(body);
235
245
  }
246
+ /** Read an owned result; never starts another provider call or payment. */
247
+ export async function getResult(token, input) {
248
+ const query = new URLSearchParams({ id: input.id });
249
+ if (input.field)
250
+ query.set('field', input.field);
251
+ if (input.offset !== undefined)
252
+ query.set('offset', String(input.offset));
253
+ if (input.limit !== undefined)
254
+ query.set('limit', String(input.limit));
255
+ return unwrapPayload(await apiRequest(`/api/wallet/results?${query}`, { token }));
256
+ }
@@ -0,0 +1,33 @@
1
+ export type ClientId = 'cursor' | 'claude' | 'codex';
2
+ export type InstallScope = 'project' | 'user';
3
+ export type McpConfigFormat = 'json' | 'claude-json' | 'toml';
4
+ export type ClientPaths = {
5
+ client: ClientId;
6
+ mcpConfigPath: string;
7
+ mcpConfigFormat: McpConfigFormat;
8
+ /** Parent skills directory; skill file lives at `${skillsDir}/bags-cash/SKILL.md`. */
9
+ skillsDir: string;
10
+ instructionsPath: string;
11
+ };
12
+ export type DetectedEnvironment = {
13
+ scope: InstallScope;
14
+ clients: ClientPaths[];
15
+ /** Absolute marker paths that were probed (for no-client diagnostics). */
16
+ lookedFor: string[];
17
+ };
18
+ export type FsExists = (path: string) => boolean;
19
+ export type DetectOptions = {
20
+ cwd: string;
21
+ home: string;
22
+ scope: InstallScope;
23
+ exists: FsExists;
24
+ requestedClients?: ClientId[];
25
+ };
26
+ /**
27
+ * Detect every supported agent client whose marker path exists.
28
+ * Detect-all (not first-match-wins). Pure: filesystem access is injected.
29
+ *
30
+ * Explicit `requestedClients` (`--client`) is user intent: include those clients
31
+ * even when their marker directory is missing, so init can force-create config.
32
+ */
33
+ export declare function detectClients(opts: DetectOptions): DetectedEnvironment;
package/dist/detect.js ADDED
@@ -0,0 +1,105 @@
1
+ import { join } from 'node:path';
2
+ const ALL_CLIENTS = ['cursor', 'claude', 'codex'];
3
+ function projectPaths(cwd, client) {
4
+ switch (client) {
5
+ case 'cursor':
6
+ return {
7
+ client,
8
+ mcpConfigPath: join(cwd, '.cursor', 'mcp.json'),
9
+ mcpConfigFormat: 'json',
10
+ skillsDir: join(cwd, '.cursor', 'skills'),
11
+ instructionsPath: join(cwd, 'AGENTS.md'),
12
+ };
13
+ case 'claude':
14
+ return {
15
+ client,
16
+ mcpConfigPath: join(cwd, '.mcp.json'),
17
+ mcpConfigFormat: 'json',
18
+ skillsDir: join(cwd, '.claude', 'skills'),
19
+ instructionsPath: join(cwd, 'CLAUDE.md'),
20
+ };
21
+ case 'codex':
22
+ return {
23
+ client,
24
+ mcpConfigPath: join(cwd, '.codex', 'config.toml'),
25
+ mcpConfigFormat: 'toml',
26
+ skillsDir: join(cwd, '.agents', 'skills'),
27
+ instructionsPath: join(cwd, 'AGENTS.md'),
28
+ };
29
+ }
30
+ }
31
+ function userPaths(home, client) {
32
+ switch (client) {
33
+ case 'cursor':
34
+ return {
35
+ client,
36
+ mcpConfigPath: join(home, '.cursor', 'mcp.json'),
37
+ mcpConfigFormat: 'json',
38
+ skillsDir: join(home, '.cursor', 'skills'),
39
+ // Assumed: Cursor has no documented global AGENTS.md; we use this path
40
+ // so user-scope installs still get a fenced routing rule on disk.
41
+ instructionsPath: join(home, '.cursor', 'AGENTS.md'),
42
+ };
43
+ case 'claude':
44
+ return {
45
+ client,
46
+ mcpConfigPath: join(home, '.claude.json'),
47
+ mcpConfigFormat: 'claude-json',
48
+ skillsDir: join(home, '.claude', 'skills'),
49
+ instructionsPath: join(home, '.claude', 'CLAUDE.md'),
50
+ };
51
+ case 'codex':
52
+ return {
53
+ client,
54
+ mcpConfigPath: join(home, '.codex', 'config.toml'),
55
+ mcpConfigFormat: 'toml',
56
+ skillsDir: join(home, '.agents', 'skills'),
57
+ instructionsPath: join(home, '.codex', 'AGENTS.md'),
58
+ };
59
+ }
60
+ }
61
+ function markerPaths(cwd, home, scope, client) {
62
+ if (scope === 'project') {
63
+ switch (client) {
64
+ case 'cursor':
65
+ return [join(cwd, '.cursor')];
66
+ case 'claude':
67
+ return [join(cwd, '.claude')];
68
+ case 'codex':
69
+ return [join(cwd, '.codex')];
70
+ }
71
+ }
72
+ switch (client) {
73
+ case 'cursor':
74
+ return [join(home, '.cursor')];
75
+ case 'claude':
76
+ // Prefer the config dir; also accept the standalone user config file.
77
+ return [join(home, '.claude'), join(home, '.claude.json')];
78
+ case 'codex':
79
+ return [join(home, '.codex')];
80
+ }
81
+ }
82
+ /**
83
+ * Detect every supported agent client whose marker path exists.
84
+ * Detect-all (not first-match-wins). Pure: filesystem access is injected.
85
+ *
86
+ * Explicit `requestedClients` (`--client`) is user intent: include those clients
87
+ * even when their marker directory is missing, so init can force-create config.
88
+ */
89
+ export function detectClients(opts) {
90
+ const forced = Boolean(opts.requestedClients?.length);
91
+ const wanted = forced ? ALL_CLIENTS.filter((id) => opts.requestedClients.includes(id)) : ALL_CLIENTS;
92
+ const lookedFor = [];
93
+ const clients = [];
94
+ for (const client of wanted) {
95
+ const markers = markerPaths(opts.cwd, opts.home, opts.scope, client);
96
+ for (const marker of markers) {
97
+ if (!lookedFor.includes(marker))
98
+ lookedFor.push(marker);
99
+ }
100
+ if (forced || markers.some((m) => opts.exists(m))) {
101
+ clients.push(opts.scope === 'project' ? projectPaths(opts.cwd, client) : userPaths(opts.home, client));
102
+ }
103
+ }
104
+ return { scope: opts.scope, clients, lookedFor };
105
+ }