@kontextmind/kxm 0.7.174 → 0.7.175
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +13 -0
- package/docs/contributing/decisions.md +22 -1
- package/docs/contributing/test-matrix.md +1 -1
- package/docs/operations/upgrade.md +5 -5
- package/docs/reference/cli-reference.md +8 -7
- package/docs/reference/config-reference.md +2 -2
- package/docs/start/install.md +3 -3
- package/package.json +2 -2
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +128 -52
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +44 -27
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +8 -3
- package/plugins/kxm/src/cli/system.ts +74 -20
- package/plugins/kxm/src/cli.ts +4 -2
- package/plugins/kxm/src/harness.ts +50 -29
- package/plugins/kxm/src/kxm-install-kind.ts +6 -0
- package/plugins/kxm/src/kxm-update-config.ts +2 -2
- package/plugins/kxm/src/kxm-update.ts +11 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/scripts/docker-install-smoke.mjs +2 -1
- package/scripts/npm-pack-json.d.mts +4 -0
- package/scripts/npm-pack-json.mjs +15 -0
- package/scripts/pr-land.mjs +45 -3
package/CHANGELOG.md
CHANGED
|
@@ -181,6 +181,19 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
181
181
|
|
|
182
182
|
### Changed
|
|
183
183
|
|
|
184
|
+
- **`kxm update --kxm` defaults to npm.** Missing or source-less `update.yaml` now
|
|
185
|
+
uses the public `@kontextmind/kxm` package instead of the GitHub release tarball.
|
|
186
|
+
`source: github` still selects the digest-checked tarball. When the working
|
|
187
|
+
directory is this repository, `--kxm` installs the local files and does not fetch.
|
|
188
|
+
- **`kxm update` is a full update by default.** Bare `kxm update` and `--all` run
|
|
189
|
+
detected harness `--self` first (default harness leading), then the kxm package,
|
|
190
|
+
then extensions and models. `--kxm` remains kxm-only. Detected harness updater
|
|
191
|
+
failures on `--all` degrade the receipt and do not fail the command; an unknown
|
|
192
|
+
harness id still exits 2.
|
|
193
|
+
- **`kxm land` runs `npm` on Windows.** The verify stage launches `npm.cmd` through `cmd.exe` when `npm` is not an `.exe`.
|
|
194
|
+
- **npm 12 `pack --json`.** The install smoke and pack tests accept the package-keyed object as well as the older one-element array.
|
|
195
|
+
- **Roadmap links on Windows.** `kxm land` keeps the git symlink text when this process cannot create the `docs/roadmap` links.
|
|
196
|
+
|
|
184
197
|
- **Tool policy is enforced at load and before dispatch.**
|
|
185
198
|
`coordinator` and `read-only` select the read-only one-shot profile.
|
|
186
199
|
`workspace-writer` and `tests-writer` select the edit profile. Dispatch
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: "Decisions"
|
|
3
3
|
description: "Decisions of medium significance or higher made by automated KXM work in this repository. One entry per decision: the options evaluated with their weights, and why the selected option won. Recorded the turn the decision is taken."
|
|
4
4
|
audience: "agents and maintainers"
|
|
5
|
-
updated: "2026-
|
|
5
|
+
updated: "2026-10-02"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Decisions
|
|
@@ -151,3 +151,24 @@ Evidence: Evidence: test/core/oneshot-producer.test.ts (real reviewer shapes), r
|
|
|
151
151
|
- Selected: port through the engine lane `vision-assert-port`; the PR is
|
|
152
152
|
closed by its replacement landing, not by hand.
|
|
153
153
|
Evidence: Evidence: merge-base 77beda4 predates the CLI restructure; merge-tree showed cli/vnext.ts deleted on main; vision-gate.ts already on main.
|
|
154
|
+
|
|
155
|
+
## 2026-10-02 — Porting the stale update-default branch onto main (landing)
|
|
156
|
+
|
|
157
|
+
- Context: `feat/update-npm-default-and-all` was based on 2026-09-24 main
|
|
158
|
+
and still uncommitted. `origin/main` had moved about 66 commits and
|
|
159
|
+
already contained the claude-bridge `apiKey` registration fix and the
|
|
160
|
+
`gh`/`curl` release downloader. `kxm land`'s rebase stage only resolves
|
|
161
|
+
union conflicts, which cannot merge the `planKxmPackageUpdate` signature
|
|
162
|
+
clash (`downloader` vs `localRoot`).
|
|
163
|
+
- Options:
|
|
164
|
+
- Rebase the 2026-09-24 branch through `kxm land`'s rebase stage — weight
|
|
165
|
+
0.2. Its union-only resolver cannot handle the `planKxmPackageUpdate`
|
|
166
|
+
signature clash.
|
|
167
|
+
- Stash-apply onto a fresh lane, take upstream for claude-bridge, `dist`,
|
|
168
|
+
and `package.json`, and merge the rest — weight 0.9. Keeps the reviewed
|
|
169
|
+
update-default work and the upstream fixes that must not be duplicated.
|
|
170
|
+
- Discard and re-dispatch to the writer — weight 0.3. Re-authors reviewed
|
|
171
|
+
work that already had a specified merge.
|
|
172
|
+
- Selected: stash-apply onto a fresh lane off `origin/main`, take upstream
|
|
173
|
+
for claude-bridge, `dist`, and `package.json`, and merge the rest.
|
|
174
|
+
Evidence: Evidence: `git diff HEAD origin/main` on the stale branch showed upstream's claude-bridge fix and curl downloader already landed; the lane port keeps both those and the npm/`--all` behavior.
|
|
@@ -145,7 +145,7 @@ The context suites in detail:
|
|
|
145
145
|
| Permission-diff trust: authority lattice, prose neutrality, Git base shadowing, CLI diff and check | `permission.test.ts`, `cli.test.ts`, `contracts.test.ts`, `package-install.test.ts` |
|
|
146
146
|
| KXM schemas, restricted YAML fixtures, cross-resource semantics and sync-safe rejection | `contracts.test.ts`, `restricted-yaml.test.ts` |
|
|
147
147
|
| Harness detection, auth and dispatch for the built-in catalog, including Windows launch rules | `harness.test.ts` |
|
|
148
|
-
| `kxm update`: `update.yaml` validation, GitHub
|
|
148
|
+
| `kxm update`: `update.yaml` validation, npm default / GitHub override, local checkout install, install-kind detection, `kxm-<v>.tgz` asset selection | `kxm-update.test.ts`, `kxm-update-cli.test.ts`, `kxm-install-kind.test.ts` |
|
|
149
149
|
|
|
150
150
|
The round trip seeds its Runtime stores in a project-local `.kxm/runtime/`
|
|
151
151
|
layout that the Runtime never writes. Separate tests seed two projects' event
|
|
@@ -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)
|
|
9
|
+
- The GitHub CLI (`gh`, signed in), only when `update.yaml` sets `source: github`. 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
|
|
|
@@ -17,7 +17,7 @@ kxm update --check
|
|
|
17
17
|
Expected output when a newer release exists:
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
kxm 0.7.1 → 0.7.2 available · kxm update
|
|
20
|
+
kxm 0.7.1 → 0.7.2 available · kxm update
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
From a source checkout, the check makes no network call and reports the checkout instead:
|
|
@@ -26,7 +26,7 @@ From a source checkout, the check makes no network call and reports the checkout
|
|
|
26
26
|
kxm 0.7.1 (running from source at /work/kxm)
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
The check reads `update.yaml` under the user state root. Its `source` is `github` (release tarballs)
|
|
29
|
+
The check reads `update.yaml` under the user state root. Its `source` is `npm` by default; `github` (release tarballs) is also accepted. When the working directory is this repository, `kxm update --kxm` installs the local files instead of fetching. See [Updater settings](../reference/config-reference.md#updater-settings-kxmupdatev1). Add `--json` to see the install kind KXM detected.
|
|
30
30
|
|
|
31
31
|
## Stop the services and back up
|
|
32
32
|
|
|
@@ -48,7 +48,7 @@ Use the path that matches how KXM was installed. `kxm update --kxm` applies an u
|
|
|
48
48
|
|
|
49
49
|
| Install kind | Upgrade with |
|
|
50
50
|
|---|---|
|
|
51
|
-
| Global npm install | `kxm update --kxm` (
|
|
51
|
+
| Global npm install | `kxm update --kxm` (npm registry, or GitHub tarball with `source: github`), or `npm install --global --omit=peer @kontextmind/kxm@latest` |
|
|
52
52
|
| Pi package | `pi update` |
|
|
53
53
|
| Claude Code marketplace plugin | Reinstall the plugin; see the plugin note below |
|
|
54
54
|
| Project dependency | `npm install @kontextmind/kxm@latest` in that project |
|
|
@@ -77,7 +77,7 @@ Refuse a tarball whose sha256 does not match the release asset digest.
|
|
|
77
77
|
|
|
78
78
|
### Update harnesses and plugins
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Bare `kxm update` (same as `--all`) runs detected harness `--self` updaters first, then the kxm package, then extensions and models; `--kxm` alone updates only kxm. Narrow it with a harness id and one scope:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
83
|
kxm update --dry-run # plan every detected harness
|
|
@@ -971,7 +971,7 @@ Not run: stores a credential.
|
|
|
971
971
|
## `kxm update`
|
|
972
972
|
|
|
973
973
|
```text
|
|
974
|
-
kxm update [harness] [--check | --kxm] [--self | --extensions | --models]
|
|
974
|
+
kxm update [harness] [--check | --kxm] [--all | --self | --extensions | --models]
|
|
975
975
|
```
|
|
976
976
|
|
|
977
977
|
Checks for or applies a KXM operator package update, and runs the native updaters of detected harnesses.
|
|
@@ -979,14 +979,15 @@ Checks for or applies a KXM operator package update, and runs the native updater
|
|
|
979
979
|
| Option | Argument | Default | Description |
|
|
980
980
|
|---|---|---|---|
|
|
981
981
|
| `--check` | none | off | Check for a kxm package update without applying |
|
|
982
|
-
| `--
|
|
983
|
-
| `--
|
|
982
|
+
| `--all` | none | off | Update harness CLIs first (`--self`), then kxm, extensions, and models (default for a bare `kxm update`) |
|
|
983
|
+
| `--kxm` | none | off | Apply only the kxm operator package update (npm by default, GitHub tarball when configured, or local files in this repo) |
|
|
984
|
+
| `--self` | none | off | Update only harness CLIs |
|
|
984
985
|
| `--extensions` | none | off | Update only extensions/plugins (Pi packages, Claude kxm) |
|
|
985
986
|
| `--models` | none | off | Refresh model catalogs where the harness supports it |
|
|
986
987
|
|
|
987
988
|
- Arguments: `[harness]`, Harness id (default: every detected harness).
|
|
988
|
-
- `--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).
|
|
989
|
-
- From a source
|
|
989
|
+
- `--check` cannot be combined with any other update flag or a harness (exit 2, `scope_conflict`), and at most one of `--all`, `--self`, `--extensions`, `--models` is allowed (exit 2).
|
|
990
|
+
- Default `source` is npm. Set `update.yaml` `source: github` for the release tarball. When the working directory is this repository (`@kontextmind/kxm` plus `.git`), `--check` reports `source: local` without a network fetch and `--kxm` runs `npm install --global --omit=peer <cwd>`. From a source-built binary used outside this repo, `--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 a registry or GitHub `--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`.
|
|
990
991
|
|
|
991
992
|
On a host with neither `gh` nor a working `kxm`, download and check it yourself:
|
|
992
993
|
|
|
@@ -1001,7 +1002,7 @@ npm install --global --omit=peer "./kxm-${version}.tgz"
|
|
|
1001
1002
|
```
|
|
1002
1003
|
|
|
1003
1004
|
- 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.
|
|
1004
|
-
- 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.
|
|
1005
|
+
- 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, except under `--all` (the default), where a failed harness updater sets `degraded: true` and exits 0.
|
|
1005
1006
|
- Honors `--dry-run`: steps are planned, not run, and the cached update notice is not refreshed.
|
|
1006
1007
|
- JSON keys: `--check` gives `current`, `available`, `auto`, `source`, `latest`, `message`, `installKind`, `root`; otherwise `dryRun`, `scope`, `notice`, `kxm` (when applying), `steps` (`harness`, `scope`, `command`, `args`, `outcome`, `detail`), `installKind`, `root`.
|
|
1007
1008
|
|
|
@@ -1010,7 +1011,7 @@ kxm update --check --dry-run --json
|
|
|
1010
1011
|
```
|
|
1011
1012
|
|
|
1012
1013
|
```text
|
|
1013
|
-
{"schema":"kxm.cli-result.v1","ok":true,"command":"update check","current":"0.7.1","available":false,"auto":false,"source":"
|
|
1014
|
+
{"schema":"kxm.cli-result.v1","ok":true,"command":"update check","current":"0.7.1","available":false,"auto":false,"source":"npm","installKind":"source","root":"/work/kxm","message":"kxm 0.7.1 (running from source at /work/kxm)"}
|
|
1014
1015
|
```
|
|
1015
1016
|
|
|
1016
1017
|
```bash
|
|
@@ -1656,14 +1656,14 @@ never mints a token or writes a file, and always exits 0. See the
|
|
|
1656
1656
|
|---|---|---|
|
|
1657
1657
|
| `schema` | `kxm.update.v1` | Required |
|
|
1658
1658
|
| `auto` | Boolean | Required |
|
|
1659
|
-
| `source` | `github` or `npm` | Optional, `
|
|
1659
|
+
| `source` | `github` or `npm` | Optional, `npm` |
|
|
1660
1660
|
|
|
1661
1661
|
Unknown fields are refused (`update.yaml unknown field <name>`).
|
|
1662
1662
|
|
|
1663
1663
|
```yaml
|
|
1664
1664
|
schema: kxm.update.v1
|
|
1665
1665
|
auto: false # required boolean
|
|
1666
|
-
source:
|
|
1666
|
+
source: npm # npm (default) | github
|
|
1667
1667
|
```
|
|
1668
1668
|
|
|
1669
1669
|
## Workspace layout: tracked, ignored, and state
|
package/docs/start/install.md
CHANGED
|
@@ -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
|
|
11
|
+
- The GitHub CLI (`gh`), only if `update.yaml` sets `source: github` so `kxm update --kxm` downloads the release tarball. 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
|
|
|
@@ -107,13 +107,13 @@ npm ci
|
|
|
107
107
|
node scripts/kxm.mjs --version
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
Use `node scripts/kxm.mjs` wherever the docs show `kxm`. It runs the CLI bundle committed in `plugins/kxm/dist/`. Update a checkout with `git pull` and `npm ci
|
|
110
|
+
Use `node scripts/kxm.mjs` wherever the docs show `kxm`. It runs the CLI bundle committed in `plugins/kxm/dist/`. Update a checkout with `git pull` and `npm ci`. From this repository, `kxm update --kxm` installs those local files globally. [Develop KXM](../contributing/development.md) covers building and testing.
|
|
111
111
|
|
|
112
112
|
## Update
|
|
113
113
|
|
|
114
114
|
| Part | Command |
|
|
115
115
|
|---|---|
|
|
116
|
-
| `kxm` CLI | `npm install --global --omit=peer @kontextmind/kxm@latest` (check first with `kxm update --check`) |
|
|
116
|
+
| `kxm` CLI | `kxm update --kxm` or `npm install --global --omit=peer @kontextmind/kxm@latest` (check first with `kxm update --check`) |
|
|
117
117
|
| Claude Code plugin | `claude plugin marketplace update kxm`, then uninstall and reinstall the plugin; `claude plugin update` never refreshes it |
|
|
118
118
|
| Pi package | `pi update --extensions` |
|
|
119
119
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontextmind/kxm",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.175",
|
|
4
4
|
"description": "KXM local-first multi-agent orchestration and operator dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "KontextMind",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test:core": "npm run build && node scripts/run-bounded.mjs 1200000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts",
|
|
38
38
|
"test:simulations": "npm run build && node scripts/run-bounded.mjs 1200000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/simulations/*.test.ts",
|
|
39
39
|
"test:complete": "npm run build && node scripts/run-bounded.mjs 2400000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts test/simulations/*.test.ts",
|
|
40
|
-
"test": "npm run build && node scripts/run-bounded.mjs
|
|
40
|
+
"test": "npm run build && node scripts/run-bounded.mjs 1800000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts packages/core/*/tests/unit/*.test.ts",
|
|
41
41
|
"test:ci-shard": "node scripts/ci-unit-shard.mjs",
|
|
42
42
|
"e2e": "node scripts/obscura.mjs --ensure && playwright test",
|
|
43
43
|
"test:coverage:core": "npm run build && node scripts/run-bounded.mjs 2400000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 --experimental-test-coverage --test-coverage-lines=91 --test-coverage-branches=80 --test-coverage-functions=92 --test-coverage-include=plugins/kxm/src/**/*.ts --test-coverage-exclude=plugins/kxm/src/server.ts --test-coverage-exclude=plugins/kxm/src/mcp-server.ts --test-coverage-exclude=plugins/kxm/src/runtime-supervisor.ts --test-coverage-include=packages/core/*/src/**/*.ts test/core/*.test.ts packages/core/*/tests/unit/*.test.ts",
|
|
@@ -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.
|
|
5
|
+
"version": "0.7.175",
|
|
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",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -19780,8 +19780,17 @@ function argsFor(entry, scope) {
|
|
|
19780
19780
|
if (scope === "extensions") return entry.update.extensions;
|
|
19781
19781
|
return entry.update.models;
|
|
19782
19782
|
}
|
|
19783
|
+
function detectedHarnesses(inventory, requestedHarness) {
|
|
19784
|
+
if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness);
|
|
19785
|
+
const detected = inventory.harnesses.filter((entry) => entry.detected);
|
|
19786
|
+
const preferred = inventory.defaultHarness;
|
|
19787
|
+
return [
|
|
19788
|
+
...detected.filter((entry) => entry.id === preferred),
|
|
19789
|
+
...detected.filter((entry) => entry.id !== preferred)
|
|
19790
|
+
];
|
|
19791
|
+
}
|
|
19783
19792
|
function planHarnessUpdate(inventory, requested) {
|
|
19784
|
-
const selected =
|
|
19793
|
+
const selected = detectedHarnesses(inventory, requested.harness);
|
|
19785
19794
|
if (requested.harness && selected.length === 0) {
|
|
19786
19795
|
return [{
|
|
19787
19796
|
harness: requested.harness,
|
|
@@ -19792,36 +19801,44 @@ function planHarnessUpdate(inventory, requested) {
|
|
|
19792
19801
|
detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness"
|
|
19793
19802
|
}];
|
|
19794
19803
|
}
|
|
19804
|
+
const waves = requested.scope === "all" ? ["self", "extensions", "models"] : [requested.scope];
|
|
19795
19805
|
const steps = [];
|
|
19796
|
-
for (const
|
|
19797
|
-
const
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19806
|
+
for (const scope of waves) {
|
|
19807
|
+
for (const status of selected) {
|
|
19808
|
+
const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
|
|
19809
|
+
if (!entry) continue;
|
|
19810
|
+
const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry);
|
|
19811
|
+
if (!supported.includes(scope)) {
|
|
19812
|
+
if (requested.scope !== "all") {
|
|
19813
|
+
steps.push({
|
|
19814
|
+
harness: status.id,
|
|
19815
|
+
scope,
|
|
19816
|
+
command: status.command ?? entry.commands[0],
|
|
19817
|
+
args: [],
|
|
19818
|
+
outcome: "skipped",
|
|
19819
|
+
detail: "no_updater"
|
|
19820
|
+
});
|
|
19821
|
+
}
|
|
19822
|
+
continue;
|
|
19823
|
+
}
|
|
19824
|
+
if (!status.detected || !status.command) {
|
|
19825
|
+
steps.push({
|
|
19826
|
+
harness: status.id,
|
|
19827
|
+
scope,
|
|
19828
|
+
command: entry.commands[0],
|
|
19829
|
+
args: argsFor(entry, scope) ?? [],
|
|
19830
|
+
outcome: "skipped",
|
|
19831
|
+
detail: "not_detected"
|
|
19832
|
+
});
|
|
19833
|
+
continue;
|
|
19834
|
+
}
|
|
19812
19835
|
steps.push({
|
|
19813
19836
|
harness: status.id,
|
|
19814
|
-
scope
|
|
19815
|
-
command:
|
|
19816
|
-
args: argsFor(entry,
|
|
19817
|
-
outcome: "
|
|
19818
|
-
detail: "not_detected"
|
|
19837
|
+
scope,
|
|
19838
|
+
command: status.command,
|
|
19839
|
+
args: argsFor(entry, scope) ?? [],
|
|
19840
|
+
outcome: "would"
|
|
19819
19841
|
});
|
|
19820
|
-
continue;
|
|
19821
|
-
}
|
|
19822
|
-
for (const scope of scopes) {
|
|
19823
|
-
const args = argsFor(entry, scope) ?? [];
|
|
19824
|
-
steps.push({ harness: status.id, scope, command: status.command, args, outcome: "would" });
|
|
19825
19842
|
}
|
|
19826
19843
|
}
|
|
19827
19844
|
return steps;
|
|
@@ -35526,7 +35543,7 @@ function compareSemver(left, right) {
|
|
|
35526
35543
|
}
|
|
35527
35544
|
function formatKxmUpdateNotice(notice) {
|
|
35528
35545
|
if (!notice.available) return notice.message;
|
|
35529
|
-
const apply = notice.auto ? "kxm update
|
|
35546
|
+
const apply = notice.auto ? "kxm update (auto)" : "kxm update";
|
|
35530
35547
|
return `kxm ${notice.current} \u2192 ${notice.latest} available \xB7 ${apply}`;
|
|
35531
35548
|
}
|
|
35532
35549
|
function withAsset(notice, asset) {
|
|
@@ -35644,7 +35661,11 @@ function verifyReleaseAssetDigest(path4, sha256) {
|
|
|
35644
35661
|
return false;
|
|
35645
35662
|
}
|
|
35646
35663
|
}
|
|
35647
|
-
function planKxmPackageUpdate(source, latest, releaseDir, asset, downloader = "gh") {
|
|
35664
|
+
function planKxmPackageUpdate(source, latest, releaseDir, asset, downloader = "gh", localRoot) {
|
|
35665
|
+
if (source === "local") {
|
|
35666
|
+
if (!localRoot) return [];
|
|
35667
|
+
return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", localRoot] }];
|
|
35668
|
+
}
|
|
35648
35669
|
if (source === "npm") {
|
|
35649
35670
|
return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", `@kontextmind/kxm@${latest}`] }];
|
|
35650
35671
|
}
|
|
@@ -51787,6 +51808,11 @@ init_hub_binding();
|
|
|
51787
51808
|
init_bindings();
|
|
51788
51809
|
import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
|
|
51789
51810
|
import { dirname as dirname21, join as join48 } from "node:path";
|
|
51811
|
+
function kxmSourceCheckoutRoot(dir) {
|
|
51812
|
+
if (!existsSync38(join48(dir, ".git"))) return void 0;
|
|
51813
|
+
if (!packageIsKxm(dir)) return void 0;
|
|
51814
|
+
return dir;
|
|
51815
|
+
}
|
|
51790
51816
|
function packageIsKxm(root) {
|
|
51791
51817
|
try {
|
|
51792
51818
|
const pkg = JSON.parse(readFileSync34(join48(root, "package.json"), "utf8"));
|
|
@@ -51872,7 +51898,7 @@ import { join as join49 } from "node:path";
|
|
|
51872
51898
|
init_bindings();
|
|
51873
51899
|
function loadKxmUpdateConfig(env = process.env) {
|
|
51874
51900
|
const path4 = join49(kxmUserStateRoot({ env }), "update.yaml");
|
|
51875
|
-
if (!existsSync39(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "
|
|
51901
|
+
if (!existsSync39(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "npm" };
|
|
51876
51902
|
let parsed;
|
|
51877
51903
|
try {
|
|
51878
51904
|
parsed = (0, import_yaml19.parse)(readFileSync35(path4, "utf8"));
|
|
@@ -51893,7 +51919,7 @@ function loadKxmUpdateConfig(env = process.env) {
|
|
|
51893
51919
|
if (typeof row.auto !== "boolean") {
|
|
51894
51920
|
throw new KxmUpdateConfigError("update.yaml auto must be a boolean");
|
|
51895
51921
|
}
|
|
51896
|
-
const source = row.source === void 0 ? "
|
|
51922
|
+
const source = row.source === void 0 ? "npm" : row.source;
|
|
51897
51923
|
if (source !== "npm" && source !== "github") {
|
|
51898
51924
|
throw new KxmUpdateConfigError("update.yaml source must be npm or github");
|
|
51899
51925
|
}
|
|
@@ -55090,7 +55116,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
55090
55116
|
const releaseDir = mkdtempSync3(join57(tmpdir3(), "kxm-pkg-update-"));
|
|
55091
55117
|
try {
|
|
55092
55118
|
const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
|
|
55093
|
-
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader);
|
|
55119
|
+
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
|
|
55094
55120
|
if (runtime.dryRun) {
|
|
55095
55121
|
return { ok: true, detail: planned.map(formatPackageUpdateStep).join(" && ") };
|
|
55096
55122
|
}
|
|
@@ -55216,22 +55242,34 @@ async function cmdSshClose(runtime, host) {
|
|
|
55216
55242
|
return 0;
|
|
55217
55243
|
}
|
|
55218
55244
|
async function cmdUpdate(runtime, harness, options) {
|
|
55219
|
-
if (options.check && (options.kxm || options.self || options.extensions || options.models || harness)) {
|
|
55245
|
+
if (options.check && (options.kxm || options.self || options.extensions || options.models || options.all || harness)) {
|
|
55220
55246
|
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "--check cannot be combined with other update flags");
|
|
55221
55247
|
return 2;
|
|
55222
55248
|
}
|
|
55223
|
-
const selected = [options.self, options.extensions, options.models].filter(Boolean).length;
|
|
55249
|
+
const selected = [options.self, options.extensions, options.models, options.all].filter(Boolean).length;
|
|
55224
55250
|
if (selected > 1) {
|
|
55225
|
-
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --self, --extensions, or --models");
|
|
55251
|
+
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --all, --self, --extensions, or --models");
|
|
55226
55252
|
return 2;
|
|
55227
55253
|
}
|
|
55228
55254
|
warnIgnoredProjectUpdateYaml(runtime);
|
|
55229
55255
|
const probe = installProbeFrom(runtime);
|
|
55230
55256
|
const classified = classifyInstallRoot(probe);
|
|
55231
55257
|
const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
|
|
55258
|
+
const localRoot = kxmSourceCheckoutRoot(runtime.dirs.workdir) ?? kxmSourceCheckoutRoot(runtime.cwd);
|
|
55232
55259
|
let notice;
|
|
55233
55260
|
let kindReport = classified;
|
|
55234
|
-
if (
|
|
55261
|
+
if (localRoot) {
|
|
55262
|
+
const localVersion = installedKxmVersion(localRoot) ?? current ?? "unknown";
|
|
55263
|
+
notice = {
|
|
55264
|
+
current: current ?? localVersion,
|
|
55265
|
+
latest: localVersion,
|
|
55266
|
+
available: true,
|
|
55267
|
+
auto: false,
|
|
55268
|
+
source: "local",
|
|
55269
|
+
message: `kxm ${current ?? localVersion} (local ${localRoot})`,
|
|
55270
|
+
localRoot
|
|
55271
|
+
};
|
|
55272
|
+
} else if (classified.kind === "source") {
|
|
55235
55273
|
if (options.check) {
|
|
55236
55274
|
const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
|
|
55237
55275
|
print(runtime.io, runtime.json, {
|
|
@@ -55240,7 +55278,7 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55240
55278
|
current: current ?? "unknown",
|
|
55241
55279
|
available: false,
|
|
55242
55280
|
auto: false,
|
|
55243
|
-
source: "
|
|
55281
|
+
source: "npm",
|
|
55244
55282
|
installKind: "source",
|
|
55245
55283
|
root: classified.root,
|
|
55246
55284
|
message
|
|
@@ -55262,7 +55300,7 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55262
55300
|
current: current ?? "unknown",
|
|
55263
55301
|
available: false,
|
|
55264
55302
|
auto: false,
|
|
55265
|
-
source: "
|
|
55303
|
+
source: "npm",
|
|
55266
55304
|
message: `kxm ${current ?? "unknown"} (running from source)`
|
|
55267
55305
|
};
|
|
55268
55306
|
} else {
|
|
@@ -55285,13 +55323,30 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55285
55323
|
}, notice.message);
|
|
55286
55324
|
return 0;
|
|
55287
55325
|
}
|
|
55288
|
-
const
|
|
55326
|
+
const fullUpdate = Boolean(options.all) || selected === 0 && !options.kxm && !harness;
|
|
55327
|
+
const applyKxm = Boolean(options.kxm || fullUpdate || notice.auto);
|
|
55289
55328
|
let kxmApply;
|
|
55290
|
-
|
|
55329
|
+
const applyKxmPackage = (requireSuccess) => {
|
|
55330
|
+
if (!applyKxm) return void 0;
|
|
55331
|
+
if (notice.source === "local" && notice.localRoot) {
|
|
55332
|
+
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
55333
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
55334
|
+
print(runtime.io, runtime.json, {
|
|
55335
|
+
ok: false,
|
|
55336
|
+
command: "update",
|
|
55337
|
+
error: kxmApply.error,
|
|
55338
|
+
kxm: kxmApply,
|
|
55339
|
+
notice,
|
|
55340
|
+
...installKindPayload(classified)
|
|
55341
|
+
}, kxmApply.detail);
|
|
55342
|
+
return 1;
|
|
55343
|
+
}
|
|
55344
|
+
return void 0;
|
|
55345
|
+
}
|
|
55291
55346
|
const resolved = resolveInstallKind(probe, npmGlobalRootFn(runtime));
|
|
55292
55347
|
kindReport = resolved;
|
|
55293
55348
|
if (resolved.kind !== "npm-global") {
|
|
55294
|
-
if (
|
|
55349
|
+
if (requireSuccess) {
|
|
55295
55350
|
print(runtime.io, runtime.json, {
|
|
55296
55351
|
ok: false,
|
|
55297
55352
|
command: "update",
|
|
@@ -55305,9 +55360,11 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55305
55360
|
}
|
|
55306
55361
|
if (notice.available) runtime.io.stderr(`kxm: ${resolved.instruction}
|
|
55307
55362
|
`);
|
|
55308
|
-
|
|
55363
|
+
return void 0;
|
|
55364
|
+
}
|
|
55365
|
+
if (notice.available) {
|
|
55309
55366
|
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
55310
|
-
if (!kxmApply.ok &&
|
|
55367
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
55311
55368
|
print(runtime.io, runtime.json, {
|
|
55312
55369
|
ok: false,
|
|
55313
55370
|
command: "update",
|
|
@@ -55319,9 +55376,12 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55319
55376
|
return 1;
|
|
55320
55377
|
}
|
|
55321
55378
|
}
|
|
55322
|
-
|
|
55323
|
-
|
|
55379
|
+
return void 0;
|
|
55380
|
+
};
|
|
55381
|
+
const skipHarness = Boolean(options.kxm && !fullUpdate && selected === 0 && !harness && !notice.auto);
|
|
55324
55382
|
if (skipHarness) {
|
|
55383
|
+
const aborted = applyKxmPackage(true);
|
|
55384
|
+
if (aborted !== void 0) return aborted;
|
|
55325
55385
|
print(
|
|
55326
55386
|
runtime.io,
|
|
55327
55387
|
runtime.json,
|
|
@@ -55332,23 +55392,39 @@ async function cmdUpdate(runtime, harness, options) {
|
|
|
55332
55392
|
}
|
|
55333
55393
|
const scope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
|
|
55334
55394
|
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
55335
|
-
const planned = planHarnessUpdate(inventory, { ...harness ? { harness } : {}, scope });
|
|
55336
|
-
const
|
|
55337
|
-
|
|
55395
|
+
const planned = planHarnessUpdate(inventory, { ...harness ? { harness } : {}, scope: fullUpdate ? "all" : scope });
|
|
55396
|
+
const runOpts = { env: runtime.env, dryRun: runtime.dryRun };
|
|
55397
|
+
let steps;
|
|
55398
|
+
if (fullUpdate) {
|
|
55399
|
+
const selfSteps = runHarnessUpdate(planned.filter((step) => step.scope === "self"), runOpts);
|
|
55400
|
+
applyKxmPackage(false);
|
|
55401
|
+
const restSteps = runHarnessUpdate(planned.filter((step) => step.scope !== "self"), runOpts);
|
|
55402
|
+
steps = [...selfSteps, ...restSteps];
|
|
55403
|
+
} else {
|
|
55404
|
+
const aborted = applyKxmPackage(Boolean(options.kxm));
|
|
55405
|
+
if (aborted !== void 0) return aborted;
|
|
55406
|
+
steps = runHarnessUpdate(planned, runOpts);
|
|
55407
|
+
}
|
|
55408
|
+
const kxmFailed = kxmApply?.ok === false;
|
|
55338
55409
|
const skippedUnknown = steps.some((step) => step.detail === "unknown_harness");
|
|
55410
|
+
const harnessFailed = steps.some((step) => step.outcome === "failed");
|
|
55339
55411
|
const text = [notice.available ? notice.message : void 0, kxmApply?.detail, formatHarnessUpdate(steps)].filter(Boolean).join("\n");
|
|
55340
55412
|
print(runtime.io, runtime.json, {
|
|
55341
|
-
ok: !
|
|
55413
|
+
ok: !kxmFailed && !skippedUnknown,
|
|
55342
55414
|
command: "update",
|
|
55343
55415
|
dryRun: runtime.dryRun,
|
|
55344
|
-
scope,
|
|
55416
|
+
scope: fullUpdate ? "all" : scope,
|
|
55417
|
+
defaultHarness: inventory.defaultHarness,
|
|
55345
55418
|
notice,
|
|
55346
55419
|
...kxmApply ? { kxm: kxmApply } : {},
|
|
55347
55420
|
steps,
|
|
55421
|
+
...harnessFailed ? { degraded: true } : {},
|
|
55348
55422
|
...installKindPayload(kindReport)
|
|
55349
55423
|
}, text);
|
|
55350
55424
|
if (skippedUnknown) return 2;
|
|
55351
|
-
|
|
55425
|
+
if (kxmFailed) return 1;
|
|
55426
|
+
if (harnessFailed && !fullUpdate) return 1;
|
|
55427
|
+
return 0;
|
|
55352
55428
|
}
|
|
55353
55429
|
async function cmdValidate(runtime, fileFlag) {
|
|
55354
55430
|
const worker = gateOf(runtime, "validate");
|
|
@@ -56420,7 +56496,7 @@ function createProgram(ctx, result, argv) {
|
|
|
56420
56496
|
addGlobalOptions(authCmd.command("token").description("Inspect, issue, or clear local disk session tokens")).option("--status", "Check status of the active session token").option("--clear", "Clear persisted disk session token").option("--issue", "Force issuing a fresh session token").action(async function authTokenAction(options) {
|
|
56421
56497
|
result.code = await cmdAuthToken(runtimeFrom(ctx, this), options);
|
|
56422
56498
|
});
|
|
56423
|
-
addGlobalOptions(program2.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs").argument("[harness]", "Harness id (default: every detected harness)").option("--check", "Check for a kxm package update without applying").option("--kxm", "Apply the kxm operator package update
|
|
56499
|
+
addGlobalOptions(program2.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs").argument("[harness]", "Harness id (default: every detected harness)").option("--check", "Check for a kxm package update without applying").option("--all", "Update harness CLIs first (--self), then kxm, extensions, and models (default)").option("--kxm", "Apply only the kxm operator package update").option("--self", "Update only harness CLIs").option("--extensions", "Update only extensions/plugins (Pi packages, Claude kxm)").option("--models", "Refresh model catalogs where the harness supports it")).action(async function updateAction(harness, options) {
|
|
56424
56500
|
result.code = await cmdUpdate(runtimeFrom(ctx, this), harness, options);
|
|
56425
56501
|
});
|
|
56426
56502
|
const runtimeCmd = addGlobalOptions(program2.command("runtime").description("Manage the KXM Runtime supervisor"));
|
|
@@ -25438,7 +25438,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25438
25438
|
}
|
|
25439
25439
|
|
|
25440
25440
|
// plugins/kxm/src/mcp-server.ts
|
|
25441
|
-
var VERSION = "0.7.
|
|
25441
|
+
var VERSION = "0.7.175";
|
|
25442
25442
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25443
25443
|
var inbox = /* @__PURE__ */ new Map();
|
|
25444
25444
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
@@ -17298,8 +17298,17 @@ function argsFor(entry, scope) {
|
|
|
17298
17298
|
if (scope === "extensions") return entry.update.extensions;
|
|
17299
17299
|
return entry.update.models;
|
|
17300
17300
|
}
|
|
17301
|
+
function detectedHarnesses(inventory, requestedHarness) {
|
|
17302
|
+
if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness);
|
|
17303
|
+
const detected = inventory.harnesses.filter((entry) => entry.detected);
|
|
17304
|
+
const preferred = inventory.defaultHarness;
|
|
17305
|
+
return [
|
|
17306
|
+
...detected.filter((entry) => entry.id === preferred),
|
|
17307
|
+
...detected.filter((entry) => entry.id !== preferred)
|
|
17308
|
+
];
|
|
17309
|
+
}
|
|
17301
17310
|
function planHarnessUpdate(inventory, requested) {
|
|
17302
|
-
const selected =
|
|
17311
|
+
const selected = detectedHarnesses(inventory, requested.harness);
|
|
17303
17312
|
if (requested.harness && selected.length === 0) {
|
|
17304
17313
|
return [{
|
|
17305
17314
|
harness: requested.harness,
|
|
@@ -17310,36 +17319,44 @@ function planHarnessUpdate(inventory, requested) {
|
|
|
17310
17319
|
detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness"
|
|
17311
17320
|
}];
|
|
17312
17321
|
}
|
|
17322
|
+
const waves = requested.scope === "all" ? ["self", "extensions", "models"] : [requested.scope];
|
|
17313
17323
|
const steps = [];
|
|
17314
|
-
for (const
|
|
17315
|
-
const
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17324
|
+
for (const scope of waves) {
|
|
17325
|
+
for (const status of selected) {
|
|
17326
|
+
const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
|
|
17327
|
+
if (!entry) continue;
|
|
17328
|
+
const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry);
|
|
17329
|
+
if (!supported.includes(scope)) {
|
|
17330
|
+
if (requested.scope !== "all") {
|
|
17331
|
+
steps.push({
|
|
17332
|
+
harness: status.id,
|
|
17333
|
+
scope,
|
|
17334
|
+
command: status.command ?? entry.commands[0],
|
|
17335
|
+
args: [],
|
|
17336
|
+
outcome: "skipped",
|
|
17337
|
+
detail: "no_updater"
|
|
17338
|
+
});
|
|
17339
|
+
}
|
|
17340
|
+
continue;
|
|
17341
|
+
}
|
|
17342
|
+
if (!status.detected || !status.command) {
|
|
17343
|
+
steps.push({
|
|
17344
|
+
harness: status.id,
|
|
17345
|
+
scope,
|
|
17346
|
+
command: entry.commands[0],
|
|
17347
|
+
args: argsFor(entry, scope) ?? [],
|
|
17348
|
+
outcome: "skipped",
|
|
17349
|
+
detail: "not_detected"
|
|
17350
|
+
});
|
|
17351
|
+
continue;
|
|
17352
|
+
}
|
|
17330
17353
|
steps.push({
|
|
17331
17354
|
harness: status.id,
|
|
17332
|
-
scope
|
|
17333
|
-
command:
|
|
17334
|
-
args: argsFor(entry,
|
|
17335
|
-
outcome: "
|
|
17336
|
-
detail: "not_detected"
|
|
17355
|
+
scope,
|
|
17356
|
+
command: status.command,
|
|
17357
|
+
args: argsFor(entry, scope) ?? [],
|
|
17358
|
+
outcome: "would"
|
|
17337
17359
|
});
|
|
17338
|
-
continue;
|
|
17339
|
-
}
|
|
17340
|
-
for (const scope of scopes) {
|
|
17341
|
-
const args = argsFor(entry, scope) ?? [];
|
|
17342
|
-
steps.push({ harness: status.id, scope, command: status.command, args, outcome: "would" });
|
|
17343
17360
|
}
|
|
17344
17361
|
}
|
|
17345
17362
|
return steps;
|
package/plugins/kxm/package.json
CHANGED
|
@@ -34,15 +34,20 @@ still blocks the tools.
|
|
|
34
34
|
| Command | Purpose |
|
|
35
35
|
|---|---|
|
|
36
36
|
| `kxm update --check` | Check for a kxm package update without applying it |
|
|
37
|
-
| `kxm update
|
|
37
|
+
| `kxm update` / `--all` | Harness `--self` first, then kxm, extensions, and models |
|
|
38
|
+
| `kxm update --kxm` | Apply only the kxm package update |
|
|
38
39
|
| `kxm update [harness] --self` | Update only the harness CLI |
|
|
39
40
|
| `kxm update [harness] --extensions` | Update only extensions and plugins (Pi packages, the Claude kxm plugin) |
|
|
40
41
|
| `kxm update [harness] --models` | Refresh model catalogs where the harness supports it |
|
|
41
42
|
|
|
42
|
-
- `kxm update
|
|
43
|
-
|
|
43
|
+
- Bare `kxm update` (and `--all`) updates detected harness CLIs first, then kxm,
|
|
44
|
+
then extensions and models. `--kxm` is kxm-only. kxm installs from npm by default;
|
|
45
|
+
it needs an authenticated `gh` only when `update.yaml` sets `source: github`.
|
|
46
|
+
It uses `gh` when that CLI answers `gh --version`, and otherwise curl with the same
|
|
44
47
|
sha256 check. A host with neither can follow the curl commands in
|
|
45
48
|
docs/operations/upgrade.md.
|
|
49
|
+
Inside the kxm repository it installs the local files. Detected harness updater
|
|
50
|
+
failures on `--all` are degraded, not fatal.
|
|
46
51
|
- `kxm update claude --extensions` updates only a user-scope plugin install;
|
|
47
52
|
its dry run prints `claude extensions: would claude plugin update kxm -y`.
|
|
48
53
|
A project-scope install is the operator's `claude plugin update kxm@kxm --scope project`.
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
} from "../init-guide-setup.ts";
|
|
69
69
|
import {
|
|
70
70
|
classifyInstallRoot,
|
|
71
|
+
kxmSourceCheckoutRoot,
|
|
71
72
|
resolveInstallKind,
|
|
72
73
|
type InstallKindReport,
|
|
73
74
|
type InstallProbe,
|
|
@@ -161,7 +162,7 @@ export function applyKxmPackageUpdate(runtime: Runtime, notice: KxmUpdateNotice)
|
|
|
161
162
|
const releaseDir = mkdtempSync(join(tmpdir(), "kxm-pkg-update-"));
|
|
162
163
|
try {
|
|
163
164
|
const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
|
|
164
|
-
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader);
|
|
165
|
+
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
|
|
165
166
|
if (runtime.dryRun) {
|
|
166
167
|
return { ok: true, detail: planned.map(formatPackageUpdateStep).join(" && ") };
|
|
167
168
|
}
|
|
@@ -309,14 +310,15 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
309
310
|
models?: boolean | undefined;
|
|
310
311
|
check?: boolean | undefined;
|
|
311
312
|
kxm?: boolean | undefined;
|
|
313
|
+
all?: boolean | undefined;
|
|
312
314
|
}): Promise<number> {
|
|
313
|
-
if (options.check && (options.kxm || options.self || options.extensions || options.models || harness)) {
|
|
315
|
+
if (options.check && (options.kxm || options.self || options.extensions || options.models || options.all || harness)) {
|
|
314
316
|
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "--check cannot be combined with other update flags");
|
|
315
317
|
return 2;
|
|
316
318
|
}
|
|
317
|
-
const selected = [options.self, options.extensions, options.models].filter(Boolean).length;
|
|
319
|
+
const selected = [options.self, options.extensions, options.models, options.all].filter(Boolean).length;
|
|
318
320
|
if (selected > 1) {
|
|
319
|
-
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --self, --extensions, or --models");
|
|
321
|
+
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --all, --self, --extensions, or --models");
|
|
320
322
|
return 2;
|
|
321
323
|
}
|
|
322
324
|
warnIgnoredProjectUpdateYaml(runtime);
|
|
@@ -328,10 +330,24 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
328
330
|
// `cd ~ && kxm update --kxm` into an uncaught ENOENT on `$HOME/package.json`,
|
|
329
331
|
// and would happily compare a stranger project's version wherever one did
|
|
330
332
|
// exist. Unreadable is now a stated refusal.
|
|
333
|
+
// Exception: a cwd that *is* this repository (`@kontextmind/kxm` plus `.git`)
|
|
334
|
+
// is a local-files apply, not a stranger version compare.
|
|
331
335
|
const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
|
|
336
|
+
const localRoot = kxmSourceCheckoutRoot(runtime.dirs.workdir) ?? kxmSourceCheckoutRoot(runtime.cwd);
|
|
332
337
|
let notice: KxmUpdateNotice;
|
|
333
338
|
let kindReport = classified;
|
|
334
|
-
if (
|
|
339
|
+
if (localRoot) {
|
|
340
|
+
const localVersion = installedKxmVersion(localRoot) ?? current ?? "unknown";
|
|
341
|
+
notice = {
|
|
342
|
+
current: current ?? localVersion,
|
|
343
|
+
latest: localVersion,
|
|
344
|
+
available: true,
|
|
345
|
+
auto: false,
|
|
346
|
+
source: "local",
|
|
347
|
+
message: `kxm ${current ?? localVersion} (local ${localRoot})`,
|
|
348
|
+
localRoot,
|
|
349
|
+
};
|
|
350
|
+
} else if (classified.kind === "source") {
|
|
335
351
|
if (options.check) {
|
|
336
352
|
const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
|
|
337
353
|
print(runtime.io, runtime.json, {
|
|
@@ -340,7 +356,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
340
356
|
current: current ?? "unknown",
|
|
341
357
|
available: false,
|
|
342
358
|
auto: false,
|
|
343
|
-
source: "
|
|
359
|
+
source: "npm",
|
|
344
360
|
installKind: "source",
|
|
345
361
|
root: classified.root,
|
|
346
362
|
message,
|
|
@@ -362,7 +378,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
362
378
|
current: current ?? "unknown",
|
|
363
379
|
available: false,
|
|
364
380
|
auto: false,
|
|
365
|
-
source: "
|
|
381
|
+
source: "npm",
|
|
366
382
|
message: `kxm ${current ?? "unknown"} (running from source)`,
|
|
367
383
|
};
|
|
368
384
|
} else {
|
|
@@ -385,13 +401,30 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
385
401
|
}, notice.message);
|
|
386
402
|
return 0;
|
|
387
403
|
}
|
|
388
|
-
const
|
|
404
|
+
const fullUpdate = Boolean(options.all) || (selected === 0 && !options.kxm && !harness);
|
|
405
|
+
const applyKxm = Boolean(options.kxm || fullUpdate || notice.auto);
|
|
389
406
|
let kxmApply: { ok: boolean; detail: string; error?: string } | undefined;
|
|
390
|
-
|
|
407
|
+
const applyKxmPackage = (requireSuccess: boolean): number | undefined => {
|
|
408
|
+
if (!applyKxm) return undefined;
|
|
409
|
+
if (notice.source === "local" && notice.localRoot) {
|
|
410
|
+
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
411
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
412
|
+
print(runtime.io, runtime.json, {
|
|
413
|
+
ok: false,
|
|
414
|
+
command: "update",
|
|
415
|
+
error: kxmApply.error,
|
|
416
|
+
kxm: kxmApply,
|
|
417
|
+
notice,
|
|
418
|
+
...installKindPayload(classified),
|
|
419
|
+
}, kxmApply.detail);
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
391
424
|
const resolved = resolveInstallKind(probe, npmGlobalRootFn(runtime));
|
|
392
425
|
kindReport = resolved;
|
|
393
426
|
if (resolved.kind !== "npm-global") {
|
|
394
|
-
if (
|
|
427
|
+
if (requireSuccess) {
|
|
395
428
|
print(runtime.io, runtime.json, {
|
|
396
429
|
ok: false,
|
|
397
430
|
command: "update",
|
|
@@ -404,9 +437,11 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
404
437
|
return 2;
|
|
405
438
|
}
|
|
406
439
|
if (notice.available) runtime.io.stderr(`kxm: ${resolved.instruction}\n`);
|
|
407
|
-
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
if (notice.available) {
|
|
408
443
|
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
409
|
-
if (!kxmApply.ok &&
|
|
444
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
410
445
|
print(runtime.io, runtime.json, {
|
|
411
446
|
ok: false,
|
|
412
447
|
command: "update",
|
|
@@ -418,9 +453,12 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
418
453
|
return 1;
|
|
419
454
|
}
|
|
420
455
|
}
|
|
421
|
-
|
|
422
|
-
|
|
456
|
+
return undefined;
|
|
457
|
+
};
|
|
458
|
+
const skipHarness = Boolean(options.kxm && !fullUpdate && selected === 0 && !harness && !notice.auto);
|
|
423
459
|
if (skipHarness) {
|
|
460
|
+
const aborted = applyKxmPackage(true);
|
|
461
|
+
if (aborted !== undefined) return aborted;
|
|
424
462
|
print(
|
|
425
463
|
runtime.io,
|
|
426
464
|
runtime.json,
|
|
@@ -431,23 +469,39 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
431
469
|
}
|
|
432
470
|
const scope: HarnessUpdateScope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
|
|
433
471
|
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
434
|
-
const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope });
|
|
435
|
-
const
|
|
436
|
-
|
|
472
|
+
const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope: fullUpdate ? "all" : scope });
|
|
473
|
+
const runOpts = { env: runtime.env, dryRun: runtime.dryRun };
|
|
474
|
+
let steps;
|
|
475
|
+
if (fullUpdate) {
|
|
476
|
+
const selfSteps = runHarnessUpdate(planned.filter((step) => step.scope === "self"), runOpts);
|
|
477
|
+
applyKxmPackage(false);
|
|
478
|
+
const restSteps = runHarnessUpdate(planned.filter((step) => step.scope !== "self"), runOpts);
|
|
479
|
+
steps = [...selfSteps, ...restSteps];
|
|
480
|
+
} else {
|
|
481
|
+
const aborted = applyKxmPackage(Boolean(options.kxm));
|
|
482
|
+
if (aborted !== undefined) return aborted;
|
|
483
|
+
steps = runHarnessUpdate(planned, runOpts);
|
|
484
|
+
}
|
|
485
|
+
const kxmFailed = kxmApply?.ok === false;
|
|
437
486
|
const skippedUnknown = steps.some((step) => step.detail === "unknown_harness");
|
|
487
|
+
const harnessFailed = steps.some((step) => step.outcome === "failed");
|
|
438
488
|
const text = [notice.available ? notice.message : undefined, kxmApply?.detail, formatHarnessUpdate(steps)].filter(Boolean).join("\n");
|
|
439
489
|
print(runtime.io, runtime.json, {
|
|
440
|
-
ok: !
|
|
490
|
+
ok: !kxmFailed && !skippedUnknown,
|
|
441
491
|
command: "update",
|
|
442
492
|
dryRun: runtime.dryRun,
|
|
443
|
-
scope,
|
|
493
|
+
scope: fullUpdate ? "all" : scope,
|
|
494
|
+
defaultHarness: inventory.defaultHarness,
|
|
444
495
|
notice,
|
|
445
496
|
...(kxmApply ? { kxm: kxmApply } : {}),
|
|
446
497
|
steps,
|
|
498
|
+
...(harnessFailed ? { degraded: true } : {}),
|
|
447
499
|
...installKindPayload(kindReport),
|
|
448
500
|
}, text);
|
|
449
501
|
if (skippedUnknown) return 2;
|
|
450
|
-
|
|
502
|
+
if (kxmFailed) return 1;
|
|
503
|
+
if (harnessFailed && !fullUpdate) return 1;
|
|
504
|
+
return 0;
|
|
451
505
|
}
|
|
452
506
|
|
|
453
507
|
export async function cmdValidate(runtime: Runtime, fileFlag?: string | undefined): Promise<number> {
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -788,8 +788,9 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
788
788
|
addGlobalOptions(program.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs")
|
|
789
789
|
.argument("[harness]", "Harness id (default: every detected harness)")
|
|
790
790
|
.option("--check", "Check for a kxm package update without applying")
|
|
791
|
-
.option("--
|
|
792
|
-
.option("--
|
|
791
|
+
.option("--all", "Update harness CLIs first (--self), then kxm, extensions, and models (default)")
|
|
792
|
+
.option("--kxm", "Apply only the kxm operator package update")
|
|
793
|
+
.option("--self", "Update only harness CLIs")
|
|
793
794
|
.option("--extensions", "Update only extensions/plugins (Pi packages, Claude kxm)")
|
|
794
795
|
.option("--models", "Refresh model catalogs where the harness supports it"))
|
|
795
796
|
.action(async function updateAction(this: Command, harness: string | undefined, options: {
|
|
@@ -798,6 +799,7 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
798
799
|
models?: boolean;
|
|
799
800
|
check?: boolean;
|
|
800
801
|
kxm?: boolean;
|
|
802
|
+
all?: boolean;
|
|
801
803
|
}) {
|
|
802
804
|
result.code = await cmdUpdate(runtimeFrom(ctx, this), harness, options);
|
|
803
805
|
});
|
|
@@ -1449,13 +1449,24 @@ function argsFor(entry: HarnessCatalogEntry, scope: Exclude<HarnessUpdateScope,
|
|
|
1449
1449
|
return entry.update.models;
|
|
1450
1450
|
}
|
|
1451
1451
|
|
|
1452
|
+
function detectedHarnesses(
|
|
1453
|
+
inventory: HarnessInventory,
|
|
1454
|
+
requestedHarness?: string,
|
|
1455
|
+
): HarnessStatus[] {
|
|
1456
|
+
if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness);
|
|
1457
|
+
const detected = inventory.harnesses.filter((entry) => entry.detected);
|
|
1458
|
+
const preferred = inventory.defaultHarness;
|
|
1459
|
+
return [
|
|
1460
|
+
...detected.filter((entry) => entry.id === preferred),
|
|
1461
|
+
...detected.filter((entry) => entry.id !== preferred),
|
|
1462
|
+
];
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1452
1465
|
export function planHarnessUpdate(
|
|
1453
1466
|
inventory: HarnessInventory,
|
|
1454
1467
|
requested: { harness?: string; scope: HarnessUpdateScope },
|
|
1455
1468
|
): HarnessUpdateStep[] {
|
|
1456
|
-
const selected = requested.harness
|
|
1457
|
-
? inventory.harnesses.filter((entry) => entry.id === requested.harness)
|
|
1458
|
-
: inventory.harnesses.filter((entry) => entry.detected);
|
|
1469
|
+
const selected = detectedHarnesses(inventory, requested.harness);
|
|
1459
1470
|
if (requested.harness && selected.length === 0) {
|
|
1460
1471
|
return [{
|
|
1461
1472
|
harness: requested.harness,
|
|
@@ -1466,36 +1477,46 @@ export function planHarnessUpdate(
|
|
|
1466
1477
|
detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness",
|
|
1467
1478
|
}];
|
|
1468
1479
|
}
|
|
1480
|
+
const waves: Exclude<HarnessUpdateScope, "all">[] = requested.scope === "all"
|
|
1481
|
+
? ["self", "extensions", "models"]
|
|
1482
|
+
: [requested.scope];
|
|
1469
1483
|
const steps: HarnessUpdateStep[] = [];
|
|
1470
|
-
for (const
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1484
|
+
for (const scope of waves) {
|
|
1485
|
+
for (const status of selected) {
|
|
1486
|
+
const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
|
|
1487
|
+
if (!entry) continue;
|
|
1488
|
+
const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry);
|
|
1489
|
+
if (!supported.includes(scope)) {
|
|
1490
|
+
if (requested.scope !== "all") {
|
|
1491
|
+
steps.push({
|
|
1492
|
+
harness: status.id,
|
|
1493
|
+
scope,
|
|
1494
|
+
command: status.command ?? entry.commands[0]!,
|
|
1495
|
+
args: [],
|
|
1496
|
+
outcome: "skipped",
|
|
1497
|
+
detail: "no_updater",
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
if (!status.detected || !status.command) {
|
|
1503
|
+
steps.push({
|
|
1504
|
+
harness: status.id,
|
|
1505
|
+
scope,
|
|
1506
|
+
command: entry.commands[0]!,
|
|
1507
|
+
args: argsFor(entry, scope) ?? [],
|
|
1508
|
+
outcome: "skipped",
|
|
1509
|
+
detail: "not_detected",
|
|
1510
|
+
});
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1486
1513
|
steps.push({
|
|
1487
1514
|
harness: status.id,
|
|
1488
|
-
scope
|
|
1489
|
-
command:
|
|
1490
|
-
args: argsFor(entry,
|
|
1491
|
-
outcome: "
|
|
1492
|
-
detail: "not_detected",
|
|
1515
|
+
scope,
|
|
1516
|
+
command: status.command,
|
|
1517
|
+
args: argsFor(entry, scope) ?? [],
|
|
1518
|
+
outcome: "would",
|
|
1493
1519
|
});
|
|
1494
|
-
continue;
|
|
1495
|
-
}
|
|
1496
|
-
for (const scope of scopes) {
|
|
1497
|
-
const args = argsFor(entry, scope) ?? [];
|
|
1498
|
-
steps.push({ harness: status.id, scope, command: status.command, args, outcome: "would" });
|
|
1499
1520
|
}
|
|
1500
1521
|
}
|
|
1501
1522
|
return steps;
|
|
@@ -19,6 +19,12 @@ export interface InstallKindReport {
|
|
|
19
19
|
instruction: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export function kxmSourceCheckoutRoot(dir: string): string | undefined {
|
|
23
|
+
if (!existsSync(join(dir, ".git"))) return undefined;
|
|
24
|
+
if (!packageIsKxm(dir)) return undefined;
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
function packageIsKxm(root: string): boolean {
|
|
23
29
|
try {
|
|
24
30
|
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { name?: unknown };
|
|
@@ -10,7 +10,7 @@ import { kxmUserStateRoot } from "./bindings.ts";
|
|
|
10
10
|
|
|
11
11
|
export function loadKxmUpdateConfig(env: NodeJS.ProcessEnv = process.env): KxmUpdateConfig {
|
|
12
12
|
const path = join(kxmUserStateRoot({ env }), "update.yaml");
|
|
13
|
-
if (!existsSync(path)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "
|
|
13
|
+
if (!existsSync(path)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "npm" };
|
|
14
14
|
let parsed: unknown;
|
|
15
15
|
try {
|
|
16
16
|
parsed = parseYaml(readFileSync(path, "utf8"));
|
|
@@ -31,7 +31,7 @@ export function loadKxmUpdateConfig(env: NodeJS.ProcessEnv = process.env): KxmUp
|
|
|
31
31
|
if (typeof row.auto !== "boolean") {
|
|
32
32
|
throw new KxmUpdateConfigError("update.yaml auto must be a boolean");
|
|
33
33
|
}
|
|
34
|
-
const source = row.source === undefined ? "
|
|
34
|
+
const source = row.source === undefined ? "npm" : row.source;
|
|
35
35
|
if (source !== "npm" && source !== "github") {
|
|
36
36
|
throw new KxmUpdateConfigError("update.yaml source must be npm or github");
|
|
37
37
|
}
|
|
@@ -10,6 +10,7 @@ const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
|
10
10
|
const SHA256_DIGEST = /^sha256:([0-9a-fA-F]{64})$/;
|
|
11
11
|
|
|
12
12
|
export type KxmUpdateSource = "npm" | "github";
|
|
13
|
+
export type KxmApplySource = KxmUpdateSource | "local";
|
|
13
14
|
|
|
14
15
|
export interface KxmReleaseAsset {
|
|
15
16
|
name: string;
|
|
@@ -27,9 +28,10 @@ export interface KxmUpdateNotice {
|
|
|
27
28
|
latest?: string;
|
|
28
29
|
available: boolean;
|
|
29
30
|
auto: boolean;
|
|
30
|
-
source:
|
|
31
|
+
source: KxmApplySource;
|
|
31
32
|
message: string;
|
|
32
33
|
asset?: KxmReleaseAsset;
|
|
34
|
+
localRoot?: string;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export class KxmUpdateConfigError extends Error {
|
|
@@ -100,7 +102,7 @@ export function compareSemver(left: string, right: string): number | undefined {
|
|
|
100
102
|
|
|
101
103
|
export function formatKxmUpdateNotice(notice: KxmUpdateNotice): string {
|
|
102
104
|
if (!notice.available) return notice.message;
|
|
103
|
-
const apply = notice.auto ? "kxm update
|
|
105
|
+
const apply = notice.auto ? "kxm update (auto)" : "kxm update";
|
|
104
106
|
return `kxm ${notice.current} → ${notice.latest} available · ${apply}`;
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -243,14 +245,19 @@ export function verifyReleaseAssetDigest(path: string, sha256: string): boolean
|
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
/** npm
|
|
248
|
+
/** npm is the default source and npm verifies registry integrity itself. "local" installs this repository's checkout. "github" downloads the release tarball (gh, or curl without gh) and verifies its sha256. */
|
|
247
249
|
export function planKxmPackageUpdate(
|
|
248
|
-
source:
|
|
250
|
+
source: KxmApplySource,
|
|
249
251
|
latest: string,
|
|
250
252
|
releaseDir: string,
|
|
251
253
|
asset?: KxmReleaseAsset,
|
|
252
254
|
downloader: "gh" | "curl" = "gh",
|
|
255
|
+
localRoot?: string,
|
|
253
256
|
): KxmPackageUpdateStep[] {
|
|
257
|
+
if (source === "local") {
|
|
258
|
+
if (!localRoot) return [];
|
|
259
|
+
return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", localRoot] }];
|
|
260
|
+
}
|
|
254
261
|
if (source === "npm") {
|
|
255
262
|
return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", `@kontextmind/kxm@${latest}`] }];
|
|
256
263
|
}
|
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.175";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -14,6 +14,7 @@ import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "nod
|
|
|
14
14
|
import { homedir, tmpdir } from "node:os";
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
16
|
|
|
17
|
+
import { npmPackArtifacts } from "./npm-pack-json.mjs";
|
|
17
18
|
const repoRoot = resolve(import.meta.dirname, "..");
|
|
18
19
|
|
|
19
20
|
const vault = process.env.KXM_SMOKE_VAULT ?? "AI Provider Keys";
|
|
@@ -92,7 +93,7 @@ function main() {
|
|
|
92
93
|
// commit gate (`npm run verify` -> check:generated) enforces that.
|
|
93
94
|
process.stdout.write("==> npm pack\n");
|
|
94
95
|
const packed = run("npm", ["pack", "--pack-destination", scratch, "--json"], { cwd: repoRoot });
|
|
95
|
-
const filename =
|
|
96
|
+
const filename = npmPackArtifacts(packed.stdout)[0]?.filename;
|
|
96
97
|
if (!filename) die("npm pack produced no tarball");
|
|
97
98
|
const tarball = join(scratch, filename);
|
|
98
99
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npm pack --json` is a one-element array through npm 11. npm 12 returns
|
|
3
|
+
* one object keyed by package name. Both shapes carry `filename` and `files`.
|
|
4
|
+
*/
|
|
5
|
+
export function npmPackArtifacts(stdout) {
|
|
6
|
+
const parsed = JSON.parse(stdout);
|
|
7
|
+
if (Array.isArray(parsed)) return parsed;
|
|
8
|
+
if (parsed && typeof parsed === "object") {
|
|
9
|
+
const values = Object.values(parsed);
|
|
10
|
+
if (values.length > 0 && values.every((value) => value && typeof value === "object" && !Array.isArray(value))) {
|
|
11
|
+
return values;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`unexpected npm pack JSON: ${String(stdout).slice(0, 200)}`);
|
|
15
|
+
}
|
package/scripts/pr-land.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
11
|
+
import { delimiter, join, resolve } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
|
|
14
14
|
const STAGES = Object.freeze([
|
|
@@ -67,13 +67,55 @@ function childEnv() {
|
|
|
67
67
|
return env;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function pathValue(env) {
|
|
71
|
+
const key = Object.keys(env).find((name) => name.toUpperCase() === "PATH");
|
|
72
|
+
return key ? env[key] : "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function comSpec(env) {
|
|
76
|
+
const key = Object.keys(env).find((name) => name.toUpperCase() === "COMSPEC");
|
|
77
|
+
return (key && env[key].trim()) || "cmd.exe";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** CreateProcess does not run `npm.cmd`. Prefer a real `.exe`; otherwise launch the `.cmd`/`.bat` through cmd. */
|
|
81
|
+
function windowsScriptOnPath(command, env) {
|
|
82
|
+
if (process.platform !== "win32") return undefined;
|
|
83
|
+
if (/\.(?:exe|com)$/i.test(command) || /[\\/]/.test(command)) return undefined;
|
|
84
|
+
if (/\.(?:cmd|bat)$/i.test(command)) return command;
|
|
85
|
+
for (const dir of pathValue(env).split(delimiter)) {
|
|
86
|
+
if (!dir) continue;
|
|
87
|
+
if (existsSync(join(dir, `${command}.exe`))) return undefined;
|
|
88
|
+
const cmd = join(dir, `${command}.cmd`);
|
|
89
|
+
if (existsSync(cmd)) return cmd;
|
|
90
|
+
const bat = join(dir, `${command}.bat`);
|
|
91
|
+
if (existsSync(bat)) return bat;
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function quoteCmdArg(value) {
|
|
97
|
+
const text = String(value);
|
|
98
|
+
if (/[\0\r\n"%!]/.test(text)) {
|
|
99
|
+
throw new Error(`cannot pass ${JSON.stringify(text)} to a Windows command script`);
|
|
100
|
+
}
|
|
101
|
+
return `"${text}"`;
|
|
102
|
+
}
|
|
103
|
+
|
|
70
104
|
function run(command, args, timeout = 120_000) {
|
|
71
|
-
const
|
|
105
|
+
const env = childEnv();
|
|
106
|
+
const script = windowsScriptOnPath(command, env);
|
|
107
|
+
const windowsCommandScript = Boolean(script);
|
|
108
|
+
const launchCommand = windowsCommandScript ? comSpec(env) : command;
|
|
109
|
+
const launchArgs = windowsCommandScript
|
|
110
|
+
? ["/d", "/s", "/v:off", "/c", `"${[script, ...args].map(quoteCmdArg).join(" ")}"`]
|
|
111
|
+
: args;
|
|
112
|
+
const result = spawnSync(launchCommand, launchArgs, {
|
|
72
113
|
cwd: root,
|
|
73
114
|
encoding: "utf8",
|
|
74
|
-
env
|
|
115
|
+
env,
|
|
75
116
|
timeout,
|
|
76
117
|
windowsHide: true,
|
|
118
|
+
windowsVerbatimArguments: windowsCommandScript,
|
|
77
119
|
});
|
|
78
120
|
return {
|
|
79
121
|
status: result.status ?? 1,
|