@mrpatronz/nexusflow 0.2.11 → 0.2.13

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 (111) hide show
  1. package/dist/commands/add-repo.d.ts.map +1 -1
  2. package/dist/commands/add-repo.js +24 -14
  3. package/dist/commands/add-repo.js.map +1 -1
  4. package/dist/commands/commit.js +1 -1
  5. package/dist/commands/commit.js.map +1 -1
  6. package/dist/commands/create.d.ts.map +1 -1
  7. package/dist/commands/create.js +9 -2
  8. package/dist/commands/create.js.map +1 -1
  9. package/dist/commands/diff.js +1 -1
  10. package/dist/commands/diff.js.map +1 -1
  11. package/dist/commands/doctor.js +1 -1
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/handoff.js +1 -1
  14. package/dist/commands/handoff.js.map +1 -1
  15. package/dist/commands/logs.js +1 -1
  16. package/dist/commands/logs.js.map +1 -1
  17. package/dist/commands/open.d.ts.map +1 -1
  18. package/dist/commands/open.js +21 -9
  19. package/dist/commands/open.js.map +1 -1
  20. package/dist/commands/pack.js +1 -1
  21. package/dist/commands/pack.js.map +1 -1
  22. package/dist/commands/refresh.js +13 -8
  23. package/dist/commands/refresh.js.map +1 -1
  24. package/dist/commands/remove.d.ts.map +1 -1
  25. package/dist/commands/remove.js +12 -7
  26. package/dist/commands/remove.js.map +1 -1
  27. package/dist/commands/start.js +1 -1
  28. package/dist/commands/start.js.map +1 -1
  29. package/dist/commands/status.js +1 -1
  30. package/dist/commands/status.js.map +1 -1
  31. package/dist/commands/stop.js +1 -1
  32. package/dist/commands/stop.js.map +1 -1
  33. package/dist/commands/sync.js +13 -8
  34. package/dist/commands/sync.js.map +1 -1
  35. package/dist/core/workspace.d.ts +7 -0
  36. package/dist/core/workspace.d.ts.map +1 -1
  37. package/dist/core/workspace.js +45 -1
  38. package/dist/core/workspace.js.map +1 -1
  39. package/dist/generators/antigravity.d.ts +15 -0
  40. package/dist/generators/antigravity.d.ts.map +1 -0
  41. package/dist/generators/antigravity.js +27 -0
  42. package/dist/generators/antigravity.js.map +1 -0
  43. package/dist/generators/index.d.ts +1 -0
  44. package/dist/generators/index.d.ts.map +1 -1
  45. package/dist/generators/index.js +17 -1
  46. package/dist/generators/index.js.map +1 -1
  47. package/dist/generators/skills-generator.d.ts.map +1 -1
  48. package/dist/generators/skills-generator.js +6 -1
  49. package/dist/generators/skills-generator.js.map +1 -1
  50. package/dist/gui/assets/index-CFWwqFux.js +23 -0
  51. package/dist/gui/assets/index-Dvc9QMvl.css +2 -0
  52. package/dist/gui/index.html +2 -2
  53. package/dist/orchestration/runner.d.ts +5 -6
  54. package/dist/orchestration/runner.d.ts.map +1 -1
  55. package/dist/orchestration/runner.js +105 -102
  56. package/dist/orchestration/runner.js.map +1 -1
  57. package/dist/server.d.ts.map +1 -1
  58. package/dist/server.js +57 -2
  59. package/dist/server.js.map +1 -1
  60. package/dist/utils/detect-ai.d.ts.map +1 -1
  61. package/dist/utils/detect-ai.js +4 -1
  62. package/dist/utils/detect-ai.js.map +1 -1
  63. package/dist/utils/detect-editors.d.ts.map +1 -1
  64. package/dist/utils/detect-editors.js +5 -2
  65. package/dist/utils/detect-editors.js.map +1 -1
  66. package/dist/utils/detect-editors.test.js +7 -6
  67. package/dist/utils/detect-editors.test.js.map +1 -1
  68. package/dist/utils/prompts.d.ts +1 -0
  69. package/dist/utils/prompts.d.ts.map +1 -1
  70. package/dist/utils/prompts.js +77 -15
  71. package/dist/utils/prompts.js.map +1 -1
  72. package/dist/utils/update-check.d.ts.map +1 -1
  73. package/dist/utils/update-check.js +16 -4
  74. package/dist/utils/update-check.js.map +1 -1
  75. package/extension/package.json +9 -0
  76. package/extension/src/extension.ts +37 -1
  77. package/gui/eslint.config.js +4 -0
  78. package/gui/src/App.tsx +375 -151
  79. package/gui/src/features/changes/ChangesViewer.tsx +246 -70
  80. package/gui/src/features/services/ServiceConsole.tsx +162 -71
  81. package/gui/src/features/workspace/WorkspaceList.tsx +184 -32
  82. package/gui/src/index.css +95 -2
  83. package/package.json +3 -2
  84. package/src/commands/add-repo.ts +30 -14
  85. package/src/commands/commit.ts +1 -1
  86. package/src/commands/create.ts +9 -2
  87. package/src/commands/diff.ts +1 -1
  88. package/src/commands/doctor.ts +1 -1
  89. package/src/commands/handoff.ts +1 -1
  90. package/src/commands/logs.ts +1 -1
  91. package/src/commands/open.ts +24 -9
  92. package/src/commands/pack.ts +1 -1
  93. package/src/commands/refresh.ts +16 -8
  94. package/src/commands/remove.ts +15 -7
  95. package/src/commands/start.ts +1 -1
  96. package/src/commands/status.ts +1 -1
  97. package/src/commands/stop.ts +1 -1
  98. package/src/commands/sync.ts +16 -8
  99. package/src/core/workspace.ts +48 -1
  100. package/src/generators/antigravity.ts +32 -0
  101. package/src/generators/index.ts +23 -1
  102. package/src/generators/skills-generator.ts +5 -1
  103. package/src/orchestration/runner.ts +112 -108
  104. package/src/server.ts +62 -2
  105. package/src/utils/detect-ai.ts +4 -1
  106. package/src/utils/detect-editors.test.ts +7 -6
  107. package/src/utils/detect-editors.ts +5 -2
  108. package/src/utils/prompts.ts +88 -14
  109. package/src/utils/update-check.ts +16 -4
  110. package/dist/gui/assets/index-Ca2VRMrQ.js +0 -22
  111. package/dist/gui/assets/index-fj0RJiOb.css +0 -2
@@ -5,7 +5,7 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import { execa } from 'execa';
8
- import { select, confirm } from '@inquirer/prompts';
8
+ import { select, confirm, search } from '@inquirer/prompts';
9
9
 
10
10
  import { loadConfig } from '../core/config.js';
11
11
  import { listWorkspaces } from '../core/workspace.js';
@@ -29,12 +29,20 @@ export async function openCommand(): Promise<void> {
29
29
  console.log(chalk.bold.cyan('\n📂 Open Workspace\n'));
30
30
 
31
31
  // Let user pick a workspace
32
- const selected = await select({
33
- message: 'Select a workspace to open:',
34
- choices: workspaces.map((ws) => ({
35
- name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos, ${ws.assistants.join(', ')})`)}`,
36
- value: ws.workspacePath,
37
- })),
32
+ const selected = await search({
33
+ message: 'Search and select a workspace to open:',
34
+ source: async (input) => {
35
+ const query = (input || '').toLowerCase();
36
+ const filtered = workspaces.filter(
37
+ (ws) =>
38
+ ws.branchName.toLowerCase().includes(query) ||
39
+ ws.workspacePath.toLowerCase().includes(query)
40
+ );
41
+ return filtered.map((ws) => ({
42
+ name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos, ${ws.assistants.join(', ')})`)}`,
43
+ value: ws.workspacePath,
44
+ }));
45
+ },
38
46
  });
39
47
 
40
48
  // Let user pick an editor
@@ -43,7 +51,10 @@ export async function openCommand(): Promise<void> {
43
51
 
44
52
  if (editor) {
45
53
  try {
46
- await execa(editor.command, [selected], { stdio: 'ignore' });
54
+ await execa(editor.command, [selected], {
55
+ stdio: 'ignore',
56
+ shell: process.platform === 'win32',
57
+ });
47
58
  console.log(chalk.green(`\n✅ Opened ${selected} in ${editor.name}\n`));
48
59
  } catch {
49
60
  console.log(chalk.yellow(`\n⚠️ Could not open editor. Navigate manually:`));
@@ -101,7 +112,11 @@ export async function openCommand(): Promise<void> {
101
112
  }
102
113
 
103
114
  try {
104
- await execa(cmdName, cmdArgs, { cwd: selected, stdio: 'inherit' });
115
+ await execa(cmdName, cmdArgs, {
116
+ cwd: selected,
117
+ stdio: 'inherit',
118
+ shell: process.platform === 'win32',
119
+ });
105
120
  console.log(chalk.green(`\n👋 Exited ${assistant} session.`));
106
121
  } catch {
107
122
  const fullCmd = [cmdName, ...cmdArgs].join(' ');
@@ -51,7 +51,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
51
51
  }
52
52
 
53
53
  const cwdFeature = await loadFeatureConfig(process.cwd());
54
- if (cwdFeature) return process.cwd();
54
+ if (cwdFeature) return cwdFeature.workspacePath;
55
55
 
56
56
  const config = await loadConfig();
57
57
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { select } from '@inquirer/prompts';
2
+ import { select, search } from '@inquirer/prompts';
3
3
  import * as path from 'node:path';
4
4
  import * as fs from 'node:fs/promises';
5
5
 
@@ -117,7 +117,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
117
117
  }
118
118
 
119
119
  const cwdFeature = await loadFeatureConfig(process.cwd());
120
- if (cwdFeature) return process.cwd();
120
+ if (cwdFeature) return cwdFeature.workspacePath;
121
121
 
122
122
  const config = await loadConfig();
123
123
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -127,12 +127,20 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
127
127
  return null;
128
128
  }
129
129
 
130
- const selected = await select({
131
- message: 'Select a workspace to refresh:',
132
- choices: workspaces.map((ws) => ({
133
- name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
134
- value: ws.workspacePath,
135
- })),
130
+ const selected = await search({
131
+ message: 'Search and select a workspace to refresh:',
132
+ source: async (input) => {
133
+ const query = (input || '').toLowerCase();
134
+ const filtered = workspaces.filter(
135
+ (ws) =>
136
+ ws.branchName.toLowerCase().includes(query) ||
137
+ ws.workspacePath.toLowerCase().includes(query)
138
+ );
139
+ return filtered.map((ws) => ({
140
+ name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
141
+ value: ws.workspacePath,
142
+ }));
143
+ },
136
144
  });
137
145
 
138
146
  return selected;
@@ -5,7 +5,7 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import ora from 'ora';
8
- import { select, confirm } from '@inquirer/prompts';
8
+ import { select, confirm, search } from '@inquirer/prompts';
9
9
  import * as path from 'node:path';
10
10
  import * as fs from 'node:fs/promises';
11
11
 
@@ -52,12 +52,20 @@ export async function removeCommand(workspaceArg?: string): Promise<void> {
52
52
  return;
53
53
  }
54
54
 
55
- const selected = await select({
56
- message: 'Select a workspace to delete:',
57
- choices: workspaces.map((ws) => ({
58
- name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
59
- value: ws,
60
- })),
55
+ const selected = await search({
56
+ message: 'Search and select a workspace to delete:',
57
+ source: async (input) => {
58
+ const query = (input || '').toLowerCase();
59
+ const filtered = workspaces.filter(
60
+ (ws) =>
61
+ ws.branchName.toLowerCase().includes(query) ||
62
+ ws.workspacePath.toLowerCase().includes(query)
63
+ );
64
+ return filtered.map((ws) => ({
65
+ name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
66
+ value: ws,
67
+ }));
68
+ },
61
69
  });
62
70
 
63
71
  workspacePath = selected.workspacePath;
@@ -93,7 +93,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
93
93
 
94
94
  // Check if CWD is a workspace
95
95
  const cwdFeature = await loadFeatureConfig(process.cwd());
96
- if (cwdFeature) return process.cwd();
96
+ if (cwdFeature) return cwdFeature.workspacePath;
97
97
 
98
98
  // Otherwise, list workspaces and let user pick
99
99
  const config = await loadConfig();
@@ -32,7 +32,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
32
32
  if (workspaceArg) return workspaceArg;
33
33
 
34
34
  const cwdFeature = await loadFeatureConfig(process.cwd());
35
- if (cwdFeature) return process.cwd();
35
+ if (cwdFeature) return cwdFeature.workspacePath;
36
36
 
37
37
  const config = await loadConfig();
38
38
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -33,7 +33,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
33
33
  if (workspaceArg) return workspaceArg;
34
34
 
35
35
  const cwdFeature = await loadFeatureConfig(process.cwd());
36
- if (cwdFeature) return process.cwd();
36
+ if (cwdFeature) return cwdFeature.workspacePath;
37
37
 
38
38
  const config = await loadConfig();
39
39
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from 'chalk';
7
- import { select } from '@inquirer/prompts';
7
+ import { select, search } from '@inquirer/prompts';
8
8
  import * as path from 'node:path';
9
9
  import * as fs from 'node:fs/promises';
10
10
 
@@ -123,7 +123,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
123
123
  }
124
124
 
125
125
  const cwdFeature = await loadFeatureConfig(process.cwd());
126
- if (cwdFeature) return process.cwd();
126
+ if (cwdFeature) return cwdFeature.workspacePath;
127
127
 
128
128
  const config = await loadConfig();
129
129
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -133,12 +133,20 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
133
133
  return null;
134
134
  }
135
135
 
136
- const selected = await select({
137
- message: 'Select a workspace to sync:',
138
- choices: workspaces.map((ws) => ({
139
- name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
140
- value: ws.workspacePath,
141
- })),
136
+ const selected = await search({
137
+ message: 'Search and select a workspace to sync:',
138
+ source: async (input) => {
139
+ const query = (input || '').toLowerCase();
140
+ const filtered = workspaces.filter(
141
+ (ws) =>
142
+ ws.branchName.toLowerCase().includes(query) ||
143
+ ws.workspacePath.toLowerCase().includes(query)
144
+ );
145
+ return filtered.map((ws) => ({
146
+ name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
147
+ value: ws.workspacePath,
148
+ }));
149
+ },
142
150
  });
143
151
 
144
152
  return selected;
@@ -82,6 +82,27 @@ export async function createWorkspace(
82
82
  console.warn('Warning: Failed to create .vscode/settings.json:', error);
83
83
  }
84
84
 
85
+ // Create .cursor/mcp.json for workspace-local Cursor MCP configuration
86
+ try {
87
+ const cursorDir = path.join(workspacePath, '.cursor');
88
+ await fs.mkdir(cursorDir, { recursive: true });
89
+ const cursorMcp = {
90
+ "mcpServers": {
91
+ "nexusflow": {
92
+ "command": "npx",
93
+ "args": ["-y", "@mrpatronz/nexusflow", "mcp", "run", workspacePath]
94
+ }
95
+ }
96
+ };
97
+ await fs.writeFile(
98
+ path.join(cursorDir, 'mcp.json'),
99
+ JSON.stringify(cursorMcp, null, 2) + '\n',
100
+ 'utf-8'
101
+ );
102
+ } catch (error) {
103
+ console.warn('Warning: Failed to create .cursor/mcp.json:', error);
104
+ }
105
+
85
106
  // Create a worktree for each repo inside the workspace.
86
107
  for (const repo of repos) {
87
108
  const worktreeTarget = path.join(workspacePath, repo.name);
@@ -173,8 +194,10 @@ export async function saveFeatureConfig(
173
194
  export async function loadFeatureConfig(
174
195
  workspacePath: string,
175
196
  ): Promise<Feature | null> {
176
- const manifestPath = path.join(workspacePath, MANIFEST_FILE);
197
+ const rootDir = await findWorkspaceRoot(workspacePath);
198
+ if (!rootDir) return null;
177
199
 
200
+ const manifestPath = path.join(rootDir, MANIFEST_FILE);
178
201
  try {
179
202
  const raw = await fs.readFile(manifestPath, 'utf-8');
180
203
  return JSON.parse(raw) as Feature;
@@ -183,6 +206,30 @@ export async function loadFeatureConfig(
183
206
  }
184
207
  }
185
208
 
209
+ /**
210
+ * Traverses up parent directories to find a directory containing `nexusflow.json`.
211
+ *
212
+ * @param startPath - Path to start searching from.
213
+ * @returns Absolute path to the workspace root directory, or null if not found.
214
+ */
215
+ export async function findWorkspaceRoot(startPath: string): Promise<string | null> {
216
+ let current = path.resolve(startPath);
217
+ while (true) {
218
+ const manifestPath = path.join(current, MANIFEST_FILE);
219
+ try {
220
+ await fs.access(manifestPath);
221
+ return current;
222
+ } catch {
223
+ const parent = path.dirname(current);
224
+ if (parent === current) {
225
+ break; // Reached root directory
226
+ }
227
+ current = parent;
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+
186
233
  /**
187
234
  * Resolves a repo path to a full RepoInfo object.
188
235
  *
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @module generators/antigravity
3
+ * Generates an AGENTS.md file for Antigravity.
4
+ */
5
+
6
+ import path from 'node:path';
7
+ import fse from 'fs-extra';
8
+ import type { WorkspaceContext } from '../types.js';
9
+ import { buildContextContent } from './base.js';
10
+
11
+ /**
12
+ * Generates an `AGENTS.md` file at the workspace root.
13
+ *
14
+ * Antigravity reads this file for persistent context about the workspace.
15
+ *
16
+ * @param ctx - The workspace context (feature + repos + analysis).
17
+ * @param workspacePath - Absolute path to the workspace root directory.
18
+ */
19
+ export async function generateAntigravityConfig(
20
+ ctx: WorkspaceContext,
21
+ workspacePath: string,
22
+ ): Promise<void> {
23
+ const content = buildContextContent(ctx);
24
+ const filePath = path.join(workspacePath, 'AGENTS.md');
25
+
26
+ try {
27
+ await fse.writeFile(filePath, content, 'utf-8');
28
+ } catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`Failed to write AGENTS.md: ${message}`);
31
+ }
32
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import fse from 'fs-extra';
4
4
  import type { AIAssistant, WorkspaceContext } from '../types.js';
5
5
  import { generateClaudeConfig } from './claude.js';
6
+ import { generateAntigravityConfig } from './antigravity.js';
6
7
  import { generateCodexConfig } from './codex.js';
7
8
  import { generateCopilotConfig } from './copilot.js';
8
9
  import { generateCursorConfig } from './cursor.js';
@@ -19,7 +20,7 @@ const GENERATORS: Record<
19
20
  }
20
21
  > = {
21
22
  claude: { generate: generateClaudeConfig, outputFile: 'CLAUDE.md' },
22
- antigravity: { generate: generateClaudeConfig, outputFile: 'CLAUDE.md' },
23
+ antigravity: { generate: generateAntigravityConfig, outputFile: 'AGENTS.md' },
23
24
  codex: { generate: generateCodexConfig, outputFile: 'AGENTS.md' },
24
25
  copilot: { generate: generateCopilotConfig, outputFile: '.github/copilot-instructions.md' },
25
26
  cursor: { generate: generateCursorConfig, outputFile: '.cursor/rules/nexusflow.mdc' },
@@ -177,6 +178,7 @@ export async function generateContextFiles(
177
178
  }
178
179
  }
179
180
 
181
+ // Generate configuration for each selected assistant
180
182
  for (const assistant of assistants) {
181
183
  const entry = GENERATORS[assistant];
182
184
 
@@ -195,6 +197,25 @@ export async function generateContextFiles(
195
197
  }
196
198
  }
197
199
 
200
+ // Ensure AGENTS.md is always generated if it is not already generated as a primary assistant file,
201
+ // since it is the universal standard for AI coding agents.
202
+ const hasAgentsGen = assistants.includes('antigravity') || assistants.includes('codex');
203
+ if (!hasAgentsGen) {
204
+ try {
205
+ await generateAntigravityConfig(ctx, workspacePath);
206
+ console.log(
207
+ chalk.green(' ✔'),
208
+ `Generated fallback ${chalk.bold('AGENTS.md')} for harness context`,
209
+ );
210
+ } catch (error) {
211
+ const message = error instanceof Error ? error.message : String(error);
212
+ console.error(
213
+ chalk.red(' ✖'),
214
+ `Failed to generate fallback AGENTS.md: ${message}`,
215
+ );
216
+ }
217
+ }
218
+
198
219
 
199
220
  // Generate implementation plan from dependency analysis (if analysis data available)
200
221
  try {
@@ -221,6 +242,7 @@ export async function generateContextFiles(
221
242
 
222
243
  // Re-export individual generators for direct use
223
244
  export { generateClaudeConfig } from './claude.js';
245
+ export { generateAntigravityConfig } from './antigravity.js';
224
246
  export { generateCodexConfig } from './codex.js';
225
247
  export { generateCopilotConfig } from './copilot.js';
226
248
  export { generateCursorConfig } from './cursor.js';
@@ -175,10 +175,14 @@ async function deploySkill(
175
175
  };
176
176
  const title = titleMap[skillName] || skillName;
177
177
 
178
- if (assistant === 'claude' || assistant === 'antigravity') {
178
+ if (assistant === 'claude') {
179
179
  const skillDir = path.join(workspacePath, '.claude', 'skills', skillName);
180
180
  await fse.ensureDir(skillDir);
181
181
  await fse.writeFile(path.join(skillDir, 'SKILL.md'), content, 'utf-8');
182
+ } else if (assistant === 'antigravity') {
183
+ const skillDir = path.join(workspacePath, '.agents', 'skills', skillName);
184
+ await fse.ensureDir(skillDir);
185
+ await fse.writeFile(path.join(skillDir, 'SKILL.md'), content, 'utf-8');
182
186
  } else if (assistant === 'cursor') {
183
187
  const ruleDir = path.join(workspacePath, '.cursor', 'rules');
184
188
  await fse.ensureDir(ruleDir);