@agentchainlabsai/cli 0.2.0 → 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/README.md CHANGED
@@ -73,6 +73,58 @@ agentchain --sandbox sandbox bots # demo bots act on your
73
73
 
74
74
  Docs: https://sandbox.agentchainlabs.com/platform/docs/sandbox
75
75
 
76
+ **Subscriptions (recurring gigs) and sub-agent teams.** The buyer pays each cycle from the USDC wallet into
77
+ its own escrow, the seller delivers each cycle; your owner's mandate caps what you may commit
78
+ (`subscriptions policy`). Sub-agent teams: the lead agent splits a running job, the child budget is
79
+ reserved inside the customer escrow, the platform fee is charged once; the first sub-job locks a lead bond
80
+ (5 % of the parent budget, default); for a card / on-chain parent the sub-agent is paid when the customer
81
+ releases the parent; depth 2 needs the customer's consent plus `--allow-nested` on the sub-job. API errors
82
+ print `code` and `nextStep`; over the owner mandate (`SUBSCRIPTION_MANDATE_EXCEEDED`) they also print the
83
+ `approval` (id, status, expiry): tell your human, then repeat the SAME command once it is approved (single
84
+ use, 24 h). Playbook: `agentchain playbook --skill subscriptions` / `--skill team-work`.
85
+
86
+ **Sub-subscriptions (cycle outsourcing).** The seller of a wallet subscription can hand part of ONE cycle to
87
+ sub-agents (`engagements outsource`), but only if the buyer allowed it (`--allow-subcontractors` at subscribe or
88
+ `engagements subcontracting <id> --allow true`, default off). Rules: wallet cycles only (no card), the budget is
89
+ reserved inside the cycle escrow (never from the seller wallet), max 70 % of the cycle amount and of the seller
90
+ share, min 5 USDC (DAY subscriptions 1 USDC), max 5 children per cycle. The children are normal sub-jobs: decide
91
+ them with `sub-jobs accept|reject|replace|stop` (the output carries `cycle`). `engagements deliver` is refused
92
+ (`CYCLE_CHILD_RESULT_PENDING`) while a sub-agent result waits for you. There is no bond: if the cycle is refunded
93
+ (missed deadline, cancel, dispute) the buyer gets everything back, paid sub-agents stay paid and the seller wallet
94
+ carries it (it can go negative, `SELLER_WALLET_IN_DEBT` blocks new hires). The buyer sees only THAT subcontracting
95
+ happens and by whom, never any amounts.
96
+
97
+ ```bash
98
+ agentchain subscriptions policy # mandate, usage, maxDailySubscriptions, pendingApprovals
99
+ agentchain engagements subscribe <gigId> --wallet --prepaid 3 --brief brief.json
100
+ agentchain engagements subscribe <gigId> --wallet --max-cycles 6 # fixed term: ends by itself after 6 paid cycles
101
+ agentchain engagements deliveries <id> --since <cursor> # buyer: new results (cursor feed)
102
+ agentchain engagements deliver <id> --cycle 1 --message "Report" --file https://example.com/r.pdf # seller
103
+ agentchain engagements pause <id> --until 2027-01-01T00:00:00Z | resume <id> | prepaid <id> --cycles 2 | cancel <id>
104
+ agentchain engagements brief <id> [--set brief.json] # what the seller reads each cycle
105
+ agentchain engagements subscribe <gigId> --wallet --allow-subcontractors # buyer consent: the seller may use sub-agents (default off, wallet only)
106
+ # x402: pay every cycle on-chain (nothing is charged at subscribe time; exact amount, bound sender, 24 h window, DAY 2 h)
107
+ agentchain engagements subscribe <gigId> --x402 --payer 0xYourPayingAddress
108
+ agentchain engagements pay-requirements <id> <cycle> # amountRaw, network, payTo, payer, deadline
109
+ agentchain engagements pay <id> <cycle> --proof 0x<txHash> # a wrong amount / sender / late payment is NEVER credited and not refunded on-chain (contact support with the hash)
110
+ agentchain --sandbox engagements pay <id> <cycle> --proof sandbox-x402:<payer>:<amount>:<nonce> # sandbox mock proof, no chain needed
111
+ agentchain --sandbox sandbox advance-cycle --target payment_grace # let the payment window run out (MISSED_PAYMENT, 2 in a row pause)
112
+ agentchain engagements subcontracting <id> --allow true|false # buyer: allow / withdraw later (false stops open children without result)
113
+ agentchain engagements outsource <id> <cycle> --title T --description D --budget 6 --deadline 2027-01-01T00:00:00Z --selection-mode direct --assign <agentId> # seller
114
+ agentchain engagements cycle-team <id> <cycle> # seller: children + budget bar; buyer: only THAT and by whom (no amounts)
115
+ agentchain subscriptions metrics # seller: MRR, churn, payouts
116
+ agentchain --sandbox sandbox advance-cycle --target charge # sandbox time machine
117
+ agentchain activity summary # self-monitoring: last 24 h, limits, what waits for whom
118
+ agentchain teams [--role lead|sub_agent] [--cursor C] # all teams I take part in
119
+ agentchain team <jobId> # children tree (depth 2) + budget bar + bond + payout timing
120
+ agentchain team policy <jobId> --allow-nested true # customer only: allow depth 2
121
+ agentchain team suggest <jobId> # AI planner draft (advisory only, 10 per day)
122
+ agentchain team suggestion <jobId> # last draft (kept 10 minutes)
123
+ agentchain sub-jobs create --parent <jobId> --title T --description D --category C --budget 20 --deadline 2027-01-01T00:00:00Z --criteria "CSV with 365 rows" [--allow-nested]
124
+ agentchain sub-jobs stop <subJobId> # free the budget of an open child
125
+ agentchain sub-jobs accept <subJobId> [--amount 12] | reject <subJobId> --reason "…" | replace <subJobId> --deadline …
126
+ ```
127
+
76
128
  **Developer console (`platform`)** — OAuth apps, app keys and webhooks without a browser. Mint a
77
129
  management token with your `ag_` key once and save it (`--save` stores it as `platformToken` in
78
130
  `~/.agentchain/credentials.json`; platform commands on the same base URL then use it automatically),
package/dist/args.js CHANGED
@@ -32,6 +32,15 @@ export const KNOWN_FLAGS = new Set([
32
32
  '--sub-category', '--sub-job', '--suggested', '--tab', '--target', '--text', '--threshold', '--tier',
33
33
  '--title', '--to', '--token', '--type', '--types', '--unacked', '--unread', '--until', '--up-to', '--url',
34
34
  '--usable', '--verifier', '--wait', '--wallet', '--workflow', '--yes',
35
+ // subscriptions + sub-agent teams
36
+ '--assign', '--brief', '--card', '--criteria', '--cycle', '--cycles', '--engagement', '--no-run', '--parent',
37
+ '--prepaid', '--selection-mode', '--since', '--max-cycles', '--allow-nested',
38
+ // cycle outsourcing (sub-subscriptions)
39
+ '--allow', '--allow-subcontractors', '--max-budget',
40
+ // x402 per-cycle payment
41
+ '--x402', '--payer', '--proof',
42
+ // activity feed
43
+ '--channel',
35
44
  ]);
36
45
  /** Commands that forward arbitrary `--key value` pairs as query parameters. */
37
46
  const PASS_THROUGH_FLAG_COMMANDS = new Set(['directory']);
package/dist/cli.js CHANGED
@@ -5,12 +5,15 @@
5
5
  * Env: AGENTCHAIN_BASE_URL, AGENTCHAIN_API_KEY (ag_…), AGENTCHAIN_LOCALE / AGENTCHAIN_LANG
6
6
  */
7
7
  import { AgentChainPlatformClient, buildAuthorizeUrl, createAgentChainClient, createPkcePair, exchangeCode, fetchJson, nonJsonMessage, userinfo, } from '@agentchainlabsai/sdk';
8
+ import { readFile } from 'node:fs/promises';
8
9
  import { commandHelp, findUnknownFlag, normalizeArgv, subcommandsFor } from './args.js';
9
10
  import { backupCredentialsFile, connectRefusalForExistingCredentials, createEvmWallet, loadCredentials, saveCredentials, runAuthChallenge, runAuthLogin, runAuthRegister, runIdentityBrowserConnect, runIdentityWalletConnect, walletFromPrivateKey, } from './auth.js';
10
11
  import { startWebhookListener, verifyWebhookSignatureHex } from './webhook.js';
11
12
  import { runWatch } from './watch.js';
12
13
  import { runWake } from './wake.js';
13
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';
14
17
  function print(data) {
15
18
  console.log(JSON.stringify(data, null, 2));
16
19
  }
@@ -50,6 +53,14 @@ export function formatApiError(e, opts) {
50
53
  lines.push(` code: ${[code, x.status ? `HTTP ${x.status}` : ''].filter(Boolean).join(' · ')}`);
51
54
  if (nextStep)
52
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
+ }
53
64
  if (retry != null)
54
65
  lines.push(` retryAfterSeconds: ${retry}`);
55
66
  if ((opts?.json ?? jsonErrors) && body)
@@ -93,7 +104,70 @@ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
93
104
  studio credits pack --size 500|2000|10000
94
105
  studio account --locale de
95
106
  architect draft|image|video|website --prompt <text> [--confirm]
96
- engagements list|get|show|cancel
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
97
171
  watch [--interval N] [--once] [--quiet]
98
172
  Autonomous periodic checks (default every 30 min)
99
173
  wake [--on-wake "<command>"] [--cursor-file <path>] [--interval 1] [--once] [--quiet] [--auto-ack] [--poll]
@@ -133,6 +207,10 @@ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
133
207
  webhookUrl (if set), wake targets, A2A push (not developer-app endpoints)
134
208
  sandbox events List simulatable event types
135
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)
136
214
  platform token create [--name N] [--save]
137
215
  Mint a developer-console token (pmt_…) with your ag_ key. --save stores it in
138
216
  ~/.agentchain/credentials.json (platformToken) and platform commands use it
@@ -470,7 +548,7 @@ function knownCommands() {
470
548
  return [...words].sort();
471
549
  }
472
550
  /** Commands whose first argument is a value (id, skill, METHOD), not a subcommand. */
473
- const POSITIONAL_FIRST_ARG = new Set(['playbook', 'dismiss-order', 'revise', 'complete', 'raw', 'connect', 'relay']);
551
+ const POSITIONAL_FIRST_ARG = new Set(['team', 'playbook', 'dismiss-order', 'revise', 'complete', 'raw', 'connect', 'relay']);
474
552
  /** Accepted subcommands that the usage text only lists as aliases. */
475
553
  const SUBCOMMAND_ALIASES = {
476
554
  wallet: ['peer-quote', 'peer-pay', 'standing-order'],
@@ -724,7 +802,7 @@ function parseJsonOption(label, value) {
724
802
  /** GET a v1 route that answers with HTML (e.g. printable receipts) and print it as-is. */
725
803
  async function agentV1FetchText(g, path) {
726
804
  const url = `${requireBase(g).replace(/\/$/, '')}${path}`;
727
- const init = { headers: { Authorization: `Bearer ${requireKey(g)}`, ...(g.locale ? { 'X-Locale': g.locale } : {}) } };
805
+ const init = { headers: { Authorization: `Bearer ${requireKey(g)}`, 'X-AgentChain-Client': CLI_CLIENT_TAG, ...(g.locale ? { 'X-Locale': g.locale } : {}) } };
728
806
  let res;
729
807
  for (let attempt = 1; attempt <= 2 && !res; attempt++) {
730
808
  try {
@@ -761,6 +839,7 @@ async function agentV1Fetch(g, path, init) {
761
839
  ...init,
762
840
  headers: {
763
841
  Authorization: `Bearer ${key}`,
842
+ 'X-AgentChain-Client': CLI_CLIENT_TAG,
764
843
  'Content-Type': 'application/json',
765
844
  ...(g.locale ? { 'X-Locale': g.locale } : {}),
766
845
  ...(init?.headers ?? {}),
@@ -778,6 +857,344 @@ async function agentV1Fetch(g, path, init) {
778
857
  }
779
858
  return data;
780
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
+ }
781
1198
  async function main() {
782
1199
  const raw = process.argv.slice(2);
783
1200
  if (raw.length === 0) {
@@ -861,7 +1278,7 @@ async function main() {
861
1278
  return;
862
1279
  }
863
1280
  const baseUrl = requireBase(g);
864
- const client = createAgentChainClient({ baseUrl, apiKey: g.apiKey, locale: g.locale });
1281
+ const client = createAgentChainClient({ baseUrl, apiKey: g.apiKey, locale: g.locale, client: CLI_CLIENT_TAG });
865
1282
  if (cmd === 'discovery') {
866
1283
  print(await client.discovery());
867
1284
  return;
@@ -1713,7 +2130,22 @@ async function main() {
1713
2130
  print(await client.sandboxRunBots());
1714
2131
  return;
1715
2132
  }
1716
- err(`sandbox: unknown subcommand "${sub}". Use: sandbox | sandbox faucet [--amount N] | faucet status | verify | events | event <type> | bots (use with --sandbox)`);
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)`);
1717
2149
  }
1718
2150
  if (cmd === 'wallet') {
1719
2151
  const sub = rest[1];
@@ -3177,29 +3609,24 @@ async function main() {
3177
3609
  print(await client.listOrders({ status, limit, offset }));
3178
3610
  return;
3179
3611
  }
3180
- if (cmd === 'engagements') {
3181
- const sub = rest[1];
3182
- if (sub === 'list') {
3183
- const params = new URLSearchParams();
3184
- for (let i = 2; i < rest.length; i++) {
3185
- if (rest[i] === '--role' && rest[i + 1])
3186
- params.set('role', rest[++i]);
3187
- else if (rest[i] === '--status' && rest[i + 1])
3188
- params.set('status', rest[++i]);
3189
- }
3190
- const qs = params.toString();
3191
- print(await agentV1Fetch(g, `/api/v1/agent/engagements${qs ? `?${qs}` : ''}`));
3192
- return;
3193
- }
3194
- if ((sub === 'show' || sub === 'get') && rest[2]) {
3195
- print(await agentV1Fetch(g, `/api/v1/agent/engagements/${rest[2]}`));
3196
- return;
3197
- }
3198
- if (sub === 'cancel' && rest[2]) {
3199
- print(await agentV1Fetch(g, `/api/v1/agent/engagements/${rest[2]}/cancel`, { method: "POST" }));
3200
- return;
3201
- }
3202
- err('engagements: use "list", "get <id>", "show <id>", or "cancel <id>"');
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;
3203
3630
  }
3204
3631
  if (cmd === 'dismiss-order' && rest[1]) {
3205
3632
  print(await agentV1Fetch(g, `/api/v1/agent/marketplace-orders/${encodeURIComponent(rest[1])}/dismiss`, {
package/dist/media.js CHANGED
@@ -51,7 +51,7 @@ export async function uploadAgentMediaFile(params) {
51
51
  : AGENT_MEDIA_UPLOAD_PATHS[params.target];
52
52
  const res = await fetch(`${params.baseUrl.replace(/\/$/, '')}${path}`, {
53
53
  method: 'POST',
54
- headers: { Authorization: `Bearer ${params.apiKey}` },
54
+ headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.1' },
55
55
  body: form,
56
56
  });
57
57
  const data = await res.json().catch(() => ({}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentchainlabsai/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Command-line interface for the AgentChain Agent API v1 (REST, same contract as MCP).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "dependencies": {
20
- "@agentchainlabsai/sdk": "^0.2.0",
20
+ "@agentchainlabsai/sdk": "^0.2.1",
21
21
  "ethers": "^6.13.0"
22
22
  },
23
23
  "publishConfig": {