@krimto-labs/krimto 0.2.19 → 0.2.21
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/package.json +1 -1
- package/src/cli/editors.ts +6 -4
- package/src/cli/init.ts +50 -1
- package/src/cli/join.ts +8 -5
- package/src/cli/status.ts +4 -3
- package/src/cli/wizard.ts +58 -19
- package/src/server/index.ts +1 -1
package/package.json
CHANGED
package/src/cli/editors.ts
CHANGED
|
@@ -126,10 +126,12 @@ async function askEditorsList(
|
|
|
126
126
|
description: current.includes(env.editor)
|
|
127
127
|
? "currently connected"
|
|
128
128
|
: env.present
|
|
129
|
-
? "detected
|
|
130
|
-
: env.
|
|
131
|
-
? "not
|
|
132
|
-
:
|
|
129
|
+
? "detected in this project"
|
|
130
|
+
: env.installed
|
|
131
|
+
? "installed on this machine (not in this project yet)"
|
|
132
|
+
: env.mcpWire === null
|
|
133
|
+
? "not detected — manual snippet only"
|
|
134
|
+
: "not detected — toggle on if you want anyway",
|
|
133
135
|
checked: current.includes(env.editor),
|
|
134
136
|
})),
|
|
135
137
|
});
|
package/src/cli/init.ts
CHANGED
|
@@ -106,14 +106,52 @@ export type McpWireMethod =
|
|
|
106
106
|
|
|
107
107
|
export interface EditorEnvironment {
|
|
108
108
|
editor: EditorKind;
|
|
109
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* True when local **project-level** signals (file/dir presence inside `cwd`) indicate this
|
|
111
|
+
* editor is in use *for this project*. Narrow scope — `.cursor/`, `CLAUDE.md`, etc.
|
|
112
|
+
*/
|
|
110
113
|
present: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* v0.2.21: True when **machine-level** signals (files/dirs in `homeDir`) indicate this editor
|
|
116
|
+
* is installed and has been used at least once on this machine. Broader than `present` — catches
|
|
117
|
+
* the case where a user is editing in Cursor but the project folder hasn't acquired a `.cursor/`
|
|
118
|
+
* directory yet. The wizard preselects an editor when `present || installed`.
|
|
119
|
+
*/
|
|
120
|
+
installed: boolean;
|
|
111
121
|
/** Project-relative rules-file path (the CLAUDE.md / .cursor/rules/krimto.mdc / etc.). */
|
|
112
122
|
rulesPath: string;
|
|
113
123
|
/** How to wire MCP config for this editor, or `null` when wiring isn't automated yet. */
|
|
114
124
|
mcpWire: McpWireMethod | null;
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
/**
|
|
128
|
+
* v0.2.21: Machine-level installation check. Returns true when this editor's home-dir footprint
|
|
129
|
+
* suggests it's been installed + run on this machine. Used by `detectEditorEnvironments` to
|
|
130
|
+
* populate `EditorEnvironment.installed` so the wizard can preselect editors even when the
|
|
131
|
+
* current project folder has no editor-specific signals yet.
|
|
132
|
+
*
|
|
133
|
+
* The home-dir paths checked are well-known per editor: each is created on first launch.
|
|
134
|
+
* • Cursor — `~/.cursor/`
|
|
135
|
+
* • Claude Code — `~/.claude.json` (user config) or `~/.claude/` (project data)
|
|
136
|
+
* • Codex — `~/.codex/`
|
|
137
|
+
* • Gemini CLI — `~/.gemini/`
|
|
138
|
+
*
|
|
139
|
+
* False positives (folder exists from a previous uninstalled tool) are harmless: the user can
|
|
140
|
+
* untoggle. False negatives (editor installed but never launched) are equally rare in practice.
|
|
141
|
+
*/
|
|
142
|
+
async function isInstalledOnMachine(editor: EditorKind, homeDir: string): Promise<boolean> {
|
|
143
|
+
if (editor === "cursor") return exists(path.join(homeDir, ".cursor"));
|
|
144
|
+
if (editor === "claude-code") {
|
|
145
|
+
return (
|
|
146
|
+
(await exists(path.join(homeDir, ".claude.json"))) ||
|
|
147
|
+
(await exists(path.join(homeDir, ".claude")))
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (editor === "codex") return exists(path.join(homeDir, ".codex"));
|
|
151
|
+
if (editor === "gemini-cli") return exists(path.join(homeDir, ".gemini"));
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
117
155
|
/**
|
|
118
156
|
* Resolve every editor Krimto can reason about into an `EditorEnvironment` triple
|
|
119
157
|
* (editor + rules-file path + mcp-wiring method). `present` reflects detection signals;
|
|
@@ -136,10 +174,18 @@ export async function detectEditorEnvironments(
|
|
|
136
174
|
(await exists(path.join(cwd, ".gemini")));
|
|
137
175
|
const cursorPresent = await exists(path.join(cwd, ".cursor"));
|
|
138
176
|
|
|
177
|
+
// v0.2.21: machine-level installation signals — catch the case where the user is editing in
|
|
178
|
+
// Cursor (or Claude Code) but the project folder hasn't acquired editor-specific files yet.
|
|
179
|
+
const cursorInstalled = await isInstalledOnMachine("cursor", homeDir);
|
|
180
|
+
const claudeInstalled = await isInstalledOnMachine("claude-code", homeDir);
|
|
181
|
+
const codexInstalled = await isInstalledOnMachine("codex", homeDir);
|
|
182
|
+
const geminiInstalled = await isInstalledOnMachine("gemini-cli", homeDir);
|
|
183
|
+
|
|
139
184
|
return [
|
|
140
185
|
{
|
|
141
186
|
editor: "cursor",
|
|
142
187
|
present: cursorPresent,
|
|
188
|
+
installed: cursorInstalled,
|
|
143
189
|
rulesPath: path.join(".cursor", "rules", "krimto.mdc"),
|
|
144
190
|
mcpWire: {
|
|
145
191
|
method: "json",
|
|
@@ -150,6 +196,7 @@ export async function detectEditorEnvironments(
|
|
|
150
196
|
{
|
|
151
197
|
editor: "claude-code",
|
|
152
198
|
present: claudePresent,
|
|
199
|
+
installed: claudeInstalled,
|
|
153
200
|
rulesPath: "CLAUDE.md",
|
|
154
201
|
// `claude mcp add krimto ...` is the supported invocation. Shelling out to the editor's
|
|
155
202
|
// own CLI sidesteps the question of which exact file Claude Code persists MCP config in
|
|
@@ -163,6 +210,7 @@ export async function detectEditorEnvironments(
|
|
|
163
210
|
{
|
|
164
211
|
editor: "gemini-cli",
|
|
165
212
|
present: geminiPresent,
|
|
213
|
+
installed: geminiInstalled,
|
|
166
214
|
rulesPath: "GEMINI.md",
|
|
167
215
|
// Deferred — Gemini CLI's MCP config path needs empirical confirmation before we write
|
|
168
216
|
// to it. v0.2.17 prints a copy-paste snippet instead.
|
|
@@ -171,6 +219,7 @@ export async function detectEditorEnvironments(
|
|
|
171
219
|
{
|
|
172
220
|
editor: "codex",
|
|
173
221
|
present: codexPresent,
|
|
222
|
+
installed: codexInstalled,
|
|
174
223
|
rulesPath: "AGENTS.md",
|
|
175
224
|
// Deferred — Codex's config is TOML (`~/.codex/config.toml`); writing TOML safely needs
|
|
176
225
|
// a parser we haven't added yet. v0.2.17 prints a copy-paste snippet instead.
|
package/src/cli/join.ts
CHANGED
|
@@ -180,11 +180,14 @@ async function askEditors(envs: EditorEnvironment[]): Promise<EditorKind[]> {
|
|
|
180
180
|
value: env.editor,
|
|
181
181
|
name: EDITOR_LABEL[env.editor],
|
|
182
182
|
description: env.present
|
|
183
|
-
? "detected
|
|
184
|
-
: env.
|
|
185
|
-
? "not
|
|
186
|
-
:
|
|
187
|
-
|
|
183
|
+
? "detected in this project"
|
|
184
|
+
: env.installed
|
|
185
|
+
? "installed on this machine (not in this project yet)"
|
|
186
|
+
: env.mcpWire === null
|
|
187
|
+
? "not detected — manual snippet only"
|
|
188
|
+
: "not detected — toggle on if you want anyway",
|
|
189
|
+
// v0.2.21: preselect on either signal — installed-on-machine still counts.
|
|
190
|
+
checked: env.present || env.installed,
|
|
188
191
|
})),
|
|
189
192
|
});
|
|
190
193
|
}
|
package/src/cli/status.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { promisify } from "node:util";
|
|
|
17
17
|
|
|
18
18
|
import { ActivityLog, type ActivityEntry } from "../server/activity";
|
|
19
19
|
import { isProcessAlive, type LockInfo } from "../server/lock";
|
|
20
|
+
import { KRIMTO_VERSION } from "../server/index";
|
|
20
21
|
import {
|
|
21
22
|
detectEditorEnvironments,
|
|
22
23
|
detectExistingSetup,
|
|
@@ -174,12 +175,12 @@ function headerLine(
|
|
|
174
175
|
): string {
|
|
175
176
|
if (status === "ok") {
|
|
176
177
|
if (lock?.alive) {
|
|
177
|
-
return `\n✅ Krimto is working ·
|
|
178
|
+
return `\n✅ Krimto is working · v${KRIMTO_VERSION}\n PID ${lock.info.pid} (${lock.info.mode}), started ${humanAgo(lock.info.started, now)}\n`;
|
|
178
179
|
}
|
|
179
|
-
return `\n✅ Krimto is configured ·
|
|
180
|
+
return `\n✅ Krimto is configured · v${KRIMTO_VERSION}\n No active server right now — it will be launched on demand by your editor.\n`;
|
|
180
181
|
}
|
|
181
182
|
if (status === "warning") {
|
|
182
|
-
return `\n⚠️ Krimto needs attention ·
|
|
183
|
+
return `\n⚠️ Krimto needs attention · v${KRIMTO_VERSION}\n`;
|
|
183
184
|
}
|
|
184
185
|
return `\n🔴 Krimto isn't set up on this machine\n Run: $ npx @krimto-labs/krimto init\n`;
|
|
185
186
|
}
|
package/src/cli/wizard.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type WizardAnswers,
|
|
25
25
|
} from "./init";
|
|
26
26
|
import { runSetupEmbeddings } from "./setupEmbeddings";
|
|
27
|
+
import { KRIMTO_VERSION } from "../server/index";
|
|
27
28
|
|
|
28
29
|
const EDITOR_LABEL: Record<EditorKind, string> = {
|
|
29
30
|
cursor: "Cursor",
|
|
@@ -149,12 +150,17 @@ async function runFreshWizard(
|
|
|
149
150
|
io: WizardIO,
|
|
150
151
|
snapshot: SetupSnapshot | null = null,
|
|
151
152
|
): Promise<ApplyResult | null> {
|
|
152
|
-
io.out(
|
|
153
|
+
io.out(`\nKrimto — Setting up your AI's memory · v${KRIMTO_VERSION}\n\n`);
|
|
153
154
|
const envs = await detectEditorEnvironments(cwd, opts.homeDir);
|
|
154
155
|
printScan(envs, io);
|
|
155
156
|
|
|
156
157
|
const selectedEditors = await askEditors(envs, snapshot);
|
|
157
|
-
|
|
158
|
+
// v0.2.20 — smart default: stdio Krimto can only serve one editor at a time (single-writer
|
|
159
|
+
// lock on the data dir). When the user picks 2+ editors, recommend "Always running" so they
|
|
160
|
+
// can all use Krimto simultaneously over HTTP. Snapshot.runMode wins on reconfigure (respects
|
|
161
|
+
// the user's prior choice). First-run with one editor → "as-needed" (simplest).
|
|
162
|
+
const smartDefault: RunMode = selectedEditors.length >= 2 ? "always-running" : "as-needed";
|
|
163
|
+
const runMode = await askRunMode(snapshot?.runMode ?? smartDefault, selectedEditors.length);
|
|
158
164
|
const whoFor = await askWhoFor();
|
|
159
165
|
if (whoFor === "team") {
|
|
160
166
|
io.out("\nGreat — team mode is set up via `krimto team init` (Phase C). Run that next.\n");
|
|
@@ -188,33 +194,52 @@ async function askEditors(
|
|
|
188
194
|
value: env.editor,
|
|
189
195
|
name: EDITOR_LABEL[env.editor],
|
|
190
196
|
description: env.present
|
|
191
|
-
? "detected
|
|
192
|
-
: env.
|
|
193
|
-
? "not
|
|
194
|
-
:
|
|
197
|
+
? "detected in this project"
|
|
198
|
+
: env.installed
|
|
199
|
+
? "installed on this machine (not in this project yet)"
|
|
200
|
+
: env.mcpWire === null
|
|
201
|
+
? "not detected — manual snippet only"
|
|
202
|
+
: "not detected — toggle on if you want anyway",
|
|
203
|
+
// v0.2.21: preselect on either project-level (present) OR machine-level (installed) signal.
|
|
195
204
|
checked: snapshot
|
|
196
205
|
? snapshot.registeredEditors.includes(env.editor)
|
|
197
|
-
: env.present,
|
|
206
|
+
: env.present || env.installed,
|
|
198
207
|
})),
|
|
199
208
|
});
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
async function askRunMode(
|
|
211
|
+
async function askRunMode(
|
|
212
|
+
defaultMode: RunMode = "as-needed",
|
|
213
|
+
editorCount = 1,
|
|
214
|
+
): Promise<RunMode> {
|
|
215
|
+
// v0.2.20 — when 2+ editors are selected, "as needed" is broken by Krimto's single-writer
|
|
216
|
+
// lock: only the first editor to call wins; the others fail. The choice descriptions reflect
|
|
217
|
+
// this so a user picking "as needed" with multiple editors sees the warning before they commit.
|
|
218
|
+
const multi = editorCount >= 2;
|
|
203
219
|
return select<RunMode>({
|
|
204
220
|
message: "How should Krimto run?",
|
|
205
221
|
default: defaultMode,
|
|
206
222
|
choices: [
|
|
207
223
|
{
|
|
208
224
|
value: "as-needed",
|
|
209
|
-
name: "As needed (recommended)",
|
|
210
|
-
description:
|
|
211
|
-
|
|
225
|
+
name: multi ? "As needed (⚠️ one editor at a time only)" : "As needed (recommended)",
|
|
226
|
+
description: multi
|
|
227
|
+
? `Your editor launches Krimto when it needs it. Simple — but Krimto's single-writer\n` +
|
|
228
|
+
`lock means only one of your ${editorCount} editors can use it at a time. The second one\n` +
|
|
229
|
+
`to call will fail until the first one exits. Pick "Always running" instead if you\n` +
|
|
230
|
+
`want all of them to work simultaneously.`
|
|
231
|
+
: "Your editor launches Krimto when it needs it. Simplest setup —\nno background process to manage. Works for solo use on one machine.",
|
|
212
232
|
},
|
|
213
233
|
{
|
|
214
234
|
value: "always-running",
|
|
215
|
-
name:
|
|
216
|
-
|
|
217
|
-
"
|
|
235
|
+
name: multi
|
|
236
|
+
? "Always running (recommended for multi-editor)"
|
|
237
|
+
: "Always running (background service)",
|
|
238
|
+
description: multi
|
|
239
|
+
? `ONE Krimto runs continuously in the background; ALL ${editorCount} of your editors connect\n` +
|
|
240
|
+
`to it over HTTP. No lock fights — they can all save and recall simultaneously.\n` +
|
|
241
|
+
`Installs launchd / systemd / schtasks on first use.`
|
|
242
|
+
: "Krimto runs continuously, even after you close your terminal.\nAuto-starts when you log in. Best if multiple editors talk to one Krimto\nor you want /ui always available.",
|
|
218
243
|
},
|
|
219
244
|
{
|
|
220
245
|
value: "manual",
|
|
@@ -296,8 +321,14 @@ async function askSearch(
|
|
|
296
321
|
function printScan(envs: EditorEnvironment[], io: WizardIO): void {
|
|
297
322
|
io.out(" Scanning your machine ...\n\n");
|
|
298
323
|
for (const env of envs) {
|
|
299
|
-
|
|
300
|
-
|
|
324
|
+
// v0.2.21: three states — project-level, machine-level only, not found.
|
|
325
|
+
const dot = env.present ? "✓" : env.installed ? "~" : "–";
|
|
326
|
+
const note = env.present
|
|
327
|
+
? "detected (in this project)"
|
|
328
|
+
: env.installed
|
|
329
|
+
? "installed (machine-wide)"
|
|
330
|
+
: "not found";
|
|
331
|
+
io.out(` ${dot} ${EDITOR_LABEL[env.editor].padEnd(14)} ${note}\n`);
|
|
301
332
|
}
|
|
302
333
|
io.out("\n");
|
|
303
334
|
}
|
|
@@ -396,16 +427,24 @@ export interface NonInteractiveOptions extends ApplyOptions {
|
|
|
396
427
|
|
|
397
428
|
/**
|
|
398
429
|
* Skip the prompts entirely and apply with sensible defaults. Used by CI and the `--yes` flag.
|
|
399
|
-
* Defaults: all detected editors, "
|
|
430
|
+
* Defaults: all detected editors, "just-me", keyword search.
|
|
431
|
+
*
|
|
432
|
+
* Run mode is "as-needed" for a single editor, "always-running" when 2+ editors are selected —
|
|
433
|
+
* mirrors the interactive wizard's smart default (v0.2.20). The stdio + lock combination only
|
|
434
|
+
* supports one editor at a time, so multi-editor `--yes` users should land on the HTTP-backed
|
|
435
|
+
* always-running mode by default. Tests that want the old behavior pass `runMode: "as-needed"`
|
|
436
|
+
* explicitly; CI runs that don't want a real service install pass `dryRun: true`.
|
|
400
437
|
*/
|
|
401
438
|
export async function runInitNonInteractive(
|
|
402
439
|
cwd: string,
|
|
403
440
|
opts: NonInteractiveOptions = {},
|
|
404
441
|
): Promise<ApplyResult> {
|
|
405
442
|
const envs = await detectEditorEnvironments(cwd, opts.homeDir);
|
|
406
|
-
|
|
443
|
+
// v0.2.21: count both project-level (`present`) AND machine-level (`installed`) signals so
|
|
444
|
+
// the --yes path matches the interactive wizard's preselect logic.
|
|
445
|
+
const detected = envs.filter((e) => e.present || e.installed).map((e) => e.editor);
|
|
407
446
|
const editors = opts.editors ?? (detected.length > 0 ? detected : envs.map((e) => e.editor));
|
|
408
|
-
const runMode = opts.runMode ?? "as-needed";
|
|
447
|
+
const runMode = opts.runMode ?? (editors.length >= 2 ? "always-running" : "as-needed");
|
|
409
448
|
const search = opts.search ?? "keyword";
|
|
410
449
|
const identity = await defaultIdentity();
|
|
411
450
|
const answers: WizardAnswers = {
|
package/src/server/index.ts
CHANGED
|
@@ -48,7 +48,7 @@ import { type Requester } from "../access/scope";
|
|
|
48
48
|
|
|
49
49
|
export type RequesterResolver = (extra: { authInfo?: AuthInfo }) => Requester;
|
|
50
50
|
|
|
51
|
-
export const KRIMTO_VERSION = "0.2.
|
|
51
|
+
export const KRIMTO_VERSION = "0.2.21";
|
|
52
52
|
|
|
53
53
|
export function resolveDataDir(): string {
|
|
54
54
|
return process.env.KRIMTO_DATA ?? path.join(homedir(), ".krimto");
|