caveat-cli 0.2.0 → 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 +385 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
9764
|
+
function resolveComponent(base2, relative3, options2, skipNormalization) {
|
|
9765
9765
|
const target = {};
|
|
9766
9766
|
if (!skipNormalization) {
|
|
9767
9767
|
base2 = parse4(serialize(base2, options2), options2);
|
|
9768
|
-
|
|
9768
|
+
relative3 = parse4(serialize(relative3, options2), options2);
|
|
9769
9769
|
}
|
|
9770
9770
|
options2 = options2 || {};
|
|
9771
|
-
if (!options2.tolerant &&
|
|
9772
|
-
target.scheme =
|
|
9773
|
-
target.userinfo =
|
|
9774
|
-
target.host =
|
|
9775
|
-
target.port =
|
|
9776
|
-
target.path = removeDotSegments(
|
|
9777
|
-
target.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 (
|
|
9780
|
-
target.userinfo =
|
|
9781
|
-
target.host =
|
|
9782
|
-
target.port =
|
|
9783
|
-
target.path = removeDotSegments(
|
|
9784
|
-
target.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 (!
|
|
9786
|
+
if (!relative3.path) {
|
|
9787
9787
|
target.path = base2.path;
|
|
9788
|
-
if (
|
|
9789
|
-
target.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 (
|
|
9795
|
-
target.path = removeDotSegments(
|
|
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 = "/" +
|
|
9798
|
+
target.path = "/" + relative3.path;
|
|
9799
9799
|
} else if (!base2.path) {
|
|
9800
|
-
target.path =
|
|
9800
|
+
target.path = relative3.path;
|
|
9801
9801
|
} else {
|
|
9802
|
-
target.path = base2.path.slice(0, base2.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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 } 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
|
|
@@ -21403,7 +21405,21 @@ function uninstallClaudeIntegration(opts) {
|
|
|
21403
21405
|
}
|
|
21404
21406
|
|
|
21405
21407
|
// src/commands/init.ts
|
|
21406
|
-
|
|
21408
|
+
var KNOWLEDGE_GITIGNORE = [
|
|
21409
|
+
"# Never commit entries flagged private (visibility: private is enforced by the",
|
|
21410
|
+
"# pre-commit gate, but this is a filename-level backup guard).",
|
|
21411
|
+
"*.private.md",
|
|
21412
|
+
"",
|
|
21413
|
+
"# Obsidian per-user config: workspace layout, theme, plugin state, cache.",
|
|
21414
|
+
".obsidian/",
|
|
21415
|
+
"",
|
|
21416
|
+
"# community/ is a local cache of shallow-cloned third-party caveat repos",
|
|
21417
|
+
"# (populated by `caveat community add`). Each contains its own .git and is not",
|
|
21418
|
+
"# part of this repo's tracked knowledge.",
|
|
21419
|
+
"community/",
|
|
21420
|
+
""
|
|
21421
|
+
].join("\n");
|
|
21422
|
+
async function runInit(ctx, opts = { skipClaude: false, skipShared: false, dryRun: false }) {
|
|
21407
21423
|
ensureUserConfig(ctx.userConfigPath);
|
|
21408
21424
|
ctx.logger.info(`user config: ${ctx.userConfigPath}`);
|
|
21409
21425
|
if (!existsSync8(ctx.paths.knowledgeRepo)) {
|
|
@@ -21413,8 +21429,23 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21413
21429
|
} else {
|
|
21414
21430
|
ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
|
|
21415
21431
|
}
|
|
21432
|
+
const gitignorePath = join9(ctx.paths.knowledgeRepo, ".gitignore");
|
|
21433
|
+
if (!existsSync8(gitignorePath)) {
|
|
21434
|
+
writeFileSync5(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
21435
|
+
ctx.logger.info(`.gitignore created: ${gitignorePath}`);
|
|
21436
|
+
}
|
|
21416
21437
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
21417
|
-
|
|
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
|
+
}
|
|
21418
21449
|
ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
|
|
21419
21450
|
if (opts.skipClaude) {
|
|
21420
21451
|
ctx.logger.info("Claude Code integration skipped (--skip-claude)");
|
|
@@ -21434,6 +21465,52 @@ function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
|
|
|
21434
21465
|
});
|
|
21435
21466
|
reportInstallResult(ctx, result, opts.dryRun);
|
|
21436
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
|
+
}
|
|
21437
21514
|
function runUninstall(ctx, opts) {
|
|
21438
21515
|
const cliScriptPath = process.argv[1];
|
|
21439
21516
|
if (!cliScriptPath) {
|
|
@@ -21478,7 +21555,7 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
21478
21555
|
}
|
|
21479
21556
|
|
|
21480
21557
|
// src/commands/indexCmd.ts
|
|
21481
|
-
import { existsSync as existsSync9, readdirSync as
|
|
21558
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, mkdirSync as mkdirSync5 } from "node:fs";
|
|
21482
21559
|
import { dirname as dirname5, join as join10 } from "node:path";
|
|
21483
21560
|
function runIndex(ctx, opts) {
|
|
21484
21561
|
const dbDir = dirname5(ctx.paths.dbPath);
|
|
@@ -21496,7 +21573,7 @@ function runIndex(ctx, opts) {
|
|
|
21496
21573
|
ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
|
|
21497
21574
|
}
|
|
21498
21575
|
if (existsSync9(ctx.paths.communityDir)) {
|
|
21499
|
-
for (const entry of
|
|
21576
|
+
for (const entry of readdirSync5(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
21500
21577
|
if (!entry.isDirectory()) continue;
|
|
21501
21578
|
const source = `community/${entry.name}`;
|
|
21502
21579
|
const root = join10(ctx.paths.communityDir, entry.name, "entries");
|
|
@@ -30005,12 +30082,12 @@ function createDetailRoute(ctx) {
|
|
|
30005
30082
|
}
|
|
30006
30083
|
|
|
30007
30084
|
// ../web/dist/routes/community.js
|
|
30008
|
-
import { existsSync as existsSync10, readdirSync as
|
|
30085
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
|
|
30009
30086
|
import { join as join12 } from "node:path";
|
|
30010
30087
|
function listCommunity(communityDir, db) {
|
|
30011
30088
|
if (!existsSync10(communityDir)) return [];
|
|
30012
30089
|
const handles = [];
|
|
30013
|
-
for (const entry of
|
|
30090
|
+
for (const entry of readdirSync6(communityDir, { withFileTypes: true })) {
|
|
30014
30091
|
if (!entry.isDirectory()) continue;
|
|
30015
30092
|
const handlePath = join12(communityDir, entry.name);
|
|
30016
30093
|
const countRow = db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
@@ -44629,6 +44706,263 @@ async function runHook(name) {
|
|
|
44629
44706
|
process.exit(0);
|
|
44630
44707
|
}
|
|
44631
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
|
+
|
|
44632
44966
|
// src/commands/community.ts
|
|
44633
44967
|
async function runCommunityAdd(ctx, url) {
|
|
44634
44968
|
try {
|
|
@@ -44681,12 +45015,16 @@ function runCommunityList(ctx) {
|
|
|
44681
45015
|
|
|
44682
45016
|
// src/index.ts
|
|
44683
45017
|
var program = new Command();
|
|
44684
|
-
program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.
|
|
45018
|
+
program.name("caveat").description("External spec gotcha knowledge base CLI").version("0.3.0");
|
|
44685
45019
|
program.command("init").description(
|
|
44686
|
-
"Initialize ~/.caveatrc.json, ~/.caveat/, and register Claude Code integration"
|
|
44687
|
-
).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) => {
|
|
44688
45022
|
const ctx = buildContext(stdoutLogger);
|
|
44689
|
-
runInit(ctx, {
|
|
45023
|
+
await runInit(ctx, {
|
|
45024
|
+
skipClaude: opts.skipClaude,
|
|
45025
|
+
skipShared: opts.skipShared,
|
|
45026
|
+
dryRun: opts.dryRun
|
|
45027
|
+
});
|
|
44690
45028
|
});
|
|
44691
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) => {
|
|
44692
45030
|
const ctx = buildContext(stdoutLogger);
|
|
@@ -44719,6 +45057,16 @@ program.command("stats").description("Show aggregate stats").action(() => {
|
|
|
44719
45057
|
const ctx = buildContext(stdoutLogger);
|
|
44720
45058
|
runStats(ctx);
|
|
44721
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
|
+
});
|
|
44722
45070
|
program.command("serve").description("Start the read-only web share portal").option("--port <n>", "port number", (v) => Number(v), 4242).action((opts) => {
|
|
44723
45071
|
runServe({ port: opts.port });
|
|
44724
45072
|
});
|