akm-cli 0.10.261019 → 0.10.261021

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.
@@ -38,10 +38,36 @@ function keyLooksSecret(key) {
38
38
  const lower = key.toLowerCase();
39
39
  return SECRET_KEY_HINTS.some((hint) => lower.includes(hint));
40
40
  }
41
+ /**
42
+ * Unix (`/`, `./`, `../`, `~/`) or Windows (`C:\`, `\\server`) filesystem path,
43
+ * or a bare relative path whose last segment carries a file extension
44
+ * (`reports/2026-10-30/summary.md`). Base64 never contains `.`, so the
45
+ * extension keeps slash-bearing base64 blobs on the whole-string check.
46
+ */
47
+ const PATH_LIKE = /^(\/|\.{1,2}[\\/]|~[\\/]|[A-Za-z]:[\\/]|\\\\)|^[\w.-]+[\\/][^?#]*\.[A-Za-z0-9]{1,8}$/;
48
+ /** A canonical UUID — an ordinary run/session directory name, not a credential. */
49
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
50
+ /**
51
+ * Whether one path segment or query piece looks like a credential: a known
52
+ * prefix, a secret-named query key (`token=...`), or a long unbroken token
53
+ * (hex/base64 run, or mixed case with digits).
54
+ */
55
+ function segmentLooksSecret(seg) {
56
+ if (SECRET_VALUE_PREFIX.test(seg))
57
+ return true;
58
+ const eq = seg.indexOf("=");
59
+ if (eq > 0 && keyLooksSecret(seg.slice(0, eq)) && seg.length - eq > 8)
60
+ return true;
61
+ if (seg.length >= 32 && !UUID.test(seg) && /^[A-Za-z0-9+_=-]+$/.test(seg) && /\d/.test(seg) && /[a-zA-Z]/.test(seg))
62
+ return true;
63
+ return seg.length >= 20 && /^[A-Za-z0-9]+$/.test(seg) && /[a-z]/.test(seg) && /[A-Z]/.test(seg) && /\d/.test(seg);
64
+ }
41
65
  /**
42
66
  * A string value is secret-shaped when it is long, whitespace-free, and either
43
67
  * carries a known credential prefix or has high character-class diversity /
44
- * base64-hex shape — an entropy proxy, not a real entropy computation.
68
+ * base64-hex shape — an entropy proxy, not a real entropy computation. A
69
+ * filesystem path is judged per segment instead, so separators, dates and
70
+ * digits do not count as diversity.
45
71
  */
46
72
  function valueLooksSecret(value) {
47
73
  const s = value.trim();
@@ -49,6 +75,8 @@ function valueLooksSecret(value) {
49
75
  return false;
50
76
  if (SECRET_VALUE_PREFIX.test(s))
51
77
  return true;
78
+ if (PATH_LIKE.test(s))
79
+ return s.split(/[/\\?&#]/).some(segmentLooksSecret);
52
80
  const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((re) => re.test(s)).length;
53
81
  if (s.length >= 24 && classes >= 3)
54
82
  return true;
@@ -34,7 +34,7 @@ harness's `--version`, looks up the latest akm release on GitHub for `cli-versio
34
34
  Claude plugin is installed. With
35
35
  `--no-probe` none of these runs: the connection rows carry no `reachable` field and the
36
36
  message says reachability was not probed, and `plugin-version` reports `unknown` (its
37
- `AKM_VERSION_RANGE` check is local and still runs). The `plugin-version` and `cli-version`
37
+ `AKM_MIN_VERSION` (or legacy `AKM_VERSION_RANGE`) check is local and still runs). The `plugin-version` and `cli-version`
38
38
  lookups are best-effort and silent on failure; see `src/commands/health/plugin-staleness.ts`
39
39
  and `src/commands/health/version-drift.ts`.
40
40
 
@@ -56,13 +56,13 @@ and `src/commands/health/version-drift.ts`.
56
56
  | `cli-version` | `warn`: a newer akm release exists on GitHub. `unknown`: `--no-probe`, or the release lookup failed. | Run `akm upgrade`. |
57
57
  | `scheduler-binary` | `warn`: the akm binary recorded in the scheduler reports a different version than the running CLI. `unknown`: `--no-probe`, no scheduled task installed, or the recorded binary cannot be run. | Run `akm task sync` to rebind the schedule. |
58
58
  | `index-state-generation` | `warn`: usage links in state.db point at index entries that no longer match (an index and state.db from different generations). | Stop concurrent writers and run `akm index --full`. |
59
- | `stash-git-exposure` | `warn`: `env/` or `secrets/` files are tracked by git in the working bundle and the bundle has a remote. Skipped when the bundle has no `.git`. | Run `git rm --cached` on the listed files and add `env/` and `secrets/` to `.gitignore`. A `.gitignore` rule alone does not untrack files. |
59
+ | `stash-git-exposure` | `warn`, one per bundle: `env/` or `secrets/` files are tracked by git in a configured bundle (the working stash included) and that bundle has a remote. Skipped for a bundle with no `.git` at its root. | Run `git rm --cached` on the listed files and add `env/` and `secrets/` to `.gitignore`. A `.gitignore` rule alone does not untrack files. To keep tracking them on purpose, add `# akm: intentionally track env and secrets` to the stash `.gitignore`; the advisory is then not reported. |
60
60
  | `binary-config-skew` | `warn`: `config.json` has a `configVersion` newer than this binary knows. | Upgrade this install. |
61
61
  | `egress-endpoints` | Every remote destination in the effective config, as one informational entry: enabled registries, git and website bundles, each connection's endpoint and the embedding endpoint (credentials in a URL are masked). Silent when nothing remote is configured. | Eyeball it against what you expect; a destination you did not configure came from an `extends` base or a shared bundle. |
62
62
  | `type-directory-disagreement` | `warn`: an indexed asset's resolved type differs from the type its directory declares. Each line gives the file, both types, and the classifier signal that won (`winner`). | Review the listed files. Intended overrides (for example a `knowledge/` file containing `$ARGUMENTS`) are marked `known-good override`. |
63
63
  | `memory-cleanup-archive` | `pass` with size and file count when the working bundle has `.akm/memory-cleanup/archive` content and no git history (the purge does not run there). `warn` when a git-backed bundle has archived files the purge cannot remove (untracked, modified, or unverifiable in git). | Commit the archived files so the purge can remove them once they age out. |
64
64
  | `data-dir-usage` | `warn`: the data directory is over 1 GB and either more than 3 times the size of `state.db`, `index.db`, and `logs.db` (with their WAL and SHM files), or one top-level subdirectory holds more than half of it. The message names that subdirectory and its size. | Inspect the named subdirectory. `backups/tasks/` holds task-file snapshots written by `akm migrate apply`. |
65
- | `plugin-version` | One row per installed Claude Code harness plugin: its version and whether its declared `AKM_VERSION_RANGE` admits the running CLI. The newest version is read on the channel the marketplace clone follows: on the remote's default branch the newest stable tag, on any other branch (such as `next`) the release tag on that branch's head, and on a clone pinned to a tag that tag. The newest-tag lookup is `--probe`-gated: with `--no-probe` the row is `unknown` ("not probed") and only the local range check still runs. `warn`: the plugin is stale, or its range does not admit the running CLI. | Run the `claude plugin update <ref>` command in the message. |
65
+ | `plugin-version` | One row per native `claude plugin list --json` AKM installation, with scope, enabled state and install path; cached versions are never used to choose an installation. `AKM_MIN_VERSION` takes precedence over legacy `AKM_VERSION_RANGE`. Disabled and other-project installations are inert. Missing metadata or multiple applicable paths/versions produce `unknown`, since the native list does not identify which conflicting installation a running session loaded. The newest version is read on the channel the marketplace clone follows: on the remote's default branch the newest stable tag, on any other branch (such as `next`) the release tag on that branch's head, and on a clone pinned to a tag that tag. The newest-tag lookup is `--probe`-gated: with `--no-probe` the row is `unknown` ("not probed") and only the local range check still runs. `warn`: the plugin is stale, or its range does not admit the running CLI. | Run the `claude plugin update <ref>` command in the message. |
66
66
  | `opencode-plugin-version` | The same check for the OpenCode plugin's own bundled `akm-cli` (read from its `package.json`, never executed): it runs in-process and shares the host's databases, so its lag matters the same way. OpenCode 1 caches each spec in its own folder, so it reads the one folder of the spec the global OpenCode config names under `plugin` (`akm-opencode`, read as `akm-opencode@latest`, or `akm-opencode@next`), and for `akm-opencode@<version>` reports the plugin as pinned with no staleness verdict; with no entry, or no folder for it, it reads `packages/akm-opencode`, and it never scans the other cached specs. It reads local files only and is never `--probe`-gated. `warn`: the bundled version differs from the running CLI. Omitted when the OpenCode plugin is not installed. | Update the OpenCode plugin. |
67
67
  | `state-db-readable` | `fail`: `state.db` cannot be read, or is missing in a read-only run (which does not create it). Every other check is skipped, because none of them can read it. | Check the owner and mode of the data directory (or point `AKM_DATA_DIR` / `XDG_DATA_HOME` at one you own); run any akm command that records state, such as `akm health`, to create it. |
68
68
 
@@ -439,18 +439,24 @@ orchestration, checksum, and failure paths.
439
439
 
440
440
  `tests/integration/upgrade-rehearsal/` runs once per origin in
441
441
  `KNOWN_UPGRADE_ORIGINS` (`previous-release.ts`): `previous`, the highest stable
442
- release below the candidate's `package.json` version, and `0.9.15`, a fixed
443
- older release. For each origin it installs that release as a real global npm
442
+ release below the candidate's `package.json` version; `0.9.32`, which keeps the
443
+ full engines migration covered after `previous` advances to 0.10; and `0.9.15`,
444
+ which covers scheduler rows installed before host-local activation was recorded.
445
+ Each origin installs that release as a real global npm
444
446
  package (a real `npm pack` / `npm install --global` into a throwaway prefix, see
445
- `scripts/package-install.ts`), uses it to build a realistic home — filesystem,
447
+ `scripts/package-install.ts`). The `previous` and `0.9.32` origins build a full
448
+ home — filesystem,
446
449
  git (with an in-bundle symlink at its root), website, and npm bundles, scheduled
447
- tasks, a synced native (fake) crontab, and the 0.9 `engines` form the release
448
- itself writes (an llm engine with an `apiKeyFile`, an agent engine, a
449
- `models.json`, a command and a task source that name an engine) — then installs
450
+ tasks, a synced native (fake) crontab, and model configuration the release
451
+ itself writes: native connections/harnesses on 0.10+, or the full 0.9 `engines`
452
+ form (an llm engine with an `apiKeyFile`, agent engines, a `models.json`, and a
453
+ command and task that name an engine). The `0.9.15` origin uses a smaller home
454
+ with one directly installed scheduled task. Each then installs
450
455
  the CANDIDATE build OVER it in the SAME prefix: an in-place swap, the same thing
451
456
  a real `npm i -g akm-cli@…` or `bun add -g` upgrade does. It then runs the
452
457
  candidate against that home (`migrate status` / `apply`, which converts the
453
- `engines` form, files its key and rewrites the `engine:` keys, `bundle list`,
458
+ `engines` form, files its key and rewrites the `engine:` keys, or preserves
459
+ native connections/harnesses, `bundle list`,
454
460
  `search` / `show`, `task sync` dry-run and real — plain, no `--rebind`, as an
455
461
  upgrading user runs it — executing the generated cron command and confirming it
456
462
  ran the candidate, `task run`, `health`, `improve --dry-run`), and finally runs a
@@ -541,11 +541,11 @@ Result sections (`schemaVersion: 1`):
541
541
  | Field | Description |
542
542
  | --- | --- |
543
543
  | `window`, `filters` | The resolved window and the usage source counted |
544
- | `usage` | Search, show, curate and select totals, `selectRate` (selects over searches that returned a hit), `searchMedianMs`, a daily series, top assets, top and zero-result queries, and rows by source |
544
+ | `usage` | Search, show, curate and select totals, `searchesWithHits` and `selectsPerSearch` (selects divided by searches that returned a hit: a plain ratio that can exceed 1, not a conversion percentage, since selects carry no source or search id), `searchMedianMs`, a daily series, top assets, top and zero-result queries, and rows by source |
545
545
  | `feedback` | Positive and negative totals, per-asset valence, per-tag counts, and the most recent negatives with their reasons |
546
546
  | `utility` | A ten-bucket histogram of `utility_scores`, the lowest and highest assets, and how many indexed entries were never used |
547
547
  | `outcomes` | The assets with the lowest `outcome_score` |
548
- | `llm` | `akm health`'s LLM usage aggregate (`byStage`, `byProcess`, and `byEngine`, keyed by the name of the connection or harness) |
548
+ | `llm` | `akm health`'s LLM usage aggregate (`byStage`, `byProcess`, and `byEngine`, keyed by the name of the connection or harness). `usageCalls` and `reasoningCalls` count the calls whose provider response reported token usage; a call without it adds no tokens and the report shows `unknown`, not zero, when none reported |
549
549
  | `index`, `tasks`, `proposals`, `workflows` | Index runs and median time, task runs and fail rate, proposals by status and accept rate by source, workflow runs and tokens by model |
550
550
  | `rows` | The raw usage and LLM rows of the window; see below |
551
551
  | `notes` | Anything that limits what the numbers mean |
@@ -1537,7 +1537,9 @@ computed, with a 256 MiB binary limit. Release/checksum metadata is capped at
1537
1537
  akm upgrade # Install a newer release if there is one, run every pending migration, then update the harness plugins
1538
1538
  akm upgrade --check # Report pending updates, CLI and plugins, without changing anything (no migration step)
1539
1539
  akm upgrade --force # Force the install even if already on latest
1540
- akm upgrade --next # Install the newest prerelease (@next) instead of the latest stable release
1540
+ akm upgrade --next # Include prereleases when selecting the newest CLI release
1541
+ akm upgrade --channel next # Switch CLI and installed harness plugins to next
1542
+ akm upgrade --channel latest # Return CLI and installed harness plugins to stable
1541
1543
  ```
1542
1544
 
1543
1545
  | Flag | Description |
@@ -1545,7 +1547,9 @@ akm upgrade --next # Install the newest prerelease (@next) instead of the
1545
1547
  | `--check` | Report pending updates (CLI and per-harness plugins) without changing anything |
1546
1548
  | `--force` | Force upgrade even if on latest version |
1547
1549
  | `--skip-post-upgrade` | Skip the post-upgrade index rebuild |
1548
- | `--next` | Follow the `next` prerelease channel: install the newest prerelease of akm (and of the OpenCode plugin, see the Prereleases section below). Works with `--check`, `--force` and `-q` |
1550
+ | `--channel latest\|next` | Switch the CLI and installed Claude, Codex and OpenCode plugins together; works with `--check` |
1551
+ | `--allow-downgrade` | Permit an older target version during an explicit `--channel` switch |
1552
+ | `--next` | Include prereleases when selecting the newest CLI release; retain configured harness channels (see below). Works with `--check`, `--force` and `-q` |
1549
1553
 
1550
1554
  Offline, or to migrate without a release check, run `akm migrate apply`
1551
1555
  directly: it is the same step.
@@ -1607,7 +1611,7 @@ target is the akm-cli that `akm-opencode@latest` depends on
1607
1611
  The result then carries `lockstep: { plugin, pinnedVersion, newestVersion, heldBack }`,
1608
1612
  `latestVersion` is the pinned version, the install names that exact version
1609
1613
  rather than `@latest`, and the text output says the CLI is held back. The CLI
1610
- is never moved backwards to meet the pin. With both OpenCode plugins present
1614
+ is never moved backwards to meet the pin unless an explicit channel switch also names `--allow-downgrade`. With both OpenCode plugins present
1611
1615
  (`akm-opencode` and `akm-opencode-v2`), the CLI is held to the older of their
1612
1616
  two pins, and `lockstep.plugin` names the one that sets it. Claude Code and Codex have no
1613
1617
  in-process copy and are not part of this rule.
@@ -1623,7 +1627,7 @@ retries.
1623
1627
  #### Prereleases (`--next`)
1624
1628
 
1625
1629
  `akm upgrade --next` means "newest available, prereleases included". The result
1626
- carries `channel: "next"` (`"latest"` otherwise); no other field changes.
1630
+ carries `channel: "next"` (`"latest"` otherwise), plus the CLI's inferred `currentChannel` and each installed plugin's configured `channel` and `channelMismatch`.
1627
1631
 
1628
1632
  - **CLI.** The target is the `next` dist-tag of `akm-cli` (read from the npm
1629
1633
  registry, so it works for binary installs too) when that is newer than the
@@ -1656,14 +1660,38 @@ carries `channel: "next"` (`"latest"` otherwise); no other field changes.
1656
1660
  - **Claude Code and Codex.** Their plugin comes from the `akm-plugins` git
1657
1661
  marketplace, and the channel is the ref the marketplace follows: the default
1658
1662
  branch is stable, the `next` branch carries prereleases. `akm upgrade` updates
1659
- whatever ref the marketplace follows and cannot switch it, with or without
1660
- `--next`. To follow prereleases, add the marketplace as `itlackey/akm-plugins@next`
1661
- in Claude Code (`extraKnownMarketplaces.akm-plugins.source.ref = "next"` in
1662
- `settings.json`), or run `codex plugin marketplace add itlackey/akm-plugins --ref next`
1663
- in Codex. Under `--next` their entries carry a note naming the ref followed.
1663
+ whatever ref the marketplace follows. `--next` alone leaves that ref unchanged;
1664
+ `--channel next` changes it. Under `--next` their entries carry a note naming
1665
+ the ref followed.
1664
1666
  The plugins need akm 0.10.261001 or newer; any newer akm passes.
1665
1667
  - `--check --next` reports all of this and changes nothing.
1666
1668
 
1669
+ #### Switching channels together (`--channel`)
1670
+
1671
+ `akm upgrade --channel next` selects the actual npm `next` CLI tag and changes
1672
+ installed harness plugins to their `next` branch or package tag.
1673
+ `--channel latest` selects stable and removes those prerelease refs. An explicit
1674
+ switch may replace a pinned plugin spec. The command checks target CLI and plugin
1675
+ versions first; if any would go backwards, repeat with `--allow-downgrade`.
1676
+ A failed CLI install or migration stops the plugin switch.
1677
+
1678
+ Claude and Codex retain their marketplace registrations, plugin enablement,
1679
+ `pluginConfigs`, and unrelated settings. akm edits only the marketplace ref,
1680
+ then uses the native updater. Claude's installed user, project and local scopes
1681
+ are updated explicitly. OpenCode changes only the installed plugin's spec,
1682
+ retaining tuple options and JSONC comments, then refreshes the desired cache.
1683
+ Every changed config file has a sibling `.akm-channel-*.bak` backup, whose path
1684
+ appears in that harness's `backups` result. Restart the harness to load updates.
1685
+
1686
+ `--check --channel next` shows `channel`, `targetChannel` and mismatches without
1687
+ writing settings or fetching Git marketplaces. The CLI's `currentChannel` is
1688
+ inferred from its version: prerelease means `next`, otherwise `latest`.
1689
+ A configured channel is distinct from a successful refresh: native update
1690
+ failures retain the configured target and backup paths but report `failed`.
1691
+ An installed plugin that cannot switch, including OpenCode awaiting shutdown,
1692
+ makes an explicit switch exit `1`; retry after resolving its reported cause.
1693
+ The switch is coordinated, not a transaction across independent harnesses.
1694
+
1667
1695
  #### Containers
1668
1696
 
1669
1697
  With the plugin step, a container entrypoint needs only:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.10.261019",
3
+ "version": "0.10.261021",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
6
6
  "keywords": [
@@ -119,9 +119,11 @@
119
119
  "citty": "^0.2.2",
120
120
  "dotenv": "^17.4.2",
121
121
  "fast-xml-parser": "^5.10.1",
122
+ "jsonc-parser": "^3.3.1",
122
123
  "marked": "^18.1.1",
123
124
  "node-html-parser": "^9.0.1",
124
125
  "semver": "^7.6.0",
126
+ "smol-toml": "^1.9.1",
125
127
  "turndown": "^7.2.4",
126
128
  "yaml": "^2.8.4",
127
129
  "zod": "^4.6.6"