@obolos_tech/cli 0.4.0 → 0.5.0
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 +35 -1
- package/dist/commands/anp.d.ts +277 -0
- package/dist/commands/anp.js +440 -0
- package/dist/commands/anp.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +34 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/jobs.d.ts +111 -0
- package/dist/commands/jobs.js +294 -0
- package/dist/commands/jobs.js.map +1 -0
- package/dist/commands/listings.d.ts +128 -0
- package/dist/commands/listings.js +246 -0
- package/dist/commands/listings.js.map +1 -0
- package/dist/commands/marketplace.d.ts +87 -0
- package/dist/commands/marketplace.js +133 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/commands/reputation.d.ts +27 -0
- package/dist/commands/reputation.js +114 -0
- package/dist/commands/reputation.js.map +1 -0
- package/dist/commands/setup.d.ts +78 -0
- package/dist/commands/setup.js +133 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/wallet.d.ts +30 -0
- package/dist/commands/wallet.js +66 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/index.d.ts +4 -24
- package/dist/index.js +61 -2847
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +53 -0
- package/dist/registry.js +39 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime/acp.d.ts +162 -0
- package/dist/runtime/acp.js +132 -0
- package/dist/runtime/acp.js.map +1 -0
- package/dist/runtime/anp.d.ts +214 -0
- package/dist/runtime/anp.js +255 -0
- package/dist/runtime/anp.js.map +1 -0
- package/dist/runtime/argv.d.ts +18 -0
- package/dist/runtime/argv.js +114 -0
- package/dist/runtime/argv.js.map +1 -0
- package/dist/runtime/config.d.ts +14 -0
- package/dist/runtime/config.js +34 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/dispatch.d.ts +13 -0
- package/dist/runtime/dispatch.js +123 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/display.d.ts +21 -0
- package/dist/runtime/display.js +68 -0
- package/dist/runtime/display.js.map +1 -0
- package/dist/runtime/errors.d.ts +19 -0
- package/dist/runtime/errors.js +23 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/http.d.ts +9 -0
- package/dist/runtime/http.js +36 -0
- package/dist/runtime/http.js.map +1 -0
- package/dist/runtime/output.d.ts +19 -0
- package/dist/runtime/output.js +12 -0
- package/dist/runtime/output.js.map +1 -0
- package/dist/runtime/payment.d.ts +21 -0
- package/dist/runtime/payment.js +91 -0
- package/dist/runtime/payment.js.map +1 -0
- package/dist/runtime/wallet.d.ts +32 -0
- package/dist/runtime/wallet.js +44 -0
- package/dist/runtime/wallet.js.map +1 -0
- package/dist/schema/json-schema.d.ts +10 -0
- package/dist/schema/json-schema.js +34 -0
- package/dist/schema/json-schema.js.map +1 -0
- package/dist/schema/zod-shape.d.ts +9 -0
- package/dist/schema/zod-shape.js +36 -0
- package/dist/schema/zod-shape.js.map +1 -0
- package/package.json +45 -3
- package/scripts/lint-stdout-purity.mjs +62 -0
package/dist/index.js
CHANGED
|
@@ -1,2877 +1,91 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Obolos CLI
|
|
3
|
+
* Obolos CLI entrypoint.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* npx @obolos_tech/cli info ext-abc123
|
|
9
|
-
* npx @obolos_tech/cli call ext-abc123 --body '{"symbol":"ETH"}'
|
|
10
|
-
* npx @obolos_tech/cli categories
|
|
11
|
-
* npx @obolos_tech/cli balance
|
|
12
|
-
* npx @obolos_tech/cli setup-mcp
|
|
13
|
-
* npx @obolos_tech/cli job list --status=open
|
|
14
|
-
* npx @obolos_tech/cli job create --title "..." --evaluator 0x...
|
|
15
|
-
* npx @obolos_tech/cli job info <id>
|
|
16
|
-
* npx @obolos_tech/cli listing list --status=open
|
|
17
|
-
* npx @obolos_tech/cli listing create --title "..." --max-budget 10.00
|
|
18
|
-
* npx @obolos_tech/cli listing bid <id> --price 5.00
|
|
19
|
-
* npx @obolos_tech/cli anp list --status=open
|
|
20
|
-
* npx @obolos_tech/cli anp create --title "..." --min-budget 5 --max-budget 50
|
|
21
|
-
* npx @obolos_tech/cli anp bid <cid> --price 25 --delivery 48h
|
|
22
|
-
* npx @obolos_tech/cli anp accept <cid> --bid <bid_cid>
|
|
23
|
-
* npx @obolos_tech/cli anp verify <cid>
|
|
24
|
-
* npx @obolos_tech/cli reputation check 16907
|
|
25
|
-
* npx @obolos_tech/cli reputation check 16907 --chain ethereum
|
|
26
|
-
* npx @obolos_tech/cli reputation compare 123 456 789
|
|
27
|
-
* npx @obolos_tech/cli rep compare base:123 ethereum:456
|
|
5
|
+
* All commands live in `cli/src/commands/` and are registered in
|
|
6
|
+
* `cli/src/commands/index.ts`. This file only dispatches argv and renders
|
|
7
|
+
* top-level help. See `cli/src/registry.ts` for the Command contract.
|
|
28
8
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
35
|
-
function loadConfig() {
|
|
36
|
-
try {
|
|
37
|
-
if (existsSync(CONFIG_FILE)) {
|
|
38
|
-
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch { }
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
function saveConfig(config) {
|
|
45
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
46
|
-
mkdirSync(CONFIG_DIR, { mode: 0o700 });
|
|
47
|
-
}
|
|
48
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
49
|
-
}
|
|
50
|
-
const config = loadConfig();
|
|
51
|
-
const OBOLOS_API_URL = process.env.OBOLOS_API_URL || config.api_url || 'https://obolos.tech';
|
|
52
|
-
const OBOLOS_PRIVATE_KEY = process.env.OBOLOS_PRIVATE_KEY || config.private_key || '';
|
|
53
|
-
// ─── Colors (no deps) ──────────────────────────────────────────────────────
|
|
54
|
-
// ─── ACP Contract ABIs (ERC-8183) ─────────────────────────────────────────
|
|
55
|
-
const ACP_ADDRESS = '0xaF3148696242F7Fb74893DC47690e37950807362';
|
|
56
|
-
const USDC_CONTRACT = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
57
|
-
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
58
|
-
const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
59
|
-
const ACP_ABI = [
|
|
60
|
-
{
|
|
61
|
-
type: 'function',
|
|
62
|
-
name: 'createJob',
|
|
63
|
-
inputs: [
|
|
64
|
-
{ name: 'provider', type: 'address' },
|
|
65
|
-
{ name: 'evaluator', type: 'address' },
|
|
66
|
-
{ name: 'expiredAt', type: 'uint256' },
|
|
67
|
-
{ name: 'description', type: 'string' },
|
|
68
|
-
{ name: 'hook', type: 'address' },
|
|
69
|
-
],
|
|
70
|
-
outputs: [{ name: 'jobId', type: 'uint256' }],
|
|
71
|
-
stateMutability: 'nonpayable',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: 'function',
|
|
75
|
-
name: 'fund',
|
|
76
|
-
inputs: [
|
|
77
|
-
{ name: 'jobId', type: 'uint256' },
|
|
78
|
-
{ name: 'expectedBudget', type: 'uint256' },
|
|
79
|
-
{ name: 'optParams', type: 'bytes' },
|
|
80
|
-
],
|
|
81
|
-
outputs: [],
|
|
82
|
-
stateMutability: 'nonpayable',
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
type: 'function',
|
|
86
|
-
name: 'submit',
|
|
87
|
-
inputs: [
|
|
88
|
-
{ name: 'jobId', type: 'uint256' },
|
|
89
|
-
{ name: 'deliverable', type: 'bytes32' },
|
|
90
|
-
{ name: 'optParams', type: 'bytes' },
|
|
91
|
-
],
|
|
92
|
-
outputs: [],
|
|
93
|
-
stateMutability: 'nonpayable',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
type: 'function',
|
|
97
|
-
name: 'complete',
|
|
98
|
-
inputs: [
|
|
99
|
-
{ name: 'jobId', type: 'uint256' },
|
|
100
|
-
{ name: 'reason', type: 'bytes32' },
|
|
101
|
-
{ name: 'optParams', type: 'bytes' },
|
|
102
|
-
],
|
|
103
|
-
outputs: [],
|
|
104
|
-
stateMutability: 'nonpayable',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
type: 'function',
|
|
108
|
-
name: 'reject',
|
|
109
|
-
inputs: [
|
|
110
|
-
{ name: 'jobId', type: 'uint256' },
|
|
111
|
-
{ name: 'reason', type: 'bytes32' },
|
|
112
|
-
{ name: 'optParams', type: 'bytes' },
|
|
113
|
-
],
|
|
114
|
-
outputs: [],
|
|
115
|
-
stateMutability: 'nonpayable',
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
type: 'event',
|
|
119
|
-
name: 'JobCreated',
|
|
120
|
-
inputs: [
|
|
121
|
-
{ name: 'jobId', type: 'uint256', indexed: true },
|
|
122
|
-
{ name: 'client', type: 'address', indexed: true },
|
|
123
|
-
{ name: 'provider', type: 'address', indexed: false },
|
|
124
|
-
{ name: 'evaluator', type: 'address', indexed: false },
|
|
125
|
-
{ name: 'expiredAt', type: 'uint256', indexed: false },
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
];
|
|
129
|
-
const ERC20_ABI = [
|
|
130
|
-
{
|
|
131
|
-
type: 'function',
|
|
132
|
-
name: 'approve',
|
|
133
|
-
inputs: [
|
|
134
|
-
{ name: 'spender', type: 'address' },
|
|
135
|
-
{ name: 'amount', type: 'uint256' },
|
|
136
|
-
],
|
|
137
|
-
outputs: [{ name: '', type: 'bool' }],
|
|
138
|
-
stateMutability: 'nonpayable',
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
type: 'function',
|
|
142
|
-
name: 'allowance',
|
|
143
|
-
inputs: [
|
|
144
|
-
{ name: 'owner', type: 'address' },
|
|
145
|
-
{ name: 'spender', type: 'address' },
|
|
146
|
-
],
|
|
147
|
-
outputs: [{ name: '', type: 'uint256' }],
|
|
148
|
-
stateMutability: 'view',
|
|
149
|
-
},
|
|
150
|
-
];
|
|
9
|
+
import { dispatch } from './runtime/dispatch.js';
|
|
10
|
+
import { registry } from './commands/index.js';
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const command = args[0];
|
|
13
|
+
const commandArgs = args.slice(1);
|
|
151
14
|
const c = {
|
|
152
|
-
reset: '\x1b[0m',
|
|
153
|
-
|
|
154
|
-
dim: '\x1b[2m',
|
|
155
|
-
green: '\x1b[32m',
|
|
156
|
-
yellow: '\x1b[33m',
|
|
157
|
-
blue: '\x1b[34m',
|
|
158
|
-
magenta: '\x1b[35m',
|
|
159
|
-
cyan: '\x1b[36m',
|
|
160
|
-
red: '\x1b[31m',
|
|
161
|
-
white: '\x1b[37m',
|
|
162
|
-
gray: '\x1b[90m',
|
|
15
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
16
|
+
cyan: '\x1b[36m', red: '\x1b[31m', yellow: '\x1b[33m',
|
|
163
17
|
};
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const res = await fetch(`${OBOLOS_API_URL}${path}`);
|
|
170
|
-
if (!res.ok)
|
|
171
|
-
throw new Error(`${res.status} ${res.statusText}`);
|
|
172
|
-
return res.json();
|
|
173
|
-
}
|
|
174
|
-
async function apiPost(path, body, headers) {
|
|
175
|
-
const res = await fetch(`${OBOLOS_API_URL}${path}`, {
|
|
176
|
-
method: 'POST',
|
|
177
|
-
headers: { 'Content-Type': 'application/json', ...headers },
|
|
178
|
-
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
179
|
-
});
|
|
180
|
-
if (!res.ok) {
|
|
181
|
-
let msg = `${res.status} ${res.statusText}`;
|
|
182
|
-
try {
|
|
183
|
-
const err = await res.json();
|
|
184
|
-
if (err.error)
|
|
185
|
-
msg = err.error;
|
|
186
|
-
else if (err.message)
|
|
187
|
-
msg = err.message;
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
190
|
-
throw new Error(msg);
|
|
191
|
-
}
|
|
192
|
-
return res.json();
|
|
193
|
-
}
|
|
194
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
195
|
-
function getFlag(args, name) {
|
|
196
|
-
// Supports --name=value and --name value
|
|
197
|
-
for (let i = 0; i < args.length; i++) {
|
|
198
|
-
if (args[i] === `--${name}` && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
199
|
-
return args[i + 1];
|
|
200
|
-
}
|
|
201
|
-
if (args[i].startsWith(`--${name}=`)) {
|
|
202
|
-
return args[i].slice(`--${name}=`.length);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
207
|
-
function getPositional(args, index) {
|
|
208
|
-
let pos = 0;
|
|
209
|
-
for (const arg of args) {
|
|
210
|
-
if (!arg.startsWith('--')) {
|
|
211
|
-
if (pos === index)
|
|
212
|
-
return arg;
|
|
213
|
-
pos++;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
function shortenAddr(addr) {
|
|
219
|
-
if (!addr)
|
|
220
|
-
return `${c.dim}—${c.reset}`;
|
|
221
|
-
if (addr.length <= 12)
|
|
222
|
-
return addr;
|
|
223
|
-
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
|
224
|
-
}
|
|
225
|
-
function shortenId(id) {
|
|
226
|
-
if (id.length <= 12)
|
|
227
|
-
return id;
|
|
228
|
-
return `${id.slice(0, 8)}...`;
|
|
229
|
-
}
|
|
230
|
-
function formatDate(iso) {
|
|
231
|
-
if (!iso)
|
|
232
|
-
return `${c.dim}—${c.reset}`;
|
|
233
|
-
const d = new Date(iso);
|
|
234
|
-
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
235
|
-
}
|
|
236
|
-
function statusColor(status) {
|
|
237
|
-
switch (status) {
|
|
238
|
-
case 'open': return `${c.yellow}${status}${c.reset}`;
|
|
239
|
-
case 'funded': return `${c.blue}${status}${c.reset}`;
|
|
240
|
-
case 'submitted': return `${c.cyan}${status}${c.reset}`;
|
|
241
|
-
case 'completed': return `${c.green}${status}${c.reset}`;
|
|
242
|
-
case 'rejected': return `${c.red}${status}${c.reset}`;
|
|
243
|
-
case 'expired': return `${c.gray}${status}${c.reset}`;
|
|
244
|
-
case 'negotiating': return `${c.magenta}${status}${c.reset}`;
|
|
245
|
-
case 'accepted': return `${c.green}${status}${c.reset}`;
|
|
246
|
-
case 'cancelled': return `${c.gray}${status}${c.reset}`;
|
|
247
|
-
default: return status;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function parseRelativeTime(input) {
|
|
251
|
-
const match = input.match(/^(\d+)\s*(h|hr|hrs|hour|hours|d|day|days|m|min|mins|minute|minutes)$/i);
|
|
252
|
-
if (!match) {
|
|
253
|
-
// Try parsing as ISO date directly
|
|
254
|
-
const d = new Date(input);
|
|
255
|
-
if (!isNaN(d.getTime()))
|
|
256
|
-
return d.toISOString();
|
|
257
|
-
throw new Error(`Cannot parse expiry: "${input}". Use formats like "24h", "7d", "1h", or an ISO date.`);
|
|
258
|
-
}
|
|
259
|
-
const num = parseInt(match[1], 10);
|
|
260
|
-
const unit = match[2].toLowerCase();
|
|
261
|
-
const now = Date.now();
|
|
262
|
-
let ms = 0;
|
|
263
|
-
if (unit.startsWith('h'))
|
|
264
|
-
ms = num * 60 * 60 * 1000;
|
|
265
|
-
else if (unit.startsWith('d'))
|
|
266
|
-
ms = num * 24 * 60 * 60 * 1000;
|
|
267
|
-
else if (unit.startsWith('m'))
|
|
268
|
-
ms = num * 60 * 1000;
|
|
269
|
-
return new Date(now + ms).toISOString();
|
|
270
|
-
}
|
|
271
|
-
function resolveWalletAddress() {
|
|
272
|
-
const cfg = loadConfig();
|
|
273
|
-
const addr = cfg.wallet_address;
|
|
274
|
-
if (addr)
|
|
275
|
-
return addr;
|
|
276
|
-
// Derive from private key if available
|
|
277
|
-
if (!OBOLOS_PRIVATE_KEY) {
|
|
278
|
-
console.error(`${c.red}No wallet configured.${c.reset} Run ${c.cyan}obolos setup${c.reset} first.`);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
// We'll derive it lazily — for now return empty and let callers handle async derivation
|
|
282
|
-
return '';
|
|
283
|
-
}
|
|
284
|
-
async function getWalletAddress() {
|
|
285
|
-
const cfg = loadConfig();
|
|
286
|
-
if (cfg.wallet_address)
|
|
287
|
-
return cfg.wallet_address;
|
|
288
|
-
if (!OBOLOS_PRIVATE_KEY) {
|
|
289
|
-
console.error(`${c.red}No wallet configured.${c.reset} Run ${c.cyan}obolos setup${c.reset} first.`);
|
|
290
|
-
process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
293
|
-
const key = OBOLOS_PRIVATE_KEY.startsWith('0x') ? OBOLOS_PRIVATE_KEY : `0x${OBOLOS_PRIVATE_KEY}`;
|
|
294
|
-
const account = privateKeyToAccount(key);
|
|
295
|
-
return account.address;
|
|
296
|
-
}
|
|
297
|
-
async function getACPClient() {
|
|
298
|
-
const key = OBOLOS_PRIVATE_KEY;
|
|
299
|
-
if (!key) {
|
|
300
|
-
console.error(`${c.red}No wallet configured.${c.reset} Run ${c.cyan}obolos setup${c.reset} first.`);
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
const { createPublicClient, createWalletClient, http: viemHttp, parseUnits, keccak256, toHex, decodeEventLog } = await import('viem');
|
|
304
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
305
|
-
const { base } = await import('viem/chains');
|
|
306
|
-
const normalizedKey = key.startsWith('0x') ? key : `0x${key}`;
|
|
307
|
-
const account = privateKeyToAccount(normalizedKey);
|
|
308
|
-
const publicClient = createPublicClient({ chain: base, transport: viemHttp() });
|
|
309
|
-
const walletClient = createWalletClient({ account, chain: base, transport: viemHttp() });
|
|
310
|
-
return { account, publicClient, walletClient, parseUnits, keccak256, toHex, decodeEventLog };
|
|
311
|
-
}
|
|
312
|
-
function stateVisualization(status) {
|
|
313
|
-
const states = ['open', 'funded', 'submitted', 'completed'];
|
|
314
|
-
const parts = states.map(s => {
|
|
315
|
-
if (s === status)
|
|
316
|
-
return `${c.bold}[${s.charAt(0).toUpperCase() + s.slice(1)}]${c.reset}`;
|
|
317
|
-
return `${c.dim}${s.charAt(0).toUpperCase() + s.slice(1)}${c.reset}`;
|
|
318
|
-
});
|
|
319
|
-
// Handle terminal states that branch off
|
|
320
|
-
if (status === 'rejected') {
|
|
321
|
-
const base = states.slice(0, 3).map(s => `${c.dim}${s.charAt(0).toUpperCase() + s.slice(1)}${c.reset}`);
|
|
322
|
-
return ` ${base.join(` ${c.dim}->${c.reset} `)} ${c.dim}->${c.reset} ${c.bold}${c.red}[Rejected]${c.reset}`;
|
|
323
|
-
}
|
|
324
|
-
if (status === 'expired') {
|
|
325
|
-
return ` ${c.bold}${c.gray}[Expired]${c.reset}`;
|
|
326
|
-
}
|
|
327
|
-
return ` ${parts.join(` ${c.dim}->${c.reset} `)}`;
|
|
328
|
-
}
|
|
329
|
-
// ─── ANP Helpers ─────────────────────────────────────────────────────────────
|
|
330
|
-
import { computeContentHash, ANP_TYPES, getANPDomain, hashListingIntent, hashBidIntent, hashAcceptIntent, hashAmendmentIntent, hashCheckpointIntent, usdToUsdc } from '@obolos_tech/anp-sdk';
|
|
331
|
-
function parseTimeToSeconds(input) {
|
|
332
|
-
const match = input.match(/^(\d+)\s*(s|sec|secs|second|seconds|h|hr|hrs|hour|hours|d|day|days|m|min|mins|minute|minutes)$/i);
|
|
333
|
-
if (!match) {
|
|
334
|
-
throw new Error(`Cannot parse time: "${input}". Use formats like "48h", "7d", "3d".`);
|
|
335
|
-
}
|
|
336
|
-
const num = parseInt(match[1], 10);
|
|
337
|
-
const unit = match[2].toLowerCase();
|
|
338
|
-
if (unit.startsWith('s'))
|
|
339
|
-
return num;
|
|
340
|
-
if (unit.startsWith('m'))
|
|
341
|
-
return num * 60;
|
|
342
|
-
if (unit.startsWith('h'))
|
|
343
|
-
return num * 3600;
|
|
344
|
-
if (unit.startsWith('d'))
|
|
345
|
-
return num * 86400;
|
|
346
|
-
return num;
|
|
347
|
-
}
|
|
348
|
-
const ANP_DOMAIN = getANPDomain(8453, '0xfEa362Bf569e97B20681289fB4D4a64CEBDFa792');
|
|
349
|
-
async function getANPSigningClient() {
|
|
350
|
-
if (!OBOLOS_PRIVATE_KEY) {
|
|
351
|
-
console.error(`${c.red}No wallet configured.${c.reset} Run ${c.cyan}obolos setup${c.reset} first.`);
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
const { createWalletClient, http: viemHttp } = await import('viem');
|
|
355
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
356
|
-
const { base } = await import('viem/chains');
|
|
357
|
-
const key = OBOLOS_PRIVATE_KEY.startsWith('0x') ? OBOLOS_PRIVATE_KEY : `0x${OBOLOS_PRIVATE_KEY}`;
|
|
358
|
-
const account = privateKeyToAccount(key);
|
|
359
|
-
const walletClient = createWalletClient({ account, chain: base, transport: viemHttp() });
|
|
360
|
-
return { account, walletClient, hashListingStruct: hashListingIntent, hashBidStruct: hashBidIntent, hashAcceptStruct: hashAcceptIntent, hashAmendmentStruct: hashAmendmentIntent, hashCheckpointStruct: hashCheckpointIntent };
|
|
361
|
-
}
|
|
362
|
-
function generateNonce() {
|
|
363
|
-
return BigInt(Math.floor(Math.random() * 2 ** 32));
|
|
364
|
-
}
|
|
365
|
-
async function computeJobHash(jobId) {
|
|
366
|
-
return computeContentHash({ jobId });
|
|
367
|
-
}
|
|
368
|
-
// ─── Commands ───────────────────────────────────────────────────────────────
|
|
369
|
-
async function cmdSearch(args) {
|
|
370
|
-
const query = args.join(' ');
|
|
371
|
-
const params = new URLSearchParams();
|
|
372
|
-
if (query)
|
|
373
|
-
params.set('q', query);
|
|
374
|
-
params.set('limit', '25');
|
|
375
|
-
params.set('type', 'native');
|
|
376
|
-
const data = await apiGet(`/api/marketplace/apis/search?${params}`);
|
|
377
|
-
const apis = data.apis;
|
|
378
|
-
if (apis.length === 0) {
|
|
379
|
-
console.log(`${c.yellow}No APIs found${query ? ` for "${query}"` : ''}.${c.reset}`);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
console.log(`\n${c.bold}${c.cyan}Obolos Marketplace${c.reset} ${c.dim}— ${data.pagination.total} APIs found${c.reset}\n`);
|
|
383
|
-
for (const api of apis) {
|
|
384
|
-
const name = (api.name || 'Unnamed').slice(0, 50);
|
|
385
|
-
const price = `$${api.price_per_call.toFixed(4)}`;
|
|
386
|
-
const cat = api.category;
|
|
387
|
-
const id = api.id;
|
|
388
|
-
console.log(` ${c.bold}${name}${c.reset}`);
|
|
389
|
-
console.log(` ${c.green}${price}${c.reset} ${c.dim}${cat}${c.reset} ${c.cyan}${id}${c.reset}\n`);
|
|
390
|
-
}
|
|
391
|
-
console.log(`${c.dim}Use: obolos info <id> for details, or copy the full ID to call it${c.reset}\n`);
|
|
392
|
-
}
|
|
393
|
-
async function cmdCategories() {
|
|
394
|
-
const data = await apiGet('/api/marketplace/categories');
|
|
395
|
-
console.log(`\n${c.bold}${c.cyan}API Categories${c.reset}\n`);
|
|
396
|
-
for (const cat of data.categories) {
|
|
397
|
-
const bar = '█'.repeat(Math.min(50, Math.ceil(cat.count / 5)));
|
|
398
|
-
console.log(` ${cat.name.padEnd(25)} ${c.green}${String(cat.count).padStart(4)}${c.reset} ${c.dim}${bar}${c.reset}`);
|
|
399
|
-
}
|
|
400
|
-
console.log(`\n ${c.bold}Total:${c.reset} ${data.nativeCount} native + ${data.externalCount} external\n`);
|
|
401
|
-
}
|
|
402
|
-
async function cmdInfo(args) {
|
|
403
|
-
const id = args[0];
|
|
404
|
-
if (!id) {
|
|
405
|
-
console.error(`${c.red}Usage: obolos info <api-id>${c.reset}`);
|
|
406
|
-
process.exit(1);
|
|
407
|
-
}
|
|
408
|
-
const api = await apiGet(`/api/marketplace/apis/${encodeURIComponent(id)}`);
|
|
409
|
-
console.log(`\n${c.bold}${c.cyan}${api.name}${c.reset}`);
|
|
410
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
411
|
-
console.log(` ${c.bold}ID:${c.reset} ${api.id}`);
|
|
412
|
-
console.log(` ${c.bold}Type:${c.reset} ${api.api_type}`);
|
|
413
|
-
console.log(` ${c.bold}Price:${c.reset} ${formatPrice(api.price_per_call)}`);
|
|
414
|
-
console.log(` ${c.bold}Method:${c.reset} ${api.http_method}`);
|
|
415
|
-
console.log(` ${c.bold}Category:${c.reset} ${api.category}`);
|
|
416
|
-
console.log(` ${c.bold}Seller:${c.reset} ${api.seller_name}`);
|
|
417
|
-
console.log(` ${c.bold}Calls:${c.reset} ${api.total_calls}`);
|
|
418
|
-
if (api.average_rating) {
|
|
419
|
-
console.log(` ${c.bold}Rating:${c.reset} ${api.average_rating.toFixed(1)}/5 (${api.review_count} reviews)`);
|
|
420
|
-
}
|
|
421
|
-
if (api.description) {
|
|
422
|
-
console.log(`\n ${c.bold}Description:${c.reset}`);
|
|
423
|
-
console.log(` ${api.description}`);
|
|
424
|
-
}
|
|
425
|
-
if (api.input_schema?.fields && Object.keys(api.input_schema.fields).length > 0) {
|
|
426
|
-
console.log(`\n ${c.bold}Input Fields:${c.reset}`);
|
|
427
|
-
for (const [name, field] of Object.entries(api.input_schema.fields)) {
|
|
428
|
-
const req = field.required ? `${c.red}*${c.reset}` : ' ';
|
|
429
|
-
const ex = field.example ? `${c.dim}(e.g. ${JSON.stringify(field.example)})${c.reset}` : '';
|
|
430
|
-
console.log(` ${req} ${c.cyan}${name}${c.reset}: ${field.type} ${ex}`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (api.example_request) {
|
|
434
|
-
console.log(`\n ${c.bold}Example Request:${c.reset}`);
|
|
435
|
-
try {
|
|
436
|
-
console.log(` ${c.dim}${JSON.stringify(JSON.parse(api.example_request), null, 2).replace(/\n/g, '\n ')}${c.reset}`);
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
console.log(` ${c.dim}${api.example_request}${c.reset}`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
if (api.example_response) {
|
|
443
|
-
console.log(`\n ${c.bold}Example Response:${c.reset}`);
|
|
444
|
-
try {
|
|
445
|
-
const parsed = JSON.parse(api.example_response);
|
|
446
|
-
const formatted = JSON.stringify(parsed, null, 2);
|
|
447
|
-
// Truncate long responses
|
|
448
|
-
const lines = formatted.split('\n');
|
|
449
|
-
if (lines.length > 20) {
|
|
450
|
-
console.log(` ${c.dim}${lines.slice(0, 20).join('\n ')}\n ... (${lines.length - 20} more lines)${c.reset}`);
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
console.log(` ${c.dim}${formatted.replace(/\n/g, '\n ')}${c.reset}`);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch {
|
|
457
|
-
console.log(` ${c.dim}${api.example_response.slice(0, 500)}${c.reset}`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
console.log(`\n ${c.bold}Call:${c.reset} obolos call ${api.id}${api.http_method === 'POST' ? " --body '{...}'" : ''}`);
|
|
461
|
-
console.log(` ${c.bold}Proxy:${c.reset} ${OBOLOS_API_URL}/api/proxy/${api.id}`);
|
|
462
|
-
if (api.slug) {
|
|
463
|
-
console.log(` ${c.bold}Slug:${c.reset} ${OBOLOS_API_URL}/api/${api.slug}`);
|
|
464
|
-
}
|
|
465
|
-
console.log(`\n ${c.dim}Note: Always use the full URL above. Do NOT call /${api.slug || api.id} directly — the /api/proxy/ or /api/ prefix is required.${c.reset}\n`);
|
|
466
|
-
}
|
|
467
|
-
async function cmdCall(args) {
|
|
468
|
-
const id = args[0];
|
|
469
|
-
if (!id) {
|
|
470
|
-
console.error(`${c.red}Usage: obolos call <api-id> [--body '{"key":"value"}'] [--method POST]${c.reset}`);
|
|
471
|
-
process.exit(1);
|
|
472
|
-
}
|
|
473
|
-
// Parse flags
|
|
474
|
-
let method = 'GET';
|
|
475
|
-
let body = undefined;
|
|
476
|
-
for (let i = 1; i < args.length; i++) {
|
|
477
|
-
if (args[i] === '--method' && args[i + 1]) {
|
|
478
|
-
method = args[++i].toUpperCase();
|
|
479
|
-
}
|
|
480
|
-
else if (args[i] === '--body' && args[i + 1]) {
|
|
481
|
-
try {
|
|
482
|
-
body = JSON.parse(args[++i]);
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
console.error(`${c.red}Invalid JSON body${c.reset}`);
|
|
486
|
-
process.exit(1);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (!OBOLOS_PRIVATE_KEY) {
|
|
491
|
-
// Free call attempt (for free APIs or to see the 402 response)
|
|
492
|
-
console.log(`${c.yellow}No wallet configured — attempting without payment${c.reset}`);
|
|
493
|
-
console.log(`${c.dim}Run "obolos setup" to configure a wallet for paid APIs${c.reset}`);
|
|
494
|
-
}
|
|
495
|
-
const url = `${OBOLOS_API_URL}/api/proxy/${encodeURIComponent(id)}`;
|
|
496
|
-
const fetchOpts = { method };
|
|
497
|
-
if (body && method !== 'GET') {
|
|
498
|
-
fetchOpts.headers = { 'Content-Type': 'application/json' };
|
|
499
|
-
fetchOpts.body = JSON.stringify(body);
|
|
500
|
-
}
|
|
501
|
-
console.log(`\n${c.dim}${method} ${url}${c.reset}`);
|
|
502
|
-
let res = await fetch(url, fetchOpts);
|
|
503
|
-
if (res.status === 402 && OBOLOS_PRIVATE_KEY) {
|
|
504
|
-
console.log(`${c.yellow}402 Payment Required — signing payment...${c.reset}`);
|
|
505
|
-
let paymentInfo;
|
|
506
|
-
try {
|
|
507
|
-
paymentInfo = await res.json();
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
console.error(`${c.red}Could not parse 402 response${c.reset}`);
|
|
511
|
-
process.exit(1);
|
|
512
|
-
}
|
|
513
|
-
// Dynamic import viem for signing
|
|
514
|
-
const { createWalletClient, http: viemHttp, keccak256, encodePacked } = await import('viem');
|
|
515
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
516
|
-
const { base } = await import('viem/chains');
|
|
517
|
-
const key = OBOLOS_PRIVATE_KEY.startsWith('0x')
|
|
518
|
-
? OBOLOS_PRIVATE_KEY
|
|
519
|
-
: `0x${OBOLOS_PRIVATE_KEY}`;
|
|
520
|
-
const account = privateKeyToAccount(key);
|
|
521
|
-
const client = createWalletClient({ account, chain: base, transport: viemHttp() });
|
|
522
|
-
const accepts = paymentInfo.accepts?.[0];
|
|
523
|
-
if (!accepts) {
|
|
524
|
-
console.error(`${c.red}No payment options in 402 response${c.reset}`);
|
|
525
|
-
process.exit(1);
|
|
526
|
-
}
|
|
527
|
-
const amount = BigInt(accepts.maxAmountRequired || accepts.amount || '0');
|
|
528
|
-
const payTo = accepts.payTo;
|
|
529
|
-
const asset = accepts.asset || '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
530
|
-
const scheme = accepts.scheme || 'exact';
|
|
531
|
-
const rawNetwork = accepts.network || 'base';
|
|
532
|
-
const network = rawNetwork.startsWith('eip155:') ? rawNetwork : 'eip155:8453';
|
|
533
|
-
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300);
|
|
534
|
-
// EIP-712 domain must match the USDC contract's domain (not "x402")
|
|
535
|
-
const domain = {
|
|
536
|
-
name: accepts.extra?.name || 'USD Coin',
|
|
537
|
-
version: accepts.extra?.version || '2',
|
|
538
|
-
chainId: 8453n,
|
|
539
|
-
verifyingContract: asset,
|
|
540
|
-
};
|
|
541
|
-
const types = {
|
|
542
|
-
TransferWithAuthorization: [
|
|
543
|
-
{ name: 'from', type: 'address' },
|
|
544
|
-
{ name: 'to', type: 'address' },
|
|
545
|
-
{ name: 'value', type: 'uint256' },
|
|
546
|
-
{ name: 'validAfter', type: 'uint256' },
|
|
547
|
-
{ name: 'validBefore', type: 'uint256' },
|
|
548
|
-
{ name: 'nonce', type: 'bytes32' },
|
|
549
|
-
],
|
|
550
|
-
};
|
|
551
|
-
// Check for v2 router settlement extension
|
|
552
|
-
const settlementKey = 'x402x-router-settlement';
|
|
553
|
-
const settlementExt = accepts.extra?.[settlementKey];
|
|
554
|
-
const settlementInfo = settlementExt?.info;
|
|
555
|
-
// Determine nonce: commitment hash for router settlement, random otherwise
|
|
556
|
-
let nonce;
|
|
557
|
-
if (settlementInfo?.settlementRouter && settlementInfo?.salt) {
|
|
558
|
-
nonce = keccak256(encodePacked(['string', 'uint256', 'address', 'address', 'address', 'uint256', 'uint256', 'uint256', 'bytes32', 'address', 'uint256', 'address', 'bytes32'], [
|
|
559
|
-
'X402/settle/v1',
|
|
560
|
-
8453n,
|
|
561
|
-
settlementInfo.settlementRouter,
|
|
562
|
-
asset,
|
|
563
|
-
account.address,
|
|
564
|
-
BigInt(amount),
|
|
565
|
-
0n,
|
|
566
|
-
deadline,
|
|
567
|
-
settlementInfo.salt,
|
|
568
|
-
(settlementInfo.finalPayTo || payTo),
|
|
569
|
-
BigInt(settlementInfo.facilitatorFee || '0'),
|
|
570
|
-
settlementInfo.hook,
|
|
571
|
-
keccak256(settlementInfo.hookData),
|
|
572
|
-
]));
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
const nonceBytes = new Uint8Array(32);
|
|
576
|
-
crypto.getRandomValues(nonceBytes);
|
|
577
|
-
nonce = `0x${Array.from(nonceBytes).map(b => b.toString(16).padStart(2, '0')).join('')}`;
|
|
578
|
-
}
|
|
579
|
-
const signature = await client.signTypedData({
|
|
580
|
-
account,
|
|
581
|
-
domain,
|
|
582
|
-
types,
|
|
583
|
-
primaryType: 'TransferWithAuthorization',
|
|
584
|
-
message: {
|
|
585
|
-
from: account.address,
|
|
586
|
-
to: payTo,
|
|
587
|
-
value: BigInt(amount),
|
|
588
|
-
validAfter: 0n,
|
|
589
|
-
validBefore: deadline,
|
|
590
|
-
nonce,
|
|
591
|
-
},
|
|
592
|
-
});
|
|
593
|
-
const authorization = {
|
|
594
|
-
from: account.address,
|
|
595
|
-
to: payTo,
|
|
596
|
-
value: amount.toString(),
|
|
597
|
-
validAfter: '0',
|
|
598
|
-
validBefore: deadline.toString(),
|
|
599
|
-
nonce,
|
|
600
|
-
};
|
|
601
|
-
let encoded;
|
|
602
|
-
let headerName;
|
|
603
|
-
if (paymentInfo.x402Version === 2) {
|
|
604
|
-
const paymentPayload = {
|
|
605
|
-
x402Version: 2,
|
|
606
|
-
scheme,
|
|
607
|
-
network,
|
|
608
|
-
payload: { signature, authorization },
|
|
609
|
-
accepted: { ...accepts, network },
|
|
610
|
-
};
|
|
611
|
-
if (settlementExt) {
|
|
612
|
-
paymentPayload.extensions = { [settlementKey]: settlementExt };
|
|
613
|
-
}
|
|
614
|
-
encoded = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
|
|
615
|
-
headerName = 'payment-signature';
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
const paymentPayload = {
|
|
619
|
-
x402Version: 1,
|
|
620
|
-
scheme,
|
|
621
|
-
network,
|
|
622
|
-
payload: { signature, authorization },
|
|
623
|
-
};
|
|
624
|
-
encoded = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
|
|
625
|
-
headerName = 'x-payment';
|
|
626
|
-
}
|
|
627
|
-
console.log(`${c.green}Payment signed. Retrying...${c.reset}`);
|
|
628
|
-
res = await fetch(url, {
|
|
629
|
-
...fetchOpts,
|
|
630
|
-
headers: {
|
|
631
|
-
...(fetchOpts.headers || {}),
|
|
632
|
-
[headerName]: encoded,
|
|
633
|
-
},
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
// Display response
|
|
637
|
-
const status = res.status;
|
|
638
|
-
const statusColor = status < 300 ? c.green : status < 400 ? c.yellow : c.red;
|
|
639
|
-
console.log(`${statusColor}${status} ${res.statusText}${c.reset}\n`);
|
|
640
|
-
const contentType = res.headers.get('content-type') || '';
|
|
641
|
-
if (contentType.includes('json')) {
|
|
642
|
-
const data = await res.json();
|
|
643
|
-
console.log(JSON.stringify(data, null, 2));
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
const text = await res.text();
|
|
647
|
-
console.log(text.slice(0, 2000));
|
|
648
|
-
}
|
|
649
|
-
console.log();
|
|
650
|
-
}
|
|
651
|
-
async function cmdBalance() {
|
|
652
|
-
if (!OBOLOS_PRIVATE_KEY) {
|
|
653
|
-
console.error(`${c.red}No wallet configured.${c.reset} Run ${c.cyan}obolos setup${c.reset} first.`);
|
|
654
|
-
process.exit(1);
|
|
655
|
-
}
|
|
656
|
-
const { createPublicClient, http: viemHttp, formatUnits } = await import('viem');
|
|
657
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
658
|
-
const { base } = await import('viem/chains');
|
|
659
|
-
const key = OBOLOS_PRIVATE_KEY.startsWith('0x') ? OBOLOS_PRIVATE_KEY : `0x${OBOLOS_PRIVATE_KEY}`;
|
|
660
|
-
const account = privateKeyToAccount(key);
|
|
661
|
-
const client = createPublicClient({ chain: base, transport: viemHttp() });
|
|
662
|
-
const balance = await client.readContract({
|
|
663
|
-
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
664
|
-
abi: [{ inputs: [{ name: 'account', type: 'address' }], name: 'balanceOf', outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', type: 'function' }],
|
|
665
|
-
functionName: 'balanceOf',
|
|
666
|
-
args: [account.address],
|
|
667
|
-
});
|
|
668
|
-
console.log(`\n${c.bold}Wallet:${c.reset} ${account.address}`);
|
|
669
|
-
console.log(`${c.bold}Balance:${c.reset} ${c.green}${formatUnits(balance, 6)} USDC${c.reset}`);
|
|
670
|
-
console.log(`${c.bold}Network:${c.reset} Base (Chain ID: 8453)\n`);
|
|
671
|
-
}
|
|
672
|
-
function prompt(question) {
|
|
673
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
674
|
-
return new Promise(resolve => {
|
|
675
|
-
rl.question(question, answer => {
|
|
676
|
-
rl.close();
|
|
677
|
-
resolve(answer.trim());
|
|
678
|
-
});
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
async function cmdSetup(args) {
|
|
682
|
-
console.log(`\n${c.bold}${c.cyan}Obolos Wallet Setup${c.reset}\n`);
|
|
683
|
-
const existing = loadConfig();
|
|
684
|
-
if (args.includes('--generate')) {
|
|
685
|
-
// Generate a new wallet
|
|
686
|
-
const { privateKeyToAccount, generatePrivateKey } = await import('viem/accounts');
|
|
687
|
-
const key = generatePrivateKey();
|
|
688
|
-
const account = privateKeyToAccount(key);
|
|
689
|
-
existing.private_key = key;
|
|
690
|
-
saveConfig(existing);
|
|
691
|
-
console.log(`${c.green}New wallet generated and saved!${c.reset}\n`);
|
|
692
|
-
console.log(` ${c.bold}Address:${c.reset} ${account.address}`);
|
|
693
|
-
console.log(` ${c.bold}Config:${c.reset} ${CONFIG_FILE}\n`);
|
|
694
|
-
console.log(`${c.yellow}Next steps:${c.reset}`);
|
|
695
|
-
console.log(` 1. Fund this address with USDC on Base`);
|
|
696
|
-
console.log(` Send USDC to: ${c.cyan}${account.address}${c.reset}`);
|
|
697
|
-
console.log(` 2. Check your balance: ${c.dim}obolos balance${c.reset}`);
|
|
698
|
-
console.log(` 3. Call an API: ${c.dim}obolos call <api-id> --body '{...}'${c.reset}\n`);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
if (args.includes('--show')) {
|
|
702
|
-
if (existing.private_key) {
|
|
703
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
704
|
-
const key = existing.private_key.startsWith('0x') ? existing.private_key : `0x${existing.private_key}`;
|
|
705
|
-
const account = privateKeyToAccount(key);
|
|
706
|
-
console.log(` ${c.bold}Address:${c.reset} ${account.address}`);
|
|
707
|
-
console.log(` ${c.bold}Config:${c.reset} ${CONFIG_FILE}`);
|
|
708
|
-
console.log(` ${c.bold}API URL:${c.reset} ${OBOLOS_API_URL}\n`);
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
console.log(` ${c.yellow}No wallet configured.${c.reset}`);
|
|
712
|
-
console.log(` Run ${c.cyan}obolos setup --generate${c.reset} to create one,`);
|
|
713
|
-
console.log(` or ${c.cyan}obolos setup${c.reset} to import an existing key.\n`);
|
|
714
|
-
}
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
// Interactive setup
|
|
718
|
-
console.log(` Config is saved to ${c.dim}${CONFIG_FILE}${c.reset} (permissions: 600)\n`);
|
|
719
|
-
if (existing.private_key) {
|
|
720
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
721
|
-
const key = existing.private_key.startsWith('0x') ? existing.private_key : `0x${existing.private_key}`;
|
|
722
|
-
const account = privateKeyToAccount(key);
|
|
723
|
-
console.log(` ${c.dim}Current wallet: ${account.address}${c.reset}\n`);
|
|
724
|
-
}
|
|
725
|
-
const keyInput = await prompt(` Private key (0x...) or "generate" for a new wallet: `);
|
|
726
|
-
if (!keyInput) {
|
|
727
|
-
console.log(`\n${c.yellow}No changes made.${c.reset}\n`);
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
if (keyInput === 'generate') {
|
|
731
|
-
return cmdSetup(['--generate']);
|
|
732
|
-
}
|
|
733
|
-
// Validate the key
|
|
734
|
-
const normalizedKey = keyInput.startsWith('0x') ? keyInput : `0x${keyInput}`;
|
|
735
|
-
try {
|
|
736
|
-
const { privateKeyToAccount } = await import('viem/accounts');
|
|
737
|
-
const account = privateKeyToAccount(normalizedKey);
|
|
738
|
-
existing.private_key = normalizedKey;
|
|
739
|
-
saveConfig(existing);
|
|
740
|
-
console.log(`\n${c.green}Wallet saved!${c.reset}\n`);
|
|
741
|
-
console.log(` ${c.bold}Address:${c.reset} ${account.address}`);
|
|
742
|
-
console.log(` ${c.bold}Config:${c.reset} ${CONFIG_FILE}\n`);
|
|
743
|
-
console.log(` Check your balance: ${c.dim}obolos balance${c.reset}\n`);
|
|
744
|
-
}
|
|
745
|
-
catch (err) {
|
|
746
|
-
console.error(`\n${c.red}Invalid private key: ${err.message}${c.reset}\n`);
|
|
747
|
-
process.exit(1);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
async function cmdSetupMcp() {
|
|
751
|
-
console.log(`\n${c.bold}${c.cyan}Obolos MCP Server Setup${c.reset}\n`);
|
|
752
|
-
console.log(`${c.bold}Install:${c.reset}`);
|
|
753
|
-
console.log(` npm install -g @obolos_tech/mcp-server\n`);
|
|
754
|
-
console.log(`${c.bold}For Claude Code (global — all projects):${c.reset}`);
|
|
755
|
-
console.log(` claude mcp add obolos ${c.yellow}--scope user${c.reset} -e OBOLOS_PRIVATE_KEY=0xyour_key -- obolos-mcp\n`);
|
|
756
|
-
console.log(`${c.bold}For Claude Code (current project only):${c.reset}`);
|
|
757
|
-
console.log(` claude mcp add obolos -e OBOLOS_PRIVATE_KEY=0xyour_key -- obolos-mcp\n`);
|
|
758
|
-
console.log(`${c.bold}Or use npx (no install):${c.reset}`);
|
|
759
|
-
console.log(` claude mcp add obolos ${c.yellow}--scope user${c.reset} -e OBOLOS_PRIVATE_KEY=0xyour_key -- npx @obolos_tech/mcp-server\n`);
|
|
760
|
-
console.log(` ${c.dim}Scope reference:${c.reset}`);
|
|
761
|
-
console.log(` ${c.dim} (default) Current project only${c.reset}`);
|
|
762
|
-
console.log(` ${c.dim} --scope user All projects on your machine${c.reset}`);
|
|
763
|
-
console.log(` ${c.dim} --scope project Shared via .mcp.json (checked into git)${c.reset}\n`);
|
|
764
|
-
console.log(`${c.bold}For Claude Desktop / Cursor / Windsurf:${c.reset}`);
|
|
765
|
-
console.log(` Add to your MCP config:\n`);
|
|
766
|
-
console.log(` ${c.dim}{`);
|
|
767
|
-
console.log(` "mcpServers": {`);
|
|
768
|
-
console.log(` "obolos": {`);
|
|
769
|
-
console.log(` "command": "npx",`);
|
|
770
|
-
console.log(` "args": ["@obolos_tech/mcp-server"],`);
|
|
771
|
-
console.log(` "env": {`);
|
|
772
|
-
console.log(` "OBOLOS_PRIVATE_KEY": "0xyour_private_key"`);
|
|
773
|
-
console.log(` }`);
|
|
774
|
-
console.log(` }`);
|
|
775
|
-
console.log(` }`);
|
|
776
|
-
console.log(` }${c.reset}\n`);
|
|
777
|
-
}
|
|
778
|
-
// ─── Job Commands (ERC-8183 ACP) ────────────────────────────────────────────
|
|
779
|
-
async function cmdJobList(args) {
|
|
780
|
-
const params = new URLSearchParams();
|
|
781
|
-
const status = getFlag(args, 'status');
|
|
782
|
-
const client = getFlag(args, 'client');
|
|
783
|
-
const provider = getFlag(args, 'provider');
|
|
784
|
-
const limit = getFlag(args, 'limit') || '20';
|
|
785
|
-
if (status)
|
|
786
|
-
params.set('status', status);
|
|
787
|
-
if (client)
|
|
788
|
-
params.set('client', client);
|
|
789
|
-
if (provider)
|
|
790
|
-
params.set('provider', provider);
|
|
791
|
-
params.set('limit', limit);
|
|
792
|
-
const data = await apiGet(`/api/jobs?${params}`);
|
|
793
|
-
const jobs = data.jobs || data.data || [];
|
|
794
|
-
if (jobs.length === 0) {
|
|
795
|
-
console.log(`${c.yellow}No jobs found.${c.reset}`);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const total = data.pagination?.total || data.total || jobs.length;
|
|
799
|
-
console.log(`\n${c.bold}${c.cyan}ACP Jobs${c.reset} ${c.dim}— ${total} jobs${c.reset}\n`);
|
|
800
|
-
// Table header
|
|
801
|
-
console.log(` ${c.bold}${'ID'.padEnd(12)} ${'Title'.padEnd(30)} ${'Status'.padEnd(12)} ${'Budget'.padEnd(12)} ${'Client'.padEnd(14)} ${'Provider'.padEnd(14)} Created${c.reset}`);
|
|
802
|
-
console.log(` ${c.dim}${'─'.repeat(110)}${c.reset}`);
|
|
803
|
-
for (const job of jobs) {
|
|
804
|
-
const id = shortenId(job.id || '');
|
|
805
|
-
const title = (job.title || 'Untitled').slice(0, 28).padEnd(30);
|
|
806
|
-
const st = statusColor((job.status || 'open').padEnd(10));
|
|
807
|
-
const budget = job.budget != null ? `$${Number(job.budget).toFixed(2)}`.padEnd(12) : `${c.dim}—${c.reset}`.padEnd(12);
|
|
808
|
-
const cl = shortenAddr(job.client).padEnd(14);
|
|
809
|
-
const prov = job.provider ? shortenAddr(job.provider).padEnd(14) : `${c.dim}Open${c.reset}`.padEnd(14);
|
|
810
|
-
const created = formatDate(job.created_at || job.createdAt);
|
|
811
|
-
console.log(` ${id.padEnd(12)} ${title} ${st} ${budget} ${cl} ${prov} ${created}`);
|
|
812
|
-
}
|
|
813
|
-
console.log(`\n${c.dim}Use: obolos job info <id> for full details${c.reset}\n`);
|
|
814
|
-
}
|
|
815
|
-
async function cmdJobCreate(args) {
|
|
816
|
-
const title = getFlag(args, 'title');
|
|
817
|
-
const description = getFlag(args, 'description');
|
|
818
|
-
const evaluator = getFlag(args, 'evaluator');
|
|
819
|
-
const provider = getFlag(args, 'provider');
|
|
820
|
-
const budget = getFlag(args, 'budget');
|
|
821
|
-
const expires = getFlag(args, 'expires');
|
|
822
|
-
if (!title) {
|
|
823
|
-
console.error(`${c.red}Usage: obolos job create --title "..." --description "..." --evaluator 0x... [--provider 0x...] [--budget 1.00] [--expires 24h]${c.reset}`);
|
|
824
|
-
process.exit(1);
|
|
825
|
-
}
|
|
826
|
-
if (!evaluator) {
|
|
827
|
-
console.error(`${c.red}--evaluator is required. Provide the evaluator address (0x...).${c.reset}`);
|
|
828
|
-
process.exit(1);
|
|
829
|
-
}
|
|
830
|
-
const walletAddress = await getWalletAddress();
|
|
831
|
-
// Create job on-chain first
|
|
832
|
-
let chainJobId = null;
|
|
833
|
-
let chainTxHash = null;
|
|
834
|
-
try {
|
|
835
|
-
const acp = await getACPClient();
|
|
836
|
-
// Parse expiry to unix timestamp (default: 7 days)
|
|
837
|
-
let expiredAt;
|
|
838
|
-
if (expires) {
|
|
839
|
-
const parsed = parseRelativeTime(expires);
|
|
840
|
-
expiredAt = Math.floor(new Date(parsed).getTime() / 1000);
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
expiredAt = Math.floor((Date.now() + 7 * 86400000) / 1000);
|
|
844
|
-
}
|
|
845
|
-
console.log(`\n ${c.dim}Creating job on-chain...${c.reset}`);
|
|
846
|
-
const txHash = await acp.walletClient.writeContract({
|
|
847
|
-
address: ACP_ADDRESS,
|
|
848
|
-
abi: ACP_ABI,
|
|
849
|
-
functionName: 'createJob',
|
|
850
|
-
args: [
|
|
851
|
-
(provider || ZERO_ADDRESS),
|
|
852
|
-
evaluator,
|
|
853
|
-
BigInt(expiredAt),
|
|
854
|
-
description || title,
|
|
855
|
-
ZERO_ADDRESS,
|
|
856
|
-
],
|
|
857
|
-
account: acp.account,
|
|
858
|
-
chain: (await import('viem/chains')).base,
|
|
859
|
-
});
|
|
860
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
861
|
-
const receipt = await acp.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
862
|
-
// Extract jobId from JobCreated event
|
|
863
|
-
for (const log of receipt.logs) {
|
|
864
|
-
try {
|
|
865
|
-
const decoded = acp.decodeEventLog({
|
|
866
|
-
abi: ACP_ABI,
|
|
867
|
-
data: log.data,
|
|
868
|
-
topics: log.topics,
|
|
869
|
-
});
|
|
870
|
-
if (decoded.eventName === 'JobCreated') {
|
|
871
|
-
chainJobId = (decoded.args.jobId).toString();
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
catch { }
|
|
876
|
-
}
|
|
877
|
-
chainTxHash = txHash;
|
|
878
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}`);
|
|
879
|
-
if (chainJobId) {
|
|
880
|
-
console.log(` ${c.green}Chain job ID: ${chainJobId}${c.reset}`);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
catch (err) {
|
|
884
|
-
console.error(` ${c.yellow}On-chain creation failed: ${err.message}${c.reset}`);
|
|
885
|
-
console.error(` ${c.dim}Falling back to backend-only...${c.reset}`);
|
|
886
|
-
}
|
|
887
|
-
const payload = {
|
|
888
|
-
title,
|
|
889
|
-
evaluator,
|
|
890
|
-
};
|
|
891
|
-
if (description)
|
|
892
|
-
payload.description = description;
|
|
893
|
-
if (provider)
|
|
894
|
-
payload.provider = provider;
|
|
895
|
-
if (budget)
|
|
896
|
-
payload.budget = parseFloat(budget);
|
|
897
|
-
if (expires)
|
|
898
|
-
payload.expires_at = parseRelativeTime(expires);
|
|
899
|
-
if (chainJobId)
|
|
900
|
-
payload.chain_job_id = chainJobId;
|
|
901
|
-
if (chainTxHash)
|
|
902
|
-
payload.chain_tx_hash = chainTxHash;
|
|
903
|
-
const data = await apiPost('/api/jobs', payload, {
|
|
904
|
-
'x-wallet-address': walletAddress,
|
|
905
|
-
});
|
|
906
|
-
const job = data.job || data;
|
|
907
|
-
console.log(`\n${c.green}Job created successfully!${c.reset}\n`);
|
|
908
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
909
|
-
console.log(` ${c.bold}ID:${c.reset} ${job.id}`);
|
|
910
|
-
if (chainJobId) {
|
|
911
|
-
console.log(` ${c.bold}Chain ID:${c.reset} ${chainJobId}`);
|
|
912
|
-
}
|
|
913
|
-
console.log(` ${c.bold}Title:${c.reset} ${job.title}`);
|
|
914
|
-
if (job.description) {
|
|
915
|
-
console.log(` ${c.bold}Description:${c.reset} ${job.description}`);
|
|
916
|
-
}
|
|
917
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'open')}`);
|
|
918
|
-
console.log(` ${c.bold}Client:${c.reset} ${job.client || walletAddress}`);
|
|
919
|
-
console.log(` ${c.bold}Evaluator:${c.reset} ${job.evaluator}`);
|
|
920
|
-
if (job.provider) {
|
|
921
|
-
console.log(` ${c.bold}Provider:${c.reset} ${job.provider}`);
|
|
922
|
-
}
|
|
923
|
-
if (job.budget != null) {
|
|
924
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}$${Number(job.budget).toFixed(2)} USDC${c.reset}`);
|
|
925
|
-
}
|
|
926
|
-
if (job.expires_at) {
|
|
927
|
-
console.log(` ${c.bold}Expires:${c.reset} ${formatDate(job.expires_at)}`);
|
|
928
|
-
}
|
|
929
|
-
if (chainTxHash) {
|
|
930
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${chainTxHash}${c.reset}`);
|
|
931
|
-
}
|
|
932
|
-
console.log(`\n${c.dim}Next: obolos job fund ${job.id}${c.reset}\n`);
|
|
933
|
-
}
|
|
934
|
-
async function cmdJobInfo(args) {
|
|
935
|
-
const id = getPositional(args, 0);
|
|
936
|
-
if (!id) {
|
|
937
|
-
console.error(`${c.red}Usage: obolos job info <id>${c.reset}`);
|
|
938
|
-
process.exit(1);
|
|
939
|
-
}
|
|
940
|
-
const data = await apiGet(`/api/jobs/${encodeURIComponent(id)}`);
|
|
941
|
-
const job = data.job || data;
|
|
942
|
-
console.log(`\n${c.bold}${c.cyan}${job.title || 'Untitled Job'}${c.reset}`);
|
|
943
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
944
|
-
console.log(` ${c.bold}ID:${c.reset} ${job.id}`);
|
|
945
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'open')}`);
|
|
946
|
-
// State machine visualization
|
|
947
|
-
console.log(` ${c.bold}Progress:${c.reset}`);
|
|
948
|
-
console.log(stateVisualization(job.status || 'open'));
|
|
949
|
-
console.log(` ${c.bold}Client:${c.reset} ${job.client || `${c.dim}—${c.reset}`}`);
|
|
950
|
-
console.log(` ${c.bold}Evaluator:${c.reset} ${job.evaluator || `${c.dim}—${c.reset}`}`);
|
|
951
|
-
console.log(` ${c.bold}Provider:${c.reset} ${job.provider || `${c.dim}Open (anyone can claim)${c.reset}`}`);
|
|
952
|
-
if (job.budget != null) {
|
|
953
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}$${Number(job.budget).toFixed(2)} USDC${c.reset}`);
|
|
954
|
-
}
|
|
955
|
-
if (job.description) {
|
|
956
|
-
console.log(`\n ${c.bold}Description:${c.reset}`);
|
|
957
|
-
const descLines = job.description.split('\n');
|
|
958
|
-
for (const line of descLines) {
|
|
959
|
-
console.log(` ${line}`);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
if (job.deliverable) {
|
|
963
|
-
console.log(`\n ${c.bold}Deliverable:${c.reset} ${c.cyan}${job.deliverable}${c.reset}`);
|
|
964
|
-
}
|
|
965
|
-
if (job.reason) {
|
|
966
|
-
console.log(` ${c.bold}Reason:${c.reset} ${job.reason}`);
|
|
967
|
-
}
|
|
968
|
-
if (job.expires_at) {
|
|
969
|
-
const expiryDate = new Date(job.expires_at);
|
|
970
|
-
const now = new Date();
|
|
971
|
-
const expired = expiryDate < now;
|
|
972
|
-
console.log(` ${c.bold}Expires:${c.reset} ${expired ? c.red : c.dim}${formatDate(job.expires_at)}${expired ? ' (expired)' : ''}${c.reset}`);
|
|
973
|
-
}
|
|
974
|
-
console.log(` ${c.bold}Created:${c.reset} ${formatDate(job.created_at || job.createdAt)}`);
|
|
975
|
-
if (job.updated_at || job.updatedAt) {
|
|
976
|
-
console.log(` ${c.bold}Updated:${c.reset} ${formatDate(job.updated_at || job.updatedAt)}`);
|
|
977
|
-
}
|
|
978
|
-
// Actions hint based on status
|
|
979
|
-
console.log();
|
|
980
|
-
const s = job.status || 'open';
|
|
981
|
-
if (s === 'open') {
|
|
982
|
-
console.log(` ${c.bold}Actions:${c.reset}`);
|
|
983
|
-
console.log(` obolos job fund ${job.id} ${c.dim}Fund the escrow${c.reset}`);
|
|
984
|
-
}
|
|
985
|
-
else if (s === 'funded') {
|
|
986
|
-
console.log(` ${c.bold}Actions:${c.reset}`);
|
|
987
|
-
console.log(` obolos job submit ${job.id} --deliverable <hash> ${c.dim}Submit work${c.reset}`);
|
|
988
|
-
}
|
|
989
|
-
else if (s === 'submitted') {
|
|
990
|
-
console.log(` ${c.bold}Actions:${c.reset}`);
|
|
991
|
-
console.log(` obolos job complete ${job.id} ${c.dim}Approve and release funds${c.reset}`);
|
|
992
|
-
console.log(` obolos job reject ${job.id} ${c.dim}Reject the submission${c.reset}`);
|
|
993
|
-
}
|
|
994
|
-
console.log();
|
|
995
|
-
}
|
|
996
|
-
async function cmdJobFund(args) {
|
|
997
|
-
const id = getPositional(args, 0);
|
|
998
|
-
if (!id) {
|
|
999
|
-
console.error(`${c.red}Usage: obolos job fund <id>${c.reset}`);
|
|
1000
|
-
process.exit(1);
|
|
1001
|
-
}
|
|
1002
|
-
const walletAddress = await getWalletAddress();
|
|
1003
|
-
// First fetch the job to show budget info
|
|
1004
|
-
const jobData = await apiGet(`/api/jobs/${encodeURIComponent(id)}`);
|
|
1005
|
-
const job = jobData.job || jobData;
|
|
1006
|
-
console.log(`\n${c.bold}${c.cyan}Fund Job${c.reset}\n`);
|
|
1007
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1008
|
-
console.log(` ${c.bold}Job:${c.reset} ${job.title || id}`);
|
|
1009
|
-
if (job.budget != null) {
|
|
1010
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}$${Number(job.budget).toFixed(2)} USDC${c.reset}`);
|
|
1011
|
-
}
|
|
1012
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'open')}`);
|
|
1013
|
-
console.log();
|
|
1014
|
-
const chainJobId = job.chain_job_id;
|
|
1015
|
-
let txHash = null;
|
|
1016
|
-
if (chainJobId && job.budget != null) {
|
|
1017
|
-
try {
|
|
1018
|
-
const acp = await getACPClient();
|
|
1019
|
-
const budgetStr = String(job.budget);
|
|
1020
|
-
// Check USDC allowance and approve if needed
|
|
1021
|
-
console.log(` ${c.dim}Checking USDC allowance...${c.reset}`);
|
|
1022
|
-
const amount = acp.parseUnits(budgetStr, 6);
|
|
1023
|
-
const allowance = await acp.publicClient.readContract({
|
|
1024
|
-
address: USDC_CONTRACT,
|
|
1025
|
-
abi: ERC20_ABI,
|
|
1026
|
-
functionName: 'allowance',
|
|
1027
|
-
args: [acp.account.address, ACP_ADDRESS],
|
|
1028
|
-
});
|
|
1029
|
-
if (allowance < amount) {
|
|
1030
|
-
console.log(` ${c.dim}Approving USDC spend...${c.reset}`);
|
|
1031
|
-
const approveTx = await acp.walletClient.writeContract({
|
|
1032
|
-
address: USDC_CONTRACT,
|
|
1033
|
-
abi: ERC20_ABI,
|
|
1034
|
-
functionName: 'approve',
|
|
1035
|
-
args: [ACP_ADDRESS, amount],
|
|
1036
|
-
account: acp.account,
|
|
1037
|
-
chain: (await import('viem/chains')).base,
|
|
1038
|
-
});
|
|
1039
|
-
await acp.publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
1040
|
-
console.log(` ${c.green}USDC approved${c.reset}`);
|
|
1041
|
-
}
|
|
1042
|
-
console.log(` ${c.dim}Funding escrow on-chain...${c.reset}`);
|
|
1043
|
-
const fundTx = await acp.walletClient.writeContract({
|
|
1044
|
-
address: ACP_ADDRESS,
|
|
1045
|
-
abi: ACP_ABI,
|
|
1046
|
-
functionName: 'fund',
|
|
1047
|
-
args: [BigInt(chainJobId), amount, '0x'],
|
|
1048
|
-
account: acp.account,
|
|
1049
|
-
chain: (await import('viem/chains')).base,
|
|
1050
|
-
});
|
|
1051
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
1052
|
-
await acp.publicClient.waitForTransactionReceipt({ hash: fundTx });
|
|
1053
|
-
txHash = fundTx;
|
|
1054
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}\n`);
|
|
1055
|
-
}
|
|
1056
|
-
catch (err) {
|
|
1057
|
-
console.error(` ${c.yellow}On-chain funding failed: ${err.message}${c.reset}`);
|
|
1058
|
-
console.error(` ${c.dim}Recording funding intent in backend...${c.reset}\n`);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
// Update backend
|
|
1062
|
-
const fundPayload = {};
|
|
1063
|
-
if (txHash)
|
|
1064
|
-
fundPayload.tx_hash = txHash;
|
|
1065
|
-
if (chainJobId)
|
|
1066
|
-
fundPayload.chain_job_id = chainJobId;
|
|
1067
|
-
const data = await apiPost(`/api/jobs/${encodeURIComponent(id)}/fund`, fundPayload, {
|
|
1068
|
-
'x-wallet-address': walletAddress,
|
|
1069
|
-
});
|
|
1070
|
-
const updated = data.job || data;
|
|
1071
|
-
console.log(`${c.green}Job funded successfully!${c.reset}`);
|
|
1072
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(updated.status || 'funded')}`);
|
|
1073
|
-
if (txHash) {
|
|
1074
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${txHash}${c.reset}`);
|
|
1075
|
-
}
|
|
1076
|
-
console.log(`${c.dim}Next: Provider submits work with: obolos job submit ${id} --deliverable <hash>${c.reset}\n`);
|
|
1077
|
-
}
|
|
1078
|
-
async function cmdJobSubmit(args) {
|
|
1079
|
-
const id = getPositional(args, 0);
|
|
1080
|
-
if (!id) {
|
|
1081
|
-
console.error(`${c.red}Usage: obolos job submit <id> --deliverable <hash/CID/URL>${c.reset}`);
|
|
1082
|
-
process.exit(1);
|
|
1083
|
-
}
|
|
1084
|
-
const deliverable = getFlag(args, 'deliverable');
|
|
1085
|
-
if (!deliverable) {
|
|
1086
|
-
console.error(`${c.red}--deliverable is required. Provide a hash, CID, or URL for the work product.${c.reset}`);
|
|
1087
|
-
process.exit(1);
|
|
1088
|
-
}
|
|
1089
|
-
const walletAddress = await getWalletAddress();
|
|
1090
|
-
// Fetch job to get chain_job_id
|
|
1091
|
-
const jobData = await apiGet(`/api/jobs/${encodeURIComponent(id)}`);
|
|
1092
|
-
const existingJob = jobData.job || jobData;
|
|
1093
|
-
const chainJobId = existingJob.chain_job_id;
|
|
1094
|
-
let txHash = null;
|
|
1095
|
-
if (chainJobId) {
|
|
1096
|
-
try {
|
|
1097
|
-
const acp = await getACPClient();
|
|
1098
|
-
const deliverableHash = acp.keccak256(acp.toHex(deliverable));
|
|
1099
|
-
console.log(`\n ${c.dim}Submitting work on-chain...${c.reset}`);
|
|
1100
|
-
const submitTx = await acp.walletClient.writeContract({
|
|
1101
|
-
address: ACP_ADDRESS,
|
|
1102
|
-
abi: ACP_ABI,
|
|
1103
|
-
functionName: 'submit',
|
|
1104
|
-
args: [BigInt(chainJobId), deliverableHash, '0x'],
|
|
1105
|
-
account: acp.account,
|
|
1106
|
-
chain: (await import('viem/chains')).base,
|
|
1107
|
-
});
|
|
1108
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
1109
|
-
await acp.publicClient.waitForTransactionReceipt({ hash: submitTx });
|
|
1110
|
-
txHash = submitTx;
|
|
1111
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}`);
|
|
1112
|
-
}
|
|
1113
|
-
catch (err) {
|
|
1114
|
-
console.error(` ${c.yellow}On-chain submission failed: ${err.message}${c.reset}`);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
const submitPayload = { deliverable };
|
|
1118
|
-
if (txHash)
|
|
1119
|
-
submitPayload.tx_hash = txHash;
|
|
1120
|
-
const data = await apiPost(`/api/jobs/${encodeURIComponent(id)}/submit`, submitPayload, {
|
|
1121
|
-
'x-wallet-address': walletAddress,
|
|
1122
|
-
});
|
|
1123
|
-
const job = data.job || data;
|
|
1124
|
-
console.log(`\n${c.green}Work submitted successfully!${c.reset}\n`);
|
|
1125
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1126
|
-
console.log(` ${c.bold}Job:${c.reset} ${job.title || id}`);
|
|
1127
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'submitted')}`);
|
|
1128
|
-
console.log(` ${c.bold}Deliverable:${c.reset} ${c.cyan}${deliverable}${c.reset}`);
|
|
1129
|
-
if (txHash) {
|
|
1130
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${txHash}${c.reset}`);
|
|
1131
|
-
}
|
|
1132
|
-
console.log(`\n${c.dim}The evaluator will now review and approve or reject the submission.${c.reset}\n`);
|
|
1133
|
-
}
|
|
1134
|
-
async function cmdJobComplete(args) {
|
|
1135
|
-
const id = getPositional(args, 0);
|
|
1136
|
-
if (!id) {
|
|
1137
|
-
console.error(`${c.red}Usage: obolos job complete <id> [--reason "..."]${c.reset}`);
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
const reason = getFlag(args, 'reason');
|
|
1141
|
-
const walletAddress = await getWalletAddress();
|
|
1142
|
-
// Fetch job to get chain_job_id
|
|
1143
|
-
const jobData = await apiGet(`/api/jobs/${encodeURIComponent(id)}`);
|
|
1144
|
-
const existingJob = jobData.job || jobData;
|
|
1145
|
-
const chainJobId = existingJob.chain_job_id;
|
|
1146
|
-
let txHash = null;
|
|
1147
|
-
if (chainJobId) {
|
|
1148
|
-
try {
|
|
1149
|
-
const acp = await getACPClient();
|
|
1150
|
-
const reasonHash = reason
|
|
1151
|
-
? acp.keccak256(acp.toHex(reason))
|
|
1152
|
-
: ZERO_BYTES32;
|
|
1153
|
-
console.log(`\n ${c.dim}Completing job on-chain...${c.reset}`);
|
|
1154
|
-
const completeTx = await acp.walletClient.writeContract({
|
|
1155
|
-
address: ACP_ADDRESS,
|
|
1156
|
-
abi: ACP_ABI,
|
|
1157
|
-
functionName: 'complete',
|
|
1158
|
-
args: [BigInt(chainJobId), reasonHash, '0x'],
|
|
1159
|
-
account: acp.account,
|
|
1160
|
-
chain: (await import('viem/chains')).base,
|
|
1161
|
-
});
|
|
1162
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
1163
|
-
await acp.publicClient.waitForTransactionReceipt({ hash: completeTx });
|
|
1164
|
-
txHash = completeTx;
|
|
1165
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}`);
|
|
1166
|
-
}
|
|
1167
|
-
catch (err) {
|
|
1168
|
-
console.error(` ${c.yellow}On-chain completion failed: ${err.message}${c.reset}`);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
const payload = {};
|
|
1172
|
-
if (reason)
|
|
1173
|
-
payload.reason = reason;
|
|
1174
|
-
if (txHash)
|
|
1175
|
-
payload.tx_hash = txHash;
|
|
1176
|
-
const data = await apiPost(`/api/jobs/${encodeURIComponent(id)}/complete`, payload, {
|
|
1177
|
-
'x-wallet-address': walletAddress,
|
|
1178
|
-
});
|
|
1179
|
-
const job = data.job || data;
|
|
1180
|
-
console.log(`\n${c.green}Job completed and approved!${c.reset}\n`);
|
|
1181
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1182
|
-
console.log(` ${c.bold}Job:${c.reset} ${job.title || id}`);
|
|
1183
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'completed')}`);
|
|
1184
|
-
if (reason) {
|
|
1185
|
-
console.log(` ${c.bold}Reason:${c.reset} ${reason}`);
|
|
1186
|
-
}
|
|
1187
|
-
if (txHash) {
|
|
1188
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${txHash}${c.reset}`);
|
|
1189
|
-
}
|
|
1190
|
-
if (job.budget != null) {
|
|
1191
|
-
console.log(`\n ${c.dim}Escrow of $${Number(job.budget).toFixed(2)} USDC released to provider.${c.reset}`);
|
|
1192
|
-
}
|
|
1193
|
-
console.log();
|
|
1194
|
-
}
|
|
1195
|
-
async function cmdJobReject(args) {
|
|
1196
|
-
const id = getPositional(args, 0);
|
|
1197
|
-
if (!id) {
|
|
1198
|
-
console.error(`${c.red}Usage: obolos job reject <id> [--reason "..."]${c.reset}`);
|
|
1199
|
-
process.exit(1);
|
|
1200
|
-
}
|
|
1201
|
-
const reason = getFlag(args, 'reason');
|
|
1202
|
-
const walletAddress = await getWalletAddress();
|
|
1203
|
-
// Fetch job to get chain_job_id
|
|
1204
|
-
const jobData = await apiGet(`/api/jobs/${encodeURIComponent(id)}`);
|
|
1205
|
-
const existingJob = jobData.job || jobData;
|
|
1206
|
-
const chainJobId = existingJob.chain_job_id;
|
|
1207
|
-
let txHash = null;
|
|
1208
|
-
if (chainJobId) {
|
|
1209
|
-
try {
|
|
1210
|
-
const acp = await getACPClient();
|
|
1211
|
-
const reasonHash = reason
|
|
1212
|
-
? acp.keccak256(acp.toHex(reason))
|
|
1213
|
-
: ZERO_BYTES32;
|
|
1214
|
-
console.log(`\n ${c.dim}Rejecting job on-chain...${c.reset}`);
|
|
1215
|
-
const rejectTx = await acp.walletClient.writeContract({
|
|
1216
|
-
address: ACP_ADDRESS,
|
|
1217
|
-
abi: ACP_ABI,
|
|
1218
|
-
functionName: 'reject',
|
|
1219
|
-
args: [BigInt(chainJobId), reasonHash, '0x'],
|
|
1220
|
-
account: acp.account,
|
|
1221
|
-
chain: (await import('viem/chains')).base,
|
|
1222
|
-
});
|
|
1223
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
1224
|
-
await acp.publicClient.waitForTransactionReceipt({ hash: rejectTx });
|
|
1225
|
-
txHash = rejectTx;
|
|
1226
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}`);
|
|
1227
|
-
}
|
|
1228
|
-
catch (err) {
|
|
1229
|
-
console.error(` ${c.yellow}On-chain rejection failed: ${err.message}${c.reset}`);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
const payload = {};
|
|
1233
|
-
if (reason)
|
|
1234
|
-
payload.reason = reason;
|
|
1235
|
-
if (txHash)
|
|
1236
|
-
payload.tx_hash = txHash;
|
|
1237
|
-
const data = await apiPost(`/api/jobs/${encodeURIComponent(id)}/reject`, payload, {
|
|
1238
|
-
'x-wallet-address': walletAddress,
|
|
1239
|
-
});
|
|
1240
|
-
const job = data.job || data;
|
|
1241
|
-
console.log(`\n${c.red}Job rejected.${c.reset}\n`);
|
|
1242
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1243
|
-
console.log(` ${c.bold}Job:${c.reset} ${job.title || id}`);
|
|
1244
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(job.status || 'rejected')}`);
|
|
1245
|
-
if (reason) {
|
|
1246
|
-
console.log(` ${c.bold}Reason:${c.reset} ${reason}`);
|
|
1247
|
-
}
|
|
1248
|
-
if (txHash) {
|
|
1249
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${txHash}${c.reset}`);
|
|
18
|
+
function showHelp() {
|
|
19
|
+
const groups = {};
|
|
20
|
+
for (const cmd of registry.all()) {
|
|
21
|
+
const [group] = cmd.name.split('.');
|
|
22
|
+
(groups[group] ||= []).push(cmd.name);
|
|
1250
23
|
}
|
|
1251
|
-
|
|
1252
|
-
}
|
|
1253
|
-
function showJobHelp() {
|
|
1254
|
-
console.log(`
|
|
1255
|
-
${c.bold}${c.cyan}obolos job${c.reset} — ERC-8183 Agentic Commerce Protocol (ACP) job management
|
|
24
|
+
process.stdout.write(`
|
|
25
|
+
${c.bold}${c.cyan}obolos${c.reset} — Commerce infrastructure for autonomous work on Base
|
|
1256
26
|
|
|
1257
27
|
${c.bold}Usage:${c.reset}
|
|
1258
|
-
obolos
|
|
1259
|
-
obolos
|
|
1260
|
-
obolos job info <id> Get full job details
|
|
1261
|
-
obolos job fund <id> Fund a job's escrow
|
|
1262
|
-
obolos job submit <id> [options] Submit work for a job
|
|
1263
|
-
obolos job complete <id> [options] Approve a job (evaluator)
|
|
1264
|
-
obolos job reject <id> [options] Reject a job submission
|
|
28
|
+
obolos <command> [options]
|
|
29
|
+
obolos <group> <subcommand> [options]
|
|
1265
30
|
|
|
1266
|
-
${c.bold}
|
|
1267
|
-
|
|
1268
|
-
--client=0x... Filter by client address
|
|
1269
|
-
--provider=0x... Filter by provider address
|
|
1270
|
-
--limit=20 Max results (default: 20)
|
|
31
|
+
${c.bold}Top-level commands:${c.reset}
|
|
32
|
+
${registry.all().filter(c => !c.name.includes('.')).map(c => ` ${c.name.padEnd(14)} ${c.summary}`).join('\n')}
|
|
1271
33
|
|
|
1272
|
-
${c.bold}
|
|
1273
|
-
|
|
1274
|
-
--description "..." Job description
|
|
1275
|
-
--evaluator 0x... Evaluator address (required)
|
|
1276
|
-
--provider 0x... Specific provider (optional, open if omitted)
|
|
1277
|
-
--budget 1.00 Budget in USDC
|
|
1278
|
-
--expires 24h Expiry (e.g., "24h", "7d", "1h")
|
|
34
|
+
${c.bold}Groups:${c.reset}
|
|
35
|
+
${Object.entries(groups).filter(([, cmds]) => cmds.some(n => n.includes('.'))).map(([g, cmds]) => ` ${g.padEnd(14)} ${cmds.filter(n => n.includes('.')).length} subcommands (obolos ${g} --help)`).join('\n')}
|
|
1279
36
|
|
|
1280
|
-
${c.bold}
|
|
1281
|
-
--
|
|
37
|
+
${c.bold}Output:${c.reset}
|
|
38
|
+
--json Machine-readable JSON (stable schema, use this when scripting)
|
|
39
|
+
--dry-run Preview destructive actions without executing
|
|
40
|
+
-h, --help Show command help (includes JSON schema for MCP/scripting)
|
|
1282
41
|
|
|
1283
|
-
${c.bold}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
${c.bold}Examples:${c.reset}
|
|
1287
|
-
obolos job list --status=open
|
|
1288
|
-
obolos job create --title "Analyze dataset" --evaluator 0xABC... --budget 5.00 --expires 7d
|
|
1289
|
-
obolos job info abc123
|
|
1290
|
-
obolos job fund abc123
|
|
1291
|
-
obolos job submit abc123 --deliverable ipfs://Qm...
|
|
1292
|
-
obolos job complete abc123 --reason "Looks great"
|
|
1293
|
-
obolos job reject abc123 --reason "Missing section 3"
|
|
1294
|
-
`);
|
|
1295
|
-
}
|
|
1296
|
-
async function cmdJob(args) {
|
|
1297
|
-
const subcommand = args[0];
|
|
1298
|
-
const subArgs = args.slice(1);
|
|
1299
|
-
switch (subcommand) {
|
|
1300
|
-
case 'list':
|
|
1301
|
-
case 'ls':
|
|
1302
|
-
await cmdJobList(subArgs);
|
|
1303
|
-
break;
|
|
1304
|
-
case 'create':
|
|
1305
|
-
case 'new':
|
|
1306
|
-
await cmdJobCreate(subArgs);
|
|
1307
|
-
break;
|
|
1308
|
-
case 'info':
|
|
1309
|
-
case 'show':
|
|
1310
|
-
await cmdJobInfo(subArgs);
|
|
1311
|
-
break;
|
|
1312
|
-
case 'fund':
|
|
1313
|
-
await cmdJobFund(subArgs);
|
|
1314
|
-
break;
|
|
1315
|
-
case 'submit':
|
|
1316
|
-
await cmdJobSubmit(subArgs);
|
|
1317
|
-
break;
|
|
1318
|
-
case 'complete':
|
|
1319
|
-
case 'approve':
|
|
1320
|
-
await cmdJobComplete(subArgs);
|
|
1321
|
-
break;
|
|
1322
|
-
case 'reject':
|
|
1323
|
-
await cmdJobReject(subArgs);
|
|
1324
|
-
break;
|
|
1325
|
-
case 'help':
|
|
1326
|
-
case '--help':
|
|
1327
|
-
case '-h':
|
|
1328
|
-
case undefined:
|
|
1329
|
-
showJobHelp();
|
|
1330
|
-
break;
|
|
1331
|
-
default:
|
|
1332
|
-
console.error(`${c.red}Unknown job subcommand: ${subcommand}${c.reset}`);
|
|
1333
|
-
showJobHelp();
|
|
1334
|
-
process.exit(1);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
// ─── Listing Commands (Negotiation Layer) ────────────────────────────────────
|
|
1338
|
-
async function cmdListingList(args) {
|
|
1339
|
-
const params = new URLSearchParams();
|
|
1340
|
-
const status = getFlag(args, 'status');
|
|
1341
|
-
const client = getFlag(args, 'client');
|
|
1342
|
-
const limit = getFlag(args, 'limit') || '20';
|
|
1343
|
-
if (status)
|
|
1344
|
-
params.set('status', status);
|
|
1345
|
-
if (client)
|
|
1346
|
-
params.set('client', client);
|
|
1347
|
-
params.set('limit', limit);
|
|
1348
|
-
const data = await apiGet(`/api/listings?${params}`);
|
|
1349
|
-
const listings = data.listings || data.data || [];
|
|
1350
|
-
if (listings.length === 0) {
|
|
1351
|
-
console.log(`${c.yellow}No listings found.${c.reset}`);
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
const total = data.pagination?.total || data.total || listings.length;
|
|
1355
|
-
console.log(`\n${c.bold}${c.cyan}Job Listings${c.reset} ${c.dim}— ${total} listings${c.reset}\n`);
|
|
1356
|
-
// Table header
|
|
1357
|
-
console.log(` ${c.bold}${'ID'.padEnd(12)} ${'Title'.padEnd(28)} ${'Status'.padEnd(14)} ${'Budget Range'.padEnd(20)} ${'Bids'.padEnd(6)} ${'Client'.padEnd(14)} Deadline${c.reset}`);
|
|
1358
|
-
console.log(` ${c.dim}${'─'.repeat(110)}${c.reset}`);
|
|
1359
|
-
for (const l of listings) {
|
|
1360
|
-
const id = shortenId(l.id || '');
|
|
1361
|
-
const title = (l.title || 'Untitled').slice(0, 26).padEnd(28);
|
|
1362
|
-
const st = statusColor((l.status || 'open').padEnd(12));
|
|
1363
|
-
const budgetMin = l.min_budget != null ? `$${Number(l.min_budget).toFixed(2)}` : '?';
|
|
1364
|
-
const budgetMax = l.max_budget != null ? `$${Number(l.max_budget).toFixed(2)}` : '?';
|
|
1365
|
-
const budget = `${budgetMin}-${budgetMax}`.padEnd(20);
|
|
1366
|
-
const bids = String(l.bid_count ?? l.bids?.length ?? 0).padEnd(6);
|
|
1367
|
-
const cl = shortenAddr(l.client_address || l.client).padEnd(14);
|
|
1368
|
-
const deadline = l.deadline ? formatDate(l.deadline) : `${c.dim}—${c.reset}`;
|
|
1369
|
-
console.log(` ${id.padEnd(12)} ${title} ${st} ${budget} ${bids} ${cl} ${deadline}`);
|
|
1370
|
-
}
|
|
1371
|
-
console.log(`\n${c.dim}Use: obolos listing info <id> for full details${c.reset}\n`);
|
|
1372
|
-
}
|
|
1373
|
-
async function cmdListingCreate(args) {
|
|
1374
|
-
const title = getFlag(args, 'title');
|
|
1375
|
-
const description = getFlag(args, 'description');
|
|
1376
|
-
const minBudget = getFlag(args, 'min-budget');
|
|
1377
|
-
const maxBudget = getFlag(args, 'max-budget');
|
|
1378
|
-
const deadline = getFlag(args, 'deadline');
|
|
1379
|
-
const duration = getFlag(args, 'duration');
|
|
1380
|
-
const evaluator = getFlag(args, 'evaluator');
|
|
1381
|
-
const hook = getFlag(args, 'hook');
|
|
1382
|
-
if (!title) {
|
|
1383
|
-
console.error(`${c.red}Usage: obolos listing create --title "..." --description "..." [--min-budget 1.00] [--max-budget 10.00] [--deadline 7d] [--duration 24]${c.reset}`);
|
|
1384
|
-
process.exit(1);
|
|
1385
|
-
}
|
|
1386
|
-
const walletAddress = await getWalletAddress();
|
|
1387
|
-
const payload = { title };
|
|
1388
|
-
if (description)
|
|
1389
|
-
payload.description = description;
|
|
1390
|
-
if (minBudget)
|
|
1391
|
-
payload.min_budget = minBudget;
|
|
1392
|
-
if (maxBudget)
|
|
1393
|
-
payload.max_budget = maxBudget;
|
|
1394
|
-
if (deadline)
|
|
1395
|
-
payload.deadline = deadline;
|
|
1396
|
-
if (duration)
|
|
1397
|
-
payload.job_duration = parseInt(duration, 10);
|
|
1398
|
-
if (evaluator)
|
|
1399
|
-
payload.preferred_evaluator = evaluator;
|
|
1400
|
-
if (hook)
|
|
1401
|
-
payload.hook_address = hook;
|
|
1402
|
-
const data = await apiPost('/api/listings', payload, {
|
|
1403
|
-
'x-wallet-address': walletAddress,
|
|
1404
|
-
});
|
|
1405
|
-
const listing = data.listing || data;
|
|
1406
|
-
console.log(`\n${c.green}Listing created successfully!${c.reset}\n`);
|
|
1407
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1408
|
-
console.log(` ${c.bold}ID:${c.reset} ${listing.id}`);
|
|
1409
|
-
console.log(` ${c.bold}Title:${c.reset} ${listing.title}`);
|
|
1410
|
-
if (listing.description) {
|
|
1411
|
-
console.log(` ${c.bold}Description:${c.reset} ${listing.description}`);
|
|
1412
|
-
}
|
|
1413
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(listing.status || 'open')}`);
|
|
1414
|
-
console.log(` ${c.bold}Client:${c.reset} ${listing.client_address || walletAddress}`);
|
|
1415
|
-
if (listing.min_budget != null || listing.max_budget != null) {
|
|
1416
|
-
const min = listing.min_budget != null ? `$${Number(listing.min_budget).toFixed(2)}` : '?';
|
|
1417
|
-
const max = listing.max_budget != null ? `$${Number(listing.max_budget).toFixed(2)}` : '?';
|
|
1418
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}${min} – ${max} USDC${c.reset}`);
|
|
1419
|
-
}
|
|
1420
|
-
if (listing.deadline) {
|
|
1421
|
-
console.log(` ${c.bold}Deadline:${c.reset} ${formatDate(listing.deadline)}`);
|
|
1422
|
-
}
|
|
1423
|
-
if (listing.job_duration) {
|
|
1424
|
-
console.log(` ${c.bold}Duration:${c.reset} ${listing.job_duration}h`);
|
|
1425
|
-
}
|
|
1426
|
-
console.log(`\n${c.dim}Share this listing with providers. They can bid with: obolos listing bid ${listing.id} --price 5.00${c.reset}\n`);
|
|
1427
|
-
}
|
|
1428
|
-
async function cmdListingInfo(args) {
|
|
1429
|
-
const id = getPositional(args, 0);
|
|
1430
|
-
if (!id) {
|
|
1431
|
-
console.error(`${c.red}Usage: obolos listing info <id>${c.reset}`);
|
|
1432
|
-
process.exit(1);
|
|
1433
|
-
}
|
|
1434
|
-
const data = await apiGet(`/api/listings/${encodeURIComponent(id)}`);
|
|
1435
|
-
const listing = data.listing || data;
|
|
1436
|
-
console.log(`\n${c.bold}${c.cyan}${listing.title || 'Untitled Listing'}${c.reset}`);
|
|
1437
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1438
|
-
console.log(` ${c.bold}ID:${c.reset} ${listing.id}`);
|
|
1439
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(listing.status || 'open')}`);
|
|
1440
|
-
console.log(` ${c.bold}Client:${c.reset} ${listing.client_address || `${c.dim}—${c.reset}`}`);
|
|
1441
|
-
if (listing.min_budget != null || listing.max_budget != null) {
|
|
1442
|
-
const min = listing.min_budget != null ? `$${Number(listing.min_budget).toFixed(2)}` : '?';
|
|
1443
|
-
const max = listing.max_budget != null ? `$${Number(listing.max_budget).toFixed(2)}` : '?';
|
|
1444
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}${min} – ${max} USDC${c.reset}`);
|
|
1445
|
-
}
|
|
1446
|
-
if (listing.deadline) {
|
|
1447
|
-
const deadlineDate = new Date(listing.deadline);
|
|
1448
|
-
const now = new Date();
|
|
1449
|
-
const expired = deadlineDate < now;
|
|
1450
|
-
console.log(` ${c.bold}Deadline:${c.reset} ${expired ? c.red : c.dim}${formatDate(listing.deadline)}${expired ? ' (passed)' : ''}${c.reset}`);
|
|
1451
|
-
}
|
|
1452
|
-
if (listing.job_duration) {
|
|
1453
|
-
console.log(` ${c.bold}Duration:${c.reset} ${listing.job_duration}h`);
|
|
1454
|
-
}
|
|
1455
|
-
if (listing.preferred_evaluator) {
|
|
1456
|
-
console.log(` ${c.bold}Evaluator:${c.reset} ${listing.preferred_evaluator}`);
|
|
1457
|
-
}
|
|
1458
|
-
if (listing.description) {
|
|
1459
|
-
console.log(`\n ${c.bold}Description:${c.reset}`);
|
|
1460
|
-
const descLines = listing.description.split('\n');
|
|
1461
|
-
for (const line of descLines) {
|
|
1462
|
-
console.log(` ${line}`);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
console.log(` ${c.bold}Created:${c.reset} ${formatDate(listing.created_at || listing.createdAt)}`);
|
|
1466
|
-
// Bids
|
|
1467
|
-
const bids = listing.bids || [];
|
|
1468
|
-
if (bids.length > 0) {
|
|
1469
|
-
console.log(`\n ${c.bold}${c.cyan}Bids (${bids.length})${c.reset}`);
|
|
1470
|
-
console.log(` ${c.dim}${'─'.repeat(56)}${c.reset}`);
|
|
1471
|
-
console.log(` ${c.bold}${'Bid ID'.padEnd(12)} ${'Provider'.padEnd(14)} ${'Price'.padEnd(12)} ${'Delivery'.padEnd(10)} Message${c.reset}`);
|
|
1472
|
-
console.log(` ${c.dim}${'─'.repeat(56)}${c.reset}`);
|
|
1473
|
-
for (const bid of bids) {
|
|
1474
|
-
const bidId = shortenId(bid.id || '');
|
|
1475
|
-
const provider = shortenAddr(bid.provider_address);
|
|
1476
|
-
const price = bid.price != null ? `${c.green}$${Number(bid.price).toFixed(2)}${c.reset}` : `${c.dim}—${c.reset}`;
|
|
1477
|
-
const delivery = bid.delivery_time ? `${bid.delivery_time}h` : `${c.dim}—${c.reset}`;
|
|
1478
|
-
const msg = (bid.message || '').slice(0, 40);
|
|
1479
|
-
console.log(` ${bidId.padEnd(12)} ${provider.padEnd(14)} ${price.padEnd(12)} ${delivery.padEnd(10)} ${c.dim}${msg}${c.reset}`);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
else {
|
|
1483
|
-
console.log(`\n ${c.dim}No bids yet.${c.reset}`);
|
|
1484
|
-
}
|
|
1485
|
-
// Actions
|
|
1486
|
-
console.log();
|
|
1487
|
-
const s = listing.status || 'open';
|
|
1488
|
-
if (s === 'open') {
|
|
1489
|
-
console.log(` ${c.bold}Actions:${c.reset}`);
|
|
1490
|
-
console.log(` obolos listing bid ${listing.id} --price 5.00 ${c.dim}Submit a bid${c.reset}`);
|
|
1491
|
-
if (bids.length > 0) {
|
|
1492
|
-
console.log(` obolos listing accept ${listing.id} --bid <bid_id> ${c.dim}Accept a bid${c.reset}`);
|
|
1493
|
-
}
|
|
1494
|
-
console.log(` obolos listing cancel ${listing.id} ${c.dim}Cancel the listing${c.reset}`);
|
|
1495
|
-
}
|
|
1496
|
-
console.log();
|
|
1497
|
-
}
|
|
1498
|
-
async function cmdListingBid(args) {
|
|
1499
|
-
const listingId = getPositional(args, 0);
|
|
1500
|
-
if (!listingId) {
|
|
1501
|
-
console.error(`${c.red}Usage: obolos listing bid <listing_id> --price 5.00 [--delivery 24] [--message "..."]${c.reset}`);
|
|
1502
|
-
process.exit(1);
|
|
1503
|
-
}
|
|
1504
|
-
const price = getFlag(args, 'price');
|
|
1505
|
-
if (!price) {
|
|
1506
|
-
console.error(`${c.red}--price is required. Provide your bid amount in USDC.${c.reset}`);
|
|
1507
|
-
process.exit(1);
|
|
1508
|
-
}
|
|
1509
|
-
const delivery = getFlag(args, 'delivery');
|
|
1510
|
-
const message = getFlag(args, 'message');
|
|
1511
|
-
const proposalHash = getFlag(args, 'proposal-hash');
|
|
1512
|
-
const walletAddress = await getWalletAddress();
|
|
1513
|
-
const payload = { price };
|
|
1514
|
-
if (delivery)
|
|
1515
|
-
payload.delivery_time = parseInt(delivery, 10);
|
|
1516
|
-
if (message)
|
|
1517
|
-
payload.message = message;
|
|
1518
|
-
if (proposalHash)
|
|
1519
|
-
payload.proposal_hash = proposalHash;
|
|
1520
|
-
const data = await apiPost(`/api/listings/${encodeURIComponent(listingId)}/bid`, payload, {
|
|
1521
|
-
'x-wallet-address': walletAddress,
|
|
1522
|
-
});
|
|
1523
|
-
const bid = data.bid || data;
|
|
1524
|
-
console.log(`\n${c.green}Bid submitted successfully!${c.reset}\n`);
|
|
1525
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1526
|
-
console.log(` ${c.bold}Bid ID:${c.reset} ${bid.id}`);
|
|
1527
|
-
console.log(` ${c.bold}Listing:${c.reset} ${listingId}`);
|
|
1528
|
-
console.log(` ${c.bold}Price:${c.reset} ${c.green}$${Number(price).toFixed(2)} USDC${c.reset}`);
|
|
1529
|
-
if (delivery) {
|
|
1530
|
-
console.log(` ${c.bold}Delivery:${c.reset} ${delivery}h`);
|
|
1531
|
-
}
|
|
1532
|
-
if (message) {
|
|
1533
|
-
console.log(` ${c.bold}Message:${c.reset} ${message}`);
|
|
1534
|
-
}
|
|
1535
|
-
console.log(`\n${c.dim}The client will review your bid. You'll be notified if accepted.${c.reset}\n`);
|
|
1536
|
-
}
|
|
1537
|
-
async function cmdListingAccept(args) {
|
|
1538
|
-
const listingId = getPositional(args, 0);
|
|
1539
|
-
if (!listingId) {
|
|
1540
|
-
console.error(`${c.red}Usage: obolos listing accept <listing_id> --bid <bid_id>${c.reset}`);
|
|
1541
|
-
process.exit(1);
|
|
1542
|
-
}
|
|
1543
|
-
const bidId = getFlag(args, 'bid');
|
|
1544
|
-
if (!bidId) {
|
|
1545
|
-
console.error(`${c.red}--bid is required. Specify the bid ID to accept.${c.reset}`);
|
|
1546
|
-
process.exit(1);
|
|
1547
|
-
}
|
|
1548
|
-
const walletAddress = await getWalletAddress();
|
|
1549
|
-
// Create on-chain ACP job if wallet is available
|
|
1550
|
-
let chainJobId = null;
|
|
1551
|
-
let chainTxHash = null;
|
|
1552
|
-
try {
|
|
1553
|
-
// Fetch listing details to get terms
|
|
1554
|
-
const listingData = await apiGet(`/api/listings/${encodeURIComponent(listingId)}`);
|
|
1555
|
-
const listing = listingData.listing || listingData;
|
|
1556
|
-
const bids = listing.bids || [];
|
|
1557
|
-
const acceptedBid = bids.find((b) => b.id === bidId);
|
|
1558
|
-
if (acceptedBid && OBOLOS_PRIVATE_KEY) {
|
|
1559
|
-
const acp = await getACPClient();
|
|
1560
|
-
const providerAddress = acceptedBid.provider_address || ZERO_ADDRESS;
|
|
1561
|
-
const evaluatorAddress = listing.preferred_evaluator || walletAddress;
|
|
1562
|
-
// Default expiry: delivery_time hours or job_duration or 7 days
|
|
1563
|
-
const durationHours = acceptedBid.delivery_time || listing.job_duration || 168;
|
|
1564
|
-
const expiredAt = Math.floor((Date.now() + durationHours * 3600000) / 1000);
|
|
1565
|
-
const description = `${listing.title}: ${listing.description || ''}`.slice(0, 500);
|
|
1566
|
-
console.log(`\n ${c.dim}Creating ACP job on-chain...${c.reset}`);
|
|
1567
|
-
const txHash = await acp.walletClient.writeContract({
|
|
1568
|
-
address: ACP_ADDRESS,
|
|
1569
|
-
abi: ACP_ABI,
|
|
1570
|
-
functionName: 'createJob',
|
|
1571
|
-
args: [
|
|
1572
|
-
providerAddress,
|
|
1573
|
-
evaluatorAddress,
|
|
1574
|
-
BigInt(expiredAt),
|
|
1575
|
-
description,
|
|
1576
|
-
(listing.hook_address || ZERO_ADDRESS),
|
|
1577
|
-
],
|
|
1578
|
-
account: acp.account,
|
|
1579
|
-
chain: (await import('viem/chains')).base,
|
|
1580
|
-
});
|
|
1581
|
-
console.log(` ${c.dim}Waiting for confirmation...${c.reset}`);
|
|
1582
|
-
const receipt = await acp.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
1583
|
-
// Extract jobId from JobCreated event
|
|
1584
|
-
for (const log of receipt.logs) {
|
|
1585
|
-
try {
|
|
1586
|
-
const decoded = acp.decodeEventLog({
|
|
1587
|
-
abi: ACP_ABI,
|
|
1588
|
-
data: log.data,
|
|
1589
|
-
topics: log.topics,
|
|
1590
|
-
});
|
|
1591
|
-
if (decoded.eventName === 'JobCreated') {
|
|
1592
|
-
chainJobId = (decoded.args.jobId).toString();
|
|
1593
|
-
break;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
catch { }
|
|
1597
|
-
}
|
|
1598
|
-
chainTxHash = txHash;
|
|
1599
|
-
console.log(` ${c.green}Transaction confirmed: ${txHash}${c.reset}`);
|
|
1600
|
-
if (chainJobId) {
|
|
1601
|
-
console.log(` ${c.green}Chain job ID: ${chainJobId}${c.reset}`);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
catch (err) {
|
|
1606
|
-
console.error(` ${c.yellow}On-chain job creation failed: ${err.message}${c.reset}`);
|
|
1607
|
-
console.error(` ${c.dim}Proceeding with backend-only acceptance...${c.reset}`);
|
|
1608
|
-
}
|
|
1609
|
-
const payload = { bid_id: bidId };
|
|
1610
|
-
if (chainJobId)
|
|
1611
|
-
payload.acp_job_id = chainJobId;
|
|
1612
|
-
if (chainTxHash)
|
|
1613
|
-
payload.chain_tx_hash = chainTxHash;
|
|
1614
|
-
const data = await apiPost(`/api/listings/${encodeURIComponent(listingId)}/accept`, payload, {
|
|
1615
|
-
'x-wallet-address': walletAddress,
|
|
1616
|
-
});
|
|
1617
|
-
const listing = data.listing || data;
|
|
1618
|
-
console.log(`\n${c.green}Bid accepted! ACP job created.${c.reset}\n`);
|
|
1619
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1620
|
-
console.log(` ${c.bold}Listing:${c.reset} ${listing.title || listingId}`);
|
|
1621
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(listing.status || 'accepted')}`);
|
|
1622
|
-
console.log(` ${c.bold}Bid:${c.reset} ${bidId}`);
|
|
1623
|
-
if (chainJobId) {
|
|
1624
|
-
console.log(` ${c.bold}Chain ID:${c.reset} ${chainJobId}`);
|
|
1625
|
-
}
|
|
1626
|
-
if (chainTxHash) {
|
|
1627
|
-
console.log(` ${c.bold}Tx:${c.reset} ${c.cyan}${chainTxHash}${c.reset}`);
|
|
1628
|
-
}
|
|
1629
|
-
if (listing.job_id || data.job_id) {
|
|
1630
|
-
console.log(` ${c.bold}Job ID:${c.reset} ${listing.job_id || data.job_id}`);
|
|
1631
|
-
}
|
|
1632
|
-
console.log(`\n${c.dim}Next: Fund the escrow with: obolos job fund <job-id>${c.reset}\n`);
|
|
1633
|
-
}
|
|
1634
|
-
async function cmdListingCancel(args) {
|
|
1635
|
-
const listingId = getPositional(args, 0);
|
|
1636
|
-
if (!listingId) {
|
|
1637
|
-
console.error(`${c.red}Usage: obolos listing cancel <listing_id>${c.reset}`);
|
|
1638
|
-
process.exit(1);
|
|
1639
|
-
}
|
|
1640
|
-
const walletAddress = await getWalletAddress();
|
|
1641
|
-
const data = await apiPost(`/api/listings/${encodeURIComponent(listingId)}/cancel`, {}, {
|
|
1642
|
-
'x-wallet-address': walletAddress,
|
|
1643
|
-
});
|
|
1644
|
-
const listing = data.listing || data;
|
|
1645
|
-
console.log(`\n${c.yellow}Listing cancelled.${c.reset}\n`);
|
|
1646
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1647
|
-
console.log(` ${c.bold}Listing:${c.reset} ${listing.title || listingId}`);
|
|
1648
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(listing.status || 'cancelled')}`);
|
|
1649
|
-
console.log();
|
|
1650
|
-
}
|
|
1651
|
-
function showListingHelp() {
|
|
1652
|
-
console.log(`
|
|
1653
|
-
${c.bold}${c.cyan}obolos listing${c.reset} — Agent-to-agent negotiation layer
|
|
1654
|
-
|
|
1655
|
-
${c.bold}Usage:${c.reset}
|
|
1656
|
-
obolos listing list [options] Browse open job listings
|
|
1657
|
-
obolos listing create [options] Create a new listing for agents to bid on
|
|
1658
|
-
obolos listing info <id> Get listing details with all bids
|
|
1659
|
-
obolos listing bid <id> [options] Submit a bid on a listing
|
|
1660
|
-
obolos listing accept <id> [options] Accept a bid (auto-creates ACP job)
|
|
1661
|
-
obolos listing cancel <id> Cancel a listing
|
|
1662
|
-
|
|
1663
|
-
${c.bold}List Options:${c.reset}
|
|
1664
|
-
--status=open|negotiating|accepted|cancelled
|
|
1665
|
-
--client=0x... Filter by client address
|
|
1666
|
-
--limit=20 Max results (default: 20)
|
|
1667
|
-
|
|
1668
|
-
${c.bold}Create Options:${c.reset}
|
|
1669
|
-
--title "..." Listing title (required)
|
|
1670
|
-
--description "..." Detailed description
|
|
1671
|
-
--min-budget 1.00 Minimum budget in USDC
|
|
1672
|
-
--max-budget 10.00 Maximum budget in USDC
|
|
1673
|
-
--deadline 7d Bidding deadline (e.g., "24h", "7d")
|
|
1674
|
-
--duration 24 Expected job duration in hours
|
|
1675
|
-
--evaluator 0x... Preferred evaluator address
|
|
1676
|
-
--hook 0x... Hook contract address
|
|
1677
|
-
|
|
1678
|
-
${c.bold}Bid Options:${c.reset}
|
|
1679
|
-
--price 5.00 Your proposed price in USDC (required)
|
|
1680
|
-
--delivery 24 Estimated delivery time in hours
|
|
1681
|
-
--message "I can do this" Pitch to the client
|
|
1682
|
-
--proposal-hash <hash> Hash of detailed proposal
|
|
42
|
+
${c.bold}Config:${c.reset}
|
|
43
|
+
~/.obolos/config.json (mode 0600) or OBOLOS_PRIVATE_KEY / OBOLOS_API_URL env vars.
|
|
44
|
+
Run ${c.cyan}obolos setup --generate${c.reset} to create a new wallet.
|
|
1683
45
|
|
|
1684
|
-
${c.bold}
|
|
1685
|
-
|
|
46
|
+
${c.bold}MCP:${c.reset}
|
|
47
|
+
Every command above is also exposed as an MCP tool by @obolos_tech/mcp-server.
|
|
48
|
+
Run ${c.cyan}obolos setup-mcp${c.reset} for install + configuration instructions.
|
|
1686
49
|
|
|
1687
|
-
${c.bold}
|
|
1688
|
-
obolos listing list --status=open
|
|
1689
|
-
obolos listing create --title "Analyze dataset" --description "Parse and summarize CSV" --max-budget 10.00 --deadline 7d
|
|
1690
|
-
obolos listing info abc123
|
|
1691
|
-
obolos listing bid abc123 --price 5.00 --delivery 24 --message "I can do this in 12h"
|
|
1692
|
-
obolos listing accept abc123 --bid bid456
|
|
1693
|
-
obolos listing cancel abc123
|
|
50
|
+
${c.bold}Docs:${c.reset} https://obolos.tech
|
|
1694
51
|
`);
|
|
1695
52
|
}
|
|
1696
|
-
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
await cmdListingList(subArgs);
|
|
1703
|
-
break;
|
|
1704
|
-
case 'create':
|
|
1705
|
-
case 'new':
|
|
1706
|
-
await cmdListingCreate(subArgs);
|
|
1707
|
-
break;
|
|
1708
|
-
case 'info':
|
|
1709
|
-
case 'show':
|
|
1710
|
-
await cmdListingInfo(subArgs);
|
|
1711
|
-
break;
|
|
1712
|
-
case 'bid':
|
|
1713
|
-
await cmdListingBid(subArgs);
|
|
1714
|
-
break;
|
|
1715
|
-
case 'accept':
|
|
1716
|
-
await cmdListingAccept(subArgs);
|
|
1717
|
-
break;
|
|
1718
|
-
case 'cancel':
|
|
1719
|
-
await cmdListingCancel(subArgs);
|
|
1720
|
-
break;
|
|
1721
|
-
case 'help':
|
|
1722
|
-
case '--help':
|
|
1723
|
-
case '-h':
|
|
1724
|
-
case undefined:
|
|
1725
|
-
showListingHelp();
|
|
1726
|
-
break;
|
|
1727
|
-
default:
|
|
1728
|
-
console.error(`${c.red}Unknown listing subcommand: ${sub}${c.reset}`);
|
|
1729
|
-
showListingHelp();
|
|
1730
|
-
process.exit(1);
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
// ─── ANP Commands (Agent Negotiation Protocol) ──────────────────────────────
|
|
1734
|
-
async function cmdAnpList(args) {
|
|
1735
|
-
const params = new URLSearchParams();
|
|
1736
|
-
const status = getFlag(args, 'status');
|
|
1737
|
-
const limit = getFlag(args, 'limit') || '20';
|
|
1738
|
-
if (status)
|
|
1739
|
-
params.set('status', status);
|
|
1740
|
-
params.set('limit', limit);
|
|
1741
|
-
const data = await apiGet(`/api/anp/listings?${params}`);
|
|
1742
|
-
const listings = data.listings || data.data || [];
|
|
1743
|
-
if (listings.length === 0) {
|
|
1744
|
-
console.log(`${c.yellow}No ANP listings found.${c.reset}`);
|
|
1745
|
-
return;
|
|
1746
|
-
}
|
|
1747
|
-
const total = data.pagination?.total || data.total || listings.length;
|
|
1748
|
-
console.log(`\n${c.bold}${c.cyan}ANP Listings${c.reset} ${c.dim}— ${total} listings${c.reset}\n`);
|
|
1749
|
-
// Table header
|
|
1750
|
-
console.log(` ${c.bold}${'CID'.padEnd(18)} ${'Title'.padEnd(28)} ${'Budget Range'.padEnd(20)} ${'Status'.padEnd(14)} ${'Bids'.padEnd(6)} Client${c.reset}`);
|
|
1751
|
-
console.log(` ${c.dim}${'─'.repeat(100)}${c.reset}`);
|
|
1752
|
-
for (const l of listings) {
|
|
1753
|
-
const cid = (l.cid || l.id || '').slice(0, 16).padEnd(18);
|
|
1754
|
-
const title = (l.title || 'Untitled').slice(0, 26).padEnd(28);
|
|
1755
|
-
const minUsd = l.minBudgetUsd ?? l.min_budget_usd ?? (l.minBudget ? Number(l.minBudget) / 1e6 : null) ?? (l.min_budget ? Number(l.min_budget) / 1e6 : null);
|
|
1756
|
-
const maxUsd = l.maxBudgetUsd ?? l.max_budget_usd ?? (l.maxBudget ? Number(l.maxBudget) / 1e6 : null) ?? (l.max_budget ? Number(l.max_budget) / 1e6 : null);
|
|
1757
|
-
const budgetMin = minUsd != null ? `$${minUsd.toFixed(0)}` : '?';
|
|
1758
|
-
const budgetMax = maxUsd != null ? `$${maxUsd.toFixed(0)}` : '?';
|
|
1759
|
-
const budget = `${budgetMin}-${budgetMax}`.padEnd(20);
|
|
1760
|
-
const st = statusColor((l.status || 'open').padEnd(12));
|
|
1761
|
-
const bids = String(l.bidCount ?? l.bid_count ?? l.bids?.length ?? 0).padEnd(6);
|
|
1762
|
-
const cl = shortenAddr(l.client_address || l.client || l.signer);
|
|
1763
|
-
console.log(` ${cid} ${title} ${budget} ${st} ${bids} ${cl}`);
|
|
1764
|
-
}
|
|
1765
|
-
console.log(`\n${c.dim}Use: obolos anp info <cid> for full details${c.reset}\n`);
|
|
1766
|
-
}
|
|
1767
|
-
async function cmdAnpInfo(args) {
|
|
1768
|
-
const cid = getPositional(args, 0);
|
|
1769
|
-
if (!cid) {
|
|
1770
|
-
console.error(`${c.red}Usage: obolos anp info <cid>${c.reset}`);
|
|
1771
|
-
process.exit(1);
|
|
1772
|
-
}
|
|
1773
|
-
const data = await apiGet(`/api/anp/listings/${encodeURIComponent(cid)}`);
|
|
1774
|
-
const listing = data.listing || data;
|
|
1775
|
-
console.log(`\n${c.bold}${c.cyan}${listing.title || 'Untitled ANP Listing'}${c.reset}`);
|
|
1776
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1777
|
-
console.log(` ${c.bold}CID:${c.reset} ${listing.cid || cid}`);
|
|
1778
|
-
console.log(` ${c.bold}Status:${c.reset} ${statusColor(listing.status || 'open')}`);
|
|
1779
|
-
console.log(` ${c.bold}Client:${c.reset} ${listing.client || listing.client_address || listing.signer || `${c.dim}—${c.reset}`}`);
|
|
1780
|
-
const minRaw = listing.minBudget ?? listing.min_budget;
|
|
1781
|
-
const maxRaw = listing.maxBudget ?? listing.max_budget;
|
|
1782
|
-
const minUsd = listing.minBudgetUsd ?? (minRaw ? Number(minRaw) / 1e6 : null);
|
|
1783
|
-
const maxUsd = listing.maxBudgetUsd ?? (maxRaw ? Number(maxRaw) / 1e6 : null);
|
|
1784
|
-
if (minUsd != null || maxUsd != null) {
|
|
1785
|
-
const min = minUsd != null ? `$${minUsd.toFixed(2)}` : '?';
|
|
1786
|
-
const max = maxUsd != null ? `$${maxUsd.toFixed(2)}` : '?';
|
|
1787
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}${min} – ${max} USDC${c.reset}`);
|
|
1788
|
-
}
|
|
1789
|
-
const deadlineRaw = listing.deadline;
|
|
1790
|
-
if (deadlineRaw) {
|
|
1791
|
-
const ts = Number(deadlineRaw);
|
|
1792
|
-
const deadlineDate = new Date(ts > 1e12 ? ts : ts * 1000);
|
|
1793
|
-
const now = new Date();
|
|
1794
|
-
const expired = deadlineDate < now;
|
|
1795
|
-
console.log(` ${c.bold}Deadline:${c.reset} ${expired ? c.red : c.dim}${formatDate(deadlineDate.toISOString())}${expired ? ' (passed)' : ''}${c.reset}`);
|
|
1796
|
-
}
|
|
1797
|
-
if (listing.job_duration || listing.jobDuration) {
|
|
1798
|
-
const dur = listing.job_duration || listing.jobDuration;
|
|
1799
|
-
console.log(` ${c.bold}Duration:${c.reset} ${dur >= 86400 ? `${Math.floor(dur / 86400)}d` : dur >= 3600 ? `${Math.floor(dur / 3600)}h` : `${dur}s`}`);
|
|
1800
|
-
}
|
|
1801
|
-
if (listing.preferred_evaluator || listing.preferredEvaluator) {
|
|
1802
|
-
const ev = listing.preferred_evaluator || listing.preferredEvaluator;
|
|
1803
|
-
if (ev !== ZERO_ADDRESS) {
|
|
1804
|
-
console.log(` ${c.bold}Evaluator:${c.reset} ${ev}`);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
if (listing.description) {
|
|
1808
|
-
console.log(`\n ${c.bold}Description:${c.reset}`);
|
|
1809
|
-
const descLines = listing.description.split('\n');
|
|
1810
|
-
for (const line of descLines) {
|
|
1811
|
-
console.log(` ${line}`);
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
if (listing.content_hash || listing.contentHash) {
|
|
1815
|
-
console.log(` ${c.bold}Content Hash:${c.reset} ${c.dim}${listing.content_hash || listing.contentHash}${c.reset}`);
|
|
1816
|
-
}
|
|
1817
|
-
if (listing.nonce != null) {
|
|
1818
|
-
console.log(` ${c.bold}Nonce:${c.reset} ${c.dim}${listing.nonce}${c.reset}`);
|
|
1819
|
-
}
|
|
1820
|
-
if (listing.signature) {
|
|
1821
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.dim}${listing.signature.slice(0, 20)}...${c.reset}`);
|
|
1822
|
-
}
|
|
1823
|
-
// Bids
|
|
1824
|
-
const bids = listing.bids || [];
|
|
1825
|
-
if (bids.length > 0) {
|
|
1826
|
-
console.log(`\n ${c.bold}${c.cyan}Bids (${bids.length})${c.reset}`);
|
|
1827
|
-
console.log(` ${c.dim}${'─'.repeat(56)}${c.reset}`);
|
|
1828
|
-
console.log(` ${c.bold}${'CID'.padEnd(18)} ${'Bidder'.padEnd(14)} ${'Price'.padEnd(12)} ${'Delivery'.padEnd(10)} Message${c.reset}`);
|
|
1829
|
-
console.log(` ${c.dim}${'─'.repeat(56)}${c.reset}`);
|
|
1830
|
-
for (const bid of bids) {
|
|
1831
|
-
const bidCid = (bid.cid || bid.id || '').slice(0, 16).padEnd(18);
|
|
1832
|
-
const bidder = shortenAddr(bid.provider || bid.signer || bid.provider_address);
|
|
1833
|
-
const priceUsd = bid.priceUsd ?? (bid.price != null ? Number(bid.price) / 1e6 : null);
|
|
1834
|
-
const price = priceUsd != null ? `${c.green}$${priceUsd.toFixed(2)}${c.reset}` : `${c.dim}—${c.reset}`;
|
|
1835
|
-
const delivery = bid.deliveryTime || bid.delivery_time;
|
|
1836
|
-
const deliveryStr = delivery ? (delivery >= 86400 ? `${Math.floor(delivery / 86400)}d` : delivery >= 3600 ? `${Math.floor(delivery / 3600)}h` : `${delivery}s`) : `${c.dim}—${c.reset}`;
|
|
1837
|
-
const msg = (bid.message || '').slice(0, 40);
|
|
1838
|
-
console.log(` ${bidCid} ${bidder.padEnd(14)} ${price.padEnd(12)} ${deliveryStr.padEnd(10)} ${c.dim}${msg}${c.reset}`);
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
else {
|
|
1842
|
-
console.log(`\n ${c.dim}No bids yet.${c.reset}`);
|
|
1843
|
-
}
|
|
1844
|
-
// Actions
|
|
1845
|
-
console.log();
|
|
1846
|
-
const s = listing.status || 'open';
|
|
1847
|
-
if (s === 'open' || s === 'negotiating') {
|
|
1848
|
-
console.log(` ${c.bold}Actions:${c.reset}`);
|
|
1849
|
-
console.log(` obolos anp bid ${cid} --price 5.00 ${c.dim}Submit a bid${c.reset}`);
|
|
1850
|
-
if (bids.length > 0) {
|
|
1851
|
-
console.log(` obolos anp accept ${cid} --bid <bid_cid> ${c.dim}Accept a bid${c.reset}`);
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
console.log(` obolos anp verify ${cid} ${c.dim}Verify document${c.reset}`);
|
|
1855
|
-
console.log();
|
|
1856
|
-
}
|
|
1857
|
-
async function cmdAnpCreate(args) {
|
|
1858
|
-
const title = getFlag(args, 'title');
|
|
1859
|
-
const description = getFlag(args, 'description');
|
|
1860
|
-
const minBudget = getFlag(args, 'min-budget');
|
|
1861
|
-
const maxBudget = getFlag(args, 'max-budget');
|
|
1862
|
-
const deadline = getFlag(args, 'deadline');
|
|
1863
|
-
const duration = getFlag(args, 'duration');
|
|
1864
|
-
const evaluator = getFlag(args, 'evaluator');
|
|
1865
|
-
if (!title) {
|
|
1866
|
-
console.error(`${c.red}Usage: obolos anp create --title "..." --description "..." --min-budget 5 --max-budget 50 --deadline 7d --duration 3d [--evaluator 0x...]${c.reset}`);
|
|
1867
|
-
process.exit(1);
|
|
1868
|
-
}
|
|
1869
|
-
const anp = await getANPSigningClient();
|
|
1870
|
-
// Compute content hash
|
|
1871
|
-
const contentHash = await computeContentHash({ title, description: description || '' });
|
|
1872
|
-
// Generate nonce
|
|
1873
|
-
const nonce = generateNonce();
|
|
1874
|
-
// Parse deadline to unix timestamp (seconds from now)
|
|
1875
|
-
let deadlineTs;
|
|
1876
|
-
if (deadline) {
|
|
1877
|
-
const secs = parseTimeToSeconds(deadline);
|
|
1878
|
-
deadlineTs = BigInt(Math.floor(Date.now() / 1000) + secs);
|
|
1879
|
-
}
|
|
1880
|
-
else {
|
|
1881
|
-
deadlineTs = BigInt(Math.floor(Date.now() / 1000) + 7 * 86400); // default 7d
|
|
1882
|
-
}
|
|
1883
|
-
// Parse duration to seconds
|
|
1884
|
-
let jobDuration;
|
|
1885
|
-
if (duration) {
|
|
1886
|
-
jobDuration = BigInt(parseTimeToSeconds(duration));
|
|
1887
|
-
}
|
|
1888
|
-
else {
|
|
1889
|
-
jobDuration = BigInt(3 * 86400); // default 3d
|
|
1890
|
-
}
|
|
1891
|
-
const minBudgetWei = BigInt(Math.floor((minBudget ? parseFloat(minBudget) : 0) * 1e6));
|
|
1892
|
-
const maxBudgetWei = BigInt(Math.floor((maxBudget ? parseFloat(maxBudget) : 0) * 1e6));
|
|
1893
|
-
const preferredEvaluator = (evaluator || ZERO_ADDRESS);
|
|
1894
|
-
const message = {
|
|
1895
|
-
contentHash,
|
|
1896
|
-
minBudget: minBudgetWei,
|
|
1897
|
-
maxBudget: maxBudgetWei,
|
|
1898
|
-
deadline: deadlineTs,
|
|
1899
|
-
jobDuration: jobDuration,
|
|
1900
|
-
preferredEvaluator,
|
|
1901
|
-
nonce,
|
|
1902
|
-
};
|
|
1903
|
-
console.log(`\n ${c.dim}Signing ListingIntent...${c.reset}`);
|
|
1904
|
-
const signature = await anp.walletClient.signTypedData({
|
|
1905
|
-
account: anp.account,
|
|
1906
|
-
domain: ANP_DOMAIN,
|
|
1907
|
-
types: { ListingIntent: ANP_TYPES.ListingIntent },
|
|
1908
|
-
primaryType: 'ListingIntent',
|
|
1909
|
-
message,
|
|
1910
|
-
});
|
|
1911
|
-
console.log(` ${c.green}Signed.${c.reset} Publishing...`);
|
|
1912
|
-
const document = {
|
|
1913
|
-
protocol: 'anp/v1',
|
|
1914
|
-
type: 'listing',
|
|
1915
|
-
data: {
|
|
1916
|
-
title,
|
|
1917
|
-
description: description || '',
|
|
1918
|
-
minBudget: minBudgetWei.toString(),
|
|
1919
|
-
maxBudget: maxBudgetWei.toString(),
|
|
1920
|
-
deadline: Number(deadlineTs),
|
|
1921
|
-
jobDuration: Number(jobDuration),
|
|
1922
|
-
preferredEvaluator,
|
|
1923
|
-
nonce: Number(nonce),
|
|
1924
|
-
},
|
|
1925
|
-
signer: anp.account.address.toLowerCase(),
|
|
1926
|
-
signature,
|
|
1927
|
-
timestamp: Date.now(),
|
|
1928
|
-
};
|
|
1929
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
1930
|
-
const result = data.listing || data;
|
|
1931
|
-
console.log(`\n${c.green}ANP listing published!${c.reset}\n`);
|
|
1932
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
1933
|
-
console.log(` ${c.bold}CID:${c.reset} ${result.cid || result.id}`);
|
|
1934
|
-
console.log(` ${c.bold}Title:${c.reset} ${title}`);
|
|
1935
|
-
console.log(` ${c.bold}Budget:${c.reset} ${c.green}$${(minBudget || '0')} – $${(maxBudget || '0')} USDC${c.reset}`);
|
|
1936
|
-
console.log(` ${c.bold}Deadline:${c.reset} ${formatDate(new Date(Number(deadlineTs) * 1000).toISOString())}`);
|
|
1937
|
-
console.log(` ${c.bold}Duration:${c.reset} ${duration || '3d'}`);
|
|
1938
|
-
console.log(` ${c.bold}Signer:${c.reset} ${anp.account.address}`);
|
|
1939
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.dim}${signature.slice(0, 20)}...${c.reset}`);
|
|
1940
|
-
console.log(`\n${c.dim}Agents can bid with: obolos anp bid ${result.cid || result.id} --price 25 --delivery 48h${c.reset}\n`);
|
|
1941
|
-
}
|
|
1942
|
-
async function cmdAnpBid(args) {
|
|
1943
|
-
const listingCid = getPositional(args, 0);
|
|
1944
|
-
if (!listingCid) {
|
|
1945
|
-
console.error(`${c.red}Usage: obolos anp bid <listing_cid> --price 25 --delivery 48h [--message "..."]${c.reset}`);
|
|
1946
|
-
process.exit(1);
|
|
1947
|
-
}
|
|
1948
|
-
const price = getFlag(args, 'price');
|
|
1949
|
-
if (!price) {
|
|
1950
|
-
console.error(`${c.red}--price is required. Provide your bid amount in USDC.${c.reset}`);
|
|
1951
|
-
process.exit(1);
|
|
1952
|
-
}
|
|
1953
|
-
const delivery = getFlag(args, 'delivery');
|
|
1954
|
-
const message = getFlag(args, 'message');
|
|
1955
|
-
const anp = await getANPSigningClient();
|
|
1956
|
-
// Fetch listing document to compute listingHash
|
|
1957
|
-
console.log(`\n ${c.dim}Fetching listing document...${c.reset}`);
|
|
1958
|
-
const listingData = await apiGet(`/api/anp/objects/${encodeURIComponent(listingCid)}`);
|
|
1959
|
-
const listingDoc = listingData;
|
|
1960
|
-
const ld = listingDoc.data || listingDoc;
|
|
1961
|
-
// Recompute listing content hash from title+description, then compute struct hash
|
|
1962
|
-
const listingContentHash = await computeContentHash({ title: ld.title, description: ld.description });
|
|
1963
|
-
const listingHash = anp.hashListingStruct({
|
|
1964
|
-
contentHash: listingContentHash,
|
|
1965
|
-
minBudget: BigInt(ld.minBudget || '0'),
|
|
1966
|
-
maxBudget: BigInt(ld.maxBudget || '0'),
|
|
1967
|
-
deadline: BigInt(ld.deadline || '0'),
|
|
1968
|
-
jobDuration: BigInt(ld.jobDuration || '0'),
|
|
1969
|
-
preferredEvaluator: (ld.preferredEvaluator || ZERO_ADDRESS),
|
|
1970
|
-
nonce: BigInt(ld.nonce || '0'),
|
|
1971
|
-
});
|
|
1972
|
-
// Compute content hash for bid
|
|
1973
|
-
const contentHash = await computeContentHash({ message: message || '', proposalCid: '' });
|
|
1974
|
-
const nonce = generateNonce();
|
|
1975
|
-
const priceWei = BigInt(Math.floor(parseFloat(price) * 1e6));
|
|
1976
|
-
let deliveryTime;
|
|
1977
|
-
if (delivery) {
|
|
1978
|
-
deliveryTime = BigInt(parseTimeToSeconds(delivery));
|
|
1979
|
-
}
|
|
1980
|
-
else {
|
|
1981
|
-
deliveryTime = BigInt(86400); // default 24h
|
|
1982
|
-
}
|
|
1983
|
-
const bidMessage = {
|
|
1984
|
-
listingHash,
|
|
1985
|
-
contentHash,
|
|
1986
|
-
price: priceWei,
|
|
1987
|
-
deliveryTime,
|
|
1988
|
-
nonce,
|
|
1989
|
-
};
|
|
1990
|
-
console.log(` ${c.dim}Signing BidIntent...${c.reset}`);
|
|
1991
|
-
const signature = await anp.walletClient.signTypedData({
|
|
1992
|
-
account: anp.account,
|
|
1993
|
-
domain: ANP_DOMAIN,
|
|
1994
|
-
types: { BidIntent: ANP_TYPES.BidIntent },
|
|
1995
|
-
primaryType: 'BidIntent',
|
|
1996
|
-
message: bidMessage,
|
|
1997
|
-
});
|
|
1998
|
-
console.log(` ${c.green}Signed.${c.reset} Publishing...`);
|
|
1999
|
-
const document = {
|
|
2000
|
-
protocol: 'anp/v1',
|
|
2001
|
-
type: 'bid',
|
|
2002
|
-
data: {
|
|
2003
|
-
listingCid,
|
|
2004
|
-
listingHash,
|
|
2005
|
-
price: priceWei.toString(),
|
|
2006
|
-
deliveryTime: Number(deliveryTime),
|
|
2007
|
-
message: message || '',
|
|
2008
|
-
nonce: Number(nonce),
|
|
2009
|
-
},
|
|
2010
|
-
signer: anp.account.address.toLowerCase(),
|
|
2011
|
-
signature,
|
|
2012
|
-
timestamp: Date.now(),
|
|
2013
|
-
};
|
|
2014
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2015
|
-
const result = data.bid || data;
|
|
2016
|
-
console.log(`\n${c.green}ANP bid published!${c.reset}\n`);
|
|
2017
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
2018
|
-
console.log(` ${c.bold}CID:${c.reset} ${result.cid || result.id}`);
|
|
2019
|
-
console.log(` ${c.bold}Listing:${c.reset} ${listingCid}`);
|
|
2020
|
-
console.log(` ${c.bold}Price:${c.reset} ${c.green}$${parseFloat(price).toFixed(2)} USDC${c.reset}`);
|
|
2021
|
-
console.log(` ${c.bold}Delivery:${c.reset} ${delivery || '24h'}`);
|
|
2022
|
-
if (message) {
|
|
2023
|
-
console.log(` ${c.bold}Message:${c.reset} ${message}`);
|
|
2024
|
-
}
|
|
2025
|
-
console.log(` ${c.bold}Signer:${c.reset} ${anp.account.address}`);
|
|
2026
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.dim}${signature.slice(0, 20)}...${c.reset}`);
|
|
2027
|
-
console.log(`\n${c.dim}The listing owner can accept with: obolos anp accept ${listingCid} --bid ${result.cid || result.id}${c.reset}\n`);
|
|
2028
|
-
}
|
|
2029
|
-
async function cmdAnpAccept(args) {
|
|
2030
|
-
const listingCid = getPositional(args, 0);
|
|
2031
|
-
if (!listingCid) {
|
|
2032
|
-
console.error(`${c.red}Usage: obolos anp accept <listing_cid> --bid <bid_cid>${c.reset}`);
|
|
2033
|
-
process.exit(1);
|
|
2034
|
-
}
|
|
2035
|
-
const bidCid = getFlag(args, 'bid');
|
|
2036
|
-
if (!bidCid) {
|
|
2037
|
-
console.error(`${c.red}--bid is required. Specify the bid CID to accept.${c.reset}`);
|
|
2038
|
-
process.exit(1);
|
|
2039
|
-
}
|
|
2040
|
-
const anp = await getANPSigningClient();
|
|
2041
|
-
// Fetch listing and bid documents
|
|
2042
|
-
console.log(`\n ${c.dim}Fetching listing and bid documents...${c.reset}`);
|
|
2043
|
-
const [listingData, bidData] = await Promise.all([
|
|
2044
|
-
apiGet(`/api/anp/objects/${encodeURIComponent(listingCid)}`),
|
|
2045
|
-
apiGet(`/api/anp/objects/${encodeURIComponent(bidCid)}`),
|
|
2046
|
-
]);
|
|
2047
|
-
const ld = listingData.data || listingData;
|
|
2048
|
-
const bd = bidData.data || bidData;
|
|
2049
|
-
// Recompute listing content hash and struct hash
|
|
2050
|
-
const listingContentHash = await computeContentHash({ title: ld.title, description: ld.description });
|
|
2051
|
-
const listingHash = anp.hashListingStruct({
|
|
2052
|
-
contentHash: listingContentHash,
|
|
2053
|
-
minBudget: BigInt(ld.minBudget || '0'),
|
|
2054
|
-
maxBudget: BigInt(ld.maxBudget || '0'),
|
|
2055
|
-
deadline: BigInt(ld.deadline || '0'),
|
|
2056
|
-
jobDuration: BigInt(ld.jobDuration || '0'),
|
|
2057
|
-
preferredEvaluator: (ld.preferredEvaluator || ZERO_ADDRESS),
|
|
2058
|
-
nonce: BigInt(ld.nonce || '0'),
|
|
2059
|
-
});
|
|
2060
|
-
// Recompute bid content hash and struct hash
|
|
2061
|
-
const bidContentHash = await computeContentHash({ message: bd.message || '', proposalCid: bd.proposalCid || '' });
|
|
2062
|
-
const bidHash = anp.hashBidStruct({
|
|
2063
|
-
listingHash: (bd.listingHash || listingHash),
|
|
2064
|
-
contentHash: bidContentHash,
|
|
2065
|
-
price: BigInt(bd.price || '0'),
|
|
2066
|
-
deliveryTime: BigInt(bd.deliveryTime || '0'),
|
|
2067
|
-
nonce: BigInt(bd.nonce || '0'),
|
|
2068
|
-
});
|
|
2069
|
-
const nonce = generateNonce();
|
|
2070
|
-
const acceptMessage = {
|
|
2071
|
-
listingHash,
|
|
2072
|
-
bidHash,
|
|
2073
|
-
nonce,
|
|
2074
|
-
};
|
|
2075
|
-
console.log(` ${c.dim}Signing AcceptIntent...${c.reset}`);
|
|
2076
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2077
|
-
account: anp.account,
|
|
2078
|
-
domain: ANP_DOMAIN,
|
|
2079
|
-
types: { AcceptIntent: ANP_TYPES.AcceptIntent },
|
|
2080
|
-
primaryType: 'AcceptIntent',
|
|
2081
|
-
message: acceptMessage,
|
|
2082
|
-
});
|
|
2083
|
-
console.log(` ${c.green}Signed.${c.reset} Publishing...`);
|
|
2084
|
-
const document = {
|
|
2085
|
-
protocol: 'anp/v1',
|
|
2086
|
-
type: 'acceptance',
|
|
2087
|
-
data: {
|
|
2088
|
-
listingCid,
|
|
2089
|
-
bidCid,
|
|
2090
|
-
listingHash,
|
|
2091
|
-
bidHash,
|
|
2092
|
-
nonce: Number(nonce),
|
|
2093
|
-
},
|
|
2094
|
-
signer: anp.account.address.toLowerCase(),
|
|
2095
|
-
signature,
|
|
2096
|
-
timestamp: Date.now(),
|
|
2097
|
-
};
|
|
2098
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2099
|
-
const result = data.accept || data;
|
|
2100
|
-
console.log(`\n${c.green}Bid accepted! ANP agreement published.${c.reset}\n`);
|
|
2101
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
2102
|
-
console.log(` ${c.bold}CID:${c.reset} ${result.cid || result.id}`);
|
|
2103
|
-
console.log(` ${c.bold}Listing:${c.reset} ${listingCid}`);
|
|
2104
|
-
console.log(` ${c.bold}Bid:${c.reset} ${bidCid}`);
|
|
2105
|
-
console.log(` ${c.bold}Signer:${c.reset} ${anp.account.address}`);
|
|
2106
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.dim}${signature.slice(0, 20)}...${c.reset}`);
|
|
2107
|
-
console.log(`\n${c.dim}The agreement is now verifiable on-chain.${c.reset}\n`);
|
|
2108
|
-
}
|
|
2109
|
-
async function cmdAnpVerify(args) {
|
|
2110
|
-
const cid = getPositional(args, 0);
|
|
2111
|
-
if (!cid) {
|
|
2112
|
-
console.error(`${c.red}Usage: obolos anp verify <cid>${c.reset}`);
|
|
2113
|
-
process.exit(1);
|
|
2114
|
-
}
|
|
2115
|
-
console.log(`\n ${c.dim}Verifying document...${c.reset}`);
|
|
2116
|
-
const data = await apiGet(`/api/anp/verify/${encodeURIComponent(cid)}`);
|
|
2117
|
-
console.log(`\n${c.bold}${c.cyan}ANP Document Verification${c.reset}`);
|
|
2118
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
2119
|
-
console.log(` ${c.bold}CID:${c.reset} ${cid}`);
|
|
2120
|
-
console.log(` ${c.bold}Type:${c.reset} ${data.type || `${c.dim}—${c.reset}`}`);
|
|
2121
|
-
console.log(` ${c.bold}Signer:${c.reset} ${data.signer || `${c.dim}—${c.reset}`}`);
|
|
2122
|
-
if (data.valid || data.verified) {
|
|
2123
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.green}Valid${c.reset}`);
|
|
2124
|
-
}
|
|
2125
|
-
else {
|
|
2126
|
-
console.log(` ${c.bold}Signature:${c.reset} ${c.red}Invalid${c.reset}`);
|
|
2127
|
-
}
|
|
2128
|
-
if (data.content_valid != null) {
|
|
2129
|
-
console.log(` ${c.bold}Content Hash:${c.reset} ${data.content_valid ? `${c.green}Matches${c.reset}` : `${c.red}Mismatch${c.reset}`}`);
|
|
2130
|
-
}
|
|
2131
|
-
if (data.chain_refs != null) {
|
|
2132
|
-
console.log(` ${c.bold}Chain Refs:${c.reset} ${data.chain_refs ? `${c.green}Valid${c.reset}` : `${c.red}Invalid${c.reset}`}`);
|
|
2133
|
-
}
|
|
2134
|
-
if (data.details) {
|
|
2135
|
-
console.log(`\n ${c.bold}Details:${c.reset}`);
|
|
2136
|
-
const details = typeof data.details === 'string' ? data.details : JSON.stringify(data.details, null, 2);
|
|
2137
|
-
for (const line of details.split('\n')) {
|
|
2138
|
-
console.log(` ${c.dim}${line}${c.reset}`);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
console.log();
|
|
2142
|
-
}
|
|
2143
|
-
// ─── IML Commands (In-Job Messaging) ────────────────────────────────────────
|
|
2144
|
-
async function cmdAnpMessage(args) {
|
|
2145
|
-
const jobId = getPositional(args, 0);
|
|
2146
|
-
const body = getFlag(args, 'message') || getFlag(args, 'body') || getFlag(args, 'm');
|
|
2147
|
-
const roleStr = getFlag(args, 'role') || 'client';
|
|
2148
|
-
if (!jobId || !body) {
|
|
2149
|
-
console.error(`${c.red}Usage: obolos anp message <job_id> --message "..." --role client|provider|evaluator${c.reset}`);
|
|
2150
|
-
process.exit(1);
|
|
2151
|
-
}
|
|
2152
|
-
const roleMap = { client: 0, provider: 1, evaluator: 2 };
|
|
2153
|
-
const role = roleMap[roleStr];
|
|
2154
|
-
if (role === undefined) {
|
|
2155
|
-
console.error(`${c.red}Invalid role. Use: client, provider, or evaluator${c.reset}`);
|
|
2156
|
-
process.exit(1);
|
|
2157
|
-
}
|
|
2158
|
-
const anp = await getANPSigningClient();
|
|
2159
|
-
const jobHash = await computeJobHash(jobId);
|
|
2160
|
-
const contentHash = await computeContentHash({ body, attachments: [] });
|
|
2161
|
-
const nonce = generateNonce();
|
|
2162
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2163
|
-
account: anp.account,
|
|
2164
|
-
domain: ANP_DOMAIN,
|
|
2165
|
-
types: { MessageIntent: ANP_TYPES.MessageIntent },
|
|
2166
|
-
primaryType: 'MessageIntent',
|
|
2167
|
-
message: { jobHash, contentHash, role, nonce },
|
|
2168
|
-
});
|
|
2169
|
-
const document = {
|
|
2170
|
-
protocol: 'anp/v1', type: 'message',
|
|
2171
|
-
data: { jobId, jobHash, body, role, nonce: Number(nonce) },
|
|
2172
|
-
signer: anp.account.address.toLowerCase(),
|
|
2173
|
-
signature, timestamp: Date.now(),
|
|
2174
|
-
};
|
|
2175
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2176
|
-
console.log(`\n${c.green}Message sent!${c.reset}\n`);
|
|
2177
|
-
console.log(` ${c.bold}CID:${c.reset} ${data.cid}`);
|
|
2178
|
-
console.log(` ${c.bold}Job:${c.reset} ${jobId}`);
|
|
2179
|
-
console.log(` ${c.bold}Role:${c.reset} ${roleStr}`);
|
|
2180
|
-
console.log(` ${c.bold}Signer:${c.reset} ${anp.account.address}\n`);
|
|
2181
|
-
}
|
|
2182
|
-
async function cmdAnpThread(args) {
|
|
2183
|
-
const jobId = getPositional(args, 0);
|
|
2184
|
-
if (!jobId) {
|
|
2185
|
-
console.error(`${c.red}Usage: obolos anp thread <job_id>${c.reset}`);
|
|
2186
|
-
process.exit(1);
|
|
2187
|
-
}
|
|
2188
|
-
const data = await apiGet(`/api/anp/jobs/${encodeURIComponent(jobId)}/thread`);
|
|
2189
|
-
const messages = data.messages || [];
|
|
2190
|
-
if (messages.length === 0) {
|
|
2191
|
-
console.log(`\n${c.yellow}No messages for job ${jobId}.${c.reset}\n`);
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
console.log(`\n${c.bold}${c.cyan}Job Thread${c.reset} ${c.dim}— ${messages.length} messages${c.reset}\n`);
|
|
2195
|
-
for (const msg of messages) {
|
|
2196
|
-
const roleColors = { client: c.blue, provider: c.green, evaluator: c.yellow };
|
|
2197
|
-
const roleColor = roleColors[msg.roleName] || c.dim;
|
|
2198
|
-
console.log(` ${roleColor}${c.bold}[${msg.roleName}]${c.reset} ${c.dim}${msg.createdAt}${c.reset}`);
|
|
2199
|
-
console.log(` ${msg.body}`);
|
|
2200
|
-
console.log(` ${c.dim}CID: ${msg.cid} | Signer: ${msg.signer}${c.reset}\n`);
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
async function cmdAnpAmend(args) {
|
|
2204
|
-
const jobId = getPositional(args, 0);
|
|
2205
|
-
const bidHash = getFlag(args, 'bid-hash');
|
|
2206
|
-
const reason = getFlag(args, 'reason');
|
|
2207
|
-
const priceStr = getFlag(args, 'price');
|
|
2208
|
-
const deliveryStr = getFlag(args, 'delivery');
|
|
2209
|
-
const scopeDelta = getFlag(args, 'scope-delta') || '';
|
|
2210
|
-
if (!jobId || !bidHash || !reason) {
|
|
2211
|
-
console.error(`${c.red}Usage: obolos anp amend <job_id> --bid-hash 0x... --reason "..." [--price 25] [--delivery 48h] [--scope-delta "..."]${c.reset}`);
|
|
2212
|
-
process.exit(1);
|
|
2213
|
-
}
|
|
2214
|
-
const anp = await getANPSigningClient();
|
|
2215
|
-
const jobHash = await computeJobHash(jobId);
|
|
2216
|
-
const newPriceUsdc = priceStr ? usdToUsdc(parseFloat(priceStr)) : '0';
|
|
2217
|
-
const newDeliveryTime = deliveryStr ? parseTimeToSeconds(deliveryStr) : 0;
|
|
2218
|
-
const contentHash = await computeContentHash({ reason, scopeDelta });
|
|
2219
|
-
const nonce = generateNonce();
|
|
2220
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2221
|
-
account: anp.account,
|
|
2222
|
-
domain: ANP_DOMAIN,
|
|
2223
|
-
types: { AmendmentIntent: ANP_TYPES.AmendmentIntent },
|
|
2224
|
-
primaryType: 'AmendmentIntent',
|
|
2225
|
-
message: {
|
|
2226
|
-
jobHash, originalBidHash: bidHash,
|
|
2227
|
-
newPrice: BigInt(newPriceUsdc), newDeliveryTime: BigInt(newDeliveryTime),
|
|
2228
|
-
contentHash, nonce,
|
|
2229
|
-
},
|
|
2230
|
-
});
|
|
2231
|
-
const document = {
|
|
2232
|
-
protocol: 'anp/v1', type: 'amendment',
|
|
2233
|
-
data: {
|
|
2234
|
-
jobId, jobHash, originalBidHash: bidHash,
|
|
2235
|
-
newPrice: newPriceUsdc, newDeliveryTime, reason, scopeDelta,
|
|
2236
|
-
nonce: Number(nonce),
|
|
2237
|
-
},
|
|
2238
|
-
signer: anp.account.address.toLowerCase(),
|
|
2239
|
-
signature, timestamp: Date.now(),
|
|
2240
|
-
};
|
|
2241
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2242
|
-
console.log(`\n${c.green}Amendment proposed!${c.reset}\n`);
|
|
2243
|
-
console.log(` ${c.bold}CID:${c.reset} ${data.cid}`);
|
|
2244
|
-
console.log(` ${c.bold}Job:${c.reset} ${jobId}`);
|
|
2245
|
-
if (priceStr)
|
|
2246
|
-
console.log(` ${c.bold}New Price:${c.reset} $${priceStr} USDC`);
|
|
2247
|
-
if (deliveryStr)
|
|
2248
|
-
console.log(` ${c.bold}New Delivery:${c.reset} ${deliveryStr}`);
|
|
2249
|
-
console.log(` ${c.bold}Reason:${c.reset} ${reason}`);
|
|
2250
|
-
console.log(`\n${c.dim}Counterparty must accept with: obolos anp accept-amend ${jobId} --amendment ${data.cid}${c.reset}\n`);
|
|
2251
|
-
}
|
|
2252
|
-
async function cmdAnpAcceptAmend(args) {
|
|
2253
|
-
const jobId = getPositional(args, 0);
|
|
2254
|
-
const amendmentCid = getFlag(args, 'amendment');
|
|
2255
|
-
if (!jobId || !amendmentCid) {
|
|
2256
|
-
console.error(`${c.red}Usage: obolos anp accept-amend <job_id> --amendment <amendment_cid>${c.reset}`);
|
|
2257
|
-
process.exit(1);
|
|
2258
|
-
}
|
|
2259
|
-
const anp = await getANPSigningClient();
|
|
2260
|
-
// Fetch amendment to compute struct hash
|
|
2261
|
-
const amendDoc = await apiGet(`/api/anp/objects/${encodeURIComponent(amendmentCid)}`);
|
|
2262
|
-
const ad = amendDoc.data || amendDoc;
|
|
2263
|
-
const contentHash = await computeContentHash({ reason: ad.reason, scopeDelta: ad.scopeDelta || '' });
|
|
2264
|
-
const amendmentHash = anp.hashAmendmentStruct({
|
|
2265
|
-
jobHash: ad.jobHash,
|
|
2266
|
-
originalBidHash: ad.originalBidHash,
|
|
2267
|
-
newPrice: BigInt(ad.newPrice),
|
|
2268
|
-
newDeliveryTime: BigInt(ad.newDeliveryTime),
|
|
2269
|
-
contentHash,
|
|
2270
|
-
nonce: BigInt(ad.nonce),
|
|
2271
|
-
});
|
|
2272
|
-
const nonce = generateNonce();
|
|
2273
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2274
|
-
account: anp.account,
|
|
2275
|
-
domain: ANP_DOMAIN,
|
|
2276
|
-
types: { AmendmentAcceptance: ANP_TYPES.AmendmentAcceptance },
|
|
2277
|
-
primaryType: 'AmendmentAcceptance',
|
|
2278
|
-
message: { amendmentHash, nonce },
|
|
2279
|
-
});
|
|
2280
|
-
const document = {
|
|
2281
|
-
protocol: 'anp/v1', type: 'amendment_acceptance',
|
|
2282
|
-
data: { jobId, amendmentCid, amendmentHash, nonce: Number(nonce) },
|
|
2283
|
-
signer: anp.account.address.toLowerCase(),
|
|
2284
|
-
signature, timestamp: Date.now(),
|
|
2285
|
-
};
|
|
2286
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2287
|
-
console.log(`\n${c.green}Amendment accepted!${c.reset}\n`);
|
|
2288
|
-
console.log(` ${c.bold}CID:${c.reset} ${data.cid}`);
|
|
2289
|
-
console.log(` ${c.bold}Amendment CID:${c.reset} ${amendmentCid}`);
|
|
2290
|
-
console.log(` ${c.bold}Job:${c.reset} ${jobId}\n`);
|
|
2291
|
-
}
|
|
2292
|
-
async function cmdAnpCheckpoint(args) {
|
|
2293
|
-
const jobId = getPositional(args, 0);
|
|
2294
|
-
const milestoneStr = getFlag(args, 'milestone') || '0';
|
|
2295
|
-
const deliverable = getFlag(args, 'deliverable');
|
|
2296
|
-
const notes = getFlag(args, 'notes') || '';
|
|
2297
|
-
if (!jobId || !deliverable) {
|
|
2298
|
-
console.error(`${c.red}Usage: obolos anp checkpoint <job_id> --deliverable "..." [--milestone 0] [--notes "..."]${c.reset}`);
|
|
2299
|
-
process.exit(1);
|
|
2300
|
-
}
|
|
2301
|
-
const milestoneIndex = parseInt(milestoneStr, 10);
|
|
2302
|
-
const anp = await getANPSigningClient();
|
|
2303
|
-
const jobHash = await computeJobHash(jobId);
|
|
2304
|
-
const contentHash = await computeContentHash({ deliverable, notes });
|
|
2305
|
-
const nonce = generateNonce();
|
|
2306
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2307
|
-
account: anp.account,
|
|
2308
|
-
domain: ANP_DOMAIN,
|
|
2309
|
-
types: { CheckpointIntent: ANP_TYPES.CheckpointIntent },
|
|
2310
|
-
primaryType: 'CheckpointIntent',
|
|
2311
|
-
message: { jobHash, milestoneIndex, contentHash, nonce },
|
|
2312
|
-
});
|
|
2313
|
-
const document = {
|
|
2314
|
-
protocol: 'anp/v1', type: 'checkpoint',
|
|
2315
|
-
data: { jobId, jobHash, milestoneIndex, deliverable, notes, nonce: Number(nonce) },
|
|
2316
|
-
signer: anp.account.address.toLowerCase(),
|
|
2317
|
-
signature, timestamp: Date.now(),
|
|
2318
|
-
};
|
|
2319
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2320
|
-
console.log(`\n${c.green}Checkpoint submitted!${c.reset}\n`);
|
|
2321
|
-
console.log(` ${c.bold}CID:${c.reset} ${data.cid}`);
|
|
2322
|
-
console.log(` ${c.bold}Job:${c.reset} ${jobId}`);
|
|
2323
|
-
console.log(` ${c.bold}Milestone:${c.reset} #${milestoneIndex}`);
|
|
2324
|
-
console.log(`\n${c.dim}Approve with: obolos anp approve-cp ${jobId} --checkpoint ${data.cid}${c.reset}\n`);
|
|
2325
|
-
}
|
|
2326
|
-
async function cmdAnpApproveCp(args) {
|
|
2327
|
-
const jobId = getPositional(args, 0);
|
|
2328
|
-
const checkpointCid = getFlag(args, 'checkpoint');
|
|
2329
|
-
if (!jobId || !checkpointCid) {
|
|
2330
|
-
console.error(`${c.red}Usage: obolos anp approve-cp <job_id> --checkpoint <checkpoint_cid>${c.reset}`);
|
|
2331
|
-
process.exit(1);
|
|
2332
|
-
}
|
|
2333
|
-
const anp = await getANPSigningClient();
|
|
2334
|
-
const cpDoc = await apiGet(`/api/anp/objects/${encodeURIComponent(checkpointCid)}`);
|
|
2335
|
-
const cd = cpDoc.data || cpDoc;
|
|
2336
|
-
const contentHash = await computeContentHash({ deliverable: cd.deliverable, notes: cd.notes || '' });
|
|
2337
|
-
const checkpointHash = anp.hashCheckpointStruct({
|
|
2338
|
-
jobHash: cd.jobHash,
|
|
2339
|
-
milestoneIndex: cd.milestoneIndex,
|
|
2340
|
-
contentHash,
|
|
2341
|
-
nonce: BigInt(cd.nonce),
|
|
2342
|
-
});
|
|
2343
|
-
const nonce = generateNonce();
|
|
2344
|
-
const signature = await anp.walletClient.signTypedData({
|
|
2345
|
-
account: anp.account,
|
|
2346
|
-
domain: ANP_DOMAIN,
|
|
2347
|
-
types: { CheckpointApproval: ANP_TYPES.CheckpointApproval },
|
|
2348
|
-
primaryType: 'CheckpointApproval',
|
|
2349
|
-
message: { checkpointHash, nonce },
|
|
2350
|
-
});
|
|
2351
|
-
const document = {
|
|
2352
|
-
protocol: 'anp/v1', type: 'checkpoint_approval',
|
|
2353
|
-
data: { jobId, checkpointCid, checkpointHash, nonce: Number(nonce) },
|
|
2354
|
-
signer: anp.account.address.toLowerCase(),
|
|
2355
|
-
signature, timestamp: Date.now(),
|
|
2356
|
-
};
|
|
2357
|
-
const data = await apiPost('/api/anp/publish', document);
|
|
2358
|
-
console.log(`\n${c.green}Checkpoint approved!${c.reset}\n`);
|
|
2359
|
-
console.log(` ${c.bold}CID:${c.reset} ${data.cid}`);
|
|
2360
|
-
console.log(` ${c.bold}Checkpoint CID:${c.reset} ${checkpointCid}`);
|
|
2361
|
-
console.log(` ${c.bold}Job:${c.reset} ${jobId}\n`);
|
|
2362
|
-
}
|
|
2363
|
-
async function cmdAnpAmendments(args) {
|
|
2364
|
-
const jobId = getPositional(args, 0);
|
|
2365
|
-
if (!jobId) {
|
|
2366
|
-
console.error(`${c.red}Usage: obolos anp amendments <job_id>${c.reset}`);
|
|
2367
|
-
process.exit(1);
|
|
2368
|
-
}
|
|
2369
|
-
const data = await apiGet(`/api/anp/jobs/${encodeURIComponent(jobId)}/amendments`);
|
|
2370
|
-
const amendments = data.amendments || [];
|
|
2371
|
-
if (amendments.length === 0) {
|
|
2372
|
-
console.log(`\n${c.yellow}No amendments for job ${jobId}.${c.reset}\n`);
|
|
2373
|
-
return;
|
|
2374
|
-
}
|
|
2375
|
-
console.log(`\n${c.bold}${c.cyan}Amendments${c.reset} ${c.dim}— ${amendments.length} total${c.reset}\n`);
|
|
2376
|
-
for (const a of amendments) {
|
|
2377
|
-
const status = a.accepted ? `${c.green}Accepted${c.reset}` : `${c.yellow}Pending${c.reset}`;
|
|
2378
|
-
console.log(` ${c.bold}CID:${c.reset} ${a.cid}`);
|
|
2379
|
-
console.log(` ${c.bold}Status:${c.reset} ${status}`);
|
|
2380
|
-
if (a.newPrice && a.newPrice !== '0')
|
|
2381
|
-
console.log(` ${c.bold}Price:${c.reset} $${(Number(a.newPrice) / 1_000_000).toFixed(2)} USDC`);
|
|
2382
|
-
if (a.newDeliveryTime)
|
|
2383
|
-
console.log(` ${c.bold}Delivery:${c.reset} ${Math.round(a.newDeliveryTime / 3600)}h`);
|
|
2384
|
-
console.log(` ${c.bold}Reason:${c.reset} ${a.reason}`);
|
|
2385
|
-
console.log(` ${c.bold}Signer:${c.reset} ${a.signer} ${c.dim}${a.createdAt}${c.reset}\n`);
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
async function cmdAnpCheckpoints(args) {
|
|
2389
|
-
const jobId = getPositional(args, 0);
|
|
2390
|
-
if (!jobId) {
|
|
2391
|
-
console.error(`${c.red}Usage: obolos anp checkpoints <job_id>${c.reset}`);
|
|
2392
|
-
process.exit(1);
|
|
2393
|
-
}
|
|
2394
|
-
const data = await apiGet(`/api/anp/jobs/${encodeURIComponent(jobId)}/checkpoints`);
|
|
2395
|
-
const checkpoints = data.checkpoints || [];
|
|
2396
|
-
if (checkpoints.length === 0) {
|
|
2397
|
-
console.log(`\n${c.yellow}No checkpoints for job ${jobId}.${c.reset}\n`);
|
|
2398
|
-
return;
|
|
2399
|
-
}
|
|
2400
|
-
console.log(`\n${c.bold}${c.cyan}Checkpoints${c.reset} ${c.dim}— ${checkpoints.length} total${c.reset}\n`);
|
|
2401
|
-
for (const cp of checkpoints) {
|
|
2402
|
-
const status = cp.approved ? `${c.green}Approved${c.reset}` : `${c.yellow}Pending${c.reset}`;
|
|
2403
|
-
console.log(` ${c.bold}#${cp.milestoneIndex}${c.reset} ${status} ${c.dim}${cp.createdAt}${c.reset}`);
|
|
2404
|
-
console.log(` ${c.bold}CID:${c.reset} ${cp.cid}`);
|
|
2405
|
-
console.log(` ${c.bold}Deliverable:${c.reset} ${cp.deliverable}`);
|
|
2406
|
-
if (cp.notes)
|
|
2407
|
-
console.log(` ${c.bold}Notes:${c.reset} ${cp.notes}`);
|
|
2408
|
-
console.log(` ${c.bold}Signer:${c.reset} ${cp.signer}\n`);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
function showAnpHelp() {
|
|
2412
|
-
console.log(`
|
|
2413
|
-
${c.bold}${c.cyan}obolos anp${c.reset} — Agent Negotiation Protocol (EIP-712 signed documents)
|
|
2414
|
-
|
|
2415
|
-
${c.bold}Usage:${c.reset}
|
|
2416
|
-
obolos anp list [options] Browse ANP listings
|
|
2417
|
-
obolos anp info <cid> Get listing details with bids
|
|
2418
|
-
obolos anp create [options] Sign and publish a listing
|
|
2419
|
-
obolos anp bid <cid> [options] Sign and publish a bid
|
|
2420
|
-
obolos anp accept <cid> [options] Accept a bid (sign AcceptIntent)
|
|
2421
|
-
obolos anp verify <cid> Verify document integrity
|
|
2422
|
-
|
|
2423
|
-
${c.bold}List Options:${c.reset}
|
|
2424
|
-
--status=open|negotiating|accepted Filter by status
|
|
2425
|
-
--limit=20 Max results (default: 20)
|
|
2426
|
-
|
|
2427
|
-
${c.bold}Create Options:${c.reset}
|
|
2428
|
-
--title "..." Listing title (required)
|
|
2429
|
-
--description "..." Detailed description
|
|
2430
|
-
--min-budget 5 Minimum budget in USDC
|
|
2431
|
-
--max-budget 50 Maximum budget in USDC
|
|
2432
|
-
--deadline 7d Bidding deadline (e.g., "24h", "7d")
|
|
2433
|
-
--duration 3d Expected job duration (e.g., "48h", "3d")
|
|
2434
|
-
--evaluator 0x... Preferred evaluator address
|
|
2435
|
-
|
|
2436
|
-
${c.bold}Bid Options:${c.reset}
|
|
2437
|
-
--price 25 Your proposed price in USDC (required)
|
|
2438
|
-
--delivery 48h Estimated delivery time (e.g., "24h", "3d")
|
|
2439
|
-
--message "I can do this" Message to the client
|
|
2440
|
-
|
|
2441
|
-
${c.bold}Accept Options:${c.reset}
|
|
2442
|
-
--bid <bid_cid> Bid CID to accept (required)
|
|
2443
|
-
|
|
2444
|
-
${c.bold}In-Job Messaging (IML):${c.reset}
|
|
2445
|
-
obolos anp message <job_id> [options] Send signed message on a running job
|
|
2446
|
-
obolos anp thread <job_id> View message thread for a job
|
|
2447
|
-
obolos anp amend <job_id> [options] Propose scope/price amendment
|
|
2448
|
-
obolos anp accept-amend <job_id> [opts] Accept a pending amendment
|
|
2449
|
-
obolos anp amendments <job_id> List amendments for a job
|
|
2450
|
-
obolos anp checkpoint <job_id> [options] Submit milestone checkpoint
|
|
2451
|
-
obolos anp approve-cp <job_id> [options] Approve a checkpoint
|
|
2452
|
-
obolos anp checkpoints <job_id> List checkpoints for a job
|
|
2453
|
-
|
|
2454
|
-
${c.bold}Message Options:${c.reset}
|
|
2455
|
-
--message "..." Message body (required)
|
|
2456
|
-
--role client|provider|evaluator Your role (default: client)
|
|
53
|
+
function showGroupHelp(group) {
|
|
54
|
+
const subcommands = registry.all().filter(c => c.name.startsWith(`${group}.`));
|
|
55
|
+
if (subcommands.length === 0)
|
|
56
|
+
return false;
|
|
57
|
+
process.stdout.write(`
|
|
58
|
+
${c.bold}${c.cyan}obolos ${group}${c.reset}
|
|
2457
59
|
|
|
2458
|
-
${c.bold}
|
|
2459
|
-
|
|
2460
|
-
--reason "..." Reason for amendment (required)
|
|
2461
|
-
--price 25 New price in USDC (optional)
|
|
2462
|
-
--delivery 48h New delivery time (optional)
|
|
2463
|
-
--scope-delta "..." Scope change description (optional)
|
|
2464
|
-
|
|
2465
|
-
${c.bold}Checkpoint Options:${c.reset}
|
|
2466
|
-
--deliverable "..." Deliverable content/URL (required)
|
|
2467
|
-
--milestone 0 Milestone index (default: 0)
|
|
2468
|
-
--notes "..." Additional notes (optional)
|
|
60
|
+
${c.bold}Subcommands:${c.reset}
|
|
61
|
+
${subcommands.map(c => ` ${c.name.slice(group.length + 1).padEnd(16)} ${c.summary}`).join('\n')}
|
|
2469
62
|
|
|
2470
|
-
${c.
|
|
2471
|
-
obolos anp list --status=open
|
|
2472
|
-
obolos anp create --title "Analyze dataset" --description "Parse CSV" --min-budget 5 --max-budget 50 --deadline 7d --duration 3d
|
|
2473
|
-
obolos anp bid sha256-abc123... --price 25 --delivery 48h --message "I can do this"
|
|
2474
|
-
obolos anp accept sha256-listing... --bid sha256-bid...
|
|
2475
|
-
obolos anp message job-uuid --message "Landscape or portrait?" --role provider
|
|
2476
|
-
obolos anp thread job-uuid
|
|
2477
|
-
obolos anp amend job-uuid --bid-hash 0x... --reason "Scope expanded" --price 35
|
|
2478
|
-
obolos anp checkpoint job-uuid --deliverable "https://..." --milestone 0 --notes "Script draft"
|
|
63
|
+
Run ${c.cyan}obolos ${group} <subcommand> --help${c.reset} for subcommand details.
|
|
2479
64
|
`);
|
|
65
|
+
return true;
|
|
2480
66
|
}
|
|
2481
|
-
async function
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
case 'list':
|
|
2486
|
-
case 'ls':
|
|
2487
|
-
await cmdAnpList(subArgs);
|
|
2488
|
-
break;
|
|
2489
|
-
case 'info':
|
|
2490
|
-
case 'show':
|
|
2491
|
-
await cmdAnpInfo(subArgs);
|
|
2492
|
-
break;
|
|
2493
|
-
case 'create':
|
|
2494
|
-
case 'new':
|
|
2495
|
-
await cmdAnpCreate(subArgs);
|
|
2496
|
-
break;
|
|
2497
|
-
case 'bid':
|
|
2498
|
-
await cmdAnpBid(subArgs);
|
|
2499
|
-
break;
|
|
2500
|
-
case 'accept':
|
|
2501
|
-
await cmdAnpAccept(subArgs);
|
|
2502
|
-
break;
|
|
2503
|
-
case 'verify':
|
|
2504
|
-
await cmdAnpVerify(subArgs);
|
|
2505
|
-
break;
|
|
2506
|
-
// IML commands
|
|
2507
|
-
case 'message':
|
|
2508
|
-
case 'msg':
|
|
2509
|
-
await cmdAnpMessage(subArgs);
|
|
2510
|
-
break;
|
|
2511
|
-
case 'thread':
|
|
2512
|
-
await cmdAnpThread(subArgs);
|
|
2513
|
-
break;
|
|
2514
|
-
case 'amend':
|
|
2515
|
-
await cmdAnpAmend(subArgs);
|
|
2516
|
-
break;
|
|
2517
|
-
case 'accept-amend':
|
|
2518
|
-
await cmdAnpAcceptAmend(subArgs);
|
|
2519
|
-
break;
|
|
2520
|
-
case 'amendments':
|
|
2521
|
-
await cmdAnpAmendments(subArgs);
|
|
2522
|
-
break;
|
|
2523
|
-
case 'checkpoint':
|
|
2524
|
-
case 'cp':
|
|
2525
|
-
await cmdAnpCheckpoint(subArgs);
|
|
2526
|
-
break;
|
|
2527
|
-
case 'approve-cp':
|
|
2528
|
-
await cmdAnpApproveCp(subArgs);
|
|
2529
|
-
break;
|
|
2530
|
-
case 'checkpoints':
|
|
2531
|
-
case 'cps':
|
|
2532
|
-
await cmdAnpCheckpoints(subArgs);
|
|
2533
|
-
break;
|
|
2534
|
-
case 'help':
|
|
2535
|
-
case '--help':
|
|
2536
|
-
case '-h':
|
|
2537
|
-
case undefined:
|
|
2538
|
-
showAnpHelp();
|
|
2539
|
-
break;
|
|
2540
|
-
default:
|
|
2541
|
-
console.error(`${c.red}Unknown anp subcommand: ${sub}${c.reset}`);
|
|
2542
|
-
showAnpHelp();
|
|
2543
|
-
process.exit(1);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
// ─── Reputation Commands ─────────────────────────────────────────────────────
|
|
2547
|
-
function tierColor(tier) {
|
|
2548
|
-
switch (tier.toLowerCase()) {
|
|
2549
|
-
case 'diamond': return `${c.bold}${c.cyan}${tier}${c.reset}`;
|
|
2550
|
-
case 'platinum': return `${c.bold}${c.white}${tier}${c.reset}`;
|
|
2551
|
-
case 'gold':
|
|
2552
|
-
case 'established': return `${c.yellow}${tier}${c.reset}`;
|
|
2553
|
-
case 'silver':
|
|
2554
|
-
case 'developing': return `${c.dim}${c.white}${tier}${c.reset}`;
|
|
2555
|
-
case 'bronze':
|
|
2556
|
-
case 'limited': return `${c.dim}${tier}${c.reset}`;
|
|
2557
|
-
case 'flagged': return `${c.red}${tier}${c.reset}`;
|
|
2558
|
-
default: return `${c.dim}${tier}${c.reset}`;
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
function scoreBar(score) {
|
|
2562
|
-
const width = 20;
|
|
2563
|
-
const filled = Math.round((score / 100) * width);
|
|
2564
|
-
const empty = width - filled;
|
|
2565
|
-
let color = c.red;
|
|
2566
|
-
if (score >= 70)
|
|
2567
|
-
color = c.green;
|
|
2568
|
-
else if (score >= 40)
|
|
2569
|
-
color = c.yellow;
|
|
2570
|
-
return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset} ${color}${score}${c.reset}/100`;
|
|
2571
|
-
}
|
|
2572
|
-
function verdictLabel(pass) {
|
|
2573
|
-
return pass
|
|
2574
|
-
? `${c.green}✔ PASS${c.reset}`
|
|
2575
|
-
: `${c.red}✘ FAIL${c.reset}`;
|
|
2576
|
-
}
|
|
2577
|
-
async function cmdReputationCheck(args) {
|
|
2578
|
-
const agentId = getPositional(args, 0);
|
|
2579
|
-
if (!agentId) {
|
|
2580
|
-
console.error(`${c.red}Usage: obolos reputation check <agentId> [--chain base]${c.reset}`);
|
|
2581
|
-
process.exit(1);
|
|
2582
|
-
}
|
|
2583
|
-
const chain = getFlag(args, 'chain') || 'base';
|
|
2584
|
-
console.log(`\n${c.dim}Checking reputation for agent ${c.bold}${agentId}${c.reset}${c.dim} on ${chain}...${c.reset}\n`);
|
|
2585
|
-
const data = await apiGet(`/api/anp/reputation/${encodeURIComponent(agentId)}?chain=${encodeURIComponent(chain)}`);
|
|
2586
|
-
// Header
|
|
2587
|
-
console.log(`${c.bold}${c.cyan}Reputation Report${c.reset} ${c.dim}Agent ${agentId}${c.reset}`);
|
|
2588
|
-
console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
|
|
2589
|
-
// Combined score
|
|
2590
|
-
const combined = data.combined || {};
|
|
2591
|
-
console.log(` ${c.bold}Combined Score:${c.reset} ${scoreBar(combined.score ?? 0)}`);
|
|
2592
|
-
console.log(` ${c.bold}Tier:${c.reset} ${tierColor(combined.tier ?? 'unknown')}`);
|
|
2593
|
-
console.log(` ${c.bold}Verdict:${c.reset} ${verdictLabel(combined.pass ?? false)}`);
|
|
2594
|
-
console.log(` ${c.bold}Chain:${c.reset} ${data.chain || chain}`);
|
|
2595
|
-
if (data.address) {
|
|
2596
|
-
console.log(` ${c.bold}Address:${c.reset} ${data.address}`);
|
|
2597
|
-
}
|
|
2598
|
-
// Sybil warning
|
|
2599
|
-
if (combined.hasSybilFlags) {
|
|
2600
|
-
console.log(`\n ${c.red}${c.bold}⚠ Sybil flags detected${c.reset}`);
|
|
2601
|
-
}
|
|
2602
|
-
// Individual provider scores
|
|
2603
|
-
const scores = data.scores || [];
|
|
2604
|
-
if (scores.length > 0) {
|
|
2605
|
-
console.log(`\n ${c.bold}${c.cyan}Provider Scores (${scores.length})${c.reset}`);
|
|
2606
|
-
console.log(` ${c.dim}${'─'.repeat(56)}${c.reset}`);
|
|
2607
|
-
for (const s of scores) {
|
|
2608
|
-
const provider = s.provider === 'rnwy' ? 'RNWY' : s.provider === 'agentproof' ? 'AgentProof' : s.provider;
|
|
2609
|
-
console.log(`\n ${c.bold}${provider}${c.reset}`);
|
|
2610
|
-
console.log(` Score: ${scoreBar(s.score ?? 0)}`);
|
|
2611
|
-
console.log(` Tier: ${tierColor(s.tier ?? 'unknown')}`);
|
|
2612
|
-
console.log(` Verdict: ${verdictLabel(s.pass ?? false)}`);
|
|
2613
|
-
if (s.sybilFlags && s.sybilFlags.length > 0) {
|
|
2614
|
-
console.log(` ${c.red}Sybil: ${s.sybilFlags.join(', ')}${c.reset}`);
|
|
2615
|
-
}
|
|
2616
|
-
if (s.riskFlags && s.riskFlags.length > 0) {
|
|
2617
|
-
console.log(` ${c.yellow}Risk: ${s.riskFlags.join(', ')}${c.reset}`);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
else {
|
|
2622
|
-
console.log(`\n ${c.dim}No provider scores available.${c.reset}`);
|
|
2623
|
-
}
|
|
2624
|
-
console.log(`\n ${c.dim}Checked: ${data.checkedAt ? formatDate(data.checkedAt) : 'just now'}${c.reset}\n`);
|
|
2625
|
-
}
|
|
2626
|
-
async function cmdReputationCompare(args) {
|
|
2627
|
-
// Collect all positional args (agent IDs, optionally prefixed with chain:)
|
|
2628
|
-
const agents = [];
|
|
2629
|
-
for (const arg of args) {
|
|
2630
|
-
if (arg.startsWith('--'))
|
|
2631
|
-
continue;
|
|
2632
|
-
const parts = arg.split(':');
|
|
2633
|
-
if (parts.length === 2) {
|
|
2634
|
-
const id = parseInt(parts[1], 10);
|
|
2635
|
-
if (isNaN(id)) {
|
|
2636
|
-
console.error(`${c.red}Invalid agent ID: ${parts[1]}${c.reset}`);
|
|
2637
|
-
process.exit(1);
|
|
2638
|
-
}
|
|
2639
|
-
agents.push({ agentId: id, chain: parts[0] });
|
|
2640
|
-
}
|
|
2641
|
-
else {
|
|
2642
|
-
const id = parseInt(parts[0], 10);
|
|
2643
|
-
if (isNaN(id)) {
|
|
2644
|
-
console.error(`${c.red}Invalid agent ID: ${parts[0]}${c.reset}`);
|
|
2645
|
-
process.exit(1);
|
|
2646
|
-
}
|
|
2647
|
-
agents.push({ agentId: id, chain: 'base' });
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
if (agents.length < 2) {
|
|
2651
|
-
console.error(`${c.red}Usage: obolos reputation compare <id1> <id2> [id3...]${c.reset}`);
|
|
2652
|
-
console.error(`${c.dim} Prefix with chain: obolos rep compare base:123 ethereum:456${c.reset}`);
|
|
2653
|
-
process.exit(1);
|
|
67
|
+
async function main() {
|
|
68
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
69
|
+
showHelp();
|
|
70
|
+
return;
|
|
2654
71
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
.catch((err) => ({ agentId: a.agentId, chain: a.chain, error: err.message }))));
|
|
2659
|
-
// Sort by combined score descending
|
|
2660
|
-
const sorted = results
|
|
2661
|
-
.map((r, i) => ({ ...r, _input: agents[i] }))
|
|
2662
|
-
.sort((a, b) => ((b.combined?.score ?? -1) - (a.combined?.score ?? -1)));
|
|
2663
|
-
// Table header
|
|
2664
|
-
console.log(`${c.bold}${c.cyan}Reputation Comparison${c.reset}`);
|
|
2665
|
-
console.log(`${c.dim}${'─'.repeat(74)}${c.reset}`);
|
|
2666
|
-
console.log(` ${c.bold}${'#'.padEnd(4)}${'Agent'.padEnd(12)}${'Chain'.padEnd(12)}${'Score'.padEnd(24)}${'Tier'.padEnd(14)}Verdict${c.reset}`);
|
|
2667
|
-
console.log(` ${c.dim}${'─'.repeat(70)}${c.reset}`);
|
|
2668
|
-
sorted.forEach((r, i) => {
|
|
2669
|
-
const rank = `${i + 1}.`.padEnd(4);
|
|
2670
|
-
const agent = String(r._input.agentId).padEnd(12);
|
|
2671
|
-
const chain = r._input.chain.padEnd(12);
|
|
2672
|
-
if (r.error) {
|
|
2673
|
-
console.log(` ${rank}${agent}${chain}${c.red}Error: ${r.error}${c.reset}`);
|
|
72
|
+
// Group help: `obolos job --help` / `obolos anp --help`.
|
|
73
|
+
if (commandArgs.length === 0 || commandArgs[0] === '--help' || commandArgs[0] === '-h') {
|
|
74
|
+
if (showGroupHelp(command))
|
|
2674
75
|
return;
|
|
2675
|
-
}
|
|
2676
|
-
const combined = r.combined || {};
|
|
2677
|
-
const score = combined.score ?? 0;
|
|
2678
|
-
const bar = scoreBar(score);
|
|
2679
|
-
// scoreBar has ANSI codes so we can't pad it normally; we pad the raw number
|
|
2680
|
-
const barPadded = bar; // already formatted
|
|
2681
|
-
const tier = tierColor(combined.tier ?? 'unknown');
|
|
2682
|
-
const verdict = verdictLabel(combined.pass ?? false);
|
|
2683
|
-
const sybil = combined.hasSybilFlags ? ` ${c.red}⚠ sybil${c.reset}` : '';
|
|
2684
|
-
console.log(` ${rank}${agent}${chain}${barPadded} ${tier.padEnd(14)} ${verdict}${sybil}`);
|
|
2685
|
-
});
|
|
2686
|
-
console.log(`${c.dim}${'─'.repeat(74)}${c.reset}\n`);
|
|
2687
|
-
}
|
|
2688
|
-
function showReputationHelp() {
|
|
2689
|
-
console.log(`
|
|
2690
|
-
${c.bold}${c.cyan}obolos reputation${c.reset} — Agent trust & reputation checking
|
|
2691
|
-
|
|
2692
|
-
${c.bold}Subcommands:${c.reset}
|
|
2693
|
-
check <agentId> Check reputation for an agent
|
|
2694
|
-
compare <id1> <id2> [...] Compare multiple agents side-by-side
|
|
2695
|
-
|
|
2696
|
-
${c.bold}Options (check):${c.reset}
|
|
2697
|
-
--chain <chain> Blockchain to check (default: base)
|
|
2698
|
-
|
|
2699
|
-
${c.bold}Examples:${c.reset}
|
|
2700
|
-
obolos reputation check 16907
|
|
2701
|
-
obolos reputation check 16907 --chain ethereum
|
|
2702
|
-
obolos rep check 16907
|
|
2703
|
-
obolos reputation compare 123 456 789
|
|
2704
|
-
obolos rep compare base:123 base:456 ethereum:789
|
|
2705
|
-
`);
|
|
2706
|
-
}
|
|
2707
|
-
async function cmdReputation(args) {
|
|
2708
|
-
const sub = args[0];
|
|
2709
|
-
const subArgs = args.slice(1);
|
|
2710
|
-
switch (sub) {
|
|
2711
|
-
case 'check':
|
|
2712
|
-
await cmdReputationCheck(subArgs);
|
|
2713
|
-
break;
|
|
2714
|
-
case 'compare':
|
|
2715
|
-
case 'cmp':
|
|
2716
|
-
await cmdReputationCompare(subArgs);
|
|
2717
|
-
break;
|
|
2718
|
-
case 'help':
|
|
2719
|
-
case '--help':
|
|
2720
|
-
case '-h':
|
|
2721
|
-
case undefined:
|
|
2722
|
-
showReputationHelp();
|
|
2723
|
-
break;
|
|
2724
|
-
default:
|
|
2725
|
-
console.error(`${c.red}Unknown reputation subcommand: ${sub}${c.reset}`);
|
|
2726
|
-
showReputationHelp();
|
|
2727
|
-
process.exit(1);
|
|
2728
76
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
${c.bold}Usage:${c.reset}
|
|
2736
|
-
obolos search [query] Search APIs by keyword
|
|
2737
|
-
obolos categories List all API categories
|
|
2738
|
-
obolos info <id> Get full API details
|
|
2739
|
-
obolos call <id> [options] Call an API with payment
|
|
2740
|
-
obolos balance Check wallet USDC balance
|
|
2741
|
-
obolos setup Configure wallet (interactive)
|
|
2742
|
-
obolos setup --generate Generate a new wallet
|
|
2743
|
-
obolos setup --show Show current wallet config
|
|
2744
|
-
obolos setup-mcp Show MCP server setup instructions
|
|
2745
|
-
|
|
2746
|
-
${c.bold}Job Commands (ERC-8183 ACP):${c.reset}
|
|
2747
|
-
obolos job list [options] List jobs with filters
|
|
2748
|
-
obolos job create [options] Create a new job
|
|
2749
|
-
obolos job info <id> Get full job details
|
|
2750
|
-
obolos job fund <id> Fund a job's escrow
|
|
2751
|
-
obolos job submit <id> [opts] Submit work for a job
|
|
2752
|
-
obolos job complete <id> Approve a job (evaluator)
|
|
2753
|
-
obolos job reject <id> Reject a job submission
|
|
2754
|
-
obolos job help Show job command help
|
|
2755
|
-
|
|
2756
|
-
${c.bold}Listing Commands (Negotiation):${c.reset}
|
|
2757
|
-
obolos listing list [options] Browse open job listings
|
|
2758
|
-
obolos listing create [opts] Create a listing for bids
|
|
2759
|
-
obolos listing info <id> Get listing details + bids
|
|
2760
|
-
obolos listing bid <id> [opts] Submit a bid on a listing
|
|
2761
|
-
obolos listing accept <id> Accept a bid (creates job)
|
|
2762
|
-
obolos listing cancel <id> Cancel a listing
|
|
2763
|
-
obolos listing help Show listing command help
|
|
2764
|
-
|
|
2765
|
-
${c.bold}ANP Commands (Agent Negotiation Protocol):${c.reset}
|
|
2766
|
-
obolos anp list [options] Browse ANP listings
|
|
2767
|
-
obolos anp info <cid> Get listing details + bids
|
|
2768
|
-
obolos anp create [options] Sign and publish a listing
|
|
2769
|
-
obolos anp bid <cid> [opts] Sign and publish a bid
|
|
2770
|
-
obolos anp accept <cid> [opts] Accept a bid (sign AcceptIntent)
|
|
2771
|
-
obolos anp verify <cid> Verify document integrity
|
|
2772
|
-
obolos anp message <job> [opts] Send in-job message
|
|
2773
|
-
obolos anp thread <job> View job message thread
|
|
2774
|
-
obolos anp amend <job> [opts] Propose amendment
|
|
2775
|
-
obolos anp checkpoint <job> Submit milestone checkpoint
|
|
2776
|
-
obolos anp help Show ANP command help
|
|
2777
|
-
|
|
2778
|
-
${c.bold}Reputation Commands:${c.reset}
|
|
2779
|
-
obolos reputation check <id> Check agent trust score
|
|
2780
|
-
obolos reputation compare ... Compare multiple agents
|
|
2781
|
-
obolos reputation help Show reputation command help
|
|
2782
|
-
${c.dim}(alias: obolos rep ...)${c.reset}
|
|
2783
|
-
|
|
2784
|
-
${c.bold}Call Options:${c.reset}
|
|
2785
|
-
--method POST|GET|PUT HTTP method (default: GET)
|
|
2786
|
-
--body '{"key":"value"}' Request body (JSON)
|
|
2787
|
-
|
|
2788
|
-
${c.bold}Config:${c.reset}
|
|
2789
|
-
Wallet key is loaded from ~/.obolos/config.json or OBOLOS_PRIVATE_KEY env var.
|
|
2790
|
-
Run ${c.cyan}obolos setup${c.reset} to configure.
|
|
2791
|
-
|
|
2792
|
-
${c.bold}Examples:${c.reset}
|
|
2793
|
-
obolos setup --generate
|
|
2794
|
-
obolos search "token price"
|
|
2795
|
-
obolos info a59a0377-d77b-4fee-...
|
|
2796
|
-
obolos call a59a0377-... --body '{"prompt":"a cat in space"}'
|
|
2797
|
-
obolos job list --status=open
|
|
2798
|
-
obolos job create --title "Analyze data" --evaluator 0xABC... --budget 5.00
|
|
2799
|
-
obolos listing list --status=open
|
|
2800
|
-
obolos listing create --title "Parse CSV data" --max-budget 10.00 --deadline 7d
|
|
2801
|
-
obolos listing bid abc123 --price 5.00 --message "I can do this"
|
|
2802
|
-
obolos listing accept abc123 --bid bid456
|
|
2803
|
-
obolos anp list --status=open
|
|
2804
|
-
obolos anp create --title "Analyze data" --min-budget 5 --max-budget 50 --deadline 7d
|
|
2805
|
-
obolos anp bid sha256-abc... --price 25 --delivery 48h --message "I can do this"
|
|
2806
|
-
obolos anp accept sha256-listing... --bid sha256-bid...
|
|
2807
|
-
obolos rep check 16907
|
|
2808
|
-
obolos rep check 16907 --chain ethereum
|
|
2809
|
-
obolos rep compare 123 456 789
|
|
2810
|
-
obolos rep compare base:123 ethereum:456
|
|
2811
|
-
`);
|
|
2812
|
-
}
|
|
2813
|
-
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
2814
|
-
const args = process.argv.slice(2);
|
|
2815
|
-
const command = args[0];
|
|
2816
|
-
const commandArgs = args.slice(1);
|
|
2817
|
-
async function main() {
|
|
2818
|
-
switch (command) {
|
|
2819
|
-
case 'search':
|
|
2820
|
-
case 's':
|
|
2821
|
-
await cmdSearch(commandArgs);
|
|
2822
|
-
break;
|
|
2823
|
-
case 'categories':
|
|
2824
|
-
case 'cats':
|
|
2825
|
-
await cmdCategories();
|
|
2826
|
-
break;
|
|
2827
|
-
case 'info':
|
|
2828
|
-
case 'i':
|
|
2829
|
-
await cmdInfo(commandArgs);
|
|
2830
|
-
break;
|
|
2831
|
-
case 'call':
|
|
2832
|
-
case 'c':
|
|
2833
|
-
await cmdCall(commandArgs);
|
|
2834
|
-
break;
|
|
2835
|
-
case 'balance':
|
|
2836
|
-
case 'bal':
|
|
2837
|
-
await cmdBalance();
|
|
2838
|
-
break;
|
|
2839
|
-
case 'setup':
|
|
2840
|
-
await cmdSetup(commandArgs);
|
|
2841
|
-
break;
|
|
2842
|
-
case 'setup-mcp':
|
|
2843
|
-
case 'mcp':
|
|
2844
|
-
await cmdSetupMcp();
|
|
2845
|
-
break;
|
|
2846
|
-
case 'job':
|
|
2847
|
-
case 'j':
|
|
2848
|
-
await cmdJob(commandArgs);
|
|
2849
|
-
break;
|
|
2850
|
-
case 'listing':
|
|
2851
|
-
case 'l':
|
|
2852
|
-
await cmdListing(commandArgs);
|
|
2853
|
-
break;
|
|
2854
|
-
case 'anp':
|
|
2855
|
-
await cmdAnp(commandArgs);
|
|
2856
|
-
break;
|
|
2857
|
-
case 'reputation':
|
|
2858
|
-
case 'rep':
|
|
2859
|
-
await cmdReputation(commandArgs);
|
|
2860
|
-
break;
|
|
2861
|
-
case 'help':
|
|
2862
|
-
case '--help':
|
|
2863
|
-
case '-h':
|
|
2864
|
-
case undefined:
|
|
2865
|
-
showHelp();
|
|
2866
|
-
break;
|
|
2867
|
-
default:
|
|
2868
|
-
console.error(`${c.red}Unknown command: ${command}${c.reset}`);
|
|
2869
|
-
showHelp();
|
|
2870
|
-
process.exit(1);
|
|
77
|
+
const result = await dispatch(command, commandArgs);
|
|
78
|
+
if (result.handled) {
|
|
79
|
+
if (result.exitCode && result.exitCode !== 0)
|
|
80
|
+
process.exit(result.exitCode);
|
|
81
|
+
return;
|
|
2871
82
|
}
|
|
83
|
+
process.stderr.write(`${c.red}Unknown command: ${command}${c.reset}\n`);
|
|
84
|
+
showHelp();
|
|
85
|
+
process.exit(1);
|
|
2872
86
|
}
|
|
2873
87
|
main().catch((err) => {
|
|
2874
|
-
|
|
88
|
+
process.stderr.write(`${c.red}Error: ${err.message}${c.reset}\n`);
|
|
2875
89
|
process.exit(1);
|
|
2876
90
|
});
|
|
2877
91
|
//# sourceMappingURL=index.js.map
|