caveat-cli 0.14.10 → 0.15.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/{chunk-ECC5LJZF.js → chunk-Z2XLYCR3.js} +641 -82
- package/dist/chunk-Z2XLYCR3.js.map +1 -0
- package/dist/index.js +318 -80
- package/dist/index.js.map +1 -1
- package/dist/{server-YQ4DJ2H7.js → server-DCE3ODHI.js} +2 -2
- package/package.json +12 -12
- package/dist/chunk-ECC5LJZF.js.map +0 -1
- /package/dist/{server-YQ4DJ2H7.js.map → server-DCE3ODHI.js.map} +0 -0
|
@@ -9071,6 +9071,141 @@ function* walkMarkdown(root) {
|
|
|
9071
9071
|
}
|
|
9072
9072
|
}
|
|
9073
9073
|
|
|
9074
|
+
// ../../packages/core/dist/autoReindex.js
|
|
9075
|
+
import { createHash } from "node:crypto";
|
|
9076
|
+
import {
|
|
9077
|
+
existsSync as existsSync3,
|
|
9078
|
+
mkdirSync as mkdirSync2,
|
|
9079
|
+
readdirSync as readdirSync3,
|
|
9080
|
+
readFileSync as readFileSync3,
|
|
9081
|
+
renameSync,
|
|
9082
|
+
statSync as statSync2,
|
|
9083
|
+
unlinkSync,
|
|
9084
|
+
writeFileSync
|
|
9085
|
+
} from "node:fs";
|
|
9086
|
+
import { join as join3, relative as relative2 } from "node:path";
|
|
9087
|
+
function sourceRoots(paths) {
|
|
9088
|
+
const roots = [];
|
|
9089
|
+
if (existsSync3(paths.entriesDir)) roots.push({ source: "own", root: paths.entriesDir });
|
|
9090
|
+
if (!existsSync3(paths.communityDir)) return roots;
|
|
9091
|
+
for (const entry of requireDirectories(paths.communityDir)) {
|
|
9092
|
+
const root = join3(paths.communityDir, entry, "entries");
|
|
9093
|
+
if (existsSync3(root)) roots.push({ source: `community/${entry}`, root });
|
|
9094
|
+
}
|
|
9095
|
+
return roots;
|
|
9096
|
+
}
|
|
9097
|
+
function requireDirectories(root) {
|
|
9098
|
+
return readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
9099
|
+
}
|
|
9100
|
+
function computeEntriesDigest(paths) {
|
|
9101
|
+
const lines = [];
|
|
9102
|
+
for (const { source, root } of sourceRoots(paths)) {
|
|
9103
|
+
for (const filePath of walkMarkdown(root)) {
|
|
9104
|
+
const stat = statSync2(filePath);
|
|
9105
|
+
const rel = relative2(root, filePath).replace(/\\/g, "/");
|
|
9106
|
+
lines.push(`${source} ${rel} ${stat.mtimeMs} ${stat.size}`);
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
lines.sort();
|
|
9110
|
+
return {
|
|
9111
|
+
digest: createHash("sha256").update(lines.join("\n")).digest("hex"),
|
|
9112
|
+
fileCount: lines.length
|
|
9113
|
+
};
|
|
9114
|
+
}
|
|
9115
|
+
function indexDir(caveatHome) {
|
|
9116
|
+
return join3(caveatHome, "index");
|
|
9117
|
+
}
|
|
9118
|
+
function digestMarkerPath(caveatHome) {
|
|
9119
|
+
return join3(indexDir(caveatHome), ".entries-digest");
|
|
9120
|
+
}
|
|
9121
|
+
function readDigestMarker(caveatHome) {
|
|
9122
|
+
try {
|
|
9123
|
+
const value = JSON.parse(readFileSync3(digestMarkerPath(caveatHome), "utf-8"));
|
|
9124
|
+
if (value !== null && typeof value === "object" && typeof value.digest === "string" && typeof value.fileCount === "number" && typeof value.generatedAt === "string") return value;
|
|
9125
|
+
} catch {
|
|
9126
|
+
}
|
|
9127
|
+
return null;
|
|
9128
|
+
}
|
|
9129
|
+
function writeDigestMarker(caveatHome, value) {
|
|
9130
|
+
const dir = indexDir(caveatHome);
|
|
9131
|
+
mkdirSync2(dir, { recursive: true });
|
|
9132
|
+
const path = digestMarkerPath(caveatHome);
|
|
9133
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
9134
|
+
writeFileSync(temporary, JSON.stringify({ ...value, generatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
|
|
9135
|
+
renameSync(temporary, path);
|
|
9136
|
+
}
|
|
9137
|
+
function lockPath(caveatHome) {
|
|
9138
|
+
return join3(indexDir(caveatHome), ".reindex-lock");
|
|
9139
|
+
}
|
|
9140
|
+
function tryCreateLock(path) {
|
|
9141
|
+
try {
|
|
9142
|
+
writeFileSync(path, String(process.pid), { flag: "wx" });
|
|
9143
|
+
return true;
|
|
9144
|
+
} catch (err) {
|
|
9145
|
+
if (err.code === "EEXIST") return false;
|
|
9146
|
+
throw err;
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
function acquireReindexLock(caveatHome) {
|
|
9150
|
+
const dir = indexDir(caveatHome);
|
|
9151
|
+
mkdirSync2(dir, { recursive: true });
|
|
9152
|
+
const path = lockPath(caveatHome);
|
|
9153
|
+
if (tryCreateLock(path)) return { path };
|
|
9154
|
+
let pid;
|
|
9155
|
+
try {
|
|
9156
|
+
pid = Number.parseInt(readFileSync3(path, "utf-8").trim(), 10);
|
|
9157
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
9158
|
+
} catch (err) {
|
|
9159
|
+
if (err.code === "ENOENT") return tryCreateLock(path) ? { path } : null;
|
|
9160
|
+
return null;
|
|
9161
|
+
}
|
|
9162
|
+
try {
|
|
9163
|
+
process.kill(pid, 0);
|
|
9164
|
+
return null;
|
|
9165
|
+
} catch (err) {
|
|
9166
|
+
const code = err.code;
|
|
9167
|
+
if (code !== "ESRCH") return null;
|
|
9168
|
+
}
|
|
9169
|
+
try {
|
|
9170
|
+
unlinkSync(path);
|
|
9171
|
+
} catch (err) {
|
|
9172
|
+
if (err.code !== "ENOENT") throw err;
|
|
9173
|
+
}
|
|
9174
|
+
return tryCreateLock(path) ? { path } : null;
|
|
9175
|
+
}
|
|
9176
|
+
function releaseReindexLock(lock) {
|
|
9177
|
+
try {
|
|
9178
|
+
unlinkSync(lock.path);
|
|
9179
|
+
} catch (err) {
|
|
9180
|
+
if (err.code !== "ENOENT") throw err;
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
9183
|
+
function reindexAllSources(opts) {
|
|
9184
|
+
const { db, paths, logger } = opts;
|
|
9185
|
+
const perSource = {};
|
|
9186
|
+
if (existsSync3(paths.entriesDir)) {
|
|
9187
|
+
perSource.own = scanSource({ db, source: "own", entriesRoot: paths.entriesDir });
|
|
9188
|
+
} else {
|
|
9189
|
+
logger.warn(`entries dir not found; preserving own index rows: ${paths.entriesDir}`);
|
|
9190
|
+
}
|
|
9191
|
+
const presentCommunitySources = /* @__PURE__ */ new Set();
|
|
9192
|
+
if (existsSync3(paths.communityDir)) {
|
|
9193
|
+
for (const handle of requireDirectories(paths.communityDir)) {
|
|
9194
|
+
const source = `community/${handle}`;
|
|
9195
|
+
const root = join3(paths.communityDir, handle, "entries");
|
|
9196
|
+
presentCommunitySources.add(source);
|
|
9197
|
+
if (!existsSync3(root)) continue;
|
|
9198
|
+
perSource[source] = scanSource({ db, source, entriesRoot: root });
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
const rows = db.prepare("SELECT DISTINCT source FROM entries WHERE source LIKE 'community/%'").all();
|
|
9202
|
+
for (const { source } of rows) {
|
|
9203
|
+
if (presentCommunitySources.has(source)) continue;
|
|
9204
|
+
db.prepare("DELETE FROM entries WHERE source = ?").run(source);
|
|
9205
|
+
}
|
|
9206
|
+
return { perSource, fileCount: computeEntriesDigest(paths).fileCount };
|
|
9207
|
+
}
|
|
9208
|
+
|
|
9074
9209
|
// ../../packages/core/dist/repository.js
|
|
9075
9210
|
var SYMPTOM_EXCERPT_LENGTH = 200;
|
|
9076
9211
|
function sanitizeFtsQuery(raw) {
|
|
@@ -9154,25 +9289,25 @@ function toSearchResult(row) {
|
|
|
9154
9289
|
title: row.title,
|
|
9155
9290
|
symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH),
|
|
9156
9291
|
confidence: row.confidence,
|
|
9157
|
-
visibility: row.visibility ?? "
|
|
9292
|
+
visibility: row.visibility ?? "private",
|
|
9158
9293
|
environment: fm.environment ?? {}
|
|
9159
9294
|
};
|
|
9160
9295
|
}
|
|
9161
9296
|
|
|
9162
9297
|
// ../../packages/core/dist/paths.js
|
|
9163
|
-
import { existsSync as
|
|
9164
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
9298
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
9299
|
+
import { dirname as dirname2, isAbsolute, join as join4, resolve } from "node:path";
|
|
9165
9300
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9166
9301
|
function expandHome(p2, userHome) {
|
|
9167
9302
|
if (p2 === "~" || p2.startsWith("~/") || p2.startsWith("~\\")) {
|
|
9168
|
-
return
|
|
9303
|
+
return join4(userHome, p2.slice(1));
|
|
9169
9304
|
}
|
|
9170
9305
|
return p2;
|
|
9171
9306
|
}
|
|
9172
9307
|
function findCaveatHome(userHome) {
|
|
9173
9308
|
const fromEnv = process.env.CAVEAT_HOME;
|
|
9174
9309
|
if (fromEnv && fromEnv.length > 0) return fromEnv;
|
|
9175
|
-
return
|
|
9310
|
+
return join4(userHome, ".caveat");
|
|
9176
9311
|
}
|
|
9177
9312
|
function resolvePaths(caveatHome, knowledgeRepo, userHome) {
|
|
9178
9313
|
const expanded = expandHome(knowledgeRepo, userHome);
|
|
@@ -9180,32 +9315,39 @@ function resolvePaths(caveatHome, knowledgeRepo, userHome) {
|
|
|
9180
9315
|
return {
|
|
9181
9316
|
caveatHome,
|
|
9182
9317
|
knowledgeRepo: resolved,
|
|
9183
|
-
dbPath:
|
|
9184
|
-
entriesDir:
|
|
9318
|
+
dbPath: join4(caveatHome, "index", "caveat.db"),
|
|
9319
|
+
entriesDir: join4(resolved, "entries"),
|
|
9185
9320
|
// community/ lives at caveatHome level, NOT inside knowledgeRepo. Community
|
|
9186
9321
|
// clones are external knowledge caches — not semantically "owned" by the
|
|
9187
9322
|
// user — and they embed their own .git dirs which would otherwise nest
|
|
9188
9323
|
// inside the user's git-tracked knowledge repo.
|
|
9189
|
-
communityDir:
|
|
9324
|
+
communityDir: join4(caveatHome, "community"),
|
|
9325
|
+
publishMirrorDir: join4(caveatHome, "publish", "mirror")
|
|
9190
9326
|
};
|
|
9191
9327
|
}
|
|
9192
9328
|
|
|
9193
9329
|
// ../../packages/core/dist/config.js
|
|
9194
|
-
import { existsSync as
|
|
9330
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9195
9331
|
var DEFAULT_CONFIG = {
|
|
9196
9332
|
knowledgeRepo: "own",
|
|
9197
9333
|
semverKeys: ["driver", "cuda", "node"],
|
|
9198
|
-
communitySources: []
|
|
9334
|
+
communitySources: [],
|
|
9335
|
+
publishTarget: null
|
|
9199
9336
|
};
|
|
9200
9337
|
function loadConfig(userConfigPath) {
|
|
9201
|
-
const userCfg =
|
|
9338
|
+
const userCfg = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
|
|
9202
9339
|
return deepMerge(DEFAULT_CONFIG, userCfg);
|
|
9203
9340
|
}
|
|
9204
9341
|
function ensureUserConfig(userConfigPath) {
|
|
9205
|
-
if (!
|
|
9206
|
-
|
|
9342
|
+
if (!existsSync5(userConfigPath)) {
|
|
9343
|
+
writeFileSync2(userConfigPath, "{}\n", "utf-8");
|
|
9207
9344
|
}
|
|
9208
9345
|
}
|
|
9346
|
+
function writeUserConfigPatch(userConfigPath, patch) {
|
|
9347
|
+
const existing = existsSync5(userConfigPath) ? JSON.parse(readFileSync4(userConfigPath, "utf-8")) : {};
|
|
9348
|
+
writeFileSync2(userConfigPath, `${JSON.stringify({ ...existing, ...patch }, null, 2)}
|
|
9349
|
+
`, "utf-8");
|
|
9350
|
+
}
|
|
9209
9351
|
function deepMerge(base, overlay) {
|
|
9210
9352
|
if (overlay === null || overlay === void 0) return base;
|
|
9211
9353
|
if (Array.isArray(overlay)) return overlay;
|
|
@@ -9219,8 +9361,8 @@ function deepMerge(base, overlay) {
|
|
|
9219
9361
|
}
|
|
9220
9362
|
|
|
9221
9363
|
// ../../packages/core/dist/community.js
|
|
9222
|
-
import { existsSync as
|
|
9223
|
-
import { join as
|
|
9364
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, rmSync, statSync as statSync3 } from "node:fs";
|
|
9365
|
+
import { join as join5 } from "node:path";
|
|
9224
9366
|
|
|
9225
9367
|
// ../../node_modules/.pnpm/simple-git@3.36.0/node_modules/simple-git/dist/esm/index.js
|
|
9226
9368
|
var import_file_exists = __toESM(require_dist(), 1);
|
|
@@ -14185,6 +14327,7 @@ var simpleGit = gitInstanceFactory;
|
|
|
14185
14327
|
|
|
14186
14328
|
// ../../packages/core/dist/community.js
|
|
14187
14329
|
var GITHUB_URL_RE = /^https:\/\/github\.com\/[^/]+\/([^/]+?)(\.git)?\/?$/;
|
|
14330
|
+
var GITHUB_HANDLE_RE = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$/;
|
|
14188
14331
|
function validateCommunityUrl(url) {
|
|
14189
14332
|
const trimmed2 = (url ?? "").trim();
|
|
14190
14333
|
const m = GITHUB_URL_RE.exec(trimmed2);
|
|
@@ -14207,26 +14350,28 @@ function resolveHandleCollision(baseHandle, exists2) {
|
|
|
14207
14350
|
return `${baseHandle}-${n}`;
|
|
14208
14351
|
}
|
|
14209
14352
|
async function communityAdd(opts) {
|
|
14210
|
-
const
|
|
14353
|
+
const supplied = opts.url ?? "";
|
|
14354
|
+
const url = /^https?:\/\//.test(supplied.trim()) ? supplied : GITHUB_HANDLE_RE.test(supplied) ? `https://github.com/${supplied}/Caveat-Public` : supplied;
|
|
14355
|
+
const validation = validateCommunityUrl(url);
|
|
14211
14356
|
if (!validation.valid) {
|
|
14212
14357
|
throw new Error(`invalid community URL: ${validation.reason}`);
|
|
14213
14358
|
}
|
|
14214
14359
|
const handle = resolveHandleCollision(
|
|
14215
14360
|
validation.handle,
|
|
14216
|
-
(h2) =>
|
|
14361
|
+
(h2) => existsSync6(join5(opts.communityDir, h2))
|
|
14217
14362
|
);
|
|
14218
|
-
const target =
|
|
14363
|
+
const target = join5(opts.communityDir, handle);
|
|
14219
14364
|
const depth = opts.depth ?? 1;
|
|
14220
14365
|
const git = simpleGit();
|
|
14221
|
-
await git.clone(
|
|
14366
|
+
await git.clone(url, target, ["--depth", String(depth)]);
|
|
14222
14367
|
return { handle, path: target };
|
|
14223
14368
|
}
|
|
14224
14369
|
async function communityPull(opts) {
|
|
14225
|
-
if (!
|
|
14370
|
+
if (!existsSync6(opts.communityDir)) return [];
|
|
14226
14371
|
const results = [];
|
|
14227
|
-
for (const entry of
|
|
14372
|
+
for (const entry of readdirSync4(opts.communityDir, { withFileTypes: true })) {
|
|
14228
14373
|
if (!entry.isDirectory()) continue;
|
|
14229
|
-
const path =
|
|
14374
|
+
const path = join5(opts.communityDir, entry.name);
|
|
14230
14375
|
const git = simpleGit(path);
|
|
14231
14376
|
try {
|
|
14232
14377
|
await git.pull();
|
|
@@ -14239,11 +14384,11 @@ async function communityPull(opts) {
|
|
|
14239
14384
|
return results;
|
|
14240
14385
|
}
|
|
14241
14386
|
function communityList(opts) {
|
|
14242
|
-
if (!
|
|
14387
|
+
if (!existsSync6(opts.communityDir)) return [];
|
|
14243
14388
|
const out = [];
|
|
14244
|
-
for (const entry of
|
|
14389
|
+
for (const entry of readdirSync4(opts.communityDir, { withFileTypes: true })) {
|
|
14245
14390
|
if (!entry.isDirectory()) continue;
|
|
14246
|
-
const path =
|
|
14391
|
+
const path = join5(opts.communityDir, entry.name);
|
|
14247
14392
|
const row = opts.db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(`community/${entry.name}`);
|
|
14248
14393
|
out.push({ handle: entry.name, path, entryCount: row?.n ?? 0 });
|
|
14249
14394
|
}
|
|
@@ -14257,8 +14402,8 @@ function communityRemove(opts) {
|
|
|
14257
14402
|
if (handle === "." || handle === ".." || /[\\/]/.test(handle)) {
|
|
14258
14403
|
throw new Error(`invalid handle (no path separators or relative segments): ${opts.handle}`);
|
|
14259
14404
|
}
|
|
14260
|
-
const target =
|
|
14261
|
-
const dirExisted =
|
|
14405
|
+
const target = join5(opts.communityDir, handle);
|
|
14406
|
+
const dirExisted = existsSync6(target) && statSync3(target).isDirectory();
|
|
14262
14407
|
const source = `community/${handle}`;
|
|
14263
14408
|
const row = opts.db.prepare("SELECT COUNT(*) AS n FROM entries WHERE source = ?").get(source);
|
|
14264
14409
|
const rowCount = row?.n ?? 0;
|
|
@@ -14369,7 +14514,7 @@ function toSearchResult2(row) {
|
|
|
14369
14514
|
title: row.title,
|
|
14370
14515
|
symptomExcerpt: symptom.slice(0, SYMPTOM_EXCERPT_LENGTH2),
|
|
14371
14516
|
confidence: row.confidence,
|
|
14372
|
-
visibility: row.visibility ?? "
|
|
14517
|
+
visibility: row.visibility ?? "private",
|
|
14373
14518
|
environment: fm.environment ?? {}
|
|
14374
14519
|
};
|
|
14375
14520
|
}
|
|
@@ -14523,7 +14668,7 @@ function stopReminderText(signals, related) {
|
|
|
14523
14668
|
}
|
|
14524
14669
|
|
|
14525
14670
|
// ../../packages/core/dist/transcriptSignals.js
|
|
14526
|
-
import { existsSync as
|
|
14671
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
14527
14672
|
var MAX_ERROR_SNIPPETS = 10;
|
|
14528
14673
|
var MAX_ERROR_SNIPPET_LENGTH = 300;
|
|
14529
14674
|
var MAX_SEARCH_QUERIES = 10;
|
|
@@ -14549,10 +14694,10 @@ function parseTimestamp(raw) {
|
|
|
14549
14694
|
return Number.isNaN(ms) ? void 0 : ms;
|
|
14550
14695
|
}
|
|
14551
14696
|
function readSessionSignals(transcriptPath) {
|
|
14552
|
-
if (!transcriptPath || !
|
|
14697
|
+
if (!transcriptPath || !existsSync7(transcriptPath)) return null;
|
|
14553
14698
|
let raw;
|
|
14554
14699
|
try {
|
|
14555
|
-
raw =
|
|
14700
|
+
raw = readFileSync5(transcriptPath, "utf-8");
|
|
14556
14701
|
} catch {
|
|
14557
14702
|
return null;
|
|
14558
14703
|
}
|
|
@@ -14633,7 +14778,7 @@ function struggleSearchText(s) {
|
|
|
14633
14778
|
}
|
|
14634
14779
|
|
|
14635
14780
|
// ../../packages/core/dist/codexTranscriptSignals.js
|
|
14636
|
-
import { existsSync as
|
|
14781
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
14637
14782
|
var MAX_ERROR_SNIPPETS2 = 10;
|
|
14638
14783
|
var MAX_ERROR_SNIPPET_LENGTH2 = 300;
|
|
14639
14784
|
var MAX_SEARCH_QUERIES2 = 10;
|
|
@@ -14698,10 +14843,10 @@ function addQuery(searchQueries, query) {
|
|
|
14698
14843
|
searchQueries.push(query.slice(0, MAX_SEARCH_QUERY_LENGTH2));
|
|
14699
14844
|
}
|
|
14700
14845
|
function readCodexSessionSignals(transcriptPath) {
|
|
14701
|
-
if (!transcriptPath || !
|
|
14846
|
+
if (!transcriptPath || !existsSync8(transcriptPath)) return null;
|
|
14702
14847
|
let raw;
|
|
14703
14848
|
try {
|
|
14704
|
-
raw =
|
|
14849
|
+
raw = readFileSync6(transcriptPath, "utf-8");
|
|
14705
14850
|
} catch {
|
|
14706
14851
|
return null;
|
|
14707
14852
|
}
|
|
@@ -14805,30 +14950,30 @@ function readCodexSessionSignals(transcriptPath) {
|
|
|
14805
14950
|
|
|
14806
14951
|
// ../../packages/core/dist/pendingReminders.js
|
|
14807
14952
|
import {
|
|
14808
|
-
existsSync as
|
|
14809
|
-
mkdirSync as
|
|
14810
|
-
readdirSync as
|
|
14811
|
-
readFileSync as
|
|
14953
|
+
existsSync as existsSync9,
|
|
14954
|
+
mkdirSync as mkdirSync3,
|
|
14955
|
+
readdirSync as readdirSync5,
|
|
14956
|
+
readFileSync as readFileSync7,
|
|
14812
14957
|
rmSync as rmSync2,
|
|
14813
|
-
statSync as
|
|
14814
|
-
unlinkSync,
|
|
14815
|
-
writeFileSync as
|
|
14958
|
+
statSync as statSync4,
|
|
14959
|
+
unlinkSync as unlinkSync2,
|
|
14960
|
+
writeFileSync as writeFileSync3
|
|
14816
14961
|
} from "node:fs";
|
|
14817
|
-
import { join as
|
|
14962
|
+
import { join as join6 } from "node:path";
|
|
14818
14963
|
import { randomBytes } from "node:crypto";
|
|
14819
14964
|
function sanitizeSessionId(raw) {
|
|
14820
14965
|
const clean = raw.replace(/[^A-Za-z0-9_-]/g, "");
|
|
14821
14966
|
return clean.length > 0 ? clean : "_unknown";
|
|
14822
14967
|
}
|
|
14823
14968
|
function pendingDirFor(caveatHome, sessionId) {
|
|
14824
|
-
return
|
|
14969
|
+
return join6(caveatHome, "pending", sanitizeSessionId(sessionId));
|
|
14825
14970
|
}
|
|
14826
14971
|
function appendPendingReminder(caveatHome, sessionId, text) {
|
|
14827
14972
|
const dir = pendingDirFor(caveatHome, sessionId);
|
|
14828
|
-
|
|
14973
|
+
mkdirSync3(dir, { recursive: true });
|
|
14829
14974
|
const name = `${Date.now()}-${randomBytes(4).toString("hex")}.txt`;
|
|
14830
|
-
const path =
|
|
14831
|
-
|
|
14975
|
+
const path = join6(dir, name);
|
|
14976
|
+
writeFileSync3(path, text, "utf-8");
|
|
14832
14977
|
return path;
|
|
14833
14978
|
}
|
|
14834
14979
|
function cleanupStalePendingDirs(caveatHome, options2 = {}) {
|
|
@@ -14838,21 +14983,21 @@ function cleanupStalePendingDirs(caveatHome, options2 = {}) {
|
|
|
14838
14983
|
}
|
|
14839
14984
|
const now = options2.now ?? /* @__PURE__ */ new Date();
|
|
14840
14985
|
const cutoffMs = now.getTime() - staleDays * 24 * 60 * 60 * 1e3;
|
|
14841
|
-
const root =
|
|
14842
|
-
if (!
|
|
14986
|
+
const root = join6(caveatHome, "pending");
|
|
14987
|
+
if (!existsSync9(root)) return { removed: [], kept: 0 };
|
|
14843
14988
|
let entries;
|
|
14844
14989
|
try {
|
|
14845
|
-
entries =
|
|
14990
|
+
entries = readdirSync5(root);
|
|
14846
14991
|
} catch {
|
|
14847
14992
|
return { removed: [], kept: 0 };
|
|
14848
14993
|
}
|
|
14849
14994
|
const removed = [];
|
|
14850
14995
|
let kept = 0;
|
|
14851
14996
|
for (const entry of entries) {
|
|
14852
|
-
const sub =
|
|
14997
|
+
const sub = join6(root, entry);
|
|
14853
14998
|
let subStat;
|
|
14854
14999
|
try {
|
|
14855
|
-
subStat =
|
|
15000
|
+
subStat = statSync4(sub);
|
|
14856
15001
|
} catch {
|
|
14857
15002
|
continue;
|
|
14858
15003
|
}
|
|
@@ -14860,10 +15005,10 @@ function cleanupStalePendingDirs(caveatHome, options2 = {}) {
|
|
|
14860
15005
|
let newest = subStat.mtimeMs;
|
|
14861
15006
|
let scanFailed = false;
|
|
14862
15007
|
try {
|
|
14863
|
-
for (const f of
|
|
14864
|
-
const fp =
|
|
15008
|
+
for (const f of readdirSync5(sub)) {
|
|
15009
|
+
const fp = join6(sub, f);
|
|
14865
15010
|
try {
|
|
14866
|
-
const fs =
|
|
15011
|
+
const fs = statSync4(fp);
|
|
14867
15012
|
if (fs.mtimeMs > newest) newest = fs.mtimeMs;
|
|
14868
15013
|
} catch {
|
|
14869
15014
|
scanFailed = true;
|
|
@@ -14898,11 +15043,11 @@ function maybeSweepPendingDirs(caveatHome, options2 = {}) {
|
|
|
14898
15043
|
);
|
|
14899
15044
|
}
|
|
14900
15045
|
const now = options2.now ?? /* @__PURE__ */ new Date();
|
|
14901
|
-
const pendingRoot =
|
|
14902
|
-
if (!
|
|
14903
|
-
const marker =
|
|
15046
|
+
const pendingRoot = join6(caveatHome, "pending");
|
|
15047
|
+
if (!existsSync9(pendingRoot)) return { skipped: "no_pending_dir" };
|
|
15048
|
+
const marker = join6(pendingRoot, ".last-sweep");
|
|
14904
15049
|
try {
|
|
14905
|
-
const m =
|
|
15050
|
+
const m = statSync4(marker);
|
|
14906
15051
|
const ageMs = now.getTime() - m.mtimeMs;
|
|
14907
15052
|
if (ageMs < debounceDays * 24 * 60 * 60 * 1e3) {
|
|
14908
15053
|
return { skipped: "debounced" };
|
|
@@ -14911,30 +15056,30 @@ function maybeSweepPendingDirs(caveatHome, options2 = {}) {
|
|
|
14911
15056
|
}
|
|
14912
15057
|
const result = cleanupStalePendingDirs(caveatHome, { staleDays, now });
|
|
14913
15058
|
try {
|
|
14914
|
-
|
|
15059
|
+
writeFileSync3(marker, "", "utf-8");
|
|
14915
15060
|
} catch {
|
|
14916
15061
|
}
|
|
14917
15062
|
return { swept: result };
|
|
14918
15063
|
}
|
|
14919
15064
|
function drainPendingReminders(caveatHome, sessionId) {
|
|
14920
15065
|
const dir = pendingDirFor(caveatHome, sessionId);
|
|
14921
|
-
if (!
|
|
15066
|
+
if (!existsSync9(dir)) return [];
|
|
14922
15067
|
let entries;
|
|
14923
15068
|
try {
|
|
14924
|
-
entries =
|
|
15069
|
+
entries = readdirSync5(dir).filter((f) => f.endsWith(".txt")).sort();
|
|
14925
15070
|
} catch {
|
|
14926
15071
|
return [];
|
|
14927
15072
|
}
|
|
14928
15073
|
const out = [];
|
|
14929
15074
|
for (const entry of entries) {
|
|
14930
|
-
const path =
|
|
15075
|
+
const path = join6(dir, entry);
|
|
14931
15076
|
try {
|
|
14932
|
-
out.push(
|
|
15077
|
+
out.push(readFileSync7(path, "utf-8"));
|
|
14933
15078
|
} catch {
|
|
14934
15079
|
continue;
|
|
14935
15080
|
}
|
|
14936
15081
|
try {
|
|
14937
|
-
|
|
15082
|
+
unlinkSync2(path);
|
|
14938
15083
|
} catch {
|
|
14939
15084
|
}
|
|
14940
15085
|
}
|
|
@@ -14978,11 +15123,414 @@ function listStale(db, opts = {}) {
|
|
|
14978
15123
|
id: r2.id,
|
|
14979
15124
|
source: r2.source,
|
|
14980
15125
|
title: r2.title,
|
|
14981
|
-
visibility: r2.visibility ?? "
|
|
15126
|
+
visibility: r2.visibility ?? "private",
|
|
14982
15127
|
last_hit_at: r2.last_hit_at
|
|
14983
15128
|
}));
|
|
14984
15129
|
}
|
|
14985
15130
|
|
|
15131
|
+
// ../../packages/core/dist/sync.js
|
|
15132
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync6, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
15133
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "node:path";
|
|
15134
|
+
|
|
15135
|
+
// ../../packages/core/dist/remoteVisibility.js
|
|
15136
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
15137
|
+
function deriveAnonymousProbeUrl(remoteUrl) {
|
|
15138
|
+
let url;
|
|
15139
|
+
try {
|
|
15140
|
+
url = new URL(remoteUrl);
|
|
15141
|
+
} catch {
|
|
15142
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/.exec(remoteUrl);
|
|
15143
|
+
if (!scp) return void 0;
|
|
15144
|
+
try {
|
|
15145
|
+
return new URL(`https://${scp[1]}/${scp[2]}`).toString();
|
|
15146
|
+
} catch {
|
|
15147
|
+
return void 0;
|
|
15148
|
+
}
|
|
15149
|
+
}
|
|
15150
|
+
if (url.protocol !== "https:" && url.protocol !== "ssh:") return void 0;
|
|
15151
|
+
return `https://${url.host}${url.pathname}${url.search}${url.hash}`;
|
|
15152
|
+
}
|
|
15153
|
+
async function probeAnonymousRead(probeUrl, options2 = {}) {
|
|
15154
|
+
if (!probeUrl) return { kind: "indeterminate", reason: "remote URL cannot be probed anonymously" };
|
|
15155
|
+
const endpoint = new URL("info/refs?service=git-upload-pack", ensureTrailingSlash(probeUrl));
|
|
15156
|
+
try {
|
|
15157
|
+
const response = await (options2.fetchImpl ?? globalThis.fetch)(endpoint, {
|
|
15158
|
+
method: "GET",
|
|
15159
|
+
redirect: "follow",
|
|
15160
|
+
signal: AbortSignal.timeout(options2.timeoutMs ?? PROBE_TIMEOUT_MS)
|
|
15161
|
+
});
|
|
15162
|
+
if (response.status === 401 || response.status === 404) {
|
|
15163
|
+
return { kind: "denied", status: response.status };
|
|
15164
|
+
}
|
|
15165
|
+
if (!response.ok) return { kind: "indeterminate", reason: `unexpected HTTP status ${response.status}` };
|
|
15166
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
15167
|
+
if (contentType !== "application/x-git-upload-pack-advertisement") {
|
|
15168
|
+
return { kind: "indeterminate", reason: "missing Git smart-HTTP advertisement content type" };
|
|
15169
|
+
}
|
|
15170
|
+
return { kind: "anonymous-readable" };
|
|
15171
|
+
} catch {
|
|
15172
|
+
return { kind: "indeterminate", reason: "anonymous read probe request failed" };
|
|
15173
|
+
}
|
|
15174
|
+
}
|
|
15175
|
+
function ensureTrailingSlash(url) {
|
|
15176
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
15177
|
+
}
|
|
15178
|
+
|
|
15179
|
+
// ../../packages/core/dist/sync.js
|
|
15180
|
+
var SyncError = class extends Error {
|
|
15181
|
+
constructor(code, message) {
|
|
15182
|
+
super(message);
|
|
15183
|
+
this.code = code;
|
|
15184
|
+
this.name = "SyncError";
|
|
15185
|
+
}
|
|
15186
|
+
code;
|
|
15187
|
+
};
|
|
15188
|
+
function normalizePath(path) {
|
|
15189
|
+
const normalized = resolve2(path).replace(/\\/g, "/");
|
|
15190
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
15191
|
+
}
|
|
15192
|
+
async function effectivePushUrls(git) {
|
|
15193
|
+
let raw;
|
|
15194
|
+
try {
|
|
15195
|
+
raw = await git.raw(["remote", "get-url", "--push", "--all", "origin"]);
|
|
15196
|
+
} catch {
|
|
15197
|
+
const remotes = (await git.raw(["remote"])).trim();
|
|
15198
|
+
const available = remotes ? remotes.split(/\r?\n/).join(", ") : "(none)";
|
|
15199
|
+
throw new SyncError("NO_REMOTE", `NO_REMOTE: origin is required (configured remotes: ${available})`);
|
|
15200
|
+
}
|
|
15201
|
+
const urls = raw.split(/\r?\n/).map((u) => u.trim()).filter(Boolean);
|
|
15202
|
+
if (urls.length === 0) throw new SyncError("NO_REMOTE", "NO_REMOTE: origin has no push URL");
|
|
15203
|
+
return urls;
|
|
15204
|
+
}
|
|
15205
|
+
async function assertPrivateRemotes(remoteUrls, opts) {
|
|
15206
|
+
const probe = opts.probeImpl ?? probeAnonymousRead;
|
|
15207
|
+
let worst = { kind: "denied", status: 0 };
|
|
15208
|
+
for (const remoteUrl of remoteUrls) {
|
|
15209
|
+
const result = await probe(deriveAnonymousProbeUrl(remoteUrl));
|
|
15210
|
+
if (result.kind === "anonymous-readable") {
|
|
15211
|
+
throw new SyncError("REMOTE_PUBLIC", `remote is anonymously readable: ${remoteUrl}`);
|
|
15212
|
+
}
|
|
15213
|
+
if (result.kind === "indeterminate") {
|
|
15214
|
+
if (!opts.trustRemotePrivate) {
|
|
15215
|
+
throw new SyncError(
|
|
15216
|
+
"REMOTE_VISIBILITY_INDETERMINATE",
|
|
15217
|
+
`could not verify remote privacy: ${remoteUrl}; rerun with --trust-remote-private to accept this risk`
|
|
15218
|
+
);
|
|
15219
|
+
}
|
|
15220
|
+
worst = result;
|
|
15221
|
+
}
|
|
15222
|
+
}
|
|
15223
|
+
return worst;
|
|
15224
|
+
}
|
|
15225
|
+
async function preflightSync(ownDir, opts = {}) {
|
|
15226
|
+
const git = simpleGit(ownDir);
|
|
15227
|
+
if (!await git.checkIsRepo()) {
|
|
15228
|
+
throw new SyncError("NOT_A_REPO", "own knowledge directory is not a git repository; run `caveat sync --init` first");
|
|
15229
|
+
}
|
|
15230
|
+
const root = realpathSync.native((await git.revparse(["--show-toplevel"])).trim());
|
|
15231
|
+
const requested = realpathSync.native(ownDir);
|
|
15232
|
+
if (normalizePath(root) !== normalizePath(requested)) {
|
|
15233
|
+
throw new SyncError("EXTERNAL_TOPLEVEL", `EXTERNAL_TOPLEVEL: own directory must be the repository root: ${requested} (root: ${root})`);
|
|
15234
|
+
}
|
|
15235
|
+
let branch;
|
|
15236
|
+
try {
|
|
15237
|
+
branch = (await git.raw(["symbolic-ref", "--short", "HEAD"])).trim();
|
|
15238
|
+
} catch {
|
|
15239
|
+
throw new SyncError("DETACHED_HEAD", "cannot sync from a detached HEAD");
|
|
15240
|
+
}
|
|
15241
|
+
const pushUrls = await effectivePushUrls(git);
|
|
15242
|
+
const probe = await assertPrivateRemotes(pushUrls, opts);
|
|
15243
|
+
return { ownDir: requested, branch, pushUrls, probe };
|
|
15244
|
+
}
|
|
15245
|
+
function reindexAndMark(opts) {
|
|
15246
|
+
mkdirSync4(dirname3(opts.paths.dbPath), { recursive: true });
|
|
15247
|
+
const db = openDb({ path: opts.paths.dbPath, logger: opts.logger });
|
|
15248
|
+
try {
|
|
15249
|
+
reindexAllSources({ db, paths: opts.paths, logger: opts.logger });
|
|
15250
|
+
writeDigestMarker(opts.caveatHome, computeEntriesDigest(opts.paths));
|
|
15251
|
+
} finally {
|
|
15252
|
+
db.close();
|
|
15253
|
+
}
|
|
15254
|
+
}
|
|
15255
|
+
async function syncOwn(opts) {
|
|
15256
|
+
const preflight = await preflightSync(opts.ownDir, opts);
|
|
15257
|
+
const git = simpleGit(preflight.ownDir);
|
|
15258
|
+
const status = await git.status();
|
|
15259
|
+
if (opts.dryRun) {
|
|
15260
|
+
return {
|
|
15261
|
+
...preflight,
|
|
15262
|
+
committed: false,
|
|
15263
|
+
pulled: false,
|
|
15264
|
+
pushed: false,
|
|
15265
|
+
dryRun: true,
|
|
15266
|
+
changedFiles: status.files.length
|
|
15267
|
+
};
|
|
15268
|
+
}
|
|
15269
|
+
let committed = false;
|
|
15270
|
+
if (!status.isClean()) {
|
|
15271
|
+
await git.add("-A");
|
|
15272
|
+
const changed = status.files.length;
|
|
15273
|
+
await git.commit(`caveat sync: ${changed} changed file${changed === 1 ? "" : "s"}`);
|
|
15274
|
+
committed = true;
|
|
15275
|
+
}
|
|
15276
|
+
const remoteBranch = (await git.raw(["ls-remote", "--heads", "origin", preflight.branch])).trim();
|
|
15277
|
+
let pulled = false;
|
|
15278
|
+
if (remoteBranch) {
|
|
15279
|
+
try {
|
|
15280
|
+
await git.pull("origin", preflight.branch, ["--rebase"]);
|
|
15281
|
+
pulled = true;
|
|
15282
|
+
} catch (err) {
|
|
15283
|
+
try {
|
|
15284
|
+
await git.raw(["rebase", "--abort"]);
|
|
15285
|
+
} catch {
|
|
15286
|
+
}
|
|
15287
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
15288
|
+
throw new SyncError("SYNC_CONFLICT", `sync rebase failed and was aborted: ${detail}`);
|
|
15289
|
+
}
|
|
15290
|
+
}
|
|
15291
|
+
reindexAndMark(opts);
|
|
15292
|
+
await git.push("origin", preflight.branch, ["-u"]);
|
|
15293
|
+
return {
|
|
15294
|
+
...preflight,
|
|
15295
|
+
committed,
|
|
15296
|
+
pulled,
|
|
15297
|
+
pushed: true,
|
|
15298
|
+
dryRun: false,
|
|
15299
|
+
changedFiles: status.files.length
|
|
15300
|
+
};
|
|
15301
|
+
}
|
|
15302
|
+
var KNOWLEDGE_GITIGNORE = [
|
|
15303
|
+
"# Private entries DO sync to your private remote (Caveat-Private) \u2014 that is the",
|
|
15304
|
+
"# intended sharing boundary. The public boundary is enforced by `caveat publish`.",
|
|
15305
|
+
"",
|
|
15306
|
+
"# Obsidian per-user config: workspace layout, theme, plugin state, cache.",
|
|
15307
|
+
".obsidian/",
|
|
15308
|
+
""
|
|
15309
|
+
].join("\n");
|
|
15310
|
+
function countMarkdownEntries(ownDir) {
|
|
15311
|
+
const entries = join7(ownDir, "entries");
|
|
15312
|
+
if (!existsSync10(entries)) return 0;
|
|
15313
|
+
let count = 0;
|
|
15314
|
+
const stack = [entries];
|
|
15315
|
+
while (stack.length > 0) {
|
|
15316
|
+
const dir = stack.pop();
|
|
15317
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
15318
|
+
const path = join7(dir, entry.name);
|
|
15319
|
+
if (entry.isDirectory()) stack.push(path);
|
|
15320
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) count++;
|
|
15321
|
+
}
|
|
15322
|
+
}
|
|
15323
|
+
return count;
|
|
15324
|
+
}
|
|
15325
|
+
function scaffold(ownDir) {
|
|
15326
|
+
mkdirSync4(join7(ownDir, "entries"), { recursive: true });
|
|
15327
|
+
const gitignore = join7(ownDir, ".gitignore");
|
|
15328
|
+
if (!existsSync10(gitignore)) writeFileSync4(gitignore, KNOWLEDGE_GITIGNORE, "utf-8");
|
|
15329
|
+
}
|
|
15330
|
+
async function defaultRemoteBranch(git, url) {
|
|
15331
|
+
const symref = await git.raw(["ls-remote", "--symref", url, "HEAD"]);
|
|
15332
|
+
const match = /^ref: refs\/heads\/([^\s]+)\s+HEAD$/m.exec(symref);
|
|
15333
|
+
if (match) return match[1];
|
|
15334
|
+
const heads = await git.raw(["ls-remote", "--heads", url]);
|
|
15335
|
+
const first2 = /refs\/heads\/([^\s]+)\s*$/m.exec(heads);
|
|
15336
|
+
if (!first2) throw new Error(`remote has refs but no branch heads: ${url}`);
|
|
15337
|
+
return first2[1];
|
|
15338
|
+
}
|
|
15339
|
+
async function initOwnSync(opts) {
|
|
15340
|
+
const ownDir = resolve2(opts.ownDir);
|
|
15341
|
+
mkdirSync4(ownDir, { recursive: true });
|
|
15342
|
+
const existing = simpleGit(ownDir);
|
|
15343
|
+
if (await existing.checkIsRepo()) {
|
|
15344
|
+
throw new SyncError("OWN_REPO_EXISTS", `own knowledge directory is already a git repository: ${ownDir}`);
|
|
15345
|
+
}
|
|
15346
|
+
const inspector = simpleGit(ownDir);
|
|
15347
|
+
const refs = (await inspector.raw(["ls-remote", "--heads", opts.url])).trim();
|
|
15348
|
+
const entryCount = countMarkdownEntries(ownDir);
|
|
15349
|
+
if (refs && entryCount > 0) {
|
|
15350
|
+
throw new SyncError("BOTH_HAVE_ENTRIES", "local and remote both contain entries; resolve the ownership conflict before initializing sync");
|
|
15351
|
+
}
|
|
15352
|
+
const git = simpleGit(ownDir);
|
|
15353
|
+
const createdGitDir = join7(ownDir, ".git");
|
|
15354
|
+
await git.init();
|
|
15355
|
+
try {
|
|
15356
|
+
await git.addRemote("origin", opts.url);
|
|
15357
|
+
await assertPrivateRemotes(await effectivePushUrls(git), opts);
|
|
15358
|
+
if (!refs) {
|
|
15359
|
+
scaffold(ownDir);
|
|
15360
|
+
await git.add("-A");
|
|
15361
|
+
await git.commit(`caveat sync: initial import (${entryCount} entries)`);
|
|
15362
|
+
const branch2 = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
15363
|
+
await git.push("origin", branch2, ["-u"]);
|
|
15364
|
+
reindexAndMark(opts);
|
|
15365
|
+
return { ownDir, branch: branch2, remoteWasEmpty: true };
|
|
15366
|
+
}
|
|
15367
|
+
const branch = await defaultRemoteBranch(git, opts.url);
|
|
15368
|
+
await git.fetch("origin", branch);
|
|
15369
|
+
await git.checkout(["--track", "-B", branch, `origin/${branch}`]);
|
|
15370
|
+
reindexAndMark(opts);
|
|
15371
|
+
return { ownDir, branch, remoteWasEmpty: false };
|
|
15372
|
+
} catch (err) {
|
|
15373
|
+
try {
|
|
15374
|
+
rmSync3(createdGitDir, { recursive: true, force: true });
|
|
15375
|
+
} catch {
|
|
15376
|
+
}
|
|
15377
|
+
throw err;
|
|
15378
|
+
}
|
|
15379
|
+
}
|
|
15380
|
+
|
|
15381
|
+
// ../../packages/core/dist/publish.js
|
|
15382
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15383
|
+
import { dirname as dirname4, join as join8, relative as relative3 } from "node:path";
|
|
15384
|
+
|
|
15385
|
+
// ../../packages/core/dist/visibility.js
|
|
15386
|
+
function classifyVisibility(value) {
|
|
15387
|
+
if (value === "public") return "public";
|
|
15388
|
+
if (value === "private") return "private";
|
|
15389
|
+
return "invalid";
|
|
15390
|
+
}
|
|
15391
|
+
|
|
15392
|
+
// ../../packages/core/dist/publish.js
|
|
15393
|
+
function collectPublishSet(entriesDir) {
|
|
15394
|
+
const files = [];
|
|
15395
|
+
const invalid = [];
|
|
15396
|
+
if (!existsSync11(entriesDir)) return { files, invalid };
|
|
15397
|
+
for (const path of walkMarkdown(entriesDir)) {
|
|
15398
|
+
const relPath = relative3(entriesDir, path).replace(/\\/g, "/");
|
|
15399
|
+
const content = readFileSync8(path);
|
|
15400
|
+
try {
|
|
15401
|
+
const visibility = classifyVisibility(parseMarkdown(content.toString("utf-8")).frontmatter.visibility);
|
|
15402
|
+
if (visibility === "public") files.push({ relPath, content });
|
|
15403
|
+
else if (visibility === "invalid") invalid.push({ relPath, reason: "visibility must be exactly public or private" });
|
|
15404
|
+
} catch (err) {
|
|
15405
|
+
invalid.push({ relPath, reason: err instanceof Error ? err.message : String(err) });
|
|
15406
|
+
}
|
|
15407
|
+
}
|
|
15408
|
+
return { files, invalid };
|
|
15409
|
+
}
|
|
15410
|
+
async function preparePublishMirror(opts) {
|
|
15411
|
+
const git = opts.git ?? simpleGit();
|
|
15412
|
+
if (!existsSync11(opts.mirrorDir)) {
|
|
15413
|
+
mkdirSync5(dirname4(opts.mirrorDir), { recursive: true });
|
|
15414
|
+
await git.clone(opts.target, opts.mirrorDir);
|
|
15415
|
+
} else {
|
|
15416
|
+
const mirrorGit2 = simpleGit(opts.mirrorDir);
|
|
15417
|
+
const old = (await mirrorGit2.raw(["remote", "get-url", "origin"])).trim();
|
|
15418
|
+
if (old !== opts.target) {
|
|
15419
|
+
throw new Error(`publish mirror points at ${old} but publishTarget is ${opts.target} \u2014 remove ${opts.mirrorDir} and re-run`);
|
|
15420
|
+
}
|
|
15421
|
+
}
|
|
15422
|
+
const mirrorGit = simpleGit(opts.mirrorDir);
|
|
15423
|
+
await mirrorGit.fetch("origin");
|
|
15424
|
+
const head = (await mirrorGit.raw(["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"]).catch(() => "")).trim();
|
|
15425
|
+
if (head) await mirrorGit.reset(["--hard", head]);
|
|
15426
|
+
await mirrorGit.raw(["clean", "-ffdx"]);
|
|
15427
|
+
}
|
|
15428
|
+
function publishReadme(fileCount, files) {
|
|
15429
|
+
const categories = [...new Set(files.map((f) => f.relPath.split("/")[0]).filter(Boolean))].sort();
|
|
15430
|
+
return [
|
|
15431
|
+
"# Caveat-Public",
|
|
15432
|
+
"",
|
|
15433
|
+
"This repository is generated by `caveat publish`. Do not edit it by hand.",
|
|
15434
|
+
"",
|
|
15435
|
+
"Subscribe with `caveat community add <https URL or username>`.",
|
|
15436
|
+
"",
|
|
15437
|
+
`Public entries: ${fileCount}`,
|
|
15438
|
+
`Categories: ${categories.length ? categories.join(", ") : "(none)"}`,
|
|
15439
|
+
"",
|
|
15440
|
+
"Caveat: https://github.com/kitepon-rgb/Caveat",
|
|
15441
|
+
""
|
|
15442
|
+
].join("\n");
|
|
15443
|
+
}
|
|
15444
|
+
function writeMirror(opts) {
|
|
15445
|
+
for (const item of readdirSync7(opts.mirrorDir, { withFileTypes: true })) {
|
|
15446
|
+
if (item.name === ".git") continue;
|
|
15447
|
+
rmSync4(join8(opts.mirrorDir, item.name), { recursive: true, force: true });
|
|
15448
|
+
}
|
|
15449
|
+
const entries = join8(opts.mirrorDir, "entries");
|
|
15450
|
+
for (const file of opts.files) {
|
|
15451
|
+
const target = join8(entries, file.relPath);
|
|
15452
|
+
mkdirSync5(dirname4(target), { recursive: true });
|
|
15453
|
+
writeFileSync5(target, file.content);
|
|
15454
|
+
}
|
|
15455
|
+
writeFileSync5(join8(opts.mirrorDir, "README.md"), opts.readme, "utf-8");
|
|
15456
|
+
}
|
|
15457
|
+
function* walkAllFiles(root) {
|
|
15458
|
+
for (const item of readdirSync7(root, { withFileTypes: true })) {
|
|
15459
|
+
if (item.name === ".git") continue;
|
|
15460
|
+
const full = join8(root, item.name);
|
|
15461
|
+
if (item.isDirectory()) yield* walkAllFiles(full);
|
|
15462
|
+
else yield full;
|
|
15463
|
+
}
|
|
15464
|
+
}
|
|
15465
|
+
function verifyMirror(mirrorDir) {
|
|
15466
|
+
const entriesRoot = join8(mirrorDir, "entries");
|
|
15467
|
+
const bad = [];
|
|
15468
|
+
let fileCount = 0;
|
|
15469
|
+
for (const path of walkAllFiles(mirrorDir)) {
|
|
15470
|
+
const relToMirror = relative3(mirrorDir, path).replace(/\\/g, "/");
|
|
15471
|
+
if (relToMirror === "README.md") continue;
|
|
15472
|
+
if (!relToMirror.startsWith("entries/")) {
|
|
15473
|
+
bad.push(`${relToMirror}: unexpected file outside entries/`);
|
|
15474
|
+
continue;
|
|
15475
|
+
}
|
|
15476
|
+
if (!relToMirror.endsWith(".md")) {
|
|
15477
|
+
bad.push(`${relToMirror}: unexpected non-markdown file`);
|
|
15478
|
+
continue;
|
|
15479
|
+
}
|
|
15480
|
+
fileCount++;
|
|
15481
|
+
const relPath = relative3(entriesRoot, path).replace(/\\/g, "/");
|
|
15482
|
+
try {
|
|
15483
|
+
const visibility = classifyVisibility(parseMarkdown(readFileSync8(path, "utf-8")).frontmatter.visibility);
|
|
15484
|
+
if (visibility !== "public") bad.push(`${relPath}: visibility is ${visibility}`);
|
|
15485
|
+
} catch (err) {
|
|
15486
|
+
bad.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
15487
|
+
}
|
|
15488
|
+
}
|
|
15489
|
+
if (bad.length) throw new Error(`publish mirror verification failed:
|
|
15490
|
+
${bad.join("\n")}`);
|
|
15491
|
+
return { fileCount };
|
|
15492
|
+
}
|
|
15493
|
+
function stagedChanges(raw) {
|
|
15494
|
+
const lines = raw.trim() ? raw.trim().split(/\r?\n/) : [];
|
|
15495
|
+
let added = 0;
|
|
15496
|
+
let modified = 0;
|
|
15497
|
+
let deleted = 0;
|
|
15498
|
+
for (const line of lines) {
|
|
15499
|
+
if (line.startsWith("A")) added++;
|
|
15500
|
+
else if (line.startsWith("D")) deleted++;
|
|
15501
|
+
else modified++;
|
|
15502
|
+
}
|
|
15503
|
+
return { lines, added, modified, deleted };
|
|
15504
|
+
}
|
|
15505
|
+
async function publishOwn(opts) {
|
|
15506
|
+
if (!opts.config.publishTarget) throw new Error("publishTarget is not configured");
|
|
15507
|
+
const collected = collectPublishSet(opts.paths.entriesDir);
|
|
15508
|
+
if (collected.invalid.length) throw new Error(`cannot publish invalid entries:
|
|
15509
|
+
${collected.invalid.map((x2) => `${x2.relPath}: ${x2.reason}`).join("\n")}`);
|
|
15510
|
+
await preparePublishMirror({ mirrorDir: opts.paths.publishMirrorDir, target: opts.config.publishTarget });
|
|
15511
|
+
writeMirror({ mirrorDir: opts.paths.publishMirrorDir, files: collected.files, readme: publishReadme(collected.files.length, collected.files) });
|
|
15512
|
+
const verified = verifyMirror(opts.paths.publishMirrorDir);
|
|
15513
|
+
if (verified.fileCount !== collected.files.length) throw new Error(`publish mirror file count mismatch: expected ${collected.files.length}, got ${verified.fileCount}`);
|
|
15514
|
+
const git = simpleGit(opts.paths.publishMirrorDir);
|
|
15515
|
+
await git.add("-A");
|
|
15516
|
+
const changes = stagedChanges(await git.raw(["diff", "--cached", "--name-status"]));
|
|
15517
|
+
if (!changes.lines.length) {
|
|
15518
|
+
opts.logger.info("no changes to publish");
|
|
15519
|
+
return { fileCount: verified.fileCount, changed: false, dryRun: Boolean(opts.dryRun) };
|
|
15520
|
+
}
|
|
15521
|
+
for (const line of changes.lines) opts.logger.info(line);
|
|
15522
|
+
if (opts.dryRun) return { fileCount: verified.fileCount, changed: true, dryRun: true };
|
|
15523
|
+
const question = `publish ${changes.lines.length} file change(s)? [y/N]`;
|
|
15524
|
+
if (!opts.yes) {
|
|
15525
|
+
if (!(opts.isTty ?? (() => Boolean(process.stdin.isTTY)))()) throw new Error(`${question}; rerun with --yes to approve non-interactively`);
|
|
15526
|
+
if (!opts.confirmImpl(question)) throw new Error("publish cancelled");
|
|
15527
|
+
}
|
|
15528
|
+
await git.commit(`caveat publish: ${verified.fileCount} public entries (+${changes.added} ~${changes.modified} -${changes.deleted})`);
|
|
15529
|
+
const branch = (await git.raw(["symbolic-ref", "--short", "HEAD"])).trim();
|
|
15530
|
+
await git.push("origin", branch, ["-u"]);
|
|
15531
|
+
return { fileCount: verified.fileCount, changed: true, dryRun: false };
|
|
15532
|
+
}
|
|
15533
|
+
|
|
14986
15534
|
// ../../packages/core/dist/codexSidecar.js
|
|
14987
15535
|
function caveatEntryToSidecarContextBlock(entry) {
|
|
14988
15536
|
const fm = entry.frontmatter;
|
|
@@ -15176,8 +15724,8 @@ function generateSourceSession(now = () => /* @__PURE__ */ new Date()) {
|
|
|
15176
15724
|
}
|
|
15177
15725
|
|
|
15178
15726
|
// ../../packages/core/dist/writer.js
|
|
15179
|
-
import { existsSync as
|
|
15180
|
-
import { dirname as
|
|
15727
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
15728
|
+
import { dirname as dirname5 } from "node:path";
|
|
15181
15729
|
function buildEntry(frontmatter, sections) {
|
|
15182
15730
|
const bodyParts = [];
|
|
15183
15731
|
for (const [heading, content2] of Object.entries(sections)) {
|
|
@@ -15195,14 +15743,14 @@ ${body}${body ? "\n" : ""}`;
|
|
|
15195
15743
|
return { content, body };
|
|
15196
15744
|
}
|
|
15197
15745
|
function writeEntryFile(filePath, content) {
|
|
15198
|
-
const dir =
|
|
15199
|
-
if (!
|
|
15200
|
-
|
|
15746
|
+
const dir = dirname5(filePath);
|
|
15747
|
+
if (!existsSync12(dir)) mkdirSync6(dir, { recursive: true });
|
|
15748
|
+
writeFileSync6(filePath, content, "utf-8");
|
|
15201
15749
|
}
|
|
15202
15750
|
|
|
15203
15751
|
// ../../packages/core/dist/record.js
|
|
15204
|
-
import { statSync as
|
|
15205
|
-
import { join as
|
|
15752
|
+
import { statSync as statSync5 } from "node:fs";
|
|
15753
|
+
import { join as join9 } from "node:path";
|
|
15206
15754
|
var DEFAULT_CATEGORY = "misc";
|
|
15207
15755
|
function recordEntry(input, opts) {
|
|
15208
15756
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -15218,7 +15766,7 @@ function recordEntry(input, opts) {
|
|
|
15218
15766
|
const frontmatter = {
|
|
15219
15767
|
id,
|
|
15220
15768
|
title: input.title,
|
|
15221
|
-
visibility: input.visibility ?? "
|
|
15769
|
+
visibility: input.visibility ?? "private",
|
|
15222
15770
|
confidence: input.confidence ?? "tentative",
|
|
15223
15771
|
outcome: input.outcome ?? "resolved",
|
|
15224
15772
|
tags: input.tags ?? [],
|
|
@@ -15238,9 +15786,9 @@ function recordEntry(input, opts) {
|
|
|
15238
15786
|
const built = buildEntry(frontmatter, sections);
|
|
15239
15787
|
const category = input.category ?? DEFAULT_CATEGORY;
|
|
15240
15788
|
const relPath = `${category}/${id}.md`;
|
|
15241
|
-
const filePath =
|
|
15789
|
+
const filePath = join9(opts.entriesRoot, relPath);
|
|
15242
15790
|
writeEntryFile(filePath, built.content);
|
|
15243
|
-
const stat =
|
|
15791
|
+
const stat = statSync5(filePath);
|
|
15244
15792
|
upsertEntry(opts.db, {
|
|
15245
15793
|
id,
|
|
15246
15794
|
source,
|
|
@@ -15268,8 +15816,8 @@ function formatYmd(d) {
|
|
|
15268
15816
|
}
|
|
15269
15817
|
|
|
15270
15818
|
// ../../packages/core/dist/update.js
|
|
15271
|
-
import { readFileSync as
|
|
15272
|
-
import { join as
|
|
15819
|
+
import { readFileSync as readFileSync9, statSync as statSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
15820
|
+
import { join as join10 } from "node:path";
|
|
15273
15821
|
var IMMUTABLE_KEYS = /* @__PURE__ */ new Set([
|
|
15274
15822
|
"id",
|
|
15275
15823
|
"created_at",
|
|
@@ -15285,8 +15833,8 @@ function updateEntry(id, patch, opts) {
|
|
|
15285
15833
|
throw new Error(`caveat not found: id=${id} source=${source}`);
|
|
15286
15834
|
}
|
|
15287
15835
|
const relPath = row.path;
|
|
15288
|
-
const filePath =
|
|
15289
|
-
const raw =
|
|
15836
|
+
const filePath = join10(opts.entriesRoot, relPath);
|
|
15837
|
+
const raw = readFileSync9(filePath, "utf-8");
|
|
15290
15838
|
const parsed = parseMarkdown(raw);
|
|
15291
15839
|
if (patch.frontmatter) {
|
|
15292
15840
|
for (const key of Object.keys(patch.frontmatter)) {
|
|
@@ -15327,8 +15875,8 @@ function updateEntry(id, patch, opts) {
|
|
|
15327
15875
|
}
|
|
15328
15876
|
}
|
|
15329
15877
|
const built = buildEntry(mergedFrontmatter, mergedSections);
|
|
15330
|
-
|
|
15331
|
-
const stat =
|
|
15878
|
+
writeFileSync7(filePath, built.content, "utf-8");
|
|
15879
|
+
const stat = statSync6(filePath);
|
|
15332
15880
|
upsertEntry(opts.db, {
|
|
15333
15881
|
id,
|
|
15334
15882
|
source,
|
|
@@ -15359,6 +15907,12 @@ export {
|
|
|
15359
15907
|
openDb,
|
|
15360
15908
|
scanSource,
|
|
15361
15909
|
rebuildAll,
|
|
15910
|
+
computeEntriesDigest,
|
|
15911
|
+
readDigestMarker,
|
|
15912
|
+
writeDigestMarker,
|
|
15913
|
+
acquireReindexLock,
|
|
15914
|
+
releaseReindexLock,
|
|
15915
|
+
reindexAllSources,
|
|
15362
15916
|
search,
|
|
15363
15917
|
get,
|
|
15364
15918
|
listRecent,
|
|
@@ -15368,6 +15922,7 @@ export {
|
|
|
15368
15922
|
resolvePaths,
|
|
15369
15923
|
loadConfig,
|
|
15370
15924
|
ensureUserConfig,
|
|
15925
|
+
writeUserConfigPatch,
|
|
15371
15926
|
communityAdd,
|
|
15372
15927
|
communityPull,
|
|
15373
15928
|
communityList,
|
|
@@ -15387,6 +15942,10 @@ export {
|
|
|
15387
15942
|
drainPendingReminders,
|
|
15388
15943
|
markHit,
|
|
15389
15944
|
listStale,
|
|
15945
|
+
syncOwn,
|
|
15946
|
+
KNOWLEDGE_GITIGNORE,
|
|
15947
|
+
initOwnSync,
|
|
15948
|
+
publishOwn,
|
|
15390
15949
|
caveatEntriesToSidecarContextBlocks,
|
|
15391
15950
|
decideCodexSidecarExecution,
|
|
15392
15951
|
buildCodexSidecarDiagnosticsCommand,
|
|
@@ -15414,4 +15973,4 @@ strip-bom-string/index.js:
|
|
|
15414
15973
|
js-yaml/dist/js-yaml.mjs:
|
|
15415
15974
|
(*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT *)
|
|
15416
15975
|
*/
|
|
15417
|
-
//# sourceMappingURL=chunk-
|
|
15976
|
+
//# sourceMappingURL=chunk-Z2XLYCR3.js.map
|