@krimto-labs/krimto 0.2.6 → 0.2.9

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 CHANGED
@@ -9,18 +9,21 @@ place and reads the right slice of it — Alice's preferences override the team'
9
9
  conventions override the org's standards, and every fact carries a paper trail (author, source,
10
10
  timestamp, reviewer).
11
11
 
12
- > **Where we are:** this is the **v0.2** surface. Here today: the markdown-in-git storage layer, the
13
- > `user → team → org` hierarchy, hybrid retrieval, server-enforced access, two-way git sync, and the
14
- > MCP server over **both stdio and HTTP the HTTP transport has `Bearer` API-key auth and
15
- > `/health` endpoints**. On the near-term roadmap: a single-Docker image and the web UI. We claim the
16
- > team-memory position now and fulfil it in the open — see [ROADMAP.md](ROADMAP.md).
12
+ > **Where we are:** this is the **v0.2.9** surface. Here today: the markdown-in-git storage layer, the
13
+ > `user → team → org` hierarchy, hybrid retrieval, server-enforced access, two-way git sync, the MCP
14
+ > server over **stdio + HTTP** (Bearer API-key auth on HTTP), a **published multi-arch Docker image**
15
+ > (`ghcr.io/krimto-labs/krimto`), a **web UI** with browse/search/admin/diagnostics, and a complete
16
+ > **CLI** (`serve`, `connect`, `init`, `usage`, `storage`, `setup-remote`, `setup-embeddings`,
17
+ > `verify-connection`, `uninit`, `where`, `--help`). We claim the team-memory position now and fulfil
18
+ > it in the open — see [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md) for what each release adds.
17
19
 
18
20
  ## Try it in 2 minutes (solo, no account)
19
21
 
20
- 1. **Run it** (data stays in `~/.krimto`):
22
+ 1. **Run it** — one command, no clone, no Docker (data stays in `~/.krimto`):
21
23
  ```bash
22
- docker run -d -p 8080:8080 -v ~/.krimto:/data ghcr.io/krimto-labs/krimto:latest # or, from a clone: pnpm dev
24
+ npx @krimto-labs/krimto serve
23
25
  ```
26
+ *Prefer Docker?* `docker run -d -p 8080:8080 -v ~/.krimto:/data ghcr.io/krimto-labs/krimto:latest`
24
27
  2. **Point Claude Code at it — one line, no key:**
25
28
  ```json
26
29
  { "mcpServers": { "krimto": { "url": "http://localhost:8080/mcp" } } }
@@ -28,9 +31,10 @@ timestamp, reviewer).
28
31
  (or run `claude mcp add --transport http krimto http://localhost:8080/mcp`)
29
32
  3. Tell your agent: **"remember that our staging DB resets every Sunday."** Then ask in a *new* chat:
30
33
  **"what do you know about staging?"** — it remembers.
31
- 4. Open **http://localhost:8080** to browse. That's your *personal* layer Krimto's point is the
32
- **team** layer: restart with `KRIMTO_BOOTSTRAP_ADMIN=you@acme.com` to turn on accounts and invite
33
- teammates.
34
+ 4. Open **http://localhost:8080** to browse. The dashboard shows your facts, a *Status* row (git
35
+ remote + embeddings health), and a *Recent activity* feed (so you can see your agent calling
36
+ Krimto in real time). That's the *personal* layer — Krimto's point is the **team** layer:
37
+ restart with `KRIMTO_BOOTSTRAP_ADMIN=you@acme.com` to turn on accounts and invite teammates.
34
38
 
35
39
  ## Connect your agent
36
40
 
@@ -62,9 +66,20 @@ mode); those are best-effort and not yet individually verified.
62
66
 
63
67
  ### Make it automatic
64
68
 
65
- By default your agent uses Krimto only when you ask. To make it recall and save **on its own**, add a
66
- standing rule to your agent's rules file Claude Code: `CLAUDE.md`; Cursor: `.cursor/rules/krimto.mdc`;
67
- Codex: `AGENTS.md`; Gemini CLI: `GEMINI.md`:
69
+ By default your agent uses Krimto only when you ask ("use krimto to remember X"). One command, run
70
+ once in your project, makes it call Krimto **on its own** (recall before tasks, write when you say
71
+ "remember"):
72
+
73
+ ```bash
74
+ npx @krimto-labs/krimto init
75
+ ```
76
+
77
+ `init` auto-detects which editor you're using (`.cursor/`, `.claude/`, existing `CLAUDE.md` /
78
+ `AGENTS.md` / `GEMINI.md`, `gemini-extension.json`) and writes the rule only into the files that
79
+ match. Pass `--all` to write to every supported rules file. Change your mind later? Run
80
+ `npx @krimto-labs/krimto uninit` to cleanly strip the rule (and delete files that held only it).
81
+
82
+ The rule it writes:
68
83
 
69
84
  ```
70
85
  # Krimto memory — always use
@@ -77,6 +92,29 @@ Codex: `AGENTS.md`; Gemini CLI: `GEMINI.md`:
77
92
 
78
93
  The in-product **Connect** page (`/ui/connect`) shows this same rule with a copy button.
79
94
 
95
+ ### The CLI surface
96
+
97
+ Everything is reachable via `npx`. Run `npx @krimto-labs/krimto --help` for the full list. Briefly:
98
+
99
+ | Command | What it does |
100
+ |---|---|
101
+ | (no args) | Start the stdio MCP server (default; for MCP clients to launch) |
102
+ | `serve` | Start the HTTP server (port 8080) — `/ui` dashboard, no clone, no Docker |
103
+ | `connect` | Print copy-paste Claude Code + Cursor config snippets |
104
+ | `init [--all]` | Switch this project to AUTO MODE — write the always-use rule (auto-detects editor; `--all` writes every file) |
105
+ | `uninit` | Switch back to DEFAULT MODE — remove the rule, delete files it created |
106
+ | `usage` | Show the five `krimto_*` tools with chat examples for both modes |
107
+ | `storage` | Explain where Krimto keeps your data (markdown / git / index), how to verify, optional add-ons |
108
+ | `setup-remote <url>` | Wire the data dir to a git remote and verify the initial push |
109
+ | `setup-embeddings` | Send a real test embedding to verify a `KRIMTO_EMBED_*` config |
110
+ | `verify-connection` | Diagnose "is my agent calling Krimto?" (lock status + last 5 calls) |
111
+ | `where` | Print the data directory |
112
+ | `--help`, `-h` | Show the full CLI surface |
113
+
114
+ The stdio entrypoint enforces a **single-writer lock** on the data dir (`.krimto/lock.json`) — two
115
+ Krimto processes can no longer race on the same `~/.krimto`. A second `serve`/stdio launch is
116
+ refused with a precise error pointing at the holder's PID.
117
+
80
118
  ## How it works
81
119
 
82
120
  Three layers, one source of truth:
@@ -128,8 +166,21 @@ OpenClaw, Cline use the same shape):
128
166
  ```
129
167
 
130
168
  The first run downloads dependencies (including `better-sqlite3`, which ships prebuilt binaries), then
131
- starts the **stdio** server with data in `~/.krimto` (override with `KRIMTO_DATA`). This is the solo
132
- path; HTTP/team mode uses Docker (Option C) or the server below.
169
+ starts the **stdio** server with data in `~/.krimto` (override with `KRIMTO_DATA`; run
170
+ `npx @krimto-labs/krimto where` to print the exact path). Want the browser dashboard too? Stop the
171
+ stdio process and run `npx @krimto-labs/krimto serve` (Option B) — same data folder, plus `/ui`.
172
+
173
+ **Make your agent actually use Krimto.** By default an editor's agent routes "remember X" to its own
174
+ built-in memory. Run this once in your project so it calls Krimto instead:
175
+
176
+ ```bash
177
+ npx @krimto-labs/krimto init
178
+ ```
179
+
180
+ It auto-detects which editor you're using and writes the idempotent "always use Krimto" rule into the
181
+ matching rules file (`CLAUDE.md` / `AGENTS.md` / `GEMINI.md` / `.cursor/rules/krimto.mdc`). Pass
182
+ `--all` to write all four. Run `npx @krimto-labs/krimto uninit` to cleanly remove the rule later.
183
+ Restart your editor afterward.
133
184
 
134
185
  From a clone instead of npm, swap the command for `pnpm`:
135
186
 
@@ -148,16 +199,23 @@ From a clone instead of npm, swap the command for `pnpm`:
148
199
  `KRIMTO_IDENTITY` is who the agent writes as (fact author + access scope). Stdio mode has **no network
149
200
  auth** — run it locally/trusted.
150
201
 
151
- ### Option B — over HTTP, with bearer auth (teams)
202
+ ### Option B — over HTTP (browser dashboard + optional team auth)
152
203
 
153
- For a shared/networked deployment. Start the HTTP server; the first run prints an admin API key once:
204
+ For solo with a browser UI, or a shared/networked deployment with bearer auth. **No clone, no
205
+ Docker** — `npx` starts the HTTP server (default port 8080):
154
206
 
155
207
  ```bash
156
- KRIMTO_HTTP_PORT=8080 KRIMTO_BOOTSTRAP_ADMIN=you@acme.com pnpm dev
208
+ # Solo, local-only (no auth, no login on /ui)
209
+ npx @krimto-labs/krimto serve
210
+
211
+ # Team mode (first run prints an admin API key once)
212
+ KRIMTO_BOOTSTRAP_ADMIN=you@acme.com npx @krimto-labs/krimto serve
157
213
  # → "issued admin API key for you@acme.com (shown once): krm_live_…"
158
214
  # MCP at http://localhost:8080/mcp ; health at http://localhost:8080/health/ready
159
215
  ```
160
216
 
217
+ (`pnpm dev` is the dev-mode equivalent — only useful when working on Krimto itself.)
218
+
161
219
  Then point your agent at it with that key:
162
220
 
163
221
  ```json
@@ -225,12 +283,25 @@ docker run -d --name krimto -p 8080:8080 \
225
283
 
226
284
  ### Web UI (humans)
227
285
 
228
- When the HTTP server is running, open `http://localhost:8080/ui` and **sign in with any Krimto API
229
- key**. You can browse and search the facts you're allowed to see, open a fact, and manage your own API
230
- keys (issue / revoke). It reuses the same access control as the MCP tools, so you only ever see facts
231
- you can read. Set `KRIMTO_SESSION_SECRET` to keep sessions valid across restarts (otherwise a random
232
- per-boot secret is used). The UI is read-only for facts; editing with a review/approval flow lands in
233
- v0.3.
286
+ When the HTTP server is running, open `http://localhost:8080/ui`. In local mode (no
287
+ `KRIMTO_BOOTSTRAP_ADMIN`), there's no login. In team mode, sign in with any Krimto API key.
288
+
289
+ The dashboard shows:
290
+
291
+ - **How Krimto works** — personal → team → org explainer for first-time users.
292
+ - **Behind the scenes — your data, your files** — names the data folder; reminds you it's just
293
+ markdown in git that you can open in any editor.
294
+ - **Status** — green/gold/red dots for the two optional add-ons (git remote sync; semantic embeddings).
295
+ - **Recent activity** — last 5 MCP tool calls (tool, detail, caller, relative timestamp). Critical
296
+ for diagnosing "did my agent actually search?" without grepping stderr.
297
+ - **Fact list / detail** — browse and search the facts you're allowed to see; the detail page shows
298
+ the absolute source-file path so you can open the underlying `.md` in any editor.
299
+ - **API keys** — issue/revoke your own keys (team mode).
300
+ - **Team admin** (`/ui/admin`, org admins only) — add members, manage teams, issue keys for others.
301
+
302
+ The UI reuses the same access control as the MCP tools — you only ever see what you can read. Set
303
+ `KRIMTO_SESSION_SECRET` to keep sessions valid across restarts (otherwise a random per-boot secret is
304
+ used). The UI is read-only for facts; editing with a review/approval flow lands in v0.3.
234
305
 
235
306
  ## The eight promises (current status)
236
307
 
@@ -240,9 +311,9 @@ v0.3.
240
311
  | 2 | Hierarchical scope (`user`/`team`/`org`) as primary primitive | ✓ v0.2 |
241
312
  | 3 | Cross-vendor SDK (MCP server + per-marketplace plugins) | ✓ MCP server over stdio + HTTP v0.2; native plugins planned |
242
313
  | 4 | Attribution baked into every fact | ✓ v0.2 |
243
- | 5 | Self-hostable, single Docker | ✓ v0.2 — stdio, HTTP, or **Docker** (`docker build` + `docker run`); a published pull-image is next |
314
+ | 5 | Self-hostable, single Docker | ✓ v0.2 — published multi-arch image at `ghcr.io/krimto-labs/krimto`; also `npx krimto serve` (no Docker needed) and `pnpm dev` |
244
315
  | 6 | Apache-2.0 — fully open, no rug-pull | ✓ |
245
- | 7 | Web interface for humans, on top of git | ✓ minimal v0.2 (`/ui` login, browse/search, fact detail, API keys); full UI + PR approval in v0.3 |
316
+ | 7 | Web interface for humans, on top of git | ✓ v0.2.8 `/ui` with browse/search, fact detail (with source-file path), status panel, recent-activity feed, API keys, team admin; full editing + PR approval in v0.3 |
246
317
  | 8 | Zero-friction migration between self-hosted and Cloud | ⏳ full flow with v1.0 Cloud (`git clone` works today) |
247
318
 
248
319
  ## How Krimto compares
package/bin/krimto.mjs CHANGED
@@ -7,8 +7,124 @@ import process from "node:process";
7
7
  import { tsImport } from "tsx/esm/api";
8
8
 
9
9
  try {
10
- const mod = await tsImport("../src/server/index.ts", import.meta.url);
11
- await mod.main();
10
+ const cmd = process.argv[2];
11
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
12
+ // `krimto --help` — surface every subcommand so a user who didn't read the README can still
13
+ // discover them. Version is read from the server module so it never drifts from KRIMTO_VERSION.
14
+ const { formatHelp } = await tsImport("../src/cli/help.ts", import.meta.url);
15
+ const { KRIMTO_VERSION } = await tsImport("../src/server/index.ts", import.meta.url);
16
+ process.stdout.write(formatHelp(KRIMTO_VERSION));
17
+ } else if (cmd === "init") {
18
+ // `krimto init` — drop the always-use-Krimto rule into this project's agent rules files.
19
+ // By default, auto-detects the editor (.cursor/, CLAUDE.md, etc.) and writes only matching
20
+ // files. Pass `--all` to write to every supported rules file regardless of signals.
21
+ const all = process.argv.slice(3).includes("--all");
22
+ const { runInit } = await tsImport("../src/cli/init.ts", import.meta.url);
23
+ const res = await runInit(process.cwd(), { all });
24
+ if (res.written.length === 0) {
25
+ const why = res.detected
26
+ ? `(detected: ${res.considered.join(", ")}). Re-run with --all to also write the others.`
27
+ : "";
28
+ process.stderr.write(`krimto: agent rules already up to date — nothing to change. ${why}\n`);
29
+ } else {
30
+ const detectionLine = res.detected
31
+ ? `Detected editor signals in this project — writing only the matching rule files.\n` +
32
+ `(Run with --all to write all 4 supported rule files instead.)\n\n`
33
+ : !all
34
+ ? `No editor signals found — writing all supported rule files. Re-run with --all to\n` +
35
+ `keep this behavior explicitly, or remove any you don't need with \`krimto uninit\`.\n\n`
36
+ : "";
37
+ process.stderr.write(
38
+ detectionLine +
39
+ `krimto: wrote the always-use-Krimto rule to:\n ${res.written.join("\n ")}\n` +
40
+ "\n" +
41
+ "What changed: a short marker-delimited block was added to each file telling your\n" +
42
+ "agent to call krimto_recall before tasks and krimto_write when you say \"remember\".\n" +
43
+ "Existing content was preserved; running `init` again is a no-op.\n" +
44
+ "\n" +
45
+ "To remove the rule: delete the block between <!-- krimto:start --> and\n" +
46
+ "<!-- krimto:end --> in each file above. Nothing else in those files will be affected.\n" +
47
+ "(Or run `krimto uninit` to remove the rule from every file.)\n" +
48
+ "\n" +
49
+ "Restart your editor so the rule takes effect.\n",
50
+ );
51
+ }
52
+ } else if (cmd === "uninit") {
53
+ // `krimto uninit` — remove the always-use-Krimto rule from this project's rules files,
54
+ // flipping the project back from AUTO MODE to DEFAULT MODE.
55
+ const { runUninit } = await tsImport("../src/cli/uninit.ts", import.meta.url);
56
+ const res = await runUninit(process.cwd());
57
+ if (res.cleaned.length === 0) {
58
+ process.stderr.write("krimto: no Krimto rule found — nothing to remove.\n");
59
+ } else {
60
+ const rewritten = res.cleaned.filter((f) => !res.deleted.includes(f));
61
+ const lines = ["krimto: removed the always-use-Krimto rule."];
62
+ if (rewritten.length > 0) lines.push("Rule block stripped (file kept):", ...rewritten.map((f) => ` ${f}`));
63
+ if (res.deleted.length > 0) lines.push("File deleted (it held only our rule):", ...res.deleted.map((f) => ` ${f}`));
64
+ lines.push("", "Your project is back in DEFAULT MODE: Krimto tools are still wired up, but");
65
+ lines.push("your agent will only call them when you explicitly ask.");
66
+ lines.push("Restart your editor so it picks up the change.");
67
+ process.stderr.write(lines.join("\n") + "\n");
68
+ }
69
+ } else if (cmd === "where") {
70
+ // `krimto where` — print the data directory (honors KRIMTO_DATA), so files aren't a surprise.
71
+ const { resolveDataDir } = await tsImport("../src/server/index.ts", import.meta.url);
72
+ process.stdout.write(`${resolveDataDir()}\n`);
73
+ } else if (cmd === "setup-remote") {
74
+ // `krimto setup-remote <url>` — point the data dir's git repo at a remote and verify a push.
75
+ // Krimto must NOT be running while this is invoked (locks the .git/ index).
76
+ const url = process.argv[3];
77
+ if (!url) {
78
+ process.stderr.write("Usage: krimto setup-remote <git-remote-url>\n e.g. krimto setup-remote git@github.com:acme/krimto-data.git\n");
79
+ process.exit(2);
80
+ }
81
+ const { runSetupRemote } = await tsImport("../src/cli/setupRemote.ts", import.meta.url);
82
+ const { resolveDataDir } = await tsImport("../src/server/index.ts", import.meta.url);
83
+ const result = await runSetupRemote(resolveDataDir(), url);
84
+ process.stdout.write(result.message + "\n");
85
+ if (result.status !== "ok") process.exitCode = 1;
86
+ } else if (cmd === "verify-connection") {
87
+ // `krimto verify-connection` — read the lockfile + activity JSONL to answer "is my agent
88
+ // actually calling Krimto right now?" Works from any terminal regardless of how Krimto launched.
89
+ const { runVerifyConnection } = await tsImport("../src/cli/verifyConnection.ts", import.meta.url);
90
+ const { resolveDataDir } = await tsImport("../src/server/index.ts", import.meta.url);
91
+ const result = await runVerifyConnection(resolveDataDir());
92
+ process.stdout.write(result.message);
93
+ if (result.status === "none") process.exitCode = 1;
94
+ } else if (cmd === "setup-embeddings") {
95
+ // `krimto setup-embeddings` — verify KRIMTO_EMBED_* config by sending one real test embedding,
96
+ // so the user finds out about a bad key now, not after they've turned embeddings on.
97
+ const { runSetupEmbeddings } = await tsImport("../src/cli/setupEmbeddings.ts", import.meta.url);
98
+ const result = await runSetupEmbeddings();
99
+ process.stdout.write(result.message + "\n");
100
+ if (result.status !== "ok") process.exitCode = 1;
101
+ } else if (cmd === "storage") {
102
+ // `krimto storage` — explain where Krimto keeps data (markdown / git / index) in plain English,
103
+ // so the "you own your data" half of Krimto's pitch is reachable without reading the README.
104
+ const { formatStorage } = await tsImport("../src/cli/storage.ts", import.meta.url);
105
+ const { resolveDataDir } = await tsImport("../src/server/index.ts", import.meta.url);
106
+ process.stdout.write(formatStorage(resolveDataDir()));
107
+ } else if (cmd === "serve") {
108
+ // `krimto serve` — boot the HTTP server (with /ui and /ui/connect) from the npx on-ramp,
109
+ // so a stranger doesn't have to clone the repo or install Docker just to see the dashboard.
110
+ // Defaults to port 8080; honors an existing KRIMTO_HTTP_PORT if the caller set one.
111
+ if (!process.env.KRIMTO_HTTP_PORT) process.env.KRIMTO_HTTP_PORT = "8080";
112
+ const mod = await tsImport("../src/server/index.ts", import.meta.url);
113
+ await mod.main();
114
+ } else if (cmd === "usage") {
115
+ // `krimto usage` — the long-form guide: the five tools, both modes, copy-paste examples.
116
+ const { formatUsage } = await tsImport("../src/cli/usage.ts", import.meta.url);
117
+ const { KRIMTO_VERSION } = await tsImport("../src/server/index.ts", import.meta.url);
118
+ process.stdout.write(formatUsage(KRIMTO_VERSION));
119
+ } else if (cmd === "connect") {
120
+ // `krimto connect` — print stdio connect snippets (the npx on-ramp shape), so a solo user
121
+ // doesn't have to chase the README. Honors KRIMTO_IDENTITY when set.
122
+ const { formatConnect } = await tsImport("../src/cli/connect.ts", import.meta.url);
123
+ process.stdout.write(formatConnect({ identity: process.env.KRIMTO_IDENTITY }));
124
+ } else {
125
+ const mod = await tsImport("../src/server/index.ts", import.meta.url);
126
+ await mod.main();
127
+ }
12
128
  } catch (e) {
13
129
  process.stderr.write(`krimto: ${e instanceof Error ? e.message : String(e)}\n`);
14
130
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krimto-labs/krimto",
3
- "version": "0.2.6",
3
+ "version": "0.2.9",
4
4
  "description": "Open-source team memory layer for AI agents — markdown files in git, user/team/org hierarchy, cross-vendor MCP server.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/krimto-labs/krimto.git"
13
+ "url": "git+https://github.com/krimto-labs/krimto.git"
14
14
  },
15
15
  "homepage": "https://krimto.com",
16
16
  "bin": {
@@ -0,0 +1,64 @@
1
+ // The standing "always use Krimto" rule, plus idempotent application to an agent's rules file.
2
+ // Single source of truth for both the CLI (`krimto init`) and the web connect panel — so the
3
+ // rule a user pastes from /ui/connect and the rule `krimto init` writes can never drift.
4
+ //
5
+ // This is the fix for the discovery problem: by default, an editor's agent routes "remember X" to
6
+ // its own built-in memory, not Krimto. Dropping this rule into the agent's rules file makes the
7
+ // agent call krimto_recall / krimto_write on its own.
8
+
9
+ export const AGENT_RULE = `# Krimto memory — always use
10
+ - Before a task, call krimto_recall to load what we already know.
11
+ - When I say "remember", or you learn a durable fact, call krimto_write
12
+ (user/me = personal, team/<slug> = shared).
13
+ - Respect precedence: user beats team beats org.
14
+ - Don't save secrets or one-off chatter.`;
15
+
16
+ const START = "<!-- krimto:start -->";
17
+ const END = "<!-- krimto:end -->";
18
+
19
+ /** The rule wrapped in stable markers, so it can be found and updated in place later. */
20
+ export function ruleBlock(): string {
21
+ return `${START}\n${AGENT_RULE}\n${END}`;
22
+ }
23
+
24
+ /**
25
+ * Insert or refresh the Krimto rule block in an agent rules file's content, idempotently:
26
+ * - null/blank existing → just the block (with a trailing newline)
27
+ * - existing WITHOUT our markers → append the block, preserving all existing content
28
+ * - existing WITH our markers → replace only the marked block, preserving the rest
29
+ * Re-applying the same rule yields identical content (so callers can detect a no-op).
30
+ */
31
+ export function applyRule(existing: string | null): string {
32
+ const block = ruleBlock();
33
+ if (!existing || existing.trim() === "") return `${block}\n`;
34
+
35
+ const startIdx = existing.indexOf(START);
36
+ const endIdx = existing.indexOf(END);
37
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
38
+ return existing.slice(0, startIdx) + block + existing.slice(endIdx + END.length);
39
+ }
40
+
41
+ const sep = existing.endsWith("\n") ? "\n" : "\n\n";
42
+ return `${existing}${sep}${block}\n`;
43
+ }
44
+
45
+ /**
46
+ * Inverse of `applyRule` — remove the marker-delimited Krimto rule block.
47
+ * Returns:
48
+ * - the original string when no markers are found (caller treats as no-op)
49
+ * - the cleaned content (markers + everything between them stripped) otherwise
50
+ * - `null` when removing the block leaves the file empty/whitespace-only,
51
+ * signalling "delete this file" (it had no pre-existing content)
52
+ */
53
+ export function removeRule(existing: string | null): string | null {
54
+ if (existing === null) return null;
55
+ const startIdx = existing.indexOf(START);
56
+ const endIdx = existing.indexOf(END);
57
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return existing;
58
+
59
+ // Strip the block. Also consume one trailing newline so re-applying doesn't leave a blank gap.
60
+ const after = endIdx + END.length;
61
+ const trimmedAfter = after < existing.length && existing[after] === "\n" ? after + 1 : after;
62
+ const cleaned = existing.slice(0, startIdx) + existing.slice(trimmedAfter);
63
+ return cleaned.trim() === "" ? null : cleaned;
64
+ }
@@ -0,0 +1,49 @@
1
+ // `krimto connect` — print the stdio connect snippets so a user on the npx on-ramp doesn't have to
2
+ // dig the README out of a closed browser tab. The shapes come from `stdioConnectSnippets` so this
3
+ // surface and the /ui/connect panel can never drift.
4
+
5
+ import { stdioConnectSnippets } from "../server/connect";
6
+
7
+ export interface ConnectOpts {
8
+ /** Identity to embed in the Cursor env block. Defaults to a generic placeholder. */
9
+ identity?: string;
10
+ }
11
+
12
+ /** Build the multi-line, copy-pasteable output `krimto connect` prints to stdout. */
13
+ export function formatConnect(opts: ConnectOpts = {}): string {
14
+ const { claude, cursorJson } = stdioConnectSnippets(opts);
15
+ return [
16
+ "Connect your agent to Krimto (solo, stdio — no key needed):",
17
+ "",
18
+ "Claude Code:",
19
+ ` ${claude}`,
20
+ "",
21
+ "Cursor (~/.cursor/mcp.json):",
22
+ ...cursorJson.split("\n").map((line) => ` ${line}`),
23
+ "",
24
+ "What you get after pasting the snippet above:",
25
+ " The five Krimto tools (write, recall, read, supersede, list_scopes) are",
26
+ " available to your agent — but it only calls them when YOU explicitly ask,",
27
+ " e.g. \"use krimto to remember X\" or \"use krimto to recall what we know\".",
28
+ "",
29
+ "Want your agent to use Krimto AUTOMATICALLY (recall before tasks, save what",
30
+ "it learns when you say \"remember\")? Also run this once per project:",
31
+ " npx @krimto-labs/krimto init",
32
+ "",
33
+ " Skip `init` if you only want Krimto on-demand. The rule it writes can be",
34
+ " removed at any time — `krimto init` will explain how when you run it.",
35
+ "",
36
+ "Optional add-ons — add these to the `env` block above if you want them:",
37
+ ' "KRIMTO_GIT_REMOTE": "git@github.com:acme/krimto-data.git"',
38
+ " ↑ pushes every batch to a private git remote (team sync across machines)",
39
+ " Set this up safely first: `krimto setup-remote <url>` verifies the push works.",
40
+ ' "KRIMTO_EMBED_PROVIDER": "openai"',
41
+ ' "KRIMTO_EMBED_API_KEY": "sk-..."',
42
+ " ↑ turns on semantic / vector search (recall matches paraphrases, not just",
43
+ " keywords). Verify the provider works first: `krimto setup-embeddings`.",
44
+ " Both are optional — Krimto works fully without them.",
45
+ "",
46
+ "For team mode (HTTP + bearer auth), start the server and open /ui/connect.",
47
+ "",
48
+ ].join("\n");
49
+ }
@@ -0,0 +1,57 @@
1
+ // `krimto --help` — list every subcommand a user could otherwise only discover by reading the README.
2
+ // Single source of truth so bin/krimto.mjs and any future surface can't drift.
3
+
4
+ /** Build the help text printed by `krimto --help` / `krimto -h` / `krimto help`. */
5
+ export function formatHelp(version: string): string {
6
+ return [
7
+ `krimto — open-source team memory layer for AI coding agents (v${version})`,
8
+ "",
9
+ "TWO WAYS TO USE KRIMTO",
10
+ " DEFAULT MODE — your agent uses Krimto only when you explicitly ask",
11
+ ' ("use krimto to remember X" / "use krimto to recall what we know").',
12
+ " Setup: paste the `connect` snippet into your editor's MCP config.",
13
+ " Want the browser dashboard too? Run `serve` (no Docker / no clone needed).",
14
+ "",
15
+ " AUTO MODE — your agent uses Krimto automatically: recall before tasks,",
16
+ ' save what it learns when you say "remember".',
17
+ " Setup: `connect` (as above), THEN run `init` once in your project.",
18
+ " Switch back to DEFAULT MODE at any time with `uninit`.",
19
+ "",
20
+ "Usage:",
21
+ " npx @krimto-labs/krimto [command]",
22
+ "",
23
+ "Commands:",
24
+ " (no args) Start the stdio MCP server (default; for MCP clients to launch)",
25
+ " serve Start the HTTP server (port 8080) — gives you /ui dashboard",
26
+ " → use this if you want the browser UI without Docker / pnpm",
27
+ " connect Print copy-paste config snippets for Claude Code & Cursor",
28
+ " → use this once per editor / per machine",
29
+ " init [--all] Switch this project to AUTO MODE — write the always-use rule",
30
+ " → use this once per project (auto-detects your editor; --all = every file)",
31
+ " uninit Switch this project back to DEFAULT MODE — remove the rule",
32
+ " → cleanly reverses `init` (deletes files it created)",
33
+ " usage Show the five krimto_* tools and chat examples for both modes",
34
+ " → read this AFTER `connect` to learn how to actually use Krimto",
35
+ " storage Explain where Krimto keeps your data (markdown / git / index)",
36
+ " → run this to see what's on your disk and what you can edit",
37
+ " setup-remote Wire the data dir to a git remote and verify the push works",
38
+ " → one-time setup for cross-machine / teammate sync",
39
+ " setup-embeddings",
40
+ " Verify a KRIMTO_EMBED_* config by sending a real test embedding",
41
+ " → one-time check before turning on semantic / vector search",
42
+ " verify-connection",
43
+ " Diagnose if your agent is actually calling Krimto (live status + last 5 calls)",
44
+ " → run this when 'I asked the agent but recall returned nothing'",
45
+ " where Print the Krimto data directory",
46
+ " --help, -h Show this help",
47
+ "",
48
+ "Environment:",
49
+ " KRIMTO_DATA Override the data directory (default: ~/.krimto)",
50
+ " KRIMTO_IDENTITY Identity used for writes (default: user@localhost)",
51
+ " KRIMTO_HTTP_PORT Serve MCP over HTTP on this port instead of stdio",
52
+ " KRIMTO_BOOTSTRAP_ADMIN First-admin email — turns on team mode + key auth",
53
+ "",
54
+ "Learn more: https://krimto.com · https://github.com/krimto-labs/krimto",
55
+ "",
56
+ ].join("\n");
57
+ }
@@ -0,0 +1,110 @@
1
+ // `krimto init` — drop the always-use-Krimto standing rule into a project's agent rules files so the
2
+ // agent actually uses Krimto (fix for the discovery problem). Idempotent and non-destructive: it only
3
+ // adds/refreshes a marker-delimited block, never clobbering other content (see ../agentRule).
4
+ //
5
+ // G4 — by default, init now auto-detects which editor is in use (`.cursor/`, `.claude/`, existing
6
+ // CLAUDE.md / AGENTS.md / GEMINI.md, etc.) and writes ONLY matching files instead of all four. If
7
+ // no signals are present, it falls back to writing all four so a brand-new project still picks up
8
+ // the rule for whichever editor ships next. `--all` keeps the legacy "write everything" behavior.
9
+
10
+ import { promises as fs } from "node:fs";
11
+ import * as path from "node:path";
12
+
13
+ import { applyRule } from "../agentRule";
14
+
15
+ /** The agent rules files `krimto init` targets, relative to the project dir. */
16
+ export const INIT_TARGETS = [
17
+ "CLAUDE.md",
18
+ "AGENTS.md",
19
+ "GEMINI.md",
20
+ path.join(".cursor", "rules", "krimto.mdc"),
21
+ ];
22
+
23
+ export interface InitResult {
24
+ /** Relative paths that were created or updated (empty when everything was already current). */
25
+ written: string[];
26
+ /** Targets considered (after detection/--all decisions). Exposed for the CLI's success message. */
27
+ considered: string[];
28
+ /** True when targets came from auto-detection (some editor signals matched). */
29
+ detected: boolean;
30
+ }
31
+
32
+ async function exists(p: string): Promise<boolean> {
33
+ try {
34
+ await fs.access(p);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Auto-detect which editor rules files the project should get based on local signals.
43
+ * Returns the subset of INIT_TARGETS that has a positive signal, OR an empty array when
44
+ * nothing matches (caller falls back to writing everything).
45
+ */
46
+ export async function detectEditorTargets(cwd: string): Promise<string[]> {
47
+ const matches: string[] = [];
48
+
49
+ // CLAUDE.md — Claude Code
50
+ if ((await exists(path.join(cwd, "CLAUDE.md"))) || (await exists(path.join(cwd, ".claude"))) || (await exists(path.join(cwd, ".claude-plugin")))) {
51
+ matches.push("CLAUDE.md");
52
+ }
53
+ // AGENTS.md — Codex CLI / generic
54
+ if (await exists(path.join(cwd, "AGENTS.md"))) {
55
+ matches.push("AGENTS.md");
56
+ }
57
+ // GEMINI.md — Gemini CLI
58
+ if ((await exists(path.join(cwd, "GEMINI.md"))) || (await exists(path.join(cwd, "gemini-extension.json"))) || (await exists(path.join(cwd, ".gemini")))) {
59
+ matches.push("GEMINI.md");
60
+ }
61
+ // .cursor/rules/krimto.mdc — Cursor
62
+ if (await exists(path.join(cwd, ".cursor"))) {
63
+ matches.push(path.join(".cursor", "rules", "krimto.mdc"));
64
+ }
65
+
66
+ return matches;
67
+ }
68
+
69
+ export interface RunInitOptions {
70
+ /** Force writing all four files regardless of detection (the legacy behavior). */
71
+ all?: boolean;
72
+ /** Override the target list directly (tests; takes precedence over `all` + detection). */
73
+ targets?: string[];
74
+ }
75
+
76
+ /** Write/refresh the Krimto standing rule into each target rules file under `cwd`, idempotently. */
77
+ export async function runInit(cwd: string, opts: RunInitOptions = {}): Promise<InitResult> {
78
+ let targets: string[];
79
+ let detected = false;
80
+ if (opts.targets) {
81
+ targets = opts.targets;
82
+ } else if (opts.all) {
83
+ targets = INIT_TARGETS;
84
+ } else {
85
+ const auto = await detectEditorTargets(cwd);
86
+ if (auto.length > 0) {
87
+ targets = auto;
88
+ detected = true;
89
+ } else {
90
+ targets = INIT_TARGETS;
91
+ }
92
+ }
93
+
94
+ const written: string[] = [];
95
+ for (const rel of targets) {
96
+ const file = path.join(cwd, rel);
97
+ let existing: string | null = null;
98
+ try {
99
+ existing = await fs.readFile(file, "utf8");
100
+ } catch {
101
+ existing = null; // file doesn't exist yet — we'll create it
102
+ }
103
+ const next = applyRule(existing);
104
+ if (next === existing) continue; // already up to date
105
+ await fs.mkdir(path.dirname(file), { recursive: true });
106
+ await fs.writeFile(file, next, "utf8");
107
+ written.push(rel);
108
+ }
109
+ return { written, considered: targets, detected };
110
+ }