4runr-os 1.0.14 → 1.0.21
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/animations.d.ts +26 -0
- package/dist/animations.d.ts.map +1 -0
- package/dist/animations.js +111 -0
- package/dist/animations.js.map +1 -0
- package/dist/copy.d.ts +62 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +70 -0
- package/dist/copy.js.map +1 -0
- package/dist/index.js +697 -279
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +28 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +87 -0
- package/dist/state.js.map +1 -0
- package/dist/store.d.ts +69 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +86 -0
- package/dist/store.js.map +1 -0
- package/dist/theme.d.ts +21 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +31 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/home-view.d.ts +27 -0
- package/dist/ui/home-view.d.ts.map +1 -0
- package/dist/ui/home-view.js +127 -0
- package/dist/ui/home-view.js.map +1 -0
- package/dist/ui-primitives.d.ts +53 -0
- package/dist/ui-primitives.d.ts.map +1 -0
- package/dist/ui-primitives.js +156 -0
- package/dist/ui-primitives.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +35 -0
- package/dist/utils.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,39 +11,55 @@ import { GatewayClient } from './gateway-client.js';
|
|
|
11
11
|
import { executeLocalModel, verifyLocalModelServer } from './local-model-executor.js';
|
|
12
12
|
import { checkOllamaInstallation, installOllama, startOllamaServer, downloadModel, verifyLocalExecution } from './local-setup.js';
|
|
13
13
|
import { loadSession, clearSession, createSession, isSessionValid, getSessionTimeRemaining, getUserAgentsPath, getUserToolsPath } from './auth.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
const { cyan, green, yellow, blue, magenta, red, gray, bright, reset, dim, brightGreen, brightCyan, brightMagenta, purpleRGB, purpleBright, purpleDim, pink, orange, teal } = colors;
|
|
14
|
+
import { C_BRAND, C_ACCENT, C_TEXT, C_MUTED, C_SUCCESS, C_WARN, C_ERROR, C_DIM, C_LINK, C_BRAND_BRIGHT, C_ACCENT_BRIGHT, C_TEXT_BRIGHT, RESET, BOLD, } from './theme.js';
|
|
15
|
+
import { Header, StatusBar, Section, Table, Alert, truncateId, truncateText, } from './ui-primitives.js';
|
|
16
|
+
import { getLastAgent, setLastAgent, setLastConnectionTarget, setLastMode, } from './state.js';
|
|
17
|
+
import { Copy } from './copy.js';
|
|
18
|
+
import { createProgress, stopAllAnimations } from './animations.js';
|
|
19
|
+
import { getState, updateAppState } from './store.js';
|
|
20
|
+
import { renderHomeView } from './ui/home-view.js';
|
|
21
|
+
// Legacy color aliases for backward compatibility during refactor
|
|
22
|
+
const cyan = C_ACCENT;
|
|
23
|
+
const green = C_SUCCESS;
|
|
24
|
+
const yellow = C_WARN;
|
|
25
|
+
const red = C_ERROR;
|
|
26
|
+
const gray = C_MUTED;
|
|
27
|
+
const bright = BOLD;
|
|
28
|
+
const reset = RESET;
|
|
29
|
+
const dim = C_DIM;
|
|
30
|
+
const brightGreen = C_SUCCESS + BOLD;
|
|
31
|
+
const brightCyan = C_ACCENT_BRIGHT;
|
|
32
|
+
const purpleRGB = C_BRAND;
|
|
33
|
+
const purpleBright = C_BRAND_BRIGHT;
|
|
34
|
+
const teal = C_ACCENT;
|
|
35
|
+
const magenta = C_BRAND;
|
|
36
|
+
const blue = C_ACCENT;
|
|
37
|
+
let currentView = 'BOOT';
|
|
38
|
+
// Legacy globals (being migrated to state store)
|
|
41
39
|
let client = null;
|
|
42
40
|
let systemReady = false;
|
|
43
41
|
let gatewayConnected = false;
|
|
44
42
|
let localMode = false; // Run without gateway (local-only)
|
|
45
43
|
let currentUser = null; // Current logged-in user
|
|
46
44
|
let sessionCheckInterval = null; // Session expiry checker
|
|
45
|
+
/**
|
|
46
|
+
* Sync legacy globals to state store
|
|
47
|
+
* Call this after any global state change to keep store in sync
|
|
48
|
+
* TODO: Remove this once all code uses state store directly
|
|
49
|
+
*/
|
|
50
|
+
function syncStateToStore() {
|
|
51
|
+
updateAppState({
|
|
52
|
+
currentView,
|
|
53
|
+
gatewayClient: client,
|
|
54
|
+
gatewayConnected,
|
|
55
|
+
gatewayUrl: gatewayUrl || null,
|
|
56
|
+
localMode,
|
|
57
|
+
currentUser,
|
|
58
|
+
sessionCheckInterval,
|
|
59
|
+
systemReady,
|
|
60
|
+
// Note: customAgents and runHistory are synced separately when they change
|
|
61
|
+
});
|
|
62
|
+
}
|
|
47
63
|
// Gateway URL - configurable via GATEWAY_URL environment variable or config file
|
|
48
64
|
// Default: Official 4Runr server (but connection requires login)
|
|
49
65
|
// Override: Set GATEWAY_URL environment variable
|
|
@@ -64,7 +80,10 @@ let multilineMode = false;
|
|
|
64
80
|
let multilineResolver = null;
|
|
65
81
|
let multilineLines = [];
|
|
66
82
|
let multilineEndMarker = 'END';
|
|
83
|
+
// Custom agents storage
|
|
84
|
+
// NOTE: Using types from types.ts now
|
|
67
85
|
const customAgents = new Map();
|
|
86
|
+
// Custom tools storage
|
|
68
87
|
const customTools = new Map();
|
|
69
88
|
// Get config directory path
|
|
70
89
|
function getConfigDir() {
|
|
@@ -149,133 +168,128 @@ const aiConfig = {
|
|
|
149
168
|
model: null,
|
|
150
169
|
apiKey: null
|
|
151
170
|
};
|
|
152
|
-
//
|
|
171
|
+
// Track if this is first run completion (for brand moment)
|
|
172
|
+
let firstRunCompleted = false;
|
|
173
|
+
// BOOT: Sequential, deterministic, never fails hard (max 2s animation)
|
|
153
174
|
async function bootSequence() {
|
|
154
175
|
console.clear();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
await sleep(400);
|
|
174
|
-
// Check for existing session
|
|
176
|
+
currentView = 'BOOT';
|
|
177
|
+
const bootStartTime = Date.now();
|
|
178
|
+
// 1. Header - render immediately, no delays
|
|
179
|
+
const mode = localMode ? 'LOCAL' : 'REMOTE';
|
|
180
|
+
console.log(Header('2.0.0', mode));
|
|
181
|
+
console.log();
|
|
182
|
+
// 2. Boot sequence messages (sequential, calm, max 2s total)
|
|
183
|
+
const checkResults = [];
|
|
184
|
+
// Step 1: Initializing runtime
|
|
185
|
+
const progress1 = createProgress(Copy.boot.initializing, 200);
|
|
186
|
+
progress1.start();
|
|
187
|
+
await sleep(150);
|
|
188
|
+
progress1.stop(true);
|
|
189
|
+
checkResults.push({ label: 'Runtime', status: 'OK' });
|
|
190
|
+
// Step 2: Loading configuration
|
|
191
|
+
const progress2 = createProgress(Copy.boot.loadingConfig, 200);
|
|
192
|
+
progress2.start();
|
|
193
|
+
await sleep(150);
|
|
175
194
|
const savedSession = loadSession();
|
|
176
|
-
|
|
195
|
+
const configLoaded = savedSession && isSessionValid(savedSession);
|
|
196
|
+
if (configLoaded) {
|
|
177
197
|
currentUser = savedSession;
|
|
178
|
-
console.log(`${dim}[BOOT] Session: ${green}${savedSession.username}${reset}${dim} (${getSessionTimeRemaining(savedSession)}m)${reset}`);
|
|
179
|
-
// Try to reconnect to gateway and verify components
|
|
180
|
-
if (!localMode && (savedSession.gatewayUrl || gatewayUrl)) {
|
|
181
|
-
const urlToUse = savedSession.gatewayUrl || gatewayUrl;
|
|
182
|
-
console.log(`${purpleRGB}└─[BOOT]${reset} ${brightCyan}Connecting to mainframe...${reset}`);
|
|
183
|
-
try {
|
|
184
|
-
client = new GatewayClient({ gatewayUrl: urlToUse });
|
|
185
|
-
const health = await client.health();
|
|
186
|
-
gatewayConnected = true;
|
|
187
|
-
gatewayUrl = urlToUse;
|
|
188
|
-
// Actually verify components with beautiful formatting
|
|
189
|
-
console.log(`${purpleDim}└─[BOOT]${reset} ${brightCyan}Verifying system components...${reset}\n`);
|
|
190
|
-
// Gateway
|
|
191
|
-
process.stdout.write(`${purpleRGB} ├─${reset} ${cyan}Gateway${reset}${gray} ${reset}`);
|
|
192
|
-
await sleep(100);
|
|
193
|
-
console.log(`${green}${bright}✓ ONLINE${reset} ${dim}(${health.status})${reset}`);
|
|
194
|
-
// Database
|
|
195
|
-
process.stdout.write(`${purpleRGB} ├─${reset} ${cyan}Database${reset}${gray} ${reset}`);
|
|
196
|
-
await sleep(100);
|
|
197
|
-
console.log(`${green}${bright}✓ ONLINE${reset} ${dim}(${health.persistence})${reset}`);
|
|
198
|
-
// Sentinel
|
|
199
|
-
process.stdout.write(`${purpleRGB} ├─${reset} ${cyan}Sentinel${reset}${gray} ${reset}`);
|
|
200
|
-
await sleep(100);
|
|
201
|
-
try {
|
|
202
|
-
const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
|
|
203
|
-
if (sentinelRes.ok) {
|
|
204
|
-
const sentinel = await sentinelRes.json();
|
|
205
|
-
console.log(`${green}${bright}✓ ACTIVE${reset} ${dim}(${sentinel?.status || 'active'})${reset}`);
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
console.log(`${yellow}⚠ WARNING${reset} ${dim}(not responding)${reset}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
console.log(`${yellow}⚠ OFFLINE${reset} ${dim}(unavailable)${reset}`);
|
|
213
|
-
}
|
|
214
|
-
// Metrics
|
|
215
|
-
process.stdout.write(`${purpleRGB} ├─${reset} ${cyan}Metrics${reset}${gray} ${reset}`);
|
|
216
|
-
await sleep(100);
|
|
217
|
-
try {
|
|
218
|
-
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
219
|
-
if (metricsRes.ok) {
|
|
220
|
-
console.log(`${green}${bright}✓ ONLINE${reset}`);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
console.log(`${yellow}⚠ OFFLINE${reset}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
console.log(`${yellow}⚠ OFFLINE${reset}`);
|
|
228
|
-
}
|
|
229
|
-
// AI Providers
|
|
230
|
-
process.stdout.write(`${purpleRGB} └─${reset} ${cyan}AI Providers${reset}${gray} ${reset}`);
|
|
231
|
-
await sleep(100);
|
|
232
|
-
console.log(`${green}${bright}✓ READY${reset} ${dim}(OpenAI, Anthropic)${reset}\n`);
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
console.log(`${yellow}✗${reset} ${dim}(mainframe unavailable)${reset}`);
|
|
236
|
-
gatewayConnected = false;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
198
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
199
|
+
progress2.stop(!!configLoaded);
|
|
200
|
+
checkResults.push({ label: 'Configuration', status: configLoaded ? 'OK' : 'WARN' });
|
|
201
|
+
// Step 3: Checking gateway
|
|
202
|
+
const progress3 = createProgress(Copy.boot.checkingGateway, 300);
|
|
203
|
+
progress3.start();
|
|
204
|
+
await sleep(200);
|
|
205
|
+
let gatewayStatus = 'WARN';
|
|
206
|
+
if (!localMode && (savedSession?.gatewayUrl || gatewayUrl)) {
|
|
207
|
+
const urlToUse = savedSession?.gatewayUrl || gatewayUrl;
|
|
208
|
+
try {
|
|
209
|
+
client = new GatewayClient({ gatewayUrl: urlToUse });
|
|
210
|
+
const health = await client.health();
|
|
211
|
+
gatewayConnected = true;
|
|
212
|
+
gatewayUrl = urlToUse;
|
|
213
|
+
gatewayStatus = 'OK';
|
|
214
|
+
setLastConnectionTarget(urlToUse);
|
|
244
215
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
console.log(`${purpleRGB} └─[ACTION]${reset} ${dim}Use ${bright}${purpleBright}connect <username>${reset}${dim} to access mainframe${reset}`);
|
|
249
|
-
console.log(`${purpleRGB} └─[ACTION]${reset} ${dim}Or run with ${bright}${purpleBright}--local${reset}${dim} for offline mode${reset}\n`);
|
|
216
|
+
catch {
|
|
217
|
+
gatewayStatus = 'WARN';
|
|
218
|
+
gatewayConnected = false;
|
|
250
219
|
}
|
|
251
220
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
221
|
+
else if (localMode) {
|
|
222
|
+
gatewayStatus = 'OK';
|
|
223
|
+
}
|
|
224
|
+
progress3.stop(gatewayStatus === 'OK');
|
|
225
|
+
checkResults.push({ label: 'Gateway', status: gatewayStatus });
|
|
226
|
+
// Step 4: Checking database
|
|
227
|
+
const progress4 = createProgress(Copy.boot.checkingDatabase, 200);
|
|
228
|
+
progress4.start();
|
|
229
|
+
await sleep(150);
|
|
230
|
+
let dbStatus = 'WARN';
|
|
231
|
+
if (gatewayConnected && client) {
|
|
232
|
+
try {
|
|
233
|
+
const health = await client.health();
|
|
234
|
+
dbStatus = health.persistence === 'enabled' ? 'OK' : 'WARN';
|
|
261
235
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
console.log(`${purpleRGB}└─[SYSTEM]${reset} ${yellow}Mainframe unavailable${reset} ${dim}(local features only)${reset}\n`);
|
|
236
|
+
catch {
|
|
237
|
+
dbStatus = 'WARN';
|
|
265
238
|
}
|
|
266
239
|
}
|
|
267
|
-
else if (
|
|
268
|
-
|
|
269
|
-
|
|
240
|
+
else if (localMode) {
|
|
241
|
+
dbStatus = 'OK'; // Local mode doesn't need DB
|
|
242
|
+
}
|
|
243
|
+
progress4.stop(dbStatus === 'OK');
|
|
244
|
+
checkResults.push({ label: 'Database', status: dbStatus });
|
|
245
|
+
// Step 5: Loading agent registry
|
|
246
|
+
const progress5 = createProgress(Copy.boot.loadingAgents, 200);
|
|
247
|
+
progress5.start();
|
|
248
|
+
await sleep(150);
|
|
249
|
+
loadCustomAgents();
|
|
250
|
+
const agentsLoaded = customAgents.size > 0;
|
|
251
|
+
progress5.stop(agentsLoaded);
|
|
252
|
+
checkResults.push({ label: 'Agent registry', status: agentsLoaded ? 'OK' : 'WARN' });
|
|
253
|
+
// Ensure total boot time ≤ 2s
|
|
254
|
+
const elapsed = Date.now() - bootStartTime;
|
|
255
|
+
if (elapsed < 2000) {
|
|
256
|
+
await sleep(2000 - elapsed);
|
|
257
|
+
}
|
|
258
|
+
// Summary
|
|
259
|
+
const checkLines = [];
|
|
260
|
+
checkResults.forEach(result => {
|
|
261
|
+
const statusColor = result.status === 'OK' ? C_SUCCESS :
|
|
262
|
+
result.status === 'WARN' ? C_WARN : C_ERROR;
|
|
263
|
+
const statusText = result.status === 'OK' ? 'OK' :
|
|
264
|
+
result.status === 'WARN' ? 'WARN' : 'ERROR';
|
|
265
|
+
checkLines.push(`${result.label}: ${statusColor}${statusText}${RESET}`);
|
|
266
|
+
});
|
|
267
|
+
console.log(Section('INITIALIZATION', checkLines));
|
|
268
|
+
console.log();
|
|
269
|
+
// Final status (ALL CAPS for system state)
|
|
270
|
+
let finalStatus;
|
|
271
|
+
if (gatewayStatus === 'OK' && gatewayConnected) {
|
|
272
|
+
finalStatus = `${C_SUCCESS}${Copy.status.ready}${RESET}`;
|
|
273
|
+
// Brand moment: boot complete
|
|
274
|
+
console.log(`${C_MUTED}${Copy.brand.bootComplete}${RESET}`);
|
|
275
|
+
}
|
|
276
|
+
else if (localMode) {
|
|
277
|
+
finalStatus = `${C_SUCCESS}${Copy.status.offlineMode}${RESET}`;
|
|
270
278
|
}
|
|
271
279
|
else {
|
|
272
|
-
|
|
280
|
+
finalStatus = `${C_WARN}${Copy.status.notConnected}${RESET}`;
|
|
273
281
|
}
|
|
282
|
+
console.log(finalStatus);
|
|
274
283
|
systemReady = true;
|
|
275
284
|
// Start session expiry checker
|
|
276
285
|
if (currentUser && !localMode) {
|
|
277
286
|
startSessionChecker();
|
|
278
287
|
}
|
|
288
|
+
// Save mode to state
|
|
289
|
+
setLastMode(localMode ? 'LOCAL' : 'REMOTE');
|
|
290
|
+
// Auto transition to HOME
|
|
291
|
+
await sleep(200);
|
|
292
|
+
await showHomeView();
|
|
279
293
|
}
|
|
280
294
|
/**
|
|
281
295
|
* Start session expiry checker
|
|
@@ -319,17 +333,103 @@ async function progress(items) {
|
|
|
319
333
|
function sleep(ms) {
|
|
320
334
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
321
335
|
}
|
|
322
|
-
// Show
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
console.log(
|
|
329
|
-
console.log(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
336
|
+
// Helper: Show agent config (read-only)
|
|
337
|
+
async function showAgentConfig(agent) {
|
|
338
|
+
// 2. AGENT CONFIG (Read-Only)
|
|
339
|
+
const identityLines = [];
|
|
340
|
+
identityLines.push(`Name: ${C_TEXT}${agent.name}${RESET}`);
|
|
341
|
+
identityLines.push(`Description: ${C_MUTED}${agent.description || 'No description'}${RESET}`);
|
|
342
|
+
console.log(Section('IDENTITY', identityLines));
|
|
343
|
+
console.log();
|
|
344
|
+
const modelLines = [];
|
|
345
|
+
const modelShort = agent.baseAgent.includes(':')
|
|
346
|
+
? agent.baseAgent.split(':').pop() || 'unknown'
|
|
347
|
+
: agent.baseAgent;
|
|
348
|
+
modelLines.push(`Model: ${C_ACCENT}${modelShort}${RESET}`);
|
|
349
|
+
if (agent.temperature !== undefined) {
|
|
350
|
+
modelLines.push(`Temperature: ${C_ACCENT}${agent.temperature}${RESET}`);
|
|
351
|
+
}
|
|
352
|
+
console.log(Section('MODEL', modelLines));
|
|
353
|
+
console.log();
|
|
354
|
+
const toolsLines = [];
|
|
355
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
356
|
+
toolsLines.push(`Enabled: ${C_ACCENT}${agent.tools.length}${RESET}`);
|
|
357
|
+
agent.tools.forEach(tool => {
|
|
358
|
+
toolsLines.push(`${C_TEXT}${tool}${RESET}`);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
toolsLines.push(`${C_MUTED}No tools enabled${RESET}`);
|
|
363
|
+
}
|
|
364
|
+
console.log(Section('TOOLS', toolsLines));
|
|
365
|
+
console.log();
|
|
366
|
+
const memoryLines = [];
|
|
367
|
+
memoryLines.push(`Memory type: ${C_ACCENT}NONE${RESET} ${C_MUTED}(not implemented)${RESET}`);
|
|
368
|
+
console.log(Section('MEMORY', memoryLines));
|
|
369
|
+
console.log();
|
|
370
|
+
// Tags (if we had them)
|
|
371
|
+
const tagsLines = [];
|
|
372
|
+
tagsLines.push(`${C_MUTED}No tags${RESET}`);
|
|
373
|
+
console.log(Section('TAGS', tagsLines));
|
|
374
|
+
console.log();
|
|
375
|
+
}
|
|
376
|
+
// Track boot time for uptime calculation
|
|
377
|
+
const bootTime = Date.now();
|
|
378
|
+
const runHistory = [];
|
|
379
|
+
function formatRelativeTime(timestamp) {
|
|
380
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
381
|
+
if (seconds < 60)
|
|
382
|
+
return `${seconds}s ago`;
|
|
383
|
+
const minutes = Math.floor(seconds / 60);
|
|
384
|
+
if (minutes < 60)
|
|
385
|
+
return `${minutes}m ago`;
|
|
386
|
+
const hours = Math.floor(minutes / 60);
|
|
387
|
+
if (hours < 24)
|
|
388
|
+
return `${hours}h ago`;
|
|
389
|
+
const days = Math.floor(hours / 24);
|
|
390
|
+
return `${days}d ago`;
|
|
391
|
+
}
|
|
392
|
+
function formatUptime() {
|
|
393
|
+
const seconds = Math.floor((Date.now() - bootTime) / 1000);
|
|
394
|
+
if (seconds < 60)
|
|
395
|
+
return `${seconds}s`;
|
|
396
|
+
const minutes = Math.floor(seconds / 60);
|
|
397
|
+
if (minutes < 60)
|
|
398
|
+
return `${minutes}m`;
|
|
399
|
+
const hours = Math.floor(minutes / 60);
|
|
400
|
+
return `${hours}h ${minutes % 60}m`;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* HOME: Situation Awareness
|
|
404
|
+
*
|
|
405
|
+
* REFACTORED: Now uses state store + UI renderer pattern
|
|
406
|
+
*
|
|
407
|
+
* Pattern:
|
|
408
|
+
* 1. Update state (view change)
|
|
409
|
+
* 2. Sync legacy globals to store
|
|
410
|
+
* 3. Call pure renderer function
|
|
411
|
+
* 4. Output result
|
|
412
|
+
*
|
|
413
|
+
* This is the pattern all views should follow.
|
|
414
|
+
*/
|
|
415
|
+
async function showHomeView() {
|
|
416
|
+
// 1. Update state
|
|
417
|
+
currentView = 'HOME';
|
|
418
|
+
// 2. Sync legacy globals to state store
|
|
419
|
+
// TODO: Once all code uses state store, this sync won't be needed
|
|
420
|
+
syncStateToStore();
|
|
421
|
+
// 3. Sync customAgents and runHistory to store
|
|
422
|
+
const state = getState();
|
|
423
|
+
updateAppState({
|
|
424
|
+
customAgents: customAgents,
|
|
425
|
+
runHistory: runHistory,
|
|
426
|
+
});
|
|
427
|
+
// 4. Call pure renderer (state → string)
|
|
428
|
+
const newState = getState();
|
|
429
|
+
const output = renderHomeView(newState);
|
|
430
|
+
// 5. Output result
|
|
431
|
+
console.log();
|
|
432
|
+
console.log(output);
|
|
333
433
|
}
|
|
334
434
|
// Interactive prompt helper
|
|
335
435
|
function prompt(rl, question) {
|
|
@@ -363,49 +463,42 @@ const commands = {
|
|
|
363
463
|
if (args.length > 0) {
|
|
364
464
|
const cmd = commands[args[0]];
|
|
365
465
|
if (cmd) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
466
|
+
const lines = [];
|
|
467
|
+
lines.push(`${C_TEXT_BRIGHT}${args[0]}${RESET}`);
|
|
468
|
+
lines.push(cmd.description);
|
|
469
|
+
lines.push(`Usage: ${C_LINK}${cmd.usage}${RESET}`);
|
|
470
|
+
console.log(Section('COMMAND', lines));
|
|
471
|
+
console.log();
|
|
369
472
|
}
|
|
370
473
|
else {
|
|
371
|
-
console.log(`${
|
|
474
|
+
console.log(Alert('error', `${Copy.error.invalidCommand}: ${args[0]}`, undefined, 'help'));
|
|
475
|
+
console.log();
|
|
372
476
|
}
|
|
373
477
|
}
|
|
374
478
|
else {
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
console.log(
|
|
386
|
-
console.log(
|
|
387
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}agents${reset}${gray} ${dim}List available agents${reset} ${purpleRGB}║${reset}`);
|
|
388
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
389
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Run Management${reset} ${purpleRGB}║${reset}`);
|
|
390
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}create${reset}${gray} ${dim}Create a new run${reset} ${purpleRGB}║${reset}`);
|
|
391
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}start${reset}${gray} ${dim}Start a run${reset} ${purpleRGB}║${reset}`);
|
|
392
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}get${reset}${gray} ${dim}Get run details${reset} ${purpleRGB}║${reset}`);
|
|
393
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}list${reset}${gray} ${dim}List all runs${reset} ${purpleRGB}║${reset}`);
|
|
394
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
395
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Connection${reset} ${purpleRGB}║${reset}`);
|
|
396
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}connect${reset}${gray} ${dim}Connect to mainframe (required for remote features)${reset} ${purpleRGB}║${reset}`);
|
|
397
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}disconnect${reset}${gray} ${dim}Disconnect from mainframe${reset} ${purpleRGB}║${reset}`);
|
|
398
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}whoami${reset}${gray} ${dim}Show current connection and session info${reset} ${purpleRGB}║${reset}`);
|
|
399
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
400
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}System${reset} ${purpleRGB}║${reset}`);
|
|
401
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}status${reset}${gray} ${dim}Show system status${reset} ${purpleRGB}║${reset}`);
|
|
402
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}gateway${reset}${gray} ${dim}Manage gateway connection${reset} ${purpleRGB}║${reset}`);
|
|
403
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}sentinel${reset}${gray} ${dim}Show Sentinel monitoring${reset} ${purpleRGB}║${reset}`);
|
|
404
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}metrics${reset}${gray} ${dim}Show system metrics${reset} ${purpleRGB}║${reset}`);
|
|
405
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}clear${reset}${gray} ${dim}Clear the screen${reset} ${purpleRGB}║${reset}`);
|
|
406
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}exit${reset}${gray} ${dim}Exit 4Runr OS${reset} ${purpleRGB}║${reset}`);
|
|
407
|
-
console.log(`${purpleRGB}╚═══════════════════════════════════════════════════════════════════════════╝${reset}\n`);
|
|
479
|
+
// Clean, precise help menu
|
|
480
|
+
const commandLines = [];
|
|
481
|
+
commandLines.push(`${C_LINK}home${RESET} Mission control overview`);
|
|
482
|
+
commandLines.push(`${C_LINK}connect${RESET} Connect to remote`);
|
|
483
|
+
commandLines.push(`${C_LINK}build${RESET} Create or edit agent`);
|
|
484
|
+
commandLines.push(`${C_LINK}start${RESET} Begin execution`);
|
|
485
|
+
commandLines.push(`${C_LINK}metrics${RESET} Performance and history`);
|
|
486
|
+
commandLines.push(`${C_LINK}system${RESET} Infrastructure status`);
|
|
487
|
+
commandLines.push(`${C_LINK}help${RESET} Show this help`);
|
|
488
|
+
commandLines.push(`${C_LINK}exit${RESET} Quit`);
|
|
489
|
+
console.log(Section('COMMANDS', commandLines));
|
|
490
|
+
console.log();
|
|
408
491
|
}
|
|
492
|
+
// StatusBar
|
|
493
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
494
|
+
console.log(StatusBar({
|
|
495
|
+
mode: mode,
|
|
496
|
+
connected: gatewayConnected,
|
|
497
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
498
|
+
workspace: currentUser?.username,
|
|
499
|
+
lastAction: 'help',
|
|
500
|
+
}));
|
|
501
|
+
console.log();
|
|
409
502
|
}
|
|
410
503
|
},
|
|
411
504
|
config: {
|
|
@@ -551,9 +644,74 @@ const commands = {
|
|
|
551
644
|
}
|
|
552
645
|
},
|
|
553
646
|
build: {
|
|
554
|
-
description: '
|
|
555
|
-
usage: 'build',
|
|
647
|
+
description: 'Create or edit agent',
|
|
648
|
+
usage: 'build [new|edit <agent>]',
|
|
556
649
|
handler: async (args, rl) => {
|
|
650
|
+
currentView = 'AGENT_LAB';
|
|
651
|
+
console.log();
|
|
652
|
+
const action = args[0] || 'list';
|
|
653
|
+
const agentName = args[1];
|
|
654
|
+
// 1. SELECT AGENT
|
|
655
|
+
if (action === 'list' || (!action && !agentName)) {
|
|
656
|
+
// Show agent list/table
|
|
657
|
+
const agentRows = [];
|
|
658
|
+
if (customAgents.size > 0) {
|
|
659
|
+
Array.from(customAgents.entries()).forEach(([key, agent]) => {
|
|
660
|
+
const agentId = truncateId(key, 8);
|
|
661
|
+
const modelShort = agent.baseAgent.includes(':')
|
|
662
|
+
? agent.baseAgent.split(':').pop()?.substring(0, 12) || 'unknown'
|
|
663
|
+
: truncateText(agent.baseAgent, 12);
|
|
664
|
+
const lastModified = 'N/A'; // Would need file modification time
|
|
665
|
+
const status = 'IDLE';
|
|
666
|
+
agentRows.push([
|
|
667
|
+
agentId,
|
|
668
|
+
truncateText(agent.name, 20),
|
|
669
|
+
modelShort,
|
|
670
|
+
lastModified,
|
|
671
|
+
status,
|
|
672
|
+
]);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const agentTable = agentRows.length > 0
|
|
676
|
+
? Table(['ID', 'NAME', 'MODEL', 'LAST MODIFIED', 'STATUS'], agentRows, 10)
|
|
677
|
+
: `${C_MUTED}${Copy.agent.noAgents}${RESET}`;
|
|
678
|
+
console.log(Section('SELECT AGENT', [agentTable]));
|
|
679
|
+
console.log();
|
|
680
|
+
// If only one agent, auto-select it
|
|
681
|
+
if (customAgents.size === 1) {
|
|
682
|
+
const singleAgent = Array.from(customAgents.values())[0];
|
|
683
|
+
// Show config for single agent
|
|
684
|
+
await showAgentConfig(singleAgent);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
// Show actions
|
|
688
|
+
const actionLines = [];
|
|
689
|
+
actionLines.push(`${C_LINK}build new${RESET} ${C_MUTED}Create new agent${RESET}`);
|
|
690
|
+
if (customAgents.size > 0) {
|
|
691
|
+
actionLines.push(`${C_LINK}build edit <name>${RESET} ${C_MUTED}Edit existing agent${RESET}`);
|
|
692
|
+
}
|
|
693
|
+
console.log(Section('ACTIONS', actionLines));
|
|
694
|
+
console.log();
|
|
695
|
+
currentView = 'HOME';
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
// Handle edit action
|
|
699
|
+
if (action === 'edit' && agentName) {
|
|
700
|
+
const agent = customAgents.get(agentName.toLowerCase());
|
|
701
|
+
if (!agent) {
|
|
702
|
+
console.log(Alert('error', `Agent not found: ${agentName}`, undefined, 'build'));
|
|
703
|
+
console.log();
|
|
704
|
+
currentView = 'HOME';
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
await showAgentConfig(agent);
|
|
708
|
+
// Then proceed to edit flow (would be implemented)
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
// Handle new action
|
|
712
|
+
if (action === 'new' || (!action && !agentName)) {
|
|
713
|
+
// Proceed with creation flow
|
|
714
|
+
}
|
|
557
715
|
// Check if API keys are stored on server
|
|
558
716
|
let providers = [];
|
|
559
717
|
try {
|
|
@@ -564,12 +722,17 @@ const commands = {
|
|
|
564
722
|
}
|
|
565
723
|
}
|
|
566
724
|
catch (error) {
|
|
567
|
-
console.log(`${red}
|
|
725
|
+
console.log(`${red}Providers unavailable${reset}`);
|
|
726
|
+
console.log(`${dim}Reason: ${error.message}${reset}`);
|
|
727
|
+
console.log(`${dim}Next: ${cyan}config${reset}${dim} to configure providers${reset}\n`);
|
|
728
|
+
currentView = 'HOME';
|
|
568
729
|
return;
|
|
569
730
|
}
|
|
570
731
|
if (providers.length === 0) {
|
|
571
|
-
console.log(`${red}No
|
|
572
|
-
console.log(`${
|
|
732
|
+
console.log(`${red}No providers configured${reset}`);
|
|
733
|
+
console.log(`${dim}Reason: No API keys found${reset}`);
|
|
734
|
+
console.log(`${dim}Next: ${cyan}config${reset}${dim} to add API keys${reset}\n`);
|
|
735
|
+
currentView = 'HOME';
|
|
573
736
|
return;
|
|
574
737
|
}
|
|
575
738
|
// Auto-select provider/model if not set
|
|
@@ -604,9 +767,19 @@ const commands = {
|
|
|
604
767
|
return;
|
|
605
768
|
}
|
|
606
769
|
}
|
|
607
|
-
|
|
608
|
-
console.log(`${
|
|
609
|
-
|
|
770
|
+
// Show agent list
|
|
771
|
+
console.log(`${purpleRGB}Agent List${reset}`);
|
|
772
|
+
if (customAgents.size > 0) {
|
|
773
|
+
customAgents.forEach((agent) => {
|
|
774
|
+
console.log(` ${cyan}${agent.name}${reset} ${dim}${agent.description || 'No description'}${reset}`);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
console.log(` ${dim}None yet${reset}`);
|
|
779
|
+
}
|
|
780
|
+
console.log();
|
|
781
|
+
// Agent configuration summary (will be shown after creation)
|
|
782
|
+
console.log(`${purpleRGB}Creating new agent${reset}\n`);
|
|
610
783
|
// Model type selection (local vs remote)
|
|
611
784
|
console.log(`${bright}Model Type:${reset}`);
|
|
612
785
|
console.log(` 1. ${cyan}Remote${reset} (OpenAI, Anthropic - requires API key)`);
|
|
@@ -867,8 +1040,24 @@ const commands = {
|
|
|
867
1040
|
};
|
|
868
1041
|
customAgents.set(name.toLowerCase(), customAgent);
|
|
869
1042
|
saveCustomAgents();
|
|
870
|
-
|
|
871
|
-
|
|
1043
|
+
// Save as last agent
|
|
1044
|
+
setLastAgent(name);
|
|
1045
|
+
// Show success and return to agent config view
|
|
1046
|
+
console.log(Alert('success', `${Copy.agent.created}: ${name}`, undefined, `start ${name}`));
|
|
1047
|
+
console.log();
|
|
1048
|
+
// Show the created agent's config (read-only)
|
|
1049
|
+
const createdAgent = customAgents.get(name.toLowerCase());
|
|
1050
|
+
if (createdAgent) {
|
|
1051
|
+
await showAgentConfig(createdAgent);
|
|
1052
|
+
}
|
|
1053
|
+
// Show actions
|
|
1054
|
+
const actionLines = [];
|
|
1055
|
+
actionLines.push(`${C_LINK}start ${name}${RESET} ${C_MUTED}Begin execution${RESET}`);
|
|
1056
|
+
actionLines.push(`${C_LINK}home${RESET} ${C_MUTED}Return to overview${RESET}`);
|
|
1057
|
+
console.log(Section('ACTIONS', actionLines));
|
|
1058
|
+
console.log();
|
|
1059
|
+
// Transition: AGENT_LAB → HOME
|
|
1060
|
+
currentView = 'HOME';
|
|
872
1061
|
}
|
|
873
1062
|
},
|
|
874
1063
|
agents: {
|
|
@@ -989,6 +1178,91 @@ const commands = {
|
|
|
989
1178
|
rl.prompt();
|
|
990
1179
|
}
|
|
991
1180
|
},
|
|
1181
|
+
system: {
|
|
1182
|
+
description: 'Show infrastructure readiness and risks',
|
|
1183
|
+
usage: 'system',
|
|
1184
|
+
handler: async () => {
|
|
1185
|
+
currentView = 'SYSTEM';
|
|
1186
|
+
console.log();
|
|
1187
|
+
// 1. INFRASTRUCTURE
|
|
1188
|
+
const infraLines = [];
|
|
1189
|
+
// Gateway
|
|
1190
|
+
if (gatewayConnected && client) {
|
|
1191
|
+
try {
|
|
1192
|
+
const health = await client.health();
|
|
1193
|
+
const endpoint = truncateText(gatewayUrl || '', 40);
|
|
1194
|
+
infraLines.push(`Gateway: ${C_SUCCESS}UP${RESET} ${C_MUTED}${endpoint}${RESET}`);
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
infraLines.push(`Gateway: ${C_ERROR}DOWN${RESET} ${C_MUTED}unhealthy${RESET}`);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
infraLines.push(`Gateway: ${C_ERROR}DOWN${RESET} ${C_MUTED}not connected${RESET}`);
|
|
1202
|
+
}
|
|
1203
|
+
// Database
|
|
1204
|
+
let dbType = 'none';
|
|
1205
|
+
let dbStatus = 'N/A';
|
|
1206
|
+
if (gatewayConnected && client) {
|
|
1207
|
+
try {
|
|
1208
|
+
const health = await client.health();
|
|
1209
|
+
dbType = health.persistence === 'enabled' ? 'sqlite' : 'none';
|
|
1210
|
+
dbStatus = health.persistence === 'enabled' ? 'connected' : 'disabled';
|
|
1211
|
+
const statusColor = dbStatus === 'connected' ? C_SUCCESS : C_WARN;
|
|
1212
|
+
infraLines.push(`Database: ${statusColor}${dbStatus.toUpperCase()}${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
infraLines.push(`Database: ${C_WARN}UNKNOWN${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
infraLines.push(`Database: ${C_WARN}MISSING${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1220
|
+
}
|
|
1221
|
+
// External APIs (simplified for v1)
|
|
1222
|
+
if (gatewayConnected) {
|
|
1223
|
+
try {
|
|
1224
|
+
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
1225
|
+
infraLines.push(`Metrics API: ${metricsRes.ok ? C_SUCCESS + 'UP' : C_WARN + 'DOWN' + RESET}`);
|
|
1226
|
+
}
|
|
1227
|
+
catch {
|
|
1228
|
+
infraLines.push(`Metrics API: ${C_WARN}DOWN${RESET}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
infraLines.push(`Metrics API: ${C_WARN}N/A${RESET} ${C_MUTED}not connected${RESET}`);
|
|
1233
|
+
}
|
|
1234
|
+
console.log(Section('INFRASTRUCTURE', infraLines));
|
|
1235
|
+
console.log();
|
|
1236
|
+
// 2. SECURITY
|
|
1237
|
+
const securityLines = [];
|
|
1238
|
+
// Auth mode
|
|
1239
|
+
let authMode = 'NONE';
|
|
1240
|
+
if (currentUser) {
|
|
1241
|
+
authMode = 'BASIC'; // Simplified - would check actual auth type
|
|
1242
|
+
}
|
|
1243
|
+
securityLines.push(`Auth mode: ${C_ACCENT}${authMode}${RESET}`);
|
|
1244
|
+
// Rate limiting
|
|
1245
|
+
const rateLimitEnabled = false; // v1: not implemented
|
|
1246
|
+
securityLines.push(`Rate limiting: ${rateLimitEnabled ? C_SUCCESS + 'ENABLED' : C_WARN + 'DISABLED' + RESET}`);
|
|
1247
|
+
// Environment
|
|
1248
|
+
const env = process.env.NODE_ENV === 'production' ? 'PROD' : 'DEV';
|
|
1249
|
+
const envColor = env === 'PROD' ? C_SUCCESS : C_WARN;
|
|
1250
|
+
securityLines.push(`Environment: ${envColor}${env}${RESET}`);
|
|
1251
|
+
console.log(Section('SECURITY', securityLines));
|
|
1252
|
+
console.log();
|
|
1253
|
+
// StatusBar
|
|
1254
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1255
|
+
console.log(StatusBar({
|
|
1256
|
+
mode: mode,
|
|
1257
|
+
connected: gatewayConnected,
|
|
1258
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1259
|
+
workspace: currentUser?.username,
|
|
1260
|
+
lastAction: 'system',
|
|
1261
|
+
}));
|
|
1262
|
+
console.log();
|
|
1263
|
+
currentView = 'HOME';
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
992
1266
|
status: {
|
|
993
1267
|
description: 'Show system status',
|
|
994
1268
|
usage: 'status',
|
|
@@ -1089,27 +1363,116 @@ const commands = {
|
|
|
1089
1363
|
}
|
|
1090
1364
|
},
|
|
1091
1365
|
start: {
|
|
1092
|
-
description: '
|
|
1093
|
-
usage: 'start
|
|
1094
|
-
handler: async (args) => {
|
|
1095
|
-
if
|
|
1096
|
-
|
|
1366
|
+
description: 'Begin execution',
|
|
1367
|
+
usage: 'start [agent]',
|
|
1368
|
+
handler: async (args, rl) => {
|
|
1369
|
+
// Auto-redirect: if no agents exist, go to agent lab
|
|
1370
|
+
if (customAgents.size === 0) {
|
|
1371
|
+
console.log(Alert('info', Copy.agent.noAgents, 'Create one', 'build'));
|
|
1372
|
+
console.log();
|
|
1373
|
+
// Transition to AGENT_LAB
|
|
1374
|
+
currentView = 'AGENT_LAB';
|
|
1375
|
+
await commands.build.handler([], rl);
|
|
1097
1376
|
return;
|
|
1098
1377
|
}
|
|
1378
|
+
currentView = 'RUN_CONSOLE';
|
|
1379
|
+
console.log();
|
|
1380
|
+
let agentName;
|
|
1381
|
+
let agent;
|
|
1099
1382
|
if (args.length === 0) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1383
|
+
// Shortcut: use last agent if available
|
|
1384
|
+
const lastAgent = getLastAgent();
|
|
1385
|
+
if (lastAgent) {
|
|
1386
|
+
agentName = lastAgent;
|
|
1387
|
+
agent = customAgents.get(lastAgent.toLowerCase());
|
|
1388
|
+
}
|
|
1389
|
+
// If no last agent or last agent not found, auto-select or prompt
|
|
1390
|
+
if (!agent) {
|
|
1391
|
+
if (customAgents.size === 1) {
|
|
1392
|
+
// Auto-select if only one agent
|
|
1393
|
+
agent = Array.from(customAgents.values())[0];
|
|
1394
|
+
agentName = agent.name;
|
|
1395
|
+
}
|
|
1396
|
+
else {
|
|
1397
|
+
// Multiple agents: list and prompt
|
|
1398
|
+
console.log(Section('AVAILABLE AGENTS', []));
|
|
1399
|
+
const agentList = Array.from(customAgents.values());
|
|
1400
|
+
agentList.forEach((a, i) => {
|
|
1401
|
+
console.log(` ${i + 1}. ${C_ACCENT}${a.name}${RESET} ${C_MUTED}${a.description || 'No description'}${RESET}`);
|
|
1402
|
+
});
|
|
1403
|
+
console.log();
|
|
1404
|
+
const choice = await prompt(rl, `${C_ACCENT}Select agent (1-${agentList.length}):${RESET} `);
|
|
1405
|
+
const index = parseInt(choice) - 1;
|
|
1406
|
+
if (index >= 0 && index < agentList.length) {
|
|
1407
|
+
agent = agentList[index];
|
|
1408
|
+
agentName = agent.name;
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
console.log(Alert('error', Copy.error.invalidCommand, undefined, 'start'));
|
|
1412
|
+
console.log();
|
|
1413
|
+
currentView = 'HOME';
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1109
1418
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1419
|
+
else {
|
|
1420
|
+
agentName = args.join(' ').trim();
|
|
1421
|
+
agent = customAgents.get(agentName.toLowerCase());
|
|
1422
|
+
if (!agent) {
|
|
1423
|
+
console.log(Alert('error', `Agent not found: ${agentName}`, undefined, 'build'));
|
|
1424
|
+
console.log();
|
|
1425
|
+
currentView = 'HOME';
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1112
1428
|
}
|
|
1429
|
+
// Save last agent
|
|
1430
|
+
if (agentName) {
|
|
1431
|
+
setLastAgent(agentName);
|
|
1432
|
+
}
|
|
1433
|
+
// 1. RUN INFO
|
|
1434
|
+
const runId = truncateId(`run-${Date.now()}`, 12);
|
|
1435
|
+
const startedTime = new Date().toISOString();
|
|
1436
|
+
const runInfoLines = [];
|
|
1437
|
+
runInfoLines.push(`Run ID: ${C_ACCENT}${runId}${RESET}`);
|
|
1438
|
+
runInfoLines.push(`Agent: ${C_TEXT}${agent.name}${RESET}`);
|
|
1439
|
+
runInfoLines.push(`Started: ${C_MUTED}${startedTime}${RESET}`);
|
|
1440
|
+
runInfoLines.push(`Target: ${C_ACCENT}${localMode ? 'LOCAL' : 'REMOTE'}${RESET}`);
|
|
1441
|
+
console.log(Section('RUN INFO', runInfoLines));
|
|
1442
|
+
console.log();
|
|
1443
|
+
// 2. LIVE STREAM
|
|
1444
|
+
const streamLines = [];
|
|
1445
|
+
streamLines.push(`${C_MUTED}${Copy.agent.awaitingOutput}${RESET}`);
|
|
1446
|
+
console.log(Section('LIVE STREAM', streamLines));
|
|
1447
|
+
console.log();
|
|
1448
|
+
// 3. STATUS
|
|
1449
|
+
const statusLines = [];
|
|
1450
|
+
statusLines.push(`Current phase: ${C_ACCENT}initializing${RESET}`);
|
|
1451
|
+
statusLines.push(`Step count: ${C_ACCENT}0${RESET}`);
|
|
1452
|
+
statusLines.push(`Time elapsed: ${C_ACCENT}0s${RESET}`);
|
|
1453
|
+
console.log(Section('STATUS', statusLines));
|
|
1454
|
+
console.log();
|
|
1455
|
+
// Note: Actual execution would stream events here
|
|
1456
|
+
// For now, simulate a brief execution
|
|
1457
|
+
await sleep(500);
|
|
1458
|
+
// Update stream with simulated events (short, declarative)
|
|
1459
|
+
console.log(`${C_MUTED}[1]${RESET} ${C_TEXT}Initializing context${RESET}`);
|
|
1460
|
+
await sleep(300);
|
|
1461
|
+
console.log(`${C_MUTED}[2]${RESET} ${C_TEXT}Processing${RESET}`);
|
|
1462
|
+
await sleep(300);
|
|
1463
|
+
console.log(`${C_MUTED}[3]${RESET} ${C_TEXT}Generating response${RESET}`);
|
|
1464
|
+
console.log();
|
|
1465
|
+
// StatusBar
|
|
1466
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1467
|
+
console.log(StatusBar({
|
|
1468
|
+
mode: mode,
|
|
1469
|
+
connected: gatewayConnected,
|
|
1470
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1471
|
+
workspace: currentUser?.username,
|
|
1472
|
+
lastAction: `start ${agent.name}`,
|
|
1473
|
+
}));
|
|
1474
|
+
console.log();
|
|
1475
|
+
currentView = 'HOME';
|
|
1113
1476
|
}
|
|
1114
1477
|
},
|
|
1115
1478
|
get: {
|
|
@@ -1190,24 +1553,71 @@ const commands = {
|
|
|
1190
1553
|
}
|
|
1191
1554
|
},
|
|
1192
1555
|
metrics: {
|
|
1193
|
-
description: 'Show
|
|
1194
|
-
usage: 'metrics',
|
|
1195
|
-
handler: async () => {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1556
|
+
description: 'Show performance and history',
|
|
1557
|
+
usage: 'metrics [recent|last|agent <name>]',
|
|
1558
|
+
handler: async (args) => {
|
|
1559
|
+
currentView = 'METRICS';
|
|
1560
|
+
console.log();
|
|
1561
|
+
// 1. SUMMARY
|
|
1562
|
+
const totalRuns = runHistory.length;
|
|
1563
|
+
const successCount = runHistory.filter(r => r.status === 'SUCCESS').length;
|
|
1564
|
+
const successRate = totalRuns > 0 ? Math.round((successCount / totalRuns) * 100) : 0;
|
|
1565
|
+
const summaryLines = [];
|
|
1566
|
+
if (totalRuns > 0) {
|
|
1567
|
+
summaryLines.push(`Total runs: ${C_ACCENT}${totalRuns}${RESET}`);
|
|
1568
|
+
summaryLines.push(`Success rate: ${C_ACCENT}${successRate}%${RESET}`);
|
|
1569
|
+
// Calculate average duration (simplified - would need actual duration tracking)
|
|
1570
|
+
summaryLines.push(`Avg duration: ${C_ACCENT}N/A${RESET} ${C_MUTED}(not tracked yet)${RESET}`);
|
|
1199
1571
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
const metrics = await client.metrics();
|
|
1203
|
-
console.log(`${bright}System Metrics:${reset}`);
|
|
1204
|
-
console.log(` Timestamp: ${metrics.timestamp}`);
|
|
1205
|
-
console.log(` Total metrics collected: ${Object.keys(metrics.metrics).length}`);
|
|
1206
|
-
console.log(`\n${gray}Use: curl ${gatewayUrl}/metrics for full details${reset}\n`);
|
|
1572
|
+
else {
|
|
1573
|
+
summaryLines.push(`${C_MUTED}Not enough data${RESET}`);
|
|
1207
1574
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1575
|
+
console.log(Section('SUMMARY', summaryLines));
|
|
1576
|
+
console.log();
|
|
1577
|
+
// 2. BY AGENT
|
|
1578
|
+
if (totalRuns > 0) {
|
|
1579
|
+
// Group runs by agent
|
|
1580
|
+
const agentStats = new Map();
|
|
1581
|
+
runHistory.forEach(run => {
|
|
1582
|
+
const stats = agentStats.get(run.agentName) || { total: 0, success: 0, lastRun: 0 };
|
|
1583
|
+
stats.total++;
|
|
1584
|
+
if (run.status === 'SUCCESS')
|
|
1585
|
+
stats.success++;
|
|
1586
|
+
if (run.timestamp > stats.lastRun)
|
|
1587
|
+
stats.lastRun = run.timestamp;
|
|
1588
|
+
agentStats.set(run.agentName, stats);
|
|
1589
|
+
});
|
|
1590
|
+
// Convert to table rows, sorted by activity
|
|
1591
|
+
const agentRows = Array.from(agentStats.entries())
|
|
1592
|
+
.sort((a, b) => b[1].total - a[1].total) // Most runs first
|
|
1593
|
+
.map(([name, stats]) => {
|
|
1594
|
+
const successPct = stats.total > 0 ? Math.round((stats.success / stats.total) * 100) : 0;
|
|
1595
|
+
const lastRunTime = formatRelativeTime(stats.lastRun);
|
|
1596
|
+
return [
|
|
1597
|
+
truncateText(name, 20),
|
|
1598
|
+
`${stats.total}`,
|
|
1599
|
+
`${successPct}%`,
|
|
1600
|
+
lastRunTime,
|
|
1601
|
+
];
|
|
1602
|
+
});
|
|
1603
|
+
const agentTable = Table(['AGENT', 'TOTAL RUNS', 'SUCCESS %', 'LAST RUN'], agentRows, 10);
|
|
1604
|
+
console.log(Section('BY AGENT', [agentTable]));
|
|
1210
1605
|
}
|
|
1606
|
+
else {
|
|
1607
|
+
console.log(Section('BY AGENT', [`${C_MUTED}No agent data available${RESET}`]));
|
|
1608
|
+
}
|
|
1609
|
+
console.log();
|
|
1610
|
+
// StatusBar
|
|
1611
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1612
|
+
console.log(StatusBar({
|
|
1613
|
+
mode: mode,
|
|
1614
|
+
connected: gatewayConnected,
|
|
1615
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1616
|
+
workspace: currentUser?.username,
|
|
1617
|
+
lastAction: 'metrics',
|
|
1618
|
+
}));
|
|
1619
|
+
console.log();
|
|
1620
|
+
currentView = 'HOME';
|
|
1211
1621
|
}
|
|
1212
1622
|
},
|
|
1213
1623
|
clear: {
|
|
@@ -1215,7 +1625,7 @@ const commands = {
|
|
|
1215
1625
|
usage: 'clear',
|
|
1216
1626
|
handler: async () => {
|
|
1217
1627
|
console.clear();
|
|
1218
|
-
|
|
1628
|
+
await showHomeView();
|
|
1219
1629
|
}
|
|
1220
1630
|
},
|
|
1221
1631
|
exit: {
|
|
@@ -1245,10 +1655,10 @@ const commands = {
|
|
|
1245
1655
|
return;
|
|
1246
1656
|
}
|
|
1247
1657
|
const username = args.join(' ').trim();
|
|
1248
|
-
console.log(
|
|
1249
|
-
await sleep(500);
|
|
1250
|
-
console.log(`${dim}[ ${brightGreen}AUTH${reset}${dim} ] Authenticating ${cyan}${username}${reset}${dim}...${reset}`);
|
|
1658
|
+
console.log(`${C_ACCENT}${Copy.connection.linking}${RESET}`);
|
|
1251
1659
|
await sleep(300);
|
|
1660
|
+
console.log(`${C_MUTED}${Copy.connection.authenticating} ${C_TEXT}${username}${RESET}`);
|
|
1661
|
+
await sleep(200);
|
|
1252
1662
|
// Create session
|
|
1253
1663
|
currentUser = createSession(username, gatewayUrl);
|
|
1254
1664
|
// Load user-specific data
|
|
@@ -1260,56 +1670,31 @@ const commands = {
|
|
|
1260
1670
|
client = new GatewayClient({ gatewayUrl });
|
|
1261
1671
|
const health = await client.health();
|
|
1262
1672
|
gatewayConnected = true;
|
|
1263
|
-
console.log(
|
|
1264
|
-
console.log(
|
|
1265
|
-
//
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
// Database
|
|
1269
|
-
process.stdout.write(`${gray} - Database: ${reset}`);
|
|
1270
|
-
console.log(`${green}✓${reset} ${dim}(${health.persistence})${reset}`);
|
|
1271
|
-
// Sentinel
|
|
1272
|
-
process.stdout.write(`${gray} - Sentinel: ${reset}`);
|
|
1273
|
-
try {
|
|
1274
|
-
const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
|
|
1275
|
-
if (sentinelRes.ok) {
|
|
1276
|
-
const sentinel = await sentinelRes.json();
|
|
1277
|
-
console.log(`${green}✓${reset} ${dim}(${sentinel?.status || 'active'})${reset}`);
|
|
1278
|
-
}
|
|
1279
|
-
else {
|
|
1280
|
-
console.log(`${yellow}⚠${reset} ${dim}(not responding)${reset}`);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
catch {
|
|
1284
|
-
console.log(`${yellow}⚠${reset} ${dim}(unavailable)${reset}`);
|
|
1285
|
-
}
|
|
1286
|
-
// Metrics
|
|
1287
|
-
process.stdout.write(`${gray} - Metrics: ${reset}`);
|
|
1288
|
-
try {
|
|
1289
|
-
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
1290
|
-
if (metricsRes.ok) {
|
|
1291
|
-
console.log(`${green}✓${reset}`);
|
|
1292
|
-
}
|
|
1293
|
-
else {
|
|
1294
|
-
console.log(`${yellow}⚠${reset}`);
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
catch {
|
|
1298
|
-
console.log(`${yellow}⚠${reset}`);
|
|
1299
|
-
}
|
|
1300
|
-
console.log(`${dim}User: ${green}${username}${reset}${dim} | Session: ${getSessionTimeRemaining(currentUser)}m${reset}\n`);
|
|
1673
|
+
console.log(Alert('success', 'Connected', `Gateway: ${gatewayUrl}`));
|
|
1674
|
+
console.log();
|
|
1675
|
+
// Save connection target to state
|
|
1676
|
+
setLastConnectionTarget(gatewayUrl);
|
|
1677
|
+
setLastMode('REMOTE');
|
|
1301
1678
|
// Start session checker
|
|
1302
1679
|
startSessionChecker();
|
|
1680
|
+
// Transition: SYSTEM → HOME
|
|
1681
|
+
currentView = 'SYSTEM';
|
|
1682
|
+
await sleep(200);
|
|
1683
|
+
await showHomeView();
|
|
1303
1684
|
}
|
|
1304
1685
|
catch (error) {
|
|
1305
|
-
console.log(
|
|
1306
|
-
console.log(
|
|
1686
|
+
console.log(Alert('warn', 'Connected, but gateway unavailable', 'Local features only', 'system diagnose'));
|
|
1687
|
+
console.log();
|
|
1307
1688
|
gatewayConnected = false;
|
|
1689
|
+
setLastMode('LOCAL');
|
|
1690
|
+
await showHomeView();
|
|
1308
1691
|
}
|
|
1309
1692
|
}
|
|
1310
1693
|
else {
|
|
1311
|
-
console.log(
|
|
1312
|
-
console.log(
|
|
1694
|
+
console.log(Alert('success', 'Connected', 'No gateway URL configured'));
|
|
1695
|
+
console.log();
|
|
1696
|
+
setLastMode('LOCAL');
|
|
1697
|
+
await showHomeView();
|
|
1313
1698
|
}
|
|
1314
1699
|
}
|
|
1315
1700
|
},
|
|
@@ -1322,11 +1707,11 @@ const commands = {
|
|
|
1322
1707
|
return;
|
|
1323
1708
|
}
|
|
1324
1709
|
const username = currentUser.username;
|
|
1325
|
-
console.log(
|
|
1326
|
-
await sleep(
|
|
1710
|
+
console.log(`${C_ACCENT}Terminating link${RESET}`);
|
|
1711
|
+
await sleep(200);
|
|
1327
1712
|
handleLogout();
|
|
1328
|
-
console.log(`${
|
|
1329
|
-
console.log(
|
|
1713
|
+
console.log(`${C_MUTED}${Copy.connection.terminated}${RESET}`);
|
|
1714
|
+
console.log();
|
|
1330
1715
|
}
|
|
1331
1716
|
},
|
|
1332
1717
|
whoami: {
|
|
@@ -1640,7 +2025,7 @@ async function startREPL() {
|
|
|
1640
2025
|
output: process.stdout,
|
|
1641
2026
|
prompt: `${cyan}4runr>${reset} `
|
|
1642
2027
|
});
|
|
1643
|
-
|
|
2028
|
+
// Don't show welcome - boot sequence transitions to HOME
|
|
1644
2029
|
rl.prompt();
|
|
1645
2030
|
rl.on('line', async (line) => {
|
|
1646
2031
|
// Handle multiline mode FIRST (before any command processing)
|
|
@@ -1804,22 +2189,55 @@ async function startREPL() {
|
|
|
1804
2189
|
if (cmd) {
|
|
1805
2190
|
try {
|
|
1806
2191
|
await cmd.handler(args, rl);
|
|
2192
|
+
// Return to stable view after command (if not already in a view)
|
|
2193
|
+
if (currentView === 'BOOT') {
|
|
2194
|
+
await showHomeView();
|
|
2195
|
+
}
|
|
1807
2196
|
}
|
|
1808
2197
|
catch (error) {
|
|
1809
|
-
|
|
2198
|
+
// Actionable errors: what failed, why, what to do next
|
|
2199
|
+
console.log(`\n${red}Failed: ${error.message}${reset}`);
|
|
2200
|
+
if (error.cause) {
|
|
2201
|
+
console.log(`${dim}Reason: ${error.cause}${reset}`);
|
|
2202
|
+
}
|
|
2203
|
+
// Suggest next action based on error type
|
|
2204
|
+
if (error.message.includes('connect') || error.message.includes('connection')) {
|
|
2205
|
+
console.log(`${dim}Next: ${cyan}connect <username>${reset}${dim} or run with ${cyan}--local${reset}\n`);
|
|
2206
|
+
}
|
|
2207
|
+
else if (error.message.includes('agent') || error.message.includes('build')) {
|
|
2208
|
+
console.log(`${dim}Next: ${cyan}build${reset}${dim} to create an agent${reset}\n`);
|
|
2209
|
+
}
|
|
2210
|
+
else {
|
|
2211
|
+
console.log(`${dim}Next: ${cyan}help${reset}${dim} for available commands${reset}\n`);
|
|
2212
|
+
}
|
|
2213
|
+
// Return to previous stable view
|
|
2214
|
+
if (currentView !== 'HOME' && currentView !== 'BOOT') {
|
|
2215
|
+
await showHomeView();
|
|
2216
|
+
}
|
|
1810
2217
|
}
|
|
1811
2218
|
}
|
|
1812
2219
|
else {
|
|
1813
|
-
console.log(`${
|
|
1814
|
-
console.log(
|
|
2220
|
+
console.log(Alert('error', `${Copy.error.invalidCommand}: ${command}`, undefined, 'help'));
|
|
2221
|
+
console.log();
|
|
1815
2222
|
}
|
|
1816
2223
|
rl.prompt();
|
|
1817
2224
|
});
|
|
1818
2225
|
rl.on('close', () => {
|
|
1819
|
-
|
|
2226
|
+
stopAllAnimations();
|
|
2227
|
+
console.log(`\n${C_ACCENT}Goodbye${RESET}\n`);
|
|
1820
2228
|
process.exit(0);
|
|
1821
2229
|
});
|
|
1822
2230
|
}
|
|
2231
|
+
// Cleanup on process termination
|
|
2232
|
+
process.on('SIGINT', () => {
|
|
2233
|
+
stopAllAnimations();
|
|
2234
|
+
console.log(`\n${C_MUTED}Terminated${RESET}\n`);
|
|
2235
|
+
process.exit(0);
|
|
2236
|
+
});
|
|
2237
|
+
process.on('SIGTERM', () => {
|
|
2238
|
+
stopAllAnimations();
|
|
2239
|
+
process.exit(0);
|
|
2240
|
+
});
|
|
1823
2241
|
// Start the OS
|
|
1824
2242
|
async function main() {
|
|
1825
2243
|
// Load custom agents on startup
|