code-session-memory 0.8.1 → 0.10.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.
Files changed (36) hide show
  1. package/README.md +59 -17
  2. package/dist/mcp/index.d.ts +1 -1
  3. package/dist/mcp/index.js +15 -13
  4. package/dist/mcp/index.js.map +1 -1
  5. package/dist/src/cli-query.d.ts +1 -0
  6. package/dist/src/cli-query.d.ts.map +1 -1
  7. package/dist/src/cli-query.js +8 -3
  8. package/dist/src/cli-query.js.map +1 -1
  9. package/dist/src/cli-sessions.d.ts.map +1 -1
  10. package/dist/src/cli-sessions.js +8 -0
  11. package/dist/src/cli-sessions.js.map +1 -1
  12. package/dist/src/cli.d.ts +1 -1
  13. package/dist/src/cli.js +608 -44
  14. package/dist/src/cli.js.map +1 -1
  15. package/dist/src/codex-session-to-messages.d.ts +22 -0
  16. package/dist/src/codex-session-to-messages.d.ts.map +1 -0
  17. package/dist/src/codex-session-to-messages.js +212 -0
  18. package/dist/src/codex-session-to-messages.js.map +1 -0
  19. package/dist/src/indexer-cli-codex.d.ts +12 -0
  20. package/dist/src/indexer-cli-codex.d.ts.map +1 -0
  21. package/dist/src/indexer-cli-codex.js +126 -0
  22. package/dist/src/indexer-cli-codex.js.map +1 -0
  23. package/dist/src/indexer-cli-vscode.d.ts +14 -0
  24. package/dist/src/indexer-cli-vscode.d.ts.map +1 -0
  25. package/dist/src/indexer-cli-vscode.js +88 -0
  26. package/dist/src/indexer-cli-vscode.js.map +1 -0
  27. package/dist/src/indexer.d.ts +1 -1
  28. package/dist/src/indexer.js +1 -1
  29. package/dist/src/types.d.ts +1 -1
  30. package/dist/src/types.d.ts.map +1 -1
  31. package/dist/src/vscode-transcript-to-messages.d.ts +41 -0
  32. package/dist/src/vscode-transcript-to-messages.d.ts.map +1 -0
  33. package/dist/src/vscode-transcript-to-messages.js +167 -0
  34. package/dist/src/vscode-transcript-to-messages.js.map +1 -0
  35. package/package.json +6 -3
  36. package/skill/memory.md +10 -4
package/dist/src/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * code-session-memory CLI
5
5
  *
6
6
  * Usage:
7
- * npx code-session-memory install — install for OpenCode + Claude Code
7
+ * npx code-session-memory install — install for all detected supported tools
8
8
  * npx code-session-memory status — show installation status
9
9
  * npx code-session-memory uninstall — remove all installed components
10
10
  * npx code-session-memory reset-db — wipe the database (with confirmation)
@@ -51,6 +51,7 @@ const fs_1 = __importDefault(require("fs"));
51
51
  const path_1 = __importDefault(require("path"));
52
52
  const os_1 = __importDefault(require("os"));
53
53
  const clack = __importStar(require("@clack/prompts"));
54
+ const smol_toml_1 = require("smol-toml");
54
55
  const database_1 = require("./database");
55
56
  const cli_sessions_1 = require("./cli-sessions");
56
57
  const cli_query_1 = require("./cli-query");
@@ -120,6 +121,12 @@ function getIndexerCliClaudePath() {
120
121
  function getIndexerCliCursorPath() {
121
122
  return path_1.default.join(getPackageRoot(), "dist", "src", "indexer-cli-cursor.js");
122
123
  }
124
+ function getIndexerCliVscodePath() {
125
+ return path_1.default.join(getPackageRoot(), "dist", "src", "indexer-cli-vscode.js");
126
+ }
127
+ function getIndexerCliCodexPath() {
128
+ return path_1.default.join(getPackageRoot(), "dist", "src", "indexer-cli-codex.js");
129
+ }
123
130
  // ---------------------------------------------------------------------------
124
131
  // Paths — Cursor
125
132
  // ---------------------------------------------------------------------------
@@ -142,8 +149,59 @@ function getCursorSkillDst() {
142
149
  return path_1.default.join(getCursorConfigDir(), "skills", "code-session-memory", "SKILL.md");
143
150
  }
144
151
  // ---------------------------------------------------------------------------
152
+ // Paths — VS Code
153
+ // ---------------------------------------------------------------------------
154
+ function getVscodeConfigDir() {
155
+ const envDir = process.env.VSCODE_CONFIG_DIR;
156
+ if (envDir)
157
+ return envDir;
158
+ if (process.platform === "darwin") {
159
+ return path_1.default.join(os_1.default.homedir(), "Library", "Application Support", "Code", "User");
160
+ }
161
+ // Linux (and fallback)
162
+ return path_1.default.join(os_1.default.homedir(), ".config", "Code", "User");
163
+ }
164
+ function getVscodeSettingsPath() {
165
+ return path_1.default.join(getVscodeConfigDir(), "settings.json");
166
+ }
167
+ function getVscodeMcpConfigPath() {
168
+ return path_1.default.join(getVscodeConfigDir(), "mcp.json");
169
+ }
170
+ function getVscodeHooksPath() {
171
+ return path_1.default.join(getVscodeConfigDir(), "hooks", "code-session-memory.json");
172
+ }
173
+ // ---------------------------------------------------------------------------
174
+ // Paths — Codex
175
+ // ---------------------------------------------------------------------------
176
+ function getCodexConfigDir() {
177
+ const envDir = process.env.CODEX_HOME;
178
+ if (envDir)
179
+ return envDir;
180
+ return path_1.default.join(os_1.default.homedir(), ".codex");
181
+ }
182
+ function getCodexConfigPath() {
183
+ return path_1.default.join(getCodexConfigDir(), "config.toml");
184
+ }
185
+ function getCodexSkillDst() {
186
+ return path_1.default.join(getCodexConfigDir(), "skills", "code-session-memory", "SKILL.md");
187
+ }
188
+ // ---------------------------------------------------------------------------
145
189
  // Helpers
146
190
  // ---------------------------------------------------------------------------
191
+ /**
192
+ * Parse a JSONC string (JSON with comments and trailing commas).
193
+ * VS Code's settings.json uses JSONC, so we need this to read it safely.
194
+ */
195
+ function parseJsonc(text) {
196
+ // Remove single-line comments (// ...)
197
+ // Remove multi-line comments (/* ... */)
198
+ // Remove trailing commas before } or ]
199
+ const stripped = text
200
+ .replace(/\/\/[^\n]*/g, "")
201
+ .replace(/\/\*[\s\S]*?\*\//g, "")
202
+ .replace(/,(\s*[}\]])/g, "$1");
203
+ return JSON.parse(stripped);
204
+ }
147
205
  function ensureDir(dir) {
148
206
  fs_1.default.mkdirSync(dir, { recursive: true });
149
207
  }
@@ -613,7 +671,7 @@ function installCursorSkill(skillSrc) {
613
671
  const cursorFrontmatter = [
614
672
  "---",
615
673
  "name: code-session-memory",
616
- "description: Search past AI coding sessions semantically across OpenCode, Claude Code, and Cursor. Use this when the user asks about past work, decisions, or implementations.",
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.",
617
675
  "---",
618
676
  "",
619
677
  ].join("\n");
@@ -639,6 +697,385 @@ function uninstallCursorSkill() {
639
697
  return "done";
640
698
  }
641
699
  // ---------------------------------------------------------------------------
700
+ // VS Code — hook
701
+ // ---------------------------------------------------------------------------
702
+ /**
703
+ * Installs the VS Code Stop hook at ~/.vscode/hooks/code-session-memory.json
704
+ * using the Copilot hook format.
705
+ */
706
+ function installVscodeHook(indexerCliVscodePath) {
707
+ const hooksPath = getVscodeHooksPath();
708
+ const existed = fs_1.default.existsSync(hooksPath);
709
+ // Always overwrite with our hook config (this file is owned by us)
710
+ const config = {
711
+ hooks: {
712
+ Stop: [
713
+ {
714
+ type: "command",
715
+ command: `node ${indexerCliVscodePath}`,
716
+ },
717
+ ],
718
+ },
719
+ };
720
+ ensureDir(path_1.default.dirname(hooksPath));
721
+ fs_1.default.writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf8");
722
+ return { hooksPath, existed };
723
+ }
724
+ /**
725
+ * Removes the VS Code Stop hook file.
726
+ */
727
+ function uninstallVscodeHook() {
728
+ const hooksPath = getVscodeHooksPath();
729
+ if (!fs_1.default.existsSync(hooksPath))
730
+ return "not_found";
731
+ fs_1.default.unlinkSync(hooksPath);
732
+ // Remove the directory if empty
733
+ try {
734
+ const dir = path_1.default.dirname(hooksPath);
735
+ if (fs_1.default.readdirSync(dir).length === 0)
736
+ fs_1.default.rmdirSync(dir);
737
+ }
738
+ catch { /* ignore */ }
739
+ return "done";
740
+ }
741
+ function checkVscodeHookInstalled() {
742
+ const hooksPath = getVscodeHooksPath();
743
+ try {
744
+ const config = JSON.parse(fs_1.default.readFileSync(hooksPath, "utf8"));
745
+ const stop = config.hooks?.Stop;
746
+ if (!Array.isArray(stop))
747
+ return false;
748
+ return stop.some((entry) => {
749
+ if (!entry || typeof entry !== "object")
750
+ return false;
751
+ const e = entry;
752
+ return typeof e.command === "string" && e.command.includes("indexer-cli-vscode");
753
+ });
754
+ }
755
+ catch {
756
+ return false;
757
+ }
758
+ }
759
+ // ---------------------------------------------------------------------------
760
+ // VS Code — hook location registration
761
+ // ---------------------------------------------------------------------------
762
+ /**
763
+ * Returns the VS Code hooks path as a ~-prefixed string.
764
+ * VS Code supports ~ in hookFilesLocations, which improves portability across
765
+ * machines and avoids VS Code showing the fully-expanded path as an error.
766
+ */
767
+ function getVscodeHooksPathTilde() {
768
+ const hooksPath = getVscodeHooksPath();
769
+ const home = os_1.default.homedir();
770
+ if (hooksPath.startsWith(home + path_1.default.sep)) {
771
+ return "~" + hooksPath.slice(home.length);
772
+ }
773
+ return hooksPath;
774
+ }
775
+ /**
776
+ * Adds the hook file path to VS Code's settings.json under
777
+ * `chat.hookFilesLocations` so VS Code discovers our hook.
778
+ * Uses a ~-prefixed path for portability.
779
+ */
780
+ function installVscodeHookLocation() {
781
+ const settingsPath = getVscodeSettingsPath();
782
+ const existed = fs_1.default.existsSync(settingsPath);
783
+ let settings = {};
784
+ if (existed) {
785
+ try {
786
+ settings = parseJsonc(fs_1.default.readFileSync(settingsPath, "utf8"));
787
+ }
788
+ catch {
789
+ throw new Error(`Could not parse existing ${settingsPath} — please check it is valid JSON.`);
790
+ }
791
+ }
792
+ const hookLocations = (settings["chat.hookFilesLocations"] ?? {});
793
+ // Remove any previously installed absolute path entry (migration)
794
+ const absolutePath = getVscodeHooksPath();
795
+ if (absolutePath in hookLocations) {
796
+ delete hookLocations[absolutePath];
797
+ }
798
+ hookLocations[getVscodeHooksPathTilde()] = true;
799
+ settings["chat.hookFilesLocations"] = hookLocations;
800
+ ensureDir(path_1.default.dirname(settingsPath));
801
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
802
+ return { settingsPath, existed };
803
+ }
804
+ /**
805
+ * Removes our hook file path from VS Code's `chat.hookFilesLocations` setting.
806
+ * Handles both ~ and absolute path variants.
807
+ */
808
+ function uninstallVscodeHookLocation() {
809
+ const settingsPath = getVscodeSettingsPath();
810
+ if (!fs_1.default.existsSync(settingsPath))
811
+ return "not_found";
812
+ try {
813
+ const settings = parseJsonc(fs_1.default.readFileSync(settingsPath, "utf8"));
814
+ const hookLocations = settings["chat.hookFilesLocations"];
815
+ if (!hookLocations)
816
+ return "not_found";
817
+ const tildePath = getVscodeHooksPathTilde();
818
+ const absolutePath = getVscodeHooksPath();
819
+ const foundTilde = tildePath in hookLocations;
820
+ const foundAbsolute = absolutePath in hookLocations;
821
+ if (!foundTilde && !foundAbsolute)
822
+ return "not_found";
823
+ if (foundTilde)
824
+ delete hookLocations[tildePath];
825
+ if (foundAbsolute)
826
+ delete hookLocations[absolutePath];
827
+ settings["chat.hookFilesLocations"] = hookLocations;
828
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
829
+ return "done";
830
+ }
831
+ catch {
832
+ return "not_found";
833
+ }
834
+ }
835
+ function checkVscodeHookLocationRegistered() {
836
+ const settingsPath = getVscodeSettingsPath();
837
+ try {
838
+ const settings = parseJsonc(fs_1.default.readFileSync(settingsPath, "utf8"));
839
+ const hookLocations = settings["chat.hookFilesLocations"];
840
+ if (!hookLocations)
841
+ return false;
842
+ return hookLocations[getVscodeHooksPathTilde()] === true ||
843
+ hookLocations[getVscodeHooksPath()] === true;
844
+ }
845
+ catch {
846
+ return false;
847
+ }
848
+ }
849
+ // ---------------------------------------------------------------------------
850
+ // VS Code — MCP config
851
+ // ---------------------------------------------------------------------------
852
+ /**
853
+ * Merges the code-session-memory MCP entry into VS Code's mcp.json.
854
+ */
855
+ function installVscodeMcpConfig(mcpServerPath) {
856
+ const configPath = getVscodeMcpConfigPath();
857
+ const existed = fs_1.default.existsSync(configPath);
858
+ let config = {};
859
+ if (existed) {
860
+ try {
861
+ config = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
862
+ }
863
+ catch {
864
+ throw new Error(`Could not parse existing ${configPath} — please check it is valid JSON.`);
865
+ }
866
+ }
867
+ if (!config.servers || typeof config.servers !== "object")
868
+ config.servers = {};
869
+ config.servers["code-session-memory"] = {
870
+ type: "stdio",
871
+ command: "node",
872
+ args: [mcpServerPath],
873
+ };
874
+ ensureDir(path_1.default.dirname(configPath));
875
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
876
+ return { configPath, existed };
877
+ }
878
+ /**
879
+ * Removes the code-session-memory MCP entry from VS Code's mcp.json.
880
+ */
881
+ function uninstallVscodeMcpConfig() {
882
+ const configPath = getVscodeMcpConfigPath();
883
+ if (!fs_1.default.existsSync(configPath))
884
+ return "not_found";
885
+ try {
886
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
887
+ if (config.servers &&
888
+ typeof config.servers === "object" &&
889
+ "code-session-memory" in config.servers) {
890
+ delete config.servers["code-session-memory"];
891
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
892
+ return "done";
893
+ }
894
+ return "not_found";
895
+ }
896
+ catch {
897
+ return "not_found";
898
+ }
899
+ }
900
+ function checkVscodeMcpConfigured() {
901
+ const configPath = getVscodeMcpConfigPath();
902
+ try {
903
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, "utf8"));
904
+ return !!(config.servers &&
905
+ typeof config.servers === "object" &&
906
+ "code-session-memory" in config.servers);
907
+ }
908
+ catch {
909
+ return false;
910
+ }
911
+ }
912
+ // ---------------------------------------------------------------------------
913
+ // Codex — config.toml (TOML)
914
+ // ---------------------------------------------------------------------------
915
+ function parseCodexConfigOrEmpty(configPath) {
916
+ if (!fs_1.default.existsSync(configPath))
917
+ return {};
918
+ try {
919
+ return (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
920
+ }
921
+ catch {
922
+ throw new Error(`Could not parse existing ${configPath} — please check it is valid TOML.`);
923
+ }
924
+ }
925
+ function mergeCodexEnvVarsPassthrough(existing) {
926
+ const values = Array.isArray(existing)
927
+ ? existing.filter((v) => typeof v === "string" && v.trim().length > 0)
928
+ : [];
929
+ if (!values.includes("OPENAI_API_KEY"))
930
+ values.push("OPENAI_API_KEY");
931
+ return values;
932
+ }
933
+ function installCodexMcpConfig(mcpServerPath) {
934
+ const configPath = getCodexConfigPath();
935
+ const existed = fs_1.default.existsSync(configPath);
936
+ const config = parseCodexConfigOrEmpty(configPath);
937
+ const mcpServersRaw = config.mcp_servers;
938
+ const mcpServers = mcpServersRaw && typeof mcpServersRaw === "object"
939
+ ? mcpServersRaw
940
+ : {};
941
+ const existingServer = mcpServers["code-session-memory"];
942
+ const serverConfig = existingServer && typeof existingServer === "object"
943
+ ? existingServer
944
+ : {};
945
+ mcpServers["code-session-memory"] = {
946
+ ...serverConfig,
947
+ command: "node",
948
+ args: [mcpServerPath],
949
+ // Codex MCP servers run with a restricted environment by default.
950
+ // Pass-through env vars are configured via env_vars (env is a map of fixed values).
951
+ env_vars: mergeCodexEnvVarsPassthrough(serverConfig.env_vars),
952
+ };
953
+ config.mcp_servers = mcpServers;
954
+ ensureDir(path_1.default.dirname(configPath));
955
+ fs_1.default.writeFileSync(configPath, (0, smol_toml_1.stringify)(config) + "\n", "utf8");
956
+ return { configPath, existed };
957
+ }
958
+ function uninstallCodexMcpConfig() {
959
+ const configPath = getCodexConfigPath();
960
+ if (!fs_1.default.existsSync(configPath))
961
+ return "not_found";
962
+ try {
963
+ const config = (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
964
+ const mcpServers = config.mcp_servers;
965
+ if (!mcpServers || !(Object.prototype.hasOwnProperty.call(mcpServers, "code-session-memory"))) {
966
+ return "not_found";
967
+ }
968
+ delete mcpServers["code-session-memory"];
969
+ fs_1.default.writeFileSync(configPath, (0, smol_toml_1.stringify)(config) + "\n", "utf8");
970
+ return "done";
971
+ }
972
+ catch {
973
+ return "not_found";
974
+ }
975
+ }
976
+ function checkCodexMcpConfigured() {
977
+ const configPath = getCodexConfigPath();
978
+ try {
979
+ const config = (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
980
+ const mcpServers = config.mcp_servers;
981
+ return !!(mcpServers && Object.prototype.hasOwnProperty.call(mcpServers, "code-session-memory"));
982
+ }
983
+ catch {
984
+ return false;
985
+ }
986
+ }
987
+ function checkCodexOpenAiPassthroughConfigured() {
988
+ const configPath = getCodexConfigPath();
989
+ try {
990
+ const config = (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
991
+ const mcpServers = config.mcp_servers;
992
+ const server = mcpServers?.["code-session-memory"];
993
+ if (!server || typeof server !== "object")
994
+ return false;
995
+ const envVars = server.env_vars;
996
+ return Array.isArray(envVars) && envVars.includes("OPENAI_API_KEY");
997
+ }
998
+ catch {
999
+ return false;
1000
+ }
1001
+ }
1002
+ function installCodexHook(indexerCliCodexPath) {
1003
+ const configPath = getCodexConfigPath();
1004
+ const existed = fs_1.default.existsSync(configPath);
1005
+ const config = parseCodexConfigOrEmpty(configPath);
1006
+ config.notify = ["node", indexerCliCodexPath];
1007
+ ensureDir(path_1.default.dirname(configPath));
1008
+ fs_1.default.writeFileSync(configPath, (0, smol_toml_1.stringify)(config) + "\n", "utf8");
1009
+ return { configPath, existed };
1010
+ }
1011
+ function uninstallCodexHook() {
1012
+ const configPath = getCodexConfigPath();
1013
+ if (!fs_1.default.existsSync(configPath))
1014
+ return "not_found";
1015
+ try {
1016
+ const config = (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
1017
+ const notify = config.notify;
1018
+ if (!Array.isArray(notify))
1019
+ return "not_found";
1020
+ const hasOurHook = notify.some((v) => typeof v === "string" && v.includes("indexer-cli-codex"));
1021
+ if (!hasOurHook)
1022
+ return "not_found";
1023
+ delete config.notify;
1024
+ fs_1.default.writeFileSync(configPath, (0, smol_toml_1.stringify)(config) + "\n", "utf8");
1025
+ return "done";
1026
+ }
1027
+ catch {
1028
+ return "not_found";
1029
+ }
1030
+ }
1031
+ function checkCodexHookInstalled() {
1032
+ const configPath = getCodexConfigPath();
1033
+ try {
1034
+ const config = (0, smol_toml_1.parse)(fs_1.default.readFileSync(configPath, "utf8"));
1035
+ const notify = config.notify;
1036
+ if (!Array.isArray(notify))
1037
+ return false;
1038
+ return notify.some((v) => typeof v === "string" && v.includes("indexer-cli-codex"));
1039
+ }
1040
+ catch {
1041
+ return false;
1042
+ }
1043
+ }
1044
+ function installCodexSkill(skillSrc) {
1045
+ const dstPath = getCodexSkillDst();
1046
+ const existed = fs_1.default.existsSync(dstPath);
1047
+ if (!fs_1.default.existsSync(skillSrc)) {
1048
+ throw new Error(`Skill source not found: ${skillSrc}\nDid you run "npm run build" first?`);
1049
+ }
1050
+ const skillBody = fs_1.default.readFileSync(skillSrc, "utf8");
1051
+ const bodyWithoutFrontmatter = skillBody
1052
+ .replace(/^---[\s\S]*?---\s*\n?/, "")
1053
+ .trimStart();
1054
+ const codexFrontmatter = [
1055
+ "---",
1056
+ "name: code-session-memory",
1057
+ "description: Search past AI coding sessions semantically across OpenCode, Claude Code, Cursor, VS Code, and Codex.",
1058
+ "---",
1059
+ "",
1060
+ ].join("\n");
1061
+ ensureDir(path_1.default.dirname(dstPath));
1062
+ fs_1.default.writeFileSync(dstPath, codexFrontmatter + bodyWithoutFrontmatter, "utf8");
1063
+ return { dstPath, existed };
1064
+ }
1065
+ function uninstallCodexSkill() {
1066
+ const dstPath = getCodexSkillDst();
1067
+ if (!fs_1.default.existsSync(dstPath))
1068
+ return "not_found";
1069
+ fs_1.default.unlinkSync(dstPath);
1070
+ try {
1071
+ const dir = path_1.default.dirname(dstPath);
1072
+ if (fs_1.default.readdirSync(dir).length === 0)
1073
+ fs_1.default.rmdirSync(dir);
1074
+ }
1075
+ catch { /* ignore */ }
1076
+ return "done";
1077
+ }
1078
+ // ---------------------------------------------------------------------------
642
1079
  // Formatting helpers
643
1080
  // ---------------------------------------------------------------------------
644
1081
  function bold(s) { return `\x1b[1m${s}\x1b[0m`; }
@@ -646,6 +1083,24 @@ function green(s) { return `\x1b[32m${s}\x1b[0m`; }
646
1083
  function red(s) { return `\x1b[31m${s}\x1b[0m`; }
647
1084
  function dim(s) { return `\x1b[2m${s}\x1b[0m`; }
648
1085
  function ok(v) { return v ? green("✓") : red("✗"); }
1086
+ // ---------------------------------------------------------------------------
1087
+ // Tool detection
1088
+ // ---------------------------------------------------------------------------
1089
+ function isOpenCodeInstalled() {
1090
+ return fs_1.default.existsSync(getOpenCodeConfigDir());
1091
+ }
1092
+ function isClaudeCodeInstalled() {
1093
+ return fs_1.default.existsSync(getClaudeConfigDir());
1094
+ }
1095
+ function isCursorInstalled() {
1096
+ return fs_1.default.existsSync(getCursorConfigDir());
1097
+ }
1098
+ function isVscodeInstalled() {
1099
+ return fs_1.default.existsSync(getVscodeConfigDir());
1100
+ }
1101
+ function isCodexInstalled() {
1102
+ return fs_1.default.existsSync(getCodexConfigDir());
1103
+ }
649
1104
  function step(label, fn) {
650
1105
  process.stdout.write(` ${label}... `);
651
1106
  try {
@@ -658,6 +1113,13 @@ function step(label, fn) {
658
1113
  process.exit(1);
659
1114
  }
660
1115
  }
1116
+ function stepIf(condition, label, fn) {
1117
+ if (!condition) {
1118
+ console.log(` ${dim("○")} ${dim(label)} ${dim("(tool not detected — skipped)")}`);
1119
+ return;
1120
+ }
1121
+ step(label, fn);
1122
+ }
661
1123
  // ---------------------------------------------------------------------------
662
1124
  // Commands
663
1125
  // ---------------------------------------------------------------------------
@@ -667,6 +1129,13 @@ function install() {
667
1129
  const mcpPath = getMcpServerPath();
668
1130
  const indexerClaudePath = getIndexerCliClaudePath();
669
1131
  const indexerCursorPath = getIndexerCliCursorPath();
1132
+ const indexerVscodePath = getIndexerCliVscodePath();
1133
+ const indexerCodexPath = getIndexerCliCodexPath();
1134
+ const openCodeInstalled = isOpenCodeInstalled();
1135
+ const claudeInstalled = isClaudeCodeInstalled();
1136
+ const cursorInstalled = isCursorInstalled();
1137
+ const vscodeInstalled = isVscodeInstalled();
1138
+ const codexInstalled = isCodexInstalled();
670
1139
  // 1. DB
671
1140
  step("Initialising database", () => {
672
1141
  ensureDir(path_1.default.dirname(dbPath));
@@ -674,53 +1143,73 @@ function install() {
674
1143
  db.close();
675
1144
  return dbPath;
676
1145
  });
677
- // 2. OpenCode plugin
678
- step("Installing OpenCode plugin", () => {
1146
+ // OpenCode
1147
+ stepIf(openCodeInstalled, "Installing OpenCode plugin", () => {
679
1148
  const dst = getOpenCodePluginDst();
680
1149
  installOpenCodePlugin(getPluginSrc(), dst);
681
1150
  return dst;
682
1151
  });
683
- // 3. OpenCode skill
684
- step("Installing OpenCode skill", () => {
1152
+ stepIf(openCodeInstalled, "Installing OpenCode skill", () => {
685
1153
  const dst = getOpenCodeSkillDst();
686
1154
  copyFile(getSkillSrc(), dst);
687
1155
  return dst;
688
1156
  });
689
- // 4. OpenCode MCP config
690
- step("Configuring OpenCode MCP server", () => {
1157
+ stepIf(openCodeInstalled, "Configuring OpenCode MCP server", () => {
691
1158
  const { configPath, existed } = installOpenCodeMcpConfig(mcpPath);
692
1159
  return `${existed ? "updated" : "created"} ${configPath}`;
693
1160
  });
694
- // 5. Claude Code MCP config
695
- step("Configuring Claude Code MCP server", () => {
1161
+ // Claude Code
1162
+ stepIf(claudeInstalled, "Configuring Claude Code MCP server", () => {
696
1163
  const { configPath, existed } = installClaudeMcpConfig(mcpPath);
697
1164
  return `${existed ? "updated" : "created"} ${configPath}`;
698
1165
  });
699
- // 6. Claude Code hook
700
- step("Installing Claude Code Stop hook", () => {
1166
+ stepIf(claudeInstalled, "Installing Claude Code Stop hook", () => {
701
1167
  const { settingsPath, existed } = installClaudeHook(indexerClaudePath);
702
1168
  return `${existed ? "updated" : "created"} ${settingsPath}`;
703
1169
  });
704
- // 7. Claude Code CLAUDE.md
705
- step("Installing Claude Code context (CLAUDE.md)", () => {
1170
+ stepIf(claudeInstalled, "Installing Claude Code context (CLAUDE.md)", () => {
706
1171
  const { mdPath, existed } = installClaudeMd(getSkillSrc());
707
1172
  return `${existed ? "updated" : "created"} ${mdPath}`;
708
1173
  });
709
- // 8. Cursor MCP config
710
- step("Configuring Cursor MCP server", () => {
1174
+ // Cursor
1175
+ stepIf(cursorInstalled, "Configuring Cursor MCP server", () => {
711
1176
  const { configPath, existed } = installCursorMcpConfig(mcpPath);
712
1177
  return `${existed ? "updated" : "created"} ${configPath}`;
713
1178
  });
714
- // 9. Cursor stop hook
715
- step("Installing Cursor stop hook", () => {
1179
+ stepIf(cursorInstalled, "Installing Cursor stop hook", () => {
716
1180
  const { hooksPath, existed } = installCursorHook(indexerCursorPath);
717
1181
  return `${existed ? "updated" : "created"} ${hooksPath}`;
718
1182
  });
719
- // 10. Cursor skill
720
- step("Installing Cursor skill", () => {
1183
+ stepIf(cursorInstalled, "Installing Cursor skill", () => {
721
1184
  const { dstPath, existed } = installCursorSkill(getSkillSrc());
722
1185
  return `${existed ? "updated" : "created"} ${dstPath}`;
723
1186
  });
1187
+ // VS Code
1188
+ stepIf(vscodeInstalled, "Configuring VS Code MCP server", () => {
1189
+ const { configPath, existed } = installVscodeMcpConfig(mcpPath);
1190
+ return `${existed ? "updated" : "created"} ${configPath}`;
1191
+ });
1192
+ stepIf(vscodeInstalled, "Installing VS Code Stop hook", () => {
1193
+ const { hooksPath, existed } = installVscodeHook(indexerVscodePath);
1194
+ return `${existed ? "updated" : "created"} ${hooksPath}`;
1195
+ });
1196
+ stepIf(vscodeInstalled, "Registering VS Code hook location", () => {
1197
+ const { settingsPath, existed } = installVscodeHookLocation();
1198
+ return `${existed ? "updated" : "created"} ${settingsPath}`;
1199
+ });
1200
+ // Codex
1201
+ stepIf(codexInstalled, "Configuring Codex MCP server", () => {
1202
+ const { configPath, existed } = installCodexMcpConfig(mcpPath);
1203
+ return `${existed ? "updated" : "created"} ${configPath}`;
1204
+ });
1205
+ stepIf(codexInstalled, "Installing Codex notify hook", () => {
1206
+ const { configPath, existed } = installCodexHook(indexerCodexPath);
1207
+ return `${existed ? "updated" : "created"} ${configPath}`;
1208
+ });
1209
+ stepIf(codexInstalled, "Installing Codex skill", () => {
1210
+ const { dstPath, existed } = installCodexSkill(getSkillSrc());
1211
+ return `${existed ? "updated" : "created"} ${dstPath}`;
1212
+ });
724
1213
  console.log(`
725
1214
  ${bold("Installation complete!")}
726
1215
 
@@ -729,7 +1218,10 @@ ${bold("Required environment variable:")}
729
1218
 
730
1219
  ${bold("Default DB path:")} ${dbPath}
731
1220
 
732
- Restart ${bold("OpenCode")}, ${bold("Claude Code")}, and ${bold("Cursor")} to activate.
1221
+ Restart ${bold("OpenCode")}, ${bold("Claude Code")}, ${bold("Cursor")}, ${bold("VS Code")}, and ${bold("Codex")} to activate.
1222
+
1223
+ ${bold("VS Code note:")} Ensure ${bold("Chat: Use Hooks")} is enabled in VS Code settings.
1224
+ ${bold("Codex note:")} The notify hook and OPENAI_API_KEY passthrough are set in ${dim(getCodexConfigPath())}.
733
1225
  Run ${bold("npx code-session-memory status")} to verify.
734
1226
  `);
735
1227
  }
@@ -737,18 +1229,57 @@ function status() {
737
1229
  console.log(bold("\ncode-session-memory status\n"));
738
1230
  const dbPath = (0, database_1.resolveDbPath)();
739
1231
  const mcpPath = getMcpServerPath();
740
- console.log(bold(" OpenCode"));
741
- console.log(` ${ok(fs_1.default.existsSync(getOpenCodePluginDst()))} Plugin ${dim(getOpenCodePluginDst())}`);
742
- console.log(` ${ok(fs_1.default.existsSync(getOpenCodeSkillDst()))} Skill ${dim(getOpenCodeSkillDst())}`);
743
- console.log(` ${ok(checkMcpConfigured())} MCP config ${dim(getGlobalOpenCodeConfigPath())}`);
744
- console.log(bold("\n Claude Code"));
745
- console.log(` ${ok(checkClaudeMcpConfigured())} MCP config ${dim(getClaudeUserConfigPath())}`);
746
- console.log(` ${ok(checkClaudeHookInstalled())} Stop hook ${dim(getClaudeSettingsPath())}`);
747
- console.log(` ${ok(checkClaudeMdInstalled())} CLAUDE.md ${dim(getClaudeMdPath())}`);
748
- console.log(bold("\n Cursor"));
749
- console.log(` ${ok(checkCursorMcpConfigured())} MCP config ${dim(getCursorMcpConfigPath())}`);
750
- console.log(` ${ok(checkCursorHookInstalled())} Stop hook ${dim(getCursorHooksPath())}`);
751
- console.log(` ${ok(fs_1.default.existsSync(getCursorSkillDst()))} Skill ${dim(getCursorSkillDst())}`);
1232
+ const openCodeInstalled = isOpenCodeInstalled();
1233
+ const claudeInstalled = isClaudeCodeInstalled();
1234
+ const cursorInstalled = isCursorInstalled();
1235
+ const vscodeInstalled = isVscodeInstalled();
1236
+ const codexInstalled = isCodexInstalled();
1237
+ if (openCodeInstalled) {
1238
+ console.log(bold(" OpenCode"));
1239
+ console.log(` ${ok(fs_1.default.existsSync(getOpenCodePluginDst()))} Plugin ${dim(getOpenCodePluginDst())}`);
1240
+ console.log(` ${ok(fs_1.default.existsSync(getOpenCodeSkillDst()))} Skill ${dim(getOpenCodeSkillDst())}`);
1241
+ console.log(` ${ok(checkMcpConfigured())} MCP config ${dim(getGlobalOpenCodeConfigPath())}`);
1242
+ }
1243
+ else {
1244
+ console.log(bold(" OpenCode") + dim(" (not installed — skipped)"));
1245
+ }
1246
+ if (claudeInstalled) {
1247
+ console.log(bold("\n Claude Code"));
1248
+ console.log(` ${ok(checkClaudeMcpConfigured())} MCP config ${dim(getClaudeUserConfigPath())}`);
1249
+ console.log(` ${ok(checkClaudeHookInstalled())} Stop hook ${dim(getClaudeSettingsPath())}`);
1250
+ console.log(` ${ok(checkClaudeMdInstalled())} CLAUDE.md ${dim(getClaudeMdPath())}`);
1251
+ }
1252
+ else {
1253
+ console.log(bold("\n Claude Code") + dim(" (not installed — skipped)"));
1254
+ }
1255
+ if (cursorInstalled) {
1256
+ console.log(bold("\n Cursor"));
1257
+ console.log(` ${ok(checkCursorMcpConfigured())} MCP config ${dim(getCursorMcpConfigPath())}`);
1258
+ console.log(` ${ok(checkCursorHookInstalled())} Stop hook ${dim(getCursorHooksPath())}`);
1259
+ console.log(` ${ok(fs_1.default.existsSync(getCursorSkillDst()))} Skill ${dim(getCursorSkillDst())}`);
1260
+ }
1261
+ else {
1262
+ console.log(bold("\n Cursor") + dim(" (not installed — skipped)"));
1263
+ }
1264
+ if (vscodeInstalled) {
1265
+ console.log(bold("\n VS Code"));
1266
+ console.log(` ${ok(checkVscodeMcpConfigured())} MCP config ${dim(getVscodeMcpConfigPath())}`);
1267
+ console.log(` ${ok(checkVscodeHookInstalled())} Stop hook ${dim(getVscodeHooksPath())}`);
1268
+ console.log(` ${ok(checkVscodeHookLocationRegistered())} Hook loc ${dim(getVscodeSettingsPath())}`);
1269
+ }
1270
+ else {
1271
+ console.log(bold("\n VS Code") + dim(" (not installed — skipped)"));
1272
+ }
1273
+ if (codexInstalled) {
1274
+ console.log(bold("\n Codex"));
1275
+ console.log(` ${ok(checkCodexMcpConfigured())} MCP config ${dim(getCodexConfigPath())}`);
1276
+ console.log(` ${ok(checkCodexOpenAiPassthroughConfigured())} OPENAI_KEY ${dim(getCodexConfigPath())}`);
1277
+ console.log(` ${ok(checkCodexHookInstalled())} Notify hook ${dim(getCodexConfigPath())}`);
1278
+ console.log(` ${ok(fs_1.default.existsSync(getCodexSkillDst()))} Skill ${dim(getCodexSkillDst())}`);
1279
+ }
1280
+ else {
1281
+ console.log(bold("\n Codex") + dim(" (not installed — skipped)"));
1282
+ }
752
1283
  console.log(bold("\n Shared"));
753
1284
  console.log(` ${ok(fs_1.default.existsSync(mcpPath))} MCP server ${dim(mcpPath)}`);
754
1285
  console.log(` ${ok(fs_1.default.existsSync(dbPath))} Database ${dim(dbPath)}`);
@@ -772,15 +1303,22 @@ function status() {
772
1303
  }
773
1304
  catch { /* DB might be empty */ }
774
1305
  }
775
- const allOk = fs_1.default.existsSync(getOpenCodePluginDst()) &&
1306
+ const allOk = (!openCodeInstalled || (fs_1.default.existsSync(getOpenCodePluginDst()) &&
776
1307
  fs_1.default.existsSync(getOpenCodeSkillDst()) &&
777
- checkMcpConfigured() &&
778
- checkClaudeMcpConfigured() &&
779
- checkClaudeHookInstalled() &&
780
- checkClaudeMdInstalled() &&
781
- checkCursorMcpConfigured() &&
782
- checkCursorHookInstalled() &&
783
- fs_1.default.existsSync(getCursorSkillDst()) &&
1308
+ checkMcpConfigured())) &&
1309
+ (!claudeInstalled || (checkClaudeMcpConfigured() &&
1310
+ checkClaudeHookInstalled() &&
1311
+ checkClaudeMdInstalled())) &&
1312
+ (!cursorInstalled || (checkCursorMcpConfigured() &&
1313
+ checkCursorHookInstalled() &&
1314
+ fs_1.default.existsSync(getCursorSkillDst()))) &&
1315
+ (!vscodeInstalled || (checkVscodeMcpConfigured() &&
1316
+ checkVscodeHookInstalled() &&
1317
+ checkVscodeHookLocationRegistered())) &&
1318
+ (!codexInstalled || (checkCodexMcpConfigured() &&
1319
+ checkCodexOpenAiPassthroughConfigured() &&
1320
+ checkCodexHookInstalled() &&
1321
+ fs_1.default.existsSync(getCodexSkillDst()))) &&
784
1322
  fs_1.default.existsSync(mcpPath) &&
785
1323
  fs_1.default.existsSync(dbPath);
786
1324
  console.log(`\n ${allOk
@@ -832,6 +1370,30 @@ function uninstall() {
832
1370
  if (uninstallCursorSkill() === "not_found")
833
1371
  throw new Error("not found");
834
1372
  }],
1373
+ ["VS Code MCP config", () => {
1374
+ if (uninstallVscodeMcpConfig() === "not_found")
1375
+ throw new Error("not found");
1376
+ }],
1377
+ ["VS Code Stop hook", () => {
1378
+ if (uninstallVscodeHook() === "not_found")
1379
+ throw new Error("not found");
1380
+ }],
1381
+ ["VS Code hook location", () => {
1382
+ if (uninstallVscodeHookLocation() === "not_found")
1383
+ throw new Error("not found");
1384
+ }],
1385
+ ["Codex MCP config", () => {
1386
+ if (uninstallCodexMcpConfig() === "not_found")
1387
+ throw new Error("not found");
1388
+ }],
1389
+ ["Codex notify hook", () => {
1390
+ if (uninstallCodexHook() === "not_found")
1391
+ throw new Error("not found");
1392
+ }],
1393
+ ["Codex skill", () => {
1394
+ if (uninstallCodexSkill() === "not_found")
1395
+ throw new Error("not found");
1396
+ }],
835
1397
  ];
836
1398
  for (const [label, fn] of items) {
837
1399
  process.stdout.write(` Removing ${label}... `);
@@ -886,15 +1448,15 @@ async function resetDb() {
886
1448
  }
887
1449
  function help() {
888
1450
  console.log(`
889
- ${bold("code-session-memory")} — Shared vector memory for OpenCode, Claude Code, and Cursor sessions
1451
+ ${bold("code-session-memory")} — Shared vector memory for OpenCode, Claude Code, Cursor, VS Code, and Codex sessions
890
1452
 
891
1453
  ${bold("Usage:")}
892
- npx code-session-memory install Install all components (OpenCode + Claude Code + Cursor)
1454
+ npx code-session-memory install Install components for detected tools
893
1455
  npx code-session-memory status Show installation status and DB stats
894
1456
  npx code-session-memory uninstall Remove all installed components (keeps DB)
895
1457
  npx code-session-memory reset-db Delete all indexed data (keeps installation)
896
1458
  npx code-session-memory query <text> Semantic search across all indexed sessions
897
- npx code-session-memory query <text> --source <s> Filter by source (opencode, claude-code, cursor)
1459
+ npx code-session-memory query <text> --source <s> Filter by source (opencode, claude-code, cursor, vscode, codex)
898
1460
  npx code-session-memory query <text> --limit <n> Max results (default: 5)
899
1461
  npx code-session-memory query <text> --from <date> Results from date (e.g. 2026-02-01)
900
1462
  npx code-session-memory query <text> --to <date> Results up to date (e.g. 2026-02-20)
@@ -911,6 +1473,8 @@ ${bold("Environment variables:")}
911
1473
  OPENCODE_CONFIG_DIR Override the OpenCode config directory
912
1474
  CLAUDE_CONFIG_DIR Override the Claude Code config directory
913
1475
  CURSOR_CONFIG_DIR Override the Cursor config directory (~/.cursor)
1476
+ VSCODE_CONFIG_DIR Override the VS Code config directory
1477
+ CODEX_HOME Override the Codex home directory (~/.codex)
914
1478
  `);
915
1479
  }
916
1480
  // ---------------------------------------------------------------------------