imprnt 0.1.3 → 0.1.4-edge.88
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/README.md +52 -12
- package/dist/cli.js +387 -65
- package/dist/imp.js +387 -65
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1879,6 +1879,28 @@ var init_tags = __esm(() => {
|
|
|
1879
1879
|
var exports_recall = {};
|
|
1880
1880
|
import { lstatSync as lstatSync2, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
1881
1881
|
import { basename as basename4, join as join12, relative as relative4 } from "node:path";
|
|
1882
|
+
function stemWord(w) {
|
|
1883
|
+
if (w.length <= 3)
|
|
1884
|
+
return w;
|
|
1885
|
+
if (/[a-z]$/.test(w) === false)
|
|
1886
|
+
return w;
|
|
1887
|
+
if (w.endsWith("sses"))
|
|
1888
|
+
return w.slice(0, -2);
|
|
1889
|
+
if (w.endsWith("ies") && w.length > 4)
|
|
1890
|
+
return w.slice(0, -3) + "y";
|
|
1891
|
+
if (w.endsWith("ss") || w.endsWith("us") || w.endsWith("is")) {} else if (w.endsWith("s") && !w.endsWith("ous"))
|
|
1892
|
+
w = w.slice(0, -1);
|
|
1893
|
+
if (w.length > 4 && w.endsWith("ing")) {
|
|
1894
|
+
const base = w.slice(0, -3);
|
|
1895
|
+
if (base.length >= 3)
|
|
1896
|
+
w = /([bdfglmnprt])\1$/.test(base) ? base.slice(0, -1) : base;
|
|
1897
|
+
} else if (w.length > 3 && w.endsWith("ed")) {
|
|
1898
|
+
const base = w.slice(0, -2);
|
|
1899
|
+
if (base.length >= 3)
|
|
1900
|
+
w = /([bdfglmnprt])\1$/.test(base) ? base.slice(0, -1) : base;
|
|
1901
|
+
}
|
|
1902
|
+
return w;
|
|
1903
|
+
}
|
|
1882
1904
|
function walk2(dir2) {
|
|
1883
1905
|
const out = [];
|
|
1884
1906
|
for (const entry of readdirSync6(dir2)) {
|
|
@@ -1902,7 +1924,53 @@ function walk2(dir2) {
|
|
|
1902
1924
|
}
|
|
1903
1925
|
return out;
|
|
1904
1926
|
}
|
|
1905
|
-
|
|
1927
|
+
function bestPassages(absPath, n) {
|
|
1928
|
+
let raw = "";
|
|
1929
|
+
try {
|
|
1930
|
+
raw = stripBom(readFileSync11(absPath, "utf8"));
|
|
1931
|
+
} catch {
|
|
1932
|
+
return [];
|
|
1933
|
+
}
|
|
1934
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1935
|
+
const body2 = fmMatch ? raw.slice(fmMatch.index + fmMatch[0].length) : raw;
|
|
1936
|
+
const paras = body2.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
1937
|
+
if (paras.length <= n)
|
|
1938
|
+
return paras;
|
|
1939
|
+
const scoredParas = paras.map((p) => {
|
|
1940
|
+
const tf = new Map;
|
|
1941
|
+
for (const t of tokenize(p))
|
|
1942
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
1943
|
+
let plen = 0;
|
|
1944
|
+
for (const c of tf.values())
|
|
1945
|
+
plen += c;
|
|
1946
|
+
let sc = 0;
|
|
1947
|
+
const used = new Set;
|
|
1948
|
+
for (const group of scoringGroups) {
|
|
1949
|
+
let best = 0, bv = "";
|
|
1950
|
+
for (const v of group) {
|
|
1951
|
+
if (used.has(v))
|
|
1952
|
+
continue;
|
|
1953
|
+
const x = bm25Term(tf.get(v) ?? 0, plen, variantIdf.get(v) ?? 0);
|
|
1954
|
+
if (x > best) {
|
|
1955
|
+
best = x;
|
|
1956
|
+
bv = v;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (best > 0) {
|
|
1960
|
+
sc += best;
|
|
1961
|
+
used.add(bv);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return { p, sc };
|
|
1965
|
+
});
|
|
1966
|
+
const keep = new Set(scoredParas.slice().sort((a, b) => b.sc - a.sc).slice(0, n).filter((x) => x.sc > 0).map((x) => x.p));
|
|
1967
|
+
const out = paras.filter((p) => keep.has(p));
|
|
1968
|
+
return out.length ? out : paras.slice(0, n);
|
|
1969
|
+
}
|
|
1970
|
+
var args2, vault2, limit = 15, gap = 0, passages = 0, proximity = false, coverage = false, stem = false, positional2, query, vocab, MAX_SYNONYM_NGRAM, STOPWORDS, tokenize = (text2) => {
|
|
1971
|
+
const raw = text2.normalize("NFC").toLowerCase().replace(/['’]/gu, "").split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
1972
|
+
return stem ? raw.map(stemWord) : raw;
|
|
1973
|
+
}, phraseSynonymTokens = (q) => {
|
|
1906
1974
|
const out = [];
|
|
1907
1975
|
const words = q.normalize("NFC").toLowerCase().split(/[\s-]+/).map((w) => w.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "")).filter(Boolean);
|
|
1908
1976
|
for (let n = Math.min(words.length, MAX_SYNONYM_NGRAM);n >= 2; n--) {
|
|
@@ -1916,14 +1984,14 @@ var args2, vault2, limit = 15, positional2, query, vocab, MAX_SYNONYM_NGRAM, STO
|
|
|
1916
1984
|
}
|
|
1917
1985
|
}
|
|
1918
1986
|
return out;
|
|
1919
|
-
}, rawTerms, contentTerms, baseTerms, queryTerms, CONTROL2, files, TITLE_BOOST = 3, TAG_BOOST = 2, SUMMARY_BOOST = 1, BODY_BOOST = 1, docs, df, N, avgdl, K1 = 1.5, B = 0.75, idf = (term) => {
|
|
1987
|
+
}, rawTerms, contentTerms, baseTerms, queryTerms, queryVariantSet, CONTROL2, files, TITLE_BOOST = 3, TAG_BOOST = 2, SUMMARY_BOOST = 1, BODY_BOOST = 1, docs, df, N, avgdl, K1 = 1.5, B = 0.75, PROX_WINDOW = 25, PROX_WEIGHT = 0.35, idf = (term) => {
|
|
1920
1988
|
const n = df.get(term) ?? 0;
|
|
1921
1989
|
return Math.max(0, Math.log(1 + (N - n + 0.5) / (n + 0.5)));
|
|
1922
1990
|
}, bm25Term = (tf, dl, termIdf) => {
|
|
1923
1991
|
if (tf <= 0)
|
|
1924
1992
|
return 0;
|
|
1925
1993
|
return termIdf * (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (dl / avgdl)));
|
|
1926
|
-
}, variantIdf, hits, scoringGroups, shown, expanded;
|
|
1994
|
+
}, variantIdf, hits, scoringGroups, cut, shown, expanded;
|
|
1927
1995
|
var init_recall = __esm(() => {
|
|
1928
1996
|
init_tags();
|
|
1929
1997
|
init_moc();
|
|
@@ -1950,12 +2018,42 @@ var init_recall = __esm(() => {
|
|
|
1950
2018
|
process.exit(1);
|
|
1951
2019
|
}
|
|
1952
2020
|
limit = n;
|
|
2021
|
+
} else if (args2[i] === "--proximity") {
|
|
2022
|
+
proximity = true;
|
|
2023
|
+
} else if (args2[i] === "--coverage") {
|
|
2024
|
+
coverage = true;
|
|
2025
|
+
} else if (args2[i] === "--stem") {
|
|
2026
|
+
stem = true;
|
|
2027
|
+
} else if (args2[i] === "--passages") {
|
|
2028
|
+
const tok = args2[++i];
|
|
2029
|
+
if (tok === undefined || !/^[0-9]+$/.test(tok)) {
|
|
2030
|
+
console.error("--passages must be a positive integer");
|
|
2031
|
+
process.exit(1);
|
|
2032
|
+
}
|
|
2033
|
+
const n = parseInt(tok, 10);
|
|
2034
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
2035
|
+
console.error("--passages must be a positive integer");
|
|
2036
|
+
process.exit(1);
|
|
2037
|
+
}
|
|
2038
|
+
passages = n;
|
|
2039
|
+
} else if (args2[i] === "--gap") {
|
|
2040
|
+
const tok = args2[++i];
|
|
2041
|
+
if (tok === undefined || !/^0?\.[0-9]+$/.test(tok)) {
|
|
2042
|
+
console.error("--gap must be a ratio between 0 and 1 (e.g. 0.25)");
|
|
2043
|
+
process.exit(1);
|
|
2044
|
+
}
|
|
2045
|
+
const g = parseFloat(tok);
|
|
2046
|
+
if (!Number.isFinite(g) || g <= 0 || g >= 1) {
|
|
2047
|
+
console.error("--gap must be a ratio between 0 and 1 (e.g. 0.25)");
|
|
2048
|
+
process.exit(1);
|
|
2049
|
+
}
|
|
2050
|
+
gap = g;
|
|
1953
2051
|
} else
|
|
1954
2052
|
positional2.push(args2[i]);
|
|
1955
2053
|
}
|
|
1956
2054
|
query = positional2.join(" ").trim();
|
|
1957
2055
|
if (!query) {
|
|
1958
|
-
console.error('usage: imprnt recall "<query>" [--vault DIR] [--limit N]');
|
|
2056
|
+
console.error('usage: imprnt recall "<query>" [--vault DIR] [--limit N] [--gap R] [--passages N] [--proximity] [--coverage] [--stem]');
|
|
1959
2057
|
process.exit(1);
|
|
1960
2058
|
}
|
|
1961
2059
|
vocab = loadTags(vault2);
|
|
@@ -2063,6 +2161,7 @@ var init_recall = __esm(() => {
|
|
|
2063
2161
|
contentTerms = rawTerms.filter((w) => !STOPWORDS.has(w));
|
|
2064
2162
|
baseTerms = contentTerms.length ? contentTerms : rawTerms;
|
|
2065
2163
|
queryTerms = baseTerms.map((w) => [...new Set([w, ...tokenize(normalize(vocab, w))])]);
|
|
2164
|
+
queryVariantSet = new Set(queryTerms.flat());
|
|
2066
2165
|
for (const t of phraseSynonymTokens(query)) {
|
|
2067
2166
|
if (!STOPWORDS.has(t))
|
|
2068
2167
|
queryTerms.push([t]);
|
|
@@ -2099,11 +2198,26 @@ var init_recall = __esm(() => {
|
|
|
2099
2198
|
add(tokenize(aliases), TITLE_BOOST);
|
|
2100
2199
|
add(tags.flatMap(tokenize), TAG_BOOST);
|
|
2101
2200
|
add(tokenize(summary), SUMMARY_BOOST);
|
|
2102
|
-
|
|
2201
|
+
const bodyTokens = tokenize(body2);
|
|
2202
|
+
add(bodyTokens, BODY_BOOST);
|
|
2203
|
+
let pos;
|
|
2204
|
+
if (proximity) {
|
|
2205
|
+
pos = new Map;
|
|
2206
|
+
for (let bi = 0;bi < bodyTokens.length; bi++) {
|
|
2207
|
+
const t = bodyTokens[bi];
|
|
2208
|
+
if (!queryVariantSet.has(t))
|
|
2209
|
+
continue;
|
|
2210
|
+
const arr = pos.get(t);
|
|
2211
|
+
if (arr)
|
|
2212
|
+
arr.push(bi);
|
|
2213
|
+
else
|
|
2214
|
+
pos.set(t, [bi]);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2103
2217
|
let len = 0;
|
|
2104
2218
|
for (const c of tf.values())
|
|
2105
2219
|
len += c;
|
|
2106
|
-
docs.push({ path, tf, len });
|
|
2220
|
+
docs.push({ path, tf, len, pos });
|
|
2107
2221
|
for (const term of tf.keys())
|
|
2108
2222
|
df.set(term, (df.get(term) ?? 0) + 1);
|
|
2109
2223
|
}
|
|
@@ -2136,23 +2250,69 @@ var init_recall = __esm(() => {
|
|
|
2136
2250
|
scored.add(bestVariant);
|
|
2137
2251
|
}
|
|
2138
2252
|
}
|
|
2139
|
-
if (score > 0)
|
|
2253
|
+
if (score > 0) {
|
|
2254
|
+
if (coverage && scoringGroups.length > 1) {
|
|
2255
|
+
score *= 0.5 + 0.5 * (scored.size / scoringGroups.length);
|
|
2256
|
+
}
|
|
2257
|
+
if (proximity && d.pos && scored.size > 1) {
|
|
2258
|
+
const present = [...scored].filter((t) => d.pos.has(t));
|
|
2259
|
+
let bonus = 0;
|
|
2260
|
+
for (let x = 0;x < present.length; x++) {
|
|
2261
|
+
for (let y = x + 1;y < present.length; y++) {
|
|
2262
|
+
const a = d.pos.get(present[x]), b = d.pos.get(present[y]);
|
|
2263
|
+
let best = Infinity;
|
|
2264
|
+
for (let ia = 0, ib = 0;ia < a.length && ib < b.length; ) {
|
|
2265
|
+
const dist = Math.abs(a[ia] - b[ib]);
|
|
2266
|
+
if (dist < best)
|
|
2267
|
+
best = dist;
|
|
2268
|
+
if (a[ia] < b[ib])
|
|
2269
|
+
ia++;
|
|
2270
|
+
else
|
|
2271
|
+
ib++;
|
|
2272
|
+
}
|
|
2273
|
+
if (best < PROX_WINDOW) {
|
|
2274
|
+
const w = Math.min(variantIdf.get(present[x]) ?? 0, variantIdf.get(present[y]) ?? 0);
|
|
2275
|
+
bonus += PROX_WEIGHT * w * (1 - best / PROX_WINDOW);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
score += bonus;
|
|
2280
|
+
}
|
|
2140
2281
|
hits.push({ path: relative4(vault2, d.path), score: Math.round(score * 100) / 100 });
|
|
2282
|
+
}
|
|
2141
2283
|
}
|
|
2142
2284
|
hits.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
2143
2285
|
if (!hits.length) {
|
|
2144
2286
|
console.log(`no matches for "${query}" in ${vault2}`);
|
|
2145
2287
|
process.exit(0);
|
|
2146
2288
|
}
|
|
2147
|
-
|
|
2289
|
+
cut = Math.min(limit, hits.length);
|
|
2290
|
+
if (gap > 0) {
|
|
2291
|
+
for (let i = 1;i < cut; i++) {
|
|
2292
|
+
if (hits[i].score < gap * hits[i - 1].score) {
|
|
2293
|
+
cut = i;
|
|
2294
|
+
break;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
shown = hits.slice(0, cut);
|
|
2148
2299
|
expanded = queryTerms.map((g) => g.join("|")).join(" ");
|
|
2149
|
-
console.log(`recall "${query}" [${expanded}]
|
|
2300
|
+
console.log(`recall "${query}" [${expanded}] - ${hits.length} match(es)${hits.length > shown.length ? `, showing top ${shown.length}` : ""}, BM25-ranked:
|
|
2150
2301
|
`);
|
|
2151
|
-
for (const h of shown)
|
|
2302
|
+
for (const h of shown) {
|
|
2152
2303
|
console.log(` [${h.score.toFixed(2)}] ${h.path}`);
|
|
2304
|
+
if (passages > 0) {
|
|
2305
|
+
for (const p of bestPassages(join12(vault2, h.path), passages)) {
|
|
2306
|
+
for (const line of p.split(`
|
|
2307
|
+
`))
|
|
2308
|
+
console.log(` ${line}`);
|
|
2309
|
+
console.log("");
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2153
2313
|
if (hits.length > shown.length) {
|
|
2154
2314
|
console.log(`
|
|
2155
|
-
|
|
2315
|
+
... ${hits.length - shown.length} lower-ranked hit(s) hidden. Raise with --limit if needed. Usually you do not.`);
|
|
2156
2316
|
}
|
|
2157
2317
|
});
|
|
2158
2318
|
|
|
@@ -2261,11 +2421,11 @@ var init_snapshot = __esm(() => {
|
|
|
2261
2421
|
continue;
|
|
2262
2422
|
}
|
|
2263
2423
|
const ext = extname2(rel);
|
|
2264
|
-
const
|
|
2265
|
-
let relD = `${
|
|
2424
|
+
const stem2 = rel.slice(0, rel.length - ext.length);
|
|
2425
|
+
let relD = `${stem2}-${hash2.slice(0, 8)}${ext}`;
|
|
2266
2426
|
let n = 2;
|
|
2267
2427
|
while (existsSync11(join13(destRoot, relD)) && Buffer.compare(readFileSync12(join13(destRoot, relD)), srcBytes2) !== 0) {
|
|
2268
|
-
relD = `${
|
|
2428
|
+
relD = `${stem2}-${hash2.slice(0, 8)}-${n}${ext}`;
|
|
2269
2429
|
n++;
|
|
2270
2430
|
}
|
|
2271
2431
|
rawPath2 = join13(destRoot, relD);
|
|
@@ -2661,19 +2821,177 @@ all plugin checks passed.`);
|
|
|
2661
2821
|
}
|
|
2662
2822
|
});
|
|
2663
2823
|
|
|
2664
|
-
// scripts/
|
|
2665
|
-
var
|
|
2666
|
-
import { cpSync as cpSync3, mkdirSync as mkdirSync5, chmodSync, existsSync as existsSync13, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
2824
|
+
// scripts/vault.ts
|
|
2825
|
+
var exports_vault = {};
|
|
2667
2826
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2668
|
-
import {
|
|
2827
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
|
|
2669
2828
|
import { homedir as homedir4 } from "node:os";
|
|
2829
|
+
import { basename as basename6, dirname as dirname4, join as join15, resolve as resolve6 } from "node:path";
|
|
2830
|
+
function noteCount(project) {
|
|
2831
|
+
const root = join15(project, "vault");
|
|
2832
|
+
if (!existsSync13(root))
|
|
2833
|
+
return 0;
|
|
2834
|
+
let n = 0;
|
|
2835
|
+
const walk3 = (dir2, atRoot) => {
|
|
2836
|
+
for (const e of readdirSync9(dir2, { withFileTypes: true })) {
|
|
2837
|
+
if (e.name.startsWith(".") || e.name.startsWith("_"))
|
|
2838
|
+
continue;
|
|
2839
|
+
if (e.isSymbolicLink())
|
|
2840
|
+
continue;
|
|
2841
|
+
if (atRoot && CONTROL3.has(e.name))
|
|
2842
|
+
continue;
|
|
2843
|
+
if (e.isDirectory())
|
|
2844
|
+
walk3(join15(dir2, e.name), false);
|
|
2845
|
+
else if (e.name.endsWith(".md"))
|
|
2846
|
+
n++;
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
try {
|
|
2850
|
+
walk3(root, true);
|
|
2851
|
+
} catch {}
|
|
2852
|
+
return n;
|
|
2853
|
+
}
|
|
2854
|
+
function resolveTarget(token) {
|
|
2855
|
+
const reg = readRegistry();
|
|
2856
|
+
if (token) {
|
|
2857
|
+
if (reg.vaults[token])
|
|
2858
|
+
return { name: token, path: reg.vaults[token] };
|
|
2859
|
+
const p = expand(token);
|
|
2860
|
+
if (isVaultProject(p)) {
|
|
2861
|
+
const named = Object.entries(reg.vaults).find(([, v]) => v === p)?.[0];
|
|
2862
|
+
return { name: named ?? basename6(p), path: p };
|
|
2863
|
+
}
|
|
2864
|
+
console.error(`no vault called "${token}", and ${p} is not a vault project`);
|
|
2865
|
+
console.error(`known: ${Object.keys(reg.vaults).join(", ") || "(none registered — run `imprnt init`)"}`);
|
|
2866
|
+
process.exit(1);
|
|
2867
|
+
}
|
|
2868
|
+
const name = reg.default ?? Object.keys(reg.vaults)[0];
|
|
2869
|
+
const path = name ? reg.vaults[name] : undefined;
|
|
2870
|
+
if (!name || !path) {
|
|
2871
|
+
console.error("no vault registered — run `imprnt init`");
|
|
2872
|
+
process.exit(1);
|
|
2873
|
+
}
|
|
2874
|
+
return { name, path };
|
|
2875
|
+
}
|
|
2876
|
+
function tar(...a) {
|
|
2877
|
+
const r = spawnSync4("tar", a, { stdio: ["ignore", "inherit", "inherit"] });
|
|
2878
|
+
if (r.error) {
|
|
2879
|
+
console.error(`tar is not available: ${r.error.message}`);
|
|
2880
|
+
process.exit(1);
|
|
2881
|
+
}
|
|
2882
|
+
return r.status ?? 1;
|
|
2883
|
+
}
|
|
2884
|
+
var args5, sub, flag = (name) => {
|
|
2885
|
+
const i = args5.indexOf(name);
|
|
2886
|
+
return i >= 0 ? args5[i + 1] : undefined;
|
|
2887
|
+
}, positionals, expand = (p) => resolve6(p.startsWith("~/") ? join15(homedir4(), p.slice(2)) : p), CONTROL3;
|
|
2888
|
+
var init_vault = __esm(() => {
|
|
2889
|
+
init_registry();
|
|
2890
|
+
args5 = process.argv.slice(2);
|
|
2891
|
+
sub = args5[0];
|
|
2892
|
+
positionals = args5.slice(1).filter((a, i, all2) => !a.startsWith("--") && !(i > 0 && all2[i - 1]?.startsWith("--")));
|
|
2893
|
+
CONTROL3 = new Set(["index.md", "hot.md", "log.md", "_tags.md"]);
|
|
2894
|
+
switch (sub) {
|
|
2895
|
+
case "list": {
|
|
2896
|
+
const reg = readRegistry();
|
|
2897
|
+
const names = Object.keys(reg.vaults);
|
|
2898
|
+
if (!names.length) {
|
|
2899
|
+
console.log("no vaults registered — run `imprnt init`");
|
|
2900
|
+
break;
|
|
2901
|
+
}
|
|
2902
|
+
const def = reg.default ?? names[0];
|
|
2903
|
+
for (const n of names) {
|
|
2904
|
+
const p = reg.vaults[n];
|
|
2905
|
+
const live = isVaultProject(p);
|
|
2906
|
+
const mark = n === def ? "*" : " ";
|
|
2907
|
+
const detail = live ? `${noteCount(p)} notes` : "MISSING — the path is registered but holds no vault";
|
|
2908
|
+
console.log(`${mark} ${n.padEnd(12)} ${p} (${detail})`);
|
|
2909
|
+
}
|
|
2910
|
+
console.log("\n* = default (what `imp` and `imprnt context` resolve to)");
|
|
2911
|
+
break;
|
|
2912
|
+
}
|
|
2913
|
+
case "archive": {
|
|
2914
|
+
const { name, path } = resolveTarget(positionals[0]);
|
|
2915
|
+
if (!isVaultProject(path)) {
|
|
2916
|
+
console.error(`${path} is not a vault project`);
|
|
2917
|
+
process.exit(1);
|
|
2918
|
+
}
|
|
2919
|
+
const outDir = expand(flag("--out") ?? ".");
|
|
2920
|
+
mkdirSync5(outDir, { recursive: true });
|
|
2921
|
+
const stamp = new Date().toISOString().slice(0, 10);
|
|
2922
|
+
const out = join15(outDir, `${name}-${stamp}.tar.gz`);
|
|
2923
|
+
if (tar("czf", out, "-C", dirname4(path), basename6(path)) !== 0) {
|
|
2924
|
+
console.error("archive failed");
|
|
2925
|
+
process.exit(1);
|
|
2926
|
+
}
|
|
2927
|
+
if (tar("tzf", out) !== 0) {
|
|
2928
|
+
console.error(`${out} does not read back — treat it as no archive at all`);
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
}
|
|
2931
|
+
const size = (statSync9(out).size / 1024 / 1024).toFixed(1);
|
|
2932
|
+
console.log(`archived ${name} (${noteCount(path)} notes) → ${out} ${size} MB, verified`);
|
|
2933
|
+
console.log(`restore with: imprnt vault restore ${out} <dir>`);
|
|
2934
|
+
break;
|
|
2935
|
+
}
|
|
2936
|
+
case "restore": {
|
|
2937
|
+
const src3 = expand(positionals[0] ?? "");
|
|
2938
|
+
const dest2 = expand(positionals[1] ?? "");
|
|
2939
|
+
if (!positionals[0] || !positionals[1]) {
|
|
2940
|
+
console.error("usage: imprnt vault restore <archive.tar.gz> <dir>");
|
|
2941
|
+
process.exit(1);
|
|
2942
|
+
}
|
|
2943
|
+
if (!existsSync13(src3)) {
|
|
2944
|
+
console.error(`no archive at ${src3}`);
|
|
2945
|
+
process.exit(1);
|
|
2946
|
+
}
|
|
2947
|
+
if (existsSync13(dest2) && readdirSync9(dest2).length) {
|
|
2948
|
+
console.error(`${dest2} is not empty — restore into a new directory, then move it`);
|
|
2949
|
+
process.exit(1);
|
|
2950
|
+
}
|
|
2951
|
+
mkdirSync5(dest2, { recursive: true });
|
|
2952
|
+
if (tar("xzf", src3, "-C", dest2) !== 0) {
|
|
2953
|
+
console.error("restore failed");
|
|
2954
|
+
process.exit(1);
|
|
2955
|
+
}
|
|
2956
|
+
const inner = readdirSync9(dest2).map((e) => join15(dest2, e)).filter((p) => statSync9(p).isDirectory());
|
|
2957
|
+
const project = inner.find((p) => isVaultProject(p)) ?? (isVaultProject(dest2) ? dest2 : undefined);
|
|
2958
|
+
if (!project) {
|
|
2959
|
+
console.error(`extracted, but ${dest2} holds no vault project — check the archive`);
|
|
2960
|
+
process.exit(1);
|
|
2961
|
+
}
|
|
2962
|
+
console.log(`restored ${noteCount(project)} notes → ${project}`);
|
|
2963
|
+
console.log(`register it with: cd ${project} && imprnt init`);
|
|
2964
|
+
break;
|
|
2965
|
+
}
|
|
2966
|
+
default:
|
|
2967
|
+
console.log(`imprnt vault — the vault as its own object
|
|
2968
|
+
|
|
2969
|
+
imprnt vault list vaults registered on this machine
|
|
2970
|
+
imprnt vault archive [name|path] one verified tarball (--out <dir>)
|
|
2971
|
+
imprnt vault restore <archive> <dir> put one back
|
|
2972
|
+
|
|
2973
|
+
Create one with \`imprnt init\`. There is no \`vault delete\`: removing knowledge is a
|
|
2974
|
+
human act with rm and a path, not a subcommand a script or an agent can reach.
|
|
2975
|
+
|
|
2976
|
+
A vault means something with no agent attached. An agent with no vault is a process
|
|
2977
|
+
pointed at nothing — so the vault outlives whatever reads it.`);
|
|
2978
|
+
process.exit(sub && sub !== "help" && sub !== "--help" ? 1 : 0);
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
// scripts/cli.ts
|
|
2983
|
+
var exports_cli = {};
|
|
2984
|
+
import { cpSync as cpSync3, mkdirSync as mkdirSync6, chmodSync, existsSync as existsSync14, readFileSync as readFileSync14, lstatSync as lstatSync4, statSync as statSync10, realpathSync as realpathSync2 } from "node:fs";
|
|
2985
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
2986
|
+
import { join as join16, dirname as dirname5, resolve as resolve7, basename as basename7 } from "node:path";
|
|
2987
|
+
import { homedir as homedir5 } from "node:os";
|
|
2670
2988
|
import { fileURLToPath } from "node:url";
|
|
2671
2989
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
2672
2990
|
function pluginRoot() {
|
|
2673
2991
|
const local = projectRoot();
|
|
2674
2992
|
if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
|
|
2675
2993
|
return local;
|
|
2676
|
-
if (
|
|
2994
|
+
if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
|
|
2677
2995
|
return local;
|
|
2678
2996
|
return registeredRoot() ?? local;
|
|
2679
2997
|
}
|
|
@@ -2697,29 +3015,29 @@ function requireVaultHome() {
|
|
|
2697
3015
|
}
|
|
2698
3016
|
return home;
|
|
2699
3017
|
}
|
|
2700
|
-
function resolvePath(input, base = process.cwd(), home =
|
|
3018
|
+
function resolvePath(input, base = process.cwd(), home = homedir5()) {
|
|
2701
3019
|
let p = input;
|
|
2702
3020
|
if (p === "~")
|
|
2703
3021
|
p = home;
|
|
2704
3022
|
else if (p.startsWith("~/"))
|
|
2705
|
-
p =
|
|
2706
|
-
return
|
|
3023
|
+
p = join16(home, p.slice(2));
|
|
3024
|
+
return resolve7(base, p);
|
|
2707
3025
|
}
|
|
2708
|
-
function initPositional(
|
|
2709
|
-
return
|
|
3026
|
+
function initPositional(args6) {
|
|
3027
|
+
return args6.find((a) => !a.startsWith("-"));
|
|
2710
3028
|
}
|
|
2711
3029
|
function physicalTarget(target) {
|
|
2712
3030
|
let existing = target;
|
|
2713
3031
|
const tail = [];
|
|
2714
|
-
while (!
|
|
2715
|
-
const parent =
|
|
3032
|
+
while (!existsSync14(existing)) {
|
|
3033
|
+
const parent = dirname5(existing);
|
|
2716
3034
|
if (parent === existing)
|
|
2717
3035
|
return target;
|
|
2718
|
-
tail.unshift(
|
|
3036
|
+
tail.unshift(basename7(existing));
|
|
2719
3037
|
existing = parent;
|
|
2720
3038
|
}
|
|
2721
3039
|
try {
|
|
2722
|
-
return tail.length ?
|
|
3040
|
+
return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
|
|
2723
3041
|
} catch {
|
|
2724
3042
|
return target;
|
|
2725
3043
|
}
|
|
@@ -2735,7 +3053,7 @@ function controlSlot(dst) {
|
|
|
2735
3053
|
return "blocked-directory";
|
|
2736
3054
|
if (st.isSymbolicLink()) {
|
|
2737
3055
|
try {
|
|
2738
|
-
return
|
|
3056
|
+
return statSync10(dst).isFile() ? "keep" : "blocked-symlink";
|
|
2739
3057
|
} catch {
|
|
2740
3058
|
return "blocked-symlink";
|
|
2741
3059
|
}
|
|
@@ -2754,11 +3072,11 @@ var init_cli = __esm(async () => {
|
|
|
2754
3072
|
init_moc();
|
|
2755
3073
|
init_registry();
|
|
2756
3074
|
init_launch();
|
|
2757
|
-
here =
|
|
2758
|
-
pkgRoot =
|
|
3075
|
+
here = dirname5(fileURLToPath(import.meta.url));
|
|
3076
|
+
pkgRoot = join16(here, "..");
|
|
2759
3077
|
[cmd, ...rest] = process.argv.slice(2);
|
|
2760
3078
|
asImp = globalThis.__IMPRNT_IMP__ === true;
|
|
2761
|
-
if (["ingest", "recall", "snapshot", "check"].includes(cmd))
|
|
3079
|
+
if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
|
|
2762
3080
|
process.argv.splice(2, 1);
|
|
2763
3081
|
switch (cmd) {
|
|
2764
3082
|
case "ingest":
|
|
@@ -2773,6 +3091,9 @@ var init_cli = __esm(async () => {
|
|
|
2773
3091
|
case "check":
|
|
2774
3092
|
await Promise.resolve().then(() => (init_check(), exports_check));
|
|
2775
3093
|
break;
|
|
3094
|
+
case "vault":
|
|
3095
|
+
await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
3096
|
+
break;
|
|
2776
3097
|
case "hot": {
|
|
2777
3098
|
const vault5 = vaultArg();
|
|
2778
3099
|
const review2 = openNeedsReview(vault5);
|
|
@@ -2782,8 +3103,8 @@ var init_cli = __esm(async () => {
|
|
|
2782
3103
|
console.log(r);
|
|
2783
3104
|
console.log("");
|
|
2784
3105
|
}
|
|
2785
|
-
const p =
|
|
2786
|
-
if (!
|
|
3106
|
+
const p = join16(vault5, "hot.md");
|
|
3107
|
+
if (!existsSync14(p)) {
|
|
2787
3108
|
console.error(`no hot.md at ${p} — run \`imprnt init\``);
|
|
2788
3109
|
process.exit(1);
|
|
2789
3110
|
}
|
|
@@ -2799,8 +3120,8 @@ var init_cli = __esm(async () => {
|
|
|
2799
3120
|
}
|
|
2800
3121
|
case "context": {
|
|
2801
3122
|
const home = requireVaultHome();
|
|
2802
|
-
const contract =
|
|
2803
|
-
if (!
|
|
3123
|
+
const contract = join16(home, "CLAUDE.md");
|
|
3124
|
+
if (!existsSync14(contract)) {
|
|
2804
3125
|
console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
|
|
2805
3126
|
process.exit(1);
|
|
2806
3127
|
}
|
|
@@ -2883,8 +3204,8 @@ var init_cli = __esm(async () => {
|
|
|
2883
3204
|
const proj = pluginRoot();
|
|
2884
3205
|
if (proj !== process.cwd())
|
|
2885
3206
|
console.error(`(targeting vault project: ${proj})`);
|
|
2886
|
-
const [
|
|
2887
|
-
if (
|
|
3207
|
+
const [sub2, ...specs] = rest;
|
|
3208
|
+
if (sub2 === "list") {
|
|
2888
3209
|
const dirs = listPluginDirs(proj);
|
|
2889
3210
|
console.log("plugins:");
|
|
2890
3211
|
if (!dirs.length)
|
|
@@ -2899,7 +3220,7 @@ available to add: ${available.join(", ")}`);
|
|
|
2899
3220
|
enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
2900
3221
|
break;
|
|
2901
3222
|
}
|
|
2902
|
-
if (
|
|
3223
|
+
if (sub2 === "add") {
|
|
2903
3224
|
let from;
|
|
2904
3225
|
let force = false;
|
|
2905
3226
|
const names = [];
|
|
@@ -2963,7 +3284,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
2963
3284
|
process.exit(1);
|
|
2964
3285
|
break;
|
|
2965
3286
|
}
|
|
2966
|
-
if (
|
|
3287
|
+
if (sub2 === "rm") {
|
|
2967
3288
|
let purge = false;
|
|
2968
3289
|
const names = [];
|
|
2969
3290
|
for (const s of specs) {
|
|
@@ -3003,9 +3324,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3003
3324
|
process.exit(1);
|
|
3004
3325
|
}
|
|
3005
3326
|
case "global": {
|
|
3006
|
-
const globalDir = process.env.CLAUDE_CONFIG_DIR ||
|
|
3007
|
-
const [
|
|
3008
|
-
if (
|
|
3327
|
+
const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
|
|
3328
|
+
const [sub2, ...gargs] = rest;
|
|
3329
|
+
if (sub2 === "list") {
|
|
3009
3330
|
const enabled = listGlobalModules(globalDir);
|
|
3010
3331
|
const onDisk = installedGlobalDirs(globalDir);
|
|
3011
3332
|
console.log(`global modules (in ${globalDir}/imprnt/):`);
|
|
@@ -3019,7 +3340,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3019
3340
|
console.log("\nthese load in every imp session (not a plain `claude`). enable: imprnt global add <name> disable: imprnt global rm <name> [--purge]");
|
|
3020
3341
|
break;
|
|
3021
3342
|
}
|
|
3022
|
-
if (
|
|
3343
|
+
if (sub2 === "add") {
|
|
3023
3344
|
let from;
|
|
3024
3345
|
const names = [];
|
|
3025
3346
|
for (let i = 0;i < gargs.length; i++) {
|
|
@@ -3038,8 +3359,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3038
3359
|
}
|
|
3039
3360
|
const name = names[0];
|
|
3040
3361
|
const proj = pluginRoot();
|
|
3041
|
-
const srcDir = from ?
|
|
3042
|
-
if (!
|
|
3362
|
+
const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
|
|
3363
|
+
if (!existsSync14(srcDir)) {
|
|
3043
3364
|
console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
|
|
3044
3365
|
process.exit(1);
|
|
3045
3366
|
}
|
|
@@ -3051,7 +3372,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3051
3372
|
console.log(r.changed ? `enabled ${name} globally → loads in every imp session (copied to ${globalDir}/imprnt/${name}/)` : `${name} already enabled globally (refreshed the copy)`);
|
|
3052
3373
|
break;
|
|
3053
3374
|
}
|
|
3054
|
-
if (
|
|
3375
|
+
if (sub2 === "rm") {
|
|
3055
3376
|
let purge = false;
|
|
3056
3377
|
const names = [];
|
|
3057
3378
|
for (const a of gargs) {
|
|
@@ -3083,7 +3404,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3083
3404
|
} else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
3084
3405
|
const inPlace = isVaultProject(process.cwd());
|
|
3085
3406
|
const display = inPlace ? process.cwd() : "~/imprnt";
|
|
3086
|
-
const fallback = inPlace ? process.cwd() :
|
|
3407
|
+
const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
|
|
3087
3408
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3088
3409
|
const answer = (await rl.question(`vault location [${display}]: `)).trim();
|
|
3089
3410
|
rl.close();
|
|
@@ -3092,7 +3413,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3092
3413
|
target = process.cwd();
|
|
3093
3414
|
}
|
|
3094
3415
|
const toCwd = target === process.cwd();
|
|
3095
|
-
const rel = (
|
|
3416
|
+
const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
|
|
3096
3417
|
const lexicalEnclosing = projectRoot(target);
|
|
3097
3418
|
const physical = physicalTarget(target);
|
|
3098
3419
|
const physicalEnclosing = projectRoot(physical);
|
|
@@ -3105,15 +3426,15 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3105
3426
|
const domains = ["identity", "health", "finances", "work", "life", "projects"];
|
|
3106
3427
|
const forms = ["events", "mistakes"];
|
|
3107
3428
|
const vaultDirs = [...entities, ...domains, ...forms];
|
|
3108
|
-
const vaultPath =
|
|
3109
|
-
const vaultExisted =
|
|
3429
|
+
const vaultPath = join16(target, "vault");
|
|
3430
|
+
const vaultExisted = existsSync14(vaultPath);
|
|
3110
3431
|
let createdDirs = 0;
|
|
3111
3432
|
for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
|
|
3112
|
-
const abs = d ?
|
|
3113
|
-
if (!
|
|
3433
|
+
const abs = d ? join16(target, d) : target;
|
|
3434
|
+
if (!existsSync14(abs))
|
|
3114
3435
|
createdDirs++;
|
|
3115
3436
|
try {
|
|
3116
|
-
|
|
3437
|
+
mkdirSync6(abs, { recursive: true });
|
|
3117
3438
|
} catch (e) {
|
|
3118
3439
|
console.error(`cannot create ${d ? rel(d) : target}: ${e instanceof Error ? e.message : String(e)}`);
|
|
3119
3440
|
process.exit(1);
|
|
@@ -3125,29 +3446,29 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3125
3446
|
};
|
|
3126
3447
|
const added = [];
|
|
3127
3448
|
for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
|
|
3128
|
-
const dst =
|
|
3449
|
+
const dst = join16(vaultPath, f);
|
|
3129
3450
|
const slot = controlSlot(dst);
|
|
3130
3451
|
if (slot.startsWith("blocked")) {
|
|
3131
3452
|
console.error(blockedMsg(`vault/${f}`, slot));
|
|
3132
3453
|
process.exit(1);
|
|
3133
3454
|
}
|
|
3134
3455
|
if (slot === "create") {
|
|
3135
|
-
cpSync3(
|
|
3456
|
+
cpSync3(join16(pkgRoot, "templates", f), dst);
|
|
3136
3457
|
added.push(`vault/${f}`);
|
|
3137
3458
|
}
|
|
3138
3459
|
}
|
|
3139
|
-
const claudeMd =
|
|
3460
|
+
const claudeMd = join16(target, "CLAUDE.md");
|
|
3140
3461
|
const cmSlot = controlSlot(claudeMd);
|
|
3141
3462
|
if (cmSlot.startsWith("blocked")) {
|
|
3142
3463
|
console.error(blockedMsg("CLAUDE.md", cmSlot));
|
|
3143
3464
|
process.exit(1);
|
|
3144
3465
|
}
|
|
3145
|
-
if (cmSlot === "create" &&
|
|
3146
|
-
cpSync3(
|
|
3466
|
+
if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
|
|
3467
|
+
cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
|
|
3147
3468
|
added.push("CLAUDE.md");
|
|
3148
3469
|
}
|
|
3149
3470
|
try {
|
|
3150
|
-
const prevMode =
|
|
3471
|
+
const prevMode = statSync10(target).mode & 511;
|
|
3151
3472
|
chmodSync(target, 448);
|
|
3152
3473
|
if (prevMode !== 448)
|
|
3153
3474
|
console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
|
|
@@ -3169,8 +3490,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3169
3490
|
console.log(` + ${rel("raw")} for immutable by-source snapshots`);
|
|
3170
3491
|
console.log("next: type `imp` to talk, or ingest a source (`imprnt ingest <file>`), then `imprnt check`.");
|
|
3171
3492
|
} else {
|
|
3172
|
-
const
|
|
3173
|
-
console.log(`found existing vault at ${rel("vault")} — ${
|
|
3493
|
+
const noteCount2 = collectNotes(vaultPath).length;
|
|
3494
|
+
console.log(`found existing vault at ${rel("vault")} — ${noteCount2} note${noteCount2 === 1 ? "" : "s"}, left untouched`);
|
|
3174
3495
|
if (added.length)
|
|
3175
3496
|
console.log(` added missing control file${added.length === 1 ? "" : "s"}: ${added.join(", ")}`);
|
|
3176
3497
|
else if (createdDirs)
|
|
@@ -3185,9 +3506,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3185
3506
|
const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
|
|
3186
3507
|
const bare = cmd === undefined;
|
|
3187
3508
|
if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
|
|
3188
|
-
const modScript =
|
|
3189
|
-
if (
|
|
3190
|
-
const proc =
|
|
3509
|
+
const modScript = join16(pluginRoot(), "plugins", cmd, `${cmd}.js`);
|
|
3510
|
+
if (existsSync14(modScript)) {
|
|
3511
|
+
const proc = spawnSync5(process.execPath, [modScript, ...rest], { stdio: "inherit" });
|
|
3191
3512
|
process.exit(proc.status ?? 1);
|
|
3192
3513
|
}
|
|
3193
3514
|
}
|
|
@@ -3230,6 +3551,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
|
|
|
3230
3551
|
imprnt global add <name> [--from D] enable a behavior module (e.g. anti-slop) in EVERY imp session (imp injects it; ~/.claude/CLAUDE.md is never touched); bare name promotes a project plugin
|
|
3231
3552
|
imprnt global rm <name> [--purge] disable a global module; --purge deletes the global copy
|
|
3232
3553
|
imprnt global list show globally-enabled modules
|
|
3554
|
+
imprnt vault list|archive|restore the vault as its own object — outlives any agent
|
|
3233
3555
|
imprnt <module> <command> [...] run an installed module's command (e.g. \`imprnt session-host login\`, \`imprnt kleinanzeigen sync\`) — no \`node\` paths
|
|
3234
3556
|
|
|
3235
3557
|
layout: entities (people · orgs · holdings) · domains (identity · health · finances · work · life · projects) · forms (events · mistakes)
|