caveat-cli 0.2.1 → 0.3.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
@@ -9761,49 +9761,49 @@ var require_fast_uri = __commonJS({
9761
9761
  schemelessOptions.skipEscape = true;
9762
9762
  return serialize(resolved, schemelessOptions);
9763
9763
  }
9764
- function resolveComponent(base2, relative2, options2, skipNormalization) {
9764
+ function resolveComponent(base2, relative3, options2, skipNormalization) {
9765
9765
  const target = {};
9766
9766
  if (!skipNormalization) {
9767
9767
  base2 = parse4(serialize(base2, options2), options2);
9768
- relative2 = parse4(serialize(relative2, options2), options2);
9768
+ relative3 = parse4(serialize(relative3, options2), options2);
9769
9769
  }
9770
9770
  options2 = options2 || {};
9771
- if (!options2.tolerant && relative2.scheme) {
9772
- target.scheme = relative2.scheme;
9773
- target.userinfo = relative2.userinfo;
9774
- target.host = relative2.host;
9775
- target.port = relative2.port;
9776
- target.path = removeDotSegments(relative2.path || "");
9777
- target.query = relative2.query;
9771
+ if (!options2.tolerant && relative3.scheme) {
9772
+ target.scheme = relative3.scheme;
9773
+ target.userinfo = relative3.userinfo;
9774
+ target.host = relative3.host;
9775
+ target.port = relative3.port;
9776
+ target.path = removeDotSegments(relative3.path || "");
9777
+ target.query = relative3.query;
9778
9778
  } else {
9779
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
9780
- target.userinfo = relative2.userinfo;
9781
- target.host = relative2.host;
9782
- target.port = relative2.port;
9783
- target.path = removeDotSegments(relative2.path || "");
9784
- target.query = relative2.query;
9779
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
9780
+ target.userinfo = relative3.userinfo;
9781
+ target.host = relative3.host;
9782
+ target.port = relative3.port;
9783
+ target.path = removeDotSegments(relative3.path || "");
9784
+ target.query = relative3.query;
9785
9785
  } else {
9786
- if (!relative2.path) {
9786
+ if (!relative3.path) {
9787
9787
  target.path = base2.path;
9788
- if (relative2.query !== void 0) {
9789
- target.query = relative2.query;
9788
+ if (relative3.query !== void 0) {
9789
+ target.query = relative3.query;
9790
9790
  } else {
9791
9791
  target.query = base2.query;
9792
9792
  }
9793
9793
  } else {
9794
- if (relative2.path[0] === "/") {
9795
- target.path = removeDotSegments(relative2.path);
9794
+ if (relative3.path[0] === "/") {
9795
+ target.path = removeDotSegments(relative3.path);
9796
9796
  } else {
9797
9797
  if ((base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0) && !base2.path) {
9798
- target.path = "/" + relative2.path;
9798
+ target.path = "/" + relative3.path;
9799
9799
  } else if (!base2.path) {
9800
- target.path = relative2.path;
9800
+ target.path = relative3.path;
9801
9801
  } else {
9802
- target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative2.path;
9802
+ target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative3.path;
9803
9803
  }
9804
9804
  target.path = removeDotSegments(target.path);
9805
9805
  }
9806
- target.query = relative2.query;
9806
+ target.query = relative3.query;
9807
9807
  }
9808
9808
  target.userinfo = base2.userinfo;
9809
9809
  target.host = base2.host;
@@ -9811,7 +9811,7 @@ var require_fast_uri = __commonJS({
9811
9811
  }
9812
9812
  target.scheme = base2.scheme;
9813
9813
  }
9814
- target.fragment = relative2.fragment;
9814
+ target.fragment = relative3.fragment;
9815
9815
  return target;
9816
9816
  }
9817
9817
  function equal(uriA, uriB, options2) {
@@ -16139,10 +16139,12 @@ 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
16143
  var DEFAULT_CONFIG = {
16143
16144
  knowledgeRepo: "own",
16144
16145
  semverKeys: ["driver", "cuda", "node"],
16145
- communitySources: []
16146
+ communitySources: [],
16147
+ sharedRepo: SHARED_REPO_URL
16146
16148
  };
16147
16149
  function loadConfig(userConfigPath) {
16148
16150
  const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
@@ -21251,7 +21253,7 @@ var stdoutLogger = {
21251
21253
  };
21252
21254
 
21253
21255
  // src/commands/init.ts
21254
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
21256
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
21255
21257
  import { join as join9 } from "node:path";
21256
21258
 
21257
21259
  // src/claudeInstall.ts
@@ -21417,7 +21419,7 @@ var KNOWLEDGE_GITIGNORE = [
21417
21419
  "community/",
21418
21420
  ""
21419
21421
  ].join("\n");
21420
- function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21422
+ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
21421
21423
  ensureUserConfig(ctx.userConfigPath);
21422
21424
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
21423
21425
  if (!existsSync8(ctx.paths.knowledgeRepo)) {
@@ -21433,7 +21435,17 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21433
21435
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
21434
21436
  }
21435
21437
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21436
- db.close();
21438
+ try {
21439
+ if (!opts.skipShared && !opts.dryRun) {
21440
+ await subscribeSharedRepo(ctx, db);
21441
+ } else if (opts.skipShared) {
21442
+ ctx.logger.info("shared community DB subscription skipped (--skip-shared)");
21443
+ } else if (opts.dryRun) {
21444
+ ctx.logger.info(`[dry-run] would subscribe to shared community DB: ${ctx.config.sharedRepo}`);
21445
+ }
21446
+ } finally {
21447
+ db.close();
21448
+ }
21437
21449
  ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21438
21450
  if (opts.skipClaude) {
21439
21451
  ctx.logger.info("Claude Code integration skipped (--skip-claude)");
@@ -21453,6 +21465,52 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21453
21465
  });
21454
21466
  reportInstallResult(ctx, result, opts.dryRun);
21455
21467
  }
21468
+ async function subscribeSharedRepo(ctx, db) {
21469
+ const url = ctx.config.sharedRepo;
21470
+ const validation = validateCommunityUrl(url);
21471
+ if (!validation.valid) {
21472
+ ctx.logger.warn(
21473
+ `sharedRepo is not a valid GitHub URL \u2014 skipping: ${validation.reason}`
21474
+ );
21475
+ return;
21476
+ }
21477
+ 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 });
21482
+ }
21483
+ try {
21484
+ await communityAdd({
21485
+ url,
21486
+ communityDir: ctx.paths.communityDir,
21487
+ logger: ctx.logger
21488
+ });
21489
+ ctx.logger.info(`shared community DB subscribed: ${url} \u2192 community/${handle}/`);
21490
+ } catch (err) {
21491
+ ctx.logger.warn(
21492
+ `shared community DB subscription failed: ${err instanceof Error ? err.message : String(err)}`
21493
+ );
21494
+ return;
21495
+ }
21496
+ } else {
21497
+ ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
21498
+ }
21499
+ if (existsSync8(ctx.paths.communityDir)) {
21500
+ rebuildAll(db);
21501
+ if (existsSync8(ctx.paths.entriesDir)) {
21502
+ scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21503
+ }
21504
+ for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
21505
+ if (!entry.isDirectory()) continue;
21506
+ const source = `community/${entry.name}`;
21507
+ const root = join9(ctx.paths.communityDir, entry.name, "entries");
21508
+ if (!existsSync8(root)) continue;
21509
+ const result = scanSource({ db, source, entriesRoot: root });
21510
+ ctx.logger.info(`indexed ${source}: +${result.added}`);
21511
+ }
21512
+ }
21513
+ }
21456
21514
  function runUninstall(ctx, opts) {
21457
21515
  const cliScriptPath = process.argv[1];
21458
21516
  if (!cliScriptPath) {
@@ -21497,7 +21555,7 @@ function reportInstallResult(ctx, result, dryRun) {
21497
21555
  }
21498
21556
 
21499
21557
  // src/commands/indexCmd.ts
21500
- import { existsSync as existsSync9, readdirSync as readdirSync4, mkdirSync as mkdirSync5 } from "node:fs";
21558
+ import { existsSync as existsSync9, readdirSync as readdirSync5, mkdirSync as mkdirSync5 } from "node:fs";
21501
21559
  import { dirname as dirname5, join as join10 } from "node:path";
21502
21560
  function runIndex(ctx, opts) {
21503
21561
  const dbDir = dirname5(ctx.paths.dbPath);
@@ -21515,7 +21573,7 @@ function runIndex(ctx, opts) {
21515
21573
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
21516
21574
  }
21517
21575
  if (existsSync9(ctx.paths.communityDir)) {
21518
- for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
21576
+ for (const entry of readdirSync5(ctx.paths.communityDir, { withFileTypes: true })) {
21519
21577
  if (!entry.isDirectory()) continue;
21520
21578
  const source = `community/${entry.name}`;
21521
21579
  const root = join10(ctx.paths.communityDir, entry.name, "entries");
@@ -30024,12 +30082,12 @@ function createDetailRoute(ctx) {
30024
30082
  }
30025
30083
 
30026
30084
  // ../web/dist/routes/community.js
30027
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
30085
+ import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
30028
30086
  import { join as join12 } from "node:path";
30029
30087
  function listCommunity(communityDir, db) {
30030
30088
  if (!existsSync10(communityDir)) return [];
30031
30089
  const handles = [];
30032
- for (const entry of readdirSync5(communityDir, { withFileTypes: true })) {
30090
+ for (const entry of readdirSync6(communityDir, { withFileTypes: true })) {
30033
30091
  if (!entry.isDirectory()) continue;
30034
30092
  const handlePath = join12(communityDir, entry.name);
30035
30093
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
@@ -44648,6 +44706,263 @@ async function runHook(name) {
44648
44706
  process.exit(0);
44649
44707
  }
44650
44708
 
44709
+ // src/commands/pull.ts
44710
+ import { existsSync as existsSync11, readdirSync as readdirSync7 } from "node:fs";
44711
+ import { join as join14 } from "node:path";
44712
+ async function runPull(ctx) {
44713
+ if (!existsSync11(ctx.paths.communityDir)) {
44714
+ ctx.logger.info(
44715
+ "no community repos yet \u2014 did you run `caveat init`? You can also `caveat community add <url>` manually."
44716
+ );
44717
+ return;
44718
+ }
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
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44731
+ 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}`);
44736
+ }
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}`);
44744
+ }
44745
+ } finally {
44746
+ db.close();
44747
+ }
44748
+ }
44749
+
44750
+ // 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
+ 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
44871
+ });
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 {
44905
+ }
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}`);
44951
+ }
44952
+ }
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
+
44651
44966
  // src/commands/community.ts
44652
44967
  async function runCommunityAdd(ctx, url) {
44653
44968
  try {
@@ -44700,12 +45015,16 @@ function runCommunityList(ctx) {
44700
45015
 
44701
45016
  // src/index.ts
44702
45017
  var program = new Command();
44703
- program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.2.1");
45018
+ program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.3.0");
44704
45019
  program.command("init").description(
44705
- "Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration"
44706
- ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).action((opts) => {
45020
+ "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
45021
+ ).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) => {
44707
45022
  const ctx = buildContext(stdoutLogger);
44708
- runInit(ctx, { skipClaude: opts.skipClaude, dryRun: opts.dryRun });
45023
+ await runInit(ctx, {
45024
+ skipClaude: opts.skipClaude,
45025
+ skipShared: opts.skipShared,
45026
+ dryRun: opts.dryRun
45027
+ });
44709
45028
  });
44710
45029
  program.command("uninstall").description("Remove Claude Code MCP server and hooks registered by `caveat init`").option("--dry-run", "show planned changes without writing", false).action((opts) => {
44711
45030
  const ctx = buildContext(stdoutLogger);
@@ -44738,6 +45057,16 @@ program.command("stats").description("Show aggregate stats").action(() => {
44738
45057
  const ctx = buildContext(stdoutLogger);
44739
45058
  runStats(ctx);
44740
45059
  });
45060
+ program.command("pull").description(
45061
+ "Refresh all subscribed community repos (incl. shared DB) and re-index. Use this to receive contributions others have merged."
45062
+ ).action(async () => {
45063
+ const ctx = buildContext(stdoutLogger);
45064
+ await runPull(ctx);
45065
+ });
45066
+ program.command("push").description("Contribute a caveat to the shared community DB via fork + PR (requires gh CLI).").argument("<id>", "entry id (from `caveat list`)").option("--dry-run", "show planned steps without making GitHub changes", false).action(async (id, opts) => {
45067
+ const ctx = buildContext(stdoutLogger);
45068
+ await runPush(ctx, { id, dryRun: opts.dryRun });
45069
+ });
44741
45070
  program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
44742
45071
  runServe({ port: opts.port });
44743
45072
  });