@noimosai/platform-mcp 0.1.3 → 0.1.5

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 (93) hide show
  1. package/README.md +17 -48
  2. package/bin/noimos-platform-mcp.mjs +4 -5
  3. package/dist/cli/cli.js +565 -0
  4. package/dist/cli/command-help.js +395 -0
  5. package/dist/cli/download.js +79 -0
  6. package/dist/cli/index.js +3 -0
  7. package/dist/cli/input-error.js +48 -0
  8. package/dist/cli/local-agents.js +199 -0
  9. package/dist/cli/mcp-supervisor.js +136 -0
  10. package/dist/cli/noimos-cli.js +72 -0
  11. package/dist/cli/project-archive.js +27 -0
  12. package/dist/cli/project-files.js +25 -0
  13. package/dist/cli/project-sync.js +265 -0
  14. package/dist/cli/setup-lock.js +1 -0
  15. package/dist/cli/setup.js +347 -0
  16. package/dist/cli/skills.js +142 -0
  17. package/dist/cli/team-harness-targets.js +337 -0
  18. package/dist/cli/team-harness.js +1562 -0
  19. package/dist/cli/team-resources.js +222 -0
  20. package/dist/cli/team-skills.js +457 -0
  21. package/dist/cli/update-bootstrap.js +437 -0
  22. package/dist/cli/update-release.js +212 -0
  23. package/dist/cli/version.js +1 -0
  24. package/dist/cli/wallet.js +85 -0
  25. package/dist/client/agent-run.js +1425 -0
  26. package/dist/client/browser.js +911 -0
  27. package/dist/client/distribution.js +2 -0
  28. package/dist/client/evm-wallet.js +214 -0
  29. package/dist/client/execution-context.js +14 -0
  30. package/dist/client/failure-recovery.js +128 -0
  31. package/dist/client/index.js +4 -225
  32. package/dist/client/local-input.js +33 -12
  33. package/dist/client/local-profile-lock.js +52 -0
  34. package/dist/client/local-profile.js +181 -0
  35. package/dist/client/local-projects.js +131 -0
  36. package/dist/client/local-setup.js +93 -0
  37. package/dist/client/realtime-voice.js +184 -0
  38. package/dist/client/tool-name-hints.js +48 -0
  39. package/dist/client/wallet-access.js +214 -0
  40. package/dist/mcp/agent-server.js +345 -0
  41. package/dist/mcp/context.js +86 -0
  42. package/dist/mcp/host-schema.js +76 -0
  43. package/dist/mcp/icon.js +1 -0
  44. package/dist/mcp/instructions.js +26 -0
  45. package/dist/mcp/local-projects.js +42 -0
  46. package/dist/mcp/local-upload.js +7 -13
  47. package/dist/mcp/noimos-runtime.js +18 -0
  48. package/dist/mcp/noimos-server.js +38 -0
  49. package/dist/mcp/operations.js +41 -0
  50. package/dist/mcp/parallel-result.js +17 -0
  51. package/dist/mcp/project-sync.js +105 -0
  52. package/dist/mcp/results-reply.js +15 -0
  53. package/dist/mcp/runtime.js +65 -11
  54. package/dist/mcp/server.js +382 -40
  55. package/dist/mcp/team-skill.js +63 -0
  56. package/dist/mcp/update-notice.js +75 -0
  57. package/dist/mcp/version.js +1 -0
  58. package/dist/schema/accountRequestMethods.js +1 -0
  59. package/dist/schema/boundedBody.js +30 -0
  60. package/dist/schema/cronValidator.js +150 -0
  61. package/dist/schema/platformApiKey.js +14 -0
  62. package/dist/schema/platformAppAuth.js +11 -0
  63. package/dist/schema/platformBillingConstants.js +4 -0
  64. package/dist/schema/platformCapabilityFamilies.js +293 -0
  65. package/dist/schema/platformConnectionCatalog.js +39 -0
  66. package/dist/schema/platformConnectionPolicy.js +33 -0
  67. package/dist/schema/platformCustomTools.js +148 -0
  68. package/dist/schema/platformEndUserApi.js +62 -0
  69. package/dist/schema/platformFunding.js +10 -0
  70. package/dist/schema/platformHttpConstraints.js +12 -0
  71. package/dist/schema/platformLocalSetup.js +25 -0
  72. package/dist/schema/platformMcpTools.js +7 -0
  73. package/dist/schema/platformMetaTools.js +63 -0
  74. package/dist/schema/platformMonitorSchedule.js +123 -0
  75. package/dist/schema/platformMonitors.js +159 -0
  76. package/dist/schema/platformOAuth.js +13 -0
  77. package/dist/schema/platformProjectPermissions.js +33 -0
  78. package/dist/schema/platformPublicExecution.js +202 -21
  79. package/dist/schema/platformRoutes.js +122 -0
  80. package/dist/schema/platformSkills.js +11 -0
  81. package/dist/schema/platformTeamSecretName.js +3 -0
  82. package/dist/schema/platformTeamSkillSlug.js +1 -0
  83. package/dist/schema/platformTeamSkills.js +302 -0
  84. package/dist/schema/platformToolAccess.js +62 -0
  85. package/dist/schema/platformTriggers.js +153 -0
  86. package/dist/schema/platformWorkflows.js +100 -0
  87. package/dist/schema/scheduleTimeZone.js +15 -0
  88. package/dist/schema/toolDiscovery.js +71 -0
  89. package/dist/schema/toolInputRepair.js +122 -0
  90. package/dist/schema/toolResultContext.js +51 -0
  91. package/dist/schema/untrustedContent.js +35 -0
  92. package/npm-shrinkwrap.json +73 -0
  93. package/package.json +5 -3
@@ -0,0 +1,565 @@
1
+ import { parseArgs } from "node:util";
2
+ import { createReadStream } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { configFromEnvironment, PlatformClient, PLATFORM_DISTRIBUTIONS, walletAutoTopUpFromEnvironment, platformQuoteRequestSchema, platformCreateExecutionSchema, platformArtifactIdSchema, platformExecutionIdSchema, platformExecutionListQuerySchema, platformConnectionCatalogQuerySchema, toolDiscoveryInputSchema, toolResultSelectionSchema, TOOL_RESULT_PAGE_MAX_ITEMS, executionReceiptContext } from "../client/index.js";
5
+ import { downloadArtifact } from "./download.js";
6
+ import { commandHelp } from "./command-help.js";
7
+ import { CliInputError, cliInputReason, isCliInputFailure, jsonOption } from "./input-error.js";
8
+ import { uploadLocalInput } from "../client/local-input.js";
9
+ import { localProfilePath, runtimeConfig } from "../client/local-profile.js";
10
+ import { localProjectConfig } from "../client/local-projects.js";
11
+ import { isTeamResource, teamResourceCommand } from "./team-resources.js";
12
+ import { PLATFORM_CLI_VERSION } from "./version.js";
13
+ import { AgentRun, agentRunSchema, agentParallelSchema, agentResultSchema, agentResultsSchema, agentResumeSchema, resultFilesFor as resultFiles } from "../client/agent-run.js";
14
+ import { platformTeamQuerySchema } from "../client/index.js";
15
+ import { platformProjectStatusQuerySchema } from "../client/index.js";
16
+ import { PLATFORM_META_LIMITS, PLATFORM_TOOL_REQUEST_MAX_BYTES, platformMetaSearchSchema, platformMetaSchemasSchema, platformMetaConnectionsSchema, platformMetaExecuteSchema, platformMetaWorkbenchSchema, platformMetaBashSchema, platformIdempotencyKeySchema, platformJsonObjectSchema, withEvidentValues } from "../client/index.js";
17
+ function metaInput(schema, input) {
18
+ const json = platformJsonObjectSchema.safeParse(input);
19
+ return json.success ? withEvidentValues(schema, json.data) : input;
20
+ }
21
+ const READ_OPTIONS = { path: { type: "string" }, offset: { type: "string" }, limit: { type: "string" }, "max-chars": { type: "string" }, fields: { type: "string" },
22
+ find: { type: "string" }, pick: { type: "string" }, wait: { type: "string" } };
23
+ function findTerms(text) {
24
+ try {
25
+ const list = JSON.parse(text);
26
+ if (Array.isArray(list) && list.every(term => typeof term === "string"))
27
+ return list;
28
+ }
29
+ catch { }
30
+ return [text];
31
+ }
32
+ const agentRead = (values) => ({ ...(values.path === undefined ? {} : { path: jsonOption("--path", values.path) }),
33
+ ...(values.fields === undefined ? {} : { fields: jsonOption("--fields", values.fields) }), ...(values.offset === undefined ? {} : { offset: Number(values.offset) }),
34
+ ...(values.limit === undefined ? {} : { limit: Number(values.limit) }), ...(values["max-chars"] === undefined ? {} : { maxChars: Number(values["max-chars"]) }),
35
+ ...(values.find === undefined ? {} : { find: findTerms(values.find) }),
36
+ ...(values.pick === undefined ? {} : { pick: jsonOption("--pick", values.pick) }), ...(values.wait === undefined ? {} : { waitSeconds: Number(values.wait) }) });
37
+ const missedRequestedOutput = (result) => "resultError" in result || "fileError" in result || "upstreamRefusal" in result;
38
+ function refuseDuplicates(tokens, message) {
39
+ const names = tokens.filter(token => token.kind === "option").map(token => token.name);
40
+ if (new Set(names).size !== names.length)
41
+ throw new CliInputError(message);
42
+ }
43
+ export const help = `Relvo CLI — discover and run project tools
44
+
45
+ tools --query <capability> --limit 1 --with-schemas
46
+ Find a tool and its input schema in one request; add --app for a connected service.
47
+ Search uses monthly free calls; overage is charged.
48
+ run <tool> '<arguments-json>' [--maximum-charge-micros <integer>] --idempotency-key <key>
49
+ Quote, save the exact purchase, submit, wait and select results in one call.
50
+ Optional: --provider <id> --path '<path-array>' --fields '<names-array>' --find '<terms>'
51
+ --pick '<label-to-paths-json>' --limit <n> --wait <1-60 seconds> --file
52
+ resume <run-id> [--wait <seconds>]
53
+ Recover the same saved purchase after a lost reply; never use a fresh key for unknown work.
54
+ parallel ('<request-json>' | - | --input-file <path>)
55
+ Start independent tools together; return every admission and execution ID without waiting.
56
+ result <execution-id> [--path '<path-array>'] [--fields '<names-array>'] [--find '<terms>'] [--wait <1-60 seconds>] [--file]
57
+ Read retained data without paying for another execution. --find keeps the lines of a
58
+ text that hold a term. --file writes the whole path as JSON Lines under ./.relvo/results.
59
+ results <execution-id>... [--path '<path-array>'] [--fields '<names-array>'] [--find '<terms>'] [--wait <1-60 seconds>]
60
+ Read several executions at once, such as every ID parallel returned.
61
+ setup Sign in and connect your installed agents.
62
+ project list | add | use <project-id>
63
+ Approve and switch projects. Add --project <id> to a tool command for one call.
64
+ help <command> | help all
65
+ Load detailed instructions only when needed.
66
+
67
+ 1 USD = 1000000 micros. A budget does not authorize sending, publishing or account changes.
68
+ Scope comes from the developer API key. Provider keys remain server-side.
69
+ Environment: RELVO_API_KEY and RELVO_API_URL, or the login saved by setup.
70
+ Other commands: tool, quote, execute, execution, wait, executions, connections, connect,
71
+ project, usage, doctor, logout, update, skills, team-skills, custom-tools, monitors, triggers,
72
+ wallet, cancel, artifact-url, download, upload. Legacy NoimosAI Platform also supports access.
73
+ `;
74
+ export const fullHelp = `Relvo CLI
75
+
76
+ Usage: relvo <command>
77
+ relvo <command> --help | help <command>
78
+
79
+ setup --api-url <url> --console-url <origin> [--client auto|claude|codex|terminal]
80
+ [--mcp-runtime registry|installed] [--local] [--no-browser] [--new-project]
81
+ Sign in through the browser and configure every installed agent
82
+ (Claude Code and Codex) with one approval; auto is the default
83
+ doctor Verify the saved login, live catalog and owned MCP connection
84
+ logout Remove the local login; revoke the server key in the console
85
+ update [status|auto on|auto off|pin <version>|rollback]
86
+ Prepare a verified CLI/MCP release for the next process start
87
+ skills list | skills update [--client claude|codex|all]
88
+ Read or explicitly update current first-party guides
89
+ team-skills sync [--client all|<agent>[,<agent>…]] [--project <id>] [--allow-scripts] | team-skills push <SKILL.md, folder, AGENTS.md, CLAUDE.md, agent or rule file> [--kind skill|instructions|agent|rule] [--slug <slug>]
90
+ Copy the team skills, agents, rules and (with --allow-scripts) hooks you may use to every coding agent, or share a local one
91
+ team-skills check [--client <agents>] | apply <snapshot> --yes [--client <agents>] [--allow-scripts | --remove-executable-settings]
92
+ team-skills auto-sync on|off|status
93
+ Detect project changes, ask before importing, and configure local MCP notification checks
94
+ team-skills list | get <slug> | versions <slug> | restore <slug> --version <n> --expected-version <n> | delete <slug>
95
+ Read a team skill's kept versions, restore one, or delete the skill
96
+ login Alias for setup
97
+ wallet sign-in [--api-url <url>] [--local] [--key-file <path>] | wallet top-up --amount <usd> --max <usd> [--key-file <path>]
98
+ Sign in or add funds with an EVM wallet; help wallet explains each
99
+ access list | access set <member-id> (--tools '<json array>' | --all) [legacy NoimosAI Platform]
100
+ Read or change what each member may use, with a key made for it
101
+
102
+ project Show the API key's project; list/add/use approved local projects
103
+ usage Show the current project's monthly request count and spend
104
+ team [--member-offset <integer>] [--invitation-offset <integer>] [--limit <1-100>]
105
+ Read the selected team and your roles; owners/admins can page members and invitations
106
+ connections [--provider <id>] [--offset <integer>] [--limit <1-20>]
107
+ List project OAuth connections, states and observed grants
108
+ connect <app>
109
+ Print the console pages where you connect an app or add an account
110
+ tools [--query <text>] [--app <name-or-id>] [--category <id>]
111
+ [--provider <id>] [--available-only | --include-unavailable] [--summary | --with-schemas]
112
+ [--max-charge-micros <integer>] [--quote-arguments '<json>']
113
+ [--funding all|relvo|provider|mixed] [--price-unit <unit>] [--sort-by relevance|price_asc]
114
+ [--include '<filter-json>'] [--exclude '<filter-json>']
115
+ [--limit <1-12>] [--mode auto|keyword|semantic]
116
+ Search up to 12 tool summaries and USD prices (default 8)
117
+ tool <name>
118
+ Inspect one tool's arguments, example and price
119
+ meta <search|schemas|connections|execute|workbench|bash> '<request-json>'
120
+ Use six session-scoped meta tools; stdin/file input accepts 1 MiB
121
+ executions [--limit <1-100>] [--before <id>] [--status <state>] [--tool <name>]
122
+ Recover the API key's project execution history
123
+ monitors list | popular | tools | delete <id> | resume <id> [--maximum-charge-micros-per-run <n>]
124
+ monitors quote|create --tool <name> [--args '<json>'] [--provider <id>] [--interval 15m|1h|1d | --cron "<5 fields>" [--time-zone <IANA>]] [--name <text>] [--maximum-charge-micros-per-run <n>]
125
+ monitors events [--monitor <id>] [--after <cursor>] [--limit <1-100>]
126
+ monitors destination-add <id> --destination '<json>' | destination-remove <id> --destination-id <dst_id>
127
+ Watch tools for changes, each run charged in full; help monitors explains each
128
+ triggers list | types --app <app> [--connection <id>] [--query <text>] [--offset <n>] [--limit <1-100>] | delete <id> | update <id> [--enabled true|false] [--name <text>]
129
+ triggers create --connection <id> (--tool <name> [--args '<json>'] [--interval 1m|5m|15m|1h] [--items-path </json/pointer>] [--maximum-charge-micros-per-poll <n>] | --type <app.event> [--config '<json>']) [--name <text>] [--destinations '<json>']
130
+ triggers events [--trigger <id>] [--after <cursor>] [--limit <1-100>]
131
+ triggers destination-add <id> --destination '<json>' | destination-remove <id> --destination-id <dst_id>
132
+ Get events from a connected app; help triggers explains each
133
+ custom-tools list | get <name> | versions <name> | delete <name> | restore <name> --version <n> --expected-version <n>
134
+ custom-tools create <slug> | update <name> --expected-version <n> (definition from - or --input-file <path>)
135
+ Add Python or an API endpoint as a tool for the team; help custom-tools explains each
136
+ quote <tool> '<arguments-json>' [--provider <id>] [--session <id>] [--relevance-check auto|light] [--maximum-charge-micros <cap>]
137
+ Get a free quote without running the tool
138
+ run <tool> '<arguments-json>' [--maximum-charge-micros <integer>] --idempotency-key <key> [--provider <id>] [--session <id>]
139
+ [--path '<path-array>'] [--fields '<names-array>'] [--find '<terms>'] [--pick '<label-to-paths-json>'] [--limit <n>] [--offset <n>] [--max-chars <n>] [--wait <1-60>] [--file] [--accept-external-billing | --decline-external-billing]
140
+ Quote, save, submit and wait for already-authorized work
141
+ resume <run-id> [--wait <seconds>]
142
+ Recover the saved exact purchase; takes run's result selection, never a change to the purchase
143
+ parallel ('<request-json>' | - | --input-file <path>)
144
+ Quote, save and start up to 50 independent calls; read each accepted execution ID with result
145
+ execute <tool> '<arguments-json>' --maximum-charge-micros <integer>
146
+ --price-version <version> --idempotency-key <key> [--provider <id>] [--session <id>] [--relevance-check auto|light] [--raw]
147
+ Reserve up to the quoted amount and enqueue paid work
148
+ execution <id> [--raw]
149
+ Read status, a bounded output preview and the final USD charge
150
+ result <id> [--path '<JSON-array>'] [--fields '<JSON-array>'] [--offset <n>] [--limit <1-${TOOL_RESULT_PAGE_MAX_ITEMS}>] [--max-chars <1-8000>] [--find '<terms>'] [--pick '<label-to-paths-json>'] [--wait <1-60 seconds>] [--file]
151
+ Read a selected page of the retained full result
152
+ results <id>... [--path '<JSON-array>'] [--fields '<JSON-array>'] [--offset <n>] [--limit <1-${TOOL_RESULT_PAGE_MAX_ITEMS}>] [--max-chars <n>] [--find '<terms>'] [--pick '<label-to-paths-json>'] [--wait <1-60 seconds>]
153
+ Read the same selection of several retained results at once
154
+ wait <id> [--timeout <seconds>] [--raw]
155
+ Wait for completion (default 480 seconds, maximum 900)
156
+ cancel <id> [--raw]
157
+ Request cancellation of work that has not started
158
+ artifact-url <execution-id> <artifact-id>
159
+ Get a private output's download URL, valid for up to 5 minutes
160
+ download <execution-id> <artifact-id> --out <file>
161
+ Save an artifact after size/SHA-256 checks; never overwrite a file
162
+ upload <file>
163
+ Upload one explicit JPG/PNG/WebP/WAV/MP3/MP4/MOV input, at most 12 MiB
164
+
165
+ run quotes internally and charges the project's balance; quote separately only when
166
+ the user asks to approve a price. execute needs a quote first: use its price version
167
+ and maximum charge (1 USD = 1000000 micros). Keep the same body and idempotency key
168
+ when retrying a submission.
169
+ Auto routing is the default (--provider auto is equivalent). Use --provider to pin a provider for both quote
170
+ and execute; tools --provider only filters discovery.
171
+ Execution is asynchronous. Poll at intervals of at least 5 seconds.
172
+ For quote, execute, run and meta, use - in place of arguments-json to read JSON from stdin,
173
+ or --input-file <path> to read a JSON file (at most 1 MiB).
174
+ wait exits 0 on success, 1 on failed/cancelled/needs_review, 3 on timeout.
175
+ A timeout leaves accepted work running; read or wait on the same execution ID.
176
+ Large outputs stay in the execution. Use result to select data; --raw explicitly
177
+ returns the full output for execution, wait, execute or cancel.
178
+
179
+ Scope comes from the developer API key. OAuth connection IDs belong to this
180
+ project; inspect the selected tool's schema before using one. Workspace, brand,
181
+ knowledge base, dataset, workspace skill and chat-session commands are unavailable.
182
+ Use --project <id> on a tool command to select an already approved login for one call.
183
+ project list/add/use manages saved approvals; reconnect MCP after changing the default.
184
+ Start a managed MCP pinned to one approval with mcp --profile <managed-path> --project <id>.
185
+
186
+ Environment: RELVO_API_URL, RELVO_API_KEY
187
+ Project status: relvo project status [--member-offset <n>] [--invitation-offset <n>] [--limit <1-100>]
188
+ Local emulators only: RELVO_ENV=local
189
+
190
+ Manage teams, members, keys and billing in the Relvo console.
191
+ `;
192
+ export const RELVO_CLI_SURFACE = { distribution: PLATFORM_DISTRIBUTIONS.relvo, help, fullHelp, commandHelp, supports: () => true, savedLogins: true, clientNameHeader: "x-relvo-client-name" };
193
+ export async function runCli(args, env, output, error, transport = fetch, stdin = process.stdin, profile, surface = RELVO_CLI_SURFACE) {
194
+ const { cliCommand, name } = surface.distribution;
195
+ let projectId;
196
+ const projectOption = args.indexOf("--project");
197
+ if (projectOption !== -1) {
198
+ if (!surface.savedLogins) {
199
+ error(`${name} has no saved project logins; use the API key of the project you want. Nothing was sent.`);
200
+ return 2;
201
+ }
202
+ const value = args[projectOption + 1];
203
+ if (!value?.trim() || value.startsWith("-") || args.lastIndexOf("--project") !== projectOption) {
204
+ error("Provide one --project <id>. Nothing was sent.");
205
+ return 2;
206
+ }
207
+ projectId = value;
208
+ args = [...args.slice(0, projectOption), ...args.slice(projectOption + 2)];
209
+ }
210
+ if (args.length === 2 && args[0] === "help" && args[1] === "all") {
211
+ output(surface.fullHelp);
212
+ return 0;
213
+ }
214
+ if (!args.length || args[0] === "--help" || args[0] === "-h" || args.length === 1 && args[0] === "help") {
215
+ output(surface.help);
216
+ return 0;
217
+ }
218
+ if (args[0] === "help" || args.slice(1).some((arg) => arg === "--help" || arg === "-h")) {
219
+ const detail = surface.commandHelp(args[0] === "help" ? args[1] : args[0]);
220
+ if (!detail || args[0] === "help" && args.length !== 2) {
221
+ error(`Unknown help command. Run ${cliCommand} --help.`);
222
+ return 2;
223
+ }
224
+ output(detail);
225
+ return 0;
226
+ }
227
+ if (args.length === 1 && args[0] === "--version") {
228
+ output(PLATFORM_CLI_VERSION);
229
+ return 0;
230
+ }
231
+ if (!surface.supports(args)) {
232
+ error(`${name} does not offer this command or option. Nothing was sent. Run ${cliCommand} --help.`);
233
+ return 2;
234
+ }
235
+ let command;
236
+ let exitCode = 0;
237
+ try {
238
+ const readInput = async (file, limit = PLATFORM_TOOL_REQUEST_MAX_BYTES) => {
239
+ if (!file || file.startsWith("--"))
240
+ throw new CliInputError("Provide an input file");
241
+ const chunks = [];
242
+ let bytes = 0;
243
+ for await (const chunk of file === "-" ? stdin : createReadStream(file)) {
244
+ const buffer = Buffer.from(chunk);
245
+ bytes += buffer.byteLength;
246
+ if (bytes > limit)
247
+ throw new CliInputError("Input exceeds its byte limit");
248
+ chunks.push(buffer);
249
+ }
250
+ return Buffer.concat(chunks).toString("utf8");
251
+ };
252
+ if (["quote", "execute", "run", "meta"].includes(args[0]) && ["-", "--input-file"].includes(args[2])) {
253
+ const input = await readInput(args[2] === "--input-file" ? args[3] : "-", args[0] === "meta" ? PLATFORM_META_LIMITS.requestBytes : PLATFORM_TOOL_REQUEST_MAX_BYTES);
254
+ args = [args[0], args[1], input, ...args.slice(args[2] === "--input-file" ? 4 : 3)];
255
+ }
256
+ if (args[0] === "parallel") {
257
+ if (!(args.length === 2 || args.length === 3 && args[1] === "--input-file"))
258
+ throw new CliInputError("Provide one parallel request as JSON, - or --input-file <path>");
259
+ const source = args[1], text = source === "--input-file" ? await readInput(args[2]) : source === "-" ? await readInput("-") : source;
260
+ const input = agentParallelSchema.parse(metaInput(agentParallelSchema, JSON.parse(text)));
261
+ command = async (client, config) => {
262
+ const result = await new AgentRun(client, env.RELVO_RUN_DIRECTORY, resultFiles(env), walletAutoTopUpFromEnvironment(env, config)).parallel(input);
263
+ exitCode = result.allAccepted ? 0 : 1;
264
+ return result;
265
+ };
266
+ }
267
+ else if (args.length === 3 && args[0] === "meta") {
268
+ const input = JSON.parse(args[2]);
269
+ if (args[1] === "search") {
270
+ const value = platformMetaSearchSchema.parse(metaInput(platformMetaSearchSchema, input));
271
+ command = client => client.searchMetaTools(value);
272
+ }
273
+ else if (args[1] === "schemas") {
274
+ const value = platformMetaSchemasSchema.parse(metaInput(platformMetaSchemasSchema, input));
275
+ command = client => client.getToolSchemas(value);
276
+ }
277
+ else if (args[1] === "connections") {
278
+ const value = platformMetaConnectionsSchema.parse(metaInput(platformMetaConnectionsSchema, input));
279
+ command = client => client.manageConnections(value);
280
+ }
281
+ else if (args[1] === "execute") {
282
+ const value = platformMetaExecuteSchema.parse(metaInput(platformMetaExecuteSchema, input));
283
+ command = async (client) => { const result = await client.multiExecute(value); if (!result.data.allAccepted)
284
+ exitCode = 1; return result; };
285
+ }
286
+ else if (args[1] === "workbench") {
287
+ const value = platformMetaWorkbenchSchema.parse(metaInput(platformMetaWorkbenchSchema, input));
288
+ command = client => client.remoteWorkbench(value);
289
+ }
290
+ else if (args[1] === "bash") {
291
+ const value = platformMetaBashSchema.parse(metaInput(platformMetaBashSchema, input));
292
+ command = client => client.remoteBash(value);
293
+ }
294
+ else
295
+ throw new CliInputError("Unknown meta tool");
296
+ }
297
+ else if (args.length === 2 && args[0] === "upload" && args[1].trim())
298
+ command = (client) => uploadLocalInput(client, args[1]);
299
+ else if (args[0] === "project" && args[1] === "status") {
300
+ const { values, tokens } = parseArgs({ args: args.slice(2), tokens: true, options: {
301
+ "member-offset": { type: "string" }, "invitation-offset": { type: "string" }, limit: { type: "string" }
302
+ } });
303
+ refuseDuplicates(tokens, "Duplicate project status option");
304
+ const input = platformProjectStatusQuerySchema.parse({ memberOffset: values["member-offset"], invitationOffset: values["invitation-offset"], limit: values.limit });
305
+ command = client => client.projectStatus(input);
306
+ }
307
+ else if (args[0] === "team") {
308
+ const { values, tokens } = parseArgs({ args: args.slice(1), tokens: true, options: {
309
+ "member-offset": { type: "string" }, "invitation-offset": { type: "string" }, limit: { type: "string" }
310
+ } });
311
+ refuseDuplicates(tokens, "Duplicate team option");
312
+ const input = platformTeamQuerySchema.parse({ memberOffset: values["member-offset"], invitationOffset: values["invitation-offset"], limit: values.limit });
313
+ command = client => client.team(input);
314
+ }
315
+ else if (args.length === 1 && args[0] === "project")
316
+ command = (client) => client.project();
317
+ else if (args.length === 1 && args[0] === "usage")
318
+ command = (client) => client.usage();
319
+ else if (args.length === 2 && args[0] === "connect" && args[1].trim() && !args[1].startsWith("-"))
320
+ command = client => client.connectApp({ app: args[1] });
321
+ else if (args[0] === "connections") {
322
+ const { values, tokens } = parseArgs({ args: args.slice(1), tokens: true, options: { provider: { type: "string" }, offset: { type: "string" }, limit: { type: "string" } } });
323
+ refuseDuplicates(tokens, "Duplicate connection option");
324
+ const input = platformConnectionCatalogQuerySchema.parse({ provider: values.provider, offset: values.offset === undefined ? undefined : Number(values.offset), limit: values.limit === undefined ? undefined : Number(values.limit) });
325
+ command = client => client.connections(input);
326
+ }
327
+ else if (args[0] === "tools") {
328
+ const { values, tokens } = parseArgs({ args: args.slice(1), tokens: true, options: {
329
+ query: { type: "string" }, app: { type: "string" }, category: { type: "string" }, provider: { type: "string" }, "available-only": { type: "boolean" }, "include-unavailable": { type: "boolean" }, summary: { type: "boolean" }, "with-schemas": { type: "boolean" }, limit: { type: "string" }, mode: { type: "string" },
330
+ "max-charge-micros": { type: "string" }, "quote-arguments": { type: "string" }, funding: { type: "string" }, "price-unit": { type: "string" }, "sort-by": { type: "string" },
331
+ include: { type: "string" }, exclude: { type: "string" },
332
+ } });
333
+ refuseDuplicates(tokens, "Duplicate discovery option");
334
+ if (values["available-only"] && values["include-unavailable"])
335
+ throw new CliInputError("Conflicting availability filters");
336
+ if (values.summary && values["with-schemas"])
337
+ throw new CliInputError("Conflicting schema options");
338
+ if (values["max-charge-micros"] !== undefined && !values["max-charge-micros"].trim())
339
+ throw new CliInputError("Invalid cost cap");
340
+ const input = toolDiscoveryInputSchema.parse({ query: values.query, app: values.app, category: values.category, provider: values.provider,
341
+ maxChargeMicros: values["max-charge-micros"] === undefined ? undefined : Number(values["max-charge-micros"]),
342
+ quoteArguments: values["quote-arguments"] === undefined ? undefined : jsonOption("--quote-arguments", values["quote-arguments"]),
343
+ funding: values.funding, priceUnit: values["price-unit"], sortBy: values["sort-by"],
344
+ include: values.include === undefined ? undefined : jsonOption("--include", values.include), exclude: values.exclude === undefined ? undefined : jsonOption("--exclude", values.exclude),
345
+ availableOnly: values["include-unavailable"] ? false : true, summary: !values["with-schemas"], limit: values.limit === undefined ? undefined : Number(values.limit), mode: values.mode });
346
+ command = (client) => client.searchTools(input);
347
+ }
348
+ else if (args.length === 2 && args[0] === "tool" && /^[a-z][a-z0-9_]{1,63}$/.test(args[1]))
349
+ command = (client) => client.inspectTool(args[1]);
350
+ else if (args[0] === "executions") {
351
+ const { values, tokens } = parseArgs({ args: args.slice(1), tokens: true, options: { limit: { type: "string" }, before: { type: "string" }, status: { type: "string" }, tool: { type: "string" } } });
352
+ refuseDuplicates(tokens, "Duplicate history option");
353
+ const input = platformExecutionListQuerySchema.parse({ limit: values.limit === undefined ? undefined : Number(values.limit), before: values.before, status: values.status, tool: values.tool });
354
+ command = (client) => client.executions(input);
355
+ }
356
+ else if (args[0] === "result") {
357
+ const id = platformExecutionIdSchema.parse(args[1]);
358
+ const { values, tokens } = parseArgs({ args: args.slice(2), tokens: true, options: { ...READ_OPTIONS, file: { type: "boolean" } } });
359
+ refuseDuplicates(tokens, "Duplicate result option");
360
+ if (values.file && (values.offset !== undefined || values.limit !== undefined || values["max-chars"] !== undefined || values.find !== undefined))
361
+ throw new CliInputError("--file writes the whole path; omit paging options");
362
+ if (values.file || values.find !== undefined || values.pick !== undefined || values.wait !== undefined) {
363
+ const input = agentResultSchema.parse({ id, ...agentRead(values), ...(values.file ? { file: true } : {}) });
364
+ command = async (client) => {
365
+ const result = await new AgentRun(client, env.RELVO_RUN_DIRECTORY, resultFiles(env)).result(input);
366
+ exitCode = "timedOut" in result && result.timedOut ? 3 : result.status === "succeeded" && !missedRequestedOutput(result) ? 0 : 1;
367
+ return result;
368
+ };
369
+ }
370
+ else {
371
+ const selection = toolResultSelectionSchema.parse({ path: values.path === undefined ? undefined : jsonOption("--path", values.path),
372
+ offset: values.offset === undefined ? undefined : Number(values.offset), limit: values.limit === undefined ? undefined : Number(values.limit),
373
+ maxChars: values["max-chars"] === undefined ? undefined : Number(values["max-chars"]),
374
+ fields: values.fields === undefined ? undefined : jsonOption("--fields", values.fields),
375
+ });
376
+ command = async (client) => {
377
+ const result = await client.readExecutionResult(id, selection);
378
+ if (result.data.status && result.data.status !== "succeeded")
379
+ exitCode = ["queued", "running", "waiting"].includes(result.data.status) ? 3 : 1;
380
+ else if (result.data.upstreamRefusal)
381
+ exitCode = 1;
382
+ return result;
383
+ };
384
+ }
385
+ }
386
+ else if (args[0] === "results") {
387
+ const { values, positionals, tokens } = parseArgs({ args: args.slice(1), tokens: true, allowPositionals: true, options: READ_OPTIONS });
388
+ refuseDuplicates(tokens, "Duplicate results option");
389
+ const input = agentResultsSchema.parse({ ids: positionals, ...agentRead(values) });
390
+ command = async (client) => {
391
+ const reply = await new AgentRun(client, env.RELVO_RUN_DIRECTORY, resultFiles(env)).results(input);
392
+ const pending = reply.results.some(item => "status" in item && ["queued", "running", "waiting"].includes(String(item.status)));
393
+ const delivered = reply.results.every(item => "status" in item && item.status === "succeeded" && !missedRequestedOutput(item));
394
+ exitCode = delivered ? 0 : pending ? 3 : 1;
395
+ return reply;
396
+ };
397
+ }
398
+ else if (args[0] === "wait") {
399
+ const id = platformExecutionIdSchema.parse(args[1]);
400
+ const { values, tokens } = parseArgs({ args: args.slice(2), tokens: true, options: { timeout: { type: "string" }, raw: { type: "boolean" } } });
401
+ refuseDuplicates(tokens, "Duplicate wait option");
402
+ const seconds = values.timeout === undefined ? 480 : Number(values.timeout);
403
+ if (!Number.isInteger(seconds) || seconds < 1 || seconds > 900)
404
+ throw new CliInputError("Invalid wait timeout");
405
+ command = async (client) => {
406
+ const result = values.raw ? await client.waitExecution(id, seconds * 1000) : await client.waitExecutionContext(id, seconds * 1000);
407
+ exitCode = result.timedOut ? 3 : result.data?.status === "succeeded" && !result.data.upstreamRefusal ? 0 : 1;
408
+ return result;
409
+ };
410
+ }
411
+ else if (["execution", "cancel"].includes(args[0])) {
412
+ const id = platformExecutionIdSchema.parse(args[1]);
413
+ const { values, tokens } = parseArgs({ args: args.slice(2), tokens: true, options: { raw: { type: "boolean" } } });
414
+ if (tokens.length > 1)
415
+ throw new CliInputError("Duplicate output option");
416
+ command = async (client) => {
417
+ if (args[0] === "execution")
418
+ return values.raw ? client.execution(id) : client.executionContext(id);
419
+ const result = await client.cancelExecution(id);
420
+ return values.raw ? result : executionReceiptContext(result);
421
+ };
422
+ }
423
+ else if (args.length === 5 && args[0] === "download" && args[3] === "--out" && args[4].trim()) {
424
+ const id = platformExecutionIdSchema.parse(args[1]);
425
+ const artifactId = platformArtifactIdSchema.parse(args[2]);
426
+ command = (client) => downloadArtifact(client, id, artifactId, args[4], transport, cliCommand);
427
+ }
428
+ else if (args.length === 3 && args[0] === "artifact-url") {
429
+ const id = platformExecutionIdSchema.parse(args[1]);
430
+ const artifactId = platformArtifactIdSchema.parse(args[2]);
431
+ command = (client) => client.artifactDownload(id, artifactId);
432
+ }
433
+ else if ((args[0] === "run" && args.length >= 3) || (args[0] === "resume" && args.length >= 2)) {
434
+ const resume = args[0] === "resume";
435
+ const { values, tokens } = parseArgs({ args: args.slice(resume ? 2 : 3), tokens: true, options: { ...READ_OPTIONS,
436
+ "maximum-charge-micros": { type: "string" }, "idempotency-key": { type: "string" }, provider: { type: "string" }, session: { type: "string" },
437
+ file: { type: "boolean" }, "accept-external-billing": { type: "boolean" }, "decline-external-billing": { type: "boolean" },
438
+ } });
439
+ refuseDuplicates(tokens, "Duplicate run option");
440
+ if (values["accept-external-billing"] && values["decline-external-billing"])
441
+ throw new CliInputError("Conflicting external billing options");
442
+ const { waitSeconds, pick, ...selection } = agentRead(values);
443
+ const view = { waitSeconds, pick, file: values.file, selection };
444
+ if (resume) {
445
+ if (values["maximum-charge-micros"] || values["idempotency-key"] || values.provider || values.session || values["accept-external-billing"] || values["decline-external-billing"])
446
+ throw new CliInputError("Resume retains the original purchase");
447
+ const input = agentResumeSchema.parse({ runId: args[1], ...view });
448
+ command = async (client, config) => {
449
+ const result = await new AgentRun(client, env.RELVO_RUN_DIRECTORY, resultFiles(env), walletAutoTopUpFromEnvironment(env, config)).resume(input);
450
+ exitCode = "timedOut" in result && result.timedOut ? 3 : result.status === "succeeded" && !missedRequestedOutput(result) ? 0 : 1;
451
+ return result;
452
+ };
453
+ }
454
+ else {
455
+ const maximum = values["maximum-charge-micros"];
456
+ if (maximum !== undefined && !/^\d+$/.test(maximum))
457
+ throw new CliInputError("Invalid run ceiling");
458
+ const input = agentRunSchema.parse({ tool: args[1], arguments: jsonOption("<arguments-json>", args[2]), ...(maximum === undefined ? {} : { maximumChargeMicros: Number(maximum) }),
459
+ idempotencyKey: values["idempotency-key"], provider: values.provider, sessionId: values.session,
460
+ ...(values["decline-external-billing"] ? { externalBillingAccepted: false } : {}), ...view });
461
+ command = async (client, config) => {
462
+ const result = await new AgentRun(client, env.RELVO_RUN_DIRECTORY, resultFiles(env), walletAutoTopUpFromEnvironment(env, config)).run(input);
463
+ exitCode = "status" in result ? result.timedOut ? 3 : result.status === "succeeded" && !missedRequestedOutput(result) ? 0 : 1 : 1;
464
+ return result;
465
+ };
466
+ }
467
+ }
468
+ else if (args.length >= 3 && args[0] === "quote") {
469
+ const { values, tokens } = parseArgs({ args: args.slice(3), tokens: true, options: { provider: { type: "string" }, session: { type: "string" },
470
+ "relevance-check": { type: "string" }, "maximum-charge-micros": { type: "string" } } });
471
+ refuseDuplicates(tokens, "Duplicate quote option");
472
+ const maximum = values["maximum-charge-micros"];
473
+ if (maximum !== undefined && !/^\d+$/.test(maximum))
474
+ throw new CliInputError("Invalid quote cap");
475
+ const input = platformQuoteRequestSchema.parse({ tool: args[1], arguments: jsonOption("<arguments-json>", args[2]), sessionId: values.session, ...(values.provider === undefined ? {} : { provider: values.provider }),
476
+ ...(values["relevance-check"] === undefined ? {} : { relevanceCheck: values["relevance-check"] }), ...(maximum === undefined ? {} : { maximumChargeMicros: Number(maximum) }) });
477
+ command = (client) => client.quote(input);
478
+ }
479
+ else if (args.length >= 3 && args[0] === "execute") {
480
+ const { values, tokens } = parseArgs({ args: args.slice(3), tokens: true, options: {
481
+ "maximum-charge-micros": { type: "string" }, "price-version": { type: "string" }, "idempotency-key": { type: "string" }, provider: { type: "string" }, session: { type: "string" }, raw: { type: "boolean" },
482
+ "relevance-check": { type: "string" },
483
+ } });
484
+ refuseDuplicates(tokens, "Duplicate execution option");
485
+ const maximum = values["maximum-charge-micros"], idempotencyKey = values["idempotency-key"];
486
+ if (!maximum || !/^\d+$/.test(maximum) || !idempotencyKey || !platformIdempotencyKeySchema.safeParse(idempotencyKey).success)
487
+ throw new CliInputError("Invalid execution confirmation");
488
+ const input = platformCreateExecutionSchema.parse({ tool: args[1], arguments: jsonOption("<arguments-json>", args[2]), sessionId: values.session, maximumChargeMicros: Number(maximum), priceVersion: values["price-version"], ...(values.provider === undefined ? {} : { provider: values.provider }),
489
+ ...(values["relevance-check"] === undefined ? {} : { relevanceCheck: values["relevance-check"] }) });
490
+ command = async (client) => {
491
+ const result = await client.createExecution(input, idempotencyKey);
492
+ return values.raw ? result : executionReceiptContext(result);
493
+ };
494
+ }
495
+ else if (isTeamResource(args[0]))
496
+ command = await teamResourceCommand(args, source => readInput(source));
497
+ else
498
+ throw new CliInputError("Invalid command");
499
+ }
500
+ catch (failure) {
501
+ error(`${cliInputReason(failure)}. Nothing was sent. Run ${cliCommand} ${surface.commandHelp(args[0]) ? `help ${args[0]}` : "--help"}.`);
502
+ return 2;
503
+ }
504
+ try {
505
+ if (projectId && !profile)
506
+ throw new Error(`--project needs a saved login. Run ${cliCommand} setup first.`);
507
+ const config = projectId && profile ? await localProjectConfig(profile, projectId, env, "cli", transport)
508
+ : profile ? await runtimeConfig(env, "cli", profile) : configFromEnvironment(env, "cli", surface.distribution);
509
+ const header = surface.clientNameHeader, client = env.CLAUDECODE === "1" ? "claude-code" : `${cliCommand}-cli`;
510
+ const result = await command(new PlatformClient(config, header ? (input, init) => transport(input, { ...init, headers: { ...init?.headers, [header]: client } }) : transport), config);
511
+ output(JSON.stringify(result));
512
+ return exitCode;
513
+ }
514
+ catch (failure) {
515
+ error(failure instanceof Error ? failure.message : `${name} request failed.`);
516
+ return 1;
517
+ }
518
+ }
519
+ export async function runRelvoCli(args, env, output, error, transport = fetch, stdin = process.stdin, options = {}) {
520
+ if (args[0] === "mcp") {
521
+ try {
522
+ const { values } = parseArgs({ args: args.slice(1), options: { profile: { type: "string" }, mode: { type: "string" }, project: { type: "string" } } });
523
+ if (!values.profile)
524
+ throw new Error("relvo mcp requires the managed --profile path.");
525
+ const [{ startPlatformMcp }, { deactivateProjectFiles }, { projectSyncFactory }] = await Promise.all([
526
+ import("../mcp/runtime.js"), import("./project-files.js"), import("./project-sync.js")
527
+ ]);
528
+ const profileHome = dirname(dirname(dirname(values.profile)));
529
+ await startPlatformMcp(options.version, values.profile, values.mode, values.project, id => deactivateProjectFiles(id, env, profileHome), projectSyncFactory(env, profileHome));
530
+ return process.exitCode ? 1 : 0;
531
+ }
532
+ catch {
533
+ error("Relvo MCP could not start. Run relvo doctor to inspect the managed connection.");
534
+ return 1;
535
+ }
536
+ }
537
+ const localFailure = (failure, fallback) => {
538
+ if (!isCliInputFailure(failure)) {
539
+ error(failure instanceof Error ? failure.message : fallback);
540
+ return 1;
541
+ }
542
+ error(`${cliInputReason(failure).replace(/\.$/u, "")}. Nothing was changed. Run relvo help ${args[0]}.`);
543
+ return 2;
544
+ };
545
+ if (args[0] === "wallet" && !args.some((arg) => ["--help", "-h"].includes(arg))) {
546
+ try {
547
+ await (await import("./wallet.js")).runWalletCommand(args, env, output, transport, options);
548
+ return 0;
549
+ }
550
+ catch (failure) {
551
+ return localFailure(failure, "Relvo wallet command failed.");
552
+ }
553
+ }
554
+ const local = ["setup", "login", "doctor", "logout", "skills"].includes(args[0]) || args[0] === "team-skills" && ["sync", "push", "check", "apply", "dismiss", "auto-sync"].includes(args[1]) || args[0] === "project" && ["list", "add", "use"].includes(args[1]);
555
+ if (local && !args.some((arg) => ["--help", "-h"].includes(arg))) {
556
+ try {
557
+ await (await import("./setup.js")).runLocalCommand(args, env, output, transport, options);
558
+ return 0;
559
+ }
560
+ catch (failure) {
561
+ return localFailure(failure, "Relvo local setup failed.");
562
+ }
563
+ }
564
+ return runCli(args, env, output, error, transport, stdin, localProfilePath(options.home));
565
+ }