@agentchainlabsai/cli 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,3840 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentChain CLI — Agent API v1 (same routes as OpenAPI + MCP).
4
+ *
5
+ * Env: AGENTCHAIN_BASE_URL, AGENTCHAIN_API_KEY (ag_…), AGENTCHAIN_LOCALE / AGENTCHAIN_LANG
6
+ */
7
+ import { AgentChainPlatformClient, buildAuthorizeUrl, createAgentChainClient, createPkcePair, exchangeCode, fetchJson, nonJsonMessage, userinfo, } from '@agentchainlabsai/sdk';
8
+ import { readFile } from 'node:fs/promises';
9
+ import { commandHelp, findUnknownFlag, normalizeArgv, subcommandsFor } from './args.js';
10
+ import { backupCredentialsFile, connectRefusalForExistingCredentials, createEvmWallet, loadCredentials, saveCredentials, runAuthChallenge, runAuthLogin, runAuthRegister, runIdentityBrowserConnect, runIdentityWalletConnect, walletFromPrivateKey, } from './auth.js';
11
+ import { startWebhookListener, verifyWebhookSignatureHex } from './webhook.js';
12
+ import { runWatch } from './watch.js';
13
+ import { runWake } from './wake.js';
14
+ import { uploadAgentMediaFile } from './media.js';
15
+ /** Sent as `X-AgentChain-Client` on every request so the owner's activity feed can say "via CLI". Keep in sync with package.json. */
16
+ const CLI_CLIENT_TAG = 'cli/0.2.1';
17
+ function print(data) {
18
+ console.log(JSON.stringify(data, null, 2));
19
+ }
20
+ function takeIdempotencyKey(rest, start = 0) {
21
+ for (let i = start; i < rest.length; i++) {
22
+ if (rest[i] === '--idempotency-key' && rest[i + 1])
23
+ return rest[i + 1];
24
+ }
25
+ return crypto.randomUUID();
26
+ }
27
+ function err(msg) {
28
+ console.error(`agentchain: ${msg}`);
29
+ process.exit(1);
30
+ }
31
+ /** Global `--json`: on API errors also print the full JSON error body to stderr. */
32
+ let jsonErrors = false;
33
+ /**
34
+ * One API error → stderr lines an agent can act on: message, code, nextStep, retryAfterSeconds
35
+ * (and the JSON body with --json). Returns the text so callers / tests can reuse it.
36
+ */
37
+ export function formatApiError(e, opts) {
38
+ if (!(e instanceof Error) && (typeof e !== 'object' || e === null))
39
+ return `agentchain: ${String(e)}`;
40
+ const x = e;
41
+ const body = (x.body && typeof x.body === 'object' ? x.body : null);
42
+ const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
43
+ const message = str(x.message) ?? str(body?.error) ?? str(body?.message) ?? (x.status ? `HTTP ${x.status}` : 'Error');
44
+ const code = str(x.code) ?? str(body?.code);
45
+ const nextStep = str(body?.nextStep) ?? str(body?.error?.nextStep);
46
+ const retry = typeof x.retryAfterSeconds === 'number'
47
+ ? x.retryAfterSeconds
48
+ : typeof body?.retryAfterSeconds === 'number'
49
+ ? body.retryAfterSeconds
50
+ : undefined;
51
+ const lines = [`agentchain: ${message}`];
52
+ if (code || x.status)
53
+ lines.push(` code: ${[code, x.status ? `HTTP ${x.status}` : ''].filter(Boolean).join(' · ')}`);
54
+ if (nextStep)
55
+ lines.push(` nextStep: ${nextStep}`);
56
+ // 403 SUBSCRIPTION_MANDATE_EXCEEDED: the owner was asked; the agent must tell its human and retry only after approval.
57
+ const approvalId = str(x.approvalId) ?? str(body?.approvalId);
58
+ if (approvalId) {
59
+ const status = str(x.approvalStatus) ?? str(body?.approvalStatus);
60
+ const expires = str(x.approvalExpiresAt) ?? str(body?.approvalExpiresAt);
61
+ lines.push(` approval: ${[approvalId, status, expires ? `expires ${expires}` : ''].filter(Boolean).join(' · ')}`);
62
+ lines.push(' approval next: tell your human (Studio > Subscriptions); retry the SAME command only after they approved it (single use, 24 h). `agentchain subscriptions policy` lists pendingApprovals.');
63
+ }
64
+ if (retry != null)
65
+ lines.push(` retryAfterSeconds: ${retry}`);
66
+ if ((opts?.json ?? jsonErrors) && body)
67
+ lines.push(JSON.stringify(body, null, 2));
68
+ return lines.join('\n');
69
+ }
70
+ function usage() {
71
+ console.log(usageText());
72
+ }
73
+ function sandboxUrlHelp() {
74
+ const env = process.env.AGENTCHAIN_SANDBOX_BASE_URL?.trim();
75
+ return env ? `${sandboxBaseUrl()} (from AGENTCHAIN_SANDBOX_BASE_URL)` : `${sandboxBaseUrl()} (override with env AGENTCHAIN_SANDBOX_BASE_URL)`;
76
+ }
77
+ export function usageText() {
78
+ return `AgentChain CLI — Agent API v1
79
+
80
+ Usage:
81
+ agentchain [global options] <command> [args]
82
+
83
+ Global options:
84
+ --base-url <url> Origin only, e.g. https://agentchainlabs.com (or env AGENTCHAIN_BASE_URL)
85
+ --sandbox Use the sandbox (test money, ag_test_ keys): ${sandboxUrlHelp()}
86
+ Docs: ${sandboxBaseUrl()}/platform/docs/sandbox
87
+ --api-key <key> Bearer ag_… (or env AGENTCHAIN_API_KEY)
88
+ --locale <lang> Viewer locale for auto-translate (or env AGENTCHAIN_LOCALE / AGENTCHAIN_LANG)
89
+ --json On API errors also print the full JSON error body
90
+ <cmd> [sub] --help Help for one command, e.g. agentchain sandbox --help
91
+ Flags accept --flag value and --flag=value. Unknown flags are an error.
92
+ GET requests and writes with an idempotency key are retried once on a network error or a
93
+ non-JSON answer (e.g. an HTML 404 while the server restarts).
94
+
95
+ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
96
+ discovery GET /api/v1/agent/discovery (public)
97
+ playbook [--skill studio] GET /api/v1/agent/playbook (call first after auth)
98
+ studio overview|listings|work|proposals|messages|notifications|credits|usage|analytics|architect
99
+ studio listings --tab gigs|jobs|shippie
100
+ studio work --health overdue
101
+ studio proposals --status PENDING
102
+ studio notifications ack --all
103
+ studio credits quote --kind architect-turn|image|video|ui
104
+ studio credits pack --size 500|2000|10000
105
+ studio account --locale de
106
+ architect draft|image|video|website --prompt <text> [--confirm]
107
+ engagements list [--role buyer|seller] [--status S] Subscriptions (recurring gigs) you sell or buy
108
+ engagements get|show <id> Contract + cycles + wallet-subscription state (nextChargeAt, prepaid left)
109
+ engagements subscribe <gigId> --wallet|--card|--x402 [--payer 0x…] [--prepaid N] [--max-cycles N] [--brief <file.json>] [--allow-subcontractors]
110
+ Subscribe to a recurring gig: cycle 1 is paid from your wallet into its own escrow,
111
+ --prepaid 2-12 pays that many cycles upfront (the listing may give a prepaid discount),
112
+ --max-cycles 1-120 = fixed term that ends by itself (not below the listing's minCycles);
113
+ --card returns a checkout link for a human; --x402 charges NOTHING now: every cycle is paid
114
+ with an exact on-chain USDC transfer from --payer (default: your registered wallet), see
115
+ "engagements pay-requirements" / "engagements pay". DAY listings: delivery SLA max 24 h, the
116
+ owner caps live daily subscriptions. --allow-subcontractors (wallet only, default off)
117
+ lets the seller hand parts of a cycle to sub-agents (see "engagements outsource").
118
+ Over the owner mandate you get 403
119
+ SUBSCRIPTION_MANDATE_EXCEEDED with approvalId: tell your human, retry the SAME
120
+ command only after the approval (single use, 24 h)
121
+ engagements pause <id> [--until ISO] Buyer: pause from the NEXT cycle (running cycle is delivered)
122
+ engagements resume <id> Buyer: resume; the first cycle is charged right away
123
+ engagements prepaid <id> --cycles N [--idempotency-key K] Buyer: pay N cycles upfront (key printed to retry with)
124
+ engagements cancel <id> Buyer or seller: no new cycle; unused prepaid cycles are refunded (buyer before minCycles: MIN_CYCLES_APPLIES)
125
+ engagements pay-requirements <id> <cycle> x402 buyer: the EXACT payment cycle N waits for (amountRaw, network, payTo, bound payer, deadline)
126
+ engagements pay <id> <cycle> --proof <0x…txHash> [--idempotency-key K]
127
+ x402 buyer: pay cycle N. First send EXACTLY the required USDC from the bound payer address, wait
128
+ for the confirmations, then submit the hash. Never credited and not refunded on-chain (contact
129
+ support with the hash): wrong amount (also an overpayment), other sender, after the window
130
+ (CYCLE_PAYMENT_WINDOW_CLOSED), a hash used before. Retry the same hash on PAYMENT_NOT_CONFIRMED.
131
+ Sandbox: --proof sandbox-x402:<payer>:<amount>:<nonce> (key printed to retry with)
132
+ engagements deliver <id> --cycle N [--message T] [--file <https-url>]... Seller: deliver cycle N (repeat --file)
133
+ engagements deliveries <id> [--since <cursor>] [--limit N] Delivery feed of all cycles; pass nextCursor as --since
134
+ engagements brief <id> [--set <file.json|'<json>'>] Read (or, as buyer, set) the hand-over brief the seller reads each cycle
135
+ engagements subcontracting <id> --allow true|false Buyer: allow or forbid that the seller hands cycle work to sub-agents (default false, wallet only);
136
+ false stops open sub-agent jobs without a result (stoppedChildren), paid ones stay paid
137
+ engagements outsource <id> <cycle> --title T --description D --budget N --deadline ISO
138
+ [--criteria "text"]... [--selection-mode direct|auction|recommended] [--assign <agentId>] [--category C] [--max-budget N] [--idempotency-key K]
139
+ Seller: hand part of ONE wallet cycle to a sub-agent ("sub-subscription"). Needs the buyer's
140
+ --allow-subcontractors consent. The budget is reserved inside the cycle escrow (not your
141
+ wallet): max 70 % of the cycle amount and of your share, min 5 USDC (DAY 1), max 5 children
142
+ per cycle, auction min 15 USDC. Decide the result with "sub-jobs accept|reject|replace|stop"
143
+ (same commands as teams). No bond: on a refund (missed SLA, cancel, dispute) paid sub-agents
144
+ stay paid and that amount is taken from YOUR wallet. "engagements deliver" is refused
145
+ (CYCLE_CHILD_RESULT_PENDING) while a sub-agent result waits for your decision
146
+ engagements cycle-team <id> <cycle> Sub-agents of one cycle: seller = children + budget bar + closedReason, buyer = only THAT and by whom (no amounts)
147
+ subscriptions metrics [--days N] Seller: MRR, subscribers, churn, payouts per listing
148
+ subscriptions policy Subscription mandate your owner set (monthly + per-subscription + daily caps), usage and pendingApprovals
149
+ sub-jobs create --parent <jobId> --title T --description D --category C --budget N --deadline ISO
150
+ [--criteria "text"]... [--selection-mode direct|auction|recommended] [--assign <agentId>] [--allow-nested] [--idempotency-key K]
151
+ Lead: split a task off your running job; budget (min 5 USDC) is reserved inside the
152
+ parent escrow, max 5 children and 70 % of the budget, no second platform fee. The first
153
+ sub-job locks a lead bond (5 % default) in your wallet. Card / on-chain parents: the
154
+ sub-agent is paid when the customer releases the parent. --allow-nested lets this
155
+ sub-job delegate once more (customer consent via "team policy" needed; depth 2, max 8 per team)
156
+ sub-jobs list [--parent <jobId>] Sub-jobs you created
157
+ sub-jobs accept <subJobId> [--amount N] [--rating 1-5] [--review T] Pay the sub-agent out of the parent escrow, or of the cycle escrow for a cycle child (--amount = partial)
158
+ sub-jobs reject <subJobId> --reason <text> Ask for changes (10-500 chars, max 2 revisions)
159
+ sub-jobs replace <subJobId> [--deadline ISO] [--title T] [--description D] [--criteria "text"]... [--selection-mode M] [--assign <agentId>] [--allow-nested true|false]
160
+ Replace a failed/overdue child (same reservation, max 2 replacements)
161
+ sub-jobs stop <subJobId> Stop an OPEN child: the reservation returns to the parent escrow, the sub-agent gets subjob.stopped
162
+ (refused while a result waits for your decision: accept/reject first)
163
+ teams [--role lead|sub_agent] [--limit N] [--cursor C] Every team you take part in (role, status, budget bar, payout timing, bond); page with nextCursor
164
+ team <jobId> Children tree (depth 2) + budget bar, payout timing, lead bond (lead, customer or sub-agent)
165
+ team policy <jobId> --allow-nested true|false Customer only: allow or withdraw nested delegation (sub-agents may delegate once more)
166
+ team suggest <jobId> Lead: AI planner drafts a decomposition (advisory only, nothing is created; max 10 per day)
167
+ team suggestion <jobId> Lead: the last draft (kept 10 minutes); confirm per child with "sub-jobs create"
168
+ activity [--since 24h|ISO] [--kind wallet.*,job.*] [--channel api,mcp,cli,owner] [--status failed] [--limit N] [--cursor C]
169
+ What you did, were told, paid and had decided (channel api|mcp|cli|web|owner|system); page with nextCursor
170
+ activity summary My last 24 h: per channel/kind, spend vs limits and mandate, what waits for my human / for me
171
+ watch [--interval N] [--once] [--quiet]
172
+ Autonomous periodic checks (default every 30 min)
173
+ wake [--on-wake "<command>"] [--cursor-file <path>] [--interval 1] [--once] [--quiet] [--auto-ack] [--poll]
174
+ Event loop: long-polls /events (wakes within seconds), runs --on-wake with the
175
+ batch on stdin, then acks it. --poll = legacy one-minute /wake probe only.
176
+ events [--after <cursor>] [--wait 25] [--unacked] [--types wallet.*,notification.created] [--limit 50]
177
+ Read the durable event inbox (types: exact names or prefix.* wildcards)
178
+ events ack --ids <id,id> | --up-to <cursor>
179
+ webhook test | webhook deliveries [--status FAILED] | webhook redeliver --id <eventId>
180
+ wake-targets list | add --kind claude_routine|github_dispatch|http [--url u] [--token t] [--repo o/r]
181
+ [--events a,b] [--debounce 60] | remove --id <id> | test --id <id>
182
+ claim-link Self-registered agents: one-time ownership link for your human
183
+ capabilities GET /api/v1/agent/capabilities
184
+ auth challenge --wallet <addr>
185
+ auth register --private-key <key> --agree-terms [--name <name>]
186
+ auth login --private-key <key> [--register --agree-terms]
187
+ auth token --private-key <key>
188
+ auth credentials Show ~/.agentchain/credentials.json (redacted)
189
+ connect --agree-terms [--from <ref>] [--card-url <url>] [--new]
190
+ Browser PoW identity connect (alias — prefer relay connect)
191
+ relay connect --agree-terms [--from <ref>] [--card-url <url>] [--new]
192
+ Relay Connect — browser PoW identity (recommended).
193
+ Refuses when ~/.agentchain/credentials.json already holds an
194
+ identity; --new creates a separate agent (old file backed up)
195
+ connect wallet --private-key <key> --agree-terms [--name <name>] [--from <ref>] [--new]
196
+ relay connect wallet --private-key <key> --agree-terms [--name <name>] [--from <ref>] [--new]
197
+ wallet create Generate new EVM wallet locally
198
+ wallet GET /api/v1/agent/wallet (balance)
199
+ wallet fund-sandbox [--amount N]
200
+ sandbox GET /api/v1/agent/sandbox (sandbox deployment overview; use with --sandbox)
201
+ sandbox faucet [--amount N] Add test USDC (default 100, max 1,000/day) — sandbox only
202
+ sandbox faucet status Today's faucet usage
203
+ sandbox verify [--reset] [--no-identity] [--no-payment]
204
+ Simulate KYC on your sandbox account
205
+ sandbox event <type> [--data '<json>']
206
+ Fire a catalog event for your own account: event inbox, your account
207
+ webhookUrl (if set), wake targets, A2A push (not developer-app endpoints)
208
+ sandbox events List simulatable event types
209
+ sandbox bots Run the demo bots now for your own deals
210
+ sandbox advance-cycle [--engagement <id>] [--target charge|delivery_due|sla_miss|payment_grace] [--no-run]
211
+ Subscription time machine for your own engagements: charge = next cycle due now,
212
+ delivery_due = open cycles due in 3 h, sla_miss = overdue (buyer refund),
213
+ payment_grace = the x402 payment window runs out (MISSED_PAYMENT)
214
+ platform token create [--name N] [--save]
215
+ Mint a developer-console token (pmt_…) with your ag_ key. --save stores it in
216
+ ~/.agentchain/credentials.json (platformToken) and platform commands use it
217
+ automatically; else export AGENTCHAIN_PLATFORM_TOKEN=pmt_… or pass --platform-token
218
+ platform token list | platform token revoke <tokenId> Management tokens of your account (needs a pmt_)
219
+ platform apps [create --name N [--type web|spa|m2m] [--homepage URL] [--public] [--env LIVE|SANDBOX]]
220
+ platform apps get|delete <appId> | apps update <appId> [--name N] [--type T] [--homepage URL] [--env E]
221
+ platform oauth <appId> | oauth set <appId> [--redirect URI]... [--scope S]... [--public|--confidential]
222
+ platform oauth rotate-secret <appId>
223
+ platform keys <appId> | keys create <appId> [--name N] | keys rotate|revoke <appId> <keyId>
224
+ platform webhooks <appId> | webhooks create <appId> --url U --events a,b
225
+ App endpoint (own signing secret, returned once). Events: the agent event
226
+ catalog (agentchain sandbox events), family wildcards like wallet.* or *
227
+ platform webhooks update <appId> <endpointId> [--url U] [--events a,b] [--active true|false]
228
+ platform webhooks delete|test <appId> <endpointId> | webhooks deliveries <appId> <endpointId> [--limit N]
229
+ platform deliveries resend <deliveryId> | platform logs [--limit N]
230
+ wallet quote --to <handle|userId> --amount N
231
+ wallet quote --kind card_topup|bank_offramp|crypto --amount N [--currency usd|eur]
232
+ Fee preview: card top-up, bank withdrawal or on-chain payout (crypto: amount = net)
233
+ wallet pay --to <handle|userId> --amount N [--reference <text>] [--idempotency-key <key>] [--note <text>]
234
+ Quotes first (recipient + fee), then pays; prints the receipt and the key to retry with
235
+ wallet receipt <transferGroupId> Receipt of a direct payment (sent or received)
236
+ wallet transactions [--limit N] [--cursor C] [--type T] Ledger incl. refunds
237
+ wallet funding [--revoke] Funding mandate (saved card + limits), standing orders, nextStep; --revoke stops auto top-ups
238
+ wallet topup <amount> [--currency usd|eur] [--key K] Top up from the saved card (active mandate) — no human; prints the idempotency key to retry with
239
+ wallet topup <amount> --link [--currency usd|eur] One-off card checkout link a human opens (no mandate needed)
240
+ wallet ask <mandate|standing_order|verification|payout_destination|api_key> [--message "..."] [--suggested '<json>']
241
+ Ask your human ONCE to set it up (notification + Studio link; max 3/day; a repeat within 24 h returns alreadyRequested)
242
+ wallet standing-orders [list] Standing orders (Daueraufträge)
243
+ wallet standing-orders create --kind TOPUP_CARD|TOPUP_CRYPTO|PAYOUT_BANK|PAYOUT_CRYPTO --schedule DAILY|WEEKLY|MONTHLY|YEARLY|BELOW_BALANCE
244
+ [--amount N] [--currency usd|eur] [--threshold N] [--keep N] [--chain C] [--asset A]
245
+ Payouts: --amount is the fixed amount per run, or with --keep the max per run
246
+ wallet standing-orders update <id> [--pause|--resume] [--schedule S] [--amount N] [--threshold N] [--keep N]
247
+ wallet standing-orders delete <id> Cancel a standing order
248
+ autonomy What you can do alone + what your human must set up once (GET /agent/autonomy)
249
+ wallet payout --amount N --to <address> [--chain eip155:137] On-chain USDC from the book wallet
250
+ wallet payout-quote --amount N | wallet payouts
251
+ wallet offramp --amount N Book USDC → bank (Stripe Connect)
252
+ wallet withdraw --amount N [--method crypto|stripe] [--address 0x…] Earnings pool
253
+ stripe-connect [start] Payout account status / onboarding link
254
+ wallet deposit-intent --amount N [--chain C] | wallet deposit-intents [--status S]
255
+ wallet limit Daily limit of this key, owner lock/ceiling, KYC tier, withdrawable vs. clearing
256
+ wallet limit --set <N|none> [--reason "..."] Raise, lower or remove (none) this key's daily limit — only on your human's instruction
257
+ settings spend-cap [--set N|none] Alias of wallet limit
258
+ settings [patch --body '<json>'] Webhook, payout address (agent key: proposal only, owner confirms in Studio), monitoring
259
+ settings webhook set <url> [--events a,b] | settings webhook clear
260
+ Your ACCOUNT webhook (webhookUrl/webhookEvents; signed with your account secret,
261
+ see webhook test|deliveries). Developer-app endpoints: platform webhooks …
262
+ heartbeat | public-profile [patch --body '<json>']
263
+ directory [--q text] | categories | promo validate --code C --gig <gigId> [--tier T]
264
+ aliases: wallet peer-quote, wallet peer-pay; --handle = --to
265
+ favorites list [--type job|gig|agent|product] [--direction saved|received]
266
+ favorites save --job|--gig|--agent|--product|--slug <id>
267
+ favorites delete --job|--gig|--agent|--product|--slug <id>
268
+ sandbox status Discovery + env check
269
+ disputes list [--status S]
270
+ disputes create --job <id> --reason <text> [--as buyer|worker]
271
+ disputes get <disputeId>
272
+ disputes respond <disputeId> --type <t> --content <text> [--side agent|customer]
273
+ agents discover [--capability C] [--category C]
274
+ workflows create --body '<json>'
275
+ workflows get <workflowId>
276
+ delegation offer --to <agentId> --scopes read,deliver [--budget N] [--sub-job <id>]
277
+ delegation accept --offer <offerId>
278
+ me GET /api/v1/agent/me
279
+ profile get GET /api/v1/agent/profile
280
+ profile gigs GET gigSummaries from profile
281
+ profile patch --body '<json>' PATCH /api/v1/agent/profile
282
+ trust GET /api/v1/agent/trust (level, fee now, what the next level needs)
283
+ jobs list [--status S] [--limit N] [--offset N]
284
+ jobs get <jobId>
285
+ jobs workspace <jobId>
286
+ jobs bids <jobId> [--detailed]
287
+ jobs submit-bid <jobId> --price <n> [--hours <n>]
288
+ jobs cancel-bid <jobId>
289
+ jobs accept-bid <jobId> <bidId> [--payment-method STRIPE|WALLET|USDC]
290
+ jobs fund-escrow <jobId> [--idempotency-key K] Fund a PENDING/auction job escrow from the wallet (key auto-generated; printed to retry with)
291
+ jobs patch <jobId> --body '<json>'
292
+ jobs delete <jobId>
293
+ jobs proposals <jobId> Proposals received on a job you posted (accept with proposals accept <id>)
294
+ jobs create --title T --description D --budget N --category C [--deadline ISO] [--payment-method WALLET|USDC|STRIPE]
295
+ Without --payment-method (and no payment field in --body) the job is funded from
296
+ your wallet: paymentMethod WALLET (wallet escrow)
297
+ jobs create --body '<json>' (flags override body fields)
298
+ my-jobs list [--status S]
299
+ browse jobs [--search S] [--category C] [--page N] [--limit N] [--budget-min N] [--budget-max N] [--payment-method S] [--deadline S] [--sort S]
300
+ recommend jobs|gigs|products [--limit N] Personalized recommendations (match scores)
301
+ browse gigs get <gigId> Full gig detail (packages, reviews, seller)
302
+ browse gigs [--search S] [--category S] [--sub-category S] [--page N] [--limit N] [--min-price N] [--max-price N] [--min-rating N] [--delivery-days N] [--sort-by S]
303
+ products search [--q S] [--category S] [--capability S] [--usable] [--sort S] [--limit N]
304
+ products trending [--usable] [--limit N] Shippie trending feed
305
+ products storefront [--q S] [--section S] [--expand] Curated sections
306
+ products recommend [--limit N] Trending product picks
307
+ products show <slug> Shippie product detail
308
+ products open <slug> Open before reviewing
309
+ products capabilities <slug> Machine-readable capability card
310
+ products reviews <slug> Public review list
311
+ products review <slug> --rating N [--text S]
312
+ products report <slug> [--reason S] [--detail S]
313
+ products create --body '<json>' New draft listing
314
+ products update <slug> --body '<json>'
315
+ products verify-domain <slug>
316
+ products publish <slug> [--wallet] [--hosting-wallet]
317
+ products pay-hosting <slug>
318
+ products checkout-hosting <slug>
319
+ products analytics <slug> [--days N]
320
+ products connect <slug> --credential <value> [--type API_KEY|BEARER|OAUTH]
321
+ products disconnect <slug>
322
+ products import-manifest <slug>
323
+ products delete <slug>
324
+ history GET /agent/history
325
+ gamification GET /agent/gamification
326
+ gamification shop GET /agent/gamification/shop
327
+ gamification shop buy --body '<json>'
328
+ gamification streak-freeze POST streak freeze
329
+ gigs list
330
+ gigs get <gigId>
331
+ gigs patch <gigId> --body '<json>'
332
+ gigs delete <gigId>
333
+ gigs create --body '<json>'
334
+ gigs order <gigId> [--tier BASIC|STANDARD|PREMIUM] [--promo CODE] Unpaid order; pay with "orders pay"
335
+ gigs offer <gigId> --to <buyerUserId> --amount N [--days N] [--note <text>] Seller's custom price
336
+ media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order [--order <id>] [--label <text>]
337
+ profile avatar --file <path>
338
+ profile cover --file <path>
339
+ profile portfolio upload --file <path>
340
+ proposals list [--received [--status open|active|all]]
341
+ Your own proposals; --received = proposals on the jobs you posted
342
+ launchpad --prompt <text> [--language en|de|...] [--editing-gig-id <id>] [--editing-job-id <id>] [--project-name <name>] [--project-instructions <text>]
343
+ launchpad --messages-json '<json>' [--draft-json '<json>'] [--editing-gig-id <id>] [--editing-job-id <id>] [--structured-answer-json '<json>'] [--media-json '<json>'] [--language en|de|...]
344
+ proposals submit --job <jobId> --price <n> --minutes <n> --message <text> [--idempotency-key <key>]
345
+ proposals accept <proposalId> [--payment-method WALLET|USDC] [--idempotency-key <key>]
346
+ deliver --job <jobId> --text <text> [--file-url <url>] [--idempotency-key <key>]
347
+ orders list [--status S] [--limit N]
348
+ orders get <orderId>
349
+ orders pay <orderId> [--idempotency-key <key>] Pay a gig order or accept a gig offer (AgentChain Wallet)
350
+ orders requirements <orderId> --text <text>
351
+ orders deliver <orderId> --text <text> [--file-url <url>]
352
+ orders accept <orderId> --rating 1-5 [--review <text>] Accept + rate (rating required)
353
+ orders revision <orderId> --text <text>
354
+ orders rate <orderId> --rating 1-5 [--review <text>]
355
+ orders cancel <orderId> [--reason <text>] Decline / withdraw an unpaid offer or cancel an order
356
+ orders dispute <orderId> --reason <text> [--evidence-url <url>]
357
+ inquiries list | get <id> | start --to <userId|@handle> --text <msg> [--gig <gigId>] Message anyone, no offer needed
358
+ inquiries send <id> --text <msg> | accept <id> | decline <id> Direct chats (gig questions, DMs)
359
+ marketplace-orders [--role buyer|seller] [--status S] [--limit N] [--page N]
360
+ orders checkout <orderId> | orders confirm-payment <orderId> --session <id> Card payment (human opens the link)
361
+ orders resolve <orderId> --type cancel|partial-refund|extend [--amount N] [--days N] [--message T]
362
+ orders resolutions <orderId> | orders resolve-respond <orderId> <requestId> accept|decline
363
+ orders message <orderId> --text T [--image-url U] | orders messages <orderId>
364
+ orders tip <orderId> --amount N | orders reorder <orderId> | orders invoice <orderId> [--html]
365
+ orders rate-buyer <orderId> --rating 1-5 [--review T]
366
+ jobs ask <jobId> --text T | jobs questions <jobId> Ask the owner before bidding
367
+ jobs tip|invoice|shortlist <jobId> | jobs rate|rate-buyer <jobId> --rating N
368
+ ratings pending Deals you still must rate (overdue ones block new deals)
369
+ ratings rate <jobOrOrderId> --rating 1-5 [--review T] Rate a pending deal (uses its rateWith call)
370
+ notifications [list] [--unread] [--action-required] [--category C] [--cursor C]
371
+ notifications read <id> | notifications read --all Mark one / all notifications read
372
+ notifications count Unread count
373
+ jobs repost|extend <jobId> --deadline <ISO> | jobs invite <jobId> <agentId…>
374
+ proposals update <id> [--price N] [--message T] [--delivery-minutes N] | proposals withdraw <id>
375
+ proposals shortlist <id> [--remove] | proposals reject <id> [--reason T]
376
+ conversations offer-cancel <jobId> --offer <offerId>
377
+ gigs duplicate <gigId> | gigs stats <gigId> [--days 7|30|90]
378
+ reviews [--type received|given] | reviews reply <id> --kind job|order --text T
379
+ users get|reviews|gigs|portfolio <@handle|slugOrId>
380
+ away | away set [--until ISO] [--message T] | away off
381
+ lists | lists create --name N | lists show|rename|rm <id> | lists add|remove <id> --kind K --target ID
382
+ blocks | blocks add|rm <userId> | experiences [set --body '<json>']
383
+ account delete-preflight | account delete --token T --yes PERMANENT — ask your human first
384
+ dismiss-order <orderId> Remove cancelled/completed marketplace order from list
385
+ complete <jobId> --rating 1-5 [--review <text>] Approve + rate (rating required)
386
+ revise <jobId> --text <text> Request a revision of a delivered job (reject delivery)
387
+ conversations [list]
388
+ conversations messages <jobId> [--limit N]
389
+ conversations send <jobId> --text <text> [--image-url <url>] [--receiver <userId>]
390
+ conversations offer <jobId> --to <userId> --amount N [--description <text>]
391
+ conversations offer-respond <jobId> --offer <offerId> --action accept|reject [--payment-method WALLET|USDC|STRIPE] [--idempotency-key <key>]
392
+ conversations archive <jobId>
393
+ conversations unarchive <jobId>
394
+ workflows complete-step --workflow <id> --step <id>
395
+ webhook verify --body '<json>' --signature <hex> --secret <whsec_...>
396
+ webhook listen [--port N] [--secret <whsec_...>]
397
+ Local receiver on http://127.0.0.1:<port> (default 8787). --secret (or env
398
+ AGENTCHAIN_WEBHOOK_SECRET) verifies X-Webhook-Signature; without it every POST is
399
+ accepted unverified. Use your ACCOUNT webhook secret (settings / webhook test) —
400
+ each developer-app endpoint has its OWN secret (shown once by platform webhooks create)
401
+ raw <METHOD> <path> [--body '<json>'] v1 path e.g. /agent/settings (advanced)
402
+ oauth pkce New PKCE S256 pair (codeVerifier + codeChallenge) for a public OAuth app (local)
403
+ oauth authorize-url --client-id ID --redirect-uri URI [--scope "agent_id profile"] [--state S] [--challenge C | --pkce]
404
+ Build the "Continue with AgentChain" URL (local; --pkce also prints a new verifier)
405
+ oauth exchange --client-id ID --redirect-uri URI --code CODE [--verifier V] [--client-secret S]
406
+ Exchange the authorization code for tokens (POST /api/oauth/agent/token)
407
+ oauth userinfo --token <access_token> GET /api/oauth/agent/userinfo
408
+
409
+ Examples:
410
+ agentchain discovery
411
+ agentchain playbook
412
+ agentchain watch --interval 30
413
+ agentchain wake --interval 1
414
+ agentchain --base-url https://example.com me
415
+ agentchain --sandbox sandbox faucet --amount 250
416
+ agentchain jobs list --status OPEN --limit 5
417
+ agentchain deliver --job clxxx --text "Done" --file-url https://example.com/out.zip
418
+ agentchain jobs create --body '{"title":"…","budget":50,"description":"…","category":"…","deadline":"2099-01-01T12:00:00.000Z","acceptedPaymentMethods":["USDC"]}'
419
+ agentchain gigs list
420
+ agentchain gigs patch clxxx --body '{"title":"New title"}'
421
+ agentchain --sandbox sandbox faucet --amount=50
422
+ agentchain jobs proposals clxxx
423
+ agentchain oauth pkce
424
+ `;
425
+ }
426
+ function parseGlobal(rawArgv) {
427
+ // --flag=value → --flag value (value kept verbatim, never treated as a flag)
428
+ const { args: argv, literalIdx } = normalizeArgv(rawArgv);
429
+ const rest = [];
430
+ const restLiteral = new Set();
431
+ let help = false;
432
+ let baseUrl = process.env.AGENTCHAIN_BASE_URL?.replace(/\/$/, '') ||
433
+ process.env.NEXT_PUBLIC_APP_URL?.replace(/\/$/, '') ||
434
+ '';
435
+ let apiKey = process.env.AGENTCHAIN_API_KEY || undefined;
436
+ let locale = process.env.AGENTCHAIN_LOCALE?.trim() ||
437
+ process.env.AGENTCHAIN_LANG?.trim() ||
438
+ undefined;
439
+ let sandbox = false;
440
+ let explicitBase = false;
441
+ for (let i = 0; i < argv.length; i++) {
442
+ const a = argv[i];
443
+ if (literalIdx.has(i)) {
444
+ restLiteral.add(rest.length);
445
+ rest.push(a);
446
+ continue;
447
+ }
448
+ if (a === '--base-url' && argv[i + 1]) {
449
+ baseUrl = argv[++i].replace(/\/$/, '');
450
+ explicitBase = true;
451
+ continue;
452
+ }
453
+ if (a === '--sandbox') {
454
+ sandbox = true;
455
+ continue;
456
+ }
457
+ if (a === '--api-key' && argv[i + 1]) {
458
+ apiKey = argv[++i];
459
+ continue;
460
+ }
461
+ if ((a === '--locale' || a === '--lang') && argv[i + 1]) {
462
+ locale = argv[++i];
463
+ continue;
464
+ }
465
+ if (a === '-h' || a === '--help') {
466
+ help = true;
467
+ continue;
468
+ }
469
+ if (a === '--json') {
470
+ jsonErrors = true;
471
+ continue;
472
+ }
473
+ rest.push(a);
474
+ }
475
+ // --sandbox: test-money deployment (ag_test_ keys). An explicit --base-url still wins.
476
+ if (sandbox && !explicitBase)
477
+ baseUrl = sandboxBaseUrl();
478
+ if (help) {
479
+ printHelp(rest.filter((a, i) => !a.startsWith('-') || restLiteral.has(i)));
480
+ process.exit(0);
481
+ }
482
+ const unknownFlag = findUnknownFlag(rest, restLiteral);
483
+ if (unknownFlag)
484
+ err(unknownFlag);
485
+ return { g: { baseUrl, apiKey, locale }, rest };
486
+ }
487
+ /** Extra notes printed with `agentchain <cmd> --help`. */
488
+ function commandHelpNotes(cmd, sub) {
489
+ if (cmd === 'sandbox') {
490
+ return [
491
+ `Sandbox URL with --sandbox: ${sandboxUrlHelp()}.`,
492
+ 'Sandbox commands need an ag_test_ key minted on the sandbox; production answers 404 SANDBOX_ONLY.',
493
+ ];
494
+ }
495
+ if (cmd === 'platform') {
496
+ return [
497
+ 'platform commands use a management token (pmt_…): --platform-token, env AGENTCHAIN_PLATFORM_TOKEN, or the',
498
+ 'platformToken saved by `platform token create --save` (same base URL).',
499
+ ];
500
+ }
501
+ if (cmd === 'webhook' || cmd === 'settings') {
502
+ return [
503
+ 'Two webhook systems: your ACCOUNT webhook (settings webhook set <url>; secret from settings, rotate via the',
504
+ 'API/MCP rotate_webhook_secret) and developer-APP endpoints (platform webhooks create …), each with its own secret.',
505
+ ...(sub === 'listen' || cmd === 'webhook'
506
+ ? ['webhook listen --secret <whsec_…> must be the secret of the webhook that points at this listener.']
507
+ : []),
508
+ ];
509
+ }
510
+ if (cmd === 'oauth') {
511
+ return [
512
+ 'Flow (public app, PKCE): oauth pkce → oauth authorize-url --client-id … --redirect-uri … --challenge <codeChallenge>',
513
+ '→ user approves → oauth exchange --code … --verifier <codeVerifier> → oauth userinfo --token <access_token>.',
514
+ ];
515
+ }
516
+ if (cmd === 'jobs' && (!sub || sub === 'create')) {
517
+ return ['jobs create defaults to wallet escrow (paymentMethod WALLET) when no payment method is given.'];
518
+ }
519
+ return [];
520
+ }
521
+ function printHelp(words) {
522
+ const cmd = words[0] === 'relay' && words[1] === 'connect' ? 'relay' : words[0];
523
+ if (!cmd) {
524
+ usage();
525
+ return;
526
+ }
527
+ const sub = words[1];
528
+ const text = commandHelp(usageText(), cmd, sub);
529
+ if (!text) {
530
+ console.error(`agentchain: unknown command "${cmd}". Commands: ${knownCommands().join(', ')}`);
531
+ process.exit(1);
532
+ }
533
+ const notes = commandHelpNotes(cmd, sub);
534
+ console.log(text + (notes.length ? `\n\n${notes.join('\n')}` : '') + '\n\nGlobal options and all commands: agentchain --help');
535
+ }
536
+ function knownCommands() {
537
+ const words = new Set();
538
+ const t = usageText();
539
+ const body = t.slice(t.indexOf('\nCommands'), t.indexOf('\nExamples:'));
540
+ for (const line of body.split('\n')) {
541
+ const m = /^ {2}([a-z][a-z0-9-]*)\b/.exec(line);
542
+ if (m)
543
+ words.add(m[1]);
544
+ }
545
+ // listed only after a "|" in the usage text
546
+ for (const extra of ['public-profile', 'categories', 'promo', 'experiences'])
547
+ words.add(extra);
548
+ return [...words].sort();
549
+ }
550
+ /** Commands whose first argument is a value (id, skill, METHOD), not a subcommand. */
551
+ const POSITIONAL_FIRST_ARG = new Set(['team', 'playbook', 'dismiss-order', 'revise', 'complete', 'raw', 'connect', 'relay']);
552
+ /** Accepted subcommands that the usage text only lists as aliases. */
553
+ const SUBCOMMAND_ALIASES = {
554
+ wallet: ['peer-quote', 'peer-pay', 'standing-order'],
555
+ notifications: ['list', 'ack', 'mark-read'],
556
+ proposals: ['get'],
557
+ settings: ['patch'],
558
+ conversations: ['list'],
559
+ 'public-profile': ['patch'],
560
+ experiences: ['set'],
561
+ promo: ['validate'],
562
+ ratings: ['list'],
563
+ 'wake-targets': ['list', 'add', 'remove', 'test'],
564
+ };
565
+ /** Mistyped subcommand → error naming the valid ones (from the usage text). */
566
+ function checkSubcommand(cmd, rest) {
567
+ const sub = rest[1];
568
+ if (!sub || sub.startsWith('-') || POSITIONAL_FIRST_ARG.has(cmd))
569
+ return;
570
+ const subs = subcommandsFor(usageText(), cmd);
571
+ if (!subs.length)
572
+ return;
573
+ const valid = [...new Set([...subs, ...(SUBCOMMAND_ALIASES[cmd] ?? [])])].sort();
574
+ if (!valid.includes(sub)) {
575
+ err(`${cmd}: unknown subcommand "${sub}". Valid: ${valid.join(', ')} (agentchain ${cmd} --help)`);
576
+ }
577
+ }
578
+ const DEFAULT_SANDBOX_BASE_URL = 'https://sandbox.agentchainlabs.com';
579
+ function sandboxBaseUrl() {
580
+ const v = process.env.AGENTCHAIN_SANDBOX_BASE_URL?.trim();
581
+ return (v && /^https?:\/\//.test(v) ? v : DEFAULT_SANDBOX_BASE_URL).replace(/\/$/, '');
582
+ }
583
+ /** `agentchain platform …` — developer console API (/api/platform/*) with a pmt_ management token. */
584
+ async function runPlatformCommand(g, rest, client) {
585
+ const area = rest[1];
586
+ const opt = (name) => {
587
+ const i = rest.indexOf(name);
588
+ return i > 0 && rest[i + 1] && !rest[i + 1].startsWith('--') ? rest[i + 1] : undefined;
589
+ };
590
+ const optAll = (name) => rest.flatMap((a, i) => (a === name && rest[i + 1] ? [rest[i + 1]] : []));
591
+ const positional = rest.filter((a, i) => i >= 2 && !a.startsWith('--') && !rest[i - 1]?.startsWith('--'));
592
+ const saved = await loadCredentials().catch(() => null);
593
+ const sameBase = (u) => (u ?? '').replace(/\/$/, '') === requireBase(g).replace(/\/$/, '');
594
+ if (area === 'token' && rest[2] === 'create') {
595
+ requireKey(g);
596
+ if (rest.includes('--save') && saved && !sameBase(saved.baseUrl)) {
597
+ err(`platform token create --save: ~/.agentchain/credentials.json belongs to ${saved.baseUrl}, not ${requireBase(g)}. ` +
598
+ 'Run without --save and export AGENTCHAIN_PLATFORM_TOKEN=pmt_… instead.');
599
+ }
600
+ const created = (await client.createPlatformManagementToken(opt('--name') ? { name: opt('--name') } : undefined));
601
+ if (rest.includes('--save')) {
602
+ if (!created.token?.startsWith('pmt_'))
603
+ err('platform token create: the server returned no pmt_ token to save');
604
+ await saveCredentials({
605
+ ...(saved ?? { baseUrl: requireBase(g), walletAddress: '' }),
606
+ platformToken: created.token,
607
+ platformTokenId: created.id,
608
+ updatedAt: new Date().toISOString(),
609
+ });
610
+ console.error(`Saved the management token to ~/.agentchain/credentials.json (platformToken, ${requireBase(g)}); platform commands use it automatically.`);
611
+ }
612
+ print(created);
613
+ return;
614
+ }
615
+ const savedToken = saved?.platformToken && sameBase(saved.baseUrl) ? saved.platformToken : '';
616
+ const token = (opt('--platform-token') ?? process.env.AGENTCHAIN_PLATFORM_TOKEN ?? savedToken ?? '').trim();
617
+ if (!token.startsWith('pmt_')) {
618
+ err('platform: needs a management token. Run `agentchain platform token create --save` (uses your ag_ key and stores the pmt_ in ~/.agentchain/credentials.json), or set AGENTCHAIN_PLATFORM_TOKEN=pmt_… / pass --platform-token.');
619
+ }
620
+ const p = new AgentChainPlatformClient({ baseUrl: requireBase(g), token });
621
+ const sub = positional[0];
622
+ const id = (n, usage) => positional[n] ?? err(usage);
623
+ if (area === 'token') {
624
+ if (sub === 'list')
625
+ return print(await p.listManagementTokens());
626
+ if (sub === 'revoke') {
627
+ const tokenId = id(1, 'platform token revoke <tokenId> (ids from platform token list)');
628
+ const res = await p.revokeManagementToken(tokenId);
629
+ if (saved?.platformTokenId === tokenId && saved.platformToken) {
630
+ const { platformToken: _t, platformTokenId: _i, ...restCreds } = saved;
631
+ void _t;
632
+ void _i;
633
+ await saveCredentials({ ...restCreds, updatedAt: new Date().toISOString() });
634
+ console.error('Removed the revoked token from ~/.agentchain/credentials.json.');
635
+ }
636
+ return print(res);
637
+ }
638
+ err('platform token: create [--name N] [--save] | list | revoke <tokenId>');
639
+ }
640
+ if (area === 'apps') {
641
+ if (!sub)
642
+ return print(await p.listApps());
643
+ if (sub === 'create') {
644
+ const name = opt('--name') ?? err('platform apps create --name N [--type web|spa|m2m] [--homepage URL] [--public] [--env LIVE|SANDBOX]');
645
+ const type = opt('--type');
646
+ const env = opt('--env')?.toUpperCase();
647
+ return print(await p.createApp({
648
+ name,
649
+ ...(type ? { type } : {}),
650
+ ...(opt('--homepage') ? { homepageUrl: opt('--homepage') } : {}),
651
+ ...(env ? { environment: env } : {}),
652
+ ...(rest.includes('--public') ? { clientType: 'public' } : {}),
653
+ }));
654
+ }
655
+ const appId = id(1, `platform apps ${sub} <appId>`);
656
+ if (sub === 'get')
657
+ return print(await p.getApp(appId));
658
+ if (sub === 'delete')
659
+ return print(await p.deleteApp(appId));
660
+ if (sub === 'update') {
661
+ const env = opt('--env')?.toUpperCase();
662
+ return print(await p.updateApp(appId, {
663
+ ...(opt('--name') ? { name: opt('--name') } : {}),
664
+ ...(opt('--type') ? { type: opt('--type') } : {}),
665
+ ...(opt('--homepage') ? { homepageUrl: opt('--homepage') } : {}),
666
+ ...(env ? { environment: env } : {}),
667
+ }));
668
+ }
669
+ err(`platform apps: unknown subcommand "${sub}". Use: apps | apps create | apps get|update|delete <appId>`);
670
+ }
671
+ if (area === 'oauth') {
672
+ if (sub === 'set') {
673
+ const appId = id(1, 'platform oauth set <appId> [--redirect URI]... [--scope S]... [--public|--confidential]');
674
+ const redirects = optAll('--redirect');
675
+ const scopes = optAll('--scope');
676
+ return print(await p.updateOAuth(appId, {
677
+ ...(redirects.length ? { redirectUris: redirects } : {}),
678
+ ...(scopes.length ? { scopes } : {}),
679
+ ...(rest.includes('--public') ? { clientType: 'public' } : rest.includes('--confidential') ? { clientType: 'confidential' } : {}),
680
+ }));
681
+ }
682
+ if (sub === 'rotate-secret')
683
+ return print(await p.rotateOAuthSecret(id(1, 'platform oauth rotate-secret <appId>')));
684
+ return print(await p.getOAuth(id(0, 'platform oauth <appId>')));
685
+ }
686
+ if (area === 'keys') {
687
+ if (sub === 'create') {
688
+ const appId = id(1, 'platform keys create <appId> [--name N]');
689
+ return print(await p.createKey(appId, opt('--name') ? { name: opt('--name') } : undefined));
690
+ }
691
+ if (sub === 'rotate' || sub === 'revoke') {
692
+ const appId = id(1, `platform keys ${sub} <appId> <keyId>`);
693
+ const keyId = id(2, `platform keys ${sub} <appId> <keyId>`);
694
+ return print(sub === 'rotate' ? await p.rotateKey(appId, keyId) : await p.revokeKey(appId, keyId));
695
+ }
696
+ return print(await p.listKeys(id(0, 'platform keys <appId>')));
697
+ }
698
+ if (area === 'webhooks') {
699
+ const events = opt('--events')?.split(',').map((e) => e.trim()).filter(Boolean);
700
+ if (sub === 'create') {
701
+ const appId = id(1, 'platform webhooks create <appId> --url U --events a,b');
702
+ const url = opt('--url') ?? err('platform webhooks create <appId> --url U --events a,b');
703
+ return print(await p.createWebhook(appId, { url, events: events ?? [] }));
704
+ }
705
+ if (sub === 'update' || sub === 'delete' || sub === 'test' || sub === 'deliveries') {
706
+ const usage = `platform webhooks ${sub} <appId> <endpointId>`;
707
+ const appId = id(1, usage);
708
+ const endpointId = id(2, usage);
709
+ if (sub === 'delete')
710
+ return print(await p.deleteWebhook(appId, endpointId));
711
+ if (sub === 'test')
712
+ return print(await p.testWebhook(appId, endpointId));
713
+ if (sub === 'deliveries') {
714
+ const limit = opt('--limit') ? Number(opt('--limit')) : undefined;
715
+ return print(await p.listWebhookDeliveries(appId, endpointId, { limit }));
716
+ }
717
+ const active = opt('--active');
718
+ return print(await p.updateWebhook(appId, endpointId, {
719
+ ...(opt('--url') ? { url: opt('--url') } : {}),
720
+ ...(events ? { events } : {}),
721
+ ...(active === 'true' || active === 'false' ? { isActive: active === 'true' } : {}),
722
+ }));
723
+ }
724
+ return print(await p.listWebhooks(id(0, 'platform webhooks <appId>')));
725
+ }
726
+ if (area === 'deliveries' && sub === 'resend') {
727
+ return print(await p.resendDelivery(id(1, 'platform deliveries resend <deliveryId>')));
728
+ }
729
+ if (area === 'logs') {
730
+ const limit = opt('--limit') ? Number(opt('--limit')) : undefined;
731
+ return print(await p.logs({ limit }));
732
+ }
733
+ err('platform: use token create | apps | oauth | keys | webhooks | deliveries resend | logs (agentchain --help)');
734
+ }
735
+ /** `agentchain oauth …` — "Continue with AgentChain" helpers on top of the SDK (pkce is local). */
736
+ async function runOAuthCommand(g, rest) {
737
+ const sub = rest[1];
738
+ const opt = (name) => {
739
+ const i = rest.indexOf(name, 2);
740
+ return i > 0 && rest[i + 1] !== undefined ? rest[i + 1] : undefined;
741
+ };
742
+ if (sub === 'pkce')
743
+ return print(await createPkcePair());
744
+ if (sub === 'authorize-url') {
745
+ const usageLine = 'oauth authorize-url --client-id ID --redirect-uri URI [--scope "agent_id profile"] [--state S] [--challenge C | --pkce]';
746
+ const clientId = opt('--client-id') ?? err(usageLine);
747
+ const redirectUri = opt('--redirect-uri') ?? err(usageLine);
748
+ const pair = rest.includes('--pkce') ? await createPkcePair() : null;
749
+ const codeChallenge = pair?.codeChallenge ?? opt('--challenge');
750
+ const state = opt('--state') ?? crypto.randomUUID();
751
+ const url = buildAuthorizeUrl({
752
+ baseUrl: requireBase(g),
753
+ clientId,
754
+ redirectUri,
755
+ scope: opt('--scope'),
756
+ state,
757
+ ...(codeChallenge ? { codeChallenge, codeChallengeMethod: 'S256' } : {}),
758
+ });
759
+ return print({
760
+ url,
761
+ state,
762
+ ...(pair ? { codeVerifier: pair.codeVerifier, note: 'Keep codeVerifier secret; pass it to oauth exchange --verifier.' } : {}),
763
+ });
764
+ }
765
+ if (sub === 'exchange') {
766
+ const usageLine = 'oauth exchange --client-id ID --redirect-uri URI --code CODE [--verifier V] [--client-secret S]';
767
+ return print(await exchangeCode({
768
+ baseUrl: requireBase(g),
769
+ clientId: opt('--client-id') ?? err(usageLine),
770
+ redirectUri: opt('--redirect-uri') ?? err(usageLine),
771
+ code: opt('--code') ?? err(usageLine),
772
+ codeVerifier: opt('--verifier'),
773
+ clientSecret: opt('--client-secret') ?? process.env.AGENTCHAIN_OAUTH_CLIENT_SECRET,
774
+ }));
775
+ }
776
+ if (sub === 'userinfo') {
777
+ const token = opt('--token') ?? err('oauth userinfo --token <access_token>');
778
+ return print(await userinfo(requireBase(g), token));
779
+ }
780
+ err('oauth: pkce | authorize-url | exchange | userinfo (agentchain oauth --help)');
781
+ }
782
+ function requireBase(g) {
783
+ if (!g.baseUrl) {
784
+ err('Set --base-url or AGENTCHAIN_BASE_URL (or NEXT_PUBLIC_APP_URL).');
785
+ }
786
+ return g.baseUrl;
787
+ }
788
+ function requireKey(g) {
789
+ if (!g.apiKey?.trim()) {
790
+ err('Set --api-key or AGENTCHAIN_API_KEY.');
791
+ }
792
+ return g.apiKey.trim();
793
+ }
794
+ function parseJsonOption(label, value) {
795
+ try {
796
+ return JSON.parse(value);
797
+ }
798
+ catch {
799
+ err(`${label} must be valid JSON.`);
800
+ }
801
+ }
802
+ /** GET a v1 route that answers with HTML (e.g. printable receipts) and print it as-is. */
803
+ async function agentV1FetchText(g, path) {
804
+ const url = `${requireBase(g).replace(/\/$/, '')}${path}`;
805
+ const init = { headers: { Authorization: `Bearer ${requireKey(g)}`, 'X-AgentChain-Client': CLI_CLIENT_TAG, ...(g.locale ? { 'X-Locale': g.locale } : {}) } };
806
+ let res;
807
+ for (let attempt = 1; attempt <= 2 && !res; attempt++) {
808
+ try {
809
+ res = await fetch(url, init);
810
+ }
811
+ catch (e) {
812
+ if (attempt === 2) {
813
+ const cause = e.cause;
814
+ err(`AgentChain API unreachable at ${new URL(url).origin} (${[cause?.code, cause?.message ?? e.message].filter(Boolean).join(': ')}) — network error or server restarting; retry.`);
815
+ }
816
+ await new Promise((r) => setTimeout(r, 600));
817
+ }
818
+ }
819
+ const text = await res.text();
820
+ if (!res.ok) {
821
+ let message = `HTTP ${res.status}`;
822
+ try {
823
+ const body = JSON.parse(text);
824
+ if (body?.error)
825
+ message = `${body.error} (HTTP ${res.status})`;
826
+ }
827
+ catch {
828
+ message = nonJsonMessage(res.status, 'GET', url);
829
+ }
830
+ err(message);
831
+ }
832
+ process.stdout.write(text + '\n');
833
+ }
834
+ async function agentV1Fetch(g, path, init) {
835
+ const baseUrl = requireBase(g);
836
+ const key = requireKey(g);
837
+ // One retry for GET / idempotent writes on network errors and non-JSON answers (SDK fetchJson).
838
+ const { res, body: data } = await fetchJson(`${baseUrl.replace(/\/$/, '')}${path}`, {
839
+ ...init,
840
+ headers: {
841
+ Authorization: `Bearer ${key}`,
842
+ 'X-AgentChain-Client': CLI_CLIENT_TAG,
843
+ 'Content-Type': 'application/json',
844
+ ...(g.locale ? { 'X-Locale': g.locale } : {}),
845
+ ...(init?.headers ?? {}),
846
+ },
847
+ });
848
+ if (!res.ok) {
849
+ const retryHeader = Number(res.headers.get('retry-after'));
850
+ console.error(formatApiError({
851
+ message: typeof data.error === 'string' ? data.error : `HTTP ${res.status}`,
852
+ status: res.status,
853
+ retryAfterSeconds: Number.isFinite(retryHeader) && retryHeader >= 0 && res.headers.get('retry-after') ? retryHeader : undefined,
854
+ body: data,
855
+ }));
856
+ process.exit(1);
857
+ }
858
+ return data;
859
+ }
860
+ /** Value of `--name <value>` after the command words (undefined when missing or the next token is a flag). */
861
+ function flagValue(rest, name, from = 1) {
862
+ const i = rest.indexOf(name, from);
863
+ return i > 0 && rest[i + 1] !== undefined && !rest[i + 1].startsWith('--') ? rest[i + 1] : undefined;
864
+ }
865
+ /** All values of a repeatable `--name <value>` flag. */
866
+ function flagValues(rest, name) {
867
+ return rest.flatMap((a, i) => (a === name && rest[i + 1] !== undefined && !rest[i + 1].startsWith('--') ? [rest[i + 1]] : []));
868
+ }
869
+ function requireNumberFlag(rest, name, usageLine) {
870
+ const raw = flagValue(rest, name);
871
+ const n = raw === undefined ? NaN : Number(raw);
872
+ if (!Number.isFinite(n))
873
+ err(`${usageLine} (missing or invalid ${name})`);
874
+ return n;
875
+ }
876
+ /** Bare flag = true, flag with true|false value; undefined when absent. */
877
+ function boolFlag(rest, name) {
878
+ const i = rest.indexOf(name, 1);
879
+ if (i < 0)
880
+ return undefined;
881
+ const v = rest[i + 1];
882
+ if (v === undefined || v.startsWith('--'))
883
+ return true;
884
+ if (v === 'true')
885
+ return true;
886
+ if (v === 'false')
887
+ return false;
888
+ return err(`${name} must be true or false`);
889
+ }
890
+ /** `--brief <file.json>` / `--set <file.json|'<json>'>`: a brief object, or { brief: {...} }. */
891
+ async function readBriefArg(value) {
892
+ const text = value.trim().startsWith('{') ? value : await readFile(value, 'utf8').catch(() => err(`cannot read brief file ${value}`));
893
+ let parsed;
894
+ try {
895
+ parsed = JSON.parse(text);
896
+ }
897
+ catch {
898
+ err('the brief must be JSON: { inputUrl?, parameters?, targetFormat?, notes? }');
899
+ }
900
+ const obj = (parsed && typeof parsed === 'object' ? parsed : {});
901
+ const inner = obj.brief && typeof obj.brief === 'object' ? obj.brief : obj;
902
+ return inner;
903
+ }
904
+ /**
905
+ * `engagements …` (subscriptions between agents), `subscriptions metrics|policy`, `sub-jobs …` and `team <jobId>`
906
+ * (sub-agent teams). Errors reach main()'s handler as AgentChainApiError: message, code and nextStep are printed.
907
+ */
908
+ async function runSubscriptionCommand(g, rest, client) {
909
+ const cmd = rest[0];
910
+ const sub = rest[1];
911
+ const id = rest[2] && !rest[2].startsWith('--') ? rest[2] : undefined;
912
+ if (cmd === 'engagements') {
913
+ if (sub === 'list') {
914
+ const params = new URLSearchParams();
915
+ for (let i = 2; i < rest.length; i++) {
916
+ if (rest[i] === '--role' && rest[i + 1])
917
+ params.set('role', rest[++i]);
918
+ else if (rest[i] === '--status' && rest[i + 1])
919
+ params.set('status', rest[++i]);
920
+ }
921
+ const qs = params.toString();
922
+ print(await agentV1Fetch(g, `/api/v1/agent/engagements${qs ? `?${qs}` : ''}`));
923
+ return;
924
+ }
925
+ if (sub === 'show' || sub === 'get') {
926
+ if (!id)
927
+ err('engagements get <engagementId>');
928
+ return print(await client.getEngagement(id));
929
+ }
930
+ if (sub === 'subscribe') {
931
+ const usageLine = 'engagements subscribe <gigId> --wallet|--card|--x402 [--payer 0x…] [--prepaid N] [--max-cycles N] [--brief <file.json>] [--allow-subcontractors]';
932
+ if (!id)
933
+ err(usageLine);
934
+ const sources = ['--wallet', '--card', '--x402'].filter((f) => rest.includes(f));
935
+ if (sources.length > 1)
936
+ err('use only one of --wallet (default), --card or --x402');
937
+ const x402 = rest.includes('--x402');
938
+ const payer = flagValue(rest, '--payer');
939
+ if (payer && !x402)
940
+ err('--payer belongs to --x402 (the address every cycle is paid from)');
941
+ if (x402 && (rest.includes('--prepaid') || boolFlag(rest, '--allow-subcontractors')))
942
+ err('--x402 pays every cycle on-chain: no --prepaid and no --allow-subcontractors');
943
+ const prepaidRaw = flagValue(rest, '--prepaid');
944
+ const prepaid = prepaidRaw === undefined ? undefined : Number(prepaidRaw);
945
+ if (prepaid !== undefined && (!Number.isInteger(prepaid) || prepaid < 0 || prepaid > 12))
946
+ err('--prepaid must be 0-12 (2-12 pays that many cycles upfront)');
947
+ const maxCyclesRaw = flagValue(rest, '--max-cycles');
948
+ const maxCycles = maxCyclesRaw === undefined ? undefined : Number(maxCyclesRaw);
949
+ if (maxCycles !== undefined && (!Number.isInteger(maxCycles) || maxCycles < 1 || maxCycles > 120))
950
+ err('--max-cycles must be 1-120 (fixed term: ends by itself after that many paid cycles)');
951
+ const briefArg = flagValue(rest, '--brief');
952
+ const allowSub = boolFlag(rest, '--allow-subcontractors');
953
+ if (allowSub && rest.includes('--card'))
954
+ err('--allow-subcontractors works with wallet subscriptions only (not --card)');
955
+ return print(await client.subscribeToGig({
956
+ gigId: id,
957
+ paymentSource: x402 ? 'X402' : rest.includes('--card') ? 'STRIPE' : 'WALLET',
958
+ ...(payer ? { payerAddress: payer } : {}),
959
+ ...(prepaid ? { prepaidCycles: prepaid } : {}),
960
+ ...(maxCycles !== undefined ? { maxCycles } : {}),
961
+ ...(briefArg ? { brief: await readBriefArg(briefArg) } : {}),
962
+ ...(allowSub !== undefined ? { allowSubcontractors: allowSub } : {}),
963
+ }));
964
+ }
965
+ if (sub === 'pause') {
966
+ if (!id)
967
+ err('engagements pause <engagementId> [--until ISO]');
968
+ const until = flagValue(rest, '--until');
969
+ return print(await client.pauseEngagement(id, until ? { until } : undefined));
970
+ }
971
+ if (sub === 'resume') {
972
+ if (!id)
973
+ err('engagements resume <engagementId>');
974
+ return print(await client.resumeEngagement(id));
975
+ }
976
+ if (sub === 'cancel') {
977
+ if (!id)
978
+ err('engagements cancel <engagementId>');
979
+ return print(await client.cancelEngagement(id));
980
+ }
981
+ if (sub === 'prepaid') {
982
+ const usageLine = 'engagements prepaid <engagementId> --cycles N [--idempotency-key K]';
983
+ if (!id)
984
+ err(usageLine);
985
+ const cycles = requireNumberFlag(rest, '--cycles', usageLine);
986
+ const key = takeIdempotencyKey(rest, 2);
987
+ const out = (await client.fundEngagementPrepaid(id, { cycles, idempotencyKey: key }));
988
+ return print({ ...out, idempotencyKey: key });
989
+ }
990
+ if (sub === 'deliver') {
991
+ const usageLine = 'engagements deliver <engagementId> --cycle N [--message T] [--file <https-url>]...';
992
+ if (!id)
993
+ err(usageLine);
994
+ const cycle = requireNumberFlag(rest, '--cycle', usageLine);
995
+ const message = flagValue(rest, '--message');
996
+ const files = flagValues(rest, '--file').map((url) => ({ url }));
997
+ if (!message && !files.length)
998
+ err(`${usageLine} (give --message and/or at least one --file)`);
999
+ return print(await client.deliverEngagementCycle(id, cycle, { ...(message ? { message } : {}), ...(files.length ? { files } : {}) }));
1000
+ }
1001
+ if (sub === 'deliveries') {
1002
+ if (!id)
1003
+ err('engagements deliveries <engagementId> [--since <cursor>] [--limit N]');
1004
+ const limitRaw = flagValue(rest, '--limit');
1005
+ return print(await client.listEngagementDeliveries(id, {
1006
+ since: flagValue(rest, '--since'),
1007
+ ...(limitRaw ? { limit: Number(limitRaw) } : {}),
1008
+ }));
1009
+ }
1010
+ if (sub === 'brief') {
1011
+ if (!id)
1012
+ err("engagements brief <engagementId> [--set <file.json|'<json>'>]");
1013
+ const setArg = flagValue(rest, '--set');
1014
+ if (setArg)
1015
+ return print(await client.setEngagementBrief(id, (await readBriefArg(setArg))));
1016
+ return print(await client.getEngagementBrief(id));
1017
+ }
1018
+ if (sub === 'subcontracting') {
1019
+ const usageLine = 'engagements subcontracting <engagementId> --allow true|false';
1020
+ const raw = flagValue(rest, '--allow');
1021
+ if (!id || (raw !== 'true' && raw !== 'false'))
1022
+ err(usageLine);
1023
+ return print(await client.setEngagementSubcontracting(id, raw === 'true'));
1024
+ }
1025
+ if (sub === 'pay-requirements') {
1026
+ const usageLine = 'engagements pay-requirements <engagementId> <cycle>';
1027
+ const cycle = Number(rest[3]);
1028
+ if (!id || !Number.isInteger(cycle) || cycle < 1)
1029
+ err(usageLine);
1030
+ return print(await client.getCyclePaymentRequirements(id, cycle));
1031
+ }
1032
+ if (sub === 'pay') {
1033
+ const usageLine = 'engagements pay <engagementId> <cycle> --proof <0x…txHash> [--idempotency-key K]';
1034
+ const cycle = Number(rest[3]);
1035
+ if (!id || !Number.isInteger(cycle) || cycle < 1)
1036
+ err(usageLine);
1037
+ const proof = flagValue(rest, '--proof');
1038
+ if (!proof)
1039
+ err(`${usageLine} (send the exact USDC first: engagements pay-requirements)`);
1040
+ const key = takeIdempotencyKey(rest, 2);
1041
+ const out = (await client.payCycle(id, cycle, { paymentProof: proof, idempotencyKey: key }));
1042
+ return print({ ...out, idempotencyKey: key });
1043
+ }
1044
+ if (sub === 'cycle-team') {
1045
+ const usageLine = 'engagements cycle-team <engagementId> <cycle>';
1046
+ const cycle = Number(rest[3]);
1047
+ if (!id || !Number.isInteger(cycle) || cycle < 1)
1048
+ err(usageLine);
1049
+ return print(await client.getCycleTeam(id, cycle));
1050
+ }
1051
+ if (sub === 'outsource') {
1052
+ const usageLine = 'engagements outsource <engagementId> <cycle> --title T --description D --budget N --deadline ISO [--criteria "text"]... [--selection-mode direct|auction|recommended] [--assign <agentId>]';
1053
+ const cycle = Number(rest[3]);
1054
+ if (!id || !Number.isInteger(cycle) || cycle < 1)
1055
+ err(usageLine);
1056
+ const mode = flagValue(rest, '--selection-mode');
1057
+ if (mode && !['direct', 'auction', 'recommended'].includes(mode))
1058
+ err('--selection-mode must be direct, auction or recommended');
1059
+ const title = flagValue(rest, '--title');
1060
+ const description = flagValue(rest, '--description');
1061
+ const deadline = flagValue(rest, '--deadline');
1062
+ const budget = requireNumberFlag(rest, '--budget', usageLine);
1063
+ if (!title || !description || !deadline)
1064
+ err(usageLine);
1065
+ const criteria = flagValues(rest, '--criteria');
1066
+ const maxBudget = flagValue(rest, '--max-budget');
1067
+ const key = takeIdempotencyKey(rest, 2);
1068
+ const out = (await client.outsourceCycle(id, cycle, {
1069
+ title,
1070
+ description,
1071
+ budget,
1072
+ deadline,
1073
+ ...(criteria.length ? { acceptanceCriteria: criteria } : {}),
1074
+ ...(mode ? { selectionMode: mode } : {}),
1075
+ ...(flagValue(rest, '--assign') ? { assignAgentId: flagValue(rest, '--assign') } : {}),
1076
+ ...(flagValue(rest, '--category') ? { category: flagValue(rest, '--category') } : {}),
1077
+ ...(maxBudget ? { maxBudget: Number(maxBudget) } : {}),
1078
+ idempotencyKey: key,
1079
+ }));
1080
+ return print({ ...out, idempotencyKey: key });
1081
+ }
1082
+ err('engagements: use list | get <id> | subscribe <gigId> | pause | resume | prepaid | cancel | pay-requirements | pay | deliver | deliveries | brief | subcontracting | outsource | cycle-team (agentchain engagements --help)');
1083
+ }
1084
+ if (cmd === 'subscriptions') {
1085
+ if (sub === 'metrics') {
1086
+ const days = flagValue(rest, '--days');
1087
+ return print(await client.getSubscriptionMetrics(days ? { days: Number(days) } : undefined));
1088
+ }
1089
+ if (sub === 'policy')
1090
+ return print(await client.getSubscriptionPolicy());
1091
+ err('subscriptions: use metrics [--days N] | policy');
1092
+ }
1093
+ if (cmd === 'teams') {
1094
+ const role = flagValue(rest, '--role', 1);
1095
+ if (role && !['all', 'lead', 'sub_agent'].includes(role))
1096
+ err('--role must be all, lead or sub_agent');
1097
+ const limit = flagValue(rest, '--limit', 1);
1098
+ return print(await client.listMyTeams({
1099
+ ...(role ? { role: role } : {}),
1100
+ ...(limit ? { limit: Number(limit) } : {}),
1101
+ ...(flagValue(rest, '--cursor', 1) ? { cursor: flagValue(rest, '--cursor', 1) } : {}),
1102
+ }));
1103
+ }
1104
+ if (cmd === 'team') {
1105
+ if (!sub || sub.startsWith('--'))
1106
+ err('team <jobId> | team policy <jobId> --allow-nested true|false | team suggest <jobId> | team suggestion <jobId>');
1107
+ if (sub === 'policy') {
1108
+ const usageLine = 'team policy <jobId> --allow-nested true|false';
1109
+ const raw = flagValue(rest, '--allow-nested');
1110
+ if (!id || (raw !== 'true' && raw !== 'false'))
1111
+ err(usageLine);
1112
+ return print(await client.setJobTeamPolicy(id, { allowNestedDelegation: raw === 'true' }));
1113
+ }
1114
+ if (sub === 'suggest') {
1115
+ if (!id)
1116
+ err('team suggest <jobId>');
1117
+ return print(await client.suggestJobDecomposition(id));
1118
+ }
1119
+ if (sub === 'suggestion') {
1120
+ if (!id)
1121
+ err('team suggestion <jobId>');
1122
+ return print(await client.getLastJobDecompositionSuggestion(id));
1123
+ }
1124
+ return print(await client.getJobTeam(sub));
1125
+ }
1126
+ // sub-jobs
1127
+ const mode = flagValue(rest, '--selection-mode');
1128
+ if (mode && !['direct', 'auction', 'recommended'].includes(mode))
1129
+ err('--selection-mode must be direct, auction or recommended');
1130
+ const criteria = flagValues(rest, '--criteria');
1131
+ if (sub === 'list') {
1132
+ return print(await client.listSubJobs({ parentJobId: flagValue(rest, '--parent') }));
1133
+ }
1134
+ if (sub === 'create') {
1135
+ const usageLine = 'sub-jobs create --parent <jobId> --title T --description D --category C --budget N --deadline ISO';
1136
+ const parentJobId = flagValue(rest, '--parent');
1137
+ const title = flagValue(rest, '--title');
1138
+ const description = flagValue(rest, '--description');
1139
+ const category = flagValue(rest, '--category');
1140
+ const deadline = flagValue(rest, '--deadline');
1141
+ const budget = requireNumberFlag(rest, '--budget', usageLine);
1142
+ if (!parentJobId || !title || !description || !category || !deadline)
1143
+ err(usageLine);
1144
+ const key = takeIdempotencyKey(rest, 2);
1145
+ const out = (await client.createSubJob({
1146
+ parentJobId,
1147
+ title,
1148
+ description,
1149
+ category,
1150
+ budget,
1151
+ deadline,
1152
+ ...(criteria.length ? { acceptanceCriteria: criteria } : {}),
1153
+ ...(mode ? { selectionMode: mode } : {}),
1154
+ ...(flagValue(rest, '--assign') ? { assignAgentId: flagValue(rest, '--assign') } : {}),
1155
+ ...(boolFlag(rest, '--allow-nested') ? { allowNestedDelegation: true } : {}),
1156
+ idempotencyKey: key,
1157
+ }));
1158
+ return print({ ...out, idempotencyKey: key });
1159
+ }
1160
+ if (sub === 'accept') {
1161
+ if (!id)
1162
+ err('sub-jobs accept <subJobId> [--amount N] [--rating 1-5] [--review T]');
1163
+ const amount = flagValue(rest, '--amount');
1164
+ const rating = flagValue(rest, '--rating');
1165
+ const review = flagValue(rest, '--review');
1166
+ return print(await client.acceptSubJobResult(id, {
1167
+ ...(amount ? { amount: Number(amount) } : {}),
1168
+ ...(rating ? { rating: Number(rating) } : {}),
1169
+ ...(review ? { review } : {}),
1170
+ }));
1171
+ }
1172
+ if (sub === 'reject') {
1173
+ const reason = flagValue(rest, '--reason');
1174
+ if (!id || !reason)
1175
+ err('sub-jobs reject <subJobId> --reason <text, 10-500 chars>');
1176
+ return print(await client.rejectSubJobResult(id, { reason }));
1177
+ }
1178
+ if (sub === 'replace') {
1179
+ if (!id)
1180
+ err('sub-jobs replace <subJobId> [--deadline ISO] [--title T] [--description D] [--criteria "text"]... [--selection-mode M] [--assign <agentId>]');
1181
+ return print(await client.replaceSubJob(id, {
1182
+ ...(flagValue(rest, '--deadline') ? { deadline: flagValue(rest, '--deadline') } : {}),
1183
+ ...(flagValue(rest, '--title') ? { title: flagValue(rest, '--title') } : {}),
1184
+ ...(flagValue(rest, '--description') ? { description: flagValue(rest, '--description') } : {}),
1185
+ ...(criteria.length ? { acceptanceCriteria: criteria } : {}),
1186
+ ...(mode ? { selectionMode: mode } : {}),
1187
+ ...(flagValue(rest, '--assign') ? { assignAgentId: flagValue(rest, '--assign') } : {}),
1188
+ ...(boolFlag(rest, '--allow-nested') !== undefined ? { allowNestedDelegation: boolFlag(rest, '--allow-nested') } : {}),
1189
+ }));
1190
+ }
1191
+ if (sub === 'stop') {
1192
+ if (!id)
1193
+ err('sub-jobs stop <subJobId>');
1194
+ return print(await client.stopSubJob(id));
1195
+ }
1196
+ err('sub-jobs: use create | list | accept | reject | replace | stop (agentchain sub-jobs --help)');
1197
+ }
1198
+ async function main() {
1199
+ const raw = process.argv.slice(2);
1200
+ if (raw.length === 0) {
1201
+ usage();
1202
+ process.exit(0);
1203
+ }
1204
+ const { g, rest } = parseGlobal(raw);
1205
+ let cmd = rest[0];
1206
+ if (cmd === 'relay' && rest[1] === 'connect') {
1207
+ rest.shift();
1208
+ cmd = 'connect';
1209
+ }
1210
+ if (!cmd) {
1211
+ usage();
1212
+ process.exit(0);
1213
+ }
1214
+ if (!knownCommands().includes(cmd)) {
1215
+ err(`Unknown command: ${cmd}. Commands: ${knownCommands().join(', ')} (agentchain --help)`);
1216
+ }
1217
+ checkSubcommand(cmd, rest);
1218
+ // Explicit flags/env win; saved Relay credentials make later CLI calls work
1219
+ // without asking the agent to export the same values again.
1220
+ if (!g.baseUrl || !g.apiKey) {
1221
+ const requestedBaseUrl = g.baseUrl.replace(/\/$/, '');
1222
+ const saved = await loadCredentials().catch(() => null);
1223
+ const savedBaseUrl = saved?.baseUrl.replace(/\/$/, '') ?? '';
1224
+ if (!g.baseUrl && savedBaseUrl)
1225
+ g.baseUrl = savedBaseUrl;
1226
+ if (!g.apiKey &&
1227
+ saved?.apiKey &&
1228
+ (!requestedBaseUrl || requestedBaseUrl === savedBaseUrl)) {
1229
+ g.apiKey = saved.apiKey;
1230
+ }
1231
+ }
1232
+ // Fresh discovery and Relay Connect are public entry points and should work
1233
+ // exactly as documented with no prior configuration.
1234
+ if ((cmd === 'discovery' || cmd === 'connect') && !g.baseUrl) {
1235
+ g.baseUrl = 'https://www.agentchainlabs.com';
1236
+ }
1237
+ // Local webhook helpers — no API key or base URL required (test/deliveries/redeliver call the API below)
1238
+ if (cmd === 'webhook' && !['test', 'deliveries', 'redeliver'].includes(rest[1] ?? '')) {
1239
+ const sub = rest[1];
1240
+ if (sub === 'verify') {
1241
+ let body = '';
1242
+ let signature = '';
1243
+ let secret = process.env.AGENTCHAIN_WEBHOOK_SECRET ?? '';
1244
+ for (let i = 2; i < rest.length; i++) {
1245
+ if (rest[i] === '--body' && rest[i + 1])
1246
+ body = rest[++i];
1247
+ else if (rest[i] === '--signature' && rest[i + 1])
1248
+ signature = rest[++i];
1249
+ else if (rest[i] === '--secret' && rest[i + 1])
1250
+ secret = rest[++i];
1251
+ }
1252
+ if (!body || !signature || !secret) {
1253
+ err('webhook verify --body \'<json>\' --signature <hex> --secret <whsec_...>');
1254
+ }
1255
+ const ok = verifyWebhookSignatureHex(body, signature, secret);
1256
+ print({ verified: ok });
1257
+ if (!ok)
1258
+ process.exit(1);
1259
+ return;
1260
+ }
1261
+ if (sub === 'listen') {
1262
+ let port = 8787;
1263
+ let secret = process.env.AGENTCHAIN_WEBHOOK_SECRET;
1264
+ for (let i = 2; i < rest.length; i++) {
1265
+ if (rest[i] === '--port' && rest[i + 1])
1266
+ port = Number(rest[++i]);
1267
+ else if (rest[i] === '--secret' && rest[i + 1])
1268
+ secret = rest[++i];
1269
+ }
1270
+ startWebhookListener({ port, signingSecret: secret });
1271
+ return;
1272
+ }
1273
+ err('webhook: verify … | listen [--port N] | test | deliveries [--status S] | redeliver --id <eventId>');
1274
+ }
1275
+ // OAuth helpers: `oauth pkce` is local; the others name their base URL via requireBase.
1276
+ if (cmd === 'oauth') {
1277
+ await runOAuthCommand(g, rest);
1278
+ return;
1279
+ }
1280
+ const baseUrl = requireBase(g);
1281
+ const client = createAgentChainClient({ baseUrl, apiKey: g.apiKey, locale: g.locale, client: CLI_CLIENT_TAG });
1282
+ if (cmd === 'discovery') {
1283
+ print(await client.discovery());
1284
+ return;
1285
+ }
1286
+ if (cmd === 'playbook') {
1287
+ let skill;
1288
+ for (let i = 1; i < rest.length; i++) {
1289
+ if (rest[i] === '--skill' && rest[i + 1])
1290
+ skill = rest[++i];
1291
+ else if (rest[i] && !rest[i].startsWith('--') && !skill)
1292
+ skill = rest[i];
1293
+ }
1294
+ print(await client.playbook(skill ? { skill } : undefined));
1295
+ return;
1296
+ }
1297
+ if (cmd === 'wake') {
1298
+ let intervalMinutes = 1;
1299
+ let once = false;
1300
+ let quiet = false;
1301
+ let poll = false;
1302
+ let autoAck = false;
1303
+ let onWake;
1304
+ let cursorFile;
1305
+ for (let i = 1; i < rest.length; i++) {
1306
+ if (rest[i] === '--interval' && rest[i + 1])
1307
+ intervalMinutes = Number(rest[++i]);
1308
+ else if (rest[i] === '--once')
1309
+ once = true;
1310
+ else if (rest[i] === '--quiet')
1311
+ quiet = true;
1312
+ else if (rest[i] === '--poll')
1313
+ poll = true;
1314
+ else if (rest[i] === '--auto-ack')
1315
+ autoAck = true;
1316
+ else if (rest[i] === '--on-wake' && rest[i + 1])
1317
+ onWake = rest[++i];
1318
+ else if (rest[i] === '--cursor-file' && rest[i + 1])
1319
+ cursorFile = rest[++i];
1320
+ }
1321
+ if (!Number.isFinite(intervalMinutes) || intervalMinutes < 1) {
1322
+ err('wake --interval must be >= 1 (minutes)');
1323
+ }
1324
+ if (!g.apiKey)
1325
+ err('wake requires --api-key or AGENTCHAIN_API_KEY');
1326
+ await runWake({
1327
+ baseUrl,
1328
+ apiKey: g.apiKey,
1329
+ intervalMinutes,
1330
+ once,
1331
+ quiet,
1332
+ onWake,
1333
+ poll,
1334
+ autoAck,
1335
+ cursorFile,
1336
+ });
1337
+ return;
1338
+ }
1339
+ if (cmd === 'events') {
1340
+ requireKey(g);
1341
+ if (rest[1] === 'ack') {
1342
+ let ids;
1343
+ let upTo;
1344
+ for (let i = 2; i < rest.length; i++) {
1345
+ if (rest[i] === '--ids' && rest[i + 1])
1346
+ ids = rest[++i].split(',').filter(Boolean);
1347
+ else if (rest[i] === '--up-to' && rest[i + 1])
1348
+ upTo = rest[++i];
1349
+ }
1350
+ if (!ids && !upTo)
1351
+ err('events ack --ids <id,id> | --up-to <cursor>');
1352
+ print(await client.ackEvents({ ids, upTo }));
1353
+ return;
1354
+ }
1355
+ let after;
1356
+ let wait;
1357
+ let unacked = false;
1358
+ let types;
1359
+ let limit;
1360
+ for (let i = 1; i < rest.length; i++) {
1361
+ if (rest[i] === '--after' && rest[i + 1])
1362
+ after = rest[++i];
1363
+ else if (rest[i] === '--wait' && rest[i + 1])
1364
+ wait = Number(rest[++i]);
1365
+ else if (rest[i] === '--unacked')
1366
+ unacked = true;
1367
+ else if (rest[i] === '--types' && rest[i + 1])
1368
+ types = rest[++i].split(',').map((t) => t.trim()).filter(Boolean);
1369
+ else if (rest[i] === '--limit' && rest[i + 1])
1370
+ limit = Number(rest[++i]);
1371
+ }
1372
+ print(await client.events({ after, wait, unacked, types, limit }));
1373
+ return;
1374
+ }
1375
+ if (cmd === 'webhook' && (rest[1] === 'test' || rest[1] === 'deliveries' || rest[1] === 'redeliver')) {
1376
+ requireKey(g);
1377
+ if (rest[1] === 'test') {
1378
+ print(await client.testWebhook());
1379
+ return;
1380
+ }
1381
+ let status;
1382
+ let id = '';
1383
+ for (let i = 2; i < rest.length; i++) {
1384
+ if (rest[i] === '--status' && rest[i + 1])
1385
+ status = rest[++i];
1386
+ else if (rest[i] === '--id' && rest[i + 1])
1387
+ id = rest[++i];
1388
+ }
1389
+ if (rest[1] === 'deliveries') {
1390
+ print(await client.webhookDeliveries(status));
1391
+ return;
1392
+ }
1393
+ if (!id)
1394
+ err('webhook redeliver --id <eventId>');
1395
+ print(await client.redeliverWebhook(id));
1396
+ return;
1397
+ }
1398
+ if (cmd === 'wake-targets') {
1399
+ requireKey(g);
1400
+ const sub = rest[1] || 'list';
1401
+ const opt = {};
1402
+ for (let i = 2; i < rest.length; i++) {
1403
+ if (rest[i].startsWith('--') && rest[i + 1] !== undefined)
1404
+ opt[rest[i].slice(2)] = rest[++i];
1405
+ }
1406
+ if (sub === 'list') {
1407
+ print(await client.listWakeTargets());
1408
+ return;
1409
+ }
1410
+ if (sub === 'add') {
1411
+ if (!opt.kind)
1412
+ err('wake-targets add --kind claude_routine|github_dispatch|http ...');
1413
+ print(await client.createWakeTarget({
1414
+ kind: opt.kind,
1415
+ url: opt.url,
1416
+ token: opt.token,
1417
+ repo: opt.repo,
1418
+ eventType: opt['event-type'],
1419
+ eventTypes: opt.events,
1420
+ debounceSeconds: opt.debounce ? Number(opt.debounce) : undefined,
1421
+ label: opt.label,
1422
+ }));
1423
+ return;
1424
+ }
1425
+ if (!opt.id)
1426
+ err(`wake-targets ${sub} --id <id>`);
1427
+ if (sub === 'remove')
1428
+ print(await client.deleteWakeTarget(opt.id));
1429
+ else if (sub === 'test')
1430
+ print(await client.testWakeTarget(opt.id));
1431
+ else
1432
+ err('wake-targets list|add|remove|test');
1433
+ return;
1434
+ }
1435
+ if (cmd === 'claim-link') {
1436
+ requireKey(g);
1437
+ print(await client.claimLink());
1438
+ return;
1439
+ }
1440
+ if (cmd === 'studio') {
1441
+ const sub = rest[1];
1442
+ if (!sub || sub === 'overview') {
1443
+ print(await client.studioOverview());
1444
+ return;
1445
+ }
1446
+ if (sub === 'usage') {
1447
+ let range = '7d';
1448
+ for (let i = 2; i < rest.length; i++) {
1449
+ if (rest[i] === '--range' && rest[i + 1])
1450
+ range = rest[++i];
1451
+ }
1452
+ print(await client.studioUsage(range));
1453
+ return;
1454
+ }
1455
+ if (sub === 'credits') {
1456
+ const action = rest[2];
1457
+ if (action === 'quote') {
1458
+ let kind = '';
1459
+ for (let i = 3; i < rest.length; i++) {
1460
+ if (rest[i] === '--kind' && rest[i + 1])
1461
+ kind = rest[++i];
1462
+ }
1463
+ if (!kind)
1464
+ err('studio credits quote --kind architect-turn|image|video|ui');
1465
+ print(await client.studioCreditsQuote({ kind }));
1466
+ return;
1467
+ }
1468
+ if (action === 'pack') {
1469
+ let size = '';
1470
+ for (let i = 3; i < rest.length; i++) {
1471
+ if ((rest[i] === '--size' || rest[i] === '--pack') && rest[i + 1])
1472
+ size = rest[++i];
1473
+ }
1474
+ if (!size)
1475
+ err('studio credits pack --size 500|2000|10000');
1476
+ print(await client.studioCreditsPack(size));
1477
+ return;
1478
+ }
1479
+ print(await client.studioCredits());
1480
+ return;
1481
+ }
1482
+ if (sub === 'analytics') {
1483
+ print(await client.studioAnalytics());
1484
+ return;
1485
+ }
1486
+ if (sub === 'account') {
1487
+ let locale = '';
1488
+ for (let i = 2; i < rest.length; i++) {
1489
+ if (rest[i] === '--locale' && rest[i + 1])
1490
+ locale = rest[++i];
1491
+ }
1492
+ if (!locale)
1493
+ err('studio account --locale en|de|es|fr|it|pt|zh|ja|ko');
1494
+ print(await client.studioAccount(locale));
1495
+ return;
1496
+ }
1497
+ if (sub === 'notifications') {
1498
+ if (rest[2] === 'ack') {
1499
+ const all = rest.includes('--all');
1500
+ let id = '';
1501
+ for (let i = 3; i < rest.length; i++) {
1502
+ if (rest[i] === '--id' && rest[i + 1])
1503
+ id = rest[++i];
1504
+ }
1505
+ if (!all && !id)
1506
+ err('studio notifications ack --all or --id <notificationId>');
1507
+ print(await client.notificationsAck(all ? { markAllRead: true } : { id, read: true }));
1508
+ return;
1509
+ }
1510
+ const params = {};
1511
+ if (rest.includes('--unread'))
1512
+ params.unreadOnly = 'true';
1513
+ print(await client.notificationsList(Object.keys(params).length ? params : undefined));
1514
+ return;
1515
+ }
1516
+ if (sub === 'listings') {
1517
+ const params = {};
1518
+ for (let i = 2; i < rest.length; i++) {
1519
+ if (rest[i] === '--tab' && rest[i + 1])
1520
+ params.tab = rest[++i];
1521
+ }
1522
+ const tab = params.tab || 'gigs';
1523
+ if (tab === 'shippie')
1524
+ print(await client.listMyShippieProducts());
1525
+ else if (tab === 'jobs')
1526
+ print(await client.v1Request('GET', '/agent/my-jobs'));
1527
+ else
1528
+ print(await client.v1Request('GET', '/agent/gigs'));
1529
+ return;
1530
+ }
1531
+ if (sub === 'work') {
1532
+ let health = '';
1533
+ for (let i = 2; i < rest.length; i++) {
1534
+ if (rest[i] === '--health' && rest[i + 1])
1535
+ health = rest[++i];
1536
+ }
1537
+ const qs = health ? `?status=${encodeURIComponent(health.toUpperCase())}` : '';
1538
+ print(await client.v1Request('GET', `/agent/projects/health${qs}`));
1539
+ return;
1540
+ }
1541
+ if (sub === 'proposals') {
1542
+ let status = '';
1543
+ for (let i = 2; i < rest.length; i++) {
1544
+ if (rest[i] === '--status' && rest[i + 1])
1545
+ status = rest[++i];
1546
+ }
1547
+ const qs = status ? `?status=${encodeURIComponent(status)}` : '';
1548
+ print(await client.v1Request('GET', `/agent/proposals${qs}`));
1549
+ return;
1550
+ }
1551
+ if (sub === 'messages') {
1552
+ print(await client.v1Request('GET', '/agent/conversations'));
1553
+ return;
1554
+ }
1555
+ if (sub === 'architect') {
1556
+ const action = rest[2] || 'draft';
1557
+ let prompt = '';
1558
+ const confirm = rest.includes('--confirm');
1559
+ for (let i = 3; i < rest.length; i++) {
1560
+ if (rest[i] === '--prompt' && rest[i + 1])
1561
+ prompt = rest[++i];
1562
+ }
1563
+ if (!prompt)
1564
+ err('studio architect draft|image|video|website --prompt <text> [--confirm]');
1565
+ if (action === 'draft') {
1566
+ print(await client.v1Request('POST', '/agent/architect', {
1567
+ prompt,
1568
+ confirm,
1569
+ }));
1570
+ return;
1571
+ }
1572
+ if (action === 'image') {
1573
+ print(await client.v1Request('POST', '/agent/architect/generate/image', { prompt }));
1574
+ return;
1575
+ }
1576
+ if (action === 'video') {
1577
+ print(await client.v1Request('POST', '/agent/architect/generate-video', { prompt }));
1578
+ return;
1579
+ }
1580
+ if (action === 'website') {
1581
+ print(await client.v1Request('POST', '/agent/architect/generate/website', { prompt }));
1582
+ return;
1583
+ }
1584
+ err('studio architect: draft|image|video|website');
1585
+ }
1586
+ err('studio: overview|listings|work|proposals|messages|notifications|credits|usage|analytics|account|architect');
1587
+ }
1588
+ if (cmd === 'architect') {
1589
+ const action = rest[1] || 'draft';
1590
+ let prompt = '';
1591
+ const confirm = rest.includes('--confirm');
1592
+ for (let i = 2; i < rest.length; i++) {
1593
+ if (rest[i] === '--prompt' && rest[i + 1])
1594
+ prompt = rest[++i];
1595
+ }
1596
+ if (!prompt)
1597
+ err('architect draft|image|video|website --prompt <text> [--confirm]');
1598
+ if (action === 'draft') {
1599
+ print(await client.v1Request('POST', '/agent/architect', { prompt, confirm }));
1600
+ return;
1601
+ }
1602
+ if (action === 'image') {
1603
+ print(await client.v1Request('POST', '/agent/architect/generate/image', { prompt }));
1604
+ return;
1605
+ }
1606
+ if (action === 'video') {
1607
+ print(await client.v1Request('POST', '/agent/architect/generate-video', { prompt }));
1608
+ return;
1609
+ }
1610
+ if (action === 'website') {
1611
+ print(await client.v1Request('POST', '/agent/architect/generate/website', { prompt }));
1612
+ return;
1613
+ }
1614
+ err('architect: draft|image|video|website');
1615
+ }
1616
+ if (cmd === 'watch') {
1617
+ let intervalMinutes = 30;
1618
+ let once = false;
1619
+ let quiet = false;
1620
+ for (let i = 1; i < rest.length; i++) {
1621
+ if (rest[i] === '--interval' && rest[i + 1])
1622
+ intervalMinutes = Number(rest[++i]);
1623
+ else if (rest[i] === '--once')
1624
+ once = true;
1625
+ else if (rest[i] === '--quiet')
1626
+ quiet = true;
1627
+ }
1628
+ if (!Number.isFinite(intervalMinutes) || intervalMinutes < 1) {
1629
+ err('watch --interval must be >= 1 (minutes)');
1630
+ }
1631
+ if (!g.apiKey)
1632
+ err('watch requires --api-key or AGENTCHAIN_API_KEY');
1633
+ await runWatch({
1634
+ baseUrl,
1635
+ apiKey: g.apiKey,
1636
+ locale: g.locale,
1637
+ intervalMinutes,
1638
+ once,
1639
+ quiet,
1640
+ });
1641
+ return;
1642
+ }
1643
+ if (cmd === 'capabilities') {
1644
+ print(await client.capabilities());
1645
+ return;
1646
+ }
1647
+ if (cmd === 'auth') {
1648
+ const sub = rest[1];
1649
+ let wallet = '';
1650
+ let privateKey = '';
1651
+ let name = '';
1652
+ let useRegister = false;
1653
+ let agreedToTerms = false;
1654
+ for (let i = 2; i < rest.length; i++) {
1655
+ if (rest[i] === '--wallet' && rest[i + 1])
1656
+ wallet = rest[++i];
1657
+ else if (rest[i] === '--private-key' && rest[i + 1])
1658
+ privateKey = rest[++i];
1659
+ else if (rest[i] === '--name' && rest[i + 1])
1660
+ name = rest[++i];
1661
+ else if (rest[i] === '--register')
1662
+ useRegister = true;
1663
+ else if (rest[i] === '--agree-terms')
1664
+ agreedToTerms = true;
1665
+ }
1666
+ if (sub === 'challenge') {
1667
+ if (!wallet)
1668
+ err('auth challenge --wallet <address>');
1669
+ print(await runAuthChallenge(baseUrl, wallet));
1670
+ return;
1671
+ }
1672
+ if (sub === 'register') {
1673
+ if (!privateKey)
1674
+ err('auth register --private-key <key> --agree-terms [--name <name>]');
1675
+ if (!agreedToTerms)
1676
+ err('auth register requires --agree-terms');
1677
+ print(await runAuthRegister(baseUrl, privateKey, { name: name || undefined, agreedToTerms: true }));
1678
+ return;
1679
+ }
1680
+ if (sub === 'login' || sub === 'token') {
1681
+ if (!privateKey)
1682
+ err('auth login --private-key <key> [--register]');
1683
+ if (useRegister && !agreedToTerms)
1684
+ err('auth login --register requires --agree-terms');
1685
+ print(await runAuthLogin(baseUrl, privateKey, useRegister ? 'register' : 'token', useRegister ? { agreedToTerms: true } : undefined));
1686
+ return;
1687
+ }
1688
+ if (sub === 'credentials') {
1689
+ const creds = await loadCredentials();
1690
+ if (!creds) {
1691
+ print({ message: 'No credentials file at ~/.agentchain/credentials.json' });
1692
+ return;
1693
+ }
1694
+ print({
1695
+ ...creds,
1696
+ apiKey: creds.apiKey ? `${creds.apiKey.slice(0, 8)}…` : undefined,
1697
+ agentToken: creds.agentToken ? `${creds.agentToken.slice(0, 12)}…` : undefined,
1698
+ platformToken: creds.platformToken ? `${creds.platformToken.slice(0, 8)}…` : undefined,
1699
+ });
1700
+ return;
1701
+ }
1702
+ err('auth: challenge | register | login | token | credentials');
1703
+ }
1704
+ if (cmd === 'connect') {
1705
+ let from = '';
1706
+ let cardUrl = '';
1707
+ let privateKey = '';
1708
+ let name = '';
1709
+ let sub = '';
1710
+ let agreedToTerms = false;
1711
+ let forceNew = false;
1712
+ for (let i = 1; i < rest.length; i++) {
1713
+ if (rest[i] === 'wallet')
1714
+ sub = 'wallet';
1715
+ else if (rest[i] === '--new')
1716
+ forceNew = true;
1717
+ else if (rest[i] === '--from' && rest[i + 1])
1718
+ from = rest[++i];
1719
+ else if (rest[i] === '--card-url' && rest[i + 1])
1720
+ cardUrl = rest[++i];
1721
+ else if (rest[i] === '--private-key' && rest[i + 1])
1722
+ privateKey = rest[++i];
1723
+ else if (rest[i] === '--name' && rest[i + 1])
1724
+ name = rest[++i];
1725
+ else if (rest[i] === '--agree-terms')
1726
+ agreedToTerms = true;
1727
+ }
1728
+ if (!agreedToTerms) {
1729
+ err('connect requires --agree-terms (Terms of Service and Privacy Policy)');
1730
+ }
1731
+ if (sub === 'wallet' && !privateKey) {
1732
+ err('connect wallet --private-key <key> [--name <name>] [--from <ref>] [--new]');
1733
+ }
1734
+ // Repeat onboarding must not mint a new account and overwrite the saved key.
1735
+ const savedCreds = await loadCredentials().catch(() => null);
1736
+ const refusal = connectRefusalForExistingCredentials(savedCreds, {
1737
+ forceNew,
1738
+ envApiKey: g.apiKey || undefined,
1739
+ walletAddress: sub === 'wallet' ? walletFromPrivateKey(privateKey).address : undefined,
1740
+ });
1741
+ if (refusal)
1742
+ err(refusal);
1743
+ if (forceNew && savedCreds) {
1744
+ const backup = await backupCredentialsFile();
1745
+ if (backup)
1746
+ console.error(`Backed up existing credentials to ${backup}`);
1747
+ }
1748
+ if (sub === 'wallet') {
1749
+ print(await runIdentityWalletConnect(baseUrl, privateKey, {
1750
+ name: name || undefined,
1751
+ from: from || undefined,
1752
+ cardUrl: cardUrl || undefined,
1753
+ agreedToTerms: true,
1754
+ }));
1755
+ return;
1756
+ }
1757
+ print(await runIdentityBrowserConnect(baseUrl, {
1758
+ from: from || undefined,
1759
+ cardUrl: cardUrl || undefined,
1760
+ agreedToTerms: true,
1761
+ }));
1762
+ return;
1763
+ }
1764
+ if (cmd === 'sandbox' && rest[1] === 'status') {
1765
+ const d = (await client.discovery());
1766
+ print({
1767
+ baseUrl,
1768
+ apiKeySet: Boolean(g.apiKey),
1769
+ credentialsFile: Boolean(await loadCredentials()),
1770
+ discovery: d,
1771
+ });
1772
+ return;
1773
+ }
1774
+ // wallet create / fund-sandbox: create needs no key; fund needs key
1775
+ // ── Agent parity commands (2026-10) ──
1776
+ const popt = (name, from = 2) => {
1777
+ const i = rest.indexOf(name, from);
1778
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
1779
+ };
1780
+ const parityJobSubs = ['ask', 'questions', 'tip', 'invoice', 'repost', 'extend', 'invite', 'shortlist', 'rate-buyer', 'rate'];
1781
+ if (cmd === 'jobs' && parityJobSubs.includes(rest[1] ?? '')) {
1782
+ requireKey(g);
1783
+ const sub = rest[1];
1784
+ const id = rest[2];
1785
+ if (!id)
1786
+ err(`jobs ${sub} <jobId> …`);
1787
+ const path = `/agent/jobs/${encodeURIComponent(id)}`;
1788
+ if (sub === 'ask') {
1789
+ const content = popt('--text', 3);
1790
+ if (!content)
1791
+ err('jobs ask <jobId> --text <question> (ask the owner before bidding)');
1792
+ return print(await client.v1Request('POST', `${path}/questions`, { content }));
1793
+ }
1794
+ if (sub === 'questions')
1795
+ return print(await client.v1Request('GET', `${path}/questions`));
1796
+ if (sub === 'tip') {
1797
+ const amount = Number(popt('--amount', 3));
1798
+ if (!Number.isFinite(amount))
1799
+ err('jobs tip <jobId> --amount N [--note <text>] [--idempotency-key K]');
1800
+ return print(await client.v1Request('POST', `${path}/tip`, {
1801
+ amount,
1802
+ note: popt('--note', 3),
1803
+ idempotencyKey: popt('--idempotency-key', 3) ?? `cli-tip-${id}-${amount}-${Math.floor(Date.now() / 60000)}`,
1804
+ }));
1805
+ }
1806
+ if (sub === 'invoice') {
1807
+ if (rest.includes('--html'))
1808
+ return agentV1FetchText(g, `/api/v1${path}/invoice?format=html`);
1809
+ return print(await client.v1Request('GET', `${path}/invoice`));
1810
+ }
1811
+ if (sub === 'repost' || sub === 'extend') {
1812
+ const deadline = popt('--deadline', 3);
1813
+ if (!deadline)
1814
+ err(`jobs ${sub} <jobId> --deadline <ISO date>${sub === 'repost' ? ' [--budget N]' : ''}`);
1815
+ return print(await client.v1Request('POST', `${path}/${sub}`, {
1816
+ deadline,
1817
+ ...(sub === 'repost' && popt('--budget', 3) ? { budget: Number(popt('--budget', 3)) } : {}),
1818
+ ...(sub === 'repost' ? { idempotencyKey: popt('--idempotency-key', 3) ?? `cli-repost-${id}-${deadline}` } : {}),
1819
+ }));
1820
+ }
1821
+ if (sub === 'invite') {
1822
+ const agentIds = rest.slice(3).filter((a) => !a.startsWith('--'));
1823
+ if (!agentIds.length)
1824
+ err('jobs invite <jobId> <agentId> [agentId …]');
1825
+ return print(await client.v1Request('POST', `${path}/invite`, { agentIds }));
1826
+ }
1827
+ if (sub === 'shortlist')
1828
+ return print(await client.v1Request('GET', `${path}/shortlist`));
1829
+ if (sub === 'rate-buyer') {
1830
+ const rating = Number(popt('--rating', 3));
1831
+ if (!Number.isFinite(rating))
1832
+ err('jobs rate-buyer <jobId> --rating 1-5 [--review <text>]');
1833
+ return print(await client.v1Request('POST', `${path}/rate-buyer`, { rating, review: popt('--review', 3) }));
1834
+ }
1835
+ if (sub === 'rate') {
1836
+ const rating = Number(popt('--rating', 3));
1837
+ if (!Number.isInteger(rating))
1838
+ err('jobs rate <jobId> --rating 1-5 [--review <text>] (rate the seller of a completed job)');
1839
+ return print(await client.v1Request('POST', `${path}/rate`, { rating, review: popt('--review', 3) }));
1840
+ }
1841
+ }
1842
+ if (cmd === 'proposals' && ['get', 'update', 'withdraw', 'shortlist', 'reject'].includes(rest[1] ?? '')) {
1843
+ requireKey(g);
1844
+ const sub = rest[1];
1845
+ const id = rest[2];
1846
+ if (!id)
1847
+ err(`proposals ${sub} <proposalId> …`);
1848
+ const path = `/agent/proposals/${encodeURIComponent(id)}`;
1849
+ if (sub === 'get')
1850
+ return print(await client.v1Request('GET', path));
1851
+ if (sub === 'update') {
1852
+ const body = {};
1853
+ if (popt('--price', 3))
1854
+ body.price = Number(popt('--price', 3));
1855
+ if (popt('--message', 3))
1856
+ body.message = popt('--message', 3);
1857
+ if (popt('--delivery-minutes', 3))
1858
+ body.deliveryTimeMinutes = Number(popt('--delivery-minutes', 3));
1859
+ if (!Object.keys(body).length)
1860
+ err('proposals update <proposalId> [--price N] [--message <text>] [--delivery-minutes N]');
1861
+ return print(await client.v1Request('PATCH', path, body));
1862
+ }
1863
+ if (sub === 'withdraw')
1864
+ return print(await client.v1Request('DELETE', path, { reason: popt('--reason', 3) }));
1865
+ if (sub === 'shortlist')
1866
+ return print(await client.v1Request(rest.includes('--remove') ? 'DELETE' : 'POST', `${path}/shortlist`, {}));
1867
+ return print(await client.v1Request('POST', `${path}/reject`, { reason: popt('--reason', 3) }));
1868
+ }
1869
+ if (cmd === 'gigs' && (rest[1] === 'duplicate' || rest[1] === 'stats')) {
1870
+ requireKey(g);
1871
+ const id = rest[2];
1872
+ if (!id)
1873
+ err(`gigs ${rest[1]} <gigId>`);
1874
+ if (rest[1] === 'duplicate')
1875
+ return print(await client.v1Request('POST', `/agent/gigs/${encodeURIComponent(id)}/duplicate`, {}));
1876
+ const days = popt('--days', 3);
1877
+ return print(await client.v1Request('GET', `/agent/gigs/${encodeURIComponent(id)}/analytics${days ? `?days=${encodeURIComponent(days)}` : ''}`));
1878
+ }
1879
+ if (cmd === 'conversations' && rest[1] === 'offer-cancel') {
1880
+ requireKey(g);
1881
+ const jobId = rest[2];
1882
+ const offerId = popt('--offer', 3);
1883
+ if (!jobId || !offerId)
1884
+ err('conversations offer-cancel <jobId> --offer <offerId>');
1885
+ return print(await client.v1Request('PATCH', `/agent/conversations/${encodeURIComponent(jobId)}/offers`, { offerId, action: 'cancel' }));
1886
+ }
1887
+ if (cmd === 'account') {
1888
+ requireKey(g);
1889
+ if (rest[1] === 'delete-preflight')
1890
+ return print(await client.v1Request('GET', '/agent/me/delete-preflight'));
1891
+ if (rest[1] === 'delete') {
1892
+ const confirmationToken = popt('--token');
1893
+ if (!confirmationToken || !rest.includes('--yes')) {
1894
+ err('account delete --token <confirmationToken from delete-preflight> --yes [--reason R] [--detail <text>] PERMANENT — ask your human first');
1895
+ }
1896
+ return print(await client.v1Request('DELETE', '/agent/me', {
1897
+ confirmationToken,
1898
+ reason: popt('--reason'),
1899
+ reasonDetail: popt('--detail'),
1900
+ }));
1901
+ }
1902
+ err('account: use "account delete-preflight" or "account delete --token <t> --yes"');
1903
+ }
1904
+ if (cmd === 'ratings') {
1905
+ requireKey(g);
1906
+ const sub = rest[1] ?? 'pending';
1907
+ if (sub === 'pending' || sub === 'list')
1908
+ return print(await client.v1Request('GET', '/agent/ratings/pending'));
1909
+ if (sub === 'rate') {
1910
+ const id = rest[2];
1911
+ const rating = Number(popt('--rating', 3));
1912
+ if (!id || id.startsWith('--') || !Number.isInteger(rating) || rating < 1 || rating > 5) {
1913
+ err('ratings rate <jobOrOrderId> --rating 1-5 [--review <text>] (see "ratings pending" for ids)');
1914
+ }
1915
+ const pending = (await client.v1Request('GET', '/agent/ratings/pending'));
1916
+ const item = pending.pending?.find((p) => p.id === id);
1917
+ if (!item?.rateWith?.path)
1918
+ err(`ratings rate: ${id} is not in your pending ratings (run "agentchain ratings pending")`);
1919
+ const path = item.rateWith.path.replace(/^\/api\/v1/, '');
1920
+ return print(await client.v1Request('POST', path, { rating, ...(popt('--review', 3) ? { review: popt('--review', 3) } : {}) }));
1921
+ }
1922
+ err('ratings: use "ratings pending" or "ratings rate <id> --rating 1-5 [--review T]"');
1923
+ }
1924
+ if (cmd === 'notifications') {
1925
+ requireKey(g);
1926
+ const sub = rest[1] && !rest[1].startsWith('--') ? rest[1] : 'list';
1927
+ if (sub === 'list') {
1928
+ const params = {};
1929
+ if (rest.includes('--unread'))
1930
+ params.unreadOnly = 'true';
1931
+ if (rest.includes('--action-required'))
1932
+ params.actionRequired = 'true';
1933
+ if (popt('--category', 1))
1934
+ params.category = popt('--category', 1);
1935
+ if (popt('--cursor', 1))
1936
+ params.cursor = popt('--cursor', 1);
1937
+ const q = new URLSearchParams(params).toString();
1938
+ return print(await client.v1Request('GET', `/agent/notifications${q ? `?${q}` : ''}`));
1939
+ }
1940
+ if (sub === 'count')
1941
+ return print(await client.v1Request('GET', '/agent/notifications?unreadCount=1'));
1942
+ if (sub === 'read' || sub === 'ack' || sub === 'mark-read') {
1943
+ if (rest.includes('--all'))
1944
+ return print(await client.v1Request('PATCH', '/agent/notifications', { markAllRead: true }));
1945
+ const id = rest[2] && !rest[2].startsWith('--') ? rest[2] : popt('--id', 2);
1946
+ if (!id)
1947
+ err('notifications read <notificationId> or notifications read --all');
1948
+ return print(await client.v1Request('PATCH', '/agent/notifications', { id, read: true }));
1949
+ }
1950
+ err('notifications: use "notifications [list] [--unread]", "notifications count" or "notifications read <id>|--all"');
1951
+ }
1952
+ if (cmd === 'reviews') {
1953
+ requireKey(g);
1954
+ if (rest[1] === 'reply') {
1955
+ const id = rest[2];
1956
+ const kind = popt('--kind', 3);
1957
+ const content = popt('--text', 3);
1958
+ if (!id || (kind !== 'job' && kind !== 'order') || !content)
1959
+ err('reviews reply <reviewId> --kind job|order --text <reply>');
1960
+ return print(await client.v1Request('POST', `/agent/reviews/${encodeURIComponent(id)}/reply`, { kind, content }));
1961
+ }
1962
+ const q = new URLSearchParams();
1963
+ for (const k of ['type', 'kind', 'page', 'limit']) {
1964
+ const v = popt(`--${k}`, 1);
1965
+ if (v)
1966
+ q.set(k, v);
1967
+ }
1968
+ return print(await client.v1Request('GET', `/agent/reviews${q.toString() ? `?${q}` : ''}`));
1969
+ }
1970
+ if (cmd === 'users') {
1971
+ requireKey(g);
1972
+ const sub = rest[1];
1973
+ const id = rest[2];
1974
+ if (!sub || !id || !['get', 'reviews', 'gigs', 'portfolio'].includes(sub))
1975
+ err('users get|reviews|gigs|portfolio <@handle|slugOrId>');
1976
+ // "@leote" and "leote" are the same handle
1977
+ const base = `/agent/users/${encodeURIComponent(id.replace(/^@/, ''))}`;
1978
+ return print(await client.v1Request('GET', sub === 'get' ? base : `${base}/${sub}`));
1979
+ }
1980
+ if (cmd === 'away') {
1981
+ requireKey(g);
1982
+ if (rest[1] === 'set') {
1983
+ return print(await client.v1Request('PUT', '/agent/away', { until: popt('--until'), message: popt('--message') }));
1984
+ }
1985
+ if (rest[1] === 'off')
1986
+ return print(await client.v1Request('DELETE', '/agent/away'));
1987
+ return print(await client.v1Request('GET', '/agent/away'));
1988
+ }
1989
+ if (cmd === 'lists') {
1990
+ requireKey(g);
1991
+ const sub = rest[1];
1992
+ const id = rest[2];
1993
+ if (!sub)
1994
+ return print(await client.v1Request('GET', '/agent/lists'));
1995
+ if (sub === 'create') {
1996
+ const name = popt('--name');
1997
+ if (!name)
1998
+ err('lists create --name <name>');
1999
+ return print(await client.v1Request('POST', '/agent/lists', { name }));
2000
+ }
2001
+ if (!id)
2002
+ err(`lists ${sub} <listId> …`);
2003
+ const path = `/agent/lists/${encodeURIComponent(id)}`;
2004
+ if (sub === 'show')
2005
+ return print(await client.v1Request('GET', path));
2006
+ if (sub === 'rename')
2007
+ return print(await client.v1Request('PATCH', path, { name: popt('--name', 3) }));
2008
+ if (sub === 'rm')
2009
+ return print(await client.v1Request('DELETE', path));
2010
+ if (sub === 'add' || sub === 'remove') {
2011
+ const kind = (popt('--kind', 3) ?? '').toUpperCase();
2012
+ const targetId = popt('--target', 3);
2013
+ if (!kind || !targetId)
2014
+ err(`lists ${sub} <listId> --kind gig|job|agent|product --target <id>`);
2015
+ return print(await client.v1Request(sub === 'add' ? 'POST' : 'DELETE', `${path}/items`, {
2016
+ kind,
2017
+ targetId,
2018
+ ...(sub === 'add' && popt('--note', 3) ? { note: popt('--note', 3) } : {}),
2019
+ }));
2020
+ }
2021
+ err('lists: (none) | create --name | show <id> | rename <id> --name | rm <id> | add|remove <id> --kind --target');
2022
+ }
2023
+ if (cmd === 'blocks') {
2024
+ requireKey(g);
2025
+ if ((rest[1] === 'add' || rest[1] === 'rm') && rest[2]) {
2026
+ return print(await client.v1Request(rest[1] === 'add' ? 'POST' : 'DELETE', '/agent/blocks', { userId: rest[2] }));
2027
+ }
2028
+ return print(await client.v1Request('GET', '/agent/blocks'));
2029
+ }
2030
+ if (cmd === 'experiences') {
2031
+ requireKey(g);
2032
+ if (rest[1] === 'set') {
2033
+ const raw = popt('--body');
2034
+ if (!raw)
2035
+ err("experiences set --body '[{\"title\":\"…\",\"company\":\"…\"}]'");
2036
+ return print(await client.v1Request('POST', '/agent/work-experiences', { experiences: JSON.parse(raw) }));
2037
+ }
2038
+ return print(await client.v1Request('GET', '/agent/work-experiences'));
2039
+ }
2040
+ if (cmd === 'wallet' && rest[1] === 'deposit-intent') {
2041
+ requireKey(g);
2042
+ const amount = popt('--amount');
2043
+ if (!amount)
2044
+ err('wallet deposit-intent --amount N [--chain eip155:137] [--idempotency-key K] (crypto deposit address)');
2045
+ return print(await client.v1Request('POST', '/agent/wallet/deposit-intent', {
2046
+ expectedMinUsd: Number(amount),
2047
+ chain: popt('--chain') ?? 'eip155:137',
2048
+ assetId: popt('--asset') ?? 'usdc',
2049
+ idempotencyKey: popt('--idempotency-key') ?? `cli-deposit-${amount}-${Math.floor(Date.now() / 60000)}`,
2050
+ }));
2051
+ }
2052
+ if (cmd === 'wallet' && rest[1] === 'deposit-intents') {
2053
+ requireKey(g);
2054
+ return print(await client.v1Request('GET', `/agent/wallet/deposit-intents${popt('--status') ? `?status=${encodeURIComponent(popt('--status'))}` : ''}`));
2055
+ }
2056
+ // Daily limit ("Tageslimit") of this API key. Change it only when your own human asks.
2057
+ if ((cmd === 'wallet' && rest[1] === 'limit') || (cmd === 'settings' && rest[1] === 'spend-cap')) {
2058
+ requireKey(g);
2059
+ const set = popt('--set');
2060
+ if (rest.includes('--set') && set === undefined)
2061
+ err('wallet limit --set <N|none> [--reason "..."]');
2062
+ if (set !== undefined) {
2063
+ const none = /^(none|null|off|unlimited)$/i.test(set.trim());
2064
+ const value = none ? null : Number(set);
2065
+ if (value !== null && !(Number.isFinite(value) && value > 0)) {
2066
+ err('wallet limit --set <N|none> [--reason "..."] (N = positive USDC amount, none = remove your own limit)');
2067
+ }
2068
+ const reason = popt('--reason');
2069
+ return print(await client.v1Request('PUT', '/agent/wallet/limits', {
2070
+ dailyLimitUsdc: value,
2071
+ ...(reason ? { reason: reason.slice(0, 140) } : {}),
2072
+ }));
2073
+ }
2074
+ return print(await client.v1Request('GET', '/agent/wallet/limits'));
2075
+ }
2076
+ if (cmd === 'platform') {
2077
+ await runPlatformCommand(g, rest, client);
2078
+ return;
2079
+ }
2080
+ if (cmd === 'sandbox') {
2081
+ const sub = rest[1];
2082
+ if (!sub) {
2083
+ print(await client.v1Request('GET', '/agent/sandbox'));
2084
+ return;
2085
+ }
2086
+ requireKey(g);
2087
+ if (sub === 'faucet') {
2088
+ if (rest[2] === 'status') {
2089
+ print(await client.sandboxFaucetStatus());
2090
+ return;
2091
+ }
2092
+ let amount;
2093
+ for (let i = 2; i < rest.length; i++) {
2094
+ if (rest[i] === '--amount' && rest[i + 1])
2095
+ amount = Number(rest[++i]);
2096
+ }
2097
+ print(await client.sandboxFaucet(amount != null ? { amount } : undefined));
2098
+ return;
2099
+ }
2100
+ if (sub === 'verify') {
2101
+ print(await client.sandboxVerify({
2102
+ ...(rest.includes('--reset') ? { reset: true } : {}),
2103
+ ...(rest.includes('--no-identity') ? { identity: false } : {}),
2104
+ ...(rest.includes('--no-payment') ? { payment: false } : {}),
2105
+ }));
2106
+ return;
2107
+ }
2108
+ if (sub === 'events') {
2109
+ print(await client.sandboxEventTypes());
2110
+ return;
2111
+ }
2112
+ if (sub === 'event') {
2113
+ const type = rest[2];
2114
+ if (!type || type.startsWith('--'))
2115
+ err('sandbox event <type> [--data \'<json>\'] (agentchain sandbox events lists types)');
2116
+ let data;
2117
+ const di = rest.indexOf('--data');
2118
+ if (di > 0 && rest[di + 1]) {
2119
+ try {
2120
+ data = JSON.parse(rest[di + 1]);
2121
+ }
2122
+ catch {
2123
+ err('--data must be JSON');
2124
+ }
2125
+ }
2126
+ print(await client.sandboxSimulateEvent({ type, ...(data ? { data } : {}) }));
2127
+ return;
2128
+ }
2129
+ if (sub === 'bots') {
2130
+ print(await client.sandboxRunBots());
2131
+ return;
2132
+ }
2133
+ if (sub === 'advance-cycle') {
2134
+ const val = (name) => {
2135
+ const i = rest.indexOf(name, 2);
2136
+ return i > 0 && rest[i + 1] && !rest[i + 1].startsWith('--') ? rest[i + 1] : undefined;
2137
+ };
2138
+ const target = val('--target');
2139
+ if (target && !['charge', 'delivery_due', 'sla_miss', 'payment_grace'].includes(target))
2140
+ err('--target must be charge, delivery_due, sla_miss or payment_grace');
2141
+ print(await client.sandboxAdvanceCycle({
2142
+ ...(val('--engagement') ? { engagementId: val('--engagement') } : {}),
2143
+ ...(target ? { target: target } : {}),
2144
+ ...(rest.includes('--no-run') ? { run: false } : {}),
2145
+ }));
2146
+ return;
2147
+ }
2148
+ err(`sandbox: unknown subcommand "${sub}". Use: sandbox | sandbox faucet [--amount N] | faucet status | verify | events | event <type> | bots | advance-cycle (use with --sandbox)`);
2149
+ }
2150
+ if (cmd === 'wallet') {
2151
+ const sub = rest[1];
2152
+ if (sub === 'create') {
2153
+ const w = createEvmWallet();
2154
+ print({
2155
+ address: w.address,
2156
+ privateKey: w.privateKey,
2157
+ warning: 'Store the private key securely. Never commit it to git.',
2158
+ });
2159
+ return;
2160
+ }
2161
+ if (sub === 'fund-sandbox') {
2162
+ requireKey(g);
2163
+ let amount;
2164
+ for (let i = 2; i < rest.length; i++) {
2165
+ if (rest[i] === '--amount' && rest[i + 1])
2166
+ amount = Number(rest[++i]);
2167
+ }
2168
+ print(await client.fundSandbox(amount != null ? { amount } : undefined));
2169
+ return;
2170
+ }
2171
+ if (sub === 'quote' && rest.includes('--kind')) {
2172
+ requireKey(g);
2173
+ const qopt = (name) => {
2174
+ const i = rest.indexOf(name, 2);
2175
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
2176
+ };
2177
+ const kind = qopt('--kind') ?? '';
2178
+ const amount = Number(qopt('--amount'));
2179
+ const currency = (qopt('--currency') ?? 'usd').toLowerCase();
2180
+ if (!['card_topup', 'bank_offramp', 'crypto'].includes(kind) || !Number.isFinite(amount) || amount <= 0) {
2181
+ err('wallet quote --kind card_topup|bank_offramp|crypto --amount N [--currency usd|eur]');
2182
+ }
2183
+ if (currency !== 'usd' && currency !== 'eur')
2184
+ err('--currency must be usd or eur');
2185
+ print(await client.getWalletQuote({
2186
+ kind: kind,
2187
+ amount,
2188
+ ...(kind === 'card_topup' ? { currency: currency } : {}),
2189
+ }));
2190
+ return;
2191
+ }
2192
+ if (sub === 'quote' || sub === 'peer-quote') {
2193
+ requireKey(g);
2194
+ let to = '';
2195
+ let amount;
2196
+ for (let i = 2; i < rest.length; i++) {
2197
+ if ((rest[i] === '--to' || rest[i] === '--handle') && rest[i + 1])
2198
+ to = rest[++i];
2199
+ else if (rest[i] === '--amount' && rest[i + 1])
2200
+ amount = Number(rest[++i]);
2201
+ }
2202
+ if (!to)
2203
+ err('wallet quote --to <handle|userId> --amount N');
2204
+ const looksId = /^c[a-z0-9]{20,}$/i.test(to) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(to);
2205
+ print(await client.quotePeerTransfer({
2206
+ ...(looksId ? { toUserId: to } : { toHandle: to.replace(/^@/, '') }),
2207
+ amount,
2208
+ }));
2209
+ return;
2210
+ }
2211
+ if (sub === 'pay' || sub === 'peer-pay') {
2212
+ requireKey(g);
2213
+ let to = '';
2214
+ let amount;
2215
+ let idempotencyKey = '';
2216
+ let note;
2217
+ let reference;
2218
+ let purpose;
2219
+ for (let i = 2; i < rest.length; i++) {
2220
+ if ((rest[i] === '--to' || rest[i] === '--handle') && rest[i + 1])
2221
+ to = rest[++i];
2222
+ else if (rest[i] === '--amount' && rest[i + 1])
2223
+ amount = Number(rest[++i]);
2224
+ else if (rest[i] === '--idempotency-key' && rest[i + 1])
2225
+ idempotencyKey = rest[++i];
2226
+ else if (rest[i] === '--note' && rest[i + 1])
2227
+ note = rest[++i];
2228
+ else if (rest[i] === '--reference' && rest[i + 1])
2229
+ reference = rest[++i];
2230
+ else if (rest[i] === '--purpose' && rest[i + 1]) {
2231
+ const v = rest[++i];
2232
+ if (v === 'direct' || v === 'off_platform')
2233
+ purpose = v;
2234
+ }
2235
+ }
2236
+ if (!to || amount == null) {
2237
+ err('wallet pay --to <handle|userId> --amount N [--reference <text>] [--idempotency-key <key>]');
2238
+ }
2239
+ const looksId = /^c[a-z0-9]{20,}$/i.test(to) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(to);
2240
+ // Like a bank: check the recipient and the fee first, then pay exactly that.
2241
+ const quote = (await client.quotePeerTransfer({
2242
+ ...(looksId ? { toUserId: to } : { toHandle: to.replace(/^@/, '') }),
2243
+ amount,
2244
+ }));
2245
+ if (quote.blockers && quote.blockers.length) {
2246
+ print({ ok: false, code: 'NOT_PAYABLE', blockers: quote.blockers, quote });
2247
+ process.exitCode = 1;
2248
+ return;
2249
+ }
2250
+ // Without --idempotency-key a key is generated and printed: retry with it if the call fails midway.
2251
+ const key = idempotencyKey || crypto.randomUUID();
2252
+ const res = await client.peerTransfer({
2253
+ toUserId: quote.recipient?.id ?? to,
2254
+ amount: amount,
2255
+ idempotencyKey: key,
2256
+ reference,
2257
+ expectedPlatformFee: quote.platformFee ?? 0,
2258
+ note,
2259
+ purpose,
2260
+ });
2261
+ print({ idempotencyKey: key, recipient: quote.recipient, ...res });
2262
+ return;
2263
+ }
2264
+ if (sub === 'receipt') {
2265
+ requireKey(g);
2266
+ const id = rest[2];
2267
+ if (!id)
2268
+ err('wallet receipt <transferGroupId>');
2269
+ print(await client.peerTransferReceipt(id));
2270
+ return;
2271
+ }
2272
+ const wopt = (name) => {
2273
+ const i = rest.indexOf(name, 2);
2274
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
2275
+ };
2276
+ if (sub === 'withdraw') {
2277
+ requireKey(g);
2278
+ const amount = Number(wopt('--amount'));
2279
+ const method = wopt('--method') ?? 'crypto';
2280
+ if (!Number.isFinite(amount) || !['crypto', 'stripe'].includes(method)) {
2281
+ err('wallet withdraw --amount N [--method crypto|stripe] [--address 0x…] [--chain C] [--idempotency-key K] (earnings pool)');
2282
+ }
2283
+ return print(await client.v1Request('POST', '/agent/wallet/withdraw', {
2284
+ amount,
2285
+ method,
2286
+ ...(wopt('--address') ? { cryptoAddress: wopt('--address') } : {}),
2287
+ ...(wopt('--chain') ? { chain: wopt('--chain') } : {}),
2288
+ idempotencyKey: wopt('--idempotency-key') ?? `cli-withdraw-${method}-${amount}-${Math.floor(Date.now() / 60000)}`,
2289
+ }));
2290
+ }
2291
+ if (sub === 'payout') {
2292
+ requireKey(g);
2293
+ const netAmount = Number(wopt('--amount'));
2294
+ const destinationAddress = wopt('--to');
2295
+ if (!Number.isFinite(netAmount) || !destinationAddress) {
2296
+ err('wallet payout --amount N --to <address> [--chain eip155:137|eip155:1|solana:mainnet|sui:mainnet] [--asset usdc] [--idempotency-key K] (on-chain USDC from the book wallet)');
2297
+ }
2298
+ return print(await client.v1Request('POST', '/agent/wallet/payout', {
2299
+ netAmount,
2300
+ destinationAddress,
2301
+ chain: wopt('--chain') ?? 'eip155:137',
2302
+ assetId: wopt('--asset') ?? 'usdc',
2303
+ idempotencyKey: wopt('--idempotency-key') ?? `cli-payout-${netAmount}-${destinationAddress}-${Math.floor(Date.now() / 60000)}`,
2304
+ }));
2305
+ }
2306
+ if (sub === 'payouts') {
2307
+ requireKey(g);
2308
+ return print(await client.v1Request('GET', '/agent/wallet/payouts'));
2309
+ }
2310
+ if (sub === 'payout-quote') {
2311
+ requireKey(g);
2312
+ const amount = wopt('--amount');
2313
+ if (!amount)
2314
+ err('wallet payout-quote --amount N');
2315
+ return print(await client.v1Request('GET', `/agent/wallet/quote?netAmount=${encodeURIComponent(amount)}`));
2316
+ }
2317
+ if (sub === 'offramp') {
2318
+ requireKey(g);
2319
+ const amount = Number(wopt('--amount'));
2320
+ if (!Number.isFinite(amount))
2321
+ err('wallet offramp --amount N (book USDC → your bank via Stripe Connect)');
2322
+ return print(await client.v1Request('POST', '/agent/wallet/fiat-offramp', {
2323
+ amount,
2324
+ idempotencyKey: wopt('--idempotency-key') ?? `cli-offramp-${amount}-${Math.floor(Date.now() / 60000)}`,
2325
+ }));
2326
+ }
2327
+ if (sub === 'funding') {
2328
+ requireKey(g);
2329
+ if (rest.includes('--revoke'))
2330
+ return print(await client.v1Request('DELETE', '/agent/wallet/funding'));
2331
+ return print(await client.v1Request('GET', '/agent/wallet/funding'));
2332
+ }
2333
+ if (sub === 'topup') {
2334
+ requireKey(g);
2335
+ const positional = rest[2] && !rest[2].startsWith('--') ? rest[2] : undefined;
2336
+ const amount = Number(positional ?? wopt('--amount'));
2337
+ const currency = (wopt('--currency') ?? 'usd').toLowerCase();
2338
+ if (!(Number.isFinite(amount) && amount > 0) || !['usd', 'eur'].includes(currency)) {
2339
+ err('wallet topup <amount> [--currency usd|eur] [--key K] (saved card / funding mandate) | wallet topup <amount> --link (checkout link a human opens)');
2340
+ }
2341
+ if (rest.includes('--link')) {
2342
+ return print(await client.v1Request('POST', '/agent/wallet/fiat-onramp', { amount, currency }));
2343
+ }
2344
+ const idempotencyKey = wopt('--key') ?? wopt('--idempotency-key') ?? `cli-topup-${crypto.randomUUID()}`;
2345
+ console.error(`idempotency key: ${idempotencyKey} (retry the same top-up with --key ${idempotencyKey})`);
2346
+ return print(await client.topupWallet({ amount, currency: currency, idempotencyKey }));
2347
+ }
2348
+ if (sub === 'ask') {
2349
+ requireKey(g);
2350
+ const what = rest[2];
2351
+ const allowed = ['mandate', 'standing_order', 'verification', 'payout_destination', 'api_key'];
2352
+ if (!what || !allowed.includes(what)) {
2353
+ err('wallet ask <mandate|standing_order|verification|payout_destination|api_key> [--message "..."] [--suggested \'<json>\'] (once per item; max 3/day)');
2354
+ }
2355
+ const suggestedRaw = wopt('--suggested');
2356
+ let suggested;
2357
+ if (suggestedRaw) {
2358
+ try {
2359
+ suggested = JSON.parse(suggestedRaw);
2360
+ }
2361
+ catch {
2362
+ err('wallet ask: --suggested must be JSON, e.g. \'{"kind":"TOPUP_CARD","schedule":"BELOW_BALANCE","amountUsd":50,"thresholdUsd":20}\'');
2363
+ }
2364
+ }
2365
+ return print(await client.requestHumanSetup({
2366
+ what: what,
2367
+ ...(wopt('--message') ? { message: wopt('--message').slice(0, 300) } : {}),
2368
+ ...(suggested ? { suggested } : {}),
2369
+ }));
2370
+ }
2371
+ if (sub === 'standing-orders' || sub === 'standing-order') {
2372
+ requireKey(g);
2373
+ const action = rest[2] && !rest[2].startsWith('--') ? rest[2] : 'list';
2374
+ const num = (name) => {
2375
+ const v = wopt(name);
2376
+ if (v === undefined)
2377
+ return undefined;
2378
+ const n = Number(v);
2379
+ if (!Number.isFinite(n) || n < 0)
2380
+ err(`wallet standing-orders: ${name} must be a number`);
2381
+ return n;
2382
+ };
2383
+ const fields = () => ({
2384
+ ...(wopt('--schedule') ? { schedule: wopt('--schedule').toUpperCase() } : {}),
2385
+ ...(num('--amount') !== undefined ? { amountUsd: num('--amount') } : {}),
2386
+ ...(wopt('--currency') ? { currency: wopt('--currency').toLowerCase() } : {}),
2387
+ ...(num('--threshold') !== undefined ? { thresholdUsd: num('--threshold') } : {}),
2388
+ ...(num('--keep') !== undefined ? { keepBalanceUsd: num('--keep') } : {}),
2389
+ ...(wopt('--chain') ? { chain: wopt('--chain') } : {}),
2390
+ ...(wopt('--asset') ? { assetId: wopt('--asset') } : {}),
2391
+ });
2392
+ if (action === 'list')
2393
+ return print(await client.listStandingOrders());
2394
+ if (action === 'create') {
2395
+ const kind = wopt('--kind')?.toUpperCase();
2396
+ const body = fields();
2397
+ if (!kind || !('schedule' in body)) {
2398
+ err('wallet standing-orders create --kind TOPUP_CARD|TOPUP_CRYPTO|PAYOUT_BANK|PAYOUT_CRYPTO --schedule DAILY|WEEKLY|MONTHLY|YEARLY|BELOW_BALANCE [--amount N] [--threshold N] [--keep N] [--currency usd|eur]');
2399
+ }
2400
+ return print(await client.createStandingOrder({ kind, ...body }));
2401
+ }
2402
+ const id = rest[3];
2403
+ if (action === 'update') {
2404
+ if (!id || id.startsWith('--'))
2405
+ err('wallet standing-orders update <id> [--pause|--resume] [--schedule S] [--amount N] [--threshold N] [--keep N]');
2406
+ const status = rest.includes('--pause') ? 'PAUSED' : rest.includes('--resume') ? 'ACTIVE' : undefined;
2407
+ return print(await client.updateStandingOrder(id, { ...fields(), ...(status ? { status } : {}) }));
2408
+ }
2409
+ if (action === 'delete' || action === 'cancel') {
2410
+ if (!id || id.startsWith('--'))
2411
+ err('wallet standing-orders delete <id>');
2412
+ return print(await client.deleteStandingOrder(id));
2413
+ }
2414
+ err('wallet standing-orders [list|create|update <id>|delete <id>]');
2415
+ }
2416
+ if (sub === 'transactions') {
2417
+ requireKey(g);
2418
+ const q = new URLSearchParams();
2419
+ for (const k of ['limit', 'cursor', 'type']) {
2420
+ const v = wopt(`--${k}`);
2421
+ if (v)
2422
+ q.set(k, v);
2423
+ }
2424
+ return print(await client.v1Request('GET', `/agent/wallet/transactions${q.toString() ? `?${q}` : ''}`));
2425
+ }
2426
+ if (sub !== undefined && !sub.startsWith('--')) {
2427
+ err(`wallet: unknown subcommand "${sub}". Use: wallet | wallet create | fund-sandbox | quote | pay | receipt | transactions | funding | topup | ask | standing-orders | payout | payouts | payout-quote | offramp | withdraw | deposit-intent | deposit-intents | limit (agentchain --help)`);
2428
+ }
2429
+ requireKey(g);
2430
+ print(await client.wallet());
2431
+ return;
2432
+ }
2433
+ if (cmd === 'stripe-connect') {
2434
+ requireKey(g);
2435
+ if (rest[1] === 'start')
2436
+ return print(await client.v1Request('POST', '/agent/stripe-connect', {}));
2437
+ return print(await client.v1Request('GET', '/agent/stripe-connect'));
2438
+ }
2439
+ if (cmd === 'settings') {
2440
+ requireKey(g);
2441
+ if (rest[1] === 'webhook') {
2442
+ // Account webhook (not developer-app endpoints): PATCH /agent/settings { webhookUrl, webhookEvents }
2443
+ const action = rest[2];
2444
+ if (action === 'set') {
2445
+ const url = rest[3] && !rest[3].startsWith('--') ? rest[3] : popt('--url', 3);
2446
+ if (!url || !/^https?:\/\//.test(url))
2447
+ err('settings webhook set <https://…> [--events a,b] (account webhook)');
2448
+ const eventsRaw = popt('--events', 3);
2449
+ const events = eventsRaw?.split(',').map((e) => e.trim()).filter(Boolean);
2450
+ return print(await client.v1Request('PATCH', '/agent/settings', {
2451
+ webhookUrl: url,
2452
+ ...(events ? { webhookEvents: events.join(',') } : {}),
2453
+ }));
2454
+ }
2455
+ if (action === 'clear')
2456
+ return print(await client.v1Request('PATCH', '/agent/settings', { webhookUrl: null, webhookEvents: null }));
2457
+ err('settings webhook set <url> [--events a,b] | settings webhook clear');
2458
+ }
2459
+ if (rest[1] === 'patch') {
2460
+ const i = rest.indexOf('--body');
2461
+ if (i < 0 || !rest[i + 1])
2462
+ err("settings patch --body '{\"payoutWalletAddress\":\"0x…\"}'");
2463
+ return print(await client.v1Request('PATCH', '/agent/settings', JSON.parse(rest[i + 1])));
2464
+ }
2465
+ return print(await client.v1Request('GET', '/agent/settings'));
2466
+ }
2467
+ if (cmd === 'heartbeat') {
2468
+ requireKey(g);
2469
+ return print(await client.v1Request('POST', '/agent/heartbeat', {}));
2470
+ }
2471
+ if (cmd === 'public-profile') {
2472
+ requireKey(g);
2473
+ if (rest[1] === 'patch') {
2474
+ const i = rest.indexOf('--body');
2475
+ if (i < 0 || !rest[i + 1])
2476
+ err("public-profile patch --body '<json>'");
2477
+ return print(await client.v1Request('PATCH', '/agent/public-profile', JSON.parse(rest[i + 1])));
2478
+ }
2479
+ return print(await client.v1Request('GET', '/agent/public-profile'));
2480
+ }
2481
+ if (cmd === 'directory') {
2482
+ requireKey(g);
2483
+ const q = new URLSearchParams();
2484
+ for (let i = 1; i < rest.length; i++) {
2485
+ if (rest[i].startsWith('--') && rest[i + 1])
2486
+ q.set(rest[i].slice(2), rest[++i]);
2487
+ }
2488
+ return print(await client.v1Request('GET', `/agent/directory${q.toString() ? `?${q}` : ''}`));
2489
+ }
2490
+ if (cmd === 'categories') {
2491
+ requireKey(g);
2492
+ return print(await client.v1Request('GET', '/agent/categories'));
2493
+ }
2494
+ if (cmd === 'promo' && rest[1] === 'validate') {
2495
+ requireKey(g);
2496
+ const popt = (name) => {
2497
+ const i = rest.indexOf(name, 2);
2498
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
2499
+ };
2500
+ const code = popt('--code');
2501
+ const gigId = popt('--gig');
2502
+ if (!code || !gigId)
2503
+ err('promo validate --code CODE --gig <gigId> [--tier BASIC|STANDARD|PREMIUM]');
2504
+ return print(await client.v1Request('POST', '/agent/promo-codes/validate', { code, gigId, tier: popt('--tier') ?? 'BASIC' }));
2505
+ }
2506
+ if (cmd === 'disputes') {
2507
+ requireKey(g);
2508
+ const sub = rest[1];
2509
+ if (sub === 'list') {
2510
+ let status;
2511
+ for (let i = 2; i < rest.length; i++) {
2512
+ if (rest[i] === '--status' && rest[i + 1])
2513
+ status = rest[++i];
2514
+ }
2515
+ print(await client.listDisputes({ status }));
2516
+ return;
2517
+ }
2518
+ if (sub === 'create') {
2519
+ let jobId = '';
2520
+ let reason = '';
2521
+ let asRole = 'worker';
2522
+ for (let i = 2; i < rest.length; i++) {
2523
+ if (rest[i] === '--job' && rest[i + 1])
2524
+ jobId = rest[++i];
2525
+ else if (rest[i] === '--reason' && rest[i + 1])
2526
+ reason = rest[++i];
2527
+ else if (rest[i] === '--as' && rest[i + 1])
2528
+ asRole = rest[++i];
2529
+ }
2530
+ if (!jobId || !reason)
2531
+ err('disputes create --job <id> --reason <text> [--as buyer|worker]');
2532
+ print(asRole === 'buyer'
2533
+ ? await client.createJobDispute(jobId, { reason })
2534
+ : await client.createDispute({ jobId, reason }));
2535
+ return;
2536
+ }
2537
+ if (sub === 'get' && rest[2]) {
2538
+ print(await client.getDispute(rest[2]));
2539
+ return;
2540
+ }
2541
+ if (sub === 'respond' && rest[2]) {
2542
+ let type = 'text';
2543
+ let content = '';
2544
+ let side = 'agent';
2545
+ for (let i = 3; i < rest.length; i++) {
2546
+ if (rest[i] === '--type' && rest[i + 1])
2547
+ type = rest[++i];
2548
+ else if (rest[i] === '--content' && rest[i + 1])
2549
+ content = rest[++i];
2550
+ else if (rest[i] === '--side' && rest[i + 1])
2551
+ side = rest[++i];
2552
+ }
2553
+ if (!content)
2554
+ err('disputes respond <disputeId> --type <t> --content <text>');
2555
+ print(await client.patchDisputeEvidence(rest[2], { evidence: { type, content }, side }));
2556
+ return;
2557
+ }
2558
+ err('disputes: list | create | get | respond');
2559
+ }
2560
+ if (cmd === 'agents' && rest[1] === 'discover') {
2561
+ requireKey(g);
2562
+ let capability;
2563
+ let category;
2564
+ for (let i = 2; i < rest.length; i++) {
2565
+ if (rest[i] === '--capability' && rest[i + 1])
2566
+ capability = rest[++i];
2567
+ else if (rest[i] === '--category' && rest[i + 1])
2568
+ category = rest[++i];
2569
+ }
2570
+ print(await client.discoverAgents({ capability, category }));
2571
+ return;
2572
+ }
2573
+ if (cmd === 'workflows') {
2574
+ requireKey(g);
2575
+ const sub = rest[1];
2576
+ if (sub === 'create') {
2577
+ let bodyJson = '';
2578
+ for (let i = 2; i < rest.length; i++) {
2579
+ if (rest[i] === '--body' && rest[i + 1])
2580
+ bodyJson = rest[++i];
2581
+ }
2582
+ if (!bodyJson)
2583
+ err('workflows create --body \'<json>\'');
2584
+ print(await client.createWorkflow(parseJsonOption('workflows create --body', bodyJson)));
2585
+ return;
2586
+ }
2587
+ if (sub === 'get' && rest[2]) {
2588
+ print(await client.getWorkflow(rest[2]));
2589
+ return;
2590
+ }
2591
+ if (sub === 'complete-step') {
2592
+ let workflowId = '';
2593
+ let stepId = '';
2594
+ let bodyJson = '';
2595
+ for (let i = 2; i < rest.length; i++) {
2596
+ if (rest[i] === '--workflow' && rest[i + 1])
2597
+ workflowId = rest[++i];
2598
+ else if (rest[i] === '--step' && rest[i + 1])
2599
+ stepId = rest[++i];
2600
+ else if (rest[i] === '--body' && rest[i + 1])
2601
+ bodyJson = rest[++i];
2602
+ }
2603
+ if (!workflowId || !stepId)
2604
+ err('workflows complete-step --workflow <id> --step <id> [--body \'<json>\']');
2605
+ print(await client.completeWorkflowStep(workflowId, stepId, bodyJson ? parseJsonOption('workflows complete-step --body', bodyJson) : undefined));
2606
+ return;
2607
+ }
2608
+ err('workflows: create | get | complete-step');
2609
+ }
2610
+ if (cmd === 'delegation') {
2611
+ requireKey(g);
2612
+ const sub = rest[1];
2613
+ if (sub === 'offer') {
2614
+ let toAgentId = '';
2615
+ let scopes = 'read,deliver';
2616
+ let budget;
2617
+ let subJobId = '';
2618
+ for (let i = 2; i < rest.length; i++) {
2619
+ if (rest[i] === '--to' && rest[i + 1])
2620
+ toAgentId = rest[++i];
2621
+ else if (rest[i] === '--scopes' && rest[i + 1])
2622
+ scopes = rest[++i];
2623
+ else if (rest[i] === '--budget' && rest[i + 1])
2624
+ budget = Number(rest[++i]);
2625
+ else if (rest[i] === '--sub-job' && rest[i + 1])
2626
+ subJobId = rest[++i];
2627
+ }
2628
+ if (!toAgentId)
2629
+ err('delegation offer --to <agentId> [--scopes read,deliver] [--budget N]');
2630
+ print(await client.createDelegationOffer({
2631
+ toAgentId,
2632
+ scopes: scopes.split(',').map((s) => s.trim()),
2633
+ budget,
2634
+ subJobId: subJobId || undefined,
2635
+ }));
2636
+ return;
2637
+ }
2638
+ if (sub === 'accept') {
2639
+ let offerId = '';
2640
+ for (let i = 2; i < rest.length; i++) {
2641
+ if (rest[i] === '--offer' && rest[i + 1])
2642
+ offerId = rest[++i];
2643
+ }
2644
+ if (!offerId)
2645
+ err('delegation accept --offer <offerId>');
2646
+ print(await client.acceptDelegationOffer(offerId));
2647
+ return;
2648
+ }
2649
+ err('delegation: offer | accept');
2650
+ }
2651
+ requireKey(g);
2652
+ if (cmd === 'me') {
2653
+ print(await client.me());
2654
+ return;
2655
+ }
2656
+ if (cmd === 'profile') {
2657
+ const sub = rest[1];
2658
+ if (sub === 'get') {
2659
+ print(await client.getProfile());
2660
+ return;
2661
+ }
2662
+ if (sub === 'gigs') {
2663
+ const data = (await client.getProfile());
2664
+ print(data?.profile?.gigSummaries ?? []);
2665
+ return;
2666
+ }
2667
+ if (sub === 'patch') {
2668
+ let bodyJson = '';
2669
+ for (let i = 2; i < rest.length; i++) {
2670
+ if (rest[i] === '--body' && rest[i + 1])
2671
+ bodyJson = rest[++i];
2672
+ }
2673
+ if (!bodyJson)
2674
+ err('profile patch --body \'<json>\'');
2675
+ print(await client.updateProfile(parseJsonOption('profile patch --body', bodyJson)));
2676
+ return;
2677
+ }
2678
+ if (sub === 'avatar' || sub === 'cover') {
2679
+ let filePath = '';
2680
+ for (let i = 2; i < rest.length; i++) {
2681
+ if (rest[i] === '--file' && rest[i + 1])
2682
+ filePath = rest[++i];
2683
+ }
2684
+ if (!filePath)
2685
+ err(`profile ${sub} --file <path>`);
2686
+ print(await uploadAgentMediaFile({
2687
+ baseUrl: requireBase(g),
2688
+ apiKey: requireKey(g),
2689
+ filePath,
2690
+ target: sub,
2691
+ }));
2692
+ return;
2693
+ }
2694
+ if (sub === 'portfolio' && rest[2] === 'upload') {
2695
+ let filePath = '';
2696
+ for (let i = 3; i < rest.length; i++) {
2697
+ if (rest[i] === '--file' && rest[i + 1])
2698
+ filePath = rest[++i];
2699
+ }
2700
+ if (!filePath)
2701
+ err('profile portfolio upload --file <path>');
2702
+ print(await uploadAgentMediaFile({
2703
+ baseUrl: requireBase(g),
2704
+ apiKey: requireKey(g),
2705
+ filePath,
2706
+ target: 'portfolio',
2707
+ }));
2708
+ return;
2709
+ }
2710
+ err('profile: use "profile get", "profile gigs", "profile patch --body \'<json>\'", "profile avatar --file <path>", "profile cover --file <path>", or "profile portfolio upload --file <path>"');
2711
+ }
2712
+ if (cmd === 'trust') {
2713
+ print(await client.v1Request('GET', '/agent/trust'));
2714
+ return;
2715
+ }
2716
+ if (cmd === 'autonomy') {
2717
+ requireKey(g);
2718
+ print(await client.getAutonomy());
2719
+ return;
2720
+ }
2721
+ if (cmd === 'jobs') {
2722
+ const sub = rest[1];
2723
+ if (sub === 'list') {
2724
+ let status;
2725
+ let limit;
2726
+ let offset;
2727
+ for (let i = 2; i < rest.length; i++) {
2728
+ if (rest[i] === '--status' && rest[i + 1])
2729
+ status = rest[++i];
2730
+ else if (rest[i] === '--limit' && rest[i + 1])
2731
+ limit = Number(rest[++i]);
2732
+ else if (rest[i] === '--offset' && rest[i + 1])
2733
+ offset = Number(rest[++i]);
2734
+ }
2735
+ print(await client.listJobs({ status, limit, offset }));
2736
+ return;
2737
+ }
2738
+ if (sub === 'get' && rest[2]) {
2739
+ print(await client.getJob(rest[2]));
2740
+ return;
2741
+ }
2742
+ if (sub === 'workspace' && rest[2]) {
2743
+ print(await client.getJobWorkspace(rest[2]));
2744
+ return;
2745
+ }
2746
+ if (sub === 'proposals') {
2747
+ if (!rest[2] || rest[2].startsWith('--'))
2748
+ err('jobs proposals <jobId> (proposals received on a job you posted)');
2749
+ print(await client.listJobProposals(rest[2]));
2750
+ return;
2751
+ }
2752
+ if (sub === 'bids' && rest[2]) {
2753
+ let detailed = false;
2754
+ for (let i = 3; i < rest.length; i++) {
2755
+ if (rest[i] === '--detailed')
2756
+ detailed = true;
2757
+ }
2758
+ print(await client.getJobBids(rest[2], { detailed }));
2759
+ return;
2760
+ }
2761
+ if (sub === 'submit-bid' && rest[2]) {
2762
+ let price;
2763
+ let estimatedHours;
2764
+ for (let i = 3; i < rest.length; i++) {
2765
+ if (rest[i] === '--price' && rest[i + 1])
2766
+ price = Number(rest[++i]);
2767
+ else if (rest[i] === '--hours' && rest[i + 1])
2768
+ estimatedHours = Number(rest[++i]);
2769
+ }
2770
+ if (price == null || !Number.isFinite(price) || price <= 0) {
2771
+ err('jobs submit-bid <jobId> --price <n> [--hours <n>]');
2772
+ }
2773
+ print(await client.submitJobBid(rest[2], {
2774
+ price,
2775
+ ...(estimatedHours != null && Number.isFinite(estimatedHours)
2776
+ ? { estimatedHours }
2777
+ : {}),
2778
+ }));
2779
+ return;
2780
+ }
2781
+ if (sub === 'cancel-bid' && rest[2]) {
2782
+ print(await client.cancelJobBid(rest[2]));
2783
+ return;
2784
+ }
2785
+ if (sub === 'fund-escrow') {
2786
+ if (!rest[2])
2787
+ err('jobs fund-escrow <jobId> [--idempotency-key K]');
2788
+ const idempotencyKey = takeIdempotencyKey(rest, 3);
2789
+ const res = await client.fundJobEscrow(rest[2], { idempotencyKey });
2790
+ print({ idempotencyKey, ...res });
2791
+ return;
2792
+ }
2793
+ if (sub === 'accept-bid' && rest[2] && rest[3]) {
2794
+ let paymentMethod;
2795
+ for (let i = 4; i < rest.length; i++) {
2796
+ if (rest[i] === '--payment-method' && rest[i + 1])
2797
+ paymentMethod = rest[++i];
2798
+ }
2799
+ print(await client.acceptJobBid(rest[2], rest[3], paymentMethod ? { paymentMethod } : undefined));
2800
+ return;
2801
+ }
2802
+ if (sub === 'patch' && rest[2]) {
2803
+ let bodyJson = '';
2804
+ for (let i = 3; i < rest.length; i++) {
2805
+ if (rest[i] === '--body' && rest[i + 1])
2806
+ bodyJson = rest[++i];
2807
+ }
2808
+ if (!bodyJson)
2809
+ err('jobs patch <jobId> --body \'<json>\'');
2810
+ print(await client.updateMyJob(rest[2], parseJsonOption('jobs patch --body', bodyJson)));
2811
+ return;
2812
+ }
2813
+ if (sub === 'delete' && rest[2]) {
2814
+ print(await client.deleteMyJob(rest[2]));
2815
+ return;
2816
+ }
2817
+ if (sub === 'create') {
2818
+ let bodyJson = '';
2819
+ const flags = {};
2820
+ for (let i = 2; i < rest.length; i++) {
2821
+ const f = rest[i];
2822
+ const v = rest[i + 1];
2823
+ if (f === '--body' && v)
2824
+ bodyJson = rest[++i];
2825
+ else if (f === '--title' && v)
2826
+ flags.title = rest[++i];
2827
+ else if (f === '--description' && v)
2828
+ flags.description = rest[++i];
2829
+ else if (f === '--category' && v)
2830
+ flags.category = rest[++i];
2831
+ else if (f === '--deadline' && v)
2832
+ flags.deadline = rest[++i];
2833
+ else if (f === '--idempotency-key' && v)
2834
+ flags.idempotencyKey = rest[++i];
2835
+ else if (f === '--budget' && v) {
2836
+ const n = Number(rest[++i]);
2837
+ if (!Number.isFinite(n) || n <= 0)
2838
+ err('jobs create: --budget must be a number > 0 (USDC)');
2839
+ flags.budget = n;
2840
+ }
2841
+ else if (f === '--payment-method' && v) {
2842
+ flags.acceptedPaymentMethods = rest[++i].split(',').map((m) => m.trim().toUpperCase()).filter(Boolean);
2843
+ }
2844
+ }
2845
+ const usageCreate = 'jobs create --title <t> --description <d> --budget <USDC> --category <c> [--deadline <ISO>] [--payment-method USDC,WALLET] [--idempotency-key K] or jobs create --body \'<json>\'';
2846
+ if (!bodyJson && Object.keys(flags).length === 0)
2847
+ err(usageCreate);
2848
+ const body = {
2849
+ ...(bodyJson ? parseJsonOption('jobs create --body', bodyJson) : {}),
2850
+ ...flags,
2851
+ };
2852
+ // Flag mode: default deadline 7 days out so title/description/budget/category suffice.
2853
+ if (!bodyJson && body.deadline === undefined) {
2854
+ body.deadline = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
2855
+ }
2856
+ // No payment choice at all → wallet escrow (the job is funded from the agent wallet on creation).
2857
+ const paymentKeys = ['paymentMethod', 'acceptedPaymentMethods', 'paymentPreference', 'escrowFundingMethod'];
2858
+ if (paymentKeys.every((k) => body[k] === undefined || body[k] === null || body[k] === '')) {
2859
+ body.paymentMethod = 'WALLET';
2860
+ console.error('jobs create: no --payment-method given → using paymentMethod WALLET (wallet escrow). Pass --payment-method USDC|STRIPE to change.');
2861
+ }
2862
+ else if (Array.isArray(body.acceptedPaymentMethods) && body.acceptedPaymentMethods.includes('WALLET') && body.paymentPreference === undefined) {
2863
+ body.paymentPreference = 'WALLET';
2864
+ }
2865
+ const missing = ['title', 'description', 'budget', 'category', 'deadline'].filter((k) => body[k] === undefined || body[k] === '');
2866
+ if (missing.length)
2867
+ err(`jobs create: missing ${missing.map((k) => `--${k}`).join(', ')}\n ${usageCreate}`);
2868
+ print(await client.createJob(body));
2869
+ return;
2870
+ }
2871
+ err('jobs: use "jobs list", "jobs get <jobId>", "jobs workspace <jobId>", "jobs bids <jobId> [--detailed]", "jobs submit-bid <jobId> --price <n> [--hours <n>]", "jobs cancel-bid <jobId>", "jobs accept-bid <jobId> <bidId> [--payment-method …]", "jobs fund-escrow <jobId>", "jobs patch …", "jobs delete …", or "jobs create …"');
2872
+ }
2873
+ if (cmd === 'my-jobs') {
2874
+ const sub = rest[1];
2875
+ if (sub === 'list') {
2876
+ let status;
2877
+ for (let i = 2; i < rest.length; i++) {
2878
+ if (rest[i] === '--status' && rest[i + 1])
2879
+ status = rest[++i];
2880
+ }
2881
+ print(await client.listMyJobs({ status }));
2882
+ return;
2883
+ }
2884
+ err('my-jobs: use "my-jobs list [--status S]"');
2885
+ }
2886
+ if (cmd === 'recommend') {
2887
+ const type = rest[1];
2888
+ if (type !== 'jobs' && type !== 'gigs' && type !== 'products') {
2889
+ err('recommend: use "recommend jobs", "recommend gigs", or "recommend products"');
2890
+ }
2891
+ let limit = 5;
2892
+ for (let i = 2; i < rest.length; i++) {
2893
+ if (rest[i] === '--limit' && rest[i + 1])
2894
+ limit = Number(rest[++i]);
2895
+ }
2896
+ print(await client.getRecommendations(type, { limit }));
2897
+ return;
2898
+ }
2899
+ if (cmd === 'browse') {
2900
+ const sub = rest[1];
2901
+ if (sub === 'jobs') {
2902
+ const p = {};
2903
+ for (let i = 2; i < rest.length; i++) {
2904
+ if (rest[i] === '--search' && rest[i + 1])
2905
+ p.search = rest[++i];
2906
+ else if (rest[i] === '--category' && rest[i + 1])
2907
+ p.category = rest[++i];
2908
+ else if (rest[i] === '--page' && rest[i + 1])
2909
+ p.page = Number(rest[++i]);
2910
+ else if (rest[i] === '--limit' && rest[i + 1])
2911
+ p.limit = Number(rest[++i]);
2912
+ else if (rest[i] === '--budget-min' && rest[i + 1])
2913
+ p.budgetMin = Number(rest[++i]);
2914
+ else if (rest[i] === '--budget-max' && rest[i + 1])
2915
+ p.budgetMax = Number(rest[++i]);
2916
+ else if (rest[i] === '--payment-method' && rest[i + 1])
2917
+ p.paymentMethod = rest[++i];
2918
+ else if (rest[i] === '--deadline' && rest[i + 1])
2919
+ p.deadline = rest[++i];
2920
+ else if (rest[i] === '--sort' && rest[i + 1])
2921
+ p.sort = rest[++i];
2922
+ }
2923
+ print(await client.browseMarketplaceJobs(p));
2924
+ return;
2925
+ }
2926
+ if (sub === 'gigs') {
2927
+ if (rest[2] === 'get' && rest[3]) {
2928
+ print(await client.getMarketplaceGig(rest[3]));
2929
+ return;
2930
+ }
2931
+ const p = {};
2932
+ for (let i = 2; i < rest.length; i++) {
2933
+ if (rest[i] === '--search' && rest[i + 1])
2934
+ p.search = rest[++i];
2935
+ else if (rest[i] === '--category' && rest[i + 1])
2936
+ p.category = rest[++i];
2937
+ else if (rest[i] === '--sub-category' && rest[i + 1])
2938
+ p.subCategory = rest[++i];
2939
+ else if (rest[i] === '--page' && rest[i + 1])
2940
+ p.page = Number(rest[++i]);
2941
+ else if (rest[i] === '--limit' && rest[i + 1])
2942
+ p.limit = Number(rest[++i]);
2943
+ else if (rest[i] === '--min-price' && rest[i + 1])
2944
+ p.minPrice = Number(rest[++i]);
2945
+ else if (rest[i] === '--max-price' && rest[i + 1])
2946
+ p.maxPrice = Number(rest[++i]);
2947
+ else if (rest[i] === '--min-rating' && rest[i + 1])
2948
+ p.minRating = Number(rest[++i]);
2949
+ else if (rest[i] === '--delivery-days' && rest[i + 1])
2950
+ p.deliveryDays = Number(rest[++i]);
2951
+ else if (rest[i] === '--sort-by' && rest[i + 1])
2952
+ p.sortBy = rest[++i];
2953
+ }
2954
+ print(await client.browseMarketplaceGigs(p));
2955
+ return;
2956
+ }
2957
+ err('browse: use "browse jobs …", "browse gigs …", or "browse gigs get <gigId>"');
2958
+ }
2959
+ if (cmd === 'products') {
2960
+ const sub = rest[1];
2961
+ if (sub === 'show' && rest[2]) {
2962
+ print(await client.getShippieProduct(rest[2]));
2963
+ return;
2964
+ }
2965
+ if (sub === 'trending') {
2966
+ const p = { safe: 1 };
2967
+ for (let i = 2; i < rest.length; i++) {
2968
+ if (rest[i] === '--limit' && rest[i + 1])
2969
+ p.limit = Number(rest[++i]);
2970
+ else if (rest[i] === '--usable')
2971
+ p.usable = 1;
2972
+ }
2973
+ print(await client.getTrendingShippieProducts(p));
2974
+ return;
2975
+ }
2976
+ if (sub === 'storefront') {
2977
+ const p = {};
2978
+ for (let i = 2; i < rest.length; i++) {
2979
+ if (rest[i] === '--q' && rest[i + 1])
2980
+ p.q = rest[++i];
2981
+ else if (rest[i] === '--section' && rest[i + 1])
2982
+ p.section = rest[++i];
2983
+ else if (rest[i] === '--category' && rest[i + 1])
2984
+ p.category = rest[++i];
2985
+ else if (rest[i] === '--expand')
2986
+ p.expand = 1;
2987
+ }
2988
+ print(await client.getShippieStorefront(p));
2989
+ return;
2990
+ }
2991
+ if (sub === 'recommend') {
2992
+ let limit = 5;
2993
+ for (let i = 2; i < rest.length; i++) {
2994
+ if (rest[i] === '--limit' && rest[i + 1])
2995
+ limit = Number(rest[++i]);
2996
+ }
2997
+ print(await client.getRecommendations('products', { limit }));
2998
+ return;
2999
+ }
3000
+ if (sub === 'open' && rest[2]) {
3001
+ print(await client.openShippieProduct(rest[2]));
3002
+ return;
3003
+ }
3004
+ if (sub === 'capabilities' && rest[2]) {
3005
+ print(await client.listShippieCapabilities(rest[2]));
3006
+ return;
3007
+ }
3008
+ if (sub === 'reviews' && rest[2]) {
3009
+ print(await client.getShippieProductReviews(rest[2]));
3010
+ return;
3011
+ }
3012
+ if (sub === 'review' && rest[2]) {
3013
+ let rating = 0;
3014
+ let text = '';
3015
+ for (let i = 3; i < rest.length; i++) {
3016
+ if (rest[i] === '--rating' && rest[i + 1])
3017
+ rating = Number(rest[++i]);
3018
+ else if (rest[i] === '--text' && rest[i + 1])
3019
+ text = rest[++i];
3020
+ }
3021
+ if (!rating || rating < 1 || rating > 5)
3022
+ err('products review <slug> --rating N (1-5)');
3023
+ print(await client.reviewShippieProduct(rest[2], { rating, review: text || undefined }));
3024
+ return;
3025
+ }
3026
+ if (sub === 'report' && rest[2]) {
3027
+ let reason = '';
3028
+ let detail = '';
3029
+ for (let i = 3; i < rest.length; i++) {
3030
+ if (rest[i] === '--reason' && rest[i + 1])
3031
+ reason = rest[++i];
3032
+ else if (rest[i] === '--detail' && rest[i + 1])
3033
+ detail = rest[++i];
3034
+ }
3035
+ print(await client.reportShippieProduct(rest[2], { reason: reason || undefined, detail: detail || undefined }));
3036
+ return;
3037
+ }
3038
+ if (sub === 'create') {
3039
+ let bodyJson = '';
3040
+ for (let i = 2; i < rest.length; i++) {
3041
+ if (rest[i] === '--body' && rest[i + 1])
3042
+ bodyJson = rest[++i];
3043
+ }
3044
+ if (!bodyJson)
3045
+ err('products create --body \'<json>\'');
3046
+ print(await client.createShippieProduct(parseJsonOption('products create --body', bodyJson)));
3047
+ return;
3048
+ }
3049
+ if (sub === 'update' && rest[2]) {
3050
+ let bodyJson = '';
3051
+ for (let i = 3; i < rest.length; i++) {
3052
+ if (rest[i] === '--body' && rest[i + 1])
3053
+ bodyJson = rest[++i];
3054
+ }
3055
+ if (!bodyJson)
3056
+ err('products update <slug> --body \'<json>\'');
3057
+ print(await client.updateShippieProduct(rest[2], parseJsonOption('products update --body', bodyJson)));
3058
+ return;
3059
+ }
3060
+ if (sub === 'verify-domain' && rest[2]) {
3061
+ print(await client.verifyShippieDomain(rest[2]));
3062
+ return;
3063
+ }
3064
+ if (sub === 'publish' && rest[2]) {
3065
+ let payViaWallet = false;
3066
+ let payHostingViaWallet = false;
3067
+ for (let i = 3; i < rest.length; i++) {
3068
+ if (rest[i] === '--wallet')
3069
+ payViaWallet = true;
3070
+ else if (rest[i] === '--hosting-wallet')
3071
+ payHostingViaWallet = true;
3072
+ }
3073
+ print(await client.publishShippieProduct(rest[2], { payViaWallet, payHostingViaWallet }));
3074
+ return;
3075
+ }
3076
+ if (sub === 'pay-hosting' && rest[2]) {
3077
+ print(await client.payShippieHosting(rest[2]));
3078
+ return;
3079
+ }
3080
+ if (sub === 'checkout-hosting' && rest[2]) {
3081
+ print(await client.checkoutShippieHosting(rest[2]));
3082
+ return;
3083
+ }
3084
+ if (sub === 'analytics' && rest[2]) {
3085
+ let days;
3086
+ for (let i = 3; i < rest.length; i++) {
3087
+ if (rest[i] === '--days' && rest[i + 1])
3088
+ days = Number(rest[++i]);
3089
+ }
3090
+ print(await client.getShippieProductAnalytics(rest[2], days != null ? { days } : undefined));
3091
+ return;
3092
+ }
3093
+ if (sub === 'connect' && rest[2]) {
3094
+ let credential = '';
3095
+ let credentialType = 'API_KEY';
3096
+ for (let i = 3; i < rest.length; i++) {
3097
+ if (rest[i] === '--credential' && rest[i + 1])
3098
+ credential = rest[++i];
3099
+ else if (rest[i] === '--type' && rest[i + 1]) {
3100
+ const t = rest[++i].toUpperCase();
3101
+ if (t === 'API_KEY' || t === 'BEARER' || t === 'OAUTH') {
3102
+ credentialType = t;
3103
+ }
3104
+ else {
3105
+ err('products connect --type must be API_KEY, BEARER, or OAUTH');
3106
+ }
3107
+ }
3108
+ }
3109
+ if (!credential) {
3110
+ err('products connect <slug> --credential <value> [--type API_KEY|BEARER|OAUTH]');
3111
+ }
3112
+ print(await client.connectShippieProduct(rest[2], { credential, credentialType }));
3113
+ return;
3114
+ }
3115
+ if (sub === 'disconnect' && rest[2]) {
3116
+ print(await client.disconnectShippieProduct(rest[2]));
3117
+ return;
3118
+ }
3119
+ if (sub === 'import-manifest' && rest[2]) {
3120
+ print(await client.importShippieManifest(rest[2]));
3121
+ return;
3122
+ }
3123
+ if (sub === 'delete' && rest[2]) {
3124
+ print(await client.deleteShippieProduct(rest[2]));
3125
+ return;
3126
+ }
3127
+ if (sub === 'search' || !sub) {
3128
+ const p = { safe: 1 };
3129
+ for (let i = 2; i < rest.length; i++) {
3130
+ if (rest[i] === '--q' && rest[i + 1])
3131
+ p.q = rest[++i];
3132
+ else if (rest[i] === '--category' && rest[i + 1])
3133
+ p.category = rest[++i];
3134
+ else if (rest[i] === '--capability' && rest[i + 1])
3135
+ p.capability = rest[++i];
3136
+ else if (rest[i] === '--sort' && rest[i + 1])
3137
+ p.sort = rest[++i];
3138
+ else if (rest[i] === '--limit' && rest[i + 1])
3139
+ p.limit = Number(rest[++i]);
3140
+ else if (rest[i] === '--usable')
3141
+ p.usable = 1;
3142
+ }
3143
+ print(await client.browseShippieProducts(p));
3144
+ return;
3145
+ }
3146
+ err('products: run "agentchain" with no args for the full products command list');
3147
+ }
3148
+ if (cmd === 'history') {
3149
+ print(await client.getJobHistory());
3150
+ return;
3151
+ }
3152
+ if (cmd === 'gamification') {
3153
+ const sub = rest[1];
3154
+ if (!sub) {
3155
+ print(await client.getGamification());
3156
+ return;
3157
+ }
3158
+ if (sub === 'shop' && !rest[2]) {
3159
+ print(await client.getGamificationShop());
3160
+ return;
3161
+ }
3162
+ if (sub === 'shop' && rest[2] === 'buy') {
3163
+ let bodyJson = '';
3164
+ for (let i = 3; i < rest.length; i++) {
3165
+ if (rest[i] === '--body' && rest[i + 1])
3166
+ bodyJson = rest[++i];
3167
+ }
3168
+ if (!bodyJson)
3169
+ err('gamification shop buy --body \'<json>\'');
3170
+ print(await client.purchaseGamificationShopItem(parseJsonOption('gamification shop buy --body', bodyJson)));
3171
+ return;
3172
+ }
3173
+ if (sub === 'streak-freeze') {
3174
+ print(await client.purchaseGamificationStreakFreeze());
3175
+ return;
3176
+ }
3177
+ err('gamification | gamification shop | gamification shop buy --body … | gamification streak-freeze');
3178
+ }
3179
+ if (cmd === 'gigs') {
3180
+ const sub = rest[1];
3181
+ if (sub === 'list') {
3182
+ print(await client.listMyGigs());
3183
+ return;
3184
+ }
3185
+ if (sub === 'get' && rest[2]) {
3186
+ print(await client.getMyGig(rest[2]));
3187
+ return;
3188
+ }
3189
+ if (sub === 'patch' && rest[2]) {
3190
+ let bodyJson = '';
3191
+ for (let i = 3; i < rest.length; i++) {
3192
+ if (rest[i] === '--body' && rest[i + 1])
3193
+ bodyJson = rest[++i];
3194
+ }
3195
+ if (!bodyJson)
3196
+ err('gigs patch <gigId> --body \'<json>\'');
3197
+ print(await client.updateMyGig(rest[2], parseJsonOption('gigs patch --body', bodyJson)));
3198
+ return;
3199
+ }
3200
+ if (sub === 'delete' && rest[2]) {
3201
+ print(await client.deleteMyGig(rest[2]));
3202
+ return;
3203
+ }
3204
+ if (sub === 'create') {
3205
+ let bodyJson = '';
3206
+ for (let i = 2; i < rest.length; i++) {
3207
+ if (rest[i] === '--body' && rest[i + 1])
3208
+ bodyJson = rest[++i];
3209
+ }
3210
+ if (!bodyJson)
3211
+ err('gigs create --body \'<json>\'');
3212
+ print(await client.createGig(parseJsonOption('gigs create --body', bodyJson)));
3213
+ return;
3214
+ }
3215
+ if (sub === 'order' && rest[2]) {
3216
+ let tier = 'BASIC';
3217
+ let promoCode;
3218
+ for (let i = 3; i < rest.length; i++) {
3219
+ if (rest[i] === '--tier' && rest[i + 1])
3220
+ tier = rest[++i].toUpperCase();
3221
+ else if (rest[i] === '--promo' && rest[i + 1])
3222
+ promoCode = rest[++i];
3223
+ }
3224
+ print(await client.v1Request('POST', `/agent/gigs/${encodeURIComponent(rest[2])}/order`, { tier, ...(promoCode ? { promoCode } : {}) }));
3225
+ return;
3226
+ }
3227
+ if (sub === 'offer' && rest[2]) {
3228
+ let buyerId = '';
3229
+ let amount = NaN;
3230
+ let deliveryDays;
3231
+ let description;
3232
+ let idempotencyKey;
3233
+ for (let i = 3; i < rest.length; i++) {
3234
+ if (rest[i] === '--to' && rest[i + 1])
3235
+ buyerId = rest[++i];
3236
+ else if (rest[i] === '--amount' && rest[i + 1])
3237
+ amount = Number(rest[++i]);
3238
+ else if (rest[i] === '--days' && rest[i + 1])
3239
+ deliveryDays = Number(rest[++i]);
3240
+ else if (rest[i] === '--note' && rest[i + 1])
3241
+ description = rest[++i];
3242
+ else if (rest[i] === '--idempotency-key' && rest[i + 1])
3243
+ idempotencyKey = rest[++i];
3244
+ }
3245
+ if (!buyerId || !Number.isFinite(amount))
3246
+ err('gigs offer <gigId> --to <buyerUserId> --amount <usd> [--days N] [--note <text>]');
3247
+ print(await client.v1Request('POST', `/agent/gigs/${encodeURIComponent(rest[2])}/offers`, {
3248
+ buyerId,
3249
+ amount,
3250
+ ...(deliveryDays ? { deliveryDays } : {}),
3251
+ ...(description ? { description } : {}),
3252
+ ...(idempotencyKey ? { idempotencyKey } : {}),
3253
+ }));
3254
+ return;
3255
+ }
3256
+ err('gigs: use "gigs list", "gigs get <gigId>", "gigs patch <gigId> --body \'<json>\'", "gigs delete <gigId>", "gigs create --body \'<json>\'", "gigs order <gigId> [--tier T]" or "gigs offer <gigId> --to <userId> --amount N"');
3257
+ }
3258
+ if (cmd === 'media') {
3259
+ const sub = rest[1];
3260
+ const targets = ['job', 'gig', 'product', 'portfolio', 'avatar', 'cover', 'chat', 'order'];
3261
+ const usage = 'media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order [--order <orderId>] [--label <text>]';
3262
+ if (sub === 'upload') {
3263
+ let filePath = '';
3264
+ let target = '';
3265
+ let orderId = '';
3266
+ let label = '';
3267
+ for (let i = 2; i < rest.length; i++) {
3268
+ if (rest[i] === '--file' && rest[i + 1])
3269
+ filePath = rest[++i];
3270
+ else if (rest[i] === '--target' && rest[i + 1])
3271
+ target = rest[++i];
3272
+ else if (rest[i] === '--order' && rest[i + 1])
3273
+ orderId = rest[++i];
3274
+ else if (rest[i] === '--label' && rest[i + 1])
3275
+ label = rest[++i];
3276
+ }
3277
+ if (!filePath || !target)
3278
+ err(usage);
3279
+ if (!targets.includes(target))
3280
+ err(`media upload --target must be one of ${targets.join(', ')}`);
3281
+ if (target === 'order' && !orderId)
3282
+ err('media upload --target order needs --order <orderId> (seller deliverable)');
3283
+ print(await uploadAgentMediaFile({
3284
+ baseUrl: requireBase(g),
3285
+ apiKey: requireKey(g),
3286
+ filePath,
3287
+ target: target,
3288
+ orderId: orderId || undefined,
3289
+ label: label || undefined,
3290
+ }));
3291
+ return;
3292
+ }
3293
+ err(`media: use "${usage}"`);
3294
+ }
3295
+ if (cmd === 'launchpad') {
3296
+ let prompt = '';
3297
+ let language = 'en';
3298
+ let messages;
3299
+ let draft;
3300
+ let media;
3301
+ let structuredAnswer;
3302
+ let editingGigId = '';
3303
+ let editingJobId = '';
3304
+ let sessionId = '';
3305
+ let requestId = '';
3306
+ let projectName = '';
3307
+ let projectInstructions = '';
3308
+ for (let i = 1; i < rest.length; i++) {
3309
+ if (rest[i] === '--prompt' && rest[i + 1])
3310
+ prompt = rest[++i];
3311
+ else if (rest[i] === '--language' && rest[i + 1])
3312
+ language = rest[++i];
3313
+ else if (rest[i] === '--messages-json' && rest[i + 1]) {
3314
+ messages = parseJsonOption('--messages-json', rest[++i]);
3315
+ }
3316
+ else if (rest[i] === '--draft-json' && rest[i + 1]) {
3317
+ draft = parseJsonOption('--draft-json', rest[++i]);
3318
+ }
3319
+ else if (rest[i] === '--structured-answer-json' && rest[i + 1]) {
3320
+ structuredAnswer = parseJsonOption('--structured-answer-json', rest[++i]);
3321
+ }
3322
+ else if (rest[i] === '--media-json' && rest[i + 1]) {
3323
+ media = parseJsonOption('--media-json', rest[++i]);
3324
+ }
3325
+ else if (rest[i] === '--session-id' && rest[i + 1]) {
3326
+ sessionId = rest[++i];
3327
+ }
3328
+ else if (rest[i] === '--request-id' && rest[i + 1]) {
3329
+ requestId = rest[++i];
3330
+ }
3331
+ else if (rest[i] === '--project-name' && rest[i + 1])
3332
+ projectName = rest[++i];
3333
+ else if (rest[i] === '--project-instructions' && rest[i + 1])
3334
+ projectInstructions = rest[++i];
3335
+ else if (rest[i] === '--editing-gig-id' && rest[i + 1])
3336
+ editingGigId = rest[++i];
3337
+ else if (rest[i] === '--editing-job-id' && rest[i + 1])
3338
+ editingJobId = rest[++i];
3339
+ }
3340
+ if (!prompt && !messages?.length && !draft && !editingGigId && !editingJobId) {
3341
+ err('launchpad needs --prompt, --messages-json, --draft-json, --editing-gig-id, or --editing-job-id (plus optional flags)');
3342
+ }
3343
+ print(await client.launchpad({
3344
+ prompt: prompt || undefined,
3345
+ language,
3346
+ messages,
3347
+ draft,
3348
+ project: projectName ? { name: projectName, instructions: projectInstructions || undefined } : undefined,
3349
+ media,
3350
+ sessionId: sessionId || undefined,
3351
+ requestId: requestId || undefined,
3352
+ structuredAnswer,
3353
+ editingGigId: editingGigId || undefined,
3354
+ editingJobId: editingJobId || undefined,
3355
+ }));
3356
+ return;
3357
+ }
3358
+ if (cmd === 'proposals') {
3359
+ if (rest[1] === 'list') {
3360
+ if (rest.includes('--received')) {
3361
+ print(await client.listReceivedProposals({ status: popt('--status') ?? 'open' }));
3362
+ return;
3363
+ }
3364
+ print(await client.listMyProposals());
3365
+ return;
3366
+ }
3367
+ if (rest[1] === 'submit') {
3368
+ let jobId = '';
3369
+ let price = NaN;
3370
+ let minutes = NaN;
3371
+ let message = '';
3372
+ for (let i = 2; i < rest.length; i++) {
3373
+ if (rest[i] === '--job' && rest[i + 1])
3374
+ jobId = rest[++i];
3375
+ else if (rest[i] === '--price' && rest[i + 1])
3376
+ price = Number(rest[++i]);
3377
+ else if (rest[i] === '--minutes' && rest[i + 1])
3378
+ minutes = Number(rest[++i]);
3379
+ else if (rest[i] === '--message' && rest[i + 1])
3380
+ message = rest[++i];
3381
+ }
3382
+ if (!jobId || !Number.isFinite(price) || !Number.isFinite(minutes) || !message) {
3383
+ err('proposals submit --job <id> --price <n> --minutes <n> --message <text> [--idempotency-key <key>]');
3384
+ }
3385
+ print(await client.submitProposal({
3386
+ jobId,
3387
+ price,
3388
+ deliveryTimeMinutes: minutes,
3389
+ message,
3390
+ idempotencyKey: takeIdempotencyKey(rest, 2),
3391
+ }));
3392
+ return;
3393
+ }
3394
+ if (rest[1] === 'accept' && rest[2]) {
3395
+ requireKey(g);
3396
+ let paymentMethod;
3397
+ for (let i = 3; i < rest.length; i++) {
3398
+ if (rest[i] === '--payment-method' && rest[i + 1])
3399
+ paymentMethod = rest[++i];
3400
+ }
3401
+ print(await client.acceptProposal(rest[2], {
3402
+ ...(paymentMethod ? { paymentMethod } : {}),
3403
+ idempotencyKey: takeIdempotencyKey(rest, 3),
3404
+ }));
3405
+ return;
3406
+ }
3407
+ err('proposals: use "proposals list", "proposals submit …", or "proposals accept <proposalId> [--payment-method WALLET]"');
3408
+ }
3409
+ if (cmd === 'deliver') {
3410
+ let jobId = '';
3411
+ let resultText = '';
3412
+ let resultFileUrl;
3413
+ for (let i = 1; i < rest.length; i++) {
3414
+ if (rest[i] === '--job' && rest[i + 1])
3415
+ jobId = rest[++i];
3416
+ else if (rest[i] === '--text' && rest[i + 1])
3417
+ resultText = rest[++i];
3418
+ else if (rest[i] === '--file-url' && rest[i + 1])
3419
+ resultFileUrl = rest[++i];
3420
+ }
3421
+ if (!jobId || !resultText) {
3422
+ err('deliver --job <jobId> --text <text> [--file-url <url>] [--idempotency-key <key>]');
3423
+ }
3424
+ print(await client.deliver({
3425
+ jobId,
3426
+ resultText,
3427
+ resultFileUrl: resultFileUrl ?? null,
3428
+ idempotencyKey: takeIdempotencyKey(rest, 1),
3429
+ }));
3430
+ return;
3431
+ }
3432
+ if (cmd === 'orders' && rest[1] && rest[1] !== 'list') {
3433
+ const sub = rest[1];
3434
+ const id = rest[2];
3435
+ if (!id)
3436
+ err(`orders ${sub} <orderId> …`);
3437
+ const path = `/agent/marketplace-orders/${encodeURIComponent(id)}`;
3438
+ const opt = (name) => {
3439
+ const i = rest.indexOf(name, 3);
3440
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
3441
+ };
3442
+ if (sub === 'get')
3443
+ return print(await client.v1Request('GET', path));
3444
+ if (sub === 'pay') {
3445
+ const idempotencyKey = opt('--idempotency-key') ?? `cli-pay-${id}`;
3446
+ return print(await client.v1Request('POST', `${path}/pay-wallet`, { idempotencyKey }));
3447
+ }
3448
+ if (sub === 'requirements') {
3449
+ const requirements = opt('--text');
3450
+ const answersRaw = opt('--answers');
3451
+ if (!requirements && !answersRaw) {
3452
+ err('orders requirements <orderId> --text <what you need> [--answers \'[{"questionId":"…","answer":"…"}]\']');
3453
+ }
3454
+ return print(await client.v1Request('POST', `${path}/requirements`, {
3455
+ requirements: requirements ?? '',
3456
+ ...(answersRaw ? { answers: JSON.parse(answersRaw) } : {}),
3457
+ }));
3458
+ }
3459
+ if (sub === 'deliver') {
3460
+ const message = opt('--text');
3461
+ const fileUrl = opt('--file-url');
3462
+ if (!message && !fileUrl)
3463
+ err('orders deliver <orderId> --text <message> [--file-url <https url>]');
3464
+ return print(await client.v1Request('POST', `${path}/deliver`, {
3465
+ message: message ?? '',
3466
+ files: fileUrl ? [{ url: fileUrl, label: opt('--label') ?? (fileUrl.split('/').pop() || 'delivery') }] : [],
3467
+ }));
3468
+ }
3469
+ if (sub === 'accept') {
3470
+ const rating = Number(opt('--rating'));
3471
+ if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
3472
+ err('orders accept <orderId> --rating 1-5 [--review <text>] (agents must rate the seller when accepting)');
3473
+ }
3474
+ return print(await client.v1Request('POST', `${path}/accept`, { rating, review: opt('--review') }));
3475
+ }
3476
+ if (sub === 'revision') {
3477
+ const message = opt('--text');
3478
+ if (!message)
3479
+ err('orders revision <orderId> --text <what to change>');
3480
+ return print(await client.v1Request('POST', `${path}/revision`, { message }));
3481
+ }
3482
+ if (sub === 'rate') {
3483
+ const rating = Number(opt('--rating'));
3484
+ if (!Number.isFinite(rating))
3485
+ err('orders rate <orderId> --rating 1-5 [--review <text>]');
3486
+ return print(await client.v1Request('POST', `${path}/rate`, { rating, review: opt('--review') }));
3487
+ }
3488
+ if (sub === 'cancel')
3489
+ return print(await client.v1Request('POST', `${path}/cancel`, { reason: opt('--reason') }));
3490
+ if (sub === 'checkout')
3491
+ return print(await client.v1Request('POST', `${path}/checkout`, {}));
3492
+ if (sub === 'resolutions')
3493
+ return print(await client.v1Request('GET', `${path}/resolution-request`));
3494
+ if (sub === 'resolve') {
3495
+ const t = (opt('--type') ?? '').toLowerCase();
3496
+ const type = t === 'cancel' ? 'CANCEL' : t === 'partial-refund' ? 'PARTIAL_REFUND' : t === 'extend' ? 'EXTEND_DEADLINE' : '';
3497
+ if (!type)
3498
+ err('orders resolve <orderId> --type cancel|partial-refund|extend [--amount N] [--days N] [--message <text>]');
3499
+ return print(await client.v1Request('POST', `${path}/resolution-request`, {
3500
+ type,
3501
+ ...(opt('--amount') ? { amount: Number(opt('--amount')) } : {}),
3502
+ ...(opt('--days') ? { days: Number(opt('--days')) } : {}),
3503
+ ...(opt('--message') ? { message: opt('--message') } : {}),
3504
+ }));
3505
+ }
3506
+ if (sub === 'resolve-respond') {
3507
+ const requestId = rest[3];
3508
+ const action = rest[4];
3509
+ if (!requestId || (action !== 'accept' && action !== 'decline'))
3510
+ err('orders resolve-respond <orderId> <requestId> accept|decline');
3511
+ return print(await client.v1Request('POST', `${path}/resolution-request/${encodeURIComponent(requestId)}/respond`, { action }));
3512
+ }
3513
+ if (sub === 'messages')
3514
+ return print(await client.v1Request('GET', `${path}/messages`));
3515
+ if (sub === 'message') {
3516
+ const content = opt('--text');
3517
+ if (!content)
3518
+ err('orders message <orderId> --text <text> [--image-url <url>]');
3519
+ return print(await client.v1Request('POST', `${path}/messages`, { content, ...(opt('--image-url') ? { imageUrl: opt('--image-url') } : {}) }));
3520
+ }
3521
+ if (sub === 'tip') {
3522
+ const amount = Number(opt('--amount'));
3523
+ if (!Number.isFinite(amount))
3524
+ err('orders tip <orderId> --amount N [--note <text>] [--idempotency-key K]');
3525
+ return print(await client.v1Request('POST', `${path}/tip`, {
3526
+ amount,
3527
+ note: opt('--note'),
3528
+ idempotencyKey: opt('--idempotency-key') ?? `cli-tip-${id}-${amount}-${Math.floor(Date.now() / 60000)}`,
3529
+ }));
3530
+ }
3531
+ if (sub === 'reorder')
3532
+ return print(await client.v1Request('POST', `${path}/reorder`, { promoCode: opt('--promo') }));
3533
+ if (sub === 'invoice') {
3534
+ if (rest.includes('--html'))
3535
+ return agentV1FetchText(g, `/api/v1${path}/invoice?format=html`);
3536
+ return print(await client.v1Request('GET', `${path}/invoice`));
3537
+ }
3538
+ if (sub === 'rate-buyer') {
3539
+ const rating = Number(opt('--rating'));
3540
+ if (!Number.isFinite(rating))
3541
+ err('orders rate-buyer <orderId> --rating 1-5 [--review <text>]');
3542
+ return print(await client.v1Request('POST', `${path}/rate-buyer`, { rating, review: opt('--review') }));
3543
+ }
3544
+ if (sub === 'confirm-payment') {
3545
+ const sessionId = opt('--session');
3546
+ if (!sessionId)
3547
+ err('orders confirm-payment <orderId> --session <stripeCheckoutSessionId>');
3548
+ return print(await client.v1Request('POST', `${path}/confirm-payment`, { sessionId }));
3549
+ }
3550
+ if (sub === 'dispute') {
3551
+ const reason = opt('--reason');
3552
+ if (!reason)
3553
+ err('orders dispute <orderId> --reason <what went wrong> [--evidence-url <https url>]');
3554
+ const evidenceUrl = opt('--evidence-url');
3555
+ return print(await client.v1Request('POST', `${path}/dispute`, {
3556
+ reason,
3557
+ ...(evidenceUrl ? { evidence: [{ url: evidenceUrl }] } : {}),
3558
+ }));
3559
+ }
3560
+ err('orders: use list | get | pay | requirements | deliver | accept | revision | rate | cancel | dispute | checkout | resolutions | resolve | resolve-respond | message | messages | tip | reorder | invoice | rate-buyer <orderId>');
3561
+ }
3562
+ if (cmd === 'inquiries') {
3563
+ const sub = rest[1];
3564
+ const opt = (name) => {
3565
+ const i = rest.indexOf(name, 2);
3566
+ return i > 0 && rest[i + 1] ? rest[i + 1] : undefined;
3567
+ };
3568
+ if (!sub || sub === 'list')
3569
+ return print(await client.v1Request('GET', '/agent/inquiries'));
3570
+ if (sub === 'start') {
3571
+ // --to takes a user id (cuid) or a public handle; "@leote" and "leote" are both sent as recipientHandle.
3572
+ const to = opt('--to');
3573
+ const content = opt('--text');
3574
+ if (!to || !content)
3575
+ err('inquiries start --to <userId|@handle|handle> --text <message> [--gig <gigId>]');
3576
+ const isUserId = /^c[a-z0-9]{20,32}$/.test(to);
3577
+ const recipient = isUserId ? { recipientId: to } : { recipientHandle: to.startsWith('@') ? to : `@${to}` };
3578
+ return print(await client.v1Request('POST', '/agent/inquiries', { ...recipient, content, gigId: opt('--gig') ?? null }));
3579
+ }
3580
+ const id = rest[2];
3581
+ if (!id)
3582
+ err('inquiries get|send|accept|decline <inquiryId> …');
3583
+ const path = `/agent/inquiries/${encodeURIComponent(id)}`;
3584
+ if (sub === 'get')
3585
+ return print(await client.v1Request('GET', path));
3586
+ if (sub === 'send') {
3587
+ const content = opt('--text');
3588
+ const imageUrl = opt('--image-url');
3589
+ if (!content && !imageUrl)
3590
+ err('inquiries send <inquiryId> --text <message> [--image-url <url>]');
3591
+ return print(await client.v1Request('POST', `${path}/messages`, { content: content ?? '', ...(imageUrl ? { imageUrl } : {}) }));
3592
+ }
3593
+ if (sub === 'accept' || sub === 'decline')
3594
+ return print(await client.v1Request('PATCH', path, { action: sub }));
3595
+ err('inquiries: use list | start | get | send | accept | decline');
3596
+ }
3597
+ if (cmd === 'orders' && rest[1] === 'list') {
3598
+ let status;
3599
+ let limit;
3600
+ let offset;
3601
+ for (let i = 2; i < rest.length; i++) {
3602
+ if (rest[i] === '--status' && rest[i + 1])
3603
+ status = rest[++i];
3604
+ else if (rest[i] === '--limit' && rest[i + 1])
3605
+ limit = Number(rest[++i]);
3606
+ else if (rest[i] === '--offset' && rest[i + 1])
3607
+ offset = Number(rest[++i]);
3608
+ }
3609
+ print(await client.listOrders({ status, limit, offset }));
3610
+ return;
3611
+ }
3612
+ if (cmd === 'activity') {
3613
+ if (rest[1] === 'summary')
3614
+ return print(await client.getActivitySummary());
3615
+ if (rest[1] && !rest[1].startsWith('--'))
3616
+ err('activity: use [--since T] [--kind K] [--channel C] [--status S] [--limit N] [--cursor C] | summary');
3617
+ const limit = flagValue(rest, '--limit', 1);
3618
+ return print(await client.getActivity({
3619
+ since: flagValue(rest, '--since', 1),
3620
+ kind: flagValue(rest, '--kind', 1),
3621
+ channel: flagValue(rest, '--channel', 1),
3622
+ status: flagValue(rest, '--status', 1),
3623
+ cursor: flagValue(rest, '--cursor', 1),
3624
+ ...(limit ? { limit: Number(limit) } : {}),
3625
+ }));
3626
+ }
3627
+ if (cmd === 'engagements' || cmd === 'subscriptions' || cmd === 'sub-jobs' || cmd === 'team' || cmd === 'teams') {
3628
+ await runSubscriptionCommand(g, rest, client);
3629
+ return;
3630
+ }
3631
+ if (cmd === 'dismiss-order' && rest[1]) {
3632
+ print(await agentV1Fetch(g, `/api/v1/agent/marketplace-orders/${encodeURIComponent(rest[1])}/dismiss`, {
3633
+ method: 'POST',
3634
+ body: JSON.stringify({}),
3635
+ }));
3636
+ return;
3637
+ }
3638
+ if (cmd === 'marketplace-orders') {
3639
+ const params = {};
3640
+ for (let i = 1; i < rest.length; i++) {
3641
+ if (rest[i] === '--status' && rest[i + 1])
3642
+ params.status = rest[++i];
3643
+ else if (rest[i] === '--limit' && rest[i + 1])
3644
+ params.limit = rest[++i];
3645
+ else if ((rest[i] === '--page' || rest[i] === '--offset') && rest[i + 1])
3646
+ params.page = rest[++i];
3647
+ else if (rest[i] === '--role' && rest[i + 1])
3648
+ params.role = rest[++i];
3649
+ }
3650
+ print(await client.marketplaceOrders(Object.keys(params).length ? params : undefined));
3651
+ return;
3652
+ }
3653
+ if (cmd === 'revise' && rest[1]) {
3654
+ const i = rest.indexOf('--text');
3655
+ const message = i > 0 ? rest[i + 1] : undefined;
3656
+ if (!message)
3657
+ err('revise <jobId> --text <what to change>');
3658
+ print(await client.v1Request('POST', `/agent/jobs/${encodeURIComponent(rest[1])}/reject-delivery`, { message }));
3659
+ return;
3660
+ }
3661
+ if (cmd === 'complete' && rest[1]) {
3662
+ const jobId = rest[1];
3663
+ let rating;
3664
+ let review;
3665
+ for (let i = 2; i < rest.length; i++) {
3666
+ if (rest[i] === '--rating' && rest[i + 1])
3667
+ rating = Number(rest[++i]);
3668
+ else if (rest[i] === '--review' && rest[i + 1])
3669
+ review = rest[++i];
3670
+ }
3671
+ if (rating == null || !Number.isInteger(rating) || rating < 1 || rating > 5) {
3672
+ err('complete <jobId> --rating 1-5 [--review <text>] (agents must rate the seller)');
3673
+ }
3674
+ print(await client.completeOwnJob(jobId, {
3675
+ rating,
3676
+ review,
3677
+ idempotencyKey: takeIdempotencyKey(rest, 2),
3678
+ }));
3679
+ return;
3680
+ }
3681
+ if (cmd === 'favorites') {
3682
+ requireKey(g);
3683
+ const sub = rest[1] || 'list';
3684
+ const parseTarget = () => {
3685
+ const body = {};
3686
+ for (let i = 2; i < rest.length; i++) {
3687
+ if (rest[i] === '--job' && rest[i + 1])
3688
+ body.jobId = rest[++i];
3689
+ else if (rest[i] === '--gig' && rest[i + 1])
3690
+ body.gigId = rest[++i];
3691
+ else if (rest[i] === '--agent' && rest[i + 1])
3692
+ body.agentUserId = rest[++i];
3693
+ else if (rest[i] === '--product' && rest[i + 1])
3694
+ body.productId = rest[++i];
3695
+ else if (rest[i] === '--slug' && rest[i + 1])
3696
+ body.productSlug = rest[++i];
3697
+ }
3698
+ return body;
3699
+ };
3700
+ if (sub === 'list') {
3701
+ let type;
3702
+ let direction;
3703
+ for (let i = 2; i < rest.length; i++) {
3704
+ if (rest[i] === '--type' && rest[i + 1])
3705
+ type = rest[++i];
3706
+ else if (rest[i] === '--direction' && rest[i + 1])
3707
+ direction = rest[++i];
3708
+ }
3709
+ print(await client.listFavorites({ type, direction }));
3710
+ return;
3711
+ }
3712
+ if (sub === 'save') {
3713
+ const body = parseTarget();
3714
+ if (Object.keys(body).length !== 1)
3715
+ err('favorites save --job|--gig|--agent|--product|--slug <id>');
3716
+ print(await client.saveFavorite(body));
3717
+ return;
3718
+ }
3719
+ if (sub === 'delete') {
3720
+ const body = parseTarget();
3721
+ if (Object.keys(body).length !== 1)
3722
+ err('favorites delete --job|--gig|--agent|--product|--slug <id>');
3723
+ print(await client.deleteFavorite(body));
3724
+ return;
3725
+ }
3726
+ err('favorites: list | save | delete');
3727
+ }
3728
+ if (cmd === 'conversations') {
3729
+ requireKey(g);
3730
+ const sub = rest[1] || 'list';
3731
+ if (sub === 'list') {
3732
+ print(await client.listConversations());
3733
+ return;
3734
+ }
3735
+ if (sub === 'messages' && rest[2]) {
3736
+ let limit;
3737
+ for (let i = 3; i < rest.length; i++) {
3738
+ if (rest[i] === '--limit' && rest[i + 1])
3739
+ limit = Number(rest[++i]);
3740
+ }
3741
+ print(await client.getMessages(rest[2], { limit }));
3742
+ return;
3743
+ }
3744
+ if (sub === 'send' && rest[2]) {
3745
+ let text = '';
3746
+ let receiverId = '';
3747
+ let imageUrl = '';
3748
+ for (let i = 3; i < rest.length; i++) {
3749
+ if (rest[i] === '--text' && rest[i + 1])
3750
+ text = rest[++i];
3751
+ else if (rest[i] === '--receiver' && rest[i + 1])
3752
+ receiverId = rest[++i];
3753
+ else if (rest[i] === '--image-url' && rest[i + 1])
3754
+ imageUrl = rest[++i];
3755
+ }
3756
+ if (!text && !imageUrl)
3757
+ err('conversations send <jobId> --text <text> [--image-url <url from media upload --target chat>] [--receiver <userId>]');
3758
+ print(await client.sendMessage(rest[2], {
3759
+ content: text,
3760
+ receiverId: receiverId || undefined,
3761
+ ...(imageUrl ? { imageUrl } : {}),
3762
+ }));
3763
+ return;
3764
+ }
3765
+ if (sub === 'offer' && rest[2]) {
3766
+ let to = '';
3767
+ let amount = 0;
3768
+ let description = '';
3769
+ for (let i = 3; i < rest.length; i++) {
3770
+ if (rest[i] === '--to' && rest[i + 1])
3771
+ to = rest[++i];
3772
+ else if (rest[i] === '--amount' && rest[i + 1])
3773
+ amount = Number(rest[++i]);
3774
+ else if (rest[i] === '--description' && rest[i + 1])
3775
+ description = rest[++i];
3776
+ }
3777
+ if (!to || !Number.isFinite(amount) || amount <= 0) {
3778
+ err('conversations offer <jobId> --to <userId> --amount N [--description <text>]');
3779
+ }
3780
+ print(await client.createChatOffer(rest[2], { amount, receiverId: to, description: description || undefined }));
3781
+ return;
3782
+ }
3783
+ if (sub === 'offer-respond' && rest[2]) {
3784
+ let offerId = '';
3785
+ let action = '';
3786
+ let paymentMethod;
3787
+ const idempotencyKey = takeIdempotencyKey(rest, 3);
3788
+ for (let i = 3; i < rest.length; i++) {
3789
+ if (rest[i] === '--offer' && rest[i + 1])
3790
+ offerId = rest[++i];
3791
+ else if (rest[i] === '--action' && rest[i + 1])
3792
+ action = rest[++i];
3793
+ else if (rest[i] === '--payment-method' && rest[i + 1]) {
3794
+ paymentMethod = rest[++i];
3795
+ }
3796
+ }
3797
+ if (!offerId || (action !== 'accept' && action !== 'reject')) {
3798
+ err('conversations offer-respond <jobId> --offer <id> --action accept|reject');
3799
+ }
3800
+ print(await client.respondToChatOffer(rest[2], {
3801
+ offerId,
3802
+ action,
3803
+ paymentMethod,
3804
+ idempotencyKey,
3805
+ }));
3806
+ return;
3807
+ }
3808
+ if (sub === 'archive' && rest[2]) {
3809
+ print(await client.archiveConversation(rest[2]));
3810
+ return;
3811
+ }
3812
+ if (sub === 'unarchive' && rest[2]) {
3813
+ print(await client.unarchiveConversation(rest[2]));
3814
+ return;
3815
+ }
3816
+ err('conversations: list | messages | send | offer | offer-respond | archive | unarchive');
3817
+ }
3818
+ if (cmd === 'raw' && rest[1] && rest[2]) {
3819
+ const method = rest[1];
3820
+ const path = rest[2].startsWith('/') ? rest[2] : `/${rest[2]}`;
3821
+ let body = undefined;
3822
+ for (let i = 3; i < rest.length; i++) {
3823
+ if (rest[i] === '--body' && rest[i + 1]) {
3824
+ try {
3825
+ body = JSON.parse(rest[++i]);
3826
+ }
3827
+ catch {
3828
+ err('raw --body must be valid JSON');
3829
+ }
3830
+ }
3831
+ }
3832
+ print(await client.v1Request(method, path, body));
3833
+ return;
3834
+ }
3835
+ err(`Unknown command: ${cmd}. Run agentchain --help.`);
3836
+ }
3837
+ main().catch((e) => {
3838
+ console.error(formatApiError(e));
3839
+ process.exit(1);
3840
+ });