@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 +53 -38
- package/SKILL.md +77 -29
- package/dist/api.d.ts +25 -1
- package/dist/api.js +25 -4
- package/dist/detect.d.ts +33 -0
- package/dist/detect.js +105 -0
- package/dist/doctor.d.ts +47 -0
- package/dist/doctor.js +212 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +18 -0
- package/dist/fetch-hints.d.ts +18 -0
- package/dist/fetch-hints.js +17 -0
- package/dist/index.js +68 -50
- package/dist/install-apply.d.ts +29 -0
- package/dist/install-apply.js +55 -0
- package/dist/install-command.d.ts +17 -0
- package/dist/install-command.js +173 -0
- package/dist/install-plan.d.ts +68 -0
- package/dist/install-plan.js +337 -0
- package/dist/mcp-instructions.d.ts +7 -0
- package/dist/mcp-instructions.js +38 -0
- package/dist/mcp-server.d.ts +72 -0
- package/dist/mcp-server.js +293 -0
- package/dist/parse.d.ts +22 -1
- package/dist/parse.js +275 -14
- package/dist/policies.d.ts +26 -0
- package/dist/policies.js +69 -0
- package/package.json +10 -5
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
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
|
62
|
-
npx @getbags/cash check 'https://
|
|
63
|
-
npx @getbags/cash fetch 'https://
|
|
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
|
-
|
|
84
|
+
Preferred path is CI via OIDC trusted publishing (no long-lived npm token).
|
|
85
|
+
|
|
86
|
+
### CI (tag or dispatch)
|
|
75
87
|
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
## Precise payments and retained output
|
|
95
114
|
|
|
96
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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` | `
|
|
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; `
|
|
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` | `
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
+
}
|
package/dist/detect.d.ts
ADDED
|
@@ -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
|
+
}
|