@noimosai/platform-mcp 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.
- package/README.md +17 -48
- package/bin/noimos-platform-mcp.mjs +4 -5
- package/dist/cli/cli.js +567 -0
- package/dist/cli/command-help.js +395 -0
- package/dist/cli/download.js +72 -0
- package/dist/cli/index.js +3 -0
- 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/instructions.js +26 -0
- package/dist/mcp/local-projects.js +42 -0
- package/dist/mcp/local-upload.js +7 -13
- 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 +65 -11
- package/dist/mcp/server.js +382 -40
- 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 +5 -3
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { TOOL_RESULT_PAGE_MAX_ITEMS } from "../client/index.js";
|
|
2
|
+
const inputHelp = `Supply one JSON object inline, use - for stdin, or use --input-file <path>.
|
|
3
|
+
File/stdin input is limited to 1 MiB. --input-file - also reads stdin.`;
|
|
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.`,
|
|
183
|
+
upload: `upload <file>
|
|
184
|
+
Upload one explicitly chosen local JPG/PNG/WebP/WAV/MP3/MP4/MOV, at most 12 MiB, to this project.
|
|
185
|
+
Requires tools:execute. No generation charge. Returns a file ID valid for up to 30 days.
|
|
186
|
+
Use {"source":{"fileId":"..."}} with an understanding or transcription tool.
|
|
187
|
+
Repeating identical bytes reuses the same project file; it does not extend its expiry.
|
|
188
|
+
File bytes and local paths are not included in command output.`,
|
|
189
|
+
project: `project
|
|
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.`,
|
|
213
|
+
usage: `usage
|
|
214
|
+
Read this project's monthly requests and spend. amountCents is USD cents. 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.
|
|
247
|
+
available=true does not override executionEnabled=false or key/budget limits.
|
|
248
|
+
|
|
249
|
+
Example: relvo tools --query search --available-only --summary`,
|
|
250
|
+
tool: `tool <name>
|
|
251
|
+
Inspect one tool's full input schema, example, availability and USD price. Free.
|
|
252
|
+
|
|
253
|
+
Example: relvo tool web_search`,
|
|
254
|
+
executions: `executions [--limit <1-100>] [--before <id>] [--status <state>] [--tool <name>]
|
|
255
|
+
Read project history, newest first. Use nextBefore as --before for the next page.
|
|
256
|
+
States: queued, running, waiting, succeeded, failed, cancelled, needs_review. Free.
|
|
257
|
+
|
|
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>]
|
|
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.
|
|
324
|
+
${inputHelp}
|
|
325
|
+
|
|
326
|
+
Examples:
|
|
327
|
+
relvo quote web_search '{"query":"content marketing trends"}'
|
|
328
|
+
relvo quote web_search --input-file brief.json`,
|
|
329
|
+
execute: `execute <tool> '<arguments-json>' --maximum-charge-micros <integer>
|
|
330
|
+
--price-version <version> --idempotency-key <key> [--provider <id>] [--session <id>]
|
|
331
|
+
[--relevance-check auto|light] [--raw]
|
|
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.
|
|
334
|
+
1 USD = 1000000 micros. Retain the same body and key when retrying a submission.
|
|
335
|
+
Keys: 8-128 letters, digits, periods, underscores, colons or hyphens.
|
|
336
|
+
${inputHelp}
|
|
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.
|
|
339
|
+
|
|
340
|
+
Example (use the values from your quote and a saved key):
|
|
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.
|
|
347
|
+
A pending zero charge is not a free completion. For needs_review, contact support;
|
|
348
|
+
do not automatically submit a replacement.`,
|
|
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]
|
|
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.
|
|
379
|
+
Exits 0 on success, 1 on failed/cancelled/needs_review or request error, 3 on timeout.
|
|
380
|
+
A timeout leaves accepted work running. Continue with the same ID. Free.`,
|
|
381
|
+
cancel: `cancel <id> [--raw]
|
|
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.
|
|
384
|
+
Requires execution ownership or project ADMIN access.`,
|
|
385
|
+
"artifact-url": `artifact-url <execution-id> <artifact-id>
|
|
386
|
+
Get a private output's temporary download URL, valid for up to 5 minutes.
|
|
387
|
+
Treat it as private access; never send the developer API key to the storage host.`,
|
|
388
|
+
download: `download <execution-id> <artifact-id> --out <file>
|
|
389
|
+
Save a private artifact after verifying size and SHA-256. Never overwrite an existing file.
|
|
390
|
+
The developer API key is sent only to the Relvo API, not the storage host.`,
|
|
391
|
+
};
|
|
392
|
+
export const commandHelpNames = Object.keys(commands);
|
|
393
|
+
export function commandHelp(name) {
|
|
394
|
+
return Object.hasOwn(commands, name) ? `Usage: relvo ${commands[name]}\n` : undefined;
|
|
395
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { link, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { outputArtifacts } from "../client/agent-run.js";
|
|
5
|
+
const STALL_MS = 60_000;
|
|
6
|
+
export async function downloadArtifact(client, id, artifactId, filename, transport = fetch) {
|
|
7
|
+
const execution = (await client.execution(id)).data;
|
|
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);
|
|
11
|
+
if (!artifact)
|
|
12
|
+
throw new Error("Artifact is not present in this execution. Inspect execution output for available artifact IDs.");
|
|
13
|
+
const { data } = await client.artifactDownload(id, artifactId);
|
|
14
|
+
const url = new URL(data.url);
|
|
15
|
+
if (url.hostname !== "storage.googleapis.com" && !url.hostname.endsWith(".storage.googleapis.com"))
|
|
16
|
+
throw new Error("Unsupported artifact download host.");
|
|
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);
|
|
22
|
+
throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok || !response.body) {
|
|
25
|
+
clearTimeout(watchdog);
|
|
26
|
+
throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
|
|
27
|
+
}
|
|
28
|
+
const reader = response.body.getReader(), chunks = [];
|
|
29
|
+
let bytes = 0, oversized = false;
|
|
30
|
+
try {
|
|
31
|
+
for (;;) {
|
|
32
|
+
progressed();
|
|
33
|
+
const part = await reader.read();
|
|
34
|
+
if (part.done)
|
|
35
|
+
break;
|
|
36
|
+
bytes += part.value.byteLength;
|
|
37
|
+
if (bytes > artifact.sizeBytes) {
|
|
38
|
+
oversized = true;
|
|
39
|
+
await reader.cancel();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
chunks.push(part.value);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(watchdog);
|
|
50
|
+
reader.releaseLock();
|
|
51
|
+
}
|
|
52
|
+
if (oversized)
|
|
53
|
+
throw new Error("Artifact exceeded its recorded size; no file was saved.");
|
|
54
|
+
const buffer = Buffer.concat(chunks);
|
|
55
|
+
if (bytes !== artifact.sizeBytes || createHash("sha256").update(buffer).digest("hex") !== artifact.sha256)
|
|
56
|
+
throw new Error("Artifact integrity check failed; no file was saved.");
|
|
57
|
+
const path = resolve(filename);
|
|
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
|
+
try {
|
|
64
|
+
const staged = join(temporary, "artifact");
|
|
65
|
+
await writeFile(staged, buffer, { flag: "wx", mode: 0o600 });
|
|
66
|
+
await link(staged, path);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await rm(temporary, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
return { id, artifactId, path, sizeBytes: bytes, sha256: artifact.sha256 };
|
|
72
|
+
}
|
|
@@ -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
|
+
}
|