caveat-cli 0.3.0 → 0.5.0

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/index.js CHANGED
@@ -12976,7 +12976,7 @@ import { Command } from "commander";
12976
12976
 
12977
12977
  // src/context.ts
12978
12978
  import { homedir } from "node:os";
12979
- import { join as join7 } from "node:path";
12979
+ import { join as join9 } from "node:path";
12980
12980
 
12981
12981
  // ../../packages/core/dist/db.js
12982
12982
  import { DatabaseSync } from "node:sqlite";
@@ -16139,7 +16139,7 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
16139
16139
 
16140
16140
  // ../../packages/core/dist/config.js
16141
16141
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
16142
- var SHARED_REPO_URL = "https://github.com/kitepon-rgb/caveats-quo";
16142
+ var SHARED_REPO_URL = "https://github.com/kitepon-rgb/Caveat";
16143
16143
  var DEFAULT_CONFIG = {
16144
16144
  knowledgeRepo: "own",
16145
16145
  semverKeys: ["driver", "cuda", "node"],
@@ -21228,15 +21228,246 @@ function stopReminderText() {
21228
21228
  return [
21229
21229
  "[caveat] \u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u5916\u90E8\u4ED5\u69D8\u306E\u7F60\uFF08GPU/\u30C9\u30E9\u30A4\u30D0/\u30C4\u30FC\u30EB\u30D0\u30FC\u30B8\u30E7\u30F3/IDE/\u518D\u73FE\u6027\uFF09\u3092\u8E0F\u307F\u3001\u6B21\u56DE\u306E\u81EA\u5206\u306E\u8ABF\u67FB\u304C\u77ED\u7E2E\u3067\u304D\u305D\u3046\u306A\u3089 mcp__caveat__caveat_record \u3067\u767B\u9332\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21230
21230
  "\u89E3\u6C7A\u3057\u305F\u30B1\u30FC\u30B9\u3060\u3051\u3067\u306A\u304F\u3001\u300C\u73FE\u72B6\u306E\u5236\u7D04\u3067\u306F\u4E0D\u53EF\u80FD\u3068\u5224\u5B9A\u3057\u305F\u300D\u7D50\u8AD6\u3082 outcome: impossible \u3068\u3057\u3066\u8A18\u9332\u5BFE\u8C61\u3002\u6B21\u56DE\u540C\u3058 3 \u6642\u9593\u3092\u6EB6\u304B\u3055\u306A\u3044\u305F\u3081\u306B\u3002",
21231
- "\u65E2\u306B\u540C\u7B49\u306E caveat \u304C\u3042\u308B\u5834\u5408\u306F caveat_update \u3067\u4E0A\u66F8\u304D\u3001\u307E\u305F\u306F last_verified \u3060\u3051\u66F4\u65B0\u3002"
21231
+ "\u65E2\u306B\u540C\u7B49\u306E caveat \u304C\u3042\u308B\u5834\u5408\u306F caveat_update \u3067\u4E0A\u66F8\u304D\u3001\u307E\u305F\u306F last_verified \u3060\u3051\u66F4\u65B0\u3002",
21232
+ "\u8A18\u9332\u3057\u305F\u7F60\u304C\u4ED6\u306E\u30E6\u30FC\u30B6\u306B\u3082\u518D\u5229\u7528\u4FA1\u5024\u304C\u3042\u308B\u306A\u3089 mcp__caveat__caveat_push \u3067\u5171\u6709 DB \u306B PR \u3092\u6295\u3052\u308B\u3053\u3068\u3082\u691C\u8A0E\uFF08\u500B\u4EBA\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u56FA\u6709\u306E\u7269\u306F push \u3057\u306A\u3044\uFF09\u3002"
21232
21233
  ].join("\n");
21233
21234
  }
21234
21235
 
21236
+ // ../../packages/core/dist/push.js
21237
+ import { spawnSync } from "node:child_process";
21238
+ import {
21239
+ copyFileSync,
21240
+ existsSync as existsSync7,
21241
+ mkdirSync as mkdirSync3,
21242
+ readdirSync as readdirSync4,
21243
+ readFileSync as readFileSync5
21244
+ } from "node:fs";
21245
+ import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
21246
+ async function pushEntry(opts) {
21247
+ if (!checkGhAvailable()) {
21248
+ return {
21249
+ status: "gh-missing",
21250
+ detail: "gh (GitHub CLI) is required. Install: https://cli.github.com/"
21251
+ };
21252
+ }
21253
+ if (!checkGhAuthed()) {
21254
+ return {
21255
+ status: "gh-unauthed",
21256
+ detail: "gh is not authenticated. Run `gh auth login`."
21257
+ };
21258
+ }
21259
+ const validation = validateCommunityUrl(opts.sharedRepoUrl);
21260
+ if (!validation.valid) {
21261
+ return { status: "failed", detail: `sharedRepo URL invalid: ${validation.reason}` };
21262
+ }
21263
+ const [sharedOwner, sharedName] = parseOwnerRepo(opts.sharedRepoUrl);
21264
+ if (!sharedOwner || !sharedName) {
21265
+ return { status: "failed", detail: `cannot parse owner/repo from ${opts.sharedRepoUrl}` };
21266
+ }
21267
+ const owned = findOwnedEntry(opts.entriesDir, opts.id);
21268
+ if (!owned) {
21269
+ return {
21270
+ status: "not-found",
21271
+ detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
21272
+ };
21273
+ }
21274
+ const ghUser = resolveGhUser();
21275
+ if (!ghUser) {
21276
+ return { status: "failed", detail: "gh api user failed" };
21277
+ }
21278
+ if (opts.dryRun) {
21279
+ return {
21280
+ status: "dry-run",
21281
+ plannedSteps: [
21282
+ `fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`,
21283
+ `clone fork \u2192 ${forkStagingDir(opts.caveatHome)}`,
21284
+ `copy entry \u2192 entries/${owned.relPath}`,
21285
+ "commit, push branch, open PR against main"
21286
+ ]
21287
+ };
21288
+ }
21289
+ try {
21290
+ ensureFork(sharedOwner, sharedName);
21291
+ const stagingDir = forkStagingDir(opts.caveatHome);
21292
+ ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
21293
+ const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
21294
+ run("git", ["checkout", "-b", branch], { cwd: stagingDir });
21295
+ const destPath = join7(stagingDir, "entries", owned.relPath);
21296
+ mkdirSync3(dirname4(destPath), { recursive: true });
21297
+ copyFileSync(owned.absPath, destPath);
21298
+ run("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21299
+ const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
21300
+ const verb = isUpdate ? "update" : "add";
21301
+ const commitMsg = `${verb}: ${owned.title}`;
21302
+ run("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21303
+ run("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21304
+ const prTitle = `${verb}: ${owned.title}`;
21305
+ const prBody = [
21306
+ `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
21307
+ "",
21308
+ `- entry path: \`entries/${owned.relPath}\``,
21309
+ `- action: ${verb}`,
21310
+ "",
21311
+ "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
21312
+ ].join("\n");
21313
+ const pr = spawnSync(
21314
+ "gh",
21315
+ [
21316
+ "pr",
21317
+ "create",
21318
+ "--repo",
21319
+ `${sharedOwner}/${sharedName}`,
21320
+ "--base",
21321
+ "main",
21322
+ "--head",
21323
+ `${ghUser}:${branch}`,
21324
+ "--title",
21325
+ prTitle,
21326
+ "--body",
21327
+ prBody
21328
+ ],
21329
+ { cwd: stagingDir, encoding: "utf-8", shell: true }
21330
+ );
21331
+ if (pr.status !== 0) {
21332
+ return {
21333
+ status: "failed",
21334
+ detail: (typeof pr.stderr === "string" ? pr.stderr : String(pr.stderr ?? "")).trim() || "gh pr create failed"
21335
+ };
21336
+ }
21337
+ const prUrl = (typeof pr.stdout === "string" ? pr.stdout : String(pr.stdout ?? "")).trim();
21338
+ return { status: "ok", prUrl };
21339
+ } catch (err) {
21340
+ return {
21341
+ status: "failed",
21342
+ detail: err instanceof Error ? err.message : String(err)
21343
+ };
21344
+ }
21345
+ }
21346
+ function checkGhAvailable() {
21347
+ const r2 = spawnSync("gh", ["--version"], { encoding: "utf-8", shell: true });
21348
+ return r2.status === 0;
21349
+ }
21350
+ function checkGhAuthed() {
21351
+ const r2 = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", shell: true });
21352
+ return r2.status === 0;
21353
+ }
21354
+ function resolveGhUser() {
21355
+ const r2 = spawnSync("gh", ["api", "user", "--jq", ".login"], {
21356
+ encoding: "utf-8",
21357
+ shell: true
21358
+ });
21359
+ if (r2.status !== 0) return void 0;
21360
+ return typeof r2.stdout === "string" ? r2.stdout.trim() : "";
21361
+ }
21362
+ function parseOwnerRepo(url) {
21363
+ const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
21364
+ if (!m) return [void 0, void 0];
21365
+ return [m[1], m[2]];
21366
+ }
21367
+ function findOwnedEntry(entriesDir, id) {
21368
+ if (!existsSync7(entriesDir)) return void 0;
21369
+ for (const categoryDirent of readdirSync4(entriesDir, { withFileTypes: true })) {
21370
+ if (!categoryDirent.isDirectory()) continue;
21371
+ const categoryDir = join7(entriesDir, categoryDirent.name);
21372
+ for (const fileDirent of readdirSync4(categoryDir, { withFileTypes: true })) {
21373
+ if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
21374
+ const absPath = join7(categoryDir, fileDirent.name);
21375
+ const raw2 = readFileSync5(absPath, "utf-8");
21376
+ try {
21377
+ const parsed = parseMarkdown(raw2);
21378
+ if (parsed.frontmatter.id === id) {
21379
+ const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
21380
+ return { id, title: parsed.frontmatter.title, absPath, relPath };
21381
+ }
21382
+ } catch {
21383
+ }
21384
+ }
21385
+ }
21386
+ return void 0;
21387
+ }
21388
+ function forkStagingDir(caveatHome) {
21389
+ return join7(caveatHome, "push-fork");
21390
+ }
21391
+ function ensureFork(owner, repo) {
21392
+ spawnSync(
21393
+ "gh",
21394
+ ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"],
21395
+ { encoding: "utf-8", shell: true }
21396
+ );
21397
+ }
21398
+ function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
21399
+ if (!existsSync7(stagingDir)) {
21400
+ mkdirSync3(dirname4(stagingDir), { recursive: true });
21401
+ const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
21402
+ run("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21403
+ run(
21404
+ "git",
21405
+ ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
21406
+ { cwd: stagingDir }
21407
+ );
21408
+ } else {
21409
+ run("git", ["checkout", "main"], { cwd: stagingDir });
21410
+ run("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21411
+ run("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21412
+ run("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
21413
+ }
21414
+ }
21415
+ function upstreamAlreadyHas(stagingDir, relFromRoot) {
21416
+ return existsSync7(join7(stagingDir, relFromRoot));
21417
+ }
21418
+ function run(command, args, opts = {}) {
21419
+ const r2 = spawnSync(command, args, {
21420
+ encoding: "utf-8",
21421
+ cwd: opts.cwd,
21422
+ shell: true
21423
+ });
21424
+ if (r2.status !== 0) {
21425
+ const stderr = typeof r2.stderr === "string" ? r2.stderr : String(r2.stderr ?? "");
21426
+ const stdout = typeof r2.stdout === "string" ? r2.stdout : String(r2.stdout ?? "");
21427
+ throw new Error(`${command} ${args.join(" ")} failed: ${(stderr || stdout || "unknown").trim()}`);
21428
+ }
21429
+ }
21430
+
21431
+ // ../../packages/core/dist/pullShared.js
21432
+ import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
21433
+ import { join as join8 } from "node:path";
21434
+ async function pullShared(opts) {
21435
+ const result = { pulled: [], indexed: [] };
21436
+ if (!existsSync8(opts.communityDir)) {
21437
+ return result;
21438
+ }
21439
+ const pulls = await communityPull({
21440
+ communityDir: opts.communityDir,
21441
+ logger: opts.logger
21442
+ });
21443
+ for (const p2 of pulls) {
21444
+ result.pulled.push({
21445
+ handle: p2.handle,
21446
+ status: p2.status,
21447
+ message: p2.message
21448
+ });
21449
+ }
21450
+ rebuildAll(opts.db);
21451
+ if (existsSync8(opts.entriesDir)) {
21452
+ const own = scanSource({ db: opts.db, source: "own", entriesRoot: opts.entriesDir });
21453
+ result.indexed.push({ source: "own", ...own });
21454
+ }
21455
+ for (const entry of readdirSync5(opts.communityDir, { withFileTypes: true })) {
21456
+ if (!entry.isDirectory()) continue;
21457
+ const source = `community/${entry.name}`;
21458
+ const root = join8(opts.communityDir, entry.name, "entries");
21459
+ if (!existsSync8(root)) continue;
21460
+ const scan = scanSource({ db: opts.db, source, entriesRoot: root });
21461
+ result.indexed.push({ source, ...scan });
21462
+ }
21463
+ return result;
21464
+ }
21465
+
21235
21466
  // src/context.ts
21236
21467
  function buildContext(logger, overrides = {}) {
21237
21468
  const userHome = overrides.userHome ?? homedir();
21238
21469
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
21239
- const userConfigPath = join7(userHome, ".caveatrc.json");
21470
+ const userConfigPath = join9(userHome, ".caveatrc.json");
21240
21471
  const config3 = loadConfig(userConfigPath);
21241
21472
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
21242
21473
  return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
@@ -21253,13 +21484,13 @@ var stdoutLogger = {
21253
21484
  };
21254
21485
 
21255
21486
  // src/commands/init.ts
21256
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
21257
- import { join as join9 } from "node:path";
21487
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
21488
+ import { join as join11 } from "node:path";
21258
21489
 
21259
21490
  // src/claudeInstall.ts
21260
- import { spawnSync } from "node:child_process";
21261
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
21262
- import { dirname as dirname4, join as join8 } from "node:path";
21491
+ import { spawnSync as spawnSync2 } from "node:child_process";
21492
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21493
+ import { dirname as dirname5, join as join10 } from "node:path";
21263
21494
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
21264
21495
  var EVENT_STOP = "Stop";
21265
21496
  function quote(p2) {
@@ -21293,16 +21524,16 @@ function removeHook(settings, event, command) {
21293
21524
  return true;
21294
21525
  }
21295
21526
  function readSettings(path) {
21296
- if (!existsSync7(path)) return {};
21297
- return JSON.parse(readFileSync5(path, "utf-8"));
21527
+ if (!existsSync9(path)) return {};
21528
+ return JSON.parse(readFileSync6(path, "utf-8"));
21298
21529
  }
21299
21530
  function writeSettings(path, settings) {
21300
- const dir = dirname4(path);
21301
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
21531
+ const dir = dirname5(path);
21532
+ if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
21302
21533
  let backupPath = "";
21303
- if (existsSync7(path)) {
21534
+ if (existsSync9(path)) {
21304
21535
  backupPath = `${path}.caveat-backup-${Date.now()}`;
21305
- copyFileSync(path, backupPath);
21536
+ copyFileSync2(path, backupPath);
21306
21537
  }
21307
21538
  writeFileSync4(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
21308
21539
  return backupPath;
@@ -21313,7 +21544,7 @@ function shellQuote(s) {
21313
21544
  }
21314
21545
  function runClaude(args) {
21315
21546
  const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
21316
- return spawnSync(line, { shell: true, encoding: "utf-8" });
21547
+ return spawnSync2(line, { shell: true, encoding: "utf-8" });
21317
21548
  }
21318
21549
  function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
21319
21550
  const args = mcpArgs(cliScriptPath);
@@ -21363,7 +21594,7 @@ function unregisterMcp(dryRun, logger) {
21363
21594
  return { action: "skipped", detail: "not registered or removal failed" };
21364
21595
  }
21365
21596
  function installClaudeIntegration(opts) {
21366
- const settingsPath = join8(opts.claudeDir, "settings.json");
21597
+ const settingsPath = join10(opts.claudeDir, "settings.json");
21367
21598
  const settings = readSettings(settingsPath);
21368
21599
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21369
21600
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21382,7 +21613,7 @@ function installClaudeIntegration(opts) {
21382
21613
  return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
21383
21614
  }
21384
21615
  function uninstallClaudeIntegration(opts) {
21385
- const settingsPath = join8(opts.claudeDir, "settings.json");
21616
+ const settingsPath = join10(opts.claudeDir, "settings.json");
21386
21617
  const settings = readSettings(settingsPath);
21387
21618
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21388
21619
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21422,15 +21653,15 @@ var KNOWLEDGE_GITIGNORE = [
21422
21653
  async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
21423
21654
  ensureUserConfig(ctx.userConfigPath);
21424
21655
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
21425
- if (!existsSync8(ctx.paths.knowledgeRepo)) {
21426
- mkdirSync4(ctx.paths.knowledgeRepo, { recursive: true });
21427
- mkdirSync4(ctx.paths.entriesDir, { recursive: true });
21656
+ if (!existsSync10(ctx.paths.knowledgeRepo)) {
21657
+ mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
21658
+ mkdirSync5(ctx.paths.entriesDir, { recursive: true });
21428
21659
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
21429
21660
  } else {
21430
21661
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
21431
21662
  }
21432
- const gitignorePath = join9(ctx.paths.knowledgeRepo, ".gitignore");
21433
- if (!existsSync8(gitignorePath)) {
21663
+ const gitignorePath = join11(ctx.paths.knowledgeRepo, ".gitignore");
21664
+ if (!existsSync10(gitignorePath)) {
21434
21665
  writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
21435
21666
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
21436
21667
  }
@@ -21457,7 +21688,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21457
21688
  return;
21458
21689
  }
21459
21690
  const result = installClaudeIntegration({
21460
- claudeDir: join9(ctx.userHome, ".claude"),
21691
+ claudeDir: join11(ctx.userHome, ".claude"),
21461
21692
  cliScriptPath,
21462
21693
  nodePath: process.execPath,
21463
21694
  dryRun: opts.dryRun,
@@ -21475,10 +21706,10 @@ async function subscribeSharedRepo(ctx, db) {
21475
21706
  return;
21476
21707
  }
21477
21708
  const handle = validation.handle;
21478
- const target = join9(ctx.paths.communityDir, handle);
21479
- if (!existsSync8(target)) {
21480
- if (!existsSync8(ctx.paths.communityDir)) {
21481
- mkdirSync4(ctx.paths.communityDir, { recursive: true });
21709
+ const target = join11(ctx.paths.communityDir, handle);
21710
+ if (!existsSync10(target)) {
21711
+ if (!existsSync10(ctx.paths.communityDir)) {
21712
+ mkdirSync5(ctx.paths.communityDir, { recursive: true });
21482
21713
  }
21483
21714
  try {
21484
21715
  await communityAdd({
@@ -21496,16 +21727,16 @@ async function subscribeSharedRepo(ctx, db) {
21496
21727
  } else {
21497
21728
  ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
21498
21729
  }
21499
- if (existsSync8(ctx.paths.communityDir)) {
21730
+ if (existsSync10(ctx.paths.communityDir)) {
21500
21731
  rebuildAll(db);
21501
- if (existsSync8(ctx.paths.entriesDir)) {
21732
+ if (existsSync10(ctx.paths.entriesDir)) {
21502
21733
  scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21503
21734
  }
21504
- for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
21735
+ for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
21505
21736
  if (!entry.isDirectory()) continue;
21506
21737
  const source = `community/${entry.name}`;
21507
- const root = join9(ctx.paths.communityDir, entry.name, "entries");
21508
- if (!existsSync8(root)) continue;
21738
+ const root = join11(ctx.paths.communityDir, entry.name, "entries");
21739
+ if (!existsSync10(root)) continue;
21509
21740
  const result = scanSource({ db, source, entriesRoot: root });
21510
21741
  ctx.logger.info(`indexed ${source}: +${result.added}`);
21511
21742
  }
@@ -21518,7 +21749,7 @@ function runUninstall(ctx, opts) {
21518
21749
  process.exit(1);
21519
21750
  }
21520
21751
  const result = uninstallClaudeIntegration({
21521
- claudeDir: join9(ctx.userHome, ".claude"),
21752
+ claudeDir: join11(ctx.userHome, ".claude"),
21522
21753
  cliScriptPath,
21523
21754
  nodePath: process.execPath,
21524
21755
  dryRun: opts.dryRun,
@@ -21555,29 +21786,29 @@ function reportInstallResult(ctx, result, dryRun) {
21555
21786
  }
21556
21787
 
21557
21788
  // src/commands/indexCmd.ts
21558
- import { existsSync as existsSync9, readdirSync as readdirSync5, mkdirSync as mkdirSync5 } from "node:fs";
21559
- import { dirname as dirname5, join as join10 } from "node:path";
21789
+ import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
21790
+ import { dirname as dirname6, join as join12 } from "node:path";
21560
21791
  function runIndex(ctx, opts) {
21561
- const dbDir = dirname5(ctx.paths.dbPath);
21562
- if (!existsSync9(dbDir)) mkdirSync5(dbDir, { recursive: true });
21792
+ const dbDir = dirname6(ctx.paths.dbPath);
21793
+ if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
21563
21794
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21564
21795
  try {
21565
21796
  if (opts.full) {
21566
21797
  ctx.logger.info("full rebuild: DELETE FROM entries");
21567
21798
  rebuildAll(db);
21568
21799
  }
21569
- if (existsSync9(ctx.paths.entriesDir)) {
21800
+ if (existsSync11(ctx.paths.entriesDir)) {
21570
21801
  const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21571
21802
  ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
21572
21803
  } else {
21573
21804
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
21574
21805
  }
21575
- if (existsSync9(ctx.paths.communityDir)) {
21576
- for (const entry of readdirSync5(ctx.paths.communityDir, { withFileTypes: true })) {
21806
+ if (existsSync11(ctx.paths.communityDir)) {
21807
+ for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
21577
21808
  if (!entry.isDirectory()) continue;
21578
21809
  const source = `community/${entry.name}`;
21579
- const root = join10(ctx.paths.communityDir, entry.name, "entries");
21580
- if (!existsSync9(root)) continue;
21810
+ const root = join12(ctx.paths.communityDir, entry.name, "entries");
21811
+ if (!existsSync11(root)) continue;
21581
21812
  const result = scanSource({ db, source, entriesRoot: root });
21582
21813
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
21583
21814
  }
@@ -22351,11 +22582,11 @@ var serve = (options2, listeningListener) => {
22351
22582
 
22352
22583
  // ../web/dist/context.js
22353
22584
  import { homedir as homedir2 } from "node:os";
22354
- import { join as join11 } from "node:path";
22585
+ import { join as join13 } from "node:path";
22355
22586
  function buildWebContext(overrides = {}) {
22356
22587
  const userHome = overrides.userHome ?? homedir2();
22357
22588
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22358
- const userConfigPath = join11(userHome, ".caveatrc.json");
22589
+ const userConfigPath = join13(userHome, ".caveatrc.json");
22359
22590
  const logger = overrides.logger ?? stderrLogger;
22360
22591
  const config3 = loadConfig(userConfigPath);
22361
22592
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -30082,14 +30313,14 @@ function createDetailRoute(ctx) {
30082
30313
  }
30083
30314
 
30084
30315
  // ../web/dist/routes/community.js
30085
- import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
30086
- import { join as join12 } from "node:path";
30316
+ import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
30317
+ import { join as join14 } from "node:path";
30087
30318
  function listCommunity(communityDir, db) {
30088
- if (!existsSync10(communityDir)) return [];
30319
+ if (!existsSync12(communityDir)) return [];
30089
30320
  const handles = [];
30090
- for (const entry of readdirSync6(communityDir, { withFileTypes: true })) {
30321
+ for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
30091
30322
  if (!entry.isDirectory()) continue;
30092
- const handlePath = join12(communityDir, entry.name);
30323
+ const handlePath = join14(communityDir, entry.name);
30093
30324
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
30094
30325
  handles.push({
30095
30326
  handle: entry.name,
@@ -44387,11 +44618,11 @@ var StdioServerTransport = class {
44387
44618
 
44388
44619
  // ../mcp/dist/context.js
44389
44620
  import { homedir as homedir3 } from "node:os";
44390
- import { join as join13 } from "node:path";
44621
+ import { join as join15 } from "node:path";
44391
44622
  function buildMcpContext(overrides = {}) {
44392
44623
  const userHome = overrides.userHome ?? homedir3();
44393
44624
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44394
- const userConfigPath = join13(userHome, ".caveatrc.json");
44625
+ const userConfigPath = join15(userHome, ".caveatrc.json");
44395
44626
  const logger = overrides.logger ?? stderrLogger;
44396
44627
  const config3 = loadConfig(userConfigPath);
44397
44628
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -44540,6 +44771,48 @@ function handleIngestResearch(ctx, args) {
44540
44771
  );
44541
44772
  }
44542
44773
 
44774
+ // ../mcp/dist/tools/pull.js
44775
+ var pullInputShape = {};
44776
+ async function handlePull(ctx, _args = {}) {
44777
+ const result = await pullShared({
44778
+ communityDir: ctx.paths.communityDir,
44779
+ entriesDir: ctx.paths.entriesDir,
44780
+ db: ctx.db,
44781
+ logger: ctx.logger
44782
+ });
44783
+ return {
44784
+ pulled: result.pulled,
44785
+ indexed: result.indexed.map((i2) => ({
44786
+ source: i2.source,
44787
+ added: i2.added,
44788
+ updated: i2.updated,
44789
+ deleted: i2.deleted
44790
+ }))
44791
+ };
44792
+ }
44793
+
44794
+ // ../mcp/dist/tools/push.js
44795
+ var pushInputShape = {
44796
+ id: external_exports.string().min(1).describe("Entry id (frontmatter.id) to push to the shared community DB"),
44797
+ dry_run: external_exports.boolean().optional().describe("If true, compute the plan but do not modify GitHub or local git state")
44798
+ };
44799
+ async function handlePush(ctx, args) {
44800
+ const result = await pushEntry({
44801
+ entriesDir: ctx.paths.entriesDir,
44802
+ caveatHome: ctx.caveatHome,
44803
+ sharedRepoUrl: ctx.config.sharedRepo,
44804
+ id: args.id,
44805
+ dryRun: args.dry_run ?? false,
44806
+ logger: ctx.logger
44807
+ });
44808
+ return {
44809
+ status: result.status,
44810
+ detail: result.detail,
44811
+ pr_url: result.prUrl,
44812
+ planned_steps: result.plannedSteps
44813
+ };
44814
+ }
44815
+
44543
44816
  // ../mcp/dist/registerTools.js
44544
44817
  function jsonResult(data) {
44545
44818
  return {
@@ -44615,6 +44888,24 @@ function registerAllTools(server, ctx) {
44615
44888
  },
44616
44889
  async (args) => jsonResult(handleIngestResearch(ctx, args))
44617
44890
  );
44891
+ server.registerTool(
44892
+ "caveat_pull",
44893
+ {
44894
+ title: "caveat_pull",
44895
+ description: "git-pull all subscribed community caveat repos (incl. the shared DB) and re-index. Call this when the user asks if others have documented a similar trap, or at the start of a session that might benefit from fresh external knowledge. Safe and idempotent \u2014 re-running is cheap.",
44896
+ inputSchema: pullInputShape
44897
+ },
44898
+ async (args) => jsonResult(await handlePull(ctx, args))
44899
+ );
44900
+ server.registerTool(
44901
+ "caveat_push",
44902
+ {
44903
+ title: "caveat_push",
44904
+ description: "Contribute a user-owned caveat to the shared community DB via fork + PR. Call this after caveat_record when the entry looks genuinely reusable by others (not a one-off project tie-in, not duplicated by existing community entries). Requires the `gh` CLI on the user's machine; returns status=gh-missing or gh-unauthed when unavailable. Use dry_run=true to preview without touching GitHub.",
44905
+ inputSchema: pushInputShape
44906
+ },
44907
+ async (args) => jsonResult(await handlePush(ctx, args))
44908
+ );
44618
44909
  }
44619
44910
 
44620
44911
  // ../mcp/dist/server.js
@@ -44707,40 +44998,31 @@ async function runHook(name) {
44707
44998
  }
44708
44999
 
44709
45000
  // src/commands/pull.ts
44710
- import { existsSync as existsSync11, readdirSync as readdirSync7 } from "node:fs";
44711
- import { join as join14 } from "node:path";
45001
+ import { existsSync as existsSync13 } from "node:fs";
44712
45002
  async function runPull(ctx) {
44713
- if (!existsSync11(ctx.paths.communityDir)) {
45003
+ if (!existsSync13(ctx.paths.communityDir)) {
44714
45004
  ctx.logger.info(
44715
45005
  "no community repos yet \u2014 did you run `caveat init`? You can also `caveat community add <url>` manually."
44716
45006
  );
44717
45007
  return;
44718
45008
  }
44719
- const results = await communityPull({
44720
- communityDir: ctx.paths.communityDir,
44721
- logger: ctx.logger
44722
- });
44723
- for (const r2 of results) {
44724
- if (r2.status === "ok") {
44725
- ctx.logger.info(`${r2.handle}: pulled`);
44726
- } else {
44727
- ctx.logger.warn(`${r2.handle}: FAILED \u2014 ${r2.message ?? "unknown"}`);
44728
- }
44729
- }
44730
45009
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44731
45010
  try {
44732
- rebuildAll(db);
44733
- if (existsSync11(ctx.paths.entriesDir)) {
44734
- const ownResult = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
44735
- ctx.logger.info(`own: +${ownResult.added}`);
45011
+ const result = await pullShared({
45012
+ communityDir: ctx.paths.communityDir,
45013
+ entriesDir: ctx.paths.entriesDir,
45014
+ db,
45015
+ logger: ctx.logger
45016
+ });
45017
+ for (const p2 of result.pulled) {
45018
+ if (p2.status === "ok") {
45019
+ ctx.logger.info(`${p2.handle}: pulled`);
45020
+ } else {
45021
+ ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
45022
+ }
44736
45023
  }
44737
- for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
44738
- if (!entry.isDirectory()) continue;
44739
- const source = `community/${entry.name}`;
44740
- const root = join14(ctx.paths.communityDir, entry.name, "entries");
44741
- if (!existsSync11(root)) continue;
44742
- const scan = scanSource({ db, source, entriesRoot: root });
44743
- ctx.logger.info(`${source}: +${scan.added}`);
45024
+ for (const i2 of result.indexed) {
45025
+ ctx.logger.info(`${i2.source}: +${i2.added}`);
44744
45026
  }
44745
45027
  } finally {
44746
45028
  db.close();
@@ -44748,220 +45030,44 @@ async function runPull(ctx) {
44748
45030
  }
44749
45031
 
44750
45032
  // src/commands/push.ts
44751
- import { spawnSync as spawnSync2 } from "node:child_process";
44752
- import {
44753
- copyFileSync as copyFileSync2,
44754
- existsSync as existsSync12,
44755
- mkdirSync as mkdirSync6,
44756
- readdirSync as readdirSync8,
44757
- readFileSync as readFileSync6,
44758
- statSync as statSync5
44759
- } from "node:fs";
44760
- import { dirname as dirname6, join as join15, relative as relative2 } from "node:path";
44761
45033
  async function runPush(ctx, opts) {
44762
- const ghOk = checkGhAvailable(ctx);
44763
- if (!ghOk) return;
44764
- const sharedValidation = validateCommunityUrl(ctx.config.sharedRepo);
44765
- if (!sharedValidation.valid) {
44766
- ctx.logger.error(
44767
- `sharedRepo is not a valid GitHub URL: ${ctx.config.sharedRepo} (${sharedValidation.reason})`
44768
- );
44769
- process.exitCode = 1;
44770
- return;
44771
- }
44772
- const [sharedOwner, sharedName] = parseOwnerRepo(ctx.config.sharedRepo);
44773
- if (!sharedOwner || !sharedName) {
44774
- ctx.logger.error(`cannot parse sharedRepo owner/repo: ${ctx.config.sharedRepo}`);
44775
- process.exitCode = 1;
44776
- return;
44777
- }
44778
- const owned = findOwnedEntry(ctx, opts.id);
44779
- if (!owned) {
44780
- ctx.logger.error(
44781
- `entry not found in ${ctx.paths.entriesDir}: id=${opts.id}. List with \`caveat list\`, or write the md file and index first.`
44782
- );
44783
- process.exitCode = 1;
44784
- return;
44785
- }
44786
- const ghUser = resolveGhUser(ctx);
44787
- if (!ghUser) return;
44788
- if (opts.dryRun) {
44789
- ctx.logger.info(`[dry-run] would push entry ${owned.id} (${owned.title})`);
44790
- ctx.logger.info(`[dry-run] 1. fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`);
44791
- ctx.logger.info(`[dry-run] 2. clone fork \u2192 ${forkStagingDir(ctx)}`);
44792
- ctx.logger.info(`[dry-run] 3. copy entry \u2192 entries/${owned.relPath}`);
44793
- ctx.logger.info("[dry-run] 4. commit, push branch, open PR against main");
44794
- return;
44795
- }
44796
- ensureFork(ctx, sharedOwner, sharedName, ghUser);
44797
- const stagingDir = forkStagingDir(ctx);
44798
- ensureStagingClone(ctx, stagingDir, ghUser, sharedName, sharedOwner);
44799
- const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
44800
- run(ctx, "git", ["checkout", "-b", branch], { cwd: stagingDir });
44801
- const destPath = join15(stagingDir, "entries", owned.relPath);
44802
- mkdirSync6(dirname6(destPath), { recursive: true });
44803
- copyFileSync2(owned.absPath, destPath);
44804
- run(ctx, "git", ["add", join15("entries", owned.relPath)], { cwd: stagingDir });
44805
- const verb = existsInUpstream(stagingDir, "entries/" + owned.relPath) ? "update" : "add";
44806
- const commitMsg = `${verb}: ${owned.title}`;
44807
- run(ctx, "git", ["commit", "-m", commitMsg], { cwd: stagingDir });
44808
- run(ctx, "git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
44809
- const prTitle = `${verb}: ${owned.title}`;
44810
- const prBody = [
44811
- `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
44812
- "",
44813
- `- entry path: \`entries/${owned.relPath}\``,
44814
- `- action: ${verb}`,
44815
- "",
44816
- "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
44817
- ].join("\n");
44818
- const prResult = runClaim(
44819
- ctx,
44820
- "gh",
44821
- [
44822
- "pr",
44823
- "create",
44824
- "--repo",
44825
- `${sharedOwner}/${sharedName}`,
44826
- "--base",
44827
- "main",
44828
- "--head",
44829
- `${ghUser}:${branch}`,
44830
- "--title",
44831
- prTitle,
44832
- "--body",
44833
- prBody
44834
- ],
44835
- { cwd: stagingDir }
44836
- );
44837
- if (prResult.status === 0) {
44838
- const url = prResult.stdout.trim();
44839
- ctx.logger.info(`PR opened: ${url}`);
44840
- ctx.logger.info(
44841
- "Once merged, other subscribers will see your entry after their next `caveat pull`."
44842
- );
44843
- } else {
44844
- ctx.logger.error(
44845
- `gh pr create failed: ${(prResult.stderr || prResult.stdout || "").trim()}`
44846
- );
44847
- process.exitCode = 1;
44848
- }
44849
- }
44850
- function checkGhAvailable(ctx) {
44851
- const r2 = spawnSync2("gh", ["--version"], { encoding: "utf-8", shell: true });
44852
- if (r2.status !== 0) {
44853
- ctx.logger.error(
44854
- "gh (GitHub CLI) is required for `caveat push`. Install: https://cli.github.com/"
44855
- );
44856
- process.exitCode = 1;
44857
- return false;
44858
- }
44859
- const auth = spawnSync2("gh", ["auth", "status"], { encoding: "utf-8", shell: true });
44860
- if (auth.status !== 0) {
44861
- ctx.logger.error("gh is not authenticated. Run `gh auth login` first.");
44862
- process.exitCode = 1;
44863
- return false;
44864
- }
44865
- return true;
44866
- }
44867
- function resolveGhUser(ctx) {
44868
- const r2 = spawnSync2("gh", ["api", "user", "--jq", ".login"], {
44869
- encoding: "utf-8",
44870
- shell: true
45034
+ const result = await pushEntry({
45035
+ entriesDir: ctx.paths.entriesDir,
45036
+ caveatHome: ctx.caveatHome,
45037
+ sharedRepoUrl: ctx.config.sharedRepo,
45038
+ id: opts.id,
45039
+ dryRun: opts.dryRun,
45040
+ logger: ctx.logger
44871
45041
  });
44872
- if (r2.status !== 0) {
44873
- ctx.logger.error(`gh api user failed: ${(r2.stderr || r2.stdout).trim()}`);
44874
- process.exitCode = 1;
44875
- return void 0;
44876
- }
44877
- return r2.stdout.trim();
44878
- }
44879
- function parseOwnerRepo(url) {
44880
- const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
44881
- if (!m) return [void 0, void 0];
44882
- return [m[1], m[2]];
44883
- }
44884
- function findOwnedEntry(ctx, id) {
44885
- if (!existsSync12(ctx.paths.entriesDir)) return void 0;
44886
- for (const categoryDirent of readdirSync8(ctx.paths.entriesDir, { withFileTypes: true })) {
44887
- if (!categoryDirent.isDirectory()) continue;
44888
- const categoryDir = join15(ctx.paths.entriesDir, categoryDirent.name);
44889
- for (const fileDirent of readdirSync8(categoryDir, { withFileTypes: true })) {
44890
- if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
44891
- const absPath = join15(categoryDir, fileDirent.name);
44892
- const raw2 = readFileSync6(absPath, "utf-8");
44893
- try {
44894
- const parsed = parseMarkdown(raw2);
44895
- if (parsed.frontmatter.id === id) {
44896
- const relPath = relative2(ctx.paths.entriesDir, absPath).replace(/\\/g, "/");
44897
- return {
44898
- id,
44899
- title: parsed.frontmatter.title,
44900
- absPath,
44901
- relPath
44902
- };
44903
- }
44904
- } catch {
45042
+ switch (result.status) {
45043
+ case "ok":
45044
+ ctx.logger.info(`PR opened: ${result.prUrl ?? ""}`);
45045
+ ctx.logger.info(
45046
+ "Once merged, other subscribers will see your entry after their next `caveat pull`."
45047
+ );
45048
+ break;
45049
+ case "dry-run":
45050
+ for (const [i2, step] of (result.plannedSteps ?? []).entries()) {
45051
+ ctx.logger.info(`[dry-run] ${i2 + 1}. ${step}`);
44905
45052
  }
44906
- }
44907
- }
44908
- return void 0;
44909
- }
44910
- function forkStagingDir(ctx) {
44911
- return join15(ctx.caveatHome, "push-fork");
44912
- }
44913
- function ensureFork(ctx, owner, repo, ghUser) {
44914
- const r2 = spawnSync2(
44915
- "gh",
44916
- ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"],
44917
- { encoding: "utf-8", shell: true }
44918
- );
44919
- if (r2.status !== 0 && !(r2.stderr || "").includes("already exists")) {
44920
- ctx.logger.warn(`gh repo fork stderr: ${r2.stderr.trim()}`);
44921
- }
44922
- void ghUser;
44923
- }
44924
- function ensureStagingClone(ctx, stagingDir, ghUser, sharedName, upstreamOwner) {
44925
- if (!existsSync12(stagingDir)) {
44926
- mkdirSync6(dirname6(stagingDir), { recursive: true });
44927
- const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
44928
- run(ctx, "git", ["clone", "--depth", "30", forkUrl, stagingDir]);
44929
- run(ctx, "git", ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`], {
44930
- cwd: stagingDir
44931
- });
44932
- } else {
44933
- run(ctx, "git", ["checkout", "main"], { cwd: stagingDir });
44934
- run(ctx, "git", ["fetch", "upstream", "main"], { cwd: stagingDir });
44935
- run(ctx, "git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
44936
- run(ctx, "git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
44937
- }
44938
- }
44939
- function existsInUpstream(stagingDir, relFromRoot) {
44940
- return existsSync12(join15(stagingDir, relFromRoot));
44941
- }
44942
- function run(ctx, command, args, opts = {}) {
44943
- const r2 = spawnSync2(command, args, {
44944
- encoding: "utf-8",
44945
- cwd: opts.cwd,
44946
- shell: true
44947
- });
44948
- if (r2.status !== 0) {
44949
- const msg = (r2.stderr || r2.stdout || "unknown").trim();
44950
- throw new Error(`${command} ${args.join(" ")} failed: ${msg}`);
45053
+ break;
45054
+ case "not-found":
45055
+ ctx.logger.error(
45056
+ `${result.detail}. List entries with \`caveat list\` or write + index first.`
45057
+ );
45058
+ process.exitCode = 1;
45059
+ break;
45060
+ case "gh-missing":
45061
+ case "gh-unauthed":
45062
+ ctx.logger.error(result.detail ?? "gh CLI issue");
45063
+ process.exitCode = 1;
45064
+ break;
45065
+ case "failed":
45066
+ ctx.logger.error(result.detail ?? "unknown failure");
45067
+ process.exitCode = 1;
45068
+ break;
44951
45069
  }
44952
45070
  }
44953
- function runClaim(ctx, command, args, opts = {}) {
44954
- const r2 = spawnSync2(command, args, {
44955
- encoding: "utf-8",
44956
- cwd: opts.cwd,
44957
- shell: true
44958
- });
44959
- return {
44960
- status: r2.status,
44961
- stdout: typeof r2.stdout === "string" ? r2.stdout : "",
44962
- stderr: typeof r2.stderr === "string" ? r2.stderr : ""
44963
- };
44964
- }
44965
45071
 
44966
45072
  // src/commands/community.ts
44967
45073
  async function runCommunityAdd(ctx, url) {
@@ -45015,7 +45121,7 @@ function runCommunityList(ctx) {
45015
45121
 
45016
45122
  // src/index.ts
45017
45123
  var program = new Command();
45018
- program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.3.0");
45124
+ program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.5.0");
45019
45125
  program.command("init").description(
45020
45126
  "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
45021
45127
  ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--skip-shared", "skip subscribing to the shared community DB", false).option("--dry-run", "show planned changes without writing", false).action(async (opts) => {