minia2a-skill 1.2.1 → 1.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 +17 -2
- package/cli/minia2a +111 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,10 +71,23 @@ Every call is priced in the 402 response. The buyer pays USDC (or spends free cr
|
|
|
71
71
|
minia2a discover [query] [--category cat] [--sort volume|price]
|
|
72
72
|
minia2a meta
|
|
73
73
|
minia2a register --name <n> --wallet <0x...> --signature <0x...>
|
|
74
|
-
minia2a
|
|
74
|
+
minia2a publish --name <n> --endpoint <url> --price <cents> --description <text> \
|
|
75
|
+
[--category tools|premium|defi|data] --wallet <0x...> --signature <0x...>
|
|
76
|
+
minia2a call <id> --wallet <0x...> [--signature <0x...> --timestamp <unix>] \
|
|
77
|
+
[--input '{}'] [--probe]
|
|
75
78
|
minia2a help
|
|
76
79
|
```
|
|
77
80
|
|
|
81
|
+
`register` is the **buyer** side (it provisions free credits). `publish` is the
|
|
82
|
+
**seller** side (it lists your API for pay-per-call). They are different endpoints
|
|
83
|
+
and sign different messages — registering does not create a listing.
|
|
84
|
+
|
|
85
|
+
`call` signs a third, per-call message: `minia2a trial:<wallet>:<serviceId>:<unixSeconds>`.
|
|
86
|
+
The `serviceId` there is the catalog id (`x402-time`), **not** the URL slug (`time`) — signing
|
|
87
|
+
the slug fails with the same 402 a bad signature returns, so the CLI resolves the id for you and
|
|
88
|
+
prints the exact string to sign. Without `--signature`, `--wallet` draws on the anonymous per-IP
|
|
89
|
+
bucket, not the wallet's own.
|
|
90
|
+
|
|
78
91
|
All commands output JSON. Exit code 0 = success. `--probe` returns the 402 payment JSON without consuming credits.
|
|
79
92
|
|
|
80
93
|
## Agent Wrappers
|
|
@@ -101,8 +114,10 @@ minia2a discover
|
|
|
101
114
|
# 3. Register (self-custody wallet + EIP-191 signature) → 500 free credits
|
|
102
115
|
minia2a register --name "My Agent" --wallet 0x... --signature 0x...
|
|
103
116
|
|
|
104
|
-
# 4. Call a service
|
|
117
|
+
# 4. Call a service. --wallet alone uses the anonymous per-IP bucket; run it once and
|
|
118
|
+
# the CLI prints the exact message to sign for the wallet's own 15 trials.
|
|
105
119
|
minia2a call x402-time --wallet 0x...
|
|
120
|
+
minia2a call x402-time --wallet 0x... --timestamp 1787113324 --signature 0x...
|
|
106
121
|
```
|
|
107
122
|
|
|
108
123
|
## Pricing
|
package/cli/minia2a
CHANGED
|
@@ -12,7 +12,10 @@ Usage:
|
|
|
12
12
|
minia2a discover [query] [--category <cat>] [--sort volume|price]
|
|
13
13
|
minia2a meta
|
|
14
14
|
minia2a register --name <n> --wallet <0x...> --signature <0x...>
|
|
15
|
-
minia2a
|
|
15
|
+
minia2a publish --name <n> --endpoint <url> --price <cents> --description <text>
|
|
16
|
+
[--category <cat>] --wallet <0x...> --signature <0x...>
|
|
17
|
+
minia2a call <service-id> --wallet <0x...> [--signature <0x...> --timestamp <unix>]
|
|
18
|
+
[--input <json>] [--probe]
|
|
16
19
|
minia2a help [topic]
|
|
17
20
|
|
|
18
21
|
No human signup. Register with a self-custody wallet + EIP-191 signature
|
|
@@ -110,6 +113,75 @@ async function register() {
|
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
// Seller path. `register` above is BUYER-side (it provisions credits, not a listing) —
|
|
117
|
+
// publishing a service is a different endpoint with a different signed message.
|
|
118
|
+
const CATEGORIES = ['tools', 'premium', 'defi', 'data'];
|
|
119
|
+
|
|
120
|
+
async function publish() {
|
|
121
|
+
const name = flag('name');
|
|
122
|
+
const endpoint = flag('endpoint');
|
|
123
|
+
const price = flag('price');
|
|
124
|
+
const wallet = flag('wallet');
|
|
125
|
+
const signature = flag('signature');
|
|
126
|
+
let category = flag('category');
|
|
127
|
+
|
|
128
|
+
if (!name || name === true) bail('--name <name> required');
|
|
129
|
+
if (!endpoint || endpoint === true) bail('--endpoint <https://...> required');
|
|
130
|
+
if (!wallet || wallet === true) bail('--wallet <0x...> required (revenue settles here)');
|
|
131
|
+
|
|
132
|
+
if (!/^https?:\/\//i.test(endpoint)) bail('--endpoint must be an absolute http(s) URL');
|
|
133
|
+
|
|
134
|
+
// Rejected server-side anyway (SSRF guard) — fail early with a clearer reason.
|
|
135
|
+
if (/^https?:\/\/(localhost|127\.|10\.|192\.168\.|169\.254\.|\[::1\])/i.test(endpoint)) {
|
|
136
|
+
bail('--endpoint must be publicly reachable; internal/loopback addresses are rejected');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const priceCents = Number(price);
|
|
140
|
+
if (!Number.isInteger(priceCents) || priceCents < 1 || priceCents > 10000) {
|
|
141
|
+
bail('--price <cents> required: integer 1–10000 (1 = $0.01, 10000 = $100.00)');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Server enforces a 20-char minimum; catch it here so the failure is legible.
|
|
145
|
+
const description = flag('description');
|
|
146
|
+
if (!description || description === true || String(description).length < 20) {
|
|
147
|
+
bail('--description <text> required: at least 20 characters (buyers see this in the catalog)');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!category || category === true) category = 'tools';
|
|
151
|
+
if (!CATEGORIES.includes(category)) {
|
|
152
|
+
bail(`--category must be one of: ${CATEGORIES.join(', ')}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!signature || signature === true) {
|
|
156
|
+
// Same contract as register: the CLI never holds your key.
|
|
157
|
+
console.error('A signature is required. Sign this exact message with your wallet (EIP-191 personal_sign):\n');
|
|
158
|
+
console.error(` minia2a publish: ${wallet}\n`);
|
|
159
|
+
console.error('Then re-run with --signature <0x...>:\n');
|
|
160
|
+
console.error(` minia2a publish --name "${name}" --endpoint "${endpoint}" --price ${priceCents} --wallet "${wallet}" --signature "0xYOUR_SIGNATURE"\n`);
|
|
161
|
+
console.error('(cast example: cast wallet sign --data 0x... "minia2a publish: <wallet>")');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const body = { name, endpoint, price_cents: priceCents, category, description, wallet, signature };
|
|
166
|
+
try {
|
|
167
|
+
const r = await fetch(`${API}/api/v1/publish-service`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
170
|
+
body: JSON.stringify(body)
|
|
171
|
+
});
|
|
172
|
+
const d = await r.json();
|
|
173
|
+
json(d);
|
|
174
|
+
if (!r.ok) process.exit(3);
|
|
175
|
+
// The catalog does not round-trip an HTTP method yet, so POST-only endpoints
|
|
176
|
+
// get called with GET by generic clients. Warn rather than let it fail silently.
|
|
177
|
+
console.error('\nPublished. Note: the catalog does not yet record an HTTP method —');
|
|
178
|
+
console.error('callers default to GET. If your endpoint requires POST, state that in');
|
|
179
|
+
console.error('the service description until method round-trip ships.');
|
|
180
|
+
} catch (e) {
|
|
181
|
+
bail(`Cannot reach marketplace: ${e.message}`, 3);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
113
185
|
async function call(id) {
|
|
114
186
|
if (!id) bail('Usage: minia2a call <service-id> --wallet <0x...> [--input <json>] [--probe]');
|
|
115
187
|
const wallet = flag('wallet');
|
|
@@ -134,8 +206,32 @@ async function call(id) {
|
|
|
134
206
|
const url = new URL(ep);
|
|
135
207
|
url.searchParams.set(probe ? 'probe' : 'wallet', probe ? '1' : wallet);
|
|
136
208
|
|
|
209
|
+
// A registered wallet's own 15 trials are only reachable with a signed call. The signed
|
|
210
|
+
// message uses the catalog id (x402-time), not the URL slug (time) — signing the slug fails
|
|
211
|
+
// with exactly the 402 a bad signature produces, so print the id-correct message rather than
|
|
212
|
+
// letting the caller guess it.
|
|
213
|
+
const trialHeaders = {};
|
|
214
|
+
const signature = flag('signature');
|
|
215
|
+
if (!probe && wallet && wallet !== true) {
|
|
216
|
+
const ts = flag('timestamp') && flag('timestamp') !== true
|
|
217
|
+
? String(flag('timestamp'))
|
|
218
|
+
: String(Math.floor(Date.now() / 1000));
|
|
219
|
+
if (signature && signature !== true) {
|
|
220
|
+
trialHeaders['X-Wallet-Signature'] = signature;
|
|
221
|
+
trialHeaders['X-Trial-Timestamp'] = ts;
|
|
222
|
+
} else {
|
|
223
|
+
console.error('Note: --wallet alone does not draw on the wallet\'s trial bucket — this call');
|
|
224
|
+
console.error('will use the anonymous per-IP bucket. To use the wallet\'s own 15 trials, sign');
|
|
225
|
+
console.error('this exact message (EIP-191 personal_sign):\n');
|
|
226
|
+
console.error(` minia2a trial:${wallet}:${svc.id}:${ts}\n`);
|
|
227
|
+
console.error('then rerun within 5 minutes:\n');
|
|
228
|
+
console.error(` minia2a call ${id} --wallet ${wallet} --timestamp ${ts} --signature 0xYOUR_SIGNATURE\n`);
|
|
229
|
+
console.error(`(cast example: cast wallet sign "minia2a trial:${wallet}:${svc.id}:${ts}")\n`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
137
233
|
try {
|
|
138
|
-
const opts = { headers: { 'User-Agent': 'minia2a-skill/1.x' } };
|
|
234
|
+
const opts = { headers: { 'User-Agent': 'minia2a-skill/1.x', ...trialHeaders } };
|
|
139
235
|
let r;
|
|
140
236
|
if (input !== undefined) {
|
|
141
237
|
opts.method = 'POST';
|
|
@@ -151,11 +247,19 @@ async function call(id) {
|
|
|
151
247
|
if (r.status === 402) {
|
|
152
248
|
// Payment required — surface the machine-readable x402 payload.
|
|
153
249
|
console.error(`Payment required (HTTP 402) for ${svc.name}. Price: $${svc.price} USDC.`);
|
|
250
|
+
if (trialHeaders['X-Wallet-Signature']) {
|
|
251
|
+
console.error(`Signed as ${wallet}: either this wallet's 15 trials are spent, or the wallet`);
|
|
252
|
+
console.error('is not registered, or the timestamp is older than 5 minutes.');
|
|
253
|
+
}
|
|
154
254
|
console.error('x402 accepts[]:');
|
|
155
255
|
json(d);
|
|
156
256
|
process.exit(3);
|
|
157
257
|
}
|
|
158
258
|
|
|
259
|
+
// Which bucket paid for the call — "wallet" means the signature was accepted.
|
|
260
|
+
const mode = r.headers.get('x-trial-mode');
|
|
261
|
+
if (mode) console.error(`trial: mode=${mode} remaining=${r.headers.get('x-trial-remaining') ?? '?'}`);
|
|
262
|
+
|
|
159
263
|
json(d);
|
|
160
264
|
if (!r.ok) process.exit(3);
|
|
161
265
|
} catch (e) {
|
|
@@ -169,7 +273,8 @@ async function help() {
|
|
|
169
273
|
console.error(`minia2a help: ${topic}\n`);
|
|
170
274
|
console.error('The full machine-readable guide lives at https://minia2a.uk/AGENTS.md');
|
|
171
275
|
console.error('Quick reference:');
|
|
172
|
-
console.error(' register — POST /api/v1/register-simple {name, wallet, signature} → 500 free credits');
|
|
276
|
+
console.error(' register — POST /api/v1/register-simple {name, wallet, signature} → 500 free credits (BUYER side)');
|
|
277
|
+
console.error(' publish — POST /api/v1/publish-service {name, endpoint, price_cents, category, wallet, signature} (SELLER side)');
|
|
173
278
|
console.error(' call — GET <service-endpoint>?wallet=0x... (credits decrement)');
|
|
174
279
|
console.error(' probe — append ?probe=1 to any endpoint → HTTP 402 with payment JSON (free)');
|
|
175
280
|
return;
|
|
@@ -189,6 +294,9 @@ async function help() {
|
|
|
189
294
|
case 'info':
|
|
190
295
|
case 'stats':
|
|
191
296
|
return await meta();
|
|
297
|
+
case 'publish':
|
|
298
|
+
case 'sell':
|
|
299
|
+
return await publish();
|
|
192
300
|
case 'register':
|
|
193
301
|
case 'reg':
|
|
194
302
|
return await register();
|
package/package.json
CHANGED