@krimto-labs/krimto 0.2.20 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krimto-labs/krimto",
3
- "version": "0.2.20",
3
+ "version": "0.2.21",
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",
@@ -126,10 +126,12 @@ async function askEditorsList(
126
126
  description: current.includes(env.editor)
127
127
  ? "currently connected"
128
128
  : env.present
129
- ? "detected on this machine"
130
- : env.mcpWire === null
131
- ? "not detected manual snippet only"
132
- : "not detected — toggle on if you want anyway",
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
- /** True when local signals (file/dir presence) indicate this editor is in use here. */
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 on this machine"
184
- : env.mcpWire === null
185
- ? "not detected manual snippet only"
186
- : "not detected — toggle on if you want anyway",
187
- checked: env.present,
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 · v0.2.17\n PID ${lock.info.pid} (${lock.info.mode}), started ${humanAgo(lock.info.started, now)}\n`;
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 · v0.2.17\n No active server right now — it will be launched on demand by your editor.\n`;
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 · v0.2.17\n`;
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,7 +150,7 @@ async function runFreshWizard(
149
150
  io: WizardIO,
150
151
  snapshot: SetupSnapshot | null = null,
151
152
  ): Promise<ApplyResult | null> {
152
- io.out("\nKrimto — Setting up your AI's memory · v0.2.17\n\n");
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
 
@@ -193,13 +194,16 @@ async function askEditors(
193
194
  value: env.editor,
194
195
  name: EDITOR_LABEL[env.editor],
195
196
  description: env.present
196
- ? "detected on this machine"
197
- : env.mcpWire === null
198
- ? "not detected manual snippet only"
199
- : "not detected — toggle on if you want anyway",
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.
200
204
  checked: snapshot
201
205
  ? snapshot.registeredEditors.includes(env.editor)
202
- : env.present,
206
+ : env.present || env.installed,
203
207
  })),
204
208
  });
205
209
  }
@@ -317,8 +321,14 @@ async function askSearch(
317
321
  function printScan(envs: EditorEnvironment[], io: WizardIO): void {
318
322
  io.out(" Scanning your machine ...\n\n");
319
323
  for (const env of envs) {
320
- const dot = env.present ? "✓" : "–";
321
- io.out(` ${dot} ${EDITOR_LABEL[env.editor].padEnd(14)} ${env.present ? "detected" : "not found"}\n`);
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`);
322
332
  }
323
333
  io.out("\n");
324
334
  }
@@ -430,7 +440,9 @@ export async function runInitNonInteractive(
430
440
  opts: NonInteractiveOptions = {},
431
441
  ): Promise<ApplyResult> {
432
442
  const envs = await detectEditorEnvironments(cwd, opts.homeDir);
433
- const detected = envs.filter((e) => e.present).map((e) => e.editor);
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);
434
446
  const editors = opts.editors ?? (detected.length > 0 ? detected : envs.map((e) => e.editor));
435
447
  const runMode = opts.runMode ?? (editors.length >= 2 ? "always-running" : "as-needed");
436
448
  const search = opts.search ?? "keyword";
@@ -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.20";
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");