Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "skillhook",
"displayName": "skillhook",
"version": "0.10.1",
"version": "0.10.2",
"description": "Turn this machine into a permanent, secure webhook endpoint that runs Agent Skills with Claude Code or Codex, and run your whole Skillhook Cloud organisation from the agent. Three skills teach the agent to install and expose skillhook, to write good webhook skills and to operate the fleet; two MCP servers manage this machine (skills, secrets, jobs, exposure) and, with an organisation API key, every machine as the Skillhook Cloud dashboard does (what needs a person, failures, stats, answers, replays, runs, hosted URLs).",
"author": {
"name": "Meter",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skillhook",
"version": "0.10.1",
"version": "0.10.2",
"description": "Turn this machine into a permanent, secure webhook endpoint that runs Agent Skills with Claude Code or Codex, and run your whole Skillhook Cloud organisation from the agent. Three skills teach the agent to install and expose skillhook, to write good webhook skills and to operate the fleet; two MCP servers manage this machine (skills, secrets, jobs, exposure) and, with an organisation API key, every machine as the Skillhook Cloud dashboard does (what needs a person, failures, stats, answers, replays, runs, hosted URLs).",
"author": {
"name": "Meter",
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skillhook",
"version": "0.10.1",
"version": "0.10.2",
"description": "Turn this machine into a permanent, secure webhook endpoint that runs Agent Skills with Claude Code or Codex, and run your whole Skillhook Cloud organisation from the agent. Three skills teach the agent to install and expose skillhook, to write good webhook skills and to operate the fleet; two MCP servers manage this machine (skills, secrets, jobs, exposure) and, with an organisation API key, every machine as the Skillhook Cloud dashboard does (what needs a person, failures, stats, answers, replays, runs, hosted URLs).",
"author": {
"name": "Meter",
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Runtime state lives outside the repo in `~/.skillhook` (`SKILLHOOK_HOME`):
- **Every CLI command supports `--json`** and returns non-zero on failure. Register new commands in `COMMANDS` (with the usage text) and `HELP` in `src/commands/main.ts`, then in the README table. `--help` / `-h` never reaches a command: `main` prints the usage from `COMMANDS` instead, so a command never checks for it; a new subcommand gets a line in the `--help` test of `src/cli.test.ts`.
- **Third-party facts** (Granola, Sentry, GitHub, Tailscale) are stated in `docs/` and the examples with the exact header names; change them only with a source.
- **Tests are hermetic**: `tempHome()` from `src/test-support/helpers.ts`, fake runners, ephemeral ports. Never touch `~/.skillhook`, the real `claude`/`codex`, `launchctl` or `tailscale` from a test. Never reach the real npm registry either: point `SKILLHOOK_NPM_REGISTRY` at a local `node:http` server or set `SKILLHOOK_NO_UPDATE_CHECK=1`. Nor Skillhook Cloud: `cloud.url` defaults to the live service (`DEFAULT_CLOUD_URL`), so a test gives a `FakeCloud` URL (`SKILLHOOK_CLOUD_URL`, `cloud.url`, `--url`), or answers what is sent to the default itself (a `fetchImpl`, or a stubbed `fetch` that hands it to a `FakeCloud`).
- **Outbound requests are opt-in and enumerated.** By default the CLI phones home at most once an hour, and only for updates (`src/update.ts`: the registry's `latest` dist-tag, cached 1 h, from a detached process so no command waits, never in CI or when `SKILLHOOK_NO_UPDATE_CHECK` / `update_check: false` say so); with `auto_update` (the default) that process also installs a newer version of skillhook itself, from the same registry, with the package manager that installed it, and nothing else. The one other outbound connection is the Skillhook Cloud link (`src/cloud/link.ts`), and only after `skillhook cloud connect` wrote `cloud.enabled` and `SKILLHOOK_CLOUD_TOKEN`: it talks to `cloud.url` over HTTPS only, sends only what `docs/cloud.md` lists (redacted, scrubbed of every `.env` value), obeys `cloud.mode` and the local allow/deny lists (which the cloud cannot change), and stops on `cloud.enabled: false`, `SKILLHOOK_NO_CLOUD=1` or `cloud disconnect`. Never enable it by default, from `init` or from a job. Besides the link, the person-invoked cloud commands send one request each, only when run, only to the machine's cloud URL (`cloud.url` or `SKILLHOOK_CLOUD_URL`, HTTPS only): `cloud connect` / `disconnect` (pairing, revocation), `cloud report` and the MCP tool `cloud_report_issue` (`src/cloud/report.ts`: the person's text plus the diagnostics `docs/cloud.md` lists, scrubbed like the link's uploads, with the machine token), and `cloud login|overview|machines|jobs|job|tools|<tool>|secret` and the tools of `skillhook mcp --cloud` (`src/cloud/api.ts`, `tools.ts`, `bridge.ts`, `remote-secret.ts`: requests with the person's organisation API key `SKILLHOOK_CLOUD_API_KEY`, never with the machine token, only to the cloud the key was checked against at login (`SKILLHOOK_CLOUD_API_URL`), each one a command the person ran or a tool their agent called; `cloud login` without `--key`, and `skillhook_cloud_setup` without a working key, sign in with the browser instead (`src/cloud/login.ts`): `POST /api/auth/device/code` with this computer's name, then `POST /api/auth/device/token` with the device code every few seconds until a person approves it on the cloud, ten minutes at most, and the page opened with the system's opener (argv, no shell; not over SSH or without a display), the key it gets kept like a pasted one; `skillhook mcp --cloud` also reads the catalogue once when it starts; skillhook's own tools do not move them for an agent: the local MCP refuses `cloud.*` settings and `SKILLHOOK_CLOUD_*` secrets, and no skill's `secret_env` may name skillhook's own credentials; an agent that runs commands acts with the person's rights all the same); the last two groups refuse under `SKILLHOOK_NO_CLOUD=1`. Do not add other outbound requests the user did not ask for, and never install anything automatically but skillhook's own update.
- **Outbound requests are opt-in and enumerated.** By default the CLI phones home at most once an hour, and only for updates (`src/update.ts`: the registry's `latest` dist-tag, cached 1 h, from a detached process so no command waits, never in CI or when `SKILLHOOK_NO_UPDATE_CHECK` / `update_check: false` say so); with `auto_update` (the default) that process also installs a newer version of skillhook itself, from the same registry, with the package manager that installed it, and nothing else. The one other outbound connection is the Skillhook Cloud link (`src/cloud/link.ts`), and only after `skillhook cloud connect` wrote `cloud.enabled` and `SKILLHOOK_CLOUD_TOKEN`: it talks to `cloud.url` over HTTPS only, sends only what `docs/cloud.md` lists (redacted, scrubbed of private `.env` values), obeys `cloud.mode` and the local allow/deny lists (which the cloud cannot change), and stops on `cloud.enabled: false`, `SKILLHOOK_NO_CLOUD=1` or `cloud disconnect`. Never enable it by default, from `init` or from a job. Besides the link, the person-invoked cloud commands send one request each, only when run, only to the machine's cloud URL (`cloud.url` or `SKILLHOOK_CLOUD_URL`, HTTPS only): `cloud connect` / `disconnect` (pairing, revocation), `cloud report` and the MCP tool `cloud_report_issue` (`src/cloud/report.ts`: the person's text plus the diagnostics `docs/cloud.md` lists, scrubbed like the link's uploads, with the machine token), and `cloud login|overview|machines|jobs|job|tools|<tool>|secret` and the tools of `skillhook mcp --cloud` (`src/cloud/api.ts`, `tools.ts`, `bridge.ts`, `remote-secret.ts`: requests with the person's organisation API key `SKILLHOOK_CLOUD_API_KEY`, never with the machine token, only to the cloud the key was checked against at login (`SKILLHOOK_CLOUD_API_URL`), each one a command the person ran or a tool their agent called; `cloud login` without `--key`, and `skillhook_cloud_setup` without a working key, sign in with the browser instead (`src/cloud/login.ts`): `POST /api/auth/device/code` with this computer's name, then `POST /api/auth/device/token` with the device code every few seconds until a person approves it on the cloud, ten minutes at most, and the page opened with the system's opener (argv, no shell; not over SSH or without a display), the key it gets kept like a pasted one; `skillhook mcp --cloud` also reads the catalogue once when it starts; skillhook's own tools do not move them for an agent: the local MCP refuses `cloud.*` settings and `SKILLHOOK_CLOUD_*` secrets, and no skill's `secret_env` may name skillhook's own credentials; an agent that runs commands acts with the person's rights all the same); the last two groups refuse under `SKILLHOOK_NO_CLOUD=1`. Do not add other outbound requests the user did not ask for, and never install anything automatically but skillhook's own update.

## Checks

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to skillhook, newest first. The format follows [Keep a Chang

## Unreleased

## 0.10.2 (2026-10-14)

- Preserve public configuration in cloud uploads through the local `cloud.public_env` declaration, fixing GitHub repository slugs redacted inside job links. Unknown values and credential variables stay private; cloud commands cannot change this policy.

- Reuse identical in-flight replay work in HTTP and cloud recovery; alternate eligible fresh/replay queue slots and serialize shared Git worktrees by default, with `serialize_cwd` overrides for coordinated parallel jobs or non-Git directories.

## 0.10.1 (2026-10-13)
Expand Down
2 changes: 1 addition & 1 deletion docs/cloud-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Events carry a per-machine `seq`; the cloud de-duplicates on `(machine_id, seq)`
| `report_id?` | A client-generated idempotency key, 8 to 100 of `A-Z a-z 0-9 _ -`: a retry with the same `report_id` returns the original report instead of filing a second one. |
| `diagnostics?` | `IssueDiagnostics`: `skillhook_version`, `node_version`, `os`, `arch`, `mode`, `link {state, reason?, last_error? (≤ 500)}`, `runners [{runner, ready}]` (≤ 3), `health {ok, summary {ok, warn, fail, skip}, failing? [{id (≤ 200), status, message? (≤ 500)}] (≤ 50)}`; every field optional, unknown fields kept (`.loose()`). |

The request is strict (unknown fields are refused). The machine scrubs every `.env` value from the title, the body and the diagnostics before sending, like everything the link uploads, and never attaches payloads, logs, prompts or job output. The answer is `IssueReportResponse` `{ok: true, issue_id, number, url, acknowledged}`: the issue's id, its number and URL on the dashboard, and whether a confirmation email went out; it is the same for a new report and for a retry of one. `skillhook cloud report` generates a `report_id` (a UUID) per report and sends it with every attempt: it retries network errors, timeouts and `5xx` (three attempts in all), a `429` only after the `retry_after_ms` it asked for (when that is at most 15 seconds), and never another `4xx`. Errors have the agent API's shape (`{ok: false, error, message, retry_after_ms?}`, as `SyncError`): `401 invalid_token`, `403 machine_disabled`, `400 invalid_request`, `413 payload_too_large`, `429 rate_limited` (with `retry_after_ms`), `500 server_error`. The endpoint and its schemas are additive, so `PROTOCOL_VERSION` stays 1; a cloud without the route answers `404`, which the CLI reports as such.
The request is strict (unknown fields are refused). The machine scrubs private `.env` values from the title, the body and the diagnostics before sending, like everything the link uploads, and never attaches payloads, logs, prompts or job output. The answer is `IssueReportResponse` `{ok: true, issue_id, number, url, acknowledged}`: the issue's id, its number and URL on the dashboard, and whether a confirmation email went out; it is the same for a new report and for a retry of one. `skillhook cloud report` generates a `report_id` (a UUID) per report and sends it with every attempt: it retries network errors, timeouts and `5xx` (three attempts in all), a `429` only after the `retry_after_ms` it asked for (when that is at most 15 seconds), and never another `4xx`. Errors have the agent API's shape (`{ok: false, error, message, retry_after_ms?}`, as `SyncError`): `401 invalid_token`, `403 machine_disabled`, `400 invalid_request`, `413 payload_too_large`, `429 rate_limited` (with `retry_after_ms`), `500 server_error`. The endpoint and its schemas are additive, so `PROTOCOL_VERSION` stays 1; a cloud without the route answers `404`, which the CLI reports as such.

## Sealed values

Expand Down
Loading
Loading