100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
#!/usr/bin/env bun
|
|
// Cross-platform plugin compose launcher for hosts whose hook commands must
|
|
// run on native Windows. Avoid `sh -c`: prefer an installed aidlc executable,
|
|
// then fall back to the sibling compose.ts with this Bun executable.
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const harnessDir = process.argv[2] || ".cursor";
|
|
const harnessName = process.argv[3] || process.env.AIDLC_HARNESS_NAME;
|
|
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
const rawInput = process.stdin.isTTY ? "" : await Bun.stdin.text();
|
|
|
|
function installedProject(path: string): string | null {
|
|
const resolved = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
return existsSync(join(resolved, harnessDir, "tools", "aidlc-graph.ts"))
|
|
? resolved
|
|
: null;
|
|
}
|
|
|
|
function projectFromPayload(): string | null {
|
|
const explicit =
|
|
process.env.AIDLC_PROJECT_DIR ??
|
|
process.env.CURSOR_PROJECT_DIR ??
|
|
process.env.CLAUDE_PROJECT_DIR;
|
|
if (explicit) {
|
|
const project = installedProject(explicit);
|
|
if (project === null) {
|
|
process.stderr.write(
|
|
`aidlc plugin compose: explicit project root does not contain an AI-DLC ${harnessDir} install: ${explicit}\n`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
return project;
|
|
}
|
|
|
|
let roots: string[] = [];
|
|
try {
|
|
const parsed = JSON.parse(rawInput) as { workspace_roots?: unknown };
|
|
if (Array.isArray(parsed.workspace_roots)) {
|
|
roots = parsed.workspace_roots.filter(
|
|
(root): root is string => typeof root === "string" && root.length > 0,
|
|
);
|
|
}
|
|
} catch {
|
|
// Fall through to cwd for manual/source-tree invocations.
|
|
}
|
|
const projects = [...new Set(roots.map(installedProject).filter((p): p is string => p !== null))];
|
|
if (projects.length > 1) {
|
|
process.stderr.write(
|
|
`aidlc plugin compose: multiple Cursor workspace roots contain AI-DLC installs (${projects.join(", ")}); set AIDLC_PROJECT_DIR to select one\n`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
if (projects.length === 1) return projects[0];
|
|
return installedProject(process.cwd());
|
|
}
|
|
|
|
const projectRoot = projectFromPayload();
|
|
if (projectRoot === null) process.exit(0);
|
|
|
|
const env = {
|
|
...process.env,
|
|
AIDLC_HARNESS_DIR: harnessDir,
|
|
...(harnessName ? { AIDLC_HARNESS_NAME: harnessName } : {}),
|
|
AIDLC_PLUGIN_ROOT: pluginRoot,
|
|
AIDLC_PROJECT_DIR: projectRoot,
|
|
CLAUDE_PROJECT_DIR: projectRoot,
|
|
CURSOR_PROJECT_DIR: projectRoot,
|
|
CLAUDE_PLUGIN_ROOT: pluginRoot,
|
|
PLUGIN_ROOT: pluginRoot,
|
|
};
|
|
const aidlc = Bun.which("aidlc");
|
|
let installedSyncSucceeded = false;
|
|
|
|
if (aidlc) {
|
|
const synced = spawnSync(aidlc, ["engine", "plugin", "sync"], {
|
|
cwd: projectRoot,
|
|
env,
|
|
encoding: "utf-8",
|
|
});
|
|
if (synced.stdout) process.stdout.write(synced.stdout);
|
|
if (synced.stderr) process.stderr.write(synced.stderr);
|
|
installedSyncSucceeded = synced.status === 0;
|
|
}
|
|
|
|
if (!installedSyncSucceeded) {
|
|
const compose = join(pluginRoot, "hooks", "compose.ts");
|
|
const fallback = spawnSync(process.execPath, [compose], {
|
|
cwd: projectRoot,
|
|
env,
|
|
encoding: "utf-8",
|
|
});
|
|
if (fallback.stdout) process.stdout.write(fallback.stdout);
|
|
if (fallback.stderr) process.stderr.write(fallback.stderr);
|
|
process.exitCode = fallback.status ?? 1;
|
|
}
|