spendos 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.dockerignore +4 -0
  2. package/.env.example +30 -0
  3. package/AGENTS.md +212 -0
  4. package/BOOTSTRAP.md +55 -0
  5. package/Dockerfile +52 -0
  6. package/HEARTBEAT.md +7 -0
  7. package/IDENTITY.md +23 -0
  8. package/LICENSE +21 -0
  9. package/README.md +162 -0
  10. package/SOUL.md +202 -0
  11. package/SUBMISSION.md +128 -0
  12. package/TOOLS.md +40 -0
  13. package/USER.md +17 -0
  14. package/acp-seller/bin/acp.ts +807 -0
  15. package/acp-seller/config.json +34 -0
  16. package/acp-seller/package.json +55 -0
  17. package/acp-seller/src/commands/agent.ts +328 -0
  18. package/acp-seller/src/commands/bounty.ts +1189 -0
  19. package/acp-seller/src/commands/deploy.ts +414 -0
  20. package/acp-seller/src/commands/job.ts +217 -0
  21. package/acp-seller/src/commands/profile.ts +71 -0
  22. package/acp-seller/src/commands/resource.ts +91 -0
  23. package/acp-seller/src/commands/search.ts +327 -0
  24. package/acp-seller/src/commands/sell.ts +883 -0
  25. package/acp-seller/src/commands/serve.ts +258 -0
  26. package/acp-seller/src/commands/setup.ts +399 -0
  27. package/acp-seller/src/commands/token.ts +88 -0
  28. package/acp-seller/src/commands/wallet.ts +123 -0
  29. package/acp-seller/src/lib/api.ts +118 -0
  30. package/acp-seller/src/lib/auth.ts +291 -0
  31. package/acp-seller/src/lib/bounty.ts +257 -0
  32. package/acp-seller/src/lib/client.ts +42 -0
  33. package/acp-seller/src/lib/config.ts +240 -0
  34. package/acp-seller/src/lib/open.ts +41 -0
  35. package/acp-seller/src/lib/openclawCron.ts +138 -0
  36. package/acp-seller/src/lib/output.ts +104 -0
  37. package/acp-seller/src/lib/wallet.ts +81 -0
  38. package/acp-seller/src/seller/offerings/_shared/preTransactionScan.ts +127 -0
  39. package/acp-seller/src/seller/offerings/canonical-catalog.ts +221 -0
  40. package/acp-seller/src/seller/offerings/spendos/spendos_summarize_url/handlers.ts +20 -0
  41. package/acp-seller/src/seller/offerings/spendos/spendos_summarize_url/offering.json +18 -0
  42. package/acp-seller/src/seller/offerings/spendos/spendos_translate/handlers.ts +21 -0
  43. package/acp-seller/src/seller/offerings/spendos/spendos_translate/offering.json +22 -0
  44. package/acp-seller/src/seller/offerings/spendos/spendos_tweet_gen/handlers.ts +20 -0
  45. package/acp-seller/src/seller/offerings/spendos/spendos_tweet_gen/offering.json +18 -0
  46. package/acp-seller/src/seller/runtime/acpSocket.ts +413 -0
  47. package/acp-seller/src/seller/runtime/logger.ts +36 -0
  48. package/acp-seller/src/seller/runtime/offeringTypes.ts +52 -0
  49. package/acp-seller/src/seller/runtime/offerings.ts +277 -0
  50. package/acp-seller/src/seller/runtime/paymentVerification.test.ts +207 -0
  51. package/acp-seller/src/seller/runtime/paymentVerification.ts +363 -0
  52. package/acp-seller/src/seller/runtime/seller.onchain.test.ts +220 -0
  53. package/acp-seller/src/seller/runtime/seller.test.ts +823 -0
  54. package/acp-seller/src/seller/runtime/seller.ts +1041 -0
  55. package/acp-seller/src/seller/runtime/sellerApi.ts +71 -0
  56. package/acp-seller/src/seller/runtime/startup.ts +270 -0
  57. package/acp-seller/src/seller/runtime/types.ts +62 -0
  58. package/acp-seller/tsconfig.json +20 -0
  59. package/bin/spendos.js +23 -0
  60. package/contracts/SpendOSAudit.sol +29 -0
  61. package/dist/mcp-server.mjs +153 -0
  62. package/jobs/translate.json +7 -0
  63. package/jobs/tweet-gen.json +7 -0
  64. package/openclaw.json +41 -0
  65. package/package.json +49 -0
  66. package/plugins/spendos-events/index.ts +78 -0
  67. package/plugins/spendos-events/package.json +14 -0
  68. package/policies/enforce-bounds.mjs +71 -0
  69. package/public/index.html +509 -0
  70. package/public/landing.html +241 -0
  71. package/railway.json +12 -0
  72. package/railway.toml +12 -0
  73. package/scripts/deploy.ts +48 -0
  74. package/scripts/test-x402-mainnet.ts +30 -0
  75. package/scripts/xmtp-listener.ts +61 -0
  76. package/setup.sh +278 -0
  77. package/skills/spendos/skill.md +26 -0
  78. package/src/agent.ts +152 -0
  79. package/src/audit.ts +166 -0
  80. package/src/governance.ts +367 -0
  81. package/src/job-registry.ts +306 -0
  82. package/src/mcp-public.ts +145 -0
  83. package/src/mcp-server.ts +171 -0
  84. package/src/opportunity-scanner.ts +138 -0
  85. package/src/server.ts +870 -0
  86. package/src/venice-x402.ts +234 -0
  87. package/src/xmtp.ts +109 -0
  88. package/src/zerion.ts +58 -0
  89. package/start.sh +168 -0
  90. package/tsconfig.json +14 -0
@@ -0,0 +1,807 @@
1
+ #!/usr/bin/env npx tsx
2
+ // =============================================================================
3
+ // acp — Unified CLI for the Agent Commerce Protocol
4
+ //
5
+ // Usage: acp <command> [subcommand] [args] [flags]
6
+ //
7
+ // Global flags:
8
+ // --json Output raw JSON (for agent/machine consumption)
9
+ // --help, -h Show help
10
+ // --version Show version
11
+ // =============================================================================
12
+
13
+ import { createRequire } from "module";
14
+ import { setJsonMode } from "../src/lib/output.js";
15
+ import { requireApiKey } from "../src/lib/config.js";
16
+ import { SEARCH_DEFAULTS } from "../src/commands/search.js";
17
+
18
+ const require = createRequire(import.meta.url);
19
+ const { version: VERSION } = require("../package.json");
20
+
21
+ // -- Arg parsing helpers --
22
+
23
+ function hasFlag(args: string[], ...flags: string[]): boolean {
24
+ return args.some((a) => flags.includes(a));
25
+ }
26
+
27
+ function removeFlags(args: string[], ...flags: string[]): string[] {
28
+ return args.filter((a) => !flags.includes(a));
29
+ }
30
+
31
+ function getFlagValue(args: string[], flag: string): string | undefined {
32
+ // --flag value
33
+ const idx = args.indexOf(flag);
34
+ if (idx !== -1 && idx + 1 < args.length) {
35
+ return args[idx + 1];
36
+ }
37
+ // --flag=value
38
+ const prefix = flag + "=";
39
+ const eq = args.find((a) => typeof a === "string" && a.startsWith(prefix));
40
+ if (eq) return eq.slice(prefix.length);
41
+ return undefined;
42
+ }
43
+
44
+ function removeFlagWithValue(args: string[], flag: string): string[] {
45
+ const idx = args.indexOf(flag);
46
+ if (idx !== -1) {
47
+ return [...args.slice(0, idx), ...args.slice(idx + 2)];
48
+ }
49
+ return args;
50
+ }
51
+
52
+ // -- Help text --
53
+
54
+ const isTTY = process.stdout.isTTY === true;
55
+ const bold = (s: string) => (isTTY ? `\x1b[1m${s}\x1b[0m` : s);
56
+ const dim = (s: string) => (isTTY ? `\x1b[2m${s}\x1b[0m` : s);
57
+ const cyan = (s: string) => (isTTY ? `\x1b[36m${s}\x1b[0m` : s);
58
+ const yellow = (s: string) => (isTTY ? `\x1b[33m${s}\x1b[0m` : s);
59
+
60
+ function cmd(command: string, desc: string, indent = 2): string {
61
+ const pad = 43 - indent;
62
+ return `${" ".repeat(indent)}${bold(command.padEnd(pad))}${dim(desc)}`;
63
+ }
64
+
65
+ function flag(name: string, desc: string): string {
66
+ return `${" ".repeat(4)}${yellow(name.padEnd(41))}${dim(desc)}`;
67
+ }
68
+
69
+ function section(title: string): string {
70
+ return ` ${cyan(title)}`;
71
+ }
72
+
73
+ function buildHelp(): string {
74
+ const lines = [
75
+ "",
76
+ ` ${bold("acp")} ${dim("—")} Agent Commerce Protocol CLI`,
77
+ "",
78
+ ` ${dim("Usage:")} ${bold("acp")} ${dim("<command> [subcommand] [args] [flags]")}`,
79
+ "",
80
+ section("Getting Started"),
81
+ cmd("setup", "Interactive setup (login + create agent)"),
82
+ cmd("login", "Re-authenticate session"),
83
+ cmd("whoami", "Show current agent profile summary"),
84
+ "",
85
+ section("Agent Management"),
86
+ cmd("agent list", "Show all agents (syncs from server)"),
87
+ cmd("agent create <agent-name>", "Create a new agent"),
88
+ cmd("agent switch <agent-name>", "Switch the active agent"),
89
+ flag("--wallet <address>", "Switch by wallet address instead of name"),
90
+ "",
91
+ section("Wallet"),
92
+ cmd("wallet address", "Get agent wallet address"),
93
+ cmd("wallet balance", "Get all token balances"),
94
+ cmd("wallet topup", "Get topup URL to add funds"),
95
+ "",
96
+ section("Token"),
97
+ cmd("token launch <symbol> <desc>", "Launch agent token"),
98
+ flag("--image <url>", "Token image URL"),
99
+ cmd("token info", "Get agent token details"),
100
+ "",
101
+ section("Profile"),
102
+ cmd("profile show", "Show full agent profile"),
103
+ cmd("profile update name <value>", "Update agent name"),
104
+ cmd("profile update description <value>", "Update agent description"),
105
+ cmd("profile update profilePic <url>", "Update agent profile picture"),
106
+ "",
107
+ section("Marketplace"),
108
+ cmd("browse <query>", "Browse agents on the marketplace"),
109
+ flag("--mode <hybrid|vector|keyword>", "Search strategy (default: hybrid)"),
110
+ flag("--contains <text>", "Keep results containing these terms"),
111
+ flag("--match <all|any>", "Term matching for --contains (default: all)"),
112
+ "",
113
+ cmd("job create <wallet> <offering>", "Start a job with an agent"),
114
+ flag("--requirements '<json>'", "Service requirements (JSON)"),
115
+ cmd("job status <job-id>", "Check job status"),
116
+ cmd("job active [page] [pageSize]", "List active jobs"),
117
+ cmd("job completed [page] [pageSize]", "List completed jobs"),
118
+ cmd("bounty create [query]", "Create a new bounty (interactive or flags)"),
119
+ flag("--title <text>", "Bounty title"),
120
+ flag("--description <text>", "Bounty description"),
121
+ flag("--budget <number>", "Budget in USD"),
122
+ flag("--category <digital|physical>", "Category (default: digital)"),
123
+ flag("--tags <csv>", "Comma-separated tags"),
124
+ cmd("bounty poll", "Poll all active bounties (cron-safe)"),
125
+ cmd("bounty list", "List active local bounties"),
126
+ cmd("bounty status <bounty-id>", "Get bounty details from server"),
127
+ cmd("bounty select <bounty-id>", "Select candidate and create ACP job"),
128
+ cmd("bounty update <bounty-id>", "Update an open bounty"),
129
+ "",
130
+ cmd("resource query <url>", "Query an agent's resource by URL"),
131
+ flag("--params '<json>'", "Parameters for the resource (JSON)"),
132
+ "",
133
+ section("Selling Services"),
134
+ cmd("sell init <offering-name>", "Scaffold a new offering"),
135
+ cmd("sell create <offering-name>", "Register offering on ACP"),
136
+ cmd("sell delete <offering-name>", "Delist offering from ACP"),
137
+ cmd("sell list", "Show all offerings with status"),
138
+ cmd("sell inspect <offering-name>", "Detailed view of an offering"),
139
+ "",
140
+ cmd("sell resource init <resource-name>", "Scaffold a new resource"),
141
+ cmd("sell resource create <resource-name>", "Register resource on ACP"),
142
+ cmd("sell resource delete <resource-name>", "Delete resource from ACP"),
143
+ cmd("sell resource list", "Show all resources with status"),
144
+ "",
145
+ section("Seller Runtime"),
146
+ cmd("serve start", "Start the seller runtime"),
147
+ cmd("serve stop", "Stop the seller runtime"),
148
+ cmd("serve status", "Show seller runtime status"),
149
+ cmd("serve logs", "Show recent seller logs"),
150
+ flag("--follow, -f", "Tail logs in real time"),
151
+ flag("--offering <name>", "Filter logs by offering name"),
152
+ flag("--job <id>", "Filter logs by job ID"),
153
+ flag("--level <level>", "Filter logs by level (e.g. error)"),
154
+ "",
155
+ section("Cloud Deployment"),
156
+ cmd("serve deploy railway", "Deploy seller runtime to Railway"),
157
+ cmd("serve deploy railway setup", "First-time Railway project setup"),
158
+ cmd("serve deploy railway status", "Show remote deployment status"),
159
+ cmd("serve deploy railway logs", "Show remote deployment logs"),
160
+ flag("--follow, -f", "Tail logs in real time"),
161
+ flag("--offering <name>", "Filter logs by offering name"),
162
+ flag("--job <id>", "Filter logs by job ID"),
163
+ flag("--level <level>", "Filter logs by level (e.g. error)"),
164
+ cmd("serve deploy railway teardown", "Remove Railway deployment"),
165
+ cmd("serve deploy railway env", "List env vars on Railway"),
166
+ cmd("serve deploy railway env set", "Set env var (KEY=value)"),
167
+ cmd("serve deploy railway env delete", "Delete an env var"),
168
+ "",
169
+ section("Flags"),
170
+ flag("--json", "Output raw JSON (for agents/scripts)"),
171
+ flag("--help, -h", "Show this help"),
172
+ flag("--version, -v", "Show version"),
173
+ "",
174
+ ];
175
+ return lines.join("\n");
176
+ }
177
+
178
+ function buildCommandHelp(command: string): string | undefined {
179
+ const h: Record<string, () => string> = {
180
+ setup: () =>
181
+ [
182
+ "",
183
+ ` ${bold("acp setup")} ${dim("— Interactive setup")}`,
184
+ "",
185
+ ` ${dim("Guides you through:")}`,
186
+ ` 1. Login to app.virtuals.io`,
187
+ ` 2. Select or create an agent`,
188
+ ` 3. Optionally launch an agent token`,
189
+ "",
190
+ ].join("\n"),
191
+
192
+ agent: () =>
193
+ [
194
+ "",
195
+ ` ${bold("acp agent")} ${dim("— Manage multiple agents")}`,
196
+ "",
197
+ cmd("list", "Show all agents (fetches from server)"),
198
+ cmd("create <agent-name>", "Create a new agent"),
199
+ cmd("switch <agent-name>", "Switch active agent"),
200
+ flag("--wallet <address>", "Switch by wallet address instead of name"),
201
+ "",
202
+ ` ${dim("All commands auto-prompt login if your session has expired.")}`,
203
+ "",
204
+ ].join("\n"),
205
+
206
+ wallet: () =>
207
+ [
208
+ "",
209
+ ` ${bold("acp wallet")} ${dim("— Manage your agent wallet")}`,
210
+ "",
211
+ cmd("address", "Get your wallet address (Base chain)"),
212
+ cmd("balance", "Get all token balances in your wallet"),
213
+ "",
214
+ ].join("\n"),
215
+
216
+ browse: () =>
217
+ [
218
+ "",
219
+ ` ${bold("acp browse <query>")} ${dim("— Browse agents on the marketplace")}`,
220
+ "",
221
+ ` ${cyan("Search Mode")}`,
222
+ flag(
223
+ "--mode <hybrid|vector|keyword>",
224
+ `Search strategy (default: ${SEARCH_DEFAULTS.mode})`,
225
+ ),
226
+ ` ${dim("hybrid: BM25 + vector embeddings")}`,
227
+ ` ${dim("vector: vector embeddings")}`,
228
+ ` ${dim("keyword: BM25")}`,
229
+ ` ${dim("Indexed data: agent name, agent description, and job descriptions.")}`,
230
+ "",
231
+ ` ${cyan("Search Filters and Configuration")}`,
232
+ flag("--contains <text>", "Keep results containing these terms"),
233
+ flag(
234
+ "--match <all|any>",
235
+ `Term matching for --contains (default: ${SEARCH_DEFAULTS.match})`,
236
+ ),
237
+ flag(
238
+ "--similarity-cutoff <0-1>",
239
+ `Min vector similarity score (default: ${SEARCH_DEFAULTS.similarityCutoff})`,
240
+ ),
241
+ flag(
242
+ "--sparse-cutoff <float>",
243
+ `Min keyword score, keyword mode only (default: ${SEARCH_DEFAULTS.sparseCutoff})`,
244
+ ),
245
+ flag(
246
+ "--top-k <n>",
247
+ `Number of results to return (default: ${SEARCH_DEFAULTS.topK})`,
248
+ ),
249
+ "",
250
+ ` ${dim(`Defaults: mode=${SEARCH_DEFAULTS.mode}, similarity cutoff=${SEARCH_DEFAULTS.similarityCutoff}, top-k=${SEARCH_DEFAULTS.topK}`)}`,
251
+ "",
252
+ ].join("\n"),
253
+
254
+ job: () =>
255
+ [
256
+ "",
257
+ ` ${bold("acp job")} ${dim("— Create and monitor jobs")}`,
258
+ "",
259
+ cmd("create <wallet> <offering>", "Start a job with an agent"),
260
+ flag("--requirements '<json>'", "Service requirements (JSON)"),
261
+ ` ${dim(
262
+ 'Example: acp job create 0x1234 "Execute Trade" --requirements \'{"pair":"ETH/USDC"}\'',
263
+ )}`,
264
+ "",
265
+ cmd("status <job-id>", "Check job status and deliverable"),
266
+ ` ${dim("Example: acp job status 12345")}`,
267
+ "",
268
+ cmd("active [page] [pageSize]", "List active jobs"),
269
+ cmd("completed [page] [pageSize]", "List completed jobs"),
270
+ ` ${dim("Pagination: positional args or --page N --pageSize N")}`,
271
+ "",
272
+ ].join("\n"),
273
+
274
+ bounty: () =>
275
+ [
276
+ "",
277
+ ` ${bold("acp bounty")} ${dim("— Manage local bounty lifecycle")}`,
278
+ "",
279
+ cmd("create [query]", "Create a bounty (interactive or via flags)"),
280
+ ` ${dim('Interactive: acp bounty create "video production"')}`,
281
+ ` ${dim('With flags: acp bounty create --title "Music video" --description "Cute girl dancing animation for my song" --budget 50 --tags "video,music" --category digital --source-channel telegram --json')}`,
282
+ "",
283
+ flag(
284
+ "--title <text>",
285
+ "Bounty title (triggers non-interactive mode, also used for update)",
286
+ ),
287
+ flag(
288
+ "--description <text>",
289
+ "Description (defaults to title, also used for update)",
290
+ ),
291
+ flag("--budget <number>", "Budget in USD (also used for update)"),
292
+ flag("--category <digital|physical>", "Category (default: digital)"),
293
+ flag("--tags <csv>", "Comma-separated tags (also used for update)"),
294
+ flag(
295
+ "--source-channel <name>",
296
+ "Channel where bounty originated (e.g. telegram, webchat)",
297
+ ),
298
+ flag("--json", "Output result in JSON format (for create)"),
299
+ "",
300
+ cmd("poll", "Poll all active bounties and update local state"),
301
+ cmd("list", "List active local bounties"),
302
+ cmd("status <bounty-id>", "Get bounty details from server"),
303
+ flag("--sync", "Sync job status with backend before fetching details"),
304
+ cmd(
305
+ "select <bounty-id>",
306
+ "Pick pending_match candidate, create ACP job, confirm match",
307
+ ),
308
+ cmd("update <bounty-id>", "Update an open bounty"),
309
+ flag("--title <text>", "New title (for update)"),
310
+ flag("--description <text>", "New description (for update)"),
311
+ flag("--budget <number>", "New budget in USD (for update)"),
312
+ flag("--tags <csv>", "New tags (for update)"),
313
+ "",
314
+ cmd("cleanup <bounty-id>", "Remove local bounty state"),
315
+ "",
316
+ ].join("\n"),
317
+
318
+ token: () =>
319
+ [
320
+ "",
321
+ ` ${bold("acp token")} ${dim("— Manage your agent token")}`,
322
+ "",
323
+ cmd(
324
+ "launch <symbol> <description>",
325
+ "Launch your agent's token (one per agent)",
326
+ ),
327
+ flag("--image <url>", "Token image URL"),
328
+ ` ${dim('Example: acp token launch MYAGENT "Agent governance token"')}`,
329
+ "",
330
+ cmd("info", "Get your agent's token details"),
331
+ "",
332
+ ].join("\n"),
333
+
334
+ profile: () =>
335
+ [
336
+ "",
337
+ ` ${bold("acp profile")} ${dim("— Manage your agent profile")}`,
338
+ "",
339
+ cmd("show", "Show your full agent profile"),
340
+ "",
341
+ cmd("update name <value>", "Update your agent's name"),
342
+ cmd("update description <value>", "Update your agent's description"),
343
+ cmd("update profilePic <url>", "Update your agent's profile picture"),
344
+ "",
345
+ ` ${dim('Example: acp profile update description "Specializes in trading"')}`,
346
+ "",
347
+ ].join("\n"),
348
+
349
+ sell: () =>
350
+ [
351
+ "",
352
+ ` ${bold("acp sell")} ${dim("— Create and manage service offerings")}`,
353
+ "",
354
+ cmd("init <offering-name>", "Scaffold a new offering"),
355
+ cmd("create <offering-name>", "Register offering on ACP"),
356
+ cmd("delete <offering-name>", "Delist offering from ACP"),
357
+ cmd("list", "Show all offerings with status"),
358
+ cmd("inspect <offering-name>", "Detailed view of an offering"),
359
+ "",
360
+ cmd("resource init <resource-name>", "Scaffold a new resource"),
361
+ cmd("resource create <resource-name>", "Register resource on ACP"),
362
+ cmd("resource delete <resource-name>", "Delete resource from ACP"),
363
+ cmd("resource list", "Show all resources with status"),
364
+ "",
365
+ ` ${dim("Workflow:")}`,
366
+ ` acp sell init my_service`,
367
+ ` ${dim("# Edit offerings/my_service/offering.json and handlers.ts")}`,
368
+ ` acp sell create my_service`,
369
+ ` acp serve start`,
370
+ "",
371
+ ].join("\n"),
372
+
373
+ serve: () =>
374
+ [
375
+ "",
376
+ ` ${bold("acp serve")} ${dim("— Manage the seller runtime")}`,
377
+ "",
378
+ cmd("start", "Start the seller runtime (listens for jobs)"),
379
+ cmd("stop", "Stop the seller runtime"),
380
+ cmd("status", "Show whether the seller is running"),
381
+ cmd("logs", "Show recent seller logs (last 50 lines)"),
382
+ flag("--follow, -f", "Tail logs in real time (Ctrl+C to stop)"),
383
+ flag("--offering <name>", "Filter logs by offering name"),
384
+ flag("--job <id>", "Filter logs by job ID"),
385
+ flag("--level <level>", "Filter logs by level (e.g. error)"),
386
+ "",
387
+ cmd("deploy railway", "Deploy seller runtime to Railway"),
388
+ cmd("deploy railway setup", "First-time Railway project setup"),
389
+ cmd("deploy railway status", "Show remote deployment status"),
390
+ cmd("deploy railway logs", "Show remote deployment logs"),
391
+ flag("--follow, -f", "Tail logs in real time"),
392
+ flag("--offering <name>", "Filter logs by offering name"),
393
+ flag("--job <id>", "Filter logs by job ID"),
394
+ flag("--level <level>", "Filter logs by level (e.g. error)"),
395
+ cmd("deploy railway teardown", "Remove Railway deployment"),
396
+ cmd("deploy railway env", "List env vars on Railway"),
397
+ cmd("deploy railway env set KEY=val", "Set an env var"),
398
+ cmd("deploy railway env delete KEY", "Delete an env var"),
399
+ "",
400
+ ].join("\n"),
401
+
402
+ deploy: () =>
403
+ [
404
+ "",
405
+ ` ${bold("acp serve deploy")} ${dim("— Deploy seller runtime to the cloud")}`,
406
+ "",
407
+ ` ${dim("Workflow:")}`,
408
+ ` acp serve deploy railway setup ${dim("# First-time setup")}`,
409
+ ` acp sell init my_service ${dim("# Create offering")}`,
410
+ ` acp sell create my_service ${dim("# Register on ACP")}`,
411
+ ` acp serve deploy railway ${dim("# Deploy to Railway")}`,
412
+ "",
413
+ ` ${dim("Management:")}`,
414
+ ` acp serve deploy railway status ${dim("# Check deployment")}`,
415
+ ` acp serve deploy railway logs -f ${dim("# Tail logs")}`,
416
+ ` acp serve deploy railway teardown ${dim("# Remove deployment")}`,
417
+ "",
418
+ ` ${dim("Environment Variables:")}`,
419
+ ` acp serve deploy railway env ${dim("# List env vars")}`,
420
+ ` acp serve deploy railway env set KEY=val ${dim("# Set an env var")}`,
421
+ ` acp serve deploy railway env delete KEY ${dim("# Delete an env var")}`,
422
+ "",
423
+ ].join("\n"),
424
+
425
+ resource: () =>
426
+ [
427
+ "",
428
+ ` ${bold("acp resource")} ${dim("— Query an agent's resources by URL")}`,
429
+ "",
430
+ cmd("query <url>", "Query an agent's resource by its URL"),
431
+ flag("--params '<json>'", "Parameters to pass to the resource (JSON)"),
432
+ "",
433
+ ` ${dim("Examples:")}`,
434
+ ` acp resource query https://api.example.com/market-data`,
435
+ ` acp resource query https://api.example.com/market-data --params '{"symbol":"BTC"}'`,
436
+ "",
437
+ ` ${dim("Note: Always uses GET requests. Params are appended as query string.")}`,
438
+ "",
439
+ ].join("\n"),
440
+ };
441
+
442
+ return h[command]?.();
443
+ }
444
+
445
+ // -- Main --
446
+
447
+ async function main(): Promise<void> {
448
+ let args = process.argv.slice(2);
449
+
450
+ // Global flags
451
+ const jsonFlag = hasFlag(args, "--json") || process.env.ACP_JSON === "1";
452
+ if (jsonFlag) setJsonMode(true);
453
+ args = removeFlags(args, "--json");
454
+
455
+ if (hasFlag(args, "--version", "-v")) {
456
+ console.log(VERSION);
457
+ return;
458
+ }
459
+
460
+ if (args.length === 0 || hasFlag(args, "--help", "-h")) {
461
+ const cmd = args.find((a) => !a.startsWith("-"));
462
+ if (cmd && buildCommandHelp(cmd)) {
463
+ console.log(buildCommandHelp(cmd));
464
+ } else {
465
+ console.log(buildHelp());
466
+ }
467
+ return;
468
+ }
469
+
470
+ const [command, subcommand, ...rest] = args;
471
+
472
+ // Commands that don't need API key
473
+ if (command === "version") {
474
+ console.log(VERSION);
475
+ return;
476
+ }
477
+
478
+ if (command === "setup") {
479
+ const { setup } = await import("../src/commands/setup.js");
480
+ return setup();
481
+ }
482
+
483
+ if (command === "login") {
484
+ const { login } = await import("../src/commands/setup.js");
485
+ return login();
486
+ }
487
+
488
+ if (command === "agent") {
489
+ const agent = await import("../src/commands/agent.js");
490
+ if (subcommand === "list") return agent.list();
491
+ if (subcommand === "create") return agent.create(rest[0]);
492
+ if (subcommand === "switch") {
493
+ const walletAddr = getFlagValue(rest, "--wallet");
494
+ if (walletAddr) return agent.switchAgent(walletAddr);
495
+ const nameArg = removeFlagWithValue(rest, "--wallet")[0];
496
+ return agent.switchAgentByName(nameArg);
497
+ }
498
+ console.log(buildCommandHelp("agent"));
499
+ return;
500
+ }
501
+
502
+ // Check for help on specific command
503
+ if (subcommand === "--help" || subcommand === "-h") {
504
+ if (buildCommandHelp(command)) {
505
+ console.log(buildCommandHelp(command));
506
+ } else {
507
+ console.log(buildHelp());
508
+ }
509
+ return;
510
+ }
511
+
512
+ // Browse uses external API — no API key required
513
+ if (command === "browse") {
514
+ const { search } = await import("../src/commands/search.js");
515
+ let searchArgs = [subcommand, ...rest].filter(Boolean);
516
+
517
+ // Parse search options
518
+ const mode = getFlagValue(searchArgs, "--mode") as
519
+ | "hybrid"
520
+ | "vector"
521
+ | "keyword"
522
+ | undefined;
523
+ searchArgs = removeFlagWithValue(searchArgs, "--mode");
524
+
525
+ const contains = getFlagValue(searchArgs, "--contains");
526
+ searchArgs = removeFlagWithValue(searchArgs, "--contains");
527
+
528
+ const matchVal = getFlagValue(searchArgs, "--match") as
529
+ | "all"
530
+ | "any"
531
+ | undefined;
532
+ searchArgs = removeFlagWithValue(searchArgs, "--match");
533
+
534
+ const simCutoff = getFlagValue(searchArgs, "--similarity-cutoff");
535
+ searchArgs = removeFlagWithValue(searchArgs, "--similarity-cutoff");
536
+
537
+ const sparCutoff = getFlagValue(searchArgs, "--sparse-cutoff");
538
+ searchArgs = removeFlagWithValue(searchArgs, "--sparse-cutoff");
539
+
540
+ const topK = getFlagValue(searchArgs, "--top-k");
541
+ searchArgs = removeFlagWithValue(searchArgs, "--top-k");
542
+
543
+ // Remaining args (non-flags) form the query
544
+ const query = searchArgs.filter((a) => a && !a.startsWith("-")).join(" ");
545
+
546
+ return search(query, {
547
+ mode,
548
+ contains,
549
+ match: matchVal,
550
+ similarityCutoff:
551
+ simCutoff !== undefined ? parseFloat(simCutoff) : undefined,
552
+ sparseCutoff:
553
+ sparCutoff !== undefined ? parseFloat(sparCutoff) : undefined,
554
+ topK: topK !== undefined ? parseInt(topK, 10) : undefined,
555
+ });
556
+ }
557
+
558
+ // All other commands need API key
559
+ requireApiKey();
560
+
561
+ switch (command) {
562
+ case "whoami": {
563
+ const { whoami } = await import("../src/commands/setup.js");
564
+ return whoami();
565
+ }
566
+
567
+ case "wallet": {
568
+ const wallet = await import("../src/commands/wallet.js");
569
+ if (subcommand === "address") return wallet.address();
570
+ if (subcommand === "balance") return wallet.balance();
571
+ if (subcommand === "topup") return wallet.topup();
572
+ console.log(buildCommandHelp("wallet"));
573
+ return;
574
+ }
575
+
576
+ case "job": {
577
+ const job = await import("../src/commands/job.js");
578
+ if (subcommand === "create") {
579
+ const walletAddr = rest[0];
580
+ const offering = rest[1];
581
+ let remaining = rest.slice(2);
582
+ const reqJson = getFlagValue(remaining, "--requirements");
583
+ let requirements: Record<string, unknown> = {};
584
+ if (reqJson) {
585
+ try {
586
+ requirements = JSON.parse(reqJson);
587
+ } catch {
588
+ console.error("Error: Invalid JSON in --requirements");
589
+ process.exit(1);
590
+ }
591
+ }
592
+ return job.create(walletAddr, offering, requirements);
593
+ }
594
+ if (subcommand === "status") {
595
+ return job.status(rest[0]);
596
+ }
597
+ if (subcommand === "active" || subcommand === "completed") {
598
+ const pageStr = getFlagValue(rest, "--page") ?? rest[0];
599
+ const pageSizeStr = getFlagValue(rest, "--pageSize") ?? rest[1];
600
+ const page =
601
+ pageStr != null && /^\d+$/.test(String(pageStr))
602
+ ? parseInt(String(pageStr), 10)
603
+ : undefined;
604
+ const pageSize =
605
+ pageSizeStr != null && /^\d+$/.test(String(pageSizeStr))
606
+ ? parseInt(String(pageSizeStr), 10)
607
+ : undefined;
608
+ const opts = {
609
+ page: Number.isNaN(page) ? undefined : page,
610
+ pageSize: Number.isNaN(pageSize) ? undefined : pageSize,
611
+ };
612
+ if (subcommand === "active") return job.active(opts);
613
+ return job.completed(opts);
614
+ }
615
+ console.log(buildCommandHelp("job"));
616
+ return;
617
+ }
618
+
619
+ case "bounty": {
620
+ const bounty = await import("../src/commands/bounty.js");
621
+ if (subcommand === "create") {
622
+ // Check for structured flags (non-interactive mode)
623
+ const titleFlag = getFlagValue(rest, "--title");
624
+ const descFlag = getFlagValue(rest, "--description");
625
+ const budgetFlag = getFlagValue(rest, "--budget");
626
+ const categoryFlag = getFlagValue(rest, "--category");
627
+ const tagsFlag = getFlagValue(rest, "--tags");
628
+ const sourceChannelFlag = getFlagValue(rest, "--source-channel");
629
+
630
+ if (titleFlag || budgetFlag) {
631
+ // Non-interactive: all from flags
632
+ const budget = budgetFlag != null ? Number(budgetFlag) : undefined;
633
+ return bounty.create(undefined, {
634
+ title: titleFlag,
635
+ description: descFlag,
636
+ budget: Number.isFinite(budget) ? budget : undefined,
637
+ category: categoryFlag,
638
+ tags: tagsFlag,
639
+ sourceChannel: sourceChannelFlag,
640
+ });
641
+ }
642
+
643
+ // Interactive fallback: treat remaining positional args as query seed
644
+ const query = rest
645
+ .filter((a) => a != null && !String(a).startsWith("-"))
646
+ .join(" ");
647
+ return bounty.create(query || undefined, {
648
+ sourceChannel: sourceChannelFlag,
649
+ });
650
+ }
651
+ if (subcommand === "update") {
652
+ const updateBountyId = rest[0];
653
+ const updateTitle = getFlagValue(rest, "--title");
654
+ const updateDesc = getFlagValue(rest, "--description");
655
+ const updateBudgetStr = getFlagValue(rest, "--budget");
656
+ const updateTags = getFlagValue(rest, "--tags");
657
+ const updateBudget =
658
+ updateBudgetStr != null ? Number(updateBudgetStr) : undefined;
659
+ return bounty.update(updateBountyId, {
660
+ title: updateTitle,
661
+ description: updateDesc,
662
+ budget: Number.isFinite(updateBudget) ? updateBudget : undefined,
663
+ tags: updateTags,
664
+ });
665
+ }
666
+ if (subcommand === "poll") return bounty.poll();
667
+ if (subcommand === "list") return bounty.list();
668
+ if (subcommand === "status") {
669
+ const syncFlag = hasFlag(rest, "--sync");
670
+ const statusBountyId = rest.filter((a) => a !== "--sync")[0];
671
+ return bounty.status(statusBountyId, { sync: syncFlag });
672
+ }
673
+ if (subcommand === "select") return bounty.select(rest[0]);
674
+ if (subcommand === "cleanup") return bounty.cleanup(rest[0]);
675
+ console.log(buildCommandHelp("bounty"));
676
+ return;
677
+ }
678
+
679
+ case "token": {
680
+ const token = await import("../src/commands/token.js");
681
+ if (subcommand === "launch") {
682
+ let remaining = rest;
683
+ const imageUrl = getFlagValue(remaining, "--image");
684
+ remaining = removeFlagWithValue(remaining, "--image");
685
+ const symbol = remaining[0];
686
+ const description = remaining.slice(1).join(" ");
687
+ return token.launch(symbol, description, imageUrl);
688
+ }
689
+ if (subcommand === "info") return token.info();
690
+ console.log(buildCommandHelp("token"));
691
+ return;
692
+ }
693
+
694
+ case "profile": {
695
+ const profile = await import("../src/commands/profile.js");
696
+ if (subcommand === "show") return profile.show();
697
+ if (subcommand === "update") {
698
+ const key = rest[0];
699
+ const value = rest.slice(1).join(" ");
700
+ return profile.update(key, value);
701
+ }
702
+ console.log(buildCommandHelp("profile"));
703
+ return;
704
+ }
705
+
706
+ case "sell": {
707
+ const sell = await import("../src/commands/sell.js");
708
+ if (subcommand === "resource") {
709
+ const resourceSubcommand = rest[0];
710
+ if (resourceSubcommand === "init") return sell.resourceInit(rest[1]);
711
+ if (resourceSubcommand === "create")
712
+ return sell.resourceCreate(rest[1]);
713
+ if (resourceSubcommand === "delete")
714
+ return sell.resourceDelete(rest[1]);
715
+ if (resourceSubcommand === "list") return sell.resourceList();
716
+ console.log(buildCommandHelp("sell"));
717
+ return;
718
+ }
719
+ if (subcommand === "init") return sell.init(rest[0]);
720
+ if (subcommand === "create") return sell.create(rest[0]);
721
+ if (subcommand === "delete") return sell.del(rest[0]);
722
+ if (subcommand === "list") return sell.list();
723
+ if (subcommand === "inspect") return sell.inspect(rest[0]);
724
+ console.log(buildCommandHelp("sell"));
725
+ return;
726
+ }
727
+
728
+ case "serve": {
729
+ const serve = await import("../src/commands/serve.js");
730
+ if (subcommand === "start") return serve.start();
731
+ if (subcommand === "stop") return serve.stop();
732
+ if (subcommand === "status") return serve.status();
733
+ if (subcommand === "logs") {
734
+ const filter = {
735
+ offering: getFlagValue(rest, "--offering"),
736
+ job: getFlagValue(rest, "--job"),
737
+ level: getFlagValue(rest, "--level"),
738
+ };
739
+ return serve.logs(hasFlag(rest, "--follow", "-f"), filter);
740
+ }
741
+ if (subcommand === "deploy") {
742
+ const deploy = await import("../src/commands/deploy.js");
743
+ const provider = rest[0];
744
+ if (provider === "railway") {
745
+ const providerSub = rest[1];
746
+ if (!providerSub) return deploy.deploy();
747
+ if (providerSub === "setup") return deploy.setup();
748
+ if (providerSub === "status") return deploy.status();
749
+ if (providerSub === "logs") {
750
+ const logsArgs = rest.slice(2);
751
+ const filter = {
752
+ offering: getFlagValue(logsArgs, "--offering"),
753
+ job: getFlagValue(logsArgs, "--job"),
754
+ level: getFlagValue(logsArgs, "--level"),
755
+ };
756
+ return deploy.logs(hasFlag(logsArgs, "--follow", "-f"), filter);
757
+ }
758
+ if (providerSub === "teardown") return deploy.teardown();
759
+ if (providerSub === "env") {
760
+ const envAction = rest[2];
761
+ if (!envAction) return deploy.env();
762
+ if (envAction === "set") return deploy.envSet(rest[3]);
763
+ if (envAction === "delete") return deploy.envDelete(rest[3]);
764
+ console.log(buildCommandHelp("deploy"));
765
+ return;
766
+ }
767
+ }
768
+ console.log(buildCommandHelp("deploy"));
769
+ return;
770
+ }
771
+ console.log(buildCommandHelp("serve"));
772
+ return;
773
+ }
774
+
775
+ case "resource": {
776
+ const resource = await import("../src/commands/resource.js");
777
+ if (subcommand === "query") {
778
+ const url = rest[0];
779
+ const paramsJson = getFlagValue(rest, "--params");
780
+ let params: Record<string, any> | undefined;
781
+ if (paramsJson) {
782
+ try {
783
+ params = JSON.parse(paramsJson);
784
+ } catch {
785
+ console.error("Error: Invalid JSON in --params");
786
+ process.exit(1);
787
+ }
788
+ }
789
+ return resource.query(url, params);
790
+ }
791
+ console.log(buildCommandHelp("resource"));
792
+ return;
793
+ }
794
+
795
+ default:
796
+ console.error(`Unknown command: ${command}\n`);
797
+ console.log(buildHelp());
798
+ process.exit(1);
799
+ }
800
+ }
801
+
802
+ main().catch((e) => {
803
+ console.error(
804
+ JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),
805
+ );
806
+ process.exit(1);
807
+ });