agentspend 0.2.2 → 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 +68 -11
- package/SKILL.md +54 -99
- package/dist/cli.js +11 -32
- package/dist/commands/configure.js +3 -42
- package/dist/commands/pay.js +2 -22
- package/dist/commands/search.js +1 -1
- package/dist/commands/use.js +117 -0
- package/dist/lib/api.js +2 -8
- package/dist/lib/auth-flow.js +1 -4
- package/dist/lib/configure-flow.js +47 -0
- package/dist/lib/local-execution.js +74 -0
- package/dist/lib/use-cloud-result.js +54 -0
- package/dist/mcp/server.js +49 -0
- package/dist/mcp/shared.js +129 -0
- package/dist/mcp/tools/configure.js +12 -0
- package/dist/mcp/tools/pay.js +28 -0
- package/dist/mcp/tools/search.js +10 -0
- package/dist/mcp/tools/status.js +11 -0
- package/dist/mcp/tools/use.js +60 -0
- package/dist/mcp-server.js +6 -0
- package/dist/openclaw-plugin/hooks/prompt-routing.js +12 -0
- package/dist/openclaw-plugin/index.js +17 -0
- package/dist/openclaw-plugin/shared.js +132 -0
- package/dist/openclaw-plugin/tools/configure.js +23 -0
- package/dist/openclaw-plugin/tools/pay.js +60 -0
- package/dist/openclaw-plugin/tools/search.js +24 -0
- package/dist/openclaw-plugin/tools/status.js +22 -0
- package/dist/openclaw-plugin/tools/use.js +80 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +12 -5
- package/dist/commands/check.js +0 -40
- package/dist/commands/setup.js +0 -36
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agentspend
|
|
2
2
|
|
|
3
|
-
AgentSpend CLI
|
|
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
|
-
##
|
|
12
|
+
## CLI commands
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
agentspend configure
|
|
16
|
-
agentspend
|
|
17
|
-
agentspend
|
|
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
|
-
|
|
76
|
+
Local credentials are in `~/.agentspend/credentials.json`.
|
|
24
77
|
|
|
25
|
-
##
|
|
78
|
+
## Skills
|
|
26
79
|
|
|
27
|
-
|
|
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
|
-
|
|
86
|
+
npm run skills:validate
|
|
31
87
|
```
|
|
32
88
|
|
|
33
|
-
|
|
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
|
|
37
|
-
node dist/dev-index.js configure
|
|
94
|
+
bun run dev:local -- configure
|
|
38
95
|
```
|
package/SKILL.md
CHANGED
|
@@ -1,136 +1,91 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentspend
|
|
3
|
-
description: Use this skill
|
|
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
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
25
31
|
```bash
|
|
26
32
|
npx agentspend configure
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
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.
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
### Pay
|
|
38
|
+
# Commands
|
|
36
39
|
|
|
40
|
+
## Use
|
|
37
41
|
```bash
|
|
38
|
-
npx agentspend
|
|
42
|
+
npx agentspend use <url>
|
|
39
43
|
```
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
**Options:**
|
|
44
|
-
- `--method <method>` — HTTP method (default: `GET`)
|
|
45
|
-
- `--body <body>` — Request body (JSON or text)
|
|
46
|
-
- `--header <header>` — Header in `key:value` format (repeatable)
|
|
47
|
-
- `--max-cost <usd>` — Maximum acceptable charge in USD (up to 6 decimal places)
|
|
45
|
+
`<url>` must be a direct HTTPS URL.
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
**Example:**
|
|
47
|
+
Options:
|
|
48
|
+
- `--method <method>` HTTP method
|
|
49
|
+
- `--header <key:value>` repeatable header
|
|
50
|
+
- `--body <json-or-text>` request body
|
|
54
51
|
|
|
52
|
+
Examples:
|
|
55
53
|
```bash
|
|
56
|
-
npx agentspend
|
|
57
|
-
--method POST \
|
|
58
|
-
--header "key:value" \
|
|
59
|
-
--body '{"key": "value"}' \
|
|
60
|
-
--max-cost 0.05
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Check
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npx agentspend check <url>
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Discover an endpoint's price without paying.
|
|
70
|
-
|
|
71
|
-
Important:
|
|
72
|
-
- `check` must use the same request shape you plan to `pay` with.
|
|
73
|
-
- Always pass `--method` for non-GET endpoints.
|
|
74
|
-
- If the endpoint needs headers/body, include the same `--header` and `--body` on `check`.
|
|
75
|
-
- If request shape is wrong, endpoint may return `404`/`400` instead of `402`, and no price can be extracted.
|
|
76
|
-
|
|
77
|
-
**Example:**
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
npx agentspend check <url> \
|
|
54
|
+
npx agentspend use https://stableenrich.dev/api/exa/search \
|
|
81
55
|
--method POST \
|
|
82
56
|
--header "content-type:application/json" \
|
|
83
|
-
--body '{"
|
|
57
|
+
--body '{"query":"latest robotics news","numResults":5}'
|
|
84
58
|
```
|
|
85
59
|
|
|
86
|
-
|
|
87
|
-
- Price in USD
|
|
88
|
-
- Description (if available)
|
|
89
|
-
|
|
90
|
-
### Search
|
|
91
|
-
|
|
60
|
+
## Search
|
|
92
61
|
```bash
|
|
93
62
|
npx agentspend search <keywords>
|
|
94
63
|
```
|
|
95
64
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
**Example:**
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npx agentspend search "video generation"
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Status
|
|
65
|
+
Returns up to 5 matching services with domain and skill link.
|
|
105
66
|
|
|
67
|
+
## Status
|
|
106
68
|
```bash
|
|
107
69
|
npx agentspend status
|
|
108
70
|
```
|
|
109
71
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**Returns:**
|
|
113
|
-
- Weekly budget
|
|
114
|
-
- Amount spent this week
|
|
115
|
-
- Remaining budget
|
|
116
|
-
- Recent charges with amounts, domains, and timestamps
|
|
117
|
-
|
|
118
|
-
### Configure
|
|
72
|
+
Shows weekly budget, spend, remaining budget, and recent charges.
|
|
119
73
|
|
|
74
|
+
## Configure
|
|
120
75
|
```bash
|
|
121
76
|
npx agentspend configure
|
|
122
77
|
```
|
|
123
78
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
## Spending Controls
|
|
127
|
-
|
|
128
|
-
- **Weekly budget** — Set during configure. Requests that would exceed the budget are rejected.
|
|
129
|
-
- **Per-request max cost** — Use `--max-cost` on `pay` to reject requests above a price threshold.
|
|
130
|
-
- **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.
|
|
131
81
|
|
|
132
|
-
|
|
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.
|
|
133
86
|
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
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 {
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
3
|
import { runConfigure } from "./commands/configure.js";
|
|
4
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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(
|
|
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("
|
|
39
|
-
.argument("<url>", "
|
|
40
|
-
.description("
|
|
41
|
-
.option("-X, --method <method>", "HTTP method
|
|
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
|
|
26
|
+
await runUse(apiClient, url, commandOptions);
|
|
48
27
|
});
|
|
49
28
|
program
|
|
50
29
|
.command("search")
|
|
@@ -1,44 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
17
|
-
|
|
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
|
}
|
package/dist/commands/pay.js
CHANGED
|
@@ -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,
|
|
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 === "
|
|
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;
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
59
|
-
return this.request("/
|
|
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);
|
package/dist/lib/auth-flow.js
CHANGED
|
@@ -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
|
|
52
|
+
throw new Error("Configure session is no longer claimable. Run `agentspend configure` again.");
|
|
56
53
|
}
|