@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
package/src/server.ts
CHANGED
|
@@ -9,16 +9,20 @@ import { serveStatic } from '@hono/node-server/serve-static';
|
|
|
9
9
|
import { cors } from 'hono/cors';
|
|
10
10
|
import { streamSSE } from 'hono/streaming';
|
|
11
11
|
import * as fs from 'node:fs/promises';
|
|
12
|
+
import { createWriteStream, existsSync } from 'node:fs';
|
|
12
13
|
import * as path from 'node:path';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
14
15
|
import { execa } from 'execa';
|
|
16
|
+
import * as os from 'node:os';
|
|
17
|
+
import { pipeline } from 'node:stream/promises';
|
|
18
|
+
import { spawn } from 'node:child_process';
|
|
15
19
|
|
|
16
20
|
import { loadConfig, saveConfig, getConfigDir } from './core/config.js';
|
|
21
|
+
import { listStorageProviders } from './core/adapters/registry.js';
|
|
17
22
|
import { scanForRepos } from './core/scanner.js';
|
|
18
23
|
import { createWorkspace, listWorkspaces, loadFeatureConfig, deleteWorkspace, addRepoToWorkspace } from './core/workspace.js';
|
|
19
24
|
import { analyzeAllRepos } from './analyzers/index.js';
|
|
20
25
|
import { generateContextFiles } from './generators/index.js';
|
|
21
|
-
import { packWorkspace } from './core/packer.js';
|
|
22
26
|
import { isOllamaModelAvailable, getOpenAiCompatibleUrl, callLocalLlm } from './utils/local-ai.js';
|
|
23
27
|
import { detectAIAssistants } from './utils/detect-ai.js';
|
|
24
28
|
import { detectEditors } from './utils/detect-editors.js';
|
|
@@ -33,7 +37,9 @@ import {
|
|
|
33
37
|
loadRunningState,
|
|
34
38
|
} from './orchestration/index.js';
|
|
35
39
|
import { checkForUpdates, getCurrentVersion, getToolsStatus } from './utils/update-check.js';
|
|
40
|
+
import { getWorkflowTemplates, saveWorkflowTemplate, deleteWorkflowTemplate } from './utils/workflows.js';
|
|
36
41
|
import type { Feature, RepoInfo, WorkspaceContext } from './types.js';
|
|
42
|
+
import { suggestWorkflow } from './utils/workflow-advisor.js';
|
|
37
43
|
|
|
38
44
|
// Resolve static files directory
|
|
39
45
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -71,10 +77,24 @@ app.get('/api/config', async (c) => {
|
|
|
71
77
|
}
|
|
72
78
|
});
|
|
73
79
|
|
|
80
|
+
// Get all registered storage adapters
|
|
81
|
+
app.get('/api/adapters', async (c) => {
|
|
82
|
+
try {
|
|
83
|
+
const adapters = listStorageProviders();
|
|
84
|
+
return c.json({ adapters });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
87
|
+
return c.json({ error: msg }, 500);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
74
91
|
function isSafeLocalEndpoint(urlStr: string): boolean {
|
|
75
92
|
try {
|
|
76
93
|
const url = new URL(urlStr);
|
|
77
94
|
const hostname = url.hostname.toLowerCase();
|
|
95
|
+
if (url.protocol === 'https:') {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
78
98
|
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') {
|
|
79
99
|
return true;
|
|
80
100
|
}
|
|
@@ -94,7 +114,7 @@ app.post('/api/config', async (c) => {
|
|
|
94
114
|
const newConfig = await c.req.json();
|
|
95
115
|
if (newConfig?.localLlm?.enabled && newConfig.localLlm.endpoint) {
|
|
96
116
|
if (!isSafeLocalEndpoint(newConfig.localLlm.endpoint)) {
|
|
97
|
-
return c.json({ error: 'Local AI endpoint must be localhost, 127.0.0.1, or a private LAN IP.' }, 400);
|
|
117
|
+
return c.json({ error: 'Local AI endpoint must be HTTPS, localhost, 127.0.0.1, or a private LAN IP.' }, 400);
|
|
98
118
|
}
|
|
99
119
|
}
|
|
100
120
|
await saveConfig(newConfig);
|
|
@@ -154,15 +174,15 @@ app.get('/api/editor-detect', async (c) => {
|
|
|
154
174
|
// 6.5. Local LLM test & recommendation
|
|
155
175
|
app.post('/api/local-llm/test', async (c) => {
|
|
156
176
|
try {
|
|
157
|
-
const { provider, endpoint, model, shoot } = await c.req.json();
|
|
177
|
+
const { provider, endpoint, model, apiKey, shoot } = await c.req.json();
|
|
158
178
|
if (!endpoint || !isSafeLocalEndpoint(endpoint)) {
|
|
159
|
-
return c.json({ success: false, error: 'Local AI endpoint must be localhost, 127.0.0.1, or a private LAN IP.' }, 400);
|
|
179
|
+
return c.json({ success: false, error: 'Local AI endpoint must be HTTPS, localhost, 127.0.0.1, or a private LAN IP.' }, 400);
|
|
160
180
|
}
|
|
161
181
|
const cleanEndpoint = endpoint.replace(/\/$/, '');
|
|
162
182
|
|
|
163
183
|
if (shoot) {
|
|
164
184
|
const responseText = await callLocalLlm(
|
|
165
|
-
{ enabled: true, provider, endpoint, model },
|
|
185
|
+
{ enabled: true, provider, endpoint, model, apiKey },
|
|
166
186
|
[{ role: 'user', content: 'Respond with the exact word "OK" and nothing else.' }]
|
|
167
187
|
);
|
|
168
188
|
const cleanResponse = responseText.trim();
|
|
@@ -298,6 +318,7 @@ async function runCreationJob(jobId: string, body: any, config: any) {
|
|
|
298
318
|
createdAt: new Date().toISOString(),
|
|
299
319
|
resumption: body.resumption,
|
|
300
320
|
localLlmEnabled: body.localLlmEnabled,
|
|
321
|
+
teamworkInstructions: body.teamworkInstructions,
|
|
301
322
|
};
|
|
302
323
|
if (job) {
|
|
303
324
|
job.feature = feature;
|
|
@@ -328,12 +349,7 @@ async function runCreationJob(jobId: string, body: any, config: any) {
|
|
|
328
349
|
await generateContextFiles(ctx, body.assistants, workspacePath);
|
|
329
350
|
updateJobStep(jobId, 'context', 'completed', 'AI context files generated.');
|
|
330
351
|
|
|
331
|
-
//
|
|
332
|
-
if (config.packContextXml) {
|
|
333
|
-
updateJobStep(jobId, 'pack', 'running', 'Packing codebase context with Repomix...');
|
|
334
|
-
const packResult = await packWorkspace(workspacePath);
|
|
335
|
-
updateJobStep(jobId, 'pack', 'completed', `Packed codebase context (${packResult.totalFiles} files, ${(packResult.fileSize / 1024).toFixed(2)} KB).`);
|
|
336
|
-
}
|
|
352
|
+
// XML context packing removed.
|
|
337
353
|
|
|
338
354
|
} catch (error) {
|
|
339
355
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -355,6 +371,7 @@ app.post('/api/workspace', async (c) => {
|
|
|
355
371
|
repos: RepoInfo[];
|
|
356
372
|
assistants: any[];
|
|
357
373
|
localLlmEnabled?: boolean;
|
|
374
|
+
teamworkInstructions?: string;
|
|
358
375
|
resumption?: {
|
|
359
376
|
testCommand?: string;
|
|
360
377
|
mockCommand?: string;
|
|
@@ -377,9 +394,7 @@ app.post('/api/workspace', async (c) => {
|
|
|
377
394
|
{ id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
|
|
378
395
|
{ id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
|
|
379
396
|
];
|
|
380
|
-
|
|
381
|
-
steps.push({ id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' });
|
|
382
|
-
}
|
|
397
|
+
// XML context packing removed.
|
|
383
398
|
|
|
384
399
|
const job: CreationJob = {
|
|
385
400
|
id: jobId,
|
|
@@ -484,6 +499,27 @@ app.post('/api/workspace/:id/repo', async (c) => {
|
|
|
484
499
|
}
|
|
485
500
|
});
|
|
486
501
|
|
|
502
|
+
// 7.8. Suggest workflow strategy based on feature description & selected repos
|
|
503
|
+
app.post('/api/workspace/suggest-workflow', async (c) => {
|
|
504
|
+
try {
|
|
505
|
+
const { description, repos } = await c.req.json() as {
|
|
506
|
+
description: string;
|
|
507
|
+
repos: RepoInfo[];
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const config = await loadConfig();
|
|
511
|
+
const suggestion = await suggestWorkflow(description, repos, config.localLlm);
|
|
512
|
+
|
|
513
|
+
return c.json({
|
|
514
|
+
success: true,
|
|
515
|
+
...suggestion
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
519
|
+
return c.json({ error: msg }, 500);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
487
523
|
// 8. Open workspace in editor
|
|
488
524
|
app.post('/api/open-editor', async (c) => {
|
|
489
525
|
try {
|
|
@@ -829,7 +865,7 @@ app.post('/api/workspace/:id/sync', async (c) => {
|
|
|
829
865
|
const results = [];
|
|
830
866
|
|
|
831
867
|
for (const repo of repos) {
|
|
832
|
-
const result = await rebaseRepo(repo.path, 'main');
|
|
868
|
+
const result = await rebaseRepo(repo.path, repo.defaultBranch || 'main');
|
|
833
869
|
results.push({
|
|
834
870
|
repoName: repo.name,
|
|
835
871
|
success: result.success,
|
|
@@ -1025,7 +1061,6 @@ app.post('/api/updates/install', async (c) => {
|
|
|
1025
1061
|
const { toolId } = await c.req.json() as { toolId: string };
|
|
1026
1062
|
const tools = [
|
|
1027
1063
|
{ id: 'nexusflow', cmd: 'npm', args: ['install', '-g', '@mrpatronz/nexusflow'] },
|
|
1028
|
-
{ id: 'repomix', cmd: 'npm', args: ['install', '-g', 'repomix'] },
|
|
1029
1064
|
{ id: 'antigravity', cmd: 'agy', args: ['update'] },
|
|
1030
1065
|
{ id: 'claude', cmd: 'npm', args: ['install', '-g', '@anthropic-ai/claude-code'] },
|
|
1031
1066
|
];
|
|
@@ -1050,30 +1085,205 @@ app.post('/api/updates/install', async (c) => {
|
|
|
1050
1085
|
}
|
|
1051
1086
|
});
|
|
1052
1087
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1088
|
+
let downloadedInstallerPath: string | null = null;
|
|
1089
|
+
|
|
1090
|
+
// 17.7. Download matching GitHub Release installer to temporary folder
|
|
1091
|
+
app.post('/api/updates/download', async (c) => {
|
|
1092
|
+
try {
|
|
1093
|
+
const { downloadUrl } = await c.req.json() as { downloadUrl: string };
|
|
1094
|
+
if (!downloadUrl) {
|
|
1095
|
+
return c.json({ error: 'Download URL is required' }, 400);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const tempDir = os.tmpdir();
|
|
1099
|
+
const fileName = 'NexusFlowSetup_Update.exe';
|
|
1100
|
+
const targetPath = path.join(tempDir, fileName);
|
|
1101
|
+
|
|
1102
|
+
const response = await fetch(downloadUrl);
|
|
1103
|
+
if (!response.ok) {
|
|
1104
|
+
throw new Error(`HTTP error ${response.status}`);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Node fetch body stream download
|
|
1108
|
+
const fileStream = createWriteStream(targetPath);
|
|
1109
|
+
if (!response.body) {
|
|
1110
|
+
throw new Error('Response body is null');
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Convert ReadableStream to Node stream
|
|
1114
|
+
await pipeline(response.body as any, fileStream);
|
|
1115
|
+
|
|
1116
|
+
downloadedInstallerPath = targetPath;
|
|
1117
|
+
return c.json({ success: true, path: targetPath });
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1120
|
+
return c.json({ error: `Download failed: ${msg}` }, 500);
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// 17.8. Launch the silent installer detached and exit server
|
|
1125
|
+
app.post('/api/updates/apply', async (c) => {
|
|
1126
|
+
if (!downloadedInstallerPath || !existsSync(downloadedInstallerPath)) {
|
|
1127
|
+
return c.json({ error: 'No downloaded installer found on disk' }, 400);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
try {
|
|
1131
|
+
const isWin = process.platform === 'win32';
|
|
1132
|
+
if (isWin) {
|
|
1133
|
+
// Inno Setup silent install flags
|
|
1134
|
+
console.log(`Applying update: Spawning detached installer at: ${downloadedInstallerPath}`);
|
|
1135
|
+
const child = spawn(downloadedInstallerPath, ['/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART'], {
|
|
1136
|
+
detached: true,
|
|
1137
|
+
stdio: 'ignore',
|
|
1138
|
+
});
|
|
1139
|
+
child.unref();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Gracefully exit server process in 1 second to release file locks
|
|
1143
|
+
setTimeout(() => {
|
|
1144
|
+
console.log('Update installer successfully spawned. Exiting Hono server process...');
|
|
1145
|
+
process.exit(0);
|
|
1146
|
+
}, 1000);
|
|
1147
|
+
|
|
1148
|
+
return c.json({ success: true, message: 'Installer spawned, app shutting down...' });
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1151
|
+
return c.json({ error: `Failed to execute update: ${msg}` }, 500);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// 17.9. Get available workflow templates
|
|
1156
|
+
app.get('/api/workflows/templates', async (c) => {
|
|
1157
|
+
try {
|
|
1158
|
+
const templates = await getWorkflowTemplates();
|
|
1159
|
+
return c.json({ templates });
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1162
|
+
return c.json({ error: msg }, 500);
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
// Save or update custom teamwork template
|
|
1167
|
+
app.post('/api/workflows/templates', async (c) => {
|
|
1168
|
+
try {
|
|
1169
|
+
const { id, name, content } = await c.req.json();
|
|
1170
|
+
if (!name || !content) {
|
|
1171
|
+
return c.json({ error: 'Name and content are required.' }, 400);
|
|
1172
|
+
}
|
|
1173
|
+
const template = await saveWorkflowTemplate(name, content, id);
|
|
1174
|
+
return c.json({ success: true, template });
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1177
|
+
return c.json({ error: msg }, 500);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
// Delete custom teamwork template
|
|
1182
|
+
app.delete('/api/workflows/templates/:id', async (c) => {
|
|
1055
1183
|
try {
|
|
1056
1184
|
const id = decodeURIComponent(c.req.param('id'));
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1185
|
+
const templates = await getWorkflowTemplates();
|
|
1186
|
+
const target = templates.find(t => t.id === id);
|
|
1187
|
+
if (!target) {
|
|
1188
|
+
return c.json({ error: 'Template not found.' }, 404);
|
|
1189
|
+
}
|
|
1190
|
+
if (!target.custom) {
|
|
1191
|
+
return c.json({ error: 'Cannot delete built-in templates.' }, 403);
|
|
1192
|
+
}
|
|
1193
|
+
await deleteWorkflowTemplate(id);
|
|
1194
|
+
return c.json({ success: true });
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1197
|
+
return c.json({ error: msg }, 500);
|
|
1198
|
+
}
|
|
1199
|
+
});
|
|
1059
1200
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1201
|
+
// Analyze teamwork template rules via selected AI coding assistant harness
|
|
1202
|
+
app.post('/api/workflows/templates/:id/analyze', async (c) => {
|
|
1203
|
+
try {
|
|
1204
|
+
const { content, assistant, comment } = await c.req.json();
|
|
1205
|
+
if (!content) {
|
|
1206
|
+
return c.json({ error: 'Content is required.' }, 400);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const selectedAssistant = assistant || 'antigravity';
|
|
1210
|
+
let command = '';
|
|
1211
|
+
let args: string[] = [];
|
|
1212
|
+
|
|
1213
|
+
let prompt = `You are an expert AI system engineering reviewer. Analyze the following Agent Teamwork Strategy guidelines.
|
|
1214
|
+
Evaluate its instructions, identify any ambiguities or contradictions, rate its expected effectiveness for orchestrating subagents, and provide specific recommendations or improvements. Format your analysis in clean Markdown with clear headings (e.g. Overview, Strengths, Weaknesses, Recommendations).
|
|
1215
|
+
|
|
1216
|
+
After your analysis, provide a fully rewritten, optimized, and complete version of the strategy guidelines incorporating all your recommendations. This rewritten version must be suitable for production orchestration.
|
|
1217
|
+
You MUST prefix the rewritten version with the exact delimiter line:
|
|
1218
|
+
=== SUGGESTED IMPROVEMENT START ===
|
|
1219
|
+
and suffix it with:
|
|
1220
|
+
=== SUGGESTED IMPROVEMENT END ===`;
|
|
1221
|
+
|
|
1222
|
+
if (comment && comment.trim()) {
|
|
1223
|
+
prompt += `\n\nIMPORTANT: The user has provided the following specific instruction/comment that you MUST consider and prioritize during your evaluation and when rewriting the guidelines:\n"${comment.trim()}"`;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
prompt += `\n\n--- GUIDELINES START ---\n${content}\n--- GUIDELINES END ---`;
|
|
1227
|
+
|
|
1228
|
+
const assistants = await detectAIAssistants();
|
|
1229
|
+
const target = assistants.find(ai => ai.name === selectedAssistant);
|
|
1230
|
+
|
|
1231
|
+
if (!target || !target.detected || !target.command) {
|
|
1232
|
+
return c.json({
|
|
1233
|
+
error: `AI assistant harness '${selectedAssistant}' is not detected or does not support command-line execution.`
|
|
1234
|
+
}, 400);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
command = target.command;
|
|
1238
|
+
if (command === 'claude') {
|
|
1239
|
+
args = ['-p', prompt];
|
|
1240
|
+
} else if (command === 'agy') {
|
|
1241
|
+
args = [prompt];
|
|
1242
|
+
} else {
|
|
1243
|
+
args = [prompt];
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const result = await execa(command, args, {
|
|
1247
|
+
input: '',
|
|
1248
|
+
shell: false,
|
|
1249
|
+
reject: false
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
if (result.exitCode !== 0) {
|
|
1253
|
+
return c.json({
|
|
1254
|
+
error: `AI Assistant harness execution failed (exit code ${result.exitCode}): ${result.stderr || result.stdout || 'Unknown error'}`
|
|
1255
|
+
}, 500);
|
|
1063
1256
|
}
|
|
1064
1257
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1258
|
+
// Clean potential warning lines or stdout prefixes if present
|
|
1259
|
+
let cleanText = result.stdout;
|
|
1260
|
+
if (cleanText.includes('Warning: no stdin data received')) {
|
|
1261
|
+
cleanText = cleanText.replace(/Warning: no stdin data received in \d+s, proceeding without it\. If piping from a slow command, redirect stdin explicitly: < \/dev\/null to skip, or wait longer\.\r?\n?/, '');
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
let analysis = cleanText.trim();
|
|
1265
|
+
let suggestedImprovement = '';
|
|
1266
|
+
|
|
1267
|
+
const startDelimiter = '=== SUGGESTED IMPROVEMENT START ===';
|
|
1268
|
+
const endDelimiter = '=== SUGGESTED IMPROVEMENT END ===';
|
|
1269
|
+
|
|
1270
|
+
const startIdx = cleanText.indexOf(startDelimiter);
|
|
1271
|
+
const endIdx = cleanText.indexOf(endDelimiter);
|
|
1067
1272
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1273
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
1274
|
+
analysis = cleanText.substring(0, startIdx).trim();
|
|
1275
|
+
suggestedImprovement = cleanText.substring(startIdx + startDelimiter.length, endIdx).trim();
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return c.json({ analysis, suggestedImprovement });
|
|
1071
1279
|
} catch (error) {
|
|
1072
1280
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1073
1281
|
return c.json({ error: msg }, 500);
|
|
1074
1282
|
}
|
|
1075
1283
|
});
|
|
1076
1284
|
|
|
1285
|
+
// Legacy pack endpoint removed.
|
|
1286
|
+
|
|
1077
1287
|
// Serve index.html explicitly on root endpoint
|
|
1078
1288
|
app.get('/', async (c) => {
|
|
1079
1289
|
try {
|
package/src/types.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface LocalLlmConfig {
|
|
|
11
11
|
provider: 'ollama' | 'openai-compatible';
|
|
12
12
|
endpoint: string;
|
|
13
13
|
model: string;
|
|
14
|
+
apiKey?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** Top-level NexusFlow configuration stored in ~/.nexusflow/config.json. */
|
|
@@ -36,14 +37,22 @@ export interface NexusFlowConfig {
|
|
|
36
37
|
/** Global patterns to exclude when packing/analyzing repositories. */
|
|
37
38
|
excludePatterns?: string[];
|
|
38
39
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
40
|
+
/** Active storage provider name. Default: 'local' */
|
|
41
|
+
storageProvider?: string;
|
|
42
|
+
|
|
43
|
+
/** Per-adapter settings keyed by adapter name. */
|
|
44
|
+
adapterConfig?: Record<string, Record<string, unknown>>;
|
|
45
|
+
|
|
46
|
+
/** List of plugin paths or npm packages to load. */
|
|
47
|
+
plugins?: string[];
|
|
41
48
|
|
|
42
49
|
/** ISO timestamp of the last update check. */
|
|
43
50
|
lastUpdateCheck?: string;
|
|
44
51
|
|
|
45
52
|
/** The last checked latest version from NPM. */
|
|
46
53
|
latestVersion?: string;
|
|
54
|
+
latestDownloadUrl?: string | null;
|
|
55
|
+
latestReleaseNotes?: string;
|
|
47
56
|
|
|
48
57
|
/** Local LLM settings for delegating simple tasks. */
|
|
49
58
|
localLlm?: LocalLlmConfig;
|
|
@@ -148,6 +157,9 @@ export interface Feature {
|
|
|
148
157
|
|
|
149
158
|
/** Whether the Local LLM Co-processor is active for this workspace. */
|
|
150
159
|
localLlmEnabled?: boolean;
|
|
160
|
+
|
|
161
|
+
/** Custom teamwork coordination instructions for the agent team. */
|
|
162
|
+
teamworkInstructions?: string;
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
/** Runtime context for an active workspace — now includes analysis data. */
|
package/src/utils/git.ts
CHANGED
|
@@ -59,3 +59,59 @@ export async function detectDefaultBranch(repoPath: string): Promise<string> {
|
|
|
59
59
|
return 'main';
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks if the given branch name matches Git branch conventions (based on git-check-ref-format):
|
|
65
|
+
* - No spaces, backslashes, or control characters.
|
|
66
|
+
* - At least 1 character long.
|
|
67
|
+
* - Is not exactly '@'.
|
|
68
|
+
* - Does not start with a dash.
|
|
69
|
+
* - Does not start or end with a forward slash.
|
|
70
|
+
* - Does not contain consecutive dots, consecutive slashes, or '@{'.
|
|
71
|
+
* - Does not end with '.'.
|
|
72
|
+
* - Does not contain forbidden characters: ~, ^, :, ?, *, [
|
|
73
|
+
* - No component starts with a dot, ends with '.lock', or is exactly 'HEAD'.
|
|
74
|
+
*
|
|
75
|
+
* @param name - The branch name to validate.
|
|
76
|
+
* @returns `true` if the branch name is valid.
|
|
77
|
+
*/
|
|
78
|
+
export function isValidBranchName(name: string): boolean {
|
|
79
|
+
if (!name || name.length === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (name === '@') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (name.startsWith('-')) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const forbiddenCharsOrSpaces = /[\x00-\x20\x7F~^:?*[\\]/;
|
|
89
|
+
if (forbiddenCharsOrSpaces.test(name)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (name.includes('..') || name.includes('@{') || name.includes('//')) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (name.startsWith('/') || name.endsWith('/')) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (name.endsWith('.')) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const components = name.split('/');
|
|
103
|
+
for (const component of components) {
|
|
104
|
+
if (component.startsWith('.')) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (component.endsWith('.lock')) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (component === 'HEAD') {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
package/src/utils/local-ai.ts
CHANGED
|
@@ -53,11 +53,16 @@ export async function callLocalLlm(
|
|
|
53
53
|
// OpenAI-compatible provider
|
|
54
54
|
const url = getOpenAiCompatibleUrl(cleanEndpoint, '/v1/chat/completions');
|
|
55
55
|
|
|
56
|
+
const headers: Record<string, string> = {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
};
|
|
59
|
+
if (config.apiKey) {
|
|
60
|
+
headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
const response = await fetch(url, {
|
|
57
64
|
method: 'POST',
|
|
58
|
-
headers
|
|
59
|
-
'Content-Type': 'application/json',
|
|
60
|
-
},
|
|
65
|
+
headers,
|
|
61
66
|
body: JSON.stringify({
|
|
62
67
|
model,
|
|
63
68
|
messages,
|
package/src/utils/multi-git.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as path from 'node:path';
|
|
|
11
11
|
import { execa } from 'execa';
|
|
12
12
|
|
|
13
13
|
import type { Feature } from '../types.js';
|
|
14
|
+
import { detectDefaultBranch } from './git.js';
|
|
14
15
|
|
|
15
16
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
16
17
|
|
|
@@ -22,6 +23,8 @@ export interface WorkspaceRepo {
|
|
|
22
23
|
path: string;
|
|
23
24
|
/** Feature branch name shared across the workspace. */
|
|
24
25
|
branchName: string;
|
|
26
|
+
/** Default branch of the repo (e.g. 'main' or 'master'). */
|
|
27
|
+
defaultBranch: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/** Result of inspecting the working-tree status of a repo. */
|
|
@@ -109,15 +112,19 @@ export async function getWorkspaceRepos(
|
|
|
109
112
|
const raw = await fs.readFile(manifestPath, 'utf-8');
|
|
110
113
|
const feature = JSON.parse(raw) as Feature;
|
|
111
114
|
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
return Promise.all(
|
|
116
|
+
feature.repos.map(async (repoPath) => {
|
|
117
|
+
const name = path.basename(repoPath);
|
|
118
|
+
const absolutePath = path.resolve(workspacePath, name);
|
|
119
|
+
const defaultBranch = await detectDefaultBranch(absolutePath);
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
path: absolutePath,
|
|
123
|
+
branchName: feature.branchName,
|
|
124
|
+
defaultBranch,
|
|
125
|
+
};
|
|
126
|
+
})
|
|
127
|
+
);
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
/**
|
|
@@ -14,6 +14,21 @@ export interface UpdateStatus {
|
|
|
14
14
|
currentVersion: string;
|
|
15
15
|
latestVersion: string;
|
|
16
16
|
updateAvailable: boolean;
|
|
17
|
+
downloadUrl?: string | null;
|
|
18
|
+
releaseNotes?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface GitHubAsset {
|
|
22
|
+
name: string;
|
|
23
|
+
browser_download_url: string;
|
|
24
|
+
size: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface GitHubRelease {
|
|
28
|
+
tag_name: string;
|
|
29
|
+
html_url: string;
|
|
30
|
+
body: string;
|
|
31
|
+
assets: GitHubAsset[];
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
/**
|
|
@@ -43,7 +58,7 @@ export function getCurrentVersion(): string {
|
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
/**
|
|
46
|
-
* Checks for updates from the
|
|
61
|
+
* Checks for updates from the GitHub Releases API (antan87/NexusFlow).
|
|
47
62
|
* Employs a 24-hour cache unless forced.
|
|
48
63
|
*
|
|
49
64
|
* @param force - If true, ignores the 24-hour cache and fetches immediately.
|
|
@@ -63,31 +78,48 @@ export async function checkForUpdates(force = false): Promise<UpdateStatus | nul
|
|
|
63
78
|
currentVersion,
|
|
64
79
|
latestVersion: config.latestVersion,
|
|
65
80
|
updateAvailable: isNewerVersion(currentVersion, config.latestVersion),
|
|
81
|
+
downloadUrl: config.latestDownloadUrl,
|
|
82
|
+
releaseNotes: config.latestReleaseNotes,
|
|
66
83
|
};
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
signal: AbortSignal.timeout(
|
|
87
|
+
const response = await fetch('https://api.github.com/repos/antan87/NexusFlow/releases/latest', {
|
|
88
|
+
headers: { 'User-Agent': 'NexusFlow-Updater' },
|
|
89
|
+
signal: AbortSignal.timeout(4000),
|
|
73
90
|
});
|
|
74
91
|
|
|
75
92
|
if (!response.ok) {
|
|
76
|
-
throw new Error(`
|
|
93
|
+
throw new Error(`GitHub API returned status ${response.status}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = await response.json() as GitHubRelease;
|
|
97
|
+
const latestVersion = data.tag_name.replace(/^v/, '');
|
|
98
|
+
|
|
99
|
+
// Resolve platform-matching asset (e.g. Setup.exe for Windows)
|
|
100
|
+
let downloadUrl: string | null = null;
|
|
101
|
+
const targetAsset = data.assets.find(asset => {
|
|
102
|
+
return asset.name.endsWith('.exe') && asset.name.toLowerCase().includes('setup');
|
|
103
|
+
});
|
|
104
|
+
if (targetAsset) {
|
|
105
|
+
downloadUrl = targetAsset.browser_download_url;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
const
|
|
80
|
-
const latestVersion = data.version;
|
|
108
|
+
const releaseNotes = data.body || '';
|
|
81
109
|
|
|
82
110
|
// Cache results
|
|
83
111
|
config.lastUpdateCheck = new Date().toISOString();
|
|
84
112
|
config.latestVersion = latestVersion;
|
|
113
|
+
config.latestDownloadUrl = downloadUrl;
|
|
114
|
+
config.latestReleaseNotes = releaseNotes;
|
|
85
115
|
await saveConfig(config);
|
|
86
116
|
|
|
87
117
|
return {
|
|
88
118
|
currentVersion,
|
|
89
119
|
latestVersion,
|
|
90
120
|
updateAvailable: isNewerVersion(currentVersion, latestVersion),
|
|
121
|
+
downloadUrl,
|
|
122
|
+
releaseNotes,
|
|
91
123
|
};
|
|
92
124
|
} catch {
|
|
93
125
|
// Fallback: If offline or check fails, return cached version status if available
|
|
@@ -96,6 +128,8 @@ export async function checkForUpdates(force = false): Promise<UpdateStatus | nul
|
|
|
96
128
|
currentVersion,
|
|
97
129
|
latestVersion: config.latestVersion,
|
|
98
130
|
updateAvailable: isNewerVersion(currentVersion, config.latestVersion),
|
|
131
|
+
downloadUrl: config.latestDownloadUrl,
|
|
132
|
+
releaseNotes: config.latestReleaseNotes,
|
|
99
133
|
};
|
|
100
134
|
}
|
|
101
135
|
return null;
|
|
@@ -170,30 +204,7 @@ export async function getToolsStatus(force = false): Promise<ToolUpdateStatus[]>
|
|
|
170
204
|
updateCmd: 'npm install -g @mrpatronz/nexusflow',
|
|
171
205
|
getCurrent: async () => currentVersion,
|
|
172
206
|
},
|
|
173
|
-
|
|
174
|
-
id: 'repomix',
|
|
175
|
-
name: 'Repomix (Codebase Packer)',
|
|
176
|
-
command: 'repomix',
|
|
177
|
-
npmPackage: 'repomix',
|
|
178
|
-
updateCmd: 'npm install -g repomix',
|
|
179
|
-
getCurrent: async () => {
|
|
180
|
-
try {
|
|
181
|
-
const res = await execa('repomix', ['--version'], {
|
|
182
|
-
reject: false,
|
|
183
|
-
shell: process.platform === 'win32',
|
|
184
|
-
});
|
|
185
|
-
if (res.exitCode === 0) return res.stdout.trim();
|
|
186
|
-
} catch {}
|
|
187
|
-
try {
|
|
188
|
-
const res = await execa('npx', ['repomix', '--version'], {
|
|
189
|
-
reject: false,
|
|
190
|
-
shell: process.platform === 'win32',
|
|
191
|
-
});
|
|
192
|
-
if (res.exitCode === 0) return res.stdout.trim();
|
|
193
|
-
} catch {}
|
|
194
|
-
return '';
|
|
195
|
-
}
|
|
196
|
-
},
|
|
207
|
+
|
|
197
208
|
{
|
|
198
209
|
id: 'antigravity',
|
|
199
210
|
name: 'Antigravity CLI',
|