squish-memory 1.0.2 → 1.1.5

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 (341) hide show
  1. package/.env.example +130 -0
  2. package/CHANGELOG.md +55 -0
  3. package/README.md +150 -287
  4. package/config/hooks/claude-code-hooks.json +39 -0
  5. package/config/hooks/cursor-hooks.json +30 -0
  6. package/config/hooks/opencode-hooks.json +30 -0
  7. package/config/hooks/windsurf-hooks.json +30 -0
  8. package/config/mcp-mode-semantics.json +23 -21
  9. package/config/plugin-manifest.json +101 -152
  10. package/{plugin.json → config/plugin.json} +2 -2
  11. package/config/settings.json +52 -51
  12. package/{commands → core/commands}/init.md +39 -39
  13. package/dist/config.d.ts +28 -4
  14. package/dist/config.js +97 -29
  15. package/dist/core/adapters/config/claude-code.d.ts +45 -0
  16. package/dist/core/adapters/config/claude-code.js +113 -0
  17. package/dist/core/adapters/config/cursor.d.ts +26 -0
  18. package/dist/core/adapters/config/cursor.js +74 -0
  19. package/dist/core/adapters/config/opencode.d.ts +23 -0
  20. package/dist/core/adapters/config/opencode.js +73 -0
  21. package/dist/core/adapters/config/windsurf.d.ts +26 -0
  22. package/dist/core/adapters/config/windsurf.js +74 -0
  23. package/dist/core/adapters/index.d.ts +45 -0
  24. package/dist/core/adapters/index.js +84 -0
  25. package/dist/core/adapters/scripts/install-adapter.d.ts +19 -0
  26. package/dist/core/adapters/scripts/install-adapter.js +149 -0
  27. package/dist/core/adapters/timeline.d.ts +23 -0
  28. package/dist/core/adapters/timeline.js +88 -0
  29. package/dist/core/adapters/types.d.ts +157 -0
  30. package/dist/core/adapters/types.js +50 -0
  31. package/dist/{algorithms → core/algorithms}/analytics/token-estimator.d.ts +1 -1
  32. package/dist/{algorithms → core/algorithms}/analytics/token-estimator.js +3 -3
  33. package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.d.ts +1 -1
  34. package/dist/{algorithms → core/algorithms}/detection/semantic-ranker.js +1 -1
  35. package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.d.ts +1 -1
  36. package/dist/{algorithms → core/algorithms}/detection/two-stage-detector.js +7 -10
  37. package/dist/{algorithms → core/algorithms}/handlers/approve-merge.js +4 -4
  38. package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.js +3 -3
  39. package/dist/{algorithms → core/algorithms}/handlers/get-stats.js +3 -3
  40. package/dist/{algorithms → core/algorithms}/handlers/list-proposals.js +3 -3
  41. package/dist/{algorithms → core/algorithms}/handlers/preview-merge.js +3 -3
  42. package/dist/{algorithms → core/algorithms}/handlers/reject-merge.js +3 -3
  43. package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.js +3 -3
  44. package/dist/core/algorithms/index.d.ts +21 -0
  45. package/dist/core/algorithms/index.js +26 -0
  46. package/dist/core/algorithms/operations/cache-maintenance.d.ts +12 -0
  47. package/dist/core/algorithms/operations/cache-maintenance.js +157 -0
  48. package/dist/{algorithms → core/algorithms}/safety/safety-checks.d.ts +1 -1
  49. package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.d.ts +19 -1
  50. package/dist/{algorithms → core/algorithms}/strategies/merge-strategies.js +74 -123
  51. package/dist/core/algorithms/types.d.ts +133 -0
  52. package/dist/core/algorithms/types.js +5 -0
  53. package/dist/core/associations.d.ts +1 -2
  54. package/dist/core/associations.js +1 -2
  55. package/dist/core/autosave.d.ts +19 -0
  56. package/dist/core/autosave.js +16 -0
  57. package/dist/{commands → core/commands}/managed-sync.js +5 -5
  58. package/dist/core/commands/mcp-server.js +739 -0
  59. package/dist/core/context/agent-context.d.ts +106 -0
  60. package/dist/core/context/agent-context.js +274 -0
  61. package/dist/core/{context-paging.d.ts → context/context-paging.d.ts} +2 -12
  62. package/dist/core/{context-paging.js → context/context-paging.js} +19 -39
  63. package/dist/core/context/context-window.d.ts +40 -0
  64. package/dist/core/context/context-window.js +177 -0
  65. package/dist/core/context/context.js +22 -0
  66. package/dist/core/embeddings.d.ts +1 -1
  67. package/dist/core/embeddings.js +54 -2
  68. package/dist/core/error-handling.d.ts +63 -0
  69. package/dist/core/error-handling.js +173 -0
  70. package/dist/core/external-folder/index.d.ts +102 -0
  71. package/dist/core/external-folder/index.js +294 -0
  72. package/dist/core/hooks/agent-hooks.d.ts +74 -0
  73. package/dist/core/hooks/agent-hooks.js +244 -0
  74. package/dist/core/hooks/auto-tagger.d.ts +19 -0
  75. package/dist/core/hooks/auto-tagger.js +155 -0
  76. package/dist/core/hooks/capture-filter.d.ts +41 -0
  77. package/dist/core/hooks/capture-filter.js +128 -0
  78. package/dist/core/index.d.ts +6 -6
  79. package/dist/core/index.js +6 -6
  80. package/dist/core/{agent-memory.js → ingestion/agent-memory.js} +5 -7
  81. package/dist/core/{core-memory.js → ingestion/core-memory.js} +4 -4
  82. package/dist/core/ingestion/learnings.d.ts +57 -0
  83. package/dist/core/ingestion/learnings.js +202 -0
  84. package/dist/core/lib/db-client.d.ts +114 -0
  85. package/dist/core/lib/db-client.js +130 -0
  86. package/dist/core/lib/schemas.d.ts +129 -0
  87. package/dist/core/lib/schemas.js +87 -0
  88. package/dist/core/{utils.d.ts → lib/utils.d.ts} +1 -0
  89. package/dist/core/{utils.js → lib/utils.js} +31 -15
  90. package/dist/core/lib/validation.d.ts +38 -0
  91. package/dist/core/lib/validation.js +151 -0
  92. package/dist/core/lifecycle.d.ts +7 -0
  93. package/dist/core/lifecycle.js +140 -20
  94. package/dist/core/local-embeddings.d.ts +6 -1
  95. package/dist/core/local-embeddings.js +6 -15
  96. package/dist/core/logger.js +7 -1
  97. package/dist/core/mcp/tools.js +35 -3
  98. package/dist/core/memory/categorizer.js +1 -0
  99. package/dist/core/memory/conflict-detector.js +1 -1
  100. package/dist/core/memory/consolidation.d.ts +1 -10
  101. package/dist/core/memory/consolidation.js +2 -11
  102. package/dist/core/memory/context-collector.js +1 -1
  103. package/dist/core/memory/edit-workflow.js +1 -1
  104. package/dist/core/memory/entity-resolver.js +7 -7
  105. package/dist/core/memory/fact-extractor.js +12 -12
  106. package/dist/core/memory/feedback-tracker.js +1 -1
  107. package/dist/core/memory/hooks.d.ts +88 -0
  108. package/dist/core/memory/hooks.js +174 -0
  109. package/dist/core/memory/hybrid-retrieval.js +2 -2
  110. package/dist/core/memory/hybrid-search.d.ts +1 -6
  111. package/dist/core/memory/hybrid-search.js +70 -84
  112. package/dist/core/memory/importance.d.ts +8 -13
  113. package/dist/core/memory/importance.js +47 -74
  114. package/dist/core/memory/loader.d.ts +31 -0
  115. package/dist/core/memory/loader.js +141 -0
  116. package/dist/core/memory/markdown/markdown-storage.d.ts +72 -0
  117. package/dist/core/memory/markdown/markdown-storage.js +243 -0
  118. package/dist/core/memory/memories.d.ts +12 -4
  119. package/dist/core/memory/memories.js +192 -180
  120. package/dist/core/memory/memory-lifecycle.d.ts +8 -0
  121. package/dist/core/memory/memory-lifecycle.js +55 -0
  122. package/dist/core/memory/migrate.d.ts +21 -0
  123. package/dist/core/memory/migrate.js +134 -0
  124. package/dist/core/memory/normalization.d.ts +22 -0
  125. package/dist/core/memory/normalization.js +26 -0
  126. package/dist/core/memory/progressive-disclosure.js +1 -1
  127. package/dist/core/memory/query-rewriter.js +9 -9
  128. package/dist/core/memory/serialization.d.ts +4 -0
  129. package/dist/core/memory/serialization.js +49 -0
  130. package/dist/core/memory/stats.d.ts +5 -0
  131. package/dist/core/memory/stats.js +63 -12
  132. package/dist/core/memory/temporal-facts.js +21 -0
  133. package/dist/core/memory/write-gate.js +1 -1
  134. package/dist/core/obsidian-vault.d.ts +30 -0
  135. package/dist/core/obsidian-vault.js +94 -0
  136. package/dist/core/places/index.d.ts +14 -0
  137. package/dist/core/places/index.js +14 -0
  138. package/dist/core/places/memory-places.d.ts +68 -0
  139. package/dist/core/places/memory-places.js +261 -0
  140. package/dist/core/places/places.d.ts +88 -0
  141. package/dist/core/places/places.js +314 -0
  142. package/dist/core/places/rules.d.ts +74 -0
  143. package/dist/core/places/rules.js +240 -0
  144. package/dist/core/places/walking.d.ts +56 -0
  145. package/dist/core/places/walking.js +121 -0
  146. package/dist/core/projects.d.ts +5 -0
  147. package/dist/core/projects.js +39 -18
  148. package/dist/core/responses.d.ts +96 -0
  149. package/dist/core/responses.js +122 -0
  150. package/dist/core/scheduler/cron-scheduler.js +29 -7
  151. package/dist/core/scheduler/index.d.ts +1 -1
  152. package/dist/core/scheduler/index.js +1 -1
  153. package/dist/core/scheduler/job-runner.js +1 -1
  154. package/dist/core/search/conversations.js +40 -42
  155. package/dist/core/search/entities.js +6 -9
  156. package/dist/core/search/graph-boost.d.ts +7 -0
  157. package/dist/core/search/graph-boost.js +23 -0
  158. package/dist/core/search/qmd-search.js +4 -4
  159. package/dist/core/security/encrypt.d.ts +6 -0
  160. package/dist/core/security/encrypt.js +47 -0
  161. package/dist/core/{governance.d.ts → security/governance.d.ts} +6 -1
  162. package/dist/core/security/governance.js +79 -0
  163. package/dist/core/session/auto-load.js +6 -6
  164. package/dist/core/session/index.d.ts +1 -1
  165. package/dist/core/session/index.js +1 -1
  166. package/dist/core/session/self-iteration-job.d.ts +20 -0
  167. package/dist/core/session/self-iteration-job.js +282 -0
  168. package/dist/core/session/session-hooks.d.ts +18 -0
  169. package/dist/core/session/session-hooks.js +58 -0
  170. package/dist/core/session-hooks/self-iteration-job.js +35 -35
  171. package/dist/core/{cache.js → storage/cache.js} +2 -2
  172. package/dist/core/sync/qmd-sync.d.ts +1 -13
  173. package/dist/core/sync/qmd-sync.js +1 -13
  174. package/dist/core/toon.d.ts +43 -0
  175. package/dist/core/toon.js +160 -0
  176. package/dist/core/utils/memory-operations.js +1 -1
  177. package/dist/core/utils/vector-operations.d.ts +71 -0
  178. package/dist/core/utils/vector-operations.js +129 -0
  179. package/dist/db/adapter.d.ts +3 -3
  180. package/dist/db/adapter.js +99 -88
  181. package/dist/db/bootstrap.js +820 -522
  182. package/dist/{drizzle → db/drizzle}/schema-sqlite.d.ts +74 -25
  183. package/dist/{drizzle → db/drizzle}/schema-sqlite.js +91 -24
  184. package/dist/{drizzle → db/drizzle}/schema.d.ts +79 -32
  185. package/dist/{drizzle → db/drizzle}/schema.js +106 -35
  186. package/dist/db/drizzle.config.d.ts +3 -0
  187. package/dist/db/drizzle.config.js +12 -0
  188. package/dist/db/index.d.ts +1 -5
  189. package/dist/db/index.js +51 -8
  190. package/dist/db/neon.d.ts +8 -0
  191. package/dist/db/neon.js +20 -0
  192. package/dist/db/schema/index.d.ts +40 -0
  193. package/dist/db/schema/index.js +105 -0
  194. package/dist/db/schema/tables/context-sessions.d.ts +9 -0
  195. package/dist/db/schema/tables/context-sessions.js +37 -0
  196. package/dist/db/schema/tables/conversations.d.ts +9 -0
  197. package/dist/db/schema/tables/conversations.js +47 -0
  198. package/dist/db/schema/tables/core-memory.d.ts +9 -0
  199. package/dist/db/schema/tables/core-memory.js +41 -0
  200. package/dist/db/schema/tables/entities.d.ts +9 -0
  201. package/dist/db/schema/tables/entities.js +39 -0
  202. package/dist/db/schema/tables/entity-relations.d.ts +9 -0
  203. package/dist/db/schema/tables/entity-relations.js +31 -0
  204. package/dist/db/schema/tables/learnings.d.ts +9 -0
  205. package/dist/db/schema/tables/learnings.js +66 -0
  206. package/dist/db/schema/tables/memories.d.ts +9 -0
  207. package/dist/db/schema/tables/memories.js +161 -0
  208. package/dist/db/schema/tables/memory-associations.d.ts +9 -0
  209. package/dist/db/schema/tables/memory-associations.js +39 -0
  210. package/dist/db/schema/tables/memory-hash-cache.d.ts +9 -0
  211. package/dist/db/schema/tables/memory-hash-cache.js +29 -0
  212. package/dist/db/schema/tables/memory-merge-history.d.ts +9 -0
  213. package/dist/db/schema/tables/memory-merge-history.js +33 -0
  214. package/dist/db/schema/tables/memory-merge-proposals.d.ts +9 -0
  215. package/dist/db/schema/tables/memory-merge-proposals.js +39 -0
  216. package/dist/db/schema/tables/messages.d.ts +9 -0
  217. package/dist/db/schema/tables/messages.js +41 -0
  218. package/dist/db/schema/tables/namespaces.d.ts +9 -0
  219. package/dist/db/schema/tables/namespaces.js +37 -0
  220. package/dist/db/schema/tables/projects.d.ts +9 -0
  221. package/dist/db/schema/tables/projects.js +31 -0
  222. package/dist/db/schema/tables/users.d.ts +9 -0
  223. package/dist/db/schema/tables/users.js +27 -0
  224. package/dist/db/schema.d.ts +1 -1
  225. package/dist/db/schema.js +2 -2
  226. package/dist/db/supabase.d.ts +9 -0
  227. package/dist/db/supabase.js +24 -0
  228. package/dist/index.d.ts +2 -14
  229. package/dist/index.js +1320 -640
  230. package/dist/vendor/sql.js/sql-wasm.wasm +0 -0
  231. package/dist/webui/server.d.ts +5 -0
  232. package/dist/{api/web/web.js → webui/server.js} +511 -508
  233. package/generated/mcp/manifest.json +1 -1
  234. package/{.mcp.json → mcp.json.example} +1 -1
  235. package/package.json +159 -181
  236. package/scripts/README.md +60 -0
  237. package/scripts/copy-runtime-assets.mjs +26 -0
  238. package/scripts/generate-mcp.mjs +264 -264
  239. package/scripts/github-release.sh +4 -4
  240. package/scripts/install-claude-code.sh +85 -0
  241. package/scripts/install-cursor.sh +56 -0
  242. package/scripts/install-hooks.sh +73 -0
  243. package/scripts/install-interactive.mjs +357 -677
  244. package/scripts/install-opencode.sh +75 -0
  245. package/scripts/install-windsurf.sh +67 -0
  246. package/skills/squish-memory/SKILL.md +104 -114
  247. package/skills/squish-memory/{install.mjs → scripts/install.mjs} +2 -2
  248. package/skills/squish-memory/{install.sh → scripts/install.sh} +2 -2
  249. package/skills/squish-memory/write_skill.js +2 -0
  250. package/.claude-plugin/marketplace.json +0 -20
  251. package/.claude-plugin/plugin.json +0 -32
  252. package/.env.mcp.example +0 -60
  253. package/QUICK-START.md +0 -71
  254. package/bin/squish-add.mjs +0 -32
  255. package/bin/squish-rm.mjs +0 -21
  256. package/commands/observe.md +0 -5
  257. package/dist/api/web/index.d.ts +0 -3
  258. package/dist/api/web/index.js +0 -4
  259. package/dist/api/web/web-server.d.ts +0 -3
  260. package/dist/api/web/web-server.js +0 -6
  261. package/dist/api/web/web.d.ts +0 -4
  262. package/dist/commands/mcp-server.js +0 -393
  263. package/dist/core/context.js +0 -24
  264. package/dist/core/governance.js +0 -64
  265. package/dist/core/observations.d.ts +0 -26
  266. package/dist/core/observations.js +0 -110
  267. package/dist/core/requirements.d.ts +0 -20
  268. package/dist/core/requirements.js +0 -35
  269. package/hooks/hooks.json +0 -52
  270. package/hooks/post-tool-use.js +0 -26
  271. package/hooks/session-end.js +0 -28
  272. package/hooks/session-start.js +0 -33
  273. package/hooks/user-prompt-submit.js +0 -26
  274. package/hooks/utils.js +0 -153
  275. package/npx-installer.js +0 -208
  276. package/packages/plugin-claude-code/README.md +0 -73
  277. package/packages/plugin-claude-code/dist/plugin-wrapper.d.ts +0 -35
  278. package/packages/plugin-claude-code/dist/plugin-wrapper.js +0 -191
  279. package/packages/plugin-claude-code/package.json +0 -31
  280. package/packages/plugin-openclaw/README.md +0 -70
  281. package/packages/plugin-openclaw/dist/index.d.ts +0 -49
  282. package/packages/plugin-openclaw/dist/index.js +0 -262
  283. package/packages/plugin-openclaw/openclaw.plugin.json +0 -94
  284. package/packages/plugin-openclaw/package.json +0 -31
  285. package/packages/plugin-opencode/install.mjs +0 -217
  286. package/packages/plugin-opencode/package.json +0 -21
  287. package/scripts/db/check-db.mjs +0 -88
  288. package/scripts/db/fix-all-columns.mjs +0 -52
  289. package/scripts/db/fix-schema-all.mjs +0 -55
  290. package/scripts/db/fix-schema-full.mjs +0 -46
  291. package/scripts/db/fix-schema.mjs +0 -38
  292. package/scripts/db/init-db.mjs +0 -13
  293. package/scripts/db/recreate-db.mjs +0 -14
  294. package/scripts/install-mcp.mjs +0 -116
  295. package/scripts/install-web.sh +0 -120
  296. package/scripts/install.mjs +0 -340
  297. package/scripts/openclaw-bootstrap.mjs +0 -127
  298. package/scripts/package-release.sh +0 -71
  299. package/scripts/test/test-all-systems.mjs +0 -139
  300. package/scripts/test/test-memory-system.mjs +0 -139
  301. package/scripts/test/test-v0.5.0.mjs +0 -210
  302. package/skills/memory-guide/SKILL.md +0 -332
  303. package/skills/squish-cli/SKILL.md +0 -240
  304. package/skills/squish-mcp/SKILL.md +0 -355
  305. package/skills/squish-memory/claude-desktop.json +0 -12
  306. package/skills/squish-memory/openclaw.json +0 -13
  307. package/skills/squish-memory/opencode.json +0 -14
  308. package/skills/squish-memory/skill.json +0 -32
  309. /package/{commands → core/commands}/context-paging.md +0 -0
  310. /package/{commands → core/commands}/context-status.md +0 -0
  311. /package/{commands → core/commands}/context.md +0 -0
  312. /package/{commands → core/commands}/core-memory.md +0 -0
  313. /package/{commands → core/commands}/health.md +0 -0
  314. /package/{commands → core/commands}/merge.md +0 -0
  315. /package/{commands → core/commands}/recall.md +0 -0
  316. /package/{commands → core/commands}/remember.md +0 -0
  317. /package/{commands → core/commands}/search.md +0 -0
  318. /package/dist/{algorithms → core/algorithms}/detection/hash-filters.d.ts +0 -0
  319. /package/dist/{algorithms → core/algorithms}/detection/hash-filters.js +0 -0
  320. /package/dist/{algorithms → core/algorithms}/handlers/approve-merge.d.ts +0 -0
  321. /package/dist/{algorithms → core/algorithms}/handlers/detect-duplicates.d.ts +0 -0
  322. /package/dist/{algorithms → core/algorithms}/handlers/get-stats.d.ts +0 -0
  323. /package/dist/{algorithms → core/algorithms}/handlers/list-proposals.d.ts +0 -0
  324. /package/dist/{algorithms → core/algorithms}/handlers/preview-merge.d.ts +0 -0
  325. /package/dist/{algorithms → core/algorithms}/handlers/reject-merge.d.ts +0 -0
  326. /package/dist/{algorithms → core/algorithms}/handlers/reverse-merge.d.ts +0 -0
  327. /package/dist/{algorithms → core/algorithms}/safety/safety-checks.js +0 -0
  328. /package/dist/{algorithms → core/algorithms}/utils/response-builder.d.ts +0 -0
  329. /package/dist/{algorithms → core/algorithms}/utils/response-builder.js +0 -0
  330. /package/dist/{commands → core/commands}/managed-sync.d.ts +0 -0
  331. /package/dist/{commands → core/commands}/mcp-server.d.ts +0 -0
  332. /package/dist/core/{context.d.ts → context/context.d.ts} +0 -0
  333. /package/dist/core/{agent-memory.d.ts → ingestion/agent-memory.d.ts} +0 -0
  334. /package/dist/core/{core-memory.d.ts → ingestion/core-memory.d.ts} +0 -0
  335. /package/dist/core/{privacy.d.ts → security/privacy.d.ts} +0 -0
  336. /package/dist/core/{privacy.js → security/privacy.js} +0 -0
  337. /package/dist/core/{secret-detector.d.ts → security/secret-detector.d.ts} +0 -0
  338. /package/dist/core/{secret-detector.js → security/secret-detector.js} +0 -0
  339. /package/dist/core/{cache.d.ts → storage/cache.d.ts} +0 -0
  340. /package/dist/core/{database.d.ts → storage/database.d.ts} +0 -0
  341. /package/dist/core/{database.js → storage/database.js} +0 -0
@@ -1,677 +1,357 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Squish Interactive Plugin Installer
5
- * Multi-step wizard with clack
6
- */
7
-
8
- import { intro, outro, confirm, multiselect, select, isCancel, cancel, spinner, note } from '@clack/prompts';
9
- import picocolors from 'picocolors';
10
- import fs from "node:fs";
11
- import os from "node:os";
12
- import path from "node:path";
13
- import { spawnSync } from "node:child_process";
14
-
15
- const root = process.cwd();
16
- const manifestPath = path.join(root, "config", "plugin-manifest.json");
17
-
18
- const c = picocolors;
19
-
20
- const icons = {
21
- squish: "🐙",
22
- check: "",
23
- cross: "✗",
24
- package: "📦",
25
- arrow: "→",
26
- dot: "●",
27
- circle: "",
28
- diamond: "",
29
- pointer: "",
30
- line: "",
31
- corner: "",
32
- section: "",
33
- cli: "⌨️",
34
- mcp: "🔌",
35
- plugin: "🔧",
36
- settings: "⚙️",
37
- local: "🏠",
38
- remote: "☁️",
39
- brain: "🧠",
40
- cloud: "☁️"
41
- };
42
-
43
- const CLIENT_DIRS = {
44
- "claude-code": path.join(os.homedir(), ".claude"),
45
- opencode: path.join(os.homedir(), ".config", "opencode"),
46
- codex: path.join(os.homedir(), ".codex"),
47
- cursor: path.join(os.homedir(), ".cursor"),
48
- vscode: path.join(os.homedir(), ".vscode", "mcp"),
49
- windsurf: path.join(os.homedir(), ".windsurf"),
50
- openclaw: path.join(os.homedir(), ".openclaw")
51
- };
52
-
53
- const PLUGINS_DIR = path.join(root, "packages");
54
-
55
- function printLogo() {
56
- console.log(c.cyan(`
57
- ███████╗ ██████╗ ██╗ ██╗██╗███████╗██╗ ██╗
58
- ██╔════╝██╔═══██╗██║ ██║██║██╔════╝██║ ██║
59
- ███████╗██║ ██║██║ ██║██║███████╗███████║
60
- ╚════██║██║ ██║██║ ██║██║╚════██║██╔══██║
61
- ███████║╚██████╔╝╚██████╔╝██║███████║██║ ██║
62
- ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝
63
- `));
64
- console.log(c.gray(" Universal Memory System for AI Agents\n"));
65
- }
66
-
67
- function loadManifest() {
68
- if (!fs.existsSync(manifestPath)) {
69
- return null;
70
- }
71
- try {
72
- return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
73
- } catch {
74
- return null;
75
- }
76
- }
77
-
78
- function detectInstalledClients() {
79
- const installed = {};
80
- for (const [client, dir] of Object.entries(CLIENT_DIRS)) {
81
- try {
82
- installed[client] = fs.existsSync(dir);
83
- } catch {
84
- installed[client] = false;
85
- }
86
- }
87
- return installed;
88
- }
89
-
90
- function checkPluginSource(pluginId) {
91
- const pluginDirs = {
92
- "claude-code": path.join(PLUGINS_DIR, "plugin-claude-code"),
93
- "openclaw": path.join(PLUGINS_DIR, "plugin-openclaw"),
94
- "opencode": path.join(PLUGINS_DIR, "plugin-opencode")
95
- };
96
-
97
- const pluginDir = pluginDirs[pluginId];
98
- if (!pluginDir) return false;
99
-
100
- return fs.existsSync(pluginDir) && fs.existsSync(path.join(pluginDir, "package.json"));
101
- }
102
-
103
- function shouldUseNonInteractive() {
104
- return process.env.CI === 'true' ||
105
- process.env.NON_INTERACTIVE === '1' ||
106
- process.env.AUTOMATION === 'true' ||
107
- !process.stdin.isTTY;
108
- }
109
-
110
- function parseArgs(argv) {
111
- const flags = {
112
- auto: false,
113
- select: [],
114
- all: false,
115
- list: false,
116
- dryRun: false,
117
- help: false,
118
- verbose: false,
119
- quick: false,
120
- interactive: false
121
- };
122
-
123
- for (let i = 2; i < argv.length; i++) {
124
- const token = argv[i];
125
-
126
- if (token === "--auto" || token === "-a") {
127
- flags.auto = true;
128
- } else if (token === "--all") {
129
- flags.all = true;
130
- } else if (token === "--list" || token === "-l") {
131
- flags.list = true;
132
- } else if (token === "--dry-run" || token === "-n") {
133
- flags.dryRun = true;
134
- } else if (token === "--verbose" || token === "-v") {
135
- flags.verbose = true;
136
- } else if (token === "--help" || token === "-h") {
137
- flags.help = true;
138
- } else if (token === "--quick" || token === "-q") {
139
- flags.quick = true;
140
- } else if (token === "--interactive" || token === "-i") {
141
- flags.interactive = true;
142
- } else if (token.startsWith("--select=")) {
143
- flags.select = token.slice(9).split(",").map(s => s.trim());
144
- } else if (token === "--select") {
145
- flags.select = [argv[i + 1]];
146
- i++;
147
- } else {
148
- console.log(c.red(`Unknown flag: ${token}`));
149
- console.log(`Run ${c.cyan("--help")} for usage information`);
150
- process.exit(1);
151
- }
152
- }
153
-
154
- return flags;
155
- }
156
-
157
- function printHelp() {
158
- printLogo();
159
- console.log(c.white("USAGE:"));
160
- console.log(` ${c.cyan("bun run install:interactive")} [OPTIONS]\n`);
161
-
162
- console.log(c.white("OPTIONS:"));
163
- console.log(` ${c.cyan("--auto")}, ${c.cyan("-a")} Skip menu, install all available plugins`);
164
- console.log(` ${c.cyan("--quick")}, ${c.cyan("-q")} Quick install (CLI + all plugins)`);
165
- console.log(` ${c.cyan("--select")}=<list> Pre-select plugins (comma-separated)`);
166
- console.log(` ${c.cyan("--all")} Install all available plugins`);
167
- console.log(` ${c.cyan("--list")}, ${c.cyan("-l")} List available plugins and exit`);
168
- console.log(` ${c.cyan("--dry-run")}, ${c.cyan("-n")} Preview changes without installing`);
169
- console.log(` ${c.cyan("--verbose")}, ${c.cyan("-v")} Show detailed output`);
170
- console.log(` ${c.cyan("--help")}, ${c.cyan("-h")} Show this help message\n`);
171
-
172
- console.log(c.white("ENVIRONMENT VARIABLES:"));
173
- console.log(` ${c.cyan("CI=true")} Force non-interactive mode`);
174
- console.log(` ${c.cyan("NON_INTERACTIVE=1")} Force non-interactive mode`);
175
- console.log(` ${c.cyan("AUTOMATION=true")} Force non-interactive mode\n`);
176
-
177
- console.log(c.white("INTERACTIVE WIZARD:"));
178
- console.log(` ${c.gray("1.")} Select components: CLI, MCP Server, Plugins`);
179
- console.log(` ${c.gray("2.")} Choose plugins (if selected)`);
180
- console.log(` ${c.gray("3.")} Configure mode (local/remote)`);
181
- console.log(` ${c.gray("4.")} Review and install\n`);
182
-
183
- console.log(c.white("EXAMPLES:"));
184
- console.log(` ${c.gray("# Interactive wizard (default)")}`);
185
- console.log(` ${c.gray("$")} bun run install:interactive\n`);
186
- console.log(` ${c.gray("# Quick install (CLI + all plugins)")}`);
187
- console.log(` ${c.gray("$")} bun run install:interactive --quick\n`);
188
- console.log(` ${c.gray("# Non-interactive: install all")}`);
189
- console.log(` ${c.gray("$")} bun run install:interactive --auto\n`);
190
-
191
- console.log(c.gray("────────────────────────────────────────────────────"));
192
- console.log(c.gray("Documentation: https://github.com/michielhdoteth/squish"));
193
- }
194
-
195
- function listPlugins() {
196
- const manifest = loadManifest();
197
- if (!manifest || !manifest.targets) {
198
- console.log(c.red("Error: Plugin manifest not found"));
199
- process.exit(1);
200
- }
201
-
202
- const installed = detectInstalledClients();
203
- const clientNames = {
204
- "claude-code": "Claude Code",
205
- "openclaw": "OpenClaw",
206
- "opencode": "OpenCode",
207
- "codex": "Codex",
208
- "cursor": "Cursor",
209
- "vscode": "VS Code",
210
- "windsurf": "Windsurf"
211
- };
212
-
213
- printLogo();
214
- console.log(c.white("Available Plugins:"));
215
- console.log(c.gray("────────────────────────────────────────────────────"));
216
- console.log();
217
-
218
- let i = 1;
219
- for (const [client, config] of Object.entries(manifest.targets)) {
220
- const isInstalled = installed[client];
221
- const hasSource = checkPluginSource(client);
222
-
223
- const status = isInstalled
224
- ? `${c.green(icons.check)} installed`
225
- : `${c.yellow(icons.dot)} not installed`;
226
-
227
- const source = hasSource
228
- ? `${c.cyan(icons.package)} source`
229
- : `${c.red(icons.cross)} no source`;
230
-
231
- console.log(` ${i}. ${c.white(clientNames[client] || client)}`);
232
- console.log(` ${c.gray("Type:")} ${config.type || 'unknown'}`);
233
- console.log(` ${status} ${source}`);
234
- console.log();
235
- i++;
236
- }
237
-
238
- console.log(c.gray("────────────────────────────────────────────────────"));
239
- console.log(c.gray(`Total: ${i - 1} plugins available`));
240
- }
241
-
242
- function getPluginChoices() {
243
- const manifest = loadManifest();
244
- if (!manifest || !manifest.targets) {
245
- return [];
246
- }
247
-
248
- const installed = detectInstalledClients();
249
- const clientNames = {
250
- "claude-code": "Claude Code",
251
- "openclaw": "OpenClaw",
252
- "opencode": "OpenCode",
253
- "codex": "Codex",
254
- "cursor": "Cursor",
255
- "vscode": "VS Code",
256
- "windsurf": "Windsurf"
257
- };
258
-
259
- const typeDescriptions = {
260
- "hooks": "Session hooks for auto-memory",
261
- "plugin-slot": "Memory slot via MCP bridge",
262
- "mcp": "MCP server configuration"
263
- };
264
-
265
- return Object.entries(manifest.targets).map(([client, config]) => {
266
- const isInstalled = installed[client];
267
- const hasSource = checkPluginSource(client);
268
-
269
- const name = clientNames[client] || client;
270
- const type = typeDescriptions[config.type] || config.type || 'Plugin';
271
-
272
- let label = name;
273
- if (isInstalled) label += ` ${c.green(icons.check)}`;
274
- if (hasSource) label += ` ${c.cyan(icons.package)}`;
275
-
276
- return {
277
- value: client,
278
- label: label,
279
- hint: type
280
- };
281
- });
282
- }
283
-
284
- async function wizardComponentSelection() {
285
- const components = await multiselect({
286
- message: 'What would you like to install?',
287
- options: [
288
- {
289
- value: 'cli',
290
- label: `${icons.cli} CLI - Command line interface`,
291
- hint: 'squish command for terminal use'
292
- },
293
- {
294
- value: 'plugins',
295
- label: `${icons.plugin} AI Agent Plugins`,
296
- hint: 'Claude Code, OpenClaw, OpenCode, etc.'
297
- }
298
- ],
299
- required: false
300
- });
301
-
302
- if (isCancel(components)) {
303
- cancel('Installation cancelled');
304
- process.exit(0);
305
- }
306
-
307
- return components;
308
- }
309
-
310
- async function wizardPluginSelection() {
311
- const choices = getPluginChoices();
312
-
313
- if (choices.length === 0) {
314
- console.log(c.yellow("No plugins available"));
315
- return [];
316
- }
317
-
318
- const plugins = await multiselect({
319
- message: 'Which AI agents do you want to integrate with?',
320
- options: choices,
321
- required: false
322
- });
323
-
324
- if (isCancel(plugins)) {
325
- cancel('Installation cancelled');
326
- process.exit(0);
327
- }
328
-
329
- return plugins;
330
- }
331
-
332
- async function wizardConfiguration() {
333
- const mode = await select({
334
- message: 'Select operation mode:',
335
- options: [
336
- {
337
- value: 'local',
338
- label: `${icons.local} Local Mode`,
339
- hint: 'Everything runs locally (default, recommended)'
340
- },
341
- {
342
- value: 'remote',
343
- label: `${icons.remote} Remote Mode`,
344
- hint: 'Connect to remote Squish server'
345
- }
346
- ]
347
- });
348
-
349
- if (isCancel(mode)) {
350
- cancel('Installation cancelled');
351
- process.exit(0);
352
- }
353
-
354
- const embeddings = await select({
355
- message: 'Select embeddings provider:',
356
- options: [
357
- {
358
- value: 'local',
359
- label: `${icons.brain} Local Embeddings`,
360
- hint: 'Uses local CPU (default, free, private)'
361
- },
362
- {
363
- value: 'openai',
364
- label: `${icons.cloud} OpenAI Embeddings`,
365
- hint: 'Requires OPENAI_API_KEY (better quality)'
366
- },
367
- {
368
- value: 'cohere',
369
- label: `${icons.cloud} Cohere Embeddings`,
370
- hint: 'Requires COHERE_API_KEY'
371
- }
372
- ]
373
- });
374
-
375
- if (isCancel(embeddings)) {
376
- cancel('Installation cancelled');
377
- process.exit(0);
378
- }
379
-
380
- return { mode, embeddings };
381
- }
382
-
383
- async function wizardReview(installConfig) {
384
- const { components, plugins, config } = installConfig;
385
-
386
- let summary = `${c.white("Installation Summary:")}\n\n`;
387
-
388
- summary += `${c.cyan("Components:")}\n`;
389
- if (components.includes('cli')) summary += ` ${icons.check} CLI\n`;
390
- if (components.includes('mcp')) summary += ` ${icons.check} MCP Server\n`;
391
- if (components.includes('plugins')) summary += ` ${icons.check} AI Agent Plugins\n`;
392
-
393
- if (components.includes('plugins') && plugins.length > 0) {
394
- summary += `\n${c.cyan("Plugins:")}\n`;
395
- plugins.forEach(p => {
396
- summary += ` ${icons.check} ${p}\n`;
397
- });
398
- }
399
-
400
- summary += `\n${c.cyan("Configuration:")}\n`;
401
- summary += ` ${icons.settings} Mode: ${config.mode}\n`;
402
- summary += ` ${icons.brain} Embeddings: ${config.embeddings}\n`;
403
-
404
- note(summary, 'Review');
405
-
406
- const shouldInstall = await confirm({
407
- message: 'Proceed with installation?',
408
- initialValue: true
409
- });
410
-
411
- if (isCancel(shouldInstall) || !shouldInstall) {
412
- cancel('Installation cancelled');
413
- process.exit(0);
414
- }
415
-
416
- return shouldInstall;
417
- }
418
-
419
- async function performInstallation(installConfig, options = {}) {
420
- const { components, plugins, config } = installConfig;
421
- const s = spinner();
422
-
423
- if (options.dryRun) {
424
- console.log();
425
- note(`${c.yellow("Dry-run mode - no changes made")}\n\nRemove --dry-run flag to perform actual installation.`, 'Preview');
426
- return;
427
- }
428
-
429
- // Install dependencies
430
- if (components.length > 0 || plugins.length > 0) {
431
- s.start('Installing dependencies...');
432
-
433
- const depResult = spawnSync(
434
- process.execPath,
435
- [path.join(root, "scripts", "dependency-manager.mjs")],
436
- { encoding: "utf8", stdio: "pipe", timeout: 120000 }
437
- );
438
-
439
- if (depResult.status !== 0) {
440
- s.stop(c.red(`${icons.cross} Dependency installation failed`));
441
- if (options.verbose && depResult.stderr) {
442
- console.log(c.gray(`Error: ${depResult.stderr}`));
443
- }
444
- process.exit(1);
445
- }
446
-
447
- s.stop(c.green(`${icons.check} Dependencies installed`));
448
- }
449
-
450
- // Install CLI
451
- if (components.includes('cli')) {
452
- s.start('Setting up CLI...');
453
- // CLI is already available via npm install, but we could add global link
454
- s.stop(c.green(`${icons.check} CLI ready`));
455
- }
456
-
457
- // Install plugins
458
- if (components.includes('plugins') && plugins.length > 0) {
459
- // Handle OpenCode specially - use its own installer
460
- const opencodeIndex = plugins.indexOf('opencode');
461
- if (opencodeIndex > -1) {
462
- plugins.splice(opencodeIndex, 1);
463
-
464
- s.start('Installing OpenCode plugin...');
465
- const opencodeResult = spawnSync(
466
- process.execPath,
467
- [path.join(root, "packages", "plugin-opencode", "install.mjs")],
468
- { encoding: "utf8", stdio: "pipe", timeout: 60000 }
469
- );
470
-
471
- if (opencodeResult.status !== 0) {
472
- s.stop(c.red(`${icons.cross} OpenCode plugin installation failed`));
473
- if (options.verbose && opencodeResult.stderr) {
474
- console.log(c.gray(`Error: ${opencodeResult.stderr}`));
475
- }
476
- } else {
477
- s.stop(c.green(`${icons.check} OpenCode plugin installed`));
478
- }
479
- }
480
-
481
- // Install remaining plugins using general installer
482
- if (plugins.length > 0) {
483
- s.start(`Installing ${plugins.length} plugin(s)...`);
484
-
485
- const installResult = spawnSync(
486
- process.execPath,
487
- [path.join(root, "scripts", "install-plugin.mjs"), "--client=" + plugins.join(",")],
488
- { encoding: "utf8", stdio: "pipe", timeout: 300000 }
489
- );
490
-
491
- if (installResult.status !== 0) {
492
- s.stop(c.red(`${icons.cross} Plugin installation failed`));
493
- if (options.verbose && installResult.stderr) {
494
- console.log(c.gray(`Error: ${installResult.stderr}`));
495
- }
496
- process.exit(1);
497
- }
498
-
499
- s.stop(c.green(`${icons.check} Plugins installed`));
500
- }
501
- }
502
-
503
- // Save configuration
504
- if (config) {
505
- s.start('Saving configuration...');
506
- // Save config to ~/.squish/config.json
507
- const configPath = path.join(os.homedir(), '.squish', 'config.json');
508
- const configDir = path.dirname(configPath);
509
-
510
- if (!fs.existsSync(configDir)) {
511
- fs.mkdirSync(configDir, { recursive: true });
512
- }
513
-
514
- fs.writeFileSync(configPath, JSON.stringify({
515
- mode: config.mode,
516
- embeddingsProvider: config.embeddings,
517
- installedAt: new Date().toISOString(),
518
- version: '1.0.0'
519
- }, null, 2));
520
-
521
- s.stop(c.green(`${icons.check} Configuration saved`));
522
- }
523
- }
524
-
525
- async function runWizard(options = {}) {
526
- printLogo();
527
-
528
- intro(c.cyan(`${icons.squish} Squish Memory Installer`));
529
-
530
- // Step 1: Component selection
531
- const components = await wizardComponentSelection();
532
-
533
- if (components.length === 0) {
534
- cancel('No components selected. Exiting.');
535
- process.exit(0);
536
- }
537
-
538
- // Step 2: Plugin selection (if chosen)
539
- let plugins = [];
540
- if (components.includes('plugins')) {
541
- plugins = await wizardPluginSelection();
542
- }
543
-
544
- // Step 3: Configuration
545
- const config = await wizardConfiguration();
546
-
547
- // Step 4: Review
548
- const installConfig = { components, plugins, config };
549
- await wizardReview(installConfig);
550
-
551
- // Step 5: Install
552
- console.log();
553
- await performInstallation(installConfig, options);
554
-
555
- // Success
556
- console.log();
557
- outro(c.green(`${icons.check} Installation Complete!`));
558
-
559
- // Next steps
560
- console.log();
561
- console.log(c.white("What's next?"));
562
- console.log(` ${c.cyan(icons.arrow)} Restart your AI assistant(s)`);
563
- console.log(` ${c.cyan(icons.arrow)} Try: ${c.gray("squish health")}`);
564
- console.log(` ${c.cyan(icons.arrow)} Try: ${c.cyan("squish remember \"Your first memory\"")}`);
565
- console.log();
566
- console.log(c.gray("Documentation: https://github.com/michielhdoteth/squish"));
567
- }
568
-
569
- async function handleNonInteractive(flags, options) {
570
- const choices = getPluginChoices();
571
-
572
- if (choices.length === 0) {
573
- console.log(c.red("No plugins available"));
574
- process.exit(1);
575
- }
576
-
577
- let pluginIds = [];
578
- let components = ['cli'];
579
-
580
- if (flags.quick) {
581
- // Quick install: CLI + all plugins
582
- pluginIds = choices.map(c => c.value);
583
- components = ['cli', 'plugins'];
584
- if (options.verbose) {
585
- printLogo();
586
- console.log(`${c.cyan("[QUICK MODE]")} Installing CLI + all plugins...\n`);
587
- }
588
- } else if (flags.all) {
589
- pluginIds = choices.map(c => c.value);
590
- components = ['cli', 'mcp', 'plugins'];
591
- if (options.verbose) {
592
- printLogo();
593
- console.log(`${c.cyan("[AUTO MODE]")} Installing all components...\n`);
594
- }
595
- } else if (flags.select.length > 0) {
596
- const validIds = choices.map(c => c.value);
597
- const invalid = flags.select.filter(s => !validIds.includes(s));
598
-
599
- if (invalid.length > 0) {
600
- console.log(c.red(`Invalid plugins: ${invalid.join(", ")}`));
601
- console.log(c.gray(`Available: ${validIds.join(", ")}`));
602
- process.exit(1);
603
- }
604
-
605
- pluginIds = flags.select;
606
- components = ['cli', 'plugins'];
607
- if (options.verbose) {
608
- printLogo();
609
- console.log(`${c.cyan("[AUTO MODE]")} Installing: ${pluginIds.join(", ")}...\n`);
610
- }
611
- } else {
612
- console.log(c.yellow("Auto mode requires --all, --quick, or --select flag"));
613
- const validIds = choices.map(c => c.value);
614
- console.log(c.gray(`Available: ${validIds.join(", ")}`));
615
- console.log(`Use ${c.cyan("--list")} to see all options`);
616
- process.exit(1);
617
- }
618
-
619
- const installConfig = {
620
- components,
621
- plugins: pluginIds,
622
- config: { mode: 'local', embeddings: 'local' }
623
- };
624
-
625
- await performInstallation(installConfig, options);
626
- }
627
-
628
- async function main() {
629
- const flags = parseArgs(process.argv);
630
-
631
- if (flags.help) {
632
- printHelp();
633
- process.exit(0);
634
- }
635
-
636
- if (flags.list) {
637
- listPlugins();
638
- process.exit(0);
639
- }
640
-
641
- const manifest = loadManifest();
642
- if (!manifest) {
643
- console.log(c.red("Error: Plugin manifest not found"));
644
- console.log(c.gray(`Expected: ${manifestPath}`));
645
- process.exit(1);
646
- }
647
-
648
- const options = {
649
- dryRun: flags.dryRun,
650
- verbose: flags.verbose
651
- };
652
-
653
- // Non-interactive mode (only if explicitly requested and not in interactive mode)
654
- if (!flags.interactive && (flags.auto || flags.quick || flags.select.length > 0 || shouldUseNonInteractive())) {
655
- await handleNonInteractive(flags, options);
656
- return;
657
- }
658
-
659
- // Interactive wizard mode (default when no flags, or when --interactive is passed)
660
- await runWizard(options);
661
- }
662
-
663
- process.on("SIGINT", () => {
664
- console.log(`\n${c.yellow("Installation cancelled.")}`);
665
- process.exit(0);
666
- });
667
-
668
- process.on("SIGTERM", () => {
669
- console.log(`\n${c.yellow("Installation cancelled.")}`);
670
- process.exit(0);
671
- });
672
-
673
- main().catch((err) => {
674
- console.log(c.red(`Fatal error: ${err.message}`));
675
- console.error(err);
676
- process.exit(1);
677
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Squish MCP Installer
5
+ * Installs Squish MCP configuration for various AI clients
6
+ * Supports: Claude Code, OpenCode, Codex, Cursor, VS Code, Windsurf
7
+ *
8
+ * Usage:
9
+ * bun run install # Interactive wizard
10
+ * bun run install --all # Auto-install for all detected clients
11
+ * bun run install --clients=claude-code,opencode # Specific clients
12
+ * bun run install --dry-run # Preview without installing
13
+ */
14
+
15
+ import { intro, outro, confirm, multiselect, select, isCancel, cancel, spinner, note } from '@clack/prompts';
16
+ import picocolors from 'picocolors';
17
+ import fs from "node:fs";
18
+ import os from "node:os";
19
+ import path from "node:path";
20
+
21
+ const root = process.cwd();
22
+ const manifestPath = path.join(root, "config", "plugin-manifest.json");
23
+
24
+ const c = picocolors;
25
+
26
+ const icons = {
27
+ squish: "🐙",
28
+ check: "",
29
+ cross: "",
30
+ arrow: "",
31
+ dot: "",
32
+ mcp: "🔌",
33
+ settings: "⚙️"
34
+ };
35
+
36
+ // Client config file locations
37
+ const CLIENT_CONFIGS = {
38
+ "claude-code": { dir: path.join(os.homedir(), ".claude"), file: "mcp.json" },
39
+ opencode: { dir: path.join(os.homedir(), ".config", "opencode"), file: "mcp-servers.json" },
40
+ codex: { dir: path.join(os.homedir(), ".codex"), file: "mcp-servers.json" },
41
+ cursor: { dir: path.join(os.homedir(), ".cursor"), file: "mcp.json" },
42
+ vscode: { dir: path.join(os.homedir(), ".vscode", "mcp"), file: "servers.json" },
43
+ windsurf: { dir: path.join(os.homedir(), ".windsurf"), file: "mcp-servers.json" }
44
+ };
45
+
46
+ // CLI argument parsing
47
+ function parseArgs() {
48
+ const args = process.argv.slice(2);
49
+ const options = {
50
+ all: false,
51
+ clients: [],
52
+ dryRun: false,
53
+ help: false
54
+ };
55
+
56
+ for (const arg of args) {
57
+ if (arg === '--all') options.all = true;
58
+ else if (arg === '--dry-run') options.dryRun = true;
59
+ else if (arg === '--help' || arg === '-h') options.help = true;
60
+ else if (arg.startsWith('--clients=')) {
61
+ options.clients = arg.slice(10).split(',').map(c => c.trim());
62
+ }
63
+ }
64
+
65
+ return options;
66
+ }
67
+
68
+ function printHelp() {
69
+ printLogo();
70
+ console.log(c.white("Usage:"));
71
+ console.log(` ${c.cyan("bun run install")} # Interactive wizard`);
72
+ console.log(` ${c.cyan("bun run install --all")} # Auto-install for all detected clients`);
73
+ console.log(` ${c.cyan("bun run install --clients=claude-code,opencode")} # Specific clients`);
74
+ console.log(` ${c.cyan("bun run install --dry-run")} # Preview without installing\n`);
75
+ console.log(c.white("Options:"));
76
+ console.log(` ${c.cyan("--all")} Install for all supported clients`);
77
+ console.log(` ${c.cyan("--clients=")} Comma-separated list of clients`);
78
+ console.log(` ${c.cyan("--dry-run")} Preview changes without installing`);
79
+ console.log(` ${c.cyan("--help")} Show this help message\n`);
80
+ console.log(c.white("Supported clients:"));
81
+ console.log(` claude-code, opencode, codex, cursor, vscode, windsurf`);
82
+ }
83
+
84
+ function printLogo() {
85
+ console.log(c.cyan(`
86
+ ██████╗ ██████╗ ██╗ ██╗██╗███████╗██╗ ██╗
87
+ ██╔════╝██╔═══██╗██║ ██║██║██╔════╝██║ ██║
88
+ ██████╗██║ ██║██║ ██║██║███████╗███████║
89
+ ╚════██║██║ ██║██║ ██║██║╚════██║██╔══██║
90
+ ██████║╚██████╔╝╚██████╔╝██║███████║██║ ██║
91
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝
92
+ `));
93
+ console.log(c.gray(" Universal Memory System for AI Agents\n"));
94
+ }
95
+
96
+ function loadManifest() {
97
+ if (!fs.existsSync(manifestPath)) {
98
+ return null;
99
+ }
100
+ try {
101
+ return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ function detectClients() {
108
+ const detected = {};
109
+ for (const [client, config] of Object.entries(CLIENT_CONFIGS)) {
110
+ const configPath = path.join(config.dir, config.file);
111
+ detected[client] = fs.existsSync(config.dir);
112
+ }
113
+ return detected;
114
+ }
115
+
116
+ function expandPath(filePath) {
117
+ if (filePath.startsWith("~")) {
118
+ return path.join(os.homedir(), filePath.slice(2));
119
+ }
120
+ return filePath;
121
+ }
122
+
123
+ function ensureDir(dirPath) {
124
+ if (!fs.existsSync(dirPath)) {
125
+ fs.mkdirSync(dirPath, { recursive: true });
126
+ }
127
+ }
128
+
129
+ function backupFile(filePath) {
130
+ if (fs.existsSync(filePath)) {
131
+ const backupPath = `${filePath}.bak.${Date.now()}`;
132
+ fs.copyFileSync(filePath, backupPath);
133
+ console.log(c.gray(` Backed up: ${backupPath}`));
134
+ }
135
+ }
136
+
137
+ // Generate MCP config for each client format
138
+ function generateMCPConfig(client, manifest) {
139
+ const target = manifest.targets[client];
140
+ if (!target) return null;
141
+
142
+ // OpenCode format: { "server-name": { type, command, enabled } }
143
+ if (target.format === "opencode") {
144
+ return {
145
+ [target.serverName]: {
146
+ type: target.type === "local" ? "local" : "stdio",
147
+ command: Array.isArray(target.command) ? target.command : [target.command],
148
+ enabled: target.enabled !== false
149
+ }
150
+ };
151
+ }
152
+
153
+ // Standard MCP format: { mcpServers: { "server-name": { type, command, args, env } } }
154
+ return {
155
+ mcpServers: {
156
+ [target.serverName]: {
157
+ command: target.command,
158
+ args: target.args || ["--stdio"],
159
+ env: target.env || {}
160
+ }
161
+ }
162
+ };
163
+ }
164
+
165
+ // Merge MCP config with existing config
166
+ function mergeMCPConfig(client, manifest, options = {}) {
167
+ const target = manifest.targets[client];
168
+ if (!target) return { error: `Unknown client: ${client}` };
169
+
170
+ const configInfo = CLIENT_CONFIGS[client];
171
+ const configPath = expandPath(path.join(configInfo.dir, configInfo.file));
172
+
173
+ // Generate new config
174
+ const newConfig = generateMCPConfig(client, manifest);
175
+ if (!newConfig) return { error: "Failed to generate config" };
176
+
177
+ ensureDir(configInfo.dir);
178
+
179
+ let existingConfig = {};
180
+ if (fs.existsSync(configPath)) {
181
+ try {
182
+ existingConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
183
+ } catch {
184
+ existingConfig = {};
185
+ }
186
+ }
187
+
188
+ // Merge based on format
189
+ let merged;
190
+ if (target.format === "opencode") {
191
+ // OpenCode format: merge at root level
192
+ merged = { ...existingConfig, ...newConfig };
193
+ } else {
194
+ // Standard format: merge under mcpServers
195
+ merged = { ...existingConfig };
196
+ if (!merged.mcpServers) merged.mcpServers = {};
197
+ merged.mcpServers = { ...merged.mcpServers, ...newConfig.mcpServers };
198
+ }
199
+
200
+ // Write config
201
+ if (!options.dryRun) {
202
+ backupFile(configPath);
203
+ fs.writeFileSync(configPath, JSON.stringify(merged, null, 2));
204
+ }
205
+
206
+ return { ok: true, configPath, merged };
207
+ }
208
+
209
+ function getClientChoices(manifest, detected) {
210
+ if (!manifest || !manifest.targets) return [];
211
+
212
+ const clientNames = {
213
+ "claude-code": "Claude Code",
214
+ opencode: "OpenCode",
215
+ codex: "Codex",
216
+ cursor: "Cursor",
217
+ vscode: "VS Code",
218
+ windsurf: "Windsurf"
219
+ };
220
+
221
+ return Object.keys(manifest.targets).map(client => {
222
+ const isInstalled = detected[client];
223
+ const name = clientNames[client] || client;
224
+ let label = name;
225
+ if (isInstalled) label += ` ${c.green(icons.check)}`;
226
+ return {
227
+ value: client,
228
+ label: label,
229
+ hint: isInstalled ? "Config found" : "Not detected"
230
+ };
231
+ });
232
+ }
233
+
234
+ async function main() {
235
+ const opts = parseArgs();
236
+
237
+ if (opts.help) {
238
+ printHelp();
239
+ process.exit(0);
240
+ }
241
+
242
+ const manifest = loadManifest();
243
+ if (!manifest) {
244
+ console.log(c.red("Error: Plugin manifest not found"));
245
+ process.exit(1);
246
+ }
247
+
248
+ const detected = detectClients();
249
+ const choices = getClientChoices(manifest, detected);
250
+
251
+ let clients;
252
+
253
+ // Non-interactive mode
254
+ if (opts.all) {
255
+ // Install for all supported clients
256
+ clients = choices.map(c => c.value);
257
+ printLogo();
258
+ console.log(c.cyan(`Auto-installing for all ${clients.length} clients...\n`));
259
+ } else if (opts.clients.length > 0) {
260
+ // Install for specific clients
261
+ clients = opts.clients;
262
+ printLogo();
263
+ console.log(c.cyan(`Installing for: ${clients.join(', ')}\n`));
264
+ } else if (process.stdin.isTTY) {
265
+ // Interactive mode
266
+ printLogo();
267
+ intro(c.cyan(`${icons.squish} Squish MCP Installer`));
268
+
269
+ // Select clients to install
270
+ clients = await multiselect({
271
+ message: 'Which AI agents do you want to install Squish MCP for?',
272
+ options: choices,
273
+ required: false
274
+ });
275
+
276
+ if (isCancel(clients) || !clients || clients.length === 0) {
277
+ console.log(c.yellow("No clients selected. Exiting."));
278
+ process.exit(0);
279
+ }
280
+ } else {
281
+ // Non-interactive without flags - show help
282
+ printHelp();
283
+ process.exit(0);
284
+ }
285
+
286
+ // Dry run mode
287
+ if (opts.dryRun) {
288
+ console.log(c.yellow("\n--- DRY RUN MODE ---\n"));
289
+ for (const client of clients) {
290
+ const configInfo = CLIENT_CONFIGS[client];
291
+ console.log(` Would install for ${client}:`);
292
+ console.log(` Config: ${configInfo.dir}/${configInfo.file}`);
293
+ }
294
+ console.log("");
295
+ process.exit(0);
296
+ }
297
+
298
+ // Non-interactive: skip review/confirm
299
+ const isInteractive = opts.all || opts.clients.length === 0 && process.stdin.isTTY;
300
+
301
+ if (isInteractive) {
302
+ // Review
303
+ let summary = `${c.white("Installation Summary:")}\n\n`;
304
+ summary += `${c.cyan("Installing Squish MCP for:")}\n`;
305
+ clients.forEach(client => {
306
+ const configInfo = CLIENT_CONFIGS[client];
307
+ summary += ` ${icons.check} ${client}\n`;
308
+ summary += ` ${c.gray(configInfo.dir)}\n`;
309
+ });
310
+
311
+ note(summary, 'Review');
312
+
313
+ const shouldInstall = await confirm({
314
+ message: 'Proceed with installation?',
315
+ initialValue: true
316
+ });
317
+
318
+ if (isCancel(shouldInstall) || !shouldInstall) {
319
+ console.log(c.yellow("Installation cancelled"));
320
+ process.exit(0);
321
+ }
322
+ }
323
+
324
+ // Install
325
+ const s = spinner();
326
+ s.start('Installing MCP configurations...');
327
+
328
+ const results = [];
329
+ for (const client of clients) {
330
+ const result = mergeMCPConfig(client, manifest);
331
+ results.push({ client, ...result });
332
+ }
333
+
334
+ const errors = results.filter(r => r.error);
335
+ const success = results.filter(r => r.ok);
336
+
337
+ if (errors.length > 0) {
338
+ s.stop(c.red(`${icons.cross} Some installations failed`));
339
+ errors.forEach(e => console.log(c.red(` ${e.client}: ${e.error}`)));
340
+ } else {
341
+ s.stop(c.green(`${icons.check} Installed for ${success.length} client(s)`));
342
+ }
343
+
344
+ // Next steps
345
+ console.log();
346
+ console.log(c.white("What's next?"));
347
+ console.log(` ${c.cyan(icons.arrow)} Restart your AI assistant(s)`);
348
+ console.log(` ${c.cyan(icons.arrow)} Try: squish remember "Your first memory"`);
349
+ console.log();
350
+
351
+ outro(c.green(`${icons.check} Installation Complete!`));
352
+ }
353
+
354
+ main().catch((err) => {
355
+ console.log(c.red(`Fatal error: ${err.message}`));
356
+ process.exit(1);
357
+ });