@krimto-labs/krimto 0.2.34 → 0.2.35
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 +115 -50
- package/package.json +1 -1
- package/src/cli/mcpConfig.ts +54 -17
- package/src/cli/wizard.ts +134 -28
- package/src/server/index.ts +1 -1
package/README.md
CHANGED
|
@@ -9,28 +9,55 @@ 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:** **v0.2.
|
|
13
|
-
>
|
|
14
|
-
> (markdown-in-git storage, `user → team → org` hierarchy,
|
|
15
|
-
> two-way git sync, MCP over stdio + HTTP, the Docker
|
|
16
|
-
>
|
|
12
|
+
> **Where we are:** **v0.2.35** is the current release — the v0.2.17 wizard redesign is now
|
|
13
|
+
> shipped end-to-end, plus eighteen patch releases of correctness fixes and agent-friendly
|
|
14
|
+
> surface. The v0.2.16 architecture (markdown-in-git storage, `user → team → org` hierarchy,
|
|
15
|
+
> hybrid retrieval, server-enforced access, two-way git sync, MCP over stdio + HTTP, the Docker
|
|
16
|
+
> image, the web UI) is unchanged. What you get on top of v0.2.16:
|
|
17
17
|
>
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
>
|
|
21
|
-
>
|
|
22
|
-
> - **
|
|
23
|
-
>
|
|
24
|
-
>
|
|
25
|
-
> thing without re-running the whole wizard. `reset` cleanly disconnects + uninstalls; `--wipe-notes`
|
|
26
|
-
> moves data to a recoverable trash sibling, never `rm -rf`.
|
|
27
|
-
> - **Notes-app `/ui`** — plain-English scope labels (Just me / Team name / Org name), inline
|
|
28
|
-
> Edit + Move + Delete on every note, and a consolidated **Settings** page for the engineering
|
|
29
|
-
> panels.
|
|
18
|
+
> **The setup story (v0.2.17 → v0.2.21).**
|
|
19
|
+
> - **One-command interactive setup wizard** (`krimto init`) — five questions, preselected
|
|
20
|
+
> defaults. Reads `git config user.email` for identity; detects editors at both project
|
|
21
|
+
> level (`.cursor/`, `CLAUDE.md`) and machine level (`~/.cursor/`, `~/.claude.json`).
|
|
22
|
+
> - **Reconfigure menu** — re-runs of `krimto init` show a "Krimto on this machine:"
|
|
23
|
+
> summary with a real **Service:** line driven by lock + launchctl reality (not just
|
|
24
|
+
> config snapshot). "Keep current" prompts let you Enter-through.
|
|
30
25
|
>
|
|
31
|
-
>
|
|
32
|
-
>
|
|
33
|
-
>
|
|
26
|
+
> **The teardown story (v0.2.32 → v0.2.33).**
|
|
27
|
+
> - **`krimto stop` / `start` / `restart`** — first-class verbs for the off-ramp the original
|
|
28
|
+
> wizard forgot. Idempotent. `stop` keeps the plist on disk so `start` can reload it.
|
|
29
|
+
> - **`krimto reset [--yes] [--wipe-notes]`** — aggressive sweep across all editors (Cursor
|
|
30
|
+
> JSON + Claude Code CLI scopes user/project/local) + launchd/systemd uninstall + lock
|
|
31
|
+
> cleanup. `--wipe-notes` collapses to one named-consequence prompt.
|
|
32
|
+
>
|
|
33
|
+
> **The runtime story (v0.2.26 → v0.2.30).**
|
|
34
|
+
> - **`/ui` notes-app redesign** — warm-paper Fraunces serif aesthetic, scope cards with
|
|
35
|
+
> emoji icons (📔 Just me / 📓 Team / 🏢 Org), notes timeline with per-row Edit / Move /
|
|
36
|
+
> Delete / View file. `krimto ui` opens it.
|
|
37
|
+
> - **Single reconciled runtime view** (`inspectRuntime`) — every read-side command (status,
|
|
38
|
+
> verify-connection, whoami, reconfigure menu) routes through the same lock + launchctl +
|
|
39
|
+
> editor-config probe. No more "status says one thing, verify says another."
|
|
40
|
+
> - **Service-first install ordering + port-ready probe** — wizard installs the service
|
|
41
|
+
> first, waits for `:8080` to accept TCP, then writes editor configs. Cursor's file
|
|
42
|
+
> watcher never fires into an unbound port (the v0.2.27/28 ECONNREFUSED fix).
|
|
43
|
+
>
|
|
44
|
+
> **The agent story (v0.2.34 → v0.2.35).**
|
|
45
|
+
> - **Phase B agent flags** — `editors --add cursor`, `service --always`, `search --keyword`,
|
|
46
|
+
> `reset --yes`, `remote --set <url>`, `folder --to <path>`. Every command that used to
|
|
47
|
+
> open an interactive prompt now has a flag form.
|
|
48
|
+
> - **Non-TTY guards** — interactive commands without flags exit 2 with copy-pasteable
|
|
49
|
+
> usage instead of hanging on an unanswerable prompt.
|
|
50
|
+
> - **`krimto whoami` + `set identity <email>`** + a **`krimto_whoami` MCP tool** so agents
|
|
51
|
+
> stop hallucinating identity.
|
|
52
|
+
> - **Editor attribution via User-Agent sniffing** — facts saved over HTTP automatically
|
|
53
|
+
> carry `source: "cursor"` / `"claude-code"` / etc., so the dashboard's "saved from a
|
|
54
|
+
> Cursor chat" line works without prompting agents.
|
|
55
|
+
> - **Cursor `alwaysApply: true` frontmatter** so `.cursor/rules/krimto.mdc` auto-attaches
|
|
56
|
+
> instead of requiring the user to type "krimto" first.
|
|
57
|
+
>
|
|
58
|
+
> See [ROADMAP.md](ROADMAP.md), [CHANGELOG.md](CHANGELOG.md), and the proposal-vs-reality
|
|
59
|
+
> diff in [docs/krimto-v0.2.17-maria-journey.html §09](docs/krimto-v0.2.17-maria-journey.html)
|
|
60
|
+
> for the design rationale + what each patch caught.
|
|
34
61
|
|
|
35
62
|
## Try it in 90 seconds (solo, no account)
|
|
36
63
|
|
|
@@ -76,6 +103,33 @@ what your agent has been calling.
|
|
|
76
103
|
**Power-user / CI:** `npx @krimto-labs/krimto init --yes` skips all prompts and applies
|
|
77
104
|
defaults non-interactively. `--all` and `--minimal` keep their v0.2.16 meaning.
|
|
78
105
|
|
|
106
|
+
### Setting Krimto up programmatically (AI agents, CI)
|
|
107
|
+
|
|
108
|
+
Krimto is designed to be driven by Claude Code, Cursor, Codex, and similar AI agents that
|
|
109
|
+
don't have a stdin TTY. Every interactive command has a flag form:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# The one-command full install (defaults: detected editors, as-needed run mode):
|
|
113
|
+
krimto init --yes
|
|
114
|
+
|
|
115
|
+
# Or pick the pieces:
|
|
116
|
+
krimto editors --add cursor --add claude-code --yes # wire editors
|
|
117
|
+
krimto service --always # install background service
|
|
118
|
+
krimto search --keyword # keyword search (default)
|
|
119
|
+
krimto search --openai --api-key sk-... # semantic search
|
|
120
|
+
krimto remote --set git@github.com:acme/krimto.git # cross-machine sync
|
|
121
|
+
krimto status # machine-readable check
|
|
122
|
+
krimto stop / start / restart # idempotent service control
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Run any interactive command (`editors`, `service`, `search`, `remote`, `folder`, `reset`)
|
|
126
|
+
without a flag from a non-TTY shell and you get `exit 2` with copy-pasteable flag usage,
|
|
127
|
+
not a hung-prompt warning. `krimto --help` has a dedicated **For AI agents (no TTY)**
|
|
128
|
+
block at the top.
|
|
129
|
+
|
|
130
|
+
The HTTP MCP handler also **sniffs `User-Agent`** to auto-stamp facts with `source:
|
|
131
|
+
"cursor"` / `"claude-code"` / etc. — no agent-prompt convention needed.
|
|
132
|
+
|
|
79
133
|
## Connect your agent
|
|
80
134
|
|
|
81
135
|
Krimto is one MCP server — point any client at `http://localhost:8080/mcp`. Verified for **Claude Code**
|
|
@@ -136,66 +190,77 @@ The in-product **Connect** page (`/ui/connect`) shows this same rule with a copy
|
|
|
136
190
|
|
|
137
191
|
Everything is reachable via `npx`. Run `npx @krimto-labs/krimto --help` for the full list. All
|
|
138
192
|
commands print clean, sectioned output with ✅ / ⚠️ / 🟢 status indicators and copy-paste shell
|
|
139
|
-
commands.
|
|
193
|
+
commands. The seven groups below match `--help`'s structure.
|
|
140
194
|
|
|
141
195
|
**Get connected (start here)**
|
|
142
196
|
|
|
143
197
|
| Command | What it does |
|
|
144
198
|
|---|---|
|
|
145
|
-
| `init` | Interactive setup wizard — five questions, preselected defaults. Connects detected editors, applies the standing rule, optionally installs a background service. `--yes` skips prompts; legacy `--all` / `--minimal` still work. |
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `uninit` | Strip the standing rule from this project's rules files |
|
|
199
|
+
| `init [--yes]` | Interactive setup wizard — five questions, preselected defaults. Connects detected editors, applies the standing rule, optionally installs a background service. `--yes` skips prompts; legacy `--all` / `--minimal` still work. |
|
|
200
|
+
| `connect` | Print copy-paste config for Claude Code & Cursor (manual path) — substitutes your real `git config user.email` for the identity. |
|
|
201
|
+
| `uninit [--also-stop | --keep-running]` | Strip the standing rule from this project's rules files. Offers to stop the service too (or skip the prompt with the flags). |
|
|
149
202
|
|
|
150
|
-
**
|
|
203
|
+
**Look at your notes**
|
|
151
204
|
|
|
152
205
|
| Command | What it does |
|
|
153
206
|
|---|---|
|
|
154
207
|
| `notes [query]` | List notes grouped by plain-English scope (`Just me` / team name / org name). With a query: ranked search via `krimto_recall`. |
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
208
|
+
| `ui` | Open `http://localhost:8080/ui` in your browser. |
|
|
209
|
+
| `open` | Reveal the notes folder in your OS file manager (`open` / `xdg-open` / `explorer`). |
|
|
210
|
+
| `edit <id>` | Open the fact's `.md` in `$EDITOR`; reindexes on save. |
|
|
211
|
+
| `mv <id> <scope>` | Move a note between scopes (id preserved). |
|
|
212
|
+
| `supersede <id>` | Replace a note with a new version. Old stays in git. |
|
|
213
|
+
| `tag <id> +new -old ...` | Add or remove tags via frontmatter rewrite. |
|
|
214
|
+
| `rm <id>` | Delete a fact (file + index + git deletion commit). |
|
|
159
215
|
|
|
160
|
-
**
|
|
216
|
+
**Stop & reset** (v0.2.32+)
|
|
161
217
|
|
|
162
218
|
| Command | What it does |
|
|
163
219
|
|---|---|
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
220
|
+
| `stop` | Stop the running krimto (uninstalls/unloads service if installed, SIGTERMs an ad-hoc PID). Plist stays on disk so `start` can reload it. Idempotent. |
|
|
221
|
+
| `start` | Start krimto (re-bootstraps the existing plist). Honest message when no service is configured. |
|
|
222
|
+
| `restart` | `stop` + `start`. Atomic on always-running mode via `launchctl kickstart -k`. |
|
|
223
|
+
| `uninit` | Project-only undo (rule files only). Offers to stop the service too. |
|
|
224
|
+
| `reset [--yes] [--wipe-notes]` | Disconnect every editor (Cursor JSON + Claude Code CLI across user/project/local scopes) + uninstall service + wipe API-key store. `--wipe-notes` collapses to one named-consequence prompt. |
|
|
167
225
|
|
|
168
|
-
**
|
|
226
|
+
**Is it working?**
|
|
169
227
|
|
|
170
228
|
| Command | What it does |
|
|
171
229
|
|---|---|
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `reset [--yes] [--wipe-notes]` | Disconnect from all editors + uninstall service + wipe local keys. `--wipe-notes` atomically moves the data dir to a timestamped trash sibling (recoverable). |
|
|
230
|
+
| `status` | One-screen consolidator: running PID + mode + launched-by, editors, storage, add-ons, recent activity, hijack warning. Nudges toward `service --always` when running ad-hoc. |
|
|
231
|
+
| `whoami` | Print the active `KRIMTO_IDENTITY` and every place it's set (each editor's MCP config + the service unit env). Flags mismatches. |
|
|
232
|
+
| `verify-connection` / `where` / `storage` / `usage` | Back-compat aliases — preserve their original stdout, point at `status` on stderr. |
|
|
176
233
|
|
|
177
|
-
**
|
|
234
|
+
**Configure (after first run)** — every command accepts flags so agents can drive them non-interactively.
|
|
178
235
|
|
|
179
236
|
| Command | What it does |
|
|
180
237
|
|---|---|
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
238
|
+
| `editors --add <name> / --remove <name> / --set <list>` | Wire / unwire editors (Cursor / Claude Code / Codex / Gemini). `--list` prints current connections. No-flag form prompts interactively when a TTY is available. |
|
|
239
|
+
| `service --as-needed / --always / --manual` | Switch run mode. Flag form skips the prompt. `service stop` / `service start` are aliases for `stop` / `start`. |
|
|
240
|
+
| `search --keyword / --openai --api-key sk-...` | Switch search provider. The OpenAI path verifies the key before persisting. |
|
|
241
|
+
| `remote --show / --set <url> / --remove` | Manage the git remote (also reachable as `setup-remote <url>`). |
|
|
242
|
+
| `folder --to <path> [--yes]` | Guided move of the data dir. Stops service, atomic rename (cross-fs fallback), reinstalls service with new env, prints `export KRIMTO_DATA=` hint. |
|
|
243
|
+
| `set identity <email>` | Change `KRIMTO_IDENTITY` everywhere atomically (editor MCP configs + service env). Preserves other env keys. |
|
|
185
244
|
|
|
186
|
-
**
|
|
245
|
+
**Team**
|
|
187
246
|
|
|
188
247
|
| Command | What it does |
|
|
189
248
|
|---|---|
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
249
|
+
| `team init` | Admin-side wizard: admin email, team slug, optional git remote, initial teammates. Prints admin key + per-teammate keys + a DM template. |
|
|
250
|
+
| `team disband [--yes]` | Per-machine step-back to solo mode. Notes / `members.yaml` / git history untouched. |
|
|
251
|
+
| `join --server <url> --key <key>` | Teammate-side: detects editors, writes HTTP MCP config with the bearer header + the standing rule. |
|
|
192
252
|
|
|
193
|
-
**
|
|
253
|
+
**Advanced / scripting**
|
|
194
254
|
|
|
195
255
|
| Command | What it does |
|
|
196
256
|
|---|---|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
257
|
+
| `serve` | Start the HTTP server in the foreground (port 8080) + browser `/ui` dashboard. |
|
|
258
|
+
| `setup-remote <url>` | One-shot git remote setup (verifies the initial push). |
|
|
259
|
+
| `setup-embeddings` | Send a real test embedding to verify a `KRIMTO_EMBED_*` config. |
|
|
260
|
+
| `reindex` | Rebuild `index.db` from markdown (fixes orphans). |
|
|
261
|
+
| `delete <id>` | Alias for `rm`. |
|
|
262
|
+
| (no args) | Start the stdio MCP server (default; for MCP clients to launch). |
|
|
263
|
+
| `--help`, `-h` | Show the full CLI surface — includes a `For AI agents (no TTY)` block at the top with copy-paste flag examples. |
|
|
199
264
|
|
|
200
265
|
The stdio entrypoint enforces a **single-writer lock** on the data dir (`.krimto/lock.json`) — two
|
|
201
266
|
Krimto processes can no longer race on the same `~/.krimto`. A second `serve`/stdio launch is
|
package/package.json
CHANGED
package/src/cli/mcpConfig.ts
CHANGED
|
@@ -148,25 +148,62 @@ export async function writeMcpConfig(
|
|
|
148
148
|
* matching `mcp remove`, but we don't depend on it).
|
|
149
149
|
*/
|
|
150
150
|
export async function removeMcpConfig(env: EditorEnvironment): Promise<{ removed: boolean }> {
|
|
151
|
-
if (env.mcpWire === null
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
text
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
if (env.mcpWire === null) return { removed: false };
|
|
152
|
+
|
|
153
|
+
if (env.mcpWire.method === "json") {
|
|
154
|
+
let text: string;
|
|
155
|
+
try {
|
|
156
|
+
text = await fs.readFile(env.mcpWire.path, "utf8");
|
|
157
|
+
} catch {
|
|
158
|
+
return { removed: false };
|
|
159
|
+
}
|
|
160
|
+
let parsed: Record<string, unknown>;
|
|
161
|
+
try {
|
|
162
|
+
parsed = JSON.parse(text) as Record<string, unknown>;
|
|
163
|
+
} catch {
|
|
164
|
+
return { removed: false };
|
|
165
|
+
}
|
|
166
|
+
const servers = parsed[env.mcpWire.key] as Record<string, unknown> | undefined;
|
|
167
|
+
if (!servers || !("krimto" in servers)) return { removed: false };
|
|
168
|
+
const { krimto: _krimto, ...rest } = servers; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
169
|
+
parsed[env.mcpWire.key] = rest;
|
|
170
|
+
await fs.writeFile(env.mcpWire.path, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
171
|
+
return { removed: true };
|
|
157
172
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
|
|
174
|
+
// v0.2.35 — CLI method (Claude Code). The smoke-6 transcript caught reset reporting "No
|
|
175
|
+
// editors were connected" while `claude mcp list` still showed krimto. Root cause: this
|
|
176
|
+
// function used to bail at the top with `method !== "json"`, so the CLI path was never
|
|
177
|
+
// exercised. The reset SWEEP rule (always run cleanup, never trust detection) covers the
|
|
178
|
+
// intent here too — we shell out to `claude mcp remove krimto` across the three scopes
|
|
179
|
+
// Claude Code supports (local / user / project). Each call is idempotent: if krimto
|
|
180
|
+
// isn't registered at a scope, claude prints "MCP server krimto not found" and exits
|
|
181
|
+
// non-zero, which we swallow. We mark removed=true when ANY scope succeeds.
|
|
182
|
+
if (env.mcpWire.method === "cli") {
|
|
183
|
+
return { removed: await removeClaudeMcpAllScopes(env.mcpWire.command) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { removed: false };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* v0.2.35 — try `claude mcp remove krimto -s <scope>` for each known scope. Returns true
|
|
191
|
+
* when at least one succeeds. The user's krimto entry may live in any scope depending on
|
|
192
|
+
* which directory they were in when they originally ran `claude mcp add`, so we sweep all
|
|
193
|
+
* three. Errors are best-effort (no-op when nothing's registered at that scope).
|
|
194
|
+
*/
|
|
195
|
+
async function removeClaudeMcpAllScopes(claudeBinary: string): Promise<boolean> {
|
|
196
|
+
const scopes = ["local", "user", "project"] as const;
|
|
197
|
+
let anyRemoved = false;
|
|
198
|
+
for (const scope of scopes) {
|
|
199
|
+
try {
|
|
200
|
+
await exec(claudeBinary, ["mcp", "remove", "krimto", "-s", scope]);
|
|
201
|
+
anyRemoved = true;
|
|
202
|
+
} catch {
|
|
203
|
+
// "MCP server krimto not found" at this scope — fine, try the next.
|
|
204
|
+
}
|
|
163
205
|
}
|
|
164
|
-
|
|
165
|
-
if (!servers || !("krimto" in servers)) return { removed: false };
|
|
166
|
-
const { krimto: _krimto, ...rest } = servers; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
167
|
-
parsed[env.mcpWire.key] = rest;
|
|
168
|
-
await fs.writeFile(env.mcpWire.path, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
169
|
-
return { removed: true };
|
|
206
|
+
return anyRemoved;
|
|
170
207
|
}
|
|
171
208
|
|
|
172
209
|
// --- internal helpers -------------------------------------------------------
|
package/src/cli/wizard.ts
CHANGED
|
@@ -25,6 +25,10 @@ import {
|
|
|
25
25
|
} from "./init";
|
|
26
26
|
import { runSetupEmbeddings } from "./setupEmbeddings";
|
|
27
27
|
import { KRIMTO_VERSION } from "../server/index";
|
|
28
|
+
import { inspectRuntime, type RuntimeState } from "./inspectRuntime";
|
|
29
|
+
import { applyService } from "./serviceCmd";
|
|
30
|
+
import * as os from "node:os";
|
|
31
|
+
import * as path from "node:path";
|
|
28
32
|
|
|
29
33
|
const EDITOR_LABEL: Record<EditorKind, string> = {
|
|
30
34
|
cursor: "Cursor",
|
|
@@ -68,46 +72,88 @@ export async function runInitWizard(
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* §06 — reconfigure menu shown when `krimto init` is re-run on a configured machine.
|
|
77
|
+
*
|
|
78
|
+
* v0.2.35: drops the ambiguous "Krimto is already set up on this machine" header (users
|
|
79
|
+
* read "set up" as "running"). The new header is neutral — "Krimto on this machine:" —
|
|
80
|
+
* and adds a real **Service:** line driven by `inspectRuntime` (config-snapshot ≠ runtime).
|
|
81
|
+
* That way the user always knows whether a process is actually serving right now, not just
|
|
82
|
+
* whether a config exists on disk. A fifth menu choice — "Start it running continuously"
|
|
83
|
+
* — appears only when no service is currently loaded, so users who want a daemon find the
|
|
84
|
+
* button without leaving the menu.
|
|
85
|
+
*/
|
|
72
86
|
async function runReconfigureMenu(
|
|
73
87
|
cwd: string,
|
|
74
88
|
snapshot: SetupSnapshot,
|
|
75
89
|
opts: RunWizardOptions,
|
|
76
90
|
io: WizardIO,
|
|
77
91
|
): Promise<ApplyResult | null> {
|
|
92
|
+
// Match applyWizardAnswers's pattern: derive dataDir from homeDir when not explicitly
|
|
93
|
+
// passed. Tests rely on this so the runtime probe hits a test temp dir, not the user's
|
|
94
|
+
// real ~/.krimto.
|
|
95
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
96
|
+
const dataDir = opts.dataDir ?? path.join(homeDir, ".krimto");
|
|
97
|
+
const runtime = await inspectRuntime(dataDir, { cwd, homeDir });
|
|
98
|
+
|
|
78
99
|
io.out("\n");
|
|
79
|
-
io.out("Krimto
|
|
80
|
-
io.out(
|
|
81
|
-
|
|
100
|
+
io.out("Krimto on this machine:\n");
|
|
101
|
+
io.out(
|
|
102
|
+
` Editors: ${
|
|
103
|
+
snapshot.registeredEditors.map((e) => EDITOR_LABEL[e]).join(", ") || "(none)"
|
|
104
|
+
}\n`,
|
|
105
|
+
);
|
|
106
|
+
io.out(` Service: ${formatServiceLine(runtime)}\n`);
|
|
82
107
|
io.out(` Search: ${searchLabel(snapshot.searchProvider)}\n`);
|
|
83
108
|
io.out("\n");
|
|
84
109
|
|
|
110
|
+
// The "Start it running continuously" choice only makes sense when no daemon is alive.
|
|
111
|
+
// (When already running as a service, there's nothing to start; when running ad-hoc, the
|
|
112
|
+
// user is better served by `krimto service --always` AFTER stopping the ad-hoc one, which
|
|
113
|
+
// is more state than this menu wants to manage.)
|
|
114
|
+
const canStartService =
|
|
115
|
+
!runtime.service.loaded && !(runtime.lock?.alive ?? false);
|
|
116
|
+
|
|
117
|
+
const choices: Array<{
|
|
118
|
+
value: "refresh" | "reconfigure" | "status" | "start-service" | "quit";
|
|
119
|
+
name: string;
|
|
120
|
+
description: string;
|
|
121
|
+
}> = [
|
|
122
|
+
{
|
|
123
|
+
value: "refresh",
|
|
124
|
+
name: "Just refresh the standing rule in this project",
|
|
125
|
+
description:
|
|
126
|
+
"Re-applies the 'always use Krimto' rule to CLAUDE.md / .cursor/rules/ here.\nUse this when you just cloned a new repo.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
value: "reconfigure",
|
|
130
|
+
name: "Change settings (reconfigure)",
|
|
131
|
+
description:
|
|
132
|
+
"Re-runs the setup wizard with your current answers pre-filled.\nSkip anything you don't want to change.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
value: "status",
|
|
136
|
+
name: "View status",
|
|
137
|
+
description: "Show what's working, what's configured, recent activity.",
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
if (canStartService) {
|
|
141
|
+
choices.push({
|
|
142
|
+
value: "start-service",
|
|
143
|
+
name: "Start it running continuously (install as a background service)",
|
|
144
|
+
description:
|
|
145
|
+
"Runs Krimto as a launchd / systemd-user / schtasks service so it stays up\nacross reboots. Equivalent to `krimto service --always`.",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
choices.push({
|
|
149
|
+
value: "quit",
|
|
150
|
+
name: "Quit",
|
|
151
|
+
description: "Leave Krimto as it is.",
|
|
152
|
+
});
|
|
153
|
+
|
|
85
154
|
const choice = await select({
|
|
86
155
|
message: "What would you like to do?",
|
|
87
|
-
choices
|
|
88
|
-
{
|
|
89
|
-
value: "refresh" as const,
|
|
90
|
-
name: "Just refresh the standing rule in this project",
|
|
91
|
-
description:
|
|
92
|
-
"Re-applies the 'always use Krimto' rule to CLAUDE.md / .cursor/rules/ here.\nUse this when you just cloned a new repo.",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
value: "reconfigure" as const,
|
|
96
|
-
name: "Change settings (reconfigure)",
|
|
97
|
-
description:
|
|
98
|
-
"Re-runs the setup wizard with your current answers pre-filled.\nSkip anything you don't want to change.",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
value: "status" as const,
|
|
102
|
-
name: "View status",
|
|
103
|
-
description: "Show what's working, what's configured, recent activity.",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
value: "quit" as const,
|
|
107
|
-
name: "Quit",
|
|
108
|
-
description: "Leave Krimto as it is.",
|
|
109
|
-
},
|
|
110
|
-
],
|
|
156
|
+
choices,
|
|
111
157
|
});
|
|
112
158
|
|
|
113
159
|
if (choice === "quit") {
|
|
@@ -139,10 +185,70 @@ async function runReconfigureMenu(
|
|
|
139
185
|
return null;
|
|
140
186
|
}
|
|
141
187
|
|
|
188
|
+
if (choice === "start-service") {
|
|
189
|
+
// v0.2.35 — install + load the always-running service from inside the menu so users
|
|
190
|
+
// who want a daemon don't have to drop back to the shell and remember the flag name.
|
|
191
|
+
// Delegates to applyService which handles the platform-specific install + port probe.
|
|
192
|
+
io.out("\nInstalling background service...\n");
|
|
193
|
+
const installResult = await applyService("always-running", {
|
|
194
|
+
...(opts.dataDir ? { dataDir: opts.dataDir } : {}),
|
|
195
|
+
...(opts.homeDir ? { homeDir: opts.homeDir } : {}),
|
|
196
|
+
...(opts.dryRun ? { dryRun: true } : {}),
|
|
197
|
+
});
|
|
198
|
+
if (installResult.install?.activated) {
|
|
199
|
+
const portMsg =
|
|
200
|
+
installResult.install.portReady === false
|
|
201
|
+
? " ⚠ Service installed but the HTTP port didn't come up within 10s. Check\n /tmp/com.krimto.server.err.log.\n"
|
|
202
|
+
: " ✓ Service started, port accepting connections.\n";
|
|
203
|
+
io.out("\n✅ Background service is now running.\n" + portMsg + "\n");
|
|
204
|
+
} else {
|
|
205
|
+
io.out("\n(Service was configured but not activated — likely a dry-run.)\n");
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
142
210
|
// "reconfigure" — re-run the five questions with snapshot as defaults
|
|
143
211
|
return runFreshWizard(cwd, opts, io, snapshot);
|
|
144
212
|
}
|
|
145
213
|
|
|
214
|
+
/**
|
|
215
|
+
* v0.2.35 — turn the reconciled runtime view into one human line for the reconfigure menu.
|
|
216
|
+
* Four cases (in priority order):
|
|
217
|
+
* 1. A live process holds the lock AND launched-by="service" → "Running as background service (PID …, started Nm ago)"
|
|
218
|
+
* 2. A live process holds the lock, launched-by="ad-hoc" → "Running ad-hoc (PID … — started by `krimto serve` or an editor)"
|
|
219
|
+
* 3. Service unit on disk but not loaded → "⚠ Installed but not running — `krimto start` to load it"
|
|
220
|
+
* 4. Nothing running, no unit on disk → "Not running (your editor launches it on demand via stdio)"
|
|
221
|
+
*
|
|
222
|
+
* This is the line that the smoke-6 user wanted: "is Krimto serving RIGHT NOW?" — driven
|
|
223
|
+
* by lock + launchctl/systemctl reality, not by the static config snapshot.
|
|
224
|
+
*/
|
|
225
|
+
function formatServiceLine(runtime: RuntimeState): string {
|
|
226
|
+
const lock = runtime.lock;
|
|
227
|
+
if (lock?.alive) {
|
|
228
|
+
const ago = humanAgoForLock(lock.started);
|
|
229
|
+
if (runtime.effectiveLaunchedBy === "service") {
|
|
230
|
+
return `Running as background service (PID ${lock.pid}, started ${ago})`;
|
|
231
|
+
}
|
|
232
|
+
return `Running ad-hoc (PID ${lock.pid} — started ${ago} by \`krimto serve\` or an editor)`;
|
|
233
|
+
}
|
|
234
|
+
if (runtime.service.installed) {
|
|
235
|
+
return "⚠ Installed but not running — `krimto start` to load it";
|
|
236
|
+
}
|
|
237
|
+
return "Not running (your editor launches it on demand via stdio)";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function humanAgoForLock(iso: string): string {
|
|
241
|
+
const t = Date.parse(iso);
|
|
242
|
+
if (Number.isNaN(t)) return iso;
|
|
243
|
+
const secs = Math.max(0, Math.round((Date.now() - t) / 1000));
|
|
244
|
+
if (secs < 60) return `${secs}s ago`;
|
|
245
|
+
const mins = Math.round(secs / 60);
|
|
246
|
+
if (mins < 60) return `${mins}m ago`;
|
|
247
|
+
const hrs = Math.round(mins / 60);
|
|
248
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
249
|
+
return `${Math.round(hrs / 24)}d ago`;
|
|
250
|
+
}
|
|
251
|
+
|
|
146
252
|
/** §03 — the fresh five-question flow. `snapshot` (when given) seeds the defaults. */
|
|
147
253
|
async function runFreshWizard(
|
|
148
254
|
cwd: string,
|
package/src/server/index.ts
CHANGED
|
@@ -49,7 +49,7 @@ import { type Requester } from "../access/scope";
|
|
|
49
49
|
|
|
50
50
|
export type RequesterResolver = (extra: { authInfo?: AuthInfo }) => Requester;
|
|
51
51
|
|
|
52
|
-
export const KRIMTO_VERSION = "0.2.
|
|
52
|
+
export const KRIMTO_VERSION = "0.2.35";
|
|
53
53
|
|
|
54
54
|
export function resolveDataDir(): string {
|
|
55
55
|
return process.env.KRIMTO_DATA ?? path.join(homedir(), ".krimto");
|