driftguard-mcp 0.1.5 → 0.1.7

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.
Files changed (3) hide show
  1. package/README.md +19 -5
  2. package/dist/bin.js +166 -153
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,7 +24,8 @@ driftguard-mcp setup
24
24
  {
25
25
  "mcpServers": {
26
26
  "driftguard": {
27
- "command": "driftguard-mcp"
27
+ "command": "driftguard-mcp",
28
+ "env": { "DRIFTCLI_ADAPTER": "claude" }
28
29
  }
29
30
  }
30
31
  }
@@ -36,7 +37,8 @@ driftguard-mcp setup
36
37
  {
37
38
  "mcpServers": {
38
39
  "driftguard": {
39
- "command": "driftguard-mcp"
40
+ "command": "driftguard-mcp",
41
+ "env": { "DRIFTCLI_ADAPTER": "gemini" }
40
42
  }
41
43
  }
42
44
  }
@@ -48,7 +50,8 @@ driftguard-mcp setup
48
50
  {
49
51
  "mcpServers": {
50
52
  "driftguard": {
51
- "command": "driftguard-mcp"
53
+ "command": "driftguard-mcp",
54
+ "env": { "DRIFTCLI_ADAPTER": "codex" }
52
55
  }
53
56
  }
54
57
  }
@@ -60,13 +63,16 @@ driftguard-mcp setup
60
63
  {
61
64
  "mcpServers": {
62
65
  "driftguard": {
63
- "command": "driftguard-mcp"
66
+ "command": "driftguard-mcp",
67
+ "env": { "DRIFTCLI_ADAPTER": "claude" }
64
68
  }
65
69
  }
66
70
  }
67
71
  ```
68
72
 
69
- > Note: Cursor drift is calculated from any active Claude Code, Gemini CLI, or Codex CLI sessions on your machine — not from Cursor's own conversation history.
73
+ > Note: Cursor drift is calculated from Claude Code sessions on your machine — not from Cursor's own conversation history.
74
+
75
+ > **`DRIFTCLI_ADAPTER`** tells driftguard-mcp which CLI's sessions to read. Without it, the server falls back to whichever session file was modified most recently, which may be from a different CLI. `driftguard-mcp setup` sets this automatically.
70
76
 
71
77
  </details>
72
78
 
@@ -169,6 +175,14 @@ Both are plain JSON. All fields are optional.
169
175
  | `storage.directory` | `~/.driftcli/history` | Override snapshot storage path |
170
176
  | `sessionResolution.cacheTtlMs` | `5000` | How long to cache the resolved session file (ms) |
171
177
 
178
+ ### Environment variables
179
+
180
+ | Variable | Description |
181
+ |----------|-------------|
182
+ | `DRIFTCLI_ADAPTER` | Pin session lookup to a specific CLI: `claude`, `gemini`, or `codex`. Set automatically by `driftguard-mcp setup`. |
183
+ | `DRIFTCLI_SESSION_ID` | Force a specific session UUID (Claude Code only). |
184
+ | `DRIFTCLI_HOME` | Override the home directory used for session file discovery. |
185
+
172
186
  ---
173
187
 
174
188
  ## CLI watcher
package/dist/bin.js CHANGED
@@ -235,39 +235,65 @@ var init_gemini_adapter = __esm({
235
235
  this.name = "gemini";
236
236
  }
237
237
  canParse(filePath) {
238
- return filePath.includes(".gemini") && filePath.endsWith(".jsonl");
238
+ return filePath.includes(".gemini") && filePath.endsWith(".json");
239
239
  }
240
240
  parse(filePath) {
241
241
  const raw = fs2.readFileSync(filePath, "utf-8");
242
- const lines = raw.split("\n").filter((l) => l.trim());
242
+ let session;
243
+ try {
244
+ session = JSON.parse(raw);
245
+ } catch {
246
+ return [];
247
+ }
248
+ if (!Array.isArray(session.messages)) return [];
243
249
  const messages = [];
244
250
  let index = 0;
245
- for (const line of lines) {
246
- try {
247
- const entry = JSON.parse(line);
248
- if (entry.role !== "user" && entry.role !== "model") continue;
249
- const parts = entry.parts ?? [];
250
- const text = parts.map((p) => p.text ?? "").join("\n").trim();
251
- if (!text) continue;
252
- let toolTokens = 0;
253
- for (const part of parts) {
254
- if (part.functionCall?.args) {
255
- toolTokens += Math.round(JSON.stringify(part.functionCall.args).length / 4);
256
- }
257
- if (part.functionResponse?.response) {
258
- toolTokens += Math.round(JSON.stringify(part.functionResponse.response).length / 4);
251
+ let pendingToolTokens = 0;
252
+ let pendingInputTokens;
253
+ for (const msg of session.messages) {
254
+ if (msg.type !== "user" && msg.type !== "gemini") continue;
255
+ let content;
256
+ if (typeof msg.content === "string") {
257
+ content = msg.content.trim();
258
+ } else if (Array.isArray(msg.content)) {
259
+ content = msg.content.map((p) => p.text ?? "").join("\n").trim();
260
+ } else {
261
+ continue;
262
+ }
263
+ if (!content && msg.type === "gemini") {
264
+ pendingToolTokens += msg.tokens?.tool ?? 0;
265
+ pendingToolTokens += msg.tokens?.thoughts ?? 0;
266
+ if ((msg.tokens?.tool ?? 0) === 0 && msg.toolCalls?.length) {
267
+ for (const tc of msg.toolCalls) {
268
+ if (tc.args) pendingToolTokens += Math.round(JSON.stringify(tc.args).length / 4);
269
+ if (tc.result) pendingToolTokens += Math.round(JSON.stringify(tc.result).length / 4);
259
270
  }
260
271
  }
261
- const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
262
- messages.push({
263
- id: `gemini-${index++}`,
264
- role: entry.role === "model" ? "assistant" : "user",
265
- content: text,
266
- timestamp: isFinite(ts) ? ts : Date.now(),
267
- ...toolTokens > 0 ? { toolTokens } : {}
268
- });
269
- } catch {
272
+ if (msg.tokens?.input) pendingInputTokens = msg.tokens.input;
273
+ continue;
274
+ }
275
+ if (!content) continue;
276
+ const role = msg.type === "gemini" ? "assistant" : "user";
277
+ const ts = msg.timestamp ? new Date(msg.timestamp).getTime() : Date.now();
278
+ let toolTokens = pendingToolTokens + (msg.tokens?.tool ?? 0);
279
+ pendingToolTokens = 0;
280
+ toolTokens += msg.tokens?.thoughts ?? 0;
281
+ if ((msg.tokens?.tool ?? 0) === 0 && msg.toolCalls?.length) {
282
+ for (const tc of msg.toolCalls) {
283
+ if (tc.args) toolTokens += Math.round(JSON.stringify(tc.args).length / 4);
284
+ if (tc.result) toolTokens += Math.round(JSON.stringify(tc.result).length / 4);
285
+ }
270
286
  }
287
+ const inputTokens = msg.tokens?.input ?? pendingInputTokens;
288
+ pendingInputTokens = void 0;
289
+ messages.push({
290
+ id: `gemini-${index++}`,
291
+ role,
292
+ content,
293
+ timestamp: isFinite(ts) ? ts : Date.now(),
294
+ ...toolTokens > 0 ? { toolTokens } : {},
295
+ ...inputTokens !== void 0 ? { inputTokens } : {}
296
+ });
271
297
  }
272
298
  return messages;
273
299
  }
@@ -289,10 +315,12 @@ var init_gemini_adapter = __esm({
289
315
  }
290
316
  });
291
317
  for (const dir of sessionDirs) {
318
+ const chatsDir = path2.join(dir, "chats");
319
+ if (!fs2.existsSync(chatsDir)) continue;
292
320
  try {
293
- const files = fs2.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
321
+ const files = fs2.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
294
322
  for (const file of files) {
295
- const fullPath = path2.join(dir, file);
323
+ const fullPath = path2.join(chatsDir, file);
296
324
  try {
297
325
  const mtime = fs2.statSync(fullPath).mtimeMs;
298
326
  if (mtime > latestTime) {
@@ -337,49 +365,40 @@ var init_codex_adapter = __esm({
337
365
  const messages = [];
338
366
  let index = 0;
339
367
  let pendingToolTokens = 0;
368
+ let pendingInputTokens;
340
369
  for (const line of lines) {
341
370
  try {
342
371
  const entry = JSON.parse(line);
343
- if (entry.role === "tool") {
344
- const resultText = typeof entry.content === "string" ? entry.content : "";
345
- pendingToolTokens += Math.round(resultText.length / 4);
372
+ if (entry.type !== "event_msg" || !entry.payload) continue;
373
+ const payload = entry.payload;
374
+ if (payload.type === "token_count") {
375
+ const tc = payload;
376
+ const usage = tc.info?.total_token_usage;
377
+ if (usage?.input_tokens) pendingInputTokens = usage.input_tokens;
378
+ if (usage?.reasoning_output_tokens) pendingToolTokens += usage.reasoning_output_tokens;
346
379
  continue;
347
380
  }
348
- if (entry.role !== "user" && entry.role !== "assistant") continue;
349
- let text = "";
350
- let toolTokens = pendingToolTokens;
351
- pendingToolTokens = 0;
352
- if (typeof entry.content === "string") {
353
- text = entry.content.trim();
354
- } else if (Array.isArray(entry.content)) {
355
- for (const block of entry.content) {
356
- if (block.type === "text" && typeof block.text === "string") {
357
- text += (text ? "\n" : "") + block.text.trim();
358
- } else if (block.type === "tool_use" && block.input) {
359
- toolTokens += Math.round(JSON.stringify(block.input).length / 4);
360
- }
361
- }
362
- text = text.trim();
363
- }
364
- if (entry.tool_calls) {
365
- for (const tc of entry.tool_calls) {
366
- if (tc.function?.arguments) {
367
- toolTokens += Math.round(tc.function.arguments.length / 4);
368
- }
369
- }
381
+ if (payload.type === "ExecCommandEnd") {
382
+ const output = payload.aggregated_output ?? "";
383
+ pendingToolTokens += Math.round(output.length / 4);
384
+ continue;
370
385
  }
386
+ if (payload.type !== "user_message" && payload.type !== "agent_message") continue;
387
+ const text = (payload.message ?? "").trim();
371
388
  if (!text) continue;
372
- let ts = Date.now();
373
- if (entry.timestamp) {
374
- const parsed = typeof entry.timestamp === "number" ? entry.timestamp : new Date(entry.timestamp).getTime();
375
- if (isFinite(parsed)) ts = parsed;
376
- }
389
+ const role = payload.type === "agent_message" ? "assistant" : "user";
390
+ const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
391
+ const toolTokens = pendingToolTokens;
392
+ pendingToolTokens = 0;
393
+ const inputTokens = pendingInputTokens;
394
+ pendingInputTokens = void 0;
377
395
  messages.push({
378
396
  id: `codex-${index++}`,
379
- role: entry.role,
397
+ role,
380
398
  content: text,
381
- timestamp: ts,
382
- ...toolTokens > 0 ? { toolTokens } : {}
399
+ timestamp: isFinite(ts) ? ts : Date.now(),
400
+ ...toolTokens > 0 ? { toolTokens } : {},
401
+ ...inputTokens !== void 0 ? { inputTokens } : {}
383
402
  });
384
403
  } catch {
385
404
  }
@@ -424,6 +443,11 @@ var init_codex_adapter = __esm({
424
443
  function detectAdapter(filePath) {
425
444
  return ADAPTERS.find((a) => a.canParse(filePath)) ?? ADAPTERS[0];
426
445
  }
446
+ function getPinnedAdapter() {
447
+ const name = process.env.DRIFTCLI_ADAPTER;
448
+ if (!name) return null;
449
+ return ADAPTERS.find((a) => a.name === name) ?? null;
450
+ }
427
451
  var ADAPTERS;
428
452
  var init_adapter_registry = __esm({
429
453
  "src/watchers/adapter-registry.ts"() {
@@ -453,7 +477,8 @@ var init_session_resolver = __esm({
453
477
  if (this.cached && this.cached.expiresAt > Date.now()) {
454
478
  return this.cached.file;
455
479
  }
456
- const result = this.resolveFromEnv() ?? this.resolveFromCwd() ?? findLatestSession();
480
+ const pinned = getPinnedAdapter();
481
+ const result = this.resolveFromEnv() ?? this.resolveFromCwd(pinned) ?? (pinned ? pinned.findLatest() : findLatestSession());
457
482
  if (result) {
458
483
  this.cached = { file: result, expiresAt: Date.now() + this.cacheTtlMs };
459
484
  } else {
@@ -470,7 +495,8 @@ var init_session_resolver = __esm({
470
495
  invalidate() {
471
496
  this.cached = null;
472
497
  }
473
- resolveFromCwd() {
498
+ resolveFromCwd(pinned) {
499
+ if (pinned && pinned.name !== "claude") return null;
474
500
  const file = findSessionByCwd();
475
501
  if (file && process.env.DRIFTCLI_DEBUG) {
476
502
  const slug = file.split(/[\\/]/).slice(-2, -1)[0];
@@ -718,15 +744,6 @@ function createWindows(messages, windowSize) {
718
744
  }
719
745
  return windows;
720
746
  }
721
- function extractTopTerms(messages, n = 5) {
722
- const freq = /* @__PURE__ */ new Map();
723
- for (const msg of messages) {
724
- for (const term of tokenize(msg.content)) {
725
- freq.set(term, (freq.get(term) ?? 0) + 1);
726
- }
727
- }
728
- return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([term]) => term);
729
- }
730
747
  function extractNgrams(text, n = 3) {
731
748
  const tokens = tokenize(text).filter((w) => !/^\d/.test(w));
732
749
  if (tokens.length < n) return /* @__PURE__ */ new Set();
@@ -1473,11 +1490,16 @@ function calculateDrift(messages, weights = DEFAULT_WEIGHTS, userGoal) {
1473
1490
  }
1474
1491
  function calcMessageDecay(messages) {
1475
1492
  let totalTokens = 0;
1476
- for (const msg of messages) {
1477
- const words = msg.content.split(/\s+/).filter((w) => w.length > 0).length;
1478
- const hasCode = msg.content.includes("```");
1479
- const tokenEstimate = Math.round(words * 1.3 * (hasCode ? 1.5 : 1));
1480
- totalTokens += tokenEstimate + (msg.toolTokens ?? 0);
1493
+ const latestInputTokens = [...messages].reverse().find((m) => (m.inputTokens ?? 0) > 0)?.inputTokens;
1494
+ if (latestInputTokens !== void 0) {
1495
+ totalTokens = latestInputTokens;
1496
+ } else {
1497
+ for (const msg of messages) {
1498
+ const words = msg.content.split(/\s+/).filter((w) => w.length > 0).length;
1499
+ const hasCode = msg.content.includes("```");
1500
+ const tokenEstimate = Math.round(words * 1.3 * (hasCode ? 1.5 : 1));
1501
+ totalTokens += tokenEstimate + (msg.toolTokens ?? 0);
1502
+ }
1481
1503
  }
1482
1504
  if (totalTokens < 500) return 0;
1483
1505
  let score = Math.min(100, Math.round(15 * Math.log(totalTokens / 1500)));
@@ -2059,38 +2081,57 @@ var setup_exports = {};
2059
2081
  __export(setup_exports, {
2060
2082
  setup: () => setup
2061
2083
  });
2062
- function setup() {
2084
+ function setupToml(target, update) {
2085
+ const existing = fs6.existsSync(target.filePath) ? fs6.readFileSync(target.filePath, "utf-8") : "";
2086
+ if (existing.includes("[mcp_servers.driftguard]")) {
2087
+ if (!update) {
2088
+ console.log(` ${target.name}: already configured (use --update to overwrite)`);
2089
+ return;
2090
+ }
2091
+ const stripped = existing.replace(/\n\[mcp_servers\.driftguard\][^\[]*/s, "");
2092
+ fs6.writeFileSync(target.filePath, stripped.trimEnd() + TOML_ENTRY(target.adapter), "utf-8");
2093
+ } else {
2094
+ fs6.writeFileSync(target.filePath, existing.trimEnd() + TOML_ENTRY(target.adapter), "utf-8");
2095
+ }
2096
+ console.log(` ${target.name}: configured (${target.filePath})`);
2097
+ }
2098
+ function setupJson(target, update) {
2099
+ let config2 = {};
2100
+ if (fs6.existsSync(target.filePath)) {
2101
+ try {
2102
+ config2 = JSON.parse(fs6.readFileSync(target.filePath, "utf-8"));
2103
+ } catch {
2104
+ console.log(` ${target.name}: skipped \u2014 could not parse ${target.filePath}`);
2105
+ return;
2106
+ }
2107
+ }
2108
+ const servers = config2.mcpServers ?? {};
2109
+ if (servers.driftguard && !update) {
2110
+ console.log(` ${target.name}: already configured (use --update to overwrite)`);
2111
+ return;
2112
+ }
2113
+ config2.mcpServers = { ...servers, driftguard: { command: "driftguard-mcp", env: { DRIFTCLI_ADAPTER: target.adapter } } };
2114
+ const dir = path8.dirname(target.filePath);
2115
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
2116
+ fs6.writeFileSync(target.filePath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
2117
+ console.log(` ${target.name}: configured (${target.filePath})`);
2118
+ }
2119
+ function setup(update = false) {
2063
2120
  console.log("driftguard-mcp setup\n");
2064
2121
  for (const target of TARGETS) {
2065
2122
  try {
2066
- const dir = path8.dirname(target.filePath);
2067
- let config2 = {};
2068
- if (fs6.existsSync(target.filePath)) {
2069
- try {
2070
- config2 = JSON.parse(fs6.readFileSync(target.filePath, "utf-8"));
2071
- } catch {
2072
- console.log(` ${target.name}: skipped \u2014 could not parse ${target.filePath}`);
2073
- continue;
2074
- }
2075
- }
2076
- const servers = config2.mcpServers ?? {};
2077
- if (servers.driftguard) {
2078
- console.log(` ${target.name}: already configured`);
2079
- continue;
2123
+ if (target.format === "toml") {
2124
+ setupToml(target, update);
2125
+ } else {
2126
+ setupJson(target, update);
2080
2127
  }
2081
- config2.mcpServers = { ...servers, driftguard: MCP_ENTRY };
2082
- if (!fs6.existsSync(dir)) {
2083
- fs6.mkdirSync(dir, { recursive: true });
2084
- }
2085
- fs6.writeFileSync(target.filePath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
2086
- console.log(` ${target.name}: configured (${target.filePath})`);
2087
2128
  } catch (err) {
2088
2129
  console.log(` ${target.name}: failed \u2014 ${err instanceof Error ? err.message : err}`);
2089
2130
  }
2090
2131
  }
2091
2132
  console.log("\nDone. Restart your AI CLI to activate the tools.");
2092
2133
  }
2093
- var fs6, path8, os6, TARGETS, MCP_ENTRY;
2134
+ var fs6, path8, os6, TARGETS, TOML_ENTRY;
2094
2135
  var init_setup = __esm({
2095
2136
  "src/setup.ts"() {
2096
2137
  "use strict";
@@ -2098,14 +2139,16 @@ var init_setup = __esm({
2098
2139
  path8 = __toESM(require("path"));
2099
2140
  os6 = __toESM(require("os"));
2100
2141
  TARGETS = [
2101
- { name: "Claude Code", filePath: path8.join(os6.homedir(), ".claude.json") },
2102
- { name: "Gemini CLI", filePath: path8.join(os6.homedir(), ".gemini", "settings.json") },
2103
- { name: "Codex CLI", filePath: path8.join(os6.homedir(), ".codex", "config.json") },
2104
- { name: "Cursor", filePath: path8.join(os6.homedir(), ".cursor", "mcp.json") }
2142
+ { name: "Claude Code", filePath: path8.join(os6.homedir(), ".claude.json"), adapter: "claude", format: "json" },
2143
+ { name: "Gemini CLI", filePath: path8.join(os6.homedir(), ".gemini", "settings.json"), adapter: "gemini", format: "json" },
2144
+ { name: "Codex CLI", filePath: path8.join(os6.homedir(), ".codex", "config.toml"), adapter: "codex", format: "toml" },
2145
+ { name: "Cursor", filePath: path8.join(os6.homedir(), ".cursor", "mcp.json"), adapter: "claude", format: "json" }
2105
2146
  ];
2106
- MCP_ENTRY = {
2107
- command: "driftguard-mcp"
2108
- };
2147
+ TOML_ENTRY = (adapter) => `
2148
+ [mcp_servers.driftguard]
2149
+ command = "driftguard-mcp"
2150
+ env.DRIFTCLI_ADAPTER = "${adapter}"
2151
+ `;
2109
2152
  }
2110
2153
  });
2111
2154
 
@@ -2114,52 +2157,27 @@ var mcp_server_exports = {};
2114
2157
  __export(mcp_server_exports, {
2115
2158
  main: () => main
2116
2159
  });
2117
- function buildHandoff(analysis, messages, chatMessages, emoji, level) {
2118
- const durationMs = analysis.sessionDuration;
2119
- const durationMin = durationMs > 0 ? Math.round(durationMs / 6e4) : null;
2120
- const durationStr = durationMin !== null ? durationMin >= 60 ? `${Math.floor(durationMin / 60)}h ${durationMin % 60}m` : `${durationMin}m` : "unknown duration";
2121
- const topTerms = extractTopTerms(chatMessages, 6);
2122
- const topicsStr = topTerms.length > 0 ? topTerms.join(", ") : "not enough content to determine";
2123
- const recentUserMessages = messages.filter((m) => m.role === "user").slice(-3).map((m, i) => `${i + 1}. ${m.content.slice(0, 200)}${m.content.length > 200 ? "\u2026" : ""}`).join("\n");
2124
- let lastCodeSnippet = "";
2125
- for (let i = messages.length - 1; i >= 0; i--) {
2126
- const match = [...messages[i].content.matchAll(/```(\w*)\n([\s\S]*?)```/g)].pop();
2127
- if (match) {
2128
- const lang = match[1] || "";
2129
- const code = match[2].split("\n").slice(0, 20).join("\n");
2130
- lastCodeSnippet = `\`\`\`${lang}
2131
- ${code}
2132
- \`\`\``;
2133
- break;
2134
- }
2135
- }
2136
- const lines = [
2137
- `## Context Handoff`,
2160
+ function buildHandoff() {
2161
+ return [
2162
+ `Please write a \`handoff.md\` file in the current working directory with the following structure:`,
2138
2163
  ``,
2139
- `**Drift:** ${analysis.score}/100 ${emoji} ${level.toUpperCase()} | **Messages:** ${messages.length} | **Duration:** ${durationStr}`,
2164
+ `## What we accomplished`,
2165
+ `A clear summary of everything completed this session.`,
2140
2166
  ``,
2141
- `**Top topics:** ${topicsStr}`,
2142
- ``,
2143
- `**Recent focus (last 3 user messages):**`,
2144
- recentUserMessages
2145
- ];
2146
- if (lastCodeSnippet) {
2147
- lines.push(``, `**Last code context:**`, lastCodeSnippet);
2148
- }
2149
- lines.push(
2167
+ `## Current state`,
2168
+ `Where things stand right now \u2014 what's working, what's broken, what's in progress.`,
2150
2169
  ``,
2151
- `---`,
2152
- `*Paste the section below into your new session to restore context.*`,
2170
+ `## Files modified`,
2171
+ `List of files changed this session and what changed in each.`,
2153
2172
  ``,
2154
- `**Continuing from a previous session** (drift was ${analysis.score}/100, ${messages.length} messages, ${durationStr}).`,
2155
- `Main topics: ${topicsStr}.`,
2173
+ `## Open questions / next steps`,
2174
+ `Anything unresolved, pending decisions, or what should be done next.`,
2156
2175
  ``,
2157
- `Recent questions:`,
2158
- recentUserMessages,
2176
+ `## Context for next session`,
2177
+ `Key decisions, constraints, or background a fresh session needs to continue without losing context.`,
2159
2178
  ``,
2160
- `Please continue from here.`
2161
- );
2162
- return lines.join("\n");
2179
+ `Write the file now.`
2180
+ ].join("\n");
2163
2181
  }
2164
2182
  async function main() {
2165
2183
  const transport = new import_stdio.StdioServerTransport();
@@ -2176,7 +2194,6 @@ var init_mcp_server = __esm({
2176
2194
  init_session_resolver();
2177
2195
  init_drift_calculator();
2178
2196
  init_types();
2179
- init_topic_analyzer();
2180
2197
  init_config();
2181
2198
  init_storage();
2182
2199
  init_ui();
@@ -2270,16 +2287,12 @@ var init_mcp_server = __esm({
2270
2287
  ];
2271
2288
  if (trendLine) lines.push(``, trendLine);
2272
2289
  if (isDegrading) {
2273
- lines.push(
2274
- ``,
2275
- `---`,
2276
- buildHandoff(analysis, messages, chatMessages, emoji, level)
2277
- );
2290
+ lines.push(``, `---`, buildHandoff());
2278
2291
  }
2279
2292
  return { content: [{ type: "text", text: lines.join("\n") }] };
2280
2293
  }
2281
2294
  if (request.params.name === "get_handoff") {
2282
- return { content: [{ type: "text", text: buildHandoff(analysis, messages, chatMessages, emoji, level) }] };
2295
+ return { content: [{ type: "text", text: buildHandoff() }] };
2283
2296
  }
2284
2297
  if (request.params.name === "get_trend") {
2285
2298
  if (!storage) {
@@ -2306,7 +2319,7 @@ if (command === "watch") {
2306
2319
  run2();
2307
2320
  } else if (command === "setup") {
2308
2321
  const { setup: setup2 } = (init_setup(), __toCommonJS(setup_exports));
2309
- setup2();
2322
+ setup2(process.argv.includes("--update"));
2310
2323
  } else {
2311
2324
  const { main: main2 } = (init_mcp_server(), __toCommonJS(mcp_server_exports));
2312
2325
  main2().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driftguard-mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Real-time AI conversation drift monitor — MCP server for Claude Code, Gemini CLI, Codex CLI, and Cursor",
5
5
  "main": "dist/bin.js",
6
6
  "bin": {