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.
- package/CHANGELOG.md +53 -5
- package/README.md +9 -9
- package/dist/cli.js +379 -1448
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/commands/curate.js +263 -0
- package/dist/{info.js → commands/info.js} +17 -11
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +14 -2
- package/dist/{installed-kits.js → commands/installed-stashes.js} +122 -50
- package/dist/commands/migration-help.js +141 -0
- package/dist/{registry-search.js → commands/registry-search.js} +68 -9
- package/dist/commands/remember.js +178 -0
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +3 -3
- package/dist/{stash-show.js → commands/show.js} +106 -81
- package/dist/{stash-add.js → commands/source-add.js} +133 -67
- package/dist/{stash-clone.js → commands/source-clone.js} +15 -13
- package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
- package/dist/{vault.js → commands/vault.js} +43 -0
- package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
- package/dist/{asset-registry.js → core/asset-registry.js} +30 -6
- package/dist/{asset-spec.js → core/asset-spec.js} +13 -6
- package/dist/{common.js → core/common.js} +147 -50
- package/dist/{config.js → core/config.js} +288 -29
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +64 -8
- package/dist/{paths.js → core/paths.js} +4 -4
- package/dist/core/write-source.js +280 -0
- package/dist/{local-search.js → indexer/db-search.js} +49 -32
- package/dist/{db.js → indexer/db.js} +210 -81
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +153 -30
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +4 -7
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +97 -55
- package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
- package/dist/{walker.js → indexer/walker.js} +1 -1
- package/dist/{lockfile.js → integrations/lockfile.js} +29 -2
- package/dist/{llm.js → llm/client.js} +12 -48
- package/dist/llm/embedder.js +127 -0
- package/dist/llm/embedders/cache.js +47 -0
- package/dist/llm/embedders/local.js +152 -0
- package/dist/llm/embedders/remote.js +121 -0
- package/dist/llm/embedders/types.js +39 -0
- package/dist/llm/metadata-enhance.js +53 -0
- package/dist/output/cli-hints.js +301 -0
- package/dist/output/context.js +95 -0
- package/dist/{renderers.js → output/renderers.js} +57 -61
- package/dist/output/shapes.js +212 -0
- package/dist/output/text.js +520 -0
- package/dist/{registry-build-index.js → registry/build-index.js} +48 -32
- package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
- package/dist/registry/factory.js +33 -0
- package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
- package/dist/registry/providers/index.js +11 -0
- package/dist/{providers → registry/providers}/skills-sh.js +60 -4
- package/dist/{providers → registry/providers}/static-index.js +126 -56
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +10 -6
- package/dist/{detect.js → setup/detect.js} +0 -27
- package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
- package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
- package/dist/{setup.js → setup/setup.js} +162 -129
- package/dist/setup/steps.js +45 -0
- package/dist/{kit-include.js → sources/include.js} +1 -1
- package/dist/sources/provider-factory.js +36 -0
- package/dist/sources/provider.js +21 -0
- package/dist/sources/providers/filesystem.js +35 -0
- package/dist/{stash-providers → sources/providers}/git.js +218 -28
- package/dist/{stash-providers → sources/providers}/index.js +4 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/sources/providers/npm.js +160 -0
- package/dist/sources/providers/provider-utils.js +173 -0
- package/dist/sources/providers/sync-from-ref.js +45 -0
- package/dist/sources/providers/tar-utils.js +154 -0
- package/dist/{stash-providers → sources/providers}/website.js +60 -20
- package/dist/{stash-resolve.js → sources/resolve.js} +13 -12
- package/dist/{wiki.js → wiki/wiki.js} +18 -17
- package/dist/{workflow-authoring.js → workflows/authoring.js} +48 -17
- package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
- package/dist/{workflow-db.js → workflows/db.js} +1 -1
- package/dist/workflows/document-cache.js +20 -0
- package/dist/workflows/parser.js +379 -0
- package/dist/workflows/renderer.js +78 -0
- package/dist/{workflow-runs.js → workflows/runs.js} +84 -30
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- package/docs/README.md +30 -0
- package/docs/migration/release-notes/0.0.13.md +4 -0
- package/docs/migration/release-notes/0.1.0.md +6 -0
- package/docs/migration/release-notes/0.2.0.md +6 -0
- package/docs/migration/release-notes/0.3.0.md +5 -0
- package/docs/migration/release-notes/0.5.0.md +6 -0
- package/docs/migration/release-notes/0.6.0.md +75 -0
- package/docs/migration/release-notes/README.md +21 -0
- package/package.json +3 -2
- package/dist/embedder.js +0 -351
- package/dist/errors.js +0 -34
- package/dist/migration-help.js +0 -110
- package/dist/registry-factory.js +0 -19
- package/dist/registry-install.js +0 -532
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -1
- package/dist/stash-providers/filesystem.js +0 -41
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-providers/provider-utils.js +0 -11
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -251
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{warn.js → core/warn.js} +0 -0
- /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
- /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
- /package/dist/{github.js → integrations/github.js} +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /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,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.
|
|
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
|
-
}
|