agentspend 0.2.1 → 0.3.2

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
@@ -1,6 +1,6 @@
1
1
  # agentspend
2
2
 
3
- AgentSpend CLI for calling x402 endpoints through AgentSpend Cloud.
3
+ AgentSpend CLI, MCP server, and OpenClaw plugin.
4
4
 
5
5
  ## Install
6
6
 
@@ -9,30 +9,87 @@ npm install
9
9
  npm run build
10
10
  ```
11
11
 
12
- ## Commands
12
+ ## CLI commands
13
13
 
14
14
  ```bash
15
15
  agentspend configure
16
- agentspend pay <url> [--method GET|POST|PUT|PATCH|DELETE|...] [--body '{"hello":"world"}'] [--header 'Content-Type:application/json'] [--max-cost 5.000000]
17
- agentspend check <url> [--method GET|POST|PUT|PATCH|DELETE|...] [--body '{"hello":"world"}'] [--header 'Content-Type:application/json']
16
+ agentspend search <query>
17
+ agentspend use <url> [--method GET|POST|PUT|PATCH|DELETE|...] [--header 'Content-Type:application/json'] [--body '{"hello":"world"}']
18
18
  agentspend status
19
19
  ```
20
20
 
21
+ `use` accepts direct HTTPS URLs only.
22
+
23
+ ## Billing behavior
24
+
25
+ - You can begin using services without adding a payment method.
26
+ - AgentSpend allows up to `$5.00` in accrued no-card usage.
27
+ - When usage would exceed `$5.00`, `agentspend use` is blocked until billing is configured.
28
+ - Weekly limit checks still apply independently of billing method.
29
+
30
+ ## OpenClaw plugin (primary OpenClaw path)
31
+
32
+ ```bash
33
+ openclaw plugins install agentspend
34
+ openclaw plugins enable agentspend
35
+ openclaw gateway restart
36
+ ```
37
+
38
+ Local install from this repo:
39
+
40
+ ```bash
41
+ openclaw plugins install -l /Users/jpbonch/as/agentspend
42
+ openclaw plugins enable agentspend
43
+ openclaw gateway restart
44
+ ```
45
+
46
+ Plugin tools:
47
+ - `agentspend_configure`
48
+ - `agentspend_search`
49
+ - `agentspend_use`
50
+ - `agentspend_status`
51
+
52
+ ## OpenClaw routing hook
53
+
54
+ When installed as an OpenClaw plugin, AgentSpend injects routing guidance each turn so the agent prefers:
55
+ 1. `agentspend_search`
56
+ 2. Read the selected service `skill_url`
57
+ 3. `agentspend_use`
58
+ 4. `agentspend_configure` if setup is needed
59
+
60
+ ## MCP server (secondary)
61
+
62
+ Run local stdio MCP server:
63
+
64
+ ```bash
65
+ agentspend-mcp
66
+ ```
67
+
68
+ MCP tools:
69
+ - `agentspend_configure`
70
+ - `agentspend_search`
71
+ - `agentspend_use`
72
+ - `agentspend_status`
73
+
21
74
  ## Credentials
22
75
 
23
- Credentials are stored at `~/.agentspend/credentials.json`.
76
+ Local credentials are in `~/.agentspend/credentials.json`.
24
77
 
25
- ## Local backend dev CLI
78
+ ## Skills
26
79
 
27
- Use the local entrypoint (hardcoded to `http://127.0.0.1:8787`) when testing against a local backend:
80
+ - Skill files live in [`skills/`](./skills).
81
+ - Included skills are declared in [`skills/manifest.json`](./skills/manifest.json).
82
+ - Each skill file must start with YAML frontmatter per [`skills/SPEC.md`](./skills/SPEC.md), including `auth_type`.
83
+ - Validate skill metadata with:
28
84
 
29
85
  ```bash
30
- bun run dev:local -- configure
86
+ npm run skills:validate
31
87
  ```
32
88
 
33
- Build a non-published local binary:
89
+ ## Local backend dev CLI
90
+
91
+ Use the local entrypoint (fixed to `http://127.0.0.1:8787`) when testing against local backend:
34
92
 
35
93
  ```bash
36
- bun run build
37
- node dist/dev-index.js configure
94
+ bun run dev:local -- configure
38
95
  ```
package/SKILL.md CHANGED
@@ -1,138 +1,91 @@
1
1
  ---
2
2
  name: agentspend
3
- description: Use this skill when you need to: search the web, generate images or pictures, create videos, scrape or read a webpage, take a screenshot of a website, get stock prices or financial data, look up business info, find news articles, translate text, parse PDFs, do OCR on images, convert files, get weather data, or access any external API that requires payment. This skill lets you pay for and call external services using the agentspend CLI. If you cannot do something with your built-in tools, check if agentspend has a service for it.
3
+ description: Use this skill to get external capabilities on demand. It lets you find and use APIs/services for web search, maps/place lookup, scraping, enrichment, social/news data, image/video generation, text-to-speech, and speech-to-text through one flow.
4
4
  ---
5
5
 
6
6
  # When To Use This Skill
7
- Use this skill whenever the user asks to:
8
- - find data from external APIs
9
- - call an endpoint
10
- - fetch/search information outside local context
11
- - pay for x402 services
12
- - generate an image, video, voice, transcription, or music with an external API
13
- - scrape/extract data from a URL
14
- - find an API for a task ("is there an API for X?")
15
-
16
- If the task needs an external paid API, start with `agentspend search`.
17
-
18
- ## Playbook (Default Workflow)
19
-
20
- 1. `npx agentspend search "<task>"`
21
- 2. Confirm cost and constraints with the user (`--max-cost`, budget, allowlist)
22
- 3. `npx agentspend pay <endpoint> --method ... --header ... --body ... --max-cost ...`
23
-
24
- ## Setup
25
-
7
+ Use this when the user asks for a capability you do not have natively and it may require external APIs or paid tools, including:
8
+ - image/video/audio generation
9
+ - speech-to-text / text-to-speech
10
+ - web/news/social search
11
+ - scraping/extracting URL content
12
+ - maps/location/place lookup
13
+ - data enrichment and people/company lookup
14
+
15
+ # MCP / Plugin Tools
16
+ If MCP/plugin tools are available, prefer:
17
+ - `agentspend_configure`
18
+ - `agentspend_search`
19
+ - `agentspend_use`
20
+ - `agentspend_status`
21
+
22
+ When running via the OpenClaw plugin, AgentSpend injects turn-level routing guidance so the agent prefers AgentSpend discovery/use for external API tasks.
23
+
24
+ # Playbook (Default)
25
+ 1. `npx agentspend search "<user intent>"`
26
+ 2. Pick the best matching service and open its `skill_url`.
27
+ 3. Use the exact URL/method/headers/body from that skill file with `npx agentspend use <url>`.
28
+ 4. If auth/setup is missing, run `npx agentspend configure` and continue after completion.
29
+
30
+ # Setup
26
31
  ```bash
27
32
  npx agentspend configure
28
33
  ```
29
34
 
30
- Opens a URL to add a credit card and set a weekly spending limit. Saves credentials to `~/.agentspend/credentials.json`.
31
-
32
- If already configured, re-running opens the dashboard to update settings.
35
+ This opens a URL for gateway configuration (billing + connections) and stores credentials in `~/.agentspend/credentials.json`.
36
+ It does not require adding a card to start using services.
33
37
 
34
- ## Commands
35
-
36
- ### Pay
38
+ # Commands
37
39
 
40
+ ## Use
38
41
  ```bash
39
- npx agentspend pay <url>
42
+ npx agentspend use <url>
40
43
  ```
41
44
 
42
- Make a paid request. AgentSpend handles the payment automatically. Works with endpoints that support x402 (HTTP 402-based payment protocol for APIs).
43
-
44
- **Options:**
45
- - `--method <method>` — HTTP method (default: `GET`)
46
- - `--body <body>` — Request body (JSON or text)
47
- - `--header <header>` — Header in `key:value` format (repeatable)
48
- - `--max-cost <usd>` — Maximum acceptable charge in USD (up to 6 decimal places)
45
+ `<url>` must be a direct HTTPS URL.
49
46
 
50
- **Returns:**
51
- - Response body from the endpoint
52
- - Charge amount and remaining weekly budget
53
-
54
- **Example:**
47
+ Options:
48
+ - `--method <method>` HTTP method
49
+ - `--header <key:value>` repeatable header
50
+ - `--body <json-or-text>` request body
55
51
 
52
+ Examples:
56
53
  ```bash
57
- npx agentspend pay <url> \
58
- --method POST \
59
- --header "key:value" \
60
- --body '{"key": "value"}' \
61
- --max-cost 0.05
62
- ```
63
-
64
- ### Check
65
-
66
- ```bash
67
- npx agentspend check <url>
68
- ```
69
-
70
- Discover an endpoint's price without paying.
71
-
72
- Important:
73
- - `check` must use the same request shape you plan to `pay` with.
74
- - Always pass `--method` for non-GET endpoints.
75
- - If the endpoint needs headers/body, include the same `--header` and `--body` on `check`.
76
- - If request shape is wrong, endpoint may return `404`/`400` instead of `402`, and no price can be extracted.
77
-
78
- **Example:**
79
-
80
- ```bash
81
- npx agentspend check <url> \
54
+ npx agentspend use https://stableenrich.dev/api/exa/search \
82
55
  --method POST \
83
56
  --header "content-type:application/json" \
84
- --body '{"key":"value"}'
57
+ --body '{"query":"latest robotics news","numResults":5}'
85
58
  ```
86
59
 
87
- **Returns:**
88
- - Price in USD
89
- - Description (if available)
90
-
91
- ### Search
92
-
60
+ ## Search
93
61
  ```bash
94
62
  npx agentspend search <keywords>
95
63
  ```
96
64
 
97
- Keyword search over service names and descriptions in the catalog. Returns up to 5 matching services.
98
-
99
- **Example:**
100
-
101
- ```bash
102
- npx agentspend search "video generation"
103
- ```
104
-
105
- ### Status
65
+ Returns up to 5 matching services with domain and skill link.
106
66
 
67
+ ## Status
107
68
  ```bash
108
69
  npx agentspend status
109
70
  ```
110
71
 
111
- Show account spending overview.
112
-
113
- **Returns:**
114
- - Weekly budget
115
- - Amount spent this week
116
- - Remaining budget
117
- - Recent charges with amounts, domains, and timestamps
118
-
119
- ### Configure
72
+ Shows weekly budget, spend, remaining budget, and recent charges.
120
73
 
74
+ ## Configure
121
75
  ```bash
122
76
  npx agentspend configure
123
77
  ```
124
78
 
125
- Run onboarding or open the dashboard to update settings (weekly budget, domain allowlist, payment method).
126
-
127
- ## Spending Controls
128
-
129
- - **Weekly budget** — Set during configure. Requests that would exceed the budget are rejected.
130
- - **Per-request max cost** — Use `--max-cost` on `pay` to reject requests above a price threshold.
131
- - **Domain allowlist** — Configurable via the dashboard. Requests to non-allowlisted domains are rejected.
79
+ Opens configuration for billing, budget, and connected auth providers.
80
+ Billing can be added later; usage accrues until the no-card allowance threshold is reached.
132
81
 
133
- ## Common Errors
82
+ # Spending Controls
83
+ - Weekly budget enforced server-side.
84
+ - Up to `$5.00` of no-card accrued usage is allowed before billing setup is required.
85
+ - Target domain must match an active service domain in AgentSpend.
134
86
 
135
- - **`WEEKLY_BUDGET_EXCEEDED`** — Weekly spending limit reached. Run `npx agentspend configure` to increase the budget.
136
- - **`DOMAIN_NOT_ALLOWLISTED`** — The target domain is not in the allowlist. Run `npx agentspend configure` to update allowed domains.
137
- - **`PRICE_EXCEEDS_MAX`**Endpoint price is higher than `--max-cost`. Increase the value or remove the flag.
138
- - **`UNSUPPORTED_PAYMENT_REQUIRED_FORMAT`**The endpoint returned a 402 response but doesn't use the x402 format AgentSpend supports.
87
+ # Common Errors
88
+ - `WEEKLY_BUDGET_EXCEEDED` weekly limit reached.
89
+ - `PAYMENT_METHOD_REQUIRED`no-card usage threshold or billing hold reached; run configure and add/update payment method.
90
+ - `SERVICE_DOMAIN_NOT_REGISTERED`target domain is not registered as an active service domain.
91
+ - `SERVICE_AUTH_REQUIRED` — required OAuth connection missing; run configure and connect provider.
package/dist/cli.js CHANGED
@@ -1,50 +1,29 @@
1
1
  import { Command } from "commander";
2
- import { runCheck } from "./commands/check.js";
2
+ import { createRequire } from "node:module";
3
3
  import { runConfigure } from "./commands/configure.js";
4
- import { runPay } from "./commands/pay.js";
4
+ import { runUse } from "./commands/use.js";
5
5
  import { runSearch } from "./commands/search.js";
6
6
  import { runStatus } from "./commands/status.js";
7
7
  import { AgentspendApiClient } from "./lib/api.js";
8
- function parsePositiveUsd(value) {
9
- const parsed = Number(value);
10
- if (!Number.isFinite(parsed) || parsed <= 0) {
11
- throw new Error(`Expected a positive USD value, received: ${value}`);
12
- }
13
- const rounded = Math.round(parsed * 1_000_000) / 1_000_000;
14
- if (Math.abs(parsed - rounded) > 1e-9) {
15
- throw new Error(`Expected at most 6 decimal places, received: ${value}`);
16
- }
17
- return parsed;
18
- }
8
+ const require = createRequire(import.meta.url);
9
+ const packageJson = require("../package.json");
10
+ const CLI_VERSION = typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
19
11
  export async function runCli(options) {
20
12
  const program = new Command();
21
13
  const apiClient = new AgentspendApiClient(options?.baseUrl);
22
14
  const programName = options?.programName ?? "agentspend";
23
- program.name(programName).description("AgentSpend CLI").version("0.2.0");
24
- program
25
- .command("pay")
26
- .argument("<url>", "URL to call")
27
- .description("Make a paid request")
28
- .option("-X, --method <method>", "HTTP method for target request", "GET")
29
- .option("--body <body>", "Request body (JSON or text)")
30
- .option("--header <header>", "Header in key:value form", (value, previous) => {
31
- return [...previous, value];
32
- }, [])
33
- .option("--max-cost <usd>", "Maximum acceptable charge in USD (up to 6 decimals)", parsePositiveUsd)
34
- .action(async (url, commandOptions) => {
35
- await runPay(apiClient, url, commandOptions);
36
- });
15
+ program.name(programName).description("AgentSpend CLI").version(CLI_VERSION);
37
16
  program
38
- .command("check")
39
- .argument("<url>", "URL to check")
40
- .description("Discover endpoint price without paying")
41
- .option("-X, --method <method>", "HTTP method for target request", "GET")
17
+ .command("use")
18
+ .argument("<url>", "Direct HTTPS URL")
19
+ .description("Call a URL through AgentSpend")
20
+ .option("-X, --method <method>", "HTTP method")
42
21
  .option("--body <body>", "Request body (JSON or text)")
43
22
  .option("--header <header>", "Header in key:value form", (value, previous) => {
44
23
  return [...previous, value];
45
24
  }, [])
46
25
  .action(async (url, commandOptions) => {
47
- await runCheck(apiClient, url, commandOptions);
26
+ await runUse(apiClient, url, commandOptions);
48
27
  });
49
28
  program
50
29
  .command("search")
@@ -1,44 +1,5 @@
1
- import { ApiError } from "../lib/api.js";
2
- import { clearPendingConfigureToken, readCredentials, savePendingConfigureToken, } from "../lib/credentials.js";
3
- import { claimConfigureToken, getPendingConfigureStatus } from "../lib/auth-flow.js";
4
- async function tryAuthenticatedConfigure(apiClient, apiKey) {
5
- try {
6
- return await apiClient.configure(undefined, apiKey);
7
- }
8
- catch (error) {
9
- if (error instanceof ApiError && error.status === 401) {
10
- return null;
11
- }
12
- throw error;
13
- }
14
- }
1
+ import { resolveConfigureStatus } from "../lib/configure-flow.js";
15
2
  export async function runConfigure(apiClient) {
16
- const credentials = await readCredentials();
17
- if (credentials) {
18
- const authenticatedResponse = await tryAuthenticatedConfigure(apiClient, credentials.api_key);
19
- if (authenticatedResponse) {
20
- console.log(`Open this URL to configure settings:\n${authenticatedResponse.configure_url}`);
21
- return;
22
- }
23
- }
24
- const pending = await getPendingConfigureStatus(apiClient);
25
- if (pending) {
26
- if (pending.status.claim_status === "awaiting_card") {
27
- console.log(`Open this URL to configure settings:\n${pending.status.configure_url}`);
28
- return;
29
- }
30
- if (pending.status.claim_status === "ready_to_claim") {
31
- const apiKey = await claimConfigureToken(apiClient, pending.token);
32
- const claimedResponse = await tryAuthenticatedConfigure(apiClient, apiKey);
33
- if (claimedResponse) {
34
- console.log(`Open this URL to configure settings:\n${claimedResponse.configure_url}`);
35
- return;
36
- }
37
- throw new Error("API key was claimed, but configure session could not be created. Run agentspend configure again.");
38
- }
39
- await clearPendingConfigureToken();
40
- }
41
- const created = await apiClient.configure();
42
- await savePendingConfigureToken(created.token);
43
- console.log(`Open this URL to configure settings:\n${created.configure_url}`);
3
+ const result = await resolveConfigureStatus(apiClient);
4
+ console.log(`Open this URL to configure settings:\n${result.status.configure_url}`);
44
5
  }
@@ -1,6 +1,6 @@
1
1
  import { ApiError } from "../lib/api.js";
2
2
  import { resolveApiKeyWithAutoClaim } from "../lib/auth-flow.js";
3
- import { formatJson, formatUsd, formatUsdEstimate, usd6ToUsd } from "../lib/output.js";
3
+ import { formatJson, formatUsd, usd6ToUsd } from "../lib/output.js";
4
4
  import { normalizeMethod, parseBody, parseHeaders } from "../lib/request-options.js";
5
5
  function isRecord(value) {
6
6
  return typeof value === "object" && value !== null;
@@ -17,8 +17,7 @@ function parsePayErrorCode(value) {
17
17
  if (typeof value !== "string") {
18
18
  return undefined;
19
19
  }
20
- if (value === "PRICE_EXCEEDS_MAX" ||
21
- value === "PRICE_NOT_CONVERTIBLE" ||
20
+ if (value === "PRICE_NOT_CONVERTIBLE" ||
22
21
  value === "WEEKLY_BUDGET_EXCEEDED" ||
23
22
  value === "DOMAIN_NOT_ALLOWLISTED") {
24
23
  return value;
@@ -37,10 +36,6 @@ function parsePayErrorBody(body) {
37
36
  }
38
37
  const detailsRecord = body.details;
39
38
  parsed.details = {
40
- offered_price_usd6: readNumber(detailsRecord, "offered_price_usd6"),
41
- offered_price_usd: readNumber(detailsRecord, "offered_price_usd"),
42
- max_cost_usd6: readNumber(detailsRecord, "max_cost_usd6"),
43
- max_cost_usd: readNumber(detailsRecord, "max_cost_usd"),
44
39
  weekly_limit_usd6: readNumber(detailsRecord, "weekly_limit_usd6"),
45
40
  weekly_limit_usd: readNumber(detailsRecord, "weekly_limit_usd"),
46
41
  spent_this_week_usd6: readNumber(detailsRecord, "spent_this_week_usd6"),
@@ -62,7 +57,6 @@ export async function runPay(apiClient, url, options) {
62
57
  method,
63
58
  headers: parseHeaders(options.header),
64
59
  body: parseBody(options.body),
65
- max_cost_usd: options.maxCost,
66
60
  });
67
61
  console.log(formatJson(response.body));
68
62
  if (response.payment) {
@@ -72,20 +66,6 @@ export async function runPay(apiClient, url, options) {
72
66
  catch (error) {
73
67
  if (error instanceof ApiError) {
74
68
  const body = parsePayErrorBody(error.body);
75
- if (error.status === 400 && body.code === "PRICE_EXCEEDS_MAX") {
76
- const offered = body.details?.offered_price_usd ??
77
- (typeof body.details?.offered_price_usd6 === "number" ? usd6ToUsd(body.details.offered_price_usd6) : 0);
78
- const max = body.details?.max_cost_usd ??
79
- (typeof body.details?.max_cost_usd6 === "number" ? usd6ToUsd(body.details.max_cost_usd6) : 0);
80
- const estimatedUsd = body.details?.estimated_usd;
81
- const amountDisplay = body.details?.amount_display;
82
- const currency = body.details?.currency ?? "USDC";
83
- console.error(`Price ${formatUsd(offered)} exceeds --max-cost ${formatUsd(max)}. Run without --max-cost or increase it.`);
84
- if (amountDisplay) {
85
- console.error(`Offered token amount: ${amountDisplay} ${currency} (~${formatUsdEstimate(estimatedUsd, offered)})`);
86
- }
87
- return;
88
- }
89
69
  if (error.status === 400 && body.code === "PRICE_NOT_CONVERTIBLE") {
90
70
  console.error("Price could not be converted to 6-decimal USD units for policy checks.");
91
71
  return;
@@ -7,7 +7,7 @@ export async function runSearch(apiClient, query) {
7
7
  return;
8
8
  }
9
9
  for (const service of response.services) {
10
- console.log(service.name);
10
+ console.log(`${service.name} (${service.slug})`);
11
11
  console.log(`Description: ${service.description}`);
12
12
  console.log(`Domain: ${service.domain}`);
13
13
  console.log(`Skill URL: ${service.skill_url ?? "n/a"}`);
@@ -0,0 +1,117 @@
1
+ import { ApiError } from "../lib/api.js";
2
+ import { resolveApiKeyWithAutoClaim } from "../lib/auth-flow.js";
3
+ import { formatJson, formatUsd, usd6ToUsd } from "../lib/output.js";
4
+ import { normalizeMethod, parseBody, parseHeaders } from "../lib/request-options.js";
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null;
7
+ }
8
+ function readNumber(record, key) {
9
+ const value = record[key];
10
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
11
+ }
12
+ function readString(record, key) {
13
+ const value = record[key];
14
+ return typeof value === "string" ? value : undefined;
15
+ }
16
+ function parseUseErrorCode(value) {
17
+ if (typeof value !== "string") {
18
+ return undefined;
19
+ }
20
+ if (value === "PRICE_NOT_CONVERTIBLE" ||
21
+ value === "WEEKLY_BUDGET_EXCEEDED" ||
22
+ value === "SERVICE_DOMAIN_NOT_REGISTERED" ||
23
+ value === "SERVICE_AUTH_REQUIRED" ||
24
+ value === "PAYMENT_METHOD_REQUIRED") {
25
+ return value;
26
+ }
27
+ return undefined;
28
+ }
29
+ function parseUseErrorBody(body) {
30
+ if (!isRecord(body)) {
31
+ return {};
32
+ }
33
+ const parsed = {
34
+ code: parseUseErrorCode(body.code),
35
+ message: readString(body, "message"),
36
+ configure_url: readString(body, "configure_url"),
37
+ };
38
+ if (!isRecord(body.details)) {
39
+ return parsed;
40
+ }
41
+ const detailsRecord = body.details;
42
+ parsed.details = {
43
+ weekly_limit_usd6: readNumber(detailsRecord, "weekly_limit_usd6"),
44
+ weekly_limit_usd: readNumber(detailsRecord, "weekly_limit_usd"),
45
+ spent_this_week_usd6: readNumber(detailsRecord, "spent_this_week_usd6"),
46
+ spent_this_week_usd: readNumber(detailsRecord, "spent_this_week_usd"),
47
+ attempted_charge_usd6: readNumber(detailsRecord, "attempted_charge_usd6"),
48
+ attempted_charge_usd: readNumber(detailsRecord, "attempted_charge_usd"),
49
+ estimated_usd: readNumber(detailsRecord, "estimated_usd"),
50
+ amount_display: readString(detailsRecord, "amount_display"),
51
+ currency: readString(detailsRecord, "currency"),
52
+ configure_url: readString(detailsRecord, "configure_url"),
53
+ };
54
+ return parsed;
55
+ }
56
+ function printCloudHttpResult(result) {
57
+ console.log(formatJson(result.body));
58
+ if (result.payment) {
59
+ console.log(`\nCharged: ${formatUsd(result.payment.charged_usd)} | Remaining: ${formatUsd(result.payment.remaining_budget_usd)}`);
60
+ }
61
+ }
62
+ export async function runUse(apiClient, url, options) {
63
+ const apiKey = await resolveApiKeyWithAutoClaim(apiClient);
64
+ try {
65
+ const response = await apiClient.use(apiKey, {
66
+ url,
67
+ method: options.method ? normalizeMethod(options.method) : undefined,
68
+ headers: parseHeaders(options.header),
69
+ body: parseBody(options.body),
70
+ });
71
+ if (response.mode === "cloud_http_result") {
72
+ printCloudHttpResult(response);
73
+ return;
74
+ }
75
+ if (response.mode === "action_required") {
76
+ const configureUrl = response.configure_url ? `\n${response.configure_url}` : "";
77
+ throw new Error(`${response.message}${configureUrl}`);
78
+ }
79
+ console.log(formatJson(response));
80
+ }
81
+ catch (error) {
82
+ if (error instanceof ApiError) {
83
+ const body = parseUseErrorBody(error.body);
84
+ if (error.status === 400 && body.code === "PRICE_NOT_CONVERTIBLE") {
85
+ console.error("Price could not be converted to 6-decimal USD units for policy checks.");
86
+ return;
87
+ }
88
+ if (error.status === 402 && body.code === "WEEKLY_BUDGET_EXCEEDED") {
89
+ const weeklyLimit = body.details?.weekly_limit_usd ??
90
+ (typeof body.details?.weekly_limit_usd6 === "number" ? usd6ToUsd(body.details.weekly_limit_usd6) : 0);
91
+ const spent = body.details?.spent_this_week_usd ??
92
+ (typeof body.details?.spent_this_week_usd6 === "number" ? usd6ToUsd(body.details.spent_this_week_usd6) : 0);
93
+ const attempted = body.details?.attempted_charge_usd ??
94
+ (typeof body.details?.attempted_charge_usd6 === "number" ? usd6ToUsd(body.details.attempted_charge_usd6) : 0);
95
+ console.error(`Weekly budget exceeded. Limit ${formatUsd(weeklyLimit)}, spent ${formatUsd(spent)}, attempted ${formatUsd(attempted)}.`);
96
+ return;
97
+ }
98
+ if (error.status === 403 && body.code === "SERVICE_DOMAIN_NOT_REGISTERED") {
99
+ console.error("This domain is not registered as an active AgentSpend service domain.");
100
+ return;
101
+ }
102
+ if (error.status === 403 && body.code === "SERVICE_AUTH_REQUIRED") {
103
+ const configureUrl = body.configure_url ?? body.details?.configure_url;
104
+ console.error(body.message ??
105
+ `Service authentication required. Complete connection setup in configure.${configureUrl ? `\n${configureUrl}` : ""}`);
106
+ return;
107
+ }
108
+ if (error.status === 403 && body.code === "PAYMENT_METHOD_REQUIRED") {
109
+ const configureUrl = body.configure_url ?? body.details?.configure_url;
110
+ console.error(body.message
111
+ ?? `Payment method required. Add or update billing details in configure.${configureUrl ? `\n${configureUrl}` : ""}`);
112
+ return;
113
+ }
114
+ }
115
+ throw error;
116
+ }
117
+ }
package/dist/lib/api.js CHANGED
@@ -55,14 +55,8 @@ export class AgentspendApiClient {
55
55
  }
56
56
  return body;
57
57
  }
58
- pay(apiKey, payload) {
59
- return this.request("/pay", {
60
- method: "POST",
61
- body: JSON.stringify(payload),
62
- }, apiKey);
63
- }
64
- check(apiKey, payload) {
65
- return this.request("/check", {
58
+ use(apiKey, payload) {
59
+ return this.request("/use", {
66
60
  method: "POST",
67
61
  body: JSON.stringify(payload),
68
62
  }, apiKey);
@@ -48,9 +48,6 @@ export async function resolveApiKeyWithAutoClaim(apiClient) {
48
48
  if (pending.status.claim_status === "ready_to_claim") {
49
49
  return claimConfigureToken(apiClient, pending.token);
50
50
  }
51
- if (pending.status.claim_status === "awaiting_card") {
52
- throw new Error(`Card setup required. Open this URL:\n${pending.status.configure_url}`);
53
- }
54
51
  await clearPendingConfigureToken();
55
- throw new Error("Configure session already claimed. Run `agentspend configure` again.");
52
+ throw new Error("Configure session is no longer claimable. Run `agentspend configure` again.");
56
53
  }