@noimosai/platform-cli 0.1.3 → 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.
Files changed (93) hide show
  1. package/README.md +12 -56
  2. package/bin/noimos-platform.mjs +3 -4
  3. package/dist/cli/cli.js +477 -87
  4. package/dist/cli/command-help.js +349 -23
  5. package/dist/cli/download.js +25 -25
  6. package/dist/cli/input-error.js +48 -0
  7. package/dist/cli/local-agents.js +199 -0
  8. package/dist/cli/mcp-supervisor.js +136 -0
  9. package/dist/cli/noimos-cli.js +72 -0
  10. package/dist/cli/project-archive.js +27 -0
  11. package/dist/cli/project-files.js +25 -0
  12. package/dist/cli/project-sync.js +259 -0
  13. package/dist/cli/setup-lock.js +1 -0
  14. package/dist/cli/setup.js +347 -0
  15. package/dist/cli/skills.js +142 -0
  16. package/dist/cli/team-harness-targets.js +337 -0
  17. package/dist/cli/team-harness.js +1562 -0
  18. package/dist/cli/team-resources.js +222 -0
  19. package/dist/cli/team-skills.js +457 -0
  20. package/dist/cli/update-bootstrap.js +437 -0
  21. package/dist/cli/update-release.js +212 -0
  22. package/dist/cli/version.js +1 -0
  23. package/dist/cli/wallet.js +85 -0
  24. package/dist/client/agent-run.js +1425 -0
  25. package/dist/client/browser.js +905 -0
  26. package/dist/client/distribution.js +2 -0
  27. package/dist/client/evm-wallet.js +214 -0
  28. package/dist/client/execution-context.js +14 -0
  29. package/dist/client/failure-recovery.js +128 -0
  30. package/dist/client/index.js +4 -225
  31. package/dist/client/local-input.js +33 -12
  32. package/dist/client/local-profile-lock.js +52 -0
  33. package/dist/client/local-profile.js +181 -0
  34. package/dist/client/local-projects.js +131 -0
  35. package/dist/client/local-setup.js +93 -0
  36. package/dist/client/realtime-voice.js +184 -0
  37. package/dist/client/tool-name-hints.js +48 -0
  38. package/dist/client/wallet-access.js +214 -0
  39. package/dist/mcp/agent-server.js +345 -0
  40. package/dist/mcp/context.js +86 -0
  41. package/dist/mcp/host-schema.js +76 -0
  42. package/dist/mcp/icon.js +1 -0
  43. package/dist/mcp/index.js +3 -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 +39 -0
  47. package/dist/mcp/noimos-runtime.js +18 -0
  48. package/dist/mcp/noimos-server.js +38 -0
  49. package/dist/mcp/operations.js +40 -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 +81 -0
  54. package/dist/mcp/server.js +410 -0
  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 +6 -3
@@ -1,69 +1,395 @@
1
+ import { TOOL_RESULT_PAGE_MAX_ITEMS } from "../client/index.js";
1
2
  const inputHelp = `Supply one JSON object inline, use - for stdin, or use --input-file <path>.
2
- File/stdin input is limited to 64 KiB. --input-file - also reads stdin.`;
3
+ File/stdin input is limited to 1 MiB. --input-file - also reads stdin.`;
3
4
  const commands = {
5
+ parallel: `parallel ('<request-json>' | - | --input-file <path>)
6
+ Start independent existing catalog tools together; no session or advance quotes are required.
7
+ Input: {"calls":[{"tool":"web_search","arguments":{"query":"public sources"},"idempotencyKey":"research-unique-key"}]}
8
+ Each call takes run's purchase fields: tool, arguments, idempotencyKey, optional provider,
9
+ sessionId, intent, relevanceCheck, maximumChargeMicros and externalBillingAccepted.
10
+ Up to 50 calls, with four concurrent admissions. Returns every receipt in input order
11
+ without waiting for completion; read each accepted executionId with result or wait.
12
+ Exit 0 means all calls were admitted (including pending/needs_review), not that they succeeded.
13
+ Exit 1 means some were rejected or admission is unknown. One failure does not stop the others.
14
+ Save the request and distinct per-call keys first. Repeat identical calls and keys after a
15
+ lost reply; never replace accepted or uncertain work. Dependencies belong in later calls.
16
+ Optional top-level maximumChargeMicros requires explicit per-call ceilings whose sum fits.
17
+ Omit ceilings or use null when none was requested. Connected-account and BYOK usage runs by
18
+ default; set externalBillingAccepted:false on calls whose external charges the user asked to avoid.
19
+ When price approval is requested, use quote and meta execute with the approved quotes instead.
20
+ ${inputHelp}`,
21
+ run: `run <tool> ('<arguments-json>' | - | --input-file <path>) --idempotency-key <key> [--maximum-charge-micros <integer>]
22
+ Execute the requested work; omit the ceiling when the user did not specify a budget.
23
+ Quotes exact inputs internally, uses the quote amount when no ceiling was supplied,
24
+ rejects quotes above an explicit ceiling (1 USD=1000000 micros; 0=free Relvo calls), saves the exact purchase before submission,
25
+ waits (default 25s, --wait 1-60), and returns a bounded result plus its retained execution ID.
26
+ Optional: --provider <id> --session <id> --path '<path-array>' --fields '<field-names>' --find '<terms>'
27
+ --pick '<label-to-paths-json>' --limit <n> --offset <n> --max-chars <n> --file.
28
+ --find keeps the lines of a text that hold a term: one term, or a JSON list of terms.
29
+ Connected-account and BYOK usage runs by default (--accept-external-billing says so explicitly);
30
+ --decline-external-billing stops before submitting when the user asked to avoid that account's or key's charges or quota use.
31
+ For pagination, keep the exact tool, actual serving provider, account and search filters.
32
+ Copy returned cursors unchanged; use the actual serving provider for later pages when it is pinnable.
33
+ A provider's internal source/lane is separate from --provider. Inspect its own continuation contract.
34
+ Follow this operation's documented cursor, page or offset method; a bounded page does not establish complete history.
35
+ The ceiling covers Relvo charges only, not the connected account's or key's own charges.
36
+ --file writes the selected path to a local file instead of printing it, as result --file does.
37
+ Use the same request/key after uncertainty. Source data remain stored; missing fields are explicit.
38
+ Do not ask for or invent a budget. Honor explicit limits across calls and pages.
39
+ When the user requests price approval, use quote and execute instead.`,
40
+ resume: `resume <run-id> [--wait <1-60 seconds>] [--find '<terms>'] [--pick '<label-to-paths-json>']
41
+ Recover the saved purchase in this credential scope with its original body and key.
42
+ You may change result selection, never the purchase. A missing journal requires checking
43
+ project execution history rather than creating replacement work.`,
44
+ meta: `meta <search|schemas|connections|execute|workbench|bash> '<request-json>'
45
+ Use session-scoped Relvo meta tools. Search returns session.id; retain it for other calls.
46
+ Supply the matching schema's JSON object inline, from stdin (-) or --input-file <path>, up to 1 MiB.
47
+ Execute batches require each accepted quote/idempotency key and a total reservation cap.
48
+ Workbench/Bash require a quote for remote_workbench/remote_bash with the same session and arguments.
49
+ Accepted work is asynchronous: wait for its execution ID. Unknown admission retains the same request/key.`,
50
+ setup: `setup --api-url <url> --console-url <origin> [--client auto|claude|codex|terminal] [--mcp-runtime registry|installed] [--local] [--no-browser] [--new-project]
51
+ Sign in in your browser and compare the printed code before approving a project.
52
+ Stores the login in a private Relvo profile. The default, auto, configures every
53
+ installed agent (Claude Code and Codex) with one approval and signs in the CLI when
54
+ neither is installed. Agent setup registers MCP through the official agent CLI and
55
+ installs current first-party guides. Restart the agent afterwards.
56
+ Use --mcp-runtime installed for a CLI tarball installed at a persistent path. It registers
57
+ the same package's MCP directly with Node, without npx or automatic MCP updates.
58
+ Keep that installation and Node path available. Existing foreign MCP entries and edited guide files are preserved. No paid work is run.
59
+ Use project add, or --new-project, for a fresh browser approval while keeping previously approved projects.`,
60
+ login: `login --api-url <url> --console-url <origin> [--client auto|claude|codex|terminal] [--local] [--no-browser]
61
+ Alias for setup. An incomplete saved request resumes without creating another key.`,
62
+ wallet: `wallet sign-in [--api-url <url>] [--local] [--key-file <path>]
63
+ wallet top-up --amount <usd> --max <usd> [--key-file <path>]
64
+ Sign in or add funds with an EVM wallet, with no browser step. The private key
65
+ (0x + 64 hex) comes only from RELVO_WALLET_PRIVATE_KEY or an owner-only --key-file;
66
+ it is never a command argument and never printed or sent.
67
+ sign-in creates the wallet's project on first use and saves a 30-day login like setup.
68
+ top-up pays --amount USD in USDC (Base) by x402 and refuses any requirement above --max.
69
+ After an unclear failure, check relvo usage before paying again.`,
70
+ doctor: `doctor
71
+ Check the private local profile, approved project, live catalog and owned MCP.
72
+ Does not update guides, submit paid tools or repair another program's configuration.`,
73
+ logout: `logout
74
+ Remove the private local Relvo login. Revoke its key in the Relvo console to disable
75
+ server access. Installed guides and MCP registration remain without a local login.`,
76
+ "custom-tools": `custom-tools list | get <name> | versions <name> | delete <name>
77
+ custom-tools create <slug> (- | --input-file <path>)
78
+ custom-tools update <name> --expected-version <n> (- | --input-file <path>)
79
+ custom-tools restore <name> --version <n> --expected-version <n>
80
+ Add a tool for the whole team, listed as custom__<slug>. The definition is JSON, either Python
81
+ { title, description, inputSchema, code } that Relvo runs in its sandbox with the validated input as the dict \`input\`,
82
+ or an API endpoint { kind: "http", title, description, baseUrl, headers?, query?, bindings?, timeoutMs?, responseMiB? }: Relvo adds the fixed, non-secret headers and query
83
+ and the credential bindings to every request; a public API needs no bindings. timeoutMs (5000 to 300000) and responseMiB (1 to 14) raise a call's limits; a tool past 5000 or 1 is metered from the balance.
84
+ A definition can hold a credential, so it is read from stdin or a file and never from an argument; a saved team secret is bound in the console.
85
+ update replaces the whole definition: read it first and pass its version; an edit made since is refused. restore adds the old version as a new one, so it can be undone.
86
+ Run a custom tool like any other: relvo run custom__<slug> '<arguments-json>' --idempotency-key <key>.
87
+
88
+ Example: relvo custom-tools create weekly_sum --input-file ./weekly_sum.json`,
89
+ "team-skills": `team-skills sync [--client all|<agent>[,<agent>…]] [--project <id>] [--allow-scripts]
90
+ team-skills check [--client <agents>]
91
+ team-skills apply|dismiss <snapshot> --yes [--client <agents>] [--allow-scripts | --remove-executable-settings]
92
+ team-skills auto-sync on|off|status
93
+ team-skills push <SKILL.md, folder, AGENTS.md, CLAUDE.md, agent or rule file> [--kind skill|instructions|agent|rule] [--slug <slug>]
94
+ team-skills list | get <slug> | versions <slug> | restore <slug> --version <n> --expected-version <n> | delete <slug>
95
+ list, get, versions, restore and delete use the API key or saved login directly; restore adds the old version as a new one, so it can be undone.
96
+ sync writes for each agent: claude (Claude Code), codex, pi, dsh (deepseek-harness), opencode, gemini (Gemini CLI), kimi (Kimi Code),
97
+ qwen (Qwen Code), cursor, copilot (GitHub Copilot CLI), amp and droid (Factory Droid). --client all, the default, means Claude Code and
98
+ Codex as setup registered them (both if it registered neither), every other agent whose folder exists here, and your own agents;
99
+ a comma list names them. Your own agent is one that reads like a listed one from a folder of its own, set in ~/.config/relvo/harnesses.json
100
+ as {"harnesses": [{"name": "my-agent", "like": "claude", "dir": "~/.my-agent"}]} (the folder inside your home, used by no other agent);
101
+ it takes what the team writes for the agent it reads like, and an agent like Codex gets MCP servers through codex with that folder as CODEX_HOME.
102
+ An item the team wrote for some agents only goes only to them: a skill to each one's own skill folder (reported as ownSkills;
103
+ Codex has none and reads ~/.agents/skills), and a rule for deepseek-harness, Kimi Code or Droid to its own AGENTS.md, not ~/.agents/AGENTS.md.
104
+ It writes every team skill this login may see to ~/.claude/skills for Claude Code and to ~/.agents/skills, which every other agent reads,
105
+ as relvo-team-<slug>/SKILL.md, updates the ones changed in Relvo and removes the ones it may no longer see. A folder it did not write, or one
106
+ edited since, is never overwritten or removed and is reported as kept. A teammate's script is written without its executable
107
+ bit and listed as scriptsNotExecutable, and a skill's hooks, allowed-tools, allowedTools and mcpServers frontmatter and its mcp.json
108
+ (which Claude Code, Qwen Code and Amp run or pre-approve) are left out and listed as settingsWithheld;
109
+ read them first, and pass --allow-scripts only when you trust who can edit the team's skills.
110
+ The rest of the team's harness is written the same way, in each agent's own files and format, and reported under harness;
111
+ a kind an agent has no place for is listed as notSupported with the reason.
112
+ Agents: ~/.claude/agents/relvo-team-<slug>.md (without hooks, mcpServers and permissionMode unless --allow-scripts),
113
+ ~/.codex/agents/relvo-team-<slug>.toml, and the agent folders of OpenCode, Gemini CLI, Kimi Code, Qwen Code, Copilot, Cursor and Droid
114
+ with only their name, description and the agent's own settings: a frontmatter map named after the agent, which Claude Code's copy leaves
115
+ out, with the keys that only describe or limit the agent (codex: model, model_reasoning_effort, model_reasoning_summary, model_verbosity,
116
+ personality, service_tier; opencode: model, variant, steps, hidden, color; gemini: model, display_name, temperature, max_turns, timeout_mins;
117
+ kimi: whenToUse; qwen: model, maxTurns, background; copilot: model, disable-model-invocation, user-invocable; cursor: model, readonly,
118
+ is_background; droid: model, reasoningEffort), each a line of text, a number (steps, max_turns, timeout_mins and maxTurns above 0) or
119
+ true/false; other keys and values of another type are left out. An agent whose Claude Code tools list another agent's file cannot hold is
120
+ listed as toolsNotKept. Pi, deepseek-harness and Amp have no agent files.
121
+ Rules: ~/.claude/rules and ~/.qwen/rules as relvo-team-<slug>.md, and one marked block of the single instruction file of each other
122
+ agent (~/.codex/AGENTS.md, ~/.pi/agent/AGENTS.md, ~/.config/opencode/AGENTS.md, ~/.gemini/GEMINI.md, ~/.copilot/copilot-instructions.md,
123
+ ~/.config/amp/AGENTS.md, and ~/.agents/AGENTS.md for deepseek-harness, Kimi Code and Droid); the rest of each file is never changed, and a
124
+ file the agent would read instead, or would stop reading, is kept and reported. Cursor keeps user rules only in its settings.
125
+ A rule's @ references to files (@path) are written as @ for the agents that would put the named file's contents into the model's
126
+ context (Claude Code outside the code its Markdown parser skips; Gemini CLI, Copilot, Amp and ~/.agents/AGENTS.md everywhere) and listed
127
+ as importsEscaped; an @ inside a word, as in an email address, stays. Pushing Claude Code's copy back sends them as @ again.
128
+ Shared folders go to ~/.config/relvo/team/relvo-team-<slug>/ only (never a path the team chooses), with their description as README.md.
129
+ Permission rules join yours in ~/.claude/settings.json and ~/.qwen/settings.json; their Bash rules also become ~/.codex/rules/relvo-team.rules,
130
+ ~/.gemini/policies/relvo-team.toml, shell rules of OpenCode's opencode.json (allow and ask before yours, so your own rules decide over them,
131
+ deny after), prefix rules in ~/.factory/settings.json, and Shell(<command>) rules in ~/.cursor/cli-config.json for one-word commands once
132
+ the Cursor CLI has written that file (other rules are listed as permissionsSkipped); a rule you already had is never taken out.
133
+ The team's hooks, MCP servers and allow rules are installed only with --allow-scripts. Hooks: files under <agent folder>/relvo-team-hooks
134
+ (a command's \${RELVO_HOOK_DIR} becomes that folder and \${RELVO_AGENT} the agent's name above), with their events added beside your own
135
+ to ~/.claude/settings.json, ~/.codex/hooks.json, ~/.qwen/settings.json, ~/.gemini/settings.json and Droid's ~/.factory/settings.json
136
+ (~/.factory/hooks.json once it exists), each under the agent's own event and tool names, and as ~/.copilot/hooks/relvo-team.json.
137
+ Pi, OpenCode and Amp run hooks only as code: a hook's files under native/pi/, native/opencode/ or native/amp/ become a plugin folder
138
+ relvo-team-<slug> in ~/.pi/agent/extensions, ~/.config/opencode/plugins or ~/.config/amp/plugins, and a hook without them is listed
139
+ as hooksWithoutPlugin. Cursor runs Claude Code's hooks itself.
140
+ MCP servers as relvo-team-<slug>: Claude Code's user scope (claude mcp add-json), codex mcp add, entries beside yours in the MCP configuration of Pi,
141
+ OpenCode, Gemini CLI, Kimi Code, Qwen Code, Cursor, Copilot, Amp and Droid (a bearer token stays an environment variable in each agent's
142
+ own syntax), and a marked block of ~/.dsh/cordis.patch.yml. Without --allow-scripts they are listed as notInstalled and any installed
143
+ earlier are taken out. Codex runs a new or changed hook only after you trust it in /hooks.
144
+ A JSON configuration with comments is left unchanged and reported, since sync would lose them.
145
+ --project uses that project's already approved credential for this sync without changing the default; approve a new project with project add first.
146
+ check reads metadata only and prints additions, updates/removals, clients and a snapshot. apply requires explicit --yes approval of that
147
+ snapshot and the same --client selection; project/version/target changes require a new check and approval. Executable settings need
148
+ separate --allow-scripts consent. If the target already has executable settings, apply without that separate consent refuses to change them;
149
+ --remove-executable-settings separately approves removing that opt-in. In MCP use removeExecutableSettings:true; never combine it with
150
+ allowScripts:true. An explicit sync without --allow-scripts still removes that opt-in as described above.
151
+ Managed local MCP starts a metadata check on the first successful tool reply and every 60 seconds while running, without delaying that reply, with a
152
+ one-time prompt in the next successful reply for each changed snapshot. It never imports automatically. Use platform_project_sync to
153
+ check/apply/dismiss or enable/disable checks. auto-sync configures this notification mode; RELVO_PROJECT_AUTO_SYNC=0 disables checks for
154
+ that process. There is no idle popup or background OS service. Receipts are private in ~/.config/relvo/project-sync.json and per client;
155
+ kept edits/withheld settings are reported as such. Reload the agent's skills/settings after import.
156
+ push shares a local SKILL.md and every file beside it with the team, and lists the files it sent; a credential file is refused:
157
+ the frontmatter description says when to use it, the first # heading is its title and the rest its steps. A file in an agents
158
+ or rules folder is pushed as an agent or rule (--kind chooses otherwise); its name, frontmatter description and # heading work the same way.
159
+ Push Claude Code's copy of a synced agent or rule; another agent's copy holds only part of it and is refused.
160
+ A synced copy updates its item (refused if it changed in Relvo since); any other file creates a new one and never
161
+ replaces one. The member's and the key's access decide what each may read, create and change, as in the console;
162
+ only project administrators write hooks, MCP servers, permissions, skills or agents whose frontmatter holds hooks,
163
+ allowed-tools, allowedTools, mcpServers or permissionMode, and skills with an mcp.json, and only in the console: pushing one is refused,
164
+ whoever's key it is.
165
+ Pushing needs a key with tools:execute; text that looks like a credential is refused.
166
+
167
+ Example: relvo team-skills push ./skills/weekly-report
168
+ Example: relvo team-skills push ~/.claude/agents/pricing-analyst.md`,
169
+ skills: `skills list | skills update [--client claude|codex|all]
170
+ Read current first-party guide summaries, or explicitly install/update verified Markdown.
171
+ Updates preserve foreign folders and user-edited guides. Endpoint discovery remains live.`,
172
+ update: `update [status|auto on|auto off|pin <version>|rollback]
173
+ Check and prepare a compatible stable CLI/MCP release for the next process start.
174
+ status reads the current policy and prepared versions. auto on enables daily checks;
175
+ In the packaged CLI, normal commands keep their selected version while a separate updater
176
+ checks automatically. It receives no command arguments and never replays user work.
177
+ auto off stops automatic installs and cancels automatically staged releases; a newer release
178
+ is still announced once a day. pin selects an exact stable version.
179
+ rollback selects the previous verified version and disables automatic updates.
180
+ Updates preserve the saved login, permissions and client configuration. A running MCP
181
+ session moves to the selected version between tool calls. RELVO_AUTO_UPDATE=0 disables updates and their announcements for a process.
182
+ This command is provided by the public @relvo/cli package launcher.`,
4
183
  upload: `upload <file>
5
- Upload one explicitly chosen local JPG/PNG/WAV/MP3/MP4, at most 12 MiB, to this project.
184
+ Upload one explicitly chosen local JPG/PNG/WebP/WAV/MP3/MP4/MOV, at most 12 MiB, to this project.
6
185
  Requires tools:execute. No generation charge. Returns a file ID valid for up to 30 days.
7
186
  Use {"source":{"fileId":"..."}} with an understanding or transcription tool.
8
187
  Repeating identical bytes reuses the same project file; it does not extend its expiry.
9
188
  File bytes and local paths are not included in command output.`,
10
189
  project: `project
11
- Read the organization and project bound to the developer API key. Free.`,
190
+ project status [--member-offset <integer>] [--invitation-offset <integer>] [--limit <1-100>]
191
+ status reads your selected project's roles, permission summary and key scopes.
192
+ Only project admins receive paged project members and pending invitations; a team admin role alone is insufficient.
193
+ project list
194
+ project add [--project <project-id>] [--no-browser]
195
+ project use <project-id>
196
+ Read the team and project bound to the API key. Free.
197
+ list shows only this installation's already approved projects, never credentials or unapproved projects.
198
+ add opens a fresh browser approval with --project selected, or else the project last open in that browser: check it there before approving.
199
+ Rerun add after an interruption.
200
+ use verifies live access and retires the old project's managed files before changing the default. Edited old files are archived under
201
+ ~/.config/relvo/project-archives; personal files and the target project's files are preserved. Reconnect MCP and start a fresh agent
202
+ session so old instructions do not remain in context. Approve the target files through platform_project_sync or team-skills sync;
203
+ a running default MCP pauses API requests after a switch. Accepted work stays in its original project.
204
+ Tool commands accept --project <id> for one command without changing the default, for example:
205
+ relvo tools --query search --project <project-id>
206
+ Run team-skills sync [--project <id>] to update managed project files. Edited files are preserved;
207
+ executable files still require --allow-scripts. use does not install files or broaden any key's scope.
208
+ An MCP launched with mcp --profile <managed-path> --project <id> stays explicitly pinned to that approval.`,
209
+ team: `team [--member-offset <integer>] [--invitation-offset <integer>] [--limit <1-100>]
210
+ Read the selected team/project, your roles and approved key scopes with the normal project:read grant.
211
+ Team owners/admins also receive paged member and pending team invitation summaries; follow each nextOffset.
212
+ Other members receive only their own context. This command changes no permissions and never needs access:write. Free.`,
12
213
  usage: `usage
13
214
  Read this project's monthly requests and spend. amountCents is USD cents. Free.`,
14
- tools: `tools [--query <text>] [--category <research|seo|social|generation>]
15
- [--available-only] [--summary]
16
- Search names/descriptions, filter by category or configured provider, and show USD prices.
17
- Use --summary to omit schemas/examples, then tool <name> for full details. Free.
215
+ connections: `connections [--provider <id>] [--offset <integer>] [--limit <1-20>]
216
+ Read a page of OAuth connections belonging to this developer project. Requires tools:read.
217
+ Default 10 rows; follow nextOffset with --offset. --provider filters before paging.
218
+ Returns IDs, labels, connected/reauth_required states and observed scopes without tokens.
219
+ Use an ID only in a selected tool's documented connectionId field. Scopes do not establish
220
+ every operation's permission; reauth_required lists the reconnectUrl that reconnects it in the Relvo console. Free.`,
221
+ connect: `connect <app>
222
+ Print the console pages where a person connects the named app or adds an account. Requires tools:read.
223
+ sign_in: open url, check the account and requested permissions, and authorize.
224
+ credentials: open url and enter the key on Relvo's page. This command takes no key, token or password.
225
+ Run it again afterwards; status shows the result. Free.`,
226
+ tools: `tools [--query <text>] [--app <name-or-id>] [--category <all|research|seo|social|enrichment|generation|finance|news>]
227
+ [--provider <id>] [--available-only | --include-unavailable]
228
+ [--max-charge-micros <integer>] [--quote-arguments '<json>']
229
+ [--funding all|relvo|provider|mixed] [--price-unit <unit>] [--sort-by relevance|price_asc]
230
+ [--include '<filter-json>'] [--exclude '<filter-json>']
231
+ [--limit <1-12>] [--mode auto|keyword|semantic] [--summary | --with-schemas]
232
+ Search capabilities, tool/model names and descriptions; return up to 12 summaries (default 8).
233
+ --app scopes discovery to an indexed connected app, e.g. --app "Google Drive".
234
+ Unknown or ambiguous apps return explicitly unscoped results; inspect the warning.
235
+ Empty/omitted means unscoped. This filter does not select an execution account.
236
+ --provider filters this list; it does not select a provider for executions.
237
+ --max-charge-micros filters per-call quotes, not unit prices (1 USD=1000000 micros; 0=free).
238
+ Unquotable and externally billed candidates are excluded by a cost cap. Without
239
+ --quote-arguments, quotes use catalog examples. Requote actual arguments before executing.
240
+ --price-unit matches an exact catalog unit. price_asc puts unknown costs last.
241
+ --include/--exclude accept {"tools":[],"providers":[],"categories":[]} with exact IDs.
242
+ OR within a list, AND across include lists; any exclusion wins. Empty/null is neutral.
243
+ Excluding a provider removes any tool listing it; it does not pin a later execution.
244
+ Schemas/examples are omitted by default. For a precise capability, --limit 1 --with-schemas
245
+ can include its definition in the search. Reuse complete schemas; use tool <name> only
246
+ for omitted definitions or missing constraints.
18
247
  available=true does not override executionEnabled=false or key/budget limits.
19
248
 
20
- Example: noimos-platform tools --query search --available-only --summary`,
249
+ Example: relvo tools --query search --available-only --summary`,
21
250
  tool: `tool <name>
22
251
  Inspect one tool's full input schema, example, availability and USD price. Free.
23
252
 
24
- Example: noimos-platform tool web_search`,
253
+ Example: relvo tool web_search`,
25
254
  executions: `executions [--limit <1-100>] [--before <id>] [--status <state>] [--tool <name>]
26
255
  Read project history, newest first. Use nextBefore as --before for the next page.
27
256
  States: queued, running, waiting, succeeded, failed, cancelled, needs_review. Free.
28
257
 
29
- Example: noimos-platform executions --status failed --limit 20`,
30
- quote: `quote <tool> '<arguments-json>'
258
+ Example: relvo executions --status failed --limit 20`,
259
+ monitors: `monitors destination-add <id> --destination '<json>' | destination-remove <id> --destination-id <dst_id>
260
+ monitors list | popular | tools | delete <id> | resume <id> [--maximum-charge-micros-per-run <n>]
261
+ monitors quote --tool <name> [--args '<json>'] [--provider <id>] [--interval 15m|1h|1d | --cron "<m h dom mon dow>" [--time-zone <IANA>]]
262
+ monitors create --tool <name> [--args '<json>'] [--provider <id>] [--interval 15m|1h|1d | --cron "<m h dom mon dow>" [--time-zone <IANA>]] [--name <text>]
263
+ [--maximum-charge-micros-per-run <n>]
264
+ monitors events [--monitor <id>] [--after <cursor>] [--limit <1-100>]
265
+ Watch a tool for changes. Relvo re-runs it on a schedule and diffs against the previous result. The
266
+ schedule is --interval (default 1h) or a 5-field --cron such as "0 9 * * 1-5" (weekdays 9:00) read in
267
+ --time-zone (default UTC, e.g. Asia/Tokyo), at least 15 minutes between runs; not both. Projects with
268
+ the same tool, arguments, provider and schedule share one run. tools lists what can
269
+ be monitored, the provider to pin and chargeCeilingMicros (0 = free). Every project watching a
270
+ target pays the full price of each run, the same as running the tool once; they only share the
271
+ fetch. quote shows that price and how many projects watch it. Creating a monitor needs
272
+ --maximum-charge-micros-per-run of at least the ceiling, and charges every run from the balance
273
+ until deleted. A monitor that cannot pay a run is suspended; resume continues it from now. The first run is a baseline with no events. Lists report
274
+ new items (added); single results report changed fields. events reads oldest first; pass nextAfter
275
+ as --after to read only newer events, omit --monitor for every monitor. popular shows monitors that
276
+ at least 3 teams already watch. destination-add also sends a monitor's changes to
277
+ {kind:"webhook",url} (signing secret shown once), {kind:"slack",url} or {kind:"discord",url}, each with a label;
278
+ email is console only. list shows each destination's dst_ id for destination-remove.
279
+
280
+ Example: relvo monitors quote --tool web_search --provider brave --args '{"query":"relvo"}'`,
281
+ triggers: `triggers list | types --app <app> [--connection <id>] [--query <text>] [--offset <n>] [--limit <1-100>] | delete <id> | update <id> [--enabled true|false] [--name <text>]
282
+ triggers create --connection <id> --tool <name> [--args '<json>'] [--interval 1m|5m|15m|1h] [--items-path </json/pointer>]
283
+ [--maximum-charge-micros-per-poll <n>] [--name <text>] [--destinations '<json array>']
284
+ triggers create --connection <id> --type <app.event> [--config '<json>'] [--name <text>] [--destinations '<json array>']
285
+ triggers events [--trigger <id>] [--after <cursor>] [--limit <1-100>]
286
+ triggers destination-add <id> --destination '<json>' | destination-remove <id> --destination-id <dst_id>
287
+ Get events from a connected app. The trigger runs as the API key that creates it. A poll trigger
288
+ (--tool) runs a read operation of the app every --interval (default 5m) and reports what is new;
289
+ the first run is a baseline with no events. Each poll is one execution: it counts toward the plan's
290
+ free runs and is charged beyond them, up to --maximum-charge-micros-per-poll. A webhook trigger
291
+ (--type) registers with the app and receives its events at once; --config must match the type's
292
+ configSchema. types --app lists an app's webhook types and a page of its read operations; with
293
+ --connection, only the ones this key may watch on that connection. Destinations
294
+ are a JSON array of {kind:"webhook",url} (signing secret shown once), {kind:"slack"}, {kind:"discord"}
295
+ or {kind:"workflow",workflow,input,maximumChargeMicros}; email is console only. events reads oldest
296
+ first; pass nextAfter as --after to read only newer events, omit --trigger for every trigger.
297
+ update --enabled false pauses a trigger; true resumes it and checks a failed one again. destination-add and
298
+ destination-remove change where an existing trigger's events go; list shows each destination's dst_ id.
299
+
300
+ Example: relvo triggers types --app github`,
301
+ access: `access list | access set <member-id> (--tools '<json array>' | --all)
302
+ Legacy NoimosAI Platform only. Relvo shares resources through project membership; use project settings
303
+ to assign Administrator or User. Relvo does not support individual member-policy keys.
304
+ Read and change what each team member may use of the team's own connections, custom tools, skills and
305
+ secrets. list shows every member and pending invitation; toolAccess null is everything, a list names
306
+ connection:<id>, connection:<id>/<tool>, connection:<id>/reads, custom__<slug>, skill:<project-id>/<slug>
307
+ and secret:<project-id>/<NAME>; what it does not name is hidden from them. set replaces one member's whole list
308
+ (--all gives everything, those added later included); read it with list first. Needs a key of the
309
+ team's owner or an admin made in the console with access:write; such a key runs no tool. An admin
310
+ changes no admin's or owner's list and gives nothing they or the key do not hold; roles are never
311
+ changed here.
312
+
313
+ Example: relvo access set 6f1c… --tools '["connection:c-1/reads"]'`,
314
+ quote: `quote <tool> '<arguments-json>' [--provider <id>] [--session <id>] [--relevance-check auto|light]
315
+ [--maximum-charge-micros <cap>]
31
316
  Get a free quote without starting paid work. Returns priceVersion and maximumChargeMicros.
317
+ Auto routing is the default; --provider auto also selects Auto.
318
+ Other --provider values pin one provider; repeat the selection on execute.
319
+ An Auto research tool accepts only the providers it lists, runs just that one when pinned,
320
+ and is billed at the recorded cost of the call. --relevance-check light keeps the rows
321
+ its first relevance classifier cannot decide instead of running the full check.
322
+ With a cap, an Auto quote above it returns the best plan within it as plan;
323
+ pass plan's provider and relevanceCheck to execute unchanged.
32
324
  ${inputHelp}
33
325
 
34
326
  Examples:
35
- noimos-platform quote web_search '{"query":"content marketing trends"}'
36
- noimos-platform quote web_search --input-file brief.json`,
327
+ relvo quote web_search '{"query":"content marketing trends"}'
328
+ relvo quote web_search --input-file brief.json`,
37
329
  execute: `execute <tool> '<arguments-json>' --maximum-charge-micros <integer>
38
- --price-version <version> --idempotency-key <key>
330
+ --price-version <version> --idempotency-key <key> [--provider <id>] [--session <id>]
331
+ [--relevance-check auto|light] [--raw]
39
332
  Enqueue paid work using an accepted quote. All three confirmation flags are required.
333
+ Auto routing is the default. Retain the same provider and relevance check used for the quote.
40
334
  1 USD = 1000000 micros. Retain the same body and key when retrying a submission.
41
335
  Keys: 8-128 letters, digits, periods, underscores, colons or hyphens.
42
336
  ${inputHelp}
43
337
  Returns an execution ID. Read it with execution <id> or wait <id>; acceptance is not completion.
338
+ Large replay receipts reference the retained result; --raw explicitly returns the full receipt.
44
339
 
45
340
  Example (use the values from your quote and a saved key):
46
- noimos-platform execute web_search --input-file brief.json \\
47
- --maximum-charge-micros "$PLATFORM_QUOTED_MAXIMUM_MICROS" \\
48
- --price-version "$PLATFORM_QUOTED_PRICE_VERSION" --idempotency-key "$PLATFORM_REQUEST_UUID"`,
49
- execution: `execution <id>
50
- Read an execution's status, output and chargedMicros (1 USD = 1000000 micros). Free.
341
+ relvo execute web_search --input-file brief.json \\
342
+ --maximum-charge-micros "$RELVO_QUOTED_MAXIMUM_MICROS" \\
343
+ --price-version "$RELVO_QUOTED_PRICE_VERSION" --idempotency-key "$RELVO_REQUEST_UUID"`,
344
+ execution: `execution <id> [--raw]
345
+ Read status, a bounded output preview and chargedMicros (1 USD = 1000000 micros). Free.
346
+ The full output stays retained. Use result <id> to read selected data, or --raw for full JSON.
51
347
  A pending zero charge is not a free completion. For needs_review, contact support;
52
348
  do not automatically submit a replacement.`,
53
- wait: `wait <id> [--timeout <seconds>]
349
+ results: `results <id>... [--path '<JSON-array>'] [--fields '<JSON-array>'] [--offset <integer>] [--limit <1-${TOOL_RESULT_PAGE_MAX_ITEMS}>] [--max-chars <n>]
350
+ [--find '<terms>'] [--pick '<label-to-paths-json>'] [--wait <1-60 seconds>]
351
+ Read the same selection of several retained outputs at once, such as every ID parallel returned. Free.
352
+ Each ID answers for itself in results, in the order asked; one that cannot be read carries its error.
353
+ A text page holds 8,000 characters by default and at most 20,000 with --max-chars.
354
+ --find keeps the lines of a text that hold a term: one term, or a JSON list of terms.
355
+ What is still pending shares one wait (default 25 seconds).
356
+ Exit 0 means every one succeeded with data, 3 that some are still pending, and 1 otherwise.`,
357
+ result: `result <id> [--path '<JSON-array>'] [--fields '<JSON-array>'] [--offset <integer>] [--limit <1-${TOOL_RESULT_PAGE_MAX_ITEMS}>] [--max-chars <1-8000>]
358
+ [--find '<terms>'] [--pick '<label-to-paths-json>'] [--wait <1-60 seconds>] [--file]
359
+ Read a page of the retained full output without repeating the paid execution. Free.
360
+ Without --wait, unfinished work answers at once with exit 3.
361
+ --find, --pick and --wait read as run does, with the reply {id, status, result}, and --max-chars goes up to 20,000.
362
+ --find keeps the lines of a text that hold a term: one term, or a JSON list of terms. findNotApplied means the path holds no single text.
363
+ Check status and chargedMicros: partial:true is incomplete saved output from a failed/cancelled task.
364
+ upstreamRefusal means the connected app refused the request: the output holds that refusal, not data.
365
+ Exit 1 preserves failed/cancelled/needs_review and an app's refusal, and exit 3 means still pending. --file also exports partial rows.
366
+ Use a JSON array of path segments, including string array indexes; default path is [].
367
+ Default offset: 0, item limit: 20, string limit: 4000 characters. Follow nextOffset.
368
+ The page includes a bounded preview and a reference when the selected value is large.
369
+
370
+ Pages shrink to fit the response budget; follow nextOffset. --fields keeps named keys from an object or each array row without changing stored data. Object offsets page the selected keys.
371
+ --file writes the whole path instead (fields applied, "a.b" keeps a nested key) to ./.relvo/results
372
+ as JSON Lines for an array, one row per line, and prints its path; git ignores that directory.
373
+ RELVO_RESULT_DIRECTORY selects another directory. Use jq, grep or a file reader on it.
374
+ Example: relvo result job_123 --path '["items"]' --fields '["id","title"]' --limit 100
375
+ Example: relvo result job_123 --path '["items"]' --fields '["title","metrics.likes"]' --file`,
376
+ wait: `wait <id> [--timeout <seconds>] [--raw]
54
377
  Wait for completion, polling every 5 seconds. Default timeout: 480 seconds; range: 1-900.
378
+ Returns a bounded preview by default; use result for selected data or --raw for full JSON.
55
379
  Exits 0 on success, 1 on failed/cancelled/needs_review or request error, 3 on timeout.
56
380
  A timeout leaves accepted work running. Continue with the same ID. Free.`,
57
- cancel: `cancel <id>
381
+ cancel: `cancel <id> [--raw]
58
382
  Request cancellation of unstarted work. Already incurred provider costs may be charged.
383
+ Large receipts reference retained output; --raw explicitly returns the full receipt.
59
384
  Requires execution ownership or project ADMIN access.`,
60
385
  "artifact-url": `artifact-url <execution-id> <artifact-id>
61
386
  Get a private output's temporary download URL, valid for up to 5 minutes.
62
387
  Treat it as private access; never send the developer API key to the storage host.`,
63
388
  download: `download <execution-id> <artifact-id> --out <file>
64
389
  Save a private artifact after verifying size and SHA-256. Never overwrite an existing file.
65
- The developer API key is sent only to the Platform API, not the storage host.`,
390
+ The developer API key is sent only to the Relvo API, not the storage host.`,
66
391
  };
392
+ export const commandHelpNames = Object.keys(commands);
67
393
  export function commandHelp(name) {
68
- return Object.hasOwn(commands, name) ? `Usage: noimos-platform ${commands[name]}\n` : undefined;
394
+ return Object.hasOwn(commands, name) ? `Usage: relvo ${commands[name]}\n` : undefined;
69
395
  }
@@ -1,50 +1,43 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { link, mkdtemp, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
- import { platformArtifactSchema } from "../client/index.js";
5
- function artifacts(output) {
6
- if (!output)
7
- return [];
8
- const candidates = Array.isArray(output.artifacts) ? output.artifacts : [];
9
- const found = [];
10
- for (const candidate of candidates) {
11
- const parsed = platformArtifactSchema.safeParse(candidate);
12
- if (parsed.success)
13
- found.push(parsed.data);
14
- }
15
- if (Array.isArray(output.results))
16
- for (const result of output.results) {
17
- if (result && typeof result === "object" && !Array.isArray(result) && result.data && typeof result.data === "object" && !Array.isArray(result.data)) {
18
- found.push(...artifacts(result.data));
19
- }
20
- }
21
- return found;
22
- }
4
+ import { outputArtifacts } from "../client/agent-run.js";
5
+ const STALL_MS = 60_000;
23
6
  export async function downloadArtifact(client, id, artifactId, filename, transport = fetch) {
24
7
  const execution = (await client.execution(id)).data;
25
- const artifact = artifacts(execution.output).find((entry) => entry.id === artifactId);
8
+ if (["queued", "running", "waiting"].includes(execution.status))
9
+ throw new Error(`This execution is still ${execution.status}. Wait for it with relvo wait ${id}, then download.`);
10
+ const artifact = outputArtifacts(execution.output).find((entry) => entry.id === artifactId);
26
11
  if (!artifact)
27
12
  throw new Error("Artifact is not present in this execution. Inspect execution output for available artifact IDs.");
28
13
  const { data } = await client.artifactDownload(id, artifactId);
29
14
  const url = new URL(data.url);
30
15
  if (url.hostname !== "storage.googleapis.com" && !url.hostname.endsWith(".storage.googleapis.com"))
31
16
  throw new Error("Unsupported artifact download host.");
32
- const response = await transport(url, { method: "GET", redirect: "error", signal: AbortSignal.timeout(60000) }).catch(() => {
17
+ const stalled = new AbortController();
18
+ let watchdog = setTimeout(() => stalled.abort(), STALL_MS);
19
+ const progressed = () => { clearTimeout(watchdog); watchdog = setTimeout(() => stalled.abort(), STALL_MS); };
20
+ const response = await transport(url, { method: "GET", redirect: "error", signal: stalled.signal }).catch(() => {
21
+ clearTimeout(watchdog);
33
22
  throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
34
23
  });
35
- if (!response.ok || !response.body)
24
+ if (!response.ok || !response.body) {
25
+ clearTimeout(watchdog);
36
26
  throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
27
+ }
37
28
  const reader = response.body.getReader(), chunks = [];
38
- let bytes = 0;
29
+ let bytes = 0, oversized = false;
39
30
  try {
40
31
  for (;;) {
32
+ progressed();
41
33
  const part = await reader.read();
42
34
  if (part.done)
43
35
  break;
44
36
  bytes += part.value.byteLength;
45
37
  if (bytes > artifact.sizeBytes) {
38
+ oversized = true;
46
39
  await reader.cancel();
47
- throw new Error("Artifact exceeded its recorded size.");
40
+ break;
48
41
  }
49
42
  chunks.push(part.value);
50
43
  }
@@ -53,13 +46,20 @@ export async function downloadArtifact(client, id, artifactId, filename, transpo
53
46
  throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
54
47
  }
55
48
  finally {
49
+ clearTimeout(watchdog);
56
50
  reader.releaseLock();
57
51
  }
52
+ if (oversized)
53
+ throw new Error("Artifact exceeded its recorded size; no file was saved.");
58
54
  const buffer = Buffer.concat(chunks);
59
55
  if (bytes !== artifact.sizeBytes || createHash("sha256").update(buffer).digest("hex") !== artifact.sha256)
60
56
  throw new Error("Artifact integrity check failed; no file was saved.");
61
57
  const path = resolve(filename);
62
- const temporary = await mkdtemp(join(dirname(path), ".noimos-platform-"));
58
+ const temporary = await mkdtemp(join(dirname(path), ".noimos-platform-")).catch((error) => {
59
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
60
+ throw new Error(`The folder for --out does not exist: ${dirname(path)}. No file was saved.`);
61
+ throw error;
62
+ });
63
63
  try {
64
64
  const staged = join(temporary, "artifact");
65
65
  await writeFile(staged, buffer, { flag: "wx", mode: 0o600 });
@@ -0,0 +1,48 @@
1
+ export class CliInputError extends Error {
2
+ }
3
+ export function jsonOption(name, text) {
4
+ try {
5
+ return JSON.parse(text);
6
+ }
7
+ catch {
8
+ throw new CliInputError(`Invalid JSON in ${name}`);
9
+ }
10
+ }
11
+ export function isCliInputFailure(error) {
12
+ return error instanceof CliInputError || error instanceof Error && "code" in error && typeof error.code === "string" && error.code.startsWith("ERR_PARSE_ARGS_");
13
+ }
14
+ export function cliInputReason(error) {
15
+ if (error instanceof CliInputError)
16
+ return error.message;
17
+ if (error instanceof SyntaxError)
18
+ return "Invalid JSON";
19
+ if (error instanceof Error && error.name === "ZodError" && "issues" in error && Array.isArray(error.issues)) {
20
+ const issues = error.issues;
21
+ const fields = new Map();
22
+ for (const issue of issues) {
23
+ if (!issue || typeof issue !== "object" || !("path" in issue) || !Array.isArray(issue.path))
24
+ continue;
25
+ const path = issue.path;
26
+ if (!path.every(part => typeof part === "string" || typeof part === "number"))
27
+ continue;
28
+ const name = path.join(".") || "input";
29
+ const expected = "code" in issue && issue.code === "invalid_type" && "expected" in issue && typeof issue.expected === "string" ? issue.expected : undefined;
30
+ const constraint = expected ? ` (expected ${expected})` : "maximum" in issue && typeof issue.maximum === "number" ? ` (max ${issue.maximum})`
31
+ : "minimum" in issue && typeof issue.minimum === "number" ? ` (min ${issue.minimum})` : "";
32
+ if (!fields.has(name) || expected)
33
+ fields.set(name, `${name}${constraint}`);
34
+ }
35
+ return `Invalid input fields: ${[...fields.values()].slice(0, 4).join(", ") || "input"}`;
36
+ }
37
+ if (error instanceof Error && "code" in error && typeof error.code === "string") {
38
+ if (error.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
39
+ const name = /^Unknown option '(--[a-z-]{1,40})'/.exec(error.message)?.[1];
40
+ return name ? `Unknown command option ${name}` : "Unknown command option";
41
+ }
42
+ if (error.code.startsWith("ERR_PARSE_ARGS_"))
43
+ return "Invalid command option or value";
44
+ if (["ENOENT", "EACCES", "EISDIR"].includes(error.code))
45
+ return "The input file could not be read";
46
+ }
47
+ return "Invalid command, JSON arguments or input file";
48
+ }