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.
Files changed (4) hide show
  1. package/README.md +46 -6
  2. package/dist/cli.js +213 -51
  3. package/dist/imp.js +213 -51
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -5,19 +5,19 @@
5
5
  [![CI](https://github.com/aleksandr-bogdanov/imprnt/actions/workflows/ci.yml/badge.svg?event=pull_request)](https://github.com/aleksandr-bogdanov/imprnt/actions/workflows/ci.yml)
6
6
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![node >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+ [![npm](https://img.shields.io/npm/v/imprnt.svg)](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
- > "You can think of the model as the brain, the harness as the body, and the tools it uses working
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
- imprnt is the tool layer Huang is pointing at, holding the part that lasts. Sibling to
20
- [Whenful](https://whenful.com): Whenful answers *when* do I do my tasks, imprnt holds *what* I know.
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/cli.ts
2825
- var exports_cli = {};
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 { join as join15, dirname as dirname4, resolve as resolve6, basename as basename6 } from "node:path";
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 (existsSync13(join15(local, "vault")) || existsSync13(join15(local, "CLAUDE.local.md")))
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 = homedir4()) {
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 = join15(home, p.slice(2));
2866
- return resolve6(base, p);
3023
+ p = join16(home, p.slice(2));
3024
+ return resolve7(base, p);
2867
3025
  }
2868
- function initPositional(args5) {
2869
- return args5.find((a) => !a.startsWith("-"));
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 (!existsSync13(existing)) {
2875
- const parent = dirname4(existing);
3032
+ while (!existsSync14(existing)) {
3033
+ const parent = dirname5(existing);
2876
3034
  if (parent === existing)
2877
3035
  return target;
2878
- tail.unshift(basename6(existing));
3036
+ tail.unshift(basename7(existing));
2879
3037
  existing = parent;
2880
3038
  }
2881
3039
  try {
2882
- return tail.length ? join15(realpathSync2(existing), ...tail) : realpathSync2(existing);
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 statSync9(dst).isFile() ? "keep" : "blocked-symlink";
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 = dirname4(fileURLToPath(import.meta.url));
2918
- pkgRoot = join15(here, "..");
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 = join15(vault5, "hot.md");
2946
- if (!existsSync13(p)) {
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 = join15(home, "CLAUDE.md");
2963
- if (!existsSync13(contract)) {
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 [sub, ...specs] = rest;
3047
- if (sub === "list") {
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 (sub === "add") {
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 (sub === "rm") {
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 || join15(homedir4(), ".claude");
3167
- const [sub, ...gargs] = rest;
3168
- if (sub === "list") {
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 (sub === "add") {
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 ? resolve6(from) : join15(proj, "plugins", name);
3202
- if (!existsSync13(srcDir)) {
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 (sub === "rm") {
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() : join15(homedir4(), "imprnt");
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 = (sub) => toCwd ? `./${sub}` : join15(target, sub);
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 = join15(target, "vault");
3269
- const vaultExisted = existsSync13(vaultPath);
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 ? join15(target, d) : target;
3273
- if (!existsSync13(abs))
3433
+ const abs = d ? join16(target, d) : target;
3434
+ if (!existsSync14(abs))
3274
3435
  createdDirs++;
3275
3436
  try {
3276
- mkdirSync5(abs, { recursive: true });
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 = join15(vaultPath, f);
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(join15(pkgRoot, "templates", f), dst);
3456
+ cpSync3(join16(pkgRoot, "templates", f), dst);
3296
3457
  added.push(`vault/${f}`);
3297
3458
  }
3298
3459
  }
3299
- const claudeMd = join15(target, "CLAUDE.md");
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" && existsSync13(join15(pkgRoot, "CLAUDE.md"))) {
3306
- cpSync3(join15(pkgRoot, "CLAUDE.md"), claudeMd);
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 = statSync9(target).mode & 511;
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 noteCount = collectNotes(vaultPath).length;
3333
- console.log(`found existing vault at ${rel("vault")} — ${noteCount} note${noteCount === 1 ? "" : "s"}, left untouched`);
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 = join15(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3349
- if (existsSync13(modScript)) {
3350
- const proc = spawnSync4(process.execPath, [modScript, ...rest], { stdio: "inherit" });
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/cli.ts
2825
- var exports_cli = {};
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 { join as join15, dirname as dirname4, resolve as resolve6, basename as basename6 } from "node:path";
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 (existsSync13(join15(local, "vault")) || existsSync13(join15(local, "CLAUDE.local.md")))
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 = homedir4()) {
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 = join15(home, p.slice(2));
2866
- return resolve6(base, p);
3023
+ p = join16(home, p.slice(2));
3024
+ return resolve7(base, p);
2867
3025
  }
2868
- function initPositional(args5) {
2869
- return args5.find((a) => !a.startsWith("-"));
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 (!existsSync13(existing)) {
2875
- const parent = dirname4(existing);
3032
+ while (!existsSync14(existing)) {
3033
+ const parent = dirname5(existing);
2876
3034
  if (parent === existing)
2877
3035
  return target;
2878
- tail.unshift(basename6(existing));
3036
+ tail.unshift(basename7(existing));
2879
3037
  existing = parent;
2880
3038
  }
2881
3039
  try {
2882
- return tail.length ? join15(realpathSync2(existing), ...tail) : realpathSync2(existing);
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 statSync9(dst).isFile() ? "keep" : "blocked-symlink";
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 = dirname4(fileURLToPath(import.meta.url));
2918
- pkgRoot = join15(here, "..");
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 = join15(vault5, "hot.md");
2946
- if (!existsSync13(p)) {
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 = join15(home, "CLAUDE.md");
2963
- if (!existsSync13(contract)) {
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 [sub, ...specs] = rest;
3047
- if (sub === "list") {
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 (sub === "add") {
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 (sub === "rm") {
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 || join15(homedir4(), ".claude");
3167
- const [sub, ...gargs] = rest;
3168
- if (sub === "list") {
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 (sub === "add") {
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 ? resolve6(from) : join15(proj, "plugins", name);
3202
- if (!existsSync13(srcDir)) {
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 (sub === "rm") {
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() : join15(homedir4(), "imprnt");
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 = (sub) => toCwd ? `./${sub}` : join15(target, sub);
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 = join15(target, "vault");
3269
- const vaultExisted = existsSync13(vaultPath);
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 ? join15(target, d) : target;
3273
- if (!existsSync13(abs))
3433
+ const abs = d ? join16(target, d) : target;
3434
+ if (!existsSync14(abs))
3274
3435
  createdDirs++;
3275
3436
  try {
3276
- mkdirSync5(abs, { recursive: true });
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 = join15(vaultPath, f);
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(join15(pkgRoot, "templates", f), dst);
3456
+ cpSync3(join16(pkgRoot, "templates", f), dst);
3296
3457
  added.push(`vault/${f}`);
3297
3458
  }
3298
3459
  }
3299
- const claudeMd = join15(target, "CLAUDE.md");
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" && existsSync13(join15(pkgRoot, "CLAUDE.md"))) {
3306
- cpSync3(join15(pkgRoot, "CLAUDE.md"), claudeMd);
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 = statSync9(target).mode & 511;
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 noteCount = collectNotes(vaultPath).length;
3333
- console.log(`found existing vault at ${rel("vault")} — ${noteCount} note${noteCount === 1 ? "" : "s"}, left untouched`);
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 = join15(pluginRoot(), "plugins", cmd, `${cmd}.js`);
3349
- if (existsSync13(modScript)) {
3350
- const proc = spawnSync4(process.execPath, [modScript, ...rest], { stdio: "inherit" });
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.74",
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",