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
102 lines (101 loc) • 3.96 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/.
import fs from "node:fs";
import path from "node:path";
import { assetPathForName, stashDirFor } from "../../core/asset/asset-placement.js";
import { parseRefInput } from "../../core/asset/resolve-ref.js";
import { isWithin } from "../../core/common.js";
import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
import { lookup } from "../indexer.js";
import { deriveInstallations } from "../installations.js";
import { resolveSourceEntries } from "../search/search-source.js";
function normalizeRef(ref) {
// String inputs use the current `[bundle//]conceptId` grammar.
return typeof ref === "string" ? parseRefInput(ref) : ref;
}
function buildDiskCandidates(sourcePath, ref, preserveDirectNameFallback) {
const placementStashDir = stashDirFor(ref.type);
// Opaque adapter conceptIds are index-resolved. Core cannot infer their
// adapter-owned serialization or path extension before indexing.
if (placementStashDir === undefined)
return [];
const typeDir = path.join(sourcePath, placementStashDir);
const candidates = [
assetPathForName(ref.type, typeDir, ref.name),
path.join(sourcePath, ref.type, `${ref.name}.md`),
path.join(sourcePath, ref.type, ref.name),
];
if (preserveDirectNameFallback) {
candidates.push(path.join(sourcePath, `${ref.name}.md`), path.join(sourcePath, ref.name));
}
return candidates;
}
function resolveDirectoryEntry(filePath, directoryIndexNames) {
let stat;
try {
stat = fs.statSync(filePath);
}
catch {
return null;
}
if (stat.isFile())
return filePath;
if (!stat.isDirectory())
return null;
for (const indexName of directoryIndexNames) {
const candidate = path.join(filePath, indexName);
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile())
return candidate;
}
return null;
}
async function resolveViaIndex(ref) {
try {
const entry = await lookup(ref);
return entry?.filePath ?? null;
}
catch {
return null;
}
}
function resolveViaDisk(ref, options) {
let sources = resolveSourceEntries(options.stashDir);
if (options.honorOrigin !== false) {
sources = resolveSourcesForOrigin(ref.origin, sources);
}
const directoryIndexNames = options.directoryIndexNames ?? ["SKILL.md"];
const preserveDirectNameFallback = options.preserveDirectNameFallback ?? true;
const installations = deriveInstallations(sources);
for (const [index, source] of sources.entries()) {
const bundleId = installations[index]?.id;
if (options.writableBundleIds && (!bundleId || !options.writableBundleIds.has(bundleId)))
continue;
const candidates = buildDiskCandidates(source.path, ref, preserveDirectNameFallback);
for (const candidate of candidates) {
if (!fs.existsSync(candidate))
continue;
const resolved = resolveDirectoryEntry(candidate, directoryIndexNames);
if (!resolved)
continue;
const resolvedRoot = fs.realpathSync(source.path);
const realTarget = fs.realpathSync(resolved);
if (!isWithin(realTarget, resolvedRoot))
continue;
return realTarget;
}
}
return null;
}
export async function resolveAssetPath(ref, options = {}) {
const parsed = normalizeRef(ref);
const mode = options.mode ?? "index-first";
if (mode !== "disk-only") {
const indexed = await resolveViaIndex(parsed);
if (indexed)
return indexed;
if (mode === "index-only")
return null;
}
return resolveViaDisk(parsed, options);
}