akm-cli 0.5.0 → 0.6.0-rc2

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.
Files changed (118) hide show
  1. package/CHANGELOG.md +53 -5
  2. package/README.md +9 -9
  3. package/dist/cli.js +379 -1448
  4. package/dist/{completions.js → commands/completions.js} +1 -1
  5. package/dist/{config-cli.js → commands/config-cli.js} +109 -11
  6. package/dist/commands/curate.js +263 -0
  7. package/dist/{info.js → commands/info.js} +17 -11
  8. package/dist/{init.js → commands/init.js} +4 -4
  9. package/dist/{install-audit.js → commands/install-audit.js} +14 -2
  10. package/dist/{installed-kits.js → commands/installed-stashes.js} +122 -50
  11. package/dist/commands/migration-help.js +141 -0
  12. package/dist/{registry-search.js → commands/registry-search.js} +68 -9
  13. package/dist/commands/remember.js +178 -0
  14. package/dist/{stash-search.js → commands/search.js} +28 -69
  15. package/dist/{self-update.js → commands/self-update.js} +3 -3
  16. package/dist/{stash-show.js → commands/show.js} +106 -81
  17. package/dist/{stash-add.js → commands/source-add.js} +133 -67
  18. package/dist/{stash-clone.js → commands/source-clone.js} +15 -13
  19. package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
  20. package/dist/{vault.js → commands/vault.js} +43 -0
  21. package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
  22. package/dist/{asset-registry.js → core/asset-registry.js} +30 -6
  23. package/dist/{asset-spec.js → core/asset-spec.js} +13 -6
  24. package/dist/{common.js → core/common.js} +147 -50
  25. package/dist/{config.js → core/config.js} +288 -29
  26. package/dist/core/errors.js +90 -0
  27. package/dist/{frontmatter.js → core/frontmatter.js} +64 -8
  28. package/dist/{paths.js → core/paths.js} +4 -4
  29. package/dist/core/write-source.js +280 -0
  30. package/dist/{local-search.js → indexer/db-search.js} +49 -32
  31. package/dist/{db.js → indexer/db.js} +210 -81
  32. package/dist/{file-context.js → indexer/file-context.js} +3 -3
  33. package/dist/{indexer.js → indexer/indexer.js} +153 -30
  34. package/dist/{manifest.js → indexer/manifest.js} +10 -10
  35. package/dist/{matchers.js → indexer/matchers.js} +4 -7
  36. package/dist/{metadata.js → indexer/metadata.js} +9 -5
  37. package/dist/{search-source.js → indexer/search-source.js} +97 -55
  38. package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
  39. package/dist/{walker.js → indexer/walker.js} +1 -1
  40. package/dist/{lockfile.js → integrations/lockfile.js} +29 -2
  41. package/dist/{llm.js → llm/client.js} +12 -48
  42. package/dist/llm/embedder.js +127 -0
  43. package/dist/llm/embedders/cache.js +47 -0
  44. package/dist/llm/embedders/local.js +152 -0
  45. package/dist/llm/embedders/remote.js +121 -0
  46. package/dist/llm/embedders/types.js +39 -0
  47. package/dist/llm/metadata-enhance.js +53 -0
  48. package/dist/output/cli-hints.js +301 -0
  49. package/dist/output/context.js +95 -0
  50. package/dist/{renderers.js → output/renderers.js} +57 -61
  51. package/dist/output/shapes.js +212 -0
  52. package/dist/output/text.js +520 -0
  53. package/dist/{registry-build-index.js → registry/build-index.js} +48 -32
  54. package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
  55. package/dist/registry/factory.js +33 -0
  56. package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
  57. package/dist/registry/providers/index.js +11 -0
  58. package/dist/{providers → registry/providers}/skills-sh.js +60 -4
  59. package/dist/{providers → registry/providers}/static-index.js +126 -56
  60. package/dist/registry/providers/types.js +25 -0
  61. package/dist/{registry-resolve.js → registry/resolve.js} +10 -6
  62. package/dist/{detect.js → setup/detect.js} +0 -27
  63. package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
  64. package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
  65. package/dist/{setup.js → setup/setup.js} +162 -129
  66. package/dist/setup/steps.js +45 -0
  67. package/dist/{kit-include.js → sources/include.js} +1 -1
  68. package/dist/sources/provider-factory.js +36 -0
  69. package/dist/sources/provider.js +21 -0
  70. package/dist/sources/providers/filesystem.js +35 -0
  71. package/dist/{stash-providers → sources/providers}/git.js +218 -28
  72. package/dist/{stash-providers → sources/providers}/index.js +4 -4
  73. package/dist/sources/providers/install-types.js +14 -0
  74. package/dist/sources/providers/npm.js +160 -0
  75. package/dist/sources/providers/provider-utils.js +173 -0
  76. package/dist/sources/providers/sync-from-ref.js +45 -0
  77. package/dist/sources/providers/tar-utils.js +154 -0
  78. package/dist/{stash-providers → sources/providers}/website.js +60 -20
  79. package/dist/{stash-resolve.js → sources/resolve.js} +13 -12
  80. package/dist/{wiki.js → wiki/wiki.js} +18 -17
  81. package/dist/{workflow-authoring.js → workflows/authoring.js} +48 -17
  82. package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
  83. package/dist/{workflow-db.js → workflows/db.js} +1 -1
  84. package/dist/workflows/document-cache.js +20 -0
  85. package/dist/workflows/parser.js +379 -0
  86. package/dist/workflows/renderer.js +78 -0
  87. package/dist/{workflow-runs.js → workflows/runs.js} +84 -30
  88. package/dist/workflows/schema.js +11 -0
  89. package/dist/workflows/validator.js +48 -0
  90. package/docs/README.md +30 -0
  91. package/docs/migration/release-notes/0.0.13.md +4 -0
  92. package/docs/migration/release-notes/0.1.0.md +6 -0
  93. package/docs/migration/release-notes/0.2.0.md +6 -0
  94. package/docs/migration/release-notes/0.3.0.md +5 -0
  95. package/docs/migration/release-notes/0.5.0.md +6 -0
  96. package/docs/migration/release-notes/0.6.0.md +75 -0
  97. package/docs/migration/release-notes/README.md +21 -0
  98. package/package.json +3 -2
  99. package/dist/embedder.js +0 -351
  100. package/dist/errors.js +0 -34
  101. package/dist/migration-help.js +0 -110
  102. package/dist/registry-factory.js +0 -19
  103. package/dist/registry-install.js +0 -532
  104. package/dist/ripgrep.js +0 -2
  105. package/dist/stash-provider-factory.js +0 -35
  106. package/dist/stash-provider.js +0 -1
  107. package/dist/stash-providers/filesystem.js +0 -41
  108. package/dist/stash-providers/openviking.js +0 -348
  109. package/dist/stash-providers/provider-utils.js +0 -11
  110. package/dist/stash-types.js +0 -1
  111. package/dist/workflow-markdown.js +0 -251
  112. /package/dist/{markdown.js → core/markdown.js} +0 -0
  113. /package/dist/{warn.js → core/warn.js} +0 -0
  114. /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
  115. /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
  116. /package/dist/{github.js → integrations/github.js} +0 -0
  117. /package/dist/{registry-provider.js → registry/types.js} +0 -0
  118. /package/dist/{registry-types.js → sources/types.js} +0 -0
package/docs/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Documentation
2
+
3
+ ## Getting Started
4
+
5
+ - [Concepts](concepts.md) -- Stashes, registries, asset types, and refs
6
+ - [Getting Started](getting-started.md) -- Quick setup guide
7
+ - [Agent Install Guide](agents/agent-install.md) -- Step-by-step automated install for agents
8
+ - [Stash Maker's Guide](stash-makers.md) -- Build and share a stash on GitHub, npm, or a network directory
9
+
10
+ ## Upgrading
11
+
12
+ - [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
13
+
14
+ ## Reference
15
+
16
+ - [CLI](cli.md) -- All `akm` commands and flags
17
+ - [Registry](registry.md) -- Registries, search, hosting, and managing sources
18
+ - [Configuration](configuration.md) -- Providers, settings, and Ollama setup
19
+ - [Filesystem](technical/filesystem.md) -- Directory layout and `.stash.json` schema
20
+
21
+ ## Internals
22
+
23
+ - [Search](technical/search.md) -- Hybrid search architecture and scoring
24
+ - [Indexing](technical/indexing.md) -- How the search index is built
25
+ - [Classification](technical/classification.md) -- Matcher and renderer behavior
26
+ - [Show Response](technical/show-response.md) -- `akm show` output fields by asset type
27
+ - [Testing Workflow](technical/testing-workflow.md) -- End-to-end, Docker, deployment, and upgrade validation
28
+ - [Ref Format](technical/ref.md) -- Wire format for asset references
29
+ - [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
30
+ - [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
@@ -0,0 +1,4 @@
1
+ Migration notes for akm v0.0.13
2
+
3
+ - Initial public release.
4
+ - No migration steps are required for earlier akm versions.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.1.0
2
+
3
+ - The package and project were rebranded from Agent-i-Kit to akm.
4
+ - Update package references from `agent-i-kit` to `akm-cli`.
5
+ - Update config, registry, plugin, path, and environment-variable references from `agent-i-kit` / `AGENT_I_KIT_*` to `akm` / `AKM_*`.
6
+ - The `tool` asset type and `submit` command were removed.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.2.0
2
+
3
+ - Asset refs are user-facing `type:name` values; do not rely on URI-style refs.
4
+ - The old fixed asset-type union was replaced by an extensible asset type system.
5
+ - `tool` assets were removed; use `script` assets instead.
6
+ - Config and docs should treat remote provider scores and local scores as part of one shared search pipeline.
@@ -0,0 +1,5 @@
1
+ Migration notes for akm v0.3.0
2
+
3
+ - The old `stash` and `kit` command groups were folded into the top-level CLI.
4
+ - Use `akm add`, `akm list`, and `akm remove` instead of the older split command surfaces.
5
+ - Documentation and examples from older releases should be updated to the unified source model.
@@ -0,0 +1,6 @@
1
+ Migration notes for akm v0.5.0
2
+
3
+ - New top-level surfaces: `akm wiki …`, `akm workflow …`, `akm vault …`, and `akm save`.
4
+ - If you tried the unreleased single-wiki LLM prototype, move to the new `akm wiki …` workflow.
5
+ - Removed from the prototype surface: `akm lint`, `akm import --llm`, `akm import --dry-run`, `knowledge.pageKinds`, and the old ingest/lint LLM prompts.
6
+ - Existing raw wiki-like content should be moved into `wikis/<name>/raw/` and then managed with the new wiki commands.
@@ -0,0 +1,75 @@
1
+ Migration notes for akm v0.6.0
2
+
3
+ This release ships the v1 architecture refactor on top of the earlier 0.6
4
+ terminology cut (`kit` → `stash` in the registry wire format). The CLI
5
+ command surface is unchanged. Most users have nothing to do.
6
+
7
+ Locked v1 decisions:
8
+ - `writable` defaults to `true` on `filesystem`, `false` on `git` /
9
+ `website` / `npm`.
10
+ - Registry results are off by default in `akm search`; pass
11
+ `--include-registry` (or `--source registry|both`) to include them.
12
+ - Write target resolves as: `--target` flag → `defaultWriteTarget` config
13
+ key → `stashDir` (working stash) → `ConfigError`.
14
+ - `writable: true` on `website` / `npm` is rejected at config load.
15
+
16
+ Manual actions required:
17
+ - If your config contains an `openviking` source, remove it. akm exits at
18
+ load with `ConfigError` and points to `akm config sources remove <name>`.
19
+ API-backed sources will return as a separate `QuerySource` tier post-v1.
20
+ - If your config sets `writable: true` on a `website` or `npm` source,
21
+ drop the flag or re-add the path as a `filesystem` source.
22
+ - If you previously ran `akm enable context-hub` / `akm disable
23
+ context-hub`, those toggles are gone. Add Context Hub as a regular git
24
+ source with `akm add github:andrewyng/context-hub`, or list it as a kit
25
+ entry in your registry.
26
+
27
+ Automatic (no action required):
28
+ - Config key `stashes[]` is loaded as `sources[]` with one deprecation
29
+ warning. The new key is persisted on the next `akm config` write.
30
+ - `stash.lock` is renamed to `akm.lock` on startup.
31
+ - `config.installed[]` entries are mapped to `config.sources[]` plus
32
+ `akm.lock` records.
33
+ - `stashDir` is loaded as an implicit `primary: true` filesystem source.
34
+ - Stash type aliases `"context-hub"` and `"github"` normalize to `"git"`.
35
+
36
+ What changed under the hood (no user impact):
37
+ - `SourceProvider` interface simplified to `{ name, kind, init, path,
38
+ sync? }`. Plugin authors should re-read the v1 spec.
39
+ - Search and show consult the unified FTS5 index and read files from
40
+ disk; the per-provider fan-out is gone.
41
+ - Single write helper (`writeAssetToSource`) handles filesystem and git
42
+ destinations.
43
+ - Error classes own their `hint()` text. Hints now print to stderr inline
44
+ without `--verbose`.
45
+ - Registry providers (`static-index`, `skills-sh`) loop through a uniform
46
+ interface; the Context Hub special case was deleted.
47
+ - `src/` reorganized into purpose-named subdirectories (`commands/`,
48
+ `core/`, `indexer/`, `output/`, `registry/`, `setup/`, `sources/`,
49
+ `wiki/`, `workflows/`). akm has no public API, so external consumers
50
+ are unaffected.
51
+ - Removed legacy re-export shims: `src/llm.ts`, `src/registry-provider.ts`,
52
+ `src/ripgrep.ts`.
53
+
54
+ Earlier-in-cycle terminology cut (still applies):
55
+ - The registry wire format `kits[]` is renamed to `stashes[]` with schema
56
+ bumped to v3. `akm-cli >= 0.6.0` only parses v3 indexes.
57
+ - npm packages and GitHub repos are discovered via the `akm-stash`
58
+ keyword/topic only. `akm-kit` and `agentikit` are no longer honored.
59
+ - Internal types: `RegistryKitEntry` → `RegistryStashEntry`,
60
+ `InstalledKitEntry` → `InstalledStashEntry`,
61
+ `KitInstallStatus` → `StashInstallStatus`, `KitSource` → `StashSource`.
62
+ - Documentation: `docs/kit-makers.md` → `docs/stash-makers.md`.
63
+
64
+ CLI command surface (unchanged in 0.6.0):
65
+ add | remove | list | update | search | show | clone | index | setup |
66
+ remember | import | feedback | registry * | info | curate | workflow |
67
+ vault | wiki | enable | disable | completions | upgrade | save | help |
68
+ hints
69
+
70
+ Full migration guides:
71
+ - https://github.com/itlackey/akm/blob/main/docs/migration/v1.md (v1
72
+ architecture refactor — read this first if you are coming from 0.5.x or
73
+ an earlier 0.6 pre-release)
74
+ - https://github.com/itlackey/akm/blob/main/docs/migration/v0.5-to-v0.6.md
75
+ (terminology cut, registry schema v3, publisher changes)
@@ -0,0 +1,21 @@
1
+ # Release-notes corpus for `akm help migrate`
2
+
3
+ This directory holds one `.md` file per release. Each file is the short,
4
+ focused migration note that `akm help migrate <version>` prints to the
5
+ terminal. The longform cross-release guides (e.g. `v0.5-to-v0.6.md`)
6
+ live one level up in `docs/migration/`.
7
+
8
+ ## Adding notes for a new release
9
+
10
+ 1. Create `<version>.md` in this directory (e.g. `0.7.0.md`).
11
+ 2. Write plain text — the content is printed verbatim. Start the body
12
+ with a line like `Migration notes for akm v0.7.0`, then list the
13
+ automatic migrations, manual actions, publisher changes, etc.
14
+ 3. The file ships with the published package via `package.json` →
15
+ `files[]` and is resolved at runtime from either `src/` or `dist/`,
16
+ so no code change is required.
17
+ 4. Link to the longform guide in the last paragraph if one exists.
18
+
19
+ Keep each note self-contained: it should tell a user everything they
20
+ need to upgrade without requiring them to open a browser (the longform
21
+ guide link is the last resort, not the first).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-rc2",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Kit Manager) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
6
6
  "keywords": [
@@ -35,7 +35,8 @@
35
35
  "dist",
36
36
  "README.md",
37
37
  "CHANGELOG.md",
38
- "LICENSE"
38
+ "LICENSE",
39
+ "docs/migration/release-notes"
39
40
  ],
40
41
  "bin": {
41
42
  "akm": "dist/cli.js"
package/dist/embedder.js DELETED
@@ -1,351 +0,0 @@
1
- import path from "node:path";
2
- import { fetchWithTimeout, isHttpUrl } from "./common";
3
- import { getCacheDir } from "./paths";
4
- import { warn } from "./warn";
5
- // ── Default local model ─────────────────────────────────────────────────────
6
- /**
7
- * Default local transformer model for embeddings.
8
- * `bge-small-en-v1.5` scores higher on MTEB benchmarks than the previous
9
- * `all-MiniLM-L6-v2` at the same 384-dimension footprint.
10
- */
11
- export const DEFAULT_LOCAL_MODEL = "Xenova/bge-small-en-v1.5";
12
- /**
13
- * Return the local model name that will be used for embedding.
14
- * When `overrideModel` is provided it takes precedence; otherwise
15
- * the default model is returned.
16
- */
17
- function getLocalModelName(overrideModel) {
18
- return overrideModel || DEFAULT_LOCAL_MODEL;
19
- }
20
- const LOCAL_EMBEDDER_DTYPE = "fp32";
21
- const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
22
- // Cache the promise itself (not the resolved result) so concurrent calls share
23
- // the same initialisation work and never download the model twice.
24
- // The cache is keyed by model name so switching models gets a fresh pipeline.
25
- let localEmbedderPromise;
26
- let localEmbedderModelName;
27
- async function getLocalEmbedder(modelName) {
28
- const resolvedModel = getLocalModelName(modelName);
29
- // If the cached pipeline was created for a different model, discard it.
30
- if (localEmbedderPromise && localEmbedderModelName !== resolvedModel) {
31
- localEmbedderPromise = undefined;
32
- localEmbedderModelName = undefined;
33
- }
34
- if (!localEmbedderPromise) {
35
- localEmbedderModelName = resolvedModel;
36
- localEmbedderPromise = (async () => {
37
- // Ensure HuggingFace model cache lives in a stable location outside
38
- // node_modules so it survives package reinstalls.
39
- if (!process.env.HF_HOME) {
40
- process.env.HF_HOME = path.join(getCacheDir(), "models");
41
- }
42
- let pipeline;
43
- try {
44
- const mod = await import("@huggingface/transformers");
45
- pipeline = mod.pipeline;
46
- }
47
- catch (importError) {
48
- const msg = importError instanceof Error ? importError.message : String(importError);
49
- if (/Cannot find module|MODULE_NOT_FOUND|Cannot resolve/i.test(msg)) {
50
- throw new Error("Semantic search requires @huggingface/transformers. Install it with: bun add @huggingface/transformers");
51
- }
52
- throw new Error(`Failed to load embedding runtime: ${msg}. Check platform compatibility.`);
53
- }
54
- const pipelineFn = pipeline;
55
- return createLocalPipeline(pipelineFn, resolvedModel);
56
- })();
57
- // HI-13: Clear the cached promise on failure so the next call retries
58
- // instead of permanently rejecting every subsequent call with the same error.
59
- localEmbedderPromise.catch(() => {
60
- localEmbedderPromise = undefined;
61
- localEmbedderModelName = undefined;
62
- });
63
- }
64
- return localEmbedderPromise;
65
- }
66
- async function createLocalPipeline(pipelineFn, modelName) {
67
- try {
68
- return await pipelineFn("feature-extraction", modelName, { dtype: LOCAL_EMBEDDER_DTYPE });
69
- }
70
- catch (error) {
71
- if (!shouldRetryWithoutExplicitDtype(error)) {
72
- throw error;
73
- }
74
- warn('Local embedding model "%s" rejected explicit dtype "%s"; retrying with explicit fallback dtype "%s".', modelName, LOCAL_EMBEDDER_DTYPE, LOCAL_EMBEDDER_FALLBACK_DTYPE);
75
- return pipelineFn("feature-extraction", modelName, { dtype: LOCAL_EMBEDDER_FALLBACK_DTYPE });
76
- }
77
- }
78
- function shouldRetryWithoutExplicitDtype(error) {
79
- const message = error instanceof Error ? error.message : String(error);
80
- return /dtype|fp32|precision|quant/i.test(message);
81
- }
82
- export function resetLocalEmbedder() {
83
- localEmbedderPromise = undefined;
84
- localEmbedderModelName = undefined;
85
- }
86
- async function embedLocal(text, modelName) {
87
- const model = await getLocalEmbedder(modelName);
88
- const result = await model(text, { pooling: "mean", normalize: true });
89
- return Array.from(result.data);
90
- }
91
- // ── Vector normalization ─────────────────────────────────────────────────────
92
- /**
93
- * L2-normalize a vector to unit length.
94
- * Required for remote embeddings because the scoring pipeline's L2-to-cosine
95
- * conversion formula (1 - distance^2/2) is only correct for unit vectors.
96
- * The local embedder already normalizes via `normalize: true`.
97
- */
98
- function l2Normalize(vec) {
99
- const norm = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
100
- if (norm === 0)
101
- return vec;
102
- return vec.map((v) => v / norm);
103
- }
104
- // ── OpenAI-compatible remote embedder ───────────────────────────────────────
105
- function normalizeEmbeddingEndpoint(endpoint) {
106
- let parsed;
107
- try {
108
- parsed = new URL(endpoint);
109
- }
110
- catch {
111
- return endpoint;
112
- }
113
- const normalizedPath = parsed.pathname.replace(/\/+$/, "");
114
- if (normalizedPath.endsWith("/embeddings")) {
115
- return parsed.toString();
116
- }
117
- parsed.pathname = normalizedPath ? `${normalizedPath}/embeddings` : "/embeddings";
118
- return parsed.toString();
119
- }
120
- function embeddingEndpointPathHint(endpoint) {
121
- const normalizedEndpoint = normalizeEmbeddingEndpoint(endpoint);
122
- if (normalizedEndpoint !== endpoint) {
123
- return ` Check that your endpoint includes the full embeddings path (for example "${normalizedEndpoint}", not just "${endpoint}").`;
124
- }
125
- return "";
126
- }
127
- async function embedRemote(text, config) {
128
- const headers = { "Content-Type": "application/json" };
129
- if (config.apiKey) {
130
- headers.Authorization = `Bearer ${config.apiKey}`;
131
- }
132
- const body = {
133
- input: text,
134
- model: config.model,
135
- };
136
- if (config.dimension) {
137
- body.dimensions = config.dimension;
138
- }
139
- const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(config.endpoint), {
140
- method: "POST",
141
- headers,
142
- body: JSON.stringify(body),
143
- });
144
- if (!response.ok) {
145
- const body = await response.text().catch(() => "");
146
- throw new Error(`Embedding request failed (${response.status}): ${body}`);
147
- }
148
- const json = (await response.json());
149
- if (!json.data?.[0]?.embedding) {
150
- throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(config.endpoint)}`);
151
- }
152
- return l2Normalize(json.data[0].embedding);
153
- }
154
- // ── Helpers ──────────────────────────────────────────────────────────────────
155
- /** Check whether an EmbeddingConnectionConfig has a valid remote endpoint. */
156
- function hasRemoteEndpoint(config) {
157
- return isHttpUrl(config.endpoint);
158
- }
159
- // ── LRU embedding cache ─────────────────────────────────────────────────────
160
- // Caches query embeddings to avoid redundant computation for repeated queries.
161
- // Uses a simple Map with LRU eviction (delete + re-insert to move to end).
162
- const EMBED_CACHE_MAX = 100;
163
- const embedCache = new Map();
164
- /**
165
- * Build a cache key from query text and optional config.
166
- * Different endpoints/models should not share cached embeddings.
167
- * apiKey deliberately excluded: same endpoint+model produce identical embeddings regardless of auth
168
- */
169
- function embedCacheKey(text, config) {
170
- if (!config)
171
- return `local::${text}`;
172
- const endpoint = config.endpoint || "";
173
- const model = config.model || config.localModel || "";
174
- return `${endpoint}:${model}:${text}`;
175
- }
176
- /**
177
- * Clear the embedding cache. Call when the embedding model changes
178
- * or when you want to force fresh embeddings.
179
- */
180
- export function clearEmbeddingCache() {
181
- embedCache.clear();
182
- }
183
- // ── Public API ──────────────────────────────────────────────────────────────
184
- /**
185
- * Generate an embedding for the given text.
186
- * If embeddingConfig has a remote endpoint, uses the configured OpenAI-compatible endpoint.
187
- * Otherwise falls back to local @huggingface/transformers using the model from
188
- * `embeddingConfig.localModel` or `DEFAULT_LOCAL_MODEL`.
189
- *
190
- * Results are cached in an LRU cache (max ~100 entries) keyed by query text
191
- * and embedding config. Repeated identical queries return the cached vector.
192
- */
193
- export async function embed(text, embeddingConfig) {
194
- const key = embedCacheKey(text, embeddingConfig);
195
- // Check cache first
196
- const cached = embedCache.get(key);
197
- if (cached) {
198
- // Move to end (most recently used) for LRU ordering
199
- embedCache.delete(key);
200
- embedCache.set(key, cached);
201
- return cached;
202
- }
203
- // Compute the embedding
204
- const result = embeddingConfig && hasRemoteEndpoint(embeddingConfig)
205
- ? await embedRemote(text, embeddingConfig)
206
- : await embedLocal(text, embeddingConfig?.localModel);
207
- // Evict oldest entry if at capacity
208
- if (embedCache.size >= EMBED_CACHE_MAX) {
209
- const oldest = embedCache.keys().next().value;
210
- if (oldest !== undefined) {
211
- embedCache.delete(oldest);
212
- }
213
- }
214
- embedCache.set(key, result);
215
- return result;
216
- }
217
- // ── Batch embedding ─────────────────────────────────────────────────────────
218
- /**
219
- * Generate embeddings for multiple texts in batch.
220
- * Uses the OpenAI-compatible batch API for remote endpoints (batches of 100).
221
- * Falls back to sequential embedding for local transformer pipeline.
222
- */
223
- export async function embedBatch(texts, embeddingConfig) {
224
- if (texts.length === 0)
225
- return [];
226
- if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
227
- return embedRemoteBatch(texts, embeddingConfig);
228
- }
229
- // Local transformer: process sequentially (pipeline handles one at a time)
230
- const localModel = embeddingConfig?.localModel;
231
- const results = [];
232
- for (const text of texts) {
233
- results.push(await embedLocal(text, localModel));
234
- }
235
- return results;
236
- }
237
- async function embedRemoteBatch(texts, config) {
238
- const BATCH_SIZE = 100;
239
- const results = [];
240
- const headers = { "Content-Type": "application/json" };
241
- if (config.apiKey) {
242
- headers.Authorization = `Bearer ${config.apiKey}`;
243
- }
244
- for (let i = 0; i < texts.length; i += BATCH_SIZE) {
245
- const batch = texts.slice(i, i + BATCH_SIZE);
246
- const body = {
247
- input: batch,
248
- model: config.model,
249
- };
250
- if (config.dimension) {
251
- body.dimensions = config.dimension;
252
- }
253
- const response = await fetchWithTimeout(normalizeEmbeddingEndpoint(config.endpoint), {
254
- method: "POST",
255
- headers,
256
- body: JSON.stringify(body),
257
- });
258
- if (!response.ok) {
259
- const respBody = await response.text().catch(() => "");
260
- throw new Error(`Embedding batch request failed (${response.status}): ${respBody}`);
261
- }
262
- const json = (await response.json());
263
- if (!json.data || json.data.length !== batch.length) {
264
- throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(config.endpoint)}`);
265
- }
266
- // Sort by index to guarantee correct order (OpenAI API doesn't guarantee order)
267
- const sorted = [...json.data].sort((a, b) => a.index - b.index);
268
- for (const [idx, d] of sorted.entries()) {
269
- if (!Array.isArray(d.embedding)) {
270
- throw new Error(`Unexpected embedding at batch index ${idx}: missing or invalid`);
271
- }
272
- results.push(l2Normalize(d.embedding));
273
- }
274
- }
275
- return results;
276
- }
277
- // ── Similarity ──────────────────────────────────────────────────────────────
278
- export function cosineSimilarity(a, b) {
279
- if (a.length !== b.length) {
280
- // MD-4: Return 0 on dimension mismatch rather than silently computing on a
281
- // truncated view, which would produce meaningless similarity scores.
282
- warn("cosineSimilarity: vector dimension mismatch (%d vs %d) — re-index recommended", a.length, b.length);
283
- return 0;
284
- }
285
- const len = a.length;
286
- if (len === 0)
287
- return 0;
288
- let dot = 0, magA = 0, magB = 0;
289
- for (let i = 0; i < len; i++) {
290
- dot += a[i] * b[i];
291
- magA += a[i] * a[i];
292
- magB += b[i] * b[i];
293
- }
294
- const denom = Math.sqrt(magA) * Math.sqrt(magB);
295
- return denom === 0 ? 0 : dot / denom;
296
- }
297
- // ── Availability check ──────────────────────────────────────────────────────
298
- /**
299
- * Check whether the `@huggingface/transformers` package can be imported.
300
- * Returns `true` if it can, `false` otherwise.
301
- */
302
- export async function isTransformersAvailable() {
303
- try {
304
- await import("@huggingface/transformers");
305
- return true;
306
- }
307
- catch {
308
- return false;
309
- }
310
- }
311
- /**
312
- * Check whether embedding is available with a detailed reason on failure.
313
- */
314
- export async function checkEmbeddingAvailability(embeddingConfig) {
315
- if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
316
- try {
317
- await embedRemote("test", embeddingConfig);
318
- return { available: true };
319
- }
320
- catch (err) {
321
- return {
322
- available: false,
323
- reason: "remote-unreachable",
324
- message: err instanceof Error ? err.message : String(err),
325
- };
326
- }
327
- }
328
- // Check if the package is importable before attempting the model download.
329
- if (!(await isTransformersAvailable())) {
330
- return {
331
- available: false,
332
- reason: "missing-package",
333
- message: "@huggingface/transformers is not installed.",
334
- };
335
- }
336
- try {
337
- await getLocalEmbedder(embeddingConfig?.localModel);
338
- return { available: true };
339
- }
340
- catch (err) {
341
- return {
342
- available: false,
343
- reason: "model-download-failed",
344
- message: err instanceof Error ? err.message : String(err),
345
- };
346
- }
347
- }
348
- export async function isEmbeddingAvailable(embeddingConfig) {
349
- const result = await checkEmbeddingAvailability(embeddingConfig);
350
- return result.available;
351
- }
package/dist/errors.js DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * Typed error classes for structured exit code classification.
3
- *
4
- * - ConfigError -> exit 78 (configuration / environment problems)
5
- * - UsageError -> exit 2 (bad CLI arguments or invalid input)
6
- * - NotFoundError -> exit 1 (requested resource missing)
7
- */
8
- /** Raised when configuration or environment is invalid or missing. */
9
- export class ConfigError extends Error {
10
- constructor(msg) {
11
- super(msg);
12
- this.name = "ConfigError";
13
- // Fixes `instanceof` checks under ES5 transpilation targets.
14
- Object.setPrototypeOf(this, new.target.prototype);
15
- }
16
- }
17
- /** Raised when the user supplies invalid arguments or input. */
18
- export class UsageError extends Error {
19
- constructor(msg) {
20
- super(msg);
21
- this.name = "UsageError";
22
- // Fixes `instanceof` checks under ES5 transpilation targets.
23
- Object.setPrototypeOf(this, new.target.prototype);
24
- }
25
- }
26
- /** Raised when a requested resource (asset, entry, file) is not found. */
27
- export class NotFoundError extends Error {
28
- constructor(msg) {
29
- super(msg);
30
- this.name = "NotFoundError";
31
- // Fixes `instanceof` checks under ES5 transpilation targets.
32
- Object.setPrototypeOf(this, new.target.prototype);
33
- }
34
- }