code-session-memory 0.11.0 → 0.12.1

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/dist/src/cli.js CHANGED
@@ -127,6 +127,9 @@ function getIndexerCliVscodePath() {
127
127
  function getIndexerCliCodexPath() {
128
128
  return path_1.default.join(getPackageRoot(), "dist", "src", "indexer-cli-codex.js");
129
129
  }
130
+ function getIndexerCliGeminiPath() {
131
+ return path_1.default.join(getPackageRoot(), "dist", "src", "indexer-cli-gemini.js");
132
+ }
130
133
  // ---------------------------------------------------------------------------
131
134
  // Paths — Cursor
132
135
  // ---------------------------------------------------------------------------
@@ -186,6 +189,21 @@ function getCodexSkillDst() {
186
189
  return path_1.default.join(getCodexConfigDir(), "skills", "code-session-memory", "SKILL.md");
187
190
  }
188
191
  // ---------------------------------------------------------------------------
192
+ // Paths — Gemini CLI
193
+ // ---------------------------------------------------------------------------
194
+ function getGeminiConfigDir() {
195
+ const envDir = process.env.GEMINI_CONFIG_DIR;
196
+ if (envDir)
197
+ return envDir;
198
+ return path_1.default.join(os_1.default.homedir(), ".gemini");
199
+ }
200
+ function getGeminiSettingsPath() {
201
+ return path_1.default.join(getGeminiConfigDir(), "settings.json");
202
+ }
203
+ function getGeminiSkillDst() {
204
+ return path_1.default.join(getGeminiConfigDir(), "skills", "code-session-memory", "SKILL.md");
205
+ }
206
+ // ---------------------------------------------------------------------------
189
207
  // Helpers
190
208
  // ---------------------------------------------------------------------------
191
209
  /**
@@ -671,7 +689,7 @@ function installCursorSkill(skillSrc) {
671
689
  const cursorFrontmatter = [
672
690
  "---",
673
691
  "name: code-session-memory",
674
- "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, and Codex. Use this when the user asks about past work, decisions, or implementations.",
692
+ "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, Codex, and Gemini CLI. Use this when the user asks about past work, decisions, or implementations.",
675
693
  "---",
676
694
  "",
677
695
  ].join("\n");
@@ -1054,7 +1072,7 @@ function installCodexSkill(skillSrc) {
1054
1072
  const codexFrontmatter = [
1055
1073
  "---",
1056
1074
  "name: code-session-memory",
1057
- "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, and Codex.",
1075
+ "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, Codex, and Gemini CLI.",
1058
1076
  "---",
1059
1077
  "",
1060
1078
  ].join("\n");
@@ -1076,6 +1094,226 @@ function uninstallCodexSkill() {
1076
1094
  return "done";
1077
1095
  }
1078
1096
  // ---------------------------------------------------------------------------
1097
+ // Gemini CLI — settings.json
1098
+ // ---------------------------------------------------------------------------
1099
+ function parseGeminiSettingsOrEmpty(settingsPath) {
1100
+ if (!fs_1.default.existsSync(settingsPath))
1101
+ return {};
1102
+ try {
1103
+ return JSON.parse(fs_1.default.readFileSync(settingsPath, "utf8"));
1104
+ }
1105
+ catch {
1106
+ throw new Error(`Could not parse existing ${settingsPath} — please check it is valid JSON.`);
1107
+ }
1108
+ }
1109
+ function installGeminiMcpConfig(mcpServerPath) {
1110
+ const settingsPath = getGeminiSettingsPath();
1111
+ const existed = fs_1.default.existsSync(settingsPath);
1112
+ const settings = parseGeminiSettingsOrEmpty(settingsPath);
1113
+ const mcpServersRaw = settings.mcpServers;
1114
+ const mcpServers = mcpServersRaw && typeof mcpServersRaw === "object"
1115
+ ? mcpServersRaw
1116
+ : {};
1117
+ mcpServers["code-session-memory"] = {
1118
+ type: "stdio",
1119
+ command: "node",
1120
+ args: [mcpServerPath],
1121
+ };
1122
+ settings.mcpServers = mcpServers;
1123
+ ensureDir(path_1.default.dirname(settingsPath));
1124
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1125
+ return { settingsPath, existed };
1126
+ }
1127
+ function uninstallGeminiMcpConfig() {
1128
+ const settingsPath = getGeminiSettingsPath();
1129
+ if (!fs_1.default.existsSync(settingsPath))
1130
+ return "not_found";
1131
+ try {
1132
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf8"));
1133
+ if (settings.mcpServers &&
1134
+ typeof settings.mcpServers === "object" &&
1135
+ "code-session-memory" in settings.mcpServers) {
1136
+ delete settings.mcpServers["code-session-memory"];
1137
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1138
+ return "done";
1139
+ }
1140
+ return "not_found";
1141
+ }
1142
+ catch {
1143
+ return "not_found";
1144
+ }
1145
+ }
1146
+ function checkGeminiMcpConfigured() {
1147
+ const settingsPath = getGeminiSettingsPath();
1148
+ try {
1149
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf8"));
1150
+ return !!(settings.mcpServers &&
1151
+ typeof settings.mcpServers === "object" &&
1152
+ "code-session-memory" in settings.mcpServers);
1153
+ }
1154
+ catch {
1155
+ return false;
1156
+ }
1157
+ }
1158
+ function installGeminiHook(indexerCliGeminiPath) {
1159
+ const settingsPath = getGeminiSettingsPath();
1160
+ const existed = fs_1.default.existsSync(settingsPath);
1161
+ const settings = parseGeminiSettingsOrEmpty(settingsPath);
1162
+ const hooksRaw = settings.hooks;
1163
+ const hooks = hooksRaw && typeof hooksRaw === "object"
1164
+ ? hooksRaw
1165
+ : {};
1166
+ const afterAgentRaw = Array.isArray(hooks.AfterAgent) ? hooks.AfterAgent : [];
1167
+ const cleanedGroups = [];
1168
+ for (const entry of afterAgentRaw) {
1169
+ if (!entry || typeof entry !== "object")
1170
+ continue;
1171
+ const group = entry;
1172
+ if (!Array.isArray(group.hooks))
1173
+ continue;
1174
+ const filteredHooks = group.hooks.filter((h) => {
1175
+ if (!h || typeof h !== "object")
1176
+ return true;
1177
+ const hook = h;
1178
+ return typeof hook.command !== "string" || !hook.command.includes("indexer-cli-gemini");
1179
+ });
1180
+ if (filteredHooks.length > 0) {
1181
+ cleanedGroups.push({ ...group, hooks: filteredHooks });
1182
+ }
1183
+ }
1184
+ cleanedGroups.push({
1185
+ hooks: [
1186
+ {
1187
+ type: "command",
1188
+ name: "code-session-memory-indexer",
1189
+ command: `node ${indexerCliGeminiPath}`,
1190
+ },
1191
+ ],
1192
+ });
1193
+ hooks.AfterAgent = cleanedGroups;
1194
+ settings.hooks = hooks;
1195
+ ensureDir(path_1.default.dirname(settingsPath));
1196
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1197
+ return { settingsPath, existed };
1198
+ }
1199
+ function uninstallGeminiHook() {
1200
+ const settingsPath = getGeminiSettingsPath();
1201
+ if (!fs_1.default.existsSync(settingsPath))
1202
+ return "not_found";
1203
+ try {
1204
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf8"));
1205
+ const hooks = settings.hooks;
1206
+ const afterAgent = hooks?.AfterAgent;
1207
+ if (!Array.isArray(afterAgent))
1208
+ return "not_found";
1209
+ let removed = false;
1210
+ const filteredGroups = [];
1211
+ for (const entry of afterAgent) {
1212
+ if (!entry || typeof entry !== "object")
1213
+ continue;
1214
+ const group = entry;
1215
+ // Legacy shape support: { command: "..." }
1216
+ if (typeof group.command === "string") {
1217
+ if (group.command.includes("indexer-cli-gemini")) {
1218
+ removed = true;
1219
+ continue;
1220
+ }
1221
+ filteredGroups.push(group);
1222
+ continue;
1223
+ }
1224
+ if (!Array.isArray(group.hooks)) {
1225
+ filteredGroups.push(group);
1226
+ continue;
1227
+ }
1228
+ const filteredHooks = group.hooks.filter((h) => {
1229
+ if (!h || typeof h !== "object")
1230
+ return true;
1231
+ const hook = h;
1232
+ const keep = typeof hook.command !== "string" || !hook.command.includes("indexer-cli-gemini");
1233
+ if (!keep)
1234
+ removed = true;
1235
+ return keep;
1236
+ });
1237
+ if (filteredHooks.length > 0) {
1238
+ filteredGroups.push({ ...group, hooks: filteredHooks });
1239
+ }
1240
+ }
1241
+ if (!removed)
1242
+ return "not_found";
1243
+ hooks.AfterAgent = filteredGroups;
1244
+ settings.hooks = hooks;
1245
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1246
+ return "done";
1247
+ }
1248
+ catch {
1249
+ return "not_found";
1250
+ }
1251
+ }
1252
+ function checkGeminiHookInstalled() {
1253
+ const settingsPath = getGeminiSettingsPath();
1254
+ try {
1255
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, "utf8"));
1256
+ const hooks = settings.hooks;
1257
+ const afterAgent = hooks?.AfterAgent;
1258
+ if (!Array.isArray(afterAgent))
1259
+ return false;
1260
+ return afterAgent.some((entry) => {
1261
+ if (!entry || typeof entry !== "object")
1262
+ return false;
1263
+ const group = entry;
1264
+ // Legacy shape support: { command: "..." }
1265
+ if (typeof group.command === "string" && group.command.includes("indexer-cli-gemini")) {
1266
+ return true;
1267
+ }
1268
+ if (!Array.isArray(group.hooks))
1269
+ return false;
1270
+ return group.hooks.some((h) => {
1271
+ if (!h || typeof h !== "object")
1272
+ return false;
1273
+ const hook = h;
1274
+ return typeof hook.command === "string" && hook.command.includes("indexer-cli-gemini");
1275
+ });
1276
+ });
1277
+ }
1278
+ catch {
1279
+ return false;
1280
+ }
1281
+ }
1282
+ function installGeminiSkill(skillSrc) {
1283
+ const dstPath = getGeminiSkillDst();
1284
+ const existed = fs_1.default.existsSync(dstPath);
1285
+ if (!fs_1.default.existsSync(skillSrc)) {
1286
+ throw new Error(`Skill source not found: ${skillSrc}\nDid you run "npm run build" first?`);
1287
+ }
1288
+ const skillBody = fs_1.default.readFileSync(skillSrc, "utf8");
1289
+ const bodyWithoutFrontmatter = skillBody
1290
+ .replace(/^---[\s\S]*?---\s*\n?/, "")
1291
+ .trimStart();
1292
+ const geminiFrontmatter = [
1293
+ "---",
1294
+ "name: code-session-memory",
1295
+ "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, Codex, and Gemini CLI.",
1296
+ "---",
1297
+ "",
1298
+ ].join("\n");
1299
+ ensureDir(path_1.default.dirname(dstPath));
1300
+ fs_1.default.writeFileSync(dstPath, geminiFrontmatter + bodyWithoutFrontmatter, "utf8");
1301
+ return { dstPath, existed };
1302
+ }
1303
+ function uninstallGeminiSkill() {
1304
+ const dstPath = getGeminiSkillDst();
1305
+ if (!fs_1.default.existsSync(dstPath))
1306
+ return "not_found";
1307
+ fs_1.default.unlinkSync(dstPath);
1308
+ try {
1309
+ const dir = path_1.default.dirname(dstPath);
1310
+ if (fs_1.default.readdirSync(dir).length === 0)
1311
+ fs_1.default.rmdirSync(dir);
1312
+ }
1313
+ catch { /* ignore */ }
1314
+ return "done";
1315
+ }
1316
+ // ---------------------------------------------------------------------------
1079
1317
  // Formatting helpers
1080
1318
  // ---------------------------------------------------------------------------
1081
1319
  function bold(s) { return `\x1b[1m${s}\x1b[0m`; }
@@ -1101,6 +1339,9 @@ function isVscodeInstalled() {
1101
1339
  function isCodexInstalled() {
1102
1340
  return fs_1.default.existsSync(getCodexConfigDir());
1103
1341
  }
1342
+ function isGeminiInstalled() {
1343
+ return fs_1.default.existsSync(getGeminiConfigDir());
1344
+ }
1104
1345
  function step(label, fn) {
1105
1346
  process.stdout.write(` ${label}... `);
1106
1347
  try {
@@ -1131,11 +1372,13 @@ function install() {
1131
1372
  const indexerCursorPath = getIndexerCliCursorPath();
1132
1373
  const indexerVscodePath = getIndexerCliVscodePath();
1133
1374
  const indexerCodexPath = getIndexerCliCodexPath();
1375
+ const indexerGeminiPath = getIndexerCliGeminiPath();
1134
1376
  const openCodeInstalled = isOpenCodeInstalled();
1135
1377
  const claudeInstalled = isClaudeCodeInstalled();
1136
1378
  const cursorInstalled = isCursorInstalled();
1137
1379
  const vscodeInstalled = isVscodeInstalled();
1138
1380
  const codexInstalled = isCodexInstalled();
1381
+ const geminiInstalled = isGeminiInstalled();
1139
1382
  // 1. DB
1140
1383
  step("Initialising database", () => {
1141
1384
  ensureDir(path_1.default.dirname(dbPath));
@@ -1210,6 +1453,19 @@ function install() {
1210
1453
  const { dstPath, existed } = installCodexSkill(getSkillSrc());
1211
1454
  return `${existed ? "updated" : "created"} ${dstPath}`;
1212
1455
  });
1456
+ // Gemini CLI
1457
+ stepIf(geminiInstalled, "Configuring Gemini CLI MCP server", () => {
1458
+ const { settingsPath, existed } = installGeminiMcpConfig(mcpPath);
1459
+ return `${existed ? "updated" : "created"} ${settingsPath}`;
1460
+ });
1461
+ stepIf(geminiInstalled, "Installing Gemini CLI AfterAgent hook", () => {
1462
+ const { settingsPath, existed } = installGeminiHook(indexerGeminiPath);
1463
+ return `${existed ? "updated" : "created"} ${settingsPath}`;
1464
+ });
1465
+ stepIf(geminiInstalled, "Installing Gemini CLI skill", () => {
1466
+ const { dstPath, existed } = installGeminiSkill(getSkillSrc());
1467
+ return `${existed ? "updated" : "created"} ${dstPath}`;
1468
+ });
1213
1469
  console.log(`
1214
1470
  ${bold("Installation complete!")}
1215
1471
 
@@ -1218,10 +1474,11 @@ ${bold("Required environment variable:")}
1218
1474
 
1219
1475
  ${bold("Default DB path:")} ${dbPath}
1220
1476
 
1221
- Restart ${bold("OpenCode")}, ${bold("Claude Code")}, ${bold("Cursor")}, ${bold("VS Code")}, and ${bold("Codex")} to activate.
1477
+ Restart ${bold("OpenCode")}, ${bold("Claude Code")}, ${bold("Cursor")}, ${bold("VS Code")}, ${bold("Codex")}, and ${bold("Gemini CLI")} to activate.
1222
1478
 
1223
1479
  ${bold("VS Code note:")} Ensure ${bold("Chat: Use Hooks")} is enabled in VS Code settings.
1224
1480
  ${bold("Codex note:")} The notify hook and OPENAI_API_KEY passthrough are set in ${dim(getCodexConfigPath())}.
1481
+ ${bold("Gemini CLI note:")} The AfterAgent hook is configured in ${dim(getGeminiSettingsPath())}.
1225
1482
  Run ${bold("npx code-session-memory status")} to verify.
1226
1483
  `);
1227
1484
  }
@@ -1234,6 +1491,7 @@ function status() {
1234
1491
  const cursorInstalled = isCursorInstalled();
1235
1492
  const vscodeInstalled = isVscodeInstalled();
1236
1493
  const codexInstalled = isCodexInstalled();
1494
+ const geminiInstalled = isGeminiInstalled();
1237
1495
  if (openCodeInstalled) {
1238
1496
  console.log(bold(" OpenCode"));
1239
1497
  console.log(` ${ok(fs_1.default.existsSync(getOpenCodePluginDst()))} Plugin ${dim(getOpenCodePluginDst())}`);
@@ -1280,6 +1538,15 @@ function status() {
1280
1538
  else {
1281
1539
  console.log(bold("\n Codex") + dim(" (not installed — skipped)"));
1282
1540
  }
1541
+ if (geminiInstalled) {
1542
+ console.log(bold("\n Gemini CLI"));
1543
+ console.log(` ${ok(checkGeminiMcpConfigured())} MCP config ${dim(getGeminiSettingsPath())}`);
1544
+ console.log(` ${ok(checkGeminiHookInstalled())} AfterAgent ${dim(getGeminiSettingsPath())}`);
1545
+ console.log(` ${ok(fs_1.default.existsSync(getGeminiSkillDst()))} Skill ${dim(getGeminiSkillDst())}`);
1546
+ }
1547
+ else {
1548
+ console.log(bold("\n Gemini CLI") + dim(" (not installed — skipped)"));
1549
+ }
1283
1550
  console.log(bold("\n Shared"));
1284
1551
  console.log(` ${ok(fs_1.default.existsSync(mcpPath))} MCP server ${dim(mcpPath)}`);
1285
1552
  console.log(` ${ok(fs_1.default.existsSync(dbPath))} Database ${dim(dbPath)}`);
@@ -1319,6 +1586,9 @@ function status() {
1319
1586
  checkCodexOpenAiPassthroughConfigured() &&
1320
1587
  checkCodexHookInstalled() &&
1321
1588
  fs_1.default.existsSync(getCodexSkillDst()))) &&
1589
+ (!geminiInstalled || (checkGeminiMcpConfigured() &&
1590
+ checkGeminiHookInstalled() &&
1591
+ fs_1.default.existsSync(getGeminiSkillDst()))) &&
1322
1592
  fs_1.default.existsSync(mcpPath) &&
1323
1593
  fs_1.default.existsSync(dbPath);
1324
1594
  console.log(`\n ${allOk
@@ -1394,6 +1664,18 @@ function uninstall() {
1394
1664
  if (uninstallCodexSkill() === "not_found")
1395
1665
  throw new Error("not found");
1396
1666
  }],
1667
+ ["Gemini CLI MCP config", () => {
1668
+ if (uninstallGeminiMcpConfig() === "not_found")
1669
+ throw new Error("not found");
1670
+ }],
1671
+ ["Gemini CLI AfterAgent hook", () => {
1672
+ if (uninstallGeminiHook() === "not_found")
1673
+ throw new Error("not found");
1674
+ }],
1675
+ ["Gemini CLI skill", () => {
1676
+ if (uninstallGeminiSkill() === "not_found")
1677
+ throw new Error("not found");
1678
+ }],
1397
1679
  ];
1398
1680
  for (const [label, fn] of items) {
1399
1681
  process.stdout.write(` Removing ${label}... `);
@@ -1448,7 +1730,7 @@ async function resetDb() {
1448
1730
  }
1449
1731
  function help() {
1450
1732
  console.log(`
1451
- ${bold("code-session-memory")} — Shared vector memory for OpenCode, Claude Code, Cursor, VS Code, and Codex sessions
1733
+ ${bold("code-session-memory")} — Shared vector memory for OpenCode, Claude Code, Cursor, VS Code, Codex, and Gemini CLI sessions
1452
1734
 
1453
1735
  ${bold("Usage:")}
1454
1736
  npx code-session-memory install Install components for detected tools
@@ -1456,7 +1738,7 @@ ${bold("Usage:")}
1456
1738
  npx code-session-memory uninstall Remove all installed components (keeps DB)
1457
1739
  npx code-session-memory reset-db Delete all indexed data (keeps installation)
1458
1740
  npx code-session-memory query <text> Semantic search across all indexed sessions
1459
- npx code-session-memory query <text> --source <s> Filter by source (opencode, claude-code, cursor, vscode, codex)
1741
+ npx code-session-memory query <text> --source <s> Filter by source (opencode, claude-code, cursor, vscode, codex, gemini-cli)
1460
1742
  npx code-session-memory query <text> --limit <n> Max results (default: 5)
1461
1743
  npx code-session-memory query <text> --from <date> Results from date (e.g. 2026-02-01)
1462
1744
  npx code-session-memory query <text> --to <date> Results up to date (e.g. 2026-02-20)
@@ -1475,6 +1757,7 @@ ${bold("Environment variables:")}
1475
1757
  CURSOR_CONFIG_DIR Override the Cursor config directory (~/.cursor)
1476
1758
  VSCODE_CONFIG_DIR Override the VS Code config directory
1477
1759
  CODEX_HOME Override the Codex home directory (~/.codex)
1760
+ GEMINI_CONFIG_DIR Override the Gemini CLI config directory (~/.gemini)
1478
1761
  `);
1479
1762
  }
1480
1763
  // ---------------------------------------------------------------------------