4runr-os 1.0.15 → 1.0.22

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/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
- // ANSI colors - Enhanced 4Runr Brand Colors
15
- const colors = {
16
- reset: '\x1b[0m',
17
- bright: '\x1b[1m',
18
- cyan: '\x1b[36m',
19
- green: '\x1b[32m',
20
- yellow: '\x1b[33m',
21
- blue: '\x1b[34m',
22
- magenta: '\x1b[35m', // 4Runr Purple
23
- purple: '\x1b[35m', // 4Runr Purple (#6E56CF)
24
- red: '\x1b[31m',
25
- gray: '\x1b[90m',
26
- dim: '\x1b[2m',
27
- brightGreen: '\x1b[1m\x1b[32m',
28
- brightCyan: '\x1b[1m\x1b[36m',
29
- brightMagenta: '\x1b[1m\x1b[35m',
30
- brightPurple: '\x1b[1m\x1b[35m',
31
- // RGB colors for more vibrant purple
32
- purpleRGB: '\x1b[38;2;110;86;207m', // #6E56CF
33
- purpleBright: '\x1b[1m\x1b[38;2;110;86;207m',
34
- purpleDim: '\x1b[2m\x1b[38;2;110;86;207m',
35
- // Accent colors
36
- pink: '\x1b[38;2;236;72;153m', // #ec4899
37
- orange: '\x1b[38;2;249;115;22m', // #f97316
38
- teal: '\x1b[38;2;6;182;212m', // #06b6d4
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,122 +168,128 @@ const aiConfig = {
149
168
  model: null,
150
169
  apiKey: null
151
170
  };
152
- // Simulate boot sequence - Revolutionary 4Runr Branding
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
- // 4Runr - The Innovation. Clean, modern, uniquely 4Runr.
156
- console.log(`${reset}
157
- ${purpleRGB}╭─────────────────────────────────────────────────────────────────────────╮${reset}
158
- ${purpleRGB}│${reset} ${purpleRGB}│${reset}
159
- ${purpleRGB}│${reset} ${purpleBright}4${reset}${brightCyan}Runr${reset} ${purpleRGB}│${reset}
160
- ${purpleRGB}│${reset} ${purpleRGB}│${reset}
161
- ${purpleRGB}│${reset} ${dim}AI Agent Operating System${reset} ${purpleRGB}│${reset} ${teal}Version 2.0.0${reset} ${purpleRGB}│${reset}
162
- ${purpleRGB}│${reset} ${purpleRGB}│${reset}
163
- ${purpleRGB}╰─────────────────────────────────────────────────────────────────────────╯${reset}
164
- ${reset}`);
165
- await sleep(300);
166
- console.log(`\n${purpleRGB}Initializing 4Runr AI Agent OS...${reset}`);
167
- await sleep(200);
168
- // 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);
169
194
  const savedSession = loadSession();
170
- if (savedSession && isSessionValid(savedSession)) {
195
+ const configLoaded = savedSession && isSessionValid(savedSession);
196
+ if (configLoaded) {
171
197
  currentUser = savedSession;
172
- console.log(`${dim}[BOOT] Session: ${green}${savedSession.username}${reset}${dim} (${getSessionTimeRemaining(savedSession)}m)${reset}`);
173
- // Try to reconnect to gateway and verify components
174
- if (!localMode && (savedSession.gatewayUrl || gatewayUrl)) {
175
- const urlToUse = savedSession.gatewayUrl || gatewayUrl;
176
- console.log(`${purpleRGB}Connecting...${reset}`);
177
- try {
178
- client = new GatewayClient({ gatewayUrl: urlToUse });
179
- const health = await client.health();
180
- gatewayConnected = true;
181
- gatewayUrl = urlToUse;
182
- // Clean component verification
183
- console.log(`${dim}Verifying components...${reset}\n`);
184
- // Gateway
185
- process.stdout.write(`${cyan}Gateway${reset}${gray} ${reset}`);
186
- await sleep(80);
187
- console.log(`${green}✓${reset} ${dim}${health.status}${reset}`);
188
- // Database
189
- process.stdout.write(`${cyan}Database${reset}${gray} ${reset}`);
190
- await sleep(80);
191
- console.log(`${green}✓${reset} ${dim}${health.persistence}${reset}`);
192
- // Sentinel
193
- process.stdout.write(`${cyan}Sentinel${reset}${gray} ${reset}`);
194
- await sleep(80);
195
- try {
196
- const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
197
- if (sentinelRes.ok) {
198
- const sentinel = await sentinelRes.json();
199
- console.log(`${green}✓${reset} ${dim}${sentinel?.status || 'active'}${reset}`);
200
- }
201
- else {
202
- console.log(`${yellow}⚠${reset} ${dim}offline${reset}`);
203
- }
204
- }
205
- catch {
206
- console.log(`${yellow}⚠${reset} ${dim}offline${reset}`);
207
- }
208
- // Metrics
209
- process.stdout.write(`${cyan}Metrics${reset}${gray} ${reset}`);
210
- await sleep(80);
211
- try {
212
- const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
213
- if (metricsRes.ok) {
214
- console.log(`${green}✓${reset}`);
215
- }
216
- else {
217
- console.log(`${yellow}⚠${reset}`);
218
- }
219
- }
220
- catch {
221
- console.log(`${yellow}⚠${reset}`);
222
- }
223
- // AI Providers
224
- process.stdout.write(`${cyan}AI Providers${reset}${gray} ${reset}`);
225
- await sleep(80);
226
- console.log(`${green}✓${reset} ${dim}ready${reset}\n`);
227
- }
228
- catch (error) {
229
- console.log(`${yellow}✗${reset} ${dim}(mainframe unavailable)${reset}`);
230
- gatewayConnected = false;
231
- }
232
- }
233
198
  }
234
- else {
235
- // No session - user needs to connect
236
- if (localMode) {
237
- console.log(`${green}Local mode${reset} ${dim}(no remote features)${reset}\n`);
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);
238
215
  }
239
- else {
240
- console.log(`${yellow}Not connected${reset}`);
241
- console.log(`${dim}Use ${purpleBright}connect <username>${reset}${dim} to connect${reset}`);
242
- console.log(`${dim}Or run with ${purpleBright}--local${reset}${dim} for offline mode${reset}\n`);
216
+ catch {
217
+ gatewayStatus = 'WARN';
218
+ gatewayConnected = false;
243
219
  }
244
220
  }
245
- await sleep(200);
246
- // Clean, modern ready state
247
- console.log(`${green}Ready${reset} ${purpleRGB}4Runr OS v2.0.0${reset}\n`);
248
- if (currentUser) {
249
- if (gatewayConnected) {
250
- console.log(`${green}Connected${reset} ${dim}${currentUser.username}${reset} ${dim}${getSessionTimeRemaining(currentUser)}m session${reset}\n`);
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';
251
235
  }
252
- else {
253
- console.log(`${yellow}Partial connection${reset} ${dim}${currentUser.username}${reset} ${dim}${getSessionTimeRemaining(currentUser)}m session${reset}`);
254
- console.log(`${dim}Remote features unavailable${reset}\n`);
236
+ catch {
237
+ dbStatus = 'WARN';
255
238
  }
256
239
  }
257
- else if (!localMode) {
258
- console.log(`${dim}Type ${purpleBright}connect <username>${reset}${dim} to connect${reset}\n`);
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}`;
259
278
  }
260
279
  else {
261
- console.log(`${green}Local mode${reset} ${dim}(no remote features)${reset}\n`);
280
+ finalStatus = `${C_WARN}${Copy.status.notConnected}${RESET}`;
262
281
  }
282
+ console.log(finalStatus);
263
283
  systemReady = true;
264
284
  // Start session expiry checker
265
285
  if (currentUser && !localMode) {
266
286
  startSessionChecker();
267
287
  }
288
+ // Save mode to state
289
+ setLastMode(localMode ? 'LOCAL' : 'REMOTE');
290
+ // Auto transition to HOME
291
+ await sleep(200);
292
+ await showHomeView();
268
293
  }
269
294
  /**
270
295
  * Start session expiry checker
@@ -308,14 +333,103 @@ async function progress(items) {
308
333
  function sleep(ms) {
309
334
  return new Promise(resolve => setTimeout(resolve, ms));
310
335
  }
311
- // Show welcome screen - Clean, modern, uniquely 4Runr
312
- function showWelcome() {
313
- console.log(`${purpleRGB}4Runr AI Agent Operating System${reset}\n`);
314
- console.log(`${dim}Quick start:${reset}`);
315
- console.log(`${dim} help${reset} ${dim}See available commands${reset}`);
316
- console.log(`${dim} config${reset} ${dim}Configure AI providers${reset}`);
317
- console.log(`${dim} build${reset} ${dim}Create a custom AI agent${reset}`);
318
- console.log(`${dim} exit${reset} ${dim}Quit${reset}\n`);
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);
319
433
  }
320
434
  // Interactive prompt helper
321
435
  function prompt(rl, question) {
@@ -349,49 +463,42 @@ const commands = {
349
463
  if (args.length > 0) {
350
464
  const cmd = commands[args[0]];
351
465
  if (cmd) {
352
- console.log(`\n${bright}${args[0]}${reset}`);
353
- console.log(` ${cmd.description}`);
354
- console.log(` Usage: ${cyan}${cmd.usage}${reset}\n`);
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();
355
472
  }
356
473
  else {
357
- console.log(`${red}Unknown command: ${args[0]}${reset}`);
474
+ console.log(Alert('error', `${Copy.error.invalidCommand}: ${args[0]}`, undefined, 'help'));
475
+ console.log();
358
476
  }
359
477
  }
360
478
  else {
361
- // Beautiful help menu with 4Runr branding
362
- console.log(`\n${purpleRGB}╔═══════════════════════════════════════════════════════════════════════════╗${reset}`);
363
- console.log(`${purpleRGB}║${reset} ${brightCyan}${bright}Available Commands${reset} ${purpleRGB}║${reset}`);
364
- console.log(`${purpleRGB}╠═══════════════════════════════════════════════════════════════════════════╣${reset}`);
365
- console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}AI Configuration${reset} ${purpleRGB}║${reset}`);
366
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}config${reset}${gray} ${dim}Configure AI provider (OpenAI/Anthropic)${reset} ${purpleRGB}║${reset}`);
367
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}models${reset}${gray} ${dim}List available AI models${reset} ${purpleRGB}║${reset}`);
368
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}ai-status${reset}${gray} ${dim}Show stored API keys and configuration${reset} ${purpleRGB}║${reset}`);
369
- console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
370
- console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Agent Management${reset} ${purpleRGB}║${reset}`);
371
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}run-agent${reset}${gray} ${dim}Start chatting with an AI agent${reset} ${purpleRGB}║${reset}`);
372
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}build${reset}${gray} ${dim}Build a custom AI agent (interactive)${reset} ${purpleRGB}║${reset}`);
373
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}agents${reset}${gray} ${dim}List available agents${reset} ${purpleRGB}║${reset}`);
374
- console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
375
- console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Run Management${reset} ${purpleRGB}║${reset}`);
376
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}create${reset}${gray} ${dim}Create a new run${reset} ${purpleRGB}║${reset}`);
377
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}start${reset}${gray} ${dim}Start a run${reset} ${purpleRGB}║${reset}`);
378
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}get${reset}${gray} ${dim}Get run details${reset} ${purpleRGB}║${reset}`);
379
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}list${reset}${gray} ${dim}List all runs${reset} ${purpleRGB}║${reset}`);
380
- console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
381
- console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Connection${reset} ${purpleRGB}║${reset}`);
382
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}connect${reset}${gray} ${dim}Connect to mainframe (required for remote features)${reset} ${purpleRGB}║${reset}`);
383
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}disconnect${reset}${gray} ${dim}Disconnect from mainframe${reset} ${purpleRGB}║${reset}`);
384
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}whoami${reset}${gray} ${dim}Show current connection and session info${reset} ${purpleRGB}║${reset}`);
385
- console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
386
- console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}System${reset} ${purpleRGB}║${reset}`);
387
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}status${reset}${gray} ${dim}Show system status${reset} ${purpleRGB}║${reset}`);
388
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}gateway${reset}${gray} ${dim}Manage gateway connection${reset} ${purpleRGB}║${reset}`);
389
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}sentinel${reset}${gray} ${dim}Show Sentinel monitoring${reset} ${purpleRGB}║${reset}`);
390
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}metrics${reset}${gray} ${dim}Show system metrics${reset} ${purpleRGB}║${reset}`);
391
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}clear${reset}${gray} ${dim}Clear the screen${reset} ${purpleRGB}║${reset}`);
392
- console.log(`${purpleRGB}║${reset} ${cyan}${bright}exit${reset}${gray} ${dim}Exit 4Runr OS${reset} ${purpleRGB}║${reset}`);
393
- 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();
394
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();
395
502
  }
396
503
  },
397
504
  config: {
@@ -537,9 +644,74 @@ const commands = {
537
644
  }
538
645
  },
539
646
  build: {
540
- description: 'Build a custom AI agent (interactive)',
541
- usage: 'build',
647
+ description: 'Create or edit agent',
648
+ usage: 'build [new|edit <agent>]',
542
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
+ }
543
715
  // Check if API keys are stored on server
544
716
  let providers = [];
545
717
  try {
@@ -550,12 +722,17 @@ const commands = {
550
722
  }
551
723
  }
552
724
  catch (error) {
553
- console.log(`${red}✗${reset} Error fetching providers: ${error.message}\n`);
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';
554
729
  return;
555
730
  }
556
731
  if (providers.length === 0) {
557
- console.log(`${red}No API keys found on server${reset}`);
558
- console.log(`${gray}Please run ${bright}config${reset}${gray} to add your API keys first${reset}\n`);
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';
559
736
  return;
560
737
  }
561
738
  // Auto-select provider/model if not set
@@ -590,9 +767,19 @@ const commands = {
590
767
  return;
591
768
  }
592
769
  }
593
- console.log(`\n${dim}┌─ AGENT BUILDER ───────────────────────────────┐${reset}`);
594
- console.log(`${dim}│${reset} ${brightGreen}Create Custom AI Agent${reset}`);
595
- console.log(`${dim}└──────────────────────────────────────────────────┘${reset}\n`);
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`);
596
783
  // Model type selection (local vs remote)
597
784
  console.log(`${bright}Model Type:${reset}`);
598
785
  console.log(` 1. ${cyan}Remote${reset} (OpenAI, Anthropic - requires API key)`);
@@ -853,8 +1040,24 @@ const commands = {
853
1040
  };
854
1041
  customAgents.set(name.toLowerCase(), customAgent);
855
1042
  saveCustomAgents();
856
- console.log(`\n${green}✓${reset} Agent created: ${bright}${name}${reset}`);
857
- console.log(` Use ${cyan}run-agent ${name}${reset} to start chatting\n`);
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';
858
1061
  }
859
1062
  },
860
1063
  agents: {
@@ -975,6 +1178,91 @@ const commands = {
975
1178
  rl.prompt();
976
1179
  }
977
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
+ },
978
1266
  status: {
979
1267
  description: 'Show system status',
980
1268
  usage: 'status',
@@ -1075,27 +1363,116 @@ const commands = {
1075
1363
  }
1076
1364
  },
1077
1365
  start: {
1078
- description: 'Start a run',
1079
- usage: 'start <run-id>',
1080
- handler: async (args) => {
1081
- if (!client || !gatewayConnected) {
1082
- console.log(`${red}Gateway not connected. Use ${bright}gateway connect${reset}${red} first.${reset}\n`);
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);
1083
1376
  return;
1084
1377
  }
1378
+ currentView = 'RUN_CONSOLE';
1379
+ console.log();
1380
+ let agentName;
1381
+ let agent;
1085
1382
  if (args.length === 0) {
1086
- console.log(`${red}Usage: start <run-id>${reset}\n`);
1087
- return;
1088
- }
1089
- const runId = args[0];
1090
- console.log(`${gray}Starting run ${runId}...${reset}`);
1091
- try {
1092
- await client.runs.start(runId);
1093
- console.log(`${green}✓${reset} Run started`);
1094
- console.log(` Use ${cyan}get ${runId}${reset} to check status\n`);
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
+ }
1095
1418
  }
1096
- catch (error) {
1097
- console.log(`${red}✗${reset} Error: ${error.message}\n`);
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
+ }
1098
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';
1099
1476
  }
1100
1477
  },
1101
1478
  get: {
@@ -1176,24 +1553,71 @@ const commands = {
1176
1553
  }
1177
1554
  },
1178
1555
  metrics: {
1179
- description: 'Show system metrics',
1180
- usage: 'metrics',
1181
- handler: async () => {
1182
- if (!client || !gatewayConnected) {
1183
- console.log(`${red}Gateway not connected. Use ${bright}gateway connect${reset}${red} first.${reset}\n`);
1184
- return;
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}`);
1185
1571
  }
1186
- console.log(`${gray}Fetching metrics...${reset}\n`);
1187
- try {
1188
- const metrics = await client.metrics();
1189
- console.log(`${bright}System Metrics:${reset}`);
1190
- console.log(` Timestamp: ${metrics.timestamp}`);
1191
- console.log(` Total metrics collected: ${Object.keys(metrics.metrics).length}`);
1192
- console.log(`\n${gray}Use: curl ${gatewayUrl}/metrics for full details${reset}\n`);
1572
+ else {
1573
+ summaryLines.push(`${C_MUTED}Not enough data${RESET}`);
1193
1574
  }
1194
- catch (error) {
1195
- console.log(`${red}✗ Metrics not available${reset}\n`);
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]));
1196
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';
1197
1621
  }
1198
1622
  },
1199
1623
  clear: {
@@ -1201,7 +1625,7 @@ const commands = {
1201
1625
  usage: 'clear',
1202
1626
  handler: async () => {
1203
1627
  console.clear();
1204
- showWelcome();
1628
+ await showHomeView();
1205
1629
  }
1206
1630
  },
1207
1631
  exit: {
@@ -1231,10 +1655,10 @@ const commands = {
1231
1655
  return;
1232
1656
  }
1233
1657
  const username = args.join(' ').trim();
1234
- console.log(`\n${dim}[ ${brightGreen}CONNECT${reset}${dim} ] Establishing link to mainframe...${reset}`);
1235
- await sleep(500);
1236
- console.log(`${dim}[ ${brightGreen}AUTH${reset}${dim} ] Authenticating ${cyan}${username}${reset}${dim}...${reset}`);
1658
+ console.log(`${C_ACCENT}${Copy.connection.linking}${RESET}`);
1237
1659
  await sleep(300);
1660
+ console.log(`${C_MUTED}${Copy.connection.authenticating} ${C_TEXT}${username}${RESET}`);
1661
+ await sleep(200);
1238
1662
  // Create session
1239
1663
  currentUser = createSession(username, gatewayUrl);
1240
1664
  // Load user-specific data
@@ -1246,56 +1670,31 @@ const commands = {
1246
1670
  client = new GatewayClient({ gatewayUrl });
1247
1671
  const health = await client.health();
1248
1672
  gatewayConnected = true;
1249
- console.log(`${green}[ ${brightGreen}CONNECTED${reset}${green} ] Link established${reset}`);
1250
- console.log(`${dim}Verifying components...${reset}`);
1251
- // Gateway
1252
- process.stdout.write(`${gray} - Gateway: ${reset}`);
1253
- console.log(`${green}✓${reset} ${dim}(${health.status})${reset}`);
1254
- // Database
1255
- process.stdout.write(`${gray} - Database: ${reset}`);
1256
- console.log(`${green}✓${reset} ${dim}(${health.persistence})${reset}`);
1257
- // Sentinel
1258
- process.stdout.write(`${gray} - Sentinel: ${reset}`);
1259
- try {
1260
- const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
1261
- if (sentinelRes.ok) {
1262
- const sentinel = await sentinelRes.json();
1263
- console.log(`${green}✓${reset} ${dim}(${sentinel?.status || 'active'})${reset}`);
1264
- }
1265
- else {
1266
- console.log(`${yellow}⚠${reset} ${dim}(not responding)${reset}`);
1267
- }
1268
- }
1269
- catch {
1270
- console.log(`${yellow}⚠${reset} ${dim}(unavailable)${reset}`);
1271
- }
1272
- // Metrics
1273
- process.stdout.write(`${gray} - Metrics: ${reset}`);
1274
- try {
1275
- const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
1276
- if (metricsRes.ok) {
1277
- console.log(`${green}✓${reset}`);
1278
- }
1279
- else {
1280
- console.log(`${yellow}⚠${reset}`);
1281
- }
1282
- }
1283
- catch {
1284
- console.log(`${yellow}⚠${reset}`);
1285
- }
1286
- 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');
1287
1678
  // Start session checker
1288
1679
  startSessionChecker();
1680
+ // Transition: SYSTEM → HOME
1681
+ currentView = 'SYSTEM';
1682
+ await sleep(200);
1683
+ await showHomeView();
1289
1684
  }
1290
1685
  catch (error) {
1291
- console.log(`${yellow}[ ${brightGreen}PARTIAL${reset}${yellow} ] Connected, but mainframe unavailable${reset}`);
1292
- console.log(`${dim}Components: ${yellow}Offline${reset} ${dim}(local features only)${reset}\n`);
1686
+ console.log(Alert('warn', 'Connected, but gateway unavailable', 'Local features only', 'system diagnose'));
1687
+ console.log();
1293
1688
  gatewayConnected = false;
1689
+ setLastMode('LOCAL');
1690
+ await showHomeView();
1294
1691
  }
1295
1692
  }
1296
1693
  else {
1297
- console.log(`${green}[ ${brightGreen}CONNECTED${reset}${green} ] Local mode${reset}`);
1298
- console.log(`${dim}Components: ${yellow}N/A${reset} ${dim}(no gateway configured)${reset}\n`);
1694
+ console.log(Alert('success', 'Connected', 'No gateway URL configured'));
1695
+ console.log();
1696
+ setLastMode('LOCAL');
1697
+ await showHomeView();
1299
1698
  }
1300
1699
  }
1301
1700
  },
@@ -1308,11 +1707,11 @@ const commands = {
1308
1707
  return;
1309
1708
  }
1310
1709
  const username = currentUser.username;
1311
- console.log(`\n${dim}[ ${brightGreen}DISCONNECT${reset}${dim} ] Terminating link for ${cyan}${username}${reset}${dim}...${reset}`);
1312
- await sleep(300);
1710
+ console.log(`${C_ACCENT}Terminating link${RESET}`);
1711
+ await sleep(200);
1313
1712
  handleLogout();
1314
- console.log(`${green}[ ${brightGreen}DISCONNECTED${reset}${green} ] Link terminated${reset}`);
1315
- console.log(`${dim}Use ${bright}connect <username>${reset}${dim} to reconnect${reset}\n`);
1713
+ console.log(`${C_MUTED}${Copy.connection.terminated}${RESET}`);
1714
+ console.log();
1316
1715
  }
1317
1716
  },
1318
1717
  whoami: {
@@ -1626,7 +2025,7 @@ async function startREPL() {
1626
2025
  output: process.stdout,
1627
2026
  prompt: `${cyan}4runr>${reset} `
1628
2027
  });
1629
- showWelcome();
2028
+ // Don't show welcome - boot sequence transitions to HOME
1630
2029
  rl.prompt();
1631
2030
  rl.on('line', async (line) => {
1632
2031
  // Handle multiline mode FIRST (before any command processing)
@@ -1790,22 +2189,55 @@ async function startREPL() {
1790
2189
  if (cmd) {
1791
2190
  try {
1792
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
+ }
1793
2196
  }
1794
2197
  catch (error) {
1795
- console.log(`${red}Error: ${error.message}${reset}\n`);
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
+ }
1796
2217
  }
1797
2218
  }
1798
2219
  else {
1799
- console.log(`${red}Unknown command: ${command}${reset}`);
1800
- console.log(`${gray}Type 'help' for available commands${reset}\n`);
2220
+ console.log(Alert('error', `${Copy.error.invalidCommand}: ${command}`, undefined, 'help'));
2221
+ console.log();
1801
2222
  }
1802
2223
  rl.prompt();
1803
2224
  });
1804
2225
  rl.on('close', () => {
1805
- console.log(`\n${cyan}Goodbye!${reset}\n`);
2226
+ stopAllAnimations();
2227
+ console.log(`\n${C_ACCENT}Goodbye${RESET}\n`);
1806
2228
  process.exit(0);
1807
2229
  });
1808
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
+ });
1809
2241
  // Start the OS
1810
2242
  async function main() {
1811
2243
  // Load custom agents on startup