imprnt 0.1.3-edge.61 → 0.1.3-edge.62
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/CLAUDE.md +4 -4
- package/README.md +4 -4
- package/dist/cli.js +72 -26
- package/dist/imp.js +72 -26
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -148,13 +148,13 @@ imprnt keeps exactly two "robot" helpers, carried over from PAI (the system impr
|
|
|
148
148
|
|
|
149
149
|
## Updating & contradictions
|
|
150
150
|
|
|
151
|
-
Correct the ONE entity note. Everything links by ID so the fix propagates. Old name → `aliases`. A contradiction stamps the stale line `> superseded by [[...]]`. Marked, never silently overwritten. Because `raw/` is immutable, any claim is traceable to its snapshot, and a schema change is just
|
|
151
|
+
Correct the ONE entity note. Everything links by ID so the fix propagates. Old name → `aliases`. A contradiction stamps the stale line `> superseded by [[...]]`. Marked, never silently overwritten. Because `raw/` is immutable, any claim is traceable to its snapshot, and a schema change is just re-running `ingest` over `raw/`.
|
|
152
152
|
|
|
153
153
|
## Migrating existing structured knowledge
|
|
154
154
|
|
|
155
155
|
A migration is the one-time bulk WRITE: snapshot every chosen source into `raw/<source>/` (immutable, complete), then the LLM fans each source out into atomic, domain-filed, linked notes. Two rules keep it honest:
|
|
156
156
|
|
|
157
|
-
- **Re-derive from the ORIGINAL source, not a prior vault.** When you rebuild the vault, go back to the original snapshots in `raw/`. Don't reshuffle a previous vault's notes (that migrates yesterday's mistakes forward). A schema change is just
|
|
157
|
+
- **Re-derive from the ORIGINAL source, not a prior vault.** When you rebuild the vault, go back to the original snapshots in `raw/`. Don't reshuffle a previous vault's notes (that migrates yesterday's mistakes forward). A schema change is just re-running `ingest` over `raw/`.
|
|
158
158
|
- **But preserve genuine prior enrichment.** When a *source* is already an atomic, enriched note (a prior system's knowledge notes), map its fields onto this contract and keep its typed links. Don't re-derive it from scratch (that pays the LLM to downgrade good work). Re-deriving from scratch is for *unstructured* sources (the dense prose blobs).
|
|
159
159
|
|
|
160
160
|
**A dense multi-topic source splits into atomic linked notes: one snapshot, many notes.** Real migration sources are dense blobs covering many objects at once (a health-overview file = the med stack + a health profile + an insurance-critical prescription decision + the doctor-as-a-person). Don't file the whole blob as one note. That loses the entity graph (the doctor never becomes a `people/`, the decision never becomes a linkable note). Instead: split it into one atomic note per object across the right folders (`people/` for the doctor, `holdings/kind:med` for a tracked drug+dose, `health/`/`identity/` for the profile/decision), link them, and keep the single `raw/` snapshot as the shared provenance for all of them. `ingest` writes one snapshot. The LLM fans it out into the right number of notes.
|
|
@@ -176,11 +176,11 @@ Core is the vault + `ingest → recall → check`. Everything else (a task-mirro
|
|
|
176
176
|
- **Write** single-writer-per-path: a plugin writes only its own folder and **proposes** vault-note changes for `ingest`/you to apply. It never mutates a note. Frontmatter labels are **slug-namespaced** (`whenful.synced`), read-your-own-only. Core ignores unknown keys.
|
|
177
177
|
- **`recall` searches `vault/` only, forever.** Sibling dirs + `raw/` are outside the corpus by construction. A plugin surfaces into search only by **proposing one low-frequency summary note** (the escape hatch).
|
|
178
178
|
- **Behavior plugins** are a separate class: ship a fixed-size fragment the **user wires into the agent's system prompt**. The vault never auto-injects. Remove = delete the wire-in. No referee for conflicting fragments, the user owns composition. (The old system imposed. imprnt composes.)
|
|
179
|
-
- **Harness plugins** are the third class: they customize Claude Code itself, never the vault (timemachine's PreToolUse snapshot hook, the statusline, a skill). The same plugin folder doubles as a **native Claude Code plugin** (`.claude-plugin/plugin.json` + `hooks/` + `skills/`, Anthropic's format) and/or carries **`imp-settings.json`** (the settings keys Claude only takes via config, e.g. `statusLine`, with `${PLUGIN_DIR}` as the plugin's absolute dir). `imp` carries enabled ones into every session it launches: one `--plugin-dir` per native plugin + one merged `--settings
|
|
179
|
+
- **Harness plugins** are the third class: they customize Claude Code itself, never the vault (timemachine's PreToolUse snapshot hook, the statusline, a skill). The same plugin folder doubles as a **native Claude Code plugin** (`.claude-plugin/plugin.json` + `hooks/` + `skills/`, Anthropic's format) and/or carries **`imp-settings.json`** (the settings keys Claude only takes via config, e.g. `statusLine`, with `${PLUGIN_DIR}` as the plugin's absolute dir). `imp` carries enabled ones into every claude session it launches: one `--plugin-dir` per native plugin + one merged `--settings` (gemini has no host for claude harness plugins, so `imp --gemini` skips them with a warning). Stock `claude` stays stock, nothing is ever written to settings files, `plugin rm` undoes everything.
|
|
180
180
|
- **Capability modules** are the one bend in "share nothing": a module may *provide* a capability another *consumes* (the session-host brokers an authed-session token to the kleinanzeigen watcher). Bounded so it can't re-bloat core: the consumer copies a tiny client (`sessionToken(site) -> string | null`) over a **localhost broker**, never imports the provider or reads its folder. The invariant that replaces "no cross-plugin reads": **removing a provider degrades a consumer gracefully** (`null` = host down, fall back), never breaks it. This is a third contact surface (vault . agent-context . broker), fenced like the sync edge: deterministic, answer-on-request, auditable (token fingerprint, never the token), never resident. The litmus extends: zero core edits to add/remove a *provider* too, because there is no capability registry in core. Credential-holding modules follow the auth finding: human login once, read-only attach, never copy a profile, never automate the password. Full rules: `plugins/README.md`.
|
|
181
181
|
- **Secrets are env vars at the plugin's edge.** A plugin calling an external service reads its key from an env var named in its README and fails loud when missing. Keys never land in the vault, the plugin folder, or anything committed. No central secret store.
|
|
182
182
|
- **Commands only, no forced daemon.** The user schedules sync. Install/remove is manual (README `## Install` / `## Remove` + `rm -rf`), no registry.
|
|
183
|
-
- **Core ↔ plugin contact = exactly
|
|
183
|
+
- **Core ↔ plugin contact = exactly four convention-based contact points**, all dumb and uniform: `imprnt check --all` globs `plugins/*/check.js` (each plugin's built artifact), runs each with `node`, reads **exit code only**, forwards stdout verbatim, never parses plugin output. `imprnt ingest --apply` files staged notes the plugins drop in `plugins/*/proposed/`. `imp` turns the enable list into launch flags (`--plugin-dir` per native plugin + one merged `--settings`) for the session it spawns. `imprnt <module> <command>` dispatches to `plugins/<module>/<module>.js` (stdio inherited, exit code passed through, a built-in subcommand always wins, zero per-module logic). All discover by filename/dir convention, never by import, never by naming a specific plugin. Fence: core may provide read-only *aggregation* helpers, never write/orchestration. (Not k8s liveness/readiness. Nothing runs, so the only health question is "is the data sound", which is what `check` answers.)
|
|
184
184
|
|
|
185
185
|
## Out of scope (on purpose)
|
|
186
186
|
No task management. No auto-injected context. No background loop. No self-grading. No MCP/vector/embeddings on the vault. No sensitivity machinery (it's private by being private). **No `out/` zone for deliverables.** A produced artifact (an article, an export) lives in your artifacts dir and a vault note points to it. The vault holds knowledge, not outputs. Every plugin is a self-contained dir you can `rm -rf`. "It belongs" is not a reason to add it. (Plugin-contract out-of-scope list lives in `plugins/README.md`.)
|
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ asking ("add the anti-slop plugin"), each a separate `imprnt-plugin-*` package:
|
|
|
127
127
|
| `imprnt-plugin-anti-slop` | Rules that keep its prose from reading like AI. |
|
|
128
128
|
| `imprnt-plugin-whenful` | A local mirror of your [Whenful](https://whenful.com) tasks, shown inline at read. |
|
|
129
129
|
| `imprnt-plugin-kleinanzeigen` | A watcher for your Kleinanzeigen inbox: regex triage of buyer messages, drafts, a phone digest. You press send. |
|
|
130
|
-
| `imprnt-plugin-session-host` | A warm browser holding your logged-in sessions, providing the authed-session capability. You enroll each site once. |
|
|
130
|
+
| `imprnt-plugin-session-host` | A warm browser holding your logged-in sessions, providing the authed-session capability. You enroll each site once. Not yet on npm: install from a repo checkout with `imprnt plugin add session-host --from <dir>`. |
|
|
131
131
|
| `imprnt-plugin-timemachine` | Snapshots your work before each change so you can recover what the agent breaks. |
|
|
132
132
|
| `imprnt-plugin-statusline` | A customizable status line: model, branch, context, cost, rate limits, clock. |
|
|
133
133
|
| `imprnt-plugin-telegram` | Your vault from your phone: text a bot, the answer comes from your notes. |
|
|
@@ -164,7 +164,7 @@ assistant that files and recalls for you:
|
|
|
164
164
|
|
|
165
165
|
The docs are the website, [imprnt.dev](https://imprnt.dev), built straight from markdown in
|
|
166
166
|
[`site/docs/`](site/docs/) (the single source): getting started, how it works, the model, plugins,
|
|
167
|
-
|
|
167
|
+
design decisions, and contributing. Edit a file there and the site updates on the next
|
|
168
168
|
deploy.
|
|
169
169
|
|
|
170
170
|
- [`CLAUDE.md`](CLAUDE.md), the contract your assistant reads inside the vault: note formats, conventions.
|
|
@@ -174,8 +174,8 @@ deploy.
|
|
|
174
174
|
|
|
175
175
|
The engine is built with [Bun](https://bun.sh) and [Turborepo](https://turborepo.com) (dev tools
|
|
176
176
|
only, never needed by people who use it through their assistant). Clone, `bun install`,
|
|
177
|
-
`bun run build`, `bun run test`. The
|
|
178
|
-
[
|
|
177
|
+
`bun run build`, `bun run test`. The reasoning behind the design and the build-and-release model are in the
|
|
178
|
+
[design decisions](https://imprnt.dev/design-decisions/) and [contributing](https://imprnt.dev/contributing/) docs.
|
|
179
179
|
|
|
180
180
|
## License
|
|
181
181
|
|
package/dist/cli.js
CHANGED
|
@@ -737,12 +737,19 @@ function readRegistry() {
|
|
|
737
737
|
for (const [k, v] of Object.entries(raw?.vaults ?? {}))
|
|
738
738
|
if (typeof v === "string")
|
|
739
739
|
vaults[k] = v;
|
|
740
|
+
const defaultModels = {};
|
|
741
|
+
for (const [k, v] of Object.entries(raw?.defaultModels ?? {}))
|
|
742
|
+
if (typeof v === "string")
|
|
743
|
+
defaultModels[k] = v;
|
|
744
|
+
const passthrough = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
740
745
|
return {
|
|
746
|
+
...passthrough,
|
|
741
747
|
default: typeof raw?.default === "string" ? raw.default : undefined,
|
|
742
748
|
vaults,
|
|
743
749
|
defaultAgent: typeof raw?.defaultAgent === "string" ? raw.defaultAgent : undefined,
|
|
744
750
|
skipPermissions: typeof raw?.skipPermissions === "boolean" ? raw.skipPermissions : undefined,
|
|
745
|
-
defaultModel: typeof raw?.defaultModel === "string" ? raw.defaultModel : undefined
|
|
751
|
+
defaultModel: typeof raw?.defaultModel === "string" ? raw.defaultModel : undefined,
|
|
752
|
+
defaultModels: Object.keys(defaultModels).length ? defaultModels : undefined
|
|
746
753
|
};
|
|
747
754
|
} catch {
|
|
748
755
|
return { vaults: {} };
|
|
@@ -787,12 +794,25 @@ function setSkipPermissions(on) {
|
|
|
787
794
|
reg.skipPermissions = on;
|
|
788
795
|
return writeRegistry(reg);
|
|
789
796
|
}
|
|
790
|
-
function readDefaultModel() {
|
|
791
|
-
|
|
797
|
+
function readDefaultModel(agent) {
|
|
798
|
+
const reg = readRegistry();
|
|
799
|
+
const scoped = reg.defaultModels?.[agent];
|
|
800
|
+
if (scoped !== undefined)
|
|
801
|
+
return scoped;
|
|
802
|
+
return agent === (reg.defaultAgent ?? "claude") ? reg.defaultModel : undefined;
|
|
792
803
|
}
|
|
793
|
-
function setDefaultModel(model) {
|
|
804
|
+
function setDefaultModel(model, agent) {
|
|
794
805
|
const reg = readRegistry();
|
|
795
|
-
reg.
|
|
806
|
+
const models = { ...reg.defaultModels ?? {} };
|
|
807
|
+
const legacyAgent = reg.defaultAgent ?? "claude";
|
|
808
|
+
if (reg.defaultModel && models[legacyAgent] === undefined)
|
|
809
|
+
models[legacyAgent] = reg.defaultModel;
|
|
810
|
+
reg.defaultModel = undefined;
|
|
811
|
+
if (model)
|
|
812
|
+
models[agent] = model;
|
|
813
|
+
else
|
|
814
|
+
delete models[agent];
|
|
815
|
+
reg.defaultModels = Object.keys(models).length ? models : undefined;
|
|
796
816
|
return writeRegistry(reg);
|
|
797
817
|
}
|
|
798
818
|
function writeRegistry(reg) {
|
|
@@ -1005,14 +1025,19 @@ function joinFragments(parts) {
|
|
|
1005
1025
|
|
|
1006
1026
|
`);
|
|
1007
1027
|
}
|
|
1028
|
+
function flagHead(args) {
|
|
1029
|
+
const term = args.indexOf("--");
|
|
1030
|
+
return term < 0 ? args : args.slice(0, term);
|
|
1031
|
+
}
|
|
1008
1032
|
function claudeRenderArgs(spec) {
|
|
1009
1033
|
const systemPrompt = spec.insideProject ? spec.globals : joinFragments([spec.cast, spec.pointer, spec.globals]);
|
|
1010
1034
|
const harness = spec.pluginsRoot ? harnessFlags(spec.pluginsRoot) : [];
|
|
1011
1035
|
const extra = spec.addDirs.flatMap((d) => ["--add-dir", d]);
|
|
1012
1036
|
const lead = [];
|
|
1013
|
-
|
|
1037
|
+
const head = flagHead(spec.passthrough);
|
|
1038
|
+
if (spec.skipPermissions && !head.includes("--dangerously-skip-permissions"))
|
|
1014
1039
|
lead.push("--dangerously-skip-permissions");
|
|
1015
|
-
if (spec.model && !
|
|
1040
|
+
if (spec.model && !head.some((a) => a === "--model" || a.startsWith("--model=")))
|
|
1016
1041
|
lead.push("--model", spec.model);
|
|
1017
1042
|
const pass = lead.length ? [...lead, ...spec.passthrough] : spec.passthrough;
|
|
1018
1043
|
const body = systemPrompt ? mergeFragment(pass, systemPrompt, extra) : [...pass, ...extra];
|
|
@@ -1052,15 +1077,15 @@ function geminiRenderArgs(spec) {
|
|
|
1052
1077
|
console.error("imp: gemini does not host claude harness plugins (statusline, timemachine) - skipping them");
|
|
1053
1078
|
}
|
|
1054
1079
|
const lead = [];
|
|
1080
|
+
const head = flagHead(spec.passthrough);
|
|
1055
1081
|
if (spec.skipPermissions) {
|
|
1056
|
-
if (!
|
|
1082
|
+
if (!head.includes("--yolo") && !head.includes("-y"))
|
|
1057
1083
|
lead.push("--yolo");
|
|
1058
|
-
if (!
|
|
1084
|
+
if (!head.includes("--skip-trust"))
|
|
1059
1085
|
lead.push("--skip-trust");
|
|
1060
1086
|
}
|
|
1061
1087
|
const pass = [...spec.passthrough];
|
|
1062
|
-
const
|
|
1063
|
-
const mEnd = mTerm < 0 ? pass.length : mTerm;
|
|
1088
|
+
const mEnd = flagHead(pass).length;
|
|
1064
1089
|
const isModelFlag = (a) => a === "-m" || a === "--model" || a.startsWith("-m=") || a.startsWith("--model=");
|
|
1065
1090
|
const mIdx = pass.slice(0, mEnd).findIndex(isModelFlag);
|
|
1066
1091
|
if (mIdx >= 0) {
|
|
@@ -1182,7 +1207,7 @@ function resolveLaunch(passthrough, config = {}) {
|
|
|
1182
1207
|
rest.push(a);
|
|
1183
1208
|
}
|
|
1184
1209
|
const name = agentFlag ?? process.env.IMPRNT_AGENT ?? config.agent ?? "claude";
|
|
1185
|
-
let backend = backends[name];
|
|
1210
|
+
let backend = Object.hasOwn(backends, name) ? backends[name] : undefined;
|
|
1186
1211
|
if (!backend) {
|
|
1187
1212
|
console.error(`imp: unknown agent "${name}" - falling back to claude (valid: ${Object.keys(backends).join(", ")})`);
|
|
1188
1213
|
backend = claudeBackend;
|
|
@@ -2318,6 +2343,13 @@ function hostMemoryStores() {
|
|
|
2318
2343
|
}
|
|
2319
2344
|
}).sort();
|
|
2320
2345
|
}
|
|
2346
|
+
function memoryIndexHasContent(dir2) {
|
|
2347
|
+
try {
|
|
2348
|
+
return readFileSync13(join14(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
|
|
2349
|
+
} catch {
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2321
2353
|
function syncNeedsReview(lines2) {
|
|
2322
2354
|
const p = join14(vault4, "_needs-review.md");
|
|
2323
2355
|
const exists = existsSync12(p);
|
|
@@ -2497,10 +2529,18 @@ var init_check = __esm(() => {
|
|
|
2497
2529
|
for (const r of uncovered)
|
|
2498
2530
|
review.push(`- [ ] unclassified snapshot \`${r}\` — no vault note points back`);
|
|
2499
2531
|
memoryStores = hostMemoryStores();
|
|
2500
|
-
strayStores = memoryStores.map((dir2) =>
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2532
|
+
strayStores = memoryStores.map((dir2) => {
|
|
2533
|
+
let files3 = [];
|
|
2534
|
+
try {
|
|
2535
|
+
if (existsSync12(dir2))
|
|
2536
|
+
files3 = readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
2537
|
+
} catch (e) {
|
|
2538
|
+
console.error(`⚠ host auto-memory sweep: could not read ${dir2} (${e instanceof Error ? e.message : String(e)}) - skipping it`);
|
|
2539
|
+
}
|
|
2540
|
+
if (memoryIndexHasContent(dir2))
|
|
2541
|
+
files3.push("MEMORY.md (has content)");
|
|
2542
|
+
return { dir: dir2, files: files3 };
|
|
2543
|
+
}).filter((s) => s.files.length > 0);
|
|
2504
2544
|
strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
|
|
2505
2545
|
for (const s of strayStores)
|
|
2506
2546
|
review.push(`- [ ] host auto-memory not empty — ${s.files.length} note(s) in ${s.dir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
|
|
@@ -2754,7 +2794,7 @@ var init_cli = __esm(async () => {
|
|
|
2754
2794
|
const home = requireVaultHome();
|
|
2755
2795
|
const { backend, skipPermissions, passthrough } = resolveLaunch(rest, { agent: readDefaultAgent(), yolo: readSkipPermissions() });
|
|
2756
2796
|
const resumed = backend.resolveResume ? await backend.resolveResume(passthrough, home) : passthrough;
|
|
2757
|
-
const spec = assembleSession({ cwd: home, vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel() });
|
|
2797
|
+
const spec = assembleSession({ cwd: home, vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel(backend.name) });
|
|
2758
2798
|
process.exit(launchBackend(backend, home, backend.renderArgs(spec), spec.env));
|
|
2759
2799
|
}
|
|
2760
2800
|
case "context": {
|
|
@@ -2777,7 +2817,7 @@ var init_cli = __esm(async () => {
|
|
|
2777
2817
|
console.log("set: imprnt agent <claude|gemini> override one session: imp --gemini | imp --claude");
|
|
2778
2818
|
break;
|
|
2779
2819
|
}
|
|
2780
|
-
if (!(name
|
|
2820
|
+
if (!Object.hasOwn(backends, name)) {
|
|
2781
2821
|
console.error(`unknown agent "${name}" — valid: ${Object.keys(backends).join(", ")}`);
|
|
2782
2822
|
process.exit(1);
|
|
2783
2823
|
}
|
|
@@ -2811,29 +2851,32 @@ var init_cli = __esm(async () => {
|
|
|
2811
2851
|
break;
|
|
2812
2852
|
}
|
|
2813
2853
|
case "model": {
|
|
2854
|
+
const envAgent = process.env.IMPRNT_AGENT;
|
|
2855
|
+
const agentName = envAgent ?? readDefaultAgent() ?? "claude";
|
|
2856
|
+
const agent = agentName in backends ? agentName : "claude";
|
|
2814
2857
|
const [arg] = rest;
|
|
2815
2858
|
if (!arg) {
|
|
2816
|
-
const m = readDefaultModel();
|
|
2817
|
-
console.log(`default model: ${m ?? "(agent's own default)"}`);
|
|
2859
|
+
const m = readDefaultModel(agent);
|
|
2860
|
+
console.log(`default model for ${agent}: ${m ?? "(agent's own default)"}`);
|
|
2818
2861
|
console.log("set: imprnt model <alias|id> clear: imprnt model off");
|
|
2819
2862
|
console.log(`gemini aliases: ${Object.entries(GEMINI_MODEL_ALIASES).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
2820
2863
|
break;
|
|
2821
2864
|
}
|
|
2822
2865
|
if (arg === "off" || arg === "default" || arg === "none") {
|
|
2823
|
-
const r2 = setDefaultModel("");
|
|
2866
|
+
const r2 = setDefaultModel("", agent);
|
|
2824
2867
|
if (!r2.ok) {
|
|
2825
2868
|
console.error(`could not clear default model (${configPath()}): ${r2.error}`);
|
|
2826
2869
|
process.exit(1);
|
|
2827
2870
|
}
|
|
2828
|
-
console.log(`default model cleared (${configPath()}) — the agent uses its own default`);
|
|
2871
|
+
console.log(`default model for ${agent} cleared (${configPath()}) — the agent uses its own default`);
|
|
2829
2872
|
break;
|
|
2830
2873
|
}
|
|
2831
|
-
const r = setDefaultModel(arg);
|
|
2874
|
+
const r = setDefaultModel(arg, agent);
|
|
2832
2875
|
if (!r.ok) {
|
|
2833
2876
|
console.error(`could not set default model (${configPath()}): ${r.error}`);
|
|
2834
2877
|
process.exit(1);
|
|
2835
2878
|
}
|
|
2836
|
-
console.log(`default model set to ${arg} (${configPath()})`);
|
|
2879
|
+
console.log(`default model for ${agent} set to ${arg} (${configPath()})`);
|
|
2837
2880
|
break;
|
|
2838
2881
|
}
|
|
2839
2882
|
case "plugin": {
|
|
@@ -3104,7 +3147,10 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3104
3147
|
added.push("CLAUDE.md");
|
|
3105
3148
|
}
|
|
3106
3149
|
try {
|
|
3150
|
+
const prevMode = statSync9(target).mode & 511;
|
|
3107
3151
|
chmodSync(target, 448);
|
|
3152
|
+
if (prevMode !== 448)
|
|
3153
|
+
console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
|
|
3108
3154
|
} catch (e) {
|
|
3109
3155
|
console.error(`could not set owner-only (chmod 700) on ${target}: ${e instanceof Error ? e.message : String(e)} — tighten it by hand to honor the private-vault promise`);
|
|
3110
3156
|
}
|
|
@@ -3154,7 +3200,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3154
3200
|
const raw = bare ? [] : [cmd, ...rest];
|
|
3155
3201
|
const { backend, skipPermissions, passthrough } = resolveLaunch(raw, { agent: readDefaultAgent(), yolo: readSkipPermissions() });
|
|
3156
3202
|
const resumed = backend.resolveResume ? await backend.resolveResume(passthrough, process.cwd()) : passthrough;
|
|
3157
|
-
const spec = assembleSession({ cwd: process.cwd(), vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel() });
|
|
3203
|
+
const spec = assembleSession({ cwd: process.cwd(), vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel(backend.name) });
|
|
3158
3204
|
process.exit(launchBackend(backend, process.cwd(), backend.renderArgs(spec), spec.env));
|
|
3159
3205
|
}
|
|
3160
3206
|
console.log(`imprnt — deterministic-first markdown knowledge vault
|
|
@@ -3174,7 +3220,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
|
|
|
3174
3220
|
imprnt context print the vault contract — agents run this before writing any note
|
|
3175
3221
|
imprnt agent [claude|gemini] show or set the per-machine default agent backend for imp
|
|
3176
3222
|
imprnt yolo [on|off] show or set the per-machine skip-permissions default for imp
|
|
3177
|
-
imprnt model [alias|id|off] show or set the per-machine default model (gemini aliases: pro, flash, pro25, lite, ...)
|
|
3223
|
+
imprnt model [alias|id|off] show or set the per-machine default model for the current default agent (gemini aliases: pro, flash, pro25, lite, ...)
|
|
3178
3224
|
imprnt check [--all] [--vault D] integrity (orphan links, disconnected notes, uncovered snapshots) + regenerate index.md; --all also runs each plugins/*/check.js
|
|
3179
3225
|
imprnt ingest --apply <file> [--vault D] file a pre-enriched staged note from a plugin into the vault (snapshot + resolve); --apply-all globs plugins/*/proposed/
|
|
3180
3226
|
imprnt hot [--vault D] needs-review + the session primer
|
package/dist/imp.js
CHANGED
|
@@ -737,12 +737,19 @@ function readRegistry() {
|
|
|
737
737
|
for (const [k, v] of Object.entries(raw?.vaults ?? {}))
|
|
738
738
|
if (typeof v === "string")
|
|
739
739
|
vaults[k] = v;
|
|
740
|
+
const defaultModels = {};
|
|
741
|
+
for (const [k, v] of Object.entries(raw?.defaultModels ?? {}))
|
|
742
|
+
if (typeof v === "string")
|
|
743
|
+
defaultModels[k] = v;
|
|
744
|
+
const passthrough = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
740
745
|
return {
|
|
746
|
+
...passthrough,
|
|
741
747
|
default: typeof raw?.default === "string" ? raw.default : undefined,
|
|
742
748
|
vaults,
|
|
743
749
|
defaultAgent: typeof raw?.defaultAgent === "string" ? raw.defaultAgent : undefined,
|
|
744
750
|
skipPermissions: typeof raw?.skipPermissions === "boolean" ? raw.skipPermissions : undefined,
|
|
745
|
-
defaultModel: typeof raw?.defaultModel === "string" ? raw.defaultModel : undefined
|
|
751
|
+
defaultModel: typeof raw?.defaultModel === "string" ? raw.defaultModel : undefined,
|
|
752
|
+
defaultModels: Object.keys(defaultModels).length ? defaultModels : undefined
|
|
746
753
|
};
|
|
747
754
|
} catch {
|
|
748
755
|
return { vaults: {} };
|
|
@@ -787,12 +794,25 @@ function setSkipPermissions(on) {
|
|
|
787
794
|
reg.skipPermissions = on;
|
|
788
795
|
return writeRegistry(reg);
|
|
789
796
|
}
|
|
790
|
-
function readDefaultModel() {
|
|
791
|
-
|
|
797
|
+
function readDefaultModel(agent) {
|
|
798
|
+
const reg = readRegistry();
|
|
799
|
+
const scoped = reg.defaultModels?.[agent];
|
|
800
|
+
if (scoped !== undefined)
|
|
801
|
+
return scoped;
|
|
802
|
+
return agent === (reg.defaultAgent ?? "claude") ? reg.defaultModel : undefined;
|
|
792
803
|
}
|
|
793
|
-
function setDefaultModel(model) {
|
|
804
|
+
function setDefaultModel(model, agent) {
|
|
794
805
|
const reg = readRegistry();
|
|
795
|
-
reg.
|
|
806
|
+
const models = { ...reg.defaultModels ?? {} };
|
|
807
|
+
const legacyAgent = reg.defaultAgent ?? "claude";
|
|
808
|
+
if (reg.defaultModel && models[legacyAgent] === undefined)
|
|
809
|
+
models[legacyAgent] = reg.defaultModel;
|
|
810
|
+
reg.defaultModel = undefined;
|
|
811
|
+
if (model)
|
|
812
|
+
models[agent] = model;
|
|
813
|
+
else
|
|
814
|
+
delete models[agent];
|
|
815
|
+
reg.defaultModels = Object.keys(models).length ? models : undefined;
|
|
796
816
|
return writeRegistry(reg);
|
|
797
817
|
}
|
|
798
818
|
function writeRegistry(reg) {
|
|
@@ -1005,14 +1025,19 @@ function joinFragments(parts) {
|
|
|
1005
1025
|
|
|
1006
1026
|
`);
|
|
1007
1027
|
}
|
|
1028
|
+
function flagHead(args) {
|
|
1029
|
+
const term = args.indexOf("--");
|
|
1030
|
+
return term < 0 ? args : args.slice(0, term);
|
|
1031
|
+
}
|
|
1008
1032
|
function claudeRenderArgs(spec) {
|
|
1009
1033
|
const systemPrompt = spec.insideProject ? spec.globals : joinFragments([spec.cast, spec.pointer, spec.globals]);
|
|
1010
1034
|
const harness = spec.pluginsRoot ? harnessFlags(spec.pluginsRoot) : [];
|
|
1011
1035
|
const extra = spec.addDirs.flatMap((d) => ["--add-dir", d]);
|
|
1012
1036
|
const lead = [];
|
|
1013
|
-
|
|
1037
|
+
const head = flagHead(spec.passthrough);
|
|
1038
|
+
if (spec.skipPermissions && !head.includes("--dangerously-skip-permissions"))
|
|
1014
1039
|
lead.push("--dangerously-skip-permissions");
|
|
1015
|
-
if (spec.model && !
|
|
1040
|
+
if (spec.model && !head.some((a) => a === "--model" || a.startsWith("--model=")))
|
|
1016
1041
|
lead.push("--model", spec.model);
|
|
1017
1042
|
const pass = lead.length ? [...lead, ...spec.passthrough] : spec.passthrough;
|
|
1018
1043
|
const body = systemPrompt ? mergeFragment(pass, systemPrompt, extra) : [...pass, ...extra];
|
|
@@ -1052,15 +1077,15 @@ function geminiRenderArgs(spec) {
|
|
|
1052
1077
|
console.error("imp: gemini does not host claude harness plugins (statusline, timemachine) - skipping them");
|
|
1053
1078
|
}
|
|
1054
1079
|
const lead = [];
|
|
1080
|
+
const head = flagHead(spec.passthrough);
|
|
1055
1081
|
if (spec.skipPermissions) {
|
|
1056
|
-
if (!
|
|
1082
|
+
if (!head.includes("--yolo") && !head.includes("-y"))
|
|
1057
1083
|
lead.push("--yolo");
|
|
1058
|
-
if (!
|
|
1084
|
+
if (!head.includes("--skip-trust"))
|
|
1059
1085
|
lead.push("--skip-trust");
|
|
1060
1086
|
}
|
|
1061
1087
|
const pass = [...spec.passthrough];
|
|
1062
|
-
const
|
|
1063
|
-
const mEnd = mTerm < 0 ? pass.length : mTerm;
|
|
1088
|
+
const mEnd = flagHead(pass).length;
|
|
1064
1089
|
const isModelFlag = (a) => a === "-m" || a === "--model" || a.startsWith("-m=") || a.startsWith("--model=");
|
|
1065
1090
|
const mIdx = pass.slice(0, mEnd).findIndex(isModelFlag);
|
|
1066
1091
|
if (mIdx >= 0) {
|
|
@@ -1182,7 +1207,7 @@ function resolveLaunch(passthrough, config = {}) {
|
|
|
1182
1207
|
rest.push(a);
|
|
1183
1208
|
}
|
|
1184
1209
|
const name = agentFlag ?? process.env.IMPRNT_AGENT ?? config.agent ?? "claude";
|
|
1185
|
-
let backend = backends[name];
|
|
1210
|
+
let backend = Object.hasOwn(backends, name) ? backends[name] : undefined;
|
|
1186
1211
|
if (!backend) {
|
|
1187
1212
|
console.error(`imp: unknown agent "${name}" - falling back to claude (valid: ${Object.keys(backends).join(", ")})`);
|
|
1188
1213
|
backend = claudeBackend;
|
|
@@ -2318,6 +2343,13 @@ function hostMemoryStores() {
|
|
|
2318
2343
|
}
|
|
2319
2344
|
}).sort();
|
|
2320
2345
|
}
|
|
2346
|
+
function memoryIndexHasContent(dir2) {
|
|
2347
|
+
try {
|
|
2348
|
+
return readFileSync13(join14(dir2, "MEMORY.md"), "utf8").split(/\r?\n/).some((l) => l.trim() !== "" && l.trim() !== "# Memory index");
|
|
2349
|
+
} catch {
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2321
2353
|
function syncNeedsReview(lines2) {
|
|
2322
2354
|
const p = join14(vault4, "_needs-review.md");
|
|
2323
2355
|
const exists = existsSync12(p);
|
|
@@ -2497,10 +2529,18 @@ var init_check = __esm(() => {
|
|
|
2497
2529
|
for (const r of uncovered)
|
|
2498
2530
|
review.push(`- [ ] unclassified snapshot \`${r}\` — no vault note points back`);
|
|
2499
2531
|
memoryStores = hostMemoryStores();
|
|
2500
|
-
strayStores = memoryStores.map((dir2) =>
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2532
|
+
strayStores = memoryStores.map((dir2) => {
|
|
2533
|
+
let files3 = [];
|
|
2534
|
+
try {
|
|
2535
|
+
if (existsSync12(dir2))
|
|
2536
|
+
files3 = readdirSync8(dir2).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
2537
|
+
} catch (e) {
|
|
2538
|
+
console.error(`⚠ host auto-memory sweep: could not read ${dir2} (${e instanceof Error ? e.message : String(e)}) - skipping it`);
|
|
2539
|
+
}
|
|
2540
|
+
if (memoryIndexHasContent(dir2))
|
|
2541
|
+
files3.push("MEMORY.md (has content)");
|
|
2542
|
+
return { dir: dir2, files: files3 };
|
|
2543
|
+
}).filter((s) => s.files.length > 0);
|
|
2504
2544
|
strayCount = strayStores.reduce((n, s) => n + s.files.length, 0);
|
|
2505
2545
|
for (const s of strayStores)
|
|
2506
2546
|
review.push(`- [ ] host auto-memory not empty — ${s.files.length} note(s) in ${s.dir}; migrate knowledge → vault notes, behavior → a CLAUDE.local.md fragment, then empty it`);
|
|
@@ -2754,7 +2794,7 @@ var init_cli = __esm(async () => {
|
|
|
2754
2794
|
const home = requireVaultHome();
|
|
2755
2795
|
const { backend, skipPermissions, passthrough } = resolveLaunch(rest, { agent: readDefaultAgent(), yolo: readSkipPermissions() });
|
|
2756
2796
|
const resumed = backend.resolveResume ? await backend.resolveResume(passthrough, home) : passthrough;
|
|
2757
|
-
const spec = assembleSession({ cwd: home, vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel() });
|
|
2797
|
+
const spec = assembleSession({ cwd: home, vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel(backend.name) });
|
|
2758
2798
|
process.exit(launchBackend(backend, home, backend.renderArgs(spec), spec.env));
|
|
2759
2799
|
}
|
|
2760
2800
|
case "context": {
|
|
@@ -2777,7 +2817,7 @@ var init_cli = __esm(async () => {
|
|
|
2777
2817
|
console.log("set: imprnt agent <claude|gemini> override one session: imp --gemini | imp --claude");
|
|
2778
2818
|
break;
|
|
2779
2819
|
}
|
|
2780
|
-
if (!(name
|
|
2820
|
+
if (!Object.hasOwn(backends, name)) {
|
|
2781
2821
|
console.error(`unknown agent "${name}" — valid: ${Object.keys(backends).join(", ")}`);
|
|
2782
2822
|
process.exit(1);
|
|
2783
2823
|
}
|
|
@@ -2811,29 +2851,32 @@ var init_cli = __esm(async () => {
|
|
|
2811
2851
|
break;
|
|
2812
2852
|
}
|
|
2813
2853
|
case "model": {
|
|
2854
|
+
const envAgent = process.env.IMPRNT_AGENT;
|
|
2855
|
+
const agentName = envAgent ?? readDefaultAgent() ?? "claude";
|
|
2856
|
+
const agent = agentName in backends ? agentName : "claude";
|
|
2814
2857
|
const [arg] = rest;
|
|
2815
2858
|
if (!arg) {
|
|
2816
|
-
const m = readDefaultModel();
|
|
2817
|
-
console.log(`default model: ${m ?? "(agent's own default)"}`);
|
|
2859
|
+
const m = readDefaultModel(agent);
|
|
2860
|
+
console.log(`default model for ${agent}: ${m ?? "(agent's own default)"}`);
|
|
2818
2861
|
console.log("set: imprnt model <alias|id> clear: imprnt model off");
|
|
2819
2862
|
console.log(`gemini aliases: ${Object.entries(GEMINI_MODEL_ALIASES).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
2820
2863
|
break;
|
|
2821
2864
|
}
|
|
2822
2865
|
if (arg === "off" || arg === "default" || arg === "none") {
|
|
2823
|
-
const r2 = setDefaultModel("");
|
|
2866
|
+
const r2 = setDefaultModel("", agent);
|
|
2824
2867
|
if (!r2.ok) {
|
|
2825
2868
|
console.error(`could not clear default model (${configPath()}): ${r2.error}`);
|
|
2826
2869
|
process.exit(1);
|
|
2827
2870
|
}
|
|
2828
|
-
console.log(`default model cleared (${configPath()}) — the agent uses its own default`);
|
|
2871
|
+
console.log(`default model for ${agent} cleared (${configPath()}) — the agent uses its own default`);
|
|
2829
2872
|
break;
|
|
2830
2873
|
}
|
|
2831
|
-
const r = setDefaultModel(arg);
|
|
2874
|
+
const r = setDefaultModel(arg, agent);
|
|
2832
2875
|
if (!r.ok) {
|
|
2833
2876
|
console.error(`could not set default model (${configPath()}): ${r.error}`);
|
|
2834
2877
|
process.exit(1);
|
|
2835
2878
|
}
|
|
2836
|
-
console.log(`default model set to ${arg} (${configPath()})`);
|
|
2879
|
+
console.log(`default model for ${agent} set to ${arg} (${configPath()})`);
|
|
2837
2880
|
break;
|
|
2838
2881
|
}
|
|
2839
2882
|
case "plugin": {
|
|
@@ -3104,7 +3147,10 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3104
3147
|
added.push("CLAUDE.md");
|
|
3105
3148
|
}
|
|
3106
3149
|
try {
|
|
3150
|
+
const prevMode = statSync9(target).mode & 511;
|
|
3107
3151
|
chmodSync(target, 448);
|
|
3152
|
+
if (prevMode !== 448)
|
|
3153
|
+
console.log(`locked ${target} to owner-only (chmod 700, was ${prevMode.toString(8).padStart(3, "0")})`);
|
|
3108
3154
|
} catch (e) {
|
|
3109
3155
|
console.error(`could not set owner-only (chmod 700) on ${target}: ${e instanceof Error ? e.message : String(e)} — tighten it by hand to honor the private-vault promise`);
|
|
3110
3156
|
}
|
|
@@ -3154,7 +3200,7 @@ enable: imprnt plugin add <name> disable: imprnt plugin rm <name> [--purge]`);
|
|
|
3154
3200
|
const raw = bare ? [] : [cmd, ...rest];
|
|
3155
3201
|
const { backend, skipPermissions, passthrough } = resolveLaunch(raw, { agent: readDefaultAgent(), yolo: readSkipPermissions() });
|
|
3156
3202
|
const resumed = backend.resolveResume ? await backend.resolveResume(passthrough, process.cwd()) : passthrough;
|
|
3157
|
-
const spec = assembleSession({ cwd: process.cwd(), vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel() });
|
|
3203
|
+
const spec = assembleSession({ cwd: process.cwd(), vaultProject: home, pkgRoot, passthrough: resumed, skipPermissions, model: readDefaultModel(backend.name) });
|
|
3158
3204
|
process.exit(launchBackend(backend, process.cwd(), backend.renderArgs(spec), spec.env));
|
|
3159
3205
|
}
|
|
3160
3206
|
console.log(`imprnt — deterministic-first markdown knowledge vault
|
|
@@ -3174,7 +3220,7 @@ engine (same subcommands under \`imp\` or \`imprnt\`):
|
|
|
3174
3220
|
imprnt context print the vault contract — agents run this before writing any note
|
|
3175
3221
|
imprnt agent [claude|gemini] show or set the per-machine default agent backend for imp
|
|
3176
3222
|
imprnt yolo [on|off] show or set the per-machine skip-permissions default for imp
|
|
3177
|
-
imprnt model [alias|id|off] show or set the per-machine default model (gemini aliases: pro, flash, pro25, lite, ...)
|
|
3223
|
+
imprnt model [alias|id|off] show or set the per-machine default model for the current default agent (gemini aliases: pro, flash, pro25, lite, ...)
|
|
3178
3224
|
imprnt check [--all] [--vault D] integrity (orphan links, disconnected notes, uncovered snapshots) + regenerate index.md; --all also runs each plugins/*/check.js
|
|
3179
3225
|
imprnt ingest --apply <file> [--vault D] file a pre-enriched staged note from a plugin into the vault (snapshot + resolve); --apply-all globs plugins/*/proposed/
|
|
3180
3226
|
imprnt hot [--vault D] needs-review + the session primer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imprnt",
|
|
3
|
-
"version": "0.1.3-edge.
|
|
3
|
+
"version": "0.1.3-edge.62",
|
|
4
4
|
"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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|