@drewsepsi/nextpi 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 (303) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +13 -0
  3. package/.next/build/chunks/33f32_323ea524._.js +6766 -0
  4. package/.next/build/chunks/33f32_323ea524._.js.map +47 -0
  5. package/.next/build/chunks/[root-of-the-server]__6ead02f9._.js +500 -0
  6. package/.next/build/chunks/[root-of-the-server]__6ead02f9._.js.map +11 -0
  7. package/.next/build/chunks/[root-of-the-server]__777e9116._.js +206 -0
  8. package/.next/build/chunks/[root-of-the-server]__777e9116._.js.map +8 -0
  9. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_597188b4._.js +13 -0
  10. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_597188b4._.js.map +5 -0
  11. package/.next/build/chunks/[turbopack]_runtime.js +795 -0
  12. package/.next/build/chunks/[turbopack]_runtime.js.map +10 -0
  13. package/.next/build/package.json +1 -0
  14. package/.next/build/postcss.js +6 -0
  15. package/.next/build/postcss.js.map +5 -0
  16. package/.next/build-manifest.json +19 -0
  17. package/.next/cache/.previewinfo +1 -0
  18. package/.next/cache/.rscinfo +1 -0
  19. package/.next/cache/.tsbuildinfo +1 -0
  20. package/.next/diagnostics/build-diagnostics.json +6 -0
  21. package/.next/diagnostics/framework.json +1 -0
  22. package/.next/export-marker.json +6 -0
  23. package/.next/fallback-build-manifest.json +12 -0
  24. package/.next/images-manifest.json +67 -0
  25. package/.next/next-minimal-server.js.nft.json +1 -0
  26. package/.next/next-server.js.nft.json +1 -0
  27. package/.next/package.json +1 -0
  28. package/.next/prerender-manifest.json +114 -0
  29. package/.next/required-server-files.js +324 -0
  30. package/.next/required-server-files.json +324 -0
  31. package/.next/routes-manifest.json +113 -0
  32. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  33. package/.next/server/app/_global-error/page/build-manifest.json +16 -0
  34. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  35. package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
  36. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  37. package/.next/server/app/_global-error/page.js +11 -0
  38. package/.next/server/app/_global-error/page.js.map +5 -0
  39. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  40. package/.next/server/app/_global-error/page_client-reference-manifest.js +2 -0
  41. package/.next/server/app/_global-error.html +2 -0
  42. package/.next/server/app/_global-error.meta +15 -0
  43. package/.next/server/app/_global-error.rsc +13 -0
  44. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
  45. package/.next/server/app/_global-error.segments/_full.segment.rsc +13 -0
  46. package/.next/server/app/_global-error.segments/_head.segment.rsc +6 -0
  47. package/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
  48. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  49. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  50. package/.next/server/app/_not-found/page/build-manifest.json +16 -0
  51. package/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
  52. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  53. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  54. package/.next/server/app/_not-found/page.js +14 -0
  55. package/.next/server/app/_not-found/page.js.map +5 -0
  56. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  57. package/.next/server/app/_not-found/page_client-reference-manifest.js +2 -0
  58. package/.next/server/app/_not-found.html +1 -0
  59. package/.next/server/app/_not-found.meta +16 -0
  60. package/.next/server/app/_not-found.rsc +15 -0
  61. package/.next/server/app/_not-found.segments/_full.segment.rsc +15 -0
  62. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  63. package/.next/server/app/_not-found.segments/_index.segment.rsc +6 -0
  64. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  65. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
  66. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  67. package/.next/server/app/api/chat/route/app-paths-manifest.json +3 -0
  68. package/.next/server/app/api/chat/route/build-manifest.json +11 -0
  69. package/.next/server/app/api/chat/route/server-reference-manifest.json +4 -0
  70. package/.next/server/app/api/chat/route.js +7 -0
  71. package/.next/server/app/api/chat/route.js.map +5 -0
  72. package/.next/server/app/api/chat/route.js.nft.json +1 -0
  73. package/.next/server/app/api/chat/route_client-reference-manifest.js +2 -0
  74. package/.next/server/app/api/config/route/app-paths-manifest.json +3 -0
  75. package/.next/server/app/api/config/route/build-manifest.json +11 -0
  76. package/.next/server/app/api/config/route/server-reference-manifest.json +4 -0
  77. package/.next/server/app/api/config/route.js +7 -0
  78. package/.next/server/app/api/config/route.js.map +5 -0
  79. package/.next/server/app/api/config/route.js.nft.json +1 -0
  80. package/.next/server/app/api/config/route_client-reference-manifest.js +2 -0
  81. package/.next/server/app/api/files/[...path]/route/app-paths-manifest.json +3 -0
  82. package/.next/server/app/api/files/[...path]/route/build-manifest.json +11 -0
  83. package/.next/server/app/api/files/[...path]/route/server-reference-manifest.json +4 -0
  84. package/.next/server/app/api/files/[...path]/route.js +7 -0
  85. package/.next/server/app/api/files/[...path]/route.js.map +5 -0
  86. package/.next/server/app/api/files/[...path]/route.js.nft.json +1 -0
  87. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +2 -0
  88. package/.next/server/app/api/files/route/app-paths-manifest.json +3 -0
  89. package/.next/server/app/api/files/route/build-manifest.json +11 -0
  90. package/.next/server/app/api/files/route/server-reference-manifest.json +4 -0
  91. package/.next/server/app/api/files/route.js +7 -0
  92. package/.next/server/app/api/files/route.js.map +5 -0
  93. package/.next/server/app/api/files/route.js.nft.json +1 -0
  94. package/.next/server/app/api/files/route_client-reference-manifest.js +2 -0
  95. package/.next/server/app/api/messages/route/app-paths-manifest.json +3 -0
  96. package/.next/server/app/api/messages/route/build-manifest.json +11 -0
  97. package/.next/server/app/api/messages/route/server-reference-manifest.json +4 -0
  98. package/.next/server/app/api/messages/route.js +7 -0
  99. package/.next/server/app/api/messages/route.js.map +5 -0
  100. package/.next/server/app/api/messages/route.js.nft.json +1 -0
  101. package/.next/server/app/api/messages/route_client-reference-manifest.js +2 -0
  102. package/.next/server/app/api/session/route/app-paths-manifest.json +3 -0
  103. package/.next/server/app/api/session/route/build-manifest.json +11 -0
  104. package/.next/server/app/api/session/route/server-reference-manifest.json +4 -0
  105. package/.next/server/app/api/session/route.js +7 -0
  106. package/.next/server/app/api/session/route.js.map +5 -0
  107. package/.next/server/app/api/session/route.js.nft.json +1 -0
  108. package/.next/server/app/api/session/route_client-reference-manifest.js +2 -0
  109. package/.next/server/app/api/stream/route/app-paths-manifest.json +3 -0
  110. package/.next/server/app/api/stream/route/build-manifest.json +11 -0
  111. package/.next/server/app/api/stream/route/server-reference-manifest.json +4 -0
  112. package/.next/server/app/api/stream/route.js +6 -0
  113. package/.next/server/app/api/stream/route.js.map +5 -0
  114. package/.next/server/app/api/stream/route.js.nft.json +1 -0
  115. package/.next/server/app/api/stream/route_client-reference-manifest.js +2 -0
  116. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  117. package/.next/server/app/favicon.ico/route/build-manifest.json +11 -0
  118. package/.next/server/app/favicon.ico/route.js +8 -0
  119. package/.next/server/app/favicon.ico/route.js.map +5 -0
  120. package/.next/server/app/favicon.ico/route.js.nft.json +1 -0
  121. package/.next/server/app/favicon.ico.body +0 -0
  122. package/.next/server/app/favicon.ico.meta +1 -0
  123. package/.next/server/app/index.html +1 -0
  124. package/.next/server/app/index.meta +14 -0
  125. package/.next/server/app/index.rsc +21 -0
  126. package/.next/server/app/index.segments/__PAGE__.segment.rsc +9 -0
  127. package/.next/server/app/index.segments/_full.segment.rsc +21 -0
  128. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  129. package/.next/server/app/index.segments/_index.segment.rsc +6 -0
  130. package/.next/server/app/index.segments/_tree.segment.rsc +4 -0
  131. package/.next/server/app/page/app-paths-manifest.json +3 -0
  132. package/.next/server/app/page/build-manifest.json +16 -0
  133. package/.next/server/app/page/next-font-manifest.json +11 -0
  134. package/.next/server/app/page/react-loadable-manifest.json +1 -0
  135. package/.next/server/app/page/server-reference-manifest.json +4 -0
  136. package/.next/server/app/page.js +16 -0
  137. package/.next/server/app/page.js.map +5 -0
  138. package/.next/server/app/page.js.nft.json +1 -0
  139. package/.next/server/app/page_client-reference-manifest.js +2 -0
  140. package/.next/server/app-paths-manifest.json +13 -0
  141. package/.next/server/chunks/33f32_next_b78429d5._.js +17 -0
  142. package/.next/server/chunks/33f32_next_b78429d5._.js.map +1 -0
  143. package/.next/server/chunks/33f32_next_dist_esm_build_templates_app-route_5a539e27.js +3 -0
  144. package/.next/server/chunks/33f32_next_dist_esm_build_templates_app-route_5a539e27.js.map +1 -0
  145. package/.next/server/chunks/[externals]_next_dist_b89b5a39._.js +3 -0
  146. package/.next/server/chunks/[externals]_next_dist_b89b5a39._.js.map +1 -0
  147. package/.next/server/chunks/[root-of-the-server]__2554f0b0._.js +17 -0
  148. package/.next/server/chunks/[root-of-the-server]__2554f0b0._.js.map +1 -0
  149. package/.next/server/chunks/[root-of-the-server]__5e671cad._.js +7 -0
  150. package/.next/server/chunks/[root-of-the-server]__5e671cad._.js.map +1 -0
  151. package/.next/server/chunks/[root-of-the-server]__629f5815._.js +3 -0
  152. package/.next/server/chunks/[root-of-the-server]__629f5815._.js.map +1 -0
  153. package/.next/server/chunks/[root-of-the-server]__633f9ccd._.js +3 -0
  154. package/.next/server/chunks/[root-of-the-server]__633f9ccd._.js.map +1 -0
  155. package/.next/server/chunks/[root-of-the-server]__65cdeb73._.js +17 -0
  156. package/.next/server/chunks/[root-of-the-server]__65cdeb73._.js.map +1 -0
  157. package/.next/server/chunks/[root-of-the-server]__bc57ef5b._.js +21 -0
  158. package/.next/server/chunks/[root-of-the-server]__bc57ef5b._.js.map +1 -0
  159. package/.next/server/chunks/[root-of-the-server]__be62c218._.js +17 -0
  160. package/.next/server/chunks/[root-of-the-server]__be62c218._.js.map +1 -0
  161. package/.next/server/chunks/[root-of-the-server]__e87dbf93._.js +3 -0
  162. package/.next/server/chunks/[root-of-the-server]__e87dbf93._.js.map +1 -0
  163. package/.next/server/chunks/[turbopack]_runtime.js +795 -0
  164. package/.next/server/chunks/[turbopack]_runtime.js.map +10 -0
  165. package/.next/server/chunks/c45a0_nextpi__next-internal_server_app_api_files_[___path]_route_actions_16a66e62.js +3 -0
  166. package/.next/server/chunks/c45a0_nextpi__next-internal_server_app_api_files_[___path]_route_actions_16a66e62.js.map +1 -0
  167. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_chat_route_actions_e342cdbf.js +3 -0
  168. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_chat_route_actions_e342cdbf.js.map +1 -0
  169. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_config_route_actions_04d1272c.js +3 -0
  170. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_config_route_actions_04d1272c.js.map +1 -0
  171. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_files_route_actions_a8035013.js +3 -0
  172. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_files_route_actions_a8035013.js.map +1 -0
  173. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_messages_route_actions_4f52ac4b.js +3 -0
  174. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_messages_route_actions_4f52ac4b.js.map +1 -0
  175. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_session_route_actions_ff684c69.js +3 -0
  176. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_session_route_actions_ff684c69.js.map +1 -0
  177. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_stream_route_actions_7a0f6dba.js +3 -0
  178. package/.next/server/chunks/picode_nextpi__next-internal_server_app_api_stream_route_actions_7a0f6dba.js.map +1 -0
  179. package/.next/server/chunks/picode_nextpi__next-internal_server_app_favicon_ico_route_actions_2526216f.js +3 -0
  180. package/.next/server/chunks/picode_nextpi__next-internal_server_app_favicon_ico_route_actions_2526216f.js.map +1 -0
  181. package/.next/server/chunks/ssr/33f32_next_dist_580d3573._.js +6 -0
  182. package/.next/server/chunks/ssr/33f32_next_dist_580d3573._.js.map +1 -0
  183. package/.next/server/chunks/ssr/33f32_next_dist_85b0ccc0._.js +4 -0
  184. package/.next/server/chunks/ssr/33f32_next_dist_85b0ccc0._.js.map +1 -0
  185. package/.next/server/chunks/ssr/33f32_next_dist_client_components_a8ffe8d4._.js +3 -0
  186. package/.next/server/chunks/ssr/33f32_next_dist_client_components_a8ffe8d4._.js.map +1 -0
  187. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_forbidden_ad97660b.js +3 -0
  188. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_forbidden_ad97660b.js.map +1 -0
  189. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_global-error_ab28468b.js +3 -0
  190. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_global-error_ab28468b.js.map +1 -0
  191. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_unauthorized_edd1fe08.js +3 -0
  192. package/.next/server/chunks/ssr/33f32_next_dist_client_components_builtin_unauthorized_edd1fe08.js.map +1 -0
  193. package/.next/server/chunks/ssr/33f32_next_dist_esm_build_templates_app-page_5ef96b51.js +4 -0
  194. package/.next/server/chunks/ssr/33f32_next_dist_esm_build_templates_app-page_5ef96b51.js.map +1 -0
  195. package/.next/server/chunks/ssr/33f32_next_dist_server_route-modules_app-page_vendored_ssr_react-dom_33aeae20.js +3 -0
  196. package/.next/server/chunks/ssr/33f32_next_dist_server_route-modules_app-page_vendored_ssr_react-dom_33aeae20.js.map +1 -0
  197. package/.next/server/chunks/ssr/[root-of-the-server]__028106ef._.js +3 -0
  198. package/.next/server/chunks/ssr/[root-of-the-server]__028106ef._.js.map +1 -0
  199. package/.next/server/chunks/ssr/[root-of-the-server]__0dc9a9e1._.js +4 -0
  200. package/.next/server/chunks/ssr/[root-of-the-server]__0dc9a9e1._.js.map +1 -0
  201. package/.next/server/chunks/ssr/[root-of-the-server]__15f57d5d._.js +3 -0
  202. package/.next/server/chunks/ssr/[root-of-the-server]__15f57d5d._.js.map +1 -0
  203. package/.next/server/chunks/ssr/[root-of-the-server]__26f2451b._.js +3 -0
  204. package/.next/server/chunks/ssr/[root-of-the-server]__26f2451b._.js.map +1 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__42ddc417._.js +3 -0
  206. package/.next/server/chunks/ssr/[root-of-the-server]__42ddc417._.js.map +1 -0
  207. package/.next/server/chunks/ssr/[root-of-the-server]__6823e2d4._.js +3 -0
  208. package/.next/server/chunks/ssr/[root-of-the-server]__6823e2d4._.js.map +1 -0
  209. package/.next/server/chunks/ssr/[root-of-the-server]__6bf5dd5d._.js +3 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__6bf5dd5d._.js.map +1 -0
  211. package/.next/server/chunks/ssr/[root-of-the-server]__7b7d477c._.js +10 -0
  212. package/.next/server/chunks/ssr/[root-of-the-server]__7b7d477c._.js.map +1 -0
  213. package/.next/server/chunks/ssr/[root-of-the-server]__85c46341._.js +3 -0
  214. package/.next/server/chunks/ssr/[root-of-the-server]__85c46341._.js.map +1 -0
  215. package/.next/server/chunks/ssr/[root-of-the-server]__99c61367._.js +3 -0
  216. package/.next/server/chunks/ssr/[root-of-the-server]__99c61367._.js.map +1 -0
  217. package/.next/server/chunks/ssr/[root-of-the-server]__a0bdeac8._.js +3 -0
  218. package/.next/server/chunks/ssr/[root-of-the-server]__a0bdeac8._.js.map +1 -0
  219. package/.next/server/chunks/ssr/[turbopack]_runtime.js +795 -0
  220. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +10 -0
  221. package/.next/server/chunks/ssr/_091ac666._.js +3 -0
  222. package/.next/server/chunks/ssr/_091ac666._.js.map +1 -0
  223. package/.next/server/chunks/ssr/picode_nextpi_1e69e558._.js +4 -0
  224. package/.next/server/chunks/ssr/picode_nextpi_1e69e558._.js.map +1 -0
  225. package/.next/server/chunks/ssr/picode_nextpi_4fadcd01._.js +3 -0
  226. package/.next/server/chunks/ssr/picode_nextpi_4fadcd01._.js.map +1 -0
  227. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app__global-error_page_actions_91eb1cf1.js +3 -0
  228. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app__global-error_page_actions_91eb1cf1.js.map +1 -0
  229. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app__not-found_page_actions_0a31df41.js +3 -0
  230. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app__not-found_page_actions_0a31df41.js.map +1 -0
  231. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app_page_actions_b14d985f.js +3 -0
  232. package/.next/server/chunks/ssr/picode_nextpi__next-internal_server_app_page_actions_b14d985f.js.map +1 -0
  233. package/.next/server/chunks/ssr/picode_nextpi_app_77d1bd95._.js +3 -0
  234. package/.next/server/chunks/ssr/picode_nextpi_app_77d1bd95._.js.map +1 -0
  235. package/.next/server/chunks/ssr/picode_nextpi_app_page_tsx_cebd7814._.js +3 -0
  236. package/.next/server/chunks/ssr/picode_nextpi_app_page_tsx_cebd7814._.js.map +1 -0
  237. package/.next/server/functions-config-manifest.json +4 -0
  238. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  239. package/.next/server/middleware-build-manifest.js +20 -0
  240. package/.next/server/middleware-manifest.json +6 -0
  241. package/.next/server/next-font-manifest.js +1 -0
  242. package/.next/server/next-font-manifest.json +15 -0
  243. package/.next/server/pages/404.html +1 -0
  244. package/.next/server/pages/500.html +2 -0
  245. package/.next/server/pages-manifest.json +4 -0
  246. package/.next/server/server-reference-manifest.js +1 -0
  247. package/.next/server/server-reference-manifest.json +5 -0
  248. package/.next/static/PdRtiPZ_ExBLCzmKGXOBT/_buildManifest.js +11 -0
  249. package/.next/static/PdRtiPZ_ExBLCzmKGXOBT/_clientMiddlewareManifest.json +1 -0
  250. package/.next/static/PdRtiPZ_ExBLCzmKGXOBT/_ssgManifest.js +1 -0
  251. package/.next/static/chunks/0ab43a45e07af0f9.js +1 -0
  252. package/.next/static/chunks/265e06106152dcbb.js +1 -0
  253. package/.next/static/chunks/65d5124f3edd1b84.js +1 -0
  254. package/.next/static/chunks/76111d9b2044b643.js +1 -0
  255. package/.next/static/chunks/81dfdd74986c8afe.css +3 -0
  256. package/.next/static/chunks/93b85e5de0842835.js +5 -0
  257. package/.next/static/chunks/a6dad97d9634a72d.js +1 -0
  258. package/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  259. package/.next/static/chunks/cace1b477872431f.js +1 -0
  260. package/.next/static/chunks/turbopack-775e95d747b6e667.js +4 -0
  261. package/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  262. package/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  263. package/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  264. package/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  265. package/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  266. package/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  267. package/.next/static/media/favicon.0b3bf435.ico +0 -0
  268. package/.next/trace +1 -0
  269. package/.next/trace-build +1 -0
  270. package/.next/turbopack +0 -0
  271. package/.next/types/routes.d.ts +79 -0
  272. package/.next/types/validator.ts +133 -0
  273. package/README.md +50 -0
  274. package/app/api/chat/route.ts +28 -0
  275. package/app/api/config/route.ts +25 -0
  276. package/app/api/files/[...path]/route.ts +55 -0
  277. package/app/api/files/route.ts +62 -0
  278. package/app/api/messages/route.ts +26 -0
  279. package/app/api/session/route.ts +55 -0
  280. package/app/api/stream/route.ts +76 -0
  281. package/app/favicon.ico +0 -0
  282. package/app/globals.css +1143 -0
  283. package/app/layout.tsx +46 -0
  284. package/app/page.tsx +1643 -0
  285. package/bin/cli.js +85 -0
  286. package/components/.gitkeep +0 -0
  287. package/components/theme-provider.tsx +71 -0
  288. package/components/ui/button.tsx +67 -0
  289. package/components/ui/file-tree.tsx +560 -0
  290. package/lib/.gitkeep +0 -0
  291. package/lib/agent.ts +105 -0
  292. package/lib/config.ts +50 -0
  293. package/lib/root-path.ts +15 -0
  294. package/lib/session-singleton.ts +48 -0
  295. package/lib/utils.ts +6 -0
  296. package/package.json +72 -0
  297. package/public/.gitkeep +0 -0
  298. package/src/agent.ts +82 -0
  299. package/src/cli.ts +107 -0
  300. package/src/tools.ts +71 -0
  301. package/src/tui.ts +238 -0
  302. package/src/web-server.ts +280 -0
  303. package/tsconfig.json +34 -0
@@ -0,0 +1,560 @@
1
+ // Styling updated to use globals.css — visual changes only
2
+ "use client"
3
+
4
+ import React, {
5
+ createContext,
6
+ forwardRef,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useState,
11
+ } from "react"
12
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
13
+ import {
14
+ FolderIcon,
15
+ FolderOpenIcon,
16
+ FileCodeIcon,
17
+ FileJsonIcon,
18
+ FileTypeIcon,
19
+ FileTextIcon,
20
+ FileImageIcon,
21
+ FileIcon,
22
+ ChevronRightIcon,
23
+ } from "lucide-react"
24
+
25
+ import { cn } from "@/lib/utils"
26
+
27
+ // File type icon mapping
28
+ const getFileIcon = (filename: string, isSelected: boolean) => {
29
+ const ext = filename.split('.').pop()?.toLowerCase()
30
+ const baseClass = cn("size-4 transition-transform duration-200", isSelected && "scale-110")
31
+
32
+ switch (ext) {
33
+ case 'tsx':
34
+ case 'ts':
35
+ case 'jsx':
36
+ case 'js':
37
+ return <FileCodeIcon className={cn(baseClass, "text-sky-400")} />
38
+ case 'json':
39
+ return <FileJsonIcon className={cn(baseClass, "text-amber-400")} />
40
+ case 'css':
41
+ case 'scss':
42
+ case 'less':
43
+ return <FileTypeIcon className={cn(baseClass, "text-pink-400")} />
44
+ case 'md':
45
+ case 'mdx':
46
+ return <FileTextIcon className={cn(baseClass, "text-emerald-400")} />
47
+ case 'png':
48
+ case 'jpg':
49
+ case 'jpeg':
50
+ case 'svg':
51
+ case 'webp':
52
+ return <FileImageIcon className={cn(baseClass, "text-purple-400")} />
53
+ default:
54
+ return <FileIcon className={cn(baseClass, "text-muted-foreground")} />
55
+ }
56
+ }
57
+
58
+ type TreeViewElement = {
59
+ id: string
60
+ name: string
61
+ type?: "file" | "folder"
62
+ isSelectable?: boolean
63
+ children?: TreeViewElement[]
64
+ metadata?: {
65
+ size?: string
66
+ modified?: string
67
+ }
68
+ }
69
+
70
+ type TreeSortMode =
71
+ | "default"
72
+ | "none"
73
+ | ((a: TreeViewElement, b: TreeViewElement) => number)
74
+
75
+ type TreeContextProps = {
76
+ selectedId: string | undefined
77
+ expandedItems: string[] | undefined
78
+ indicator: boolean
79
+ handleExpand: (id: string) => void
80
+ selectItem: (id: string) => void
81
+ setExpandedItems?: React.Dispatch<React.SetStateAction<string[] | undefined>>
82
+ openIcon?: React.ReactNode
83
+ closeIcon?: React.ReactNode
84
+ direction: "rtl" | "ltr"
85
+ onFolderContextMenu?: (e: React.MouseEvent, id: string) => void
86
+ }
87
+
88
+ const TreeContext = createContext<TreeContextProps | null>(null)
89
+
90
+ const useTree = () => {
91
+ const context = useContext(TreeContext)
92
+ if (!context) {
93
+ throw new Error("useTree must be used within a TreeProvider")
94
+ }
95
+ return context
96
+ }
97
+
98
+ type Direction = "rtl" | "ltr" | undefined
99
+
100
+ const isFolderElement = (element: TreeViewElement) => {
101
+ if (element.type) {
102
+ return element.type === "folder"
103
+ }
104
+
105
+ return Array.isArray(element.children)
106
+ }
107
+
108
+ const mergeExpandedItems = (
109
+ currentItems: string[] | undefined,
110
+ nextItems: string[]
111
+ ) => [...new Set([...(currentItems ?? []), ...nextItems])]
112
+
113
+ const treeCollator = new Intl.Collator("en", {
114
+ numeric: true,
115
+ sensitivity: "base",
116
+ })
117
+
118
+ const defaultTreeComparator = (a: TreeViewElement, b: TreeViewElement) => {
119
+ const aIsFolder = isFolderElement(a)
120
+ const bIsFolder = isFolderElement(b)
121
+
122
+ if (aIsFolder !== bIsFolder) {
123
+ return aIsFolder ? -1 : 1
124
+ }
125
+
126
+ return treeCollator.compare(a.name, b.name)
127
+ }
128
+
129
+ const getTreeComparator = (sort: TreeSortMode) => {
130
+ if (sort === "none") {
131
+ return undefined
132
+ }
133
+
134
+ if (sort === "default") {
135
+ return defaultTreeComparator
136
+ }
137
+
138
+ return sort
139
+ }
140
+
141
+ const sortTreeElements = (
142
+ elements: TreeViewElement[],
143
+ sort: TreeSortMode
144
+ ): TreeViewElement[] => {
145
+ const comparator = getTreeComparator(sort)
146
+
147
+ const nextElements = elements.map((element) => {
148
+ if (!Array.isArray(element.children)) {
149
+ return element
150
+ }
151
+
152
+ return {
153
+ ...element,
154
+ children: sortTreeElements(element.children, sort),
155
+ }
156
+ })
157
+
158
+ if (!comparator) {
159
+ return nextElements
160
+ }
161
+
162
+ return [...nextElements].sort(comparator)
163
+ }
164
+
165
+ const renderTreeElements = (
166
+ elements: TreeViewElement[],
167
+ sort: TreeSortMode,
168
+ onFolderContextMenu?: (e: React.MouseEvent, id: string) => void
169
+ ): React.ReactNode =>
170
+ sortTreeElements(elements, sort).map((element) => {
171
+ if (isFolderElement(element)) {
172
+ return (
173
+ <Folder
174
+ key={element.id}
175
+ value={element.id}
176
+ element={element.name}
177
+ isSelectable={element.isSelectable}
178
+ >
179
+ {Array.isArray(element.children)
180
+ ? renderTreeElements(element.children, sort, onFolderContextMenu)
181
+ : null}
182
+ </Folder>
183
+ )
184
+ }
185
+
186
+ return (
187
+ <File
188
+ key={element.id}
189
+ value={element.id}
190
+ isSelectable={element.isSelectable}
191
+ fileName={element.name}
192
+ >
193
+ {element.name}
194
+ </File>
195
+ )
196
+ })
197
+
198
+ type TreeViewProps = {
199
+ initialSelectedId?: string
200
+ indicator?: boolean
201
+ elements?: TreeViewElement[]
202
+ initialExpandedItems?: string[]
203
+ openIcon?: React.ReactNode
204
+ closeIcon?: React.ReactNode
205
+ sort?: TreeSortMode
206
+ onExpandedItemsChange?: (items: string[] | undefined) => void
207
+ onSelectedIdChange?: (id: string | undefined) => void
208
+ onFolderContextMenu?: (e: React.MouseEvent, id: string) => void
209
+ } & Omit<
210
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>,
211
+ "defaultValue" | "onValueChange" | "type" | "value"
212
+ >
213
+
214
+ const Tree = forwardRef<HTMLDivElement, TreeViewProps>(
215
+ (
216
+ {
217
+ className,
218
+ elements,
219
+ initialSelectedId,
220
+ initialExpandedItems,
221
+ onExpandedItemsChange,
222
+ onSelectedIdChange,
223
+ onFolderContextMenu,
224
+ children,
225
+ indicator = true,
226
+ openIcon,
227
+ closeIcon,
228
+ sort = "default",
229
+ dir,
230
+ ...props
231
+ },
232
+ ref
233
+ ) => {
234
+ const [selectedId, setSelectedId] = useState<string | undefined>(
235
+ initialSelectedId
236
+ )
237
+ const [expandedItems, setExpandedItems] = useState<string[] | undefined>(
238
+ initialExpandedItems
239
+ )
240
+
241
+ useEffect(() => {
242
+ setExpandedItems(initialExpandedItems)
243
+ }, [initialExpandedItems])
244
+
245
+ const selectItem = useCallback((id: string) => {
246
+ setSelectedId(id)
247
+ onSelectedIdChange?.(id)
248
+ }, [onSelectedIdChange])
249
+
250
+ const handleExpand = useCallback((id: string, expanded?: boolean) => {
251
+ let next: string[] | undefined
252
+
253
+ setExpandedItems((prev) => {
254
+ const isCurrentlyExpanded = prev?.includes(id)
255
+ const shouldExpand = expanded !== undefined ? expanded : !isCurrentlyExpanded
256
+
257
+ if (shouldExpand && !isCurrentlyExpanded) {
258
+ next = [...(prev ?? []), id]
259
+ } else if (!shouldExpand && isCurrentlyExpanded) {
260
+ next = prev?.filter((item) => item !== id) ?? []
261
+ } else {
262
+ next = prev
263
+ }
264
+ return next
265
+ })
266
+
267
+ // Call side effect after state update
268
+ if (next !== undefined) {
269
+ onExpandedItemsChange?.(next)
270
+ }
271
+ }, [onExpandedItemsChange])
272
+
273
+ const expandSpecificTargetedElements = useCallback(
274
+ (elements?: TreeViewElement[], selectId?: string) => {
275
+ if (!elements || !selectId) return
276
+ const findParent = (
277
+ currentElement: TreeViewElement,
278
+ currentPath: string[] = []
279
+ ) => {
280
+ const isSelectable = currentElement.isSelectable ?? true
281
+ const newPath = [...currentPath, currentElement.id]
282
+ if (currentElement.id === selectId) {
283
+ if (isSelectable) {
284
+ setExpandedItems((prev) => mergeExpandedItems(prev, newPath))
285
+ } else {
286
+ if (newPath.includes(currentElement.id)) {
287
+ newPath.pop()
288
+ setExpandedItems((prev) => mergeExpandedItems(prev, newPath))
289
+ }
290
+ }
291
+ return
292
+ }
293
+ if (
294
+ Array.isArray(currentElement.children) &&
295
+ currentElement.children.length > 0
296
+ ) {
297
+ currentElement.children.forEach((child) => {
298
+ findParent(child, newPath)
299
+ })
300
+ }
301
+ }
302
+ elements.forEach((element) => {
303
+ findParent(element)
304
+ })
305
+ },
306
+ []
307
+ )
308
+
309
+ useEffect(() => {
310
+ if (initialSelectedId) {
311
+ expandSpecificTargetedElements(elements, initialSelectedId)
312
+ }
313
+ }, [initialSelectedId, elements, expandSpecificTargetedElements])
314
+
315
+ const direction = dir === "rtl" ? "rtl" : "ltr"
316
+ const treeChildren =
317
+ children ?? (elements ? renderTreeElements(elements, sort) : null)
318
+
319
+ return (
320
+ <TreeContext.Provider
321
+ value={{
322
+ selectedId,
323
+ expandedItems,
324
+ handleExpand,
325
+ selectItem,
326
+ setExpandedItems,
327
+ indicator,
328
+ openIcon,
329
+ closeIcon,
330
+ direction,
331
+ onFolderContextMenu,
332
+ }}
333
+ >
334
+ <div className={cn("size-full", className)}>
335
+ <div
336
+ ref={ref}
337
+ className="relative h-full overflow-auto scrollbar-thin"
338
+ dir={dir as Direction}
339
+ >
340
+ <AccordionPrimitive.Root
341
+ {...props}
342
+ type="multiple"
343
+ value={expandedItems}
344
+ onValueChange={(newValue) => {
345
+ setExpandedItems?.(newValue)
346
+ onExpandedItemsChange?.(newValue)
347
+ }}
348
+ className="flex flex-col"
349
+ dir={dir as Direction}
350
+ >
351
+ {treeChildren}
352
+ </AccordionPrimitive.Root>
353
+ </div>
354
+ </div>
355
+ </TreeContext.Provider>
356
+ )
357
+ }
358
+ )
359
+
360
+ Tree.displayName = "Tree"
361
+
362
+ const TreeIndicator = forwardRef<
363
+ HTMLDivElement,
364
+ React.HTMLAttributes<HTMLDivElement>
365
+ >(({ className, ...props }, ref) => {
366
+ const { direction } = useTree()
367
+
368
+ return (
369
+ <div
370
+ dir={direction}
371
+ ref={ref}
372
+ className={cn(
373
+ "bg-border absolute left-1.5 h-full w-px rounded-md py-3 duration-300 ease-in-out hover:bg-muted-foreground/30 rtl:right-1.5",
374
+ className
375
+ )}
376
+ {...props}
377
+ />
378
+ )
379
+ })
380
+
381
+ TreeIndicator.displayName = "TreeIndicator"
382
+
383
+ type FolderProps = {
384
+ expandedItems?: string[]
385
+ element: string
386
+ isSelectable?: boolean
387
+ isSelect?: boolean
388
+ onContextMenu?: (e: React.MouseEvent) => void
389
+ } & React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
390
+
391
+ const Folder = forwardRef<
392
+ HTMLDivElement,
393
+ FolderProps & React.HTMLAttributes<HTMLDivElement>
394
+ >(
395
+ (
396
+ {
397
+ className,
398
+ element,
399
+ value,
400
+ isSelectable = true,
401
+ isSelect,
402
+ onContextMenu,
403
+ children,
404
+ ...props
405
+ },
406
+ ref
407
+ ) => {
408
+ const {
409
+ direction,
410
+ handleExpand,
411
+ expandedItems,
412
+ setExpandedItems,
413
+ indicator,
414
+ selectedId,
415
+ selectItem,
416
+ onFolderContextMenu,
417
+ } = useTree()
418
+ const isSelected = isSelect ?? selectedId === value
419
+ const isExpanded = expandedItems?.includes(value)
420
+
421
+ return (
422
+ <AccordionPrimitive.Item
423
+ ref={ref}
424
+ {...props}
425
+ value={value}
426
+ className="relative overflow-hidden"
427
+ >
428
+ <AccordionPrimitive.Trigger
429
+ className={cn(
430
+ "group flex items-center gap-2 rounded-lg text-sm px-3 py-1.5 w-full text-left transition-all duration-200 ease-out",
431
+ "hover:bg-accent hover:translate-x-0.5",
432
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
433
+ className,
434
+ {
435
+ "bg-accent": isSelected && isSelectable,
436
+ "cursor-pointer": isSelectable,
437
+ "cursor-not-allowed opacity-40": !isSelectable,
438
+ }
439
+ )}
440
+ disabled={!isSelectable}
441
+ onClick={() => handleExpand(value)}
442
+ onContextMenu={(e) => {
443
+ if (onFolderContextMenu) {
444
+ onFolderContextMenu(e, value);
445
+ }
446
+ onContextMenu?.(e);
447
+ }}
448
+ >
449
+ {/* Chevron rotates via Radix data-state so it animates smoothly */}
450
+ <ChevronRightIcon
451
+ className="size-3.5 text-muted-foreground/50 transition-transform duration-200 ease-out shrink-0 group-data-[state=open]:rotate-90 group-data-[state=open]:text-muted-foreground"
452
+ />
453
+ {isExpanded
454
+ ? <FolderOpenIcon className="size-4 text-amber-500 dark:text-amber-400/90 transition-colors duration-200" />
455
+ : <FolderIcon className="size-4 text-amber-500/70 dark:text-amber-400/70 transition-colors duration-200 group-hover:text-amber-500 dark:group-hover:text-amber-400/90" />
456
+ }
457
+ <span className={cn(
458
+ "font-medium truncate transition-colors duration-200",
459
+ isSelected ? "text-foreground" : "text-foreground/80 group-hover:text-foreground"
460
+ )}>
461
+ {element}
462
+ </span>
463
+ </AccordionPrimitive.Trigger>
464
+ <AccordionPrimitive.Content
465
+ className="overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
466
+ >
467
+ <div className="relative">
468
+ {indicator && (
469
+ <div className="absolute left-[1.125rem] top-0 bottom-0 w-px bg-gradient-to-b from-border via-border/40 to-transparent" />
470
+ )}
471
+ <AccordionPrimitive.Root
472
+ dir={direction}
473
+ type="multiple"
474
+ className="ml-[2.25rem] flex flex-col py-1"
475
+ value={expandedItems}
476
+ onValueChange={(newValue) => {
477
+ // Sync nested accordion changes back to tree context
478
+ setExpandedItems?.(newValue)
479
+ }}
480
+ >
481
+ {children}
482
+ </AccordionPrimitive.Root>
483
+ </div>
484
+ </AccordionPrimitive.Content>
485
+ </AccordionPrimitive.Item>
486
+ )
487
+ }
488
+ )
489
+
490
+ Folder.displayName = "Folder"
491
+
492
+ const File = forwardRef<
493
+ HTMLButtonElement,
494
+ {
495
+ value: string
496
+ handleSelect?: (id: string) => void
497
+ isSelectable?: boolean
498
+ isSelect?: boolean
499
+ fileIcon?: React.ReactNode
500
+ fileName?: string
501
+ } & React.ButtonHTMLAttributes<HTMLButtonElement>
502
+ >(
503
+ (
504
+ {
505
+ value,
506
+ className,
507
+ handleSelect,
508
+ onClick,
509
+ isSelectable = true,
510
+ isSelect,
511
+ fileIcon,
512
+ fileName,
513
+ children,
514
+ ...props
515
+ },
516
+ ref
517
+ ) => {
518
+ const { direction, selectedId, selectItem } = useTree()
519
+ const isSelected = isSelect ?? selectedId === value
520
+ const displayName = fileName || (typeof children === 'string' ? children : value)
521
+
522
+ return (
523
+ <button
524
+ ref={ref}
525
+ type="button"
526
+ disabled={!isSelectable}
527
+ className={cn(
528
+ "group flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm w-full text-left transition-all duration-200 ease-out",
529
+ "hover:bg-accent hover:translate-x-0.5",
530
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
531
+ {
532
+ "bg-accent": isSelected && isSelectable,
533
+ },
534
+ isSelectable ? "cursor-pointer" : "cursor-not-allowed opacity-40",
535
+ direction === "rtl" ? "rtl" : "ltr",
536
+ className
537
+ )}
538
+ onClick={(event) => {
539
+ selectItem(value)
540
+ handleSelect?.(value)
541
+ onClick?.(event)
542
+ }}
543
+ {...props}
544
+ >
545
+ {fileIcon ?? getFileIcon(displayName, isSelected)}
546
+ <span className={cn(
547
+ "truncate transition-colors duration-200",
548
+ isSelected ? "text-foreground" : "text-muted-foreground group-hover:text-foreground"
549
+ )}>
550
+ {children}
551
+ </span>
552
+ </button>
553
+ )
554
+ }
555
+ )
556
+
557
+ File.displayName = "File"
558
+
559
+ export { File, Folder, Tree, type TreeViewElement }
560
+ export type { TreeSortMode }
package/lib/.gitkeep ADDED
File without changes
package/lib/agent.ts ADDED
@@ -0,0 +1,105 @@
1
+ import {
2
+ createAgentSession,
3
+ SessionManager,
4
+ type AgentSession
5
+ } from "@mariozechner/pi-coding-agent";
6
+ import { getModel, streamSimple } from "@mariozechner/pi-ai";
7
+ import * as path from "path";
8
+ import * as fs from "fs";
9
+ import { getProjectRoot } from "./root-path";
10
+ import { getEffectiveApiKey } from "./config";
11
+
12
+ export interface ResearchAgentConfig {
13
+ sessionDir?: string;
14
+ sessionFile?: string;
15
+ modelId?: string;
16
+ thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
17
+ }
18
+
19
+ // Default OpenRouter model
20
+ const DEFAULT_MODEL = "openrouter/free";
21
+
22
+ // Research tools (web search, web fetch)
23
+ const researchTools = [
24
+ {
25
+ name: "web_search",
26
+ description: "Search the web for information using Exa AI",
27
+ parameters: {
28
+ type: "object",
29
+ properties: {
30
+ query: { type: "string", description: "Search query" },
31
+ numResults: { type: "number", description: "Number of results (default: 5)" }
32
+ },
33
+ required: ["query"]
34
+ }
35
+ },
36
+ {
37
+ name: "web_fetch",
38
+ description: "Fetch content from a specific URL",
39
+ parameters: {
40
+ type: "object",
41
+ properties: {
42
+ url: { type: "string", description: "URL to fetch" }
43
+ },
44
+ required: ["url"]
45
+ }
46
+ }
47
+ ];
48
+
49
+ export async function createResearchAgent(
50
+ config: ResearchAgentConfig = {}
51
+ ): Promise<AgentSession> {
52
+ const {
53
+ sessionDir = path.join(getProjectRoot(), ".sessions"),
54
+ sessionFile = path.join(sessionDir, "research-agent.jsonl"),
55
+ modelId = DEFAULT_MODEL,
56
+ thinkingLevel = "low",
57
+ } = config;
58
+
59
+ // Ensure session directory exists
60
+ fs.mkdirSync(sessionDir, { recursive: true });
61
+
62
+ // Use OpenRouter model
63
+ const model = getModel("openrouter", modelId as "openrouter/free");
64
+
65
+ // Create session with research tools and system prompt
66
+ const { session } = await createAgentSession({
67
+ model,
68
+ thinkingLevel,
69
+ systemPrompt: RESEARCH_SYSTEM_PROMPT,
70
+ sessionManager: SessionManager.open(sessionFile),
71
+ customTools: researchTools as any,
72
+ } as any);
73
+
74
+ // Wrap stream function with OpenRouter headers
75
+ const originalStreamFn = session.agent.streamFn;
76
+ session.agent.streamFn = (model, context, options) => {
77
+ return streamSimple(model, context, {
78
+ ...options,
79
+ headers: {
80
+ ...options?.headers,
81
+ "X-Title": "NextPi Agent",
82
+ "HTTP-Referer": "https://github.com/drewsepsi/nextpi",
83
+ },
84
+ });
85
+ };
86
+
87
+ return session;
88
+ }
89
+
90
+ // System prompt for research and coding
91
+ export const RESEARCH_SYSTEM_PROMPT = `You are NextPi, an expert research and coding assistant. You have access to the web and the local file system.
92
+
93
+ ### GUIDELINES
94
+ - ALWAYS start by explaining your plan to the user in the main message or thinking block.
95
+ - BEFORE calling any tool that modifies the file system (write_file, edit_file, etc.), you MUST describe the specific changes you are about to make and why.
96
+ - If you are editing a file, summarize the intended changes first.
97
+ - If you are researching, explain what you hope to find.
98
+
99
+ ### CAPABILITIES
100
+ 1. **Web Search**: Search for docs, errors, news, etc.
101
+ 2. **Web Fetch**: Read specific URLs.
102
+ 3. **File Operations**: Read, write, list (ls), and edit files.
103
+ 4. **Shell**: Execute bash commands for testing or exploration.
104
+
105
+ Always be surgical with edits and transparent with your process.`;
package/lib/config.ts ADDED
@@ -0,0 +1,50 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.nextpi');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+
8
+ export interface NextPiConfig {
9
+ openRouterApiKey?: string;
10
+ defaultModel?: string;
11
+ }
12
+
13
+ export function getConfig(): NextPiConfig {
14
+ try {
15
+ if (!fs.existsSync(CONFIG_FILE)) {
16
+ return {};
17
+ }
18
+ const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
19
+ return JSON.parse(data);
20
+ } catch (err) {
21
+ console.error('Error reading config:', err);
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export function saveConfig(newConfig: Partial<NextPiConfig>) {
27
+ try {
28
+ if (!fs.existsSync(CONFIG_DIR)) {
29
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
30
+ }
31
+ const current = getConfig();
32
+ const updated = { ...current, ...newConfig };
33
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2));
34
+
35
+ // Also update current process env for immediate use
36
+ if (updated.openRouterApiKey) {
37
+ process.env.OPENROUTER_API_KEY = updated.openRouterApiKey;
38
+ }
39
+
40
+ return updated;
41
+ } catch (err) {
42
+ console.error('Error saving config:', err);
43
+ throw err;
44
+ }
45
+ }
46
+
47
+ export function getEffectiveApiKey(): string | undefined {
48
+ // Priority: 1. Environment Variable, 2. Config File
49
+ return process.env.OPENROUTER_API_KEY || getConfig().openRouterApiKey;
50
+ }