Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions src/deps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { $ } from "bun";

// =============================================================================
Expand Down Expand Up @@ -57,9 +58,28 @@ export function resetDepCache(): void {
// Individual Dependency Checks
// =============================================================================

/** Check if qmd is installed */
/** Check if qmd is installed and functional.
* The binary shim may exist on PATH but point to a missing dist/ directory
* (e.g. when `bun install -g` didn't run the build step). We verify by
* running `qmd collection list` which exercises the actual module. */
export async function checkQmd(): Promise<DepStatus> {
return checkBinary("qmd");
const base = await checkBinary("qmd");
if (!base.available) return base;
// Verify qmd can actually execute — not just that the shim exists
try {
await $`qmd collection list`.quiet();
return base;
} catch (err) {
const stderr = (err as { stderr?: { toString(): string } })?.stderr?.toString() ?? "";
const msg = stderr || (err instanceof Error ? err.message : String(err));
// Module not found = broken install (shim exists but dist/ missing)
if (msg.includes("Module not found") || msg.includes("Cannot find module")) {
_cache.qmd = { available: false, version: "broken install", path: base.path };
return _cache.qmd!;
}
// Other errors (e.g. no collections yet) are fine — qmd itself works
return base;
}
}

/** Check if claude CLI is installed */
Expand Down Expand Up @@ -101,6 +121,28 @@ export async function installQmd(): Promise<boolean> {
console.log(` qmd installed successfully.`);
return true;
}
// Binary exists but module missing — try running the build step
if (check.version === "broken install") {
console.log(" qmd shim installed but build step did not run. Building...");
const pkgDir = join(home, ".bun", "install", "global", "node_modules", "@tobilu", "qmd");
try {
await $`cd ${pkgDir} && bun run build`.quiet();
resetDepCache();
const recheck = await checkQmd();
if (recheck.available) {
console.log(` qmd built and installed successfully.`);
return true;
}
} catch (buildErr) {
const stderr = (buildErr as { stderr?: { toString(): string } })?.stderr?.toString() ?? "";
console.error(
` qmd build failed: ${stderr.trim() || (buildErr instanceof Error ? buildErr.message : String(buildErr))}`,
);
}
console.error(` qmd build did not produce a working install.`);
console.error(` Try manually: cd ${pkgDir} && bun run build`);
return false;
}
console.error(" qmd installation completed but binary not found on PATH.");
console.error(" Ensure ~/.bun/bin is in your PATH.");
return false;
Expand Down Expand Up @@ -147,7 +189,15 @@ export function printDepsReport(report: DepsReport): void {
const ver = (s: DepStatus) => (s.version ? ` (${s.version})` : "");

console.log(` bun: ${ok(report.bun)}${ver(report.bun)}`);
console.log(` qmd: ${ok(report.qmd)}${ver(report.qmd)}`);
if (report.qmd.version === "broken install") {
const qmdPkgDir = join(home, ".bun", "install", "global", "node_modules", "@tobilu", "qmd");
console.log(` qmd: broken install (shim exists but module missing)`);
console.log(` The qmd build step may not have run during installation.`);
console.log(` Fix: cd ${qmdPkgDir} && bun run build`);
console.log(` Or reinstall: bun install -g github:tobi/qmd --force`);
} else {
console.log(` qmd: ${ok(report.qmd)}${ver(report.qmd)}`);
}
console.log(` claude: ${ok(report.claude)}${ver(report.claude)}`);
console.log(` sqlite (brew): ${ok(report.sqlite)}`);
}
Expand Down
17 changes: 9 additions & 8 deletions src/qmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,23 @@ export async function searchSessions(query: string, opts?: { tag?: string; colle
}
}

/** Create initial QMD collection for the project. Returns true if successful. */
export async function createCollection(root: string): Promise<boolean> {
if (!(await isQmdAvailable())) return false;
/** Create initial QMD collection for the project. Returns { ok, reason } for diagnostic output. */
export async function createCollection(root: string): Promise<{ ok: boolean; reason?: string }> {
if (!(await isQmdAvailable())) return { ok: false, reason: "qmd not available" };
const name = await collectionName(root);
const dir = completedDir(root);
try {
if (await collectionExists(root)) return true;
if (await collectionExists(root)) return { ok: true };
await $`qmd collection add ${dir} --name ${name}`.quiet();
await $`qmd context add ${dir} "AI coding session transcripts and reasoning"`.quiet();
return true;
return { ok: true };
} catch (err) {
// If collection already exists (collectionExists may have failed to detect it),
// treat as success. Bun ShellError puts the actual message in stderr.
const msg = `${err instanceof Error ? err.message : String(err)} ${(err as { stderr?: { toString(): string } })?.stderr?.toString() ?? ""}`;
if (msg.includes("already exists")) return true;
return false;
const stderr = (err as { stderr?: { toString(): string } })?.stderr?.toString() ?? "";
const msg = `${err instanceof Error ? err.message : String(err)} ${stderr}`;
if (msg.includes("already exists")) return { ok: true };
return { ok: false, reason: stderr.trim() || (err instanceof Error ? err.message : String(msg)).trim() };
}
}

Expand Down
15 changes: 10 additions & 5 deletions src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ export async function enable(root: string, opts?: { install?: boolean; genesis?:
await chmod(postCommitPath, 0o755);

// 9. Create initial QMD collection (if available, uses main repo root)
let qmdOk = false;
let qmdResult: { ok: boolean; reason?: string } = { ok: false };
if (report.qmd.available) {
qmdOk = await createCollection(mainRoot);
qmdResult = await createCollection(mainRoot);
}

// 10. Report results
Expand All @@ -185,9 +185,14 @@ export async function enable(root: string, opts?: { install?: boolean; genesis?:
console.log(` ${c.bold}Hooks:${c.reset} .claude/settings.json`);
console.log(` ${c.bold}Git notes:${c.reset} refs/notes/ai-sessions`);
if (report.qmd.available) {
console.log(
` ${c.bold}QMD:${c.reset} ${name}${qmdOk ? ` ${c.green}(created)${c.reset}` : ` ${c.red}(failed to create)${c.reset}`}`,
);
if (qmdResult.ok) {
console.log(` ${c.bold}QMD:${c.reset} ${name} ${c.green}(created)${c.reset}`);
} else {
console.log(` ${c.bold}QMD:${c.reset} ${name} ${c.red}(failed to create)${c.reset}`);
if (qmdResult.reason) {
console.log(` ${c.dim} ${qmdResult.reason}${c.reset}`);
}
}
console.log(` ${c.bold}MCP server:${c.reset} ghost-sessions`);
} else {
console.log(` ${c.bold}QMD:${c.reset} ${c.yellow}not installed${c.reset} (search disabled)`);
Expand Down
Loading