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
52 lines (45 loc) • 2.29 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/.
// Node ESM loader hook: support `import x from "./foo.md" with { type: "text" }`.
//
// Bun has a built-in text loader; Node does not (it only understands
// `type: "json"`). akm embeds prompt/template assets via `with { type: "text" }`
// imports that are statically hoisted into the module graph, so Node fails at
// load time with ERR_UNKNOWN_FILE_EXTENSION / unsupported import attribute
// before any command runs. This hook makes Node treat those imports as a module
// whose default export is the file's UTF-8 contents — byte-identical to Bun's
// text loader. It is ONLY registered on the Node entry path (see cli-node.mjs);
// Bun never loads it, so the Bun runtime is untouched.
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const TEXT_EXTENSIONS = new Set([".md", ".xml", ".txt", ".sql", ".yaml", ".yml", ".html"]);
function isTextImport(url, importAttributes) {
if (importAttributes && importAttributes.type === "text") return true;
const i = url.lastIndexOf(".");
if (i === -1) return false;
return TEXT_EXTENSIONS.has(url.slice(i).toLowerCase().split("?")[0]);
}
export async function load(url, context, nextLoad) {
if (url.startsWith("file:") && isTextImport(url, context.importAttributes)) {
const text = await readFile(fileURLToPath(url), "utf8");
return {
format: "module",
shortCircuit: true,
source: `export default ${JSON.stringify(text)};`,
};
}
return nextLoad(url, context);
}
// Node validates import attributes against the resolved format and rejects an
// unknown `type: "text"` during resolution. Strip the attribute here so our
// `load` hook (above) can take over; the assertion has already served its
// purpose of routing to text handling.
export async function resolve(specifier, context, nextResolve) {
const result = await nextResolve(specifier, context);
if (result.importAttributes && result.importAttributes.type === "text") {
const { type, ...rest } = result.importAttributes;
return { ...result, importAttributes: rest };
}
return result;
}