@krimto-labs/krimto 0.2.21 → 0.2.22
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/bin/krimto.mjs +43 -1
- package/package.json +1 -1
- package/src/cli/help.ts +4 -0
- package/src/cli/setIdentity.ts +232 -0
- package/src/cli/whoami.ts +172 -0
- package/src/server/index.ts +1 -1
package/bin/krimto.mjs
CHANGED
|
@@ -13,7 +13,9 @@ try {
|
|
|
13
13
|
const cmd =
|
|
14
14
|
rawCmd === "team" && typeof process.argv[3] === "string"
|
|
15
15
|
? `team ${process.argv[3]}`
|
|
16
|
-
: rawCmd
|
|
16
|
+
: rawCmd === "set" && typeof process.argv[3] === "string"
|
|
17
|
+
? `set ${process.argv[3]}`
|
|
18
|
+
: rawCmd;
|
|
17
19
|
|
|
18
20
|
// Guard: `krimto team` alone (or with an unknown subverb) shouldn't fall through to the stdio
|
|
19
21
|
// MCP server. Print usage and exit instead.
|
|
@@ -27,6 +29,16 @@ try {
|
|
|
27
29
|
process.exit(2);
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
// Same guard for `krimto set <subverb>`.
|
|
33
|
+
const knownSetCmds = ["set identity"];
|
|
34
|
+
if (rawCmd === "set" && !knownSetCmds.includes(cmd)) {
|
|
35
|
+
process.stderr.write(
|
|
36
|
+
"Usage: krimto set <identity> <value>\n" +
|
|
37
|
+
" identity <email> Change the identity used for new fact writes\n",
|
|
38
|
+
);
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
31
43
|
// `krimto --help` — surface every subcommand so a user who didn't read the README can still
|
|
32
44
|
// discover them. Version is read from the server module so it never drifts from KRIMTO_VERSION.
|
|
@@ -351,6 +363,36 @@ try {
|
|
|
351
363
|
// doesn't have to chase the README. Honors KRIMTO_IDENTITY when set.
|
|
352
364
|
const { formatConnect } = await tsImport("../src/cli/connect.ts", import.meta.url);
|
|
353
365
|
process.stdout.write(formatConnect({ identity: process.env.KRIMTO_IDENTITY }));
|
|
366
|
+
} else if (cmd === "whoami") {
|
|
367
|
+
// `krimto whoami` — show the active KRIMTO_IDENTITY and every place it's currently set
|
|
368
|
+
// (each editor's MCP config + the always-running service unit). Surfaces drift between
|
|
369
|
+
// sources so users notice before notes start splitting across two scopes.
|
|
370
|
+
const { runWhoami } = await tsImport("../src/cli/whoami.ts", import.meta.url);
|
|
371
|
+
const result = await runWhoami();
|
|
372
|
+
process.stdout.write(result.message);
|
|
373
|
+
if (result.mismatch) process.exitCode = 1;
|
|
374
|
+
} else if (cmd === "set identity") {
|
|
375
|
+
// `krimto set identity <email>` — update KRIMTO_IDENTITY across every registered editor
|
|
376
|
+
// and the always-running service. Preserves other env keys (KRIMTO_EMBED_*). Existing
|
|
377
|
+
// notes do NOT move — that's an intentional separate step.
|
|
378
|
+
const newIdentity = process.argv[4];
|
|
379
|
+
const flags = process.argv.slice(5);
|
|
380
|
+
const yes = flags.includes("--yes");
|
|
381
|
+
if (!newIdentity) {
|
|
382
|
+
process.stderr.write(
|
|
383
|
+
"Usage: krimto set identity <email> [--yes]\n e.g. krimto set identity alice@acme.com\n",
|
|
384
|
+
);
|
|
385
|
+
process.exit(2);
|
|
386
|
+
}
|
|
387
|
+
const { runSetIdentity } = await tsImport("../src/cli/setIdentity.ts", import.meta.url);
|
|
388
|
+
const { resolveDataDir } = await tsImport("../src/server/index.ts", import.meta.url);
|
|
389
|
+
const result = await runSetIdentity({
|
|
390
|
+
identity: newIdentity,
|
|
391
|
+
dataDir: resolveDataDir(),
|
|
392
|
+
yes,
|
|
393
|
+
});
|
|
394
|
+
process.stdout.write(result.message);
|
|
395
|
+
if (result.status === "error") process.exitCode = 1;
|
|
354
396
|
} else {
|
|
355
397
|
const mod = await tsImport("../src/server/index.ts", import.meta.url);
|
|
356
398
|
await mod.main();
|
package/package.json
CHANGED
package/src/cli/help.ts
CHANGED
|
@@ -37,6 +37,10 @@ export function formatHelp(version: string): string {
|
|
|
37
37
|
" rm <id> Delete a fact (file + index + git deletion commit)",
|
|
38
38
|
" reindex Rebuild index.db from markdown (fixes manual-delete orphans)",
|
|
39
39
|
"",
|
|
40
|
+
" Identity:",
|
|
41
|
+
" whoami Show which email Krimto is using (and where it's set)",
|
|
42
|
+
" set identity <e> Change the identity used for new fact writes",
|
|
43
|
+
"",
|
|
40
44
|
" Diagnose:",
|
|
41
45
|
" verify-connection Is my agent actually calling Krimto? (live status + last 5 calls)",
|
|
42
46
|
" setup-remote <url> Wire data dir to a git remote, verify the push works",
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// `krimto set identity <email>` — change KRIMTO_IDENTITY everywhere the wizard wrote it.
|
|
2
|
+
//
|
|
3
|
+
// Surfaces touched (in order):
|
|
4
|
+
// 1. Each registered editor's MCP config — for JSON-method editors, mutate
|
|
5
|
+
// `env.KRIMTO_IDENTITY` in place (other env keys like KRIMTO_EMBED_* are preserved).
|
|
6
|
+
// For CLI-method editors (Claude Code), rebuild a fresh stdio entry via writeMcpConfig
|
|
7
|
+
// (extra env keys are lost — known limitation noted below).
|
|
8
|
+
// 2. The always-running service (launchd/systemd/schtasks) — uninstall + reinstall with
|
|
9
|
+
// the new env. Same dataDir + port as before, mirroring serviceCmd.applyService.
|
|
10
|
+
//
|
|
11
|
+
// HTTP-transport MCP entries don't carry identity (it lives in the service env); they are
|
|
12
|
+
// left untouched.
|
|
13
|
+
//
|
|
14
|
+
// Existing notes do NOT migrate. The folder for the old identity stays under
|
|
15
|
+
// `~/.krimto/user/<old-email>/` — moving them is intentionally a separate step (would need
|
|
16
|
+
// a git rename + reindex; out of scope for this command).
|
|
17
|
+
|
|
18
|
+
import { confirm } from "@inquirer/prompts";
|
|
19
|
+
import { promises as fs } from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
|
|
22
|
+
import { stdioMcpEntry, type KrimtoMcpEntry } from "../server/connect";
|
|
23
|
+
import {
|
|
24
|
+
detectEditorEnvironments,
|
|
25
|
+
detectExistingSetup,
|
|
26
|
+
type EditorEnvironment,
|
|
27
|
+
type EditorKind,
|
|
28
|
+
} from "./init";
|
|
29
|
+
import { writeMcpConfig } from "./mcpConfig";
|
|
30
|
+
import {
|
|
31
|
+
detectPlatform,
|
|
32
|
+
installService,
|
|
33
|
+
isServiceInstalled,
|
|
34
|
+
uninstallService,
|
|
35
|
+
} from "./service";
|
|
36
|
+
import { defaultIO, isExitPrompt, type WizardIO } from "./promptHelpers";
|
|
37
|
+
import { runWhoami } from "./whoami";
|
|
38
|
+
|
|
39
|
+
const EDITOR_LABEL: Record<EditorKind, string> = {
|
|
40
|
+
cursor: "Cursor",
|
|
41
|
+
"claude-code": "Claude Code",
|
|
42
|
+
codex: "Codex",
|
|
43
|
+
"gemini-cli": "Gemini CLI",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Same permissive shape used by defaultIdentity() — accepts anything that looks like name@host.
|
|
47
|
+
const EMAIL_REGEX = /^[^@\s]+@[^@\s]+$/;
|
|
48
|
+
|
|
49
|
+
export interface SetIdentityOptions {
|
|
50
|
+
identity: string;
|
|
51
|
+
io?: WizardIO;
|
|
52
|
+
cwd?: string;
|
|
53
|
+
homeDir?: string;
|
|
54
|
+
dataDir?: string;
|
|
55
|
+
/** Skip the confirmation prompt (CI / scripted runs). */
|
|
56
|
+
yes?: boolean;
|
|
57
|
+
/** Forwarded to install/uninstallService for tests. */
|
|
58
|
+
dryRun?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SetIdentityResult {
|
|
62
|
+
status: "ok" | "no-change" | "error";
|
|
63
|
+
message: string;
|
|
64
|
+
updatedEditors: EditorKind[];
|
|
65
|
+
serviceUpdated: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runSetIdentity(opts: SetIdentityOptions): Promise<SetIdentityResult> {
|
|
69
|
+
const io = opts.io ?? defaultIO;
|
|
70
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
71
|
+
|
|
72
|
+
if (!EMAIL_REGEX.test(opts.identity)) {
|
|
73
|
+
return {
|
|
74
|
+
status: "error",
|
|
75
|
+
message:
|
|
76
|
+
`\n❌ Not a valid email: "${opts.identity}"\n` +
|
|
77
|
+
` Expected something like name@example.com\n\n`,
|
|
78
|
+
updatedEditors: [],
|
|
79
|
+
serviceUpdated: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const current = await runWhoami({ cwd, homeDir: opts.homeDir });
|
|
84
|
+
const snapshot = await detectExistingSetup(cwd, opts.homeDir);
|
|
85
|
+
|
|
86
|
+
if (!snapshot.configured) {
|
|
87
|
+
return {
|
|
88
|
+
status: "error",
|
|
89
|
+
message: "\n❌ Krimto isn't set up here yet. Run `krimto init` first.\n\n",
|
|
90
|
+
updatedEditors: [],
|
|
91
|
+
serviceUpdated: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (current.activeIdentity === opts.identity && !current.mismatch) {
|
|
96
|
+
return {
|
|
97
|
+
status: "no-change",
|
|
98
|
+
message: `\nNo change — identity is already ${opts.identity}.\n\n`,
|
|
99
|
+
updatedEditors: [],
|
|
100
|
+
serviceUpdated: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!opts.yes) {
|
|
105
|
+
io.out("\nKrimto — Set identity\n\n");
|
|
106
|
+
io.out(` Current identity: ${current.activeIdentity}\n`);
|
|
107
|
+
io.out(` New identity: ${opts.identity}\n\n`);
|
|
108
|
+
io.out(" Will update:\n");
|
|
109
|
+
for (const e of snapshot.registeredEditors) {
|
|
110
|
+
io.out(` • ${EDITOR_LABEL[e]} MCP config\n`);
|
|
111
|
+
}
|
|
112
|
+
if (snapshot.runMode === "always-running") {
|
|
113
|
+
io.out(" • Background service (launchd/systemd/schtasks)\n");
|
|
114
|
+
}
|
|
115
|
+
io.out("\n ⚠️ Existing notes will NOT move — they stay under\n");
|
|
116
|
+
io.out(` ~/.krimto/user/${current.activeIdentity}/\n`);
|
|
117
|
+
io.out(` New facts will go to ~/.krimto/user/${opts.identity}/\n\n`);
|
|
118
|
+
try {
|
|
119
|
+
const ok = await confirm({ message: "Apply this change?", default: true });
|
|
120
|
+
if (!ok) {
|
|
121
|
+
return {
|
|
122
|
+
status: "no-change",
|
|
123
|
+
message: "\nAborted. No changes were made.\n\n",
|
|
124
|
+
updatedEditors: [],
|
|
125
|
+
serviceUpdated: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
if (isExitPrompt(e)) {
|
|
130
|
+
return {
|
|
131
|
+
status: "no-change",
|
|
132
|
+
message: "\nAborted. No changes were made.\n\n",
|
|
133
|
+
updatedEditors: [],
|
|
134
|
+
serviceUpdated: false,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
throw e;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const envs = await detectEditorEnvironments(cwd, opts.homeDir);
|
|
142
|
+
const updatedEditors: EditorKind[] = [];
|
|
143
|
+
for (const env of envs) {
|
|
144
|
+
if (!snapshot.registeredEditors.includes(env.editor)) continue;
|
|
145
|
+
const ok = await updateEditorIdentity(env, opts.identity);
|
|
146
|
+
if (ok) updatedEditors.push(env.editor);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let serviceUpdated = false;
|
|
150
|
+
const platform = detectPlatform();
|
|
151
|
+
const service = await isServiceInstalled(platform, opts.homeDir);
|
|
152
|
+
if (service.installed) {
|
|
153
|
+
const dataDir = opts.dataDir ?? path.join(opts.homeDir ?? "", ".krimto");
|
|
154
|
+
await uninstallService({ platform, homeDir: opts.homeDir, dryRun: opts.dryRun });
|
|
155
|
+
await installService(
|
|
156
|
+
{
|
|
157
|
+
binPath: process.execPath,
|
|
158
|
+
args: [process.argv[1] ?? "krimto", "serve"],
|
|
159
|
+
env: { KRIMTO_IDENTITY: opts.identity, KRIMTO_DATA: dataDir, KRIMTO_HTTP_PORT: "8080" },
|
|
160
|
+
homeDir: opts.homeDir,
|
|
161
|
+
},
|
|
162
|
+
{ dryRun: opts.dryRun, platform },
|
|
163
|
+
);
|
|
164
|
+
serviceUpdated = true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lines: string[] = ["", `✅ Identity changed → ${opts.identity}`, ""];
|
|
168
|
+
for (const e of updatedEditors) {
|
|
169
|
+
lines.push(` • Updated ${EDITOR_LABEL[e]} MCP config`);
|
|
170
|
+
}
|
|
171
|
+
if (serviceUpdated) lines.push(" • Restarted background service");
|
|
172
|
+
if (updatedEditors.length === 0 && !serviceUpdated) {
|
|
173
|
+
lines.push(" (Nothing to update — no editors registered + no service installed)");
|
|
174
|
+
}
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push("Restart your editor(s) so they pick up the new identity.");
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push(`New facts → ~/.krimto/user/${opts.identity}/`);
|
|
179
|
+
lines.push(`Old facts stay at ~/.krimto/user/${current.activeIdentity}/`);
|
|
180
|
+
lines.push("");
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
status: "ok",
|
|
184
|
+
message: lines.join("\n"),
|
|
185
|
+
updatedEditors,
|
|
186
|
+
serviceUpdated,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Update KRIMTO_IDENTITY in this editor's MCP entry.
|
|
192
|
+
* • JSON method: surgical mutation preserves any other env keys (KRIMTO_EMBED_*).
|
|
193
|
+
* • CLI method (Claude Code): rebuild + re-add via writeMcpConfig. Extra env keys are lost.
|
|
194
|
+
* • HTTP entry (url-based): no-op — identity lives in the service env.
|
|
195
|
+
* • mcpWire === null: no automated wiring; skip silently.
|
|
196
|
+
*/
|
|
197
|
+
async function updateEditorIdentity(env: EditorEnvironment, identity: string): Promise<boolean> {
|
|
198
|
+
if (env.mcpWire === null) return false;
|
|
199
|
+
if (env.mcpWire.method === "json") {
|
|
200
|
+
return updateIdentityInJson(env.mcpWire.path, env.mcpWire.key, identity);
|
|
201
|
+
}
|
|
202
|
+
const entry: KrimtoMcpEntry = { transport: "stdio", ...stdioMcpEntry({ identity }) };
|
|
203
|
+
await writeMcpConfig(env, entry);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function updateIdentityInJson(
|
|
208
|
+
filePath: string,
|
|
209
|
+
key: string,
|
|
210
|
+
identity: string,
|
|
211
|
+
): Promise<boolean> {
|
|
212
|
+
let text: string;
|
|
213
|
+
try {
|
|
214
|
+
text = await fs.readFile(filePath, "utf8");
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
let parsed: Record<string, unknown>;
|
|
219
|
+
try {
|
|
220
|
+
parsed = JSON.parse(text) as Record<string, unknown>;
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const servers = parsed[key] as Record<string, unknown> | undefined;
|
|
225
|
+
if (!servers || !("krimto" in servers)) return false;
|
|
226
|
+
const krimto = servers.krimto as { env?: Record<string, string>; url?: string };
|
|
227
|
+
if (krimto.url) return false;
|
|
228
|
+
const nextEnv = { ...(krimto.env ?? {}), KRIMTO_IDENTITY: identity };
|
|
229
|
+
(servers.krimto as { env: Record<string, string> }).env = nextEnv;
|
|
230
|
+
await fs.writeFile(filePath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// `krimto whoami` — show the active KRIMTO_IDENTITY and every place it's set.
|
|
2
|
+
//
|
|
3
|
+
// Motivation: the v0.2.21 "data-location surprise" bug class — users end up with two scopes
|
|
4
|
+
// (`user/lpdthemes@gmail.com` and `user/user@localhost`) because different surfaces saw
|
|
5
|
+
// different identities and they only noticed weeks later. `whoami` makes drift visible
|
|
6
|
+
// before it leads to split notes.
|
|
7
|
+
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defaultIdentity,
|
|
12
|
+
detectEditorEnvironments,
|
|
13
|
+
type EditorEnvironment,
|
|
14
|
+
type EditorKind,
|
|
15
|
+
} from "./init";
|
|
16
|
+
import { detectPlatform, isServiceInstalled, type ServicePlatform } from "./service";
|
|
17
|
+
|
|
18
|
+
const EDITOR_LABEL: Record<EditorKind, string> = {
|
|
19
|
+
cursor: "Cursor",
|
|
20
|
+
"claude-code": "Claude Code",
|
|
21
|
+
codex: "Codex",
|
|
22
|
+
"gemini-cli": "Gemini CLI",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface WhoamiOptions {
|
|
26
|
+
cwd?: string;
|
|
27
|
+
homeDir?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface IdentitySource {
|
|
31
|
+
label: string;
|
|
32
|
+
/** The literal value found; `null` when nothing readable, `"(http)"` for HTTP entries with no inline identity. */
|
|
33
|
+
identity: string | null;
|
|
34
|
+
detail?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WhoamiResult {
|
|
38
|
+
/** The identity Krimto would use for a write right now (best inference). */
|
|
39
|
+
activeIdentity: string;
|
|
40
|
+
sources: IdentitySource[];
|
|
41
|
+
/** True when registered surfaces disagree on identity. */
|
|
42
|
+
mismatch: boolean;
|
|
43
|
+
message: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runWhoami(opts: WhoamiOptions = {}): Promise<WhoamiResult> {
|
|
47
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
48
|
+
const homeDir = opts.homeDir;
|
|
49
|
+
const envs = await detectEditorEnvironments(cwd, homeDir);
|
|
50
|
+
|
|
51
|
+
const sources: IdentitySource[] = [];
|
|
52
|
+
const distinctIdentities = new Set<string>();
|
|
53
|
+
|
|
54
|
+
for (const env of envs) {
|
|
55
|
+
const found = await readIdentityFromEditor(env);
|
|
56
|
+
if (found === null) continue;
|
|
57
|
+
sources.push(found);
|
|
58
|
+
if (found.identity && found.identity !== "(http)") distinctIdentities.add(found.identity);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const platform = detectPlatform();
|
|
62
|
+
const service = await isServiceInstalled(platform, homeDir);
|
|
63
|
+
if (service.installed && service.unitPath) {
|
|
64
|
+
const id = await readIdentityFromServiceUnit(service.unitPath, platform);
|
|
65
|
+
if (id) {
|
|
66
|
+
sources.push({ label: "Background service", identity: id, detail: service.unitPath });
|
|
67
|
+
distinctIdentities.add(id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const envOverride = process.env.KRIMTO_IDENTITY ?? null;
|
|
72
|
+
const gitDefault = await defaultIdentity();
|
|
73
|
+
|
|
74
|
+
// Active identity = first concrete source we found; fall back through env → git → server default.
|
|
75
|
+
const firstConcrete = sources.find((s) => s.identity && s.identity !== "(http)")?.identity;
|
|
76
|
+
const activeIdentity = firstConcrete ?? envOverride ?? gitDefault;
|
|
77
|
+
const mismatch = distinctIdentities.size > 1;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
activeIdentity,
|
|
81
|
+
sources,
|
|
82
|
+
mismatch,
|
|
83
|
+
message: formatWhoami({ activeIdentity, sources, mismatch, envOverride, gitDefault }),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function readIdentityFromEditor(env: EditorEnvironment): Promise<IdentitySource | null> {
|
|
88
|
+
if (env.mcpWire === null || env.mcpWire.method !== "json") return null;
|
|
89
|
+
let text: string;
|
|
90
|
+
try {
|
|
91
|
+
text = await fs.readFile(env.mcpWire.path, "utf8");
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let parsed: Record<string, unknown>;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(text) as Record<string, unknown>;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const servers = parsed[env.mcpWire.key] as Record<string, unknown> | undefined;
|
|
102
|
+
if (!servers || !("krimto" in servers)) return null;
|
|
103
|
+
const krimto = servers.krimto as { env?: Record<string, string>; url?: string };
|
|
104
|
+
const label = `${EDITOR_LABEL[env.editor]} MCP config`;
|
|
105
|
+
if (krimto.env?.KRIMTO_IDENTITY) {
|
|
106
|
+
return { label, identity: krimto.env.KRIMTO_IDENTITY, detail: env.mcpWire.path };
|
|
107
|
+
}
|
|
108
|
+
if (krimto.url) {
|
|
109
|
+
return { label, identity: "(http)", detail: env.mcpWire.path };
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function readIdentityFromServiceUnit(
|
|
115
|
+
unitPath: string,
|
|
116
|
+
platform: ServicePlatform,
|
|
117
|
+
): Promise<string | null> {
|
|
118
|
+
let text: string;
|
|
119
|
+
try {
|
|
120
|
+
text = await fs.readFile(unitPath, "utf8");
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (platform === "darwin") {
|
|
125
|
+
// plist XML pattern written by installService — KRIMTO_IDENTITY appears as a <key>/<string> pair.
|
|
126
|
+
const m = text.match(/<key>KRIMTO_IDENTITY<\/key>\s*<string>([^<]+)<\/string>/);
|
|
127
|
+
return m && m[1] ? m[1] : null;
|
|
128
|
+
}
|
|
129
|
+
if (platform === "linux") {
|
|
130
|
+
const m = text.match(/KRIMTO_IDENTITY=([^"\s\n]+)/);
|
|
131
|
+
return m && m[1] ? m[1] : null;
|
|
132
|
+
}
|
|
133
|
+
// Windows: schtasks doesn't store env in a readable file we own. Skip.
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function formatWhoami(opts: {
|
|
138
|
+
activeIdentity: string;
|
|
139
|
+
sources: IdentitySource[];
|
|
140
|
+
mismatch: boolean;
|
|
141
|
+
envOverride: string | null;
|
|
142
|
+
gitDefault: string;
|
|
143
|
+
}): string {
|
|
144
|
+
const lines: string[] = ["", "Krimto — Identity", ""];
|
|
145
|
+
lines.push(` Active identity: ${opts.activeIdentity}`, "");
|
|
146
|
+
lines.push(" Where it's set:");
|
|
147
|
+
if (opts.sources.length === 0) {
|
|
148
|
+
lines.push(" (no editors registered, no service installed — run `krimto init` first)");
|
|
149
|
+
} else {
|
|
150
|
+
for (const s of opts.sources) {
|
|
151
|
+
const display = s.identity === "(http)" ? "(uses service identity)" : (s.identity ?? "(unset)");
|
|
152
|
+
const marker = s.identity === "(http)" || s.identity === opts.activeIdentity ? "✓" : "⚠";
|
|
153
|
+
lines.push(` ${marker} ${s.label.padEnd(28)} ${display}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push(" Fallback chain (used when no source above sets it):");
|
|
158
|
+
lines.push(` KRIMTO_IDENTITY env ${opts.envOverride ?? "(unset)"}`);
|
|
159
|
+
lines.push(` git config user.email ${opts.gitDefault}`);
|
|
160
|
+
lines.push(" Server default user@localhost");
|
|
161
|
+
lines.push("");
|
|
162
|
+
if (opts.mismatch) {
|
|
163
|
+
lines.push(" ⚠️ Mismatch — different sources disagree on identity.");
|
|
164
|
+
lines.push(" Run `krimto set identity <email>` to sync them.");
|
|
165
|
+
} else if (opts.sources.length > 0) {
|
|
166
|
+
lines.push(" ✅ All sources agree.");
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("To change it: krimto set identity <email>");
|
|
170
|
+
lines.push("");
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
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.22";
|
|
52
52
|
|
|
53
53
|
export function resolveDataDir(): string {
|
|
54
54
|
return process.env.KRIMTO_DATA ?? path.join(homedir(), ".krimto");
|