@mrpatronz/nexusflow 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/README.md +57 -23
  2. package/dist/agent/AntigravityCliAdapter.d.ts +7 -0
  3. package/dist/agent/AntigravityCliAdapter.d.ts.map +1 -0
  4. package/dist/agent/AntigravityCliAdapter.js +13 -0
  5. package/dist/agent/AntigravityCliAdapter.js.map +1 -0
  6. package/dist/agent/ClaudeCliAdapter.d.ts +16 -0
  7. package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -0
  8. package/dist/agent/ClaudeCliAdapter.js +34 -0
  9. package/dist/agent/ClaudeCliAdapter.js.map +1 -0
  10. package/dist/agent/CliAdapterBase.d.ts +48 -0
  11. package/dist/agent/CliAdapterBase.d.ts.map +1 -0
  12. package/dist/agent/CliAdapterBase.js +120 -0
  13. package/dist/agent/CliAdapterBase.js.map +1 -0
  14. package/dist/agent/NativeAgent.d.ts +13 -0
  15. package/dist/agent/NativeAgent.d.ts.map +1 -0
  16. package/dist/agent/NativeAgent.js +97 -0
  17. package/dist/agent/NativeAgent.js.map +1 -0
  18. package/dist/agent/NativeAgentBase.d.ts +27 -0
  19. package/dist/agent/NativeAgentBase.d.ts.map +1 -0
  20. package/dist/agent/NativeAgentBase.js +58 -0
  21. package/dist/agent/NativeAgentBase.js.map +1 -0
  22. package/dist/agent/NativeClaudeAgent.d.ts +13 -0
  23. package/dist/agent/NativeClaudeAgent.d.ts.map +1 -0
  24. package/dist/agent/NativeClaudeAgent.js +115 -0
  25. package/dist/agent/NativeClaudeAgent.js.map +1 -0
  26. package/dist/agent/NativeGoogleAgent.d.ts +13 -0
  27. package/dist/agent/NativeGoogleAgent.d.ts.map +1 -0
  28. package/dist/agent/NativeGoogleAgent.js +90 -0
  29. package/dist/agent/NativeGoogleAgent.js.map +1 -0
  30. package/dist/agent/ProviderRegistry.d.ts +31 -0
  31. package/dist/agent/ProviderRegistry.d.ts.map +1 -0
  32. package/dist/agent/ProviderRegistry.js +20 -0
  33. package/dist/agent/ProviderRegistry.js.map +1 -0
  34. package/dist/agent/ProviderRegistry.test.d.ts +2 -0
  35. package/dist/agent/ProviderRegistry.test.d.ts.map +1 -0
  36. package/dist/agent/ProviderRegistry.test.js +57 -0
  37. package/dist/agent/ProviderRegistry.test.js.map +1 -0
  38. package/dist/agent/adapters.d.ts +3 -0
  39. package/dist/agent/adapters.d.ts.map +1 -0
  40. package/dist/agent/adapters.js +48 -0
  41. package/dist/agent/adapters.js.map +1 -0
  42. package/dist/agent/nativeAgent.test.d.ts +2 -0
  43. package/dist/agent/nativeAgent.test.d.ts.map +1 -0
  44. package/dist/agent/nativeAgent.test.js +35 -0
  45. package/dist/agent/nativeAgent.test.js.map +1 -0
  46. package/dist/agent/nativeTools.d.ts +26 -0
  47. package/dist/agent/nativeTools.d.ts.map +1 -0
  48. package/dist/agent/nativeTools.js +62 -0
  49. package/dist/agent/nativeTools.js.map +1 -0
  50. package/dist/agent/nativeTools.test.d.ts +2 -0
  51. package/dist/agent/nativeTools.test.d.ts.map +1 -0
  52. package/dist/agent/nativeTools.test.js +67 -0
  53. package/dist/agent/nativeTools.test.js.map +1 -0
  54. package/dist/agent/session.d.ts +25 -0
  55. package/dist/agent/session.d.ts.map +1 -0
  56. package/dist/agent/session.js +31 -0
  57. package/dist/agent/session.js.map +1 -0
  58. package/dist/agent/session.test.d.ts +2 -0
  59. package/dist/agent/session.test.d.ts.map +1 -0
  60. package/dist/agent/session.test.js +44 -0
  61. package/dist/agent/session.test.js.map +1 -0
  62. package/dist/commands/create.d.ts +8 -7
  63. package/dist/commands/create.d.ts.map +1 -1
  64. package/dist/commands/create.js +134 -65
  65. package/dist/commands/create.js.map +1 -1
  66. package/dist/commands/desktop.d.ts +4 -5
  67. package/dist/commands/desktop.d.ts.map +1 -1
  68. package/dist/commands/desktop.js +35 -89
  69. package/dist/commands/desktop.js.map +1 -1
  70. package/dist/commands/doctor.d.ts.map +1 -1
  71. package/dist/commands/doctor.js +0 -1
  72. package/dist/commands/doctor.js.map +1 -1
  73. package/dist/commands/init.d.ts.map +1 -1
  74. package/dist/commands/init.js +1 -72
  75. package/dist/commands/init.js.map +1 -1
  76. package/dist/commands/list.d.ts.map +1 -1
  77. package/dist/commands/list.js +2 -1
  78. package/dist/commands/list.js.map +1 -1
  79. package/dist/commands/project.d.ts +30 -0
  80. package/dist/commands/project.d.ts.map +1 -0
  81. package/dist/commands/project.js +125 -0
  82. package/dist/commands/project.js.map +1 -0
  83. package/dist/commands/refresh.js +1 -1
  84. package/dist/commands/refresh.js.map +1 -1
  85. package/dist/commands/start.d.ts.map +1 -1
  86. package/dist/commands/start.js +18 -2
  87. package/dist/commands/start.js.map +1 -1
  88. package/dist/commands/stop.d.ts +1 -1
  89. package/dist/commands/stop.d.ts.map +1 -1
  90. package/dist/commands/stop.js +19 -2
  91. package/dist/commands/stop.js.map +1 -1
  92. package/dist/commands/tui.js +0 -1
  93. package/dist/commands/tui.js.map +1 -1
  94. package/dist/commands/ui.d.ts +7 -3
  95. package/dist/commands/ui.d.ts.map +1 -1
  96. package/dist/commands/ui.js +12 -6
  97. package/dist/commands/ui.js.map +1 -1
  98. package/dist/core/config.d.ts.map +1 -1
  99. package/dist/core/config.js +0 -9
  100. package/dist/core/config.js.map +1 -1
  101. package/dist/core/config.test.js +0 -9
  102. package/dist/core/config.test.js.map +1 -1
  103. package/dist/core/doctor.d.ts +1 -3
  104. package/dist/core/doctor.d.ts.map +1 -1
  105. package/dist/core/doctor.js +18 -69
  106. package/dist/core/doctor.js.map +1 -1
  107. package/dist/core/finish.d.ts.map +1 -1
  108. package/dist/core/finish.js +5 -0
  109. package/dist/core/finish.js.map +1 -1
  110. package/dist/core/graph.d.ts.map +1 -1
  111. package/dist/core/graph.js +2 -1
  112. package/dist/core/graph.js.map +1 -1
  113. package/dist/core/projects.d.ts +58 -0
  114. package/dist/core/projects.d.ts.map +1 -0
  115. package/dist/core/projects.js +190 -0
  116. package/dist/core/projects.js.map +1 -0
  117. package/dist/core/projects.test.d.ts +2 -0
  118. package/dist/core/projects.test.d.ts.map +1 -0
  119. package/dist/core/projects.test.js +165 -0
  120. package/dist/core/projects.test.js.map +1 -0
  121. package/dist/core/sync.d.ts.map +1 -1
  122. package/dist/core/sync.js +21 -0
  123. package/dist/core/sync.js.map +1 -1
  124. package/dist/core/sync.test.js +15 -0
  125. package/dist/core/sync.test.js.map +1 -1
  126. package/dist/core/workspace.create.test.js +68 -1
  127. package/dist/core/workspace.create.test.js.map +1 -1
  128. package/dist/core/workspace.d.ts +4 -3
  129. package/dist/core/workspace.d.ts.map +1 -1
  130. package/dist/core/workspace.js +138 -64
  131. package/dist/core/workspace.js.map +1 -1
  132. package/dist/desktop-server.d.ts +13 -0
  133. package/dist/desktop-server.d.ts.map +1 -0
  134. package/dist/desktop-server.js +23 -0
  135. package/dist/desktop-server.js.map +1 -0
  136. package/dist/generators/antigravity.js +1 -1
  137. package/dist/generators/antigravity.js.map +1 -1
  138. package/dist/generators/base.d.ts +1 -8
  139. package/dist/generators/base.d.ts.map +1 -1
  140. package/dist/generators/base.js +107 -58
  141. package/dist/generators/base.js.map +1 -1
  142. package/dist/generators/base.test.d.ts +2 -0
  143. package/dist/generators/base.test.d.ts.map +1 -0
  144. package/dist/generators/base.test.js +80 -0
  145. package/dist/generators/base.test.js.map +1 -0
  146. package/dist/generators/claude.js +1 -1
  147. package/dist/generators/claude.js.map +1 -1
  148. package/dist/generators/codex.js +1 -1
  149. package/dist/generators/codex.js.map +1 -1
  150. package/dist/generators/copilot.js +1 -1
  151. package/dist/generators/copilot.js.map +1 -1
  152. package/dist/generators/cursor.js +1 -1
  153. package/dist/generators/cursor.js.map +1 -1
  154. package/dist/generators/diff-context.d.ts.map +1 -1
  155. package/dist/generators/diff-context.js +3 -1
  156. package/dist/generators/diff-context.js.map +1 -1
  157. package/dist/generators/index.js +1 -1
  158. package/dist/generators/index.js.map +1 -1
  159. package/dist/generators/map-generator.d.ts.map +1 -1
  160. package/dist/generators/map-generator.js +3 -1
  161. package/dist/generators/map-generator.js.map +1 -1
  162. package/dist/gui/assets/DashboardPage-C3NH97y2.js +1 -0
  163. package/dist/gui/assets/GettingStartedPage-og-Kv7Sm.js +1 -0
  164. package/dist/gui/assets/ProjectsPage-COqqadEr.js +1 -0
  165. package/dist/gui/assets/ScaffoldRepoInline-DRipQsos.js +1 -0
  166. package/dist/gui/assets/Separator-Chx6O62Y.js +1 -0
  167. package/dist/gui/assets/SettingsPage-CpU3V9oU.js +1 -0
  168. package/dist/gui/assets/StartWorkPage-DIND0hJy.js +1 -0
  169. package/dist/gui/assets/StrategiesPage-6LWOnKdk.js +3 -0
  170. package/dist/gui/assets/WorkspacesPage-BUqgOhuy.js +6 -0
  171. package/dist/gui/assets/badge-BnkhqVEF.js +1 -0
  172. package/dist/gui/assets/button-cPldH5S4.js +1 -0
  173. package/dist/gui/assets/card-82INhRRE.js +1 -0
  174. package/dist/gui/assets/circle-plus-C0PNc7h4.js +1 -0
  175. package/dist/gui/assets/cpu-CPUNnHRM.js +1 -0
  176. package/dist/gui/assets/empty-DWyDwDYB.js +1 -0
  177. package/dist/gui/assets/external-link-BtYhMxno.js +1 -0
  178. package/dist/gui/assets/git-branch-Dw55pQWa.js +1 -0
  179. package/dist/gui/assets/index-CsA2SueF.css +2 -0
  180. package/dist/gui/assets/index-CyYbQwRf.js +52 -0
  181. package/dist/gui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  182. package/dist/gui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  183. package/dist/gui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  184. package/dist/gui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  185. package/dist/gui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  186. package/dist/gui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  187. package/dist/gui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  188. package/dist/gui/assets/jetbrains-mono-latin-100-normal-DlYB2XW3.woff2 +0 -0
  189. package/dist/gui/assets/jetbrains-mono-latin-100-normal-DzH8uRxw.woff +0 -0
  190. package/dist/gui/assets/jetbrains-mono-latin-200-normal-CHn02WOn.woff +0 -0
  191. package/dist/gui/assets/jetbrains-mono-latin-200-normal-DvzYDkvL.woff2 +0 -0
  192. package/dist/gui/assets/jetbrains-mono-latin-300-normal-BYcAiAh2.woff +0 -0
  193. package/dist/gui/assets/jetbrains-mono-latin-300-normal-DuMDZskh.woff2 +0 -0
  194. package/dist/gui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  195. package/dist/gui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  196. package/dist/gui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  197. package/dist/gui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  198. package/dist/gui/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  199. package/dist/gui/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  200. package/dist/gui/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  201. package/dist/gui/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  202. package/dist/gui/assets/jetbrains-mono-latin-800-normal-D2mQHRMK.woff2 +0 -0
  203. package/dist/gui/assets/jetbrains-mono-latin-800-normal-Dj9qwObk.woff +0 -0
  204. package/dist/gui/assets/label-JFgMNsTn.js +1 -0
  205. package/dist/gui/assets/search-ChVovkOL.js +1 -0
  206. package/dist/gui/assets/skeleton-CCPbfn1L.js +1 -0
  207. package/dist/gui/assets/spinner-DXt6mnJk.js +1 -0
  208. package/dist/gui/assets/textarea-BLU5HXmZ.js +1 -0
  209. package/dist/gui/assets/trash-2-Da8F2gKd.js +1 -0
  210. package/dist/gui/assets/useAriaLabelledBy-CjNlctb9.js +1 -0
  211. package/dist/gui/assets/useCompositeListItem-DWGFLKqA.js +1 -0
  212. package/dist/gui/index.html +16 -6
  213. package/dist/index.js +56 -5
  214. package/dist/index.js.map +1 -1
  215. package/dist/mcp/tools.d.ts.map +1 -1
  216. package/dist/mcp/tools.js +4 -63
  217. package/dist/mcp/tools.js.map +1 -1
  218. package/dist/orchestration/detect.d.ts.map +1 -1
  219. package/dist/orchestration/detect.js +72 -26
  220. package/dist/orchestration/detect.js.map +1 -1
  221. package/dist/orchestration/index.d.ts +3 -1
  222. package/dist/orchestration/index.d.ts.map +1 -1
  223. package/dist/orchestration/index.js +3 -1
  224. package/dist/orchestration/index.js.map +1 -1
  225. package/dist/orchestration/log-tail.d.ts +27 -0
  226. package/dist/orchestration/log-tail.d.ts.map +1 -0
  227. package/dist/orchestration/log-tail.js +93 -0
  228. package/dist/orchestration/log-tail.js.map +1 -0
  229. package/dist/orchestration/log-tail.test.d.ts +2 -0
  230. package/dist/orchestration/log-tail.test.d.ts.map +1 -0
  231. package/dist/orchestration/log-tail.test.js +72 -0
  232. package/dist/orchestration/log-tail.test.js.map +1 -0
  233. package/dist/orchestration/orchestrator.d.ts +26 -0
  234. package/dist/orchestration/orchestrator.d.ts.map +1 -0
  235. package/dist/orchestration/orchestrator.js +102 -0
  236. package/dist/orchestration/orchestrator.js.map +1 -0
  237. package/dist/orchestration/orchestrator.test.d.ts +2 -0
  238. package/dist/orchestration/orchestrator.test.d.ts.map +1 -0
  239. package/dist/orchestration/orchestrator.test.js +85 -0
  240. package/dist/orchestration/orchestrator.test.js.map +1 -0
  241. package/dist/orchestration/runner.d.ts +62 -3
  242. package/dist/orchestration/runner.d.ts.map +1 -1
  243. package/dist/orchestration/runner.js +198 -103
  244. package/dist/orchestration/runner.js.map +1 -1
  245. package/dist/orchestration/runner.test.js +125 -1
  246. package/dist/orchestration/runner.test.js.map +1 -1
  247. package/dist/server.d.ts.map +1 -1
  248. package/dist/server.js +395 -126
  249. package/dist/server.js.map +1 -1
  250. package/dist/server.test.js +111 -123
  251. package/dist/server.test.js.map +1 -1
  252. package/dist/types.d.ts +79 -16
  253. package/dist/types.d.ts.map +1 -1
  254. package/dist/utils/feature.d.ts +42 -0
  255. package/dist/utils/feature.d.ts.map +1 -0
  256. package/dist/utils/feature.js +52 -0
  257. package/dist/utils/feature.js.map +1 -0
  258. package/dist/utils/multi-git.d.ts.map +1 -1
  259. package/dist/utils/multi-git.js +30 -7
  260. package/dist/utils/multi-git.js.map +1 -1
  261. package/dist/utils/session-finder.d.ts +21 -1
  262. package/dist/utils/session-finder.d.ts.map +1 -1
  263. package/dist/utils/session-finder.js +220 -143
  264. package/dist/utils/session-finder.js.map +1 -1
  265. package/dist/utils/session-finder.test.d.ts +2 -0
  266. package/dist/utils/session-finder.test.d.ts.map +1 -0
  267. package/dist/utils/session-finder.test.js +65 -0
  268. package/dist/utils/session-finder.test.js.map +1 -0
  269. package/dist/utils/slug.d.ts +12 -0
  270. package/dist/utils/slug.d.ts.map +1 -0
  271. package/dist/utils/slug.js +18 -0
  272. package/dist/utils/slug.js.map +1 -0
  273. package/dist/utils/test-command.d.ts +12 -0
  274. package/dist/utils/test-command.d.ts.map +1 -0
  275. package/dist/utils/test-command.js +22 -0
  276. package/dist/utils/test-command.js.map +1 -0
  277. package/dist/utils/update-check.d.ts +8 -0
  278. package/dist/utils/update-check.d.ts.map +1 -1
  279. package/dist/utils/update-check.js +13 -7
  280. package/dist/utils/update-check.js.map +1 -1
  281. package/dist/utils/update-check.test.d.ts +2 -0
  282. package/dist/utils/update-check.test.d.ts.map +1 -0
  283. package/dist/utils/update-check.test.js +25 -0
  284. package/dist/utils/update-check.test.js.map +1 -0
  285. package/dist/utils/workflow-advisor.d.ts +2 -2
  286. package/dist/utils/workflow-advisor.d.ts.map +1 -1
  287. package/dist/utils/workflow-advisor.js +50 -92
  288. package/dist/utils/workflow-advisor.js.map +1 -1
  289. package/dist/utils/workflows.d.ts.map +1 -1
  290. package/dist/utils/workflows.js +2 -4
  291. package/dist/utils/workflows.js.map +1 -1
  292. package/package.json +10 -1
  293. package/dist/gui/assets/index-CB-Ekvrs.css +0 -1
  294. package/dist/gui/assets/index-CixLsYSa.js +0 -26
  295. package/dist/utils/local-ai.d.ts +0 -27
  296. package/dist/utils/local-ai.d.ts.map +0 -1
  297. package/dist/utils/local-ai.js +0 -100
  298. package/dist/utils/local-ai.js.map +0 -1
  299. package/dist/utils/local-ai.test.d.ts +0 -2
  300. package/dist/utils/local-ai.test.d.ts.map +0 -1
  301. package/dist/utils/local-ai.test.js +0 -108
  302. package/dist/utils/local-ai.test.js.map +0 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @module agent/nativeTools
3
+ * Shared prompt, tool metadata, and execution for the SDK-backed native agents
4
+ * (OpenAI / Anthropic / Gemini). Keeping the tool executor here means the
5
+ * read-only workspace access — and its path-containment guard — is defined
6
+ * once instead of copied into three agent loops.
7
+ */
8
+ import fs from 'fs-extra';
9
+ import path from 'node:path';
10
+ export const NATIVE_STEP_LIMIT = 5;
11
+ export const STEP_LIMIT_NOTICE = '\n\n*Stopped after reaching the tool-step limit without a final answer. Send a follow-up message to continue.*\n';
12
+ export function buildSystemPrompt(cwd) {
13
+ return `You are an expert coding assistant running within the NexusFlow IDE.
14
+ You have read-only access to the user's workspace at ${cwd} through your tools: you can read files and list directories, but you cannot edit files or run commands.
15
+ When suggesting code changes, include the proposed diff directly in your text response so the user can apply it themselves.`;
16
+ }
17
+ /** Provider-agnostic description of the two read-only tools. Each agent maps
18
+ * these into its own SDK's schema shape. */
19
+ export const NATIVE_TOOLS = [
20
+ { name: 'read_file', description: 'Read the contents of a file', argName: 'filePath', argDescription: 'Path to file relative to workspace' },
21
+ { name: 'list_directory', description: 'List the contents of a directory', argName: 'dirPath', argDescription: 'Path to directory relative to workspace' },
22
+ ];
23
+ function assertWithin(base, target, rel) {
24
+ // Trailing separator stops a sibling like `workspace-secret` from passing
25
+ // the prefix test for base `workspace`.
26
+ if (target !== base && !target.startsWith(base + path.sep)) {
27
+ throw new Error(`Path escapes the workspace: ${String(rel)}`);
28
+ }
29
+ }
30
+ /**
31
+ * Resolve a workspace-relative path and refuse anything that escapes `cwd`,
32
+ * including via symlinks. The lexical check catches `..`/absolute paths; the
33
+ * realpath check catches a symlink inside the workspace that points outside.
34
+ */
35
+ async function resolveWithin(cwd, rel) {
36
+ const base = path.resolve(cwd);
37
+ const resolved = path.resolve(base, typeof rel === 'string' ? rel : '');
38
+ assertWithin(base, resolved, rel);
39
+ // Follow symlinks and re-check. realpath throws ENOENT for a missing target,
40
+ // which the fs read below would raise anyway — let it surface as-is.
41
+ const realBase = await fs.realpath(base);
42
+ const realTarget = await fs.realpath(resolved);
43
+ assertWithin(realBase, realTarget, rel);
44
+ return realTarget;
45
+ }
46
+ /**
47
+ * Execute a native tool call. Returns the tool's textual result; throws on an
48
+ * unknown tool, a path-containment violation, or a filesystem error (callers
49
+ * surface the message back to the model).
50
+ */
51
+ export async function executeNativeTool(cwd, name, args) {
52
+ if (name === 'read_file') {
53
+ const abs = await resolveWithin(cwd, args?.filePath);
54
+ return await fs.readFile(abs, 'utf8');
55
+ }
56
+ if (name === 'list_directory') {
57
+ const abs = await resolveWithin(cwd, args?.dirPath);
58
+ return (await fs.readdir(abs)).join('\n');
59
+ }
60
+ throw new Error(`Unknown tool: ${name}`);
61
+ }
62
+ //# sourceMappingURL=nativeTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeTools.js","sourceRoot":"","sources":["../../src/agent/nativeTools.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,iBAAiB,GAC5B,kHAAkH,CAAC;AAErH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO;uDAC8C,GAAG;4HACkE,CAAC;AAC7H,CAAC;AASD;6CAC6C;AAC7C,MAAM,CAAC,MAAM,YAAY,GAAqB;IAC5C,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,6BAA6B,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oCAAoC,EAAE;IAC5I,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,kCAAkC,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,yCAAyC,EAAE;CAC3J,CAAC;AAEF,SAAS,YAAY,CAAC,IAAY,EAAE,MAAc,EAAE,GAAY;IAC9D,0EAA0E;IAC1E,wCAAwC;IACxC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,GAAY;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxE,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IAElC,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC/C,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IACxC,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAY,EAAE,IAAS;IAC1E,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACrD,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeTools.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeTools.test.d.ts","sourceRoot":"","sources":["../../src/agent/nativeTools.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import fs from 'fs-extra';
5
+ import { executeNativeTool, buildSystemPrompt } from './nativeTools.js';
6
+ let workspace;
7
+ beforeAll(async () => {
8
+ workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'nf-native-tools-'));
9
+ await fs.writeFile(path.join(workspace, 'inside.txt'), 'hello', 'utf8');
10
+ await fs.ensureDir(path.join(workspace, 'sub'));
11
+ });
12
+ afterAll(async () => {
13
+ await fs.remove(workspace);
14
+ });
15
+ describe('executeNativeTool', () => {
16
+ it('reads a file inside the workspace', async () => {
17
+ const out = await executeNativeTool(workspace, 'read_file', { filePath: 'inside.txt' });
18
+ expect(out).toBe('hello');
19
+ });
20
+ it('lists a directory inside the workspace with real newlines', async () => {
21
+ const out = await executeNativeTool(workspace, 'list_directory', { dirPath: '.' });
22
+ expect(out.split('\n').sort()).toEqual(['inside.txt', 'sub']);
23
+ });
24
+ it('rejects a relative path that escapes the workspace', async () => {
25
+ await expect(executeNativeTool(workspace, 'read_file', { filePath: '../../../etc/hosts' }))
26
+ .rejects.toThrow(/escapes the workspace/);
27
+ });
28
+ it('rejects an absolute path outside the workspace', async () => {
29
+ const outside = process.platform === 'win32' ? 'C:\\Windows\\win.ini' : '/etc/hosts';
30
+ await expect(executeNativeTool(workspace, 'read_file', { filePath: outside }))
31
+ .rejects.toThrow(/escapes the workspace/);
32
+ });
33
+ it('rejects an unknown tool', async () => {
34
+ await expect(executeNativeTool(workspace, 'delete_everything', {}))
35
+ .rejects.toThrow(/Unknown tool/);
36
+ });
37
+ it('rejects a symlink inside the workspace that points outside it', async () => {
38
+ const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), 'nf-outside-'));
39
+ const secret = path.join(outsideDir, 'secret.txt');
40
+ await fs.writeFile(secret, 'top secret', 'utf8');
41
+ const link = path.join(workspace, 'escape.txt');
42
+ try {
43
+ await fs.symlink(secret, link);
44
+ }
45
+ catch {
46
+ // Symlink creation needs privileges on Windows; skip if unavailable.
47
+ await fs.remove(outsideDir);
48
+ return;
49
+ }
50
+ try {
51
+ await expect(executeNativeTool(workspace, 'read_file', { filePath: 'escape.txt' }))
52
+ .rejects.toThrow(/escapes the workspace/);
53
+ }
54
+ finally {
55
+ await fs.remove(link);
56
+ await fs.remove(outsideDir);
57
+ }
58
+ });
59
+ });
60
+ describe('buildSystemPrompt', () => {
61
+ it('states the workspace path and read-only scope', () => {
62
+ const p = buildSystemPrompt('/tmp/ws');
63
+ expect(p).toContain('/tmp/ws');
64
+ expect(p).toContain('read-only');
65
+ });
66
+ });
67
+ //# sourceMappingURL=nativeTools.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeTools.test.js","sourceRoot":"","sources":["../../src/agent/nativeTools.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module agent/session
3
+ * Session identity for chat agents that support resumable conversations.
4
+ */
5
+ /** A chat session to create or resume. `resume` means the session already has turns on disk. */
6
+ export interface AgentSession {
7
+ id: string;
8
+ resume: boolean;
9
+ }
10
+ /**
11
+ * Strict UUID check. Session ids come from the client and end up in the argv of a
12
+ * shell:true spawn, so only the inert UUID charset may pass.
13
+ */
14
+ export declare function isValidSessionUuid(id: unknown): id is string;
15
+ /**
16
+ * Builds the claude CLI args for one print-mode turn. The prompt itself is
17
+ * always piped via stdin, never argv.
18
+ *
19
+ * Without a session the legacy behavior applies: `-c` continues the most
20
+ * recent conversation in the cwd. With a session, a fresh id is created via
21
+ * --session-id on the first turn and every later turn resumes that id;
22
+ * resumed sessions use --resume from the start.
23
+ */
24
+ export declare function buildClaudeTurnArgs(isFirstTurn: boolean, session?: AgentSession): string[];
25
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/agent/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,gGAAgG;AAChG,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;CACjB;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,MAAM,CAE5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAQ1F"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module agent/session
3
+ * Session identity for chat agents that support resumable conversations.
4
+ */
5
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
+ /**
7
+ * Strict UUID check. Session ids come from the client and end up in the argv of a
8
+ * shell:true spawn, so only the inert UUID charset may pass.
9
+ */
10
+ export function isValidSessionUuid(id) {
11
+ return typeof id === 'string' && UUID_RE.test(id);
12
+ }
13
+ /**
14
+ * Builds the claude CLI args for one print-mode turn. The prompt itself is
15
+ * always piped via stdin, never argv.
16
+ *
17
+ * Without a session the legacy behavior applies: `-c` continues the most
18
+ * recent conversation in the cwd. With a session, a fresh id is created via
19
+ * --session-id on the first turn and every later turn resumes that id;
20
+ * resumed sessions use --resume from the start.
21
+ */
22
+ export function buildClaudeTurnArgs(isFirstTurn, session) {
23
+ if (!session) {
24
+ return isFirstTurn ? ['-p'] : ['-c', '-p'];
25
+ }
26
+ if (!session.resume && isFirstTurn) {
27
+ return ['-p', '--session-id', session.id];
28
+ }
29
+ return ['-p', '--resume', session.id];
30
+ }
31
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/agent/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAElF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAW;IAC5C,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAoB,EAAE,OAAsB;IAC9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/agent/session.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildClaudeTurnArgs, isValidSessionUuid } from './session.js';
3
+ const ID = '123e4567-e89b-42d3-a456-426614174000';
4
+ describe('buildClaudeTurnArgs', () => {
5
+ it('uses plain -p on the first legacy turn', () => {
6
+ expect(buildClaudeTurnArgs(true)).toEqual(['-p']);
7
+ });
8
+ it('continues with -c on later legacy turns', () => {
9
+ expect(buildClaudeTurnArgs(false)).toEqual(['-c', '-p']);
10
+ });
11
+ it('creates a new session with --session-id on the first turn', () => {
12
+ expect(buildClaudeTurnArgs(true, { id: ID, resume: false })).toEqual(['-p', '--session-id', ID]);
13
+ });
14
+ it('resumes the created session on later turns', () => {
15
+ expect(buildClaudeTurnArgs(false, { id: ID, resume: false })).toEqual(['-p', '--resume', ID]);
16
+ });
17
+ it('resumes an existing session from the first turn', () => {
18
+ expect(buildClaudeTurnArgs(true, { id: ID, resume: true })).toEqual(['-p', '--resume', ID]);
19
+ });
20
+ it('keeps resuming an existing session on later turns', () => {
21
+ expect(buildClaudeTurnArgs(false, { id: ID, resume: true })).toEqual(['-p', '--resume', ID]);
22
+ });
23
+ });
24
+ describe('isValidSessionUuid', () => {
25
+ it('accepts a lowercase uuid', () => {
26
+ expect(isValidSessionUuid(ID)).toBe(true);
27
+ });
28
+ it('accepts an uppercase uuid', () => {
29
+ expect(isValidSessionUuid(ID.toUpperCase())).toBe(true);
30
+ });
31
+ it('rejects empty strings', () => {
32
+ expect(isValidSessionUuid('')).toBe(false);
33
+ });
34
+ it('rejects shell metacharacters', () => {
35
+ expect(isValidSessionUuid('x; rm -rf .')).toBe(false);
36
+ expect(isValidSessionUuid(`${ID} && echo pwned`)).toBe(false);
37
+ });
38
+ it('rejects non-strings', () => {
39
+ expect(isValidSessionUuid(undefined)).toBe(false);
40
+ expect(isValidSessionUuid(42)).toBe(false);
41
+ expect(isValidSessionUuid({ id: ID })).toBe(false);
42
+ });
43
+ });
44
+ //# sourceMappingURL=session.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.test.js","sourceRoot":"","sources":["../../src/agent/session.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvE,MAAM,EAAE,GAAG,sCAAsC,CAAC;AAElD,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -5,13 +5,14 @@
5
5
  */
6
6
  /**
7
7
  * Executes the full "create workspace" flow:
8
- * 1. Prompt for branch name
9
- * 2. Prompt for feature description
10
- * 3. Scan for repos and let user pick
11
- * 4. Detect AI assistants and let user pick
12
- * 5. Create workspace with git worktrees
13
- * 6. Generate AI context files
14
- * 7. Optionally open in editor
8
+ * 1. Pick a registered project (or ad-hoc repos)
9
+ * 2. Choose the work mode: in-place (no git ceremony) or isolated worktrees
10
+ * 3. Prompt for branch name (worktree) or workspace name (in-place)
11
+ * 4. Prompt for feature description; select repos when ad hoc
12
+ * 5. Detect AI assistants and let user pick
13
+ * 6. Create the workspace (worktrees only in worktree mode)
14
+ * 7. Generate AI context files
15
+ * 8. Optionally open in editor
15
16
  */
16
17
  export declare function createCommand(): Promise<void>;
17
18
  //# sourceMappingURL=create.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH;;;;;;;;;GASG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA+OnD"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoCH;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAwTnD"}
@@ -7,78 +7,143 @@ import chalk from 'chalk';
7
7
  import ora from 'ora';
8
8
  import path from 'node:path';
9
9
  import { execa } from 'execa';
10
- import { confirm } from '@inquirer/prompts';
10
+ import { confirm, input, select } from '@inquirer/prompts';
11
11
  import { loadConfig } from '../core/config.js';
12
12
  import { scanForRepos } from '../core/scanner.js';
13
- import { createWorkspace } from '../core/workspace.js';
13
+ import { createWorkspace, resolveRepoInfos } from '../core/workspace.js';
14
14
  import { generateContextFiles } from '../generators/index.js';
15
15
  import { analyzeAllRepos } from '../analyzers/index.js';
16
16
  import { detectAIAssistants } from '../utils/detect-ai.js';
17
17
  import { detectEditors } from '../utils/detect-editors.js';
18
18
  import { openInEditor } from '../utils/open-editor.js';
19
19
  import { debugLog } from '../utils/debug.js';
20
+ import { getSessionCwd } from '../utils/feature.js';
20
21
  import { promptBranchName, promptDescription, promptSelectRepos, promptSelectAI, promptSelectEditor, promptSelectStrategy, promptNewStrategy, promptNewProjectName, promptRepoBranches, } from '../utils/prompts.js';
21
22
  import { createNewRepo } from '../core/new-repo.js';
23
+ import { loadProjects, slugifyProjectName } from '../core/projects.js';
22
24
  import { suggestWorkflow } from '../utils/workflow-advisor.js';
23
25
  import { getWorkflowTemplates, saveWorkflowTemplate } from '../utils/workflows.js';
24
26
  /**
25
27
  * Executes the full "create workspace" flow:
26
- * 1. Prompt for branch name
27
- * 2. Prompt for feature description
28
- * 3. Scan for repos and let user pick
29
- * 4. Detect AI assistants and let user pick
30
- * 5. Create workspace with git worktrees
31
- * 6. Generate AI context files
32
- * 7. Optionally open in editor
28
+ * 1. Pick a registered project (or ad-hoc repos)
29
+ * 2. Choose the work mode: in-place (no git ceremony) or isolated worktrees
30
+ * 3. Prompt for branch name (worktree) or workspace name (in-place)
31
+ * 4. Prompt for feature description; select repos when ad hoc
32
+ * 5. Detect AI assistants and let user pick
33
+ * 6. Create the workspace (worktrees only in worktree mode)
34
+ * 7. Generate AI context files
35
+ * 8. Optionally open in editor
33
36
  */
34
37
  export async function createCommand() {
35
- console.log(chalk.bold.cyan('\n🚀 NexusFlow — Create Feature Workspace\n'));
36
- // ── 1. Branch name ──────────────────────────────────────────────────
37
- const branchName = await promptBranchName();
38
- // ── 2. Feature description ──────────────────────────────────────────
39
- const description = await promptDescription();
40
- // ── 3. Scan for repos ───────────────────────────────────────────────
38
+ console.log(chalk.bold.cyan('\n🚀 NexusFlow — Start Work\n'));
41
39
  const config = await loadConfig();
42
- const spinner = ora('Scanning for projects...').start();
43
- let repos;
44
- try {
45
- repos = await scanForRepos(config.devDir, config.scanDepth);
46
- spinner.succeed(`Found ${chalk.bold(repos.length)} projects in ${config.devDir}`);
40
+ // ── 0. Project selection (when a registry exists) ───────────────────
41
+ const projects = await loadProjects({ quiet: true });
42
+ let project = null;
43
+ if (projects.length > 0) {
44
+ const choice = await select({
45
+ message: 'Start from a project?',
46
+ choices: [
47
+ ...projects.map((p) => ({
48
+ name: `${p.name} ${chalk.dim(`(${p.repos.map((r) => path.basename(r.path)).join(', ')})`)}`,
49
+ value: p.id,
50
+ })),
51
+ { name: 'Ad-hoc — pick repos manually', value: '' },
52
+ ],
53
+ });
54
+ project = choice ? projects.find((p) => p.id === choice) ?? null : null;
47
55
  }
48
- catch (error) {
49
- spinner.fail('Failed to scan for projects');
50
- throw error;
56
+ // ── 1. Work mode ─────────────────────────────────────────────────────
57
+ const mode = await select({
58
+ message: 'How do you want to work?',
59
+ choices: [
60
+ {
61
+ name: 'In-place — directly in the source repos (no branches, fastest start)',
62
+ value: 'in-place',
63
+ },
64
+ {
65
+ name: 'Isolated worktrees — a feature branch and worktree per repo',
66
+ value: 'worktree',
67
+ },
68
+ ],
69
+ });
70
+ const inPlace = mode === 'in-place';
71
+ // ── 2. Identity: feature branch, or a plain name for in-place ───────
72
+ let branchName = '';
73
+ let workspaceId = '';
74
+ if (inPlace) {
75
+ const workspaceName = await input({
76
+ message: 'Workspace name:',
77
+ validate: (value) => slugifyProjectName(value).length > 0 || 'Name needs at least one letter or digit',
78
+ });
79
+ workspaceId = slugifyProjectName(workspaceName);
80
+ branchName = workspaceId; // populated for display/back-compat; no branch is created
51
81
  }
52
- if (repos.length === 0) {
53
- console.log(chalk.yellow('No git projects found. Check your devDir setting.'));
54
- return;
82
+ else {
83
+ branchName = await promptBranchName();
84
+ workspaceId = branchName;
55
85
  }
56
- // ── 4. Select repos ────────────────────────────────────────────────
57
- const selectedRepos = await promptSelectRepos(repos);
58
- // ── 4.1. Optionally scaffold brand-new projects ─────────────────────
59
- while (await confirm({
60
- message: '➕ Create a brand-new project to include in this workspace?',
61
- default: false,
62
- })) {
63
- const projectName = await promptNewProjectName(config.devDir);
64
- const newRepoSpinner = ora(`Creating new project ${projectName}...`).start();
86
+ // ── 2.1. Feature description ─────────────────────────────────────────
87
+ const description = await promptDescription();
88
+ // ── 3-4. Repos: from the project, or scanned and picked ad hoc ───────
89
+ let selectedRepos;
90
+ if (project) {
91
+ // Re-resolve at create time: the registry's defaultBranch is a snapshot
92
+ // from `project add` and may have gone stale (repo moved, default branch
93
+ // renamed) basing a new feature branch on it would be silently wrong.
94
+ const projectSpinner = ora(`Validating repositories of ${project.name}...`).start();
65
95
  try {
66
- const newRepo = await createNewRepo(config.devDir, projectName);
67
- selectedRepos.push(newRepo);
68
- newRepoSpinner.succeed(`Created ${chalk.bold(newRepo.name)} at ${newRepo.path}`);
96
+ selectedRepos = await resolveRepoInfos(project.repos.map((r) => r.path));
97
+ projectSpinner.succeed(`Repos from ${project.name}: ${selectedRepos.map((r) => r.name).join(', ')}`);
69
98
  }
70
99
  catch (error) {
71
- const message = error instanceof Error ? error.message : String(error);
72
- newRepoSpinner.fail(`Could not create project: ${message}`);
100
+ projectSpinner.fail(`A repository of project "${project.name}" is missing or not a git repo — fix it with "nexusflow project add/remove".`);
101
+ throw error;
73
102
  }
74
103
  }
75
- if (selectedRepos.length === 0) {
76
- console.log(chalk.yellow('No projects selected. Exiting.'));
77
- return;
104
+ else {
105
+ const spinner = ora('Scanning for projects...').start();
106
+ let repos;
107
+ try {
108
+ repos = await scanForRepos(config.devDir, config.scanDepth);
109
+ spinner.succeed(`Found ${chalk.bold(repos.length)} projects in ${config.devDir}`);
110
+ }
111
+ catch (error) {
112
+ spinner.fail('Failed to scan for projects');
113
+ throw error;
114
+ }
115
+ if (repos.length === 0) {
116
+ console.log(chalk.yellow('No git projects found. Check your devDir setting.'));
117
+ return;
118
+ }
119
+ selectedRepos = await promptSelectRepos(repos);
120
+ // ── 4.1. Optionally scaffold brand-new projects ─────────────────────
121
+ while (await confirm({
122
+ message: '➕ Create a brand-new project to include in this workspace?',
123
+ default: false,
124
+ })) {
125
+ const projectName = await promptNewProjectName(config.devDir);
126
+ const newRepoSpinner = ora(`Creating new project ${projectName}...`).start();
127
+ try {
128
+ const newRepo = await createNewRepo(config.devDir, projectName);
129
+ selectedRepos.push(newRepo);
130
+ newRepoSpinner.succeed(`Created ${chalk.bold(newRepo.name)} at ${newRepo.path}`);
131
+ }
132
+ catch (error) {
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ newRepoSpinner.fail(`Could not create project: ${message}`);
135
+ }
136
+ }
137
+ if (selectedRepos.length === 0) {
138
+ console.log(chalk.yellow('No projects selected. Exiting.'));
139
+ return;
140
+ }
141
+ console.log(chalk.dim(` Selected: ${selectedRepos.map((r) => r.name).join(', ')}`));
78
142
  }
79
- console.log(chalk.dim(` Selected: ${selectedRepos.map((r) => r.name).join(', ')}`));
80
- // ── 4.2. Optionally use existing branches per repo ──────────────────
81
- const branchOverrides = await promptRepoBranches(selectedRepos, branchName);
143
+ // ── 4.2. Optionally use existing branches per repo (worktree only) ───
144
+ const branchOverrides = inPlace
145
+ ? new Map()
146
+ : await promptRepoBranches(selectedRepos, branchName);
82
147
  for (const repo of selectedRepos) {
83
148
  const override = branchOverrides.get(repo.name);
84
149
  if (override) {
@@ -89,9 +154,6 @@ export async function createCommand() {
89
154
  // ── 5. Detect and select AI assistants ──────────────────────────────
90
155
  const detectedAI = await detectAIAssistants();
91
156
  const selectedAI = await promptSelectAI(detectedAI);
92
- const localLlmEnabled = config.localLlm?.enabled
93
- ? await confirm({ message: 'Enable Local AI Co-processor in this workspace context?', default: true })
94
- : false;
95
157
  // ── 5.5. Suggest workflow strategy ───────────────────────────────
96
158
  const templates = await getWorkflowTemplates();
97
159
  const selectedStrategyId = await promptSelectStrategy(templates);
@@ -99,7 +161,7 @@ export async function createCommand() {
99
161
  if (selectedStrategyId === 'auto') {
100
162
  const workflowSpinner = ora('Suggesting teamwork collaboration strategy...').start();
101
163
  try {
102
- const suggestion = await suggestWorkflow(description, selectedRepos, config.localLlm);
164
+ const suggestion = await suggestWorkflow(description, selectedRepos);
103
165
  teamworkInstructions = suggestion.customInstructions;
104
166
  workflowSpinner.succeed(`Auto-selected strategy for ${chalk.bold(suggestion.difficulty)} difficulty task`);
105
167
  }
@@ -129,21 +191,24 @@ export async function createCommand() {
129
191
  }
130
192
  }
131
193
  // ── 6. Create workspace ─────────────────────────────────────────────
132
- const workspacePath = path.join(config.workspacesDir, branchName);
194
+ const workspacePath = path.join(config.workspacesDir, workspaceId);
133
195
  const feature = {
134
- id: branchName,
196
+ id: workspaceId,
197
+ mode,
198
+ projectId: project?.id,
135
199
  branchName,
136
200
  description,
137
- repos: selectedRepos.map((r) => path.join(workspacePath, r.name)),
201
+ repos: inPlace
202
+ ? selectedRepos.map((r) => r.path)
203
+ : selectedRepos.map((r) => path.join(workspacePath, r.name)),
138
204
  originalRepos: selectedRepos.map((r) => r.path),
139
205
  repoBranches: branchOverrides.size > 0 ? Object.fromEntries(branchOverrides) : undefined,
140
206
  assistants: selectedAI,
141
207
  workspacePath,
142
208
  createdAt: new Date().toISOString(),
143
- localLlmEnabled,
144
209
  teamworkInstructions,
145
210
  };
146
- const wsSpinner = ora('Creating workspace with git worktrees...').start();
211
+ const wsSpinner = ora(inPlace ? 'Registering workspace...' : 'Creating workspace with git worktrees...').start();
147
212
  try {
148
213
  await createWorkspace(feature, selectedRepos, (repoName, index, total) => {
149
214
  wsSpinner.text = `Creating worktrees… ${repoName} (${index + 1}/${total})`;
@@ -161,15 +226,17 @@ export async function createCommand() {
161
226
  // ── 7-8. Analyze projects & generate context ────────────────────────
162
227
  // The worktrees are the product; if analysis/generation fails the workspace
163
228
  // is still usable, so keep it and tell the user how to retry rather than
164
- // exiting with an error.
165
- const workspaceRepos = selectedRepos.map((repo) => ({
166
- ...repo,
167
- path: path.join(workspacePath, repo.name),
168
- }));
229
+ // exiting with an error. In-place analysis runs on the source repos.
230
+ const workspaceRepos = inPlace
231
+ ? selectedRepos
232
+ : selectedRepos.map((repo) => ({
233
+ ...repo,
234
+ path: path.join(workspacePath, repo.name),
235
+ }));
169
236
  try {
170
237
  console.log(chalk.cyan('\nAnalyzing projects...'));
171
238
  const analysis = await analyzeAllRepos(workspaceRepos);
172
- const ctx = { feature, repos: workspaceRepos, analysis, localLlm: config.localLlm };
239
+ const ctx = { feature, repos: workspaceRepos, analysis };
173
240
  console.log(chalk.cyan('\nGenerating AI context files...'));
174
241
  await generateContextFiles(ctx, selectedAI, workspacePath);
175
242
  }
@@ -194,10 +261,11 @@ export async function createCommand() {
194
261
  // ── Done ────────────────────────────────────────────────────────────
195
262
  console.log(chalk.bold.green('\n✅ Workspace ready!\n'));
196
263
  console.log(` ${chalk.dim('Path:')} ${workspacePath}`);
197
- console.log(` ${chalk.dim('Branch:')} ${branchName}`);
264
+ console.log(inPlace
265
+ ? ` ${chalk.dim('Mode:')} in-place (working directly in the source repos)`
266
+ : ` ${chalk.dim('Branch:')} ${branchName}`);
198
267
  console.log(` ${chalk.dim('Repos:')} ${selectedRepos.map((r) => r.name).join(', ')}`);
199
268
  console.log(` ${chalk.dim('AI:')} ${selectedAI.join(', ')}`);
200
- console.log(` ${chalk.dim('Local AI:')} ${localLlmEnabled ? 'Enabled' : 'Disabled'}`);
201
269
  console.log(`\n ${chalk.dim('To navigate:')} cd "${workspacePath}"`);
202
270
  console.log();
203
271
  // ── 9. Start AI Assistant Session ───────────────────────────────────
@@ -219,16 +287,17 @@ export async function createCommand() {
219
287
  });
220
288
  if (confirmStart) {
221
289
  console.log(chalk.cyan(`\n🚀 Starting ${label} session inside workspace...\n`));
290
+ const sessionCwd = getSessionCwd(feature);
222
291
  try {
223
292
  await execa(launchCmd, [], {
224
- cwd: workspacePath,
293
+ cwd: sessionCwd,
225
294
  stdio: 'inherit',
226
295
  shell: process.platform === 'win32',
227
296
  });
228
297
  console.log(chalk.green(`\n👋 Exited ${label} session.`));
229
298
  }
230
299
  catch {
231
- console.log(chalk.yellow(`\n⚠️ Could not start ${label}. Please start it manually:\n ${chalk.dim(`cd "${workspacePath}" && ${launchCmd}`)}`));
300
+ console.log(chalk.yellow(`\n⚠️ Could not start ${label}. Please start it manually:\n ${chalk.dim(`cd "${sessionCwd}" && ${launchCmd}`)}`));
232
301
  }
233
302
  }
234
303
  }