claude-session-continuity-mcp 1.16.1 → 1.17.0

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/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # claude-session-continuity-mcp
2
2
 
3
- > **Never re-explain your project to Claude again.** 100% local session memory for Claude Code — auto context injection, semantic search, and error→solution recall. Zero config, zero API cost.
3
+ > **Never re-explain your project to your AI coding agent again.** 100% local session memory for **Claude Code & OpenAI Codex CLI** — auto context injection, semantic search, and error→solution recall. Zero config, zero API cost.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/claude-session-continuity-mcp.svg)](https://www.npmjs.com/package/claude-session-continuity-mcp)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/claude-session-continuity-mcp.svg)](https://www.npmjs.com/package/claude-session-continuity-mcp)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
  [![claude-session-continuity-mcp MCP server](https://glama.ai/mcp/servers/leesgit/claude-session-continuity-mcp/badges/card.svg)](https://glama.ai/mcp/servers/leesgit/claude-session-continuity-mcp)
9
9
 
10
+ **⚡ One install → context auto-loads every session · 🧩 survives compaction (0 re-explaining) · 🔒 100% local, $0 API**
11
+
10
12
  ![Session continuity demo — Claude auto-restores your project context on session start](assets/demo.gif)
11
13
 
12
14
  ## The Problem
@@ -24,7 +26,7 @@ Every new Claude Code session:
24
26
 
25
27
  ## The Solution
26
28
 
27
- **Fully automatic.** Claude Hooks handle everything without manual calls:
29
+ **Fully automatic.** Lifecycle hooks handle everything without manual calls — on **Claude Code** and **OpenAI Codex CLI**, sharing one local memory so context carries across both:
28
30
 
29
31
  ```bash
30
32
  # Session start → Auto-loads relevant context + recent session history
@@ -79,6 +81,19 @@ Most Claude memory tools rely on **explicit tool calls** ("remember this"), a **
79
81
 
80
82
  If you want zero-config, offline, no-cost memory that just *happens* while you work — this is it.
81
83
 
84
+ ### Auto-injection vs. explicit search
85
+
86
+ There's also a great class of **local search** tools (e.g. [ctx](https://github.com/ctxrs/ctx)) that index your agent history so you can *query* it (`search "failed migration"`). That's complementary, not the same job:
87
+
88
+ | | claude-session-continuity-mcp | Local-search tools (ctx, etc.) |
89
+ |---|---|---|
90
+ | **How you use it** | **Automatic** — context appears on session start, no command | You (or the agent) run a search query |
91
+ | **Compaction** | **PreCompact hook re-injects a handover** → 0 context re-explained after a compact | Not its job (it's a search index) |
92
+ | **Best at** | *Never losing your thread* across sessions & compacts, hands-off | *Finding* a specific past decision/command on demand |
93
+ | **Coverage** | Claude Code + Codex CLI (where auto-injection is possible) | Often 30+ agents indexed for search |
94
+
95
+ Use search when you want to *look something up*. Use this when you want your context to *follow you* without asking.
96
+
82
97
  ---
83
98
 
84
99
  ## Works with Codex CLI too (v1.16.0+)
@@ -99,6 +114,26 @@ interface (it can be null at startup), so host detection uses an installer-injec
99
114
 
100
115
  ---
101
116
 
117
+ ## Works with Gemini CLI too (v1.17.0+)
118
+
119
+ Also supports **Google Gemini CLI**. If `~/.gemini` exists, the installer registers
120
+ the hooks in `~/.gemini/settings.json` (SessionStart, BeforeAgent, PreCompress,
121
+ SessionEnd — Gemini's event names), preserving your other settings. Same shared
122
+ local `sessions.db`, so context carries across all three agents.
123
+
124
+ Gemini's transcript format was verified against real `~/.gemini/tmp/.../chats/*.jsonl`
125
+ files — it uses **two shapes** (a flat `{type, content}` line and an older
126
+ `{"$set":{"messages":[…]}}` diff line); the parser handles both. Like Codex,
127
+ `transcript_path` can be null at startup, so host detection uses a `--gemini` marker.
128
+
129
+ **Honest scope note:** session save (SessionEnd) and context output are verified working.
130
+ Gemini's `SessionStart` context injection is documented as *advisory-only* upstream
131
+ ([gemini-cli#15413](https://github.com/google-gemini/gemini-cli/issues/15413)) — if your
132
+ Gemini build doesn't render the injected context on startup, that's an upstream limit,
133
+ not this tool. Session continuity still works via the saved history.
134
+
135
+ ---
136
+
102
137
  ## Quick Start
103
138
 
104
139
  ### Recommended: Global Installation
@@ -19,6 +19,9 @@ const MCP_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
19
19
  // Codex CLI (2026-07-09): hooks register in ~/.codex/hooks.json (same JSON shape as Claude)
20
20
  const CODEX_DIR = path.join(os.homedir(), '.codex');
21
21
  const CODEX_HOOKS_FILE = path.join(CODEX_DIR, 'hooks.json');
22
+ // Gemini CLI (2026-07-10): hooks register inside ~/.gemini/settings.json under a "hooks" key
23
+ const GEMINI_DIR = path.join(os.homedir(), '.gemini');
24
+ const GEMINI_SETTINGS_FILE = path.join(GEMINI_DIR, 'settings.json');
22
25
  // 설치된 패키지 경로 찾기
23
26
  function getPackagePath() {
24
27
  // 1. 글로벌 설치 확인
@@ -164,6 +167,45 @@ function installCodexHooks() {
164
167
  }
165
168
  catch { /* non-fatal: Codex hooks are optional */ }
166
169
  }
170
+ /**
171
+ * Register the same hooks in ~/.gemini/settings.json for Gemini CLI (2026-07-10).
172
+ * Only runs if ~/.gemini exists. Hooks live under a "hooks" key inside settings.json
173
+ * (not a separate file). Preserves the user's other settings and their own hooks;
174
+ * replaces only ours (matched by the claude-hook- command prefix).
175
+ * Gemini event names differ: BeforeAgent (≈UserPromptSubmit), PreCompress (≈PreCompact),
176
+ * SessionEnd (≈Stop). transcript_path can be null at SessionStart, so we inject "--gemini".
177
+ */
178
+ function installGeminiHooks() {
179
+ if (!fs.existsSync(GEMINI_DIR))
180
+ return; // Gemini not installed -> skip silently
181
+ const OUR_PREFIX = 'claude-hook-';
182
+ let settings = {};
183
+ if (fs.existsSync(GEMINI_SETTINGS_FILE)) {
184
+ try {
185
+ settings = JSON.parse(fs.readFileSync(GEMINI_SETTINGS_FILE, 'utf-8'));
186
+ }
187
+ catch {
188
+ settings = {};
189
+ }
190
+ }
191
+ const hooks = settings.hooks || {};
192
+ const merge = (event, ourEntries) => {
193
+ const existing = (hooks[event] || []);
194
+ const userEntries = existing.filter(e => !(e.command && e.command.includes(OUR_PREFIX)));
195
+ hooks[event] = [...userEntries, ...ourEntries];
196
+ };
197
+ // Gemini hook entries are flat {type, command} (no nested "hooks" array like Claude/Codex).
198
+ merge('SessionStart', [{ type: 'command', command: 'npm exec -- claude-hook-session-start --gemini' }]);
199
+ merge('BeforeAgent', [{ type: 'command', command: 'npm exec -- claude-hook-user-prompt --gemini' }]);
200
+ merge('PreCompress', [{ type: 'command', command: 'npm exec -- claude-hook-pre-compact --gemini' }]);
201
+ merge('SessionEnd', [{ type: 'command', command: 'npm exec -- claude-hook-session-end --gemini' }]);
202
+ settings.hooks = hooks;
203
+ try {
204
+ fs.writeFileSync(GEMINI_SETTINGS_FILE, JSON.stringify(settings, null, 2));
205
+ console.log('✅ Gemini CLI hooks installed (~/.gemini/settings.json)');
206
+ }
207
+ catch { /* non-fatal: Gemini hooks are optional */ }
208
+ }
167
209
  function install() {
168
210
  console.log('');
169
211
  console.log('╔════════════════════════════════════════════════════════════╗');
@@ -214,6 +256,8 @@ function install() {
214
256
  // Codex CLI hooks (2026-07-09): register the same hooks in ~/.codex/hooks.json
215
257
  // if Codex is present. Hooks auto-detect the host and emit the right output format.
216
258
  installCodexHooks();
259
+ // Gemini CLI hooks (2026-07-10): same, in ~/.gemini/settings.json if Gemini is present.
260
+ installGeminiHooks();
217
261
  console.log('✅ Hooks installed (npm exec mode - works with local or global install!)');
218
262
  console.log(' SessionStart: context auto-load');
219
263
  console.log(' UserPromptSubmit: relevant memory injection');
@@ -286,6 +330,24 @@ function uninstall() {
286
330
  }
287
331
  catch { /* non-fatal */ }
288
332
  }
333
+ // Also remove our hooks from Gemini (2026-07-10), preserving user's settings + hooks.
334
+ if (fs.existsSync(GEMINI_SETTINGS_FILE)) {
335
+ try {
336
+ const cfg = JSON.parse(fs.readFileSync(GEMINI_SETTINGS_FILE, 'utf-8'));
337
+ const gh = cfg.hooks || {};
338
+ for (const event of ['SessionStart', 'BeforeAgent', 'PreCompress', 'SessionEnd']) {
339
+ const existing = (gh[event] || []);
340
+ const remaining = existing.filter(e => !(e.command && e.command.includes(OUR_PREFIX)));
341
+ if (remaining.length === 0)
342
+ delete gh[event];
343
+ else
344
+ gh[event] = remaining;
345
+ }
346
+ cfg.hooks = gh;
347
+ fs.writeFileSync(GEMINI_SETTINGS_FILE, JSON.stringify(cfg, null, 2));
348
+ }
349
+ catch { /* non-fatal */ }
350
+ }
289
351
  console.log('✅ Hooks removed successfully!');
290
352
  }
291
353
  function status() {
@@ -14,7 +14,7 @@ import * as path from 'path';
14
14
  import * as readline from 'readline';
15
15
  import * as crypto from 'crypto';
16
16
  import Database from 'better-sqlite3';
17
- import { logHookError, isCodexHost } from '../utils/logger.js';
17
+ import { logHookError, isCodexHost, isGeminiHost } from '../utils/logger.js';
18
18
  function detectWorkspaceRoot(cwd) {
19
19
  let current = cwd;
20
20
  const root = path.parse(current).root;
@@ -209,6 +209,8 @@ function parseUserText(entry) {
209
209
  text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
210
210
  // Codex support (2026-07-09): Codex injects <environment_context> into the first user message -> strip it
211
211
  text = text.replace(/<environment_context>[\s\S]*?<\/environment_context>/g, '').trim();
212
+ // Gemini support (2026-07-10): Gemini injects <session_context> into the first user message -> strip it
213
+ text = text.replace(/<session_context>[\s\S]*?<\/session_context>/g, '').trim();
212
214
  text = text.replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '').trim();
213
215
  text = text.replace(/<command-name>[\s\S]*?<\/command-name>/g, '').trim();
214
216
  text = text.replace(/<command-message>[\s\S]*?<\/command-message>/g, '').trim();
@@ -297,6 +299,40 @@ function normalizeCodexLine(codexEntry) {
297
299
  }
298
300
  return null; // skip reasoning/token_count/session_meta etc.
299
301
  }
302
+ /** One Gemini message object -> Claude entry (or null to skip). */
303
+ function geminiMsgToEntry(m) {
304
+ const t = m.type || m.role; // some builds use role
305
+ if (t !== 'user' && t !== 'gemini' && t !== 'model' && t !== 'assistant')
306
+ return null;
307
+ const text = (m.content || []).map(c => c.text || '').filter(Boolean).join('\n');
308
+ if (!text)
309
+ return null;
310
+ return {
311
+ type: t === 'user' ? 'user' : 'assistant',
312
+ timestamp: m.timestamp,
313
+ message: { content: [{ type: 'text', text }] },
314
+ };
315
+ }
316
+ /**
317
+ * Normalize a Gemini CLI transcript line into Claude entries (2026-07-10).
318
+ * REAL Gemini format has two shapes (verified against actual ~/.gemini transcripts —
319
+ * the docs claimed only the flat shape, but older sessions use the $set diff shape):
320
+ * A) flat: {type:"user"|"gemini", content:[{text}], timestamp}
321
+ * B) diff: {"$set":{"messages":[ {type/role, content:[{text}]}, ... ]}}
322
+ * Top-level type:"info" lines and message_update patches are skipped.
323
+ * Returns an array (shape B can carry multiple messages per line).
324
+ */
325
+ function normalizeGeminiLine(raw) {
326
+ const entry = raw;
327
+ // Shape B: $set.messages array
328
+ const setMsgs = entry?.['$set']?.messages;
329
+ if (Array.isArray(setMsgs)) {
330
+ const out = setMsgs.map(geminiMsgToEntry).filter((e) => e !== null);
331
+ return out.length ? out : null;
332
+ }
333
+ // Shape A: flat message line
334
+ return geminiMsgToEntry(entry);
335
+ }
300
336
  /**
301
337
  * Single-Pass Transcript Parser
302
338
  * JSONL을 1회 스트림으로 읽으며 모든 데이터를 동시 추출
@@ -314,8 +350,10 @@ async function parseTranscriptSinglePass(transcriptPath) {
314
350
  };
315
351
  if (!transcriptPath || !fs.existsSync(transcriptPath))
316
352
  return result;
317
- // Host detection: Codex uses ~/.codex/sessions/...rollout-*.jsonl, Claude uses ~/.claude/projects/...
353
+ // Host detection: Codex uses ~/.codex/sessions/...rollout-*.jsonl,
354
+ // Gemini uses ~/.gemini/tmp/.../chats/*.jsonl, Claude uses ~/.claude/projects/...
318
355
  const isCodex = isCodexHost(transcriptPath); // shared detection (argv marker or path)
356
+ const isGemini = isGeminiHost(transcriptPath);
319
357
  // commit 추출용 패턴
320
358
  const commitPatterns = [
321
359
  /git commit.*?-m\s*"\$\(cat <<'?EOF'?\n(.+?)(?:\n\n|\nCo-Authored|\nEOF)/s,
@@ -340,73 +378,79 @@ async function parseTranscriptSinglePass(transcriptPath) {
340
378
  continue;
341
379
  try {
342
380
  const rawEntry = JSON.parse(line);
343
- // For Codex, normalize the rollout line into a Claude entry shape. Skipped lines (reasoning etc.) return null.
344
- const entry = isCodex ? normalizeCodexLine(rawEntry) : rawEntry;
345
- if (!entry)
381
+ // Normalize per host into Claude entry shape. Gemini's $set line can yield
382
+ // multiple entries, so unify everything into an array and process each.
383
+ const normalized = isCodex ? normalizeCodexLine(rawEntry)
384
+ : isGemini ? normalizeGeminiLine(rawEntry)
385
+ : rawEntry;
386
+ if (!normalized)
346
387
  continue;
347
- const role = entry.type || entry.role || '';
348
- const content = entry.message?.content;
349
- // === 0. Timestamp 추출 (세션 duration 계산용) ===
350
- if (entry.timestamp) {
351
- if (!result.firstTimestamp)
352
- result.firstTimestamp = entry.timestamp;
353
- result.lastTimestamp = entry.timestamp;
354
- }
355
- // === 1. Commit 추출 (tool_use 블록에서) ===
356
- if (line.includes('git commit') && Array.isArray(content)) {
357
- for (const block of content) {
358
- if (block.type !== 'tool_use')
359
- continue;
360
- const cmd = block.input?.command;
361
- if (!cmd || !cmd.includes('-m'))
362
- continue;
363
- if (!/(?:^|&&\s*)git\s+commit/.test(cmd))
364
- continue;
365
- for (const pattern of commitPatterns) {
366
- const match = cmd.match(pattern);
367
- if (match?.[1]) {
368
- const msg = match[1].trim().split('\n')[0];
369
- if (msg.length > 10 && !msg.startsWith('Co-Authored')) {
370
- commitSet.add(msg.slice(0, 150));
388
+ const entries = Array.isArray(normalized) ? normalized : [normalized];
389
+ for (const entry of entries) {
390
+ const role = entry.type || entry.role || '';
391
+ const content = entry.message?.content;
392
+ // === 0. Timestamp 추출 (세션 duration 계산용) ===
393
+ if (entry.timestamp) {
394
+ if (!result.firstTimestamp)
395
+ result.firstTimestamp = entry.timestamp;
396
+ result.lastTimestamp = entry.timestamp;
397
+ }
398
+ // === 1. Commit 추출 (tool_use 블록에서) ===
399
+ if (line.includes('git commit') && Array.isArray(content)) {
400
+ for (const block of content) {
401
+ if (block.type !== 'tool_use')
402
+ continue;
403
+ const cmd = block.input?.command;
404
+ if (!cmd || !cmd.includes('-m'))
405
+ continue;
406
+ if (!/(?:^|&&\s*)git\s+commit/.test(cmd))
407
+ continue;
408
+ for (const pattern of commitPatterns) {
409
+ const match = cmd.match(pattern);
410
+ if (match?.[1]) {
411
+ const msg = match[1].trim().split('\n')[0];
412
+ if (msg.length > 10 && !msg.startsWith('Co-Authored')) {
413
+ commitSet.add(msg.slice(0, 150));
414
+ }
415
+ break;
371
416
  }
372
- break;
373
417
  }
374
418
  }
375
419
  }
376
- }
377
- // === 2. User Requests 추출 ===
378
- if (role === 'human' || role === 'user') {
379
- const text = parseUserText(entry);
380
- if (text) {
381
- const planMatch = text.match(/^Implement the following plan:\s*\n+#\s*(.+)/);
382
- const cleaned = planMatch
383
- ? planMatch[1].trim().slice(0, 100)
384
- : stripMarkdown(text.split('\n')[0].trim()).slice(0, 100);
385
- if (cleaned && cleaned.length >= 3) {
386
- if (!result.userRequests.firstRequest)
387
- result.userRequests.firstRequest = cleaned;
388
- result.userRequests.allRequests.push(cleaned);
420
+ // === 2. User Requests 추출 ===
421
+ if (role === 'human' || role === 'user') {
422
+ const text = parseUserText(entry);
423
+ if (text) {
424
+ const planMatch = text.match(/^Implement the following plan:\s*\n+#\s*(.+)/);
425
+ const cleaned = planMatch
426
+ ? planMatch[1].trim().slice(0, 100)
427
+ : stripMarkdown(text.split('\n')[0].trim()).slice(0, 100);
428
+ if (cleaned && cleaned.length >= 3) {
429
+ if (!result.userRequests.firstRequest)
430
+ result.userRequests.firstRequest = cleaned;
431
+ result.userRequests.allRequests.push(cleaned);
432
+ }
389
433
  }
390
434
  }
391
- }
392
- // === 3. Assistant 메시지 수집 (decisions + recentMessages) ===
393
- if (role === 'assistant') {
394
- const text = extractTextFromContent(content);
395
- if (text.length > 10) {
396
- // 최근 10개만 유지 (decision 추출용)
397
- result.recentAssistantMessages.push(text);
398
- if (result.recentAssistantMessages.length > 10) {
399
- result.recentAssistantMessages.shift();
435
+ // === 3. Assistant 메시지 수집 (decisions + recentMessages) ===
436
+ if (role === 'assistant') {
437
+ const text = extractTextFromContent(content);
438
+ if (text.length > 10) {
439
+ // 최근 10개만 유지 (decision 추출용)
440
+ result.recentAssistantMessages.push(text);
441
+ if (result.recentAssistantMessages.length > 10) {
442
+ result.recentAssistantMessages.shift();
443
+ }
400
444
  }
401
445
  }
402
- }
403
- // === 4. Error-Fix pair용 entries 수집 (최근 30개) ===
404
- const text = extractTextFromContent(content);
405
- if (text.length > 5) {
406
- recentEntries.push({ role, text: text.slice(0, 500) });
407
- if (recentEntries.length > 30)
408
- recentEntries.shift();
409
- }
446
+ // === 4. Error-Fix pair용 entries 수집 (최근 30개) ===
447
+ const text = extractTextFromContent(content);
448
+ if (text.length > 5) {
449
+ recentEntries.push({ role, text: text.slice(0, 500) });
450
+ if (recentEntries.length > 30)
451
+ recentEntries.shift();
452
+ }
453
+ } // end for (entry of entries)
410
454
  }
411
455
  catch { /* skip malformed lines */ }
412
456
  }
@@ -5,7 +5,7 @@
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import Database from 'better-sqlite3';
8
- import { logHookError, emitContext, isCodexHost } from '../utils/logger.js';
8
+ import { logHookError, emitContext, isCodexHost, isGeminiHost } from '../utils/logger.js';
9
9
  function detectWorkspaceRoot(cwd) {
10
10
  let current = cwd;
11
11
  const root = path.parse(current).root;
@@ -262,8 +262,9 @@ async function main() {
262
262
  emitContext(`\n<session-context project="${project}">\n${context}\n</session-context>\n`, 'SessionStart', input.transcript_path);
263
263
  }
264
264
  else {
265
- // Only Claude gets the "no context" placeholder; Codex would just get empty context.
266
- if (!isCodexHost(input.transcript_path)) {
265
+ // Only Claude gets the plain "no context" placeholder; Codex and Gemini
266
+ // expect JSON-only stdout, so a stray console.log would corrupt their parsing.
267
+ if (!isCodexHost(input.transcript_path) && !isGeminiHost(input.transcript_path)) {
267
268
  console.log(`\n[Session] Project: ${project} (no context yet)\n`);
268
269
  }
269
270
  }
@@ -37,9 +37,17 @@ export declare function logHookError(hook: string, err: unknown): void;
37
37
  */
38
38
  export declare function isCodexHost(transcriptPath?: string): boolean;
39
39
  /**
40
- * Emit context to stdout in the host's expected format (Codex support, 2026-07-09).
40
+ * Detect whether the hook is running under Gemini CLI (Google), 2026-07-10.
41
+ * Same two-signal approach as Codex — Gemini's transcript_path can be null/stubbed
42
+ * at SessionStart (google-gemini/gemini-cli#14715), so the installer-injected
43
+ * "--gemini" argv marker is the reliable signal; the ~/.gemini/tmp/.../chats path
44
+ * is the fallback for later events.
45
+ */
46
+ export declare function isGeminiHost(transcriptPath?: string): boolean;
47
+ /**
48
+ * Emit context to stdout in the host's expected format.
41
49
  * - Claude Code injects raw stdout text directly.
42
- * - Codex CLI expects {hookSpecificOutput:{hookEventName, additionalContext}}.
50
+ * - Codex CLI and Gemini CLI both expect {hookSpecificOutput:{hookEventName, additionalContext}}.
43
51
  */
44
52
  export declare function emitContext(context: string, hookEventName: string, transcriptPath?: string): void;
45
53
  export {};
@@ -156,12 +156,29 @@ export function isCodexHost(transcriptPath) {
156
156
  return transcriptPath.includes('/.codex/sessions/') || /rollout-.*\.jsonl$/.test(transcriptPath);
157
157
  }
158
158
  /**
159
- * Emit context to stdout in the host's expected format (Codex support, 2026-07-09).
159
+ * Detect whether the hook is running under Gemini CLI (Google), 2026-07-10.
160
+ * Same two-signal approach as Codex — Gemini's transcript_path can be null/stubbed
161
+ * at SessionStart (google-gemini/gemini-cli#14715), so the installer-injected
162
+ * "--gemini" argv marker is the reliable signal; the ~/.gemini/tmp/.../chats path
163
+ * is the fallback for later events.
164
+ */
165
+ export function isGeminiHost(transcriptPath) {
166
+ if (process.argv.includes('--gemini'))
167
+ return true;
168
+ if (!transcriptPath)
169
+ return false;
170
+ // Gemini stores chats at ~/.gemini/tmp/<hash>/chats/*.jsonl. Match that specific
171
+ // path, NOT a bare "/.gemini/" — a Claude session run from a dir containing
172
+ // ".gemini" would otherwise be misclassified (audit-3 2026-07-10 finding).
173
+ return transcriptPath.includes('/.gemini/tmp/');
174
+ }
175
+ /**
176
+ * Emit context to stdout in the host's expected format.
160
177
  * - Claude Code injects raw stdout text directly.
161
- * - Codex CLI expects {hookSpecificOutput:{hookEventName, additionalContext}}.
178
+ * - Codex CLI and Gemini CLI both expect {hookSpecificOutput:{hookEventName, additionalContext}}.
162
179
  */
163
180
  export function emitContext(context, hookEventName, transcriptPath) {
164
- if (isCodexHost(transcriptPath)) {
181
+ if (isCodexHost(transcriptPath) || isGeminiHost(transcriptPath)) {
165
182
  process.stdout.write(JSON.stringify({
166
183
  hookSpecificOutput: { hookEventName, additionalContext: context },
167
184
  }));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.16.1",
4
- "description": "Session Continuity for Claude Code - Never re-explain your project again",
3
+ "version": "1.17.0",
4
+ "description": "Session continuity for Claude Code, OpenAI Codex CLI & Google Gemini CLI - never re-explain your project again. 100% local, zero config, auto context injection.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -31,12 +31,19 @@
31
31
  "model-context-protocol",
32
32
  "claude",
33
33
  "claude-code",
34
+ "codex",
35
+ "codex-cli",
36
+ "openai",
37
+ "gemini",
38
+ "gemini-cli",
39
+ "google",
34
40
  "ai",
35
41
  "context",
36
42
  "memory",
37
43
  "session",
38
44
  "continuity",
39
- "anthropic"
45
+ "anthropic",
46
+ "agent-memory"
40
47
  ],
41
48
  "author": "Byeongchang Lee <leesgit@github.com>",
42
49
  "license": "MIT",