codealmanac 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codealmanac.js +976 -386
- package/dist/codealmanac.js.map +1 -1
- package/guides/mini.md +187 -0
- package/guides/reference.md +599 -0
- package/package.json +3 -2
- package/prompts/bootstrap.md +55 -10
package/dist/codealmanac.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { createRequire as createRequire3 } from "module";
|
|
4
5
|
import { basename as basename6 } from "path";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
|
|
@@ -16,10 +17,8 @@ import { dirname, join } from "path";
|
|
|
16
17
|
var AUTH_TIMEOUT_MS = 1e4;
|
|
17
18
|
function resolveCliJsPath() {
|
|
18
19
|
const require2 = createRequire(import.meta.url);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
return join(dirname(pkgJsonPath), "cli.js");
|
|
20
|
+
const entry = require2.resolve("@anthropic-ai/claude-agent-sdk");
|
|
21
|
+
return join(dirname(entry), "cli.js");
|
|
23
22
|
}
|
|
24
23
|
var defaultSpawnCli = (args) => {
|
|
25
24
|
const cliPath = resolveCliJsPath();
|
|
@@ -227,7 +226,7 @@ function findNearestAlmanacDir(startDir) {
|
|
|
227
226
|
}
|
|
228
227
|
}
|
|
229
228
|
|
|
230
|
-
// src/commands/
|
|
229
|
+
// src/commands/_init.ts
|
|
231
230
|
import { existsSync as existsSync3 } from "fs";
|
|
232
231
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
233
232
|
import { basename, join as join3 } from "path";
|
|
@@ -241,10 +240,10 @@ function toKebabCase(input) {
|
|
|
241
240
|
import { mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
|
|
242
241
|
import { dirname as dirname3 } from "path";
|
|
243
242
|
async function readRegistry() {
|
|
244
|
-
const
|
|
243
|
+
const path5 = getRegistryPath();
|
|
245
244
|
let raw;
|
|
246
245
|
try {
|
|
247
|
-
raw = await readFile2(
|
|
246
|
+
raw = await readFile2(path5, "utf8");
|
|
248
247
|
} catch (err) {
|
|
249
248
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
250
249
|
return [];
|
|
@@ -260,10 +259,10 @@ async function readRegistry() {
|
|
|
260
259
|
parsed = JSON.parse(trimmed);
|
|
261
260
|
} catch (err) {
|
|
262
261
|
const message = err instanceof Error ? err.message : String(err);
|
|
263
|
-
throw new Error(`registry at ${
|
|
262
|
+
throw new Error(`registry at ${path5} is not valid JSON: ${message}`);
|
|
264
263
|
}
|
|
265
264
|
if (!Array.isArray(parsed)) {
|
|
266
|
-
throw new Error(`registry at ${
|
|
265
|
+
throw new Error(`registry at ${path5} must be a JSON array`);
|
|
267
266
|
}
|
|
268
267
|
return parsed.map((item, idx) => {
|
|
269
268
|
if (typeof item !== "object" || item === null) {
|
|
@@ -271,29 +270,29 @@ async function readRegistry() {
|
|
|
271
270
|
}
|
|
272
271
|
const e = item;
|
|
273
272
|
const name = typeof e.name === "string" ? e.name : "";
|
|
274
|
-
const
|
|
273
|
+
const path6 = typeof e.path === "string" ? e.path : "";
|
|
275
274
|
if (name.length === 0) {
|
|
276
275
|
throw new Error(`registry entry ${idx} is missing a non-empty "name"`);
|
|
277
276
|
}
|
|
278
|
-
if (
|
|
277
|
+
if (path6.length === 0) {
|
|
279
278
|
throw new Error(`registry entry ${idx} is missing a non-empty "path"`);
|
|
280
279
|
}
|
|
281
280
|
return {
|
|
282
281
|
name,
|
|
283
282
|
description: typeof e.description === "string" ? e.description : "",
|
|
284
|
-
path:
|
|
283
|
+
path: path6,
|
|
285
284
|
registered_at: typeof e.registered_at === "string" ? e.registered_at : ""
|
|
286
285
|
};
|
|
287
286
|
});
|
|
288
287
|
}
|
|
289
288
|
async function writeRegistry(entries) {
|
|
290
|
-
const
|
|
291
|
-
await mkdir(dirname3(
|
|
289
|
+
const path5 = getRegistryPath();
|
|
290
|
+
await mkdir(dirname3(path5), { recursive: true });
|
|
292
291
|
const body = `${JSON.stringify(entries, null, 2)}
|
|
293
292
|
`;
|
|
294
|
-
const tmpPath = `${
|
|
293
|
+
const tmpPath = `${path5}.tmp`;
|
|
295
294
|
await writeFile(tmpPath, body, "utf8");
|
|
296
|
-
await rename(tmpPath,
|
|
295
|
+
await rename(tmpPath, path5);
|
|
297
296
|
}
|
|
298
297
|
function pathsEqual(a, b) {
|
|
299
298
|
if (process.platform === "darwin" || process.platform === "win32") {
|
|
@@ -337,7 +336,7 @@ function isNodeError(err) {
|
|
|
337
336
|
return err instanceof Error && "code" in err;
|
|
338
337
|
}
|
|
339
338
|
|
|
340
|
-
// src/commands/
|
|
339
|
+
// src/commands/_init.ts
|
|
341
340
|
async function initWiki(options) {
|
|
342
341
|
const repoRoot = findNearestAlmanacDir(options.cwd) ?? options.cwd;
|
|
343
342
|
const almanacDir = getRepoAlmanacDir(repoRoot);
|
|
@@ -367,15 +366,15 @@ async function initWiki(options) {
|
|
|
367
366
|
return { entry, almanacDir, created: !alreadyExisted };
|
|
368
367
|
}
|
|
369
368
|
async function ensureGitignoreHasIndexDb(cwd) {
|
|
370
|
-
const
|
|
369
|
+
const path5 = join3(cwd, ".gitignore");
|
|
371
370
|
const targets = [
|
|
372
371
|
".almanac/index.db",
|
|
373
372
|
".almanac/index.db-wal",
|
|
374
373
|
".almanac/index.db-shm"
|
|
375
374
|
];
|
|
376
375
|
let existing = "";
|
|
377
|
-
if (existsSync3(
|
|
378
|
-
existing = await readFile3(
|
|
376
|
+
if (existsSync3(path5)) {
|
|
377
|
+
existing = await readFile3(path5, "utf8");
|
|
379
378
|
}
|
|
380
379
|
const lines = existing.split(/\r?\n/).map((l) => l.trim());
|
|
381
380
|
const missing = targets.filter((t) => !lines.includes(t));
|
|
@@ -385,7 +384,7 @@ async function ensureGitignoreHasIndexDb(cwd) {
|
|
|
385
384
|
${missing.join("\n")}
|
|
386
385
|
`;
|
|
387
386
|
const sep = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
388
|
-
await writeFile2(
|
|
387
|
+
await writeFile2(path5, `${existing}${sep}${block}`, "utf8");
|
|
389
388
|
}
|
|
390
389
|
function starterReadme() {
|
|
391
390
|
return `# Wiki
|
|
@@ -988,8 +987,8 @@ async function filterTranscriptsByCwd(transcripts, repoRoot) {
|
|
|
988
987
|
}
|
|
989
988
|
return hits;
|
|
990
989
|
}
|
|
991
|
-
async function readHead(
|
|
992
|
-
const content = await readFile4(
|
|
990
|
+
async function readHead(path5, bytes) {
|
|
991
|
+
const content = await readFile4(path5, "utf8");
|
|
993
992
|
return content.length > bytes ? content.slice(0, bytes) : content;
|
|
994
993
|
}
|
|
995
994
|
async function snapshotPages(pagesDir) {
|
|
@@ -1296,13 +1295,13 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
1296
1295
|
import { mkdir as mkdir4, readFile as readFile6, rename as rename3, writeFile as writeFile4 } from "fs/promises";
|
|
1297
1296
|
import { dirname as dirname4 } from "path";
|
|
1298
1297
|
import yaml2 from "js-yaml";
|
|
1299
|
-
async function loadTopicsFile(
|
|
1300
|
-
if (!existsSync7(
|
|
1298
|
+
async function loadTopicsFile(path5) {
|
|
1299
|
+
if (!existsSync7(path5)) {
|
|
1301
1300
|
return { topics: [] };
|
|
1302
1301
|
}
|
|
1303
1302
|
let raw;
|
|
1304
1303
|
try {
|
|
1305
|
-
raw = await readFile6(
|
|
1304
|
+
raw = await readFile6(path5, "utf8");
|
|
1306
1305
|
} catch (err) {
|
|
1307
1306
|
if (isNodeError2(err) && err.code === "ENOENT") {
|
|
1308
1307
|
return { topics: [] };
|
|
@@ -1318,13 +1317,13 @@ async function loadTopicsFile(path3) {
|
|
|
1318
1317
|
parsed = yaml2.load(raw);
|
|
1319
1318
|
} catch (err) {
|
|
1320
1319
|
const message = err instanceof Error ? err.message : String(err);
|
|
1321
|
-
throw new Error(`topics.yaml at ${
|
|
1320
|
+
throw new Error(`topics.yaml at ${path5} is not valid YAML: ${message}`);
|
|
1322
1321
|
}
|
|
1323
1322
|
if (parsed === null || parsed === void 0) {
|
|
1324
1323
|
return { topics: [] };
|
|
1325
1324
|
}
|
|
1326
1325
|
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1327
|
-
throw new Error(`topics.yaml at ${
|
|
1326
|
+
throw new Error(`topics.yaml at ${path5} must be a mapping`);
|
|
1328
1327
|
}
|
|
1329
1328
|
const obj = parsed;
|
|
1330
1329
|
const rawTopics = obj.topics;
|
|
@@ -1332,7 +1331,7 @@ async function loadTopicsFile(path3) {
|
|
|
1332
1331
|
return { topics: [] };
|
|
1333
1332
|
}
|
|
1334
1333
|
if (!Array.isArray(rawTopics)) {
|
|
1335
|
-
throw new Error(`topics.yaml at ${
|
|
1334
|
+
throw new Error(`topics.yaml at ${path5} \u2014 "topics" must be a list`);
|
|
1336
1335
|
}
|
|
1337
1336
|
const topics = [];
|
|
1338
1337
|
for (const item of rawTopics) {
|
|
@@ -1361,7 +1360,7 @@ async function loadTopicsFile(path3) {
|
|
|
1361
1360
|
}
|
|
1362
1361
|
return { topics };
|
|
1363
1362
|
}
|
|
1364
|
-
async function writeTopicsFile(
|
|
1363
|
+
async function writeTopicsFile(path5, file) {
|
|
1365
1364
|
const sorted = [...file.topics].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
1366
1365
|
const doc = {
|
|
1367
1366
|
topics: sorted.map((t) => {
|
|
@@ -1386,13 +1385,13 @@ async function writeTopicsFile(path3, file) {
|
|
|
1386
1385
|
sortKeys: false
|
|
1387
1386
|
});
|
|
1388
1387
|
const content = `${header}${body}`;
|
|
1389
|
-
const tmpPath = `${
|
|
1390
|
-
const parent = dirname4(
|
|
1388
|
+
const tmpPath = `${path5}.tmp`;
|
|
1389
|
+
const parent = dirname4(path5);
|
|
1391
1390
|
if (!existsSync7(parent)) {
|
|
1392
1391
|
await mkdir4(parent, { recursive: true });
|
|
1393
1392
|
}
|
|
1394
1393
|
await writeFile4(tmpPath, content, "utf8");
|
|
1395
|
-
await rename3(tmpPath,
|
|
1394
|
+
await rename3(tmpPath, path5);
|
|
1396
1395
|
}
|
|
1397
1396
|
function findTopic(file, slug) {
|
|
1398
1397
|
for (const t of file.topics) {
|
|
@@ -1541,10 +1540,10 @@ function classifyWikilink(raw) {
|
|
|
1541
1540
|
}
|
|
1542
1541
|
if (firstSlash !== -1) {
|
|
1543
1542
|
const isDir = looksLikeDir(body);
|
|
1544
|
-
const
|
|
1543
|
+
const path5 = normalizePath(body, isDir);
|
|
1545
1544
|
const originalPath = normalizePathPreservingCase(body, isDir);
|
|
1546
|
-
if (
|
|
1547
|
-
return isDir ? { kind: "folder", path:
|
|
1545
|
+
if (path5.length === 0) return null;
|
|
1546
|
+
return isDir ? { kind: "folder", path: path5, originalPath } : { kind: "file", path: path5, originalPath };
|
|
1548
1547
|
}
|
|
1549
1548
|
const target = toKebabCase(body);
|
|
1550
1549
|
if (target.length === 0) return null;
|
|
@@ -1764,10 +1763,10 @@ async function indexPagesInto(db, pagesDir) {
|
|
|
1764
1763
|
}
|
|
1765
1764
|
for (const raw of p.frontmatterFiles) {
|
|
1766
1765
|
const isDir = looksLikeDir(raw);
|
|
1767
|
-
const
|
|
1766
|
+
const path5 = normalizePath(raw, isDir);
|
|
1768
1767
|
const originalPath = normalizePathPreservingCase(raw, isDir);
|
|
1769
|
-
if (
|
|
1770
|
-
insertFileRef.run(p.slug,
|
|
1768
|
+
if (path5.length === 0) continue;
|
|
1769
|
+
insertFileRef.run(p.slug, path5, originalPath, isDir ? 1 : 0);
|
|
1771
1770
|
}
|
|
1772
1771
|
for (const ref of p.wikilinks) {
|
|
1773
1772
|
switch (ref.kind) {
|
|
@@ -1823,8 +1822,8 @@ function hashContent(raw) {
|
|
|
1823
1822
|
return createHash2("sha256").update(raw).digest("hex");
|
|
1824
1823
|
}
|
|
1825
1824
|
function topicsYamlNewerThan(almanacDir, dbPath) {
|
|
1826
|
-
const
|
|
1827
|
-
if (!existsSync8(
|
|
1825
|
+
const path5 = join6(almanacDir, "topics.yaml");
|
|
1826
|
+
if (!existsSync8(path5)) return false;
|
|
1828
1827
|
let dbMtime;
|
|
1829
1828
|
try {
|
|
1830
1829
|
dbMtime = statSync2(dbPath).mtimeMs;
|
|
@@ -1832,7 +1831,7 @@ function topicsYamlNewerThan(almanacDir, dbPath) {
|
|
|
1832
1831
|
return true;
|
|
1833
1832
|
}
|
|
1834
1833
|
try {
|
|
1835
|
-
const st = statSync2(
|
|
1834
|
+
const st = statSync2(path5);
|
|
1836
1835
|
return st.mtimeMs > dbMtime;
|
|
1837
1836
|
} catch {
|
|
1838
1837
|
return false;
|
|
@@ -2254,153 +2253,6 @@ function section(label, count, lines) {
|
|
|
2254
2253
|
${lines.join("\n")}`;
|
|
2255
2254
|
}
|
|
2256
2255
|
|
|
2257
|
-
// src/commands/info.ts
|
|
2258
|
-
import { join as join9 } from "path";
|
|
2259
|
-
async function runInfo(options) {
|
|
2260
|
-
const repoRoot = await resolveWikiRoot({
|
|
2261
|
-
cwd: options.cwd,
|
|
2262
|
-
wiki: options.wiki
|
|
2263
|
-
});
|
|
2264
|
-
await ensureFreshIndex({ repoRoot });
|
|
2265
|
-
const dbPath = join9(repoRoot, ".almanac", "index.db");
|
|
2266
|
-
const db = openIndex(dbPath);
|
|
2267
|
-
try {
|
|
2268
|
-
const slugs = collectSlugs(options);
|
|
2269
|
-
if (slugs.length === 0) {
|
|
2270
|
-
return {
|
|
2271
|
-
stdout: "",
|
|
2272
|
-
stderr: "almanac: info requires a slug (or --stdin)\n",
|
|
2273
|
-
exitCode: 1
|
|
2274
|
-
};
|
|
2275
|
-
}
|
|
2276
|
-
const records = [];
|
|
2277
|
-
const missing = [];
|
|
2278
|
-
for (const slug of slugs) {
|
|
2279
|
-
const rec = fetchInfo(db, slug);
|
|
2280
|
-
if (rec === null) {
|
|
2281
|
-
missing.push(slug);
|
|
2282
|
-
continue;
|
|
2283
|
-
}
|
|
2284
|
-
records.push(rec);
|
|
2285
|
-
}
|
|
2286
|
-
const bulk = options.stdin === true;
|
|
2287
|
-
const jsonOut = options.json === true || bulk;
|
|
2288
|
-
let stdout;
|
|
2289
|
-
if (jsonOut) {
|
|
2290
|
-
if (bulk) {
|
|
2291
|
-
stdout = `${JSON.stringify(records, null, 2)}
|
|
2292
|
-
`;
|
|
2293
|
-
} else {
|
|
2294
|
-
const only = records[0] ?? null;
|
|
2295
|
-
stdout = `${JSON.stringify(only, null, 2)}
|
|
2296
|
-
`;
|
|
2297
|
-
}
|
|
2298
|
-
} else {
|
|
2299
|
-
stdout = records.map(formatHumanReadable).join("\n");
|
|
2300
|
-
}
|
|
2301
|
-
const stderr = missing.map((s) => `almanac: no such page "${s}"
|
|
2302
|
-
`).join("");
|
|
2303
|
-
return {
|
|
2304
|
-
stdout,
|
|
2305
|
-
stderr,
|
|
2306
|
-
exitCode: missing.length > 0 ? 1 : 0
|
|
2307
|
-
};
|
|
2308
|
-
} finally {
|
|
2309
|
-
db.close();
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
function fetchInfo(db, slug) {
|
|
2313
|
-
const pageRow = db.prepare(
|
|
2314
|
-
"SELECT slug, title, file_path, updated_at, archived_at, superseded_by FROM pages WHERE slug = ?"
|
|
2315
|
-
).get(slug);
|
|
2316
|
-
if (pageRow === void 0) return null;
|
|
2317
|
-
const topics = db.prepare(
|
|
2318
|
-
"SELECT topic_slug FROM page_topics WHERE page_slug = ? ORDER BY topic_slug"
|
|
2319
|
-
).all(slug).map((r) => r.topic_slug);
|
|
2320
|
-
const refs = db.prepare(
|
|
2321
|
-
// Display the author's casing (`original_path`), not the
|
|
2322
|
-
// lowercased lookup form. The lowercased `path` column is the
|
|
2323
|
-
// query key for `--mentions`; it's not a user-facing string.
|
|
2324
|
-
"SELECT original_path, is_dir FROM file_refs WHERE page_slug = ? ORDER BY original_path"
|
|
2325
|
-
).all(slug).map((r) => ({ path: r.original_path, is_dir: r.is_dir === 1 }));
|
|
2326
|
-
const linksOut = db.prepare(
|
|
2327
|
-
"SELECT target_slug FROM wikilinks WHERE source_slug = ? ORDER BY target_slug"
|
|
2328
|
-
).all(slug).map((r) => r.target_slug);
|
|
2329
|
-
const linksIn = db.prepare(
|
|
2330
|
-
"SELECT source_slug FROM wikilinks WHERE target_slug = ? ORDER BY source_slug"
|
|
2331
|
-
).all(slug).map((r) => r.source_slug);
|
|
2332
|
-
const xwiki = db.prepare(
|
|
2333
|
-
"SELECT target_wiki, target_slug FROM cross_wiki_links WHERE source_slug = ? ORDER BY target_wiki, target_slug"
|
|
2334
|
-
).all(slug).map((r) => ({ wiki: r.target_wiki, target: r.target_slug }));
|
|
2335
|
-
const supersedesRows = db.prepare(
|
|
2336
|
-
"SELECT slug FROM pages WHERE superseded_by = ? ORDER BY slug"
|
|
2337
|
-
).all(slug).map((r) => r.slug);
|
|
2338
|
-
return {
|
|
2339
|
-
slug: pageRow.slug,
|
|
2340
|
-
title: pageRow.title,
|
|
2341
|
-
file_path: pageRow.file_path,
|
|
2342
|
-
updated_at: pageRow.updated_at,
|
|
2343
|
-
archived_at: pageRow.archived_at,
|
|
2344
|
-
superseded_by: pageRow.superseded_by,
|
|
2345
|
-
supersedes: supersedesRows,
|
|
2346
|
-
topics,
|
|
2347
|
-
file_refs: refs,
|
|
2348
|
-
wikilinks_out: linksOut,
|
|
2349
|
-
wikilinks_in: linksIn,
|
|
2350
|
-
cross_wiki_links: xwiki
|
|
2351
|
-
};
|
|
2352
|
-
}
|
|
2353
|
-
function collectSlugs(options) {
|
|
2354
|
-
if (options.stdin === true && options.stdinInput !== void 0) {
|
|
2355
|
-
return options.stdinInput.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2356
|
-
}
|
|
2357
|
-
if (options.slug !== void 0 && options.slug.length > 0) {
|
|
2358
|
-
return [options.slug];
|
|
2359
|
-
}
|
|
2360
|
-
return [];
|
|
2361
|
-
}
|
|
2362
|
-
function formatHumanReadable(rec) {
|
|
2363
|
-
const lines = [];
|
|
2364
|
-
lines.push(`slug: ${rec.slug}`);
|
|
2365
|
-
lines.push(`title: ${rec.title ?? "\u2014"}`);
|
|
2366
|
-
lines.push(`file: ${rec.file_path}`);
|
|
2367
|
-
lines.push(`updated_at: ${new Date(rec.updated_at * 1e3).toISOString()}`);
|
|
2368
|
-
if (rec.archived_at !== null) {
|
|
2369
|
-
lines.push(
|
|
2370
|
-
`archived_at: ${new Date(rec.archived_at * 1e3).toISOString()}`
|
|
2371
|
-
);
|
|
2372
|
-
}
|
|
2373
|
-
if (rec.superseded_by !== null) {
|
|
2374
|
-
lines.push(`superseded_by: ${rec.superseded_by}`);
|
|
2375
|
-
}
|
|
2376
|
-
if (rec.supersedes.length > 0) {
|
|
2377
|
-
lines.push(`supersedes: ${rec.supersedes.join(", ")}`);
|
|
2378
|
-
}
|
|
2379
|
-
lines.push(`topics: ${rec.topics.length > 0 ? rec.topics.join(", ") : "\u2014"}`);
|
|
2380
|
-
lines.push("file_refs:");
|
|
2381
|
-
if (rec.file_refs.length === 0) {
|
|
2382
|
-
lines.push(" \u2014");
|
|
2383
|
-
} else {
|
|
2384
|
-
for (const r of rec.file_refs) {
|
|
2385
|
-
lines.push(` ${r.path}${r.is_dir ? " (dir)" : ""}`);
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
lines.push("wikilinks_out:");
|
|
2389
|
-
if (rec.wikilinks_out.length === 0) lines.push(" \u2014");
|
|
2390
|
-
else for (const t of rec.wikilinks_out) lines.push(` ${t}`);
|
|
2391
|
-
lines.push("wikilinks_in:");
|
|
2392
|
-
if (rec.wikilinks_in.length === 0) lines.push(" \u2014");
|
|
2393
|
-
else for (const s of rec.wikilinks_in) lines.push(` ${s}`);
|
|
2394
|
-
if (rec.cross_wiki_links.length > 0) {
|
|
2395
|
-
lines.push("cross_wiki_links:");
|
|
2396
|
-
for (const x of rec.cross_wiki_links) {
|
|
2397
|
-
lines.push(` ${x.wiki}:${x.target}`);
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
return `${lines.join("\n")}
|
|
2401
|
-
`;
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
2256
|
// src/commands/list.ts
|
|
2405
2257
|
import { existsSync as existsSync11 } from "fs";
|
|
2406
2258
|
async function listWikis(options) {
|
|
@@ -2453,61 +2305,6 @@ function formatPretty(entries) {
|
|
|
2453
2305
|
`;
|
|
2454
2306
|
}
|
|
2455
2307
|
|
|
2456
|
-
// src/commands/path.ts
|
|
2457
|
-
import { join as join10 } from "path";
|
|
2458
|
-
async function runPath(options) {
|
|
2459
|
-
const repoRoot = await resolveWikiRoot({
|
|
2460
|
-
cwd: options.cwd,
|
|
2461
|
-
wiki: options.wiki
|
|
2462
|
-
});
|
|
2463
|
-
await ensureFreshIndex({ repoRoot });
|
|
2464
|
-
const dbPath = join10(repoRoot, ".almanac", "index.db");
|
|
2465
|
-
const db = openIndex(dbPath);
|
|
2466
|
-
try {
|
|
2467
|
-
const slugs = collectSlugs2(options);
|
|
2468
|
-
if (slugs.length === 0) {
|
|
2469
|
-
return {
|
|
2470
|
-
stdout: "",
|
|
2471
|
-
stderr: "almanac: path requires a slug (or --stdin)\n",
|
|
2472
|
-
exitCode: 1
|
|
2473
|
-
};
|
|
2474
|
-
}
|
|
2475
|
-
const stmt = db.prepare(
|
|
2476
|
-
"SELECT file_path FROM pages WHERE slug = ?"
|
|
2477
|
-
);
|
|
2478
|
-
const resolved = [];
|
|
2479
|
-
const missing = [];
|
|
2480
|
-
for (const slug of slugs) {
|
|
2481
|
-
const row = stmt.get(slug);
|
|
2482
|
-
if (row === void 0) {
|
|
2483
|
-
missing.push(slug);
|
|
2484
|
-
continue;
|
|
2485
|
-
}
|
|
2486
|
-
resolved.push(row.file_path);
|
|
2487
|
-
}
|
|
2488
|
-
const stdout = resolved.length > 0 ? `${resolved.join("\n")}
|
|
2489
|
-
` : "";
|
|
2490
|
-
const stderr = missing.map((s) => `almanac: no such page "${s}"
|
|
2491
|
-
`).join("");
|
|
2492
|
-
return {
|
|
2493
|
-
stdout,
|
|
2494
|
-
stderr,
|
|
2495
|
-
exitCode: missing.length > 0 ? 1 : 0
|
|
2496
|
-
};
|
|
2497
|
-
} finally {
|
|
2498
|
-
db.close();
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
function collectSlugs2(options) {
|
|
2502
|
-
if (options.stdin === true && options.stdinInput !== void 0) {
|
|
2503
|
-
return options.stdinInput.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2504
|
-
}
|
|
2505
|
-
if (options.slug !== void 0 && options.slug.length > 0) {
|
|
2506
|
-
return [options.slug];
|
|
2507
|
-
}
|
|
2508
|
-
return [];
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
2308
|
// src/commands/reindex.ts
|
|
2512
2309
|
async function runReindex(options) {
|
|
2513
2310
|
const repoRoot = await resolveWikiRoot({
|
|
@@ -2522,14 +2319,14 @@ async function runReindex(options) {
|
|
|
2522
2319
|
}
|
|
2523
2320
|
|
|
2524
2321
|
// src/commands/search.ts
|
|
2525
|
-
import { join as
|
|
2322
|
+
import { join as join9 } from "path";
|
|
2526
2323
|
async function runSearch(options) {
|
|
2527
2324
|
const repoRoot = await resolveWikiRoot({
|
|
2528
2325
|
cwd: options.cwd,
|
|
2529
2326
|
wiki: options.wiki
|
|
2530
2327
|
});
|
|
2531
2328
|
await ensureFreshIndex({ repoRoot });
|
|
2532
|
-
const dbPath =
|
|
2329
|
+
const dbPath = join9(repoRoot, ".almanac", "index.db");
|
|
2533
2330
|
const db = openIndex(dbPath);
|
|
2534
2331
|
try {
|
|
2535
2332
|
const rows = executeQuery(db, options);
|
|
@@ -2697,19 +2494,313 @@ function buildStderr(rows, options) {
|
|
|
2697
2494
|
return "";
|
|
2698
2495
|
}
|
|
2699
2496
|
|
|
2497
|
+
// src/commands/setup.ts
|
|
2498
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2499
|
+
import {
|
|
2500
|
+
copyFile,
|
|
2501
|
+
mkdir as mkdir5,
|
|
2502
|
+
readFile as readFile9,
|
|
2503
|
+
writeFile as writeFile5
|
|
2504
|
+
} from "fs/promises";
|
|
2505
|
+
import { createRequire as createRequire2 } from "module";
|
|
2506
|
+
import { homedir as homedir4 } from "os";
|
|
2507
|
+
import path3 from "path";
|
|
2508
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2509
|
+
var RST = "\x1B[0m";
|
|
2510
|
+
var BOLD = "\x1B[1m";
|
|
2511
|
+
var DIM = "\x1B[2m";
|
|
2512
|
+
var WHITE_BOLD = "\x1B[1;37m";
|
|
2513
|
+
var BLUE = "\x1B[38;5;75m";
|
|
2514
|
+
var BLUE_DIM = "\x1B[38;5;69m";
|
|
2515
|
+
var ACCENT_BG = "\x1B[48;5;252m\x1B[38;5;16m";
|
|
2516
|
+
var GRADIENT = [
|
|
2517
|
+
"\x1B[38;5;255m",
|
|
2518
|
+
"\x1B[38;5;253m",
|
|
2519
|
+
"\x1B[38;5;251m",
|
|
2520
|
+
"\x1B[38;5;249m",
|
|
2521
|
+
"\x1B[38;5;246m",
|
|
2522
|
+
"\x1B[38;5;243m"
|
|
2523
|
+
];
|
|
2524
|
+
var LOGO_LINES = [
|
|
2525
|
+
" ___ ___ ___ ___ _ _ __ __ _ _ _ _ ___ ",
|
|
2526
|
+
" / __/ _ \\| \\| __| /_\\ | | | \\/ | /_\\ | \\| | /_\\ / __|",
|
|
2527
|
+
"| (_| (_) | |) | _| / _ \\| |__| |\\/| |/ _ \\| .` |/ _ \\ (__ ",
|
|
2528
|
+
" \\___\\___/|___/|___/_/ \\_\\____|_| |_/_/ \\_\\_|\\_/_/ \\_\\___|",
|
|
2529
|
+
" ",
|
|
2530
|
+
" a living wiki for codebases, for your agent "
|
|
2531
|
+
];
|
|
2532
|
+
var BAR = ` ${DIM}\u2502${RST}`;
|
|
2533
|
+
function printBanner(out) {
|
|
2534
|
+
out.write("\n");
|
|
2535
|
+
for (let i = 0; i < LOGO_LINES.length; i++) {
|
|
2536
|
+
const color = GRADIENT[Math.min(i, GRADIENT.length - 1)] ?? "";
|
|
2537
|
+
out.write(`${color}${LOGO_LINES[i]}${RST}
|
|
2538
|
+
`);
|
|
2539
|
+
}
|
|
2540
|
+
out.write(`
|
|
2541
|
+
${WHITE_BOLD} Install the hook + agent guides${RST}
|
|
2542
|
+
`);
|
|
2543
|
+
}
|
|
2544
|
+
function printBadge(out) {
|
|
2545
|
+
out.write(`
|
|
2546
|
+
${ACCENT_BG} codealmanac ${RST}
|
|
2547
|
+
|
|
2548
|
+
`);
|
|
2549
|
+
}
|
|
2550
|
+
function stepDone(out, msg) {
|
|
2551
|
+
out.write(` ${BLUE}\u25C7${RST} ${msg}
|
|
2552
|
+
`);
|
|
2553
|
+
}
|
|
2554
|
+
function stepActive(out, msg) {
|
|
2555
|
+
out.write(` ${BLUE}\u25C6${RST} ${msg}
|
|
2556
|
+
`);
|
|
2557
|
+
}
|
|
2558
|
+
function stepSkipped(out, msg) {
|
|
2559
|
+
out.write(` ${DIM}\u25CB ${msg}${RST}
|
|
2560
|
+
`);
|
|
2561
|
+
}
|
|
2562
|
+
async function runSetup(options = {}) {
|
|
2563
|
+
const out = options.stdout ?? process.stdout;
|
|
2564
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
2565
|
+
const interactive = isTTY && options.yes !== true;
|
|
2566
|
+
printBanner(out);
|
|
2567
|
+
printBadge(out);
|
|
2568
|
+
const auth = await safeCheckAuth(options.spawnCli);
|
|
2569
|
+
reportAuth(out, auth);
|
|
2570
|
+
out.write(BAR + "\n");
|
|
2571
|
+
let hookAction = "install";
|
|
2572
|
+
if (options.skipHook === true) {
|
|
2573
|
+
hookAction = "skip";
|
|
2574
|
+
} else if (interactive) {
|
|
2575
|
+
hookAction = await confirm(
|
|
2576
|
+
out,
|
|
2577
|
+
"Install the SessionEnd hook so capture runs at the end of every Claude Code session?",
|
|
2578
|
+
true
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
let hookResultLine = "";
|
|
2582
|
+
if (hookAction === "install") {
|
|
2583
|
+
const res = await runHookInstall({
|
|
2584
|
+
settingsPath: options.settingsPath,
|
|
2585
|
+
hookScriptPath: options.hookScriptPath
|
|
2586
|
+
});
|
|
2587
|
+
if (res.exitCode !== 0) {
|
|
2588
|
+
stepActive(out, `SessionEnd hook: ${res.stderr.trim()}`);
|
|
2589
|
+
return {
|
|
2590
|
+
stdout: "",
|
|
2591
|
+
stderr: res.stderr,
|
|
2592
|
+
exitCode: res.exitCode
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
hookResultLine = res.stdout.includes("already installed") ? `SessionEnd hook ${DIM}already installed${RST}` : `SessionEnd hook installed`;
|
|
2596
|
+
stepDone(out, hookResultLine);
|
|
2597
|
+
} else {
|
|
2598
|
+
stepSkipped(out, `SessionEnd hook ${DIM}skipped${RST}`);
|
|
2599
|
+
}
|
|
2600
|
+
out.write(BAR + "\n");
|
|
2601
|
+
let guidesAction = "install";
|
|
2602
|
+
if (options.skipGuides === true) {
|
|
2603
|
+
guidesAction = "skip";
|
|
2604
|
+
} else if (interactive) {
|
|
2605
|
+
guidesAction = await confirm(
|
|
2606
|
+
out,
|
|
2607
|
+
"Install the codealmanac usage guides into ~/.claude/ and import them from CLAUDE.md?",
|
|
2608
|
+
true
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
let guidesSummary;
|
|
2612
|
+
if (guidesAction === "install") {
|
|
2613
|
+
try {
|
|
2614
|
+
const summary = await installGuides({
|
|
2615
|
+
claudeDir: options.claudeDir ?? path3.join(homedir4(), ".claude"),
|
|
2616
|
+
guidesDir: options.guidesDir ?? resolveGuidesDir()
|
|
2617
|
+
});
|
|
2618
|
+
guidesSummary = summary.anyChanges ? `Guides installed (${summary.filesWritten.join(", ")})` : `Guides ${DIM}already installed${RST}`;
|
|
2619
|
+
stepDone(out, guidesSummary);
|
|
2620
|
+
} catch (err) {
|
|
2621
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2622
|
+
return {
|
|
2623
|
+
stdout: "",
|
|
2624
|
+
stderr: `almanac: guide install failed: ${msg}
|
|
2625
|
+
`,
|
|
2626
|
+
exitCode: 1
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
} else {
|
|
2630
|
+
stepSkipped(out, `Guides ${DIM}skipped${RST}`);
|
|
2631
|
+
}
|
|
2632
|
+
out.write(BAR + "\n");
|
|
2633
|
+
stepDone(out, `${BLUE}Setup complete${RST}`);
|
|
2634
|
+
out.write("\n");
|
|
2635
|
+
printNextSteps(out);
|
|
2636
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
2637
|
+
}
|
|
2638
|
+
async function safeCheckAuth(spawnCli) {
|
|
2639
|
+
try {
|
|
2640
|
+
return await checkClaudeAuth(spawnCli);
|
|
2641
|
+
} catch {
|
|
2642
|
+
return { loggedIn: false };
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
function reportAuth(out, auth) {
|
|
2646
|
+
if (auth.loggedIn) {
|
|
2647
|
+
const who = auth.email ?? "Claude account";
|
|
2648
|
+
const plan = auth.subscriptionType !== void 0 ? ` ${DIM}(${auth.subscriptionType})${RST}` : "";
|
|
2649
|
+
stepDone(out, `Claude auth: ${WHITE_BOLD}${who}${RST}${plan}`);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
if (process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0) {
|
|
2653
|
+
stepDone(out, `Claude auth: ${WHITE_BOLD}ANTHROPIC_API_KEY${RST} set`);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
stepActive(out, `Claude auth: ${DIM}not signed in${RST}`);
|
|
2657
|
+
for (const line of UNAUTHENTICATED_MESSAGE.split("\n")) {
|
|
2658
|
+
out.write(` ${DIM}\u2502 ${line}${RST}
|
|
2659
|
+
`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
async function installGuides(options) {
|
|
2663
|
+
await mkdir5(options.claudeDir, { recursive: true });
|
|
2664
|
+
const srcMini = path3.join(options.guidesDir, "mini.md");
|
|
2665
|
+
const srcRef = path3.join(options.guidesDir, "reference.md");
|
|
2666
|
+
if (!existsSync12(srcMini)) {
|
|
2667
|
+
throw new Error(`missing bundled guide: ${srcMini}`);
|
|
2668
|
+
}
|
|
2669
|
+
if (!existsSync12(srcRef)) {
|
|
2670
|
+
throw new Error(`missing bundled guide: ${srcRef}`);
|
|
2671
|
+
}
|
|
2672
|
+
const destMini = path3.join(options.claudeDir, "codealmanac.md");
|
|
2673
|
+
const destRef = path3.join(options.claudeDir, "codealmanac-reference.md");
|
|
2674
|
+
const miniChanged = await copyIfChanged(srcMini, destMini);
|
|
2675
|
+
const refChanged = await copyIfChanged(srcRef, destRef);
|
|
2676
|
+
const claudeMd = path3.join(options.claudeDir, "CLAUDE.md");
|
|
2677
|
+
const importChanged = await ensureImport(claudeMd);
|
|
2678
|
+
const filesWritten = [];
|
|
2679
|
+
if (miniChanged) filesWritten.push("codealmanac.md");
|
|
2680
|
+
if (refChanged) filesWritten.push("codealmanac-reference.md");
|
|
2681
|
+
if (importChanged) filesWritten.push("CLAUDE.md");
|
|
2682
|
+
return { anyChanges: filesWritten.length > 0, filesWritten };
|
|
2683
|
+
}
|
|
2684
|
+
async function copyIfChanged(src, dest) {
|
|
2685
|
+
const srcBytes = await readFile9(src);
|
|
2686
|
+
if (existsSync12(dest)) {
|
|
2687
|
+
try {
|
|
2688
|
+
const destBytes = await readFile9(dest);
|
|
2689
|
+
if (srcBytes.equals(destBytes)) return false;
|
|
2690
|
+
} catch {
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
await copyFile(src, dest);
|
|
2694
|
+
return true;
|
|
2695
|
+
}
|
|
2696
|
+
var IMPORT_LINE = "@~/.claude/codealmanac.md";
|
|
2697
|
+
async function ensureImport(claudeMdPath) {
|
|
2698
|
+
let existing = "";
|
|
2699
|
+
if (existsSync12(claudeMdPath)) {
|
|
2700
|
+
existing = await readFile9(claudeMdPath, "utf8");
|
|
2701
|
+
}
|
|
2702
|
+
if (hasImportLine(existing)) return false;
|
|
2703
|
+
const sep = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2704
|
+
const body = `${existing}${sep}${IMPORT_LINE}
|
|
2705
|
+
`;
|
|
2706
|
+
await writeFile5(claudeMdPath, body, "utf8");
|
|
2707
|
+
return true;
|
|
2708
|
+
}
|
|
2709
|
+
function hasImportLine(contents) {
|
|
2710
|
+
const lines = contents.split(/\r?\n/).map((l) => l.trim());
|
|
2711
|
+
return lines.includes(IMPORT_LINE);
|
|
2712
|
+
}
|
|
2713
|
+
function confirm(out, question, defaultYes) {
|
|
2714
|
+
return new Promise((resolve2) => {
|
|
2715
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
2716
|
+
out.write(` ${BLUE}\u25C6${RST} ${question} ${DIM}${hint}${RST} `);
|
|
2717
|
+
let buf = "";
|
|
2718
|
+
const onData = (chunk) => {
|
|
2719
|
+
buf += chunk.toString("utf8");
|
|
2720
|
+
const nl = buf.indexOf("\n");
|
|
2721
|
+
if (nl === -1) return;
|
|
2722
|
+
process.stdin.removeListener("data", onData);
|
|
2723
|
+
process.stdin.pause();
|
|
2724
|
+
const answer = buf.slice(0, nl).trim().toLowerCase();
|
|
2725
|
+
const accepted = answer.length === 0 ? defaultYes : answer === "y" || answer === "yes";
|
|
2726
|
+
resolve2(accepted ? "install" : "skip");
|
|
2727
|
+
};
|
|
2728
|
+
process.stdin.resume();
|
|
2729
|
+
process.stdin.on("data", onData);
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
function printNextSteps(out) {
|
|
2733
|
+
const innerW = 62;
|
|
2734
|
+
const vis = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
2735
|
+
const row = (content) => {
|
|
2736
|
+
const padding = Math.max(0, innerW - vis(content));
|
|
2737
|
+
return ` ${BLUE_DIM}\u2502${RST}${content}${" ".repeat(padding)}${BLUE_DIM}\u2502${RST}
|
|
2738
|
+
`;
|
|
2739
|
+
};
|
|
2740
|
+
const empty = row("");
|
|
2741
|
+
out.write(` ${BLUE_DIM}\u256D${"\u2500".repeat(innerW)}\u256E${RST}
|
|
2742
|
+
`);
|
|
2743
|
+
out.write(empty);
|
|
2744
|
+
out.write(row(` ${WHITE_BOLD}Next steps${RST}`));
|
|
2745
|
+
out.write(empty);
|
|
2746
|
+
out.write(
|
|
2747
|
+
row(` ${BLUE}1.${RST} ${BOLD}cd${RST} into a repo you want to document`)
|
|
2748
|
+
);
|
|
2749
|
+
out.write(
|
|
2750
|
+
row(
|
|
2751
|
+
` ${BLUE}2.${RST} ${BOLD}almanac bootstrap${RST} ${DIM}# scaffold the wiki${RST}`
|
|
2752
|
+
)
|
|
2753
|
+
);
|
|
2754
|
+
out.write(
|
|
2755
|
+
row(
|
|
2756
|
+
` ${BLUE}3.${RST} Work normally \u2014 capture runs on session end`
|
|
2757
|
+
)
|
|
2758
|
+
);
|
|
2759
|
+
out.write(empty);
|
|
2760
|
+
out.write(` ${BLUE_DIM}\u2570${"\u2500".repeat(innerW)}\u256F${RST}
|
|
2761
|
+
|
|
2762
|
+
`);
|
|
2763
|
+
}
|
|
2764
|
+
function resolveGuidesDir() {
|
|
2765
|
+
const here = path3.dirname(fileURLToPath3(import.meta.url));
|
|
2766
|
+
const candidates = [
|
|
2767
|
+
path3.resolve(here, "..", "guides"),
|
|
2768
|
+
// dist layout
|
|
2769
|
+
path3.resolve(here, "..", "..", "guides"),
|
|
2770
|
+
// src layout
|
|
2771
|
+
path3.resolve(here, "..", "..", "..", "guides")
|
|
2772
|
+
];
|
|
2773
|
+
for (const dir of candidates) {
|
|
2774
|
+
if (looksLikeGuidesDir(dir)) return dir;
|
|
2775
|
+
}
|
|
2776
|
+
try {
|
|
2777
|
+
const require2 = createRequire2(import.meta.url);
|
|
2778
|
+
const pkgJson = require2.resolve("codealmanac/package.json");
|
|
2779
|
+
const guides = path3.join(path3.dirname(pkgJson), "guides");
|
|
2780
|
+
if (looksLikeGuidesDir(guides)) return guides;
|
|
2781
|
+
} catch {
|
|
2782
|
+
}
|
|
2783
|
+
throw new Error(
|
|
2784
|
+
"could not locate bundled guides/ directory. Tried:\n" + candidates.map((c) => ` - ${c}`).join("\n")
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
function looksLikeGuidesDir(dir) {
|
|
2788
|
+
return existsSync12(path3.join(dir, "mini.md"));
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2700
2791
|
// src/commands/show.ts
|
|
2701
|
-
import { readFile as
|
|
2702
|
-
import { join as
|
|
2792
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2793
|
+
import { join as join10 } from "path";
|
|
2703
2794
|
async function runShow(options) {
|
|
2704
2795
|
const repoRoot = await resolveWikiRoot({
|
|
2705
2796
|
cwd: options.cwd,
|
|
2706
2797
|
wiki: options.wiki
|
|
2707
2798
|
});
|
|
2708
2799
|
await ensureFreshIndex({ repoRoot });
|
|
2709
|
-
const dbPath =
|
|
2800
|
+
const dbPath = join10(repoRoot, ".almanac", "index.db");
|
|
2710
2801
|
const db = openIndex(dbPath);
|
|
2711
2802
|
try {
|
|
2712
|
-
const slugs =
|
|
2803
|
+
const slugs = collectSlugs(options);
|
|
2713
2804
|
if (slugs.length === 0) {
|
|
2714
2805
|
return {
|
|
2715
2806
|
stdout: "",
|
|
@@ -2717,32 +2808,18 @@ async function runShow(options) {
|
|
|
2717
2808
|
exitCode: 1
|
|
2718
2809
|
};
|
|
2719
2810
|
}
|
|
2720
|
-
const stmt = db.prepare(
|
|
2721
|
-
"SELECT file_path FROM pages WHERE slug = ?"
|
|
2722
|
-
);
|
|
2723
2811
|
const records = [];
|
|
2724
2812
|
const missing = [];
|
|
2725
2813
|
for (const slug of slugs) {
|
|
2726
|
-
const
|
|
2727
|
-
if (
|
|
2814
|
+
const rec = await fetchRecord(db, slug);
|
|
2815
|
+
if (rec === null) {
|
|
2728
2816
|
missing.push(slug);
|
|
2729
2817
|
continue;
|
|
2730
2818
|
}
|
|
2731
|
-
|
|
2732
|
-
records.push({ slug, content: await readFile9(row.file_path, "utf8") });
|
|
2733
|
-
} catch (err) {
|
|
2734
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2735
|
-
missing.push(`${slug} (${message})`);
|
|
2736
|
-
}
|
|
2819
|
+
records.push(rec);
|
|
2737
2820
|
}
|
|
2738
2821
|
const bulk = options.stdin === true;
|
|
2739
|
-
|
|
2740
|
-
if (bulk) {
|
|
2741
|
-
stdout = records.map((r) => JSON.stringify(r)).join("\n");
|
|
2742
|
-
if (stdout.length > 0) stdout += "\n";
|
|
2743
|
-
} else {
|
|
2744
|
-
stdout = records.map((r) => r.content).join("");
|
|
2745
|
-
}
|
|
2822
|
+
const stdout = bulk ? formatBulk(records) : formatSingle(records, options);
|
|
2746
2823
|
const stderr = missing.map((s) => `almanac: no such page "${s}"
|
|
2747
2824
|
`).join("");
|
|
2748
2825
|
return {
|
|
@@ -2754,7 +2831,266 @@ async function runShow(options) {
|
|
|
2754
2831
|
db.close();
|
|
2755
2832
|
}
|
|
2756
2833
|
}
|
|
2757
|
-
function
|
|
2834
|
+
async function fetchRecord(db, slug) {
|
|
2835
|
+
const pageRow = db.prepare(
|
|
2836
|
+
"SELECT slug, title, file_path, updated_at, archived_at, superseded_by FROM pages WHERE slug = ?"
|
|
2837
|
+
).get(slug);
|
|
2838
|
+
if (pageRow === void 0) return null;
|
|
2839
|
+
const topics = db.prepare(
|
|
2840
|
+
"SELECT topic_slug FROM page_topics WHERE page_slug = ? ORDER BY topic_slug"
|
|
2841
|
+
).all(slug).map((r) => r.topic_slug);
|
|
2842
|
+
const refs = db.prepare(
|
|
2843
|
+
"SELECT original_path, is_dir FROM file_refs WHERE page_slug = ? ORDER BY original_path"
|
|
2844
|
+
).all(slug).map((r) => ({ path: r.original_path, is_dir: r.is_dir === 1 }));
|
|
2845
|
+
const linksOut = db.prepare(
|
|
2846
|
+
"SELECT target_slug FROM wikilinks WHERE source_slug = ? ORDER BY target_slug"
|
|
2847
|
+
).all(slug).map((r) => r.target_slug);
|
|
2848
|
+
const linksIn = db.prepare(
|
|
2849
|
+
"SELECT source_slug FROM wikilinks WHERE target_slug = ? ORDER BY source_slug"
|
|
2850
|
+
).all(slug).map((r) => r.source_slug);
|
|
2851
|
+
const xwiki = db.prepare(
|
|
2852
|
+
"SELECT target_wiki, target_slug FROM cross_wiki_links WHERE source_slug = ? ORDER BY target_wiki, target_slug"
|
|
2853
|
+
).all(slug).map((r) => ({ wiki: r.target_wiki, target: r.target_slug }));
|
|
2854
|
+
const supersedesRows = db.prepare(
|
|
2855
|
+
"SELECT slug FROM pages WHERE superseded_by = ? ORDER BY slug"
|
|
2856
|
+
).all(slug).map((r) => r.slug);
|
|
2857
|
+
let body = "";
|
|
2858
|
+
try {
|
|
2859
|
+
body = stripFrontmatter(await readFile10(pageRow.file_path, "utf8"));
|
|
2860
|
+
} catch {
|
|
2861
|
+
}
|
|
2862
|
+
return {
|
|
2863
|
+
slug: pageRow.slug,
|
|
2864
|
+
title: pageRow.title,
|
|
2865
|
+
file_path: pageRow.file_path,
|
|
2866
|
+
updated_at: pageRow.updated_at,
|
|
2867
|
+
archived_at: pageRow.archived_at,
|
|
2868
|
+
superseded_by: pageRow.superseded_by,
|
|
2869
|
+
supersedes: supersedesRows,
|
|
2870
|
+
topics,
|
|
2871
|
+
file_refs: refs,
|
|
2872
|
+
wikilinks_out: linksOut,
|
|
2873
|
+
wikilinks_in: linksIn,
|
|
2874
|
+
cross_wiki_links: xwiki,
|
|
2875
|
+
body
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
function formatBulk(records) {
|
|
2879
|
+
if (records.length === 0) return "";
|
|
2880
|
+
return records.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
2881
|
+
}
|
|
2882
|
+
function formatSingle(records, options) {
|
|
2883
|
+
if (options.json === true) {
|
|
2884
|
+
const only = records[0] ?? null;
|
|
2885
|
+
return `${JSON.stringify(only, null, 2)}
|
|
2886
|
+
`;
|
|
2887
|
+
}
|
|
2888
|
+
return records.map((r) => formatRecord(r, options)).join("");
|
|
2889
|
+
}
|
|
2890
|
+
var FIELD_ORDER = [
|
|
2891
|
+
"title",
|
|
2892
|
+
"topics",
|
|
2893
|
+
"files",
|
|
2894
|
+
"links",
|
|
2895
|
+
"backlinks",
|
|
2896
|
+
"xwiki",
|
|
2897
|
+
"lineage",
|
|
2898
|
+
"updated",
|
|
2899
|
+
"path"
|
|
2900
|
+
];
|
|
2901
|
+
function selectedFields(options) {
|
|
2902
|
+
const selected = [];
|
|
2903
|
+
for (const f of FIELD_ORDER) {
|
|
2904
|
+
if (options[f] === true) selected.push(f);
|
|
2905
|
+
}
|
|
2906
|
+
return selected;
|
|
2907
|
+
}
|
|
2908
|
+
function formatRecord(rec, options) {
|
|
2909
|
+
if (options.raw === true) {
|
|
2910
|
+
return rec.body;
|
|
2911
|
+
}
|
|
2912
|
+
const fields = selectedFields(options);
|
|
2913
|
+
if (fields.length > 0) {
|
|
2914
|
+
if (fields.length === 1) {
|
|
2915
|
+
return bareField(rec, fields[0]);
|
|
2916
|
+
}
|
|
2917
|
+
return labeledFields(rec, fields);
|
|
2918
|
+
}
|
|
2919
|
+
if (options.meta === true) {
|
|
2920
|
+
return metadataHeader(rec) + "\n";
|
|
2921
|
+
}
|
|
2922
|
+
if (options.lead === true) {
|
|
2923
|
+
return firstParagraph(rec.body) + "\n";
|
|
2924
|
+
}
|
|
2925
|
+
const header = metadataHeader(rec);
|
|
2926
|
+
const body = rec.body;
|
|
2927
|
+
const sep = body.length > 0 ? "\n\n---\n\n" : "\n";
|
|
2928
|
+
return header + sep + body;
|
|
2929
|
+
}
|
|
2930
|
+
function bareField(rec, field) {
|
|
2931
|
+
switch (field) {
|
|
2932
|
+
case "title":
|
|
2933
|
+
return (rec.title ?? "") + "\n";
|
|
2934
|
+
case "topics":
|
|
2935
|
+
return rec.topics.map((t) => `${t}
|
|
2936
|
+
`).join("");
|
|
2937
|
+
case "files":
|
|
2938
|
+
return rec.file_refs.map((r) => `${r.path}
|
|
2939
|
+
`).join("");
|
|
2940
|
+
case "links":
|
|
2941
|
+
return rec.wikilinks_out.map((t) => `${t}
|
|
2942
|
+
`).join("");
|
|
2943
|
+
case "backlinks":
|
|
2944
|
+
return rec.wikilinks_in.map((t) => `${t}
|
|
2945
|
+
`).join("");
|
|
2946
|
+
case "xwiki":
|
|
2947
|
+
return rec.cross_wiki_links.map((x) => `${x.wiki}:${x.target}
|
|
2948
|
+
`).join("");
|
|
2949
|
+
case "lineage": {
|
|
2950
|
+
const lines = [];
|
|
2951
|
+
if (rec.archived_at !== null) {
|
|
2952
|
+
lines.push(
|
|
2953
|
+
`archived_at: ${new Date(rec.archived_at * 1e3).toISOString()}`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
if (rec.superseded_by !== null) {
|
|
2957
|
+
lines.push(`superseded_by: ${rec.superseded_by}`);
|
|
2958
|
+
}
|
|
2959
|
+
if (rec.supersedes.length > 0) {
|
|
2960
|
+
lines.push(`supersedes: ${rec.supersedes.join(", ")}`);
|
|
2961
|
+
}
|
|
2962
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
2963
|
+
` : "";
|
|
2964
|
+
}
|
|
2965
|
+
case "updated":
|
|
2966
|
+
return `${new Date(rec.updated_at * 1e3).toISOString()}
|
|
2967
|
+
`;
|
|
2968
|
+
case "path":
|
|
2969
|
+
return `${rec.file_path}
|
|
2970
|
+
`;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
function labeledFields(rec, fields) {
|
|
2974
|
+
const parts = [];
|
|
2975
|
+
for (const f of fields) {
|
|
2976
|
+
parts.push(labeledSection(rec, f));
|
|
2977
|
+
}
|
|
2978
|
+
return parts.join("\n") + (parts.length > 0 ? "" : "");
|
|
2979
|
+
}
|
|
2980
|
+
function labeledSection(rec, field) {
|
|
2981
|
+
switch (field) {
|
|
2982
|
+
case "title":
|
|
2983
|
+
return `title: ${rec.title ?? "\u2014"}
|
|
2984
|
+
`;
|
|
2985
|
+
case "topics":
|
|
2986
|
+
return rec.topics.length > 0 ? `topics: ${rec.topics.join(", ")}
|
|
2987
|
+
` : `topics: \u2014
|
|
2988
|
+
`;
|
|
2989
|
+
case "files":
|
|
2990
|
+
return formatListSection(
|
|
2991
|
+
"files",
|
|
2992
|
+
rec.file_refs.map((r) => `${r.path}`)
|
|
2993
|
+
);
|
|
2994
|
+
case "links":
|
|
2995
|
+
return formatListSection("links", rec.wikilinks_out);
|
|
2996
|
+
case "backlinks":
|
|
2997
|
+
return formatListSection("backlinks", rec.wikilinks_in);
|
|
2998
|
+
case "xwiki":
|
|
2999
|
+
return formatListSection(
|
|
3000
|
+
"xwiki",
|
|
3001
|
+
rec.cross_wiki_links.map((x) => `${x.wiki}:${x.target}`)
|
|
3002
|
+
);
|
|
3003
|
+
case "lineage": {
|
|
3004
|
+
const lines = ["lineage:"];
|
|
3005
|
+
if (rec.archived_at !== null) {
|
|
3006
|
+
lines.push(
|
|
3007
|
+
` archived_at: ${new Date(rec.archived_at * 1e3).toISOString()}`
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
if (rec.superseded_by !== null) {
|
|
3011
|
+
lines.push(` superseded_by: ${rec.superseded_by}`);
|
|
3012
|
+
}
|
|
3013
|
+
if (rec.supersedes.length > 0) {
|
|
3014
|
+
lines.push(` supersedes: ${rec.supersedes.join(", ")}`);
|
|
3015
|
+
}
|
|
3016
|
+
if (lines.length === 1) lines.push(" \u2014");
|
|
3017
|
+
return lines.join("\n") + "\n";
|
|
3018
|
+
}
|
|
3019
|
+
case "updated":
|
|
3020
|
+
return `updated: ${new Date(rec.updated_at * 1e3).toISOString()}
|
|
3021
|
+
`;
|
|
3022
|
+
case "path":
|
|
3023
|
+
return `path: ${rec.file_path}
|
|
3024
|
+
`;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
function formatListSection(label, items) {
|
|
3028
|
+
if (items.length === 0) return `${label}: \u2014
|
|
3029
|
+
`;
|
|
3030
|
+
if (items.length <= 3) return `${label}: ${items.join(", ")}
|
|
3031
|
+
`;
|
|
3032
|
+
return `${label}:
|
|
3033
|
+
${items.map((i) => ` ${i}`).join("\n")}
|
|
3034
|
+
`;
|
|
3035
|
+
}
|
|
3036
|
+
function metadataHeader(rec) {
|
|
3037
|
+
const lines = [];
|
|
3038
|
+
lines.push(`slug: ${rec.slug}`);
|
|
3039
|
+
lines.push(`title: ${rec.title ?? "\u2014"}`);
|
|
3040
|
+
lines.push(
|
|
3041
|
+
`topics: ${rec.topics.length > 0 ? rec.topics.join(", ") : "\u2014"}`
|
|
3042
|
+
);
|
|
3043
|
+
if (rec.file_refs.length > 0) {
|
|
3044
|
+
const parts = rec.file_refs.map(
|
|
3045
|
+
(r) => `${r.path}`
|
|
3046
|
+
);
|
|
3047
|
+
lines.push(`files: ${parts.join(", ")}`);
|
|
3048
|
+
}
|
|
3049
|
+
lines.push(
|
|
3050
|
+
`updated: ${new Date(rec.updated_at * 1e3).toISOString()}`
|
|
3051
|
+
);
|
|
3052
|
+
if (rec.wikilinks_out.length > 0) {
|
|
3053
|
+
lines.push(`links: ${rec.wikilinks_out.join(", ")}`);
|
|
3054
|
+
}
|
|
3055
|
+
if (rec.wikilinks_in.length > 0) {
|
|
3056
|
+
lines.push(`backlinks: ${rec.wikilinks_in.join(", ")}`);
|
|
3057
|
+
}
|
|
3058
|
+
if (rec.cross_wiki_links.length > 0) {
|
|
3059
|
+
lines.push(
|
|
3060
|
+
`xwiki: ${rec.cross_wiki_links.map((x) => `${x.wiki}:${x.target}`).join(", ")}`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
if (rec.archived_at !== null) {
|
|
3064
|
+
lines.push(
|
|
3065
|
+
`archived: ${new Date(rec.archived_at * 1e3).toISOString()}`
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
if (rec.superseded_by !== null) {
|
|
3069
|
+
lines.push(`superseded_by: ${rec.superseded_by}`);
|
|
3070
|
+
}
|
|
3071
|
+
if (rec.supersedes.length > 0) {
|
|
3072
|
+
lines.push(`supersedes: ${rec.supersedes.join(", ")}`);
|
|
3073
|
+
}
|
|
3074
|
+
return lines.join("\n");
|
|
3075
|
+
}
|
|
3076
|
+
function stripFrontmatter(src) {
|
|
3077
|
+
if (!src.startsWith("---\n") && !src.startsWith("---\r\n")) return src;
|
|
3078
|
+
const rest = src.slice(src.indexOf("\n") + 1);
|
|
3079
|
+
const endMatch = rest.match(/^---[ \t]*\r?\n/m);
|
|
3080
|
+
if (endMatch === null || endMatch.index === void 0) return src;
|
|
3081
|
+
return rest.slice(endMatch.index + endMatch[0].length);
|
|
3082
|
+
}
|
|
3083
|
+
function firstParagraph(body) {
|
|
3084
|
+
let src = body.trimStart();
|
|
3085
|
+
if (src.startsWith("# ")) {
|
|
3086
|
+
const nl = src.indexOf("\n");
|
|
3087
|
+
src = nl === -1 ? "" : src.slice(nl + 1).trimStart();
|
|
3088
|
+
}
|
|
3089
|
+
const blank = src.search(/\n[ \t]*\n/);
|
|
3090
|
+
if (blank === -1) return src.trimEnd();
|
|
3091
|
+
return src.slice(0, blank).trimEnd();
|
|
3092
|
+
}
|
|
3093
|
+
function collectSlugs(options) {
|
|
2758
3094
|
if (options.stdin === true && options.stdinInput !== void 0) {
|
|
2759
3095
|
return options.stdinInput.split(/\r?\n/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2760
3096
|
}
|
|
@@ -2765,17 +3101,17 @@ function collectSlugs3(options) {
|
|
|
2765
3101
|
}
|
|
2766
3102
|
|
|
2767
3103
|
// src/topics/frontmatterRewrite.ts
|
|
2768
|
-
import { readFile as
|
|
3104
|
+
import { readFile as readFile11, rename as rename4, writeFile as writeFile6 } from "fs/promises";
|
|
2769
3105
|
import yaml3 from "js-yaml";
|
|
2770
3106
|
async function rewritePageTopics(filePath, transform) {
|
|
2771
|
-
const raw = await
|
|
3107
|
+
const raw = await readFile11(filePath, "utf8");
|
|
2772
3108
|
const { before, after, output, changed } = applyTopicsTransform(
|
|
2773
3109
|
raw,
|
|
2774
3110
|
transform
|
|
2775
3111
|
);
|
|
2776
3112
|
if (changed) {
|
|
2777
3113
|
const tmp = `${filePath}.tmp`;
|
|
2778
|
-
await
|
|
3114
|
+
await writeFile6(tmp, output, "utf8");
|
|
2779
3115
|
await rename4(tmp, filePath);
|
|
2780
3116
|
}
|
|
2781
3117
|
return { before, after, changed };
|
|
@@ -2982,12 +3318,12 @@ function arraysEqual(a, b) {
|
|
|
2982
3318
|
}
|
|
2983
3319
|
|
|
2984
3320
|
// src/topics/paths.ts
|
|
2985
|
-
import { join as
|
|
3321
|
+
import { join as join11 } from "path";
|
|
2986
3322
|
function topicsYamlPath(repoRoot) {
|
|
2987
|
-
return
|
|
3323
|
+
return join11(repoRoot, ".almanac", "topics.yaml");
|
|
2988
3324
|
}
|
|
2989
3325
|
function indexDbPath(repoRoot) {
|
|
2990
|
-
return
|
|
3326
|
+
return join11(repoRoot, ".almanac", "index.db");
|
|
2991
3327
|
}
|
|
2992
3328
|
|
|
2993
3329
|
// src/commands/tag.ts
|
|
@@ -3146,8 +3482,8 @@ async function runUntag(options) {
|
|
|
3146
3482
|
}
|
|
3147
3483
|
|
|
3148
3484
|
// src/commands/topics.ts
|
|
3149
|
-
import { readFile as
|
|
3150
|
-
import { join as
|
|
3485
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
3486
|
+
import { join as join12 } from "path";
|
|
3151
3487
|
import fg3 from "fast-glob";
|
|
3152
3488
|
async function runTopicsList(options) {
|
|
3153
3489
|
const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });
|
|
@@ -3619,7 +3955,7 @@ async function runTopicsDescribe(options) {
|
|
|
3619
3955
|
};
|
|
3620
3956
|
}
|
|
3621
3957
|
async function rewriteTopicOnPages(repoRoot, transform) {
|
|
3622
|
-
const pagesDir =
|
|
3958
|
+
const pagesDir = join12(repoRoot, ".almanac", "pages");
|
|
3623
3959
|
const files = await fg3("**/*.md", {
|
|
3624
3960
|
cwd: pagesDir,
|
|
3625
3961
|
absolute: true,
|
|
@@ -3627,7 +3963,7 @@ async function rewriteTopicOnPages(repoRoot, transform) {
|
|
|
3627
3963
|
});
|
|
3628
3964
|
let changed = 0;
|
|
3629
3965
|
for (const filePath of files) {
|
|
3630
|
-
const raw = await
|
|
3966
|
+
const raw = await readFile12(filePath, "utf8");
|
|
3631
3967
|
const applied = applyTopicsTransform(raw, transform);
|
|
3632
3968
|
if (!applied.changed) continue;
|
|
3633
3969
|
await rewritePageTopics(filePath, transform);
|
|
@@ -3636,14 +3972,146 @@ async function rewriteTopicOnPages(repoRoot, transform) {
|
|
|
3636
3972
|
return changed;
|
|
3637
3973
|
}
|
|
3638
3974
|
|
|
3975
|
+
// src/commands/uninstall.ts
|
|
3976
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3977
|
+
import { readFile as readFile13, rm, writeFile as writeFile7 } from "fs/promises";
|
|
3978
|
+
import { homedir as homedir5 } from "os";
|
|
3979
|
+
import path4 from "path";
|
|
3980
|
+
var BLUE2 = "\x1B[38;5;75m";
|
|
3981
|
+
var DIM2 = "\x1B[2m";
|
|
3982
|
+
var RST2 = "\x1B[0m";
|
|
3983
|
+
async function runUninstall(options = {}) {
|
|
3984
|
+
const out = options.stdout ?? process.stdout;
|
|
3985
|
+
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
3986
|
+
const interactive = isTTY && options.yes !== true;
|
|
3987
|
+
const claudeDir = options.claudeDir ?? path4.join(homedir5(), ".claude");
|
|
3988
|
+
out.write("\n");
|
|
3989
|
+
let removeHook = true;
|
|
3990
|
+
if (options.keepHook === true) {
|
|
3991
|
+
removeHook = false;
|
|
3992
|
+
} else if (interactive) {
|
|
3993
|
+
removeHook = await confirm2(
|
|
3994
|
+
out,
|
|
3995
|
+
"Remove the SessionEnd hook from ~/.claude/settings.json?",
|
|
3996
|
+
true
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
if (removeHook) {
|
|
4000
|
+
const res = await runHookUninstall({
|
|
4001
|
+
settingsPath: options.settingsPath,
|
|
4002
|
+
hookScriptPath: options.hookScriptPath
|
|
4003
|
+
});
|
|
4004
|
+
if (res.exitCode !== 0) {
|
|
4005
|
+
return { stdout: "", stderr: res.stderr, exitCode: res.exitCode };
|
|
4006
|
+
}
|
|
4007
|
+
out.write(` ${BLUE2}\u25C7${RST2} ${res.stdout.trim()}
|
|
4008
|
+
`);
|
|
4009
|
+
} else {
|
|
4010
|
+
out.write(` ${DIM2}\u25CB Hook kept${RST2}
|
|
4011
|
+
`);
|
|
4012
|
+
}
|
|
4013
|
+
let removeGuides = true;
|
|
4014
|
+
if (options.keepGuides === true) {
|
|
4015
|
+
removeGuides = false;
|
|
4016
|
+
} else if (interactive) {
|
|
4017
|
+
removeGuides = await confirm2(
|
|
4018
|
+
out,
|
|
4019
|
+
"Remove the guides + CLAUDE.md import line?",
|
|
4020
|
+
true
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
if (removeGuides) {
|
|
4024
|
+
const summary = await removeGuideFiles(claudeDir);
|
|
4025
|
+
if (summary.anyChanges) {
|
|
4026
|
+
out.write(
|
|
4027
|
+
` ${BLUE2}\u25C7${RST2} Guides removed (${summary.filesTouched.join(", ")})
|
|
4028
|
+
`
|
|
4029
|
+
);
|
|
4030
|
+
} else {
|
|
4031
|
+
out.write(` ${DIM2}\u25CB Guides not installed${RST2}
|
|
4032
|
+
`);
|
|
4033
|
+
}
|
|
4034
|
+
} else {
|
|
4035
|
+
out.write(` ${DIM2}\u25CB Guides kept${RST2}
|
|
4036
|
+
`);
|
|
4037
|
+
}
|
|
4038
|
+
out.write(`
|
|
4039
|
+
${BLUE2}\u25C7${RST2} ${BLUE2}Uninstall complete${RST2}
|
|
4040
|
+
|
|
4041
|
+
`);
|
|
4042
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
4043
|
+
}
|
|
4044
|
+
async function removeGuideFiles(claudeDir) {
|
|
4045
|
+
const touched = [];
|
|
4046
|
+
const mini = path4.join(claudeDir, "codealmanac.md");
|
|
4047
|
+
const ref = path4.join(claudeDir, "codealmanac-reference.md");
|
|
4048
|
+
const claudeMd = path4.join(claudeDir, "CLAUDE.md");
|
|
4049
|
+
if (existsSync13(mini)) {
|
|
4050
|
+
await rm(mini, { force: true });
|
|
4051
|
+
touched.push("codealmanac.md");
|
|
4052
|
+
}
|
|
4053
|
+
if (existsSync13(ref)) {
|
|
4054
|
+
await rm(ref, { force: true });
|
|
4055
|
+
touched.push("codealmanac-reference.md");
|
|
4056
|
+
}
|
|
4057
|
+
if (existsSync13(claudeMd)) {
|
|
4058
|
+
const existing = await readFile13(claudeMd, "utf8");
|
|
4059
|
+
const { changed, body } = removeImportLine(existing);
|
|
4060
|
+
if (changed) {
|
|
4061
|
+
if (body.trim().length === 0) {
|
|
4062
|
+
await rm(claudeMd, { force: true });
|
|
4063
|
+
touched.push("CLAUDE.md (deleted)");
|
|
4064
|
+
} else {
|
|
4065
|
+
await writeFile7(claudeMd, body, "utf8");
|
|
4066
|
+
touched.push("CLAUDE.md");
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
return { anyChanges: touched.length > 0, filesTouched: touched };
|
|
4071
|
+
}
|
|
4072
|
+
function removeImportLine(contents) {
|
|
4073
|
+
const eol = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
4074
|
+
const lines = contents.split(/\r?\n/);
|
|
4075
|
+
const indices = [];
|
|
4076
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4077
|
+
if (lines[i].trim() === IMPORT_LINE) indices.push(i);
|
|
4078
|
+
}
|
|
4079
|
+
if (indices.length === 0) return { changed: false, body: contents };
|
|
4080
|
+
for (let i = indices.length - 1; i >= 0; i--) {
|
|
4081
|
+
lines.splice(indices[i], 1);
|
|
4082
|
+
}
|
|
4083
|
+
let body = lines.join(eol);
|
|
4084
|
+
body = body.replace(/\n\n\n+/g, "\n\n");
|
|
4085
|
+
return { changed: true, body };
|
|
4086
|
+
}
|
|
4087
|
+
function confirm2(out, question, defaultYes) {
|
|
4088
|
+
return new Promise((resolve2) => {
|
|
4089
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
4090
|
+
out.write(` ${BLUE2}\u25C6${RST2} ${question} ${DIM2}${hint}${RST2} `);
|
|
4091
|
+
let buf = "";
|
|
4092
|
+
const onData = (chunk) => {
|
|
4093
|
+
buf += chunk.toString("utf8");
|
|
4094
|
+
const nl = buf.indexOf("\n");
|
|
4095
|
+
if (nl === -1) return;
|
|
4096
|
+
process.stdin.removeListener("data", onData);
|
|
4097
|
+
process.stdin.pause();
|
|
4098
|
+
const answer = buf.slice(0, nl).trim().toLowerCase();
|
|
4099
|
+
const accepted = answer.length === 0 ? defaultYes : answer === "y" || answer === "yes";
|
|
4100
|
+
resolve2(accepted);
|
|
4101
|
+
};
|
|
4102
|
+
process.stdin.resume();
|
|
4103
|
+
process.stdin.on("data", onData);
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
|
|
3639
4107
|
// src/registry/autoregister.ts
|
|
3640
|
-
import { existsSync as
|
|
4108
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3641
4109
|
import { basename as basename5 } from "path";
|
|
3642
4110
|
async function autoRegisterIfNeeded(cwd) {
|
|
3643
4111
|
try {
|
|
3644
4112
|
const repoRoot = findNearestAlmanacDir(cwd);
|
|
3645
4113
|
if (repoRoot === null) return null;
|
|
3646
|
-
if (!
|
|
4114
|
+
if (!existsSync14(repoRoot)) return null;
|
|
3647
4115
|
const entries = await readRegistry();
|
|
3648
4116
|
const existing = entries.find((e) => samePath(e.path, repoRoot));
|
|
3649
4117
|
if (existing !== void 0) return existing;
|
|
@@ -3688,38 +4156,20 @@ function samePath(a, b) {
|
|
|
3688
4156
|
|
|
3689
4157
|
// src/cli.ts
|
|
3690
4158
|
async function run(argv) {
|
|
3691
|
-
const program = new Command();
|
|
3692
4159
|
const invoked = argv[1] !== void 0 ? basename6(argv[1]) : "almanac";
|
|
3693
4160
|
const programName = invoked === "codealmanac" ? "codealmanac" : "almanac";
|
|
4161
|
+
const program = new Command();
|
|
3694
4162
|
program.name(programName).description(
|
|
3695
4163
|
"codealmanac \u2014 a living wiki for codebases, maintained by AI agents"
|
|
3696
|
-
).version(
|
|
3697
|
-
|
|
3698
|
-
const result = await
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
`${verb} wiki "${result.entry.name}" at ${result.almanacDir}
|
|
3706
|
-
`
|
|
3707
|
-
);
|
|
3708
|
-
});
|
|
3709
|
-
program.command("list").description("list registered wikis").option("--json", "emit structured JSON").option(
|
|
3710
|
-
"--drop <name>",
|
|
3711
|
-
"remove a wiki from the registry (the only way entries are ever removed)"
|
|
3712
|
-
).action(async (opts) => {
|
|
3713
|
-
if (opts.drop === void 0) {
|
|
3714
|
-
await autoRegisterIfNeeded(process.cwd());
|
|
3715
|
-
}
|
|
3716
|
-
const result = await listWikis(opts);
|
|
3717
|
-
process.stdout.write(result.stdout);
|
|
3718
|
-
if (result.exitCode !== 0) {
|
|
3719
|
-
process.exitCode = result.exitCode;
|
|
3720
|
-
}
|
|
3721
|
-
});
|
|
3722
|
-
program.command("search [query]").description("query pages by text, topic, file mentions, or freshness").option(
|
|
4164
|
+
).version(readPackageVersion(), "-v, --version", "print version");
|
|
4165
|
+
if (programName === "codealmanac" && argv.length === 2) {
|
|
4166
|
+
const result = await runSetup({});
|
|
4167
|
+
if (result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
4168
|
+
if (result.stdout.length > 0) process.stdout.write(result.stdout);
|
|
4169
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
4172
|
+
program.command("search [query]").description("find pages by text, topic, file mentions, freshness").option(
|
|
3723
4173
|
"--topic <name...>",
|
|
3724
4174
|
"filter by topic (repeat for intersection)",
|
|
3725
4175
|
collectOption,
|
|
@@ -3750,12 +4200,10 @@ async function run(argv) {
|
|
|
3750
4200
|
json: opts.json,
|
|
3751
4201
|
limit: opts.limit
|
|
3752
4202
|
});
|
|
3753
|
-
|
|
3754
|
-
process.stdout.write(result.stdout);
|
|
3755
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4203
|
+
emit(result);
|
|
3756
4204
|
}
|
|
3757
4205
|
);
|
|
3758
|
-
program.command("show [slug]").description("print
|
|
4206
|
+
program.command("show [slug]").description("print a page (metadata + body; flags to narrow)").option("--stdin", "read slugs from stdin (one per line)").option("--wiki <name>", "target a specific registered wiki").option("--json", "structured JSON (overrides other view/field flags)").option("--raw", "body only (alias: --body)").option("--body", "body only (alias: --raw)").option("--meta", "metadata only, no body").option("--lead", "first paragraph of the body only").option("--title", "print title").option("--topics", "print topics").option("--files", "print file refs").option("--links", "print outgoing wikilinks").option("--backlinks", "print incoming wikilinks").option("--xwiki", "print cross-wiki links").option("--lineage", "print archived_at / supersedes / superseded_by").option("--updated", "print updated timestamp").option("--path", "print absolute file path").action(
|
|
3759
4207
|
async (slug, opts) => {
|
|
3760
4208
|
await autoRegisterIfNeeded(process.cwd());
|
|
3761
4209
|
const result = await runShow({
|
|
@@ -3763,54 +4211,85 @@ async function run(argv) {
|
|
|
3763
4211
|
slug,
|
|
3764
4212
|
stdin: opts.stdin,
|
|
3765
4213
|
stdinInput: opts.stdin === true ? await readStdin() : void 0,
|
|
3766
|
-
wiki: opts.wiki
|
|
4214
|
+
wiki: opts.wiki,
|
|
4215
|
+
json: opts.json,
|
|
4216
|
+
// `--body` is a surface alias for `--raw`. Fold it into one
|
|
4217
|
+
// field at the CLI boundary so the implementation never has
|
|
4218
|
+
// to care which spelling the user typed.
|
|
4219
|
+
raw: opts.raw === true || opts.body === true,
|
|
4220
|
+
meta: opts.meta,
|
|
4221
|
+
lead: opts.lead,
|
|
4222
|
+
title: opts.title,
|
|
4223
|
+
topics: opts.topics,
|
|
4224
|
+
files: opts.files,
|
|
4225
|
+
links: opts.links,
|
|
4226
|
+
backlinks: opts.backlinks,
|
|
4227
|
+
xwiki: opts.xwiki,
|
|
4228
|
+
lineage: opts.lineage,
|
|
4229
|
+
updated: opts.updated,
|
|
4230
|
+
path: opts.path
|
|
3767
4231
|
});
|
|
3768
|
-
|
|
3769
|
-
process.stdout.write(result.stdout);
|
|
3770
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4232
|
+
emit(result);
|
|
3771
4233
|
}
|
|
3772
4234
|
);
|
|
3773
|
-
program.command("
|
|
3774
|
-
async (
|
|
4235
|
+
program.command("health").description("report graph integrity problems").option("--topic <name>", "scope to a topic + its descendants").option("--stale <duration>", "stale threshold (default 90d)").option("--stdin", "read page slugs from stdin (limit to these pages)").option("--json", "emit structured JSON").option("--wiki <name>", "target a specific registered wiki").action(
|
|
4236
|
+
async (opts) => {
|
|
3775
4237
|
await autoRegisterIfNeeded(process.cwd());
|
|
3776
|
-
const result = await
|
|
4238
|
+
const result = await runHealth({
|
|
3777
4239
|
cwd: process.cwd(),
|
|
3778
|
-
|
|
4240
|
+
topic: opts.topic,
|
|
4241
|
+
stale: opts.stale,
|
|
3779
4242
|
stdin: opts.stdin,
|
|
3780
4243
|
stdinInput: opts.stdin === true ? await readStdin() : void 0,
|
|
4244
|
+
json: opts.json,
|
|
3781
4245
|
wiki: opts.wiki
|
|
3782
4246
|
});
|
|
3783
|
-
|
|
3784
|
-
process.stdout.write(result.stdout);
|
|
3785
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4247
|
+
emit(result);
|
|
3786
4248
|
}
|
|
3787
4249
|
);
|
|
3788
|
-
program.command("
|
|
3789
|
-
|
|
4250
|
+
program.command("list").description("list registered wikis").option("--json", "emit structured JSON").option(
|
|
4251
|
+
"--drop <name>",
|
|
4252
|
+
"remove a wiki from the registry (the only way entries are ever removed)"
|
|
4253
|
+
).action(async (opts) => {
|
|
4254
|
+
if (opts.drop === void 0) {
|
|
4255
|
+
await autoRegisterIfNeeded(process.cwd());
|
|
4256
|
+
}
|
|
4257
|
+
const result = await listWikis(opts);
|
|
4258
|
+
process.stdout.write(result.stdout);
|
|
4259
|
+
if (result.exitCode !== 0) {
|
|
4260
|
+
process.exitCode = result.exitCode;
|
|
4261
|
+
}
|
|
4262
|
+
});
|
|
4263
|
+
program.command("tag [page] [topics...]").description("add topics to a page (auto-creates missing topics)").option("--stdin", "read page slugs from stdin (one per line)").option("--wiki <name>", "target a specific registered wiki").action(
|
|
4264
|
+
async (page, topicsArg, opts) => {
|
|
3790
4265
|
await autoRegisterIfNeeded(process.cwd());
|
|
3791
|
-
const
|
|
4266
|
+
const resolvedTopics = opts.stdin === true ? [page, ...topicsArg].filter(
|
|
4267
|
+
(t) => typeof t === "string" && t.length > 0
|
|
4268
|
+
) : topicsArg;
|
|
4269
|
+
const result = await runTag({
|
|
3792
4270
|
cwd: process.cwd(),
|
|
3793
|
-
|
|
4271
|
+
page: opts.stdin === true ? void 0 : page,
|
|
4272
|
+
topics: resolvedTopics,
|
|
3794
4273
|
stdin: opts.stdin,
|
|
3795
4274
|
stdinInput: opts.stdin === true ? await readStdin() : void 0,
|
|
3796
|
-
json: opts.json,
|
|
3797
4275
|
wiki: opts.wiki
|
|
3798
4276
|
});
|
|
3799
|
-
|
|
3800
|
-
process.stdout.write(result.stdout);
|
|
3801
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4277
|
+
emit(result);
|
|
3802
4278
|
}
|
|
3803
4279
|
);
|
|
3804
|
-
program.command("
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
4280
|
+
program.command("untag <page> <topic>").description("remove a topic from a page's frontmatter").option("--wiki <name>", "target a specific registered wiki").action(
|
|
4281
|
+
async (page, topic, opts) => {
|
|
4282
|
+
await autoRegisterIfNeeded(process.cwd());
|
|
4283
|
+
const result = await runUntag({
|
|
4284
|
+
cwd: process.cwd(),
|
|
4285
|
+
page,
|
|
4286
|
+
topic,
|
|
4287
|
+
wiki: opts.wiki
|
|
4288
|
+
});
|
|
4289
|
+
emit(result);
|
|
4290
|
+
}
|
|
4291
|
+
);
|
|
4292
|
+
const topics = program.command("topics").description("manage the topic DAG");
|
|
3814
4293
|
topics.command("list", { isDefault: true }).description("list all topics with page counts").option("--wiki <name>", "target a specific registered wiki").option("--json", "emit structured JSON").action(async (opts) => {
|
|
3815
4294
|
await autoRegisterIfNeeded(process.cwd());
|
|
3816
4295
|
const result = await runTopicsList({
|
|
@@ -3907,37 +4386,8 @@ async function run(argv) {
|
|
|
3907
4386
|
emit(result);
|
|
3908
4387
|
}
|
|
3909
4388
|
);
|
|
3910
|
-
program.command("tag [page] [topics...]").description("add topics to a page (auto-creates missing topics)").option("--stdin", "read page slugs from stdin (one per line)").option("--wiki <name>", "target a specific registered wiki").action(
|
|
3911
|
-
async (page, topicsArg, opts) => {
|
|
3912
|
-
await autoRegisterIfNeeded(process.cwd());
|
|
3913
|
-
const resolvedTopics = opts.stdin === true ? [page, ...topicsArg].filter(
|
|
3914
|
-
(t) => typeof t === "string" && t.length > 0
|
|
3915
|
-
) : topicsArg;
|
|
3916
|
-
const result = await runTag({
|
|
3917
|
-
cwd: process.cwd(),
|
|
3918
|
-
page: opts.stdin === true ? void 0 : page,
|
|
3919
|
-
topics: resolvedTopics,
|
|
3920
|
-
stdin: opts.stdin,
|
|
3921
|
-
stdinInput: opts.stdin === true ? await readStdin() : void 0,
|
|
3922
|
-
wiki: opts.wiki
|
|
3923
|
-
});
|
|
3924
|
-
emit(result);
|
|
3925
|
-
}
|
|
3926
|
-
);
|
|
3927
|
-
program.command("untag <page> <topic>").description("remove a topic from a page's frontmatter").option("--wiki <name>", "target a specific registered wiki").action(
|
|
3928
|
-
async (page, topic, opts) => {
|
|
3929
|
-
await autoRegisterIfNeeded(process.cwd());
|
|
3930
|
-
const result = await runUntag({
|
|
3931
|
-
cwd: process.cwd(),
|
|
3932
|
-
page,
|
|
3933
|
-
topic,
|
|
3934
|
-
wiki: opts.wiki
|
|
3935
|
-
});
|
|
3936
|
-
emit(result);
|
|
3937
|
-
}
|
|
3938
|
-
);
|
|
3939
4389
|
program.command("bootstrap").description(
|
|
3940
|
-
"
|
|
4390
|
+
"scaffold a wiki in this repo via an AI agent (requires ANTHROPIC_API_KEY or Claude subscription)"
|
|
3941
4391
|
).option("--quiet", "suppress per-tool streaming; print only the final line").option("--model <model>", "override the agent model").option(
|
|
3942
4392
|
"--force",
|
|
3943
4393
|
"overwrite an existing populated wiki (default: refuse)"
|
|
@@ -3953,7 +4403,7 @@ async function run(argv) {
|
|
|
3953
4403
|
}
|
|
3954
4404
|
);
|
|
3955
4405
|
program.command("capture [transcript]").description(
|
|
3956
|
-
"
|
|
4406
|
+
"run the writer/reviewer pipeline on a session (usually automatic)"
|
|
3957
4407
|
).option("--session <id>", "target a specific session by ID").option(
|
|
3958
4408
|
"--quiet",
|
|
3959
4409
|
"suppress per-tool streaming; print only the final summary"
|
|
@@ -3970,9 +4420,7 @@ async function run(argv) {
|
|
|
3970
4420
|
emit(result);
|
|
3971
4421
|
}
|
|
3972
4422
|
);
|
|
3973
|
-
const hook = program.command("hook").description(
|
|
3974
|
-
"install, uninstall, or inspect the SessionEnd hook in ~/.claude/settings.json"
|
|
3975
|
-
);
|
|
4423
|
+
const hook = program.command("hook").description("manage the SessionEnd auto-capture hook");
|
|
3976
4424
|
hook.command("install").description("add a SessionEnd entry that runs 'almanac capture' on session end").action(async () => {
|
|
3977
4425
|
const result = await runHookInstall();
|
|
3978
4426
|
emit(result);
|
|
@@ -3985,23 +4433,165 @@ async function run(argv) {
|
|
|
3985
4433
|
const result = await runHookStatus();
|
|
3986
4434
|
emit(result);
|
|
3987
4435
|
});
|
|
3988
|
-
program.command("
|
|
4436
|
+
program.command("reindex").description("force a full rebuild of .almanac/index.db").option("--wiki <name>", "target a specific registered wiki").action(async (opts) => {
|
|
4437
|
+
await autoRegisterIfNeeded(process.cwd());
|
|
4438
|
+
const result = await runReindex({
|
|
4439
|
+
cwd: process.cwd(),
|
|
4440
|
+
wiki: opts.wiki
|
|
4441
|
+
});
|
|
4442
|
+
process.stdout.write(result.stdout);
|
|
4443
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
4444
|
+
});
|
|
4445
|
+
program.command("setup").description("install the hook + CLAUDE.md guides (bare codealmanac alias)").option("-y, --yes", "skip prompts; install everything").option("--skip-hook", "opt out of the SessionEnd hook").option("--skip-guides", "opt out of the CLAUDE.md guides").action(
|
|
3989
4446
|
async (opts) => {
|
|
3990
|
-
await
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
4447
|
+
const result = await runSetup({
|
|
4448
|
+
yes: opts.yes,
|
|
4449
|
+
skipHook: opts.skipHook,
|
|
4450
|
+
skipGuides: opts.skipGuides
|
|
4451
|
+
});
|
|
4452
|
+
emit(result);
|
|
4453
|
+
}
|
|
4454
|
+
);
|
|
4455
|
+
program.command("uninstall").description("remove the hook + guides + import line").option("-y, --yes", "skip confirmations; remove everything").option("--keep-hook", "leave the hook alone").option("--keep-guides", "leave the guides + CLAUDE.md import alone").action(
|
|
4456
|
+
async (opts) => {
|
|
4457
|
+
const result = await runUninstall({
|
|
4458
|
+
yes: opts.yes,
|
|
4459
|
+
keepHook: opts.keepHook,
|
|
4460
|
+
keepGuides: opts.keepGuides
|
|
3999
4461
|
});
|
|
4000
4462
|
emit(result);
|
|
4001
4463
|
}
|
|
4002
4464
|
);
|
|
4465
|
+
configureGroupedHelp(program);
|
|
4003
4466
|
await program.parseAsync(argv);
|
|
4004
4467
|
}
|
|
4468
|
+
var HELP_GROUPS = [
|
|
4469
|
+
{
|
|
4470
|
+
title: "Query",
|
|
4471
|
+
commands: ["search", "show", "health", "list"]
|
|
4472
|
+
},
|
|
4473
|
+
{
|
|
4474
|
+
title: "Edit",
|
|
4475
|
+
commands: ["tag", "untag", "topics"]
|
|
4476
|
+
},
|
|
4477
|
+
{
|
|
4478
|
+
title: "Wiki lifecycle",
|
|
4479
|
+
commands: ["bootstrap", "capture", "hook", "reindex"]
|
|
4480
|
+
},
|
|
4481
|
+
{
|
|
4482
|
+
title: "Setup",
|
|
4483
|
+
commands: ["setup", "uninstall"]
|
|
4484
|
+
}
|
|
4485
|
+
];
|
|
4486
|
+
function configureGroupedHelp(program) {
|
|
4487
|
+
program.configureHelp({
|
|
4488
|
+
formatHelp(cmd, helper) {
|
|
4489
|
+
if (cmd.parent !== null) {
|
|
4490
|
+
return renderDefault(cmd, helper);
|
|
4491
|
+
}
|
|
4492
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
4493
|
+
const helpWidth = helper.helpWidth ?? process.stdout.columns ?? 80;
|
|
4494
|
+
const itemSepWidth = 2;
|
|
4495
|
+
const out = [];
|
|
4496
|
+
out.push(`Usage: ${helper.commandUsage(cmd)}
|
|
4497
|
+
`);
|
|
4498
|
+
const description = helper.commandDescription(cmd);
|
|
4499
|
+
if (description.length > 0) {
|
|
4500
|
+
out.push(
|
|
4501
|
+
helper.wrap(description, helpWidth, 0) + "\n"
|
|
4502
|
+
);
|
|
4503
|
+
}
|
|
4504
|
+
const optionList = helper.visibleOptions(cmd).map(
|
|
4505
|
+
(o) => `${helper.optionTerm(o)}${" ".repeat(Math.max(0, termWidth - helper.optionTerm(o).length) + itemSepWidth)}${helper.optionDescription(o)}`
|
|
4506
|
+
);
|
|
4507
|
+
if (optionList.length > 0) {
|
|
4508
|
+
out.push("Options:");
|
|
4509
|
+
for (const l of optionList) out.push(` ${l}`);
|
|
4510
|
+
out.push("");
|
|
4511
|
+
}
|
|
4512
|
+
const visible = helper.visibleCommands(cmd);
|
|
4513
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4514
|
+
for (const c of visible) byName.set(c.name(), c);
|
|
4515
|
+
for (const group of HELP_GROUPS) {
|
|
4516
|
+
const members = group.commands.map((n) => byName.get(n)).filter((c) => c !== void 0);
|
|
4517
|
+
if (members.length === 0) continue;
|
|
4518
|
+
out.push(`${group.title}:`);
|
|
4519
|
+
for (const c of members) {
|
|
4520
|
+
const term = helper.subcommandTerm(c);
|
|
4521
|
+
const desc = helper.subcommandDescription(c);
|
|
4522
|
+
const padding = Math.max(
|
|
4523
|
+
0,
|
|
4524
|
+
termWidth - term.length + itemSepWidth
|
|
4525
|
+
);
|
|
4526
|
+
out.push(` ${term}${" ".repeat(padding)}${desc}`);
|
|
4527
|
+
byName.delete(c.name());
|
|
4528
|
+
}
|
|
4529
|
+
out.push("");
|
|
4530
|
+
}
|
|
4531
|
+
if (byName.size > 0) {
|
|
4532
|
+
out.push("Other:");
|
|
4533
|
+
for (const c of byName.values()) {
|
|
4534
|
+
const term = helper.subcommandTerm(c);
|
|
4535
|
+
const desc = helper.subcommandDescription(c);
|
|
4536
|
+
const padding = Math.max(
|
|
4537
|
+
0,
|
|
4538
|
+
termWidth - term.length + itemSepWidth
|
|
4539
|
+
);
|
|
4540
|
+
out.push(` ${term}${" ".repeat(padding)}${desc}`);
|
|
4541
|
+
}
|
|
4542
|
+
out.push("");
|
|
4543
|
+
}
|
|
4544
|
+
return out.join("\n");
|
|
4545
|
+
}
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4548
|
+
function renderDefault(cmd, helper) {
|
|
4549
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
4550
|
+
const helpWidth = helper.helpWidth ?? process.stdout.columns ?? 80;
|
|
4551
|
+
const itemSepWidth = 2;
|
|
4552
|
+
const lines = [`Usage: ${helper.commandUsage(cmd)}
|
|
4553
|
+
`];
|
|
4554
|
+
const description = helper.commandDescription(cmd);
|
|
4555
|
+
if (description.length > 0) {
|
|
4556
|
+
lines.push(helper.wrap(description, helpWidth, 0) + "\n");
|
|
4557
|
+
}
|
|
4558
|
+
const args = helper.visibleArguments(cmd).map(
|
|
4559
|
+
(a) => `${helper.argumentTerm(a)}${" ".repeat(Math.max(0, termWidth - helper.argumentTerm(a).length) + itemSepWidth)}${helper.argumentDescription(a)}`
|
|
4560
|
+
);
|
|
4561
|
+
if (args.length > 0) {
|
|
4562
|
+
lines.push("Arguments:");
|
|
4563
|
+
for (const a of args) lines.push(` ${a}`);
|
|
4564
|
+
lines.push("");
|
|
4565
|
+
}
|
|
4566
|
+
const opts = helper.visibleOptions(cmd).map(
|
|
4567
|
+
(o) => `${helper.optionTerm(o)}${" ".repeat(Math.max(0, termWidth - helper.optionTerm(o).length) + itemSepWidth)}${helper.optionDescription(o)}`
|
|
4568
|
+
);
|
|
4569
|
+
if (opts.length > 0) {
|
|
4570
|
+
lines.push("Options:");
|
|
4571
|
+
for (const o of opts) lines.push(` ${o}`);
|
|
4572
|
+
lines.push("");
|
|
4573
|
+
}
|
|
4574
|
+
const subs = helper.visibleCommands(cmd).map(
|
|
4575
|
+
(c) => `${helper.subcommandTerm(c)}${" ".repeat(Math.max(0, termWidth - helper.subcommandTerm(c).length) + itemSepWidth)}${helper.subcommandDescription(c)}`
|
|
4576
|
+
);
|
|
4577
|
+
if (subs.length > 0) {
|
|
4578
|
+
lines.push("Commands:");
|
|
4579
|
+
for (const s of subs) lines.push(` ${s}`);
|
|
4580
|
+
lines.push("");
|
|
4581
|
+
}
|
|
4582
|
+
return lines.join("\n");
|
|
4583
|
+
}
|
|
4584
|
+
function readPackageVersion() {
|
|
4585
|
+
try {
|
|
4586
|
+
const require2 = createRequire3(import.meta.url);
|
|
4587
|
+
const pkg = require2("../package.json");
|
|
4588
|
+
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
4589
|
+
return pkg.version;
|
|
4590
|
+
}
|
|
4591
|
+
} catch {
|
|
4592
|
+
}
|
|
4593
|
+
return "unknown";
|
|
4594
|
+
}
|
|
4005
4595
|
function emit(result) {
|
|
4006
4596
|
if (result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
4007
4597
|
if (result.stdout.length > 0) process.stdout.write(result.stdout);
|