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