akm-cli
Version:
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 Cur
103 lines (102 loc) • 4.86 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* Bundle-map config write helpers (0.9.0 config-shape cutover, spec §10.1).
*
* The `stashDir`/`sources[]`/`installed[]` trio is retired: every source is now
* a `bundles.<slug>` entry, and the primary working stash is the `defaultBundle`.
* These helpers give the source writers (`akm bundle create`, `akm setup`, `akm bundle add`,
* `akm sources add/remove`) one place to derive slug-legal bundle keys (via the
* shared {@link deriveBundleId} — D-R5), locate an existing bundle by its source
* descriptor, and set/repoint the primary bundle — without duplicating the
* keying rules across call sites.
*/
import path from "node:path";
import { bundleKeyForContentRoot, primaryBundlePath } from "../../core/config/config.js";
import { UsageError } from "../../core/errors.js";
import { deriveBundleId } from "../../indexer/installations.js";
export { primaryBundlePath };
/**
* Insert or move a bundle at an explicit config-map position. Object insertion
* order is bundle resolution priority after `defaultBundle`, which always
* remains first. Without a position an existing key keeps its place and a new
* key is appended.
*/
export function placeBundle(bundles, key, entry, position = {}) {
if (position.before && position.after) {
throw new UsageError("Only one of --before or --after may be used.");
}
const target = position.before ?? position.after;
if (!target)
return { ...bundles, [key]: entry };
if (target === key)
throw new UsageError(`Bundle "${key}" cannot be positioned relative to itself.`);
if (!(target in bundles))
throw new UsageError(`Bundle position target "${target}" is not configured.`);
const ordered = {};
for (const [existingKey, existingEntry] of Object.entries(bundles)) {
if (existingKey === key)
continue;
if (position.before === existingKey)
ordered[key] = entry;
ordered[existingKey] = existingEntry;
if (position.after === existingKey)
ordered[key] = entry;
}
return ordered;
}
/**
* Upsert the primary filesystem bundle (`{ path, writable: true }`) and point
* `defaultBundle` at it. Reuses the current default key when it already names a
* filesystem bundle (so re-pointing the primary keeps a stable id). Otherwise,
* reuses whichever configured bundle ALREADY resolves to `stashDir`'s content
* root (issue #870 — `AKM_BUNDLE_DIR`/`--dir` pointed at a directory already
* configured under a different id must not mint a second bundle for it); only
* when no bundle owns that root yet is a fresh slug-legal key derived.
*/
export function withPrimaryBundle(config, stashDir) {
const bundles = { ...(config.bundles ?? {}) };
let key = config.defaultBundle;
if (!key || !(key in bundles) || typeof bundles[key]?.path !== "string") {
key =
bundleKeyForContentRoot(config, path.resolve(stashDir)) ??
deriveBundleId(undefined, stashDir, new Set(Object.keys(bundles)));
}
bundles[key] = {
...bundles[key],
path: stashDir,
writable: true,
components: { main: { root: ".", adapter: "akm", writable: true } },
};
return { ...config, bundles, defaultBundle: key };
}
/** Existing bundle key whose filesystem descriptor resolves to `resolvedPath`, or `undefined`. */
export function bundleKeyForPath(config, resolvedPath) {
for (const [key, bundle] of Object.entries(config.bundles ?? {})) {
if (typeof bundle.path === "string" && path.resolve(bundle.path) === resolvedPath)
return key;
}
return undefined;
}
/** Existing bundle key whose git/website/npm descriptor matches `url`, or `undefined`. */
export function bundleKeyForUrl(config, url) {
for (const [key, bundle] of Object.entries(config.bundles ?? {})) {
if (bundle.git === url || bundle.website?.url === url || bundle.npm === url)
return key;
}
return undefined;
}
/**
* Derive a slug-legal, batch-unique bundle key for a new source: prefer the
* caller's `preferredName` when it is a legal slug, else slug the seed locator
* (path/url) — the shared {@link deriveBundleId} rule (D-R5), made unique against
* the currently-configured bundle keys.
*
* Every add path validates an explicit `--name` with
* {@link validateExplicitBundleName} before calling in (an illegal or taken
* name fails loudly), so a `preferredName` that reaches here is returned as is.
*/
export function nextBundleKey(bundles, preferredName, seedLocator) {
return deriveBundleId(preferredName, seedLocator, new Set(Object.keys(bundles)));
}