caveat-cli 0.6.2 → 0.7.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, relative3, options2, skipNormalization) {
9764
+ function resolveComponent(base2, relative2, options2, skipNormalization) {
9765
9765
  const target = {};
9766
9766
  if (!skipNormalization) {
9767
9767
  base2 = parse4(serialize(base2, options2), options2);
9768
- relative3 = parse4(serialize(relative3, options2), options2);
9768
+ relative2 = parse4(serialize(relative2, options2), options2);
9769
9769
  }
9770
9770
  options2 = options2 || {};
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;
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;
9778
9778
  } else {
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;
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;
9785
9785
  } else {
9786
- if (!relative3.path) {
9786
+ if (!relative2.path) {
9787
9787
  target.path = base2.path;
9788
- if (relative3.query !== void 0) {
9789
- target.query = relative3.query;
9788
+ if (relative2.query !== void 0) {
9789
+ target.query = relative2.query;
9790
9790
  } else {
9791
9791
  target.query = base2.query;
9792
9792
  }
9793
9793
  } else {
9794
- if (relative3.path[0] === "/") {
9795
- target.path = removeDotSegments(relative3.path);
9794
+ if (relative2.path[0] === "/") {
9795
+ target.path = removeDotSegments(relative2.path);
9796
9796
  } else {
9797
9797
  if ((base2.userinfo !== void 0 || base2.host !== void 0 || base2.port !== void 0) && !base2.path) {
9798
- target.path = "/" + relative3.path;
9798
+ target.path = "/" + relative2.path;
9799
9799
  } else if (!base2.path) {
9800
- target.path = relative3.path;
9800
+ target.path = relative2.path;
9801
9801
  } else {
9802
- target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative3.path;
9802
+ target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) + relative2.path;
9803
9803
  }
9804
9804
  target.path = removeDotSegments(target.path);
9805
9805
  }
9806
- target.query = relative3.query;
9806
+ target.query = relative2.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 = relative3.fragment;
9814
+ target.fragment = relative2.fragment;
9815
9815
  return target;
9816
9816
  }
9817
9817
  function equal(uriA, uriB, options2) {
@@ -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 join9 } from "node:path";
12979
+ import { join as join7 } from "node:path";
12980
12980
 
12981
12981
  // ../../packages/core/dist/db.js
12982
12982
  import { DatabaseSync } from "node:sqlite";
@@ -15819,7 +15819,14 @@ function search(db, opts = {}) {
15819
15819
  conditions.push(`e.confidence IN (${placeholders})`);
15820
15820
  params.push(...filters.confidence);
15821
15821
  }
15822
+ if (filters.visibility === "public" || filters.visibility === "private") {
15823
+ conditions.push(`e.visibility = ?`);
15824
+ params.push(filters.visibility);
15825
+ }
15822
15826
  if (conditions.length) sql += " AND " + conditions.join(" AND ");
15827
+ if (!ftsQuery) {
15828
+ sql += ` ORDER BY json_extract(e.frontmatter_json, '$.updated_at') DESC`;
15829
+ }
15823
15830
  sql += " LIMIT ?";
15824
15831
  params.push(limit);
15825
15832
  const rows = db.prepare(sql).all(...params);
@@ -15864,6 +15871,7 @@ function toSearchResult(row) {
15864
15871
  title: row.title,
15865
15872
  symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH),
15866
15873
  confidence: row.confidence,
15874
+ visibility: row.visibility ?? "public",
15867
15875
  environment: fm.environment ?? {}
15868
15876
  };
15869
15877
  }
@@ -16105,12 +16113,10 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
16105
16113
 
16106
16114
  // ../../packages/core/dist/config.js
16107
16115
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
16108
- var SHARED_REPO_URL = "https://github.com/kitepon-rgb/Caveat";
16109
16116
  var DEFAULT_CONFIG = {
16110
16117
  knowledgeRepo: "own",
16111
16118
  semverKeys: ["driver", "cuda", "node"],
16112
- communitySources: [],
16113
- sharedRepo: SHARED_REPO_URL
16119
+ communitySources: []
16114
16120
  };
16115
16121
  function loadConfig(userConfigPath) {
16116
16122
  const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
@@ -16134,7 +16140,7 @@ function deepMerge(base2, overlay) {
16134
16140
  }
16135
16141
 
16136
16142
  // ../../packages/core/dist/community.js
16137
- import { existsSync as existsSync6, readdirSync as readdirSync3 } from "node:fs";
16143
+ import { existsSync as existsSync6, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "node:fs";
16138
16144
  import { join as join6 } from "node:path";
16139
16145
 
16140
16146
  // ../../node_modules/.pnpm/simple-git@3.36.0/node_modules/simple-git/dist/esm/index.js
@@ -21164,6 +21170,37 @@ function communityList(opts) {
21164
21170
  }
21165
21171
  return out;
21166
21172
  }
21173
+ function communityRemove(opts) {
21174
+ const handle = (opts.handle ?? "").trim();
21175
+ if (!handle) {
21176
+ throw new Error("handle is required");
21177
+ }
21178
+ if (handle === "." || handle === ".." || /[\\/]/.test(handle)) {
21179
+ throw new Error(`invalid handle (no path separators or relative segments): ${opts.handle}`);
21180
+ }
21181
+ const target = join6(opts.communityDir, handle);
21182
+ const dirExisted = existsSync6(target) && statSync4(target).isDirectory();
21183
+ const source = `community/${handle}`;
21184
+ const row = opts.db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(source);
21185
+ const rowCount = row?.n ?? 0;
21186
+ if (opts.dryRun) {
21187
+ return { handle, path: target, dirExisted, rowCount, removed: false, dryRun: true };
21188
+ }
21189
+ if (dirExisted) {
21190
+ rmSync(target, { recursive: true, force: true });
21191
+ }
21192
+ if (rowCount > 0) {
21193
+ opts.db.prepare("DELETE FROM entries WHERE source = ?").run(source);
21194
+ }
21195
+ return {
21196
+ handle,
21197
+ path: target,
21198
+ dirExisted,
21199
+ rowCount,
21200
+ removed: dirExisted || rowCount > 0,
21201
+ dryRun: false
21202
+ };
21203
+ }
21167
21204
 
21168
21205
  // ../../packages/core/dist/claudeHooks.js
21169
21206
  var CAVEAT_TRIGGERS = [
@@ -21194,274 +21231,28 @@ function stopReminderText() {
21194
21231
  return [
21195
21232
  "[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",
21196
21233
  "\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",
21197
- "\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",
21198
- "\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"
21234
+ "\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"
21199
21235
  ].join("\n");
21200
21236
  }
21201
21237
 
21202
- // ../../packages/core/dist/push.js
21203
- import { spawnSync } from "node:child_process";
21204
- import {
21205
- copyFileSync,
21206
- existsSync as existsSync7,
21207
- mkdirSync as mkdirSync3,
21208
- readdirSync as readdirSync4,
21209
- readFileSync as readFileSync5
21210
- } from "node:fs";
21211
- import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
21212
- async function pushEntry(opts) {
21213
- if (!checkGhAvailable()) {
21214
- return {
21215
- status: "gh-missing",
21216
- detail: "gh (GitHub CLI) is required. Install: https://cli.github.com/"
21217
- };
21218
- }
21219
- if (!checkGhAuthed()) {
21220
- return {
21221
- status: "gh-unauthed",
21222
- detail: "gh is not authenticated. Run `gh auth login`."
21223
- };
21224
- }
21225
- const validation = validateCommunityUrl(opts.sharedRepoUrl);
21226
- if (!validation.valid) {
21227
- return { status: "failed", detail: `sharedRepo URL invalid: ${validation.reason}` };
21228
- }
21229
- const [sharedOwner, sharedName] = parseOwnerRepo(opts.sharedRepoUrl);
21230
- if (!sharedOwner || !sharedName) {
21231
- return { status: "failed", detail: `cannot parse owner/repo from ${opts.sharedRepoUrl}` };
21232
- }
21233
- const owned = findOwnedEntry(opts.entriesDir, opts.id);
21234
- if (!owned) {
21235
- return {
21236
- status: "not-found",
21237
- detail: `entry id=${opts.id} not found under ${opts.entriesDir}`
21238
- };
21239
- }
21240
- if (owned.visibility === "private") {
21241
- return {
21242
- status: "visibility-private",
21243
- detail: `entry id=${opts.id} has visibility: private and cannot be pushed to the public community DB. Update the entry to visibility: public first (the user declared this entry local-only).`
21244
- };
21245
- }
21246
- const ghUser = resolveGhUser();
21247
- if (!ghUser) {
21248
- return { status: "failed", detail: "gh api user failed" };
21249
- }
21250
- if (opts.dryRun) {
21251
- return {
21252
- status: "dry-run",
21253
- plannedSteps: [
21254
- `fork ${sharedOwner}/${sharedName} as ${ghUser}/${sharedName}`,
21255
- `clone fork \u2192 ${forkStagingDir(opts.caveatHome)}`,
21256
- `copy entry \u2192 entries/${owned.relPath}`,
21257
- "commit, push branch, open PR against main"
21258
- ]
21259
- };
21260
- }
21261
- try {
21262
- ensureFork(sharedOwner, sharedName);
21263
- const stagingDir = forkStagingDir(opts.caveatHome);
21264
- ensureStagingClone(stagingDir, ghUser, sharedName, sharedOwner);
21265
- const branch = `caveat-push-${owned.id}-${Date.now().toString(36)}`;
21266
- runOrThrow("git", ["checkout", "-b", branch], { cwd: stagingDir });
21267
- const destPath = join7(stagingDir, "entries", owned.relPath);
21268
- mkdirSync3(dirname4(destPath), { recursive: true });
21269
- copyFileSync(owned.absPath, destPath);
21270
- runOrThrow("git", ["add", join7("entries", owned.relPath)], { cwd: stagingDir });
21271
- const isUpdate = upstreamAlreadyHas(stagingDir, "entries/" + owned.relPath);
21272
- const verb = isUpdate ? "update" : "add";
21273
- const commitMsg = `${verb}: ${owned.title}`;
21274
- runOrThrow("git", ["commit", "-m", commitMsg], { cwd: stagingDir });
21275
- runOrThrow("git", ["push", "--set-upstream", "origin", branch], { cwd: stagingDir });
21276
- const prTitle = `${verb}: ${owned.title}`;
21277
- const prBody = [
21278
- `Contribution of caveat \`${owned.id}\` via \`caveat push\`.`,
21279
- "",
21280
- `- entry path: \`entries/${owned.relPath}\``,
21281
- `- action: ${verb}`,
21282
- "",
21283
- "Merged PRs will appear in subscribers' repos on their next `caveat pull`."
21284
- ].join("\n");
21285
- const pr = runCapture(
21286
- "gh",
21287
- [
21288
- "pr",
21289
- "create",
21290
- "--repo",
21291
- `${sharedOwner}/${sharedName}`,
21292
- "--base",
21293
- "main",
21294
- "--head",
21295
- `${ghUser}:${branch}`,
21296
- "--title",
21297
- prTitle,
21298
- "--body",
21299
- prBody
21300
- ],
21301
- { cwd: stagingDir }
21302
- );
21303
- if (pr.status !== 0) {
21304
- return {
21305
- status: "failed",
21306
- detail: (pr.stderr || "gh pr create failed").trim()
21307
- };
21308
- }
21309
- return { status: "ok", prUrl: pr.stdout.trim() };
21310
- } catch (err) {
21311
- return {
21312
- status: "failed",
21313
- detail: err instanceof Error ? err.message : String(err)
21314
- };
21315
- }
21316
- }
21317
- function shellQuote(s) {
21318
- if (!/[\s&|<>^()"`$!*?\[\]{}\\]/.test(s)) return s;
21319
- return `"${s.replace(/"/g, '""')}"`;
21320
- }
21321
- function runCapture(command, args, opts = {}) {
21322
- const line = [command, ...args].map(shellQuote).join(" ");
21323
- const r2 = spawnSync(line, {
21324
- encoding: "utf-8",
21325
- cwd: opts.cwd,
21326
- shell: true
21327
- });
21328
- return {
21329
- status: r2.status,
21330
- stdout: typeof r2.stdout === "string" ? r2.stdout : "",
21331
- stderr: typeof r2.stderr === "string" ? r2.stderr : ""
21332
- };
21333
- }
21334
- function runOrThrow(command, args, opts = {}) {
21335
- const r2 = runCapture(command, args, opts);
21336
- if (r2.status !== 0) {
21337
- throw new Error(
21338
- `${command} ${args.join(" ")} failed: ${(r2.stderr || r2.stdout || "unknown").trim()}`
21339
- );
21340
- }
21341
- }
21342
- function checkGhAvailable() {
21343
- return runCapture("gh", ["--version"]).status === 0;
21344
- }
21345
- function checkGhAuthed() {
21346
- return runCapture("gh", ["auth", "status"]).status === 0;
21347
- }
21348
- function resolveGhUser() {
21349
- const r2 = runCapture("gh", ["api", "user", "--jq", ".login"]);
21350
- if (r2.status !== 0) return void 0;
21351
- return r2.stdout.trim() || void 0;
21352
- }
21353
- function parseOwnerRepo(url) {
21354
- const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?\/?$/.exec(url);
21355
- if (!m) return [void 0, void 0];
21356
- return [m[1], m[2]];
21357
- }
21358
- function findOwnedEntry(entriesDir, id) {
21359
- if (!existsSync7(entriesDir)) return void 0;
21360
- for (const categoryDirent of readdirSync4(entriesDir, { withFileTypes: true })) {
21361
- if (!categoryDirent.isDirectory()) continue;
21362
- const categoryDir = join7(entriesDir, categoryDirent.name);
21363
- for (const fileDirent of readdirSync4(categoryDir, { withFileTypes: true })) {
21364
- if (!fileDirent.isFile() || !fileDirent.name.endsWith(".md")) continue;
21365
- const absPath = join7(categoryDir, fileDirent.name);
21366
- const raw2 = readFileSync5(absPath, "utf-8");
21367
- try {
21368
- const parsed = parseMarkdown(raw2);
21369
- if (parsed.frontmatter.id === id) {
21370
- const relPath = relative2(entriesDir, absPath).replace(/\\/g, "/");
21371
- return {
21372
- id,
21373
- title: parsed.frontmatter.title,
21374
- absPath,
21375
- relPath,
21376
- visibility: parsed.frontmatter.visibility
21377
- };
21378
- }
21379
- } catch {
21380
- }
21381
- }
21382
- }
21383
- return void 0;
21384
- }
21385
- function forkStagingDir(caveatHome) {
21386
- return join7(caveatHome, "push-fork");
21387
- }
21388
- function ensureFork(owner, repo) {
21389
- runCapture("gh", ["repo", "fork", `${owner}/${repo}`, "--clone=false", "--remote=false"]);
21390
- }
21391
- function ensureStagingClone(stagingDir, ghUser, sharedName, upstreamOwner) {
21392
- if (!existsSync7(stagingDir)) {
21393
- mkdirSync3(dirname4(stagingDir), { recursive: true });
21394
- const forkUrl = `https://github.com/${ghUser}/${sharedName}.git`;
21395
- runOrThrow("git", ["clone", "--depth", "30", forkUrl, stagingDir]);
21396
- runOrThrow(
21397
- "git",
21398
- ["remote", "add", "upstream", `https://github.com/${upstreamOwner}/${sharedName}.git`],
21399
- { cwd: stagingDir }
21400
- );
21401
- } else {
21402
- runOrThrow("git", ["checkout", "main"], { cwd: stagingDir });
21403
- runOrThrow("git", ["fetch", "upstream", "main"], { cwd: stagingDir });
21404
- runOrThrow("git", ["reset", "--hard", "upstream/main"], { cwd: stagingDir });
21405
- runOrThrow("git", ["push", "origin", "main", "--force-with-lease"], { cwd: stagingDir });
21406
- }
21407
- }
21408
- function upstreamAlreadyHas(stagingDir, relFromRoot) {
21409
- return existsSync7(join7(stagingDir, relFromRoot));
21410
- }
21411
-
21412
- // ../../packages/core/dist/pullShared.js
21413
- import { existsSync as existsSync8, readdirSync as readdirSync5 } from "node:fs";
21414
- import { join as join8 } from "node:path";
21415
- async function pullShared(opts) {
21416
- const result = { pulled: [], indexed: [] };
21417
- if (!existsSync8(opts.communityDir)) {
21418
- return result;
21419
- }
21420
- const pulls = await communityPull({
21421
- communityDir: opts.communityDir,
21422
- logger: opts.logger
21423
- });
21424
- for (const p2 of pulls) {
21425
- result.pulled.push({
21426
- handle: p2.handle,
21427
- status: p2.status,
21428
- message: p2.message
21429
- });
21430
- }
21431
- rebuildAll(opts.db);
21432
- if (existsSync8(opts.entriesDir)) {
21433
- const own = scanSource({ db: opts.db, source: "own", entriesRoot: opts.entriesDir });
21434
- result.indexed.push({ source: "own", ...own });
21435
- }
21436
- for (const entry of readdirSync5(opts.communityDir, { withFileTypes: true })) {
21437
- if (!entry.isDirectory()) continue;
21438
- const source = `community/${entry.name}`;
21439
- const root = join8(opts.communityDir, entry.name, "entries");
21440
- if (!existsSync8(root)) continue;
21441
- const scan = scanSource({ db: opts.db, source, entriesRoot: root });
21442
- result.indexed.push({ source, ...scan });
21443
- }
21444
- return result;
21445
- }
21446
-
21447
21238
  // src/context.ts
21448
21239
  function buildContext(logger, overrides = {}) {
21449
21240
  const userHome = overrides.userHome ?? homedir();
21450
21241
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
21451
- const userConfigPath = join9(userHome, ".caveatrc.json");
21242
+ const userConfigPath = join7(userHome, ".caveatrc.json");
21452
21243
  const config3 = loadConfig(userConfigPath);
21453
21244
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
21454
21245
  return { caveatHome, userHome, userConfigPath, config: config3, paths, logger };
21455
21246
  }
21456
21247
 
21457
21248
  // src/version.ts
21458
- import { readFileSync as readFileSync6 } from "node:fs";
21459
- import { dirname as dirname5, join as join10 } from "node:path";
21249
+ import { readFileSync as readFileSync5 } from "node:fs";
21250
+ import { dirname as dirname4, join as join8 } from "node:path";
21460
21251
  import { fileURLToPath as fileURLToPath3 } from "node:url";
21461
21252
  function resolveVersion() {
21462
21253
  try {
21463
- const here2 = dirname5(fileURLToPath3(import.meta.url));
21464
- const pkg = JSON.parse(readFileSync6(join10(here2, "..", "package.json"), "utf-8"));
21254
+ const here2 = dirname4(fileURLToPath3(import.meta.url));
21255
+ const pkg = JSON.parse(readFileSync5(join8(here2, "..", "package.json"), "utf-8"));
21465
21256
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
21466
21257
  } catch {
21467
21258
  return "0.0.0";
@@ -21480,13 +21271,13 @@ var stdoutLogger = {
21480
21271
  };
21481
21272
 
21482
21273
  // src/commands/init.ts
21483
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync6, renameSync, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
21484
- import { join as join12 } from "node:path";
21274
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
21275
+ import { dirname as dirname6, join as join10 } from "node:path";
21485
21276
 
21486
21277
  // src/claudeInstall.ts
21487
- import { spawnSync as spawnSync2 } from "node:child_process";
21488
- import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
21489
- import { dirname as dirname6, join as join11 } from "node:path";
21278
+ import { spawnSync } from "node:child_process";
21279
+ import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
21280
+ import { dirname as dirname5, join as join9 } from "node:path";
21490
21281
  var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
21491
21282
  var EVENT_STOP = "Stop";
21492
21283
  function quote(p2) {
@@ -21520,27 +21311,27 @@ function removeHook(settings, event, command) {
21520
21311
  return true;
21521
21312
  }
21522
21313
  function readSettings(path) {
21523
- if (!existsSync9(path)) return {};
21524
- return JSON.parse(readFileSync7(path, "utf-8"));
21314
+ if (!existsSync7(path)) return {};
21315
+ return JSON.parse(readFileSync6(path, "utf-8"));
21525
21316
  }
21526
21317
  function writeSettings(path, settings) {
21527
- const dir = dirname6(path);
21528
- if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
21318
+ const dir = dirname5(path);
21319
+ if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
21529
21320
  let backupPath = "";
21530
- if (existsSync9(path)) {
21321
+ if (existsSync7(path)) {
21531
21322
  backupPath = `${path}.caveat-backup-${Date.now()}`;
21532
- copyFileSync2(path, backupPath);
21323
+ copyFileSync(path, backupPath);
21533
21324
  }
21534
21325
  writeFileSync4(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
21535
21326
  return backupPath;
21536
21327
  }
21537
21328
  var CLAUDE_BIN = "claude";
21538
- function shellQuote2(s) {
21329
+ function shellQuote(s) {
21539
21330
  return /[\s&|<>^()]/.test(s) ? `"${s}"` : s;
21540
21331
  }
21541
21332
  function runClaude(args) {
21542
- const line = [CLAUDE_BIN, ...args].map(shellQuote2).join(" ");
21543
- return spawnSync2(line, { shell: true, encoding: "utf-8" });
21333
+ const line = [CLAUDE_BIN, ...args].map(shellQuote).join(" ");
21334
+ return spawnSync(line, { shell: true, encoding: "utf-8" });
21544
21335
  }
21545
21336
  function registerMcp(nodePath, cliScriptPath, dryRun, logger) {
21546
21337
  const args = mcpArgs(cliScriptPath);
@@ -21590,7 +21381,7 @@ function unregisterMcp(dryRun, logger) {
21590
21381
  return { action: "skipped", detail: "not registered or removal failed" };
21591
21382
  }
21592
21383
  function installClaudeIntegration(opts) {
21593
- const settingsPath = join11(opts.claudeDir, "settings.json");
21384
+ const settingsPath = join9(opts.claudeDir, "settings.json");
21594
21385
  const settings = readSettings(settingsPath);
21595
21386
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21596
21387
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21609,7 +21400,7 @@ function installClaudeIntegration(opts) {
21609
21400
  return { mcp, hooks: { userPromptSubmit, stop }, backupPath };
21610
21401
  }
21611
21402
  function uninstallClaudeIntegration(opts) {
21612
- const settingsPath = join11(opts.claudeDir, "settings.json");
21403
+ const settingsPath = join9(opts.claudeDir, "settings.json");
21613
21404
  const settings = readSettings(settingsPath);
21614
21405
  const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
21615
21406
  const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
@@ -21641,35 +21432,34 @@ var KNOWLEDGE_GITIGNORE = [
21641
21432
  ".obsidian/",
21642
21433
  ""
21643
21434
  ].join("\n");
21644
- async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
21435
+ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
21645
21436
  ensureUserConfig(ctx.userConfigPath);
21646
21437
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
21647
- if (!existsSync10(ctx.paths.knowledgeRepo)) {
21648
- mkdirSync5(ctx.paths.knowledgeRepo, { recursive: true });
21649
- mkdirSync5(ctx.paths.entriesDir, { recursive: true });
21438
+ if (!existsSync8(ctx.paths.knowledgeRepo)) {
21439
+ mkdirSync4(ctx.paths.knowledgeRepo, { recursive: true });
21440
+ mkdirSync4(ctx.paths.entriesDir, { recursive: true });
21650
21441
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
21651
21442
  } else {
21652
21443
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
21653
21444
  }
21654
21445
  migrateLegacyCommunityDir(ctx);
21655
- const gitignorePath = join12(ctx.paths.knowledgeRepo, ".gitignore");
21656
- if (!existsSync10(gitignorePath)) {
21446
+ const gitignorePath = join10(ctx.paths.knowledgeRepo, ".gitignore");
21447
+ if (!existsSync8(gitignorePath)) {
21657
21448
  writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
21658
21449
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
21659
21450
  }
21660
- const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21661
- try {
21662
- if (!opts.skipShared && !opts.dryRun) {
21663
- await subscribeSharedRepo(ctx, db);
21664
- } else if (opts.skipShared) {
21665
- ctx.logger.info("shared community DB subscription skipped (--skip-shared)");
21666
- } else if (opts.dryRun) {
21667
- ctx.logger.info(`[dry-run] would subscribe to shared community DB: ${ctx.config.sharedRepo}`);
21668
- }
21669
- } finally {
21451
+ if (!opts.dryRun) {
21452
+ const dbDir = dirname6(ctx.paths.dbPath);
21453
+ if (!existsSync8(dbDir)) mkdirSync4(dbDir, { recursive: true });
21454
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21670
21455
  db.close();
21456
+ ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21457
+ } else {
21458
+ ctx.logger.info(`[dry-run] db path: ${ctx.paths.dbPath}`);
21671
21459
  }
21672
- ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
21460
+ ctx.logger.info(
21461
+ "tip: subscribe to a group repo with `caveat community add <github-url>`, then `caveat pull`."
21462
+ );
21673
21463
  if (opts.skipClaude) {
21674
21464
  ctx.logger.info("Claude Code integration skipped (--skip-claude)");
21675
21465
  return;
@@ -21680,7 +21470,7 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21680
21470
  return;
21681
21471
  }
21682
21472
  const result = installClaudeIntegration({
21683
- claudeDir: join12(ctx.userHome, ".claude"),
21473
+ claudeDir: join10(ctx.userHome, ".claude"),
21684
21474
  cliScriptPath,
21685
21475
  nodePath: process.execPath,
21686
21476
  dryRun: opts.dryRun,
@@ -21689,20 +21479,20 @@ async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRu
21689
21479
  reportInstallResult(ctx, result, opts.dryRun);
21690
21480
  }
21691
21481
  function migrateLegacyCommunityDir(ctx) {
21692
- const legacy = join12(ctx.paths.knowledgeRepo, "community");
21482
+ const legacy = join10(ctx.paths.knowledgeRepo, "community");
21693
21483
  const current = ctx.paths.communityDir;
21694
21484
  if (legacy === current) return;
21695
- if (!existsSync10(legacy)) return;
21696
- if (existsSync10(current)) {
21485
+ if (!existsSync8(legacy)) return;
21486
+ if (existsSync8(current)) {
21697
21487
  ctx.logger.warn(
21698
21488
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
21699
21489
  );
21700
21490
  return;
21701
21491
  }
21702
- mkdirSync5(current, { recursive: true });
21703
- for (const entry of readdirSync6(legacy, { withFileTypes: true })) {
21492
+ mkdirSync4(current, { recursive: true });
21493
+ for (const entry of readdirSync4(legacy, { withFileTypes: true })) {
21704
21494
  if (!entry.isDirectory()) continue;
21705
- renameSync(join12(legacy, entry.name), join12(current, entry.name));
21495
+ renameSync(join10(legacy, entry.name), join10(current, entry.name));
21706
21496
  }
21707
21497
  try {
21708
21498
  rmdirSync(legacy);
@@ -21710,52 +21500,6 @@ function migrateLegacyCommunityDir(ctx) {
21710
21500
  }
21711
21501
  ctx.logger.info(`migrated legacy community/ \u2192 ${current}`);
21712
21502
  }
21713
- async function subscribeSharedRepo(ctx, db) {
21714
- const url = ctx.config.sharedRepo;
21715
- const validation = validateCommunityUrl(url);
21716
- if (!validation.valid) {
21717
- ctx.logger.warn(
21718
- `sharedRepo is not a valid GitHub URL \u2014 skipping: ${validation.reason}`
21719
- );
21720
- return;
21721
- }
21722
- const handle = validation.handle;
21723
- const target = join12(ctx.paths.communityDir, handle);
21724
- if (!existsSync10(target)) {
21725
- if (!existsSync10(ctx.paths.communityDir)) {
21726
- mkdirSync5(ctx.paths.communityDir, { recursive: true });
21727
- }
21728
- try {
21729
- await communityAdd({
21730
- url,
21731
- communityDir: ctx.paths.communityDir,
21732
- logger: ctx.logger
21733
- });
21734
- ctx.logger.info(`shared community DB subscribed: ${url} \u2192 community/${handle}/`);
21735
- } catch (err) {
21736
- ctx.logger.warn(
21737
- `shared community DB subscription failed: ${err instanceof Error ? err.message : String(err)}`
21738
- );
21739
- return;
21740
- }
21741
- } else {
21742
- ctx.logger.info(`shared community DB already subscribed: community/${handle}/`);
21743
- }
21744
- if (existsSync10(ctx.paths.communityDir)) {
21745
- rebuildAll(db);
21746
- if (existsSync10(ctx.paths.entriesDir)) {
21747
- scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21748
- }
21749
- for (const entry of readdirSync6(ctx.paths.communityDir, { withFileTypes: true })) {
21750
- if (!entry.isDirectory()) continue;
21751
- const source = `community/${entry.name}`;
21752
- const root = join12(ctx.paths.communityDir, entry.name, "entries");
21753
- if (!existsSync10(root)) continue;
21754
- const result = scanSource({ db, source, entriesRoot: root });
21755
- ctx.logger.info(`indexed ${source}: +${result.added}`);
21756
- }
21757
- }
21758
- }
21759
21503
  function runUninstall(ctx, opts) {
21760
21504
  const cliScriptPath = process.argv[1];
21761
21505
  if (!cliScriptPath) {
@@ -21763,7 +21507,7 @@ function runUninstall(ctx, opts) {
21763
21507
  process.exit(1);
21764
21508
  }
21765
21509
  const result = uninstallClaudeIntegration({
21766
- claudeDir: join12(ctx.userHome, ".claude"),
21510
+ claudeDir: join10(ctx.userHome, ".claude"),
21767
21511
  cliScriptPath,
21768
21512
  nodePath: process.execPath,
21769
21513
  dryRun: opts.dryRun,
@@ -21800,29 +21544,29 @@ function reportInstallResult(ctx, result, dryRun) {
21800
21544
  }
21801
21545
 
21802
21546
  // src/commands/indexCmd.ts
21803
- import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync6 } from "node:fs";
21804
- import { dirname as dirname7, join as join13 } from "node:path";
21547
+ import { existsSync as existsSync9, readdirSync as readdirSync5, mkdirSync as mkdirSync5 } from "node:fs";
21548
+ import { dirname as dirname7, join as join11 } from "node:path";
21805
21549
  function runIndex(ctx, opts) {
21806
21550
  const dbDir = dirname7(ctx.paths.dbPath);
21807
- if (!existsSync11(dbDir)) mkdirSync6(dbDir, { recursive: true });
21551
+ if (!existsSync9(dbDir)) mkdirSync5(dbDir, { recursive: true });
21808
21552
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
21809
21553
  try {
21810
21554
  if (opts.full) {
21811
21555
  ctx.logger.info("full rebuild: DELETE FROM entries");
21812
21556
  rebuildAll(db);
21813
21557
  }
21814
- if (existsSync11(ctx.paths.entriesDir)) {
21558
+ if (existsSync9(ctx.paths.entriesDir)) {
21815
21559
  const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
21816
21560
  ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
21817
21561
  } else {
21818
21562
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
21819
21563
  }
21820
- if (existsSync11(ctx.paths.communityDir)) {
21821
- for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
21564
+ if (existsSync9(ctx.paths.communityDir)) {
21565
+ for (const entry of readdirSync5(ctx.paths.communityDir, { withFileTypes: true })) {
21822
21566
  if (!entry.isDirectory()) continue;
21823
21567
  const source = `community/${entry.name}`;
21824
- const root = join13(ctx.paths.communityDir, entry.name, "entries");
21825
- if (!existsSync11(root)) continue;
21568
+ const root = join11(ctx.paths.communityDir, entry.name, "entries");
21569
+ if (!existsSync9(root)) continue;
21826
21570
  const result = scanSource({ db, source, entriesRoot: root });
21827
21571
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
21828
21572
  }
@@ -22596,11 +22340,11 @@ var serve = (options2, listeningListener) => {
22596
22340
 
22597
22341
  // ../web/dist/context.js
22598
22342
  import { homedir as homedir2 } from "node:os";
22599
- import { join as join14 } from "node:path";
22343
+ import { join as join12 } from "node:path";
22600
22344
  function buildWebContext(overrides = {}) {
22601
22345
  const userHome = overrides.userHome ?? homedir2();
22602
22346
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
22603
- const userConfigPath = join14(userHome, ".caveatrc.json");
22347
+ const userConfigPath = join12(userHome, ".caveatrc.json");
22604
22348
  const logger = overrides.logger ?? stderrLogger;
22605
22349
  const config3 = loadConfig(userConfigPath);
22606
22350
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -24705,7 +24449,7 @@ ul.entries li { padding: 0.5rem 0; border-bottom: 1px solid #eee; }
24705
24449
  ul.entries a.title { font-weight: 600; color: #0a5fff; text-decoration: none; }
24706
24450
  ul.entries a.title:hover { text-decoration: underline; }
24707
24451
  ul.entries .meta { font-size: 0.85rem; color: #666; margin-top: 0.2rem; }
24708
- ul.entries .meta .badge {
24452
+ .badge {
24709
24453
  display: inline-block;
24710
24454
  padding: 0 0.4rem;
24711
24455
  margin-right: 0.3rem;
@@ -24713,10 +24457,12 @@ ul.entries .meta .badge {
24713
24457
  background: #f0f0f0;
24714
24458
  font-size: 0.8rem;
24715
24459
  }
24716
- ul.entries .meta .badge.confirmed { background: #d7f4dc; color: #16703a; }
24717
- ul.entries .meta .badge.reproduced { background: #e6eeff; color: #234ea3; }
24718
- ul.entries .meta .badge.tentative { background: #fff4d1; color: #8a5a00; }
24719
- ul.entries .meta .badge.impossible { background: #ffd9d9; color: #8a1a1a; }
24460
+ .badge.confirmed { background: #d7f4dc; color: #16703a; }
24461
+ .badge.reproduced { background: #e6eeff; color: #234ea3; }
24462
+ .badge.tentative { background: #fff4d1; color: #8a5a00; }
24463
+ .badge.impossible { background: #ffd9d9; color: #8a1a1a; }
24464
+ .badge.public { background: #eef2f5; color: #5a6470; }
24465
+ .badge.private { background: #fde7e7; color: #a02525; font-weight: 600; }
24720
24466
  ul.entries .excerpt { margin-top: 0.3rem; color: #444; font-size: 0.92rem; }
24721
24467
  article h2 { border-bottom: 1px solid #eee; padding-bottom: 0.2rem; margin-top: 1.6rem; }
24722
24468
  article pre {
@@ -24791,6 +24537,7 @@ function createIndexRoute(ctx) {
24791
24537
  const q2 = c3.req.query("q")?.trim() ?? "";
24792
24538
  const sourceParam = c3.req.query("source");
24793
24539
  const tagParam = c3.req.query("tag");
24540
+ const visibilityParam = c3.req.query("visibility");
24794
24541
  const filters = {};
24795
24542
  if (sourceParam === "own" || sourceParam === "community" || sourceParam === "all") {
24796
24543
  filters.source = sourceParam;
@@ -24798,17 +24545,22 @@ function createIndexRoute(ctx) {
24798
24545
  if (tagParam) {
24799
24546
  filters.tags = [tagParam];
24800
24547
  }
24801
- const results = q2 ? search(ctx.db, { query: q2, filters, limit: 50 }) : listRecent(ctx.db, 50);
24548
+ if (visibilityParam === "public" || visibilityParam === "private") {
24549
+ filters.visibility = visibilityParam;
24550
+ }
24551
+ const results = search(ctx.db, { query: q2, filters, limit: 50 });
24802
24552
  const rows = results.map((r2) => {
24803
24553
  const envEntries = Object.entries(r2.environment ?? {});
24804
24554
  const envSummary = envEntries.slice(0, 3).map(([k2, v]) => `${escapeHtml(k2)}:${escapeHtml(String(v))}`).join(" ");
24805
24555
  const hrefSource = r2.source === "own" ? "" : `?source=${encodeURIComponent(r2.source)}`;
24556
+ const visibilityBadge = r2.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
24806
24557
  return `
24807
24558
  <li>
24808
24559
  <a class="title" href="/g/${encodeURIComponent(r2.id)}${hrefSource}">${escapeHtml(r2.title)}</a>
24809
24560
  <div class="meta">
24810
24561
  <span class="badge ${escapeHtml(r2.confidence ?? "")}">${escapeHtml(r2.confidence ?? "")}</span>
24811
24562
  <span class="badge">${escapeHtml(r2.source)}</span>
24563
+ ${visibilityBadge}
24812
24564
  ${envSummary ? `<span>${envSummary}</span>` : ""}
24813
24565
  </div>
24814
24566
  ${r2.symptomExcerpt ? `<div class="excerpt">${escapeHtml(r2.symptomExcerpt)}</div>` : ""}
@@ -24822,9 +24574,18 @@ function createIndexRoute(ctx) {
24822
24574
  <option value="own" ${sourceParam === "own" ? "selected" : ""}>own</option>
24823
24575
  <option value="community" ${sourceParam === "community" ? "selected" : ""}>community</option>
24824
24576
  </select>
24577
+ <select name="visibility" title="visibility filter \u2014 useful when sharing screen">
24578
+ <option value="" ${!visibilityParam ? "selected" : ""}>any visibility</option>
24579
+ <option value="public" ${visibilityParam === "public" ? "selected" : ""}>public only</option>
24580
+ <option value="private" ${visibilityParam === "private" ? "selected" : ""}>private only</option>
24581
+ </select>
24825
24582
  <button type="submit">search</button>
24826
24583
  </form>
24827
- ${results.length > 0 ? `<ul class="entries">${rows}</ul>` : `<div class="empty">${q2 ? `no results for "${escapeHtml(q2)}"` : "no entries yet \u2014 run <code>caveat index</code> after adding md files"}</div>`}`;
24584
+ ${results.length > 0 ? `<ul class="entries">${rows}</ul>` : (() => {
24585
+ if (q2) return `<div class="empty">no results for "${escapeHtml(q2)}"</div>`;
24586
+ const hasFilter = !!(sourceParam || tagParam || visibilityParam);
24587
+ return hasFilter ? `<div class="empty">no entries match the active filters</div>` : `<div class="empty">no entries yet \u2014 run <code>caveat index</code> after adding md files</div>`;
24588
+ })()}`;
24828
24589
  const title = q2 ? `search: ${q2} \xB7 Caveat` : "Caveat";
24829
24590
  return c3.html(layout(title, body));
24830
24591
  });
@@ -30290,10 +30051,11 @@ function createDetailRoute(ctx) {
30290
30051
  }
30291
30052
  const fm = entry.frontmatter;
30292
30053
  const envRows = Object.entries(fm.environment ?? {}).map(([k2, v]) => `<dt>${escapeHtml(k2)}</dt><dd>${escapeHtml(String(v))}</dd>`).join("");
30054
+ const visibilityBadge = fm.visibility === "private" ? `<span class="badge private" title="local-only \u2014 never pushed to community DB">\u{1F512} private</span>` : `<span class="badge public">public</span>`;
30293
30055
  const metaRows = [
30294
30056
  `<dt>id</dt><dd><code>${escapeHtml(fm.id)}</code></dd>`,
30295
30057
  `<dt>source</dt><dd>${escapeHtml(entry.source)}</dd>`,
30296
- `<dt>visibility</dt><dd>${escapeHtml(fm.visibility)}</dd>`,
30058
+ `<dt>visibility</dt><dd>${visibilityBadge}</dd>`,
30297
30059
  `<dt>confidence</dt><dd><span class="badge ${escapeHtml(fm.confidence)}">${escapeHtml(fm.confidence)}</span></dd>`
30298
30060
  ];
30299
30061
  if (fm.outcome) {
@@ -30327,14 +30089,14 @@ function createDetailRoute(ctx) {
30327
30089
  }
30328
30090
 
30329
30091
  // ../web/dist/routes/community.js
30330
- import { existsSync as existsSync12, readdirSync as readdirSync8, statSync as statSync4 } from "node:fs";
30331
- import { join as join15 } from "node:path";
30092
+ import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
30093
+ import { join as join13 } from "node:path";
30332
30094
  function listCommunity(communityDir, db) {
30333
- if (!existsSync12(communityDir)) return [];
30095
+ if (!existsSync10(communityDir)) return [];
30334
30096
  const handles = [];
30335
- for (const entry of readdirSync8(communityDir, { withFileTypes: true })) {
30097
+ for (const entry of readdirSync6(communityDir, { withFileTypes: true })) {
30336
30098
  if (!entry.isDirectory()) continue;
30337
- const handlePath = join15(communityDir, entry.name);
30099
+ const handlePath = join13(communityDir, entry.name);
30338
30100
  const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
30339
30101
  handles.push({
30340
30102
  handle: entry.name,
@@ -30351,7 +30113,7 @@ function createCommunityRoute(ctx) {
30351
30113
  const list2 = handles.map((h2) => {
30352
30114
  let mtime = "";
30353
30115
  try {
30354
- mtime = statSync4(h2.path).mtime.toISOString().slice(0, 10);
30116
+ mtime = statSync5(h2.path).mtime.toISOString().slice(0, 10);
30355
30117
  } catch {
30356
30118
  mtime = "";
30357
30119
  }
@@ -44632,11 +44394,11 @@ var StdioServerTransport = class {
44632
44394
 
44633
44395
  // ../mcp/dist/context.js
44634
44396
  import { homedir as homedir3 } from "node:os";
44635
- import { join as join16 } from "node:path";
44397
+ import { join as join14 } from "node:path";
44636
44398
  function buildMcpContext(overrides = {}) {
44637
44399
  const userHome = overrides.userHome ?? homedir3();
44638
44400
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
44639
- const userConfigPath = join16(userHome, ".caveatrc.json");
44401
+ const userConfigPath = join14(userHome, ".caveatrc.json");
44640
44402
  const logger = overrides.logger ?? stderrLogger;
44641
44403
  const config3 = loadConfig(userConfigPath);
44642
44404
  const paths = resolvePaths(caveatHome, config3.knowledgeRepo, userHome);
@@ -44745,45 +44507,37 @@ function handleListRecent(ctx, args) {
44745
44507
  }
44746
44508
 
44747
44509
  // ../mcp/dist/tools/pull.js
44510
+ import { existsSync as existsSync11, readdirSync as readdirSync7 } from "node:fs";
44511
+ import { join as join15 } from "node:path";
44748
44512
  var pullInputShape = {};
44749
44513
  async function handlePull(ctx, _args = {}) {
44750
- const result = await pullShared({
44751
- communityDir: ctx.paths.communityDir,
44752
- entriesDir: ctx.paths.entriesDir,
44753
- db: ctx.db,
44754
- logger: ctx.logger
44755
- });
44756
- return {
44757
- pulled: result.pulled,
44758
- indexed: result.indexed.map((i2) => ({
44759
- source: i2.source,
44760
- added: i2.added,
44761
- updated: i2.updated,
44762
- deleted: i2.deleted
44763
- }))
44764
- };
44765
- }
44766
-
44767
- // ../mcp/dist/tools/push.js
44768
- var pushInputShape = {
44769
- id: external_exports.string().min(1).describe("Entry id (frontmatter.id) to push to the shared community DB"),
44770
- dry_run: external_exports.boolean().optional().describe("If true, compute the plan but do not modify GitHub or local git state")
44771
- };
44772
- async function handlePush(ctx, args) {
44773
- const result = await pushEntry({
44774
- entriesDir: ctx.paths.entriesDir,
44775
- caveatHome: ctx.caveatHome,
44776
- sharedRepoUrl: ctx.config.sharedRepo,
44777
- id: args.id,
44778
- dryRun: args.dry_run ?? false,
44779
- logger: ctx.logger
44780
- });
44781
- return {
44782
- status: result.status,
44783
- detail: result.detail,
44784
- pr_url: result.prUrl,
44785
- planned_steps: result.plannedSteps
44786
- };
44514
+ const pulled = [];
44515
+ const indexed = [];
44516
+ if (existsSync11(ctx.paths.communityDir)) {
44517
+ const results = await communityPull({
44518
+ communityDir: ctx.paths.communityDir,
44519
+ logger: ctx.logger
44520
+ });
44521
+ for (const r2 of results) {
44522
+ pulled.push({ handle: r2.handle, status: r2.status, message: r2.message });
44523
+ }
44524
+ }
44525
+ rebuildAll(ctx.db);
44526
+ if (existsSync11(ctx.paths.entriesDir)) {
44527
+ const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
44528
+ indexed.push({ source: "own", ...own });
44529
+ }
44530
+ if (existsSync11(ctx.paths.communityDir)) {
44531
+ for (const entry of readdirSync7(ctx.paths.communityDir, { withFileTypes: true })) {
44532
+ if (!entry.isDirectory()) continue;
44533
+ const source = `community/${entry.name}`;
44534
+ const root = join15(ctx.paths.communityDir, entry.name, "entries");
44535
+ if (!existsSync11(root)) continue;
44536
+ const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
44537
+ indexed.push({ source, ...scan });
44538
+ }
44539
+ }
44540
+ return { pulled, indexed };
44787
44541
  }
44788
44542
 
44789
44543
  // ../mcp/dist/registerTools.js
@@ -44847,20 +44601,11 @@ function registerAllTools(server, ctx) {
44847
44601
  "caveat_pull",
44848
44602
  {
44849
44603
  title: "caveat_pull",
44850
- description: "git-pull the subscribed community caveat repos (the shared knowledge DB + any user-added remotes) and re-index. Call when: (a) the user explicitly asks about others' knowledge on a topic, or (b) caveat_search returned empty for a query that feels like it should have hits and the DB might be stale. Do NOT call reflexively at session start \u2014 it is cheap but not free, and stale-by-minutes is acceptable. Safe and idempotent.",
44604
+ description: "git-pull every subscribed community caveat repo (added via `caveat community add`) and re-index. Call when: (a) the user explicitly asks about others' knowledge on a topic, or (b) caveat_search returned empty for a query that feels like it should have hits and a subscribed repo might be stale. Do NOT call reflexively at session start \u2014 it is cheap but not free, and stale-by-minutes is acceptable. Safe and idempotent.",
44851
44605
  inputSchema: pullInputShape
44852
44606
  },
44853
44607
  async (args) => jsonResult(await handlePull(ctx, args))
44854
44608
  );
44855
- server.registerTool(
44856
- "caveat_push",
44857
- {
44858
- title: "caveat_push",
44859
- description: "Contribute a user-owned caveat to the public shared DB via fork + PR on GitHub. This is a PUBLIC, externally-visible action (the PR appears on GitHub and is hard to fully retract). Confirm with the user before calling without dry_run \u2014 or call with dry_run=true first to show the plan. Only push entries that document a trap genuinely reusable by others (not project-specific ties, not duplicated by existing community entries). Entries with `visibility: private` are rejected (`status=visibility-private`) \u2014 the user has declared them local-only. Requires `gh` CLI authenticated; returns `status=gh-missing` / `gh-unauthed` on failure.",
44860
- inputSchema: pushInputShape
44861
- },
44862
- async (args) => jsonResult(await handlePush(ctx, args))
44863
- );
44864
44609
  }
44865
44610
 
44866
44611
  // ../mcp/dist/server.js
@@ -44953,77 +44698,46 @@ async function runHook(name) {
44953
44698
  }
44954
44699
 
44955
44700
  // src/commands/pull.ts
44956
- import { existsSync as existsSync13 } from "node:fs";
44701
+ import { existsSync as existsSync12, readdirSync as readdirSync8 } from "node:fs";
44702
+ import { join as join16 } from "node:path";
44957
44703
  async function runPull(ctx) {
44958
- if (!existsSync13(ctx.paths.communityDir)) {
44704
+ if (!existsSync12(ctx.paths.communityDir)) {
44959
44705
  ctx.logger.info(
44960
- "no community repos yet \u2014 did you run `caveat init`? You can also `caveat community add <url>` manually."
44706
+ "no community repos yet \u2014 add one with `caveat community add <github-url>`."
44961
44707
  );
44962
44708
  return;
44963
44709
  }
44710
+ const pulls = await communityPull({
44711
+ communityDir: ctx.paths.communityDir,
44712
+ logger: ctx.logger
44713
+ });
44714
+ for (const p2 of pulls) {
44715
+ if (p2.status === "ok") {
44716
+ ctx.logger.info(`${p2.handle}: pulled`);
44717
+ } else {
44718
+ ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
44719
+ }
44720
+ }
44964
44721
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44965
44722
  try {
44966
- const result = await pullShared({
44967
- communityDir: ctx.paths.communityDir,
44968
- entriesDir: ctx.paths.entriesDir,
44969
- db,
44970
- logger: ctx.logger
44971
- });
44972
- for (const p2 of result.pulled) {
44973
- if (p2.status === "ok") {
44974
- ctx.logger.info(`${p2.handle}: pulled`);
44975
- } else {
44976
- ctx.logger.warn(`${p2.handle}: FAILED \u2014 ${p2.message ?? "unknown"}`);
44977
- }
44723
+ rebuildAll(db);
44724
+ if (existsSync12(ctx.paths.entriesDir)) {
44725
+ const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
44726
+ ctx.logger.info(`own: +${own.added}`);
44978
44727
  }
44979
- for (const i2 of result.indexed) {
44980
- ctx.logger.info(`${i2.source}: +${i2.added}`);
44728
+ for (const entry of readdirSync8(ctx.paths.communityDir, { withFileTypes: true })) {
44729
+ if (!entry.isDirectory()) continue;
44730
+ const source = `community/${entry.name}`;
44731
+ const root = join16(ctx.paths.communityDir, entry.name, "entries");
44732
+ if (!existsSync12(root)) continue;
44733
+ const scan = scanSource({ db, source, entriesRoot: root });
44734
+ ctx.logger.info(`${source}: +${scan.added}`);
44981
44735
  }
44982
44736
  } finally {
44983
44737
  db.close();
44984
44738
  }
44985
44739
  }
44986
44740
 
44987
- // src/commands/push.ts
44988
- async function runPush(ctx, opts) {
44989
- const result = await pushEntry({
44990
- entriesDir: ctx.paths.entriesDir,
44991
- caveatHome: ctx.caveatHome,
44992
- sharedRepoUrl: ctx.config.sharedRepo,
44993
- id: opts.id,
44994
- dryRun: opts.dryRun,
44995
- logger: ctx.logger
44996
- });
44997
- switch (result.status) {
44998
- case "ok":
44999
- ctx.logger.info(`PR opened: ${result.prUrl ?? ""}`);
45000
- ctx.logger.info(
45001
- "Once merged, other subscribers will see your entry after their next `caveat pull`."
45002
- );
45003
- break;
45004
- case "dry-run":
45005
- for (const [i2, step] of (result.plannedSteps ?? []).entries()) {
45006
- ctx.logger.info(`[dry-run] ${i2 + 1}. ${step}`);
45007
- }
45008
- break;
45009
- case "not-found":
45010
- ctx.logger.error(
45011
- `${result.detail}. List entries with \`caveat list\` or write + index first.`
45012
- );
45013
- process.exitCode = 1;
45014
- break;
45015
- case "gh-missing":
45016
- case "gh-unauthed":
45017
- ctx.logger.error(result.detail ?? "gh CLI issue");
45018
- process.exitCode = 1;
45019
- break;
45020
- case "failed":
45021
- ctx.logger.error(result.detail ?? "unknown failure");
45022
- process.exitCode = 1;
45023
- break;
45024
- }
45025
- }
45026
-
45027
44741
  // src/commands/community.ts
45028
44742
  async function runCommunityAdd(ctx, url) {
45029
44743
  try {
@@ -45073,17 +44787,46 @@ function runCommunityList(ctx) {
45073
44787
  db.close();
45074
44788
  }
45075
44789
  }
44790
+ function runCommunityRemove(ctx, handle, opts) {
44791
+ const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
44792
+ try {
44793
+ const result = communityRemove({
44794
+ communityDir: ctx.paths.communityDir,
44795
+ handle,
44796
+ db,
44797
+ dryRun: opts.dryRun,
44798
+ logger: ctx.logger
44799
+ });
44800
+ if (!result.dirExisted && result.rowCount === 0) {
44801
+ ctx.logger.warn(`no community subscription named "${handle}" \u2014 nothing to remove`);
44802
+ return;
44803
+ }
44804
+ const prefix = result.dryRun ? "[dry-run] would " : "";
44805
+ if (result.dirExisted) {
44806
+ ctx.logger.info(`${prefix}remove dir: ${result.path}`);
44807
+ }
44808
+ if (result.rowCount > 0) {
44809
+ ctx.logger.info(
44810
+ `${prefix}delete ${result.rowCount} db row(s) for source=community/${handle}`
44811
+ );
44812
+ }
44813
+ } catch (err) {
44814
+ ctx.logger.error(err instanceof Error ? err.message : String(err));
44815
+ process.exitCode = 1;
44816
+ } finally {
44817
+ db.close();
44818
+ }
44819
+ }
45076
44820
 
45077
44821
  // src/index.ts
45078
44822
  var program = new Command();
45079
44823
  program.name("caveat").description("External spec gotcha knowledge base CLI").version(CAVEAT_VERSION);
45080
44824
  program.command("init").description(
45081
- "Initialize ~/.caveatrc.json, ~/.caveat/, subscribe to the shared community DB, and register Claude Code integration"
45082
- ).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) => {
44825
+ "Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration. Add knowledge sources later with `caveat community add <github-url>`."
44826
+ ).option("--skip-claude", "skip Claude Code MCP + hook registration", false).option("--dry-run", "show planned changes without writing", false).action(async (opts) => {
45083
44827
  const ctx = buildContext(stdoutLogger);
45084
44828
  await runInit(ctx, {
45085
44829
  skipClaude: opts.skipClaude,
45086
- skipShared: opts.skipShared,
45087
44830
  dryRun: opts.dryRun
45088
44831
  });
45089
44832
  });
@@ -45119,15 +44862,11 @@ program.command("stats").description("Show aggregate stats").action(() => {
45119
44862
  runStats(ctx);
45120
44863
  });
45121
44864
  program.command("pull").description(
45122
- "Refresh all subscribed community repos (incl. shared DB) and re-index. Use this to receive contributions others have merged."
44865
+ "git-pull every subscribed community repo and re-index. Use this to receive updates from group/teammate repos."
45123
44866
  ).action(async () => {
45124
44867
  const ctx = buildContext(stdoutLogger);
45125
44868
  await runPull(ctx);
45126
44869
  });
45127
- 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) => {
45128
- const ctx = buildContext(stdoutLogger);
45129
- await runPush(ctx, { id, dryRun: opts.dryRun });
45130
- });
45131
44870
  program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
45132
44871
  runServe({ port: opts.port });
45133
44872
  });
@@ -45150,6 +44889,10 @@ community.command("list").description("List imported community repos with entry
45150
44889
  const ctx = buildContext(stdoutLogger);
45151
44890
  runCommunityList(ctx);
45152
44891
  });
44892
+ community.command("remove <handle>").description("Unsubscribe from a community repo: delete community/<handle>/ and purge its DB rows").option("--dry-run", "show what would be removed without touching disk or db", false).action((handle, opts) => {
44893
+ const ctx = buildContext(stdoutLogger);
44894
+ runCommunityRemove(ctx, handle, { dryRun: opts.dryRun });
44895
+ });
45153
44896
  program.parseAsync(process.argv).catch((err) => {
45154
44897
  const msg = err instanceof Error ? err.message : String(err);
45155
44898
  process.stderr.write(`[caveat:error] ${msg}