4runr-os 2.5.2 → 2.6.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"package-integrity.d.ts","sourceRoot":"","sources":["../../src/security/package-integrity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBvF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAoB9F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAyB5F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAwCzG"}
1
+ {"version":3,"file":"package-integrity.d.ts","sourceRoot":"","sources":["../../src/security/package-integrity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBvF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAkB9F;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAwB5F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAuCzG"}
@@ -4,13 +4,14 @@
4
4
  */
5
5
  import * as crypto from 'crypto';
6
6
  import { spawnSync } from 'child_process';
7
+ import * as fs from 'fs';
8
+ import { sanitizePackageName } from './validation.js';
7
9
  /**
8
10
  * Get package integrity hash from npm registry
9
11
  */
10
12
  export function getPackageIntegrity(packageName, version) {
11
13
  try {
12
14
  // SECURITY: Validate package name
13
- const { sanitizePackageName } = require('./validation.js');
14
15
  const validation = sanitizePackageName(packageName);
15
16
  if (!validation.valid || !validation.sanitized) {
16
17
  throw new Error(`Invalid package name: ${validation.error}`);
@@ -35,7 +36,6 @@ export function getPackageIntegrity(packageName, version) {
35
36
  */
36
37
  export function verifyPackageIntegrity(tarballPath, expectedIntegrity) {
37
38
  try {
38
- const fs = require('fs');
39
39
  // Parse integrity string (format: algorithm-base64hash)
40
40
  const match = expectedIntegrity.match(/^(sha\d+)-(.+)$/);
41
41
  if (!match) {
@@ -56,7 +56,6 @@ export function verifyPackageIntegrity(tarballPath, expectedIntegrity) {
56
56
  */
57
57
  export function verifyInstalledVersion(packageName, expectedVersion) {
58
58
  try {
59
- const { sanitizePackageName } = require('./validation.js');
60
59
  const validation = sanitizePackageName(packageName);
61
60
  if (!validation.valid || !validation.sanitized) {
62
61
  return false;
@@ -81,7 +80,6 @@ export function verifyInstalledVersion(packageName, expectedVersion) {
81
80
  */
82
81
  export function safeNpmInstall(packageName, version) {
83
82
  try {
84
- const { sanitizePackageName } = require('./validation.js');
85
83
  const validation = sanitizePackageName(packageName);
86
84
  if (!validation.valid || !validation.sanitized) {
87
85
  return { success: false, error: `Invalid package name: ${validation.error}` };
@@ -1 +1 @@
1
- {"version":3,"file":"package-integrity.js","sourceRoot":"","sources":["../../src/security/package-integrity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,OAAe;IACtE,IAAI,CAAC;QACH,kCAAkC;QAClC,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAEpD,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,yBAAyB,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,sDAAsD;QACtD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,IAAI,OAAO,EAAE,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE;YAC1G,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzD,OAAO,SAAS,IAAI,IAAI,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB,EAAE,iBAAyB;IACnF,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAEzB,wDAAwD;QACxD,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC;QAE1C,gCAAgC;QAChC,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAErG,OAAO,IAAI,KAAK,YAAY,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB,EAAE,eAAuB;IACjF,IAAI,CAAC;QACH,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAEpD,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YAC/C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE;YAC3F,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,gBAAgB,GAAG,IAAI,EAAE,YAAY,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAE7E,OAAO,gBAAgB,KAAK,eAAe,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,OAAe;IACjE,IAAI,CAAC;QACH,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAEpD,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YAC/C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;QAChF,CAAC;QAED,uCAAuC;QACvC,MAAM,SAAS,GAAG,mBAAmB,CAAC,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACrE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC;QACzE,CAAC;QAED,oDAAoD;QACpD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE;YAC9B,SAAS,EAAE,IAAI;YACf,GAAG,UAAU,CAAC,SAAS,IAAI,OAAO,EAAE;YACpC,eAAe,SAAS,EAAE;YAC1B,YAAY,EAAE,iDAAiD;SAChE,EAAE;YACD,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtE,CAAC;QAED,2BAA2B;QAC3B,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACvE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAClD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"package-integrity.js","sourceRoot":"","sources":["../../src/security/package-integrity.ts"],"names":[],"mappings":"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"}
@@ -43,14 +43,25 @@ fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
43
43
  // === HEADER ===
44
44
  let agent_count = state.agent_list.agents.len();
45
45
  let header_block = Block::default()
46
- .title(format!(" 🤖 Agent List ({} agents) ", agent_count))
46
+ .title(format!(" 🤖 Agent List ", ))
47
47
  .title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
48
48
  .borders(Borders::ALL)
49
49
  .border_style(Style::default().fg(CYBER_CYAN))
50
50
  .style(Style::default().bg(BG_PANEL));
51
51
 
52
+ let header_inner = header_block.inner(chunks[0]);
52
53
  f.render_widget(header_block, chunks[0]);
53
54
 
55
+ // Add count on the right side
56
+ let count_text = Line::from(vec![
57
+ Span::styled(format!("{} total", agent_count), Style::default().fg(TEXT_DIM)),
58
+ ]);
59
+
60
+ f.render_widget(
61
+ Paragraph::new(count_text).alignment(Alignment::Right),
62
+ header_inner
63
+ );
64
+
54
65
  // === CONTENT: Agent Table ===
55
66
  let content_block = Block::default()
56
67
  .borders(Borders::ALL)
@@ -84,15 +95,30 @@ fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
84
95
  format!(" {}", agent.name)
85
96
  };
86
97
 
87
- let desc = agent.description.as_deref().unwrap_or("No description").chars().take(30).collect::<String>();
88
- let model = agent.model.chars().take(15).collect::<String>();
89
- let provider = agent.provider.chars().take(10).collect::<String>();
98
+ let desc = agent.description.as_deref().unwrap_or("No description");
99
+ let desc_display = if desc.is_empty() || desc == "No description" {
100
+ "—".to_string()
101
+ } else {
102
+ desc.chars().take(35).collect::<String>()
103
+ };
104
+
105
+ let model_display = if agent.model.is_empty() || agent.model == "unknown" {
106
+ "—".to_string()
107
+ } else {
108
+ agent.model.chars().take(15).collect::<String>()
109
+ };
110
+
111
+ let provider_display = if agent.provider.is_empty() || agent.provider == "unknown" {
112
+ "—".to_string()
113
+ } else {
114
+ agent.provider.chars().take(12).collect::<String>()
115
+ };
90
116
 
91
117
  Row::new(vec![
92
118
  Cell::from(name).style(style),
93
- Cell::from(desc).style(style),
94
- Cell::from(model).style(style),
95
- Cell::from(provider).style(style),
119
+ Cell::from(desc_display).style(style),
120
+ Cell::from(model_display).style(style),
121
+ Cell::from(provider_display).style(style),
96
122
  ])
97
123
  }).collect();
98
124
 
@@ -103,10 +129,10 @@ fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
103
129
  Constraint::Percentage(20),
104
130
  ])
105
131
  .header(Row::new(vec![
106
- Cell::from("Name").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
107
- Cell::from("Description").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
108
- Cell::from("Model").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
109
- Cell::from("Provider").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
132
+ Cell::from("NAME").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
133
+ Cell::from("DESCRIPTION").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
134
+ Cell::from("MODEL").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
135
+ Cell::from("PROVIDER").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
110
136
  ]))
111
137
  .column_spacing(1)
112
138
  .style(Style::default().fg(TEXT_PRIMARY));
@@ -116,12 +142,14 @@ fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
116
142
 
117
143
  // === FOOTER ===
118
144
  let footer_text = Line::from(vec![
119
- Span::styled("↑/↓", Style::default().fg(CYBER_CYAN)),
120
- Span::styled(" Navigate | ", Style::default().fg(TEXT_DIM)),
121
- Span::styled("Enter", Style::default().fg(NEON_GREEN)),
122
- Span::styled(" View Details | ", Style::default().fg(TEXT_DIM)),
123
- Span::styled("ESC", Style::default().fg(BRAND_PURPLE)),
124
- Span::styled(" Close", Style::default().fg(TEXT_DIM)),
145
+ Span::styled("↑/↓", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
146
+ Span::styled(" Navigate", Style::default().fg(TEXT_PRIMARY)),
147
+ Span::styled("", Style::default().fg(TEXT_MUTED)),
148
+ Span::styled("Enter", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
149
+ Span::styled(" View Details", Style::default().fg(TEXT_PRIMARY)),
150
+ Span::styled("", Style::default().fg(TEXT_MUTED)),
151
+ Span::styled("ESC", Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
152
+ Span::styled(" Close", Style::default().fg(TEXT_PRIMARY)),
125
153
  ]);
126
154
 
127
155
  f.render_widget(
@@ -134,8 +162,11 @@ fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
134
162
  }
135
163
 
136
164
  fn render_detail_popup(f: &mut Frame, area: Rect, state: &AppState) {
137
- // First render list view (dimmed)
138
- render_list_view(f, area, state);
165
+ // Render full-screen dimmed overlay first
166
+ f.render_widget(
167
+ Block::default().style(Style::default().bg(Color::Black)),
168
+ area
169
+ );
139
170
 
140
171
  // Then render detail popup on top
141
172
  let detail_index = state.agent_list.detail_view.unwrap();
@@ -145,10 +176,11 @@ fn render_detail_popup(f: &mut Frame, area: Rect, state: &AppState) {
145
176
  }
146
177
 
147
178
  fn render_agent_detail(f: &mut Frame, area: Rect, agent: &AgentInfo) {
179
+ use ratatui::layout::{Constraint, Direction, Layout};
148
180
 
149
- // Calculate popup size (70% width, 80% height, centered)
150
- let popup_width = (area.width * 70 / 100).max(50);
151
- let popup_height = (area.height * 80 / 100).max(15);
181
+ // Calculate popup size (65% width, 75% height, centered)
182
+ let popup_width = (area.width * 65 / 100).max(55);
183
+ let popup_height = (area.height * 75 / 100).max(18);
152
184
  let popup_x = (area.width.saturating_sub(popup_width)) / 2;
153
185
  let popup_y = (area.height.saturating_sub(popup_height)) / 2;
154
186
 
@@ -159,102 +191,146 @@ fn render_agent_detail(f: &mut Frame, area: Rect, agent: &AgentInfo) {
159
191
  height: popup_height,
160
192
  };
161
193
 
162
- // Render dimmed overlay
163
- for y in area.y..area.y + area.height {
164
- for x in area.x..area.x + area.width {
165
- if x < popup_x || x >= popup_x + popup_width || y < popup_y || y >= popup_y + popup_height {
166
- f.render_widget(
167
- Block::default().style(Style::default().bg(Color::Black)),
168
- Rect { x, y, width: 1, height: 1 }
169
- );
170
- }
171
- }
172
- }
194
+ // Split popup into header, content, footer
195
+ let chunks = Layout::default()
196
+ .direction(Direction::Vertical)
197
+ .constraints([
198
+ Constraint::Length(3), // Header
199
+ Constraint::Min(0), // Content
200
+ Constraint::Length(1), // Footer
201
+ ])
202
+ .split(popup_area);
173
203
 
174
- // Render detail popup
175
- let detail_block = Block::default()
176
- .title(format!(" 📋 Agent Details: {} ", agent.name))
204
+ // === HEADER ===
205
+ let header_block = Block::default()
206
+ .title(format!(" 📋 Agent: {} ", agent.name))
177
207
  .title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
178
208
  .borders(Borders::ALL)
179
209
  .border_style(Style::default().fg(CYBER_CYAN))
180
210
  .style(Style::default().bg(BG_PANEL));
181
211
 
182
- let inner = detail_block.inner(popup_area);
183
- f.render_widget(detail_block, popup_area);
212
+ let header_inner = header_block.inner(chunks[0]);
213
+ f.render_widget(header_block, chunks[0]);
214
+
215
+ // Add ESC instruction
216
+ let esc_text = Line::from(vec![
217
+ Span::styled("[ESC]", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
218
+ ]);
219
+ f.render_widget(
220
+ Paragraph::new(esc_text).alignment(Alignment::Right),
221
+ header_inner
222
+ );
223
+
224
+ // === CONTENT ===
225
+ let content_block = Block::default()
226
+ .borders(Borders::ALL)
227
+ .border_style(Style::default().fg(TEXT_MUTED))
228
+ .style(Style::default().bg(BG_PANEL));
229
+
230
+ let content_inner = content_block.inner(chunks[1]);
231
+ f.render_widget(content_block, chunks[1]);
184
232
 
185
- // Create detail text
233
+ // Create detail text with cleaner layout
186
234
  let mut detail_lines = vec![
187
- Line::from(vec![
188
- Span::styled("Name: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
189
- Span::styled(&agent.name, Style::default().fg(NEON_GREEN)),
190
- ]),
191
235
  Line::from(""),
192
236
  ];
193
237
 
238
+ // Basic Info
239
+ add_field(&mut detail_lines, "Name", &agent.name);
240
+
194
241
  if let Some(desc) = &agent.description {
195
- detail_lines.push(Line::from(vec![
196
- Span::styled("Description: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
197
- Span::styled(desc, Style::default().fg(TEXT_PRIMARY)),
198
- ]));
199
- detail_lines.push(Line::from(""));
242
+ if !desc.is_empty() {
243
+ add_field(&mut detail_lines, "Description", desc);
244
+ }
200
245
  }
201
246
 
202
- detail_lines.push(Line::from(vec![
203
- Span::styled("Model: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
204
- Span::styled(&agent.model, Style::default().fg(TEXT_PRIMARY)),
205
- ]));
206
- detail_lines.push(Line::from(vec![
207
- Span::styled("Provider: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
208
- Span::styled(&agent.provider, Style::default().fg(TEXT_PRIMARY)),
209
- ]));
247
+ add_field(&mut detail_lines, "Model", if agent.model.is_empty() || agent.model == "unknown" { "—" } else { &agent.model });
248
+ add_field(&mut detail_lines, "Provider", if agent.provider.is_empty() || agent.provider == "unknown" { "—" } else { &agent.provider });
210
249
 
211
250
  if let Some(temp) = agent.temperature {
212
- detail_lines.push(Line::from(vec![
213
- Span::styled("Temperature: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
214
- Span::styled(format!("{:.2}", temp), Style::default().fg(TEXT_PRIMARY)),
215
- ]));
251
+ add_field(&mut detail_lines, "Temperature", &format!("{:.2}", temp));
216
252
  }
217
253
 
218
254
  if let Some(max_tokens) = agent.max_tokens {
219
- detail_lines.push(Line::from(vec![
220
- Span::styled("Max Tokens: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
221
- Span::styled(format!("{}", max_tokens), Style::default().fg(TEXT_PRIMARY)),
222
- ]));
255
+ add_field(&mut detail_lines, "Max Tokens", &format!("{}", max_tokens));
223
256
  }
224
257
 
225
258
  if !agent.tools.is_empty() {
226
259
  detail_lines.push(Line::from(""));
227
260
  detail_lines.push(Line::from(vec![
228
- Span::styled("Tools: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
261
+ Span::styled(" Tools: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
229
262
  ]));
230
263
  for tool in &agent.tools {
231
264
  detail_lines.push(Line::from(vec![
232
- Span::raw(" • "),
265
+ Span::raw(" • "),
233
266
  Span::styled(tool, Style::default().fg(NEON_GREEN)),
234
267
  ]));
235
268
  }
236
269
  }
237
270
 
238
271
  if let Some(prompt) = &agent.system_prompt {
239
- detail_lines.push(Line::from(""));
240
- detail_lines.push(Line::from(vec![
241
- Span::styled("System Prompt: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
242
- ]));
243
- // Show first 200 chars of prompt
244
- let prompt_preview = prompt.chars().take(200).collect::<String>();
245
- detail_lines.push(Line::from(format!(" {}", prompt_preview)).style(Style::default().fg(TEXT_DIM)));
272
+ if !prompt.is_empty() {
273
+ detail_lines.push(Line::from(""));
274
+ detail_lines.push(Line::from(vec![
275
+ Span::styled(" ────────────────────────────────────────", Style::default().fg(TEXT_MUTED)),
276
+ ]));
277
+ detail_lines.push(Line::from(""));
278
+ detail_lines.push(Line::from(vec![
279
+ Span::styled(" System Prompt:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
280
+ ]));
281
+ detail_lines.push(Line::from(""));
282
+ // Wrap system prompt text (simple manual wrapping)
283
+ let words: Vec<&str> = prompt.split_whitespace().collect();
284
+ let max_width = (popup_width as usize).saturating_sub(8);
285
+ let mut current_line = String::from(" ");
286
+
287
+ for word in words {
288
+ if current_line.len() + word.len() + 1 > max_width {
289
+ detail_lines.push(Line::from(vec![
290
+ Span::styled(current_line.trim_end().to_string(), Style::default().fg(TEXT_DIM)),
291
+ ]));
292
+ current_line = format!(" {}", word);
293
+ } else {
294
+ if current_line.len() > 2 {
295
+ current_line.push(' ');
296
+ }
297
+ current_line.push_str(word);
298
+ }
299
+ }
300
+ if current_line.trim().len() > 0 {
301
+ detail_lines.push(Line::from(vec![
302
+ Span::styled(current_line.trim_end().to_string(), Style::default().fg(TEXT_DIM)),
303
+ ]));
304
+ }
305
+ }
246
306
  }
247
307
 
248
- detail_lines.push(Line::from(""));
249
- detail_lines.push(Line::from(vec![
308
+ // Render content
309
+ f.render_widget(
310
+ Paragraph::new(detail_lines)
311
+ .wrap(Wrap { trim: false }),
312
+ content_inner
313
+ );
314
+
315
+ // === FOOTER ===
316
+ let footer_text = Line::from(vec![
250
317
  Span::styled("Press ", Style::default().fg(TEXT_DIM)),
251
318
  Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
252
319
  Span::styled(" to close", Style::default().fg(TEXT_DIM)),
253
- ]));
320
+ ]);
254
321
 
255
322
  f.render_widget(
256
- Paragraph::new(detail_lines)
257
- .wrap(Wrap { trim: false }),
258
- inner
323
+ Paragraph::new(footer_text)
324
+ .alignment(Alignment::Center)
325
+ .style(Style::default().bg(BG_PANEL)),
326
+ chunks[2]
259
327
  );
260
328
  }
329
+
330
+ /// Helper function to add a field line
331
+ fn add_field(lines: &mut Vec<Line<'static>>, label: &str, value: &str) {
332
+ lines.push(Line::from(vec![
333
+ Span::styled(format!(" {:<15}", label + ":"), Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
334
+ Span::styled(value.to_string(), Style::default().fg(TEXT_PRIMARY)),
335
+ ]));
336
+ }
@@ -1,8 +1,8 @@
1
- /// Help popup screen
2
- /// Professional, organized command reference
1
+ /// Help popup screen - Professional card-based layout
2
+ /// Clean, readable command reference
3
3
 
4
4
  use ratatui::prelude::*;
5
- use ratatui::widgets::{Block, Borders, Paragraph, Wrap, List, ListItem};
5
+ use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
6
6
  use crate::app::AppState;
7
7
 
8
8
  // === 4RUNR BRAND COLORS ===
@@ -17,9 +17,9 @@ const BG_PANEL: Color = Color::Rgb(18, 18, 25);
17
17
 
18
18
  /// Render help popup overlay
19
19
  pub fn render_help(f: &mut Frame, area: Rect, _state: &AppState) {
20
- // Calculate popup size (80% width, 85% height, centered)
21
- let popup_width = (area.width * 80 / 100).max(60);
22
- let popup_height = (area.height * 85 / 100).max(20);
20
+ // Calculate popup size (85% width, 90% height, centered)
21
+ let popup_width = (area.width * 85 / 100).max(70);
22
+ let popup_height = (area.height * 90 / 100).max(25);
23
23
  let popup_x = (area.width.saturating_sub(popup_width)) / 2;
24
24
  let popup_y = (area.height.saturating_sub(popup_height)) / 2;
25
25
 
@@ -30,10 +30,11 @@ pub fn render_help(f: &mut Frame, area: Rect, _state: &AppState) {
30
30
  height: popup_height,
31
31
  };
32
32
 
33
- // Render background overlay (dimmed)
34
- let overlay = Block::default()
35
- .style(Style::default().bg(Color::Black).fg(Color::Black));
36
- f.render_widget(overlay, area);
33
+ // Render dimmed overlay
34
+ f.render_widget(
35
+ Block::default().style(Style::default().bg(Color::Black)),
36
+ area
37
+ );
37
38
 
38
39
  // Render help popup
39
40
  render_help_content(f, popup_area);
@@ -42,12 +43,12 @@ pub fn render_help(f: &mut Frame, area: Rect, _state: &AppState) {
42
43
  fn render_help_content(f: &mut Frame, area: Rect) {
43
44
  use ratatui::layout::{Constraint, Direction, Layout};
44
45
 
45
- // Split into sections
46
+ // Split into header, content, footer
46
47
  let chunks = Layout::default()
47
48
  .direction(Direction::Vertical)
48
49
  .constraints([
49
50
  Constraint::Length(3), // Header
50
- Constraint::Min(0), // Content
51
+ Constraint::Min(0), // Content
51
52
  Constraint::Length(1), // Footer
52
53
  ])
53
54
  .split(area);
@@ -60,45 +61,136 @@ fn render_help_content(f: &mut Frame, area: Rect) {
60
61
  .border_style(Style::default().fg(CYBER_CYAN))
61
62
  .style(Style::default().bg(BG_PANEL));
62
63
 
64
+ let header_inner = header_block.inner(chunks[0]);
65
+ f.render_widget(header_block, chunks[0]);
66
+
63
67
  let header_text = Line::from(vec![
64
68
  Span::styled("Press ", Style::default().fg(TEXT_DIM)),
65
69
  Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
66
70
  Span::styled(" to close", Style::default().fg(TEXT_DIM)),
67
71
  ]);
68
72
 
69
- f.render_widget(header_block, chunks[0]);
70
73
  f.render_widget(
71
74
  Paragraph::new(header_text).alignment(Alignment::Right),
72
- Rect {
73
- x: chunks[0].x + 1,
74
- y: chunks[0].y + 1,
75
- width: chunks[0].width.saturating_sub(2),
76
- height: 1,
77
- }
75
+ header_inner
78
76
  );
79
77
 
80
78
  // === CONTENT ===
81
- let content_chunks = Layout::default()
82
- .direction(Direction::Horizontal)
83
- .constraints([
84
- Constraint::Percentage(40), // Left: Command list
85
- Constraint::Percentage(60), // Right: Details
86
- ])
87
- .split(chunks[1]);
88
-
89
- // Left panel: Command categories
90
- render_command_list(f, content_chunks[0]);
79
+ let content_block = Block::default()
80
+ .borders(Borders::ALL)
81
+ .border_style(Style::default().fg(TEXT_MUTED))
82
+ .style(Style::default().bg(BG_PANEL));
91
83
 
92
- // Right panel: Command details
93
- render_command_details(f, content_chunks[1]);
84
+ let content_inner = content_block.inner(chunks[1]);
85
+ f.render_widget(content_block, chunks[1]);
86
+
87
+ // Build help content with card-based sections
88
+ let mut lines = Vec::new();
89
+
90
+ // Navigation Commands Section
91
+ lines.push(Line::from(vec![
92
+ Span::styled(" ", Style::default()),
93
+ ]));
94
+ lines.push(Line::from(vec![
95
+ Span::styled(" 🧭 NAVIGATION COMMANDS ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
96
+ ]));
97
+ lines.push(Line::from(vec![
98
+ Span::styled(" ", Style::default()),
99
+ ]));
100
+
101
+ add_command(&mut lines, "build", "Open Agent Builder (6-step wizard)");
102
+ add_command(&mut lines, "runs", "Open Run Manager (filter & sort)");
103
+ add_command(&mut lines, "config, settings", "Open Settings (mode & provider)");
104
+ add_command(&mut lines, "ESC", "Close overlay or clear input");
105
+
106
+ lines.push(Line::from(vec![Span::styled(" ", Style::default())]));
107
+
108
+ // Local Commands Section
109
+ lines.push(Line::from(vec![
110
+ Span::styled(" 💻 LOCAL COMMANDS ", Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
111
+ ]));
112
+ lines.push(Line::from(vec![
113
+ Span::styled(" ", Style::default()),
114
+ ]));
115
+
116
+ add_command(&mut lines, "quit, exit", "Exit application");
117
+ add_command(&mut lines, "clear", "Clear operations log");
118
+ add_command(&mut lines, "help", "Show this help");
119
+ add_command(&mut lines, ":perf", "Show performance stats");
120
+
121
+ lines.push(Line::from(vec![Span::styled(" ", Style::default())]));
122
+
123
+ // WebSocket Commands Section
124
+ lines.push(Line::from(vec![
125
+ Span::styled(" 🌐 WEBSOCKET COMMANDS ", Style::default().fg(AMBER_WARN).add_modifier(Modifier::BOLD)),
126
+ Span::styled(" (requires connection)", Style::default().fg(TEXT_DIM)),
127
+ ]));
128
+ lines.push(Line::from(vec![
129
+ Span::styled(" ", Style::default()),
130
+ ]));
131
+
132
+ add_command(&mut lines, "agent.list", "List all agents");
133
+ add_command(&mut lines, "agent.get", "Get agent details");
134
+ add_command(&mut lines, "agent.create", "Create new agent");
135
+ add_command(&mut lines, "agent.delete", "Delete agent");
136
+ add_command(&mut lines, "system.status", "Get system status");
137
+ add_command(&mut lines, "run.list", "List all runs");
138
+ add_command(&mut lines, "tool.list", "List available tools");
139
+
140
+ lines.push(Line::from(vec![Span::styled(" ", Style::default())]));
141
+
142
+ // Keyboard Shortcuts Section
143
+ lines.push(Line::from(vec![
144
+ Span::styled(" ⌨️ KEYBOARD SHORTCUTS ", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
145
+ ]));
146
+ lines.push(Line::from(vec![
147
+ Span::styled(" ", Style::default()),
148
+ ]));
149
+
150
+ lines.push(Line::from(vec![
151
+ Span::styled(" Agent Builder: ", Style::default().fg(CYBER_CYAN)),
152
+ Span::styled("Enter", Style::default().fg(NEON_GREEN)),
153
+ Span::styled("=Next ", Style::default().fg(TEXT_PRIMARY)),
154
+ Span::styled("Backspace", Style::default().fg(NEON_GREEN)),
155
+ Span::styled("=Prev ", Style::default().fg(TEXT_PRIMARY)),
156
+ Span::styled("ESC", Style::default().fg(NEON_GREEN)),
157
+ Span::styled("=Cancel", Style::default().fg(TEXT_PRIMARY)),
158
+ ]));
159
+
160
+ lines.push(Line::from(vec![
161
+ Span::styled(" Run Manager: ", Style::default().fg(CYBER_CYAN)),
162
+ Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
163
+ Span::styled("=Navigate ", Style::default().fg(TEXT_PRIMARY)),
164
+ Span::styled("F", Style::default().fg(NEON_GREEN)),
165
+ Span::styled("=Filter ", Style::default().fg(TEXT_PRIMARY)),
166
+ Span::styled("S", Style::default().fg(NEON_GREEN)),
167
+ Span::styled("=Sort ", Style::default().fg(TEXT_PRIMARY)),
168
+ Span::styled("R", Style::default().fg(NEON_GREEN)),
169
+ Span::styled("=Refresh", Style::default().fg(TEXT_PRIMARY)),
170
+ ]));
171
+
172
+ lines.push(Line::from(vec![
173
+ Span::styled(" Settings: ", Style::default().fg(CYBER_CYAN)),
174
+ Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
175
+ Span::styled("=Navigate ", Style::default().fg(TEXT_PRIMARY)),
176
+ Span::styled("Space", Style::default().fg(NEON_GREEN)),
177
+ Span::styled("=Toggle ", Style::default().fg(TEXT_PRIMARY)),
178
+ Span::styled("Enter", Style::default().fg(NEON_GREEN)),
179
+ Span::styled("=Save", Style::default().fg(TEXT_PRIMARY)),
180
+ ]));
181
+
182
+ // Render content
183
+ f.render_widget(
184
+ Paragraph::new(lines)
185
+ .wrap(Wrap { trim: false }),
186
+ content_inner
187
+ );
94
188
 
95
189
  // === FOOTER ===
96
190
  let footer_text = Line::from(vec![
97
- Span::styled("Navigate: ", Style::default().fg(TEXT_DIM)),
98
- Span::styled("↑/↓", Style::default().fg(CYBER_CYAN)),
99
- Span::styled(" Select | ", Style::default().fg(TEXT_MUTED)),
100
- Span::styled("ESC", Style::default().fg(NEON_GREEN)),
101
- Span::styled(" Close", Style::default().fg(TEXT_DIM)),
191
+ Span::styled("Press ", Style::default().fg(TEXT_DIM)),
192
+ Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
193
+ Span::styled(" to close", Style::default().fg(TEXT_DIM)),
102
194
  ]);
103
195
 
104
196
  f.render_widget(
@@ -109,206 +201,11 @@ fn render_help_content(f: &mut Frame, area: Rect) {
109
201
  );
110
202
  }
111
203
 
112
- fn render_command_list(f: &mut Frame, area: Rect) {
113
- use ratatui::layout::{Constraint, Direction, Layout};
114
-
115
- let chunks = Layout::default()
116
- .direction(Direction::Vertical)
117
- .constraints([
118
- Constraint::Length(7), // Navigation Commands
119
- Constraint::Length(7), // Local Commands
120
- Constraint::Min(0), // WebSocket Commands
121
- ])
122
- .split(area);
123
-
124
- // Navigation Commands section
125
- let nav_block = Block::default()
126
- .title(" 🧭 Navigation ")
127
- .title_style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD))
128
- .borders(Borders::ALL)
129
- .border_style(Style::default().fg(CYBER_CYAN))
130
- .style(Style::default().bg(BG_PANEL));
131
-
132
- let nav_items = vec![
133
- ListItem::new("build").style(Style::default().fg(NEON_GREEN)),
134
- ListItem::new("runs").style(Style::default().fg(NEON_GREEN)),
135
- ListItem::new("config, settings").style(Style::default().fg(NEON_GREEN)),
136
- ListItem::new("ESC").style(Style::default().fg(NEON_GREEN)),
137
- ];
138
-
139
- let nav_list = List::new(nav_items);
140
-
141
- let nav_inner = nav_block.inner(chunks[0]);
142
- f.render_widget(nav_block, chunks[0]);
143
- f.render_widget(nav_list, nav_inner);
144
-
145
- // Local Commands section
146
- let local_block = Block::default()
147
- .title(" 💻 Local ")
148
- .title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
149
- .borders(Borders::ALL)
150
- .border_style(Style::default().fg(BRAND_PURPLE))
151
- .style(Style::default().bg(BG_PANEL));
152
-
153
- let local_items = vec![
154
- ListItem::new("quit, exit").style(Style::default().fg(NEON_GREEN)),
155
- ListItem::new("clear").style(Style::default().fg(NEON_GREEN)),
156
- ListItem::new("help").style(Style::default().fg(NEON_GREEN)),
157
- ListItem::new(":perf").style(Style::default().fg(NEON_GREEN)),
158
- ];
159
-
160
- let local_list = List::new(local_items);
161
-
162
- let local_inner = local_block.inner(chunks[1]);
163
- f.render_widget(local_block, chunks[1]);
164
- f.render_widget(local_list, local_inner);
165
-
166
- // WebSocket Commands section
167
- let ws_block = Block::default()
168
- .title(" 🌐 WebSocket (requires connection) ")
169
- .title_style(Style::default().fg(AMBER_WARN).add_modifier(Modifier::BOLD))
170
- .borders(Borders::ALL)
171
- .border_style(Style::default().fg(AMBER_WARN))
172
- .style(Style::default().bg(BG_PANEL));
173
-
174
- let ws_items = vec![
175
- ListItem::new("agent.list").style(Style::default().fg(NEON_GREEN)),
176
- ListItem::new("agent.get").style(Style::default().fg(NEON_GREEN)),
177
- ListItem::new("agent.create").style(Style::default().fg(NEON_GREEN)),
178
- ListItem::new("agent.delete").style(Style::default().fg(NEON_GREEN)),
179
- ListItem::new("system.status").style(Style::default().fg(NEON_GREEN)),
180
- ListItem::new("run.list").style(Style::default().fg(NEON_GREEN)),
181
- ListItem::new("tool.list").style(Style::default().fg(NEON_GREEN)),
182
- ];
183
-
184
- let ws_list = List::new(ws_items);
185
-
186
- let ws_inner = ws_block.inner(chunks[2]);
187
- f.render_widget(ws_block, chunks[2]);
188
- f.render_widget(ws_list, ws_inner);
189
- }
190
-
191
- fn render_command_details(f: &mut Frame, area: Rect) {
192
- use ratatui::layout::{Constraint, Direction, Layout};
193
-
194
- let chunks = Layout::default()
195
- .direction(Direction::Vertical)
196
- .constraints([
197
- Constraint::Percentage(50), // Command descriptions
198
- Constraint::Percentage(50), // Screen controls
199
- ])
200
- .split(area);
201
-
202
- // Command descriptions
203
- let desc_block = Block::default()
204
- .title(" 📋 Command Details ")
205
- .title_style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD))
206
- .borders(Borders::ALL)
207
- .border_style(Style::default().fg(CYBER_CYAN))
208
- .style(Style::default().bg(BG_PANEL));
209
-
210
- let desc_text = vec![
211
- Line::from(vec![
212
- Span::styled("build", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
213
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
214
- Span::styled("Open Agent Builder (6-step wizard)", Style::default().fg(TEXT_PRIMARY)),
215
- ]),
216
- Line::from(""),
217
- Line::from(vec![
218
- Span::styled("runs", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
219
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
220
- Span::styled("Open Run Manager (list, filter, sort)", Style::default().fg(TEXT_PRIMARY)),
221
- ]),
222
- Line::from(""),
223
- Line::from(vec![
224
- Span::styled("config, settings", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
225
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
226
- Span::styled("Open Settings (mode, AI provider)", Style::default().fg(TEXT_PRIMARY)),
227
- ]),
228
- Line::from(""),
229
- Line::from(vec![
230
- Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
231
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
232
- Span::styled("Close overlay/popup or clear input", Style::default().fg(TEXT_PRIMARY)),
233
- ]),
234
- Line::from(""),
235
- Line::from(vec![
236
- Span::styled("quit, exit", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
237
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
238
- Span::styled("Exit application", Style::default().fg(TEXT_PRIMARY)),
239
- ]),
240
- Line::from(""),
241
- Line::from(vec![
242
- Span::styled("clear", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
243
- Span::styled(" - ", Style::default().fg(TEXT_MUTED)),
244
- Span::styled("Clear operations log", Style::default().fg(TEXT_PRIMARY)),
245
- ]),
246
- ];
247
-
248
- let desc_inner = desc_block.inner(chunks[0]);
249
- f.render_widget(desc_block, chunks[0]);
250
- f.render_widget(
251
- Paragraph::new(desc_text)
252
- .wrap(Wrap { trim: false }),
253
- desc_inner
254
- );
255
-
256
- // Screen controls
257
- let controls_block = Block::default()
258
- .title(" ⌨️ Screen Controls ")
259
- .title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
260
- .borders(Borders::ALL)
261
- .border_style(Style::default().fg(BRAND_PURPLE))
262
- .style(Style::default().bg(BG_PANEL));
263
-
264
- let controls_text = vec![
265
- Line::from(vec![
266
- Span::styled("Agent Builder:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
267
- ]),
268
- Line::from(vec![
269
- Span::raw(" "),
270
- Span::styled("Enter", Style::default().fg(NEON_GREEN)),
271
- Span::raw(" = Next | "),
272
- Span::styled("Backspace", Style::default().fg(NEON_GREEN)),
273
- Span::raw(" = Prev | "),
274
- Span::styled("ESC", Style::default().fg(NEON_GREEN)),
275
- Span::raw(" = Cancel"),
276
- ]),
277
- Line::from(""),
278
- Line::from(vec![
279
- Span::styled("Run Manager:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
280
- ]),
281
- Line::from(vec![
282
- Span::raw(" "),
283
- Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
284
- Span::raw(" = Navigate | "),
285
- Span::styled("F", Style::default().fg(NEON_GREEN)),
286
- Span::raw(" = Filter | "),
287
- Span::styled("S", Style::default().fg(NEON_GREEN)),
288
- Span::raw(" = Sort | "),
289
- Span::styled("R", Style::default().fg(NEON_GREEN)),
290
- Span::raw(" = Refresh"),
291
- ]),
292
- Line::from(""),
293
- Line::from(vec![
294
- Span::styled("Settings:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
295
- ]),
296
- Line::from(vec![
297
- Span::raw(" "),
298
- Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
299
- Span::raw(" = Navigate | "),
300
- Span::styled("Space", Style::default().fg(NEON_GREEN)),
301
- Span::raw(" = Toggle | "),
302
- Span::styled("Enter", Style::default().fg(NEON_GREEN)),
303
- Span::raw(" = Save"),
304
- ]),
305
- ];
306
-
307
- let controls_inner = controls_block.inner(chunks[1]);
308
- f.render_widget(controls_block, chunks[1]);
309
- f.render_widget(
310
- Paragraph::new(controls_text)
311
- .wrap(Wrap { trim: false }),
312
- controls_inner
313
- );
204
+ /// Helper function to add a command line
205
+ fn add_command(lines: &mut Vec<Line<'static>>, command: &str, description: &str) {
206
+ lines.push(Line::from(vec![
207
+ Span::styled(" ", Style::default()),
208
+ Span::styled(format!("{:<20}", command), Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
209
+ Span::styled(description, Style::default().fg(TEXT_PRIMARY)),
210
+ ]));
314
211
  }
@@ -120,7 +120,7 @@ fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
120
120
 
121
121
  // Line 1: Brand + version + uptime - Bug 3 fix: Use "4Runr." with dot (matches brand logo)
122
122
  // Use npm package version (2.3.5) - matches package.json
123
- const PACKAGE_VERSION: &str = "2.5.2";
123
+ const PACKAGE_VERSION: &str = "2.6.0";
124
124
  let brand_line = Line::from(vec![
125
125
  Span::styled("4Runr.", Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
126
126
  Span::styled(" AI AGENT OS", Style::default().fg(BRAND_VIOLET)),
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.5.2: Fixed loading screen being overshadowed by agent list during boot. Resolved all compilation warnings. Professional help popup interface, Agent List viewer with detail popup. Enhanced UX with clean navigation. Built with Rust + Ratatui. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.6.0: MAJOR UI POLISH - Complete redesign of help popup (single-column, readable layout), fixed agent detail popup overlay (proper full-screen dimming), enhanced agent list (replaced 'unknown' with '—', better styling). Professional, clean, readable interface. Built with Rust + Ratatui. ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",
@@ -5,6 +5,8 @@
5
5
 
6
6
  import * as crypto from 'crypto';
7
7
  import { spawnSync } from 'child_process';
8
+ import * as fs from 'fs';
9
+ import { sanitizePackageName } from './validation.js';
8
10
 
9
11
  /**
10
12
  * Get package integrity hash from npm registry
@@ -12,7 +14,6 @@ import { spawnSync } from 'child_process';
12
14
  export function getPackageIntegrity(packageName: string, version: string): string | null {
13
15
  try {
14
16
  // SECURITY: Validate package name
15
- const { sanitizePackageName } = require('./validation.js');
16
17
  const validation = sanitizePackageName(packageName);
17
18
 
18
19
  if (!validation.valid || !validation.sanitized) {
@@ -41,8 +42,6 @@ export function getPackageIntegrity(packageName: string, version: string): strin
41
42
  */
42
43
  export function verifyPackageIntegrity(tarballPath: string, expectedIntegrity: string): boolean {
43
44
  try {
44
- const fs = require('fs');
45
-
46
45
  // Parse integrity string (format: algorithm-base64hash)
47
46
  const match = expectedIntegrity.match(/^(sha\d+)-(.+)$/);
48
47
  if (!match) {
@@ -66,7 +65,6 @@ export function verifyPackageIntegrity(tarballPath: string, expectedIntegrity: s
66
65
  */
67
66
  export function verifyInstalledVersion(packageName: string, expectedVersion: string): boolean {
68
67
  try {
69
- const { sanitizePackageName } = require('./validation.js');
70
68
  const validation = sanitizePackageName(packageName);
71
69
 
72
70
  if (!validation.valid || !validation.sanitized) {
@@ -96,7 +94,6 @@ export function verifyInstalledVersion(packageName: string, expectedVersion: str
96
94
  */
97
95
  export function safeNpmInstall(packageName: string, version: string): { success: boolean; error?: string } {
98
96
  try {
99
- const { sanitizePackageName } = require('./validation.js');
100
97
  const validation = sanitizePackageName(packageName);
101
98
 
102
99
  if (!validation.valid || !validation.sanitized) {