@kontextmind/kxm 0.7.167 → 0.7.168

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.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.167",
14
+ "version": "0.7.168",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -605,6 +605,24 @@ All notable user-facing changes are documented here. The project follows [Semant
605
605
 
606
606
  ### Fixed
607
607
 
608
+ - **`kxm hub view` and `kxm hub bind` require a kxm hub body.** HTTP 200 HTML
609
+ (an Authentik login page) and 30x redirects are not healthy. The JSON
610
+ `health` field is `{ "error": "auth_proxy" }`, not the page. Bind refuses
611
+ with `hub_not_kxm` unless `--force` is set. A timeout or a dead host still
612
+ binds. `kxm hub view` names the key source that would supply the token
613
+ (`env:<NAME>` only when that variable is set, `op:<ref>`, `token-command`,
614
+ `hub-env`, or `missing`).
615
+ - **A cloud bind no longer breaks a 0.7.159 Runtime supervisor.** Cloud fields
616
+ live in `hub-binding.cloud.json`. `hub-binding.json` stays a three-key local
617
+ record or is omitted, so the old reader falls back to the local hub instead
618
+ of throwing `malformed hub binding` and stopping sync. A newer schema is a
619
+ warning and `no_hub`, not a stalled tick. Restart the supervisor, hub, and
620
+ workers after upgrading and before `kxm hub bind --cloud`.
621
+ - **`kxm update --kxm` downloads with curl when `gh` is missing**, and still
622
+ verifies the release sha256.
623
+ - **The npm publish job waits until `npm view` shows the version** (bounded)
624
+ before it reports success. GitHub release log lines no longer say "published"
625
+ in a way that means the package is on npm.
608
626
  - **A one-shot outcome is the last top-level JSON object that ends the reply.** It is accepted only when the prefix before it leaves every string, object, and array closed (a naive state machine plus a container lexer over the last container openers). Anything ambiguous, truncated, or followed by prose fails closed. The authoring witness counts `HEAD`, so a writer that commits is credited.
609
627
 
610
628
  - **The test suite no longer passes `--test-timeout`.** Under `node --test` that flag bounds each file, so coverage on CI timed out `test/core/engine.test.ts` at three minutes. The wall clock in `scripts/run-bounded.mjs` still bounds each script.
@@ -201,7 +201,12 @@ versions by hand.
201
201
  4. **Publish npm** runs in the protected `npm-publish` environment. It skips a
202
202
  version already on npm. Otherwise `scripts/kxm-publish-npm.mjs` confirms the
203
203
  GitHub release is published, downloads the asset, verifies its sha256
204
- against the release digest, and runs `npm publish` on that exact file.
204
+ against the release digest, and runs `npm publish` on that exact file. The
205
+ job then polls `npm view @kontextmind/kxm@<version> version` (up to 20
206
+ minutes, `KXM_NPM_VISIBLE_WAIT_MS=0` skips the wait) and reports success
207
+ only after the registry shows that version. Log lines that say the GitHub
208
+ release is public are not an npm publish. The registry can lag the GitHub
209
+ release by many minutes.
205
210
 
206
211
  Every step is idempotent. Rerunning the Release workflow for a tag that is
207
212
  already published or already on npm reports it and changes nothing.
@@ -94,8 +94,9 @@ sync prj_a17d607765e144cd95b93a8715d360b6: blocked (pending 0, acked 1, refused
94
94
  | State | Meaning | Action |
95
95
  |---|---|---|
96
96
  | `ok` | Rows are being acknowledged | None |
97
- | `no_hub` | No hub is bound or set; rows are kept locally by design | Bind a hub if you want hub-side views |
97
+ | `no_hub` | No hub is bound or set, or this build ignored a binding it does not understand; rows are kept locally by design | Bind a hub if you want hub-side views. A warning that names `hub-binding.json` means restart the supervisor after upgrading, then bind again |
98
98
  | `blocked` | Transport or credential failure; rows stay pending and the tick backs off, up to 5 minutes | Fix the cause; the next attempt resumes on its own |
99
+ | `sync: the supervisor is running but did not answer /v1/sync/status` | The process is up, and `kxm runtime status` could not read `GET /v1/sync/status`. A hung supervisor, a token mismatch, or a supervisor started before an upgrade | `kxm runtime stop`, then `kxm runtime start`. Restart the hub and workers too after an upgrade and before a cloud bind |
99
100
  | `refusing` | The hub durably refused rows, or answered without a result for some | Fix the hub side, then run `kxm runtime sync-retry` |
100
101
 
101
102
  A project whose run store this release cannot open shows `blocked (its store is not readable by this build)` with the reason; see [Upgrade KXM](upgrade.md#understand-schema-changes). Right after the supervisor starts, the status can read `no project registered with this Runtime yet` until the first tick.
@@ -89,6 +89,7 @@ If a malformed claim remains, read the exact `.pid` JSON in the state directory
89
89
  | `hub_credential_unreadable` | Repair or remove `hub-env.json` under the user state root |
90
90
  | `cloud_token_missing` | A `--cloud` binding's variable is empty and it has no working command. Export the variable or fix `--token-command`. This machine's hub-env token is not sent |
91
91
  | `cloud_flag_required` | `--token-command` was passed without `--cloud`. `--token-env` and `--key-op` are key references and are valid on a local bind |
92
+ | `hub_not_kxm` | The URL returned an HTML login page or a 30x redirect, not a kxm `/health` body. `https://hub.kxmd.dev` is an Authentik forward-auth page. Bind the SSH forward `http://127.0.0.1:17331`. `--force` records the URL anyway and does not make the proxy a hub |
92
93
  | 401 after binding a loopback forward | The bind was not `--cloud`, so the client sent the local hub-env token. See [Cross-box peer attach](../operations.md#cross-box-peer-attach) |
93
94
 
94
95
  ## Claude Code plugin and MCP
@@ -209,6 +210,9 @@ A gate step declares an outcome its `expect` value never produces. The message n
209
210
  | `runtime supervisor is not running` or `runtime_not_running` | The supervisor stopped | `kxm runtime start`; commands such as `kxm run` also start it on demand |
210
211
  | `runtime_supervisor_unreachable` | A live supervisor process does not answer its token probe | Check the PID from `kxm runtime status`, stop a hung process with your OS tools, then `kxm runtime start` |
211
212
  | `project_required` | The command ran outside a KXM project | Run it from the checkout, or run `kxm init` |
213
+ | `project_home_conflict` | This checkout is not the registered home for that project id. A different repository already owns it, the home runtime is immutable, or the primary checkout is already the home row | Use the original checkout, or a worktree of that repository. Do not copy the project id into an unrelated clone, and do not delete the database |
214
+ | `runtime_schema_newer` | The store was written by a newer release than this build | Upgrade KXM. Do not delete the database |
215
+ | `sync: the supervisor is running but did not answer /v1/sync/status` | The supervisor process is up, and `GET /v1/sync/status` failed. A hung process, a token mismatch, or a supervisor started on an older release | `kxm runtime stop`, then `kxm runtime start`. After an upgrade, restart the hub and workers before a cloud bind. See [Check sync status](runtime-sync.md#check-sync-status) |
212
216
  | `producer_route_not_admitted` | A live drive uses a model route that is not admitted | `kxm routes admit --model <provider/model>`, or drive with `--simulated` |
213
217
  | A 429, timeout, or provider error fails the attempt on the first route | The role's `policy.fallback.onError` is empty, omits that class, or `maxSwitches` is already spent. Cancellation, policy refusal, and authentication do not walk | List the class on the role, admit the next route, and read `routing.route_switched` in the run events. `kxm routing report` prints Route switches |
214
218
  | `pi_not_authenticated: pi harness not detected (pi_native_impersonation_blocked)` | A Pi agent's model belongs to a vendor with its own harness | Set the agent's native `harness:`, or choose an admitted Pi route whose vendor has none |
@@ -6,7 +6,7 @@ Move the `kxm` CLI, the hub, the Runtime, and the Claude Code or Pi integrations
6
6
 
7
7
  - Protected storage for a full backup; see [Back up and restore KXM](backup-and-restore.md).
8
8
  - A window in which the hub, the Runtime supervisor and long-lived workers may stop.
9
- - The GitHub CLI (`gh`, signed in) if you apply updates with `kxm update --kxm` from the default `github` source, which downloads the release tarball with it.
9
+ - The GitHub CLI (`gh`, signed in) if you apply updates with `kxm update --kxm` from the default `github` source. When `gh` is not installed, that command downloads the same `kxm-<version>.tgz` with curl and still checks the release sha256 before installing. Without a working `kxm` either, use the curl commands in [Update the CLI without gh](#update-the-cli-without-gh).
10
10
 
11
11
  ## Check for an update
12
12
 
@@ -54,12 +54,27 @@ Use the path that matches how KXM was installed. `kxm update --kxm` applies an u
54
54
  | Project dependency | `npm install @kontextmind/kxm@latest` in that project |
55
55
  | Source checkout | `git pull`, then `npm ci` |
56
56
 
57
- `kxm update --kxm` refuses to install a GitHub release that does not publish a SHA-256 digest for its tarball (`release_digest_missing`) or whose download does not match it (`release_digest_mismatch`). Preview the steps first:
57
+ `kxm update --kxm` refuses to install a GitHub release that does not publish a SHA-256 digest for its tarball (`release_digest_missing`) or whose download does not match it (`release_digest_mismatch`). When `gh --version` fails, the download step is `curl --fail --silent --show-error --location` against `https://github.com/kontextmind/kxm/releases/download/v<version>/kxm-<version>.tgz`, and the digest check is unchanged. Preview the steps first:
58
58
 
59
59
  ```bash
60
60
  kxm update --kxm --dry-run
61
61
  ```
62
62
 
63
+ ### Update the CLI without gh
64
+
65
+ On a host that has neither `gh` nor a working `kxm`, download the release tarball and check the digest printed on the GitHub release before installing:
66
+
67
+ ```bash
68
+ version=0.7.166
69
+ curl --fail --silent --show-error --location \
70
+ --output "kxm-${version}.tgz" \
71
+ "https://github.com/kontextmind/kxm/releases/download/v${version}/kxm-${version}.tgz"
72
+ sha256sum "kxm-${version}.tgz"
73
+ npm install --global --omit=peer "./kxm-${version}.tgz"
74
+ ```
75
+
76
+ Refuse a tarball whose sha256 does not match the release asset digest.
77
+
63
78
  ### Update harnesses and plugins
64
79
 
65
80
  Without `--check` or a lone `--kxm`, `kxm update` also runs the native updaters of detected harnesses. Narrow it with a harness id and one scope:
@@ -76,7 +91,7 @@ kxm update pi --models # refresh Pi model catalogs
76
91
 
77
92
  ## Start and verify
78
93
 
79
- 1. Start the hub (or its service), then the Runtime with `kxm runtime start`.
94
+ 1. Start the hub (or its service), then the Runtime with `kxm runtime start`. Restart workers too. Do this after the upgrade and again before `kxm hub bind --cloud`, so an older supervisor does not keep a binding it cannot read.
80
95
  2. Check `kxm hub view`, `/ready` and `/metrics`.
81
96
  3. Check `kxm runtime status` until every project reports `ok` or `no_hub`.
82
97
  4. Send one request between two agents and read one Runtime run with `kxm runs status <run-id>`.
@@ -88,16 +88,32 @@ kxm hub bind --cloud \
88
88
  http://127.0.0.1:17331
89
89
  ```
90
90
 
91
- Expected output:
91
+ Expected output when `KXMD_HUB_TOKEN` is set:
92
92
 
93
93
  ```text
94
94
  bound hub http://127.0.0.1:17331 · remote · health=on · token is not stored
95
95
  hub health=true ready=true · remote hub · mode=cloud project=prj_example (.kxm/config.yaml hub.cloud.project) key=env:KXMD_HUB_TOKEN
96
96
  ```
97
97
 
98
+ With only `--token-command`, and `KXMD_HUB_TOKEN` unset, `kxm hub view` prints
99
+ `key=token-command`. It names the source that would actually supply the token:
100
+ `env:<NAME>` only when that variable is non-empty, otherwise `op:<ref>`,
101
+ `token-command`, `hub-env`, or `missing`. An unset variable is not reported as
102
+ the source.
103
+
98
104
  `--token-env` and `--token-command` can be combined. A non-empty variable wins.
99
105
  Otherwise the command runs. The command is a program and arguments, not a shell
100
- pipeline (`|`, `&`, `;`, and redirection are refused).
106
+ pipeline (`|`, `&`, `;`, and redirection are refused). `--token-command` is
107
+ stored only in the machine-wide binding (`hub-binding.cloud.json` under the
108
+ user state root). It is not written to `.kxm/config.yaml`. Config holds
109
+ `key.op` and `key.env` references only.
110
+
111
+ `https://hub.kxmd.dev` is behind an Authentik forward-auth proxy. Clients
112
+ cannot use it: `GET /health` returns the login page (HTTP 200 HTML, or a 302).
113
+ `kxm hub view` treats that as unhealthy (`health=false`, and the JSON `health`
114
+ field is `{ "error": "auth_proxy" }`, not the HTML). `kxm hub bind` refuses
115
+ with `hub_not_kxm` and writes nothing unless you pass `--force`. The supported
116
+ client path is the SSH forward above.
101
117
 
102
118
  <details><summary>PowerShell</summary>
103
119
 
@@ -114,8 +130,8 @@ kxm hub view
114
130
  | | Local bind | `--cloud` bind |
115
131
  |---|---|---|
116
132
  | Loopback URL | Scope `loopback`. No credential required | Scope `remote`, including an SSH forward |
117
- | Token stored in `hub-binding.json` | No | No. The file names `tokenEnv` and/or `tokenCommand` |
118
- | Credential later commands send | `KXM_AUTH_TOKEN`, then the active mode's key reference in `.kxm/config.yaml`, then this machine's hub-env | The active mode's key reference, then the binding's named source. Hub-env is not a fallback |
133
+ | Token stored in the binding | No | No. `hub-binding.cloud.json` names `tokenEnv` and/or `tokenCommand`. `hub-binding.json` stays a three-key local record, or is omitted, so a 0.7.159 reader does not throw |
134
+ | Credential later commands send | Agent commands use `KXM_AUTH_TOKEN`, then `hub.local` `key.env` / `key.op`, then this project's saved project token. They never use the hub-env admin token (`project_token_missing`). `kxm hub view` and `kxm tenant status` may use the admin token | The active mode's key reference, then the binding's named source. Hub-env is not a fallback |
119
135
  | Missing source | Agent commands report `project_token_missing` and name the project id that was sent | `cloud_token_missing` or `cloud_token_command_failed`. The local admin token is not sent |
120
136
  | `kxm hub start` from an extension | May start a local hub | Does not start a local hub when the forward is down (`cloud_hub_unreachable`) |
121
137
 
@@ -124,6 +140,47 @@ kxm hub view
124
140
  all use that source. `kxm agent worker` puts `KXM_AUTH_TOKEN` in the child
125
141
  process environment only. It does not write the token to disk.
126
142
 
143
+ A local bind does not give agents the admin token from hub-env. Give them a
144
+ project token in one of these ways:
145
+
146
+ - On the client, export `KXM_AUTH_TOKEN` to that project's token, or set
147
+ `hub.local.key.env` / `hub.local.key.op` in `.kxm/config.yaml`.
148
+ - On the hub, map the project id in `hub.projects` (an `op://` reference or an
149
+ environment variable name) or in `KXM_PROJECT_TOKENS`, keyed by the same id
150
+ the client sends.
151
+
152
+ ### Where to store tokens
153
+
154
+ Keep hub admin and project tokens in 1Password and point KXM at `op://` references
155
+ (`--key-op`, `key.op`, or `--token-command "op read op://..."`). Do not paste the
156
+ token into `.kxm/config.yaml`, `hub-binding.json`, or `hub-binding.cloud.json`.
157
+
158
+ A hub host that has no `op` binary should take the token from the environment
159
+ or from a systemd credentials file. The unit in
160
+ [Supervise the hub and Runtime](operations/deploy.md#the-hub) loads
161
+ `EnvironmentFile=/etc/kxm/hub.env` (mode 0600) for `KXM_AUTH_TOKEN` and
162
+ `KXM_PROJECT_TOKENS`.
163
+
164
+ ### Restart after an upgrade, and before a cloud bind
165
+
166
+ Restart the Runtime supervisor, the hub, and workers after upgrading and before
167
+ `kxm hub bind --cloud`:
168
+
169
+ ```bash
170
+ kxm runtime stop
171
+ kxm hub stop
172
+ kxm runtime start
173
+ kxm hub start
174
+ ```
175
+
176
+ A 0.7.159 supervisor treats any extra key in `hub-binding.json` as
177
+ `malformed hub binding` and stops syncing. This release keeps cloud fields in
178
+ `hub-binding.cloud.json` and leaves `hub-binding.json` as a three-key local
179
+ record, or omits it. An older reader then keeps the local hub instead of
180
+ throwing. A binding newer than this build is ignored with a warning, and sync
181
+ continues as `no_hub` rather than stalling. Processes that were already running
182
+ keep the binding they loaded at start, so restart them after the upgrade.
183
+
127
184
  The hub project id is the same value everywhere: `--project` or `KXM_PROJECT`,
128
185
  then the active mode's `project` in `.kxm/config.yaml`, then the `id` in
129
186
  `.kxm/project.yaml`, then `package.json` `name`, then the directory name.
@@ -147,7 +204,8 @@ hub:
147
204
  op: op://Private/kxm/local-project-token
148
205
  env: KXM_LOCAL_PROJECT_TOKEN
149
206
  cloud:
150
- url: https://hub.kxmd.dev
207
+ # Clients use an SSH forward. https://hub.kxmd.dev is an Authentik login page.
208
+ url: http://127.0.0.1:17331
151
209
  project: prj_example
152
210
  key:
153
211
  op: op://Private/kxmd/project-token
@@ -167,7 +225,8 @@ with `hub_config_invalid`.
167
225
  `kxm hub bind` and `kxm hub bind --cloud` write the mode, URL, project id, and
168
226
  key reference. `--key-op` is the `op://` reference for either mode.
169
227
  `--token-env` names the variable for either mode. `--token-command` stays
170
- cloud-only. `kxm hub view` prints the mode, URL, project id, and key source.
228
+ cloud-only, and only in `hub-binding.cloud.json`, not in this file.
229
+ `kxm hub view` prints the mode, URL, project id, and key source.
171
230
  It does not print the token. `kxm hub unbind` removes the binding and
172
231
  `hub.mode`, and leaves the `local` and `cloud` blocks in place.
173
232
 
@@ -185,7 +244,8 @@ send the `.kxm/project.yaml` id and the hub has no token for it.
185
244
  kxm hub unbind
186
245
  ```
187
246
 
188
- Unbind deletes `hub-binding.json` and clears `hub.mode` in `.kxm/config.yaml`.
247
+ Unbind deletes `hub-binding.json` and `hub-binding.cloud.json`, and clears
248
+ `hub.mode` in `.kxm/config.yaml`.
189
249
  The `local` and `cloud` blocks stay, so the next bind can switch mode again.
190
250
  It does not delete hub-env, and it does not close the SSH forward. Stop the
191
251
  forward by ending the `ssh -N` session.
@@ -200,6 +260,9 @@ forward by ending the `ssh -N` session.
200
260
  | `cloud_token_command_failed` | `op` exited non-zero, timed out, or printed more than one line | Run the same command in a terminal and sign in to 1Password. Do not paste the token into the binding |
201
261
  | Clients through one SSH host get locked out together | The hub rate-limits by agent id, otherwise by source address. Every forward from one SSH session shares `127.0.0.1` on the hub. `sshd` `MaxStartups` and `MaxSessions` can refuse new forwards first | Give each long-lived agent its own name. Raise the SSH limits on that host if many people share one jump host. Do not point two tenants at one hub |
202
262
  | `kxm hub view` says `loopback` for the forward | `--cloud` was omitted | Bind again with `--cloud`. `KXM_SERVER_URL` overrides the binding URL and is labeled on its own |
263
+ | `hub_not_kxm`, or `kxm hub view` prints `auth proxy` with `health=false` | The URL is an Authentik (or other) login page, or a 30x redirect, not a kxm hub. `https://hub.kxmd.dev` is that case | Bind `http://127.0.0.1:17331` through the SSH forward. `--force` writes the binding anyway and is not a working client path |
264
+ | `key=env:KXMD_HUB_TOKEN` while that variable is unset | An older `kxm hub view` named the configured variable even when it was empty | Upgrade. The line names `token-command`, `op:<ref>`, `hub-env`, or `missing` for the source that actually supplies the token |
265
+ | `malformed hub binding` and sync stops after a cloud bind | A supervisor older than this release read cloud fields in `hub-binding.json` | Upgrade, then restart the supervisor, hub, and workers before binding `--cloud` again |
203
266
 
204
267
  ## Next steps
205
268
 
@@ -477,12 +477,12 @@ Starts, inspects, and stops the local KXM hub, and binds this machine to a hub.
477
477
  kxm hub view
478
478
  ```
479
479
 
480
- Calls `GET /health` and `GET /ready` on the target hub and reports whether the URL is loopback or remote, plus the mode, project id, and key source in effect. The key source is an environment variable name or an `op://` reference. The token is not printed.
480
+ Calls `GET /health` and `GET /ready` on the target hub and reports whether the URL is loopback or remote, plus the mode, project id, and key source in effect. A probe counts only when the response is JSON (`application/json`, or `text/plain` with a JSON object) of the hub shape: `/health` is `{ "ok": true, "agents": <number> }` and `/ready` is `{ "ok": true, "storage": "sqlite"|"memory" }`. An HTTP 200 HTML login page, or a 30x redirect, is not healthy. The JSON `health` and `ready` fields then hold `{ "error": "auth_proxy" }` and never the HTML. The key source is the one that would supply the token: `env:<NAME>` only when that variable is non-empty, `op:<ref>`, `token-command`, `hub-env`, or `missing`. The token is not printed.
481
481
 
482
482
  No command-specific options.
483
483
 
484
484
  - Needs a hub to succeed. Reads only.
485
- - JSON keys: `target` (`url`, `scope`, and `source: "env"` when `KXM_SERVER_URL` overrides a binding), `identity` (`mode`, `modeSource`, `url`, `urlSource`, `project`, `projectSource`, `keySource`), `health`, `ready`. An unreachable hub reports `{"error":"hub_unreachable"}` for both probes.
485
+ - JSON keys: `target` (`url`, `scope`, and `source: "env"` when `KXM_SERVER_URL` overrides a binding), `identity` (`mode`, `modeSource`, `url`, `urlSource`, `project`, `projectSource`, `keySource`), `health`, `ready`. An unreachable hub reports `{"error":"hub_unreachable"}` for both probes. An auth proxy or redirect reports `{"error":"auth_proxy"}`.
486
486
  - Exit 0 when both probes succeed, 1 otherwise. A cloud mode with no resolvable key exits 2 with `cloud_token_missing`.
487
487
 
488
488
  ```bash
@@ -569,15 +569,16 @@ kxm hub stop --wait-ms 8000 --json
569
569
  ### `kxm hub bind`
570
570
 
571
571
  ```text
572
- kxm hub bind <url> [--cloud] [--token-env <name>] [--token-command <command>] [--key-op <op://vault/item/field>]
572
+ kxm hub bind <url> [--cloud] [--token-env <name>] [--token-command <command>] [--key-op <op://vault/item/field>] [--force]
573
573
  ```
574
574
 
575
- Binds this machine to a running hub by writing `hub-binding.json` under the user state root and the non-secret endpoint (mode, URL, project id, key reference) into `.kxm/config.yaml`, then probes the hub's health for up to 300 ms. Every hub client uses that endpoint when `KXM_SERVER_URL` is unset. The token is never written.
575
+ Binds this machine to a running hub by writing the binding under the user state root and the non-secret endpoint (mode, URL, project id, key reference) into `.kxm/config.yaml`, then probes the hub's health for up to 300 ms. A local bind writes `hub-binding.json` (`schema`, `url`, `boundAt` only). A `--cloud` bind writes `hub-binding.cloud.json` and leaves a three-key local file in place, or omits `hub-binding.json`, so a 0.7.159 reader does not throw `malformed hub binding` and stop sync. `--token-command` is stored only in that cloud file, not in `.kxm/config.yaml`. Every hub client uses that endpoint when `KXM_SERVER_URL` is unset. The token is never written.
576
576
 
577
577
  - Arguments: `<url>`, Hub base URL (http or https).
578
578
  - A URL with credentials, a query, a fragment, or a scheme other than http or https fails with `hub_url_invalid` (exit 2).
579
579
  - A remote (non-loopback) URL is refused with `hub_bind_unauthenticated` (exit 2, `nextAction: "export_kxm_auth_token"`) unless a credential for the current project resolves from `KXM_AUTH_TOKEN` or the persisted `hub-env.json`. A malformed record fails with `hub_credential_unreadable` (exit 2). A loopback URL without `--cloud` still binds with no credential.
580
580
  - `--cloud` marks the binding remote even when the URL is a loopback forward. It requires `--token-env`, `--token-command`, `--key-op`, or a combination. The token is not written. A non-empty variable wins; otherwise an `op://` reference is read with `op read`, or the command runs. `--token-command` without `--cloud` fails with `cloud_flag_required`. `--token-env` and `--key-op` are also valid on a local bind and are stored only as references in config. A missing cloud source later fails with `cloud_token_missing` and does not fall back to hub-env. See [Cross-box peer attach](../operations.md#cross-box-peer-attach).
581
+ - The probe accepts a kxm `/health` body. A timeout (`health=unknown`) or a connection failure (`health=off`) still writes the binding. An HTML login page or a 30x redirect is `hub_not_kxm` (exit 2, `kind` `auth_proxy` or `redirect`) and writes nothing. The error names an auth proxy or a redirect. `--force` writes anyway. `https://hub.kxmd.dev` is an Authentik login page; clients use the SSH forward at `http://127.0.0.1:17331`.
581
582
  - Mutates the binding file and `.kxm/config.yaml`. `--dry-run` validates and prints the plan without writing.
582
583
  - JSON keys: `url`, `scope`, `file`, `configFile`, `project`, `health` (`on`, `off`, or `unknown`), `probeMs`. A cloud bind also returns `cloud`, and `tokenEnv` or `tokenCommand` when set.
583
584
 
@@ -984,7 +985,20 @@ Checks for or applies a KXM operator package update, and runs the native updater
984
985
 
985
986
  - Arguments: `[harness]`, Harness id (default: every detected harness).
986
987
  - `--check` cannot be combined with any other update flag or a harness (exit 2, `scope_conflict`), and at most one of `--self`, `--extensions`, `--models` is allowed (exit 2).
987
- - From a source checkout, `--check` reports the running version without network access, and `--kxm` is refused (exit 2, `install_kind_source`) with an instruction to `git pull`. Only npm-global installs can apply `--kxm`; other install kinds exit 2 with `install_kind_<kind>`. A GitHub release must publish a sha256 digest for `kxm-<version>.tgz` or the install fails closed (`release_digest_missing`, `release_digest_mismatch`).
988
+ - From a source checkout, `--check` reports the running version without network access, and `--kxm` is refused (exit 2, `install_kind_source`) with an instruction to `git pull`. Only npm-global installs can apply `--kxm`; other install kinds exit 2 with `install_kind_<kind>`. A GitHub release must publish a sha256 digest for `kxm-<version>.tgz` or the install fails closed (`release_digest_missing`, `release_digest_mismatch`). When `gh` is missing, the github source downloads that same asset with `curl --fail --silent --show-error --location` and still verifies the digest before `npm install -g`.
989
+
990
+ On a host with neither `gh` nor a working `kxm`, download and check it yourself:
991
+
992
+ ```bash
993
+ version=0.7.166
994
+ curl --fail --silent --show-error --location \
995
+ --output "kxm-${version}.tgz" \
996
+ "https://github.com/kontextmind/kxm/releases/download/v${version}/kxm-${version}.tgz"
997
+ # Compare with the sha256 digest published on the GitHub release asset.
998
+ sha256sum "kxm-${version}.tgz"
999
+ npm install --global --omit=peer "./kxm-${version}.tgz"
1000
+ ```
1001
+
988
1002
  - Settings come only from `update.yaml` under the user state root (`auto: true` enables auto-apply); a project `.kxm/update.yaml` is ignored with a warning.
989
1003
  - Without `--check` or a lone `--kxm`, KXM probes harnesses (as `harness list` does) and runs each updater for the selected scope. An unknown harness id exits 2 (`unknown_harness` step); a failed step exits 1.
990
1004
  - Honors `--dry-run`: steps are planned, not run, and the cached update notice is not refreshed.
@@ -1612,7 +1626,7 @@ Create a KXM run without executing steps. The run pins the project's `homeRuntim
1612
1626
  - Refuses `--workspace` (exit 2, `workspace_option_unsupported`).
1613
1627
  - Needs a KXM project. Starts and uses the Runtime; no hub needed. Honors `--dry-run`, which validates the project and prints the plan without starting the supervisor.
1614
1628
  - JSON keys: `idempotent`, `run` (`runId`, `homeRuntimeId`, `status`, `configRevision`), `supervisor` (`runtimeId`, `port`, `started`), and `execution` (`status: not_started`, `mode: live`, `defaultHarness`, `authentication: not_checked`, `prerequisites`, `nextSteps`). Dry run: `projectRoot`, `workflowId`, `configRevision`, `defaultHarness`, `prerequisites`. The obsolete `phase: pre-3a` field is no longer returned.
1615
- - Errors: `workflow_required` (exit 2), `project_required`, `run_workflow_unknown`, `brief_and_prompt`, `brief_unreadable`, `lane_missing`, `lane_run_open`, `run_failed` with `issues` (any invalid file in the project fails the load, for example `gate_outcome_impossible`), `run_io_failed` (exit 1).
1629
+ - Errors: `workflow_required` (exit 2), `project_required`, `run_workflow_unknown`, `brief_and_prompt`, `brief_unreadable`, `lane_missing`, `lane_run_open`, `run_failed` with `issues` (any invalid file in the project fails the load, for example `gate_outcome_impossible`), `run_io_failed` (exit 1). `project_home_conflict` means this checkout is not the registered home for that project id: a different repository already owns it, the home runtime is immutable, or the primary checkout is already the home row. Use the original checkout, or a worktree of that repository. Do not copy the project id into an unrelated clone, and do not delete the database. `runtime_schema_newer` means the store was written by a newer release than this build. Upgrade KXM. Do not delete the database.
1616
1630
  - The `default` workflow that `kxm init` writes does not set `limits.maxAgentTimeMs`, so it can be driven. A workflow that sets that limit is created, but `kxm runs drive` refuses it with `run_handoff_required` (`limit_unsupported`); projects from older `kxm init` templates carry it on `default`.
1617
1631
 
1618
1632
  ```bash
@@ -1826,6 +1840,7 @@ No command-specific options.
1826
1840
  - Reads only. Never starts the supervisor.
1827
1841
  - JSON keys: `running`, `runtimeId`, `pid`, `port`, `state`, `heartbeatAt`, `startedAt`, `sync`.
1828
1842
  - Exit 0 when running, 1 when not (the JSON still says `ok: true`).
1843
+ - When the supervisor process is up but `GET /v1/sync/status` fails, the text line is `sync: the supervisor is running but did not answer /v1/sync/status`. The usual causes are a hung supervisor, a token the status command does not hold, or a supervisor started before an upgrade. `kxm runtime stop` and `kxm runtime start` replace it. A binding this build cannot read is a warning and `no_hub`, not a stalled sync; restart after upgrading and before a cloud bind. See [Check sync status](../operations/runtime-sync.md#check-sync-status).
1829
1844
 
1830
1845
  ```bash
1831
1846
  kxm runtime status
@@ -1311,6 +1311,8 @@ no recorded cost is never ready.
1311
1311
  ```yaml
1312
1312
  # .kxm/config.yaml (project scope) or ~/.config/kxm/config.yaml (user scope).
1313
1313
  # The project file wins. Key fields are references, never tokens.
1314
+ # --token-command is not a config field. It lives only in the machine-wide
1315
+ # hub-binding.cloud.json written by `kxm hub bind --cloud`.
1314
1316
  hub:
1315
1317
  autoStart: background # background | off
1316
1318
  mode: local # local | cloud
@@ -1321,7 +1323,8 @@ hub:
1321
1323
  op: op://Private/kxm/local-project-token
1322
1324
  env: KXM_LOCAL_PROJECT_TOKEN
1323
1325
  cloud:
1324
- url: https://hub.kxmd.dev
1326
+ # Clients use an SSH forward. https://hub.kxmd.dev is an Authentik login page.
1327
+ url: http://127.0.0.1:17331
1325
1328
  project: prj_example
1326
1329
  key:
1327
1330
  op: op://Private/kxmd/project-token
@@ -8,7 +8,7 @@ KXM has three parts you can install: the `kxm` command-line tool, the Claude Cod
8
8
  - Git.
9
9
  - Claude Code, if you want the plugin.
10
10
  - Pi, if you want Pi agents. Install it with `npm install --global @earendil-works/pi-coding-agent` and sign in to a model provider as the [Pi documentation](https://pi.dev/docs/latest) describes.
11
- - The GitHub CLI (`gh`), only if you plan to update with `kxm update --kxm`, which downloads the release tarball from GitHub.
11
+ - The GitHub CLI (`gh`), only if you plan to update with `kxm update --kxm` from the GitHub source. When `gh` is missing, that command falls back to curl and still checks the release checksum. The manual curl commands are in [Upgrade KXM](../operations/upgrade.md#update-the-cli-without-gh).
12
12
 
13
13
  ## Choose what to install
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.167",
3
+ "version": "0.7.168",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.167",
5
+ "version": "0.7.168",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",