@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.
- package/README.md +57 -23
- package/dist/agent/AntigravityCliAdapter.d.ts +7 -0
- package/dist/agent/AntigravityCliAdapter.d.ts.map +1 -0
- package/dist/agent/AntigravityCliAdapter.js +13 -0
- package/dist/agent/AntigravityCliAdapter.js.map +1 -0
- package/dist/agent/ClaudeCliAdapter.d.ts +16 -0
- package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -0
- package/dist/agent/ClaudeCliAdapter.js +34 -0
- package/dist/agent/ClaudeCliAdapter.js.map +1 -0
- package/dist/agent/CliAdapterBase.d.ts +48 -0
- package/dist/agent/CliAdapterBase.d.ts.map +1 -0
- package/dist/agent/CliAdapterBase.js +120 -0
- package/dist/agent/CliAdapterBase.js.map +1 -0
- package/dist/agent/NativeAgent.d.ts +13 -0
- package/dist/agent/NativeAgent.d.ts.map +1 -0
- package/dist/agent/NativeAgent.js +97 -0
- package/dist/agent/NativeAgent.js.map +1 -0
- package/dist/agent/NativeAgentBase.d.ts +27 -0
- package/dist/agent/NativeAgentBase.d.ts.map +1 -0
- package/dist/agent/NativeAgentBase.js +58 -0
- package/dist/agent/NativeAgentBase.js.map +1 -0
- package/dist/agent/NativeClaudeAgent.d.ts +13 -0
- package/dist/agent/NativeClaudeAgent.d.ts.map +1 -0
- package/dist/agent/NativeClaudeAgent.js +115 -0
- package/dist/agent/NativeClaudeAgent.js.map +1 -0
- package/dist/agent/NativeGoogleAgent.d.ts +13 -0
- package/dist/agent/NativeGoogleAgent.d.ts.map +1 -0
- package/dist/agent/NativeGoogleAgent.js +90 -0
- package/dist/agent/NativeGoogleAgent.js.map +1 -0
- package/dist/agent/ProviderRegistry.d.ts +31 -0
- package/dist/agent/ProviderRegistry.d.ts.map +1 -0
- package/dist/agent/ProviderRegistry.js +20 -0
- package/dist/agent/ProviderRegistry.js.map +1 -0
- package/dist/agent/ProviderRegistry.test.d.ts +2 -0
- package/dist/agent/ProviderRegistry.test.d.ts.map +1 -0
- package/dist/agent/ProviderRegistry.test.js +57 -0
- package/dist/agent/ProviderRegistry.test.js.map +1 -0
- package/dist/agent/adapters.d.ts +3 -0
- package/dist/agent/adapters.d.ts.map +1 -0
- package/dist/agent/adapters.js +48 -0
- package/dist/agent/adapters.js.map +1 -0
- package/dist/agent/nativeAgent.test.d.ts +2 -0
- package/dist/agent/nativeAgent.test.d.ts.map +1 -0
- package/dist/agent/nativeAgent.test.js +35 -0
- package/dist/agent/nativeAgent.test.js.map +1 -0
- package/dist/agent/nativeTools.d.ts +26 -0
- package/dist/agent/nativeTools.d.ts.map +1 -0
- package/dist/agent/nativeTools.js +62 -0
- package/dist/agent/nativeTools.js.map +1 -0
- package/dist/agent/nativeTools.test.d.ts +2 -0
- package/dist/agent/nativeTools.test.d.ts.map +1 -0
- package/dist/agent/nativeTools.test.js +67 -0
- package/dist/agent/nativeTools.test.js.map +1 -0
- package/dist/agent/session.d.ts +25 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +31 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/session.test.d.ts +2 -0
- package/dist/agent/session.test.d.ts.map +1 -0
- package/dist/agent/session.test.js +44 -0
- package/dist/agent/session.test.js.map +1 -0
- package/dist/commands/create.d.ts +8 -7
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +134 -65
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/desktop.d.ts +4 -5
- package/dist/commands/desktop.d.ts.map +1 -1
- package/dist/commands/desktop.js +35 -89
- package/dist/commands/desktop.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -72
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +2 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/project.d.ts +30 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +125 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/refresh.js +1 -1
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +18 -2
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.d.ts +1 -1
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +19 -2
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/tui.js +0 -1
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/ui.d.ts +7 -3
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +12 -6
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +0 -9
- package/dist/core/config.js.map +1 -1
- package/dist/core/config.test.js +0 -9
- package/dist/core/config.test.js.map +1 -1
- package/dist/core/doctor.d.ts +1 -3
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +18 -69
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/finish.d.ts.map +1 -1
- package/dist/core/finish.js +5 -0
- package/dist/core/finish.js.map +1 -1
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +2 -1
- package/dist/core/graph.js.map +1 -1
- package/dist/core/projects.d.ts +58 -0
- package/dist/core/projects.d.ts.map +1 -0
- package/dist/core/projects.js +190 -0
- package/dist/core/projects.js.map +1 -0
- package/dist/core/projects.test.d.ts +2 -0
- package/dist/core/projects.test.d.ts.map +1 -0
- package/dist/core/projects.test.js +165 -0
- package/dist/core/projects.test.js.map +1 -0
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/core/sync.js +21 -0
- package/dist/core/sync.js.map +1 -1
- package/dist/core/sync.test.js +15 -0
- package/dist/core/sync.test.js.map +1 -1
- package/dist/core/workspace.create.test.js +68 -1
- package/dist/core/workspace.create.test.js.map +1 -1
- package/dist/core/workspace.d.ts +4 -3
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +138 -64
- package/dist/core/workspace.js.map +1 -1
- package/dist/desktop-server.d.ts +13 -0
- package/dist/desktop-server.d.ts.map +1 -0
- package/dist/desktop-server.js +23 -0
- package/dist/desktop-server.js.map +1 -0
- package/dist/generators/antigravity.js +1 -1
- package/dist/generators/antigravity.js.map +1 -1
- package/dist/generators/base.d.ts +1 -8
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +107 -58
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/base.test.d.ts +2 -0
- package/dist/generators/base.test.d.ts.map +1 -0
- package/dist/generators/base.test.js +80 -0
- package/dist/generators/base.test.js.map +1 -0
- package/dist/generators/claude.js +1 -1
- package/dist/generators/claude.js.map +1 -1
- package/dist/generators/codex.js +1 -1
- package/dist/generators/codex.js.map +1 -1
- package/dist/generators/copilot.js +1 -1
- package/dist/generators/copilot.js.map +1 -1
- package/dist/generators/cursor.js +1 -1
- package/dist/generators/cursor.js.map +1 -1
- package/dist/generators/diff-context.d.ts.map +1 -1
- package/dist/generators/diff-context.js +3 -1
- package/dist/generators/diff-context.js.map +1 -1
- package/dist/generators/index.js +1 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts.map +1 -1
- package/dist/generators/map-generator.js +3 -1
- package/dist/generators/map-generator.js.map +1 -1
- package/dist/gui/assets/DashboardPage-C3NH97y2.js +1 -0
- package/dist/gui/assets/GettingStartedPage-og-Kv7Sm.js +1 -0
- package/dist/gui/assets/ProjectsPage-COqqadEr.js +1 -0
- package/dist/gui/assets/ScaffoldRepoInline-DRipQsos.js +1 -0
- package/dist/gui/assets/Separator-Chx6O62Y.js +1 -0
- package/dist/gui/assets/SettingsPage-CpU3V9oU.js +1 -0
- package/dist/gui/assets/StartWorkPage-DIND0hJy.js +1 -0
- package/dist/gui/assets/StrategiesPage-6LWOnKdk.js +3 -0
- package/dist/gui/assets/WorkspacesPage-BUqgOhuy.js +6 -0
- package/dist/gui/assets/badge-BnkhqVEF.js +1 -0
- package/dist/gui/assets/button-cPldH5S4.js +1 -0
- package/dist/gui/assets/card-82INhRRE.js +1 -0
- package/dist/gui/assets/circle-plus-C0PNc7h4.js +1 -0
- package/dist/gui/assets/cpu-CPUNnHRM.js +1 -0
- package/dist/gui/assets/empty-DWyDwDYB.js +1 -0
- package/dist/gui/assets/external-link-BtYhMxno.js +1 -0
- package/dist/gui/assets/git-branch-Dw55pQWa.js +1 -0
- package/dist/gui/assets/index-CsA2SueF.css +2 -0
- package/dist/gui/assets/index-CyYbQwRf.js +52 -0
- package/dist/gui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/gui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/gui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/gui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/gui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/gui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/gui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-100-normal-DlYB2XW3.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-100-normal-DzH8uRxw.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-200-normal-CHn02WOn.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-200-normal-DvzYDkvL.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-300-normal-BYcAiAh2.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-300-normal-DuMDZskh.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-800-normal-D2mQHRMK.woff2 +0 -0
- package/dist/gui/assets/jetbrains-mono-latin-800-normal-Dj9qwObk.woff +0 -0
- package/dist/gui/assets/label-JFgMNsTn.js +1 -0
- package/dist/gui/assets/search-ChVovkOL.js +1 -0
- package/dist/gui/assets/skeleton-CCPbfn1L.js +1 -0
- package/dist/gui/assets/spinner-DXt6mnJk.js +1 -0
- package/dist/gui/assets/textarea-BLU5HXmZ.js +1 -0
- package/dist/gui/assets/trash-2-Da8F2gKd.js +1 -0
- package/dist/gui/assets/useAriaLabelledBy-CjNlctb9.js +1 -0
- package/dist/gui/assets/useCompositeListItem-DWGFLKqA.js +1 -0
- package/dist/gui/index.html +16 -6
- package/dist/index.js +56 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +4 -63
- package/dist/mcp/tools.js.map +1 -1
- package/dist/orchestration/detect.d.ts.map +1 -1
- package/dist/orchestration/detect.js +72 -26
- package/dist/orchestration/detect.js.map +1 -1
- package/dist/orchestration/index.d.ts +3 -1
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +3 -1
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/log-tail.d.ts +27 -0
- package/dist/orchestration/log-tail.d.ts.map +1 -0
- package/dist/orchestration/log-tail.js +93 -0
- package/dist/orchestration/log-tail.js.map +1 -0
- package/dist/orchestration/log-tail.test.d.ts +2 -0
- package/dist/orchestration/log-tail.test.d.ts.map +1 -0
- package/dist/orchestration/log-tail.test.js +72 -0
- package/dist/orchestration/log-tail.test.js.map +1 -0
- package/dist/orchestration/orchestrator.d.ts +26 -0
- package/dist/orchestration/orchestrator.d.ts.map +1 -0
- package/dist/orchestration/orchestrator.js +102 -0
- package/dist/orchestration/orchestrator.js.map +1 -0
- package/dist/orchestration/orchestrator.test.d.ts +2 -0
- package/dist/orchestration/orchestrator.test.d.ts.map +1 -0
- package/dist/orchestration/orchestrator.test.js +85 -0
- package/dist/orchestration/orchestrator.test.js.map +1 -0
- package/dist/orchestration/runner.d.ts +62 -3
- package/dist/orchestration/runner.d.ts.map +1 -1
- package/dist/orchestration/runner.js +198 -103
- package/dist/orchestration/runner.js.map +1 -1
- package/dist/orchestration/runner.test.js +125 -1
- package/dist/orchestration/runner.test.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +395 -126
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +111 -123
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +79 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/feature.d.ts +42 -0
- package/dist/utils/feature.d.ts.map +1 -0
- package/dist/utils/feature.js +52 -0
- package/dist/utils/feature.js.map +1 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +30 -7
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/session-finder.d.ts +21 -1
- package/dist/utils/session-finder.d.ts.map +1 -1
- package/dist/utils/session-finder.js +220 -143
- package/dist/utils/session-finder.js.map +1 -1
- package/dist/utils/session-finder.test.d.ts +2 -0
- package/dist/utils/session-finder.test.d.ts.map +1 -0
- package/dist/utils/session-finder.test.js +65 -0
- package/dist/utils/session-finder.test.js.map +1 -0
- package/dist/utils/slug.d.ts +12 -0
- package/dist/utils/slug.d.ts.map +1 -0
- package/dist/utils/slug.js +18 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/utils/test-command.d.ts +12 -0
- package/dist/utils/test-command.d.ts.map +1 -0
- package/dist/utils/test-command.js +22 -0
- package/dist/utils/test-command.js.map +1 -0
- package/dist/utils/update-check.d.ts +8 -0
- package/dist/utils/update-check.d.ts.map +1 -1
- package/dist/utils/update-check.js +13 -7
- package/dist/utils/update-check.js.map +1 -1
- package/dist/utils/update-check.test.d.ts +2 -0
- package/dist/utils/update-check.test.d.ts.map +1 -0
- package/dist/utils/update-check.test.js +25 -0
- package/dist/utils/update-check.test.js.map +1 -0
- package/dist/utils/workflow-advisor.d.ts +2 -2
- package/dist/utils/workflow-advisor.d.ts.map +1 -1
- package/dist/utils/workflow-advisor.js +50 -92
- package/dist/utils/workflow-advisor.js.map +1 -1
- package/dist/utils/workflows.d.ts.map +1 -1
- package/dist/utils/workflows.js +2 -4
- package/dist/utils/workflows.js.map +1 -1
- package/package.json +10 -1
- package/dist/gui/assets/index-CB-Ekvrs.css +0 -1
- package/dist/gui/assets/index-CixLsYSa.js +0 -26
- package/dist/utils/local-ai.d.ts +0 -27
- package/dist/utils/local-ai.d.ts.map +0 -1
- package/dist/utils/local-ai.js +0 -100
- package/dist/utils/local-ai.js.map +0 -1
- package/dist/utils/local-ai.test.d.ts +0 -2
- package/dist/utils/local-ai.test.d.ts.map +0 -1
- package/dist/utils/local-ai.test.js +0 -108
- 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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Executes the full "create workspace" flow:
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
10
|
-
* 3.
|
|
11
|
-
* 4.
|
|
12
|
-
* 5.
|
|
13
|
-
* 6.
|
|
14
|
-
* 7.
|
|
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;
|
|
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"}
|
package/dist/commands/create.js
CHANGED
|
@@ -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.
|
|
27
|
-
* 2.
|
|
28
|
-
* 3.
|
|
29
|
-
* 4.
|
|
30
|
-
* 5.
|
|
31
|
-
* 6.
|
|
32
|
-
* 7.
|
|
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 —
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
82
|
+
else {
|
|
83
|
+
branchName = await promptBranchName();
|
|
84
|
+
workspaceId = branchName;
|
|
55
85
|
}
|
|
56
|
-
// ──
|
|
57
|
-
const
|
|
58
|
-
// ── 4.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
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
|
-
|
|
67
|
-
selectedRepos.
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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,
|
|
194
|
+
const workspacePath = path.join(config.workspacesDir, workspaceId);
|
|
133
195
|
const feature = {
|
|
134
|
-
id:
|
|
196
|
+
id: workspaceId,
|
|
197
|
+
mode,
|
|
198
|
+
projectId: project?.id,
|
|
135
199
|
branchName,
|
|
136
200
|
description,
|
|
137
|
-
repos:
|
|
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 =
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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 "${
|
|
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
|
}
|