@mrpatronz/nexusflow 0.1.7 → 0.1.9

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/gui/src/App.tsx CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  ExternalLink,
10
10
  Check,
11
11
  AlertTriangle,
12
+ AlertCircle,
12
13
  FolderOpen,
13
14
  ArrowRight,
14
15
  ArrowLeft,
@@ -126,6 +127,13 @@ export default function App() {
126
127
  const [selectedEditor, setSelectedEditor] = useState<DetectedEditor | null>(null);
127
128
  const [creating, setCreating] = useState(false);
128
129
  const [createdWorkspace, setCreatedWorkspace] = useState<{ path: string } | null>(null);
130
+ const [creationError, setCreationError] = useState<string | null>(null);
131
+ const [creationSteps, setCreationSteps] = useState<any[]>([
132
+ { id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
133
+ { id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
134
+ { id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
135
+ { id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
136
+ ]);
129
137
 
130
138
  // Resumption Commands State
131
139
  const [testCommand, setTestCommand] = useState('npm run test');
@@ -561,6 +569,16 @@ export default function App() {
561
569
  const handleCreateWorkspace = async () => {
562
570
  if (!branchName || selectedRepos.length === 0) return;
563
571
  setCreating(true);
572
+ setCreationError(null);
573
+
574
+ // Reset steps to pending
575
+ setCreationSteps([
576
+ { id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
577
+ { id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
578
+ { id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
579
+ { id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
580
+ ]);
581
+
564
582
  try {
565
583
  const res = await fetch(`${API_BASE}/api/workspace`, {
566
584
  method: 'POST',
@@ -579,32 +597,62 @@ export default function App() {
579
597
  });
580
598
  const data = await res.json();
581
599
  if (res.ok && data.success) {
582
- setCreatedWorkspace({ path: data.workspacePath });
583
- setActiveStep(3);
584
- fetchWorkspaces();
585
-
586
- if (selectedEditor) {
587
- if (isVsCode) {
588
- window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath: data.workspacePath }, '*');
589
- } else {
590
- await fetch(`${API_BASE}/api/open-editor`, {
591
- method: 'POST',
592
- headers: { 'Content-Type': 'application/json' },
593
- body: JSON.stringify({
594
- workspacePath: data.workspacePath,
595
- command: selectedEditor.command,
596
- }),
597
- });
600
+ const jobId = data.jobId;
601
+
602
+ // Establish EventSource SSE connection
603
+ const eventSource = new EventSource(`${API_BASE}/api/workspace/create-stream/${encodeURIComponent(jobId)}`);
604
+
605
+ eventSource.addEventListener('progress', (e) => {
606
+ try {
607
+ const job = JSON.parse(e.data);
608
+ if (job.steps) {
609
+ setCreationSteps(job.steps);
610
+ }
611
+ if (job.status === 'completed') {
612
+ eventSource.close();
613
+ setCreatedWorkspace({ path: job.workspacePath });
614
+ setActiveStep(3);
615
+ setCreating(false);
616
+ fetchWorkspaces();
617
+
618
+ if (selectedEditor) {
619
+ if (isVsCode) {
620
+ window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath: job.workspacePath }, '*');
621
+ } else {
622
+ fetch(`${API_BASE}/api/open-editor`, {
623
+ method: 'POST',
624
+ headers: { 'Content-Type': 'application/json' },
625
+ body: JSON.stringify({
626
+ workspacePath: job.workspacePath,
627
+ command: selectedEditor.command,
628
+ }),
629
+ }).catch(console.error);
630
+ }
631
+ }
632
+ } else if (job.status === 'failed') {
633
+ eventSource.close();
634
+ setCreating(false);
635
+ setCreationError(job.error || 'Failed to create workspace');
636
+ }
637
+ } catch (err) {
638
+ console.error('Failed to parse SSE data:', err);
598
639
  }
599
- }
640
+ });
641
+
642
+ eventSource.onerror = (err) => {
643
+ console.error('SSE Error:', err);
644
+ eventSource.close();
645
+ setCreating(false);
646
+ setCreationError('Connection lost while building workspace.');
647
+ };
600
648
  } else {
601
- alert(`Error: ${data.error || 'Failed to create workspace'}`);
649
+ setCreating(false);
650
+ setCreationError(data.error || 'Failed to initialize workspace build');
602
651
  }
603
652
  } catch (e) {
604
653
  console.error(e);
605
- alert('Network error when creating workspace.');
606
- } finally {
607
654
  setCreating(false);
655
+ setCreationError('Network error when creating workspace.');
608
656
  }
609
657
  };
610
658
 
@@ -1374,145 +1422,235 @@ Core Instructions:
1374
1422
  {/* Step 2: AI & Editor Settings */}
1375
1423
  {activeStep === 2 && (
1376
1424
  <div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
1377
- <div className="mb-8">
1378
- <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Target AI Assistant(s)</label>
1379
- <p className="text-xs text-gray-500 mb-4">
1380
- We generate context configurations matching the specifications of each checked assistant.
1381
- </p>
1382
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
1383
- {aiAssistants.map((ai) => {
1384
- const isSelected = selectedAI.includes(ai.name);
1385
- return (
1386
- <div
1387
- key={ai.name}
1388
- className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-3 cursor-pointer hover:border-gray-700 transition-all ${
1389
- isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1390
- }`}
1391
- onClick={() => handleToggleAI(ai.name)}
1392
- >
1393
- <div className="flex justify-between items-center">
1394
- <span className="text-sm font-bold text-white">{ai.displayName}</span>
1395
- <span className={`text-[9px] px-2 py-0.5 rounded font-semibold uppercase ${
1396
- ai.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1397
- }`}>
1398
- {ai.detected ? 'Installed' : 'Missing'}
1399
- </span>
1425
+ {creating ? (
1426
+ <div className="flex flex-col items-center py-6">
1427
+ <h3 className="text-lg font-bold text-white mb-2 flex items-center gap-2.5">
1428
+ <RefreshCw className="animate-spin text-indigo-400" size={20} />
1429
+ Building Workspace...
1430
+ </h3>
1431
+ <p className="text-xs text-gray-400 mb-8">
1432
+ Setting up your multi-repo workspace. This will take a moment.
1433
+ </p>
1434
+
1435
+ <div className="w-full max-w-md space-y-4">
1436
+ {creationSteps.map((step) => {
1437
+ const isPending = step.status === 'pending';
1438
+ const isRunning = step.status === 'running';
1439
+ const isCompleted = step.status === 'completed';
1440
+ const isFailed = step.status === 'failed';
1441
+
1442
+ return (
1443
+ <div
1444
+ key={step.id}
1445
+ className={`flex items-start gap-4 p-4 rounded-xl border transition-all duration-300 ${
1446
+ isRunning
1447
+ ? 'bg-indigo-500/10 border-indigo-500/50 shadow-md shadow-indigo-500/5'
1448
+ : isCompleted
1449
+ ? 'bg-emerald-500/5 border-emerald-500/20 opacity-80'
1450
+ : isFailed
1451
+ ? 'bg-rose-500/5 border-rose-500/30'
1452
+ : 'bg-gray-900/20 border-gray-800/40 opacity-40'
1453
+ }`}
1454
+ >
1455
+ <div className="mt-0.5">
1456
+ {isRunning && (
1457
+ <div className="relative flex h-5 w-5 items-center justify-center">
1458
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-400 opacity-75"></span>
1459
+ <RefreshCw className="animate-spin text-indigo-400 relative" size={16} />
1460
+ </div>
1461
+ )}
1462
+ {isCompleted && (
1463
+ <div className="h-5 w-5 rounded-full bg-emerald-500/20 border border-emerald-500/40 flex items-center justify-center text-emerald-400">
1464
+ <Check size={12} />
1465
+ </div>
1466
+ )}
1467
+ {isFailed && (
1468
+ <div className="h-5 w-5 rounded-full bg-rose-500/20 border border-rose-500/40 flex items-center justify-center text-rose-400">
1469
+ <AlertTriangle size={12} />
1470
+ </div>
1471
+ )}
1472
+ {isPending && (
1473
+ <div className="h-5 w-5 rounded-full border-2 border-gray-800 flex items-center justify-center text-gray-600">
1474
+ <div className="w-1.5 h-1.5 rounded-full bg-gray-800"></div>
1475
+ </div>
1476
+ )}
1477
+ </div>
1478
+ <div className="flex-1 min-w-0">
1479
+ <h4 className={`text-sm font-bold truncate ${
1480
+ isRunning ? 'text-indigo-400' : isCompleted ? 'text-emerald-400' : isFailed ? 'text-rose-400' : 'text-gray-500'
1481
+ }`}>
1482
+ {step.name}
1483
+ </h4>
1484
+ <p className="text-xs text-gray-400 mt-1 font-mono break-words leading-relaxed">
1485
+ {step.message}
1486
+ </p>
1487
+ </div>
1400
1488
  </div>
1401
- <p className="text-[11px] text-gray-500">
1402
- {ai.name === 'claude' && 'CLAUDE.md guidelines'}
1403
- {ai.name === 'antigravity' && 'CLAUDE.md guidelines (harness)'}
1404
- {ai.name === 'codex' && 'AGENTS.md config structure'}
1405
- {ai.name === 'copilot' && 'GitHub Copilot Workspace configs'}
1406
- {ai.name === 'cursor' && 'Cursor MDC rules and instructions'}
1407
- </p>
1408
- </div>
1409
- );
1410
- })}
1489
+ );
1490
+ })}
1491
+ </div>
1492
+
1493
+ {creationError && (
1494
+ <div className="mt-6 p-4 bg-rose-500/10 border border-rose-500/20 text-rose-450 rounded-xl text-xs w-full max-w-md flex flex-col gap-3">
1495
+ <span className="font-bold flex items-center gap-1.5">
1496
+ <AlertCircle size={14} className="text-rose-450" /> Build Failed
1497
+ </span>
1498
+ <span className="font-mono">{creationError}</span>
1499
+ <button
1500
+ className="w-full mt-2 py-2 px-4 rounded-lg bg-rose-600 hover:bg-rose-700 text-white font-bold text-[11px] transition-colors cursor-pointer"
1501
+ onClick={() => {
1502
+ setCreating(false);
1503
+ setCreationError(null);
1504
+ }}
1505
+ >
1506
+ Dismiss
1507
+ </button>
1508
+ </div>
1509
+ )}
1411
1510
  </div>
1412
- </div>
1511
+ ) : (
1512
+ <>
1513
+ <div className="mb-8">
1514
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Target AI Assistant(s)</label>
1515
+ <p className="text-xs text-gray-500 mb-4">
1516
+ We generate context configurations matching the specifications of each checked assistant.
1517
+ </p>
1518
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
1519
+ {aiAssistants.map((ai) => {
1520
+ const isSelected = selectedAI.includes(ai.name);
1521
+ return (
1522
+ <div
1523
+ key={ai.name}
1524
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-3 cursor-pointer hover:border-gray-700 transition-all ${
1525
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1526
+ }`}
1527
+ onClick={() => handleToggleAI(ai.name)}
1528
+ >
1529
+ <div className="flex justify-between items-center">
1530
+ <span className="text-sm font-bold text-white">{ai.displayName}</span>
1531
+ <span className={`text-[9px] px-2 py-0.5 rounded font-semibold uppercase ${
1532
+ ai.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1533
+ }`}>
1534
+ {ai.detected ? 'Installed' : 'Missing'}
1535
+ </span>
1536
+ </div>
1537
+ <p className="text-[11px] text-gray-500">
1538
+ {ai.name === 'claude' && 'CLAUDE.md guidelines'}
1539
+ {ai.name === 'antigravity' && 'CLAUDE.md guidelines (harness)'}
1540
+ {ai.name === 'codex' && 'AGENTS.md config structure'}
1541
+ {ai.name === 'copilot' && 'GitHub Copilot Workspace configs'}
1542
+ {ai.name === 'cursor' && 'Cursor MDC rules and instructions'}
1543
+ </p>
1544
+ </div>
1545
+ );
1546
+ })}
1547
+ </div>
1548
+ </div>
1413
1549
 
1414
- <div className="mb-8">
1415
- <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Open Workspace In</label>
1416
- <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
1417
- {editors.map((ed) => {
1418
- const isSelected = selectedEditor?.command === ed.command;
1419
- return (
1550
+ <div className="mb-8">
1551
+ <label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Open Workspace In</label>
1552
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
1553
+ {editors.map((ed) => {
1554
+ const isSelected = selectedEditor?.command === ed.command;
1555
+ return (
1556
+ <div
1557
+ key={ed.command}
1558
+ className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
1559
+ isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1560
+ }`}
1561
+ onClick={() => setSelectedEditor(ed)}
1562
+ >
1563
+ <span className="text-sm font-bold text-white">{ed.name}</span>
1564
+ <span className={`text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase ${
1565
+ ed.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1566
+ }`}>
1567
+ {ed.detected ? 'Detected' : 'Missing'}
1568
+ </span>
1569
+ </div>
1570
+ );
1571
+ })}
1420
1572
  <div
1421
- key={ed.command}
1422
1573
  className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
1423
- isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1574
+ selectedEditor === null ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1424
1575
  }`}
1425
- onClick={() => setSelectedEditor(ed)}
1576
+ onClick={() => setSelectedEditor(null)}
1426
1577
  >
1427
- <span className="text-sm font-bold text-white">{ed.name}</span>
1428
- <span className={`text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase ${
1429
- ed.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
1430
- }`}>
1431
- {ed.detected ? 'Detected' : 'Missing'}
1578
+ <span className="text-sm font-bold text-white">None</span>
1579
+ <span className="text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase bg-gray-800 text-gray-500">
1580
+ Skip opening
1432
1581
  </span>
1433
1582
  </div>
1434
- );
1435
- })}
1436
- <div
1437
- className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
1438
- selectedEditor === null ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
1439
- }`}
1440
- onClick={() => setSelectedEditor(null)}
1441
- >
1442
- <span className="text-sm font-bold text-white">None</span>
1443
- <span className="text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase bg-gray-800 text-gray-500">
1444
- Skip opening
1445
- </span>
1583
+ </div>
1446
1584
  </div>
1447
- </div>
1448
- </div>
1449
1585
 
1450
- <div className="mb-8 border border-gray-800/80 rounded-xl p-5 bg-gray-950/20">
1451
- <h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
1452
- <Terminal size={14} className="text-indigo-400" /> Advanced Session Resumption Settings
1453
- </h4>
1454
- <p className="text-[11px] text-gray-500 mb-4">
1455
- Configure setup and verification test commands. AI assistants and the dashboard use these to resume your sessions green.
1456
- </p>
1457
- <div className="space-y-4">
1458
- <div>
1459
- <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Verification / Test Command</label>
1460
- <input
1461
- type="text"
1462
- className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1463
- placeholder="e.g., npm run test or vitest run"
1464
- value={testCommand}
1465
- onChange={(e) => setTestCommand(e.target.value)}
1466
- />
1467
- </div>
1468
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1469
- <div>
1470
- <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Mock / Database Command (Optional)</label>
1471
- <input
1472
- type="text"
1473
- className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1474
- placeholder="e.g., docker compose up -d redis"
1475
- value={mockCommand}
1476
- onChange={(e) => setMockCommand(e.target.value)}
1477
- />
1478
- </div>
1479
- <div>
1480
- <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Start / Run Command (Optional)</label>
1481
- <input
1482
- type="text"
1483
- className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1484
- placeholder="e.g., npm run start:dev"
1485
- value={startCommand}
1486
- onChange={(e) => setStartCommand(e.target.value)}
1487
- />
1586
+ <div className="mb-8 border border-gray-800/80 rounded-xl p-5 bg-gray-950/20">
1587
+ <h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
1588
+ <Terminal size={14} className="text-indigo-400" /> Advanced Session Resumption Settings
1589
+ </h4>
1590
+ <p className="text-[11px] text-gray-500 mb-4">
1591
+ Configure setup and verification test commands. AI assistants and the dashboard use these to resume your sessions green.
1592
+ </p>
1593
+ <div className="space-y-4">
1594
+ <div>
1595
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Verification / Test Command</label>
1596
+ <input
1597
+ type="text"
1598
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1599
+ placeholder="e.g., npm run test or vitest run"
1600
+ value={testCommand}
1601
+ onChange={(e) => setTestCommand(e.target.value)}
1602
+ />
1603
+ </div>
1604
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1605
+ <div>
1606
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Mock / Database Command (Optional)</label>
1607
+ <input
1608
+ type="text"
1609
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1610
+ placeholder="e.g., docker compose up -d redis"
1611
+ value={mockCommand}
1612
+ onChange={(e) => setMockCommand(e.target.value)}
1613
+ />
1614
+ </div>
1615
+ <div>
1616
+ <label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Start / Run Command (Optional)</label>
1617
+ <input
1618
+ type="text"
1619
+ className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
1620
+ placeholder="e.g., npm run start:dev"
1621
+ value={startCommand}
1622
+ onChange={(e) => setStartCommand(e.target.value)}
1623
+ />
1624
+ </div>
1625
+ </div>
1488
1626
  </div>
1489
1627
  </div>
1490
- </div>
1491
- </div>
1492
1628
 
1493
- <div className="flex justify-between">
1494
- <button
1495
- className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
1496
- onClick={() => setActiveStep(1)}
1497
- >
1498
- <ArrowLeft size={16} /> Back
1499
- </button>
1500
- <button
1501
- className="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed"
1502
- disabled={creating}
1503
- onClick={handleCreateWorkspace}
1504
- >
1505
- {creating ? (
1506
- <>
1507
- <RefreshCw className="animate-spin" size={14} /> Building...
1508
- </>
1509
- ) : (
1510
- <>
1511
- <Sparkles size={14} /> Build Workspace
1512
- </>
1513
- )}
1514
- </button>
1515
- </div>
1629
+ <div className="flex justify-between">
1630
+ <button
1631
+ className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
1632
+ onClick={() => setActiveStep(1)}
1633
+ >
1634
+ <ArrowLeft size={16} /> Back
1635
+ </button>
1636
+ <button
1637
+ className="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed"
1638
+ disabled={creating}
1639
+ onClick={handleCreateWorkspace}
1640
+ >
1641
+ {creating ? (
1642
+ <>
1643
+ <RefreshCw className="animate-spin" size={14} /> Building...
1644
+ </>
1645
+ ) : (
1646
+ <>
1647
+ <Sparkles size={14} /> Build Workspace
1648
+ </>
1649
+ )}
1650
+ </button>
1651
+ </div>
1652
+ </>
1653
+ )}
1516
1654
  </div>
1517
1655
  )}
1518
1656
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrpatronz/nexusflow",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Combine multiple repos into a workspace with rich AI assistant context",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,6 +14,7 @@ import { confirm } from '@inquirer/prompts';
14
14
  import { loadConfig } from '../core/config.js';
15
15
  import { scanForRepos } from '../core/scanner.js';
16
16
  import { createWorkspace } from '../core/workspace.js';
17
+ import { packWorkspace } from '../core/packer.js';
17
18
  import { generateContextFiles } from '../generators/index.js';
18
19
  import { analyzeAllRepos } from '../analyzers/index.js';
19
20
  import { detectAIAssistants } from '../utils/detect-ai.js';
@@ -113,6 +114,18 @@ export async function createCommand(): Promise<void> {
113
114
  console.log(chalk.cyan('\nGenerating AI context files...'));
114
115
  await generateContextFiles(ctx, selectedAI, workspacePath);
115
116
 
117
+ // ── 8.5. Pack codebase context ──────────────────────────────────────
118
+ const packSpinner = ora('Packing codebase context with Repomix...').start();
119
+ try {
120
+ const packResult = await packWorkspace(workspacePath);
121
+ packSpinner.succeed(
122
+ `Packed codebase context (${packResult.totalFiles} files, ${(packResult.fileSize / 1024).toFixed(2)} KB)`
123
+ );
124
+ } catch (error) {
125
+ packSpinner.fail('Failed to pack codebase context');
126
+ console.error(chalk.red(` ${error}`));
127
+ }
128
+
116
129
  // ── 8. Open in editor ───────────────────────────────────────────────
117
130
  const detectedEditors = await detectEditors();
118
131
  const editor = await promptSelectEditor(detectedEditors);
@@ -11,6 +11,7 @@ import { loadConfig } from '../core/config.js';
11
11
  import { listWorkspaces } from '../core/workspace.js';
12
12
  import { detectEditors } from '../utils/detect-editors.js';
13
13
  import { promptSelectEditor } from '../utils/prompts.js';
14
+ import { findSessions } from '../utils/session-finder.js';
14
15
 
15
16
  /**
16
17
  * Lets the user pick an existing workspace and open it in an editor.
@@ -64,17 +65,48 @@ export async function openCommand(): Promise<void> {
64
65
  if (confirmStart) {
65
66
  console.log(chalk.cyan(`\n🚀 Starting ${assistant} session inside workspace...\n`));
66
67
 
67
- let cmd = 'agy';
68
- if (assistant === 'claude') cmd = 'claude';
69
- else if (assistant === 'codex') cmd = 'codex';
70
- else if (assistant === 'copilot') cmd = 'copilot';
68
+ const sessions = await findSessions(selected, loadedFeature.repos);
69
+ let cmdName = 'agy';
70
+ let cmdArgs: string[] = [];
71
+
72
+ if (assistant === 'claude') {
73
+ cmdName = 'claude';
74
+ } else if (assistant === 'codex') {
75
+ cmdName = 'codex';
76
+ } else if (assistant === 'copilot') {
77
+ cmdName = 'copilot';
78
+ }
79
+
80
+ if (sessions.length > 0 && sessions[0].assistant === assistant) {
81
+ const latestSessionId = sessions[0].id;
82
+ if (assistant === 'antigravity') {
83
+ cmdArgs = ['--conversation', latestSessionId];
84
+ } else if (assistant === 'claude') {
85
+ cmdArgs = ['--resume', latestSessionId];
86
+ } else if (assistant === 'codex') {
87
+ cmdArgs = ['resume', latestSessionId];
88
+ } else if (assistant === 'copilot') {
89
+ cmdArgs = ['--resume', latestSessionId];
90
+ }
91
+ } else {
92
+ if (assistant === 'antigravity') {
93
+ cmdArgs = ['--continue'];
94
+ } else if (assistant === 'claude') {
95
+ cmdArgs = ['--resume'];
96
+ } else if (assistant === 'codex') {
97
+ cmdArgs = ['resume'];
98
+ } else if (assistant === 'copilot') {
99
+ cmdArgs = ['--resume'];
100
+ }
101
+ }
71
102
 
72
103
  try {
73
- await execa(cmd, [], { cwd: selected, stdio: 'inherit' });
104
+ await execa(cmdName, cmdArgs, { cwd: selected, stdio: 'inherit' });
74
105
  console.log(chalk.green(`\n👋 Exited ${assistant} session.`));
75
106
  } catch {
107
+ const fullCmd = [cmdName, ...cmdArgs].join(' ');
76
108
  console.log(
77
- chalk.yellow(`\n⚠️ Could not start ${assistant}. Please start it manually:\n ${chalk.dim(`cd "${selected}" && ${cmd}`)}`)
109
+ chalk.yellow(`\n⚠️ Could not start ${assistant}. Please start it manually:\n ${chalk.dim(`cd "${selected}" && ${fullCmd}`)}`)
78
110
  );
79
111
  }
80
112
  }