@mrpatronz/nexusflow 0.2.13 → 0.2.18
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/.agents/skills/ccc/SKILL.md +71 -0
- package/.agents/skills/ccc/references/management.md +110 -0
- package/.agents/skills/ccc/references/settings.md +126 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/dependabot-merge.yml +53 -0
- package/.github/workflows/release-desktop.yml +67 -0
- package/.github/workflows/release-npm.yml +65 -0
- package/.github/workflows/release-vscode.yml +61 -0
- package/GETTING_STARTED.md +5 -0
- package/README.md +53 -0
- package/desktop/build-installer.js +228 -0
- package/desktop/build.js +118 -0
- package/desktop/installer/nexusflow.iss +35 -0
- package/desktop/neutralino.config.json +39 -0
- package/desktop/package-lock.json +1060 -0
- package/desktop/package.json +14 -0
- package/dist/commands/adapter.d.ts +27 -0
- package/dist/commands/adapter.d.ts.map +1 -0
- package/dist/commands/adapter.js +318 -0
- package/dist/commands/adapter.js.map +1 -0
- package/dist/commands/add-repo.d.ts.map +1 -1
- package/dist/commands/add-repo.js +0 -1
- package/dist/commands/add-repo.js.map +1 -1
- package/dist/commands/commands.test.js +2 -2
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/config.d.ts +22 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +14 -17
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/desktop.d.ts +12 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +119 -0
- package/dist/commands/desktop.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +11 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/refresh.d.ts +1 -0
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +2 -14
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +1 -8
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tui.d.ts +8 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +439 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/commands/ui.d.ts +3 -1
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +76 -19
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/adapters/local-storage.d.ts +14 -0
- package/dist/core/adapters/local-storage.d.ts.map +1 -0
- package/dist/core/adapters/local-storage.js +58 -0
- package/dist/core/adapters/local-storage.js.map +1 -0
- package/dist/core/adapters/obsidian-storage.d.ts +28 -0
- package/dist/core/adapters/obsidian-storage.d.ts.map +1 -0
- package/dist/core/adapters/obsidian-storage.js +135 -0
- package/dist/core/adapters/obsidian-storage.js.map +1 -0
- package/dist/core/adapters/registry.d.ts +8 -0
- package/dist/core/adapters/registry.d.ts.map +1 -0
- package/dist/core/adapters/registry.js +37 -0
- package/dist/core/adapters/registry.js.map +1 -0
- package/dist/core/adapters/storage.test.d.ts +2 -0
- package/dist/core/adapters/storage.test.d.ts.map +1 -0
- package/dist/core/adapters/storage.test.js +106 -0
- package/dist/core/adapters/storage.test.js.map +1 -0
- package/dist/core/adapters/vault-storage.d.ts +16 -0
- package/dist/core/adapters/vault-storage.d.ts.map +1 -0
- package/dist/core/adapters/vault-storage.js +77 -0
- package/dist/core/adapters/vault-storage.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +28 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/plugins/index.d.ts +14 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +2 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +2 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +34 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/ports/storage.d.ts +59 -0
- package/dist/core/ports/storage.d.ts.map +1 -0
- package/dist/core/ports/storage.js +10 -0
- package/dist/core/ports/storage.js.map +1 -0
- package/dist/core/storage.d.ts +10 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +29 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +110 -16
- package/dist/core/workspace.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +19 -6
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/diff-context.d.ts +6 -0
- package/dist/generators/diff-context.d.ts.map +1 -0
- package/dist/generators/diff-context.js +68 -0
- package/dist/generators/diff-context.js.map +1 -0
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +75 -18
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts.map +1 -1
- package/dist/generators/map-generator.js +7 -11
- package/dist/generators/map-generator.js.map +1 -1
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +3 -5
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/gui/app_logo.png +0 -0
- package/dist/gui/assets/index-CB-jWded.css +1 -0
- package/dist/gui/assets/index-DDXpZZu3.js +25 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +174 -20
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +212 -28
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +173 -38
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +11 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/git.d.ts +16 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +52 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/local-ai.d.ts.map +1 -1
- package/dist/utils/local-ai.js +7 -3
- package/dist/utils/local-ai.js.map +1 -1
- package/dist/utils/multi-git.d.ts +2 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +5 -2
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/update-check.d.ts +3 -1
- package/dist/utils/update-check.d.ts.map +1 -1
- package/dist/utils/update-check.js +23 -34
- package/dist/utils/update-check.js.map +1 -1
- package/dist/utils/workflow-advisor.d.ts +16 -0
- package/dist/utils/workflow-advisor.d.ts.map +1 -0
- package/dist/utils/workflow-advisor.js +100 -0
- package/dist/utils/workflow-advisor.js.map +1 -0
- package/dist/utils/workflows.d.ts +11 -0
- package/dist/utils/workflows.d.ts.map +1 -0
- package/dist/utils/workflows.js +156 -0
- package/dist/utils/workflows.js.map +1 -0
- package/dist/utils/workflows.test.d.ts +2 -0
- package/dist/utils/workflows.test.d.ts.map +1 -0
- package/dist/utils/workflows.test.js +86 -0
- package/dist/utils/workflows.test.js.map +1 -0
- package/extension/media/icon.svg +1 -0
- package/extension/package-lock.json +4893 -1660
- package/extension/package.json +58 -11
- package/extension/src/extension.ts +476 -27
- package/extension/tsconfig.json +4 -2
- package/gui/e2e/wizard.spec.ts +19 -0
- package/gui/package-lock.json +342 -381
- package/gui/package.json +12 -12
- package/gui/public/app_logo.png +0 -0
- package/gui/src/App.tsx +991 -81
- package/gui/src/features/workspace/WorkspaceList.tsx +30 -30
- package/gui/src/index.css +64 -22
- package/gui/src/main.tsx +89 -5
- package/package.json +11 -12
- package/resources/workflows/plan-implement-review.md +8 -0
- package/resources/workflows/research-verify.md +6 -0
- package/resources/workflows/solo-developer.md +3 -0
- package/skills-lock.json +11 -0
- package/src/commands/adapter.ts +348 -0
- package/src/commands/add-repo.ts +0 -1
- package/src/commands/commands.test.ts +3 -2
- package/src/commands/config.ts +52 -0
- package/src/commands/create.ts +14 -17
- package/src/commands/desktop.ts +128 -0
- package/src/commands/doctor.ts +13 -0
- package/src/commands/mcp.ts +2 -0
- package/src/commands/refresh.ts +4 -14
- package/src/commands/sync.ts +2 -8
- package/src/commands/tui.ts +484 -0
- package/src/commands/ui.ts +83 -19
- package/src/core/adapters/local-storage.ts +66 -0
- package/src/core/adapters/obsidian-storage.ts +151 -0
- package/src/core/adapters/registry.ts +44 -0
- package/src/core/adapters/storage.test.ts +174 -0
- package/src/core/adapters/vault-storage.ts +86 -0
- package/src/core/config.ts +29 -4
- package/src/core/plugins/index.ts +17 -0
- package/src/core/plugins/loader.ts +36 -0
- package/src/core/ports/storage.ts +71 -0
- package/src/core/storage.ts +37 -0
- package/src/core/workspace.ts +122 -15
- package/src/generators/base.ts +21 -5
- package/src/generators/diff-context.ts +75 -0
- package/src/generators/index.ts +93 -21
- package/src/generators/map-generator.ts +7 -10
- package/src/generators/plan-generator.ts +6 -4
- package/src/index.ts +178 -21
- package/src/server.test.ts +193 -39
- package/src/server.ts +238 -28
- package/src/types.ts +14 -2
- package/src/utils/git.ts +56 -0
- package/src/utils/local-ai.ts +8 -3
- package/src/utils/multi-git.ts +16 -9
- package/src/utils/update-check.ts +42 -31
- package/src/utils/workflow-advisor.ts +118 -0
- package/src/utils/workflows.test.ts +114 -0
- package/src/utils/workflows.ts +178 -0
- package/.github/workflows/release.yml +0 -100
- package/dist/commands/pack.d.ts +0 -7
- package/dist/commands/pack.d.ts.map +0 -1
- package/dist/commands/pack.js +0 -64
- package/dist/commands/pack.js.map +0 -1
- package/dist/core/packer.d.ts +0 -14
- package/dist/core/packer.d.ts.map +0 -1
- package/dist/core/packer.js +0 -87
- package/dist/core/packer.js.map +0 -1
- package/dist/core/packer.test.d.ts +0 -2
- package/dist/core/packer.test.d.ts.map +0 -1
- package/dist/core/packer.test.js +0 -82
- package/dist/core/packer.test.js.map +0 -1
- package/dist/gui/assets/index-CFWwqFux.js +0 -23
- package/dist/gui/assets/index-Dvc9QMvl.css +0 -2
- package/src/commands/pack.ts +0 -73
- package/src/core/packer.test.ts +0 -99
- package/src/core/packer.ts +0 -107
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import fse from 'fs-extra';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
+
import { writeWorkspaceFile } from '../core/storage.js';
|
|
10
11
|
import type {
|
|
11
12
|
WorkspaceContext,
|
|
12
13
|
ProjectAnalysis,
|
|
@@ -187,8 +188,10 @@ export async function generateImplementationPlan(
|
|
|
187
188
|
.map((n) => `- ${n}`),
|
|
188
189
|
'',
|
|
189
190
|
];
|
|
190
|
-
await
|
|
191
|
-
|
|
191
|
+
await writeWorkspaceFile(
|
|
192
|
+
workspacePath,
|
|
193
|
+
feature.id,
|
|
194
|
+
'nexusflow-plan.md',
|
|
192
195
|
lines.join('\n'),
|
|
193
196
|
);
|
|
194
197
|
console.log(chalk.green(' ✔'), 'Generated nexusflow-plan.md');
|
|
@@ -420,8 +423,7 @@ export async function generateImplementationPlan(
|
|
|
420
423
|
md.push('');
|
|
421
424
|
|
|
422
425
|
// ── Write file ──────────────────────────────────────────────────────
|
|
423
|
-
|
|
424
|
-
await fse.outputFile(outPath, md.join('\n'));
|
|
426
|
+
await writeWorkspaceFile(workspacePath, feature.id, 'nexusflow-plan.md', md.join('\n'));
|
|
425
427
|
console.log(chalk.green(' ✔'), 'Generated nexusflow-plan.md');
|
|
426
428
|
} catch (error) {
|
|
427
429
|
const message = error instanceof Error ? error.message : String(error);
|
package/src/index.ts
CHANGED
|
@@ -20,16 +20,21 @@ import { stopCommand } from './commands/stop.js';
|
|
|
20
20
|
import { logsCommand } from './commands/logs.js';
|
|
21
21
|
import { statusCommand } from './commands/status.js';
|
|
22
22
|
import { uiCommand } from './commands/ui.js';
|
|
23
|
+
import { tuiCommand } from './commands/tui.js';
|
|
23
24
|
import { syncCommand } from './commands/sync.js';
|
|
24
25
|
import { commitCommand } from './commands/commit.js';
|
|
25
26
|
import { diffCommand } from './commands/diff.js';
|
|
26
|
-
import { packCommand } from './commands/pack.js';
|
|
27
27
|
import { removeCommand } from './commands/remove.js';
|
|
28
|
+
import { loadConfig } from './core/config.js';
|
|
29
|
+
import { loadPlugins } from './core/plugins/loader.js';
|
|
28
30
|
import { addRepoCommand } from './commands/add-repo.js';
|
|
29
31
|
import { mcpRunCommand, mcpSetupCommand } from './commands/mcp.js';
|
|
30
32
|
import { handoffCommand } from './commands/handoff.js';
|
|
31
33
|
import { refreshCommand } from './commands/refresh.js';
|
|
32
34
|
import { doctorCommand } from './commands/doctor.js';
|
|
35
|
+
import { desktopCommand } from './commands/desktop.js';
|
|
36
|
+
import { configShowCommand, configGetCommand, configSetCommand } from './commands/config.js';
|
|
37
|
+
import { adapterListCommand, adapterUseCommand, adapterInfoCommand, adapterInitCommand } from './commands/adapter.js';
|
|
33
38
|
import { getCurrentVersion, checkForUpdates, printUpdateBanner } from './utils/update-check.js';
|
|
34
39
|
|
|
35
40
|
const program = new Command();
|
|
@@ -177,7 +182,9 @@ program
|
|
|
177
182
|
.command('ui')
|
|
178
183
|
.description('Open the web GUI dashboard for NexusFlow')
|
|
179
184
|
.option('-p, --port <number>', 'Port to run the dashboard server on', '3000')
|
|
180
|
-
.
|
|
185
|
+
.option('-d, --daemon', 'Run the dashboard server in the background (daemon mode)')
|
|
186
|
+
.option('--server-only', 'Start the dashboard server without opening the browser')
|
|
187
|
+
.action(async (options: { port?: string; daemon?: boolean; serverOnly?: boolean }) => {
|
|
181
188
|
try {
|
|
182
189
|
await uiCommand(options);
|
|
183
190
|
} catch (error) {
|
|
@@ -186,6 +193,34 @@ program
|
|
|
186
193
|
}
|
|
187
194
|
});
|
|
188
195
|
|
|
196
|
+
program
|
|
197
|
+
.command('dashboard')
|
|
198
|
+
.alias('dash')
|
|
199
|
+
.description('Instantly open the web GUI dashboard in your default browser')
|
|
200
|
+
.option('-p, --port <number>', 'Port to run the dashboard server on', '3000')
|
|
201
|
+
.action(async (options: { port?: string }) => {
|
|
202
|
+
try {
|
|
203
|
+
await uiCommand({ ...options, daemon: true });
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(error);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
program
|
|
212
|
+
.command('tui')
|
|
213
|
+
.description('Open the interactive terminal GUI (TUI) dashboard')
|
|
214
|
+
.argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
|
|
215
|
+
.action(async (workspace?: string) => {
|
|
216
|
+
try {
|
|
217
|
+
await tuiCommand({ workspace });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(error);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
189
224
|
program
|
|
190
225
|
.command('sync')
|
|
191
226
|
.description('Sync all repositories in a workspace')
|
|
@@ -240,23 +275,7 @@ program
|
|
|
240
275
|
}
|
|
241
276
|
});
|
|
242
277
|
|
|
243
|
-
|
|
244
|
-
.command('pack')
|
|
245
|
-
.description('Pack the workspace codebase into a single token-efficient XML file for AI consumption')
|
|
246
|
-
.argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
|
|
247
|
-
.option('--no-compress', 'Do not compress files (strip comments, empty lines)')
|
|
248
|
-
.action(async (workspace: string | undefined, options: { compress?: boolean }) => {
|
|
249
|
-
try {
|
|
250
|
-
await packCommand(workspace, options);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (error instanceof Error && error.message.includes('User force closed')) {
|
|
253
|
-
console.log('\nCancelled.');
|
|
254
|
-
process.exit(0);
|
|
255
|
-
}
|
|
256
|
-
console.error(error);
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
});
|
|
278
|
+
// Pack command removed.
|
|
260
279
|
|
|
261
280
|
program
|
|
262
281
|
.command('remove')
|
|
@@ -313,7 +332,8 @@ program
|
|
|
313
332
|
.description('Refresh workspace context, maps, plans and handoff files')
|
|
314
333
|
.argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
|
|
315
334
|
.option('-r, --repo <repo>', 'Only refresh the map for a specific repository')
|
|
316
|
-
.
|
|
335
|
+
.option('-b, --base', 'Only refresh base-layer maps and codebase knowledge from main')
|
|
336
|
+
.action(async (workspace: string | undefined, options: { repo?: string; base?: boolean }) => {
|
|
317
337
|
try {
|
|
318
338
|
await refreshCommand(options, workspace);
|
|
319
339
|
} catch (error) {
|
|
@@ -335,6 +355,133 @@ program
|
|
|
335
355
|
}
|
|
336
356
|
});
|
|
337
357
|
|
|
358
|
+
program
|
|
359
|
+
.command('desktop')
|
|
360
|
+
.description('Launch the NexusFlow desktop application')
|
|
361
|
+
.action(async () => {
|
|
362
|
+
try {
|
|
363
|
+
await desktopCommand();
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(error);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
// Config command group
|
|
370
|
+
const configCmd = program.command('config').description('View and update NexusFlow configuration');
|
|
371
|
+
|
|
372
|
+
configCmd
|
|
373
|
+
.command('show')
|
|
374
|
+
.description('Display the current configuration')
|
|
375
|
+
.action(async () => {
|
|
376
|
+
try {
|
|
377
|
+
await configShowCommand();
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error(error);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
configCmd
|
|
385
|
+
.command('get')
|
|
386
|
+
.description('Get a specific configuration key')
|
|
387
|
+
.argument('<key>', 'Configuration key to read')
|
|
388
|
+
.action(async (key: string) => {
|
|
389
|
+
try {
|
|
390
|
+
await configGetCommand(key);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(error);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
configCmd
|
|
398
|
+
.command('set')
|
|
399
|
+
.description('Set a configuration key to a value')
|
|
400
|
+
.argument('<key>', 'Configuration key to set')
|
|
401
|
+
.argument('<value>', 'Value to assign')
|
|
402
|
+
.action(async (key: string, value: string) => {
|
|
403
|
+
try {
|
|
404
|
+
await configSetCommand(key, value);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error(error);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Default action when 'nexusflow config' is run without a subcommand
|
|
412
|
+
configCmd.action(async () => {
|
|
413
|
+
try {
|
|
414
|
+
await configShowCommand();
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error(error);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Adapter command group
|
|
422
|
+
const adapterCmd = program.command('adapter').description('Manage storage adapters — list, switch, configure, or create new ones');
|
|
423
|
+
|
|
424
|
+
adapterCmd
|
|
425
|
+
.command('list')
|
|
426
|
+
.description('List all available storage adapters')
|
|
427
|
+
.action(async () => {
|
|
428
|
+
try {
|
|
429
|
+
await adapterListCommand();
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error(error);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
adapterCmd
|
|
437
|
+
.command('use')
|
|
438
|
+
.description('Switch to a different storage adapter (prompts for config if needed)')
|
|
439
|
+
.argument('<name>', 'Adapter name to activate')
|
|
440
|
+
.action(async (name: string) => {
|
|
441
|
+
try {
|
|
442
|
+
await adapterUseCommand(name);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error(error);
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
adapterCmd
|
|
450
|
+
.command('info')
|
|
451
|
+
.description('Show detailed information about an adapter')
|
|
452
|
+
.argument('<name>', 'Adapter name to inspect')
|
|
453
|
+
.action(async (name: string) => {
|
|
454
|
+
try {
|
|
455
|
+
await adapterInfoCommand(name);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
console.error(error);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
adapterCmd
|
|
463
|
+
.command('init')
|
|
464
|
+
.description('Scaffold a new adapter plugin project')
|
|
465
|
+
.argument('<name>', 'Name for the new adapter')
|
|
466
|
+
.action(async (name: string) => {
|
|
467
|
+
try {
|
|
468
|
+
await adapterInitCommand(name);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error(error);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Default action when 'nexusflow adapter' is run without a subcommand
|
|
476
|
+
adapterCmd.action(async () => {
|
|
477
|
+
try {
|
|
478
|
+
await adapterListCommand();
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error(error);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
338
485
|
const mcp = program.command('mcp').description('Manage the NexusFlow MCP Server for AI assistants');
|
|
339
486
|
|
|
340
487
|
mcp
|
|
@@ -378,5 +525,15 @@ program.hook('postAction', async (thisCommand, actionCommand) => {
|
|
|
378
525
|
}
|
|
379
526
|
});
|
|
380
527
|
|
|
381
|
-
|
|
528
|
+
async function bootstrap() {
|
|
529
|
+
try {
|
|
530
|
+
const config = await loadConfig();
|
|
531
|
+
if (config.plugins && config.plugins.length > 0) {
|
|
532
|
+
await loadPlugins(program, config.plugins);
|
|
533
|
+
}
|
|
534
|
+
} catch {}
|
|
535
|
+
await program.parseAsync(process.argv);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
bootstrap();
|
|
382
539
|
|
package/src/server.test.ts
CHANGED
|
@@ -9,7 +9,8 @@ import * as localAi from './utils/local-ai.js';
|
|
|
9
9
|
import * as updateCheck from './utils/update-check.js';
|
|
10
10
|
import * as analyzers from './analyzers/index.js';
|
|
11
11
|
import * as generators from './generators/index.js';
|
|
12
|
-
import * as
|
|
12
|
+
import * as workflows from './utils/workflows.js';
|
|
13
|
+
import * as detectAi from './utils/detect-ai.js';
|
|
13
14
|
|
|
14
15
|
// Mock dependencies
|
|
15
16
|
vi.mock('node:fs/promises');
|
|
@@ -21,7 +22,10 @@ vi.mock('./utils/local-ai.js');
|
|
|
21
22
|
vi.mock('./utils/update-check.js');
|
|
22
23
|
vi.mock('./analyzers/index.js');
|
|
23
24
|
vi.mock('./generators/index.js');
|
|
24
|
-
vi.mock('./
|
|
25
|
+
vi.mock('./utils/workflows.js');
|
|
26
|
+
vi.mock('./utils/detect-ai.js', () => ({
|
|
27
|
+
detectAIAssistants: vi.fn().mockResolvedValue([])
|
|
28
|
+
}));
|
|
25
29
|
|
|
26
30
|
describe('Server API Endpoints Unit Tests', () => {
|
|
27
31
|
beforeEach(() => {
|
|
@@ -200,6 +204,25 @@ describe('Server API Endpoints Unit Tests', () => {
|
|
|
200
204
|
});
|
|
201
205
|
});
|
|
202
206
|
|
|
207
|
+
describe('GET /api/adapters', () => {
|
|
208
|
+
it('should return all registered storage adapters with meta', async () => {
|
|
209
|
+
const response = await app.request('/api/adapters');
|
|
210
|
+
expect(response.status).toBe(200);
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
expect(data.adapters).toBeDefined();
|
|
213
|
+
expect(Array.isArray(data.adapters)).toBe(true);
|
|
214
|
+
// It should include at least 'local', 'central-vault', and 'obsidian'
|
|
215
|
+
const names = data.adapters.map((a: any) => a.name);
|
|
216
|
+
expect(names).toContain('local');
|
|
217
|
+
expect(names).toContain('central-vault');
|
|
218
|
+
expect(names).toContain('obsidian');
|
|
219
|
+
|
|
220
|
+
const obsidian = data.adapters.find((a: any) => a.name === 'obsidian');
|
|
221
|
+
expect(obsidian.displayName).toBe('Obsidian Vault');
|
|
222
|
+
expect(obsidian.configFields.length).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
203
226
|
describe('POST /api/config', () => {
|
|
204
227
|
it('should validate endpoint domain safety', async () => {
|
|
205
228
|
const response = await app.request('/api/config', {
|
|
@@ -215,7 +238,7 @@ describe('Server API Endpoints Unit Tests', () => {
|
|
|
215
238
|
|
|
216
239
|
expect(response.status).toBe(400);
|
|
217
240
|
const data = await response.json();
|
|
218
|
-
expect(data.error).toContain('Local AI endpoint must be localhost, 127.0.0.1, or a private LAN IP.');
|
|
241
|
+
expect(data.error).toContain('Local AI endpoint must be HTTPS, localhost, 127.0.0.1, or a private LAN IP.');
|
|
219
242
|
});
|
|
220
243
|
|
|
221
244
|
it('should save safe endpoint and config', async () => {
|
|
@@ -360,22 +383,22 @@ describe('Server API Endpoints Unit Tests', () => {
|
|
|
360
383
|
const response = await app.request('/api/updates/install', {
|
|
361
384
|
method: 'POST',
|
|
362
385
|
headers: { 'Content-Type': 'application/json' },
|
|
363
|
-
body: JSON.stringify({ toolId: '
|
|
386
|
+
body: JSON.stringify({ toolId: 'nexusflow' })
|
|
364
387
|
});
|
|
365
388
|
|
|
366
389
|
expect(response.status).toBe(200);
|
|
367
390
|
const data = await response.json();
|
|
368
391
|
expect(data.success).toBe(true);
|
|
369
392
|
expect(data.output).toBe('Successfully updated');
|
|
370
|
-
expect(execa).toHaveBeenCalledWith('npm', ['install', '-g', '
|
|
393
|
+
expect(execa).toHaveBeenCalledWith('npm', ['install', '-g', '@mrpatronz/nexusflow'], expect.any(Object));
|
|
371
394
|
});
|
|
372
395
|
});
|
|
373
396
|
|
|
374
397
|
describe('POST /api/workspace', () => {
|
|
375
|
-
it('should complete workspace creation successfully
|
|
398
|
+
it('should complete workspace creation successfully', async () => {
|
|
376
399
|
vi.spyOn(config, 'loadConfig').mockResolvedValue({
|
|
377
400
|
workspacesDir: '/mock/workspaces',
|
|
378
|
-
|
|
401
|
+
storageProvider: 'local'
|
|
379
402
|
} as any);
|
|
380
403
|
|
|
381
404
|
vi.spyOn(workspace, 'createWorkspace').mockResolvedValue('/mock/workspaces/test-ws-creation-no-pack');
|
|
@@ -413,52 +436,183 @@ describe('Server API Endpoints Unit Tests', () => {
|
|
|
413
436
|
expect(text).toContain('"progress":100');
|
|
414
437
|
});
|
|
415
438
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
workspacesDir: '/mock/workspaces',
|
|
419
|
-
packContextXml: true
|
|
420
|
-
} as any);
|
|
439
|
+
// XML context packing tests removed.
|
|
440
|
+
});
|
|
421
441
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
vi.spyOn(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
442
|
+
describe('Workflows Templates API', () => {
|
|
443
|
+
it('GET /api/workflows/templates should return list of templates', async () => {
|
|
444
|
+
vi.spyOn(workflows, 'getWorkflowTemplates').mockResolvedValue([
|
|
445
|
+
{ id: 'test-id', name: 'Test Name', description: 'Test Desc', content: 'Test Content', custom: false }
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
const response = await app.request('/api/workflows/templates');
|
|
449
|
+
expect(response.status).toBe(200);
|
|
450
|
+
const data = await response.json();
|
|
451
|
+
expect(data).toEqual({
|
|
452
|
+
templates: [
|
|
453
|
+
{ id: 'test-id', name: 'Test Name', description: 'Test Desc', content: 'Test Content', custom: false }
|
|
454
|
+
]
|
|
430
455
|
});
|
|
456
|
+
});
|
|
431
457
|
|
|
432
|
-
|
|
458
|
+
it('POST /api/workflows/templates should create or update template', async () => {
|
|
459
|
+
vi.spyOn(workflows, 'saveWorkflowTemplate').mockResolvedValue({
|
|
460
|
+
id: 'test-id',
|
|
461
|
+
name: 'Test Name',
|
|
462
|
+
description: 'Test Desc',
|
|
463
|
+
content: 'Test Content',
|
|
464
|
+
custom: true
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const response = await app.request('/api/workflows/templates', {
|
|
468
|
+
method: 'POST',
|
|
469
|
+
headers: { 'Content-Type': 'application/json' },
|
|
470
|
+
body: JSON.stringify({ id: 'old-id', name: 'Test Name', content: 'Test Content' })
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
expect(response.status).toBe(200);
|
|
474
|
+
const data = await response.json();
|
|
475
|
+
expect(data.success).toBe(true);
|
|
476
|
+
expect(data.template.id).toBe('test-id');
|
|
477
|
+
expect(workflows.saveWorkflowTemplate).toHaveBeenCalledWith('Test Name', 'Test Content', 'old-id');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('DELETE /api/workflows/templates/:id should delete template', async () => {
|
|
481
|
+
vi.spyOn(workflows, 'getWorkflowTemplates').mockResolvedValue([
|
|
482
|
+
{ id: 'test-id', name: 'Test Name', description: 'Test Desc', content: 'Test Content', custom: true }
|
|
483
|
+
]);
|
|
484
|
+
vi.spyOn(workflows, 'deleteWorkflowTemplate').mockResolvedValue(undefined);
|
|
485
|
+
|
|
486
|
+
const response = await app.request('/api/workflows/templates/test-id', {
|
|
487
|
+
method: 'DELETE'
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
expect(response.status).toBe(200);
|
|
491
|
+
const data = await response.json();
|
|
492
|
+
expect(data.success).toBe(true);
|
|
493
|
+
expect(workflows.deleteWorkflowTemplate).toHaveBeenCalledWith('test-id');
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('POST /api/workflows/templates/:id/analyze should run inspection with comment', async () => {
|
|
497
|
+
vi.mocked(detectAi.detectAIAssistants).mockResolvedValue([
|
|
498
|
+
{ name: 'antigravity', displayName: 'Antigravity', detected: true, command: 'agy' }
|
|
499
|
+
]);
|
|
500
|
+
|
|
501
|
+
vi.mocked(execa).mockResolvedValue({
|
|
502
|
+
exitCode: 0,
|
|
503
|
+
stdout: 'Review result: Success\n=== SUGGESTED IMPROVEMENT START ===\n# Refined Strategy\n=== SUGGESTED IMPROVEMENT END ==='
|
|
504
|
+
} as any);
|
|
505
|
+
|
|
506
|
+
const response = await app.request('/api/workflows/templates/test-id/analyze', {
|
|
433
507
|
method: 'POST',
|
|
434
508
|
headers: { 'Content-Type': 'application/json' },
|
|
435
509
|
body: JSON.stringify({
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
assistants: ['antigravity']
|
|
510
|
+
content: 'My Strategy guidelines',
|
|
511
|
+
assistant: 'antigravity',
|
|
512
|
+
comment: 'Check for timeouts'
|
|
440
513
|
})
|
|
441
514
|
});
|
|
442
515
|
|
|
443
516
|
expect(response.status).toBe(200);
|
|
444
517
|
const data = await response.json();
|
|
445
|
-
expect(data.
|
|
446
|
-
expect(data.
|
|
518
|
+
expect(data.analysis).toContain('Review result: Success');
|
|
519
|
+
expect(data.suggestedImprovement).toBe('# Refined Strategy');
|
|
520
|
+
expect(execa).toHaveBeenCalled();
|
|
521
|
+
|
|
522
|
+
const calledArgs = vi.mocked(execa).mock.calls[0][1];
|
|
523
|
+
expect(JSON.stringify(calledArgs)).toContain('Check for timeouts');
|
|
524
|
+
});
|
|
447
525
|
|
|
448
|
-
|
|
449
|
-
|
|
526
|
+
describe('POST /api/workspace/suggest-workflow', () => {
|
|
527
|
+
it('should suggest a workflow using heuristics when local LLM is disabled', async () => {
|
|
528
|
+
vi.spyOn(config, 'loadConfig').mockResolvedValue({
|
|
529
|
+
version: '1.0',
|
|
530
|
+
devDir: '/dev',
|
|
531
|
+
workspacesDir: '/dev/workspaces',
|
|
532
|
+
defaultAssistant: null,
|
|
533
|
+
scanDepth: 2,
|
|
534
|
+
localLlm: { enabled: false, provider: 'ollama', endpoint: 'http://localhost:11434', model: 'qwen' }
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const response = await app.request('/api/workspace/suggest-workflow', {
|
|
538
|
+
method: 'POST',
|
|
539
|
+
headers: { 'Content-Type': 'application/json' },
|
|
540
|
+
body: JSON.stringify({
|
|
541
|
+
description: 'Fix a typo in README.md and update comments',
|
|
542
|
+
repos: [{ name: 'my-project', path: '/dev/my-project', defaultBranch: 'main' }]
|
|
543
|
+
})
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
expect(response.status).toBe(200);
|
|
547
|
+
const data = await response.json();
|
|
548
|
+
expect(data.success).toBe(true);
|
|
549
|
+
expect(data.difficulty).toBe('simple');
|
|
550
|
+
expect(data.suggestedWorkflowId).toBe('solo-developer');
|
|
551
|
+
expect(data.customInstructions).toContain('Solo Developer');
|
|
552
|
+
});
|
|
450
553
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
554
|
+
it('should suggest a complex workflow when description contains complex keywords', async () => {
|
|
555
|
+
vi.spyOn(config, 'loadConfig').mockResolvedValue({
|
|
556
|
+
version: '1.0',
|
|
557
|
+
devDir: '/dev',
|
|
558
|
+
workspacesDir: '/dev/workspaces',
|
|
559
|
+
defaultAssistant: null,
|
|
560
|
+
scanDepth: 2,
|
|
561
|
+
localLlm: { enabled: false, provider: 'ollama', endpoint: 'http://localhost:11434', model: 'qwen' }
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const response = await app.request('/api/workspace/suggest-workflow', {
|
|
565
|
+
method: 'POST',
|
|
566
|
+
headers: { 'Content-Type': 'application/json' },
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
description: 'Refactor database schema and migrate data to postgres',
|
|
569
|
+
repos: [{ name: 'my-project', path: '/dev/my-project', defaultBranch: 'main' }]
|
|
570
|
+
})
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
expect(response.status).toBe(200);
|
|
574
|
+
const data = await response.json();
|
|
575
|
+
expect(data.success).toBe(true);
|
|
576
|
+
expect(data.difficulty).toBe('complex');
|
|
577
|
+
expect(data.suggestedWorkflowId).toBe('plan-implement-review');
|
|
578
|
+
expect(data.customInstructions).toContain('Plan, Implement, Review');
|
|
579
|
+
});
|
|
455
580
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
581
|
+
it('should call local LLM and return suggested workflow when LLM is enabled', async () => {
|
|
582
|
+
vi.spyOn(config, 'loadConfig').mockResolvedValue({
|
|
583
|
+
version: '1.0',
|
|
584
|
+
devDir: '/dev',
|
|
585
|
+
workspacesDir: '/dev/workspaces',
|
|
586
|
+
defaultAssistant: null,
|
|
587
|
+
scanDepth: 2,
|
|
588
|
+
localLlm: { enabled: true, provider: 'ollama', endpoint: 'http://localhost:11434', model: 'qwen' }
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
vi.spyOn(localAi, 'callLocalLlm').mockResolvedValue(JSON.stringify({
|
|
592
|
+
difficulty: 'moderate',
|
|
593
|
+
rationale: 'LLM selected moderate strategy.',
|
|
594
|
+
suggestedWorkflowId: 'research-verify',
|
|
595
|
+
customInstructions: '# LLM Custom Instructions'
|
|
596
|
+
}));
|
|
597
|
+
|
|
598
|
+
const response = await app.request('/api/workspace/suggest-workflow', {
|
|
599
|
+
method: 'POST',
|
|
600
|
+
headers: { 'Content-Type': 'application/json' },
|
|
601
|
+
body: JSON.stringify({
|
|
602
|
+
description: 'Implement new UI component',
|
|
603
|
+
repos: [{ name: 'my-project', path: '/dev/my-project', defaultBranch: 'main' }]
|
|
604
|
+
})
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
expect(response.status).toBe(200);
|
|
608
|
+
const data = await response.json();
|
|
609
|
+
expect(data.success).toBe(true);
|
|
610
|
+
expect(data.difficulty).toBe('moderate');
|
|
611
|
+
expect(data.rationale).toBe('LLM selected moderate strategy.');
|
|
612
|
+
expect(data.suggestedWorkflowId).toBe('research-verify');
|
|
613
|
+
expect(data.customInstructions).toBe('# LLM Custom Instructions');
|
|
614
|
+
expect(localAi.callLocalLlm).toHaveBeenCalled();
|
|
615
|
+
});
|
|
462
616
|
});
|
|
463
617
|
});
|
|
464
618
|
});
|