@mrpatronz/nexusflow 0.2.12 → 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 (88) 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/diff.js +1 -1
  7. package/dist/commands/diff.js.map +1 -1
  8. package/dist/commands/doctor.js +1 -1
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/handoff.js +1 -1
  11. package/dist/commands/handoff.js.map +1 -1
  12. package/dist/commands/logs.js +1 -1
  13. package/dist/commands/logs.js.map +1 -1
  14. package/dist/commands/open.d.ts.map +1 -1
  15. package/dist/commands/open.js +12 -7
  16. package/dist/commands/open.js.map +1 -1
  17. package/dist/commands/pack.js +1 -1
  18. package/dist/commands/pack.js.map +1 -1
  19. package/dist/commands/refresh.js +13 -8
  20. package/dist/commands/refresh.js.map +1 -1
  21. package/dist/commands/remove.d.ts.map +1 -1
  22. package/dist/commands/remove.js +12 -7
  23. package/dist/commands/remove.js.map +1 -1
  24. package/dist/commands/start.js +1 -1
  25. package/dist/commands/start.js.map +1 -1
  26. package/dist/commands/status.js +1 -1
  27. package/dist/commands/status.js.map +1 -1
  28. package/dist/commands/stop.js +1 -1
  29. package/dist/commands/stop.js.map +1 -1
  30. package/dist/commands/sync.js +13 -8
  31. package/dist/commands/sync.js.map +1 -1
  32. package/dist/core/workspace.d.ts +7 -0
  33. package/dist/core/workspace.d.ts.map +1 -1
  34. package/dist/core/workspace.js +45 -1
  35. package/dist/core/workspace.js.map +1 -1
  36. package/dist/generators/index.d.ts.map +1 -1
  37. package/dist/generators/index.js +14 -0
  38. package/dist/generators/index.js.map +1 -1
  39. package/dist/gui/assets/index-CFWwqFux.js +23 -0
  40. package/dist/gui/assets/index-Dvc9QMvl.css +2 -0
  41. package/dist/gui/index.html +2 -2
  42. package/dist/orchestration/runner.d.ts +5 -6
  43. package/dist/orchestration/runner.d.ts.map +1 -1
  44. package/dist/orchestration/runner.js +105 -102
  45. package/dist/orchestration/runner.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +53 -1
  48. package/dist/server.js.map +1 -1
  49. package/dist/utils/detect-editors.js +1 -1
  50. package/dist/utils/detect-editors.js.map +1 -1
  51. package/dist/utils/detect-editors.test.js +3 -3
  52. package/dist/utils/detect-editors.test.js.map +1 -1
  53. package/dist/utils/prompts.d.ts +1 -0
  54. package/dist/utils/prompts.d.ts.map +1 -1
  55. package/dist/utils/prompts.js +77 -15
  56. package/dist/utils/prompts.js.map +1 -1
  57. package/extension/package.json +9 -0
  58. package/extension/src/extension.ts +37 -1
  59. package/gui/eslint.config.js +4 -0
  60. package/gui/src/App.tsx +375 -151
  61. package/gui/src/features/changes/ChangesViewer.tsx +246 -70
  62. package/gui/src/features/services/ServiceConsole.tsx +162 -71
  63. package/gui/src/features/workspace/WorkspaceList.tsx +184 -32
  64. package/gui/src/index.css +95 -2
  65. package/package.json +2 -1
  66. package/src/commands/add-repo.ts +30 -14
  67. package/src/commands/commit.ts +1 -1
  68. package/src/commands/diff.ts +1 -1
  69. package/src/commands/doctor.ts +1 -1
  70. package/src/commands/handoff.ts +1 -1
  71. package/src/commands/logs.ts +1 -1
  72. package/src/commands/open.ts +15 -7
  73. package/src/commands/pack.ts +1 -1
  74. package/src/commands/refresh.ts +16 -8
  75. package/src/commands/remove.ts +15 -7
  76. package/src/commands/start.ts +1 -1
  77. package/src/commands/status.ts +1 -1
  78. package/src/commands/stop.ts +1 -1
  79. package/src/commands/sync.ts +16 -8
  80. package/src/core/workspace.ts +48 -1
  81. package/src/generators/index.ts +20 -0
  82. package/src/orchestration/runner.ts +112 -108
  83. package/src/server.ts +58 -1
  84. package/src/utils/detect-editors.test.ts +3 -3
  85. package/src/utils/detect-editors.ts +1 -1
  86. package/src/utils/prompts.ts +88 -14
  87. package/dist/gui/assets/index-Ca2VRMrQ.js +0 -22
  88. package/dist/gui/assets/index-fj0RJiOb.css +0 -2
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { Play, RefreshCw, FolderGit2, Sparkles, ExternalLink, Trash2 } from 'lucide-react';
3
- import type { Feature, RunningService, ServiceConfig, OrchestrationDetection, RepoInfo } from '../../types.js';
2
+ import { Play, RefreshCw, FolderGit2, Sparkles, ExternalLink, Trash2, Terminal, Cpu, Layers, Copy, Check } from 'lucide-react';
3
+ import type { Feature, RunningService, ServiceConfig, OrchestrationDetection, RepoInfo, DetectedAI } from '../../types.js';
4
4
 
5
5
  // Feature subcomponents
6
6
  import { SessionHistory } from '../sessions/SessionHistory.js';
@@ -82,8 +82,9 @@ interface WorkspaceListProps {
82
82
  fetchSessionTranscript: (assistant: string, sessionId: string) => Promise<void>;
83
83
 
84
84
  // Navigation tab states
85
- subTab: any;
86
- setSubTab: (tab: any) => void;
85
+ subTab: 'overview' | 'sessions' | 'services' | 'changes' | 'knowledge' | 'plan';
86
+ setSubTab: (tab: 'overview' | 'sessions' | 'services' | 'changes' | 'knowledge' | 'plan') => void;
87
+ aiAssistants: DetectedAI[];
87
88
  }
88
89
 
89
90
  const parseInlineStyles = (text: string): React.ReactNode => {
@@ -241,7 +242,15 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
241
242
  // Tabs
242
243
  subTab,
243
244
  setSubTab,
245
+ aiAssistants,
244
246
  }) => {
247
+ const [copiedText, setCopiedText] = React.useState<string | null>(null);
248
+ const handleCopy = (text: string) => {
249
+ navigator.clipboard.writeText(text);
250
+ setCopiedText(text);
251
+ setTimeout(() => setCopiedText(null), 2000);
252
+ };
253
+
245
254
  return (
246
255
  <div className="max-w-5xl mx-auto">
247
256
  <header className="flex justify-between items-center mb-8">
@@ -276,19 +285,19 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
276
285
  {workspaces.map((ws) => {
277
286
  const isExpanded = activeWsId === ws.branchName;
278
287
  return (
279
- <div key={ws.id} className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-md">
288
+ <div key={ws.id} className="glass-card p-8 rounded-2xl shadow-xl hover:shadow-2xl border border-slate-800/40 relative overflow-hidden transition-all duration-300 before:absolute before:inset-x-0 before:top-0 before:h-[1px] before:bg-gradient-to-r before:from-indigo-500/10 via-purple-500/10 to-transparent">
280
289
  <div className="flex justify-between items-start gap-4 mb-4">
281
290
  <div>
282
291
  <h3 className="text-lg font-bold text-white hover:text-indigo-400 transition-colors">{ws.branchName}</h3>
283
- <div className="flex items-center gap-3 text-xs text-gray-500 mt-1">
292
+ <div className="flex items-center gap-3 text-xs text-slate-500 mt-1">
284
293
  <span>Created: {new Date(ws.createdAt).toLocaleDateString()}</span>
285
- <span className="h-1 w-1 rounded-full bg-gray-700"></span>
294
+ <span className="h-1 w-1 rounded-full bg-slate-850"></span>
286
295
  <span>{ws.repos.length} repos</span>
287
296
  </div>
288
297
  </div>
289
298
  <div className="flex gap-2">
290
299
  <button
291
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-emerald-650 hover:bg-emerald-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer shadow-md shadow-emerald-600/10 disabled:opacity-40 disabled:cursor-not-allowed"
300
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-emerald-650 hover:bg-emerald-600 text-white rounded-xl text-xs font-semibold transition-all cursor-pointer shadow-md shadow-emerald-600/10 disabled:opacity-40 disabled:cursor-not-allowed"
292
301
  onClick={() => handleResumeSession(ws)}
293
302
  disabled={resumingWs === ws.branchName}
294
303
  >
@@ -296,19 +305,19 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
296
305
  {resumingWs === ws.branchName ? 'Resuming...' : 'Resume Session'}
297
306
  </button>
298
307
  <button
299
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
308
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-slate-800 hover:border-slate-700 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-350 hover:text-white"
300
309
  onClick={() => handleCopyPrompt(ws)}
301
310
  >
302
311
  <Sparkles size={12} className="text-cyan-400" /> Copy Prompt
303
312
  </button>
304
313
  <button
305
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
314
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-slate-800 hover:border-slate-700 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-350 hover:text-white"
306
315
  onClick={() => handleOpenInEditor(ws.workspacePath)}
307
316
  >
308
317
  <ExternalLink size={12} /> Open
309
318
  </button>
310
319
  <button
311
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer shadow-md shadow-indigo-500/10"
320
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-indigo-500 hover:bg-indigo-600 text-white rounded-xl text-xs font-semibold transition-all cursor-pointer shadow-md shadow-indigo-500/10"
312
321
  onClick={() => {
313
322
  if (isExpanded) {
314
323
  setActiveWsId(null);
@@ -320,7 +329,7 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
320
329
  {isExpanded ? 'Hide Runner' : 'Orchestrate'}
321
330
  </button>
322
331
  <button
323
- className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-red-950/40 border border-red-900/60 hover:bg-red-900/60 hover:border-red-800 rounded-lg text-xs font-semibold text-red-200 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed animate-fade-in"
332
+ className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2.5 bg-red-950/40 border border-red-900/60 hover:bg-red-900/60 hover:border-red-800 rounded-xl text-xs font-semibold text-red-200 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed animate-fade-in"
324
333
  onClick={() => handleDeleteWorkspace(ws.branchName)}
325
334
  disabled={deleteWsLoading === ws.branchName}
326
335
  >
@@ -330,14 +339,14 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
330
339
  </div>
331
340
  </div>
332
341
 
333
- <div className="text-xs text-gray-400 bg-gray-950/40 border-l-2 border-indigo-500 rounded-r-lg px-4 py-3 mb-4 font-medium italic">
342
+ <div className="text-xs text-slate-350 bg-slate-950/60 border-l-2 border-indigo-500/80 rounded-r-xl px-5 py-3.5 mb-5 font-normal leading-relaxed relative overflow-hidden before:absolute before:inset-y-0 before:right-0 before:w-12 before:bg-gradient-to-r before:from-transparent before:to-indigo-500/2">
334
343
  {renderFormattedDescription(ws.description)}
335
344
  </div>
336
345
 
337
346
  <div className="flex items-center gap-3 mb-4">
338
347
  <div className="flex flex-wrap gap-2">
339
348
  {ws.repos.map((repoPath) => (
340
- <span key={repoPath} className="text-[10px] px-2.5 py-1 bg-gray-900 border border-gray-800 text-gray-300 rounded-md font-semibold">
349
+ <span key={repoPath} className="text-[10px] px-2.5 py-1.5 bg-slate-950/60 border border-slate-850 text-slate-300 rounded-lg font-semibold select-all">
341
350
  {repoPath.split(/[\\/]/).pop()}
342
351
  </span>
343
352
  ))}
@@ -347,7 +356,7 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
347
356
  <div className="flex items-center gap-1.5 ml-auto">
348
357
  <select
349
358
  id={`add-repo-select-feature-${ws.branchName}`}
350
- className="bg-gray-900 border border-gray-800 text-gray-300 rounded-md text-[10px] px-2 py-1 font-semibold outline-none focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
359
+ className="bg-slate-905 border border-slate-800 text-slate-300 rounded-lg text-[10px] px-3 py-1.5 font-semibold outline-none focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-all hover:border-slate-700"
351
360
  defaultValue=""
352
361
  onChange={async (e) => {
353
362
  const val = e.target.value;
@@ -374,54 +383,64 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
374
383
 
375
384
  {/* Expansion: Process management console */}
376
385
  {isExpanded && (
377
- <div className="mt-6 pt-6 border-t border-gray-800/80">
386
+ <div className="mt-6 pt-6 border-t border-slate-850/80">
378
387
  {/* Sub-tab Navigation */}
379
- <div className="flex border-b border-gray-800/80 mb-6 gap-2">
388
+ <div className="flex flex-wrap items-center gap-1.5 p-1 bg-slate-950/60 rounded-xl border border-slate-900/60 mb-6 w-max max-w-full overflow-x-auto">
389
+ <button
390
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
391
+ subTab === 'overview'
392
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
393
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
394
+ }`}
395
+ onClick={() => setSubTab('overview')}
396
+ >
397
+ Harness & Capabilities
398
+ </button>
380
399
  <button
381
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
400
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
382
401
  subTab === 'sessions'
383
- ? 'border-indigo-500 text-white'
384
- : 'border-transparent text-gray-500 hover:text-gray-300'
402
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
403
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
385
404
  }`}
386
405
  onClick={() => setSubTab('sessions')}
387
406
  >
388
407
  AI Session History
389
408
  </button>
390
409
  <button
391
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
410
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
392
411
  subTab === 'services'
393
- ? 'border-indigo-500 text-white'
394
- : 'border-transparent text-gray-500 hover:text-gray-300'
412
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
413
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
395
414
  }`}
396
415
  onClick={() => setSubTab('services')}
397
416
  >
398
417
  Orchestrated Services
399
418
  </button>
400
419
  <button
401
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
420
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
402
421
  subTab === 'changes'
403
- ? 'border-indigo-500 text-white'
404
- : 'border-transparent text-gray-500 hover:text-gray-300'
422
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
423
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
405
424
  }`}
406
425
  onClick={() => setSubTab('changes')}
407
426
  >
408
427
  Active Changes (AI Diffs)
409
428
  </button>
410
429
  <button
411
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
430
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
412
431
  subTab === 'knowledge'
413
- ? 'border-indigo-500 text-white'
414
- : 'border-transparent text-gray-500 hover:text-gray-300'
432
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
433
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
415
434
  }`}
416
435
  onClick={() => setSubTab('knowledge')}
417
436
  >
418
437
  Knowledge Base
419
438
  </button>
420
439
  <button
421
- className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
440
+ className={`px-4 py-2 rounded-lg text-xs font-bold transition-all cursor-pointer select-none ${
422
441
  subTab === 'plan'
423
- ? 'border-indigo-500 text-white'
424
- : 'border-transparent text-gray-500 hover:text-gray-300'
442
+ ? 'bg-indigo-500/15 border border-indigo-500/35 text-indigo-300 shadow-[0_0_15px_rgba(99,102,241,0.05)]'
443
+ : 'text-slate-500 hover:text-slate-350 hover:bg-slate-900/30 border border-transparent'
425
444
  }`}
426
445
  onClick={() => setSubTab('plan')}
427
446
  >
@@ -429,6 +448,139 @@ export const WorkspaceList: React.FC<WorkspaceListProps> = ({
429
448
  </button>
430
449
  </div>
431
450
 
451
+ {subTab === 'overview' && (
452
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-gray-300 animate-fade-in">
453
+ {/* Section 1: AI Assistant Harnesses */}
454
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
455
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
456
+ <Cpu size={18} className="text-indigo-400" />
457
+ <h4 className="font-bold text-white">AI Assistant Harnesses</h4>
458
+ </div>
459
+ <p className="text-xs text-gray-400 mb-4">
460
+ These coding assistants are configured to run contextually inside this workspace. Select an assistant to see its setup.
461
+ </p>
462
+ <div className="flex flex-col gap-3">
463
+ {['antigravity', 'claude', 'copilot', 'cursor', 'codex'].map((name) => {
464
+ const isConfigured = ws.assistants.includes(name as any);
465
+ const isDetected = aiAssistants.find((a) => a.name === name)?.detected;
466
+ let displayName = name.charAt(0).toUpperCase() + name.slice(1);
467
+ if (name === 'antigravity') displayName = 'Antigravity (Ollama/Gemini)';
468
+ else if (name === 'codex') displayName = 'OpenAI Codex';
469
+ else if (name === 'copilot') displayName = 'GitHub Copilot';
470
+ else if (name === 'claude') displayName = 'Claude Code';
471
+
472
+ let resumeCmd = '';
473
+ if (name === 'antigravity') resumeCmd = 'agy --continue';
474
+ else if (name === 'claude') resumeCmd = 'claude --resume';
475
+ else if (name === 'codex') resumeCmd = 'codex resume';
476
+ else if (name === 'copilot') resumeCmd = 'copilot --resume';
477
+
478
+ return (
479
+ <div key={name} className="flex flex-col p-3 bg-gray-900/40 border border-gray-800/60 rounded-lg">
480
+ <div className="flex items-center justify-between gap-2">
481
+ <div className="flex items-center gap-2">
482
+ <span className={`h-2 w-2 rounded-full ${isConfigured ? 'bg-indigo-500 shadow-sm' : 'bg-gray-700'}`}></span>
483
+ <span className="font-semibold text-xs text-gray-200">{displayName}</span>
484
+ </div>
485
+ <div className="flex items-center gap-1.5 text-[10px] font-medium">
486
+ {isConfigured && <span className="text-indigo-400 bg-indigo-500/10 px-2 py-0.5 rounded border border-indigo-500/20 text-[9px] font-bold uppercase tracking-wider">Active</span>}
487
+ {isDetected ? (
488
+ <span className="text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20 text-[9px] font-bold uppercase tracking-wider">Installed</span>
489
+ ) : (
490
+ <span className="text-gray-500 bg-gray-950 px-2 py-0.5 rounded border border-gray-800/60 text-[9px] font-bold uppercase tracking-wider">Not Probed</span>
491
+ )}
492
+ </div>
493
+ </div>
494
+ {isConfigured && resumeCmd && (
495
+ <div className="mt-2.5 flex items-center justify-between gap-2 bg-gray-950/80 px-2.5 py-1.5 rounded border border-gray-800/60 font-mono text-[10px]">
496
+ <span className="text-indigo-300 truncate">{resumeCmd}</span>
497
+ <button
498
+ onClick={() => handleCopy(resumeCmd)}
499
+ className="text-gray-400 hover:text-white transition-colors cursor-pointer"
500
+ title="Copy Resume Command"
501
+ >
502
+ {copiedText === resumeCmd ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
503
+ </button>
504
+ </div>
505
+ )}
506
+ </div>
507
+ );
508
+ })}
509
+ </div>
510
+ </div>
511
+
512
+ {/* Section 2: Model Context Protocol (MCP) */}
513
+ <div className="flex flex-col gap-6">
514
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
515
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
516
+ <Layers size={18} className="text-indigo-400" />
517
+ <h4 className="font-bold text-white">Local MCP Server (Tools)</h4>
518
+ </div>
519
+ <p className="text-xs text-gray-400 mb-4">
520
+ NexusFlow runs a Model Context Protocol (MCP) server that exposes local workspace-scoped tools to your AI agents.
521
+ </p>
522
+ <div className="flex flex-col gap-3.5">
523
+ <div className="flex flex-col gap-1">
524
+ <div className="flex items-center gap-2 text-xs font-semibold text-indigo-300">
525
+ <code>search_workspace</code>
526
+ </div>
527
+ <span className="text-[11px] text-gray-400">
528
+ Extremely fast query/regex search across all microservices and repos in the workspace.
529
+ </span>
530
+ </div>
531
+ <div className="flex flex-col gap-1 border-t border-gray-800/40 pt-2.5">
532
+ <div className="flex items-center gap-2 text-xs font-semibold text-indigo-300">
533
+ <code>get_service_logs</code>
534
+ </div>
535
+ <span className="text-[11px] text-gray-400">
536
+ Fetch and parse recent stdout/stderr output from any background service running in the workspace.
537
+ </span>
538
+ </div>
539
+ <div className="flex flex-col gap-1 border-t border-gray-800/40 pt-2.5">
540
+ <div className="flex items-center gap-2 text-xs font-semibold text-indigo-300">
541
+ <code>delegate_to_local_agent</code>
542
+ </div>
543
+ <span className="text-[11px] text-gray-400">
544
+ Delegate sub-tasks or log analysis to a lightweight LLM running locally to optimize remote token usage.
545
+ </span>
546
+ </div>
547
+ </div>
548
+ </div>
549
+
550
+ {/* CLI shortcuts */}
551
+ <div className="bg-gray-950/20 border border-gray-800/80 rounded-xl p-5">
552
+ <div className="flex items-center gap-2 mb-4 border-b border-gray-800/80 pb-3">
553
+ <Terminal size={18} className="text-indigo-400" />
554
+ <h4 className="font-bold text-white">Workspace CLI Shortcuts</h4>
555
+ </div>
556
+ <div className="flex flex-col gap-2.5">
557
+ {[
558
+ { cmd: 'nexusflow start', desc: 'Spin up detected backend/frontend services' },
559
+ { cmd: 'nexusflow sync', desc: 'Rebase worktrees with upstream defaults' },
560
+ { cmd: 'nexusflow commit -m "update"', desc: 'Stage and commit changes across repos' },
561
+ { cmd: 'nexusflow diff', desc: 'Unified codebase diff across repos' },
562
+ { cmd: 'nexusflow handoff', desc: 'Generate resume context file' }
563
+ ].map(({ cmd, desc }) => (
564
+ <div key={cmd} className="flex flex-col gap-1">
565
+ <div className="flex items-center justify-between gap-2">
566
+ <code className="text-xs text-indigo-300">{cmd}</code>
567
+ <button
568
+ onClick={() => handleCopy(cmd)}
569
+ className="text-gray-400 hover:text-white transition-colors cursor-pointer"
570
+ title="Copy Command"
571
+ >
572
+ {copiedText === cmd ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
573
+ </button>
574
+ </div>
575
+ <span className="text-[11px] text-gray-550">{desc}</span>
576
+ </div>
577
+ ))}
578
+ </div>
579
+ </div>
580
+ </div>
581
+ </div>
582
+ )}
583
+
432
584
  {subTab === 'sessions' && (
433
585
  <SessionHistory
434
586
  ws={ws}
package/gui/src/index.css CHANGED
@@ -1,12 +1,62 @@
1
1
  @import "tailwindcss";
2
2
 
3
+ /* Custom Sleek Scrollbars */
4
+ ::-webkit-scrollbar {
5
+ width: 6px;
6
+ height: 6px;
7
+ }
8
+ ::-webkit-scrollbar-track {
9
+ background: transparent;
10
+ }
11
+ ::-webkit-scrollbar-thumb {
12
+ background: rgba(99, 102, 241, 0.15);
13
+ border-radius: 8px;
14
+ border: 1px solid rgba(255, 255, 255, 0.03);
15
+ }
16
+ ::-webkit-scrollbar-thumb:hover {
17
+ background: rgba(99, 102, 241, 0.3);
18
+ }
19
+
20
+ /* Glassmorphism and Modern UI Helpers */
21
+ @utility glass-card {
22
+ background: rgba(17, 24, 39, 0.45);
23
+ backdrop-filter: blur(16px);
24
+ -webkit-backdrop-filter: blur(16px);
25
+ border: 1px solid rgba(255, 255, 255, 0.05);
26
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
27
+ }
28
+
29
+ @utility glass-card-glow {
30
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.1), inset 0 0 1px 1px rgba(255, 255, 255, 0.05);
31
+ }
32
+
33
+ @utility text-glow-indigo {
34
+ text-shadow: 0 0 12px rgba(99, 102, 241, 0.4);
35
+ }
36
+
3
37
  @utility animate-slide-in {
4
- animation: slide-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
38
+ animation: slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
39
+ }
40
+
41
+ @utility animate-fade-in {
42
+ animation: fade-in 0.2s ease-out forwards;
43
+ }
44
+
45
+ @utility animate-spin-slow {
46
+ animation: spin 8s linear infinite;
47
+ }
48
+
49
+ @utility animate-pulse-glow-green {
50
+ animation: pulse-glow-green 2s infinite ease-in-out;
51
+ }
52
+
53
+ @utility animate-pulse-glow-indigo {
54
+ animation: pulse-glow-indigo 2.5s infinite ease-in-out;
5
55
  }
6
56
 
7
57
  @keyframes slide-in {
8
58
  from {
9
- transform: translateY(1rem);
59
+ transform: translateY(0.75rem);
10
60
  opacity: 0;
11
61
  }
12
62
  to {
@@ -15,3 +65,46 @@
15
65
  }
16
66
  }
17
67
 
68
+ @keyframes fade-in {
69
+ from {
70
+ opacity: 0;
71
+ }
72
+ to {
73
+ opacity: 1;
74
+ }
75
+ }
76
+
77
+ @keyframes pulse-glow-green {
78
+ 0%, 100% {
79
+ box-shadow: 0 0 4px rgba(52, 211, 153, 0.2);
80
+ opacity: 0.8;
81
+ }
82
+ 50% {
83
+ box-shadow: 0 0 12px rgba(52, 211, 153, 0.6);
84
+ opacity: 1;
85
+ }
86
+ }
87
+
88
+ @keyframes pulse-glow-indigo {
89
+ 0%, 100% {
90
+ box-shadow: 0 0 4px rgba(99, 102, 241, 0.2);
91
+ }
92
+ 50% {
93
+ box-shadow: 0 0 14px rgba(99, 102, 241, 0.5);
94
+ }
95
+ }
96
+
97
+ /* Custom terminal colors */
98
+ .term-matrix {
99
+ color: #10b981;
100
+ background-color: #030712;
101
+ }
102
+ .term-dracula {
103
+ color: #f8f8f2;
104
+ background-color: #282a36;
105
+ }
106
+ .term-classic {
107
+ color: #e5e7eb;
108
+ background-color: #0b0f19;
109
+ }
110
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrpatronz/nexusflow",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Combine multiple repos into a workspace with rich AI assistant context",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -43,6 +43,7 @@
43
43
  "globby": "^14.1.0",
44
44
  "hono": "^4.12.23",
45
45
  "ora": "^8.1.0",
46
+ "pm2": "^7.0.1",
46
47
  "repomix": "^1.14.1"
47
48
  },
48
49
  "devDependencies": {
@@ -5,7 +5,7 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import ora from 'ora';
8
- import { select } from '@inquirer/prompts';
8
+ import { select, search } from '@inquirer/prompts';
9
9
  import * as path from 'node:path';
10
10
 
11
11
  import { loadConfig } from '../core/config.js';
@@ -57,7 +57,7 @@ export async function addRepoCommand(
57
57
  } else {
58
58
  const cwdFeature = await loadFeatureConfig(process.cwd());
59
59
  if (cwdFeature) {
60
- workspacePath = process.cwd();
60
+ workspacePath = cwdFeature.workspacePath;
61
61
  workspaceName = cwdFeature.branchName;
62
62
  } else {
63
63
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -66,12 +66,20 @@ export async function addRepoCommand(
66
66
  return;
67
67
  }
68
68
 
69
- const selected = await select({
70
- message: 'Select a workspace to add a repository to:',
71
- choices: workspaces.map((ws) => ({
72
- name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
73
- value: ws,
74
- })),
69
+ const selected = await search({
70
+ message: 'Search and select a workspace to add a repository to:',
71
+ source: async (input) => {
72
+ const query = (input || '').toLowerCase();
73
+ const filtered = workspaces.filter(
74
+ (ws) =>
75
+ ws.branchName.toLowerCase().includes(query) ||
76
+ ws.workspacePath.toLowerCase().includes(query)
77
+ );
78
+ return filtered.map((ws) => ({
79
+ name: `${ws.branchName} ${chalk.dim(`(${ws.repos.length} repos)`)}`,
80
+ value: ws,
81
+ }));
82
+ },
75
83
  });
76
84
 
77
85
  workspacePath = selected.workspacePath;
@@ -113,12 +121,20 @@ export async function addRepoCommand(
113
121
  return;
114
122
  }
115
123
 
116
- repoPathToAdd = await select({
117
- message: 'Select a repository to add:',
118
- choices: availableRepos.map((r) => ({
119
- name: `${r.name} ${chalk.dim(`(${r.path})`)}`,
120
- value: r.path,
121
- })),
124
+ repoPathToAdd = await search({
125
+ message: 'Search and select a repository to add:',
126
+ source: async (input) => {
127
+ const query = (input || '').toLowerCase();
128
+ const filtered = availableRepos.filter(
129
+ (r) =>
130
+ r.name.toLowerCase().includes(query) ||
131
+ r.path.toLowerCase().includes(query)
132
+ );
133
+ return filtered.map((r) => ({
134
+ name: `${r.name} ${chalk.dim(`(${r.path})`)}`,
135
+ value: r.path,
136
+ }));
137
+ },
122
138
  });
123
139
  }
124
140
 
@@ -109,7 +109,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
109
109
  }
110
110
 
111
111
  const cwdFeature = await loadFeatureConfig(process.cwd());
112
- if (cwdFeature) return process.cwd();
112
+ if (cwdFeature) return cwdFeature.workspacePath;
113
113
 
114
114
  const config = await loadConfig();
115
115
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -103,7 +103,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
103
103
  }
104
104
 
105
105
  const cwdFeature = await loadFeatureConfig(process.cwd());
106
- if (cwdFeature) return process.cwd();
106
+ if (cwdFeature) return cwdFeature.workspacePath;
107
107
 
108
108
  const config = await loadConfig();
109
109
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -335,7 +335,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
335
335
  }
336
336
 
337
337
  const cwdFeature = await loadFeatureConfig(process.cwd());
338
- if (cwdFeature) return process.cwd();
338
+ if (cwdFeature) return cwdFeature.workspacePath;
339
339
 
340
340
  const config = await loadConfig();
341
341
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -244,7 +244,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
244
244
  }
245
245
 
246
246
  const cwdFeature = await loadFeatureConfig(process.cwd());
247
- if (cwdFeature) return process.cwd();
247
+ if (cwdFeature) return cwdFeature.workspacePath;
248
248
 
249
249
  const config = await loadConfig();
250
250
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -41,7 +41,7 @@ async function resolveWorkspace(workspaceArg?: string): Promise<string | null> {
41
41
  if (workspaceArg) return workspaceArg;
42
42
 
43
43
  const cwdFeature = await loadFeatureConfig(process.cwd());
44
- if (cwdFeature) return process.cwd();
44
+ if (cwdFeature) return cwdFeature.workspacePath;
45
45
 
46
46
  const config = await loadConfig();
47
47
  const workspaces = await listWorkspaces(config.workspacesDir);
@@ -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
@@ -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);