@krimto-labs/krimto 0.2.21 → 0.2.26
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 +93 -3
- package/package.json +1 -1
- package/src/cli/cliRuntime.ts +1 -0
- package/src/cli/connect.ts +36 -5
- package/src/cli/deleteFact.ts +1 -0
- package/src/cli/help.ts +4 -0
- package/src/cli/init.ts +45 -16
- package/src/cli/inspectRuntime.ts +146 -0
- package/src/cli/reindex.ts +1 -0
- package/src/cli/reset.ts +68 -14
- package/src/cli/service.ts +100 -3
- package/src/cli/setIdentity.ts +232 -0
- package/src/cli/status.ts +12 -25
- package/src/cli/usage.ts +3 -1
- package/src/cli/verifyConnection.ts +21 -5
- package/src/cli/whoami.ts +172 -0
- package/src/cli/wizard.ts +4 -1
- package/src/server/connect.ts +2 -1
- package/src/server/index.ts +20 -1
- package/src/server/lock.ts +11 -1
- package/src/server/tools.ts +40 -1
package/src/cli/wizard.ts
CHANGED
|
@@ -388,7 +388,10 @@ function printApplyResult(res: ApplyResult, io: WizardIO): void {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
io.out(
|
|
391
|
+
io.out(
|
|
392
|
+
"Restart your editor once so it loads the new MCP server (the krimto_*\n" +
|
|
393
|
+
"tools won't appear in chat until you do) and picks up the standing rule.\n",
|
|
394
|
+
);
|
|
392
395
|
}
|
|
393
396
|
|
|
394
397
|
function printRefreshSummary(res: ApplyResult, io: WizardIO): void {
|
package/src/server/connect.ts
CHANGED
|
@@ -74,13 +74,14 @@ export function cursorDeeplink(host: string): string {
|
|
|
74
74
|
return `cursor://anysphere.cursor-deeplink/mcp/install?name=krimto&config=${config}`;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/** The
|
|
77
|
+
/** The six MCP tools Krimto exposes (kept in lockstep with src/server/index.ts registrations). */
|
|
78
78
|
export const MCP_TOOL_NAMES = [
|
|
79
79
|
"krimto_write",
|
|
80
80
|
"krimto_recall",
|
|
81
81
|
"krimto_read",
|
|
82
82
|
"krimto_supersede",
|
|
83
83
|
"krimto_list_scopes",
|
|
84
|
+
"krimto_whoami",
|
|
84
85
|
] as const;
|
|
85
86
|
|
|
86
87
|
/** The transport-level contract for wiring up any MCP client we haven't shipped a verified snippet for. */
|
package/src/server/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
krimtoRead,
|
|
42
42
|
krimtoRecall,
|
|
43
43
|
krimtoSupersede,
|
|
44
|
+
krimtoWhoami,
|
|
44
45
|
krimtoWrite,
|
|
45
46
|
type ToolContext,
|
|
46
47
|
} from "./tools";
|
|
@@ -48,7 +49,7 @@ import { type Requester } from "../access/scope";
|
|
|
48
49
|
|
|
49
50
|
export type RequesterResolver = (extra: { authInfo?: AuthInfo }) => Requester;
|
|
50
51
|
|
|
51
|
-
export const KRIMTO_VERSION = "0.2.
|
|
52
|
+
export const KRIMTO_VERSION = "0.2.26";
|
|
52
53
|
|
|
53
54
|
export function resolveDataDir(): string {
|
|
54
55
|
return process.env.KRIMTO_DATA ?? path.join(homedir(), ".krimto");
|
|
@@ -192,6 +193,24 @@ export function buildServer(ctx: ToolContext, resolveRequester?: RequesterResolv
|
|
|
192
193
|
},
|
|
193
194
|
);
|
|
194
195
|
|
|
196
|
+
server.registerTool(
|
|
197
|
+
"krimto_whoami",
|
|
198
|
+
{
|
|
199
|
+
description:
|
|
200
|
+
"Report the caller's identity plus the scopes they can read and write. Call this before " +
|
|
201
|
+
"claiming to know the user's email or which team scopes exist — Krimto knows; the agent doesn't.",
|
|
202
|
+
inputSchema: {},
|
|
203
|
+
},
|
|
204
|
+
async (_args, extra) => {
|
|
205
|
+
try {
|
|
206
|
+
const requester = resolveRequester ? resolveRequester(extra) : ctx.requester;
|
|
207
|
+
return ok(await krimtoWhoami({ ...ctx, requester }));
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return fail(e);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
);
|
|
213
|
+
|
|
195
214
|
return server;
|
|
196
215
|
}
|
|
197
216
|
|
package/src/server/lock.ts
CHANGED
|
@@ -16,11 +16,19 @@ import { promises as fs } from "node:fs";
|
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
|
|
18
18
|
export type LockMode = "stdio" | "http";
|
|
19
|
+
/**
|
|
20
|
+
* How this Krimto process was launched. "service" = via launchd / systemd / schtasks (the
|
|
21
|
+
* always-running setup); "ad-hoc" = direct invocation (`krimto serve`, editor-spawned stdio,
|
|
22
|
+
* tests). The service installers inject KRIMTO_LAUNCHED_BY=service into the unit env, so this
|
|
23
|
+
* field is just `process.env.KRIMTO_LAUNCHED_BY` at acquire time with a safe default.
|
|
24
|
+
*/
|
|
25
|
+
export type LaunchedBy = "service" | "ad-hoc";
|
|
19
26
|
|
|
20
27
|
export interface LockInfo {
|
|
21
28
|
pid: number;
|
|
22
29
|
started: string;
|
|
23
30
|
mode: LockMode;
|
|
31
|
+
launchedBy: LaunchedBy;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export interface LockHandle {
|
|
@@ -73,6 +81,7 @@ export async function acquireLock(dataDir: string, mode: LockMode): Promise<Lock
|
|
|
73
81
|
pid: existing.pid,
|
|
74
82
|
started: typeof existing.started === "string" ? existing.started : "unknown",
|
|
75
83
|
mode: (existing.mode as LockMode) ?? "stdio",
|
|
84
|
+
launchedBy: existing.launchedBy === "service" ? "service" : "ad-hoc",
|
|
76
85
|
},
|
|
77
86
|
file,
|
|
78
87
|
);
|
|
@@ -83,7 +92,8 @@ export async function acquireLock(dataDir: string, mode: LockMode): Promise<Lock
|
|
|
83
92
|
// File missing / unreadable / malformed — treat as no lock and continue.
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
const
|
|
95
|
+
const launchedBy: LaunchedBy = process.env.KRIMTO_LAUNCHED_BY === "service" ? "service" : "ad-hoc";
|
|
96
|
+
const info: LockInfo = { pid: process.pid, started: new Date().toISOString(), mode, launchedBy };
|
|
87
97
|
await fs.writeFile(file, JSON.stringify(info, null, 2), "utf8");
|
|
88
98
|
|
|
89
99
|
return {
|
package/src/server/tools.ts
CHANGED
|
@@ -109,6 +109,20 @@ export interface SupersedeResult {
|
|
|
109
109
|
|
|
110
110
|
export interface ListScopesResult {
|
|
111
111
|
scopes: { path: string; fact_count: number; last_updated: string | null }[];
|
|
112
|
+
/** Present only when `scopes` is empty — tells the calling agent how to make a scope appear. */
|
|
113
|
+
hint?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* v0.2.25 — Gap 3. The smoke-6 transcript showed an agent in chat inventing a wrong identity
|
|
118
|
+
* (`lpd.themes@gmail.com` instead of `lpdthemes@gmail.com`) because it had no MCP-side way to
|
|
119
|
+
* ask "who am I writing as?". `krimto_whoami` returns the resolved identity plus the scopes
|
|
120
|
+
* the caller can read and write, so the agent never has to guess.
|
|
121
|
+
*/
|
|
122
|
+
export interface WhoamiResult {
|
|
123
|
+
identity: string;
|
|
124
|
+
readable_scopes: string[];
|
|
125
|
+
writable_scopes: string[];
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
function clock(ctx: ToolContext): Date {
|
|
@@ -329,17 +343,42 @@ export async function krimtoSupersede(
|
|
|
329
343
|
});
|
|
330
344
|
}
|
|
331
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Return the caller's identity plus the scopes they can read and write. The agent in chat uses
|
|
348
|
+
* this to avoid hallucinating identity (Gap 3 from the smoke-6 transcript audit).
|
|
349
|
+
*/
|
|
350
|
+
export async function krimtoWhoami(ctx: ToolContext): Promise<WhoamiResult> {
|
|
351
|
+
const readable = readableScopesFor(ctx);
|
|
352
|
+
const writable = writableScopesFor(ctx);
|
|
353
|
+
if (ctx.activity) await ctx.activity.record("krimto_whoami", ctx.requester.identity, ctx.requester.identity);
|
|
354
|
+
return {
|
|
355
|
+
identity: ctx.requester.identity,
|
|
356
|
+
readable_scopes: readable,
|
|
357
|
+
writable_scopes: writable,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
332
361
|
/** Discover the scopes that exist and what they contain. */
|
|
333
362
|
export async function krimtoListScopes(ctx: ToolContext): Promise<ListScopesResult> {
|
|
334
363
|
const scopes = ctx.index.listScopes(readableScopesFor(ctx));
|
|
335
364
|
if (ctx.activity) await ctx.activity.record("krimto_list_scopes", ctx.requester.identity, `${scopes.length} scope(s)`);
|
|
336
|
-
|
|
365
|
+
const result: ListScopesResult = {
|
|
337
366
|
scopes: scopes.map((s) => ({
|
|
338
367
|
path: s.path,
|
|
339
368
|
fact_count: s.factCount,
|
|
340
369
|
last_updated: s.lastUpdated,
|
|
341
370
|
})),
|
|
342
371
|
};
|
|
372
|
+
// v0.2.24 — empty result is the #1 first-impression confuser ("Krimto must be broken").
|
|
373
|
+
// Surface the next step in the response itself, so an agent in chat can relay it verbatim
|
|
374
|
+
// instead of inventing a hallucinated explanation about "scopes aren't configured".
|
|
375
|
+
if (result.scopes.length === 0) {
|
|
376
|
+
result.hint =
|
|
377
|
+
`No scopes exist yet for ${ctx.requester.identity}. Scopes are created on the first ` +
|
|
378
|
+
`write — try krimto_write with a small fact (e.g. "we use pnpm in this repo") and ` +
|
|
379
|
+
`your user/<email> scope will appear here.`;
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
343
382
|
}
|
|
344
383
|
|
|
345
384
|
/** Build a Requester from a validated bearer token's AuthInfo (its `extra` carries identity+teams). */
|