Skillhook Cloud from the terminal and the agent: the cloud's tools and the skillhook-cloud MCP server (0.7.0) - #17
Merged
Merged
Conversation
…he skillhook-cloud MCP server (0.7.0) Everything the Skillhook Cloud dashboard shows and does, with an organisation API key. The cloud publishes its tools (GET /api/v1/tools: JSON Schema and scope for each) and skillhook builds its commands and MCP tools from that list at run time, so a tool the cloud adds needs no new skillhook. - skillhook cloud overview: what needs a person across the organisation (describe_cloud). - skillhook cloud tools [tool] and skillhook cloud <tool> [args] [--param value]: any tool by name, required parameters as arguments, flags typed from the schema (switches, checked numbers, JSON as a literal, @file or -, --param-file for long text, --input for the whole input), the answer as text or as it came with --json; a tool beyond the key's scope is refused before anything is sent. - skillhook cloud secret <machine> <skill|NAME>: the machine generates the secret sealed to a one-time key pair here (POST /machines/<m>/secrets, POST /commands/<id>/claim); the cloud never sees it. - skillhook mcp --cloud, registered by the plugin as skillhook-cloud: the same tools for the agent, the cloud's instructions, generate_secret; without a key only skillhook_cloud_setup, which loads the tools once the person logged in (the agent never handles the key). - cloud login --url names the cloud (kept as cloud.url, refused over a pairing elsewhere) and asks for the key without echo at a terminal; cloud status / cloud_status say whether a key is kept. - The link sends a fresh snapshot right after skill.put, skill.delete, config.patch and secret commands, so the dashboard shows a saved skill and its secret at once, not at the next snapshot. - A plugin skill, skillhook-cloud: triage (failure kinds, rejected deliveries, failing checks), acting safely, and setting up machines, skills, secrets and hosted URLs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rts: an MCP client waits for the server meanwhile Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int it, tools read their own arguments - cloud login keeps the key with the cloud it was checked against (SKILLHOOK_CLOUD_API_URL in .env) and every API request goes there, whatever cloud.url says later; login no longer touches cloud.url, so it can never move a machine's link (also one paused with cloud.enabled: false). - The local MCP server refuses cloud.* settings in update_config and SKILLHOOK_CLOUD_* names in set_secret and generate_secret: an agent could otherwise point cloud.url (or the key's URL) at another host and have the bridge send the organisation's key there. - skillhook cloud <tool> reads the arguments after the tool's name with its schema: a switch never swallows the next word (save_skill m --allow-unauthenticated s kept m, s in order), a text parameter always takes it (--body works without a special case), --x=value, --no-x and -- work, and --input yields to flags. - cloud secret and generate_secret generate only a skill's secret: the skill, SKILLHOOK_SECRET_*, or a variable a skill there names; never the machine's admin token or a runner's key. A claim that finds the value already collected or expired says so. - Text from the cloud is printed without control characters (keys too, and the overview, tables and job views); --json is unchanged. - The catalogue keeps the entries it can read and the first of a duplicated name, refuses a newer catalogue shape with "update skillhook", and never lets a catalogue tool replace the local generate_secret. - Only a missing key turns an unknown word into "unknown subcommand"; SKILLHOOK_NO_CLOUD or a bad URL is the command's failure (exit 1, JSON with --json). - Docs: login and secrets in docs/cloud.md, the bridge's catalogue lifetime in docs/mcp.md, pairing on the dashboard in the plugin skill, CHANGELOG, AGENTS.md, llms.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ill cannot name skillhook's credentials - skillhook cloud <tool>: the tool's options may come before its name too (they were dropped). skillhook's own options (--json, --help/-h, --version/-v, --dir/--home) mean the same wherever they stand and are never a parameter's value, so both readings of the line agree and --help never runs a tool; such a text is --answer=--help. - A skill's secret_env can no longer be SKILLHOOK_ADMIN_TOKEN or SKILLHOOK_CLOUD_*: generating or setting the skill's secret would overwrite it. The machine refuses the cloud's secret.generate and secret.set for those by the resolved name (a skill's, "admin", the variable's), so the cloud cannot rotate the admin token either. - The API key's cloud: the same key from the environment goes to the cloud login kept with it; login defaults to the cloud logged in to before; a 0.6 key without one goes to the machine's cloud and cloud status says so; the unreachable hint no longer points at cloud.url. - A catalogue entry whose input schema is not an object of object parameters is skipped, and a tool the MCP server refuses is left out, never taking the bridge down. - printable() also drops C1 controls and bidirectional overrides and isolates, and covers error output and every cloud command's text. - Docs no longer claim an agent cannot move the key: the local MCP's configuration and secret tools refuse it, an agent that runs commands acts with the person's rights. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, in job artifacts too job.artifact and job.get sent a job's payload, event and prompt (prompt.md quotes the payload) whenever artifacts could be uploaded, whatever cloud.upload_payloads or the organisation's hint said: the cloud discarded them, or a machine that kept its bodies home in an organisation that stores them had them stored anyway. They are now refused (job.artifact) or listed as withheld (job.get) when bodies may not leave the machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s too, login never guesses between two clouds - skillhook cloud: before the subcommand or tool only skillhook's own options. A tool's switch that only its schema knows (--store-payloads, --include-body) swallowed the tool's name (cloud --store-payloads update_settings ran cloud status), and words before `cloud` never reached the tool: both are now usage errors that say where options go. - --no-json, --no-help, --no-dir… are skillhook's own options as every command reads them: never a tool's value. - login without --url, when the cloud kept with the last key and the machine's cloud differ, asks for --url instead of sending the new key to one of them; a refused key's hint names the cloud (login --url <it>). - --json output escapes DEL, C1 and bidirectional characters (\u…): the same JSON, nothing a terminal acts on. - cloud secret (and generate_secret) refuse the machine's own credentials by name or through a skill an older machine still has naming one; docs/security.md no longer says the local MCP refuses the admin token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blank --url is refused, the docs say what bodies mean - `--` ends the options for every command, so a tool parameter never takes it as its value (`--answer -- --help` answered "--"); before the subcommand it works again (`skillhook cloud -- status`), every word after it an argument. - login: a blank --url is refused instead of skipping the two-clouds check; that check comes before the key is asked for at a terminal. - The reason for a withheld body names both causes (cloud.upload_payloads here, or an organisation that keeps none); the hint path is tested for artifacts. - docs: cloud.upload_payloads covers a delivery's body and a job's payload, event and prompt; an agent's transcript and result are its own output and can quote what it read (cloud.upload_artifacts keeps transcripts home). The protocol doc gives the artifact refusal; docs/security.md says API-key requests go to the key's cloud, not cloud.url. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Everything the Skillhook Cloud dashboard shows and does, from the terminal and for an agent, with an organisation API key. The cloud now publishes its tools (
GET /api/v1/tools: each with its JSON Schema and the scope it needs; MeterApp/skillhook-cloud#18), and skillhook builds its commands and MCP tools from that list at run time: a tool the cloud adds works here without a new skillhook, and no cloud tool is hand-written in this repo.CLI
skillhook cloud overview: what needs a person across the organisation (agents waiting with their question, open alerts, failing checks with the fix, failed jobs and rejected webhooks of 24 h, the day's numbers, next steps).skillhook cloud tools [tool]: what this key can do (and what a wider key would add); one tool's parameters.skillhook cloud <tool> [args] [--param value]…: any tool by name (answer_job <job> "yes" --option yes,run_skill mac-mini triage --payload @event.json --wait-seconds 120,get_stats --days 30,save_skill mac-mini triage --content-file SKILL.md, …). The words after the tool's name are read with its schema (before it, only skillhook's own options; a switch never swallows the next word, a text parameter takes it;--x=value,--no-x,--); required parameters as arguments in order, checked numbers, JSON as a literal,@fileor-,--param-filefor long text,--inputfor the whole input. skillhook's own options (--json,--help/-h,--version/-v,--dir) mean the same wherever they stand and are never a parameter's value, so--helpnever runs a tool and--answer=--helpsends that text. Text output without control characters or bidirectional overrides (errors included), or--json(where they are\uescapes, for every command). A tool beyond the key's scope is refused before anything is sent.skillhook cloud secret <machine> <skill|NAME> [--force]: the machine generates a skill's secret (only a skill's: never its admin token or a runner's key) sealed to a one-time X25519 key pair here; only this terminal opens it, the cloud only forwards the sealed value.skillhook cloud login [--url URL]checks the key against the cloud named (else the one logged in to before, else the machine's; when those two differ it asks which) and keeps it bound to that cloud (SKILLHOOK_CLOUD_API_URLin.env): the key never goes anywhere else, whatevercloud.urlsays later, and login never touches the machine's link. The same key from the environment goes there too; a key kept by 0.6 goes to the machine's cloud until the next login, andcloud statussays so. At a terminal it asks for the key without echo.cloud status/ MCPcloud_statussay whether a key is kept, for which cloud.cloud.*inupdate_configandSKILLHOOK_CLOUD_*inset_secret/generate_secret, and a skill'ssecret_envcan no longer beSKILLHOOK_ADMIN_TOKENorSKILLHOOK_CLOUD_*(the machine also refuses the cloud'ssecret.generate/secret.setfor those, by whatever name they are asked for). An agent that runs commands on the machine acts with the person's rights all the same; the docs say so.MCP and plugin
skillhook mcp --cloud, registered by the plugin asskillhook-cloudnext toskillhook: the cloud's tools (its names, descriptions, schemas and instructions), each call forwarded with the key and validated, authorised and audited by the cloud; plusgenerate_secret(sealed end to end). Without a key it offers onlyskillhook_cloud_setup, which says what is missing and loads the tools once the person logged in (tools/list_changed); the agent never handles the key.skillhook-cloud: start withdescribe_cloud, triage by failure kind, rejected deliveries and failing checks, act safely (confirm destructive actions, observe mode), set up machines, skills, secrets and hosted URLs.Link
skill.put,skill.delete,config.patchandsecret.*the next sync carries a fresh snapshot, so the dashboard (andlist_skills) shows a saved skill and its secret at once instead of up to a minute later. The CLI also falls back to the machine's own answer (get_skill) for a skill saved a moment ago.Release 0.7.0 (also carries #15,
--helpnever runs a command). Docs:docs/cloud.md("The whole organisation with an API key"),docs/mcp.md(the cloud server), README, llms.txt, AGENTS.md (layout, outbound requests), plugin manifests, CHANGELOG.Review fixes (independent review of this PR)
The review found that an agent could use the local
update_configtool to pointcloud.urlat another host and have the bridge send the organisation API key there. Fixed by binding the key to the cloud it was checked against and refusingcloud.*/SKILLHOOK_CLOUD_*changes from the local MCP. Also fixed: argument order around switches,--bodyneeding a special case, control characters in keys and the overview, misleading "unknown subcommand" errors, one bad catalogue entry failing the whole catalogue (now skipped; a newer catalogue shape asks for an update), a catalogue tool being able to replace the local sealedgenerate_secret, and secret names beyond a skill's own.Second review (of the fixes): flags before a tool's name were dropped (now read); skillhook's global parser and a tool's parser read
--json/--help/-vdifferently (now reserved words for both); the docs overstated what keeps an agent from moving the key, and a skill namingSKILLHOOK_CLOUD_API_URL(or the admin token) as itssecret_envcould have it overwritten, also by the cloud throughsecret.set(now refused by the skill schema and by the resolved name on the machine); an environment key ignored the cloud kept with it and login forgot the previous cloud; a malformed catalogue schema could take the MCP bridge down (now skipped, and registration failures are contained);printablemissed C1 controls, bidi overrides and error output.Third review: a tool's switch before its name could swallow the name, and words before
cloudnever reached the tool (now: only skillhook's own options before the subcommand or tool, a usage error otherwise);--no-json/--no-help/--no-dirare skillhook's options too;loginwithout--urlcould check a new key against a stale cloud (now it asks when the kept cloud and the machine's differ);--jsonoutput escapes C1 and bidi characters;cloud secretrefuses the machine's own credentials even through a skill an older machine still has. Also: withcloud.upload_payloadsfalse (or an organisation that keeps no bodies) the machine no longer sends a job'spayload,eventorpromptartifact.Final review:
--is never a parameter's value and works again before the subcommand (skillhook cloud -- status); a blank--urlis refused instead of skipping the two-clouds check, which now comes before the key is asked for; the withheld-body reason names both causes. The docs now say exactly whatcloud.upload_payloadscovers (a delivery's body and a job's payload, event and prompt): an agent's transcript and result are its own output and can quote what it read, andcloud.upload_artifacts: falsekeeps transcripts home.Verification
npm run check: typecheck, 303 tests (newsrc/cloud/tools.test.ts,src/cloud/bridge.test.ts, a CLI end-to-end test against the fake cloud's catalogue, the--helpcoverage lines for every new subcommand, and a link test that fails without the snapshot fix), build, schema, release metadata.answer_job(delivered live), save_skill thencloud secret(decrypted only on the client),enable_hosted_urlwith a real webhook through it (accepted, run, via ingress), stats, audit log, andskillhook mcp --cloudover stdio (42 tools for an admin key; onlyskillhook_cloud_setupwithout a key). After the second review:cloud --machine e2e-mac list_jobs,--answer --jsonrefused with the--answer=--jsonhint (nothing sent),-vprinting the version, ESC and U+202E stripped from a cloud error, andcloud statusfor a bound and an unbound key. After the third: options beforecloudor before a tool's name refused,loginasking which cloud when two differ,--jsoncarrying\u202eas an escape.🤖 Generated with Claude Code