@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
@@ -0,0 +1,170 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { streamText, stepCountIs, type ModelMessage } from 'ai';
3
+ import { NextRequest, NextResponse } from 'next/server';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { getFileContent, getMindRoot } from '@/lib/fs';
7
+ import { getModel, knowledgeBaseTools, truncate, AGENT_SYSTEM_PROMPT } from '@/lib/agent';
8
+
9
+ function readKnowledgeFile(filePath: string): { ok: boolean; content: string; error?: string } {
10
+ try {
11
+ return { ok: true, content: truncate(getFileContent(filePath)) };
12
+ } catch (err) {
13
+ return { ok: false, content: '', error: err instanceof Error ? err.message : String(err) };
14
+ }
15
+ }
16
+
17
+ function readAbsoluteFile(absPath: string): { ok: boolean; content: string; error?: string } {
18
+ try {
19
+ const raw = fs.readFileSync(absPath, 'utf-8');
20
+ return { ok: true, content: truncate(raw) };
21
+ } catch (err) {
22
+ return { ok: false, content: '', error: err instanceof Error ? err.message : String(err) };
23
+ }
24
+ }
25
+
26
+ function dirnameOf(filePath?: string): string | null {
27
+ if (!filePath) return null;
28
+ const normalized = filePath.replace(/\\/g, '/');
29
+ const idx = normalized.lastIndexOf('/');
30
+ if (idx <= 0) return null;
31
+ return normalized.slice(0, idx);
32
+ }
33
+
34
+ export async function POST(req: NextRequest) {
35
+ let body: {
36
+ messages: ModelMessage[];
37
+ currentFile?: string;
38
+ attachedFiles?: string[];
39
+ uploadedFiles?: Array<{ name: string; content: string }>;
40
+ maxSteps?: number;
41
+ };
42
+ try {
43
+ body = await req.json();
44
+ } catch {
45
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
46
+ }
47
+
48
+ const { messages, currentFile, attachedFiles, uploadedFiles, maxSteps } = body;
49
+ const stepLimit = Number.isFinite(maxSteps) ? Math.min(30, Math.max(1, Number(maxSteps))) : 20;
50
+
51
+ // Auto-load skill + bootstrap context for each request.
52
+ const skillPath = path.resolve(process.cwd(), 'data/skills/mindos/SKILL.md');
53
+ const skill = readAbsoluteFile(skillPath);
54
+
55
+ const targetDir = dirnameOf(currentFile);
56
+ const bootstrap = {
57
+ instruction: readKnowledgeFile('INSTRUCTION.md'),
58
+ index: readKnowledgeFile('README.md'),
59
+ config_json: readKnowledgeFile('CONFIG.json'),
60
+ config_md: readKnowledgeFile('CONFIG.md'),
61
+ target_readme: targetDir ? readKnowledgeFile(`${targetDir}/README.md`) : null,
62
+ target_instruction: targetDir ? readKnowledgeFile(`${targetDir}/INSTRUCTION.md`) : null,
63
+ target_config_json: targetDir ? readKnowledgeFile(`${targetDir}/CONFIG.json`) : null,
64
+ target_config_md: targetDir ? readKnowledgeFile(`${targetDir}/CONFIG.md`) : null,
65
+ };
66
+
67
+ const initStatus = [
68
+ `skill.mindos: ${skill.ok ? 'ok' : `failed (${skill.error})`} [${skillPath}]`,
69
+ `bootstrap.instruction: ${bootstrap.instruction.ok ? 'ok' : `failed (${bootstrap.instruction.error})`}`,
70
+ `bootstrap.index: ${bootstrap.index.ok ? 'ok' : `failed (${bootstrap.index.error})`}`,
71
+ `bootstrap.config_json: ${bootstrap.config_json.ok ? 'ok' : `failed (${bootstrap.config_json.error})`}`,
72
+ `bootstrap.config_md: ${bootstrap.config_md.ok ? 'ok' : `failed (${bootstrap.config_md.error})`}`,
73
+ `bootstrap.target_dir: ${targetDir ?? '(none)'}`,
74
+ `bootstrap.target_readme: ${bootstrap.target_readme ? (bootstrap.target_readme.ok ? 'ok' : `failed (${bootstrap.target_readme.error})`) : 'skipped'}`,
75
+ `bootstrap.target_instruction: ${bootstrap.target_instruction ? (bootstrap.target_instruction.ok ? 'ok' : `failed (${bootstrap.target_instruction.error})`) : 'skipped'}`,
76
+ `bootstrap.target_config_json: ${bootstrap.target_config_json ? (bootstrap.target_config_json.ok ? 'ok' : `failed (${bootstrap.target_config_json.error})`) : 'skipped'}`,
77
+ `bootstrap.target_config_md: ${bootstrap.target_config_md ? (bootstrap.target_config_md.ok ? 'ok' : `failed (${bootstrap.target_config_md.error})`) : 'skipped'}`,
78
+ `bootstrap.mind_root: ${getMindRoot()}`,
79
+ ].join('\n');
80
+
81
+ const initContextBlocks: string[] = [];
82
+ if (skill.ok) initContextBlocks.push(`## mindos_skill_md\n\n${skill.content}`);
83
+ if (bootstrap.instruction.ok) initContextBlocks.push(`## bootstrap_instruction\n\n${bootstrap.instruction.content}`);
84
+ if (bootstrap.index.ok) initContextBlocks.push(`## bootstrap_index\n\n${bootstrap.index.content}`);
85
+ if (bootstrap.config_json.ok) initContextBlocks.push(`## bootstrap_config_json\n\n${bootstrap.config_json.content}`);
86
+ if (bootstrap.config_md.ok) initContextBlocks.push(`## bootstrap_config_md\n\n${bootstrap.config_md.content}`);
87
+ if (bootstrap.target_readme?.ok) initContextBlocks.push(`## bootstrap_target_readme\n\n${bootstrap.target_readme.content}`);
88
+ if (bootstrap.target_instruction?.ok) initContextBlocks.push(`## bootstrap_target_instruction\n\n${bootstrap.target_instruction.content}`);
89
+ if (bootstrap.target_config_json?.ok) initContextBlocks.push(`## bootstrap_target_config_json\n\n${bootstrap.target_config_json.content}`);
90
+ if (bootstrap.target_config_md?.ok) initContextBlocks.push(`## bootstrap_target_config_md\n\n${bootstrap.target_config_md.content}`);
91
+
92
+ // Build initial context from attached/current files
93
+ const contextParts: string[] = [];
94
+ const seen = new Set<string>();
95
+ const hasAttached = Array.isArray(attachedFiles) && attachedFiles.length > 0;
96
+
97
+ if (hasAttached) {
98
+ for (const filePath of attachedFiles) {
99
+ if (seen.has(filePath)) continue;
100
+ seen.add(filePath);
101
+ try {
102
+ const content = truncate(getFileContent(filePath));
103
+ contextParts.push(`## Attached: ${filePath}\n\n${content}`);
104
+ } catch {}
105
+ }
106
+ }
107
+
108
+ if (currentFile && !seen.has(currentFile)) {
109
+ seen.add(currentFile);
110
+ try {
111
+ const content = truncate(getFileContent(currentFile));
112
+ contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
113
+ } catch {}
114
+ }
115
+
116
+ // Uploaded files go into a SEPARATE top-level section so the Agent
117
+ // treats them with high priority and never tries to look them up via tools.
118
+ const uploadedParts: string[] = [];
119
+ if (Array.isArray(uploadedFiles) && uploadedFiles.length > 0) {
120
+ for (const f of uploadedFiles.slice(0, 8)) {
121
+ if (!f || typeof f.name !== 'string' || typeof f.content !== 'string') continue;
122
+ uploadedParts.push(`### ${f.name}\n\n${truncate(f.content)}`);
123
+ }
124
+ }
125
+
126
+ const promptParts: string[] = [AGENT_SYSTEM_PROMPT];
127
+ promptParts.push(`---\n\nInitialization status (auto-loaded at request start):\n\n${initStatus}`);
128
+
129
+ if (initContextBlocks.length > 0) {
130
+ promptParts.push(`---\n\nInitialization context:\n\n${initContextBlocks.join('\n\n---\n\n')}`);
131
+ }
132
+
133
+ if (contextParts.length > 0) {
134
+ promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
135
+ }
136
+
137
+ if (uploadedParts.length > 0) {
138
+ promptParts.push(
139
+ `---\n\n## ⚠️ USER-UPLOADED FILES (ACTIVE ATTACHMENTS)\n\n` +
140
+ `The user has uploaded the following file(s) in this conversation. ` +
141
+ `Their FULL CONTENT is provided below. You MUST use this content directly when the user refers to these files. ` +
142
+ `Do NOT use read_file or search tools to find them — they exist only here, not in the knowledge base.\n\n` +
143
+ uploadedParts.join('\n\n---\n\n'),
144
+ );
145
+ }
146
+
147
+ const systemPrompt = promptParts.join('\n\n');
148
+
149
+ try {
150
+ const model = getModel();
151
+ const result = streamText({
152
+ model,
153
+ system: systemPrompt,
154
+ messages,
155
+ tools: knowledgeBaseTools,
156
+ stopWhen: stepCountIs(stepLimit),
157
+ onError: ({ error }) => {
158
+ console.error('[ask] Stream error:', error);
159
+ },
160
+ });
161
+
162
+ return result.toTextStreamResponse();
163
+ } catch (err) {
164
+ console.error('[ask] Failed to initialize model:', err);
165
+ return NextResponse.json(
166
+ { error: err instanceof Error ? err.message : 'Failed to initialize AI model' },
167
+ { status: 500 },
168
+ );
169
+ }
170
+ }
@@ -0,0 +1,90 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+
7
+ interface Message {
8
+ role: 'user' | 'assistant';
9
+ content: string;
10
+ }
11
+
12
+ interface ChatSession {
13
+ id: string;
14
+ currentFile?: string;
15
+ createdAt: number;
16
+ updatedAt: number;
17
+ messages: Message[];
18
+ }
19
+
20
+ const STORE_PATH = path.resolve(os.homedir(), '.mindos', 'sessions.json');
21
+ const MAX_SESSIONS = 30;
22
+
23
+ function ensureStoreDir() {
24
+ fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true });
25
+ }
26
+
27
+ function readSessions(): ChatSession[] {
28
+ try {
29
+ const raw = fs.readFileSync(STORE_PATH, 'utf-8');
30
+ const parsed = JSON.parse(raw) as ChatSession[];
31
+ if (!Array.isArray(parsed)) return [];
32
+ return parsed.filter((s) => s && typeof s.id === 'string' && Array.isArray(s.messages));
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ function writeSessions(sessions: ChatSession[]) {
39
+ ensureStoreDir();
40
+ fs.writeFileSync(STORE_PATH, JSON.stringify(sessions.slice(0, MAX_SESSIONS), null, 2), 'utf-8');
41
+ }
42
+
43
+ export async function GET() {
44
+ const sessions = readSessions()
45
+ .sort((a, b) => b.updatedAt - a.updatedAt)
46
+ .slice(0, MAX_SESSIONS);
47
+ return NextResponse.json(sessions);
48
+ }
49
+
50
+ export async function POST(req: NextRequest) {
51
+ let body: { session?: ChatSession };
52
+ try {
53
+ body = await req.json();
54
+ } catch {
55
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
56
+ }
57
+
58
+ const session = body.session;
59
+ if (!session || typeof session.id !== 'string' || !Array.isArray(session.messages)) {
60
+ return NextResponse.json({ error: 'Invalid session payload' }, { status: 400 });
61
+ }
62
+
63
+ const sessions = readSessions();
64
+ const idx = sessions.findIndex((s) => s.id === session.id);
65
+ if (idx >= 0) {
66
+ sessions[idx] = session;
67
+ } else {
68
+ sessions.push(session);
69
+ }
70
+
71
+ sessions.sort((a, b) => b.updatedAt - a.updatedAt);
72
+ writeSessions(sessions);
73
+ return NextResponse.json({ ok: true });
74
+ }
75
+
76
+ export async function DELETE(req: NextRequest) {
77
+ let body: { id?: string };
78
+ try {
79
+ body = await req.json();
80
+ } catch {
81
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
82
+ }
83
+
84
+ const id = body.id;
85
+ if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 });
86
+
87
+ const sessions = readSessions().filter((s) => s.id !== id);
88
+ writeSessions(sessions);
89
+ return NextResponse.json({ ok: true });
90
+ }
@@ -0,0 +1,37 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { signJwt } from '@/lib/jwt';
4
+
5
+ const COOKIE_NAME = 'mindos-session';
6
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
7
+
8
+ // POST /api/auth — validate password and set JWT session cookie
9
+ export async function POST(req: NextRequest) {
10
+ const webPassword = process.env.WEB_PASSWORD;
11
+ if (!webPassword) return NextResponse.json({ ok: false }, { status: 401 });
12
+
13
+ const body = await req.json().catch(() => ({})) as { password?: string };
14
+ if (body.password !== webPassword) return NextResponse.json({ ok: false }, { status: 401 });
15
+
16
+ const token = await signJwt(
17
+ { sub: 'user', exp: Math.floor(Date.now() / 1000) + COOKIE_MAX_AGE },
18
+ webPassword,
19
+ );
20
+
21
+ // Only set Secure flag when served over HTTPS (x-forwarded-proto set by reverse proxy)
22
+ const isHttps = req.headers.get('x-forwarded-proto') === 'https';
23
+ const secure = isHttps ? '; Secure' : '';
24
+ const res = NextResponse.json({ ok: true });
25
+ res.headers.set(
26
+ 'Set-Cookie',
27
+ `${COOKIE_NAME}=${token}; HttpOnly; SameSite=Strict; Max-Age=${COOKIE_MAX_AGE}; Path=/${secure}`,
28
+ );
29
+ return res;
30
+ }
31
+
32
+ // DELETE /api/auth — clear session cookie (logout)
33
+ export async function DELETE() {
34
+ const res = NextResponse.json({ ok: true });
35
+ res.headers.set('Set-Cookie', `${COOKIE_NAME}=; HttpOnly; SameSite=Strict; Max-Age=0; Path=/`);
36
+ return res;
37
+ }
@@ -0,0 +1,22 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { findBacklinks } from '@/lib/fs';
4
+
5
+ // GET /api/backlinks?path=Profile/Identity.md
6
+ // Returns: Array<{ filePath: string; snippets: string[] }>
7
+ // (transforms core BacklinkEntry shape to frontend-expected shape)
8
+ export async function GET(req: NextRequest) {
9
+ const target = req.nextUrl.searchParams.get('path');
10
+ if (!target) return NextResponse.json({ error: 'path required' }, { status: 400 });
11
+
12
+ try {
13
+ const backlinks = findBacklinks(target);
14
+ // Transform core shape { source, line, context } → frontend shape { filePath, snippets }
15
+ return NextResponse.json(backlinks.map(b => ({
16
+ filePath: b.source,
17
+ snippets: [b.context],
18
+ })));
19
+ } catch (e) {
20
+ return NextResponse.json({ error: (e as Error).message }, { status: 500 });
21
+ }
22
+ }
@@ -0,0 +1,37 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { getFileContent } from '@/lib/fs';
4
+ import path from 'path';
5
+
6
+ function tryRead(filePath: string): string | undefined {
7
+ try {
8
+ return getFileContent(filePath);
9
+ } catch {
10
+ return undefined;
11
+ }
12
+ }
13
+
14
+ // GET /api/bootstrap?target_dir=Workflows/Research
15
+ export async function GET(req: NextRequest) {
16
+ const targetDir = req.nextUrl.searchParams.get('target_dir') ?? undefined;
17
+
18
+ try {
19
+ const result: Record<string, string | undefined> = {
20
+ instruction: tryRead('INSTRUCTION.md'),
21
+ index: tryRead('README.md'),
22
+ config_json: tryRead('CONFIG.json'),
23
+ config_md: tryRead('CONFIG.md'),
24
+ };
25
+
26
+ if (targetDir) {
27
+ result.target_readme = tryRead(path.join(targetDir, 'README.md'));
28
+ result.target_instruction = tryRead(path.join(targetDir, 'INSTRUCTION.md'));
29
+ result.target_config_json = tryRead(path.join(targetDir, 'CONFIG.json'));
30
+ result.target_config_md = tryRead(path.join(targetDir, 'CONFIG.md'));
31
+ }
32
+
33
+ return NextResponse.json(result);
34
+ } catch (e) {
35
+ return NextResponse.json({ error: (e as Error).message }, { status: 500 });
36
+ }
37
+ }
@@ -0,0 +1,82 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { execFileSync } from 'child_process';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import os from 'os';
7
+
8
+ export const runtime = 'nodejs';
9
+
10
+ const MAX_TEXT_CHARS = 30_000;
11
+ const MAX_BYTES = 12 * 1024 * 1024; // 12MB
12
+
13
+ function truncateText(text: string): string {
14
+ if (text.length <= MAX_TEXT_CHARS) return text;
15
+ return `${text.slice(0, MAX_TEXT_CHARS)}\n\n[...truncated from PDF]`;
16
+ }
17
+
18
+ /**
19
+ * Extract PDF text by spawning a Node child process.
20
+ *
21
+ * pdfjs-dist requires a web-worker file. Turbopack rewrites the ESM
22
+ * `import.meta.url` references inside the bundle, breaking the worker
23
+ * resolution at runtime. Running the extraction in a plain Node process
24
+ * avoids the bundler entirely.
25
+ */
26
+ function extractPdf(buf: Buffer): { text: string; pages: number } {
27
+ // Write PDF to a temp file so the child script can read it.
28
+ const tmpDir = os.tmpdir();
29
+ const tmpPdf = path.join(tmpDir, `pdf-extract-${Date.now()}.pdf`);
30
+ // Dynamic path construction to prevent Turbopack static analysis
31
+ const scriptPath = [process.cwd(), 'scripts', 'extract-pdf.cjs'].join(path.sep);
32
+
33
+ fs.writeFileSync(tmpPdf, buf);
34
+ try {
35
+ const stdout = execFileSync('node', [scriptPath, tmpPdf], {
36
+ encoding: 'utf-8',
37
+ timeout: 30_000,
38
+ maxBuffer: 10 * 1024 * 1024,
39
+ });
40
+ return JSON.parse(stdout);
41
+ } finally {
42
+ try { fs.unlinkSync(tmpPdf); } catch { /* ignore */ }
43
+ }
44
+ }
45
+
46
+ export async function POST(req: NextRequest) {
47
+ let body: { name?: string; dataBase64?: string };
48
+ try {
49
+ body = await req.json();
50
+ } catch {
51
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
52
+ }
53
+
54
+ const name = body.name ?? 'uploaded.pdf';
55
+ const dataBase64 = body.dataBase64;
56
+ if (!dataBase64 || typeof dataBase64 !== 'string') {
57
+ return NextResponse.json({ error: 'dataBase64 is required' }, { status: 400 });
58
+ }
59
+
60
+ try {
61
+ const raw = Buffer.from(dataBase64, 'base64');
62
+ if (raw.byteLength > MAX_BYTES) {
63
+ return NextResponse.json({ error: 'PDF is too large (max 12MB)' }, { status: 400 });
64
+ }
65
+
66
+ const { text: rawText, pages } = extractPdf(raw);
67
+ const text = rawText.replace(/\u0000/g, '').trim();
68
+
69
+ return NextResponse.json({
70
+ name,
71
+ text: truncateText(text),
72
+ extracted: text.length > 0,
73
+ pagesParsed: pages,
74
+ });
75
+ } catch (err) {
76
+ console.error('[extract-pdf] Error:', err);
77
+ return NextResponse.json(
78
+ { error: err instanceof Error ? err.message : 'Failed to extract PDF text' },
79
+ { status: 500 },
80
+ );
81
+ }
82
+ }
@@ -0,0 +1,138 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import {
4
+ getFileContent,
5
+ saveFileContent,
6
+ createFile,
7
+ appendToFile,
8
+ readLines,
9
+ insertLines,
10
+ updateLines,
11
+ insertAfterHeading,
12
+ updateSection,
13
+ deleteFile,
14
+ renameFile,
15
+ moveFile,
16
+ appendCsvRow,
17
+ } from '@/lib/fs';
18
+
19
+ function err(msg: string, status = 400) {
20
+ return NextResponse.json({ error: msg }, { status });
21
+ }
22
+
23
+ // GET /api/file?path=foo.md&op=read_file|read_lines
24
+ export async function GET(req: NextRequest) {
25
+ const filePath = req.nextUrl.searchParams.get('path');
26
+ const op = req.nextUrl.searchParams.get('op') ?? 'read_file';
27
+ if (!filePath) return err('missing path');
28
+
29
+ try {
30
+ if (op === 'read_lines') {
31
+ return NextResponse.json({ lines: readLines(filePath) });
32
+ }
33
+ // default: read_file
34
+ return NextResponse.json({ content: getFileContent(filePath) });
35
+ } catch (e) {
36
+ return err((e as Error).message, 500);
37
+ }
38
+ }
39
+
40
+ // POST /api/file body: { op, path, ...params }
41
+ export async function POST(req: NextRequest) {
42
+ let body: Record<string, unknown>;
43
+ try { body = await req.json(); } catch { return err('invalid JSON'); }
44
+
45
+ const { op, path: filePath, ...params } = body as Record<string, unknown>;
46
+ if (!op || typeof op !== 'string') return err('missing op');
47
+ if (!filePath || typeof filePath !== 'string') return err('missing path');
48
+
49
+ try {
50
+ switch (op) {
51
+
52
+ case 'save_file': {
53
+ const { content } = params as { content: string };
54
+ if (typeof content !== 'string') return err('missing content');
55
+ saveFileContent(filePath, content);
56
+ return NextResponse.json({ ok: true });
57
+ }
58
+
59
+ case 'append_to_file': {
60
+ const { content } = params as { content: string };
61
+ if (typeof content !== 'string') return err('missing content');
62
+ appendToFile(filePath, content);
63
+ return NextResponse.json({ ok: true });
64
+ }
65
+
66
+ case 'insert_lines': {
67
+ const { after_index, lines } = params as { after_index: number; lines: string[] };
68
+ if (typeof after_index !== 'number') return err('missing after_index');
69
+ if (!Array.isArray(lines)) return err('lines must be array');
70
+ insertLines(filePath, after_index, lines);
71
+ return NextResponse.json({ ok: true });
72
+ }
73
+
74
+ case 'update_lines': {
75
+ const { start, end, lines } = params as { start: number; end: number; lines: string[] };
76
+ if (typeof start !== 'number' || typeof end !== 'number') return err('missing start/end');
77
+ if (!Array.isArray(lines)) return err('lines must be array');
78
+ if (start < 0 || end < 0) return err('start/end must be >= 0');
79
+ if (start > end) return err('start must be <= end');
80
+ updateLines(filePath, start, end, lines);
81
+ return NextResponse.json({ ok: true });
82
+ }
83
+
84
+ case 'insert_after_heading': {
85
+ const { heading, content } = params as { heading: string; content: string };
86
+ if (typeof heading !== 'string') return err('missing heading');
87
+ if (typeof content !== 'string') return err('missing content');
88
+ insertAfterHeading(filePath, heading, content);
89
+ return NextResponse.json({ ok: true });
90
+ }
91
+
92
+ case 'update_section': {
93
+ const { heading, content } = params as { heading: string; content: string };
94
+ if (typeof heading !== 'string') return err('missing heading');
95
+ if (typeof content !== 'string') return err('missing content');
96
+ updateSection(filePath, heading, content);
97
+ return NextResponse.json({ ok: true });
98
+ }
99
+
100
+ case 'delete_file': {
101
+ deleteFile(filePath);
102
+ return NextResponse.json({ ok: true });
103
+ }
104
+
105
+ case 'rename_file': {
106
+ const { new_name } = params as { new_name: string };
107
+ if (typeof new_name !== 'string' || !new_name) return err('missing new_name');
108
+ const newPath = renameFile(filePath, new_name);
109
+ return NextResponse.json({ ok: true, newPath });
110
+ }
111
+
112
+ case 'create_file': {
113
+ const { content } = params as { content?: string };
114
+ createFile(filePath, typeof content === 'string' ? content : '');
115
+ return NextResponse.json({ ok: true });
116
+ }
117
+
118
+ case 'move_file': {
119
+ const { to_path } = params as { to_path: string };
120
+ if (typeof to_path !== 'string' || !to_path) return err('missing to_path');
121
+ const result = moveFile(filePath, to_path);
122
+ return NextResponse.json({ ok: true, ...result });
123
+ }
124
+
125
+ case 'append_csv': {
126
+ const { row } = params as { row: string[] };
127
+ if (!Array.isArray(row) || row.length === 0) return err('row must be non-empty array');
128
+ const result = appendCsvRow(filePath, row);
129
+ return NextResponse.json({ ok: true, ...result });
130
+ }
131
+
132
+ default:
133
+ return err(`unknown op: ${op}`);
134
+ }
135
+ } catch (e) {
136
+ return err((e as Error).message, 500);
137
+ }
138
+ }
@@ -0,0 +1,12 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { collectAllFiles } from '@/lib/fs';
3
+ import { NextResponse } from 'next/server';
4
+
5
+ export async function GET() {
6
+ try {
7
+ const files = collectAllFiles();
8
+ return NextResponse.json(files);
9
+ } catch (e) {
10
+ return NextResponse.json({ error: (e as Error).message }, { status: 500 });
11
+ }
12
+ }
@@ -0,0 +1,42 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { isGitRepo, gitLog, gitShowFile } from '@/lib/fs';
4
+
5
+ function err(msg: string, status = 400) {
6
+ return NextResponse.json({ error: msg }, { status });
7
+ }
8
+
9
+ // GET /api/git?op=is_repo|history|show&path=x&limit=10&commit=abc
10
+ export async function GET(req: NextRequest) {
11
+ const op = req.nextUrl.searchParams.get('op') ?? 'is_repo';
12
+
13
+ try {
14
+ switch (op) {
15
+ case 'is_repo': {
16
+ return NextResponse.json({ isRepo: isGitRepo() });
17
+ }
18
+
19
+ case 'history': {
20
+ const filePath = req.nextUrl.searchParams.get('path');
21
+ if (!filePath) return err('missing path');
22
+ const limit = parseInt(req.nextUrl.searchParams.get('limit') ?? '10', 10);
23
+ const entries = gitLog(filePath, limit);
24
+ return NextResponse.json({ entries });
25
+ }
26
+
27
+ case 'show': {
28
+ const filePath = req.nextUrl.searchParams.get('path');
29
+ const commit = req.nextUrl.searchParams.get('commit');
30
+ if (!filePath) return err('missing path');
31
+ if (!commit) return err('missing commit');
32
+ const content = gitShowFile(filePath, commit);
33
+ return NextResponse.json({ content });
34
+ }
35
+
36
+ default:
37
+ return err(`unknown op: ${op}`);
38
+ }
39
+ } catch (e) {
40
+ return err((e as Error).message, 500);
41
+ }
42
+ }