akm-cli 0.10.261014-beta → 0.10.261015-beta

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.
@@ -125,66 +125,32 @@ function loadBunSqlite() {
125
125
  }
126
126
  let betterSqlite3Ctor;
127
127
  /**
128
- * The binding is absent or unbuildable — a toolchain/install problem.
128
+ * The package is missing, or it carries no binding for this platform. Its N-API
129
+ * bindings ship prebuilt in the package (see package.json → pinNotes), so neither
130
+ * a build toolchain nor the Node version decides whether it loads.
129
131
  */
130
- const MISSING_BINDING_REMEDY = "akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.\n" +
131
- " • Reinstall akm with a working C/C++ build toolchain so its optional\n" +
132
- " 'better-sqlite3' native binding builds (a global `npm i -g better-sqlite3`\n" +
133
- " will NOT be resolved — Node loads it from akm's own node_modules).\n" +
134
- " • Or run akm under Bun, which has a built-in SQLite driver and needs no native build.";
135
- /**
136
- * Recognize a binding built for a DIFFERENT Node ABI than the one now running,
137
- * and answer with the one command that fixes it.
138
- *
139
- * This is the most likely failure a real user hits, and it is not a broken
140
- * install: a native addon is compiled (or a prebuilt binary is selected) for
141
- * the Node major present at `npm install` time. Upgrade Node afterwards and the
142
- * same file no longer loads.
143
- *
144
- * It is matched rather than described because the symptom text varies and the
145
- * previous wording only named ONE of them. The prebuilt-binary path — which is
146
- * now the normal path, since better-sqlite3 is pinned to a version publishing a
147
- * prebuild for every supported Node (see package.json → pinNotes) — reports
148
- * `Module did not self-register`, saying nothing about versions at all. A
149
- * from-source build reports the explicit `NODE_MODULE_VERSION` mismatch. Asking
150
- * the user to decide which bullet applies is the step worth deleting.
151
- */
152
- export function abiMismatchRemedy(message) {
153
- const ABI_MISMATCH_SHAPES = [
154
- "did not self-register", // prebuilt binary for another ABI
155
- "NODE_MODULE_VERSION", // explicit mismatch, from-source build
156
- "was compiled against a different", // same, older phrasing
157
- "invalid ELF header", // binary for another platform/arch entirely
158
- ];
159
- if (!ABI_MISMATCH_SHAPES.some((shape) => message.includes(shape)))
160
- return undefined;
161
- return ("akm could not load 'better-sqlite3': its native binding was built for a different\n" +
162
- `Node.js version than the one now running (this Node is ABI ${process.versions.modules}).\n` +
163
- "This is what happens when Node is upgraded after akm is installed. It is NOT a\n" +
164
- "broken install, and reinstalling akm is not required.\n" +
165
- " Fix: npm rebuild better-sqlite3 # in akm's install directory\n" +
166
- " Or: npm install -g akm-cli # reinstall, rebuilding against this Node\n" +
167
- " Or: run akm under Bun, whose built-in SQLite driver needs no native binding.");
132
+ function missingBindingRemedy() {
133
+ return ("akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.\n" +
134
+ " • Its package carries prebuilt bindings for Linux and macOS on x64 and arm64 and for\n" +
135
+ ` Windows on x64 and arm64; this machine is ${process.platform} ${process.arch}.\n` +
136
+ " • Reinstall akm (`npm install -g akm-cli`) if the package is missing (a global\n" +
137
+ " `npm i -g better-sqlite3` will NOT be resolved — Node loads it from akm's own node_modules).\n" +
138
+ " • Or run akm under Bun, which has a built-in SQLite driver and needs no native binding.");
168
139
  }
169
140
  function loadBetterSqlite3() {
170
141
  if (!betterSqlite3Ctor) {
171
142
  // Runtime-gated dynamic require: only reached when NOT on Bun, so Bun never
172
143
  // resolves or loads the optional `better-sqlite3` native dependency.
173
144
  // `better-sqlite3` is an optionalDependency, so `npm i` can succeed without
174
- // it (or with a native build that failed). Convert the raw MODULE_NOT_FOUND
175
- // into an actionable message instead of a cryptic onboarding crash.
145
+ // it. Convert the raw MODULE_NOT_FOUND into an actionable message instead of
146
+ // a cryptic onboarding crash.
176
147
  let mod;
177
148
  try {
178
149
  mod = nodeRequire("better-sqlite3");
179
150
  }
180
151
  catch (err) {
181
- // An ABI mismatch does NOT arrive here — `require` succeeds and the
182
- // failure lands at construction (see openNodeDatabase). This path is a
183
- // genuinely absent or unresolvable module. `abiMismatchRemedy` is still
184
- // consulted because a from-source build CAN fail at load with the
185
- // explicit NODE_MODULE_VERSION message.
186
152
  const raw = err instanceof Error ? err.message : String(err);
187
- throw new Error(`${abiMismatchRemedy(raw) ?? MISSING_BINDING_REMEDY}\n Underlying load error: ${raw}`);
153
+ throw new Error(`${missingBindingRemedy()}\n Underlying load error: ${raw}`);
188
154
  }
189
155
  betterSqlite3Ctor = mod.default ?? mod;
190
156
  }
@@ -201,23 +167,17 @@ function openNodeDatabase(path, opts) {
201
167
  options.readonly = opts.readonly;
202
168
  if (opts?.create === false)
203
169
  options.fileMustExist = true;
204
- // Construction, not `require`, is where an ABI mismatch surfaces.
205
- // `require("better-sqlite3")` SUCCEEDS against a binding built for another
206
- // Node ABI — the package resolves its `.node` file lazily — so the loader's
207
- // catch never sees this error and cannot explain it. Verified against a real
208
- // ABI-127 binding under Node 24 (ABI 137): `require()` returned a function
209
- // and `new Database(...)` threw. Wrapping the require alone left the most
210
- // likely real-world failure reported as a bare Node internals message.
211
170
  let db;
212
171
  try {
213
172
  db = opts ? new BetterSqlite3(path, options) : new BetterSqlite3(path);
214
173
  }
215
174
  catch (err) {
175
+ // The package loads its binding at the first construction, not at `require`, so a platform it carries no
176
+ // binding for fails here.
216
177
  const raw = err instanceof Error ? err.message : String(err);
217
- const remedy = abiMismatchRemedy(raw);
218
- if (!remedy)
178
+ if (!raw.includes("better_sqlite3.node"))
219
179
  throw err;
220
- throw new Error(`${remedy}\n Underlying error: ${raw}`);
180
+ throw new Error(`${missingBindingRemedy()}\n Underlying error: ${raw}`);
221
181
  }
222
182
  return {
223
183
  prepare: db.prepare.bind(db),
@@ -225,8 +225,9 @@ Why: an accidental API is a compatibility promise nobody agreed to make.
225
225
 
226
226
  Enforced by: `tests/contracts/api-surface.test.ts` (the export list),
227
227
  `tests/integration/install/package-exports.test.ts` (the `exports` map),
228
- `package.json` (`engines.node` and the `preinstall` check), and the
229
- `node-smoke` job in `.github/workflows/ci.yml` (Node 22 and 24).
228
+ `package.json` (`engines.node`), the Node version check in the launchers and
229
+ `src/cli.ts`, and the `node-smoke` job in `.github/workflows/ci.yml` (Node 22
230
+ and 24).
230
231
 
231
232
  ### 10. Agent harnesses are thin, replaceable internals
232
233
 
@@ -26,7 +26,7 @@ changes.
26
26
  | `connections-and-harnesses` | One table of every configured connection (endpoint, whether its key is held — a `$VAR` set in this shell, a `credentials.json` entry, or none — and whether the endpoint answers) and harness (platform, whether its binary is on `PATH`, and with a probe its `--version`), then the work that runs on them: improve, judges, agents, extract, enrich, index, embedding and curate rerank, each naming the connection or harness and the model it resolves to through the same resolver the calls use (`evidence.rows`, typed `connection`, `harness` or `process`). `warn` when a key variable is unset, an endpoint does not answer, a harness is not installed, several connections (or harnesses) are configured and none is called `default` (said once, with the fix: name one `default`), or a work row cannot resolve because of the config; `unknown` when neither a connection nor a harness is configured; otherwise `pass`. Endpoint reachability is one bounded `GET /models` and the version is the harness's own `--version`; with `--no-probe` neither runs and the message says so. | Correct the named connection, key or harness; run `akm setup` when nothing is configured. A work row whose parts run somewhere other than the row's own target lists them in `overrides`, each with the kind (connection or harness), name and model it runs on: that is a per-process pin, working as set. |
27
27
  | `task-fail-rate` | ≥5% of scheduled task runs failed in the window (exit 143/70 recurring), or a single task_id fails at/above the same threshold. (#943) For `command`-kind (agent/LLM dispatch) failures specifically, `evidence.agentFailureReasonCounts` always carries a `detail.reason` breakdown (`timeout`, `non_zero_exit`, `spawn_failed`, …), and the warn message names the dominant reason — e.g. `(timeout-dominant: 9/12 command-task failures)` — when one reason is at least half of the counted command-task failures. | Triage as a bug: `akm task doctor`, inspect failing lane logs. For a native shell/script task, exit-143 = killed/timeout, exit-70 = internal error. For a `command`-kind (agent/LLM) task, read `detail.reason` from `akm task history` instead of the exit code — `timeout` means the dispatch's own deadline (not an outer process kill) expired; `non_zero_exit`/`spawn_failed`/`aborted`/etc. name the other failure shapes. |
28
28
  | `data-dir-usage` | The data directory is more than 3× the live databases (`state.db` + `index.db` + `logs.db`), or one top-level subdirectory holds more than half of it; the message names that subdirectory with its size and share. | Look at the named subdirectory. `backups/tasks/` holds one snapshot per `akm migrate apply` run that rewrote a task file; everything under `backups/` is yours to prune. |
29
- | `semantic-search` | What the last `akm index` recorded about semantic search, with the index's entry and vector counts read now; no model load, no request. `pass` when every entry has a vector (local: on `onnxruntime-node`) or `semanticSearchMode` is `off`; `warn` when vectors lag entries, local embeddings ran on another runtime, or the platform has no native ONNX Runtime library (`unavailable: platform`: Alpine/musl, Intel macOS, where keyword search is the healthy state); `fail` when the last index found it `blocked` (endpoint or model download failed) or `unavailable: load` (the library is there but did not load: a missing package or addon, a `noexec` temp or cache directory), with the reason; `unknown` before the first index. A hard check, so a `fail` fails `akm health`. | `fail`: fix the reason named, then run `akm index`; the check reads the next index's record. `warn` for the platform: set `semanticSearchMode` to `off`, or `embedding.endpoint` to a remote embedding service. |
29
+ | `semantic-search` | What the last `akm index` recorded about semantic search, with the index's entry and vector counts read now; no model load, no request. `pass` when every entry has a vector (local: on `onnxruntime-node`) or `semanticSearchMode` is `off`; `warn` when vectors lag entries, local embeddings ran on another runtime, or akm offers no local embeddings on this platform (`unavailable: platform`, where keyword search or an endpoint is the default); `fail` when the last index found it `blocked` (endpoint or model download failed) or `unavailable: load` (the runtime could not be downloaded or did not load, for example from a `noexec` cache directory), with the reason; `unknown` before the first index. A hard check, so a `fail` fails `akm health`. | `fail`: fix the reason named, then run `akm index`; the check reads the next index's record. `warn` for the platform: `akm setup --embedding off` for keyword search, or `akm setup --embedding <model> --embedding-endpoint <url>` for a remote endpoint. |
30
30
  | `stash-git-exposure` | `env/` or `secrets/` assets are git-tracked **and** a remote is set — `git push` can leak keys. | `git rm --cached` the files, add `env/`+`secrets/` to `.gitignore` (a rule alone does not untrack). |
31
31
  | `session-extraction` | (#914) Derived from the `extract_sessions_seen` ledger for the last 7 days — not `improve_runs`, which the hook-driven `akm proposal extract --session-id ...` never writes. `unknown` when nothing was recorded; `warn` when every session in the window was skipped for an infrastructure reason (`llm_unavailable`, `read_failed`, `exception`, `locked_concurrent`), naming the reason and engine; otherwise `pass` with per-outcome counts. | An `unknown` machine may be deliberately not configured for extraction, or may have stopped harvesting — check for recent sessions. A `warn` names the broken engine/reason directly. |
32
32
  | `auto-accept-validation` | Proposals passed the confidence gate but failed validation (bad frontmatter, truncation). | Review the affected pending proposals via `akm proposal list`; they were held, not lost. |
@@ -810,6 +810,7 @@ adapter recognizes — `schema.md` + `pages/` is the probe) contains:
810
810
  | `$CACHE/tasks/logs/<task-id>/` | Per-run stdout/stderr log files (`<ISO-ts>.log`) | No cleanup |
811
811
  | `$CACHE/bin/rg` | Auto-downloaded ripgrep binary | Permanent |
812
812
  | `$CACHE/opencode/<version>/opencode` | The OpenCode binary akm's own runtime runs (about 240 MB), fetched from the npm registry on its first use | Permanent; safe to delete (fetched again) |
813
+ | `$CACHE/onnxruntime/<version>-<platform>-<arch>/` | `onnxruntime_binding.node` and the ONNX Runtime library it links, which the local embedder loads; fetched from the npm registry by `akm setup --embedding local` or the first local embedding (about 36 MB kept). Must be on a mount that allows executing files | Permanent; safe to delete (fetched again) |
813
814
  | `$CACHE/index/unresolved-sources/<stash>/<name>` | Synthetic placeholder path for a configured source whose content root did not resolve this run; never written to disk, only reported as a `SearchSource.path`. Moved out of `$STASH/.akm/unresolved-sources/` (itlackey/akm#890). | N/A (not a real directory) |
814
815
 
815
816
  Cache-backed bundles (`git`, `website`, `npm`) are materialised into `$CACHE`
@@ -240,19 +240,27 @@ sets the same path and restores it from a model/source-identified Actions
240
240
  cache. Candidate tags and manual runs are restore-only; only the scheduled
241
241
  default-branch run may save a cache entry.
242
242
 
243
- The same gate runs real semantic index/search round-trips on `onnxruntime-node`,
244
- asserts the `embeddingRuntime` the index reports, and compares the embedder's
245
- vectors with the Transformers.js reference in
246
- `tests/fixtures/embedder-reference-vectors.json`. Package acceptance separately
247
- verifies the ordinary npm package surface; there is no installed-consumer
248
- compatibility architecture or copied semantic runtime to audit.
243
+ The same gate runs real semantic index/search round-trips on `onnxruntime-node`'s
244
+ binding, asserts the `embeddingRuntime` the index reports, and compares the
245
+ embedder's vectors with the Transformers.js reference in
246
+ `tests/fixtures/embedder-reference-vectors.json`. The binding and its library are
247
+ shared across runs the same way as the model: `tests/_helpers/onnx-runtime-cache.ts`
248
+ copies the repo-local `.ci-cache/akm/onnxruntime/` into each sandbox's akm cache
249
+ and keeps a folder a run fetched there, and gated CI caches that directory under
250
+ a key from `src/llm/embedders/onnx-runtime.ts`, which pins the ONNX Runtime
251
+ version. Package acceptance separately verifies
252
+ the ordinary npm package surface.
249
253
 
250
254
  `tests/integration/standalone-semantic.test.ts` (the same `AKM_SEMANTIC_TESTS=1`
251
- gate) builds the host's standalone binary and indexes a small stash with it,
252
- asserting a ready status, a vector per entry and the native runtime. Gated CI
253
- runs it in the Semantic Search job (Linux) and in the Native Scheduler job on
254
- macOS and Windows, the only places the binary's preload of each platform's ONNX
255
- Runtime library runs.
255
+ gate) builds the host's standalone binary and asserts it embeds no `.node` file.
256
+ Where akm offers local embeddings it runs `akm setup --yes --embedding local`,
257
+ expects the binding and the library in the cache folder, and indexes a small
258
+ stash, asserting a ready status, a vector per entry and the native runtime.
259
+ Everywhere else it asserts the default: setup warns and saves keyword search, an
260
+ index with semantic search on reports it unavailable for the platform, `akm
261
+ health` warns, and search is keyword. Gated CI runs it in the Semantic Search job
262
+ (Linux) and in the Native Scheduler job on macOS (local embeddings) and Windows
263
+ (the default), the only places the binary runs on those platforms.
256
264
 
257
265
  ### What to test explicitly
258
266
 
@@ -282,7 +290,8 @@ This validates three deployment methods across four Linux families:
282
290
  - bun-based install: Ubuntu, Debian, Alpine, Fedora
283
291
  - compiled binary install: Ubuntu, Debian, Fedora
284
292
  - npm install: Debian on `node:24-slim`, which has no compiler, python or make
285
- (`npm install -g` of the packed package; the install's exit code is the check)
293
+ (`npm install -g --ignore-scripts` of the packed package, as npm 12 installs by
294
+ default; the install's exit code and the smoke test are the check)
286
295
 
287
296
  Binary validation currently excludes Alpine. The compiled Linux binary used in
288
297
  this repo's Docker tests is not packaged for Alpine/musl compatibility, so the
@@ -34,23 +34,24 @@ Or download a standalone binary directly from the
34
34
  Hitting a PowerShell execution-policy error on Windows? See
35
35
  [Troubleshooting](#troubleshooting) at the end of this guide.
36
36
 
37
- ### Standalone binary and semantic search
38
-
39
- Local semantic search (`semanticSearchMode: auto`) runs its embedding model on
40
- ONNX Runtime. A standalone binary carries the library for its platform. The
41
- first time it embeds, it copies that library once into akm's cache directory,
42
- under `onnxruntime/<version>-<platform>-<arch>/` (18 to 34 MB depending on the
43
- platform; `akm info` prints the cache directory), and loads it from there. Two
44
- limits follow:
45
-
46
- - A standalone binary writes its native addon to the system temp directory and
47
- loads it from there, so a temp directory mounted `noexec` blocks semantic
48
- search (`akm index` reports it `blocked`; keyword search works). Set `TMPDIR`
49
- to a writable, executable directory. The cache directory must be on
50
- an executable mount too.
51
- - Alpine/musl and Intel macOS have no native ONNX Runtime library. Semantic
52
- search is unavailable there: `akm index` reports it `blocked` with the reason
53
- and search stays keyword-only. A remote `embedding.endpoint` still works.
37
+ ### Local semantic search
38
+
39
+ Local semantic search runs its embedding model on ONNX Runtime, and akm offers
40
+ it on Linux x64 (glibc) and Apple-silicon macOS; on every other
41
+ platform search is keyword-only or uses an embedding endpoint you configure. Choosing the built-in embedder in
42
+ `akm setup` (or `akm setup --yes --embedding local`) downloads what it needs once
43
+ into akm's cache directory (`akm info` prints it): ONNX Runtime for your platform,
44
+ under `onnxruntime/<version>-<platform>-<arch>/` (about 86 MB downloaded,
45
+ about 36 MB kept), and the model (about 127 MB). Nothing is installed with the npm
46
+ package or carried in a standalone binary, and the first `akm index` downloads
47
+ what setup did not. Two notes:
48
+
49
+ - The cache directory must be on a mount that allows executing files: the
50
+ runtime is loaded from there, and on a `noexec` mount `akm index` reports
51
+ semantic search `blocked` (keyword search works).
52
+ - On a platform without local embeddings, `akm setup` offers an embedding
53
+ endpoint or keyword search only, and `akm setup --embedding local` warns and
54
+ keeps keyword search.
54
55
 
55
56
  ### akm's own agent runtime
56
57
 
@@ -60,7 +61,8 @@ is the same for the npm package and the standalone binary: nothing of OpenCode
60
61
  is installed with akm, and the first time the runtime runs, akm downloads the
61
62
  OpenCode binary for your platform from the npm registry (one package, about
62
63
  114 MB, 240 MB on disk), checks it against the registry's integrity hash and
63
- keeps it in the cache directory under `opencode/<version>/`. It is fetched once.
64
+ keeps it in the cache directory under `opencode/<version>/`. It is fetched once;
65
+ the `akm setup` wizard offers to fetch it then, when agent work runs there.
64
66
  Three limits follow:
65
67
 
66
68
  - That step needs the npm registry. Without network access for it, enable the
@@ -54,19 +54,24 @@ it and never appears on the command line:
54
54
  akm setup --yes --endpoint https://api.example.com/v1 --api-key-env EXAMPLE_API_KEY
55
55
  ```
56
56
 
57
- `--no-probe` keeps the run offline (the endpoint's model list is not fetched), so a
58
- new connection has no model until `akm models set` names one.
57
+ `--no-probe` keeps the run offline: nothing is contacted or downloaded (the
58
+ endpoint's model list is not fetched, so a new connection has no model until
59
+ `akm models set` names one).
59
60
  For the full set of flags, see `akm setup --help`; for the config they write,
60
61
  see [configuration.md](../../reference/configuration.md).
61
62
 
62
- **Semantic search.** Headless setups (`--yes` or `--from`, no wizard
63
- prompts) defer the embedding-model download to the first `akm index` run,
64
- unlike the interactive wizard which downloads immediately during `akm
65
- setup`. Set the mode explicitly rather than relying on the implicit default:
63
+ **Semantic search.** `--embedding local` chooses the built-in embedder, offered
64
+ on Linux x64 (glibc) and Apple-silicon macOS; elsewhere it warns and
65
+ keeps keyword search. Once the config is saved, a headless setup (`--yes` or
66
+ `--from`) downloads the embedder's runtime and model into akm's cache and
67
+ reports `semanticAssets` in its result (`ready`, `blocked: <reason>`, or
68
+ `skipped` with `--no-probe`, which leaves the download to the first
69
+ `akm index`). Without the flag, semantic search stays as the config has it
70
+ (`off` by default):
66
71
 
67
72
  ```sh
68
- akm config set semanticSearchMode auto # local embeddings, downloaded on first index
69
- akm config set semanticSearchMode off # skip embeddings entirely
73
+ akm setup --yes --embedding local # local embeddings, downloaded now
74
+ akm config set semanticSearchMode off # skip embeddings entirely
70
75
  ```
71
76
 
72
77
  For resource requirements and remote-embedding alternatives, see
@@ -609,6 +609,23 @@ yours to run it, and its runs are in your OpenCode history, which improve reads.
609
609
  - **A transient reflect failure** (transport error, timeout, crash) no longer
610
610
  uses up the feedback that planned it; the ref is retried next run.
611
611
 
612
+ ### Local semantic search and installs
613
+
614
+ - **akm no longer installs ONNX Runtime through npm.** `akm setup` with the
615
+ built-in embedder (`--embedding local`), or the first `akm index` after the
616
+ upgrade, downloads ONNX Runtime 1.24.3 once (about 86 MB) into akm's cache.
617
+ The vectors are unchanged, so no re-index is needed.
618
+ - **Local embeddings are offered on Linux x64 (glibc) and
619
+ Apple-silicon macOS.** Every other platform uses keyword search or an
620
+ embedding endpoint, and `akm setup` offers only those there.
621
+ - **Standalone binaries are up to 36 MB smaller**, and a `noexec` temp directory
622
+ no longer matters; akm's cache directory must still allow executing files.
623
+ - **On Node, the SQLite driver ships prebuilt for every supported Node
624
+ version**, so `npm install -g akm-cli` works where npm skips install scripts
625
+ (npm 12 does by default), and a Node upgrade no longer needs `npm rebuild`.
626
+ Nothing in the package runs an install script, the Node.js version check
627
+ included: akm checks the version when it starts.
628
+
612
629
  ### The environment programs run in
613
630
 
614
631
  A harness (agent tasks and workflow agent steps included) and an `exec` unit's
@@ -218,9 +218,16 @@ The wizard asks, in order:
218
218
  the config as this run will save it: a further `--name` connection does not
219
219
  serve embeddings, its models are not offered, and with several connections
220
220
  none called `default`, `--embedding <model>` refuses and asks you to name one
221
- default or pass `--embedding-endpoint`.
221
+ default or pass `--embedding-endpoint`. The built-in embedder is offered on
222
+ Linux x64 (glibc) and Apple-silicon macOS only; elsewhere the step
223
+ says so in one sentence and offers an endpoint or off, and `--embedding local`
224
+ warns and saves off.
222
225
  5. **Save.** The config is written, the bundle directory initialized, and a
223
- summary says what runs where.
226
+ summary says what runs where. With the built-in embedder chosen, setup then
227
+ downloads its runtime (ONNX Runtime, about 86 MB) and model (about 127 MB)
228
+ into akm's cache and loads them; the wizard asks first. When agent work runs
229
+ on akm's own runtime, the wizard also offers to download its OpenCode binary
230
+ (about 114 MB) now rather than on the first agent run.
224
231
 
225
232
  Flags answer one question each; `--yes` takes the detected default for every
226
233
  question left, so nothing is prompted. Without a terminal, `akm setup` refuses
@@ -236,7 +243,7 @@ unless `--yes` or `--from` is given.
236
243
  | `--embedding-endpoint <url>`, `--embedding-api-key-env <NAME>` | Another endpoint serving the embedding model |
237
244
  | `--from <file>` | A config file (JSON or YAML) to merge |
238
245
  | `--dir <path>`, `--no-init` | The bundle directory; skip scaffolding it |
239
- | `--no-probe` | Offline: the endpoint is not contacted and its models are not listed; a connection added this way has no model until `akm models set` names one |
246
+ | `--no-probe` | Offline: nothing is contacted or downloaded. The endpoint's models are not listed, so a connection added this way has no model until `akm models set` names one, and the built-in embedder's runtime and model are downloaded by the first `akm index` |
240
247
 
241
248
  The detected defaults are: when no agent program is configured yet, every one
242
249
  found on `PATH` is enabled and the first found is the default. If no connection
@@ -249,7 +256,11 @@ whose endpoint lists an embedding model.
249
256
 
250
257
  The wizard also lets you review registries and add bundle sources. A scripted run
251
258
  prints its result with a `runs` object saying what runs where: `modelCalls`,
252
- `agentWork` and `embeddings`.
259
+ `agentWork` and `embeddings`. When the saved config uses the built-in embedder,
260
+ the result also has `semanticAssets`: `ready` once its runtime and model are
261
+ downloaded and load, `blocked: <reason>` when they do not (the config is saved
262
+ all the same), or `skipped` with `--no-probe`. A scripted run does not download
263
+ akm's own agent runtime; its first agent run does.
253
264
 
254
265
  ### index
255
266
 
@@ -464,13 +475,13 @@ search, with the index's entry and vector counts read now. It loads no model and
464
475
  sends no request. It `pass`es when every entry has a vector (and a local model
465
476
  embedded on `onnxruntime-node`) or `semanticSearchMode` is `off`; `warn`s when
466
477
  vectors lag the entries (`akm index` embeds the rest), local embeddings ran on
467
- another runtime, or this platform has no native ONNX Runtime library
468
- (Alpine/musl, Intel macOS: keyword search is all there is, so the message says to
469
- set `semanticSearchMode` to `off` or `embedding.endpoint` to a remote service);
478
+ another runtime, or akm offers no local embeddings on this platform (keyword
479
+ search or an endpoint is the default there, so the message names
480
+ `akm setup --embedding off` and `akm setup --embedding <model> --embedding-endpoint <url>`);
470
481
  `fail`s, with the recorded reason, when the last index found semantic search
471
482
  `blocked` (an embedding endpoint or model download failed) or `unavailable` for
472
- a cause that can be fixed (the library is there but did not load: a missing
473
- package or addon, a `noexec` temp or cache directory); and is `unknown` before
483
+ a cause that can be fixed (the runtime could not be downloaded or did not load,
484
+ for example from a `noexec` cache directory); and is `unknown` before
474
485
  the first index. Search never reads this record: a query still
475
486
  tries semantic search and falls back to keyword search on its own. A fix shows
476
487
  here after the next `akm index`. `evidence` carries the recorded `status`,
@@ -690,21 +690,43 @@ semantic search is disabled or has not been built. A read-only sandbox that
690
690
  cannot record best-effort usage telemetry does not by itself mark search as
691
691
  degraded.
692
692
  The local embedder runs its model (`Xenova/bge-small-en-v1.5` unless
693
- `embedding.localModel` names another) on `onnxruntime-node`, with
694
- `@huggingface/tokenizers` for the token ids. Both are normal dependencies of the
695
- package, imported directly; AKM does not carry a copied runtime under `src/` or
696
- `dist/`. The model's four files (about 127 MB) are fetched from the Hugging Face
697
- Hub on first use into `$HF_HOME/<repo>/` (default `<cache dir>/models`), and
698
- `HF_HUB_OFFLINE=1` forbids the fetch. `akm index --format json` reports the
699
- runtime it embedded with as `verification.embeddingRuntime`.
700
- If the runtime cannot load, because the dependency is missing or the platform
701
- has no native ONNX Runtime library (Alpine/musl, Intel macOS), the index reports
702
- semantic search `blocked` with the reason and search stays keyword-only:
703
- reinstall `akm-cli` for a missing dependency, or configure a remote
704
- `embedding.endpoint`. A standalone binary copies the library it embeds once into
705
- `<cache dir>/onnxruntime/` (see
706
- [Getting Started](../guides/getting-started.md#standalone-binary-and-semantic-search)).
707
- Setup does not mutate a global installation to add runtime packages.
693
+ `embedding.localModel` names another) on ONNX Runtime, through
694
+ `onnxruntime-node`'s native binding, with `@huggingface/tokenizers` for the token
695
+ ids. akm offers local embeddings on Linux x64 (glibc) and Apple-silicon
696
+ macOS; on every other platform semantic search uses an embedding endpoint or
697
+ stays keyword-only. akm does not install `onnxruntime-node`:
698
+ `akm setup --embedding local` (or the wizard's built-in embedder) downloads that
699
+ package's binding and library for this platform once from the npm registry
700
+ (about 86 MB; the registry's sha512 integrity is checked before anything is
701
+ extracted) into `<cache dir>/onnxruntime/<version>-<platform>-<arch>/`, and the
702
+ first local embedding downloads them when setup did not. The model's four files
703
+ (about 127 MB) come from the Hugging Face Hub the same way, into
704
+ `$HF_HOME/<repo>/` (default `<cache dir>/models`); `HF_HUB_OFFLINE=1` forbids
705
+ that download. `akm index --format json` reports the runtime it embedded with as
706
+ `verification.embeddingRuntime`. If the runtime cannot be downloaded or does not
707
+ load (the cache directory is mounted `noexec`), the index reports semantic search
708
+ `blocked` with the reason and search stays keyword-only. On a platform without
709
+ local embeddings, `akm setup` offers an endpoint or keyword search,
710
+ `--embedding local` warns and saves keyword search, and a config that turns
711
+ semantic search on anyway gets it reported unavailable for the platform by the
712
+ index and a warning from `akm health`.
713
+
714
+ **Without network access**, fill akm's cache directory (`akm info` prints
715
+ `cacheDir`; `AKM_CACHE_DIR` moves it) before the first `akm index`:
716
+
717
+ - `onnxruntime/<version>-<platform>-<arch>/` (for example
718
+ `onnxruntime/1.24.3-linux-x64/`) holding `onnxruntime_binding.node` and the
719
+ library it links (`libonnxruntime.so.1` on Linux,
720
+ `libonnxruntime.1.24.3.dylib` on macOS). Take both from
721
+ `package/bin/napi-v6/<platform>/<arch>/` in the `onnxruntime-node@1.24.3`
722
+ tarball (`npm pack onnxruntime-node@1.24.3`), or copy the folder from a
723
+ connected machine of the same platform after
724
+ `akm setup --yes --embedding local`.
725
+ - `models/Xenova/bge-small-en-v1.5/` holding `tokenizer.json`,
726
+ `tokenizer_config.json`, `config.json` and `onnx/model.onnx`, or set `HF_HOME`
727
+ to a directory that holds them and `HF_HUB_OFFLINE=1`.
728
+ - `opencode/2.1.4/opencode` (`opencode.exe` on Windows) for akm's own agent
729
+ runtime.
708
730
  The default is `"off"` so a bare or headless install (`akm bundle create`, `--yes`,
709
731
  `--config`) never silently downloads the local embedding model on first
710
732
  index. A config that has an `embedding` block and sets no `semanticSearchMode`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.10.261014-beta",
3
+ "version": "0.10.261015-beta",
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": [
@@ -34,9 +34,8 @@
34
34
  },
35
35
  "license": "MPL-2.0",
36
36
  "pinNotes": {
37
- "better-sqlite3@12.11.1": "Exact pin (#790). This is the SQLite driver akm loads on Node (src/storage/database.ts); Bun never touches it. The pin is about PREBUILT BINARIES, not API surface. better-sqlite3 ships one prebuild per Node ABI as a GitHub release asset and its install script is `prebuild-install || node-gyp rebuild` — so any (version, Node ABI) pair with no prebuild silently COMPILES FROM SOURCE against the headers of whatever Node is on the machine that day. The 11.x line predates Node 24 and declares no `engines` at all: its newest release (11.10.0, 2025-05-08) publishes ABI 108/115/127/131 (Node 18/20/22/23) and nothing for ABI 137 (Node 24). Node 24.19.0 then changed the public `node_object_wrap.h` so `node::ObjectWrap`'s ctor/dtor register and unregister an environment cleanup hook; a from-source 11.x build against those headers aborts at teardown in `Statement::~Statement()` with `RemoveEnvironmentCleanupHook ... Assertion (env) != nullptr`, intermittently, depending on GC timing. 12.11.1 publishes ABI 127/137/141/147 (Node 22/24/25/26) and declares `engines: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x`, so every Node akm supports installs a prebuilt binary and never compiles. Before bumping: confirm the target version publishes a prebuild for EVERY Node major in `engines` (probe https://github.com/WiseLibs/better-sqlite3/releases/download/vX.Y.Z/better-sqlite3-vX.Y.Z-node-vABI-linux-x64.tar.gz), not just that the version is newer. 13.x is the eventual destination — it moved to node-addon-api/N-API with prebuilds bundled in the npm tarball and no install script, which retires this failure mode entirely — but it is a fresh major rewrite of the binding, so it wants its own soak, not a patch release. The CI node-smoke job installs this exact string by reading it back out of this file (.github/workflows/ci.yml), so the two cannot drift.",
37
+ "better-sqlite3@13.0.3": "Exact pin. The SQLite driver akm loads on Node (src/storage/database.ts); Bun uses bun:sqlite and never loads it. 13.x is built on N-API (node-addon-api), so one binding per platform serves every Node version, and the npm tarball carries those bindings prebuilt in prebuilds/ for Linux (glibc and musl) and macOS on x64 and arm64 and for Windows on x64 and arm64. It declares no install script and sets gypfile: false, so it installs the same where npm runs install scripts and where it does not (npm 12 blocks dependency scripts by default). Not covered: 32-bit ARM Linux (linux-arm, linuxmusl-arm), which 12.x served only through its install script; akm under Node has no SQLite driver there. `bun install` tries to build it anyway (Bun ignores gypfile: false for a trusted package) and, as an optional dependency, drops it silently when that fails, which is why CI installs it with npm. This replaced 12.11.1, whose binding came per Node ABI from its install script (`prebuild-install || node-gyp rebuild`, #790); the earlier note asked for a soak before 13.x, and 13.0.3 soaks through the 0.10 betas instead. Before bumping: the tarball must still carry prebuilds/ for every platform above and declare no install script, and the CI node-smoke job, which installs this exact string read back out of this file (.github/workflows/ci.yml), must pass on every Node major in engines.",
38
38
  "@huggingface/tokenizers@0.1.3": "Exact pin: the tokenizer of the local embedding model (src/llm/embedders/local.ts). Pure JS with no dependencies. A different release can change the token ids and with them every stored vector, so re-run the real-model semantic gate (tests/integration/semantic-search-e2e.test.ts, which compares the vectors with tests/fixtures/embedder-reference-vectors.json) before changing it.",
39
- "onnxruntime-node@1.24.3": "Exact pin: the local embedding runtime. The standalone binary embeds this version's native library from node_modules/onnxruntime-node/bin/napi-v6/ (src/standalone/native/) and its addon is bound to exactly that library, so a bump moves this pin, the library file names in src/standalone/native/ (the macOS one carries the version), the list of platforms it ships a library for in src/llm/embedders/local.ts and the real-model semantic gate together.",
40
39
  "ajv@8.20.0": "Exact pin (devDependency, used only by tests: tests/tasks-schema.test.ts validates task sources against schemas/akm-task.json, tests/contracts/config-schema-drift.test.ts checks schemas/akm-config.json). Pinned in 4d7d77992 without a recorded reason; keep exact until the pin is next reviewed.",
41
40
  "cmd-shim@9.0.2": "Exact pin (devDependency), from cd5a3be12 \"pin cmd-shim for launcher tests\": tests/integration/install/package-launcher.test.ts writes the Windows shims npm would write with this package, where it used to load the copy inside the installed npm's own node_modules. That commit records no reason for an exact version over a range; keep exact until the pin is next reviewed."
42
41
  },
@@ -72,7 +71,6 @@
72
71
  "akm-migrate": "dist/akm-migrate"
73
72
  },
74
73
  "scripts": {
75
- "preinstall": "node -e \"var v=(process.versions.node||'0').split('.').map(function(n){return parseInt(n,10)||0});var ok=v[0]>=22;if(ok){process.exit(0)}console.error('\\n ERROR: the akm-cli npm package requires Node.js >= 22.\\n A working Bun >= 1.0 on PATH is optional and preferred for akm and akm-migrate.\\n Upgrade Node.js (https://nodejs.org), or install the runtime-free standalone binary:\\n curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\\n');process.exit(1)\"",
76
74
  "build": "rm -rf dist && bun scripts/gen-config-schema.ts &&bun run tsc --project ./tsconfig.build.json && bun scripts/copy-assets.ts && bun scripts/fix-esm-extensions.ts",
77
75
  "check": "bun run lint && bunx tsc --noEmit && bun run test:unit && bun run test:integration",
78
76
  "check:fast": "bun run lint && bunx tsc --noEmit && bun run test:unit",
@@ -109,7 +107,7 @@
109
107
  "zod-to-json-schema": "^3.23.0"
110
108
  },
111
109
  "optionalDependencies": {
112
- "better-sqlite3": "12.11.1",
110
+ "better-sqlite3": "13.0.3",
113
111
  "sqlite-vec": "^0.1.9"
114
112
  },
115
113
  "engines": {
@@ -123,7 +121,6 @@
123
121
  "fast-xml-parser": "^5.10.1",
124
122
  "marked": "^18.1.1",
125
123
  "node-html-parser": "^9.0.1",
126
- "onnxruntime-node": "1.24.3",
127
124
  "semver": "^7.6.0",
128
125
  "turndown": "^7.2.4",
129
126
  "yaml": "^2.8.4",
@@ -1,52 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /**
5
- * The ONNX Runtime shared library for a compiled standalone binary.
6
- *
7
- * `onnxruntime-node`'s addon links a shared library that sits beside it in the
8
- * package. A Bun executable extracts an embedded addon at load but not the
9
- * library it links, so the addon cannot start. The standalone build embeds the
10
- * library for its target (`src/standalone/native/`, chosen by
11
- * `scripts/build-standalone.ts`); this copies it once into akm's cache and
12
- * loads it with `bun:ffi`, after which the addon's own link to it is already
13
- * satisfied.
14
- *
15
- * Outside a standalone binary (`bun src/cli.ts`, the Node build) nothing here
16
- * is defined and nothing is evaluated: the package's own files are used.
17
- */
18
- import fs from "node:fs";
19
- import path from "node:path";
20
- import { getCacheDir } from "../core/paths.js";
21
- /**
22
- * Put the embedded library on disk and load it, before `onnxruntime-node` is
23
- * imported. Throws when the library cannot be loaded; the caller reports that
24
- * as the embedding runtime being unavailable.
25
- */
26
- export async function preloadOnnxRuntimeLibrary() {
27
- if (typeof AKM_STANDALONE_NATIVE_MODULE === "undefined")
28
- return;
29
- // The specifier is a build constant, so the standalone build bundles this one module and no other build bundles any.
30
- const { embeddedPath, fileName } = (await import(AKM_STANDALONE_NATIVE_MODULE));
31
- if (!embeddedPath || !fileName)
32
- return;
33
- const directory = path.join(getCacheDir(), "onnxruntime", `${AKM_ONNXRUNTIME_VERSION}-${process.platform}-${process.arch}`);
34
- const library = path.join(directory, fileName);
35
- const size = fs.statSync(embeddedPath).size;
36
- if (!fs.existsSync(library) || fs.statSync(library).size !== size) {
37
- fs.mkdirSync(directory, { recursive: true });
38
- const partial = `${library}.${process.pid}.tmp`;
39
- fs.writeFileSync(partial, fs.readFileSync(embeddedPath));
40
- try {
41
- fs.renameSync(partial, library);
42
- }
43
- catch (error) {
44
- // Another akm process copied it first and has it loaded, which Windows will not replace: use that copy.
45
- fs.rmSync(partial, { force: true });
46
- if (!fs.existsSync(library) || fs.statSync(library).size !== size)
47
- throw error;
48
- }
49
- }
50
- const { dlopen } = await import("bun:ffi");
51
- dlopen(library, { OrtGetApiBase: { args: [], returns: "ptr" } });
52
- }