@geminilight/mindos 0.1.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 (282) hide show
  1. package/.env.local.example +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +423 -0
  4. package/README_zh.md +423 -0
  5. package/app/README.md +152 -0
  6. package/app/app/api/ask/route.ts +170 -0
  7. package/app/app/api/ask-sessions/route.ts +90 -0
  8. package/app/app/api/auth/route.ts +37 -0
  9. package/app/app/api/backlinks/route.ts +22 -0
  10. package/app/app/api/bootstrap/route.ts +37 -0
  11. package/app/app/api/extract-pdf/route.ts +82 -0
  12. package/app/app/api/file/route.ts +138 -0
  13. package/app/app/api/files/route.ts +12 -0
  14. package/app/app/api/git/route.ts +42 -0
  15. package/app/app/api/graph/route.ts +113 -0
  16. package/app/app/api/recent-files/route.ts +10 -0
  17. package/app/app/api/search/route.ts +17 -0
  18. package/app/app/api/settings/reset-token/route.ts +21 -0
  19. package/app/app/api/settings/route.ts +123 -0
  20. package/app/app/error.tsx +33 -0
  21. package/app/app/globals.css +368 -0
  22. package/app/app/icon.svg +35 -0
  23. package/app/app/layout.tsx +103 -0
  24. package/app/app/login/page.tsx +120 -0
  25. package/app/app/page.tsx +12 -0
  26. package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
  27. package/app/app/view/[...path]/error.tsx +33 -0
  28. package/app/app/view/[...path]/loading.tsx +15 -0
  29. package/app/app/view/[...path]/page.tsx +93 -0
  30. package/app/components/AskFab.tsx +59 -0
  31. package/app/components/AskModal.tsx +398 -0
  32. package/app/components/Backlinks.tsx +75 -0
  33. package/app/components/Breadcrumb.tsx +31 -0
  34. package/app/components/CsvView.tsx +325 -0
  35. package/app/components/DirView.tsx +138 -0
  36. package/app/components/Editor.tsx +124 -0
  37. package/app/components/EditorWrapper.tsx +17 -0
  38. package/app/components/ErrorBoundary.tsx +53 -0
  39. package/app/components/FileTree.tsx +369 -0
  40. package/app/components/HomeContent.tsx +262 -0
  41. package/app/components/JsonView.tsx +27 -0
  42. package/app/components/MarkdownEditor.tsx +95 -0
  43. package/app/components/MarkdownView.tsx +118 -0
  44. package/app/components/SearchModal.tsx +193 -0
  45. package/app/components/SettingsModal.tsx +237 -0
  46. package/app/components/Sidebar.tsx +136 -0
  47. package/app/components/SidebarLayout.tsx +36 -0
  48. package/app/components/TableOfContents.tsx +150 -0
  49. package/app/components/ThemeToggle.tsx +34 -0
  50. package/app/components/WysiwygEditor.tsx +75 -0
  51. package/app/components/ask/FileChip.tsx +30 -0
  52. package/app/components/ask/MentionPopover.tsx +52 -0
  53. package/app/components/ask/MessageList.tsx +126 -0
  54. package/app/components/ask/SessionHistory.tsx +49 -0
  55. package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
  56. package/app/components/renderers/BacklinksRenderer.tsx +147 -0
  57. package/app/components/renderers/ConfigRenderer.tsx +236 -0
  58. package/app/components/renderers/CsvRenderer.tsx +77 -0
  59. package/app/components/renderers/DiffRenderer.tsx +310 -0
  60. package/app/components/renderers/GraphRenderer.tsx +428 -0
  61. package/app/components/renderers/SummaryRenderer.tsx +251 -0
  62. package/app/components/renderers/TimelineRenderer.tsx +213 -0
  63. package/app/components/renderers/TodoRenderer.tsx +474 -0
  64. package/app/components/renderers/WorkflowRenderer.tsx +404 -0
  65. package/app/components/renderers/csv/BoardView.tsx +146 -0
  66. package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
  67. package/app/components/renderers/csv/EditableCell.tsx +43 -0
  68. package/app/components/renderers/csv/GalleryView.tsx +40 -0
  69. package/app/components/renderers/csv/TableView.tsx +164 -0
  70. package/app/components/renderers/csv/types.ts +87 -0
  71. package/app/components/settings/AiTab.tsx +111 -0
  72. package/app/components/settings/AppearanceTab.tsx +101 -0
  73. package/app/components/settings/KnowledgeTab.tsx +157 -0
  74. package/app/components/settings/PluginsTab.tsx +82 -0
  75. package/app/components/settings/Primitives.tsx +60 -0
  76. package/app/components/settings/ShortcutsTab.tsx +22 -0
  77. package/app/components/settings/types.ts +41 -0
  78. package/app/components/ui/button.tsx +60 -0
  79. package/app/components/ui/dialog.tsx +157 -0
  80. package/app/components/ui/input.tsx +20 -0
  81. package/app/components/ui/scroll-area.tsx +55 -0
  82. package/app/components/ui/toggle.tsx +44 -0
  83. package/app/components/ui/tooltip.tsx +66 -0
  84. package/app/components.json +25 -0
  85. package/app/data/pages/home-dark.png +0 -0
  86. package/app/data/pages/home-mobile-crop.png +0 -0
  87. package/app/data/pages/home-mobile.png +0 -0
  88. package/app/data/pages/home.png +0 -0
  89. package/app/data/pages/view-dir.png +0 -0
  90. package/app/data/pages/view-file-bot.png +0 -0
  91. package/app/data/pages/view-file-dark-crop.png +0 -0
  92. package/app/data/pages/view-file-dark.png +0 -0
  93. package/app/data/pages/view-file-mobile.png +0 -0
  94. package/app/data/pages/view-file-sm.png +0 -0
  95. package/app/data/pages/view-file-top.png +0 -0
  96. package/app/data/pages/view-file.png +0 -0
  97. package/app/eslint.config.mjs +18 -0
  98. package/app/hooks/useAskSession.ts +181 -0
  99. package/app/hooks/useFileUpload.ts +126 -0
  100. package/app/hooks/useMention.ts +65 -0
  101. package/app/lib/LocaleContext.tsx +40 -0
  102. package/app/lib/actions.ts +40 -0
  103. package/app/lib/agent/index.ts +3 -0
  104. package/app/lib/agent/model.ts +18 -0
  105. package/app/lib/agent/prompt.ts +32 -0
  106. package/app/lib/agent/tools.ts +151 -0
  107. package/app/lib/api.ts +55 -0
  108. package/app/lib/core/backlinks.ts +40 -0
  109. package/app/lib/core/csv.ts +28 -0
  110. package/app/lib/core/fs-ops.ts +118 -0
  111. package/app/lib/core/git.ts +50 -0
  112. package/app/lib/core/index.ts +58 -0
  113. package/app/lib/core/lines.ts +89 -0
  114. package/app/lib/core/search.ts +79 -0
  115. package/app/lib/core/security.ts +43 -0
  116. package/app/lib/core/tree.ts +113 -0
  117. package/app/lib/core/types.ts +40 -0
  118. package/app/lib/fs.ts +467 -0
  119. package/app/lib/i18n.ts +300 -0
  120. package/app/lib/jwt.ts +58 -0
  121. package/app/lib/renderers/index.ts +79 -0
  122. package/app/lib/renderers/registry.ts +70 -0
  123. package/app/lib/settings.ts +150 -0
  124. package/app/lib/types.ts +32 -0
  125. package/app/lib/utils.ts +34 -0
  126. package/app/next-env.d.ts +6 -0
  127. package/app/next.config.ts +10 -0
  128. package/app/package-lock.json +15306 -0
  129. package/app/package.json +71 -0
  130. package/app/postcss.config.mjs +7 -0
  131. package/app/proxy.ts +64 -0
  132. package/app/public/file.svg +1 -0
  133. package/app/public/globe.svg +1 -0
  134. package/app/public/landing/index.html +353 -0
  135. package/app/public/landing/style.css +216 -0
  136. package/app/public/logo-square.svg +37 -0
  137. package/app/public/logo.svg +37 -0
  138. package/app/public/next.svg +1 -0
  139. package/app/public/vercel.svg +1 -0
  140. package/app/public/window.svg +1 -0
  141. package/app/scripts/extract-pdf.cjs +56 -0
  142. package/app/tsconfig.json +34 -0
  143. package/app/vitest.config.ts +14 -0
  144. package/assets/demo-flow-zh.html +622 -0
  145. package/assets/images/demo-flow-dark.png +0 -0
  146. package/assets/images/demo-flow-light.png +0 -0
  147. package/assets/images/demo-flow-zh-dark.png +0 -0
  148. package/assets/images/demo-flow-zh-light.png +0 -0
  149. package/assets/images/gui-sync-cv.png +0 -0
  150. package/assets/logo-square.svg +37 -0
  151. package/bin/cli.js +894 -0
  152. package/mcp/README.md +113 -0
  153. package/mcp/package-lock.json +1717 -0
  154. package/mcp/package.json +18 -0
  155. package/mcp/src/index.ts +494 -0
  156. package/mcp/tsconfig.json +13 -0
  157. package/package.json +49 -0
  158. package/scripts/setup.js +675 -0
  159. package/scripts/upgrade-prompt.md +147 -0
  160. package/skills/mindos/SKILL.md +319 -0
  161. package/skills/mindos-zh/SKILL.md +318 -0
  162. package/templates/README.md +31 -0
  163. package/templates/empty/CHANGELOG.md +9 -0
  164. package/templates/empty/CONFIG.json +197 -0
  165. package/templates/empty/CONFIG.md +73 -0
  166. package/templates/empty/INSTRUCTION.md +177 -0
  167. package/templates/empty/README.md +27 -0
  168. package/templates/en/CHANGELOG.md +9 -0
  169. package/templates/en/CONFIG.json +197 -0
  170. package/templates/en/CONFIG.md +73 -0
  171. package/templates/en/INSTRUCTION.md +177 -0
  172. package/templates/en/README.md +27 -0
  173. package/templates/en/TODO.md +13 -0
  174. package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
  175. package/templates/en//360/237/221/244 Profile/README.md" +15 -0
  176. package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
  177. package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
  178. package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
  179. package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
  180. package/templates/en//360/237/223/232 Resources/README.md" +21 -0
  181. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
  182. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
  183. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
  184. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
  185. package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
  186. package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
  187. package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
  188. package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
  189. package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
  190. package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
  191. package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
  192. package/templates/en//360/237/223/235 Notes/README.md" +24 -0
  193. package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
  194. package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
  195. package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
  196. package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
  197. package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
  198. package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
  199. package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
  200. package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
  201. package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
  202. package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
  203. package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
  204. package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
  205. package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
  206. package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
  207. package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
  208. package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
  209. package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
  210. package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
  211. package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
  212. package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
  213. package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
  214. package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
  215. package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
  216. package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
  217. package/templates/en//360/237/224/227 Connections/README.md" +20 -0
  218. package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
  219. package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
  220. package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
  221. package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
  222. package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
  223. package/templates/en//360/237/232/200 Projects/README.md" +21 -0
  224. package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
  225. package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
  226. package/templates/template-generation-skill.md +79 -0
  227. package/templates/zh/CHANGELOG.md +9 -0
  228. package/templates/zh/CONFIG.json +197 -0
  229. package/templates/zh/CONFIG.md +66 -0
  230. package/templates/zh/INSTRUCTION.md +177 -0
  231. package/templates/zh/README.md +27 -0
  232. package/templates/zh/TODO.md +13 -0
  233. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
  234. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
  235. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
  236. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
  237. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
  238. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
  239. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
  240. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
  241. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +1 -0
  242. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +1 -0
  243. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
  244. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
  245. package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
  246. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
  247. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256//360/237/247/252_example_/344/274/232/350/256/256/347/272/252/350/246/201.md" +17 -0
  248. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210/README.md" +8 -0
  249. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210//360/237/247/252_example_/345/276/205/345/217/215/351/246/210/344/272/213/351/241/271.md" +16 -0
  250. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
  251. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +16 -0
  252. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261/README.md" +8 -0
  253. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261//360/237/247/252_example_/344/270/264/346/227/266/351/200/237/350/256/260.md" +13 -0
  254. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
  255. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
  256. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
  257. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257//360/237/247/252_example_/344/277/241/346/201/257/351/207/207/351/233/206/346/265/201/347/250/213.md" +13 -0
  258. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
  259. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223//360/237/247/252_example_/345/206/205/345/256/271/345/217/221/345/270/203/346/265/201/347/250/213.md" +13 -0
  260. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
  261. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224//360/237/247/252_example_/346/226/207/347/214/256/347/273/274/350/277/260/346/265/201/347/250/213.md" +16 -0
  262. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
  263. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +26 -0
  264. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
  265. package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
  266. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/205/263/347/263/273/346/200/273/350/247/210.csv" +5 -0
  267. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
  268. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213//360/237/247/252_example_/345/220/214/344/272/213/350/265/265/344/270/200/350/276/260.md" +16 -0
  269. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
  270. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246//360/237/247/252_example_/345/220/214/345/255/246/351/231/210/347/253/213/346/254/247.md" +16 -0
  271. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
  272. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272//360/237/247/252_example_/345/256/266/344/272/272/347/216/213/345/273/272/345/233/275.md" +16 -0
  273. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
  274. package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213//360/237/247/252_example_/346/234/213/345/217/213/346/236/227/345/260/217/344/270/275.md" +16 -0
  275. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
  276. package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
  277. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
  278. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +20 -0
  279. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243/README.md" +9 -0
  280. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243//360/237/247/252_example_/345/275/222/346/241/243/351/241/271/347/233/256/350/256/260/345/275/225.md" +15 -0
  281. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
  282. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224//360/237/247/252_example_/347/247/221/347/240/224/351/241/271/347/233/256/347/256/200/346/212/245.md" +16 -0
package/bin/cli.js ADDED
@@ -0,0 +1,894 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MindOS CLI
5
+ *
6
+ * Usage:
7
+ * mindos onboard — interactive setup → writes ~/.mindos/config.json
8
+ * mindos onboard --install-daemon — setup + install & start as background OS service
9
+ * mindos start — start app + MCP server (production, auto-rebuilds if needed)
10
+ * mindos start --daemon — install + start as background OS service
11
+ * mindos start --verbose — start with verbose MCP logging
12
+ * mindos dev — start app + MCP server (dev mode)
13
+ * mindos dev --turbopack — start with Turbopack (faster HMR)
14
+ * mindos build — build the app for production
15
+ * mindos mcp — start MCP server only
16
+ * mindos stop — stop running MindOS processes
17
+ * mindos restart — stop then start
18
+ * mindos token — show current auth token and MCP config snippet
19
+ * mindos gateway install — install background service (systemd/launchd)
20
+ * mindos gateway uninstall — remove background service
21
+ * mindos gateway start — start the background service
22
+ * mindos gateway stop — stop the background service
23
+ * mindos gateway status — show service status
24
+ * mindos gateway logs — tail service logs
25
+ * mindos doctor — health check (config, ports, build, daemon)
26
+ * mindos update — update to latest version
27
+ * mindos logs — tail service logs (~/.mindos/mindos.log)
28
+ * mindos config show — print current config (API keys masked)
29
+ * mindos config set <key> <val> — update a single config field
30
+ * mindos config validate — validate config file
31
+ */
32
+
33
+ import { execSync, spawn } from 'node:child_process';
34
+ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
35
+ import { resolve, dirname } from 'node:path';
36
+ import { homedir, networkInterfaces } from 'node:os';
37
+ import { fileURLToPath } from 'node:url';
38
+ import { createConnection } from 'node:net';
39
+
40
+ const __dirname = dirname(fileURLToPath(import.meta.url));
41
+ const ROOT = resolve(__dirname, '..');
42
+ const CONFIG_PATH = resolve(homedir(), '.mindos', 'config.json');
43
+ const PID_PATH = resolve(homedir(), '.mindos', 'mindos.pid');
44
+ const BUILD_STAMP = resolve(ROOT, 'app', '.next', '.mindos-build-version');
45
+
46
+ // ── Colors ────────────────────────────────────────────────────────────────────
47
+
48
+ const isTTY = process.stdout.isTTY;
49
+ const bold = (s) => isTTY ? `\x1b[1m${s}\x1b[0m` : s;
50
+ const dim = (s) => isTTY ? `\x1b[2m${s}\x1b[0m` : s;
51
+ const cyan = (s) => isTTY ? `\x1b[36m${s}\x1b[0m` : s;
52
+ const green = (s) => isTTY ? `\x1b[32m${s}\x1b[0m` : s;
53
+ const red = (s) => isTTY ? `\x1b[31m${s}\x1b[0m` : s;
54
+ const yellow= (s) => isTTY ? `\x1b[33m${s}\x1b[0m` : s;
55
+
56
+ // ── Config ────────────────────────────────────────────────────────────────────
57
+
58
+ function loadConfig() {
59
+ if (!existsSync(CONFIG_PATH)) return;
60
+ let config;
61
+ try {
62
+ config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
63
+ } catch {
64
+ console.error(`Warning: failed to parse ${CONFIG_PATH}`);
65
+ return;
66
+ }
67
+
68
+ const set = (key, val) => {
69
+ if (val && !process.env[key]) process.env[key] = String(val);
70
+ };
71
+
72
+ set('MIND_ROOT', config.mindRoot);
73
+ set('MINDOS_WEB_PORT', config.port);
74
+ set('MINDOS_MCP_PORT', config.mcpPort);
75
+ set('AUTH_TOKEN', config.authToken);
76
+ set('WEB_PASSWORD', config.webPassword);
77
+ set('AI_PROVIDER', config.ai?.provider);
78
+
79
+ const providers = config.ai?.providers;
80
+ if (providers) {
81
+ set('ANTHROPIC_API_KEY', providers.anthropic?.apiKey);
82
+ set('ANTHROPIC_MODEL', providers.anthropic?.model);
83
+ set('OPENAI_API_KEY', providers.openai?.apiKey);
84
+ set('OPENAI_MODEL', providers.openai?.model);
85
+ set('OPENAI_BASE_URL', providers.openai?.baseUrl);
86
+ } else {
87
+ set('ANTHROPIC_API_KEY', config.ai?.anthropicApiKey);
88
+ set('ANTHROPIC_MODEL', config.ai?.anthropicModel);
89
+ set('OPENAI_API_KEY', config.ai?.openaiApiKey);
90
+ set('OPENAI_MODEL', config.ai?.openaiModel);
91
+ set('OPENAI_BASE_URL', config.ai?.openaiBaseUrl);
92
+ }
93
+ }
94
+
95
+ function getStartMode() {
96
+ try {
97
+ return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).startMode || 'start';
98
+ } catch {
99
+ return 'start';
100
+ }
101
+ }
102
+
103
+ // ── Build helpers ─────────────────────────────────────────────────────────────
104
+
105
+ function needsBuild() {
106
+ const nextDir = resolve(ROOT, 'app', '.next');
107
+ if (!existsSync(nextDir)) return true;
108
+ try {
109
+ const builtVersion = readFileSync(BUILD_STAMP, 'utf-8').trim();
110
+ const currentVersion = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
111
+ return builtVersion !== currentVersion;
112
+ } catch {
113
+ return true;
114
+ }
115
+ }
116
+
117
+ function writeBuildStamp() {
118
+ const version = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
119
+ writeFileSync(BUILD_STAMP, version, 'utf-8');
120
+ }
121
+
122
+ // ── Port check ────────────────────────────────────────────────────────────────
123
+
124
+ function isPortInUse(port) {
125
+ return new Promise((resolve) => {
126
+ const sock = createConnection({ port, host: '127.0.0.1' });
127
+ sock.once('connect', () => { sock.destroy(); resolve(true); });
128
+ sock.once('error', () => { sock.destroy(); resolve(false); });
129
+ });
130
+ }
131
+
132
+ async function assertPortFree(port, name) {
133
+ if (await isPortInUse(port)) {
134
+ console.error(`\n${red('✘')} ${bold(`Port ${port} is already in use`)} ${dim(`(${name})`)}`);
135
+ console.error(`\n ${dim('Stop MindOS:')} mindos stop`);
136
+ console.error(` ${dim('Find the process:')} lsof -i :${port}\n`);
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ // ── PID file ──────────────────────────────────────────────────────────────────
142
+
143
+ function savePids(...pids) {
144
+ writeFileSync(PID_PATH, pids.filter(Boolean).join('\n'), 'utf-8');
145
+ }
146
+
147
+ function loadPids() {
148
+ if (!existsSync(PID_PATH)) return [];
149
+ return readFileSync(PID_PATH, 'utf-8').split('\n').map(Number).filter(Boolean);
150
+ }
151
+
152
+ function clearPids() {
153
+ if (existsSync(PID_PATH)) rmSync(PID_PATH);
154
+ }
155
+
156
+ // ── Stop ──────────────────────────────────────────────────────────────────────
157
+
158
+ function stopMindos() {
159
+ const pids = loadPids();
160
+ if (!pids.length) {
161
+ console.log(yellow('No PID file found, trying pattern-based stop...'));
162
+ try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
163
+ try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
164
+ console.log(green('✔ Done'));
165
+ return;
166
+ }
167
+ let stopped = 0;
168
+ for (const pid of pids) {
169
+ try {
170
+ process.kill(pid, 'SIGTERM');
171
+ stopped++;
172
+ } catch {
173
+ // process already gone — ignore
174
+ }
175
+ }
176
+ clearPids();
177
+ console.log(stopped
178
+ ? green(`✔ Stopped ${stopped} process${stopped > 1 ? 'es' : ''}`)
179
+ : dim('No running processes found'));
180
+ }
181
+
182
+ // ── Daemon / gateway helpers ───────────────────────────────────────────────────
183
+
184
+ const MINDOS_DIR = resolve(homedir(), '.mindos');
185
+ const LOG_PATH = resolve(MINDOS_DIR, 'mindos.log');
186
+ const CLI_PATH = resolve(__dirname, 'cli.js');
187
+ const NODE_BIN = process.execPath;
188
+
189
+ function getPlatform() {
190
+ if (process.platform === 'darwin') return 'launchd';
191
+ if (process.platform === 'linux') return 'systemd';
192
+ return null;
193
+ }
194
+
195
+ function ensureMindosDir() {
196
+ if (!existsSync(MINDOS_DIR)) mkdirSync(MINDOS_DIR, { recursive: true });
197
+ }
198
+
199
+ // ── systemd (Linux) ───────────────────────────────────────────────────────────
200
+
201
+ const SYSTEMD_DIR = resolve(homedir(), '.config', 'systemd', 'user');
202
+ const SYSTEMD_UNIT = resolve(SYSTEMD_DIR, 'mindos.service');
203
+
204
+ const systemd = {
205
+ install() {
206
+ if (!existsSync(SYSTEMD_DIR)) mkdirSync(SYSTEMD_DIR, { recursive: true });
207
+ ensureMindosDir();
208
+ const unit = [
209
+ '[Unit]',
210
+ 'Description=MindOS app + MCP server',
211
+ 'After=network.target',
212
+ '',
213
+ '[Service]',
214
+ 'Type=simple',
215
+ `ExecStart=${NODE_BIN} ${CLI_PATH} start`,
216
+ 'Restart=on-failure',
217
+ 'RestartSec=3',
218
+ `Environment=HOME=${homedir()}`,
219
+ `EnvironmentFile=-${resolve(MINDOS_DIR, 'env')}`,
220
+ `StandardOutput=append:${LOG_PATH}`,
221
+ `StandardError=append:${LOG_PATH}`,
222
+ '',
223
+ '[Install]',
224
+ 'WantedBy=default.target',
225
+ ].join('\n');
226
+ writeFileSync(SYSTEMD_UNIT, unit, 'utf-8');
227
+ console.log(green(`✔ Wrote ${SYSTEMD_UNIT}`));
228
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
229
+ execSync('systemctl --user enable mindos', { stdio: 'inherit' });
230
+ console.log(green('✔ Service installed and enabled'));
231
+ },
232
+
233
+ start() {
234
+ execSync('systemctl --user start mindos', { stdio: 'inherit' });
235
+ console.log(green('✔ Service started'));
236
+ },
237
+
238
+ stop() {
239
+ execSync('systemctl --user stop mindos', { stdio: 'inherit' });
240
+ console.log(green('✔ Service stopped'));
241
+ },
242
+
243
+ status() {
244
+ try {
245
+ execSync('systemctl --user status mindos', { stdio: 'inherit' });
246
+ } catch { /* status exits non-zero when stopped */ }
247
+ },
248
+
249
+ logs() {
250
+ execSync(`journalctl --user -u mindos -f`, { stdio: 'inherit' });
251
+ },
252
+
253
+ uninstall() {
254
+ try {
255
+ execSync('systemctl --user disable --now mindos', { stdio: 'inherit' });
256
+ } catch { /* may already be stopped */ }
257
+ if (existsSync(SYSTEMD_UNIT)) {
258
+ rmSync(SYSTEMD_UNIT);
259
+ console.log(green(`✔ Removed ${SYSTEMD_UNIT}`));
260
+ }
261
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
262
+ console.log(green('✔ Service uninstalled'));
263
+ },
264
+ };
265
+
266
+ // ── launchd (macOS) ───────────────────────────────────────────────────────────
267
+
268
+ const LAUNCHD_DIR = resolve(homedir(), 'Library', 'LaunchAgents');
269
+ const LAUNCHD_PLIST = resolve(LAUNCHD_DIR, 'com.mindos.app.plist');
270
+ const LAUNCHD_LABEL = 'com.mindos.app';
271
+
272
+ function launchctlUid() {
273
+ return execSync('id -u').toString().trim();
274
+ }
275
+
276
+ const launchd = {
277
+ install() {
278
+ if (!existsSync(LAUNCHD_DIR)) mkdirSync(LAUNCHD_DIR, { recursive: true });
279
+ ensureMindosDir();
280
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
281
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
282
+ <plist version="1.0">
283
+ <dict>
284
+ <key>Label</key><string>${LAUNCHD_LABEL}</string>
285
+ <key>ProgramArguments</key>
286
+ <array>
287
+ <string>${NODE_BIN}</string>
288
+ <string>${CLI_PATH}</string>
289
+ <string>start</string>
290
+ </array>
291
+ <key>RunAtLoad</key><true/>
292
+ <key>KeepAlive</key><true/>
293
+ <key>StandardOutPath</key><string>${LOG_PATH}</string>
294
+ <key>StandardErrorPath</key><string>${LOG_PATH}</string>
295
+ <key>EnvironmentVariables</key>
296
+ <dict>
297
+ <key>HOME</key><string>${homedir()}</string>
298
+ </dict>
299
+ </dict>
300
+ </plist>
301
+ `;
302
+ writeFileSync(LAUNCHD_PLIST, plist, 'utf-8');
303
+ console.log(green(`✔ Wrote ${LAUNCHD_PLIST}`));
304
+ try {
305
+ execSync(`launchctl bootstrap gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
306
+ } catch { /* already bootstrapped */ }
307
+ console.log(green('✔ Service installed'));
308
+ },
309
+
310
+ start() {
311
+ execSync(`launchctl kickstart -k gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: 'inherit' });
312
+ console.log(green('✔ Service started'));
313
+ },
314
+
315
+ stop() {
316
+ try {
317
+ execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
318
+ } catch { /* may not be running */ }
319
+ console.log(green('✔ Service stopped'));
320
+ },
321
+
322
+ status() {
323
+ try {
324
+ execSync(`launchctl print gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: 'inherit' });
325
+ } catch {
326
+ console.log(dim('Service is not running'));
327
+ }
328
+ },
329
+
330
+ logs() {
331
+ execSync(`tail -f ${LOG_PATH}`, { stdio: 'inherit' });
332
+ },
333
+
334
+ uninstall() {
335
+ try {
336
+ execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
337
+ } catch { /* may not be running */ }
338
+ if (existsSync(LAUNCHD_PLIST)) {
339
+ rmSync(LAUNCHD_PLIST);
340
+ console.log(green(`✔ Removed ${LAUNCHD_PLIST}`));
341
+ }
342
+ console.log(green('✔ Service uninstalled'));
343
+ },
344
+ };
345
+
346
+ // ── gateway dispatcher ────────────────────────────────────────────────────────
347
+
348
+ async function runGatewayCommand(sub) {
349
+ const platform = getPlatform();
350
+ if (!platform) {
351
+ console.error(red('Daemon mode is not supported on this platform (requires Linux/systemd or macOS/launchd)'));
352
+ process.exit(1);
353
+ }
354
+ const impl = platform === 'systemd' ? systemd : launchd;
355
+ const fn = impl[sub];
356
+ if (!fn) {
357
+ console.error(red(`Unknown gateway subcommand: ${sub}`));
358
+ console.error(dim('Available: install | uninstall | start | stop | status | logs'));
359
+ process.exit(1);
360
+ }
361
+ await fn();
362
+ }
363
+
364
+ // ── Startup info ──────────────────────────────────────────────────────────────
365
+
366
+ function getLocalIP() {
367
+ try {
368
+ for (const ifaces of Object.values(networkInterfaces())) {
369
+ for (const iface of ifaces) {
370
+ if (iface.family === 'IPv4' && !iface.internal) return iface.address;
371
+ }
372
+ }
373
+ } catch { /* ignore */ }
374
+ return null;
375
+ }
376
+
377
+ function printStartupInfo(webPort, mcpPort) {
378
+ let config = {};
379
+ try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* ignore */ }
380
+ const authToken = config.authToken || '';
381
+ const localIP = getLocalIP();
382
+
383
+ const auth = authToken
384
+ ? `,\n "headers": { "Authorization": "Bearer ${authToken}" }`
385
+ : '';
386
+ const block = (host) =>
387
+ ` {\n "mcpServers": {\n "mindos": {\n "url": "http://${host}:${mcpPort}/mcp"${auth}\n }\n }\n }`;
388
+
389
+ console.log(`\n${'─'.repeat(53)}`);
390
+ console.log(`${bold('🧠 MindOS is starting')}\n`);
391
+ console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
392
+ console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}\n`);
393
+ console.log(bold('Configure MCP in your Agent:'));
394
+ console.log(dim(' Local (same machine):'));
395
+ console.log(block('localhost'));
396
+ if (localIP) {
397
+ console.log(dim('\n Remote (other device):'));
398
+ console.log(block(localIP));
399
+ }
400
+ if (authToken) {
401
+ console.log(`\n 🔑 ${bold('Auth token:')} ${cyan(authToken)}`);
402
+ console.log(dim(' Run `mindos token` anytime to view it again'));
403
+ }
404
+ console.log(dim('\n Install Skills (optional):'));
405
+ console.log(dim(' npx skills add https://github.com/GeminiLight/MindOS --skill mindos -g -y'));
406
+ console.log(`${'─'.repeat(53)}\n`);
407
+ }
408
+
409
+ // ── MCP spawn ─────────────────────────────────────────────────────────────────
410
+
411
+ function spawnMcp(verbose = false) {
412
+ const mcpPort = process.env.MINDOS_MCP_PORT || '8787';
413
+ const webPort = process.env.MINDOS_WEB_PORT || '3000';
414
+ const env = {
415
+ ...process.env,
416
+ MCP_PORT: mcpPort,
417
+ MINDOS_URL: `http://localhost:${webPort}`,
418
+ ...(verbose ? { MCP_VERBOSE: '1' } : {}),
419
+ };
420
+ const child = spawn('npx', ['tsx', 'src/index.ts'], {
421
+ cwd: resolve(ROOT, 'mcp'),
422
+ stdio: 'inherit',
423
+ env,
424
+ });
425
+ child.on('error', (err) => {
426
+ if (err.message.includes('EADDRINUSE')) {
427
+ console.error(`\n${red('✘')} ${bold(`MCP port ${mcpPort} is already in use`)}`);
428
+ console.error(` ${dim('Run:')} mindos stop\n`);
429
+ } else {
430
+ console.error(red('MCP server error:'), err.message);
431
+ }
432
+ });
433
+ return child;
434
+ }
435
+
436
+ // ── Commands ──────────────────────────────────────────────────────────────────
437
+
438
+ const cmd = process.argv[2];
439
+ const isDaemon = process.argv.includes('--daemon');
440
+ const isVerbose = process.argv.includes('--verbose');
441
+ const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose').join(' ');
442
+
443
+ const commands = {
444
+ // ── onboard ────────────────────────────────────────────────────────────────
445
+ onboard: async () => {
446
+ run(`node ${resolve(ROOT, 'scripts/setup.js')}`);
447
+ if (process.argv.includes('--install-daemon')) {
448
+ const platform = getPlatform();
449
+ if (!platform) {
450
+ console.warn(yellow('Warning: daemon mode not supported on this platform. Skipping service install.'));
451
+ return;
452
+ }
453
+ console.log(cyan(`\nInstalling MindOS as a background service (${platform})...`));
454
+ await runGatewayCommand('install');
455
+ await runGatewayCommand('start');
456
+ console.log(`\n${green('✔ MindOS is running as a background service')}`);
457
+ console.log(dim(' View logs: mindos logs'));
458
+ console.log(dim(' Stop: mindos gateway stop'));
459
+ console.log(dim(' Uninstall: mindos gateway uninstall\n'));
460
+ }
461
+ },
462
+ init: () => run(`node ${resolve(ROOT, 'scripts/setup.js')}`),
463
+ setup: () => run(`node ${resolve(ROOT, 'scripts/setup.js')}`),
464
+
465
+ // ── token ──────────────────────────────────────────────────────────────────
466
+ token: () => {
467
+ if (!existsSync(CONFIG_PATH)) {
468
+ console.error(red('No config found. Run `mindos onboard` first.'));
469
+ process.exit(1);
470
+ }
471
+ let token = '';
472
+ try { token = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')).authToken || ''; } catch {}
473
+ if (!token) {
474
+ console.log(dim('No auth token set. Run `mindos onboard` to configure one.'));
475
+ process.exit(0);
476
+ }
477
+ console.log(`\n${bold('🔑 Auth token:')} ${cyan(token)}\n`);
478
+ console.log(dim('Add to your Agent MCP config:'));
479
+ console.log(` "headers": { "Authorization": "Bearer ${cyan(token)}" }\n`);
480
+ console.log(dim('Run `mindos onboard` to regenerate.\n'));
481
+ },
482
+
483
+ // ── dev ────────────────────────────────────────────────────────────────────
484
+ dev: async () => {
485
+ loadConfig();
486
+ const webPort = process.env.MINDOS_WEB_PORT || '3000';
487
+ const mcpPort = process.env.MINDOS_MCP_PORT || '8787';
488
+ await assertPortFree(Number(webPort), 'web');
489
+ await assertPortFree(Number(mcpPort), 'mcp');
490
+ const mcp = spawnMcp(isVerbose);
491
+ savePids(process.pid, mcp.pid);
492
+ process.on('exit', clearPids);
493
+ printStartupInfo(webPort, mcpPort);
494
+ run(`npx next dev -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
495
+ },
496
+
497
+ // ── start ──────────────────────────────────────────────────────────────────
498
+ start: async () => {
499
+ if (isDaemon) {
500
+ const platform = getPlatform();
501
+ if (!platform) {
502
+ console.warn(yellow('Warning: daemon mode not supported on this platform. Falling back to foreground.'));
503
+ } else {
504
+ console.log(cyan(`Installing MindOS as a background service (${platform})...`));
505
+ await runGatewayCommand('install');
506
+ await runGatewayCommand('start');
507
+ console.log(`\n${green('✔ MindOS is running as a background service')}`);
508
+ console.log(dim(' View logs: mindos logs'));
509
+ console.log(dim(' Stop: mindos gateway stop'));
510
+ console.log(dim(' Uninstall: mindos gateway uninstall\n'));
511
+ return;
512
+ }
513
+ }
514
+ loadConfig();
515
+ const webPort = process.env.MINDOS_WEB_PORT || '3000';
516
+ const mcpPort = process.env.MINDOS_MCP_PORT || '8787';
517
+ await assertPortFree(Number(webPort), 'web');
518
+ await assertPortFree(Number(mcpPort), 'mcp');
519
+ if (needsBuild()) {
520
+ console.log(yellow('Building MindOS (first run or new version detected)...\n'));
521
+ run('npx next build', resolve(ROOT, 'app'));
522
+ writeBuildStamp();
523
+ }
524
+ const mcp = spawnMcp(isVerbose);
525
+ savePids(process.pid, mcp.pid);
526
+ process.on('exit', clearPids);
527
+ printStartupInfo(webPort, mcpPort);
528
+ run(`npx next start -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
529
+ },
530
+
531
+ // ── build ──────────────────────────────────────────────────────────────────
532
+ build: () => {
533
+ run(`npx next build ${extra}`, resolve(ROOT, 'app'));
534
+ writeBuildStamp();
535
+ },
536
+
537
+ mcp: () => { loadConfig(); run(`npx tsx src/index.ts`, resolve(ROOT, 'mcp')); },
538
+
539
+ // ── stop / restart ─────────────────────────────────────────────────────────
540
+ stop: () => stopMindos(),
541
+
542
+ restart: async () => {
543
+ stopMindos();
544
+ await new Promise((r) => setTimeout(r, 1500));
545
+ await commands[getStartMode()]();
546
+ },
547
+
548
+ // ── gateway ────────────────────────────────────────────────────────────────
549
+ gateway: async () => {
550
+ const sub = process.argv[3];
551
+ if (!sub) {
552
+ const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
553
+ console.log(`
554
+ ${bold('mindos gateway')} — manage MindOS as a background OS service
555
+
556
+ ${bold('Subcommands:')}
557
+ ${row('mindos gateway install', 'Install and enable the service (systemd/launchd)')}
558
+ ${row('mindos gateway uninstall', 'Disable and remove the service')}
559
+ ${row('mindos gateway start', 'Start the service')}
560
+ ${row('mindos gateway stop', 'Stop the service')}
561
+ ${row('mindos gateway status', 'Show service status')}
562
+ ${row('mindos gateway logs', 'Tail service logs')}
563
+
564
+ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
565
+ `);
566
+ return;
567
+ }
568
+ await runGatewayCommand(sub);
569
+ },
570
+
571
+ // ── doctor ─────────────────────────────────────────────────────────────────
572
+ doctor: async () => {
573
+ const ok = (msg) => console.log(` ${green('✔')} ${msg}`);
574
+ const err = (msg) => console.log(` ${red('✘')} ${msg}`);
575
+ const warn= (msg) => console.log(` ${yellow('!')} ${msg}`);
576
+
577
+ console.log(`\n${bold('🩺 MindOS Doctor')}\n`);
578
+ let hasError = false;
579
+
580
+ // 1. config file
581
+ if (!existsSync(CONFIG_PATH)) {
582
+ err(`Config not found at ${dim(CONFIG_PATH)}`);
583
+ console.log(`\n ${dim('Run `mindos onboard` to create it.')}\n`);
584
+ process.exit(1);
585
+ }
586
+ let config;
587
+ try {
588
+ config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
589
+ ok(`Config file found and valid JSON ${dim(CONFIG_PATH)}`);
590
+ } catch {
591
+ err(`Config file exists but failed to parse ${dim(CONFIG_PATH)}`);
592
+ hasError = true;
593
+ }
594
+
595
+ // 2. mindRoot
596
+ if (config) {
597
+ const mindRoot = config.mindRoot;
598
+ if (!mindRoot) {
599
+ err('Config missing required field: mindRoot');
600
+ hasError = true;
601
+ } else if (!existsSync(mindRoot.replace(/^~/, homedir()))) {
602
+ warn(`mindRoot path does not exist: ${dim(mindRoot)} (will be created on first start)`);
603
+ } else {
604
+ ok(`Knowledge base path exists ${dim(mindRoot)}`);
605
+ }
606
+ }
607
+
608
+ // 3. AI config
609
+ if (config) {
610
+ const provider = config.ai?.provider;
611
+ const providers = config.ai?.providers;
612
+ const hasAnthropic = providers?.anthropic?.apiKey || config.ai?.anthropicApiKey;
613
+ const hasOpenai = providers?.openai?.apiKey || config.ai?.openaiApiKey;
614
+ if (!provider) {
615
+ warn('AI provider not configured (run `mindos onboard` to set up)');
616
+ } else if (provider === 'anthropic' && !hasAnthropic) {
617
+ err('AI provider is "anthropic" but no API key found');
618
+ hasError = true;
619
+ } else if (provider === 'openai' && !hasOpenai) {
620
+ err('AI provider is "openai" but no API key found');
621
+ hasError = true;
622
+ } else {
623
+ ok(`AI provider configured ${dim(provider)}`);
624
+ }
625
+ }
626
+
627
+ // 4. Node version
628
+ const nodeVersion = process.versions.node;
629
+ const [nodeMajor] = nodeVersion.split('.').map(Number);
630
+ if (nodeMajor < 18) {
631
+ err(`Node.js ${nodeVersion} is below minimum required (18+)`);
632
+ hasError = true;
633
+ } else {
634
+ ok(`Node.js ${nodeVersion}`);
635
+ }
636
+
637
+ // 5. Build
638
+ if (!existsSync(resolve(ROOT, 'app', '.next'))) {
639
+ warn(`App not built yet — will build automatically on next ${dim('mindos start')}`);
640
+ } else if (needsBuild()) {
641
+ warn(`Build is outdated — will rebuild automatically on next ${dim('mindos start')}`);
642
+ } else {
643
+ ok('Production build is up to date');
644
+ }
645
+
646
+ // 6. Ports
647
+ const webPort = Number(config?.port || process.env.MINDOS_WEB_PORT || 3000);
648
+ const mcpPort = Number(config?.mcpPort || process.env.MINDOS_MCP_PORT || 8787);
649
+ const webInUse = await isPortInUse(webPort);
650
+ const mcpInUse = await isPortInUse(mcpPort);
651
+ if (webInUse) {
652
+ ok(`Web server is listening on port ${webPort}`);
653
+ } else {
654
+ warn(`Web server is not running on port ${webPort}`);
655
+ }
656
+ if (mcpInUse) {
657
+ ok(`MCP server is listening on port ${mcpPort}`);
658
+ } else {
659
+ warn(`MCP server is not running on port ${mcpPort}`);
660
+ }
661
+
662
+ // 7. Daemon status
663
+ const platform = getPlatform();
664
+ if (platform === 'systemd') {
665
+ try {
666
+ execSync('systemctl --user is-active mindos', { stdio: 'pipe' });
667
+ ok('Systemd service mindos is active');
668
+ } catch {
669
+ warn('Systemd service mindos is not active (run `mindos gateway start` to start)');
670
+ }
671
+ } else if (platform === 'launchd') {
672
+ try {
673
+ execSync(`launchctl print gui/${launchctlUid()}/com.mindos.app`, { stdio: 'pipe' });
674
+ ok('LaunchAgent com.mindos.app is loaded');
675
+ } catch {
676
+ warn('LaunchAgent com.mindos.app is not loaded (run `mindos gateway start` to start)');
677
+ }
678
+ }
679
+
680
+ console.log(hasError
681
+ ? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
682
+ : `\n${green('All checks passed.')}\n`);
683
+ if (hasError) process.exit(1);
684
+ },
685
+
686
+ // ── update ─────────────────────────────────────────────────────────────────
687
+ update: () => {
688
+ const currentVersion = (() => {
689
+ try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
690
+ })();
691
+ console.log(`\n${bold('⬆ Updating MindOS...')} ${dim(`(current: ${currentVersion})`)}\n`);
692
+ try {
693
+ execSync('npm install -g mindos@latest', { stdio: 'inherit' });
694
+ } catch {
695
+ console.error(red('Update failed. Try: npm install -g mindos@latest'));
696
+ process.exit(1);
697
+ }
698
+ // Clear build stamp so next `mindos start` rebuilds if version changed
699
+ if (existsSync(BUILD_STAMP)) rmSync(BUILD_STAMP);
700
+ const newVersion = (() => {
701
+ try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
702
+ })();
703
+ if (newVersion !== currentVersion) {
704
+ console.log(`\n${green(`✔ Updated ${currentVersion} → ${newVersion}`)}`);
705
+ console.log(dim(' Run `mindos start` — it will rebuild automatically.\n'));
706
+ } else {
707
+ console.log(`\n${green('✔ Already on the latest version')} ${dim(`(${currentVersion})`)}\n`);
708
+ }
709
+ },
710
+
711
+ // ── logs ───────────────────────────────────────────────────────────────────
712
+ logs: () => {
713
+ ensureMindosDir();
714
+ if (!existsSync(LOG_PATH)) {
715
+ console.log(dim(`No log file yet at ${LOG_PATH}`));
716
+ console.log(dim('Logs are written when running in daemon mode (mindos start --daemon).'));
717
+ process.exit(0);
718
+ }
719
+ const noFollow = process.argv.includes('--no-follow');
720
+ if (noFollow) {
721
+ execSync(`tail -n 100 ${LOG_PATH}`, { stdio: 'inherit' });
722
+ } else {
723
+ execSync(`tail -f ${LOG_PATH}`, { stdio: 'inherit' });
724
+ }
725
+ },
726
+
727
+ // ── config ─────────────────────────────────────────────────────────────────
728
+ config: () => {
729
+ const sub = process.argv[3];
730
+
731
+ function maskKey(val) {
732
+ if (!val) return val;
733
+ if (val.length <= 8) return '****';
734
+ return val.slice(0, 6) + '****';
735
+ }
736
+
737
+ if (sub === 'show') {
738
+ if (!existsSync(CONFIG_PATH)) {
739
+ console.error(red('No config found. Run `mindos onboard` first.'));
740
+ process.exit(1);
741
+ }
742
+ let config;
743
+ try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
744
+ console.error(red('Failed to parse config file.'));
745
+ process.exit(1);
746
+ }
747
+ // Mask API keys for display
748
+ const display = JSON.parse(JSON.stringify(config));
749
+ if (display.ai?.providers?.anthropic?.apiKey)
750
+ display.ai.providers.anthropic.apiKey = maskKey(display.ai.providers.anthropic.apiKey);
751
+ if (display.ai?.providers?.openai?.apiKey)
752
+ display.ai.providers.openai.apiKey = maskKey(display.ai.providers.openai.apiKey);
753
+ if (display.ai?.anthropicApiKey)
754
+ display.ai.anthropicApiKey = maskKey(display.ai.anthropicApiKey);
755
+ if (display.ai?.openaiApiKey)
756
+ display.ai.openaiApiKey = maskKey(display.ai.openaiApiKey);
757
+ if (display.authToken)
758
+ display.authToken = maskKey(display.authToken);
759
+ if (display.webPassword)
760
+ display.webPassword = maskKey(display.webPassword);
761
+ console.log(`\n${bold('📋 MindOS Config')} ${dim(CONFIG_PATH)}\n`);
762
+ console.log(JSON.stringify(display, null, 2));
763
+ console.log();
764
+ return;
765
+ }
766
+
767
+ if (sub === 'validate') {
768
+ if (!existsSync(CONFIG_PATH)) {
769
+ console.error(red('No config found. Run `mindos onboard` first.'));
770
+ process.exit(1);
771
+ }
772
+ let config;
773
+ try {
774
+ config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
775
+ } catch (e) {
776
+ console.error(red(`✘ Invalid JSON: ${e.message}`));
777
+ process.exit(1);
778
+ }
779
+ const issues = [];
780
+ if (!config.mindRoot) issues.push('missing required field: mindRoot');
781
+ if (!config.ai?.provider) issues.push('missing field: ai.provider');
782
+ if (config.ai?.provider === 'anthropic') {
783
+ const key = config.ai?.providers?.anthropic?.apiKey || config.ai?.anthropicApiKey;
784
+ if (!key) issues.push('ai.provider is "anthropic" but no API key found');
785
+ }
786
+ if (config.ai?.provider === 'openai') {
787
+ const key = config.ai?.providers?.openai?.apiKey || config.ai?.openaiApiKey;
788
+ if (!key) issues.push('ai.provider is "openai" but no API key found');
789
+ }
790
+ if (issues.length) {
791
+ console.error(`\n${red('✘ Config has issues:')}`);
792
+ issues.forEach(i => console.error(` ${red('•')} ${i}`));
793
+ console.error(`\n ${dim('Run `mindos onboard` to fix.\n')}`);
794
+ process.exit(1);
795
+ }
796
+ console.log(`\n${green('✔ Config is valid')}\n`);
797
+ return;
798
+ }
799
+
800
+ if (sub === 'set') {
801
+ const key = process.argv[4];
802
+ const val = process.argv[5];
803
+ if (!key || val === undefined) {
804
+ console.error(red('Usage: mindos config set <key> <value>'));
805
+ console.error(dim(' Examples:'));
806
+ console.error(dim(' mindos config set port 3002'));
807
+ console.error(dim(' mindos config set mcpPort 8788'));
808
+ console.error(dim(' mindos config set ai.provider openai'));
809
+ process.exit(1);
810
+ }
811
+ if (!existsSync(CONFIG_PATH)) {
812
+ console.error(red('No config found. Run `mindos onboard` first.'));
813
+ process.exit(1);
814
+ }
815
+ let config;
816
+ try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
817
+ console.error(red('Failed to parse config file.'));
818
+ process.exit(1);
819
+ }
820
+ // Support dot-notation for nested keys (e.g. ai.provider)
821
+ const parts = key.split('.');
822
+ let obj = config;
823
+ for (let i = 0; i < parts.length - 1; i++) {
824
+ if (typeof obj[parts[i]] !== 'object' || !obj[parts[i]]) obj[parts[i]] = {};
825
+ obj = obj[parts[i]];
826
+ }
827
+ // Coerce numbers
828
+ const coerced = isNaN(Number(val)) ? val : Number(val);
829
+ obj[parts[parts.length - 1]] = coerced;
830
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
831
+ console.log(`${green('✔')} Set ${cyan(key)} = ${bold(String(coerced))}`);
832
+ return;
833
+ }
834
+
835
+ // no subcommand or unknown → show help
836
+ const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
837
+ console.log(`
838
+ ${bold('mindos config')} — view and update MindOS configuration
839
+
840
+ ${bold('Subcommands:')}
841
+ ${row('mindos config show', 'Print current config (API keys masked)')}
842
+ ${row('mindos config validate', 'Validate config file')}
843
+ ${row('mindos config set <key> <v>', 'Update a single field (dot-notation supported)')}
844
+
845
+ ${bold('Examples:')}
846
+ ${dim('mindos config set port 3002')}
847
+ ${dim('mindos config set ai.provider openai')}
848
+ `);
849
+ },
850
+ };
851
+
852
+ // ── Entry ─────────────────────────────────────────────────────────────────────
853
+
854
+ const resolvedCmd = cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null);
855
+
856
+ if (!resolvedCmd || !commands[resolvedCmd]) {
857
+ const row = (c, d) => ` ${cyan(c.padEnd(36))}${dim(d)}`;
858
+ console.log(`
859
+ ${bold('🧠 MindOS CLI')}
860
+
861
+ ${bold('Usage:')}
862
+ ${row('mindos onboard', 'Interactive setup (writes ~/.mindos/config.json)')}
863
+ ${row('mindos onboard --install-daemon', 'Setup + install & start as background OS service')}
864
+ ${row('mindos start', 'Start app + MCP server (production, auto-rebuilds if needed)')}
865
+ ${row('mindos start --daemon', 'Install + start as background OS service (survives terminal close)')}
866
+ ${row('mindos start --verbose', 'Start with verbose MCP logging')}
867
+ ${row('mindos dev', 'Start app + MCP server (dev mode)')}
868
+ ${row('mindos dev --turbopack', 'Start with Turbopack (faster HMR)')}
869
+ ${row('mindos stop', 'Stop running MindOS processes')}
870
+ ${row('mindos restart', 'Stop then start again')}
871
+ ${row('mindos build', 'Build the app for production')}
872
+ ${row('mindos mcp', 'Start MCP server only')}
873
+ ${row('mindos token', 'Show current auth token and MCP config snippet')}
874
+ ${row('mindos gateway <subcommand>', 'Manage background service (install/uninstall/start/stop/status/logs)')}
875
+ ${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
876
+ ${row('mindos update', 'Update MindOS to the latest version')}
877
+ ${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
878
+ ${row('mindos config <subcommand>', 'View/update config (show/validate/set)')}
879
+ ${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
880
+ `);
881
+ process.exit(cmd ? 1 : 0);
882
+ }
883
+
884
+ commands[resolvedCmd]();
885
+
886
+ // ── run helper ────────────────────────────────────────────────────────────────
887
+
888
+ function run(command, cwd = ROOT) {
889
+ try {
890
+ execSync(command, { cwd, stdio: 'inherit', env: process.env });
891
+ } catch {
892
+ process.exit(1);
893
+ }
894
+ }