@getbags/cash 0.3.0 → 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 +27 -20
- package/SKILL.md +18 -7
- package/dist/api.d.ts +21 -0
- package/dist/api.js +20 -2
- 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 +23 -23
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +24 -7
- package/dist/parse.d.ts +9 -0
- package/dist/parse.js +67 -3
- package/dist/policies.d.ts +26 -0
- package/dist/policies.js +69 -0
- package/package.json +7 -6
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
|
|
|
@@ -39,6 +40,7 @@ Staging: use `https://staging.getbags.app/wallet/skill.md` (or set `BAGS_API_URL
|
|
|
39
40
|
| `search <query>` | NL search across BAGS + ecosystem index |
|
|
40
41
|
| `check <url>` | Probe schema + exact quote **without paying** |
|
|
41
42
|
| `fetch <url> [flags]` | Pay and proxy an x402 endpoint |
|
|
43
|
+
| `result <id> [flags]` | Retrieve retained output without another paid call |
|
|
42
44
|
| `transactions` | Spend / ledger history |
|
|
43
45
|
| `policies get\|set` | Read or update spend policies |
|
|
44
46
|
| `keys create\|list\|revoke` | Per-agent wallet keys |
|
|
@@ -49,7 +51,7 @@ Staging: use `https://staging.getbags.app/wallet/skill.md` (or set `BAGS_API_URL
|
|
|
49
51
|
|
|
50
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`.
|
|
51
53
|
|
|
52
|
-
Fetch / check flags: `-m/--method`, `-b/--body` (JSON string), `--max-price-cents` (fetch), `--format json|pretty`.
|
|
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`.
|
|
53
55
|
|
|
54
56
|
## Env
|
|
55
57
|
|
|
@@ -79,34 +81,39 @@ Token at `~/.bags/credentials.json`. Hosted custody — no local private keys. M
|
|
|
79
81
|
|
|
80
82
|
## Publishing `@getbags/cash`
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
Preferred path is CI via OIDC trusted publishing (no long-lived npm token).
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
### CI (tag or dispatch)
|
|
87
|
+
|
|
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):
|
|
85
96
|
|
|
86
97
|
```bash
|
|
87
|
-
|
|
88
|
-
bun run publish:cash # same as dry-run unless NPM_TOKEN is set
|
|
89
|
-
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
|
|
90
99
|
```
|
|
91
100
|
|
|
92
|
-
|
|
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
|
|
93
106
|
|
|
94
107
|
```bash
|
|
95
|
-
|
|
96
|
-
bun install && bun run build && bun run test
|
|
97
|
-
# npm publish --access public # only with npm auth for @getbags
|
|
108
|
+
bun run publish:cash --dry-run # install + build + test; never publishes
|
|
98
109
|
```
|
|
99
110
|
|
|
100
111
|
`prepublishOnly` runs the build. Version bumps are manual in `packages/cli/package.json`.
|
|
101
112
|
|
|
102
|
-
|
|
113
|
+
## Precise payments and retained output
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
1. Bump `version` in `packages/cli/package.json`
|
|
107
|
-
2. Tag and push: `git tag cash-v0.2.0 && git push origin cash-v0.2.0` (tag must match package version)
|
|
108
|
-
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.
|
|
109
116
|
|
|
110
|
-
`
|
|
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"`.
|
|
111
118
|
|
|
112
|
-
|
|
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,13 +3,14 @@ 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
|
|
@@ -68,7 +69,7 @@ If there is no token yet, redeem (test or invite) first. If live balance is too
|
|
|
68
69
|
|
|
69
70
|
## Workflow: balance → discover → check → fetch
|
|
70
71
|
|
|
71
|
-
|
|
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.
|
|
72
73
|
|
|
73
74
|
### 1. Balance
|
|
74
75
|
|
|
@@ -84,7 +85,7 @@ npx @getbags/cash discover weather forecast # BAGS first, then open x402 index
|
|
|
84
85
|
npx @getbags/cash search "send physical mail" # NL search (catalog + ecosystem)
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
|
|
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.
|
|
88
89
|
|
|
89
90
|
### 3. Check (exact quote, no payment)
|
|
90
91
|
|
|
@@ -103,11 +104,11 @@ npx @getbags/cash fetch '<url>' -m POST -b '{"key":"value"}'
|
|
|
103
104
|
npx @getbags/cash fetch '<url>' --max-price-cents 25
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
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.
|
|
107
108
|
|
|
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.
|
|
109
110
|
|
|
110
|
-
**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.
|
|
111
112
|
|
|
112
113
|
## Other commands
|
|
113
114
|
|
|
@@ -150,3 +151,13 @@ Env: `BAGS_API_URL` (default `https://www.getbags.app`; staging example `https:/
|
|
|
150
151
|
Token path: `~/.bags/credentials.json`.
|
|
151
152
|
|
|
152
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;
|
|
@@ -123,6 +136,7 @@ export declare function walletFetch(token: string, input: {
|
|
|
123
136
|
body?: unknown;
|
|
124
137
|
max_price_cents?: number;
|
|
125
138
|
payment_protocol?: 'x402' | 'mpp' | 'auto';
|
|
139
|
+
result_mode?: 'inline' | 'summary';
|
|
126
140
|
}): Promise<FetchResponse>;
|
|
127
141
|
export type MppSessionSummary = {
|
|
128
142
|
id: string;
|
|
@@ -168,3 +182,10 @@ export declare function registerOrigin(token: string, origin: string, opts?: {
|
|
|
168
182
|
label?: string;
|
|
169
183
|
description?: string;
|
|
170
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,7 +50,7 @@ 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
|
}
|
|
@@ -77,6 +77,10 @@ export async function getBalance(token) {
|
|
|
77
77
|
? body.wallet_status
|
|
78
78
|
: 'unknown';
|
|
79
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 } : {}),
|
|
80
84
|
balance_cents: Number(data.balance_cents),
|
|
81
85
|
daily_spent_cents: Number(data.daily_spent_cents),
|
|
82
86
|
daily_cap_cents: Number(data.daily_cap_cents),
|
|
@@ -89,6 +93,7 @@ export async function discover(token, query) {
|
|
|
89
93
|
const body = await apiRequest(path, { token });
|
|
90
94
|
const data = unwrapPayload(body);
|
|
91
95
|
return {
|
|
96
|
+
...(data.coverage ? { coverage: data.coverage } : {}),
|
|
92
97
|
endpoints: Array.isArray(data.endpoints) ? data.endpoints : [],
|
|
93
98
|
query: data.query,
|
|
94
99
|
};
|
|
@@ -98,6 +103,7 @@ export async function search(token, query) {
|
|
|
98
103
|
const body = await apiRequest(path, { token });
|
|
99
104
|
const data = unwrapPayload(body);
|
|
100
105
|
return {
|
|
106
|
+
...(data.coverage ? { coverage: data.coverage } : {}),
|
|
101
107
|
endpoints: Array.isArray(data.endpoints) ? data.endpoints : [],
|
|
102
108
|
query: typeof data.query === 'string' ? data.query : query,
|
|
103
109
|
};
|
|
@@ -118,6 +124,7 @@ export async function walletFetch(token, input) {
|
|
|
118
124
|
});
|
|
119
125
|
const data = unwrapPayload(body);
|
|
120
126
|
return {
|
|
127
|
+
...data,
|
|
121
128
|
status: typeof data.status === 'string' ? data.status : 'success',
|
|
122
129
|
http_status: Number(data.http_status),
|
|
123
130
|
payment: data.payment ?? null,
|
|
@@ -236,3 +243,14 @@ export async function registerOrigin(token, origin, opts = {}) {
|
|
|
236
243
|
});
|
|
237
244
|
return unwrapPayload(body);
|
|
238
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/errors.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
export declare const ERROR_REMEDIES: Record<string, string>;
|
|
2
2
|
export declare function formatApiError(code: string, message: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Surface the wallet API's nested upstream diagnostic (data.http_status +
|
|
5
|
+
* data.response) so PRE_PAYMENT_FAILED failures are legible from the CLI
|
|
6
|
+
* instead of requiring a raw API call to see why a merchant rejected payment.
|
|
7
|
+
*/
|
|
8
|
+
export declare function withUpstreamDetail(message: string, data: unknown): string;
|
package/dist/errors.js
CHANGED
|
@@ -26,3 +26,21 @@ export function formatApiError(code, message) {
|
|
|
26
26
|
const line = `${code}: ${message}`;
|
|
27
27
|
return remedy ? `${line}\n${remedy}` : line;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Surface the wallet API's nested upstream diagnostic (data.http_status +
|
|
31
|
+
* data.response) so PRE_PAYMENT_FAILED failures are legible from the CLI
|
|
32
|
+
* instead of requiring a raw API call to see why a merchant rejected payment.
|
|
33
|
+
*/
|
|
34
|
+
export function withUpstreamDetail(message, data) {
|
|
35
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
36
|
+
return message;
|
|
37
|
+
const record = data;
|
|
38
|
+
const status = record.http_status;
|
|
39
|
+
const response = record.response;
|
|
40
|
+
if (status === undefined && response === undefined)
|
|
41
|
+
return message;
|
|
42
|
+
const body = typeof response === 'string' ? response : response ? JSON.stringify(response) : '';
|
|
43
|
+
const head = status !== undefined ? `upstream http ${String(status)}` : 'upstream';
|
|
44
|
+
const detail = body ? `${head}: ${body.slice(0, 300)}` : head;
|
|
45
|
+
return `${message} (${detail})`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch output diagnostics for failed paid attempts.
|
|
3
|
+
*
|
|
4
|
+
* A paid call can settle on-chain and then hit a merchant 4xx (e.g. a 422 for
|
|
5
|
+
* a bad request param) — money gone with nothing to show. When the 402
|
|
6
|
+
* challenge declared a bazaar input schema, point the agent at `bags check`
|
|
7
|
+
* so the retry carries the required fields.
|
|
8
|
+
*/
|
|
9
|
+
export declare const INPUT_SCHEMA_HINT = "this endpoint declares an input schema - run `bags check` to see required fields";
|
|
10
|
+
/** True when payment settled but the upstream response was an error status. */
|
|
11
|
+
export declare function isFailedPaidAttempt(payment: {
|
|
12
|
+
paid?: boolean;
|
|
13
|
+
} | null | undefined, httpStatus: number): boolean;
|
|
14
|
+
/** Should `bags fetch` print the input-schema hint for this fetch result? */
|
|
15
|
+
export declare function shouldPrintInputSchemaHint(payment: {
|
|
16
|
+
paid?: boolean;
|
|
17
|
+
input_schema_declared?: boolean;
|
|
18
|
+
} | null | undefined, httpStatus: number): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch output diagnostics for failed paid attempts.
|
|
3
|
+
*
|
|
4
|
+
* A paid call can settle on-chain and then hit a merchant 4xx (e.g. a 422 for
|
|
5
|
+
* a bad request param) — money gone with nothing to show. When the 402
|
|
6
|
+
* challenge declared a bazaar input schema, point the agent at `bags check`
|
|
7
|
+
* so the retry carries the required fields.
|
|
8
|
+
*/
|
|
9
|
+
export const INPUT_SCHEMA_HINT = 'this endpoint declares an input schema - run `bags check` to see required fields';
|
|
10
|
+
/** True when payment settled but the upstream response was an error status. */
|
|
11
|
+
export function isFailedPaidAttempt(payment, httpStatus) {
|
|
12
|
+
return payment?.paid === true && httpStatus >= 400;
|
|
13
|
+
}
|
|
14
|
+
/** Should `bags fetch` print the input-schema hint for this fetch result? */
|
|
15
|
+
export function shouldPrintInputSchemaHint(payment, httpStatus) {
|
|
16
|
+
return isFailedPaidAttempt(payment, httpStatus) && payment?.input_schema_declared === true;
|
|
17
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { checkEndpoint, createDeposit, createKey, discover, getBalance, getFund, getPolicies, getTransactions, listKeys, redeem, revokeKey, registerOrigin, search, setPolicies, listSessions, closeSessions, walletFetch, CliApiError, } from './api.js';
|
|
3
|
-
import { getBaseUrl, requireToken, saveCredentials } from './config.js';
|
|
2
|
+
import { checkEndpoint, createDeposit, createKey, discover, getBalance, getResult, getFund, getPolicies, getTransactions, listKeys, redeem, revokeKey, registerOrigin, search, setPolicies, listSessions, closeSessions, walletFetch, CliApiError, } from './api.js';
|
|
3
|
+
import { getBaseUrl, loadCredentials, requireToken, saveCredentials } from './config.js';
|
|
4
|
+
import { INPUT_SCHEMA_HINT, shouldPrintInputSchemaHint } from './fetch-hints.js';
|
|
4
5
|
import { formatDoctorSummary, runDoctor } from './doctor.js';
|
|
5
6
|
import { runInitCommand } from './install-command.js';
|
|
6
7
|
import { runStdioServer } from './mcp-server.js';
|
|
8
|
+
import { policyPatchFromSets } from './policies.js';
|
|
7
9
|
import { parseArgs, USAGE } from './parse.js';
|
|
8
10
|
function centsToDollars(cents) {
|
|
9
|
-
return `$${(cents / 100).toFixed(2)}`;
|
|
11
|
+
return `$${(cents / 100).toFixed(Number.isInteger(cents) ? 2 : 6).replace(/(\.\d{2,}?)0+$/, '$1')}`;
|
|
10
12
|
}
|
|
11
13
|
function fail(message) {
|
|
12
14
|
console.error(message);
|
|
@@ -20,7 +22,9 @@ function printDiscoverTable(endpoints) {
|
|
|
20
22
|
const rows = endpoints.map((ep) => ({
|
|
21
23
|
url: ep.url,
|
|
22
24
|
method: ep.method,
|
|
23
|
-
price: typeof ep.price_usd === 'number'
|
|
25
|
+
price: typeof ep.price_usd === 'number'
|
|
26
|
+
? `$${ep.price_usd.toFixed(6).replace(/(\.\d{2,}?)0+$/, '$1')}`
|
|
27
|
+
: String(ep.price_usd),
|
|
24
28
|
merchant: ep.merchant_name ?? '',
|
|
25
29
|
source: ep.source ?? 'bags',
|
|
26
30
|
}));
|
|
@@ -36,28 +40,16 @@ function printDiscoverTable(endpoints) {
|
|
|
36
40
|
function printJson(data) {
|
|
37
41
|
console.log(JSON.stringify(data, null, 2));
|
|
38
42
|
}
|
|
39
|
-
function policyPatchFromSets(sets) {
|
|
40
|
-
const patch = {};
|
|
41
|
-
for (const [key, value] of Object.entries(sets)) {
|
|
42
|
-
const camel = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
43
|
-
const num = Number(value);
|
|
44
|
-
patch[camel] =
|
|
45
|
-
Number.isFinite(num) && String(num) === value
|
|
46
|
-
? num
|
|
47
|
-
: value === 'true'
|
|
48
|
-
? true
|
|
49
|
-
: value === 'false'
|
|
50
|
-
? false
|
|
51
|
-
: value;
|
|
52
|
-
}
|
|
53
|
-
return patch;
|
|
54
|
-
}
|
|
55
43
|
async function main() {
|
|
56
44
|
const parsed = parseArgs(process.argv.slice(2));
|
|
57
45
|
if (parsed.command === 'help') {
|
|
58
46
|
console.log(USAGE);
|
|
59
47
|
return;
|
|
60
48
|
}
|
|
49
|
+
if (parsed.command === 'usage') {
|
|
50
|
+
console.log(parsed.message);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
61
53
|
if (parsed.command === 'error') {
|
|
62
54
|
fail(parsed.message);
|
|
63
55
|
}
|
|
@@ -88,7 +80,7 @@ async function main() {
|
|
|
88
80
|
return;
|
|
89
81
|
}
|
|
90
82
|
case 'discover': {
|
|
91
|
-
const result = await discover(
|
|
83
|
+
const result = await discover(loadCredentials()?.token ?? '', parsed.query);
|
|
92
84
|
if (parsed.format === 'json') {
|
|
93
85
|
printJson(result);
|
|
94
86
|
}
|
|
@@ -98,7 +90,7 @@ async function main() {
|
|
|
98
90
|
return;
|
|
99
91
|
}
|
|
100
92
|
case 'search': {
|
|
101
|
-
const result = await search(
|
|
93
|
+
const result = await search(loadCredentials()?.token ?? '', parsed.query);
|
|
102
94
|
if (parsed.format === 'json') {
|
|
103
95
|
printJson(result);
|
|
104
96
|
}
|
|
@@ -165,7 +157,11 @@ async function main() {
|
|
|
165
157
|
payment_protocol: parsed.paymentProtocol,
|
|
166
158
|
});
|
|
167
159
|
if (result.payment?.paid) {
|
|
168
|
-
|
|
160
|
+
const upstreamNote = result.http_status >= 400 ? ` | upstream HTTP ${result.http_status}` : '';
|
|
161
|
+
console.error(`payment: ${centsToDollars(result.payment.amount_cents)} | tx ${result.payment.tx_hash} | ${result.payment.network}${upstreamNote}`);
|
|
162
|
+
if (shouldPrintInputSchemaHint(result.payment, result.http_status)) {
|
|
163
|
+
console.error(INPUT_SCHEMA_HINT);
|
|
164
|
+
}
|
|
169
165
|
}
|
|
170
166
|
else if (result.payment) {
|
|
171
167
|
console.error(`payment: not charged | tx ${result.payment.tx_hash || 'n/a'} | ${result.payment.network || 'n/a'}`);
|
|
@@ -229,6 +225,10 @@ async function main() {
|
|
|
229
225
|
}
|
|
230
226
|
return;
|
|
231
227
|
}
|
|
228
|
+
case 'result': {
|
|
229
|
+
printJson(await getResult(requireToken(), parsed));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
232
|
case 'transactions': {
|
|
233
233
|
try {
|
|
234
234
|
const result = await getTransactions(requireToken(), {
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { type CheckResponse, type DiscoverResponse, type FetchResponse, type BalanceResponse, type PoliciesResponse, type TransactionsResponse, type RedeemResponse, type MppSessionSummary } from './api.js';
|
|
3
3
|
export declare const WALLET_MCP_TOOL_NAMES: readonly ["wallet_balance", "wallet_discover", "wallet_search", "wallet_check_endpoint", "wallet_fetch", "wallet_sessions_list", "wallet_sessions_close", "wallet_sessions_tick", "wallet_redeem", "wallet_transactions", "wallet_get_settings", "wallet_update_settings"];
|
|
4
4
|
export type WalletMcpApi = {
|
|
5
|
+
getResult: (token: string, input: {
|
|
6
|
+
id: string;
|
|
7
|
+
field?: string;
|
|
8
|
+
offset?: number;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}) => Promise<Record<string, unknown>>;
|
|
5
11
|
getBalance: (token: string) => Promise<BalanceResponse>;
|
|
6
12
|
discover: (token: string, query?: string) => Promise<DiscoverResponse>;
|
|
7
13
|
search: (token: string, query: string) => Promise<DiscoverResponse>;
|
|
@@ -17,6 +23,7 @@ export type WalletMcpApi = {
|
|
|
17
23
|
body?: unknown;
|
|
18
24
|
max_price_cents?: number;
|
|
19
25
|
payment_protocol?: 'x402' | 'mpp' | 'auto';
|
|
26
|
+
result_mode?: 'inline' | 'summary';
|
|
20
27
|
}) => Promise<FetchResponse>;
|
|
21
28
|
listSessions: (token: string) => Promise<{
|
|
22
29
|
sessions: MppSessionSummary[];
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { z } from 'zod';
|
|
4
|
-
import { CliApiError, apiRequest, checkEndpoint, closeSessions, discover, getBalance, getPolicies, getTransactions, listSessions, redeem, search, setPolicies, walletFetch, } from './api.js';
|
|
5
|
+
import { CliApiError, apiRequest, checkEndpoint, closeSessions, discover, getBalance, getResult, getPolicies, getTransactions, listSessions, redeem, search, setPolicies, walletFetch, } from './api.js';
|
|
5
6
|
import { loadCredentials, saveCredentials as defaultSaveCredentials } from './config.js';
|
|
6
7
|
import { WALLET_MCP_INSTRUCTIONS } from './mcp-instructions.js';
|
|
7
8
|
export const WALLET_MCP_TOOL_NAMES = [
|
|
@@ -47,6 +48,7 @@ function missingTokenResult() {
|
|
|
47
48
|
function resolveApi(partial) {
|
|
48
49
|
return {
|
|
49
50
|
getBalance,
|
|
51
|
+
getResult,
|
|
50
52
|
discover,
|
|
51
53
|
search,
|
|
52
54
|
checkEndpoint,
|
|
@@ -73,7 +75,8 @@ function resolveToken(deps) {
|
|
|
73
75
|
export function createWalletMcpServer(deps = {}) {
|
|
74
76
|
const api = resolveApi(deps.api);
|
|
75
77
|
const persistCredentials = deps.saveCredentials ?? defaultSaveCredentials;
|
|
76
|
-
const
|
|
78
|
+
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
79
|
+
const server = new McpServer({ name: 'bags-cash', version }, { instructions: WALLET_MCP_INSTRUCTIONS });
|
|
77
80
|
async function withToken(run) {
|
|
78
81
|
const token = resolveToken(deps);
|
|
79
82
|
if (!token)
|
|
@@ -104,10 +107,11 @@ export function createWalletMcpServer(deps = {}) {
|
|
|
104
107
|
query: z.string().optional().describe('Optional search keywords'),
|
|
105
108
|
},
|
|
106
109
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
107
|
-
}, async (args) =>
|
|
110
|
+
}, async (args) => (async () => {
|
|
111
|
+
const token = resolveToken(deps) ?? '';
|
|
108
112
|
const q = typeof args.query === 'string' ? args.query.trim() : '';
|
|
109
113
|
return textContent(await api.discover(token, q || undefined));
|
|
110
|
-
}));
|
|
114
|
+
})());
|
|
111
115
|
server.registerTool('wallet_search', {
|
|
112
116
|
title: 'Search payable endpoints',
|
|
113
117
|
description: 'Natural-language search for x402 endpoints. BAGS catalog first, best-effort ecosystem (Bazaar) second. Returns price, description, schemas when available.',
|
|
@@ -115,7 +119,7 @@ export function createWalletMcpServer(deps = {}) {
|
|
|
115
119
|
query: z.string().min(1).describe('Natural-language or keyword query'),
|
|
116
120
|
},
|
|
117
121
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
118
|
-
}, async (args) =>
|
|
122
|
+
}, async (args) => textContent(await api.search(resolveToken(deps) ?? '', String(args.query ?? '').trim())));
|
|
119
123
|
server.registerTool('wallet_check_endpoint', {
|
|
120
124
|
title: 'Check endpoint quote',
|
|
121
125
|
description: 'Probe an x402 URL for schema + exact price quote without paying (AgentCash check parity). Use before wallet_fetch.',
|
|
@@ -130,13 +134,25 @@ export function createWalletMcpServer(deps = {}) {
|
|
|
130
134
|
method: typeof args.method === 'string' ? args.method : undefined,
|
|
131
135
|
body: args.body,
|
|
132
136
|
}))));
|
|
137
|
+
server.registerTool('wallet_result', {
|
|
138
|
+
title: 'Read retained wallet result',
|
|
139
|
+
description: 'Retrieve full output, fields or array pages from a previous wallet_fetch result for 24 hours. Does not execute or charge again. Provider asset URLs may expire earlier.',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
id: z.string().uuid(),
|
|
142
|
+
field: z.string().max(200).optional(),
|
|
143
|
+
offset: z.number().int().nonnegative().optional(),
|
|
144
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
145
|
+
},
|
|
146
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
147
|
+
}, async (args) => withToken(async (token) => textContent(await api.getResult(token, args))));
|
|
133
148
|
server.registerTool('wallet_fetch', {
|
|
134
149
|
title: 'Wallet fetch (pay + proxy)',
|
|
135
|
-
description: 'Pay for
|
|
150
|
+
description: 'Pay for an x402 or one-shot MPP tempo:charge HTTP request with the hosted wallet. Check the quote and wallet mode first. Hosted MPP sessions are currently unavailable.',
|
|
136
151
|
inputSchema: {
|
|
137
152
|
url: z.string().url(),
|
|
138
153
|
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD']).optional(),
|
|
139
154
|
body: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
|
155
|
+
result_mode: z.enum(['inline', 'summary']).optional(),
|
|
140
156
|
max_price_cents: z.number().int().positive().optional(),
|
|
141
157
|
payment_protocol: z
|
|
142
158
|
.enum(['x402', 'mpp', 'auto'])
|
|
@@ -148,6 +164,7 @@ export function createWalletMcpServer(deps = {}) {
|
|
|
148
164
|
url: String(args.url),
|
|
149
165
|
method: typeof args.method === 'string' ? args.method : undefined,
|
|
150
166
|
body: args.body,
|
|
167
|
+
result_mode: args.result_mode,
|
|
151
168
|
max_price_cents: typeof args.max_price_cents === 'number' ? args.max_price_cents : undefined,
|
|
152
169
|
payment_protocol: args.payment_protocol === 'x402' ||
|
|
153
170
|
args.payment_protocol === 'mpp' ||
|
|
@@ -165,7 +182,7 @@ export function createWalletMcpServer(deps = {}) {
|
|
|
165
182
|
}, async () => withToken(async (token) => textContent(await api.listSessions(token))));
|
|
166
183
|
server.registerTool('wallet_sessions_close', {
|
|
167
184
|
title: 'Close MPP session',
|
|
168
|
-
description: '
|
|
185
|
+
description: 'Request cleanup of existing MPP sessions. On-chain close is currently unsupported: the call reports failure and preserves the reservation and ownership. New sessions cannot be opened.',
|
|
169
186
|
inputSchema: {
|
|
170
187
|
session_id: z.string().uuid().optional(),
|
|
171
188
|
channel_id: z.string().min(1).optional(),
|
package/dist/parse.d.ts
CHANGED
|
@@ -2,12 +2,21 @@ import type { ClientId, InstallScope } from './detect.js';
|
|
|
2
2
|
export type PaymentProtocol = 'x402' | 'mpp' | 'auto';
|
|
3
3
|
export type ParsedArgs = {
|
|
4
4
|
command: 'help';
|
|
5
|
+
} | {
|
|
6
|
+
command: 'usage';
|
|
7
|
+
message: string;
|
|
5
8
|
} | {
|
|
6
9
|
command: 'redeem';
|
|
7
10
|
code?: string;
|
|
8
11
|
test: boolean;
|
|
9
12
|
} | {
|
|
10
13
|
command: 'balance';
|
|
14
|
+
} | {
|
|
15
|
+
command: 'result';
|
|
16
|
+
id: string;
|
|
17
|
+
field?: string;
|
|
18
|
+
offset?: number;
|
|
19
|
+
limit?: number;
|
|
11
20
|
} | {
|
|
12
21
|
command: 'discover';
|
|
13
22
|
format: 'json' | 'pretty';
|
package/dist/parse.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { walletDepositsVisible } from './deposits-visible.js';
|
|
2
2
|
const VALID_CLIENTS = ['cursor', 'claude', 'codex'];
|
|
3
|
+
/**
|
|
4
|
+
* Per-command usage strings - the same text the flag-error paths print, so
|
|
5
|
+
* `bags <cmd> --help` never disagrees with a bad-flag error.
|
|
6
|
+
*/
|
|
7
|
+
const COMMAND_USAGE = {
|
|
8
|
+
redeem: 'usage: bags redeem <code> | bags redeem --test',
|
|
9
|
+
balance: 'usage: bags balance',
|
|
10
|
+
result: 'usage: bags result <id> [--field path] [--offset N] [--limit N]',
|
|
11
|
+
discover: 'usage: bags discover [query] [--format json|pretty]',
|
|
12
|
+
search: 'usage: bags search <query> [--format json|pretty]',
|
|
13
|
+
check: 'usage: bags check <url> [flags]',
|
|
14
|
+
fetch: 'usage: bags fetch <url> [flags]',
|
|
15
|
+
sessions: 'usage: bags sessions list|close [id] [--all --yes]',
|
|
16
|
+
init: 'usage: bags init [--dry-run] [--client cursor|claude|codex] [--scope project|user]',
|
|
17
|
+
doctor: 'usage: bags doctor [--scope project|user] [--client cursor|claude|codex]',
|
|
18
|
+
fund: 'usage: bags fund [--open] [--create] [--network NAME]',
|
|
19
|
+
transactions: 'usage: bags transactions [--limit N] [--cursor C] [--format json|pretty]',
|
|
20
|
+
policies: 'usage: bags policies get|set [--key=value ...]',
|
|
21
|
+
keys: 'usage: bags keys create|list|revoke',
|
|
22
|
+
register: 'usage: bags register <origin>',
|
|
23
|
+
mcp: 'usage: bags mcp',
|
|
24
|
+
};
|
|
3
25
|
function parseClientList(raw) {
|
|
4
26
|
const parts = raw
|
|
5
27
|
.split(',')
|
|
@@ -48,6 +70,13 @@ export function parseArgs(argv) {
|
|
|
48
70
|
return { command: 'help' };
|
|
49
71
|
}
|
|
50
72
|
const [cmd, ...rest] = argv;
|
|
73
|
+
// -h/--help on any command prints that command's usage (stdout, exit 0).
|
|
74
|
+
if (rest.includes('-h') || rest.includes('--help')) {
|
|
75
|
+
const known = cmd in COMMAND_USAGE;
|
|
76
|
+
if (!known)
|
|
77
|
+
return { command: 'error', message: `unknown command: ${cmd}` };
|
|
78
|
+
return { command: 'usage', message: COMMAND_USAGE[cmd] };
|
|
79
|
+
}
|
|
51
80
|
switch (cmd) {
|
|
52
81
|
case 'redeem': {
|
|
53
82
|
let code;
|
|
@@ -411,6 +440,34 @@ export function parseArgs(argv) {
|
|
|
411
440
|
}
|
|
412
441
|
return { command: 'fund', open, create, network };
|
|
413
442
|
}
|
|
443
|
+
case 'result': {
|
|
444
|
+
const id = rest[0];
|
|
445
|
+
if (!id || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(id))
|
|
446
|
+
return { command: 'error', message: COMMAND_USAGE.result };
|
|
447
|
+
const out = {
|
|
448
|
+
command: 'result',
|
|
449
|
+
id,
|
|
450
|
+
};
|
|
451
|
+
for (let i = 1; i < rest.length; i += 2) {
|
|
452
|
+
const flag = rest[i], value = rest[i + 1];
|
|
453
|
+
if (!value || !['--field', '--offset', '--limit'].includes(flag))
|
|
454
|
+
return { command: 'error', message: COMMAND_USAGE.result };
|
|
455
|
+
if (flag === '--field')
|
|
456
|
+
out.field = value;
|
|
457
|
+
else {
|
|
458
|
+
const n = Number(value);
|
|
459
|
+
if (!Number.isSafeInteger(n) ||
|
|
460
|
+
n < (flag === '--offset' ? 0 : 1) ||
|
|
461
|
+
(flag === '--limit' && n > 100))
|
|
462
|
+
return { command: 'error', message: COMMAND_USAGE.result };
|
|
463
|
+
if (flag === '--offset')
|
|
464
|
+
out.offset = n;
|
|
465
|
+
else
|
|
466
|
+
out.limit = n;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
414
471
|
case 'transactions': {
|
|
415
472
|
const parsed = takeFormat(rest, 'usage: bags transactions [--limit N] [--cursor C] [--format json|pretty]');
|
|
416
473
|
if ('error' in parsed)
|
|
@@ -621,7 +678,7 @@ Usage:
|
|
|
621
678
|
npx @getbags/cash <command>
|
|
622
679
|
|
|
623
680
|
Workflow: balance → discover|search → check → fetch
|
|
624
|
-
MPP
|
|
681
|
+
MPP: one-shot tempo:charge; new hosted sessions are unavailable
|
|
625
682
|
|
|
626
683
|
Commands:
|
|
627
684
|
redeem <code> Redeem an invite code and save the wallet token
|
|
@@ -631,7 +688,9 @@ ${fundLine} discover [query] List BAGS merchant endpoints (BAGS fi
|
|
|
631
688
|
search <query> NL search: BAGS catalog + ecosystem index
|
|
632
689
|
check <url> Probe an endpoint for schema + exact quote (no pay)
|
|
633
690
|
fetch <url> [flags] Pay and proxy an x402 / MPP endpoint
|
|
634
|
-
sessions list
|
|
691
|
+
sessions list List existing MPP session reservations
|
|
692
|
+
sessions close <id> Request cleanup; unsupported close preserves the hold
|
|
693
|
+
result <id> [flags] Read retained output without another payment
|
|
635
694
|
transactions List wallet ledger / spend history
|
|
636
695
|
policies get|set Read or update spend policies
|
|
637
696
|
keys create|list|revoke Manage per-agent wallet keys
|
|
@@ -661,7 +720,12 @@ Fetch / check flags:
|
|
|
661
720
|
--payment-protocol x402|mpp|auto Force rail (default: auto)
|
|
662
721
|
--format json|pretty Machine or human output
|
|
663
722
|
|
|
664
|
-
|
|
723
|
+
Result flags:
|
|
724
|
+
--field <path> Select a retained field (e.g. response.items)
|
|
725
|
+
--offset <N> Array offset (default: 0)
|
|
726
|
+
--limit <N> Array page size (1–100; default: 20)
|
|
727
|
+
|
|
728
|
+
Sessions (existing holds only; hosted close currently reports unsupported):
|
|
665
729
|
npx @getbags/cash sessions list
|
|
666
730
|
npx @getbags/cash sessions close <id>
|
|
667
731
|
npx @getbags/cash sessions close --all --yes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side policy patch construction for `bags policies set`.
|
|
3
|
+
*
|
|
4
|
+
* The wallet policies route validates a snake_case PatchSchema
|
|
5
|
+
* (src/app/api/wallet/policies/route.ts). Zod strips unknown keys there, so a
|
|
6
|
+
* misspelled or wrong-case flag used to return 200 with an unchanged policy —
|
|
7
|
+
* a silent no-op. This module normalizes every accepted spelling to
|
|
8
|
+
* snake_case and rejects unrecognized keys before the request leaves the CLI.
|
|
9
|
+
*/
|
|
10
|
+
export declare class PolicyPatchError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a flag key to the server's snake_case spelling.
|
|
15
|
+
* Accepts kebab-case (`per-call-cap-cents`), camelCase (`perCallCapCents`),
|
|
16
|
+
* and snake_case (`per_call_cap_cents`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function policyKeyToSnake(key: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Build the PATCH body for `bags policies set --key=value ...`.
|
|
21
|
+
*
|
|
22
|
+
* Values coerce like before: integer-looking strings become numbers,
|
|
23
|
+
* `true`/`false` become booleans, anything else stays a string. Unknown keys
|
|
24
|
+
* throw (the server would silently strip them and report success).
|
|
25
|
+
*/
|
|
26
|
+
export declare function policyPatchFromSets(sets: Record<string, string>): Record<string, unknown>;
|
package/dist/policies.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side policy patch construction for `bags policies set`.
|
|
3
|
+
*
|
|
4
|
+
* The wallet policies route validates a snake_case PatchSchema
|
|
5
|
+
* (src/app/api/wallet/policies/route.ts). Zod strips unknown keys there, so a
|
|
6
|
+
* misspelled or wrong-case flag used to return 200 with an unchanged policy —
|
|
7
|
+
* a silent no-op. This module normalizes every accepted spelling to
|
|
8
|
+
* snake_case and rejects unrecognized keys before the request leaves the CLI.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Exact keys the server PatchSchema accepts. Keep in sync with
|
|
12
|
+
* src/app/api/wallet/policies/route.ts.
|
|
13
|
+
*/
|
|
14
|
+
const VALID_POLICY_KEYS = new Set([
|
|
15
|
+
'per_call_cap_cents',
|
|
16
|
+
'daily_cap_cents',
|
|
17
|
+
'weekly_cap_cents',
|
|
18
|
+
'monthly_cap_cents',
|
|
19
|
+
'total_cap_cents',
|
|
20
|
+
'human_approval_threshold_cents',
|
|
21
|
+
'origin_allowlist',
|
|
22
|
+
'origin_blocklist',
|
|
23
|
+
'status',
|
|
24
|
+
]);
|
|
25
|
+
export class PolicyPatchError extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = 'PolicyPatchError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalize a flag key to the server's snake_case spelling.
|
|
33
|
+
* Accepts kebab-case (`per-call-cap-cents`), camelCase (`perCallCapCents`),
|
|
34
|
+
* and snake_case (`per_call_cap_cents`).
|
|
35
|
+
*/
|
|
36
|
+
export function policyKeyToSnake(key) {
|
|
37
|
+
return key
|
|
38
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
39
|
+
.replace(/-/g, '_')
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the PATCH body for `bags policies set --key=value ...`.
|
|
44
|
+
*
|
|
45
|
+
* Values coerce like before: integer-looking strings become numbers,
|
|
46
|
+
* `true`/`false` become booleans, anything else stays a string. Unknown keys
|
|
47
|
+
* throw (the server would silently strip them and report success).
|
|
48
|
+
*/
|
|
49
|
+
export function policyPatchFromSets(sets) {
|
|
50
|
+
const patch = {};
|
|
51
|
+
for (const [key, value] of Object.entries(sets)) {
|
|
52
|
+
const snake = policyKeyToSnake(key);
|
|
53
|
+
if (!VALID_POLICY_KEYS.has(snake)) {
|
|
54
|
+
throw new PolicyPatchError(`unknown policy key: ${key}. ` +
|
|
55
|
+
`Valid keys: ${[...VALID_POLICY_KEYS].join(', ')}. ` +
|
|
56
|
+
`Example: bags policies set --per-call-cap-cents=100`);
|
|
57
|
+
}
|
|
58
|
+
const num = Number(value);
|
|
59
|
+
patch[snake] =
|
|
60
|
+
Number.isFinite(num) && String(num) === value
|
|
61
|
+
? num
|
|
62
|
+
: value === 'true'
|
|
63
|
+
? true
|
|
64
|
+
: value === 'false'
|
|
65
|
+
? false
|
|
66
|
+
: value;
|
|
67
|
+
}
|
|
68
|
+
return patch;
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getbags/cash",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "BAGS Cash CLI
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "BAGS Cash CLI \u2014 hosted agent wallet for x402 (balance \u2192 discover \u2192 check \u2192 fetch)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"bags": "dist/index.js"
|
|
@@ -39,12 +39,13 @@
|
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@modelcontextprotocol/sdk": "1.
|
|
43
|
-
"zod": "^4.
|
|
42
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
43
|
+
"zod": "^4.6.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^25.0.3",
|
|
47
47
|
"typescript": "6.0.2",
|
|
48
|
-
"vitest": "^
|
|
49
|
-
}
|
|
48
|
+
"vitest": "^5.0.3"
|
|
49
|
+
},
|
|
50
|
+
"packageManager": "bun@1.4.2"
|
|
50
51
|
}
|