projax 3.3.69 → 3.3.71
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/dist/agent-runner.d.ts +60 -0
- package/dist/agent-runner.js +382 -0
- package/dist/api/database.d.ts +38 -1
- package/dist/api/database.d.ts.map +1 -1
- package/dist/api/database.js +327 -0
- package/dist/api/database.js.map +1 -1
- package/dist/api/routes/agents.d.ts +4 -0
- package/dist/api/routes/agents.d.ts.map +1 -0
- package/dist/api/routes/agents.js +375 -0
- package/dist/api/routes/agents.js.map +1 -0
- package/dist/api/routes/index.d.ts.map +1 -1
- package/dist/api/routes/index.js +4 -0
- package/dist/api/routes/index.js.map +1 -1
- package/dist/api/routes/todos.d.ts +4 -0
- package/dist/api/routes/todos.d.ts.map +1 -0
- package/dist/api/routes/todos.js +595 -0
- package/dist/api/routes/todos.js.map +1 -0
- package/dist/api/types.d.ts +67 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/core/database.d.ts +47 -0
- package/dist/core/database.js +48 -0
- package/dist/core-bridge.d.ts +1 -1
- package/dist/electron/core/database.d.ts +47 -0
- package/dist/electron/core/database.js +48 -0
- package/dist/electron/renderer/assets/index-6afBeDFD.js +66 -0
- package/dist/electron/renderer/assets/index-Bd3aFi7B.css +1 -0
- package/dist/electron/renderer/index.html +2 -2
- package/dist/index.js +6 -175
- package/dist/octopus-cli.d.ts +2 -0
- package/dist/octopus-cli.js +45 -0
- package/dist/octopus-show-tui.d.ts +1 -0
- package/dist/octopus-show-tui.js +802 -0
- package/dist/octopus-tui.d.ts +1 -0
- package/dist/octopus-tui.js +115 -0
- package/dist/prxi.js +373 -63
- package/dist/prxi.tsx +472 -65
- package/package.json +7 -2
- package/dist/electron/renderer/assets/index-BjZn_mEF.js +0 -66
- package/dist/electron/renderer/assets/index-CZmDxbJO.js +0 -66
- package/dist/electron/renderer/assets/index-Cd1mTO3s.js +0 -66
- package/dist/electron/renderer/assets/index-Cj4QON0s.js +0 -66
- package/dist/electron/renderer/assets/index-CmtZriN5.js +0 -66
- package/dist/electron/renderer/assets/index-DJDPi0kp.css +0 -1
- package/dist/electron/renderer/assets/index-DfocdjIj.css +0 -1
package/dist/prxi.tsx
CHANGED
|
@@ -22,8 +22,21 @@ import {
|
|
|
22
22
|
removeProject,
|
|
23
23
|
getCurrentBranch,
|
|
24
24
|
Project,
|
|
25
|
+
Agent,
|
|
26
|
+
AgentCliType,
|
|
27
|
+
RunningAgent,
|
|
25
28
|
} from './core-bridge';
|
|
26
29
|
import { getProjectScripts, getRunningProcessesClean, runScript, runScriptInBackground, stopScript } from './script-runner';
|
|
30
|
+
import {
|
|
31
|
+
getAgentsByProject,
|
|
32
|
+
getRunningAgents,
|
|
33
|
+
launchAgentInTerminal,
|
|
34
|
+
createAgent,
|
|
35
|
+
updateAgent,
|
|
36
|
+
deleteAgent,
|
|
37
|
+
CLI_DISPLAY_NAMES,
|
|
38
|
+
VALID_CLI_TYPES,
|
|
39
|
+
} from './agent-runner';
|
|
27
40
|
import { spawn } from 'child_process';
|
|
28
41
|
import * as path from 'path';
|
|
29
42
|
import * as os from 'os';
|
|
@@ -68,10 +81,36 @@ function truncateText(text: string, maxLength: number): string {
|
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
// Type definitions for views and filters
|
|
71
|
-
type ViewType = 'projects' | '
|
|
84
|
+
type ViewType = 'projects' | 'tasks' | 'agents' | 'processes' | 'workspaces' | 'settings';
|
|
72
85
|
type FilterType = 'all' | 'name' | 'path' | 'ports' | 'tags' | 'running';
|
|
73
86
|
type SortType = 'name-asc' | 'name-desc' | 'recent' | 'oldest' | 'running';
|
|
74
87
|
|
|
88
|
+
// Task types
|
|
89
|
+
interface TodoTask {
|
|
90
|
+
id: number;
|
|
91
|
+
list_id: number;
|
|
92
|
+
title: string;
|
|
93
|
+
status: 'pending' | 'in_progress' | 'completed' | 'blocked';
|
|
94
|
+
priority: 'low' | 'medium' | 'high' | 'urgent';
|
|
95
|
+
assignee_agent_id: number | null;
|
|
96
|
+
worktree_path: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Task status config
|
|
100
|
+
const taskStatusConfig: Record<string, { color: string; icon: string }> = {
|
|
101
|
+
pending: { color: colors.textTertiary, icon: '○' },
|
|
102
|
+
in_progress: { color: colors.accentOrange, icon: '◐' },
|
|
103
|
+
completed: { color: colors.accentGreen, icon: '●' },
|
|
104
|
+
blocked: { color: '#f85149', icon: '✗' },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const taskPriorityColors: Record<string, string> = {
|
|
108
|
+
low: colors.textTertiary,
|
|
109
|
+
medium: '#d29922',
|
|
110
|
+
high: colors.accentOrange,
|
|
111
|
+
urgent: '#f85149',
|
|
112
|
+
};
|
|
113
|
+
|
|
75
114
|
const FILTER_TYPES: FilterType[] = ['all', 'name', 'path', 'ports', 'tags', 'running'];
|
|
76
115
|
const SORT_TYPES: SortType[] = ['name-asc', 'name-desc', 'recent', 'oldest', 'running'];
|
|
77
116
|
|
|
@@ -138,9 +177,11 @@ const HelpModal: React.FC<HelpModalProps> = ({ onClose }) => {
|
|
|
138
177
|
<Text> </Text>
|
|
139
178
|
<Text color={colors.accentCyan}>View Navigation:</Text>
|
|
140
179
|
<Text> 1 Projects view</Text>
|
|
141
|
-
<Text> 2
|
|
142
|
-
<Text> 3
|
|
143
|
-
<Text> 4
|
|
180
|
+
<Text> 2 Tasks view (requires selected project)</Text>
|
|
181
|
+
<Text> 3 Agents view (requires selected project)</Text>
|
|
182
|
+
<Text> 4 Processes view</Text>
|
|
183
|
+
<Text> 5 Workspaces view</Text>
|
|
184
|
+
<Text> 6 Settings</Text>
|
|
144
185
|
<Text> T Toggle terminal output panel</Text>
|
|
145
186
|
<Text> </Text>
|
|
146
187
|
<Text color={colors.accentCyan}>Projects View - Navigation:</Text>
|
|
@@ -694,10 +735,10 @@ const ProjectDetailsComponent: React.FC<ProjectDetailsProps> = ({
|
|
|
694
735
|
.then(res => {
|
|
695
736
|
if (res.ok) setNpmPackage(pkg.name);
|
|
696
737
|
})
|
|
697
|
-
.catch(() => {});
|
|
738
|
+
.catch(() => { /* Ignore fetch errors */ });
|
|
698
739
|
}
|
|
699
740
|
}
|
|
700
|
-
} catch {}
|
|
741
|
+
} catch (e) { /* Ignore parsing or file read errors */ }
|
|
701
742
|
}, [project]);
|
|
702
743
|
|
|
703
744
|
if (!project) {
|
|
@@ -1207,6 +1248,23 @@ const App: React.FC = () => {
|
|
|
1207
1248
|
'chrome', 'firefox', 'safari', 'edge', 'custom',
|
|
1208
1249
|
];
|
|
1209
1250
|
|
|
1251
|
+
// Agents state
|
|
1252
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
1253
|
+
const [selectedAgentIndex, setSelectedAgentIndex] = useState(0);
|
|
1254
|
+
const [runningAgentsList, setRunningAgentsList] = useState<RunningAgent[]>([]);
|
|
1255
|
+
const [showAddAgentModal, setShowAddAgentModal] = useState(false);
|
|
1256
|
+
const [editingAgent, setEditingAgent] = useState<Agent | null>(null);
|
|
1257
|
+
const [newAgentName, setNewAgentName] = useState('');
|
|
1258
|
+
const [newAgentCliType, setNewAgentCliType] = useState<AgentCliType>('claude');
|
|
1259
|
+
const [newAgentCliTypeIndex, setNewAgentCliTypeIndex] = useState(0);
|
|
1260
|
+
const [agentInputField, setAgentInputField] = useState<'name' | 'cli_type' | 'model' | 'api_key'>('name');
|
|
1261
|
+
const [newAgentModel, setNewAgentModel] = useState('');
|
|
1262
|
+
const [newAgentApiKey, setNewAgentApiKey] = useState('');
|
|
1263
|
+
|
|
1264
|
+
// Tasks state
|
|
1265
|
+
const [tasks, setTasks] = useState<TodoTask[]>([]);
|
|
1266
|
+
const [selectedTaskIdx, setSelectedTaskIdx] = useState(0);
|
|
1267
|
+
|
|
1210
1268
|
// Get terminal dimensions
|
|
1211
1269
|
const terminalHeight = process.stdout.rows || 24;
|
|
1212
1270
|
const availableHeight = terminalHeight - 4; // Subtract status bar (increased for view indicator)
|
|
@@ -1227,14 +1285,23 @@ const App: React.FC = () => {
|
|
|
1227
1285
|
// Use defaults
|
|
1228
1286
|
}
|
|
1229
1287
|
|
|
1230
|
-
// Refresh running processes
|
|
1288
|
+
// Refresh running processes, git branches, and running agents every 5 seconds
|
|
1231
1289
|
const interval = setInterval(() => {
|
|
1232
1290
|
loadRunningProcesses();
|
|
1291
|
+
loadRunningAgentsList();
|
|
1233
1292
|
}, 5000);
|
|
1234
1293
|
|
|
1235
1294
|
return () => clearInterval(interval);
|
|
1236
1295
|
}, []);
|
|
1237
1296
|
|
|
1297
|
+
// Load agents when switching to agents view or when selected project changes
|
|
1298
|
+
useEffect(() => {
|
|
1299
|
+
const currentProject = projects.length > 0 ? projects[selectedIndex] : null;
|
|
1300
|
+
if (currentView === 'agents' && currentProject) {
|
|
1301
|
+
loadAgentsForProject(currentProject.id);
|
|
1302
|
+
}
|
|
1303
|
+
}, [currentView, projects, selectedIndex]);
|
|
1304
|
+
|
|
1238
1305
|
// Load git branches when projects change
|
|
1239
1306
|
useEffect(() => {
|
|
1240
1307
|
if (allProjects.length > 0) {
|
|
@@ -1265,12 +1332,12 @@ const App: React.FC = () => {
|
|
|
1265
1332
|
setDetailSelectedScript(0); // Reset selected script
|
|
1266
1333
|
}, [selectedIndex]);
|
|
1267
1334
|
|
|
1268
|
-
// Load workspaces when switching to workspaces view
|
|
1269
|
-
useEffect(() => {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
}, [currentView]);
|
|
1335
|
+
// Load workspaces when switching to workspaces view (legacy - kept for future use)
|
|
1336
|
+
// useEffect(() => {
|
|
1337
|
+
// if (currentView === 'workspaces' && workspaces.length === 0) {
|
|
1338
|
+
// loadWorkspacesFromApi();
|
|
1339
|
+
// }
|
|
1340
|
+
// }, [currentView]);
|
|
1274
1341
|
|
|
1275
1342
|
// Update scroll offset when selected index changes
|
|
1276
1343
|
useEffect(() => {
|
|
@@ -1301,6 +1368,27 @@ const App: React.FC = () => {
|
|
|
1301
1368
|
}
|
|
1302
1369
|
};
|
|
1303
1370
|
|
|
1371
|
+
const loadAgentsForProject = (projectId: number) => {
|
|
1372
|
+
try {
|
|
1373
|
+
const projectAgents = getAgentsByProject(projectId);
|
|
1374
|
+
setAgents(projectAgents);
|
|
1375
|
+
if (selectedAgentIndex >= projectAgents.length) {
|
|
1376
|
+
setSelectedAgentIndex(Math.max(0, projectAgents.length - 1));
|
|
1377
|
+
}
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
setAgents([]);
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
const loadRunningAgentsList = () => {
|
|
1384
|
+
try {
|
|
1385
|
+
const running = getRunningAgents();
|
|
1386
|
+
setRunningAgentsList(running);
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
setRunningAgentsList([]);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1304
1392
|
const loadProjects = () => {
|
|
1305
1393
|
const loadedProjects = getAllProjects();
|
|
1306
1394
|
setAllProjects(loadedProjects);
|
|
@@ -1729,62 +1817,135 @@ const App: React.FC = () => {
|
|
|
1729
1817
|
return;
|
|
1730
1818
|
}
|
|
1731
1819
|
|
|
1732
|
-
//
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1820
|
+
// Tasks view navigation is handled in renderTasksView
|
|
1821
|
+
|
|
1822
|
+
// Handle navigation in agents view
|
|
1823
|
+
if (currentView === 'agents') {
|
|
1824
|
+
// Handle add agent modal
|
|
1825
|
+
if (showAddAgentModal) {
|
|
1826
|
+
if (key.escape) {
|
|
1827
|
+
setShowAddAgentModal(false);
|
|
1828
|
+
setNewAgentName('');
|
|
1829
|
+
setNewAgentModel('');
|
|
1830
|
+
setNewAgentApiKey('');
|
|
1831
|
+
setNewAgentCliTypeIndex(0);
|
|
1832
|
+
setAgentInputField('name');
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if (key.tab) {
|
|
1836
|
+
// Cycle through input fields
|
|
1837
|
+
const fields: Array<'name' | 'cli_type' | 'model' | 'api_key'> = ['name', 'cli_type', 'model', 'api_key'];
|
|
1838
|
+
const currentIdx = fields.indexOf(agentInputField);
|
|
1839
|
+
setAgentInputField(fields[(currentIdx + 1) % fields.length]);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (agentInputField === 'cli_type') {
|
|
1843
|
+
if (key.upArrow || input === 'k') {
|
|
1844
|
+
setNewAgentCliTypeIndex(prev => Math.max(0, prev - 1));
|
|
1845
|
+
setNewAgentCliType(VALID_CLI_TYPES[Math.max(0, newAgentCliTypeIndex - 1)]);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (key.downArrow || input === 'j') {
|
|
1849
|
+
setNewAgentCliTypeIndex(prev => Math.min(VALID_CLI_TYPES.length - 1, prev + 1));
|
|
1850
|
+
setNewAgentCliType(VALID_CLI_TYPES[Math.min(VALID_CLI_TYPES.length - 1, newAgentCliTypeIndex + 1)]);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (key.return && agentInputField === 'api_key' && newAgentName.trim()) {
|
|
1855
|
+
// Create agent
|
|
1856
|
+
if (selectedProject) {
|
|
1857
|
+
const agent = createAgent(selectedProject.id, {
|
|
1858
|
+
name: newAgentName.trim(),
|
|
1859
|
+
cli_type: newAgentCliType,
|
|
1860
|
+
model: newAgentModel.trim() || null,
|
|
1861
|
+
api_key: newAgentApiKey.trim() || null,
|
|
1862
|
+
});
|
|
1863
|
+
if (agent) {
|
|
1864
|
+
loadAgentsForProject(selectedProject.id);
|
|
1865
|
+
setShowAddAgentModal(false);
|
|
1866
|
+
setNewAgentName('');
|
|
1867
|
+
setNewAgentModel('');
|
|
1868
|
+
setNewAgentApiKey('');
|
|
1869
|
+
setNewAgentCliTypeIndex(0);
|
|
1870
|
+
setAgentInputField('name');
|
|
1871
|
+
} else {
|
|
1872
|
+
setError('Failed to create agent');
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
// Handle text input
|
|
1878
|
+
if (agentInputField === 'name') {
|
|
1879
|
+
if (key.backspace || key.delete) {
|
|
1880
|
+
setNewAgentName(prev => prev.slice(0, -1));
|
|
1881
|
+
} else if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
1882
|
+
setNewAgentName(prev => prev + input);
|
|
1883
|
+
}
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
if (agentInputField === 'model') {
|
|
1887
|
+
if (key.backspace || key.delete) {
|
|
1888
|
+
setNewAgentModel(prev => prev.slice(0, -1));
|
|
1889
|
+
} else if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
1890
|
+
setNewAgentModel(prev => prev + input);
|
|
1891
|
+
}
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
if (agentInputField === 'api_key') {
|
|
1895
|
+
if (key.backspace || key.delete) {
|
|
1896
|
+
setNewAgentApiKey(prev => prev.slice(0, -1));
|
|
1897
|
+
} else if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
1898
|
+
setNewAgentApiKey(prev => prev + input);
|
|
1899
|
+
}
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1740
1902
|
return;
|
|
1741
1903
|
}
|
|
1742
|
-
}
|
|
1743
1904
|
|
|
1744
|
-
|
|
1745
|
-
if (currentView === 'processes') {
|
|
1905
|
+
// Navigation
|
|
1746
1906
|
if (key.upArrow || input === 'k') {
|
|
1747
|
-
|
|
1907
|
+
setSelectedAgentIndex(prev => Math.max(0, prev - 1));
|
|
1748
1908
|
return;
|
|
1749
1909
|
}
|
|
1750
1910
|
if (key.downArrow || input === 'j') {
|
|
1751
|
-
|
|
1911
|
+
setSelectedAgentIndex(prev => Math.min(agents.length - 1, prev + 1));
|
|
1752
1912
|
return;
|
|
1753
1913
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1914
|
+
// Add agent
|
|
1915
|
+
if (input === 'a') {
|
|
1916
|
+
setShowAddAgentModal(true);
|
|
1917
|
+
setAgentInputField('name');
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
// Launch agent
|
|
1921
|
+
if (key.return || input === 'l') {
|
|
1922
|
+
const agent = agents[selectedAgentIndex];
|
|
1923
|
+
if (agent && selectedProject) {
|
|
1924
|
+
const success = launchAgentInTerminal(agent, selectedProject.path);
|
|
1925
|
+
if (success) {
|
|
1926
|
+
setError(null);
|
|
1927
|
+
} else {
|
|
1928
|
+
setError('Failed to launch agent');
|
|
1768
1929
|
}
|
|
1769
|
-
}
|
|
1930
|
+
}
|
|
1770
1931
|
return;
|
|
1771
1932
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
await loadRunningProcesses();
|
|
1782
|
-
setIsLoading(false);
|
|
1783
|
-
} catch (err) {
|
|
1784
|
-
setIsLoading(false);
|
|
1785
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1933
|
+
// Delete agent
|
|
1934
|
+
if (input === 'd') {
|
|
1935
|
+
const agent = agents[selectedAgentIndex];
|
|
1936
|
+
if (agent) {
|
|
1937
|
+
const success = deleteAgent(agent.id);
|
|
1938
|
+
if (success && selectedProject) {
|
|
1939
|
+
loadAgentsForProject(selectedProject.id);
|
|
1940
|
+
} else {
|
|
1941
|
+
setError('Failed to delete agent');
|
|
1786
1942
|
}
|
|
1787
|
-
}
|
|
1943
|
+
}
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
// Back to projects
|
|
1947
|
+
if (key.escape) {
|
|
1948
|
+
setCurrentView('projects');
|
|
1788
1949
|
return;
|
|
1789
1950
|
}
|
|
1790
1951
|
}
|
|
@@ -1835,14 +1996,27 @@ const App: React.FC = () => {
|
|
|
1835
1996
|
return;
|
|
1836
1997
|
}
|
|
1837
1998
|
if (input === '2') {
|
|
1838
|
-
setCurrentView('
|
|
1999
|
+
setCurrentView('tasks');
|
|
1839
2000
|
return;
|
|
1840
2001
|
}
|
|
1841
2002
|
if (input === '3') {
|
|
1842
|
-
|
|
2003
|
+
if (selectedProject) {
|
|
2004
|
+
setCurrentView('agents');
|
|
2005
|
+
loadAgentsForProject(selectedProject.id);
|
|
2006
|
+
} else {
|
|
2007
|
+
setError('Select a project first to manage agents');
|
|
2008
|
+
}
|
|
1843
2009
|
return;
|
|
1844
2010
|
}
|
|
1845
2011
|
if (input === '4') {
|
|
2012
|
+
setCurrentView('processes');
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (input === '5') {
|
|
2016
|
+
setCurrentView('workspaces');
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (input === '6') {
|
|
1846
2020
|
setCurrentView('settings');
|
|
1847
2021
|
return;
|
|
1848
2022
|
}
|
|
@@ -2557,6 +2731,229 @@ const App: React.FC = () => {
|
|
|
2557
2731
|
);
|
|
2558
2732
|
};
|
|
2559
2733
|
|
|
2734
|
+
// Load tasks when switching to tasks view
|
|
2735
|
+
useEffect(() => {
|
|
2736
|
+
if (currentView === 'tasks' && selectedProject) {
|
|
2737
|
+
loadTasksForProject();
|
|
2738
|
+
}
|
|
2739
|
+
}, [currentView, selectedProject]);
|
|
2740
|
+
|
|
2741
|
+
const loadTasksForProject = async () => {
|
|
2742
|
+
if (!selectedProject) return;
|
|
2743
|
+
try {
|
|
2744
|
+
const ports = [38124, 38125, 38126];
|
|
2745
|
+
let apiBaseUrl = '';
|
|
2746
|
+
for (const port of ports) {
|
|
2747
|
+
try {
|
|
2748
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(500) });
|
|
2749
|
+
if (res.ok) { apiBaseUrl = `http://localhost:${port}/api`; break; }
|
|
2750
|
+
} catch (e) { /* Ignore API port check errors */ }
|
|
2751
|
+
}
|
|
2752
|
+
if (!apiBaseUrl) return;
|
|
2753
|
+
|
|
2754
|
+
// Get todo lists
|
|
2755
|
+
const listsRes = await fetch(`${apiBaseUrl}/projects/${selectedProject.id}/todo-lists`);
|
|
2756
|
+
const lists = listsRes.ok ? (await listsRes.json()) as Array<{ id: number }> : [];
|
|
2757
|
+
|
|
2758
|
+
// Get tasks for each list
|
|
2759
|
+
const allTasks: TodoTask[] = [];
|
|
2760
|
+
for (const list of lists) {
|
|
2761
|
+
const tasksRes = await fetch(`${apiBaseUrl}/todo-lists/${list.id}/tasks`);
|
|
2762
|
+
if (tasksRes.ok) {
|
|
2763
|
+
const listTasks = (await tasksRes.json()) as TodoTask[];
|
|
2764
|
+
allTasks.push(...listTasks);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
} catch (e) { /* Ignore task loading errors */ }
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
// Render Tasks view
|
|
2771
|
+
const renderTasksView = () => {
|
|
2772
|
+
if (!selectedProject) {
|
|
2773
|
+
return (
|
|
2774
|
+
<Box flexDirection="column" padding={2}>
|
|
2775
|
+
<Text bold color={colors.accentCyan}>Tasks</Text>
|
|
2776
|
+
<Text> </Text>
|
|
2777
|
+
<Text color={colors.textTertiary}>Select a project first to view tasks</Text>
|
|
2778
|
+
</Box>
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
return (
|
|
2783
|
+
<Box flexDirection="column" padding={1} height={availableHeight}>
|
|
2784
|
+
<Text bold color={colors.accentCyan}>
|
|
2785
|
+
Tasks for {selectedProject.name} ({tasks.length})
|
|
2786
|
+
</Text>
|
|
2787
|
+
<Text> </Text>
|
|
2788
|
+
|
|
2789
|
+
{tasks.length === 0 ? (
|
|
2790
|
+
<Box flexDirection="column">
|
|
2791
|
+
<Text color={colors.textTertiary}>No tasks found</Text>
|
|
2792
|
+
<Text> </Text>
|
|
2793
|
+
<Text color={colors.textSecondary}>Press 'a' to add a task</Text>
|
|
2794
|
+
</Box>
|
|
2795
|
+
) : (
|
|
2796
|
+
<Box flexDirection="column">
|
|
2797
|
+
{tasks.map((task, idx) => {
|
|
2798
|
+
const isSelected = idx === selectedTaskIdx;
|
|
2799
|
+
const cfg = taskStatusConfig[task.status];
|
|
2800
|
+
return (
|
|
2801
|
+
<Box key={task.id}>
|
|
2802
|
+
<Text color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
|
|
2803
|
+
{isSelected ? '▶ ' : ' '}
|
|
2804
|
+
<Text color={cfg.color}>{cfg.icon}</Text> {truncateText(task.title, 40)}
|
|
2805
|
+
</Text>
|
|
2806
|
+
{task.worktree_path && <Text color={colors.accentGreen}> 🌿</Text>}
|
|
2807
|
+
</Box>
|
|
2808
|
+
);
|
|
2809
|
+
})}
|
|
2810
|
+
</Box>
|
|
2811
|
+
)}
|
|
2812
|
+
|
|
2813
|
+
<Text> </Text>
|
|
2814
|
+
<Text color={colors.textTertiary}>
|
|
2815
|
+
↑↓/jk: navigate | a: add task | Space: toggle | d: delete
|
|
2816
|
+
</Text>
|
|
2817
|
+
</Box>
|
|
2818
|
+
);
|
|
2819
|
+
};
|
|
2820
|
+
|
|
2821
|
+
// Render Agents view
|
|
2822
|
+
const renderAgentsView = () => {
|
|
2823
|
+
const selectedAgent = agents[selectedAgentIndex];
|
|
2824
|
+
const isAgentRunning = selectedAgent
|
|
2825
|
+
? runningAgentsList.some(r => r.agentId === selectedAgent.id)
|
|
2826
|
+
: false;
|
|
2827
|
+
|
|
2828
|
+
return (
|
|
2829
|
+
<Box flexDirection="column" padding={1} height={availableHeight}>
|
|
2830
|
+
{/* Add Agent Modal */}
|
|
2831
|
+
{showAddAgentModal && (
|
|
2832
|
+
<Box
|
|
2833
|
+
flexDirection="column"
|
|
2834
|
+
borderStyle="round"
|
|
2835
|
+
borderColor={colors.accentCyan}
|
|
2836
|
+
padding={1}
|
|
2837
|
+
width={60}
|
|
2838
|
+
>
|
|
2839
|
+
<Text bold color={colors.accentCyan}>Add New Agent</Text>
|
|
2840
|
+
<Text> </Text>
|
|
2841
|
+
<Box>
|
|
2842
|
+
<Text color={agentInputField === 'name' ? colors.accentCyan : colors.textSecondary}>
|
|
2843
|
+
Name: {agentInputField === 'name' ? '▶ ' : ' '}
|
|
2844
|
+
</Text>
|
|
2845
|
+
<Text color={colors.textPrimary}>{newAgentName || '_'}</Text>
|
|
2846
|
+
</Box>
|
|
2847
|
+
<Text> </Text>
|
|
2848
|
+
<Text color={agentInputField === 'cli_type' ? colors.accentCyan : colors.textSecondary}>
|
|
2849
|
+
CLI Type: {agentInputField === 'cli_type' ? '▶' : ''}
|
|
2850
|
+
</Text>
|
|
2851
|
+
{VALID_CLI_TYPES.map((cliType, idx) => (
|
|
2852
|
+
<Text
|
|
2853
|
+
key={cliType}
|
|
2854
|
+
color={idx === newAgentCliTypeIndex ? colors.accentGreen : colors.textTertiary}
|
|
2855
|
+
>
|
|
2856
|
+
{idx === newAgentCliTypeIndex ? ' ● ' : ' ○ '}
|
|
2857
|
+
{CLI_DISPLAY_NAMES[cliType]}
|
|
2858
|
+
</Text>
|
|
2859
|
+
))}
|
|
2860
|
+
<Text> </Text>
|
|
2861
|
+
<Box>
|
|
2862
|
+
<Text color={agentInputField === 'model' ? colors.accentCyan : colors.textSecondary}>
|
|
2863
|
+
Model (optional): {agentInputField === 'model' ? '▶ ' : ' '}
|
|
2864
|
+
</Text>
|
|
2865
|
+
<Text color={colors.textPrimary}>{newAgentModel || '_'}</Text>
|
|
2866
|
+
</Box>
|
|
2867
|
+
<Text> </Text>
|
|
2868
|
+
<Box>
|
|
2869
|
+
<Text color={agentInputField === 'api_key' ? colors.accentCyan : colors.textSecondary}>
|
|
2870
|
+
API Key (optional): {agentInputField === 'api_key' ? '▶ ' : ' '}
|
|
2871
|
+
</Text>
|
|
2872
|
+
<Text color={colors.textPrimary}>
|
|
2873
|
+
{newAgentApiKey ? '*'.repeat(Math.min(newAgentApiKey.length, 20)) : '_'}
|
|
2874
|
+
</Text>
|
|
2875
|
+
</Box>
|
|
2876
|
+
<Text> </Text>
|
|
2877
|
+
<Text color={colors.textTertiary}>Tab: next field | ↑↓: select CLI | Enter: create | Esc: cancel</Text>
|
|
2878
|
+
</Box>
|
|
2879
|
+
)}
|
|
2880
|
+
|
|
2881
|
+
{!showAddAgentModal && (
|
|
2882
|
+
<>
|
|
2883
|
+
<Text bold color={colors.accentCyan}>
|
|
2884
|
+
Agents for {selectedProject?.name || 'No Project'} ({agents.length})
|
|
2885
|
+
</Text>
|
|
2886
|
+
<Text> </Text>
|
|
2887
|
+
|
|
2888
|
+
{agents.length === 0 ? (
|
|
2889
|
+
<Box flexDirection="column">
|
|
2890
|
+
<Text color={colors.textTertiary}>No agents configured for this project.</Text>
|
|
2891
|
+
<Text> </Text>
|
|
2892
|
+
<Text color={colors.textSecondary}>Press 'a' to add an agent.</Text>
|
|
2893
|
+
</Box>
|
|
2894
|
+
) : (
|
|
2895
|
+
<Box flexDirection="row" height={availableHeight - 6}>
|
|
2896
|
+
{/* Agent List */}
|
|
2897
|
+
<Box flexDirection="column" width="40%" borderStyle="round" borderColor={colors.borderColor} padding={1}>
|
|
2898
|
+
{agents.map((agent, index) => {
|
|
2899
|
+
const isSelected = index === selectedAgentIndex;
|
|
2900
|
+
const running = runningAgentsList.some(r => r.agentId === agent.id);
|
|
2901
|
+
return (
|
|
2902
|
+
<Box key={agent.id}>
|
|
2903
|
+
<Text color={isSelected ? colors.accentCyan : colors.textPrimary} bold={isSelected}>
|
|
2904
|
+
{isSelected ? '▶ ' : ' '}
|
|
2905
|
+
{running ? '● ' : '○ '}
|
|
2906
|
+
{truncateText(agent.name, 25)}
|
|
2907
|
+
</Text>
|
|
2908
|
+
</Box>
|
|
2909
|
+
);
|
|
2910
|
+
})}
|
|
2911
|
+
</Box>
|
|
2912
|
+
<Box width={1} />
|
|
2913
|
+
{/* Agent Details */}
|
|
2914
|
+
<Box flexDirection="column" width="60%" borderStyle="round" borderColor={colors.borderColor} padding={1}>
|
|
2915
|
+
{selectedAgent && (
|
|
2916
|
+
<>
|
|
2917
|
+
<Text bold color={colors.accentCyan}>{selectedAgent.name}</Text>
|
|
2918
|
+
<Text> </Text>
|
|
2919
|
+
<Text color={colors.textSecondary}>
|
|
2920
|
+
Type: <Text color={colors.textPrimary}>{CLI_DISPLAY_NAMES[selectedAgent.cli_type]}</Text>
|
|
2921
|
+
</Text>
|
|
2922
|
+
{selectedAgent.model && (
|
|
2923
|
+
<Text color={colors.textSecondary}>
|
|
2924
|
+
Model: <Text color={colors.textPrimary}>{selectedAgent.model}</Text>
|
|
2925
|
+
</Text>
|
|
2926
|
+
)}
|
|
2927
|
+
{selectedAgent.api_key && (
|
|
2928
|
+
<Text color={colors.textSecondary}>
|
|
2929
|
+
API Key: <Text color={colors.accentGreen}>configured</Text>
|
|
2930
|
+
</Text>
|
|
2931
|
+
)}
|
|
2932
|
+
{selectedAgent.system_prompt && (
|
|
2933
|
+
<Text color={colors.textSecondary}>
|
|
2934
|
+
System Prompt: <Text color={colors.textPrimary}>{truncateText(selectedAgent.system_prompt, 40)}</Text>
|
|
2935
|
+
</Text>
|
|
2936
|
+
)}
|
|
2937
|
+
<Text> </Text>
|
|
2938
|
+
<Text color={isAgentRunning ? colors.accentGreen : colors.textTertiary}>
|
|
2939
|
+
Status: {isAgentRunning ? 'Running' : 'Stopped'}
|
|
2940
|
+
</Text>
|
|
2941
|
+
</>
|
|
2942
|
+
)}
|
|
2943
|
+
</Box>
|
|
2944
|
+
</Box>
|
|
2945
|
+
)}
|
|
2946
|
+
|
|
2947
|
+
<Text> </Text>
|
|
2948
|
+
<Text color={colors.textTertiary}>
|
|
2949
|
+
↑↓/jk: navigate | a: add agent | Enter/l: launch | d: delete | Esc: back to projects
|
|
2950
|
+
</Text>
|
|
2951
|
+
</>
|
|
2952
|
+
)}
|
|
2953
|
+
</Box>
|
|
2954
|
+
);
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2560
2957
|
return (
|
|
2561
2958
|
<Box flexDirection="column" height={terminalHeight}>
|
|
2562
2959
|
{/* View indicator bar */}
|
|
@@ -2565,29 +2962,39 @@ const App: React.FC = () => {
|
|
|
2565
2962
|
[1] Projects
|
|
2566
2963
|
</Text>
|
|
2567
2964
|
<Text> </Text>
|
|
2568
|
-
<Text color={currentView === '
|
|
2569
|
-
[2]
|
|
2965
|
+
<Text color={currentView === 'tasks' ? colors.accentCyan : colors.textTertiary}>
|
|
2966
|
+
[2] Tasks
|
|
2967
|
+
</Text>
|
|
2968
|
+
<Text> </Text>
|
|
2969
|
+
<Text color={currentView === 'agents' ? colors.accentCyan : colors.textTertiary}>
|
|
2970
|
+
[3] Agents
|
|
2570
2971
|
</Text>
|
|
2571
2972
|
<Text> </Text>
|
|
2572
2973
|
<Text color={currentView === 'processes' ? colors.accentCyan : colors.textTertiary}>
|
|
2573
|
-
[
|
|
2974
|
+
[4] Processes
|
|
2975
|
+
</Text>
|
|
2976
|
+
<Text> </Text>
|
|
2977
|
+
<Text color={currentView === 'workspaces' ? colors.accentCyan : colors.textTertiary}>
|
|
2978
|
+
[5] Workspaces
|
|
2574
2979
|
</Text>
|
|
2575
2980
|
<Text> </Text>
|
|
2576
2981
|
<Text color={currentView === 'settings' ? colors.accentCyan : colors.textTertiary}>
|
|
2577
|
-
[
|
|
2982
|
+
[6] Settings
|
|
2578
2983
|
</Text>
|
|
2579
|
-
{
|
|
2984
|
+
{runningAgentsList.length > 0 && (
|
|
2580
2985
|
<>
|
|
2581
2986
|
<Text> | </Text>
|
|
2582
|
-
<Text color={colors.accentGreen}
|
|
2987
|
+
<Text color={colors.accentGreen}>● {runningAgentsList.length} running</Text>
|
|
2583
2988
|
</>
|
|
2584
2989
|
)}
|
|
2585
2990
|
</Box>
|
|
2586
2991
|
|
|
2587
2992
|
{/* Main content based on current view */}
|
|
2588
2993
|
{currentView === 'projects' && renderProjectsView()}
|
|
2589
|
-
{currentView === '
|
|
2994
|
+
{currentView === 'tasks' && renderTasksView()}
|
|
2995
|
+
{currentView === 'agents' && renderAgentsView()}
|
|
2590
2996
|
{currentView === 'processes' && renderProcessesView()}
|
|
2997
|
+
{currentView === 'workspaces' && renderWorkspacesView()}
|
|
2591
2998
|
{currentView === 'settings' && renderSettingsView()}
|
|
2592
2999
|
|
|
2593
3000
|
{/* Status bar */}
|