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
156 lines (155 loc) • 7.18 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/.
/**
* Resolve the absolute invocation that the OS scheduler should run.
*
* cron / launchd / schtasks all execute jobs with a stripped environment and
* a minimal PATH, so the registered command must be an absolute path.
*
* Resolution order:
*
* 1. `process.execPath` alone for a Bun standalone executable.
* 2. Absolute Node plus the public `dist/akm` package launcher.
* 3. Absolute runtime plus the source/build CLI entry of a checkout.
*
* `via` names which one was used. `checkout` — a package launcher inside a
* git work tree, or a source/build entry — runs whatever the checkout holds
* when the scheduler fires; `task sync` says so once when it writes one.
*
* Returns the argv array the scheduler should execute (e.g.
* `["/usr/local/bin/node", "/usr/lib/node_modules/akm-cli/dist/akm"]`). The
* caller appends subcommand args (`"task", "run", "<id>"`).
*/
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { ConfigError } from "../core/errors.js";
import { mainPath as runtimeMainPath } from "../runtime.js";
/** A source entry or local build run by a runtime: `src/cli.ts`, `dist/cli.js`, `dist/cli-node.mjs`. */
const CHECKOUT_ENTRY = /(?:^|[\\/])src[\\/]cli\.ts$|(?:^|[\\/])dist[\\/](?:cli\.js|cli-node\.mjs)$/i;
/** True only for Bun's virtual main path inside a compiled standalone executable. */
export function isBunStandaloneMain(mainPath = runtimeMainPath) {
return (mainPath?.startsWith("/$bunfs/") === true || (mainPath !== undefined && /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(mainPath)));
}
export function resolveAkmInvocation(options = {}) {
const env = options.env ?? process.env;
const runtime = options.runtime ?? (process.versions.bun ? "bun" : "node");
const execPath = options.execPath ?? process.execPath;
const mainPath = options.mainPath ?? runtimeMainPath;
if (runtime === "bun" && isBunStandaloneMain(mainPath) && execPath) {
return { argv: [absoluteInvocationPath(execPath)], via: "standalone" };
}
const launcherPath = options.launcherPath ?? env.AKM_LAUNCHER_PATH?.trim();
const nodePath = options.nodePath ?? env.AKM_LAUNCHER_NODE?.trim() ?? (runtime === "node" ? execPath : undefined);
if (launcherPath && nodePath && isPublicPackageLauncher(launcherPath)) {
return {
argv: [absoluteInvocationPath(nodePath), absoluteInvocationPath(launcherPath)],
via: isCheckoutLauncher(launcherPath) ? "checkout" : "npm",
};
}
const checkoutEntry = resolveCheckoutEntry(options.cliEntryUrl ?? import.meta.url, runtime, mainPath);
if (checkoutEntry && execPath) {
return { argv: [absoluteInvocationPath(execPath), absoluteInvocationPath(checkoutEntry)], via: "checkout" };
}
throw new ConfigError("Cannot resolve absolute path to the akm binary for scheduler registration.", "INVALID_CONFIG_FILE", "Run the npm-global launcher or a standalone akm executable.");
}
/** Whether an installed launcher argv runs akm out of a source checkout. */
export function isCheckoutInvocation(argv) {
return argv.some((part) => CHECKOUT_ENTRY.test(part) || (isPublicPackageLauncher(part) && isCheckoutLauncher(part)));
}
function resolveCheckoutEntry(moduleUrl, runtime, mainPath) {
try {
const modulePath = fileURLToPath(moduleUrl);
const parent = path.dirname(path.dirname(modulePath));
if (runtime === "node") {
const wrapper = path.join(parent, "cli-node.mjs");
if (fs.existsSync(wrapper))
return wrapper;
if (mainPath) {
if (path.basename(mainPath) === "cli-node.mjs" && fs.existsSync(mainPath))
return mainPath;
const siblingWrapper = path.join(path.dirname(mainPath), "cli-node.mjs");
if (fs.existsSync(siblingWrapper))
return siblingWrapper;
}
return undefined;
}
const extension = path.extname(modulePath);
const candidate = path.join(parent, `cli${extension}`);
if (fs.existsSync(candidate))
return candidate;
const alternate = path.join(parent, extension === ".ts" ? "cli.js" : "cli.ts");
return fs.existsSync(alternate) ? alternate : undefined;
}
catch {
return runtime === "bun" ? mainPath : undefined;
}
}
function absoluteInvocationPath(value) {
return path.win32.isAbsolute(value) || path.posix.isAbsolute(value) ? value : path.resolve(value);
}
function isPublicPackageLauncher(file) {
return (path.basename(file).toLowerCase() === "akm" &&
path.basename(path.dirname(file)).toLowerCase() === "dist" &&
fs.existsSync(file));
}
/** A package launcher whose package root is a git work tree (`npm link`, a local clone). */
function isCheckoutLauncher(file) {
let launcher = path.resolve(file);
try {
launcher = fs.realpathSync(file);
}
catch {
// The caller gets a missing-path diagnostic from doctor.
}
const packageRoot = path.dirname(path.dirname(launcher));
return fs.existsSync(path.join(packageRoot, ".git"));
}
/** `npm root --global` for the npm that ships with `nodePath`; `akm upgrade` uses it to detect a global install. */
export function resolveNpmGlobalRoot(nodePath, env) {
const npmCli = resolveAssociatedNpmCli(nodePath);
if (!npmCli)
return undefined;
const result = spawnSync(absoluteInvocationPath(nodePath), [npmCli, "root", "--global"], {
encoding: "utf8",
env,
stdio: ["ignore", "pipe", "ignore"],
timeout: 5_000,
windowsHide: true,
});
if (result.status !== 0)
return undefined;
const lines = result.stdout.trim().split(/\r?\n/);
if (lines.length !== 1 || !lines[0] || !path.isAbsolute(lines[0]))
return undefined;
return lines[0];
}
function resolveAssociatedNpmCli(nodePath) {
const nodeDirs = new Set([path.dirname(absoluteInvocationPath(nodePath))]);
try {
nodeDirs.add(path.dirname(fs.realpathSync(nodePath)));
}
catch {
// The scheduler binding will separately report a missing Node path.
}
for (const binDir of nodeDirs) {
const candidates = [
...(process.platform === "win32" ? [] : [path.join(binDir, "npm")]),
path.join(binDir, "node_modules", "npm", "bin", "npm-cli.js"),
path.join(path.dirname(binDir), "lib", "node_modules", "npm", "bin", "npm-cli.js"),
];
for (const candidate of candidates) {
try {
const resolved = fs.realpathSync(candidate);
if (fs.statSync(resolved).isFile())
return resolved;
}
catch {
// Try the next layout associated with this Node installation.
}
}
}
return undefined;
}