imprnt 0.1.4-edge.74 → 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 +46 -6
- package/dist/cli.js +213 -51
- package/dist/imp.js +213 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
[](https://github.com/aleksandr-bogdanov/imprnt/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
|
+
[](https://www.npmjs.com/package/imprnt)
|
|
8
9
|
|
|
9
10
|
Your assistant starts every chat blank. You re-explain your projects, your people, and your
|
|
10
11
|
decisions every time, and whatever it learns dies with the session. imprnt fixes that. You talk,
|
|
11
12
|
your assistant files what matters into plain text files on your disk, and weeks later it answers
|
|
12
13
|
from your real history. You can read every note with your own eyes, and no company can switch
|
|
13
|
-
them off.
|
|
14
|
+
them off. Sibling to [Whenful](https://whenful.com): Whenful answers *when* do I do my tasks,
|
|
15
|
+
imprnt holds *what* I know.
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
> in a runtime."
|
|
17
|
-
> - Jensen Huang, NVIDIA (GTC Taipei keynote, June 2026)
|
|
17
|
+
<picture><source media="(prefers-color-scheme: dark)" srcset=".github/assets/recall-dark.svg"><img src=".github/assets/recall-light.svg" alt="imprnt recall: ranked results from a local BM25 search" width="830"></picture>
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
That is the whole read path. Term frequencies and a bit of arithmetic over a folder, with no model
|
|
20
|
+
in the loop.
|
|
21
21
|
|
|
22
22
|
## See it work
|
|
23
23
|
|
|
@@ -41,6 +41,8 @@ plain, cheap, local code.
|
|
|
41
41
|
|
|
42
42
|
## Why plain files win
|
|
43
43
|
|
|
44
|
+
<picture><source media="(prefers-color-scheme: dark)" srcset=".github/assets/lanes-dark.svg"><img src=".github/assets/lanes-light.svg" alt="The write path runs once per source and uses the model. The read path runs thousands of times and uses grep plus BM25." width="830"></picture>
|
|
45
|
+
|
|
44
46
|
A vector database or a hidden memory feature could hold your knowledge too. Plain files make your
|
|
45
47
|
assistant cheap, honest, and yours:
|
|
46
48
|
|
|
@@ -59,6 +61,44 @@ assistant cheap, honest, and yours:
|
|
|
59
61
|
- **Yours, in the strongest sense.** Plain text on your disk. It opens in any editor, graphs in
|
|
60
62
|
[Obsidian](https://obsidian.md), and cannot 404, bloat, or hold your context hostage.
|
|
61
63
|
|
|
64
|
+
## The numbers, including the one we lose
|
|
65
|
+
|
|
66
|
+
LoCoMo is the benchmark this category is rated on, 1,540 questions about conversations that ran
|
|
67
|
+
for months. On the same answering model Letta published with, imprnt scores 58.6 against their
|
|
68
|
+
74.0. We lose that comparison by about fifteen points, and the confidence intervals do not
|
|
69
|
+
overlap.
|
|
70
|
+
|
|
71
|
+
Running it turned up something worth more than the score. Take one fixed set of imprnt's answers,
|
|
72
|
+
change only the LLM judge, and the result moves 9 points: 68.4 with Claude grading, 77.6 with
|
|
73
|
+
gpt-4.1. That single swap is wider than the entire published spread in the category (mem0 68.44,
|
|
74
|
+
Letta 74.0, Zep 75.14). A LoCoMo number without a named judge is close to meaningless, ours
|
|
75
|
+
included.
|
|
76
|
+
|
|
77
|
+
On the paper's own scorer, token F1 across all five categories, imprnt gets 64.2%. The human
|
|
78
|
+
baseline on the same questions is 87.9%, and the paper's own GPT-4-turbo baseline is 32.1%.
|
|
79
|
+
|
|
80
|
+
imprnt also ships a retrieval eval over the two example vaults: the right note comes back first
|
|
81
|
+
for 89.7% of questions and appears in the top five for 97.4%, across 39 hand-written questions.
|
|
82
|
+
Small corpus, and we wrote the questions, so run it yourself with `bun run eval`.
|
|
83
|
+
|
|
84
|
+
Every run, interval, and reproduction step is written up in
|
|
85
|
+
[`docs/benchmark-2026-08.md`](docs/benchmark-2026-08.md). The tool-by-tool read lives at
|
|
86
|
+
[imprnt.dev/comparison](https://imprnt.dev/comparison/).
|
|
87
|
+
|
|
88
|
+
Prior art, by name: Letta/MemGPT, mem0, and Zep run memory as a service with their own store and
|
|
89
|
+
their own retrieval. Khoj indexes your files with embeddings. Obsidian gives you the plain-file
|
|
90
|
+
vault without an agent contract on top. PAI wires an assistant into a fixed personal structure.
|
|
91
|
+
imprnt keeps both halves: plain markdown you own, and an agent contract that files into it and reads it back with grep and BM25.
|
|
92
|
+
|
|
93
|
+
## Known limits
|
|
94
|
+
|
|
95
|
+
BM25 is lexical. It ranks on the words a note actually contains, so imprnt bridges vocabulary at
|
|
96
|
+
ingest: the model assigns tags, proposes aliases, and records synonyms once per note, and every
|
|
97
|
+
later search rides on that one pass. Embeddings pay that cost per query instead. The bridge has
|
|
98
|
+
holes. Ask "what do I do for work" when the note says "freelance designer" and nothing connects
|
|
99
|
+
the two, and you can miss. The vault is single-user by design. That is the trade for a read path
|
|
100
|
+
that is free, local, and identical every time you run it.
|
|
101
|
+
|
|
62
102
|
## The robot does the work. You stay the boss.
|
|
63
103
|
|
|
64
104
|
The current crop of AI tools wants the wheel. Resident agents (OpenClaw and friends) read your
|
package/dist/cli.js
CHANGED
|
@@ -2821,19 +2821,177 @@ all plugin checks passed.`);
|
|
|
2821
2821
|
}
|
|
2822
2822
|
});
|
|
2823
2823
|
|
|
2824
|
-
// scripts/
|
|
2825
|
-
var
|
|
2826
|
-
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 = {};
|
|
2827
2826
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2828
|
-
import {
|
|
2827
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
|
|
2829
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";
|
|
2830
2988
|
import { fileURLToPath } from "node:url";
|
|
2831
2989
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
2832
2990
|
function pluginRoot() {
|
|
2833
2991
|
const local = projectRoot();
|
|
2834
2992
|
if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
|
|
2835
2993
|
return local;
|
|
2836
|
-
if (
|
|
2994
|
+
if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
|
|
2837
2995
|
return local;
|
|
2838
2996
|
return registeredRoot() ?? local;
|
|
2839
2997
|
}
|
|
@@ -2857,29 +3015,29 @@ function requireVaultHome() {
|
|
|
2857
3015
|
}
|
|
2858
3016
|
return home;
|
|
2859
3017
|
}
|
|
2860
|
-
function resolvePath(input, base = process.cwd(), home =
|
|
3018
|
+
function resolvePath(input, base = process.cwd(), home = homedir5()) {
|
|
2861
3019
|
let p = input;
|
|
2862
3020
|
if (p === "~")
|
|
2863
3021
|
p = home;
|
|
2864
3022
|
else if (p.startsWith("~/"))
|
|
2865
|
-
p =
|
|
2866
|
-
return
|
|
3023
|
+
p = join16(home, p.slice(2));
|
|
3024
|
+
return resolve7(base, p);
|
|
2867
3025
|
}
|
|
2868
|
-
function initPositional(
|
|
2869
|
-
return
|
|
3026
|
+
function initPositional(args6) {
|
|
3027
|
+
return args6.find((a) => !a.startsWith("-"));
|
|
2870
3028
|
}
|
|
2871
3029
|
function physicalTarget(target) {
|
|
2872
3030
|
let existing = target;
|
|
2873
3031
|
const tail = [];
|
|
2874
|
-
while (!
|
|
2875
|
-
const parent =
|
|
3032
|
+
while (!existsSync14(existing)) {
|
|
3033
|
+
const parent = dirname5(existing);
|
|
2876
3034
|
if (parent === existing)
|
|
2877
3035
|
return target;
|
|
2878
|
-
tail.unshift(
|
|
3036
|
+
tail.unshift(basename7(existing));
|
|
2879
3037
|
existing = parent;
|
|
2880
3038
|
}
|
|
2881
3039
|
try {
|
|
2882
|
-
return tail.length ?
|
|
3040
|
+
return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
|
|
2883
3041
|
} catch {
|
|
2884
3042
|
return target;
|
|
2885
3043
|
}
|
|
@@ -2895,7 +3053,7 @@ function controlSlot(dst) {
|
|
|
2895
3053
|
return "blocked-directory";
|
|
2896
3054
|
if (st.isSymbolicLink()) {
|
|
2897
3055
|
try {
|
|
2898
|
-
return
|
|
3056
|
+
return statSync10(dst).isFile() ? "keep" : "blocked-symlink";
|
|
2899
3057
|
} catch {
|
|
2900
3058
|
return "blocked-symlink";
|
|
2901
3059
|
}
|
|
@@ -2914,11 +3072,11 @@ var init_cli = __esm(async () => {
|
|
|
2914
3072
|
init_moc();
|
|
2915
3073
|
init_registry();
|
|
2916
3074
|
init_launch();
|
|
2917
|
-
here =
|
|
2918
|
-
pkgRoot =
|
|
3075
|
+
here = dirname5(fileURLToPath(import.meta.url));
|
|
3076
|
+
pkgRoot = join16(here, "..");
|
|
2919
3077
|
[cmd, ...rest] = process.argv.slice(2);
|
|
2920
3078
|
asImp = globalThis.__IMPRNT_IMP__ === true;
|
|
2921
|
-
if (["ingest", "recall", "snapshot", "check"].includes(cmd))
|
|
3079
|
+
if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
|
|
2922
3080
|
process.argv.splice(2, 1);
|
|
2923
3081
|
switch (cmd) {
|
|
2924
3082
|
case "ingest":
|
|
@@ -2933,6 +3091,9 @@ var init_cli = __esm(async () => {
|
|
|
2933
3091
|
case "check":
|
|
2934
3092
|
await Promise.resolve().then(() => (init_check(), exports_check));
|
|
2935
3093
|
break;
|
|
3094
|
+
case "vault":
|
|
3095
|
+
await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
3096
|
+
break;
|
|
2936
3097
|
case "hot": {
|
|
2937
3098
|
const vault5 = vaultArg();
|
|
2938
3099
|
const review2 = openNeedsReview(vault5);
|
|
@@ -2942,8 +3103,8 @@ var init_cli = __esm(async () => {
|
|
|
2942
3103
|
console.log(r);
|
|
2943
3104
|
console.log("");
|
|
2944
3105
|
}
|
|
2945
|
-
const p =
|
|
2946
|
-
if (!
|
|
3106
|
+
const p = join16(vault5, "hot.md");
|
|
3107
|
+
if (!existsSync14(p)) {
|
|
2947
3108
|
console.error(`no hot.md at ${p} — run \`imprnt init\``);
|
|
2948
3109
|
process.exit(1);
|
|
2949
3110
|
}
|
|
@@ -2959,8 +3120,8 @@ var init_cli = __esm(async () => {
|
|
|
2959
3120
|
}
|
|
2960
3121
|
case "context": {
|
|
2961
3122
|
const home = requireVaultHome();
|
|
2962
|
-
const contract =
|
|
2963
|
-
if (!
|
|
3123
|
+
const contract = join16(home, "CLAUDE.md");
|
|
3124
|
+
if (!existsSync14(contract)) {
|
|
2964
3125
|
console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
|
|
2965
3126
|
process.exit(1);
|
|
2966
3127
|
}
|
|
@@ -3043,8 +3204,8 @@ var init_cli = __esm(async () => {
|
|
|
3043
3204
|
const proj = pluginRoot();
|
|
3044
3205
|
if (proj !== process.cwd())
|
|
3045
3206
|
console.error(`(targeting vault project: ${proj})`);
|
|
3046
|
-
const [
|
|
3047
|
-
if (
|
|
3207
|
+
const [sub2, ...specs] = rest;
|
|
3208
|
+
if (sub2 === "list") {
|
|
3048
3209
|
const dirs = listPluginDirs(proj);
|
|
3049
3210
|
console.log("plugins:");
|
|
3050
3211
|
if (!dirs.length)
|
|
@@ -3059,7 +3220,7 @@ available to add: ${available.join(", ")}`);
|
|
|
3059
3220
|
enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
3060
3221
|
break;
|
|
3061
3222
|
}
|
|
3062
|
-
if (
|
|
3223
|
+
if (sub2 === "add") {
|
|
3063
3224
|
let from;
|
|
3064
3225
|
let force = false;
|
|
3065
3226
|
const names = [];
|
|
@@ -3123,7 +3284,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3123
3284
|
process.exit(1);
|
|
3124
3285
|
break;
|
|
3125
3286
|
}
|
|
3126
|
-
if (
|
|
3287
|
+
if (sub2 === "rm") {
|
|
3127
3288
|
let purge = false;
|
|
3128
3289
|
const names = [];
|
|
3129
3290
|
for (const s of specs) {
|
|
@@ -3163,9 +3324,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3163
3324
|
process.exit(1);
|
|
3164
3325
|
}
|
|
3165
3326
|
case "global": {
|
|
3166
|
-
const globalDir = process.env.CLAUDE_CONFIG_DIR ||
|
|
3167
|
-
const [
|
|
3168
|
-
if (
|
|
3327
|
+
const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
|
|
3328
|
+
const [sub2, ...gargs] = rest;
|
|
3329
|
+
if (sub2 === "list") {
|
|
3169
3330
|
const enabled = listGlobalModules(globalDir);
|
|
3170
3331
|
const onDisk = installedGlobalDirs(globalDir);
|
|
3171
3332
|
console.log(`global modules (in ${globalDir}/imprnt/):`);
|
|
@@ -3179,7 +3340,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3179
3340
|
console.log("\nthese load in every imp session (not a plain `claude`). enable: imprnt global add <name> disable: imprnt global rm <name> [--purge]");
|
|
3180
3341
|
break;
|
|
3181
3342
|
}
|
|
3182
|
-
if (
|
|
3343
|
+
if (sub2 === "add") {
|
|
3183
3344
|
let from;
|
|
3184
3345
|
const names = [];
|
|
3185
3346
|
for (let i = 0;i < gargs.length; i++) {
|
|
@@ -3198,8 +3359,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3198
3359
|
}
|
|
3199
3360
|
const name = names[0];
|
|
3200
3361
|
const proj = pluginRoot();
|
|
3201
|
-
const srcDir = from ?
|
|
3202
|
-
if (!
|
|
3362
|
+
const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
|
|
3363
|
+
if (!existsSync14(srcDir)) {
|
|
3203
3364
|
console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
|
|
3204
3365
|
process.exit(1);
|
|
3205
3366
|
}
|
|
@@ -3211,7 +3372,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3211
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)`);
|
|
3212
3373
|
break;
|
|
3213
3374
|
}
|
|
3214
|
-
if (
|
|
3375
|
+
if (sub2 === "rm") {
|
|
3215
3376
|
let purge = false;
|
|
3216
3377
|
const names = [];
|
|
3217
3378
|
for (const a of gargs) {
|
|
@@ -3243,7 +3404,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3243
3404
|
} else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
3244
3405
|
const inPlace = isVaultProject(process.cwd());
|
|
3245
3406
|
const display = inPlace ? process.cwd() : "~/imprnt";
|
|
3246
|
-
const fallback = inPlace ? process.cwd() :
|
|
3407
|
+
const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
|
|
3247
3408
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3248
3409
|
const answer = (await rl.question(`vault location [${display}]: `)).trim();
|
|
3249
3410
|
rl.close();
|
|
@@ -3252,7 +3413,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3252
3413
|
target = process.cwd();
|
|
3253
3414
|
}
|
|
3254
3415
|
const toCwd = target === process.cwd();
|
|
3255
|
-
const rel = (
|
|
3416
|
+
const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
|
|
3256
3417
|
const lexicalEnclosing = projectRoot(target);
|
|
3257
3418
|
const physical = physicalTarget(target);
|
|
3258
3419
|
const physicalEnclosing = projectRoot(physical);
|
|
@@ -3265,15 +3426,15 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3265
3426
|
const domains = ["identity", "health", "finances", "work", "life", "projects"];
|
|
3266
3427
|
const forms = ["events", "mistakes"];
|
|
3267
3428
|
const vaultDirs = [...entities, ...domains, ...forms];
|
|
3268
|
-
const vaultPath =
|
|
3269
|
-
const vaultExisted =
|
|
3429
|
+
const vaultPath = join16(target, "vault");
|
|
3430
|
+
const vaultExisted = existsSync14(vaultPath);
|
|
3270
3431
|
let createdDirs = 0;
|
|
3271
3432
|
for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
|
|
3272
|
-
const abs = d ?
|
|
3273
|
-
if (!
|
|
3433
|
+
const abs = d ? join16(target, d) : target;
|
|
3434
|
+
if (!existsSync14(abs))
|
|
3274
3435
|
createdDirs++;
|
|
3275
3436
|
try {
|
|
3276
|
-
|
|
3437
|
+
mkdirSync6(abs, { recursive: true });
|
|
3277
3438
|
} catch (e) {
|
|
3278
3439
|
console.error(`cannot create ${d ? rel(d) : target}: ${e instanceof Error ? e.message : String(e)}`);
|
|
3279
3440
|
process.exit(1);
|
|
@@ -3285,29 +3446,29 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3285
3446
|
};
|
|
3286
3447
|
const added = [];
|
|
3287
3448
|
for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
|
|
3288
|
-
const dst =
|
|
3449
|
+
const dst = join16(vaultPath, f);
|
|
3289
3450
|
const slot = controlSlot(dst);
|
|
3290
3451
|
if (slot.startsWith("blocked")) {
|
|
3291
3452
|
console.error(blockedMsg(`vault/${f}`, slot));
|
|
3292
3453
|
process.exit(1);
|
|
3293
3454
|
}
|
|
3294
3455
|
if (slot === "create") {
|
|
3295
|
-
cpSync3(
|
|
3456
|
+
cpSync3(join16(pkgRoot, "templates", f), dst);
|
|
3296
3457
|
added.push(`vault/${f}`);
|
|
3297
3458
|
}
|
|
3298
3459
|
}
|
|
3299
|
-
const claudeMd =
|
|
3460
|
+
const claudeMd = join16(target, "CLAUDE.md");
|
|
3300
3461
|
const cmSlot = controlSlot(claudeMd);
|
|
3301
3462
|
if (cmSlot.startsWith("blocked")) {
|
|
3302
3463
|
console.error(blockedMsg("CLAUDE.md", cmSlot));
|
|
3303
3464
|
process.exit(1);
|
|
3304
3465
|
}
|
|
3305
|
-
if (cmSlot === "create" &&
|
|
3306
|
-
cpSync3(
|
|
3466
|
+
if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
|
|
3467
|
+
cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
|
|
3307
3468
|
added.push("CLAUDE.md");
|
|
3308
3469
|
}
|
|
3309
3470
|
try {
|
|
3310
|
-
const prevMode =
|
|
3471
|
+
const prevMode = statSync10(target).mode & 511;
|
|
3311
3472
|
chmodSync(target, 448);
|
|
3312
3473
|
if (prevMode !== 448)
|
|
3313
3474
|
console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
|
|
@@ -3329,8 +3490,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3329
3490
|
console.log(` + ${rel("raw")} for immutable by-source snapshots`);
|
|
3330
3491
|
console.log("next: type `imp` to talk, or ingest a source (`imprnt ingest <file>`), then `imprnt check`.");
|
|
3331
3492
|
} else {
|
|
3332
|
-
const
|
|
3333
|
-
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`);
|
|
3334
3495
|
if (added.length)
|
|
3335
3496
|
console.log(` added missing control file${added.length === 1 ? "" : "s"}: ${added.join(", ")}`);
|
|
3336
3497
|
else if (createdDirs)
|
|
@@ -3345,9 +3506,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3345
3506
|
const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
|
|
3346
3507
|
const bare = cmd === undefined;
|
|
3347
3508
|
if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
|
|
3348
|
-
const modScript =
|
|
3349
|
-
if (
|
|
3350
|
-
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" });
|
|
3351
3512
|
process.exit(proc.status ?? 1);
|
|
3352
3513
|
}
|
|
3353
3514
|
}
|
|
@@ -3390,6 +3551,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
|
|
|
3390
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
|
|
3391
3552
|
imprnt global rm <name> [--purge] disable a global module; --purge deletes the global copy
|
|
3392
3553
|
imprnt global list show globally-enabled modules
|
|
3554
|
+
imprnt vault list|archive|restore the vault as its own object — outlives any agent
|
|
3393
3555
|
imprnt <module> <command> [...] run an installed module's command (e.g. \`imprnt session-host login\`, \`imprnt kleinanzeigen sync\`) — no \`node\` paths
|
|
3394
3556
|
|
|
3395
3557
|
layout: entities (people · orgs · holdings) · domains (identity · health · finances · work · life · projects) · forms (events · mistakes)
|
package/dist/imp.js
CHANGED
|
@@ -2821,19 +2821,177 @@ all plugin checks passed.`);
|
|
|
2821
2821
|
}
|
|
2822
2822
|
});
|
|
2823
2823
|
|
|
2824
|
-
// scripts/
|
|
2825
|
-
var
|
|
2826
|
-
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 = {};
|
|
2827
2826
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2828
|
-
import {
|
|
2827
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, statSync as statSync9 } from "node:fs";
|
|
2829
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";
|
|
2830
2988
|
import { fileURLToPath } from "node:url";
|
|
2831
2989
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
2832
2990
|
function pluginRoot() {
|
|
2833
2991
|
const local = projectRoot();
|
|
2834
2992
|
if (process.env.IMPRNT_ROOT || process.env.IMPRINT_ROOT)
|
|
2835
2993
|
return local;
|
|
2836
|
-
if (
|
|
2994
|
+
if (existsSync14(join16(local, "vault")) || existsSync14(join16(local, "CLAUDE.local.md")))
|
|
2837
2995
|
return local;
|
|
2838
2996
|
return registeredRoot() ?? local;
|
|
2839
2997
|
}
|
|
@@ -2857,29 +3015,29 @@ function requireVaultHome() {
|
|
|
2857
3015
|
}
|
|
2858
3016
|
return home;
|
|
2859
3017
|
}
|
|
2860
|
-
function resolvePath(input, base = process.cwd(), home =
|
|
3018
|
+
function resolvePath(input, base = process.cwd(), home = homedir5()) {
|
|
2861
3019
|
let p = input;
|
|
2862
3020
|
if (p === "~")
|
|
2863
3021
|
p = home;
|
|
2864
3022
|
else if (p.startsWith("~/"))
|
|
2865
|
-
p =
|
|
2866
|
-
return
|
|
3023
|
+
p = join16(home, p.slice(2));
|
|
3024
|
+
return resolve7(base, p);
|
|
2867
3025
|
}
|
|
2868
|
-
function initPositional(
|
|
2869
|
-
return
|
|
3026
|
+
function initPositional(args6) {
|
|
3027
|
+
return args6.find((a) => !a.startsWith("-"));
|
|
2870
3028
|
}
|
|
2871
3029
|
function physicalTarget(target) {
|
|
2872
3030
|
let existing = target;
|
|
2873
3031
|
const tail = [];
|
|
2874
|
-
while (!
|
|
2875
|
-
const parent =
|
|
3032
|
+
while (!existsSync14(existing)) {
|
|
3033
|
+
const parent = dirname5(existing);
|
|
2876
3034
|
if (parent === existing)
|
|
2877
3035
|
return target;
|
|
2878
|
-
tail.unshift(
|
|
3036
|
+
tail.unshift(basename7(existing));
|
|
2879
3037
|
existing = parent;
|
|
2880
3038
|
}
|
|
2881
3039
|
try {
|
|
2882
|
-
return tail.length ?
|
|
3040
|
+
return tail.length ? join16(realpathSync2(existing), ...tail) : realpathSync2(existing);
|
|
2883
3041
|
} catch {
|
|
2884
3042
|
return target;
|
|
2885
3043
|
}
|
|
@@ -2895,7 +3053,7 @@ function controlSlot(dst) {
|
|
|
2895
3053
|
return "blocked-directory";
|
|
2896
3054
|
if (st.isSymbolicLink()) {
|
|
2897
3055
|
try {
|
|
2898
|
-
return
|
|
3056
|
+
return statSync10(dst).isFile() ? "keep" : "blocked-symlink";
|
|
2899
3057
|
} catch {
|
|
2900
3058
|
return "blocked-symlink";
|
|
2901
3059
|
}
|
|
@@ -2914,11 +3072,11 @@ var init_cli = __esm(async () => {
|
|
|
2914
3072
|
init_moc();
|
|
2915
3073
|
init_registry();
|
|
2916
3074
|
init_launch();
|
|
2917
|
-
here =
|
|
2918
|
-
pkgRoot =
|
|
3075
|
+
here = dirname5(fileURLToPath(import.meta.url));
|
|
3076
|
+
pkgRoot = join16(here, "..");
|
|
2919
3077
|
[cmd, ...rest] = process.argv.slice(2);
|
|
2920
3078
|
asImp = globalThis.__IMPRNT_IMP__ === true;
|
|
2921
|
-
if (["ingest", "recall", "snapshot", "check"].includes(cmd))
|
|
3079
|
+
if (["ingest", "recall", "snapshot", "check", "vault"].includes(cmd))
|
|
2922
3080
|
process.argv.splice(2, 1);
|
|
2923
3081
|
switch (cmd) {
|
|
2924
3082
|
case "ingest":
|
|
@@ -2933,6 +3091,9 @@ var init_cli = __esm(async () => {
|
|
|
2933
3091
|
case "check":
|
|
2934
3092
|
await Promise.resolve().then(() => (init_check(), exports_check));
|
|
2935
3093
|
break;
|
|
3094
|
+
case "vault":
|
|
3095
|
+
await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
3096
|
+
break;
|
|
2936
3097
|
case "hot": {
|
|
2937
3098
|
const vault5 = vaultArg();
|
|
2938
3099
|
const review2 = openNeedsReview(vault5);
|
|
@@ -2942,8 +3103,8 @@ var init_cli = __esm(async () => {
|
|
|
2942
3103
|
console.log(r);
|
|
2943
3104
|
console.log("");
|
|
2944
3105
|
}
|
|
2945
|
-
const p =
|
|
2946
|
-
if (!
|
|
3106
|
+
const p = join16(vault5, "hot.md");
|
|
3107
|
+
if (!existsSync14(p)) {
|
|
2947
3108
|
console.error(`no hot.md at ${p} — run \`imprnt init\``);
|
|
2948
3109
|
process.exit(1);
|
|
2949
3110
|
}
|
|
@@ -2959,8 +3120,8 @@ var init_cli = __esm(async () => {
|
|
|
2959
3120
|
}
|
|
2960
3121
|
case "context": {
|
|
2961
3122
|
const home = requireVaultHome();
|
|
2962
|
-
const contract =
|
|
2963
|
-
if (!
|
|
3123
|
+
const contract = join16(home, "CLAUDE.md");
|
|
3124
|
+
if (!existsSync14(contract)) {
|
|
2964
3125
|
console.error(`no CLAUDE.md at ${home} — run \`imprnt init\` there to drop the contract`);
|
|
2965
3126
|
process.exit(1);
|
|
2966
3127
|
}
|
|
@@ -3043,8 +3204,8 @@ var init_cli = __esm(async () => {
|
|
|
3043
3204
|
const proj = pluginRoot();
|
|
3044
3205
|
if (proj !== process.cwd())
|
|
3045
3206
|
console.error(`(targeting vault project: ${proj})`);
|
|
3046
|
-
const [
|
|
3047
|
-
if (
|
|
3207
|
+
const [sub2, ...specs] = rest;
|
|
3208
|
+
if (sub2 === "list") {
|
|
3048
3209
|
const dirs = listPluginDirs(proj);
|
|
3049
3210
|
console.log("plugins:");
|
|
3050
3211
|
if (!dirs.length)
|
|
@@ -3059,7 +3220,7 @@ available to add: ${available.join(", ")}`);
|
|
|
3059
3220
|
enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
3060
3221
|
break;
|
|
3061
3222
|
}
|
|
3062
|
-
if (
|
|
3223
|
+
if (sub2 === "add") {
|
|
3063
3224
|
let from;
|
|
3064
3225
|
let force = false;
|
|
3065
3226
|
const names = [];
|
|
@@ -3123,7 +3284,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3123
3284
|
process.exit(1);
|
|
3124
3285
|
break;
|
|
3125
3286
|
}
|
|
3126
|
-
if (
|
|
3287
|
+
if (sub2 === "rm") {
|
|
3127
3288
|
let purge = false;
|
|
3128
3289
|
const names = [];
|
|
3129
3290
|
for (const s of specs) {
|
|
@@ -3163,9 +3324,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3163
3324
|
process.exit(1);
|
|
3164
3325
|
}
|
|
3165
3326
|
case "global": {
|
|
3166
|
-
const globalDir = process.env.CLAUDE_CONFIG_DIR ||
|
|
3167
|
-
const [
|
|
3168
|
-
if (
|
|
3327
|
+
const globalDir = process.env.CLAUDE_CONFIG_DIR || join16(homedir5(), ".claude");
|
|
3328
|
+
const [sub2, ...gargs] = rest;
|
|
3329
|
+
if (sub2 === "list") {
|
|
3169
3330
|
const enabled = listGlobalModules(globalDir);
|
|
3170
3331
|
const onDisk = installedGlobalDirs(globalDir);
|
|
3171
3332
|
console.log(`global modules (in ${globalDir}/imprnt/):`);
|
|
@@ -3179,7 +3340,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3179
3340
|
console.log("\nthese load in every imp session (not a plain `claude`). enable: imprnt global add <name> disable: imprnt global rm <name> [--purge]");
|
|
3180
3341
|
break;
|
|
3181
3342
|
}
|
|
3182
|
-
if (
|
|
3343
|
+
if (sub2 === "add") {
|
|
3183
3344
|
let from;
|
|
3184
3345
|
const names = [];
|
|
3185
3346
|
for (let i = 0;i < gargs.length; i++) {
|
|
@@ -3198,8 +3359,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3198
3359
|
}
|
|
3199
3360
|
const name = names[0];
|
|
3200
3361
|
const proj = pluginRoot();
|
|
3201
|
-
const srcDir = from ?
|
|
3202
|
-
if (!
|
|
3362
|
+
const srcDir = from ? resolve7(from) : join16(proj, "plugins", name);
|
|
3363
|
+
if (!existsSync14(srcDir)) {
|
|
3203
3364
|
console.error(`global add ${name}: no source - ${from ? `--from path not found: ${srcDir}` : `no plugins/${name}/ to promote; pass --from <dir>`}`);
|
|
3204
3365
|
process.exit(1);
|
|
3205
3366
|
}
|
|
@@ -3211,7 +3372,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3211
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)`);
|
|
3212
3373
|
break;
|
|
3213
3374
|
}
|
|
3214
|
-
if (
|
|
3375
|
+
if (sub2 === "rm") {
|
|
3215
3376
|
let purge = false;
|
|
3216
3377
|
const names = [];
|
|
3217
3378
|
for (const a of gargs) {
|
|
@@ -3243,7 +3404,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3243
3404
|
} else if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
3244
3405
|
const inPlace = isVaultProject(process.cwd());
|
|
3245
3406
|
const display = inPlace ? process.cwd() : "~/imprnt";
|
|
3246
|
-
const fallback = inPlace ? process.cwd() :
|
|
3407
|
+
const fallback = inPlace ? process.cwd() : join16(homedir5(), "imprnt");
|
|
3247
3408
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3248
3409
|
const answer = (await rl.question(`vault location [${display}]: `)).trim();
|
|
3249
3410
|
rl.close();
|
|
@@ -3252,7 +3413,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3252
3413
|
target = process.cwd();
|
|
3253
3414
|
}
|
|
3254
3415
|
const toCwd = target === process.cwd();
|
|
3255
|
-
const rel = (
|
|
3416
|
+
const rel = (sub2) => toCwd ? `./${sub2}` : join16(target, sub2);
|
|
3256
3417
|
const lexicalEnclosing = projectRoot(target);
|
|
3257
3418
|
const physical = physicalTarget(target);
|
|
3258
3419
|
const physicalEnclosing = projectRoot(physical);
|
|
@@ -3265,15 +3426,15 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3265
3426
|
const domains = ["identity", "health", "finances", "work", "life", "projects"];
|
|
3266
3427
|
const forms = ["events", "mistakes"];
|
|
3267
3428
|
const vaultDirs = [...entities, ...domains, ...forms];
|
|
3268
|
-
const vaultPath =
|
|
3269
|
-
const vaultExisted =
|
|
3429
|
+
const vaultPath = join16(target, "vault");
|
|
3430
|
+
const vaultExisted = existsSync14(vaultPath);
|
|
3270
3431
|
let createdDirs = 0;
|
|
3271
3432
|
for (const d of ["", "vault", ...vaultDirs.map((t) => `vault/${t}`), "raw"]) {
|
|
3272
|
-
const abs = d ?
|
|
3273
|
-
if (!
|
|
3433
|
+
const abs = d ? join16(target, d) : target;
|
|
3434
|
+
if (!existsSync14(abs))
|
|
3274
3435
|
createdDirs++;
|
|
3275
3436
|
try {
|
|
3276
|
-
|
|
3437
|
+
mkdirSync6(abs, { recursive: true });
|
|
3277
3438
|
} catch (e) {
|
|
3278
3439
|
console.error(`cannot create ${d ? rel(d) : target}: ${e instanceof Error ? e.message : String(e)}`);
|
|
3279
3440
|
process.exit(1);
|
|
@@ -3285,29 +3446,29 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3285
3446
|
};
|
|
3286
3447
|
const added = [];
|
|
3287
3448
|
for (const f of ["index.md", "hot.md", "log.md", "_tags.md"]) {
|
|
3288
|
-
const dst =
|
|
3449
|
+
const dst = join16(vaultPath, f);
|
|
3289
3450
|
const slot = controlSlot(dst);
|
|
3290
3451
|
if (slot.startsWith("blocked")) {
|
|
3291
3452
|
console.error(blockedMsg(`vault/${f}`, slot));
|
|
3292
3453
|
process.exit(1);
|
|
3293
3454
|
}
|
|
3294
3455
|
if (slot === "create") {
|
|
3295
|
-
cpSync3(
|
|
3456
|
+
cpSync3(join16(pkgRoot, "templates", f), dst);
|
|
3296
3457
|
added.push(`vault/${f}`);
|
|
3297
3458
|
}
|
|
3298
3459
|
}
|
|
3299
|
-
const claudeMd =
|
|
3460
|
+
const claudeMd = join16(target, "CLAUDE.md");
|
|
3300
3461
|
const cmSlot = controlSlot(claudeMd);
|
|
3301
3462
|
if (cmSlot.startsWith("blocked")) {
|
|
3302
3463
|
console.error(blockedMsg("CLAUDE.md", cmSlot));
|
|
3303
3464
|
process.exit(1);
|
|
3304
3465
|
}
|
|
3305
|
-
if (cmSlot === "create" &&
|
|
3306
|
-
cpSync3(
|
|
3466
|
+
if (cmSlot === "create" && existsSync14(join16(pkgRoot, "CLAUDE.md"))) {
|
|
3467
|
+
cpSync3(join16(pkgRoot, "CLAUDE.md"), claudeMd);
|
|
3307
3468
|
added.push("CLAUDE.md");
|
|
3308
3469
|
}
|
|
3309
3470
|
try {
|
|
3310
|
-
const prevMode =
|
|
3471
|
+
const prevMode = statSync10(target).mode & 511;
|
|
3311
3472
|
chmodSync(target, 448);
|
|
3312
3473
|
if (prevMode !== 448)
|
|
3313
3474
|
console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
|
|
@@ -3329,8 +3490,8 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3329
3490
|
console.log(` + ${rel("raw")} for immutable by-source snapshots`);
|
|
3330
3491
|
console.log("next: type `imp` to talk, or ingest a source (`imprnt ingest <file>`), then `imprnt check`.");
|
|
3331
3492
|
} else {
|
|
3332
|
-
const
|
|
3333
|
-
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`);
|
|
3334
3495
|
if (added.length)
|
|
3335
3496
|
console.log(` added missing control file${added.length === 1 ? "" : "s"}: ${added.join(", ")}`);
|
|
3336
3497
|
else if (createdDirs)
|
|
@@ -3345,9 +3506,9 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3345
3506
|
const isHelp = cmd === "help" || cmd === "--help" || cmd === "-h";
|
|
3346
3507
|
const bare = cmd === undefined;
|
|
3347
3508
|
if (cmd && !isHelp && !bare && !cmd.startsWith("-")) {
|
|
3348
|
-
const modScript =
|
|
3349
|
-
if (
|
|
3350
|
-
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" });
|
|
3351
3512
|
process.exit(proc.status ?? 1);
|
|
3352
3513
|
}
|
|
3353
3514
|
}
|
|
@@ -3390,6 +3551,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
|
|
|
3390
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
|
|
3391
3552
|
imprnt global rm <name> [--purge] disable a global module; --purge deletes the global copy
|
|
3392
3553
|
imprnt global list show globally-enabled modules
|
|
3554
|
+
imprnt vault list|archive|restore the vault as its own object — outlives any agent
|
|
3393
3555
|
imprnt <module> <command> [...] run an installed module's command (e.g. \`imprnt session-host login\`, \`imprnt kleinanzeigen sync\`) — no \`node\` paths
|
|
3394
3556
|
|
|
3395
3557
|
layout: entities (people · orgs · holdings) · domains (identity · health · finances · work · life · projects) · forms (events · mistakes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imprnt",
|
|
3
|
-
"version": "0.1.4-edge.
|
|
3
|
+
"version": "0.1.4-edge.88",
|
|
4
4
|
"stable": true,
|
|
5
5
|
"description": "Deterministic-first, plain-markdown knowledge vault. Code does the bulk transform. The LLM only touches the irreducibly-semantic 20%. Native grep plus BM25 retrieval, zero MCP on the vault.",
|
|
6
6
|
"type": "module",
|