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.
- package/dist/security/package-integrity.d.ts.map +1 -1
- package/dist/security/package-integrity.js +2 -4
- package/dist/security/package-integrity.js.map +1 -1
- package/mk3-tui/src/ui/agent_list.rs +155 -79
- package/mk3-tui/src/ui/help.rs +135 -238
- package/mk3-tui/src/ui/layout.rs +1 -1
- package/package.json +2 -2
- package/src/security/package-integrity.ts +2 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-integrity.d.ts","sourceRoot":"","sources":["../../src/security/package-integrity.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
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;
|
|
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;AAC1C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,OAAe;IACtE,IAAI,CAAC;QACH,kCAAkC;QAClC,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,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,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,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"}
|
|
@@ -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
|
|
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")
|
|
88
|
-
let
|
|
89
|
-
|
|
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(
|
|
94
|
-
Cell::from(
|
|
95
|
-
Cell::from(
|
|
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("
|
|
107
|
-
Cell::from("
|
|
108
|
-
Cell::from("
|
|
109
|
-
Cell::from("
|
|
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
|
|
121
|
-
Span::styled("
|
|
122
|
-
Span::styled("
|
|
123
|
-
Span::styled("
|
|
124
|
-
Span::styled("
|
|
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
|
-
//
|
|
138
|
-
|
|
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 (
|
|
150
|
-
let popup_width = (area.width *
|
|
151
|
-
let popup_height = (area.height *
|
|
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
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
//
|
|
175
|
-
let
|
|
176
|
-
.title(format!(" 📋 Agent
|
|
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
|
|
183
|
-
f.render_widget(
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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.
|
|
203
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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(
|
|
257
|
-
.
|
|
258
|
-
|
|
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
|
+
}
|
package/mk3-tui/src/ui/help.rs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/// Help popup screen
|
|
2
|
-
///
|
|
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
|
|
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 (
|
|
21
|
-
let popup_width = (area.width *
|
|
22
|
-
let popup_height = (area.height *
|
|
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
|
|
34
|
-
|
|
35
|
-
.style(Style::default().bg(Color::Black)
|
|
36
|
-
|
|
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
|
|
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),
|
|
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
|
-
|
|
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
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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("
|
|
98
|
-
Span::styled("
|
|
99
|
-
Span::styled("
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
.
|
|
118
|
-
|
|
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
|
}
|
package/mk3-tui/src/ui/layout.rs
CHANGED
|
@@ -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.
|
|
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.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.
|
|
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) {
|