@huynle/hub 0.7.1 → 0.7.2
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/CHANGELOG.md +70 -0
- package/Dockerfile +3 -0
- package/README.md +148 -11
- package/package.json +3 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/main.ts +4 -4
- package/packages/claude-code/src/wrapper.ts +3 -2
- package/packages/cli/dist/hub-cli.js +19334 -7849
- package/packages/cli/package.json +11 -0
- package/packages/cli/src/acting.ts +70 -0
- package/packages/cli/src/args.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/archive-stream.ts +276 -0
- package/packages/cli/src/bootstrap/adapters/claude/README.md +63 -0
- package/packages/cli/src/bootstrap/adapters/claude/adapter.ts +250 -0
- package/packages/cli/src/bootstrap/adapters/claude/index.ts +42 -0
- package/packages/cli/src/bootstrap/adapters/claude/node.ts +99 -0
- package/packages/cli/src/bootstrap/adapters/claude/openpgp.ts +221 -0
- package/packages/cli/src/bootstrap/adapters/claude/ports.ts +44 -0
- package/packages/cli/src/bootstrap/adapters/claude/source.ts +160 -0
- package/packages/cli/src/bootstrap/adapters/claude/verify.ts +165 -0
- package/packages/cli/src/bootstrap/adapters/codex/archive.ts +103 -0
- package/packages/cli/src/bootstrap/adapters/codex/index.ts +60 -0
- package/packages/cli/src/bootstrap/adapters/codex/plan.ts +129 -0
- package/packages/cli/src/bootstrap/adapters/codex/sigstore.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/codex/source.ts +102 -0
- package/packages/cli/src/bootstrap/adapters/codex/verify.ts +298 -0
- package/packages/cli/src/bootstrap/adapters/inflate.ts +433 -0
- package/packages/cli/src/bootstrap/adapters/node-services/control.ts +392 -0
- package/packages/cli/src/bootstrap/adapters/node-services/index.ts +23 -0
- package/packages/cli/src/bootstrap/adapters/node-services/launch.ts +223 -0
- package/packages/cli/src/bootstrap/adapters/node-services/plan.ts +222 -0
- package/packages/cli/src/bootstrap/adapters/node-services/render.ts +183 -0
- package/packages/cli/src/bootstrap/adapters/node-services/validate.ts +197 -0
- package/packages/cli/src/bootstrap/adapters/opencode/README.md +204 -0
- package/packages/cli/src/bootstrap/adapters/opencode/apply.ts +205 -0
- package/packages/cli/src/bootstrap/adapters/opencode/archive.ts +91 -0
- package/packages/cli/src/bootstrap/adapters/opencode/config.ts +212 -0
- package/packages/cli/src/bootstrap/adapters/opencode/index.ts +81 -0
- package/packages/cli/src/bootstrap/adapters/opencode/plan.ts +266 -0
- package/packages/cli/src/bootstrap/adapters/opencode/verify.ts +208 -0
- package/packages/cli/src/bootstrap/adapters/readiness.ts +19 -0
- package/packages/cli/src/bootstrap/ansible/index.ts +2 -0
- package/packages/cli/src/bootstrap/ansible/render.ts +279 -0
- package/packages/cli/src/bootstrap/contracts/actions.ts +470 -0
- package/packages/cli/src/bootstrap/contracts/artifact.ts +323 -0
- package/packages/cli/src/bootstrap/contracts/canonical.ts +54 -0
- package/packages/cli/src/bootstrap/contracts/credentials.ts +108 -0
- package/packages/cli/src/bootstrap/contracts/discovery.ts +215 -0
- package/packages/cli/src/bootstrap/contracts/envelope.ts +143 -0
- package/packages/cli/src/bootstrap/contracts/exec.ts +74 -0
- package/packages/cli/src/bootstrap/contracts/harness.ts +202 -0
- package/packages/cli/src/bootstrap/contracts/index.ts +28 -0
- package/packages/cli/src/bootstrap/contracts/journal.ts +198 -0
- package/packages/cli/src/bootstrap/contracts/paths.ts +91 -0
- package/packages/cli/src/bootstrap/contracts/plan.ts +155 -0
- package/packages/cli/src/bootstrap/contracts/platform.ts +62 -0
- package/packages/cli/src/bootstrap/contracts/preview.ts +190 -0
- package/packages/cli/src/bootstrap/contracts/profile.ts +105 -0
- package/packages/cli/src/bootstrap/contracts/secrets.ts +52 -0
- package/packages/cli/src/bootstrap/contracts/validate.ts +212 -0
- package/packages/cli/src/bootstrap/discovery/discover.ts +146 -0
- package/packages/cli/src/bootstrap/discovery/executables.ts +134 -0
- package/packages/cli/src/bootstrap/discovery/host.ts +96 -0
- package/packages/cli/src/bootstrap/discovery/index.ts +6 -0
- package/packages/cli/src/bootstrap/discovery/node-probe.ts +97 -0
- package/packages/cli/src/bootstrap/discovery/probe.ts +41 -0
- package/packages/cli/src/bootstrap/discovery/util.ts +13 -0
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +226 -0
- package/packages/cli/src/bootstrap/enrollment/index.ts +7 -0
- package/packages/cli/src/bootstrap/enrollment/policy.ts +54 -0
- package/packages/cli/src/bootstrap/enrollment/result.ts +63 -0
- package/packages/cli/src/bootstrap/enrollment/status.ts +119 -0
- package/packages/cli/src/bootstrap/enrollment/store.ts +173 -0
- package/packages/cli/src/bootstrap/preview/index.ts +5 -0
- package/packages/cli/src/bootstrap/preview/links.ts +31 -0
- package/packages/cli/src/bootstrap/preview/plan.ts +242 -0
- package/packages/cli/src/bootstrap/preview/preview.ts +104 -0
- package/packages/cli/src/bootstrap/preview/render.ts +30 -0
- package/packages/cli/src/bootstrap/profiles/audit.ts +87 -0
- package/packages/cli/src/bootstrap/profiles/catalog.ts +49 -0
- package/packages/cli/src/bootstrap/profiles/desired.ts +96 -0
- package/packages/cli/src/bootstrap/profiles/drift.ts +286 -0
- package/packages/cli/src/bootstrap/profiles/history.ts +263 -0
- package/packages/cli/src/bootstrap/profiles/index.ts +11 -0
- package/packages/cli/src/bootstrap/profiles/upgrade.ts +276 -0
- package/packages/cli/src/bootstrap/profiles/versions.ts +50 -0
- package/packages/cli/src/bootstrap/release/README.md +115 -0
- package/packages/cli/src/bootstrap/release/assets.ts +39 -0
- package/packages/cli/src/bootstrap/release/cli.ts +332 -0
- package/packages/cli/src/bootstrap/release/harness-pins.json +94 -0
- package/packages/cli/src/bootstrap/release/manifest.ts +118 -0
- package/packages/cli/src/bootstrap/release/pins.ts +192 -0
- package/packages/cli/src/bootstrap/release/text-assets.d.ts +9 -0
- package/packages/cli/src/bootstrap/release/trust-root.json +13 -0
- package/packages/cli/src/bootstrap/release/trust-root.ts +39 -0
- package/packages/cli/src/bootstrap/release/verify.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/builtin.ts +312 -0
- package/packages/cli/src/bootstrap/transaction/engine.ts +994 -0
- package/packages/cli/src/bootstrap/transaction/executor.ts +112 -0
- package/packages/cli/src/bootstrap/transaction/fs.ts +194 -0
- package/packages/cli/src/bootstrap/transaction/index.ts +12 -0
- package/packages/cli/src/bootstrap/transaction/json-merge.ts +403 -0
- package/packages/cli/src/bootstrap/transaction/lock.ts +132 -0
- package/packages/cli/src/bootstrap/transaction/resolve.ts +48 -0
- package/packages/cli/src/bootstrap/transaction/result.ts +60 -0
- package/packages/cli/src/bootstrap/transaction/state.ts +88 -0
- package/packages/cli/src/bootstrap/transaction/store.ts +189 -0
- package/packages/cli/src/bootstrap/transaction/stream.ts +254 -0
- package/packages/cli/src/cli.ts +814 -0
- package/packages/cli/src/credentials.ts +75 -0
- package/packages/cli/src/decisions.ts +106 -0
- package/packages/cli/src/format.ts +185 -0
- package/packages/cli/src/grants.ts +105 -0
- package/packages/cli/src/http.ts +86 -0
- package/packages/cli/src/launches.ts +157 -0
- package/packages/cli/src/login.ts +160 -0
- package/packages/cli/src/main.ts +63 -0
- package/packages/cli/src/mcp.ts +879 -0
- package/packages/cli/src/messaging.ts +108 -0
- package/packages/cli/src/node.ts +279 -0
- package/packages/cli/src/openshell.ts +166 -0
- package/packages/cli/src/setup/commands.ts +809 -0
- package/packages/cli/src/setup/contributors.ts +171 -0
- package/packages/cli/src/setup/dispatch.ts +74 -0
- package/packages/cli/src/setup/launch-driver.ts +109 -0
- package/packages/cli/src/setup/runtime.ts +456 -0
- package/packages/cli/src/setup/spoke-probe.ts +120 -0
- package/packages/cli/src/setup/state.ts +244 -0
- package/packages/cli/src/setup/user-bin.ts +39 -0
- package/packages/cli/src/spoke-services.ts +109 -0
- package/packages/cli/src/watch.ts +28 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/main.ts +3 -2
- package/packages/codex/src/spoke.ts +4 -1
- package/packages/codex/src/wrapper.ts +7 -3
- package/packages/node/package.json +1 -1
- package/packages/node/src/harnesses.ts +39 -4
- package/packages/node/src/run.ts +1 -1
- package/packages/preset/package.json +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/client.ts +55 -4
- package/packages/sdk/src/errors.ts +1 -0
- package/packages/sdk/src/sse.ts +4 -1
- package/packages/sdk/src/types.ts +59 -0
- package/packages/spoke-sdk/package.json +1 -1
- package/packages/spoke-sdk/src/enroll.ts +26 -4
- package/src/central/agent/app-tokens.ts +27 -17
- package/src/central/agent/archive-inactive.ts +66 -0
- package/src/central/agent/directory-routes.ts +155 -12
- package/src/central/agent/directory.ts +1 -1
- package/src/central/agent/errors.ts +2 -0
- package/src/central/agent/event-cursor.ts +49 -0
- package/src/central/agent/event-writers.ts +181 -0
- package/src/central/agent/events-routes.ts +148 -0
- package/src/central/agent/events.ts +277 -0
- package/src/central/agent/names.ts +1 -1
- package/src/central/agent/read-routes.ts +32 -36
- package/src/central/agent/routes.ts +14 -11
- package/src/central/agent/watch.ts +65 -174
- package/src/central/agent/webhook-routes.ts +10 -2
- package/src/central/agent/webhooks.ts +49 -5
- package/src/central/auth.ts +127 -1
- package/src/central/backend/index.ts +3 -0
- package/src/central/backend/types.ts +2 -0
- package/src/central/http.ts +115 -32
- package/src/central/mcp-http.ts +116 -0
- package/src/central/store.ts +53 -0
- package/src/config.ts +70 -7
- package/src/index.ts +90 -91
- package/src/spoke/launchers.ts +109 -3
- package/web/dist/apple-touch-icon.png +0 -0
- package/web/dist/assets/index-DWnMhXk9.js +20 -0
- package/web/dist/assets/index-DmUXEnw9.css +1 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/icon-192.png +0 -0
- package/web/dist/icon-512.png +0 -0
- package/web/dist/icon-maskable-512.png +0 -0
- package/web/dist/icon.svg +5 -5
- package/web/dist/index.html +10 -4
- package/web/dist/manifest.webmanifest +2 -2
- package/web/dist/assets/index-D9zMbxSg.css +0 -1
- package/web/dist/assets/index-db1z4duA.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,76 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.7.2
|
|
6
|
+
|
|
7
|
+
First release with signed standalone `hubctl` binaries (`hubctl-<target>`, `hubctl-manifest.json` signed by `hub-release-1`, `HUBCTL_SHA256SUMS`) and pinned OpenCode 2.2.0 for `hubctl setup`.
|
|
8
|
+
|
|
9
|
+
- **`hub login` with a device code**: without `--token`, `hub login [--url URL] [--scope S]... [--name N]` prints an
|
|
10
|
+
approval link and code; you approve it in the console as yourself (`/approve`: "CLI login for <name>", every
|
|
11
|
+
scope as a checkbox, the requested ones ticked) and get an app token with the scopes you chose (default
|
|
12
|
+
`sessions:read`, `messages:send`, `messages:read`; `--scope sessions:launch` or ticking it for launching, which
|
|
13
|
+
also needs a grant with verb `launch`, as the output says). Stored like `--token` (owner-only, now written
|
|
14
|
+
atomically); the URL defaults to this machine's enrollment. `--token` and a piped token still work. New hub routes:
|
|
15
|
+
`POST /cli/login`, `/cli/login/poll`, `GET /api/cli-logins/:code`, `POST …/approve|deny` (devices only; single-use,
|
|
16
|
+
10-minute codes; audited `cli-login.approve|deny`, never the token).
|
|
17
|
+
- On an enrolled machine without a user login, `hub launch`, `whoami` and the other user commands now say that the
|
|
18
|
+
machine enrollment (`mch_…`) is not a user login and to run `hub login`; a forbidden `hub launch` with an app token
|
|
19
|
+
says it needs a grant with verb `launch`.
|
|
20
|
+
- **`hub` on PATH for plugin-only installs**: the OpenCode plugin links `~/.local/bin/hub` to its launcher (only when
|
|
21
|
+
nothing of yours is there and no other `hub` is on PATH; the setup's `hub` → `hubctl` link is kept), with one hint
|
|
22
|
+
when `~/.local/bin` is not on PATH.
|
|
23
|
+
|
|
24
|
+
- **hubctl bootstrap**: `hubctl machine enroll|status` and `hubctl setup discover|preview|apply|verify|drift|
|
|
25
|
+
rollback|recover|ansible` (also as `hub …`; the program name grants nothing). A standalone hubctl enrolls the
|
|
26
|
+
machine with the device code, previews a deterministic typed plan (writes nothing; `--out` keeps it for apply),
|
|
27
|
+
and applies exactly that plan after rediscovering and re-planning: pinned OpenCode, Claude Code and Codex installs
|
|
28
|
+
from the signed release manifest (`--manifest`), lossless config edits, hub's user services (loaded only with
|
|
29
|
+
`--confirm-activation`), an optional bounded verification launch (`--verify-launch`), all journaled and undone
|
|
30
|
+
with `setup rollback`. Readiness comes from real probes (`setup verify`, exit 15 when not ready). `--json` prints
|
|
31
|
+
one `hub.bootstrap.result` envelope; exits 10–15 are new (docs/bootstrap-contracts.md §3.11).
|
|
32
|
+
- **hubctl bootstrap, install is not readiness (contract v1.2)**: `setup apply` on a fresh host no longer rolls
|
|
33
|
+
back because you have not logged in to a provider or started the harness yet (which needs the installed harness).
|
|
34
|
+
Readiness probes are classified: a broken install (missing or wrong binary, version mismatch, container shim, a
|
|
35
|
+
binary that does not run) still rolls back; a harness that only waits on you is committed as "installed, not
|
|
36
|
+
ready", with each probe's status and the next steps (`--json`: `data.harnesses`). `--require-ready` keeps the
|
|
37
|
+
strict behaviour (exit 15, rolled back); `setup verify` stays the strict gate; `--verify-launch` keeps its
|
|
38
|
+
harness strict. A rollback now also removes the root directories the apply created (e.g. `~/.local/bin`), and a
|
|
39
|
+
directory hub created that something else wrote into is kept as a note, not a conflict. The Ansible role passes
|
|
40
|
+
`--require-ready` only with `hub_bootstrap_apply_require_ready` and gates readiness at its verify step.
|
|
41
|
+
- **hubctl bootstrap, HBS-12 acceptance fixes**: `machine enroll` turns sharing on (`mode: "always"`) when
|
|
42
|
+
`config.json` has no mode, so the OpenCode spoke of a hubctl-enrolled machine connects (an explicit `"off"` is kept
|
|
43
|
+
and reported); a rerun on a converged host plans only verification (the installed hubctl is compared by digest, not
|
|
44
|
+
path); a hub-installed OpenCode, Claude Code or Codex is linked into `~/.local/bin` when no other one is on PATH
|
|
45
|
+
(with a hint when `~/.local/bin` is not on PATH); the OpenCode probes retry once when a cold OpenCode service has
|
|
46
|
+
not finished activating its plugins (HTTP 503); and the plugin no longer loads `@opencode/plugin`'s runtime, so it
|
|
47
|
+
activates faster on a cold start. Units for a standalone hubctl no longer set `BUN_BE_BUN=1` (it made the compiled
|
|
48
|
+
binary act as the bun CLI and crash-loop), and `--confirm-activation` now checks that each activated service stays
|
|
49
|
+
running: a failed or crash-looping service fails the apply with `service_failed` (exit 15), is unloaded and rolled
|
|
50
|
+
back, and the message points at `journalctl --user -u <unit>` or the agent's log.
|
|
51
|
+
- **Hub API**: `GET /spoke/presence` (machine token only, read-only) lists which harness spokes of the caller's own
|
|
52
|
+
machine are connected (harness, evidence, features), for `hubctl setup verify`.
|
|
53
|
+
- `hub node install`, `hub claude-code install` and `hub codex-spoke install` render their units from templates
|
|
54
|
+
embedded in the program (so a standalone hubctl can install them); a standalone hubctl's units start the binary
|
|
55
|
+
itself. The hints for an unenrolled machine now say `hubctl machine enroll`.
|
|
56
|
+
- The `hub`/`hubctl` launchers run `~/.local/bin/hubctl` when it is installed. `hub claude`, the Claude Code daemon
|
|
57
|
+
and the node run the Claude Code `hubctl setup` installed (HUB_CLAUDE_BIN, else hub's `claude-code.json`
|
|
58
|
+
`binary`, else PATH); `hub codex`, the Codex spoke and its unit the hub-installed Codex (HUB_CODEX_BIN, else
|
|
59
|
+
`~/.local/share/hub/harnesses/codex/current/bin/codex`, else PATH). The node's host runtime now offers `codex`
|
|
60
|
+
when the Codex CLI is found.
|
|
61
|
+
|
|
62
|
+
- **Archive through MCP**: `archive` and `unarchive` (and the narrow agent API
|
|
63
|
+
`PUT /api/agent/sessions/:addr/archive`) change the Hub's archived state without deleting a harness session.
|
|
64
|
+
App tokens use the new explicit `sessions:manage` scope, selectable in Settings → Agents. Existing tokens do
|
|
65
|
+
not gain the scope; operations are idempotent and preserve unrelated view preferences.
|
|
66
|
+
|
|
67
|
+
- Add App token management to Settings → Agents: create scoped tokens, copy the one-time bearer secret and grant-holder ID, list existing tokens and revoke them.
|
|
68
|
+
|
|
69
|
+
- **MCP over HTTP**: the hub serves `hub mcp`'s tools at `POST /mcp` (MCP Streamable HTTP, stateless JSON
|
|
70
|
+
responses) for MCP clients and gateways. Auth is `Authorization: Bearer <token>` (an app token with the tools'
|
|
71
|
+
scopes; device, session and act tokens too); a missing or bad token answers `401` with `WWW-Authenticate: Bearer`.
|
|
72
|
+
The tools run as that token through the agent API in process, so scopes, grants, tenancy and the audit apply.
|
|
73
|
+
Packages now ship `packages/cli/src` (the image copies it too).
|
|
74
|
+
|
|
5
75
|
## 0.7.1
|
|
6
76
|
|
|
7
77
|
- **Release**: the npm package is published again. 0.7.0's publish step had no Bun for the CLI bundle, so 0.7.0 is
|
package/Dockerfile
CHANGED
|
@@ -63,6 +63,9 @@ COPY packages/protocol/src ./packages/protocol/src
|
|
|
63
63
|
# The SDK's pure envelope and address helpers (src/central/agent/envelope.ts and names.ts re-export them).
|
|
64
64
|
COPY packages/sdk/package.json ./packages/sdk/
|
|
65
65
|
COPY packages/sdk/src ./packages/sdk/src
|
|
66
|
+
# The CLI's MCP tool table (POST /mcp serves it; src/central/mcp-http.ts imports packages/cli/src/mcp.ts).
|
|
67
|
+
COPY packages/cli/package.json ./packages/cli/
|
|
68
|
+
COPY packages/cli/src ./packages/cli/src
|
|
66
69
|
COPY --from=web /build/web/dist ./web/dist
|
|
67
70
|
|
|
68
71
|
ENV HUB_HOST=0.0.0.0 \
|
package/README.md
CHANGED
|
@@ -133,6 +133,75 @@ Optional: put the voice agent on a fast model in `~/.config/hub/config.json`:
|
|
|
133
133
|
`"voiceModel": "anthropic/claude-haiku-4-5"` (any `provider/model` you have). Subagents keep the
|
|
134
134
|
session's model.
|
|
135
135
|
|
|
136
|
+
## Quick start (hubctl, before any harness)
|
|
137
|
+
|
|
138
|
+
`hubctl` is a standalone, signed program (Bun and the CLI compiled into one file; no bun, node or harness needed)
|
|
139
|
+
that bootstraps a machine into Hub: it enrolls the machine, then previews and applies a typed plan that installs
|
|
140
|
+
pinned harnesses, edits their configs losslessly and writes hub's user services. `hub` and `hubctl` are the same
|
|
141
|
+
program; the name grants nothing (only the machine credential and your consent flags do). Nothing is run through a
|
|
142
|
+
shell, and the hub never gets a way to run commands here.
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
# 1. Download hubctl for your platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64) and the signed manifest
|
|
146
|
+
# from the release, then install the binary yourself (the first apply installs the release's copy as
|
|
147
|
+
# ~/.local/bin/hubctl, with `hub` linked to it):
|
|
148
|
+
curl -fsSLo hubctl https://github.com/huynle/hub/releases/download/v<version>/hubctl-darwin-arm64 && chmod +x hubctl
|
|
149
|
+
curl -fsSLO https://github.com/huynle/hub/releases/download/v<version>/hubctl-manifest.json
|
|
150
|
+
|
|
151
|
+
# 2. Enroll this machine (device code: approve it on the hub; Ctrl-C cancels). One identity for every spoke here.
|
|
152
|
+
./hubctl machine enroll --server https://hub.example.com
|
|
153
|
+
./hubctl machine status # verified | revoked (exit 9) | offline (exit 7) | insecure (plain-http hub)
|
|
154
|
+
|
|
155
|
+
# 3. Preview: discover the host and print the exact plan and any manual edits. It writes nothing.
|
|
156
|
+
./hubctl setup preview --profile session-only --manifest hubctl-manifest.json --out preview.json
|
|
157
|
+
|
|
158
|
+
# 4. Apply exactly that plan (rediscovered and re-planned; refused when the host changed since, exit 12). A harness
|
|
159
|
+
# that is installed and runs but still waits on you (provider login, spoke not started) is committed as
|
|
160
|
+
# "installed, not ready" with the next steps; --require-ready fails (exit 15) and rolls back instead.
|
|
161
|
+
./hubctl setup apply --preview preview.json --manifest hubctl-manifest.json
|
|
162
|
+
|
|
163
|
+
# 5. Do the next steps it printed (e.g. `opencode auth login`, start OpenCode), then check readiness with real
|
|
164
|
+
# probes (version, provider login, spoke connected; exit 15 when not ready), and undo whenever you like.
|
|
165
|
+
hubctl setup verify
|
|
166
|
+
hubctl setup rollback # or --to <txID>; files you changed since are kept and reported
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Profiles: `session-only` (OpenCode), `launch-node` (+ `hub node` as a user service; give
|
|
170
|
+
`--workspace-root <dir>`), `full-harness` (OpenCode, Claude Code, Codex and their spokes), or a profile file
|
|
171
|
+
(`--profile <file>` or `-` for stdin).
|
|
172
|
+
- Consent is explicit and never a default: `--confirm-activation` (load the user services; give it at preview and
|
|
173
|
+
again at apply), `--escalate` (system locations), `--allow-downgrade`.
|
|
174
|
+
- `--enrollment separate` leaves enrollment to `hubctl machine enroll` (the default is inline when you pass
|
|
175
|
+
`--server` to preview). A config behind a symlink (dotfiles) is never written through: the preview prints the
|
|
176
|
+
exact edit and the file it belongs in.
|
|
177
|
+
- `hubctl setup drift` reports hub's managed fields (in sync, missing, outdated, changed by you);
|
|
178
|
+
`hubctl setup recover [--resume]` finishes or rolls back an interrupted apply.
|
|
179
|
+
- With `--json`, every command prints one `hub.bootstrap.result` envelope; exit codes are listed in
|
|
180
|
+
[docs/bootstrap-contracts.md](docs/bootstrap-contracts.md#311-output-envelope-and-exit-codes) (10 conflict,
|
|
181
|
+
11 artifact rejected, 12 preview invalid, 13 locked, 14 recovery required, 15 not ready).
|
|
182
|
+
- Many machines: the optional Ansible role in [contrib/ansible](contrib/ansible/roles/hub_bootstrap/README.md)
|
|
183
|
+
runs the same preview and apply (`hubctl setup ansible` renders its vars; enrollment stays a separate step).
|
|
184
|
+
|
|
185
|
+
### Start a launch from a terminal
|
|
186
|
+
|
|
187
|
+
On a machine set up with `hubctl setup` (or the OpenCode plugin), `hub` is on your PATH: `~/.local/bin/hub` links to
|
|
188
|
+
hubctl, or (plugin only) to the plugin's launcher; if `~/.local/bin` is not on your PATH, both say so once. A machine
|
|
189
|
+
enrollment identifies the **machine**, never you, so launching from your own shell needs a user login first:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
hub login --scope sessions:launch # prints a link and a code; approve it in the console as yourself
|
|
193
|
+
# (the page lists the scopes; tick "sessions:launch" if you did not ask)
|
|
194
|
+
hub launch --repo ~/projects/app --branch feat/x --prompt "Fix the flaky test"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`hub login` without `--token` is a device code (Ctrl-C cancels); its URL defaults to `HUB_URL`, your last login, else
|
|
198
|
+
this machine's enrollment. You get an app token with the scopes you approved (default `sessions:read`,
|
|
199
|
+
`messages:send`, `messages:read`), stored owner-only in `~/.config/hub/cli.json`, and revocable in Settings → Agents.
|
|
200
|
+
An app token launches only under a **grant** with verb `launch` covering the repo: create it in Settings → Agents, or
|
|
201
|
+
ask for it from the terminal (`hub grant request --project ~/projects/app --allow launch --why "…"`) and approve it.
|
|
202
|
+
Without a user login, `hub launch` on an enrolled machine says so (`this machine is enrolled (mch_…), but launching
|
|
203
|
+
needs a user login: run hub login`). `hub login --token <app token>` (or a token piped on stdin) still works.
|
|
204
|
+
|
|
136
205
|
## Running the hub
|
|
137
206
|
|
|
138
207
|
The hub serves the web app, pairs devices, enrolls machines, and does push and (for Voice) speech. Pick one:
|
|
@@ -198,7 +267,8 @@ foreground with `hub.json`; `scripts/io.github.huynle.hub.plist` is a launchd te
|
|
|
198
267
|
paired device or a signed-in user; spokes need machine tokens; nothing trusts the client IP. For a public
|
|
199
268
|
hostname, consider [sign-in with Google](#multi-user-sign-in-with-google) or an authenticating proxy in
|
|
200
269
|
front of the web app, and leave `/spoke/*`, `/auth/*`, `/healthz`, `/.well-known/hub`, the manifest and
|
|
201
|
-
icons reachable for spokes, sign-in, app discovery and the PWA
|
|
270
|
+
icons reachable for spokes, sign-in, app discovery and the PWA (and `/mcp`, bearer-authenticated, if MCP clients
|
|
271
|
+
or gateways use [MCP over HTTP](#over-http-mcp)).
|
|
202
272
|
|
|
203
273
|
## Configuration
|
|
204
274
|
|
|
@@ -244,6 +314,32 @@ conversation mode and utterances (403 `not_exposed` for sessions not shared with
|
|
|
244
314
|
|
|
245
315
|
## Agent identity (agent API)
|
|
246
316
|
|
|
317
|
+
**Organization.** `PUT /api/agent/sessions/:addr/archive {archived: true|false}` archives or unarchives one
|
|
318
|
+
session in Hub views without deleting it in its harness. Users always may; app tokens need `sessions:manage`.
|
|
319
|
+
The operation is idempotent, returns `{sessionID, archived, changed}`, and preserves unrelated view preferences.
|
|
320
|
+
MCP exposes the same operations as `archive` and `unarchive`. Naming (`POST /api/agent/name`) also takes an app
|
|
321
|
+
token with `sessions:manage`; role claims and releases take one with `sessions:manage` **and** a grant allowing
|
|
322
|
+
`roles` whose projects cover the session's directory (the session must be online), under the kill switch and hourly
|
|
323
|
+
cap, with every outcome in the agent audit (verbs `roles` and `roles.release`). Reserved roles stay yours to assign.
|
|
324
|
+
Apps and users name the session (`session`); foreign and unknown sessions are both `404 not_found`. MCP: `name`,
|
|
325
|
+
`role_claim` and `role_release`. Each change is one `session.named`, `role.claimed` or `role.released` event.
|
|
326
|
+
|
|
327
|
+
**Events.** `GET /api/agent/events?cursor=&wait=&limit=&type=&session=` reads a bounded per-user journal (24 h or
|
|
328
|
+
10,000 events) of compact events: `session.added/removed/changed`, `blocker.opened/closed`, `machine.changed`,
|
|
329
|
+
`message.changed`, `launch.changed`, `session.archived`, `session.named`, `role.claimed`, `role.released`. Each is `{id, at, type, subject, data}` with IDs and states
|
|
330
|
+
only: never prompts, message bodies, titles, blocker summaries, launch reasons or labels. Without a cursor the answer
|
|
331
|
+
is empty with a cursor at the latest event; pass the returned `cursor` back to get what came after. `wait` (0–60 s)
|
|
332
|
+
holds the request while nothing matches; `limit` is 1–200 (default 50); `type` (exact, or `launch.*`) and `session`
|
|
333
|
+
may repeat and still advance the cursor over what they skip. At most 1,000 events are inspected per request; `more`
|
|
334
|
+
means some remain. A cursor the journal no longer covers (expired, trimmed, hub restarted) answers
|
|
335
|
+
`{events: [], cursor, more: false, reset: true, refresh: ["status", "sessions", "inbox", "decisions", "launches"]}`:
|
|
336
|
+
re-read those, then continue with the new cursor. `GET /api/agent/watch` streams the same events live over SSE
|
|
337
|
+
(`{type: "ready"}` first, `{type: "reset", refresh}` when its cursor is no longer covered, `event: ping` while
|
|
338
|
+
idle), with the same visibility and filters (`blocked=1` is `type=blocker.*`). A malformed, forged or another user's cursor is
|
|
339
|
+
`400 invalid_cursor`. Unknown event types are valid; ignore them. App tokens need `events:subscribe`, and see each
|
|
340
|
+
family only with its scope: session and machine events with `sessions:read`, archive, name and role events with `sessions:manage`,
|
|
341
|
+
messages they are a party to with `messages:read`, launches they started with `sessions:launch`. MCP: `events`.
|
|
342
|
+
|
|
247
343
|
Agents, scripts and apps talk to the hub through `/api/agent/*`, a small API with its own credentials, separate
|
|
248
344
|
from the console's routes. Every request resolves to exactly one **actor**:
|
|
249
345
|
|
|
@@ -251,7 +347,7 @@ from the console's routes. Every request resolves to exactly one **actor**:
|
|
|
251
347
|
|---|---|---|
|
|
252
348
|
| user | the device cookie (browsers) or the same secret as `Authorization: Bearer <device token>` (apps, CLIs; [linked with a code](#multi-user-sign-in-with-google)) | you, on one of your devices |
|
|
253
349
|
| session | a **session token** `hst1.…` the hub mints for each session a spoke reports | that session's shells (`HUB_SESSION_TOKEN`) |
|
|
254
|
-
| app | an **app token** `hat_…` you create (scopes `sessions:read`, `messages:send`, `messages:read`, `sessions:act`) | a script or tool outside a session (`hub login`) |
|
|
350
|
+
| app | an **app token** `hat_…` you create (scopes `sessions:read`, `sessions:manage`, `messages:send`, `messages:read`, `sessions:act`, `sessions:launch`, `events:subscribe`) | a script or tool outside a session (`hub login`) |
|
|
255
351
|
|
|
256
352
|
**Session tokens in shells.** The hub mints a 24 h token for every session (subagents included) its spokes report
|
|
257
353
|
for you and pushes it to the spoke, which keeps it in memory only. Every shell an agent starts through OpenCode's
|
|
@@ -296,10 +392,11 @@ cookie and a hub bearer token (400).
|
|
|
296
392
|
| `GET /api/agent/sessions?project=&state=&machine=&label=k=v` | ✓ | ✓ | ✓ | the same sessions as a flat list |
|
|
297
393
|
| `GET /api/agent/sessions/:addr` | ✓ | ✓ | ✓ | one session with full blockers and the spoke's summary (`unsupported` when its harness lacks it) |
|
|
298
394
|
| `GET /api/agent/sessions/:addr/log?n=20` | ✓ | ✓ | ✓ | newest timeline rows (1–200); `422 unsupported {feature, harness}` when the spoke has no timeline, `503 offline` |
|
|
299
|
-
| `GET /api/agent/watch?blocked=1&session=<addr
|
|
395
|
+
| `GET /api/agent/watch?blocked=1&session=<addr>&cursor=` | ✓ | ✓ | ✓ (+ each family's scope) | SSE of the same compact events as `/api/agent/events` (see **Events**), after `ready`; each frame's `id:` is a cursor to resume with (`?cursor=` or `Last-Event-ID`); `reset` when it is no longer covered |
|
|
396
|
+
| `GET /api/agent/events?cursor=&wait=&limit=&type=&session=` | ✓ | ✓ | `events:subscribe` (+ each family's scope) | compact events after a resumable cursor (see **Events** above) |
|
|
300
397
|
| `GET /api/agent/who?project=&role=` | ✓ | ✓ | ✓ | named, role-holding and online sessions |
|
|
301
|
-
| `POST /api/agent/name {name, session?}` | ✓ | own session |
|
|
302
|
-
| `POST /api/agent/roles/claim {role, exclusive?, project?, session?}` · `…/roles/release` | ✓ | own session |
|
|
398
|
+
| `POST /api/agent/name {name, session?}` | ✓ | own session | `sessions:manage` | names a session (`409 name_taken`) |
|
|
399
|
+
| `POST /api/agent/roles/claim {role, exclusive?, project?, session?}` · `…/roles/release` | ✓ | own session | `sessions:manage` + `roles` grant | claims (with a new `generation`) or releases a role; never a reserved role for an app |
|
|
303
400
|
| `POST /api/agent/msg {to, text, wake?, urgent?, all?}` | ✓ | ✓ | `messages:send` | tells a session (`all`: every holder of a role); never yourself |
|
|
304
401
|
| `POST /api/agent/ask {to, text, expiresIn?, wait?}` | ✓ | ✓ | `messages:send` | an ask, open until answered, declined or expired; `wait` (≤ 60 s) returns the `answer` |
|
|
305
402
|
| `POST /api/agent/ask {to: "@me", text, options?, recommendation?, urgency?, tier?, deadline?, onTimeout?, about?, wait?}` | – | ✓ | `messages:send` | opens a **Decision** for the user (below): `{message, decision, answer?}` |
|
|
@@ -396,7 +493,8 @@ sessions or apps; see [Decisions and `@me`](#agent-identity-agent-api) for the r
|
|
|
396
493
|
- The model itself: the plugin's `hub` tool (verbs `msg`, `ask`, `reply`, `decline`, `inbox`, `who`, `status`,
|
|
397
494
|
`whoami`, and the [act verbs](#agent-actions-and-grants)), as the calling session ([Spoke](#spoke-opencode-plugin)).
|
|
398
495
|
- You, in the OpenCode TUI: `/msg @name text` (as the session on screen) and `/who`.
|
|
399
|
-
- Other agents and tools: [`hub mcp`](#hub-mcp-mcp-server), as the app token you logged in with
|
|
496
|
+
- Other agents and tools: [`hub mcp`](#hub-mcp-mcp-server), as the app token you logged in with, or the hub's
|
|
497
|
+
[`POST /mcp`](#over-http-mcp) as the bearer token sent.
|
|
400
498
|
- Scripts: `hub login` with an app token that has the `messages:send` (and `messages:read`) scopes.
|
|
401
499
|
|
|
402
500
|
**Limits.** Per 10 minutes: 20 messages per sender→recipient pair and 300 per user (`rate_limited`, exit 6, with
|
|
@@ -448,7 +546,7 @@ where the call is visible and audited.
|
|
|
448
546
|
reserved roles), or `app:<token id>` (one of your app tokens).
|
|
449
547
|
- **scope.projects**: 1–20 absolute project directories; a session is in scope when its directory is one of them or
|
|
450
548
|
inside one (on a path boundary, after resolving `.`/`..`; worktrees inside the project count).
|
|
451
|
-
- **allow**: the verbs (`approve`, `deny`, `answer`, `prompt`, `stop`; `roles`
|
|
549
|
+
- **allow**: the verbs (`approve`, `deny`, `answer`, `prompt`, `stop`; `roles` lets an app token with `sessions:manage` claim and release roles of sessions in scope).
|
|
452
550
|
`deny`, `answer`, `prompt` and `stop` need only the verb; `approve` also needs the request to pass the patterns.
|
|
453
551
|
- **permissions**: patterns `action[:glob]` in OpenCode's wildcard syntax (`*` any run, `?` one character, a trailing
|
|
454
552
|
` *` optional; `bash` is OpenCode's `shell`; the action alone means every resource). A request is allowed only
|
|
@@ -579,6 +677,12 @@ for agents and scripts. The OpenCode plugin writes both launchers into the hub b
|
|
|
579
677
|
session `hub` just works. A launcher runs the CLI bundle shipped in this package (`packages/cli/dist/hub-cli.js`)
|
|
580
678
|
with the OpenCode binary itself (`BUN_BE_BUN=1 exec <opencode> hub-cli.js "$@"`), so neither bun nor node has to
|
|
581
679
|
be installed; without Bun it falls back to `node`. The launchers are rewritten only when their content changes.
|
|
680
|
+
When a standalone hubctl is installed as `~/.local/bin/hubctl` (what `hubctl setup` installs), the launchers run it
|
|
681
|
+
instead, so shells, services and MCP clients all use that one signed program. For your own terminals the plugin also
|
|
682
|
+
links `~/.local/bin/hub` to its `hub` launcher, with the setup's rule for `~/.local/bin`: only when nothing is there or
|
|
683
|
+
it is hub's own link, never when another `hub` is on PATH (then it prints where hub's is), and only from the default
|
|
684
|
+
state dir (not with `HUB_STATE_DIR`). `hubctl setup`'s `hub` → `hubctl` link is kept; `~/.local/bin/hubctl` is left to
|
|
685
|
+
`hubctl setup`. When `~/.local/bin` is not on PATH it prints one hint.
|
|
582
686
|
|
|
583
687
|
**Name clash:** GitHub's old `hub` CLI (github/hub) uses the same name. In hub sessions ours comes first on PATH;
|
|
584
688
|
elsewhere use `hubctl`, or call the bundle directly (`bun …/packages/cli/dist/hub-cli.js`).
|
|
@@ -628,7 +732,8 @@ hub codex-spoke install|uninstall [--dry-run] · hub codex-spoke status the Co
|
|
|
628
732
|
hub preset list · hub preset check <name|file>
|
|
629
733
|
hub preset run <name|file> [--socket <name> | --current] [--cwd <dir>] [--resume <id>] [--no-hub] [-- args…]
|
|
630
734
|
hub mcp an MCP server on stdio (see below)
|
|
631
|
-
hub login [--url URL] [--
|
|
735
|
+
hub login [--url URL] [--scope S]... [--name N] device code: approve it in the console, choose the scopes
|
|
736
|
+
hub login [--url URL] --token APP_TOKEN|- · hub logout
|
|
632
737
|
hub help · hub --version
|
|
633
738
|
```
|
|
634
739
|
|
|
@@ -742,7 +847,12 @@ OpenCode (Claude Desktop, Cursor, other agents). It speaks JSON-RPC 2.0, one mes
|
|
|
742
847
|
transport; protocol revisions `2025-06-18`, `2025-03-26` and `2024-11-05`), and answers `initialize`, `ping`,
|
|
743
848
|
`tools/list` and `tools/call`; notifications (`notifications/initialized`, `notifications/cancelled`, which aborts
|
|
744
849
|
the call) get no answer, other methods answer `-32601`. Its tools are the CLI's verbs: `whoami`, `status`,
|
|
745
|
-
`sessions`, `show`, `log`, `who`, `
|
|
850
|
+
`sessions`, `show`, `log`, `who`, `archive`, `unarchive`, `pin`, `unpin` (`addr`), `archive_inactive`
|
|
851
|
+
(`preview`, `inactiveFor`, `limit`, then `previewToken`, `confirm`), `msg`, `ask`, `reply`, `decline`, `inbox`, `message_show` (`id`, `wait`),
|
|
852
|
+
`mute` and `unmute` (`addr`; agent-message mutes, not console notifications), `decisions` (`state`) and
|
|
853
|
+
`decision_show` (`id`) (reading the Decisions you asked; no tool answers one, only the user does), and
|
|
854
|
+
`webhook_create` (`url`, `events`, `label`), `webhooks`, `webhook_show` (`id`) and `webhook_delete` (`id`). Webhooks
|
|
855
|
+
carry the same canonical compact events as `events`; their signing secret appears only in `webhook_create`. The act verbs are `approve`, `deny`,
|
|
746
856
|
`answer` (`answer` object or `cancel`), `prompt` (`delivery`) and `stop`, and the launch tools `launch` (`repo`
|
|
747
857
|
required and absolute; `mode`, `branch`, `base`, `remote`, `key`, `name`, `roles`, `harness`, `agent`, `model`,
|
|
748
858
|
`runtime`, `profile`, `machine`, `nodeLabels` and `labels` objects, `prefer`, `prompt`, `end`, `timeout`,
|
|
@@ -750,7 +860,8 @@ required and absolute; `mode`, `branch`, `base`, `remote`, `key`, `name`, `roles
|
|
|
750
860
|
(`to`, `text`, `addr`, `id`, `requestID`, `formID`, `why`, `n`, `wake`, `urgent`, `all`, `project`, `role`, …). The act
|
|
751
861
|
tools need an app token with scope `sessions:act` and a grant, the launch tools scope `sessions:launch` and a
|
|
752
862
|
launch grant; `needs_user` results include the evaluation. `ask` takes `wait` of at
|
|
753
|
-
most 60 s (one long poll; a longer wait is cut to 60) and `expires` in seconds or as `2h
|
|
863
|
+
most 60 s (one long poll; a longer wait is cut to 60) and `expires` in seconds or as `2h`; `ask @me` reports the
|
|
864
|
+
Decision it opened. Answers are the CLI's
|
|
754
865
|
short text; messages and answers from other sessions come in the hub's envelope and are marked as not from the
|
|
755
866
|
user. Hub errors are tool results with `isError: true` and the hub's `code: message` (plus candidates and hint),
|
|
756
867
|
never protocol errors. Only protocol frames go to stdout; the server ends when stdin closes (after answering the
|
|
@@ -766,9 +877,35 @@ calls in flight) or on SIGINT/SIGTERM.
|
|
|
766
877
|
set, else the `hub login` file. MCP clients start servers without a hub session's shell environment, so in practice
|
|
767
878
|
it acts as the **app token** you logged in with (needs `sessions:read`, `messages:send` and `messages:read`), not
|
|
768
879
|
as an OpenCode session: messages are sent from the app, and replies to them land in its inbox (the `inbox` tool),
|
|
769
|
-
not in a session.
|
|
880
|
+
not in a session. App tokens can archive, unarchive and name sessions with `sessions:manage`, and claim or release roles with it plus a `roles` grant. Inside OpenCode, agents use the plugin's
|
|
770
881
|
`hub` tool instead, which acts as the calling session.
|
|
771
882
|
|
|
883
|
+
#### Over HTTP (/mcp)
|
|
884
|
+
|
|
885
|
+
The hub serves the same tools at **`POST /mcp`** ([Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http)),
|
|
886
|
+
for MCP clients and gateways that take a remote server URL. It is stateless: one JSON-RPC message per POST, answered
|
|
887
|
+
as `application/json` (no SSE stream, no `Mcp-Session-Id`); notifications and responses get `202` with no body;
|
|
888
|
+
batches answer `-32600`; `GET`/`DELETE` answer `405` (`Allow: POST`). `initialize` also returns short
|
|
889
|
+
`instructions`. Requests need `Content-Type: application/json`, an `Accept` that allows `application/json` (or
|
|
890
|
+
none), a body of at most 1 MiB (else `413`) and, when sent, a supported `MCP-Protocol-Version` (else `400`). A
|
|
891
|
+
browser `Origin` other than the hub's own is refused (`403`); requests without one (servers, gateways) are fine.
|
|
892
|
+
|
|
893
|
+
**Auth.** `Authorization: Bearer <token>`: any credential the agent API takes, in practice an **app token**
|
|
894
|
+
(Settings → Agents, or `POST /api/agent/app-tokens`) with the scopes the tools need: `sessions:read` (reading), `sessions:manage` (archive/unarchive, name; roles also need a `roles` grant), `events:subscribe` (events),
|
|
895
|
+
`messages:send` and `messages:read` (messaging), `sessions:act` plus a grant (act tools), `sessions:launch` plus a
|
|
896
|
+
launch grant (launch tools). Device, session and act tokens work too. No or a bad token answers `401` with
|
|
897
|
+
`WWW-Authenticate: Bearer realm="hub"` (no OAuth discovery). The tools call `/api/agent` as that token, inside the
|
|
898
|
+
hub process: scopes, grants, tenancy, kill switches and the agent audit apply as on the HTTP API, and a scope the
|
|
899
|
+
token lacks is an `isError` tool result. Cookies never authenticate `/mcp`.
|
|
900
|
+
|
|
901
|
+
Add it to an MCP client or gateway as a remote (HTTP) server with bearer auth, one token per user:
|
|
902
|
+
|
|
903
|
+
```json
|
|
904
|
+
{ "mcpServers": { "hub": { "type": "http", "url": "https://hub.example.com/mcp", "headers": { "Authorization": "Bearer hat_…" } } } }
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
(an MCP gateway: URL `https://<hub>/mcp`, auth kind bearer, per-user credential: each user pastes their own app token).
|
|
908
|
+
|
|
772
909
|
## Spoke (OpenCode plugin)
|
|
773
910
|
|
|
774
911
|
The plugin is a thin client ("spoke") of the hub. Every OpenCode process that loads
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huynle/hub",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Hub: a harness-agnostic central hub your AI agent harnesses connect to (OpenCode first). Follow, steer and stop sessions from your phone or browser, with notifications, optional hands-free Voice and a self-hostable server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Huy Le",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"packages/protocol/package.json",
|
|
50
50
|
"packages/sdk/src",
|
|
51
51
|
"packages/sdk/package.json",
|
|
52
|
+
"packages/cli/src",
|
|
53
|
+
"packages/cli/package.json",
|
|
52
54
|
"packages/cli/dist/hub-cli.js",
|
|
53
55
|
"packages/runtime-sdk/src",
|
|
54
56
|
"packages/runtime-sdk/package.json",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huynle/hub-claude-code",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "The Claude Code hooks spoke: per-user daemon, hook bridge, plugin dir, `hub claude` wrapper and daemon-backed MCP server (internal; shipped inside @huynle/hub, not published on its own).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@ import { spawnSync } from "node:child_process"
|
|
|
9
9
|
import { realpathSync } from "node:fs"
|
|
10
10
|
import { configPath } from "../../../src/config.ts"
|
|
11
11
|
import { nodeConfigPath, readNodeConfig, readSpokeCredentials } from "../../node/src/config.ts"
|
|
12
|
-
import { findExecutable } from "../../node/src/harnesses.ts"
|
|
12
|
+
import { claudeBinary, findExecutable } from "../../node/src/harnesses.ts"
|
|
13
13
|
import { runHook } from "./bridge.ts"
|
|
14
14
|
import { startDaemon, type DaemonLaunchOptions } from "./daemon.ts"
|
|
15
15
|
import { runMcpPipe } from "./mcp.ts"
|
|
@@ -42,12 +42,12 @@ export function claudeVersion(bin: string, env: ClaudeCodeIo["env"]): string {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* How the daemon starts Claude Code for hub launches, or undefined when it does not: the Claude Code CLI must be
|
|
45
|
-
* found (HUB_CLAUDE_BIN, else `claude` on PATH) and node.json must not withhold claude-code (harnesses). Launch
|
|
45
|
+
* found (claudeBinary: HUB_CLAUDE_BIN, hub's claude-code.json, else `claude` on PATH) and node.json must not withhold claude-code (harnesses). Launch
|
|
46
46
|
* directories must be under node.json's workspaceRoots (read again at each create); launched sessions load the hub
|
|
47
47
|
* plugin `hub claude` loads (written when the first launch needs it).
|
|
48
48
|
*/
|
|
49
49
|
export function daemonLaunchOptions(io: { readonly env: ClaudeCodeIo["env"]; readonly home: string; readonly program: Program }): DaemonLaunchOptions | undefined {
|
|
50
|
-
const bin = findExecutable(io.env.
|
|
50
|
+
const bin = findExecutable(claudeBinary(io.env, io.home), io.env)
|
|
51
51
|
if (!bin) return undefined
|
|
52
52
|
const file = nodeConfigPath(io.env, io.home)
|
|
53
53
|
if (readNodeConfig(file).config.harnesses?.["claude-code"] === false) return undefined
|
|
@@ -83,7 +83,7 @@ async function daemon(io: ClaudeCodeIo): Promise<number> {
|
|
|
83
83
|
socketPath: socketPath(io.env, io.home),
|
|
84
84
|
server: credentials.server,
|
|
85
85
|
token: credentials.token,
|
|
86
|
-
harnessVersion: claudeVersion(io.env.
|
|
86
|
+
harnessVersion: claudeVersion(claudeBinary(io.env, io.home), io.env),
|
|
87
87
|
pluginVersion: VERSION,
|
|
88
88
|
directory: io.home,
|
|
89
89
|
log,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// asyncRewake Stop hook would hold the run until its timeout). On a machine that is not enrolled (no machine token) or when the daemon
|
|
8
8
|
// cannot be started, Claude Code runs without the hub, and the command says why.
|
|
9
9
|
//
|
|
10
|
-
// Settings (environment): HUB_CLAUDE_BIN (default `claude` on PATH), HUB_CLAUDE_PERMISSION_WAIT (seconds a
|
|
10
|
+
// Settings (environment): HUB_CLAUDE_BIN (default: hub's claude-code.json `binary`, else `claude` on PATH), HUB_CLAUDE_PERMISSION_WAIT (seconds a
|
|
11
11
|
// permission request waits for the hub, default 120; 0: report only), HUB_CLAUDE_REWAKE_TIMEOUT (seconds the
|
|
12
12
|
// asyncRewake Stop hook may wait for a message, default 3600).
|
|
13
13
|
import { spawn } from "node:child_process"
|
|
@@ -16,6 +16,7 @@ import { closeSync, openSync } from "node:fs"
|
|
|
16
16
|
import path from "node:path"
|
|
17
17
|
import { configPath } from "../../../src/config.ts"
|
|
18
18
|
import { readSpokeCredentials } from "../../node/src/config.ts"
|
|
19
|
+
import { claudeBinary } from "../../node/src/harnesses.ts"
|
|
19
20
|
import pkg from "../package.json" with { type: "json" }
|
|
20
21
|
import { writePlugin } from "./plugin.ts"
|
|
21
22
|
import { claudeStateDir, ensurePrivateDir, socketPath } from "./paths.ts"
|
|
@@ -149,7 +150,7 @@ export async function runClaude(args: readonly string[], io: ClaudeIo): Promise<
|
|
|
149
150
|
if (printWithoutStreamingInput(args)) env.HUB_CLAUDE_REWAKE = "off"
|
|
150
151
|
await ask(socket, { v: WIRE_VERSION, kind: "launch", launch, pid: process.pid }, { timeoutMs: ASK_MS })
|
|
151
152
|
}
|
|
152
|
-
const code = await spawnClaude(io.env.
|
|
153
|
+
const code = await spawnClaude(claudeBinary(io.env, io.home), plugin ? ["--plugin-dir", plugin, ...args] : [...args], env, io.stderr)
|
|
153
154
|
if (plugin) await ask(socket, { v: WIRE_VERSION, kind: "exit", launch }, { timeoutMs: ASK_MS })
|
|
154
155
|
return code
|
|
155
156
|
}
|