{"_id":"@aywengo/mercury-fleet","_rev":"2-a5549268862754f1cad693e6de89be5e","name":"@aywengo/mercury-fleet","dist-tags":{"bootstrap":"0.0.1-bootstrap","latest":"0.1.0"},"versions":{"0.0.1-bootstrap":{"name":"@aywengo/mercury-fleet","version":"0.0.1-bootstrap","license":"MIT","_id":"@aywengo/mercury-fleet@0.0.1-bootstrap","maintainers":[{"name":"aywengo","email":"aywengo@gmail.com"}],"bin":{"fleet":"dist/cli.js"},"dist":{"shasum":"e95c524092169f04b6371773226cfec620f4818f","tarball":"https://registry.npmjs.org/@aywengo/mercury-fleet/-/mercury-fleet-0.0.1-bootstrap.tgz","fileCount":26,"integrity":"sha512-H9X8bLp4x+3X/pbpRPcwQzEZGiKHZoc74N3KJdbgsBtZIeBX8ibZday2YBvgWNVzva7dXXHiuKKCFWRW2CIFcw==","signatures":[{"sig":"MEUCIBhwhV2eEzEkRn4o15e5/YxKQJuGh2etc26RyACEUQqmAiEAzo7WjbWARu6szURR50bvcMzM9Lz1NbgqoJ46gXDU9rE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":177186},"type":"module","engines":{"node":">=22.18.0"},"gitHead":"c085b12095b03b3ba02616fe65986cf2c3838213","scripts":{"prepare":"node -e \"const fs=require('node:fs');const p='../node_modules/typescript/bin/tsc';if(!fs.existsSync(p)){console.error('fleet: TypeScript is not installed. Run npm ci at the repository root before packing or publishing fleet/.');process.exit(1)}\" && node ../node_modules/typescript/bin/tsc -p ../tsconfig.fleet.json"},"_npmUser":{"name":"aywengo","email":"aywengo@gmail.com"},"_npmVersion":"11.19.0","description":"Federation layer for independent Mercury hosts: host registry, probing, Run dispatch and routing, reconciliation, event aggregation and a Prometheus rollup","directories":{},"_nodeVersion":"26.7.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mercury-fleet_0.0.1-bootstrap_1788988304847_0.1950245492275735","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"_id":"@aywengo/mercury-fleet@0.1.0","bin":{"fleet":"dist/cli.js"},"bugs":{"url":"https://github.com/aywengo/mercury/issues"},"dist":{"shasum":"089211deb789d2599ca749613cf703c55f42a74d","tarball":"https://registry.npmjs.org/@aywengo/mercury-fleet/-/mercury-fleet-0.1.0.tgz","integrity":"sha512-ONaBJGchGw5x+SDMEzzrXyrA+iJxCw1ljZ8N1pxZzK1Diyfz5U1tP/4FX8VyGnExad5dD+ek0B6cnrt9j1EfCw==","fileCount":26,"unpackedSize":177641,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aywengo%2fmercury-fleet@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIA7hmLzXNg0xA9zoBG9clI0sdXfvqibdJyo+pggW+DH4AiEA4ZAThEZpVyTfnj3NGSEz+SC4B+piqT42e4MCrH1loss="}]},"name":"@aywengo/mercury-fleet","type":"module","engines":{"node":">=22.18.0"},"gitHead":"11be84eb8b6938d11caaad90e2ee6ca67916af7c","license":"MIT","scripts":{"prepare":"node -e \"const fs=require('node:fs');const p='../node_modules/typescript/bin/tsc';if(!fs.existsSync(p)){console.error('fleet: TypeScript is not installed. Run npm ci at the repository root before packing or publishing fleet/.');process.exit(1)}\" && node ../node_modules/typescript/bin/tsc -p ../tsconfig.fleet.json"},"version":"0.1.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:532a36aa-e80f-4e78-9ebd-2d71c3f3aca2"},"approver":{"name":"aywengo","email":"aywengo@gmail.com"}},"homepage":"https://github.com/aywengo/mercury#readme","repository":{"url":"git+https://github.com/aywengo/mercury.git","type":"git","directory":"fleet"},"_npmVersion":"11.19.0","description":"Federation layer for independent Mercury hosts: host registry, probing, Run dispatch and routing, reconciliation, event aggregation and a Prometheus rollup","directories":{},"maintainers":[{"name":"aywengo","email":"aywengo@gmail.com"}],"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mercury-fleet_0.1.0_1789018954927_0.23989790527040178"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-09T21:11:44.671Z","modified":"2026-09-10T05:42:35.320Z","0.0.1-bootstrap":"2026-09-09T21:11:44.981Z","0.1.0":"2026-09-10T05:42:35.023Z"},"license":"MIT","description":"Federation layer for independent Mercury hosts: host registry, probing, Run dispatch and routing, reconciliation, event aggregation and a Prometheus rollup","maintainers":[{"name":"aywengo","email":"aywengo@gmail.com"}],"readme":"# Fleet\n\nFleet manages several independent Mercury instances from one place. It is a federation layer: it talks to\neach Mercury over its public HTTP API and never touches a Mercury database or imports Mercury code.\n\nDesign: [`docs/fleet-design.md`](../docs/fleet-design.md). All six build phases are shipped: registry and\nprobe, dispatch, reconciliation, event aggregation, routing, interaction, and the metrics rollup.\n\nTwo surfaces, deliberately different:\n\n- **`fleet` (this CLI)** — the host registry, probing, and credential inspection. Local, interactive, no\n  network service of its own.\n- **`fleet serve` (the service)** — everything that has to outlive the person who started it: submitting\n  Runs, routing them, reconciling their state, aggregating events, and the Prometheus rollup. See\n  [Service](#service) below.\n\nVersion `FLEET_VERSION` lives in [`version.ts`](version.ts) and must match\n[`package.json`](package.json). Changelog: [`CHANGELOG.md`](CHANGELOG.md).\n`fleet --version` prints `mercury-fleet <version>`. `GET /healthz` includes\n`product: \"fleet\"` and that same version.\n\n## Quick start\n\n```bash\n# 1. Child credentials live in a 0600 file, referenced by name. Never a command-line argument: argv is\n#    world-readable through ps, and Fleet holds a credential for every Mercury it can reach.\nmkdir -p ~/.fleet && chmod 700 ~/.fleet\ncat > ~/.fleet/credentials.json <<'JSON'\n{ \"mac-studio\": \"<token from that host's MERCURY_API_TOKENS>\" }\nJSON\nchmod 600 ~/.fleet/credentials.json\n\n# 2. Register hosts and look at them.\nnpm run fleet -- hosts add mac-studio --url https://studio.lan:3000 --credential mac-studio\nnpm run fleet -- hosts list --live\n```\n\n```\nID         STATE      SEEN  WORKERS  RUNS  QUEUE  AGENTS  URL\nmac-studio up         0s    1        2     0      5       https://studio.lan:3000\nbox-lan-2  auth-fail  0s    0        0     3      -       http://box2.lan:3000\n    box-lan-2: HTTP 401 from /api/agents: the host is reachable but this credential was rejected.\nbox-lan-3  down       4m    -        -     -      -       http://box3.lan:3000\n```\n\n## Commands\n\n| Command | Does |\n| --- | --- |\n| `fleet hosts add <id> --url <base> --credential <ref> [--label k=v] [--path <abs>] [--disabled]` | Register a host. `--credential` names a ref; the secret is never an argument. |\n| `fleet hosts list [--json] [--live]` | Show hosts with their last probe. `--live` probes first. |\n| `fleet hosts probe [<id>] [--json]` | Probe now. Exits non-zero if the host is unusable, so it composes in a readiness check. |\n| `fleet hosts enable\\|disable <id>` | Include or exclude a host from sweeps. |\n| `fleet hosts rm <id>` | Forget a host and its cached probe. |\n| `fleet probe --watch` | Sweep every enabled host on `FLEET_PROBE_INTERVAL_MS` until interrupted. |\n| `fleet credentials list` | Credential **names** only. Values are never printed by any command. |\n\n## What each state means\n\nThe states are deliberately not collapsed into up/down. Each one sends the operator somewhere different,\nand a probe that reports \"down\" for a host that is healthy but rejecting our token wastes the operator's\ntime on the wrong machine.\n\n| State | Means | Fix |\n| --- | --- | --- |\n| `up` | Reachable, queue configured, credential accepted. | — |\n| `auth-fail` | Host is fine. Our credential is not. | Check the ref in the credential file. |\n| `no-worker` | API answers, but its queue is not configured, so it executes nothing. | Start that host's worker. |\n| `down` | Nothing listening. | Host, port, or `MERCURY_BIND_HOST`. |\n| `timeout` | Something answered too slowly to trust. | Load, or a half-dead process. |\n| `not-mercury` | A server is there; it is not a Mercury API. | Wrong port or URL. |\n| `http-error` | Unexpected status from a Mercury endpoint. | Check that host's logs. |\n| `never-probed` | Registered, not swept yet. | `fleet hosts probe`. |\n\n## Configuration\n\n| Variable | Default | Meaning |\n| --- | --- | --- |\n| `FLEET_DB` | `fleet.db` | Fleet's own SQLite database, separate from every Mercury's. |\n| `FLEET_CREDENTIALS_FILE` | `~/.fleet/credentials.json` | Must be mode `0600`; Fleet refuses otherwise. |\n| `FLEET_PROBE_INTERVAL_MS` | `15000` | Sweep interval for `probe --watch`. |\n| `FLEET_PROBE_TIMEOUT_MS` | `5000` | Per-request timeout. A hung host must not stall the sweep. |\n| `FLEET_ALLOW_INSECURE_CREDENTIALS` | unset | `1` bypasses the mode check. For filesystems that cannot do `0600`. |\n\n> The default is laptop-shaped on purpose, for development. Fleet runs as a **service**\n> ([`docs/fleet-design.md` §15](../docs/fleet-design.md#15-fleet-as-a-service)), and a hardened unit sets\n> `ProtectHome=true`, which cannot read anything under a home directory. A service deployment sets\n> `FLEET_CREDENTIALS_FILE=/etc/fleet/credentials.json` explicitly instead of relying on this default.\n\n## Service\n\n`fleet serve` runs the HTTP API. It binds `127.0.0.1:3100` by default.\n\n| Variable | Default | Meaning |\n| --- | --- | --- |\n| `FLEET_BIND_HOST` | `127.0.0.1` | Bind address. |\n| `FLEET_PORT` | `3100` | Listen port. |\n| `FLEET_API_TOKENS` | unset | `token:owner[:hosts]`, comma-separated. `hosts` scopes a caller to a subset. |\n| `FLEET_ADMIN_TOKEN` | unset | A caller that may see and change every host. |\n| `FLEET_TLS_CERT` / `FLEET_TLS_KEY` | unset | Both or neither. Required to bind beyond loopback. |\n| `FLEET_SWEEP_INTERVAL_MS` | `10000` | How often bindings are reconciled against every host. |\n| `FLEET_STREAM_POLL_MS` | `1000` | Poll interval behind the aggregated Run stream. |\n| `FLEET_REPO_URLS_FILE` | unset | `localPath` → git URL map used by the router. |\n\n**It refuses to start in an unsafe configuration**, rather than serving and leaving discovery to an audit:\nbinding beyond loopback without TLS, half a TLS pair, or no caller tokens at all each fail at startup with\nthe reason. A Fleet token reaches every Mercury in the fleet, so plaintext on a shared network is not a\ndeployment someone should arrive at by omission.\n\n| Endpoint | Does |\n| --- | --- |\n| `GET /healthz` | Liveness, with `product: \"fleet\"` and the version. Unauthenticated. |\n| `GET /metrics` | Prometheus rollup across hosts, every series relabelled `host=\"<hostId>\"`. |\n| `GET\\|POST /fleet/hosts` | List and register hosts. |\n| `POST /fleet/hosts/:id/enable`, `.../disable`, `DELETE /fleet/hosts/:id` | Include or exclude a host from sweeps. |\n| `POST /fleet/hosts/:id/probe` | Probe one host now. |\n| `POST /fleet/runs` | Submit a Run. Name a `host`, or omit it and let the router choose. |\n| `GET /fleet/runs` | Every Run across the fleet, one merged view. |\n| `GET /fleet/runs/:id` | One Run, with its binding and current child state. |\n| `GET /fleet/runs/:id/events`, `.../stream` | Aggregated history and SSE for a fleet Run. |\n| `POST /fleet/runs/:id/input`, `.../cancel`, `.../retry` | Answer, cancel, or retry through Fleet. |\n| `POST /fleet/probe` | Sweep every enabled host. |\n\nChanging the registry — adding, removing, enabling or disabling a host, and sweeping — requires the admin\ntoken. Reads and Run submission do not, but are scoped: a caller limited to a subset of hosts cannot route\nwork onto a hidden host, read another host's Run, or learn another host's queue depth from `/metrics`.\nRouting failures name every host considered and why each was excluded, because \"no host matched\" without\nreasons is an hour of guessing.\n\n## Two rules this directory is built around\n\n**Nothing here imports from `src/`.** Fleet speaks HTTP so that it can drive a Mercury it did not build.\n`fleet/test/coupling.test.ts` enforces this, and includes tests proving the guard can actually fire.\n\n**`hosts` is truth; everything else is cache.** Probe results live in their own table, so deleting them\ncosts one sweep. That split is what makes a Fleet crash cheap: the registry survives, and nothing Fleet\nholds can orphan a Run on a machine nobody is watching.\n\n## Development\n\n```bash\nnpm run test:fleet    # 187 tests, no network beyond localhost\nnpm run typecheck     # covers fleet/ as well as src/\n```\n","readmeFilename":"README.md","homepage":"https://github.com/aywengo/mercury#readme","repository":{"url":"git+https://github.com/aywengo/mercury.git","type":"git","directory":"fleet"},"bugs":{"url":"https://github.com/aywengo/mercury/issues"}}