@jlongo78/agent-spaces 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +7 -0
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +19 -19
  6. package/.next/standalone/.next/routes-manifest.json +42 -0
  7. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js +1 -1
  8. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/(desktop)/admin/users/page.js +1 -1
  11. package/.next/standalone/.next/server/app/(desktop)/admin/users/page.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/(desktop)/analytics/page.js +1 -1
  14. package/.next/standalone/.next/server/app/(desktop)/analytics/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/(desktop)/cortex/page.js +1 -1
  17. package/.next/standalone/.next/server/app/(desktop)/cortex/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(desktop)/network/page.js +1 -1
  20. package/.next/standalone/.next/server/app/(desktop)/network/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/(desktop)/page.js +1 -1
  23. package/.next/standalone/.next/server/app/(desktop)/page.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/(desktop)/projects/page.js +1 -1
  26. package/.next/standalone/.next/server/app/(desktop)/projects/page.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js +1 -1
  29. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/.next/server/app/(desktop)/sessions/page.js +1 -1
  32. package/.next/standalone/.next/server/app/(desktop)/sessions/page.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/(desktop)/settings/page.js +1 -1
  35. package/.next/standalone/.next/server/app/(desktop)/settings/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/(desktop)/terminal/page.js +1 -1
  38. package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js +1 -1
  41. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js +1 -1
  44. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/(desktop)/workspaces/page.js +1 -1
  47. package/.next/standalone/.next/server/app/(desktop)/workspaces/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  50. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  51. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  52. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  53. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  55. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  58. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  59. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  60. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  61. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  63. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  64. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  65. package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
  66. package/.next/standalone/.next/server/app/admin/analytics.rsc +17 -16
  67. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
  68. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
  69. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  70. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  71. package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +17 -16
  72. package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
  73. package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
  74. package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
  75. package/.next/standalone/.next/server/app/admin/users.html +1 -1
  76. package/.next/standalone/.next/server/app/admin/users.rsc +17 -16
  77. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +2 -2
  78. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
  79. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  80. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  81. package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +17 -16
  82. package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
  83. package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
  84. package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
  85. package/.next/standalone/.next/server/app/analytics.html +1 -1
  86. package/.next/standalone/.next/server/app/analytics.rsc +17 -16
  87. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
  88. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
  89. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  90. package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +17 -16
  91. package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
  92. package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
  93. package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
  94. package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
  96. package/.next/standalone/.next/server/app/api/chat/route.js +1 -1
  97. package/.next/standalone/.next/server/app/api/chat/route.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
  100. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/app-paths-manifest.json +3 -0
  101. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/build-manifest.json +11 -0
  102. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route/server-reference-manifest.json +4 -0
  103. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js +7 -0
  104. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.map +5 -0
  105. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -0
  106. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route_client-reference-manifest.js +2 -0
  107. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/app-paths-manifest.json +3 -0
  108. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/build-manifest.json +11 -0
  109. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route/server-reference-manifest.json +4 -0
  110. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js +7 -0
  111. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.map +5 -0
  112. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -0
  113. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route_client-reference-manifest.js +2 -0
  114. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/app-paths-manifest.json +3 -0
  115. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/build-manifest.json +11 -0
  116. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route/server-reference-manifest.json +4 -0
  117. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js +7 -0
  118. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.map +5 -0
  119. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -0
  120. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route_client-reference-manifest.js +2 -0
  121. package/.next/standalone/.next/server/app/api/cortex/curation/review/route/app-paths-manifest.json +3 -0
  122. package/.next/standalone/.next/server/app/api/cortex/curation/review/route/build-manifest.json +11 -0
  123. package/.next/standalone/.next/server/app/api/cortex/curation/review/route/server-reference-manifest.json +4 -0
  124. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js +7 -0
  125. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.map +5 -0
  126. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -0
  127. package/.next/standalone/.next/server/app/api/cortex/curation/review/route_client-reference-manifest.js +2 -0
  128. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/app-paths-manifest.json +3 -0
  129. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/build-manifest.json +11 -0
  130. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route/server-reference-manifest.json +4 -0
  131. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js +7 -0
  132. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.map +5 -0
  133. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -0
  134. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route_client-reference-manifest.js +2 -0
  135. package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
  139. package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
  140. package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
  145. package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
  147. package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
  149. package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
  150. package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
  152. package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/app-paths-manifest.json +3 -0
  154. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/build-manifest.json +11 -0
  155. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route/server-reference-manifest.json +4 -0
  156. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js +7 -0
  157. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.map +5 -0
  158. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -0
  159. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route_client-reference-manifest.js +2 -0
  160. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/app-paths-manifest.json +3 -0
  161. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/build-manifest.json +11 -0
  162. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route/server-reference-manifest.json +4 -0
  163. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js +7 -0
  164. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.map +5 -0
  165. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -0
  166. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route_client-reference-manifest.js +2 -0
  167. package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
  168. package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
  169. package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
  170. package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
  171. package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
  172. package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
  173. package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
  174. package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
  175. package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
  176. package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
  177. package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
  178. package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
  179. package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
  180. package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
  181. package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
  182. package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
  183. package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
  184. package/.next/standalone/.next/server/app/api/network/workspaces/route.js +1 -1
  185. package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
  186. package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
  187. package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
  188. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  189. package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  190. package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
  191. package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
  192. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  193. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  194. package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
  195. package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
  196. package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
  197. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
  198. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
  199. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
  200. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
  201. package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
  202. package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
  203. package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  204. package/.next/standalone/.next/server/app/cortex.html +1 -1
  205. package/.next/standalone/.next/server/app/cortex.rsc +17 -16
  206. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
  207. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
  208. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  209. package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +17 -16
  210. package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
  211. package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
  212. package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
  213. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  214. package/.next/standalone/.next/server/app/login.html +1 -1
  215. package/.next/standalone/.next/server/app/login.rsc +2 -2
  216. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
  217. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
  218. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
  219. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  220. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  221. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
  222. package/.next/standalone/.next/server/app/m/page.js.nft.json +1 -1
  223. package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
  224. package/.next/standalone/.next/server/app/m/projects/page.js.nft.json +1 -1
  225. package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
  226. package/.next/standalone/.next/server/app/m/projects.html +1 -1
  227. package/.next/standalone/.next/server/app/m/projects.rsc +4 -4
  228. package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +4 -4
  229. package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
  230. package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
  231. package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
  232. package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +2 -2
  233. package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
  234. package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +2 -2
  235. package/.next/standalone/.next/server/app/m/sessions/[id]/page.js.nft.json +1 -1
  236. package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
  237. package/.next/standalone/.next/server/app/m/sessions/page.js.nft.json +1 -1
  238. package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
  239. package/.next/standalone/.next/server/app/m/sessions.html +1 -1
  240. package/.next/standalone/.next/server/app/m/sessions.rsc +4 -4
  241. package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +4 -4
  242. package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
  243. package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
  244. package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
  245. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +2 -2
  246. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
  247. package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +2 -2
  248. package/.next/standalone/.next/server/app/m/settings/page.js.nft.json +1 -1
  249. package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
  250. package/.next/standalone/.next/server/app/m/settings.html +1 -1
  251. package/.next/standalone/.next/server/app/m/settings.rsc +4 -4
  252. package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +4 -4
  253. package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
  254. package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
  255. package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
  256. package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +2 -2
  257. package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
  258. package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +2 -2
  259. package/.next/standalone/.next/server/app/m/terminal/page.js.nft.json +1 -1
  260. package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
  261. package/.next/standalone/.next/server/app/m/terminal.html +1 -1
  262. package/.next/standalone/.next/server/app/m/terminal.rsc +4 -4
  263. package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +4 -4
  264. package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
  265. package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
  266. package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
  267. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +2 -2
  268. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
  269. package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +2 -2
  270. package/.next/standalone/.next/server/app/m.html +1 -1
  271. package/.next/standalone/.next/server/app/m.rsc +4 -4
  272. package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +4 -4
  273. package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
  274. package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
  275. package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
  276. package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +2 -2
  277. package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +2 -2
  278. package/.next/standalone/.next/server/app/network.html +1 -1
  279. package/.next/standalone/.next/server/app/network.rsc +17 -16
  280. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +2 -2
  281. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
  282. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  283. package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +17 -16
  284. package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
  285. package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
  286. package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
  287. package/.next/standalone/.next/server/app/projects.html +1 -1
  288. package/.next/standalone/.next/server/app/projects.rsc +17 -16
  289. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +2 -2
  290. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
  291. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  292. package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +17 -16
  293. package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
  294. package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
  295. package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
  296. package/.next/standalone/.next/server/app/sessions.html +1 -1
  297. package/.next/standalone/.next/server/app/sessions.rsc +17 -16
  298. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +2 -2
  299. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
  300. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  301. package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +17 -16
  302. package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
  303. package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
  304. package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
  305. package/.next/standalone/.next/server/app/settings.html +1 -1
  306. package/.next/standalone/.next/server/app/settings.rsc +17 -16
  307. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +2 -2
  308. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
  309. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  310. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +17 -16
  311. package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  312. package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  313. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  314. package/.next/standalone/.next/server/app/terminal.html +1 -1
  315. package/.next/standalone/.next/server/app/terminal.rsc +17 -16
  316. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
  317. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
  318. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  319. package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +17 -16
  320. package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
  321. package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
  322. package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
  323. package/.next/standalone/.next/server/app/workspaces.html +1 -1
  324. package/.next/standalone/.next/server/app/workspaces.rsc +17 -16
  325. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +2 -2
  326. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
  327. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +5 -4
  328. package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +17 -16
  329. package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
  330. package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
  331. package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
  332. package/.next/standalone/.next/server/app-paths-manifest.json +7 -0
  333. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0041efe4._.js +98 -0
  334. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00bf0ace._.js +2 -2
  335. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e71d908._.js +2 -2
  336. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e9142f3._.js +2 -2
  337. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10e47926._.js +1 -1
  338. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1665dc78._.js +2 -2
  339. package/.next/standalone/.next/server/chunks/[root-of-the-server]__175cbabf._.js +2 -2
  340. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adae357._.js +2 -2
  341. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d359752._.js +2 -2
  342. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1e8fabeb._.js +3 -3
  343. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1f8deca0._.js +8 -8
  344. package/.next/standalone/.next/server/chunks/[root-of-the-server]__253fdda1._.js +2 -2
  345. package/.next/standalone/.next/server/chunks/[root-of-the-server]__28e6434f._.js +2 -2
  346. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a386564._.js +1 -1
  347. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2c20fb38._.js +2 -2
  348. package/.next/standalone/.next/server/chunks/[root-of-the-server]__309132cd._.js +1 -1
  349. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__cf3c60c2._.js → [root-of-the-server]__33fec964._.js} +4 -4
  350. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3786d8ae._.js +1 -1
  351. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3ae92407._.js +2 -2
  352. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3beda9fe._.js +2 -2
  353. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4619e9bd._.js +1 -1
  354. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a051043._.js +1 -1
  355. package/.next/standalone/.next/server/chunks/[root-of-the-server]__508002e4._.js +2 -2
  356. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5086c373._.js +2 -2
  357. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5913e097._.js +2 -2
  358. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5b5f68d2._.js +2 -2
  359. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c1f2459._.js +2 -2
  360. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5ec8c977._.js +2 -2
  361. package/.next/standalone/.next/server/chunks/[root-of-the-server]__63cebc6c._.js +98 -0
  362. package/.next/standalone/.next/server/chunks/[root-of-the-server]__64d30d4d._.js +2 -2
  363. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c54fc2e._.js +2 -2
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6dc1fb7e._.js +1 -1
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6e568102._.js +1 -1
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6faa04c0._.js +2 -2
  367. package/.next/standalone/.next/server/chunks/[root-of-the-server]__74a34dc3._.js +98 -0
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e7250a4._.js +3 -3
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8309e0a4._.js +2 -2
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__86cc0e2b._.js +6 -6
  371. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c2565a._.js +2 -2
  372. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d178ad9._.js +2 -2
  373. package/.next/standalone/.next/server/chunks/[root-of-the-server]__93ee06f3._.js +3 -3
  374. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e4c154a._.js +2 -2
  375. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d2e1d3._.js +2 -2
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ae53d343._.js +2 -2
  377. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b3a04cef._.js +2 -2
  378. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b4270b77._.js +3 -0
  379. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b6b6ce60._.js +1 -1
  380. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b9545dd9._.js +1 -1
  381. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c88b63f7._.js +98 -0
  382. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__eee4c5e8._.js → [root-of-the-server]__cba5f007._.js} +2 -2
  383. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cbf4ceb0._.js +2 -2
  384. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cefdba2f._.js +2 -2
  385. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf9e82bb._.js +2 -2
  386. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d2897392._.js +2 -2
  387. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d3b2d856._.js +2 -2
  388. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d73273ca._.js +1 -1
  389. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8417eb6._.js +2 -2
  390. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc2a55de._.js +2 -2
  391. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e0d4690b._.js +3 -3
  392. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e678dd53._.js +3 -0
  393. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e9223f55._.js +2 -2
  394. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ea630076._.js +3 -3
  395. package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb8acb65._.js +1 -1
  396. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f26ca49d._.js +1 -1
  397. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f33e1101._.js +1 -1
  398. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f515f865._.js +2 -2
  399. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fceb5d60._.js +98 -0
  400. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fed41403._.js +2 -2
  401. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff2e98c2._.js +2 -2
  402. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_assess_route_actions_3e8ef2a6.js +3 -0
  403. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_publish_route_actions_49238be3.js +3 -0
  404. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_refine_route_actions_05675688.js +3 -0
  405. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_review_route_actions_5cbb5f6b.js +3 -0
  406. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_curation_seed_route_actions_21084bdb.js +3 -0
  407. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_marketplace_browse_route_actions_7ed0768c.js +3 -0
  408. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_cortex_marketplace_preview_route_actions_38d4cc17.js +3 -0
  409. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19afc53d._.js +3 -0
  410. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
  411. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__efef0a06._.js → [root-of-the-server]__ca5beb09._.js} +2 -2
  412. package/.next/standalone/.next/server/chunks/ssr/{_7dd2ac82._.js → _078dd64d._.js} +1 -1
  413. package/.next/standalone/.next/server/chunks/ssr/_163d0838._.js +3 -0
  414. package/.next/standalone/.next/server/chunks/ssr/{_a4eeff0d._.js → _2230ad2d._.js} +2 -2
  415. package/.next/standalone/.next/server/chunks/ssr/{_9303a965._.js → _701606d5._.js} +1 -1
  416. package/.next/standalone/.next/server/chunks/ssr/_72b1de37._.js +3 -0
  417. package/.next/standalone/.next/server/chunks/ssr/{_d39bcfda._.js → _93ef0f79._.js} +2 -2
  418. package/.next/standalone/.next/server/chunks/ssr/_950142a4._.js +1 -1
  419. package/.next/standalone/.next/server/chunks/ssr/_a22b5eb0._.js +3 -0
  420. package/.next/standalone/.next/server/chunks/ssr/_aeeff784._.js +3 -0
  421. package/.next/standalone/.next/server/chunks/ssr/_c1cfdd09._.js +3 -0
  422. package/.next/standalone/.next/server/chunks/ssr/_c2d3f6de._.js +3 -0
  423. package/.next/standalone/.next/server/chunks/ssr/{_8167090e._.js → _db2fec84._.js} +2 -2
  424. package/.next/standalone/.next/server/chunks/ssr/src_02bae6e5._.js +3 -0
  425. package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_terminal_page_tsx_de5e8d85._.js +2 -2
  426. package/.next/standalone/.next/server/chunks/ssr/src_components_terminal_terminal-pane_tsx_803c5e2c._.js +2 -2
  427. package/.next/standalone/.next/server/edge/chunks/_d73df637._.js +1 -1
  428. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  429. package/.next/standalone/.next/server/pages/404.html +1 -1
  430. package/.next/standalone/.next/server/pages/500.html +2 -2
  431. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  432. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  433. package/.next/standalone/.next/static/chunks/25b7a243a404a1a7.js +1 -0
  434. package/.next/standalone/.next/static/chunks/2b997e211a5d547b.js +1 -0
  435. package/.next/standalone/.next/static/chunks/5e08abb00653754a.js +1 -0
  436. package/.next/standalone/.next/static/chunks/61f2ed39b75b1efc.js +1 -0
  437. package/.next/standalone/.next/static/chunks/6c78a1dfa7ec2959.css +3 -0
  438. package/.next/standalone/.next/static/chunks/7246f1ee445f7024.js +1 -0
  439. package/.next/standalone/.next/static/chunks/7424664c6ffa94bd.js +1 -0
  440. package/.next/standalone/.next/static/chunks/7e0091ab6c5ee8bd.js +1 -0
  441. package/.next/standalone/.next/static/chunks/8b3f4572fec83caa.js +5 -0
  442. package/.next/standalone/.next/static/chunks/{a7b2795949a6c63e.js → 9899cf4c2bdbe61d.js} +2 -2
  443. package/.next/standalone/.next/static/chunks/ac339e970df82fa5.js +5 -0
  444. package/.next/standalone/.next/static/chunks/{0e61e67b7b8fc3f3.js → c418112e102673ce.js} +1 -1
  445. package/.next/standalone/.next/static/chunks/e2f0a2330dedff4b.js +85 -0
  446. package/.next/standalone/.next/static/chunks/f0c8b3f8cc1939ec.js +1 -0
  447. package/.next/standalone/.next/static/chunks/f9f2628207848ac2.js +1 -0
  448. package/.next/standalone/bin/cortex-hook.sh +62 -62
  449. package/.next/standalone/bin/cortex-mcp.js +60 -60
  450. package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
  451. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
  452. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
  453. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
  454. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
  455. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
  456. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
  457. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
  458. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
  459. package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -0
  460. package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
  461. package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
  462. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
  463. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
  464. package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -0
  465. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +46 -0
  466. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +221 -0
  467. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +1 -0
  468. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  469. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +42 -0
  470. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +46 -0
  471. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +221 -0
  472. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +1 -0
  473. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  474. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +42 -0
  475. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
  476. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  477. package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-linux-x64}/package.json +46 -39
  478. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  479. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +46 -0
  480. package/.next/standalone/package.json +102 -102
  481. package/.next/standalone/server.js +1 -1
  482. package/.next/standalone/src/app/(desktop)/cortex/page.tsx +78 -75
  483. package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
  484. package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -0
  485. package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -0
  486. package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -0
  487. package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -0
  488. package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -0
  489. package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
  490. package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
  491. package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
  492. package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
  493. package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
  494. package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
  495. package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
  496. package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
  497. package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
  498. package/.next/standalone/src/app/api/cortex/import/route.ts +75 -43
  499. package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
  500. package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +29 -29
  501. package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
  502. package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
  503. package/.next/standalone/src/app/api/cortex/knowledge/route.ts +93 -93
  504. package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
  505. package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
  506. package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
  507. package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -0
  508. package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -0
  509. package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
  510. package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +6 -6
  511. package/.next/standalone/src/app/api/cortex/search/route.ts +43 -43
  512. package/.next/standalone/src/app/api/cortex/settings/route.ts +33 -33
  513. package/.next/standalone/src/app/api/cortex/status/route.ts +169 -129
  514. package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
  515. package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
  516. package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
  517. package/.next/standalone/src/components/cortex/constants.ts +29 -0
  518. package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -228
  519. package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
  520. package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
  521. package/.next/standalone/src/components/cortex/cortex-settings.tsx +221 -221
  522. package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -0
  523. package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
  524. package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
  525. package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -0
  526. package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
  527. package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -127
  528. package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -56
  529. package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -199
  530. package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -0
  531. package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -0
  532. package/.next/standalone/src/lib/cortex/config.ts +40 -40
  533. package/.next/standalone/src/lib/cortex/debug.ts +10 -10
  534. package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
  535. package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
  536. package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
  537. package/.next/standalone/src/lib/cortex/index.ts +56 -56
  538. package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
  539. package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
  540. package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
  541. package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
  542. package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
  543. package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
  544. package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
  545. package/.next/standalone/src/lib/cortex/mcp/server.ts +8 -8
  546. package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
  547. package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
  548. package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
  549. package/.next/standalone/src/lib/cortex/types.ts +39 -38
  550. package/.next/standalone/tsconfig.json +34 -34
  551. package/LICENSE +661 -661
  552. package/README.md +131 -131
  553. package/bin/cortex-hook.sh +62 -62
  554. package/bin/cortex-mcp.js +60 -60
  555. package/bin/fix-standalone-externals.js +79 -79
  556. package/bin/lib/auto-setup.js +110 -110
  557. package/bin/mdns-service.js +171 -171
  558. package/bin/postinstall.js +35 -35
  559. package/bin/setup-admin.js +195 -195
  560. package/bin/spaces-dev.js +208 -208
  561. package/bin/spaces-install.js +599 -599
  562. package/bin/spaces-reset-totp.js +50 -50
  563. package/bin/spaces-service.js +1020 -1020
  564. package/bin/spaces-setup.js +253 -253
  565. package/bin/spaces.js +776 -728
  566. package/bin/ssh-auth-keys.sh +68 -68
  567. package/bin/terminal-server.js +1649 -1646
  568. package/package.json +102 -102
  569. package/.next/standalone/.claude/settings.local.json +0 -55
  570. package/.next/standalone/.claude/spaces-env.json +0 -1
  571. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ecce08b._.js +0 -3
  572. package/.next/standalone/.next/server/chunks/ssr/_7e63066a._.js +0 -3
  573. package/.next/standalone/.next/server/chunks/ssr/_a012c43b._.js +0 -3
  574. package/.next/standalone/.next/server/chunks/ssr/_b4db3983._.js +0 -3
  575. package/.next/standalone/.next/server/chunks/ssr/_cd50a174._.js +0 -3
  576. package/.next/standalone/.next/server/chunks/ssr/_e7a9e2b0._.js +0 -3
  577. package/.next/standalone/.next/server/chunks/ssr/_ed6c285b._.js +0 -3
  578. package/.next/standalone/.next/server/chunks/ssr/src_133a7c8a._.js +0 -3
  579. package/.next/standalone/.next/static/chunks/17d164c01fa1aaa9.js +0 -5
  580. package/.next/standalone/.next/static/chunks/19da759dde107f02.js +0 -1
  581. package/.next/standalone/.next/static/chunks/58d78765e5140dcb.js +0 -1
  582. package/.next/standalone/.next/static/chunks/596bd56e0ad09173.js +0 -5
  583. package/.next/standalone/.next/static/chunks/78dd908e70bf8c4b.js +0 -85
  584. package/.next/standalone/.next/static/chunks/79aacab676df80c4.js +0 -1
  585. package/.next/standalone/.next/static/chunks/846d6ef408f69390.js +0 -1
  586. package/.next/standalone/.next/static/chunks/8de5e432a2fc563a.js +0 -1
  587. package/.next/standalone/.next/static/chunks/9196173f0d081f2c.js +0 -1
  588. package/.next/standalone/.next/static/chunks/9bd8a119aaeafc9e.js +0 -1
  589. package/.next/standalone/.next/static/chunks/e35e863c09511a51.js +0 -1
  590. package/.next/standalone/.next/static/chunks/f644c11ace7a0d9d.css +0 -3
  591. package/.next/standalone/.spaces/cortex-context.md +0 -70
  592. package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
  593. package/.next/standalone/src/components/cortex/context-tab.tsx +0 -171
  594. /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_buildManifest.js +0 -0
  595. /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_clientMiddlewareManifest.json +0 -0
  596. /package/.next/standalone/.next/static/{B207XFa7QvXAYS7Sqq2_4 → 77VYbwIoyxFNr5xevTrCu}/_ssgManifest.js +0 -0
  597. /package/.next/standalone/node_modules/@img/{sharp-win32-x64 → sharp-libvips-linux-x64}/versions.json +0 -0
@@ -1,1020 +1,1020 @@
1
- #!/usr/bin/env node
2
-
3
- 'use strict';
4
-
5
- const { execFileSync, spawnSync } = require('child_process');
6
- const path = require('path');
7
- const os = require('os');
8
- const fs = require('fs');
9
- const readline = require('readline');
10
-
11
- let SPACES_DIR = path.join(os.homedir(), '.spaces');
12
- let CONFIG_PATH = path.join(SPACES_DIR, 'server.json');
13
- let LOGS_DIR = path.join(SPACES_DIR, 'logs');
14
- const SERVICE_NAME = 'spaces';
15
- const LABEL = 'com.agentspaces.spaces';
16
- const TASK_NAME = 'Spaces';
17
-
18
- // Override SPACES_DIR to point to a different user's home
19
- function setTargetHome(homedir) {
20
- SPACES_DIR = path.join(homedir, '.spaces');
21
- CONFIG_PATH = path.join(SPACES_DIR, 'server.json');
22
- LOGS_DIR = path.join(SPACES_DIR, 'logs');
23
- }
24
-
25
- // ─── Helpers ──────────────────────────────────────────────────
26
- function log(msg) { console.log(` ${msg}`); }
27
- function logOk(msg) { console.log(` ✓ ${msg}`); }
28
- function logErr(msg) { console.error(` ✗ ${msg}`); }
29
-
30
- function resolveConfig() {
31
- let config = {};
32
- if (fs.existsSync(CONFIG_PATH)) {
33
- try { config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
34
- }
35
- return {
36
- port: config.port || 3457,
37
- tier: config.tier || 'community',
38
- basePath: config.basePath || '',
39
- allowedOrigins: config.allowedOrigins || '',
40
- };
41
- }
42
-
43
- function resolveSpacesPath() {
44
- return path.join(__dirname, 'spaces.js');
45
- }
46
-
47
- function resolveProjectDir() {
48
- return path.join(__dirname, '..');
49
- }
50
-
51
- function resolveNodePath() {
52
- return process.execPath;
53
- }
54
-
55
- function promptLevel() {
56
- return new Promise((resolve) => {
57
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
58
- console.log('');
59
- log('Install as:');
60
- log(' 1. System service (starts on boot, all users)');
61
- log(' 2. User service (starts on login, current user only)');
62
- console.log('');
63
- rl.question(' Choice [2]: ', (answer) => {
64
- rl.close();
65
- const choice = answer.trim() || '2';
66
- resolve(choice === '1' ? 'system' : 'user');
67
- });
68
- });
69
- }
70
-
71
- function ensureLogsDir() {
72
- fs.mkdirSync(LOGS_DIR, { recursive: true });
73
- }
74
-
75
- // Detect Windows user profiles that have .claude/ (Claude Code data)
76
- function findClaudeUsers() {
77
- if (process.platform !== 'win32') return [];
78
- const usersDir = path.dirname(os.homedir());
79
- const skip = new Set(['Public', 'Default', 'Default User', 'All Users']);
80
- try {
81
- return fs.readdirSync(usersDir)
82
- .filter(name => !skip.has(name) && !name.startsWith('.'))
83
- .filter(name => {
84
- const claudeDir = path.join(usersDir, name, '.claude');
85
- return fs.existsSync(claudeDir);
86
- })
87
- .map(name => ({ name, homedir: path.join(usersDir, name) }));
88
- } catch { return []; }
89
- }
90
-
91
- // Prompt for which user's home directory to target (system service only).
92
- // Looks for users with .claude/ (Claude Code data) since that's what gets synced.
93
- function promptTargetUser() {
94
- return new Promise((resolve) => {
95
- const currentHome = os.homedir();
96
- const users = findClaudeUsers();
97
-
98
- // No Claude users found — fall back to current user
99
- if (users.length === 0) {
100
- resolve(currentHome);
101
- return;
102
- }
103
- // Single Claude user — auto-select
104
- if (users.length === 1) {
105
- log(`Using home directory: ${users[0].homedir}`);
106
- resolve(users[0].homedir);
107
- return;
108
- }
109
-
110
- // Multiple Claude users — ask which one
111
- console.log('');
112
- log('Multiple users have Claude Code data:');
113
- users.forEach((u, i) => log(` ${i + 1}. ${u.name} (${u.homedir})`));
114
- console.log('');
115
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
116
- rl.question(` Run as user [1]: `, (answer) => {
117
- rl.close();
118
- const idx = parseInt(answer.trim() || '1', 10) - 1;
119
- const chosen = users[idx] || users[0];
120
- resolve(chosen.homedir);
121
- });
122
- });
123
- }
124
-
125
-
126
- // --- SSH AuthorizedKeysCommand setup (Linux/macOS) ---
127
- // Configures sshd to dynamically authorize Spaces users via a lookup script,
128
- // eliminating the need to manually manage ~/.ssh/authorized_keys per user.
129
- function configureAuthorizedKeysCommand() {
130
- if (process.platform === 'win32') return; // Windows uses a different approach
131
-
132
- const scriptSrc = path.join(__dirname, 'ssh-auth-keys.sh');
133
- const scriptDst = '/opt/spaces/bin/ssh-auth-keys.sh';
134
- const sshdConfig = '/etc/ssh/sshd_config';
135
- const spacesConfDrop = '/etc/ssh/sshd_config.d/spaces.conf';
136
-
137
- // Install the script to a stable system path
138
- try {
139
- spawnSync('sudo', ['mkdir', '-p', '/opt/spaces/bin'], { stdio: 'pipe', timeout: 5000 });
140
- spawnSync('sudo', ['cp', scriptSrc, scriptDst], { stdio: 'pipe', timeout: 5000 });
141
- spawnSync('sudo', ['chmod', '755', scriptDst], { stdio: 'pipe', timeout: 5000 });
142
- spawnSync('sudo', ['chown', 'root:root', scriptDst], { stdio: 'pipe', timeout: 5000 });
143
- logOk('Installed ssh-auth-keys.sh to ' + scriptDst);
144
- } catch (e) {
145
- logErr('Failed to install ssh-auth-keys.sh: ' + e.message);
146
- return;
147
- }
148
-
149
- // Prefer sshd_config.d drop-in if the Include directive exists
150
- const sshdContent = fs.existsSync(sshdConfig)
151
- ? fs.readFileSync(sshdConfig, 'utf-8') : '';
152
-
153
- const configLines = [
154
- '# Spaces: dynamically authorize shell users via admin DB lookup',
155
- 'AuthorizedKeysCommand ' + scriptDst + ' %u',
156
- 'AuthorizedKeysCommandUser nobody',
157
- ].join('\n') + '\n';
158
-
159
- // Already configured?
160
- if (sshdContent.includes('ssh-auth-keys.sh')) {
161
- logOk('AuthorizedKeysCommand already configured in sshd_config');
162
- return;
163
- }
164
-
165
- // Try drop-in directory first (cleaner, doesn't touch main config)
166
- const hasIncludeDir = sshdContent.includes('Include /etc/ssh/sshd_config.d/');
167
- if (hasIncludeDir) {
168
- try {
169
- spawnSync('sudo', ['mkdir', '-p', '/etc/ssh/sshd_config.d'], { stdio: 'pipe', timeout: 5000 });
170
- const result = spawnSync('sudo', ['tee', spacesConfDrop], {
171
- input: configLines, stdio: ['pipe', 'pipe', 'inherit'], timeout: 5000,
172
- });
173
- if (result.status === 0) {
174
- logOk('Created ' + spacesConfDrop);
175
- }
176
- } catch (e) {
177
- logErr('Failed to write drop-in config: ' + e.message);
178
- }
179
- } else {
180
- // Append directly to sshd_config
181
- try {
182
- const appendContent = '\n# Spaces SSH authorization\n' + configLines;
183
- const result = spawnSync('sudo', ['tee', '-a', sshdConfig], {
184
- input: appendContent, stdio: ['pipe', 'pipe', 'inherit'], timeout: 5000,
185
- });
186
- if (result.status === 0) {
187
- logOk('Added AuthorizedKeysCommand to ' + sshdConfig);
188
- }
189
- } catch (e) {
190
- logErr('Failed to update sshd_config: ' + e.message);
191
- }
192
- }
193
-
194
- // Restart sshd to pick up the change
195
- try {
196
- spawnSync('sudo', ['systemctl', 'restart', 'sshd'], { stdio: 'pipe', timeout: 10000 });
197
- logOk('Restarted sshd');
198
- } catch {
199
- try {
200
- spawnSync('sudo', ['systemctl', 'restart', 'ssh'], { stdio: 'pipe', timeout: 10000 });
201
- logOk('Restarted ssh');
202
- } catch {
203
- log('Warning: could not restart sshd — restart it manually');
204
- }
205
- }
206
- }
207
-
208
- // --- SSH key provisioning (for multi-user system service) ---
209
- function checkOpenSSHServer() {
210
- if (process.platform !== 'win32') return true;
211
- try {
212
- const result = spawnSync('powershell', ['-NoProfile', '-Command',
213
- 'Get-Service sshd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Status'
214
- ], { encoding: 'utf-8', timeout: 10000 });
215
- const status = (result.stdout || '').trim();
216
- return status === 'Running' || status === 'Stopped';
217
- } catch { return false; }
218
- }
219
-
220
- function ensureOpenSSHServer() {
221
- if (process.platform !== 'win32') return;
222
- if (checkOpenSSHServer()) {
223
- try {
224
- spawnSync('powershell', ['-NoProfile', '-Command',
225
- 'Start-Service sshd; Set-Service -Name sshd -StartupType Automatic'
226
- ], { stdio: 'pipe', timeout: 15000 });
227
- logOk('OpenSSH Server started and set to automatic');
228
- } catch {
229
- log('Warning: could not start OpenSSH Server');
230
- }
231
- return;
232
- }
233
- log('Installing OpenSSH Server (required for multi-user terminals)...');
234
- try {
235
- const result = spawnSync('powershell', ['-NoProfile', '-Command',
236
- 'Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0'
237
- ], { encoding: 'utf-8', stdio: 'pipe', timeout: 120000 });
238
- if (result.status === 0) {
239
- logOk('OpenSSH Server installed');
240
- spawnSync('powershell', ['-NoProfile', '-Command',
241
- 'Start-Service sshd; Set-Service -Name sshd -StartupType Automatic'
242
- ], { stdio: 'pipe', timeout: 15000 });
243
- logOk('OpenSSH Server started');
244
- } else {
245
- logErr('Failed to install OpenSSH Server: ' + (result.stderr || '').trim());
246
- log('Multi-user terminals will not work. Install manually:');
247
- log(' Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0');
248
- }
249
- } catch (e) {
250
- logErr('Failed to install OpenSSH Server: ' + e.message);
251
- }
252
- }
253
-
254
- function ensureServiceKey() {
255
- const keyPath = path.join(SPACES_DIR, 'service_key');
256
- if (fs.existsSync(keyPath)) return keyPath;
257
- log('Generating SSH service key...');
258
- fs.mkdirSync(SPACES_DIR, { recursive: true });
259
- const result = spawnSync('ssh-keygen', [
260
- '-t', 'ed25519',
261
- '-f', keyPath,
262
- '-N', '',
263
- '-C', 'spaces-service-key',
264
- ], { stdio: 'pipe', timeout: 10000 });
265
- if (result.status !== 0) {
266
- logErr('Failed to generate SSH key');
267
- return null;
268
- }
269
-
270
- // On Windows, restrict private key permissions so OpenSSH accepts it.
271
- // OpenSSH requires: no inherited ACLs, only the file owner + SYSTEM may have access.
272
- if (process.platform === 'win32') {
273
- try {
274
- const currentUser = os.userInfo().username;
275
- // Remove inheritance and all default ACLs, then grant only owner + SYSTEM
276
- spawnSync('icacls', [keyPath, '/inheritance:r',
277
- '/remove', 'BUILTIN\\Administrators',
278
- '/remove', 'BUILTIN\\Users',
279
- '/remove', 'Everyone',
280
- '/grant:r', currentUser + ':(F)',
281
- '/grant', 'NT AUTHORITY\\SYSTEM:(F)'], { stdio: 'pipe', timeout: 5000 });
282
- } catch {}
283
- }
284
-
285
- logOk('SSH service key generated');
286
- return keyPath;
287
- }
288
-
289
- function authorizeServiceKey(keyPath, targetUser) {
290
- const pubKey = fs.readFileSync(keyPath + '.pub', 'utf-8').trim();
291
-
292
- if (process.platform === 'win32') {
293
- // Windows OpenSSH ignores ~/.ssh/authorized_keys for admin users.
294
- // Must use C:\ProgramData\ssh\administrators_authorized_keys instead.
295
- const adminAuthKeys = path.join(process.env.ProgramData || 'C:\\ProgramData', 'ssh', 'administrators_authorized_keys');
296
- const userAuthKeys = path.join(path.dirname(os.homedir()), targetUser, '.ssh', 'authorized_keys');
297
-
298
- // Check if user is an administrator (exact match, not substring)
299
- let isAdmin = false;
300
- try {
301
- const result = spawnSync('net', ['localgroup', 'Administrators'], { encoding: 'utf-8', timeout: 5000 });
302
- const lines = (result.stdout || '').split(/\r?\n/);
303
- const sep = lines.findIndex(l => l.trim().startsWith('---'));
304
- if (sep >= 0) {
305
- const members = lines.slice(sep + 1).map(l => l.trim()).filter(Boolean);
306
- isAdmin = members.some(m => m.toLowerCase() === targetUser.toLowerCase());
307
- }
308
- } catch {}
309
-
310
- const authKeysPath = isAdmin ? adminAuthKeys : userAuthKeys;
311
- const authDir = path.dirname(authKeysPath);
312
- if (!fs.existsSync(authDir)) fs.mkdirSync(authDir, { recursive: true });
313
-
314
- // Check if key already authorized
315
- if (fs.existsSync(authKeysPath)) {
316
- const existing = fs.readFileSync(authKeysPath, 'utf-8');
317
- if (existing.includes(pubKey)) return;
318
- }
319
- fs.appendFileSync(authKeysPath, pubKey + String.fromCharCode(10));
320
-
321
- // Fix permissions for administrators_authorized_keys
322
- if (isAdmin) {
323
- try {
324
- spawnSync('icacls', [authKeysPath, '/inheritance:r', '/grant', 'SYSTEM:(R)', '/grant', 'Administrators:(R)'], { stdio: 'pipe', timeout: 5000 });
325
- } catch {}
326
- }
327
- } else {
328
- // Linux/macOS: use ~/.ssh/authorized_keys
329
- let userHome;
330
- try {
331
- // Use getent to resolve the actual home directory (works with LDAP, NIS, etc.)
332
- const result = spawnSync('getent', ['passwd', targetUser], { encoding: 'utf-8', timeout: 5000 });
333
- const fields = (result.stdout || '').split(':');
334
- userHome = fields[5] || (process.platform === 'darwin' ? `/Users/${targetUser}` : `/home/${targetUser}`);
335
- } catch {
336
- userHome = process.platform === 'darwin' ? `/Users/${targetUser}` : `/home/${targetUser}`;
337
- }
338
- const sshDir = path.join(userHome, '.ssh');
339
- const authKeysPath = path.join(sshDir, 'authorized_keys');
340
- if (!fs.existsSync(sshDir)) fs.mkdirSync(sshDir, { recursive: true, mode: 0o700 });
341
- if (fs.existsSync(authKeysPath)) {
342
- const existing = fs.readFileSync(authKeysPath, 'utf-8');
343
- if (existing.includes(pubKey)) return;
344
- }
345
- fs.appendFileSync(authKeysPath, pubKey + String.fromCharCode(10));
346
- }
347
- logOk('SSH key authorized for ' + targetUser);
348
- }
349
-
350
- const LEVEL_PATH = path.join(SPACES_DIR, 'service-level');
351
-
352
- function saveLevel(level) {
353
- fs.mkdirSync(SPACES_DIR, { recursive: true });
354
- fs.writeFileSync(LEVEL_PATH, level);
355
- }
356
-
357
- function loadLevel() {
358
- try { return fs.readFileSync(LEVEL_PATH, 'utf-8').trim(); } catch { return null; }
359
- }
360
-
361
- // ─── Platform detection ───────────────────────────────────────
362
- function getPlatform() {
363
- switch (process.platform) {
364
- case 'linux': return 'linux';
365
- case 'darwin': return 'darwin';
366
- case 'win32': return 'win32';
367
- default:
368
- logErr(`Unsupported platform: ${process.platform}`);
369
- process.exit(1);
370
- }
371
- }
372
-
373
- // ─── Linux (systemd) ─────────────────────────────────────────
374
- function linuxServicePath(level) {
375
- if (level === 'system') {
376
- return `/etc/systemd/system/${SERVICE_NAME}.service`;
377
- }
378
- const userDir = path.join(os.homedir(), '.config', 'systemd', 'user');
379
- fs.mkdirSync(userDir, { recursive: true });
380
- return path.join(userDir, `${SERVICE_NAME}.service`);
381
- }
382
-
383
- function linuxUnitFile(level) {
384
- const config = resolveConfig();
385
- const nodePath = resolveNodePath();
386
- const spacesPath = resolveSpacesPath();
387
- const projectDir = resolveProjectDir();
388
-
389
- let envLines = [
390
- `Environment=SPACES_SERVICE=1`,
391
- `Environment=SPACES_PORT=${config.port}`,
392
- ];
393
- // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
394
- if (config.tier && config.tier !== 'community') {
395
- envLines.push(`Environment=SPACES_TIER=${config.tier}`);
396
- }
397
- if (config.basePath) {
398
- envLines.push(`Environment=SPACES_BASE_PATH=${config.basePath}`);
399
- }
400
- if (config.allowedOrigins) {
401
- envLines.push(`Environment=SPACES_ALLOWED_ORIGINS=${config.allowedOrigins}`);
402
- }
403
-
404
- // Allow users to set API keys (OPENAI_API_KEY, etc.) in ~/.spaces/env
405
- const envFilePath = path.join(SPACES_DIR, 'env');
406
-
407
- let serviceSection = [
408
- 'Type=simple',
409
- `ExecStart=${nodePath} ${spacesPath}`,
410
- `WorkingDirectory=${projectDir}`,
411
- `EnvironmentFile=-${envFilePath}`,
412
- ...envLines,
413
- 'Restart=on-failure',
414
- 'RestartSec=5',
415
- ];
416
-
417
- if (level === 'system') {
418
- const username = os.userInfo().username;
419
- serviceSection.push(`User=${username}`);
420
- serviceSection.push(`Group=${username}`);
421
- }
422
-
423
- const wantedBy = level === 'system' ? 'multi-user.target' : 'default.target';
424
-
425
- return [
426
- '[Unit]',
427
- 'Description=Spaces - Agent Workspace Manager',
428
- 'After=network.target',
429
- '',
430
- '[Service]',
431
- ...serviceSection,
432
- '',
433
- '[Install]',
434
- `WantedBy=${wantedBy}`,
435
- '',
436
- ].join('\n');
437
- }
438
-
439
- function linuxSystemctl(level, ...args) {
440
- if (level === 'system') {
441
- execFileSync('sudo', ['systemctl', ...args], { stdio: 'inherit' });
442
- } else {
443
- // User-level systemctl needs XDG_RUNTIME_DIR to connect to the user bus.
444
- // Always set it from the real UID — inherited env may have a stale value
445
- // (e.g. from a prior session with a different UID mapping).
446
- const uid = process.getuid();
447
- const env = {
448
- ...process.env,
449
- XDG_RUNTIME_DIR: `/run/user/${uid}`,
450
- DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
451
- };
452
- execFileSync('systemctl', ['--user', ...args], { stdio: 'inherit', env });
453
- }
454
- }
455
-
456
- async function linuxInstall() {
457
- const level = await promptLevel();
458
- const unitContent = linuxUnitFile(level);
459
- const servicePath = linuxServicePath(level);
460
-
461
- log(`Writing unit file to ${servicePath}`);
462
- if (level === 'system') {
463
- const result = spawnSync('sudo', ['tee', servicePath], {
464
- input: unitContent,
465
- stdio: ['pipe', 'pipe', 'inherit'],
466
- });
467
- if (result.status !== 0) {
468
- logErr('Failed to write unit file');
469
- process.exit(1);
470
- }
471
- } else {
472
- fs.writeFileSync(servicePath, unitContent);
473
- }
474
- logOk('Unit file written');
475
-
476
- saveLevel(level);
477
-
478
- linuxSystemctl(level, 'daemon-reload');
479
- logOk('Reloaded systemd daemon');
480
-
481
- linuxSystemctl(level, 'enable', `${SERVICE_NAME}.service`);
482
- logOk('Service enabled');
483
-
484
- linuxSystemctl(level, 'start', `${SERVICE_NAME}.service`);
485
- logOk('Service started');
486
-
487
- if (level === 'user') {
488
- try {
489
- const uid = process.getuid();
490
- const env = {
491
- ...process.env,
492
- XDG_RUNTIME_DIR: `/run/user/${uid}`,
493
- DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
494
- };
495
- execFileSync('loginctl', ['enable-linger', os.userInfo().username], { stdio: 'inherit', env });
496
- logOk('Enabled login lingering for user service');
497
- } catch {
498
- log('Warning: could not enable-linger (user service may not start on boot)');
499
- }
500
- }
501
-
502
- // Set up SSH AuthorizedKeysCommand for seamless multi-user terminal access
503
- if (level === 'system') {
504
- configureAuthorizedKeysCommand();
505
- const keyPath = ensureServiceKey();
506
- if (keyPath) {
507
- authorizeServiceKey(keyPath, os.userInfo().username);
508
- }
509
- }
510
-
511
- logOk(`Spaces installed as ${level} service`);
512
- }
513
-
514
- async function linuxUninstall() {
515
- const level = loadLevel() || 'user';
516
- const servicePath = linuxServicePath(level);
517
-
518
- try {
519
- linuxSystemctl(level, 'stop', `${SERVICE_NAME}.service`);
520
- logOk('Service stopped');
521
- } catch {
522
- log('Service was not running');
523
- }
524
-
525
- try {
526
- linuxSystemctl(level, 'disable', `${SERVICE_NAME}.service`);
527
- logOk('Service disabled');
528
- } catch {
529
- log('Service was not enabled');
530
- }
531
-
532
- try {
533
- if (level === 'system') {
534
- execFileSync('sudo', ['rm', '-f', servicePath], { stdio: 'inherit' });
535
- } else {
536
- fs.unlinkSync(servicePath);
537
- }
538
- logOk('Unit file removed');
539
- } catch {
540
- log('Unit file was already removed');
541
- }
542
-
543
- try {
544
- linuxSystemctl(level, 'daemon-reload');
545
- logOk('Reloaded systemd daemon');
546
- } catch {
547
- log('Warning: daemon-reload failed');
548
- }
549
-
550
- try {
551
- fs.unlinkSync(LEVEL_PATH);
552
- } catch {}
553
-
554
- logOk('Spaces service uninstalled');
555
- }
556
-
557
- async function linuxStart() {
558
- const level = loadLevel() || 'user';
559
- linuxSystemctl(level, 'start', `${SERVICE_NAME}.service`);
560
- logOk('Service started');
561
- }
562
-
563
- async function linuxStop() {
564
- const level = loadLevel() || 'user';
565
- linuxSystemctl(level, 'stop', `${SERVICE_NAME}.service`);
566
- logOk('Service stopped');
567
- }
568
-
569
- async function linuxStatus() {
570
- const level = loadLevel() || 'user';
571
- try {
572
- linuxSystemctl(level, 'status', `${SERVICE_NAME}.service`);
573
- } catch {
574
- // systemctl status returns non-zero for stopped/failed services
575
- }
576
- }
577
-
578
- async function linuxLogs() {
579
- const level = loadLevel() || 'user';
580
- if (level === 'user') {
581
- const uid = process.getuid();
582
- const env = {
583
- ...process.env,
584
- XDG_RUNTIME_DIR: `/run/user/${uid}`,
585
- DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
586
- };
587
- spawnSync('journalctl', ['--user', '-u', SERVICE_NAME, '-f', '--no-pager'], { stdio: 'inherit', env });
588
- } else {
589
- spawnSync('sudo', ['journalctl', '-u', SERVICE_NAME, '-f', '--no-pager'], { stdio: 'inherit' });
590
- }
591
- }
592
-
593
- // ─── macOS (launchd) ─────────────────────────────────────────
594
- function darwinPlistPath(level) {
595
- if (level === 'system') {
596
- return `/Library/LaunchDaemons/${LABEL}.plist`;
597
- }
598
- return path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
599
- }
600
-
601
- function darwinPlistContent(level) {
602
- ensureLogsDir();
603
- const config = resolveConfig();
604
- const nodePath = resolveNodePath();
605
- const spacesPath = resolveSpacesPath();
606
- const projectDir = resolveProjectDir();
607
- const outLog = path.join(LOGS_DIR, 'spaces.out.log');
608
- const errLog = path.join(LOGS_DIR, 'spaces.err.log');
609
-
610
- let envEntries = [
611
- ` <key>SPACES_SERVICE</key>`,
612
- ` <string>1</string>`,
613
- ` <key>SPACES_PORT</key>`,
614
- ` <string>${config.port}</string>`,
615
- ];
616
- // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
617
- if (config.tier && config.tier !== 'community') {
618
- envEntries.push(` <key>SPACES_TIER</key>`);
619
- envEntries.push(` <string>${config.tier}</string>`);
620
- }
621
- if (config.basePath) {
622
- envEntries.push(` <key>SPACES_BASE_PATH</key>`);
623
- envEntries.push(` <string>${config.basePath}</string>`);
624
- }
625
- if (config.allowedOrigins) {
626
- envEntries.push(` <key>SPACES_ALLOWED_ORIGINS</key>`);
627
- envEntries.push(` <string>${config.allowedOrigins}</string>`);
628
- }
629
-
630
- let extraKeys = '';
631
- if (level === 'system') {
632
- extraKeys = ` <key>UserName</key>\n <string>${os.userInfo().username}</string>\n`;
633
- }
634
-
635
- return [
636
- '<?xml version="1.0" encoding="UTF-8"?>',
637
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
638
- '<plist version="1.0">',
639
- '<dict>',
640
- ` <key>Label</key>`,
641
- ` <string>${LABEL}</string>`,
642
- ` <key>ProgramArguments</key>`,
643
- ` <array>`,
644
- ` <string>${nodePath}</string>`,
645
- ` <string>${spacesPath}</string>`,
646
- ` </array>`,
647
- ` <key>WorkingDirectory</key>`,
648
- ` <string>${projectDir}</string>`,
649
- ` <key>EnvironmentVariables</key>`,
650
- ` <dict>`,
651
- ...envEntries,
652
- ` </dict>`,
653
- ` <key>RunAtLoad</key>`,
654
- ` <true/>`,
655
- ` <key>KeepAlive</key>`,
656
- ` <true/>`,
657
- ` <key>StandardOutPath</key>`,
658
- ` <string>${outLog}</string>`,
659
- ` <key>StandardErrorPath</key>`,
660
- ` <string>${errLog}</string>`,
661
- extraKeys ? extraKeys.trimEnd() : null,
662
- '</dict>',
663
- '</plist>',
664
- '',
665
- ].filter((line) => line !== null).join('\n');
666
- }
667
-
668
- async function darwinInstall() {
669
- const level = await promptLevel();
670
- const plistPath = darwinPlistPath(level);
671
- const plistContent = darwinPlistContent(level);
672
-
673
- // Unload existing (ignore errors)
674
- try {
675
- execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'pipe' });
676
- } catch {}
677
-
678
- log(`Writing plist to ${plistPath}`);
679
- if (level === 'system') {
680
- const result = spawnSync('sudo', ['tee', plistPath], {
681
- input: plistContent,
682
- stdio: ['pipe', 'pipe', 'inherit'],
683
- });
684
- if (result.status !== 0) {
685
- logErr('Failed to write plist file');
686
- process.exit(1);
687
- }
688
- execFileSync('sudo', ['chown', 'root:wheel', plistPath], { stdio: 'inherit' });
689
- execFileSync('sudo', ['chmod', '644', plistPath], { stdio: 'inherit' });
690
- } else {
691
- const agentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
692
- fs.mkdirSync(agentsDir, { recursive: true });
693
- fs.writeFileSync(plistPath, plistContent);
694
- }
695
- logOk('Plist file written');
696
-
697
- saveLevel(level);
698
-
699
- if (level === 'system') {
700
- execFileSync('sudo', ['launchctl', 'load', '-w', plistPath], { stdio: 'inherit' });
701
- } else {
702
- execFileSync('launchctl', ['load', '-w', plistPath], { stdio: 'inherit' });
703
- }
704
- logOk('Service loaded');
705
-
706
- // Set up SSH AuthorizedKeysCommand for seamless multi-user terminal access
707
- if (level === 'system') {
708
- configureAuthorizedKeysCommand();
709
- const keyPath = ensureServiceKey();
710
- if (keyPath) {
711
- authorizeServiceKey(keyPath, os.userInfo().username);
712
- }
713
- }
714
-
715
- logOk(`Spaces installed as ${level} service`);
716
- }
717
-
718
- async function darwinUninstall() {
719
- const level = loadLevel() || 'user';
720
- const plistPath = darwinPlistPath(level);
721
-
722
- try {
723
- if (level === 'system') {
724
- execFileSync('sudo', ['launchctl', 'unload', '-w', plistPath], { stdio: 'inherit' });
725
- } else {
726
- execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'inherit' });
727
- }
728
- logOk('Service unloaded');
729
- } catch {
730
- log('Service was not loaded');
731
- }
732
-
733
- try {
734
- if (level === 'system') {
735
- execFileSync('sudo', ['rm', '-f', plistPath], { stdio: 'inherit' });
736
- } else {
737
- fs.unlinkSync(plistPath);
738
- }
739
- logOk('Plist file removed');
740
- } catch {
741
- log('Plist file was already removed');
742
- }
743
-
744
- try {
745
- fs.unlinkSync(LEVEL_PATH);
746
- } catch {}
747
-
748
- logOk('Spaces service uninstalled');
749
- }
750
-
751
- async function darwinStart() {
752
- const level = loadLevel() || 'user';
753
- const plistPath = darwinPlistPath(level);
754
- if (level === 'system') {
755
- execFileSync('sudo', ['launchctl', 'load', '-w', plistPath], { stdio: 'inherit' });
756
- } else {
757
- execFileSync('launchctl', ['load', '-w', plistPath], { stdio: 'inherit' });
758
- }
759
- logOk('Service started');
760
- }
761
-
762
- async function darwinStop() {
763
- const level = loadLevel() || 'user';
764
- const plistPath = darwinPlistPath(level);
765
- if (level === 'system') {
766
- execFileSync('sudo', ['launchctl', 'unload', '-w', plistPath], { stdio: 'inherit' });
767
- } else {
768
- execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'inherit' });
769
- }
770
- logOk('Service stopped');
771
- }
772
-
773
- async function darwinStatus() {
774
- try {
775
- const result = execFileSync('launchctl', ['list'], { encoding: 'utf-8' });
776
- const lines = result.split('\n').filter((line) => line.includes(LABEL));
777
- if (lines.length > 0) {
778
- log('Spaces service status:');
779
- lines.forEach((line) => log(line));
780
- } else {
781
- log('Spaces service is not loaded');
782
- }
783
- } catch {
784
- log('Spaces service is not loaded');
785
- }
786
- }
787
-
788
- async function darwinLogs() {
789
- ensureLogsDir();
790
- const outLogPath = path.join(LOGS_DIR, 'spaces.out.log');
791
- if (!fs.existsSync(outLogPath)) {
792
- logErr(`Log file not found: ${outLogPath}`);
793
- log('Service may not have started yet');
794
- process.exit(1);
795
- }
796
- spawnSync('tail', ['-f', outLogPath], { stdio: 'inherit' });
797
- }
798
-
799
- // ─── Windows (Task Scheduler) ────────────────────────────────
800
- function win32WrapperScript(level) {
801
- ensureLogsDir();
802
- const config = resolveConfig();
803
- const nodePath = resolveNodePath();
804
- const spacesPath = resolveSpacesPath();
805
- const outLog = path.join(LOGS_DIR, 'spaces.out.log');
806
- const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
807
-
808
- const lines = [
809
- '@echo off',
810
- ];
811
- // When running as SYSTEM, override USERPROFILE so os.homedir() resolves
812
- // to the target user's home directory (where .spaces/ config lives).
813
- if (level === 'system') {
814
- const homedir = path.dirname(SPACES_DIR); // use resolved target, not os.homedir()
815
- const drive = path.parse(homedir).root.slice(0, -1);
816
- const rest = homedir.slice(drive.length);
817
- lines.push(`set USERPROFILE=${homedir}`);
818
- lines.push(`set HOMEDRIVE=${drive}`);
819
- lines.push(`set HOMEPATH=${rest}`);
820
- }
821
- lines.push('set SPACES_SERVICE=1');
822
- lines.push(`set SPACES_PORT=${config.port}`);
823
- // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
824
- if (config.tier && config.tier !== 'community') {
825
- lines.push(`set SPACES_TIER=${config.tier}`);
826
- }
827
- if (config.basePath) {
828
- lines.push(`set SPACES_BASE_PATH=${config.basePath}`);
829
- }
830
- if (config.allowedOrigins) {
831
- lines.push(`set SPACES_ALLOWED_ORIGINS=${config.allowedOrigins}`);
832
- }
833
- lines.push(`"${nodePath}" "${spacesPath}" >> "${outLog}" 2>&1`);
834
- lines.push('');
835
-
836
- fs.writeFileSync(wrapperPath, lines.join('\r\n'));
837
- return wrapperPath;
838
- }
839
-
840
- async function win32Install() {
841
- const level = await promptLevel();
842
-
843
- // For system service, determine the target user's home directory
844
- // (may differ from the admin account running the installer)
845
- if (level === 'system' && process.platform === 'win32') {
846
- const targetHome = await promptTargetUser();
847
- setTargetHome(targetHome);
848
- }
849
-
850
- const wrapperPath = win32WrapperScript(level);
851
-
852
- log(`Wrapper script written to ${wrapperPath}`);
853
-
854
- // Delete existing task (ignore errors)
855
- try {
856
- execFileSync('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'pipe' });
857
- } catch {}
858
-
859
- if (level === 'system') {
860
- execFileSync('schtasks', ['/Create', '/TN', TASK_NAME, '/TR', `"${wrapperPath}"`, '/SC', 'ONSTART', '/RU', 'SYSTEM', '/F'], { stdio: 'inherit' });
861
- } else {
862
- execFileSync('schtasks', ['/Create', '/TN', TASK_NAME, '/TR', `"${wrapperPath}"`, '/SC', 'ONLOGON', '/RL', 'HIGHEST', '/F'], { stdio: 'inherit' });
863
- }
864
- logOk('Scheduled task created');
865
-
866
- saveLevel(level);
867
-
868
- // Set up SSH for multi-user support (system service only)
869
- // Only ensure OpenSSH is available — key generation is handled at runtime
870
- // by ensureServiceKeyAtRuntime() in terminal-server.js, which runs as SYSTEM
871
- // so the key is owned by SYSTEM and OpenSSH accepts it.
872
- if (level === 'system') {
873
- ensureOpenSSHServer();
874
- log('SSH service key will be generated on first run (as SYSTEM)');
875
- }
876
-
877
- execFileSync('schtasks', ['/Run', '/TN', TASK_NAME], { stdio: 'inherit' });
878
- logOk('Task started');
879
-
880
- logOk(`Spaces installed as ${level} service`);
881
- }
882
-
883
- async function win32Uninstall() {
884
- try {
885
- execFileSync('schtasks', ['/End', '/TN', TASK_NAME], { stdio: 'pipe' });
886
- logOk('Task ended');
887
- } catch {
888
- log('Task was not running');
889
- }
890
-
891
- try {
892
- execFileSync('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'pipe' });
893
- logOk('Scheduled task removed');
894
- } catch {
895
- log('Scheduled task was already removed');
896
- }
897
-
898
- try {
899
- const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
900
- fs.unlinkSync(wrapperPath);
901
- logOk('Wrapper script removed');
902
- } catch {
903
- log('Wrapper script was already removed');
904
- }
905
-
906
- try {
907
- fs.unlinkSync(LEVEL_PATH);
908
- } catch {}
909
-
910
- logOk('Spaces service uninstalled');
911
- }
912
-
913
- async function win32Start() {
914
- // Try schtasks /Run first (works when running as admin or for user-level tasks)
915
- try {
916
- execFileSync('schtasks', ['/Run', '/TN', TASK_NAME], { stdio: 'pipe' });
917
- logOk('Task started');
918
- return;
919
- } catch {}
920
-
921
- // Fallback: launch the wrapper script directly as a detached process.
922
- // This works for non-admin shells when the task is registered as SYSTEM.
923
- // The service will run as the current user, not SYSTEM, but functionally
924
- // equivalent for local development.
925
- const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
926
- if (!fs.existsSync(wrapperPath)) {
927
- logErr('Wrapper script not found — run "spaces service install" first');
928
- process.exit(1);
929
- }
930
-
931
- const { spawn } = require('child_process');
932
- const child = spawn('cmd.exe', ['/c', wrapperPath], {
933
- detached: true,
934
- stdio: 'ignore',
935
- windowsHide: true,
936
- });
937
- child.unref();
938
- logOk('Service started (direct launch)');
939
- }
940
-
941
- async function win32Stop() {
942
- try {
943
- execFileSync('schtasks', ['/End', '/TN', TASK_NAME], { stdio: 'pipe' });
944
- } catch {}
945
-
946
- // Kill the actual node processes on our ports (verify it's node before killing)
947
- const config = resolveConfig();
948
- const ports = [config.port || 3457, 3400];
949
- let killed = 0;
950
- for (const port of ports) {
951
- try {
952
- const output = execFileSync('netstat', ['-ano'], { encoding: 'utf-8' });
953
- for (const line of output.split(String.fromCharCode(10))) {
954
- if (line.includes(':' + port + ' ') && line.includes('LISTENING')) {
955
- const parts = line.trim().split(/\s+/);
956
- const pid = parseInt(parts[parts.length - 1], 10);
957
- if (pid > 0) {
958
- // Verify the process is node.exe before killing to avoid terminating unrelated processes
959
- try {
960
- const taskInfo = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf-8' });
961
- if (!taskInfo.toLowerCase().includes('node.exe')) continue;
962
- process.kill(pid, 'SIGTERM'); killed++;
963
- } catch {}
964
- }
965
- }
966
- }
967
- } catch {}
968
- }
969
-
970
- if (killed > 0) {
971
- logOk('Stopped ' + killed + ' process(es)');
972
- } else {
973
- logOk('Task stopped');
974
- }
975
- }
976
-
977
- async function win32Status() {
978
- try {
979
- execFileSync('schtasks', ['/Query', '/TN', TASK_NAME, '/V', '/FO', 'LIST'], { stdio: 'inherit' });
980
- } catch {
981
- log('Spaces service is not installed');
982
- }
983
- }
984
-
985
- async function win32Logs() {
986
- ensureLogsDir();
987
- const outLogPath = path.join(LOGS_DIR, 'spaces.out.log');
988
- if (!fs.existsSync(outLogPath)) {
989
- logErr(`Log file not found: ${outLogPath}`);
990
- log('Service may not have started yet');
991
- process.exit(1);
992
- }
993
- spawnSync('powershell', ['-Command', `Get-Content "${outLogPath}" -Wait -Tail 50`], { stdio: 'inherit' });
994
- }
995
-
996
- // ─── Dispatch table ──────────────────────────────────────────
997
- const platforms = {
998
- linux: { install: linuxInstall, uninstall: linuxUninstall, start: linuxStart, stop: linuxStop, status: linuxStatus, logs: linuxLogs },
999
- darwin: { install: darwinInstall, uninstall: darwinUninstall, start: darwinStart, stop: darwinStop, status: darwinStatus, logs: darwinLogs },
1000
- win32: { install: win32Install, uninstall: win32Uninstall, start: win32Start, stop: win32Stop, status: win32Status, logs: win32Logs },
1001
- };
1002
-
1003
- // ─── CLI ──────────────────────────────────────────────────────
1004
- async function main() {
1005
- const action = process.argv[2];
1006
- const platform = getPlatform();
1007
- const dispatch = platforms[platform];
1008
-
1009
- if (!action || !dispatch[action]) {
1010
- log('Usage: spaces service <install|uninstall|start|stop|status|logs>');
1011
- process.exit(action ? 1 : 0);
1012
- }
1013
-
1014
- await dispatch[action]();
1015
- }
1016
-
1017
- main().catch((err) => {
1018
- logErr(err.message);
1019
- process.exit(1);
1020
- });
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { execFileSync, spawnSync } = require('child_process');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const fs = require('fs');
9
+ const readline = require('readline');
10
+
11
+ let SPACES_DIR = path.join(os.homedir(), '.spaces');
12
+ let CONFIG_PATH = path.join(SPACES_DIR, 'server.json');
13
+ let LOGS_DIR = path.join(SPACES_DIR, 'logs');
14
+ const SERVICE_NAME = 'spaces';
15
+ const LABEL = 'com.agentspaces.spaces';
16
+ const TASK_NAME = 'Spaces';
17
+
18
+ // Override SPACES_DIR to point to a different user's home
19
+ function setTargetHome(homedir) {
20
+ SPACES_DIR = path.join(homedir, '.spaces');
21
+ CONFIG_PATH = path.join(SPACES_DIR, 'server.json');
22
+ LOGS_DIR = path.join(SPACES_DIR, 'logs');
23
+ }
24
+
25
+ // ─── Helpers ──────────────────────────────────────────────────
26
+ function log(msg) { console.log(` ${msg}`); }
27
+ function logOk(msg) { console.log(` ✓ ${msg}`); }
28
+ function logErr(msg) { console.error(` ✗ ${msg}`); }
29
+
30
+ function resolveConfig() {
31
+ let config = {};
32
+ if (fs.existsSync(CONFIG_PATH)) {
33
+ try { config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
34
+ }
35
+ return {
36
+ port: config.port || 3457,
37
+ tier: config.tier || 'community',
38
+ basePath: config.basePath || '',
39
+ allowedOrigins: config.allowedOrigins || '',
40
+ };
41
+ }
42
+
43
+ function resolveSpacesPath() {
44
+ return path.join(__dirname, 'spaces.js');
45
+ }
46
+
47
+ function resolveProjectDir() {
48
+ return path.join(__dirname, '..');
49
+ }
50
+
51
+ function resolveNodePath() {
52
+ return process.execPath;
53
+ }
54
+
55
+ function promptLevel() {
56
+ return new Promise((resolve) => {
57
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
58
+ console.log('');
59
+ log('Install as:');
60
+ log(' 1. System service (starts on boot, all users)');
61
+ log(' 2. User service (starts on login, current user only)');
62
+ console.log('');
63
+ rl.question(' Choice [2]: ', (answer) => {
64
+ rl.close();
65
+ const choice = answer.trim() || '2';
66
+ resolve(choice === '1' ? 'system' : 'user');
67
+ });
68
+ });
69
+ }
70
+
71
+ function ensureLogsDir() {
72
+ fs.mkdirSync(LOGS_DIR, { recursive: true });
73
+ }
74
+
75
+ // Detect Windows user profiles that have .claude/ (Claude Code data)
76
+ function findClaudeUsers() {
77
+ if (process.platform !== 'win32') return [];
78
+ const usersDir = path.dirname(os.homedir());
79
+ const skip = new Set(['Public', 'Default', 'Default User', 'All Users']);
80
+ try {
81
+ return fs.readdirSync(usersDir)
82
+ .filter(name => !skip.has(name) && !name.startsWith('.'))
83
+ .filter(name => {
84
+ const claudeDir = path.join(usersDir, name, '.claude');
85
+ return fs.existsSync(claudeDir);
86
+ })
87
+ .map(name => ({ name, homedir: path.join(usersDir, name) }));
88
+ } catch { return []; }
89
+ }
90
+
91
+ // Prompt for which user's home directory to target (system service only).
92
+ // Looks for users with .claude/ (Claude Code data) since that's what gets synced.
93
+ function promptTargetUser() {
94
+ return new Promise((resolve) => {
95
+ const currentHome = os.homedir();
96
+ const users = findClaudeUsers();
97
+
98
+ // No Claude users found — fall back to current user
99
+ if (users.length === 0) {
100
+ resolve(currentHome);
101
+ return;
102
+ }
103
+ // Single Claude user — auto-select
104
+ if (users.length === 1) {
105
+ log(`Using home directory: ${users[0].homedir}`);
106
+ resolve(users[0].homedir);
107
+ return;
108
+ }
109
+
110
+ // Multiple Claude users — ask which one
111
+ console.log('');
112
+ log('Multiple users have Claude Code data:');
113
+ users.forEach((u, i) => log(` ${i + 1}. ${u.name} (${u.homedir})`));
114
+ console.log('');
115
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
116
+ rl.question(` Run as user [1]: `, (answer) => {
117
+ rl.close();
118
+ const idx = parseInt(answer.trim() || '1', 10) - 1;
119
+ const chosen = users[idx] || users[0];
120
+ resolve(chosen.homedir);
121
+ });
122
+ });
123
+ }
124
+
125
+
126
+ // --- SSH AuthorizedKeysCommand setup (Linux/macOS) ---
127
+ // Configures sshd to dynamically authorize Spaces users via a lookup script,
128
+ // eliminating the need to manually manage ~/.ssh/authorized_keys per user.
129
+ function configureAuthorizedKeysCommand() {
130
+ if (process.platform === 'win32') return; // Windows uses a different approach
131
+
132
+ const scriptSrc = path.join(__dirname, 'ssh-auth-keys.sh');
133
+ const scriptDst = '/opt/spaces/bin/ssh-auth-keys.sh';
134
+ const sshdConfig = '/etc/ssh/sshd_config';
135
+ const spacesConfDrop = '/etc/ssh/sshd_config.d/spaces.conf';
136
+
137
+ // Install the script to a stable system path
138
+ try {
139
+ spawnSync('sudo', ['mkdir', '-p', '/opt/spaces/bin'], { stdio: 'pipe', timeout: 5000 });
140
+ spawnSync('sudo', ['cp', scriptSrc, scriptDst], { stdio: 'pipe', timeout: 5000 });
141
+ spawnSync('sudo', ['chmod', '755', scriptDst], { stdio: 'pipe', timeout: 5000 });
142
+ spawnSync('sudo', ['chown', 'root:root', scriptDst], { stdio: 'pipe', timeout: 5000 });
143
+ logOk('Installed ssh-auth-keys.sh to ' + scriptDst);
144
+ } catch (e) {
145
+ logErr('Failed to install ssh-auth-keys.sh: ' + e.message);
146
+ return;
147
+ }
148
+
149
+ // Prefer sshd_config.d drop-in if the Include directive exists
150
+ const sshdContent = fs.existsSync(sshdConfig)
151
+ ? fs.readFileSync(sshdConfig, 'utf-8') : '';
152
+
153
+ const configLines = [
154
+ '# Spaces: dynamically authorize shell users via admin DB lookup',
155
+ 'AuthorizedKeysCommand ' + scriptDst + ' %u',
156
+ 'AuthorizedKeysCommandUser nobody',
157
+ ].join('\n') + '\n';
158
+
159
+ // Already configured?
160
+ if (sshdContent.includes('ssh-auth-keys.sh')) {
161
+ logOk('AuthorizedKeysCommand already configured in sshd_config');
162
+ return;
163
+ }
164
+
165
+ // Try drop-in directory first (cleaner, doesn't touch main config)
166
+ const hasIncludeDir = sshdContent.includes('Include /etc/ssh/sshd_config.d/');
167
+ if (hasIncludeDir) {
168
+ try {
169
+ spawnSync('sudo', ['mkdir', '-p', '/etc/ssh/sshd_config.d'], { stdio: 'pipe', timeout: 5000 });
170
+ const result = spawnSync('sudo', ['tee', spacesConfDrop], {
171
+ input: configLines, stdio: ['pipe', 'pipe', 'inherit'], timeout: 5000,
172
+ });
173
+ if (result.status === 0) {
174
+ logOk('Created ' + spacesConfDrop);
175
+ }
176
+ } catch (e) {
177
+ logErr('Failed to write drop-in config: ' + e.message);
178
+ }
179
+ } else {
180
+ // Append directly to sshd_config
181
+ try {
182
+ const appendContent = '\n# Spaces SSH authorization\n' + configLines;
183
+ const result = spawnSync('sudo', ['tee', '-a', sshdConfig], {
184
+ input: appendContent, stdio: ['pipe', 'pipe', 'inherit'], timeout: 5000,
185
+ });
186
+ if (result.status === 0) {
187
+ logOk('Added AuthorizedKeysCommand to ' + sshdConfig);
188
+ }
189
+ } catch (e) {
190
+ logErr('Failed to update sshd_config: ' + e.message);
191
+ }
192
+ }
193
+
194
+ // Restart sshd to pick up the change
195
+ try {
196
+ spawnSync('sudo', ['systemctl', 'restart', 'sshd'], { stdio: 'pipe', timeout: 10000 });
197
+ logOk('Restarted sshd');
198
+ } catch {
199
+ try {
200
+ spawnSync('sudo', ['systemctl', 'restart', 'ssh'], { stdio: 'pipe', timeout: 10000 });
201
+ logOk('Restarted ssh');
202
+ } catch {
203
+ log('Warning: could not restart sshd — restart it manually');
204
+ }
205
+ }
206
+ }
207
+
208
+ // --- SSH key provisioning (for multi-user system service) ---
209
+ function checkOpenSSHServer() {
210
+ if (process.platform !== 'win32') return true;
211
+ try {
212
+ const result = spawnSync('powershell', ['-NoProfile', '-Command',
213
+ 'Get-Service sshd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Status'
214
+ ], { encoding: 'utf-8', timeout: 10000 });
215
+ const status = (result.stdout || '').trim();
216
+ return status === 'Running' || status === 'Stopped';
217
+ } catch { return false; }
218
+ }
219
+
220
+ function ensureOpenSSHServer() {
221
+ if (process.platform !== 'win32') return;
222
+ if (checkOpenSSHServer()) {
223
+ try {
224
+ spawnSync('powershell', ['-NoProfile', '-Command',
225
+ 'Start-Service sshd; Set-Service -Name sshd -StartupType Automatic'
226
+ ], { stdio: 'pipe', timeout: 15000 });
227
+ logOk('OpenSSH Server started and set to automatic');
228
+ } catch {
229
+ log('Warning: could not start OpenSSH Server');
230
+ }
231
+ return;
232
+ }
233
+ log('Installing OpenSSH Server (required for multi-user terminals)...');
234
+ try {
235
+ const result = spawnSync('powershell', ['-NoProfile', '-Command',
236
+ 'Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0'
237
+ ], { encoding: 'utf-8', stdio: 'pipe', timeout: 120000 });
238
+ if (result.status === 0) {
239
+ logOk('OpenSSH Server installed');
240
+ spawnSync('powershell', ['-NoProfile', '-Command',
241
+ 'Start-Service sshd; Set-Service -Name sshd -StartupType Automatic'
242
+ ], { stdio: 'pipe', timeout: 15000 });
243
+ logOk('OpenSSH Server started');
244
+ } else {
245
+ logErr('Failed to install OpenSSH Server: ' + (result.stderr || '').trim());
246
+ log('Multi-user terminals will not work. Install manually:');
247
+ log(' Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0');
248
+ }
249
+ } catch (e) {
250
+ logErr('Failed to install OpenSSH Server: ' + e.message);
251
+ }
252
+ }
253
+
254
+ function ensureServiceKey() {
255
+ const keyPath = path.join(SPACES_DIR, 'service_key');
256
+ if (fs.existsSync(keyPath)) return keyPath;
257
+ log('Generating SSH service key...');
258
+ fs.mkdirSync(SPACES_DIR, { recursive: true });
259
+ const result = spawnSync('ssh-keygen', [
260
+ '-t', 'ed25519',
261
+ '-f', keyPath,
262
+ '-N', '',
263
+ '-C', 'spaces-service-key',
264
+ ], { stdio: 'pipe', timeout: 10000 });
265
+ if (result.status !== 0) {
266
+ logErr('Failed to generate SSH key');
267
+ return null;
268
+ }
269
+
270
+ // On Windows, restrict private key permissions so OpenSSH accepts it.
271
+ // OpenSSH requires: no inherited ACLs, only the file owner + SYSTEM may have access.
272
+ if (process.platform === 'win32') {
273
+ try {
274
+ const currentUser = os.userInfo().username;
275
+ // Remove inheritance and all default ACLs, then grant only owner + SYSTEM
276
+ spawnSync('icacls', [keyPath, '/inheritance:r',
277
+ '/remove', 'BUILTIN\\Administrators',
278
+ '/remove', 'BUILTIN\\Users',
279
+ '/remove', 'Everyone',
280
+ '/grant:r', currentUser + ':(F)',
281
+ '/grant', 'NT AUTHORITY\\SYSTEM:(F)'], { stdio: 'pipe', timeout: 5000 });
282
+ } catch {}
283
+ }
284
+
285
+ logOk('SSH service key generated');
286
+ return keyPath;
287
+ }
288
+
289
+ function authorizeServiceKey(keyPath, targetUser) {
290
+ const pubKey = fs.readFileSync(keyPath + '.pub', 'utf-8').trim();
291
+
292
+ if (process.platform === 'win32') {
293
+ // Windows OpenSSH ignores ~/.ssh/authorized_keys for admin users.
294
+ // Must use C:\ProgramData\ssh\administrators_authorized_keys instead.
295
+ const adminAuthKeys = path.join(process.env.ProgramData || 'C:\\ProgramData', 'ssh', 'administrators_authorized_keys');
296
+ const userAuthKeys = path.join(path.dirname(os.homedir()), targetUser, '.ssh', 'authorized_keys');
297
+
298
+ // Check if user is an administrator (exact match, not substring)
299
+ let isAdmin = false;
300
+ try {
301
+ const result = spawnSync('net', ['localgroup', 'Administrators'], { encoding: 'utf-8', timeout: 5000 });
302
+ const lines = (result.stdout || '').split(/\r?\n/);
303
+ const sep = lines.findIndex(l => l.trim().startsWith('---'));
304
+ if (sep >= 0) {
305
+ const members = lines.slice(sep + 1).map(l => l.trim()).filter(Boolean);
306
+ isAdmin = members.some(m => m.toLowerCase() === targetUser.toLowerCase());
307
+ }
308
+ } catch {}
309
+
310
+ const authKeysPath = isAdmin ? adminAuthKeys : userAuthKeys;
311
+ const authDir = path.dirname(authKeysPath);
312
+ if (!fs.existsSync(authDir)) fs.mkdirSync(authDir, { recursive: true });
313
+
314
+ // Check if key already authorized
315
+ if (fs.existsSync(authKeysPath)) {
316
+ const existing = fs.readFileSync(authKeysPath, 'utf-8');
317
+ if (existing.includes(pubKey)) return;
318
+ }
319
+ fs.appendFileSync(authKeysPath, pubKey + String.fromCharCode(10));
320
+
321
+ // Fix permissions for administrators_authorized_keys
322
+ if (isAdmin) {
323
+ try {
324
+ spawnSync('icacls', [authKeysPath, '/inheritance:r', '/grant', 'SYSTEM:(R)', '/grant', 'Administrators:(R)'], { stdio: 'pipe', timeout: 5000 });
325
+ } catch {}
326
+ }
327
+ } else {
328
+ // Linux/macOS: use ~/.ssh/authorized_keys
329
+ let userHome;
330
+ try {
331
+ // Use getent to resolve the actual home directory (works with LDAP, NIS, etc.)
332
+ const result = spawnSync('getent', ['passwd', targetUser], { encoding: 'utf-8', timeout: 5000 });
333
+ const fields = (result.stdout || '').split(':');
334
+ userHome = fields[5] || (process.platform === 'darwin' ? `/Users/${targetUser}` : `/home/${targetUser}`);
335
+ } catch {
336
+ userHome = process.platform === 'darwin' ? `/Users/${targetUser}` : `/home/${targetUser}`;
337
+ }
338
+ const sshDir = path.join(userHome, '.ssh');
339
+ const authKeysPath = path.join(sshDir, 'authorized_keys');
340
+ if (!fs.existsSync(sshDir)) fs.mkdirSync(sshDir, { recursive: true, mode: 0o700 });
341
+ if (fs.existsSync(authKeysPath)) {
342
+ const existing = fs.readFileSync(authKeysPath, 'utf-8');
343
+ if (existing.includes(pubKey)) return;
344
+ }
345
+ fs.appendFileSync(authKeysPath, pubKey + String.fromCharCode(10));
346
+ }
347
+ logOk('SSH key authorized for ' + targetUser);
348
+ }
349
+
350
+ const LEVEL_PATH = path.join(SPACES_DIR, 'service-level');
351
+
352
+ function saveLevel(level) {
353
+ fs.mkdirSync(SPACES_DIR, { recursive: true });
354
+ fs.writeFileSync(LEVEL_PATH, level);
355
+ }
356
+
357
+ function loadLevel() {
358
+ try { return fs.readFileSync(LEVEL_PATH, 'utf-8').trim(); } catch { return null; }
359
+ }
360
+
361
+ // ─── Platform detection ───────────────────────────────────────
362
+ function getPlatform() {
363
+ switch (process.platform) {
364
+ case 'linux': return 'linux';
365
+ case 'darwin': return 'darwin';
366
+ case 'win32': return 'win32';
367
+ default:
368
+ logErr(`Unsupported platform: ${process.platform}`);
369
+ process.exit(1);
370
+ }
371
+ }
372
+
373
+ // ─── Linux (systemd) ─────────────────────────────────────────
374
+ function linuxServicePath(level) {
375
+ if (level === 'system') {
376
+ return `/etc/systemd/system/${SERVICE_NAME}.service`;
377
+ }
378
+ const userDir = path.join(os.homedir(), '.config', 'systemd', 'user');
379
+ fs.mkdirSync(userDir, { recursive: true });
380
+ return path.join(userDir, `${SERVICE_NAME}.service`);
381
+ }
382
+
383
+ function linuxUnitFile(level) {
384
+ const config = resolveConfig();
385
+ const nodePath = resolveNodePath();
386
+ const spacesPath = resolveSpacesPath();
387
+ const projectDir = resolveProjectDir();
388
+
389
+ let envLines = [
390
+ `Environment=SPACES_SERVICE=1`,
391
+ `Environment=SPACES_PORT=${config.port}`,
392
+ ];
393
+ // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
394
+ if (config.tier && config.tier !== 'community') {
395
+ envLines.push(`Environment=SPACES_TIER=${config.tier}`);
396
+ }
397
+ if (config.basePath) {
398
+ envLines.push(`Environment=SPACES_BASE_PATH=${config.basePath}`);
399
+ }
400
+ if (config.allowedOrigins) {
401
+ envLines.push(`Environment=SPACES_ALLOWED_ORIGINS=${config.allowedOrigins}`);
402
+ }
403
+
404
+ // Allow users to set API keys (OPENAI_API_KEY, etc.) in ~/.spaces/env
405
+ const envFilePath = path.join(SPACES_DIR, 'env');
406
+
407
+ let serviceSection = [
408
+ 'Type=simple',
409
+ `ExecStart=${nodePath} ${spacesPath}`,
410
+ `WorkingDirectory=${projectDir}`,
411
+ `EnvironmentFile=-${envFilePath}`,
412
+ ...envLines,
413
+ 'Restart=on-failure',
414
+ 'RestartSec=5',
415
+ ];
416
+
417
+ if (level === 'system') {
418
+ const username = os.userInfo().username;
419
+ serviceSection.push(`User=${username}`);
420
+ serviceSection.push(`Group=${username}`);
421
+ }
422
+
423
+ const wantedBy = level === 'system' ? 'multi-user.target' : 'default.target';
424
+
425
+ return [
426
+ '[Unit]',
427
+ 'Description=Spaces - Agent Workspace Manager',
428
+ 'After=network.target',
429
+ '',
430
+ '[Service]',
431
+ ...serviceSection,
432
+ '',
433
+ '[Install]',
434
+ `WantedBy=${wantedBy}`,
435
+ '',
436
+ ].join('\n');
437
+ }
438
+
439
+ function linuxSystemctl(level, ...args) {
440
+ if (level === 'system') {
441
+ execFileSync('sudo', ['systemctl', ...args], { stdio: 'inherit' });
442
+ } else {
443
+ // User-level systemctl needs XDG_RUNTIME_DIR to connect to the user bus.
444
+ // Always set it from the real UID — inherited env may have a stale value
445
+ // (e.g. from a prior session with a different UID mapping).
446
+ const uid = process.getuid();
447
+ const env = {
448
+ ...process.env,
449
+ XDG_RUNTIME_DIR: `/run/user/${uid}`,
450
+ DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
451
+ };
452
+ execFileSync('systemctl', ['--user', ...args], { stdio: 'inherit', env });
453
+ }
454
+ }
455
+
456
+ async function linuxInstall() {
457
+ const level = await promptLevel();
458
+ const unitContent = linuxUnitFile(level);
459
+ const servicePath = linuxServicePath(level);
460
+
461
+ log(`Writing unit file to ${servicePath}`);
462
+ if (level === 'system') {
463
+ const result = spawnSync('sudo', ['tee', servicePath], {
464
+ input: unitContent,
465
+ stdio: ['pipe', 'pipe', 'inherit'],
466
+ });
467
+ if (result.status !== 0) {
468
+ logErr('Failed to write unit file');
469
+ process.exit(1);
470
+ }
471
+ } else {
472
+ fs.writeFileSync(servicePath, unitContent);
473
+ }
474
+ logOk('Unit file written');
475
+
476
+ saveLevel(level);
477
+
478
+ linuxSystemctl(level, 'daemon-reload');
479
+ logOk('Reloaded systemd daemon');
480
+
481
+ linuxSystemctl(level, 'enable', `${SERVICE_NAME}.service`);
482
+ logOk('Service enabled');
483
+
484
+ linuxSystemctl(level, 'start', `${SERVICE_NAME}.service`);
485
+ logOk('Service started');
486
+
487
+ if (level === 'user') {
488
+ try {
489
+ const uid = process.getuid();
490
+ const env = {
491
+ ...process.env,
492
+ XDG_RUNTIME_DIR: `/run/user/${uid}`,
493
+ DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
494
+ };
495
+ execFileSync('loginctl', ['enable-linger', os.userInfo().username], { stdio: 'inherit', env });
496
+ logOk('Enabled login lingering for user service');
497
+ } catch {
498
+ log('Warning: could not enable-linger (user service may not start on boot)');
499
+ }
500
+ }
501
+
502
+ // Set up SSH AuthorizedKeysCommand for seamless multi-user terminal access
503
+ if (level === 'system') {
504
+ configureAuthorizedKeysCommand();
505
+ const keyPath = ensureServiceKey();
506
+ if (keyPath) {
507
+ authorizeServiceKey(keyPath, os.userInfo().username);
508
+ }
509
+ }
510
+
511
+ logOk(`Spaces installed as ${level} service`);
512
+ }
513
+
514
+ async function linuxUninstall() {
515
+ const level = loadLevel() || 'user';
516
+ const servicePath = linuxServicePath(level);
517
+
518
+ try {
519
+ linuxSystemctl(level, 'stop', `${SERVICE_NAME}.service`);
520
+ logOk('Service stopped');
521
+ } catch {
522
+ log('Service was not running');
523
+ }
524
+
525
+ try {
526
+ linuxSystemctl(level, 'disable', `${SERVICE_NAME}.service`);
527
+ logOk('Service disabled');
528
+ } catch {
529
+ log('Service was not enabled');
530
+ }
531
+
532
+ try {
533
+ if (level === 'system') {
534
+ execFileSync('sudo', ['rm', '-f', servicePath], { stdio: 'inherit' });
535
+ } else {
536
+ fs.unlinkSync(servicePath);
537
+ }
538
+ logOk('Unit file removed');
539
+ } catch {
540
+ log('Unit file was already removed');
541
+ }
542
+
543
+ try {
544
+ linuxSystemctl(level, 'daemon-reload');
545
+ logOk('Reloaded systemd daemon');
546
+ } catch {
547
+ log('Warning: daemon-reload failed');
548
+ }
549
+
550
+ try {
551
+ fs.unlinkSync(LEVEL_PATH);
552
+ } catch {}
553
+
554
+ logOk('Spaces service uninstalled');
555
+ }
556
+
557
+ async function linuxStart() {
558
+ const level = loadLevel() || 'user';
559
+ linuxSystemctl(level, 'start', `${SERVICE_NAME}.service`);
560
+ logOk('Service started');
561
+ }
562
+
563
+ async function linuxStop() {
564
+ const level = loadLevel() || 'user';
565
+ linuxSystemctl(level, 'stop', `${SERVICE_NAME}.service`);
566
+ logOk('Service stopped');
567
+ }
568
+
569
+ async function linuxStatus() {
570
+ const level = loadLevel() || 'user';
571
+ try {
572
+ linuxSystemctl(level, 'status', `${SERVICE_NAME}.service`);
573
+ } catch {
574
+ // systemctl status returns non-zero for stopped/failed services
575
+ }
576
+ }
577
+
578
+ async function linuxLogs() {
579
+ const level = loadLevel() || 'user';
580
+ if (level === 'user') {
581
+ const uid = process.getuid();
582
+ const env = {
583
+ ...process.env,
584
+ XDG_RUNTIME_DIR: `/run/user/${uid}`,
585
+ DBUS_SESSION_BUS_ADDRESS: `unix:path=/run/user/${uid}/bus`,
586
+ };
587
+ spawnSync('journalctl', ['--user', '-u', SERVICE_NAME, '-f', '--no-pager'], { stdio: 'inherit', env });
588
+ } else {
589
+ spawnSync('sudo', ['journalctl', '-u', SERVICE_NAME, '-f', '--no-pager'], { stdio: 'inherit' });
590
+ }
591
+ }
592
+
593
+ // ─── macOS (launchd) ─────────────────────────────────────────
594
+ function darwinPlistPath(level) {
595
+ if (level === 'system') {
596
+ return `/Library/LaunchDaemons/${LABEL}.plist`;
597
+ }
598
+ return path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
599
+ }
600
+
601
+ function darwinPlistContent(level) {
602
+ ensureLogsDir();
603
+ const config = resolveConfig();
604
+ const nodePath = resolveNodePath();
605
+ const spacesPath = resolveSpacesPath();
606
+ const projectDir = resolveProjectDir();
607
+ const outLog = path.join(LOGS_DIR, 'spaces.out.log');
608
+ const errLog = path.join(LOGS_DIR, 'spaces.err.log');
609
+
610
+ let envEntries = [
611
+ ` <key>SPACES_SERVICE</key>`,
612
+ ` <string>1</string>`,
613
+ ` <key>SPACES_PORT</key>`,
614
+ ` <string>${config.port}</string>`,
615
+ ];
616
+ // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
617
+ if (config.tier && config.tier !== 'community') {
618
+ envEntries.push(` <key>SPACES_TIER</key>`);
619
+ envEntries.push(` <string>${config.tier}</string>`);
620
+ }
621
+ if (config.basePath) {
622
+ envEntries.push(` <key>SPACES_BASE_PATH</key>`);
623
+ envEntries.push(` <string>${config.basePath}</string>`);
624
+ }
625
+ if (config.allowedOrigins) {
626
+ envEntries.push(` <key>SPACES_ALLOWED_ORIGINS</key>`);
627
+ envEntries.push(` <string>${config.allowedOrigins}</string>`);
628
+ }
629
+
630
+ let extraKeys = '';
631
+ if (level === 'system') {
632
+ extraKeys = ` <key>UserName</key>\n <string>${os.userInfo().username}</string>\n`;
633
+ }
634
+
635
+ return [
636
+ '<?xml version="1.0" encoding="UTF-8"?>',
637
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
638
+ '<plist version="1.0">',
639
+ '<dict>',
640
+ ` <key>Label</key>`,
641
+ ` <string>${LABEL}</string>`,
642
+ ` <key>ProgramArguments</key>`,
643
+ ` <array>`,
644
+ ` <string>${nodePath}</string>`,
645
+ ` <string>${spacesPath}</string>`,
646
+ ` </array>`,
647
+ ` <key>WorkingDirectory</key>`,
648
+ ` <string>${projectDir}</string>`,
649
+ ` <key>EnvironmentVariables</key>`,
650
+ ` <dict>`,
651
+ ...envEntries,
652
+ ` </dict>`,
653
+ ` <key>RunAtLoad</key>`,
654
+ ` <true/>`,
655
+ ` <key>KeepAlive</key>`,
656
+ ` <true/>`,
657
+ ` <key>StandardOutPath</key>`,
658
+ ` <string>${outLog}</string>`,
659
+ ` <key>StandardErrorPath</key>`,
660
+ ` <string>${errLog}</string>`,
661
+ extraKeys ? extraKeys.trimEnd() : null,
662
+ '</dict>',
663
+ '</plist>',
664
+ '',
665
+ ].filter((line) => line !== null).join('\n');
666
+ }
667
+
668
+ async function darwinInstall() {
669
+ const level = await promptLevel();
670
+ const plistPath = darwinPlistPath(level);
671
+ const plistContent = darwinPlistContent(level);
672
+
673
+ // Unload existing (ignore errors)
674
+ try {
675
+ execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'pipe' });
676
+ } catch {}
677
+
678
+ log(`Writing plist to ${plistPath}`);
679
+ if (level === 'system') {
680
+ const result = spawnSync('sudo', ['tee', plistPath], {
681
+ input: plistContent,
682
+ stdio: ['pipe', 'pipe', 'inherit'],
683
+ });
684
+ if (result.status !== 0) {
685
+ logErr('Failed to write plist file');
686
+ process.exit(1);
687
+ }
688
+ execFileSync('sudo', ['chown', 'root:wheel', plistPath], { stdio: 'inherit' });
689
+ execFileSync('sudo', ['chmod', '644', plistPath], { stdio: 'inherit' });
690
+ } else {
691
+ const agentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
692
+ fs.mkdirSync(agentsDir, { recursive: true });
693
+ fs.writeFileSync(plistPath, plistContent);
694
+ }
695
+ logOk('Plist file written');
696
+
697
+ saveLevel(level);
698
+
699
+ if (level === 'system') {
700
+ execFileSync('sudo', ['launchctl', 'load', '-w', plistPath], { stdio: 'inherit' });
701
+ } else {
702
+ execFileSync('launchctl', ['load', '-w', plistPath], { stdio: 'inherit' });
703
+ }
704
+ logOk('Service loaded');
705
+
706
+ // Set up SSH AuthorizedKeysCommand for seamless multi-user terminal access
707
+ if (level === 'system') {
708
+ configureAuthorizedKeysCommand();
709
+ const keyPath = ensureServiceKey();
710
+ if (keyPath) {
711
+ authorizeServiceKey(keyPath, os.userInfo().username);
712
+ }
713
+ }
714
+
715
+ logOk(`Spaces installed as ${level} service`);
716
+ }
717
+
718
+ async function darwinUninstall() {
719
+ const level = loadLevel() || 'user';
720
+ const plistPath = darwinPlistPath(level);
721
+
722
+ try {
723
+ if (level === 'system') {
724
+ execFileSync('sudo', ['launchctl', 'unload', '-w', plistPath], { stdio: 'inherit' });
725
+ } else {
726
+ execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'inherit' });
727
+ }
728
+ logOk('Service unloaded');
729
+ } catch {
730
+ log('Service was not loaded');
731
+ }
732
+
733
+ try {
734
+ if (level === 'system') {
735
+ execFileSync('sudo', ['rm', '-f', plistPath], { stdio: 'inherit' });
736
+ } else {
737
+ fs.unlinkSync(plistPath);
738
+ }
739
+ logOk('Plist file removed');
740
+ } catch {
741
+ log('Plist file was already removed');
742
+ }
743
+
744
+ try {
745
+ fs.unlinkSync(LEVEL_PATH);
746
+ } catch {}
747
+
748
+ logOk('Spaces service uninstalled');
749
+ }
750
+
751
+ async function darwinStart() {
752
+ const level = loadLevel() || 'user';
753
+ const plistPath = darwinPlistPath(level);
754
+ if (level === 'system') {
755
+ execFileSync('sudo', ['launchctl', 'load', '-w', plistPath], { stdio: 'inherit' });
756
+ } else {
757
+ execFileSync('launchctl', ['load', '-w', plistPath], { stdio: 'inherit' });
758
+ }
759
+ logOk('Service started');
760
+ }
761
+
762
+ async function darwinStop() {
763
+ const level = loadLevel() || 'user';
764
+ const plistPath = darwinPlistPath(level);
765
+ if (level === 'system') {
766
+ execFileSync('sudo', ['launchctl', 'unload', '-w', plistPath], { stdio: 'inherit' });
767
+ } else {
768
+ execFileSync('launchctl', ['unload', '-w', plistPath], { stdio: 'inherit' });
769
+ }
770
+ logOk('Service stopped');
771
+ }
772
+
773
+ async function darwinStatus() {
774
+ try {
775
+ const result = execFileSync('launchctl', ['list'], { encoding: 'utf-8' });
776
+ const lines = result.split('\n').filter((line) => line.includes(LABEL));
777
+ if (lines.length > 0) {
778
+ log('Spaces service status:');
779
+ lines.forEach((line) => log(line));
780
+ } else {
781
+ log('Spaces service is not loaded');
782
+ }
783
+ } catch {
784
+ log('Spaces service is not loaded');
785
+ }
786
+ }
787
+
788
+ async function darwinLogs() {
789
+ ensureLogsDir();
790
+ const outLogPath = path.join(LOGS_DIR, 'spaces.out.log');
791
+ if (!fs.existsSync(outLogPath)) {
792
+ logErr(`Log file not found: ${outLogPath}`);
793
+ log('Service may not have started yet');
794
+ process.exit(1);
795
+ }
796
+ spawnSync('tail', ['-f', outLogPath], { stdio: 'inherit' });
797
+ }
798
+
799
+ // ─── Windows (Task Scheduler) ────────────────────────────────
800
+ function win32WrapperScript(level) {
801
+ ensureLogsDir();
802
+ const config = resolveConfig();
803
+ const nodePath = resolveNodePath();
804
+ const spacesPath = resolveSpacesPath();
805
+ const outLog = path.join(LOGS_DIR, 'spaces.out.log');
806
+ const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
807
+
808
+ const lines = [
809
+ '@echo off',
810
+ ];
811
+ // When running as SYSTEM, override USERPROFILE so os.homedir() resolves
812
+ // to the target user's home directory (where .spaces/ config lives).
813
+ if (level === 'system') {
814
+ const homedir = path.dirname(SPACES_DIR); // use resolved target, not os.homedir()
815
+ const drive = path.parse(homedir).root.slice(0, -1);
816
+ const rest = homedir.slice(drive.length);
817
+ lines.push(`set USERPROFILE=${homedir}`);
818
+ lines.push(`set HOMEDRIVE=${drive}`);
819
+ lines.push(`set HOMEPATH=${rest}`);
820
+ }
821
+ lines.push('set SPACES_SERVICE=1');
822
+ lines.push(`set SPACES_PORT=${config.port}`);
823
+ // Don't hardcode SPACES_TIER — let spaces.js auto-detect from installed packages
824
+ if (config.tier && config.tier !== 'community') {
825
+ lines.push(`set SPACES_TIER=${config.tier}`);
826
+ }
827
+ if (config.basePath) {
828
+ lines.push(`set SPACES_BASE_PATH=${config.basePath}`);
829
+ }
830
+ if (config.allowedOrigins) {
831
+ lines.push(`set SPACES_ALLOWED_ORIGINS=${config.allowedOrigins}`);
832
+ }
833
+ lines.push(`"${nodePath}" "${spacesPath}" >> "${outLog}" 2>&1`);
834
+ lines.push('');
835
+
836
+ fs.writeFileSync(wrapperPath, lines.join('\r\n'));
837
+ return wrapperPath;
838
+ }
839
+
840
+ async function win32Install() {
841
+ const level = await promptLevel();
842
+
843
+ // For system service, determine the target user's home directory
844
+ // (may differ from the admin account running the installer)
845
+ if (level === 'system' && process.platform === 'win32') {
846
+ const targetHome = await promptTargetUser();
847
+ setTargetHome(targetHome);
848
+ }
849
+
850
+ const wrapperPath = win32WrapperScript(level);
851
+
852
+ log(`Wrapper script written to ${wrapperPath}`);
853
+
854
+ // Delete existing task (ignore errors)
855
+ try {
856
+ execFileSync('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'pipe' });
857
+ } catch {}
858
+
859
+ if (level === 'system') {
860
+ execFileSync('schtasks', ['/Create', '/TN', TASK_NAME, '/TR', `"${wrapperPath}"`, '/SC', 'ONSTART', '/RU', 'SYSTEM', '/F'], { stdio: 'inherit' });
861
+ } else {
862
+ execFileSync('schtasks', ['/Create', '/TN', TASK_NAME, '/TR', `"${wrapperPath}"`, '/SC', 'ONLOGON', '/RL', 'HIGHEST', '/F'], { stdio: 'inherit' });
863
+ }
864
+ logOk('Scheduled task created');
865
+
866
+ saveLevel(level);
867
+
868
+ // Set up SSH for multi-user support (system service only)
869
+ // Only ensure OpenSSH is available — key generation is handled at runtime
870
+ // by ensureServiceKeyAtRuntime() in terminal-server.js, which runs as SYSTEM
871
+ // so the key is owned by SYSTEM and OpenSSH accepts it.
872
+ if (level === 'system') {
873
+ ensureOpenSSHServer();
874
+ log('SSH service key will be generated on first run (as SYSTEM)');
875
+ }
876
+
877
+ execFileSync('schtasks', ['/Run', '/TN', TASK_NAME], { stdio: 'inherit' });
878
+ logOk('Task started');
879
+
880
+ logOk(`Spaces installed as ${level} service`);
881
+ }
882
+
883
+ async function win32Uninstall() {
884
+ try {
885
+ execFileSync('schtasks', ['/End', '/TN', TASK_NAME], { stdio: 'pipe' });
886
+ logOk('Task ended');
887
+ } catch {
888
+ log('Task was not running');
889
+ }
890
+
891
+ try {
892
+ execFileSync('schtasks', ['/Delete', '/TN', TASK_NAME, '/F'], { stdio: 'pipe' });
893
+ logOk('Scheduled task removed');
894
+ } catch {
895
+ log('Scheduled task was already removed');
896
+ }
897
+
898
+ try {
899
+ const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
900
+ fs.unlinkSync(wrapperPath);
901
+ logOk('Wrapper script removed');
902
+ } catch {
903
+ log('Wrapper script was already removed');
904
+ }
905
+
906
+ try {
907
+ fs.unlinkSync(LEVEL_PATH);
908
+ } catch {}
909
+
910
+ logOk('Spaces service uninstalled');
911
+ }
912
+
913
+ async function win32Start() {
914
+ // Try schtasks /Run first (works when running as admin or for user-level tasks)
915
+ try {
916
+ execFileSync('schtasks', ['/Run', '/TN', TASK_NAME], { stdio: 'pipe' });
917
+ logOk('Task started');
918
+ return;
919
+ } catch {}
920
+
921
+ // Fallback: launch the wrapper script directly as a detached process.
922
+ // This works for non-admin shells when the task is registered as SYSTEM.
923
+ // The service will run as the current user, not SYSTEM, but functionally
924
+ // equivalent for local development.
925
+ const wrapperPath = path.join(SPACES_DIR, 'spaces-service.cmd');
926
+ if (!fs.existsSync(wrapperPath)) {
927
+ logErr('Wrapper script not found — run "spaces service install" first');
928
+ process.exit(1);
929
+ }
930
+
931
+ const { spawn } = require('child_process');
932
+ const child = spawn('cmd.exe', ['/c', wrapperPath], {
933
+ detached: true,
934
+ stdio: 'ignore',
935
+ windowsHide: true,
936
+ });
937
+ child.unref();
938
+ logOk('Service started (direct launch)');
939
+ }
940
+
941
+ async function win32Stop() {
942
+ try {
943
+ execFileSync('schtasks', ['/End', '/TN', TASK_NAME], { stdio: 'pipe' });
944
+ } catch {}
945
+
946
+ // Kill the actual node processes on our ports (verify it's node before killing)
947
+ const config = resolveConfig();
948
+ const ports = [config.port || 3457, 3400];
949
+ let killed = 0;
950
+ for (const port of ports) {
951
+ try {
952
+ const output = execFileSync('netstat', ['-ano'], { encoding: 'utf-8' });
953
+ for (const line of output.split(String.fromCharCode(10))) {
954
+ if (line.includes(':' + port + ' ') && line.includes('LISTENING')) {
955
+ const parts = line.trim().split(/\s+/);
956
+ const pid = parseInt(parts[parts.length - 1], 10);
957
+ if (pid > 0) {
958
+ // Verify the process is node.exe before killing to avoid terminating unrelated processes
959
+ try {
960
+ const taskInfo = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf-8' });
961
+ if (!taskInfo.toLowerCase().includes('node.exe')) continue;
962
+ process.kill(pid, 'SIGTERM'); killed++;
963
+ } catch {}
964
+ }
965
+ }
966
+ }
967
+ } catch {}
968
+ }
969
+
970
+ if (killed > 0) {
971
+ logOk('Stopped ' + killed + ' process(es)');
972
+ } else {
973
+ logOk('Task stopped');
974
+ }
975
+ }
976
+
977
+ async function win32Status() {
978
+ try {
979
+ execFileSync('schtasks', ['/Query', '/TN', TASK_NAME, '/V', '/FO', 'LIST'], { stdio: 'inherit' });
980
+ } catch {
981
+ log('Spaces service is not installed');
982
+ }
983
+ }
984
+
985
+ async function win32Logs() {
986
+ ensureLogsDir();
987
+ const outLogPath = path.join(LOGS_DIR, 'spaces.out.log');
988
+ if (!fs.existsSync(outLogPath)) {
989
+ logErr(`Log file not found: ${outLogPath}`);
990
+ log('Service may not have started yet');
991
+ process.exit(1);
992
+ }
993
+ spawnSync('powershell', ['-Command', `Get-Content "${outLogPath}" -Wait -Tail 50`], { stdio: 'inherit' });
994
+ }
995
+
996
+ // ─── Dispatch table ──────────────────────────────────────────
997
+ const platforms = {
998
+ linux: { install: linuxInstall, uninstall: linuxUninstall, start: linuxStart, stop: linuxStop, status: linuxStatus, logs: linuxLogs },
999
+ darwin: { install: darwinInstall, uninstall: darwinUninstall, start: darwinStart, stop: darwinStop, status: darwinStatus, logs: darwinLogs },
1000
+ win32: { install: win32Install, uninstall: win32Uninstall, start: win32Start, stop: win32Stop, status: win32Status, logs: win32Logs },
1001
+ };
1002
+
1003
+ // ─── CLI ──────────────────────────────────────────────────────
1004
+ async function main() {
1005
+ const action = process.argv[2];
1006
+ const platform = getPlatform();
1007
+ const dispatch = platforms[platform];
1008
+
1009
+ if (!action || !dispatch[action]) {
1010
+ log('Usage: spaces service <install|uninstall|start|stop|status|logs>');
1011
+ process.exit(action ? 1 : 0);
1012
+ }
1013
+
1014
+ await dispatch[action]();
1015
+ }
1016
+
1017
+ main().catch((err) => {
1018
+ logErr(err.message);
1019
+ process.exit(1);
1020
+ });