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
113 lines (112 loc) • 4.76 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/.
/**
* The one shared implementation of frozen-script byte/interpreter capture
* (spec docs/plans/specs/p1b-model-extraction.md §4.3). Both prepare.ts's
* script arm (the moved prepareTaskV3Execution) and
* prepare-script-target.ts's typed prepareScriptTarget() call
* captureScriptTarget() below, so the two never drift into two copies of the
* same capture logic. scriptInterpreter() and captureDirectoryIdentity() are
* moved body-intact out of the pre-P1b src/tasks/runtime-v3.ts.
*/
import { createHash } from "node:crypto";
import path from "node:path";
import { UsageError } from "../../core/errors.js";
import { captureFrozenDirectoryIdentity } from "../../execution/directory-identity.js";
import { isBunStandaloneMain } from "../resolve-akm-bin.js";
const SCRIPT_INTERPRETERS = Object.freeze({
".sh": "sh",
".ts": "bun",
".js": "bun",
".ps1": "powershell",
".cmd": "cmd",
".bat": "cmd",
".py": "python",
".rb": "ruby",
".go": "go",
".pl": "perl",
".php": "php",
".lua": "lua",
".r": "rscript",
".swift": "swift",
".kt": "kotlin",
".kts": "kotlin",
});
const SHEBANG_INTERPRETERS = [
[/^(bash|sh|zsh|dash|ksh)$/, "sh"],
[/^python[0-9.]*$/, "python"],
[/^ruby$/, "ruby"],
[/^perl$/, "perl"],
[/^php$/, "php"],
[/^lua$/, "lua"],
[/^node$/, "node"],
];
function interpreterFromShebang(bytes) {
if (!bytes || bytes.length < 2 || bytes[0] !== 0x23 || bytes[1] !== 0x21)
return undefined;
const newline = bytes.indexOf(0x0a);
const lineBytes = newline === -1 ? bytes.subarray(2) : bytes.subarray(2, newline);
const tokens = Buffer.from(lineBytes).toString("utf8").trim().split(/\s+/).filter(Boolean);
const [first, second] = tokens;
const named = first?.split("/").pop() === "env" ? second : first?.split("/").pop();
if (!named)
return undefined;
for (const [pattern, interpreter] of SHEBANG_INTERPRETERS) {
if (pattern.test(named))
return interpreter;
}
return undefined;
}
function nodeStripsTypeScript() {
const features = process.features;
return Boolean(features?.typescript);
}
export function scriptInterpreter(extension, ref, bytes) {
const interpreter = SCRIPT_INTERPRETERS[extension] ?? (extension === "" ? interpreterFromShebang(bytes) : undefined);
if (!interpreter) {
throw new UsageError(`Task v3 script target ${JSON.stringify(ref)} has no closed runtime interpreter for extension ${JSON.stringify(extension)}.`, "TASK_TARGET_UNSUPPORTED");
}
if (interpreter !== "bun")
return interpreter;
if (process.versions.bun) {
return isBunStandaloneMain() ? "bun-standalone" : "bun";
}
if (extension === ".js" || (extension === ".ts" && nodeStripsTypeScript())) {
return "node";
}
throw new UsageError(`Task v3 script target ${JSON.stringify(ref)} requires Bun for ${extension} execution, but this runtime cannot provide it.`, "TASK_TARGET_UNSUPPORTED");
}
export function captureDirectoryIdentity(bundleRoot, workingDirectory) {
try {
return captureFrozenDirectoryIdentity(bundleRoot, workingDirectory);
}
catch (cause) {
if (cause instanceof UsageError)
throw cause;
throw new UsageError(`Task working directory ${JSON.stringify(workingDirectory ?? ".")} cannot be physically verified: ${cause instanceof Error ? cause.message : String(cause)}`, "TASK_SOURCE_INVALID");
}
}
/**
* Read a script's bytes off disk (or the caller's own read seam) and freeze
* the interpreter/digest/directory-identity shape every script dispatch
* needs. `ref` is used only for interpreter-selection error messages — it is
* the script's own qualified ref, never a synthetic task ref. `bundleRoot` is
* where directory identity is captured from (one level above the script file
* itself, matching prepareTaskV3Execution's historical behavior).
*/
export function captureScriptTarget(ref, file, bundleRoot, readFile) {
const extension = path.extname(file).toLowerCase();
const raw = readFile(file, bundleRoot);
const bytes = Uint8Array.from(raw);
const cwdIdentity = captureDirectoryIdentity(bundleRoot);
return Object.freeze({
interpreter: scriptInterpreter(extension, ref, bytes),
extension,
bytesBase64: Buffer.from(bytes).toString("base64"),
byteLength: bytes.byteLength,
sha256: createHash("sha256").update(bytes).digest("hex"),
cwd: cwdIdentity.realCwd,
cwdIdentity,
});
}