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
167 lines (166 loc) • 6.8 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/.
/**
* Stash `.meta/` convention.
*
* A stash may carry an optional, human-authored `.meta/` directory at its
* root holding orientation docs for the stash as a whole: purpose, key
* assets, conventions, maintainer info. Because `.meta/` is a dot-directory,
* the indexer's walker already skips it (see `src/indexer/walk/walker.ts`), so
* these files never pollute the search corpus. They are surfaced on demand
* via `akm show [<origin>//]meta[:<name>]`, which direct-reads the file
* rather than going through the index.
*
* This is deliberately a *convention* enabled by a thin resolver: stash
* owners extend it by dropping new files (`.meta/about.md`, `.meta/license`,
* `.meta/conventions.md`) with zero further code changes.
*/
import fs from "node:fs";
import path from "node:path";
import { isWithin } from "../common.js";
import { UsageError } from "../errors.js";
/** Root-relative directory holding a stash's meta docs. */
export const META_DIR = ".meta";
/** Default meta doc shown when no name is given (`akm show <ref>//meta`). */
export const META_DEFAULT_NAME = "index";
/**
* Parse a `meta` show target. Returns `null` when `ref` is not a meta ref so
* callers can fall through to normal asset resolution.
*
* Accepted shapes (the leading `[origin//]` is optional):
* - `meta` → { name: "index" }
* - `meta:about` → { name: "about" }
* - `local//meta` → { origin: "local", name: "index" }
* - `github:o/r//meta:conventions` → { origin: "github:o/r", name: "conventions" }
*/
export function parseMetaRef(ref) {
const trimmed = ref.trim();
if (!trimmed)
return null;
let origin;
let body = trimmed;
const boundary = trimmed.indexOf("//");
if (boundary >= 0) {
origin = trimmed.slice(0, boundary) || undefined;
body = trimmed.slice(boundary + 2);
}
if (body === "meta")
return { origin, name: META_DEFAULT_NAME };
if (body.startsWith("meta:")) {
const name = body.slice("meta:".length).trim();
return { origin, name: name || META_DEFAULT_NAME };
}
return null;
}
/**
* Reject meta names that would escape the `.meta/` directory. Mirrors the
* traversal guards in the ref parser's `validateName`.
*/
function assertSafeMetaName(name) {
if (name.includes("\0")) {
throw new UsageError("Null byte in meta name.", "PATH_ESCAPE_VIOLATION");
}
if (/^[A-Za-z]:/.test(name)) {
throw new UsageError("Windows drive path in meta name.", "PATH_ESCAPE_VIOLATION");
}
const normalized = path.posix.normalize(name.replace(/\\/g, "/"));
if (path.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
throw new UsageError("Path traversal in meta name.", "PATH_ESCAPE_VIOLATION");
}
if (normalized.split("/").some((seg) => seg === "." || seg === "..")) {
throw new UsageError("Meta name cannot contain relative path segments.", "PATH_ESCAPE_VIOLATION");
}
}
/**
* Candidate filenames for a meta doc, in resolution order. Markdown is
* preferred so `meta:about` resolves `.meta/about.md` ahead of `.meta/about`,
* while names that already carry an extension (`license.txt`) are tried
* verbatim first.
*/
function metaCandidates(name) {
if (path.posix.extname(name))
return [name, `${name}.md`];
return [`${name}.md`, name];
}
/**
* Resolve a meta doc to an absolute file path under `<sourceRoot>/.meta/`,
* or `null` when no candidate file exists. Guards against path traversal
* both before and after resolution (symlink containment).
*/
export function resolveMetaFilePath(sourceRoot, name) {
assertSafeMetaName(name);
const metaRoot = path.resolve(sourceRoot, META_DIR);
for (const candidate of metaCandidates(name)) {
const resolved = path.resolve(metaRoot, candidate);
if (resolved !== metaRoot && !resolved.startsWith(metaRoot + path.sep)) {
throw new UsageError("Meta ref resolves outside the stash .meta directory.", "PATH_ESCAPE_VIOLATION");
}
if (isSafeRegularMetaFile(sourceRoot, metaRoot, resolved))
return resolved;
}
return null;
}
/**
* Resolve and read a meta doc from one stable, no-follow file descriptor.
* The descriptor/path identity check closes the swap window between path
* validation and reading: even if a writable local bundle changes the path
* concurrently, bytes are returned only when the opened inode is still the
* validated file inside the bundle's real `.meta/` directory.
*/
export function readMetaFile(sourceRoot, name) {
const filePath = resolveMetaFilePath(sourceRoot, name);
if (!filePath)
return null;
let fd;
try {
fd = fs.openSync(filePath, fs.constants.O_RDONLY);
const opened = fs.fstatSync(fd);
if (!opened.isFile())
throw metaPathEscape();
const metaRoot = path.resolve(sourceRoot, META_DIR);
if (!isSafeRegularMetaFile(sourceRoot, metaRoot, filePath))
throw metaPathEscape();
const current = fs.statSync(filePath);
if (opened.dev !== current.dev || opened.ino !== current.ino)
throw metaPathEscape();
return { path: filePath, content: fs.readFileSync(fd, "utf8") };
}
catch (error) {
if (error instanceof UsageError)
throw error;
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
if (code === "ENOENT" || code === "ENOTDIR")
return null;
if (code === "ELOOP")
throw metaPathEscape();
throw new UsageError("Meta doc could not be read safely.", "PATH_ESCAPE_VIOLATION");
}
finally {
if (fd !== undefined)
fs.closeSync(fd);
}
}
function isSafeRegularMetaFile(sourceRoot, metaRoot, filePath) {
const relative = path.relative(metaRoot, filePath);
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
throw metaPathEscape();
}
const finalStat = statOrNull(filePath);
if (!finalStat?.isFile())
return false;
if (!isWithin(filePath, sourceRoot))
throw metaPathEscape();
return true;
}
function statOrNull(filePath) {
try {
return fs.statSync(filePath);
}
catch {
return null;
}
}
function metaPathEscape() {
return new UsageError("Meta doc must resolve to a regular file inside the stash .meta directory, not one that escapes it.", "PATH_ESCAPE_VIOLATION");
}