swarmlancer-cli 0.5.0 → 0.5.2
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/app.js +84 -102
- package/dist/screens/agent-config.d.ts +2 -2
- package/dist/screens/agent-config.js +21 -7
- package/dist/screens/banner.js +8 -8
- package/dist/screens/dashboard.d.ts +3 -3
- package/dist/screens/dashboard.js +9 -5
- package/dist/screens/menu-screen.d.ts +4 -3
- package/dist/screens/menu-screen.js +14 -11
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -19,7 +19,7 @@ import { MessageScreen } from "./screens/message.js";
|
|
|
19
19
|
import { screenProfiles } from "./screening.js";
|
|
20
20
|
let terminal;
|
|
21
21
|
let tui;
|
|
22
|
-
let runningAgentId;
|
|
22
|
+
let runningAgentId;
|
|
23
23
|
function setScreen(component) {
|
|
24
24
|
tui.clear();
|
|
25
25
|
tui.addChild(component);
|
|
@@ -48,7 +48,6 @@ function confirm(question) {
|
|
|
48
48
|
// ── Silent setup ──────────────────────────────────────────
|
|
49
49
|
async function silentSetup() {
|
|
50
50
|
migrateLegacyAgent();
|
|
51
|
-
// Auto-login if not logged in
|
|
52
51
|
const config = getConfig();
|
|
53
52
|
if (!config.token) {
|
|
54
53
|
try {
|
|
@@ -69,12 +68,7 @@ function askName(title, subtitle, current = "") {
|
|
|
69
68
|
function editInstructions(agent) {
|
|
70
69
|
return new Promise((resolve) => {
|
|
71
70
|
const screen = new AgentEditorScreen(tui, agent.instructions);
|
|
72
|
-
screen.onSave = async (
|
|
73
|
-
agent.instructions = newContent;
|
|
74
|
-
saveAgent(agent);
|
|
75
|
-
await showMessage("Saved", ["Agent instructions updated."], "success");
|
|
76
|
-
resolve();
|
|
77
|
-
};
|
|
71
|
+
screen.onSave = async (c) => { agent.instructions = c; saveAgent(agent); resolve(); };
|
|
78
72
|
screen.onCancel = () => resolve();
|
|
79
73
|
setScreen(screen);
|
|
80
74
|
});
|
|
@@ -82,12 +76,7 @@ function editInstructions(agent) {
|
|
|
82
76
|
function editLimits(agent) {
|
|
83
77
|
return new Promise((resolve) => {
|
|
84
78
|
const screen = new SettingsScreen(tui, agent.limits);
|
|
85
|
-
screen.onSave = async (
|
|
86
|
-
agent.limits = newLimits;
|
|
87
|
-
saveAgent(agent);
|
|
88
|
-
await showMessage("Saved", ["Agent limits updated."], "success");
|
|
89
|
-
resolve();
|
|
90
|
-
};
|
|
79
|
+
screen.onSave = async (l) => { agent.limits = l; saveAgent(agent); resolve(); };
|
|
91
80
|
screen.onCancel = () => resolve();
|
|
92
81
|
setScreen(screen);
|
|
93
82
|
});
|
|
@@ -95,12 +84,7 @@ function editLimits(agent) {
|
|
|
95
84
|
function editDiscovery(agent) {
|
|
96
85
|
return new Promise((resolve) => {
|
|
97
86
|
const screen = new DiscoverySettingsScreen(tui, agent.discovery);
|
|
98
|
-
screen.onSave = async (
|
|
99
|
-
agent.discovery = newSettings;
|
|
100
|
-
saveAgent(agent);
|
|
101
|
-
await showMessage("Saved", ["Discovery settings updated."], "success");
|
|
102
|
-
resolve();
|
|
103
|
-
};
|
|
87
|
+
screen.onSave = async (s) => { agent.discovery = s; saveAgent(agent); resolve(); };
|
|
104
88
|
screen.onCancel = () => resolve();
|
|
105
89
|
setScreen(screen);
|
|
106
90
|
});
|
|
@@ -109,15 +93,14 @@ function editModel(agent) {
|
|
|
109
93
|
return new Promise(async (resolve) => {
|
|
110
94
|
const models = getAvailableModels();
|
|
111
95
|
if (models.length === 0) {
|
|
112
|
-
await showMessage("No models", ["No provider API keys configured.", "Add one in
|
|
96
|
+
await showMessage("No models", ["No provider API keys configured.", "Add one in Manage → Providers."], "error");
|
|
113
97
|
resolve();
|
|
114
98
|
return;
|
|
115
99
|
}
|
|
116
100
|
const screen = new ModelPickerScreen(tui, models);
|
|
117
|
-
screen.onSelect = async (
|
|
118
|
-
agent.modelPattern = `${
|
|
101
|
+
screen.onSelect = async (m) => {
|
|
102
|
+
agent.modelPattern = `${m.provider}/${m.id}`;
|
|
119
103
|
saveAgent(agent);
|
|
120
|
-
await showMessage("Model selected", [`${model.provider}/${model.id}`], "success");
|
|
121
104
|
resolve();
|
|
122
105
|
};
|
|
123
106
|
screen.onCancel = () => resolve();
|
|
@@ -132,27 +115,29 @@ function pickAgent() {
|
|
|
132
115
|
return Promise.resolve(agents[0]);
|
|
133
116
|
return new Promise((resolve) => {
|
|
134
117
|
const screen = new AgentPickerScreen(tui, agents);
|
|
135
|
-
screen.onSelect = (
|
|
118
|
+
screen.onSelect = (a) => resolve(a);
|
|
136
119
|
screen.onCancel = () => resolve(null);
|
|
137
120
|
setScreen(screen);
|
|
138
121
|
});
|
|
139
122
|
}
|
|
140
123
|
// ── Agent config ──────────────────────────────────────────
|
|
141
124
|
async function runAgentConfig(agentId) {
|
|
125
|
+
let idx = 0;
|
|
142
126
|
while (true) {
|
|
143
127
|
const agent = getAgent(agentId);
|
|
144
128
|
if (!agent)
|
|
145
129
|
return;
|
|
146
130
|
const action = await new Promise((resolve) => {
|
|
147
|
-
const screen = new AgentConfigScreen(tui, agent);
|
|
148
|
-
screen.onAction = resolve;
|
|
131
|
+
const screen = new AgentConfigScreen(tui, agent, idx);
|
|
132
|
+
screen.onAction = (a, i) => resolve({ action: a, index: i });
|
|
149
133
|
setScreen(screen);
|
|
150
134
|
});
|
|
151
|
-
|
|
135
|
+
idx = action.index >= 0 ? action.index : idx;
|
|
136
|
+
switch (action.action) {
|
|
152
137
|
case "edit-name": {
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
agent.name =
|
|
138
|
+
const n = await askName("Rename Agent", "Enter a new name.", agent.name);
|
|
139
|
+
if (n) {
|
|
140
|
+
agent.name = n;
|
|
156
141
|
saveAgent(agent);
|
|
157
142
|
}
|
|
158
143
|
break;
|
|
@@ -181,33 +166,9 @@ async function runAgentConfig(agentId) {
|
|
|
181
166
|
}
|
|
182
167
|
}
|
|
183
168
|
}
|
|
184
|
-
// ── Swarm
|
|
169
|
+
// ── Manage → Swarm (agents) ──────────────────────────────
|
|
185
170
|
async function runSwarm() {
|
|
186
|
-
|
|
187
|
-
const action = await new Promise((resolve) => {
|
|
188
|
-
const items = [
|
|
189
|
-
{ value: "manage", label: "Manage", description: "" },
|
|
190
|
-
{ value: "providers", label: "Providers", description: "" },
|
|
191
|
-
];
|
|
192
|
-
const screen = new MenuScreen(tui, "Swarm", items);
|
|
193
|
-
screen.onSelect = (v) => resolve(v);
|
|
194
|
-
screen.onBack = () => resolve(null);
|
|
195
|
-
setScreen(screen);
|
|
196
|
-
});
|
|
197
|
-
if (!action)
|
|
198
|
-
return;
|
|
199
|
-
switch (action) {
|
|
200
|
-
case "manage":
|
|
201
|
-
await runManage();
|
|
202
|
-
break;
|
|
203
|
-
case "providers":
|
|
204
|
-
await runProviders();
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// ── Manage screen ─────────────────────────────────────────
|
|
210
|
-
async function runManage() {
|
|
171
|
+
let idx = 0;
|
|
211
172
|
while (true) {
|
|
212
173
|
const agents = getAgents();
|
|
213
174
|
const items = [
|
|
@@ -216,54 +177,49 @@ async function runManage() {
|
|
|
216
177
|
for (const agent of agents) {
|
|
217
178
|
const isRunning = agent.id === runningAgentId;
|
|
218
179
|
const suffix = isRunning ? " [⚡]" : "";
|
|
219
|
-
items.push({
|
|
220
|
-
value: agent.id,
|
|
221
|
-
label: `${agent.name}${suffix}`,
|
|
222
|
-
description: "",
|
|
223
|
-
});
|
|
180
|
+
items.push({ value: agent.id, label: `${agent.name}${suffix}`, description: "" });
|
|
224
181
|
}
|
|
225
|
-
const
|
|
226
|
-
const screen = new MenuScreen(tui, "
|
|
227
|
-
screen.onSelect = (v) => resolve(v);
|
|
228
|
-
screen.onBack = () => resolve(null);
|
|
182
|
+
const result = await new Promise((resolve) => {
|
|
183
|
+
const screen = new MenuScreen(tui, "Swarm", items, idx);
|
|
184
|
+
screen.onSelect = (v, i) => resolve({ value: v, index: i });
|
|
185
|
+
screen.onBack = () => resolve({ value: null, index: idx });
|
|
229
186
|
setScreen(screen);
|
|
230
187
|
});
|
|
231
|
-
if (!
|
|
188
|
+
if (!result.value)
|
|
232
189
|
return;
|
|
233
|
-
|
|
190
|
+
idx = result.index;
|
|
191
|
+
if (result.value === "__create__") {
|
|
234
192
|
const name = await askName("New Agent", "Give your agent a name.");
|
|
235
193
|
if (name) {
|
|
236
|
-
const
|
|
237
|
-
await runAgentConfig(
|
|
194
|
+
const a = createAgent(name);
|
|
195
|
+
await runAgentConfig(a.id);
|
|
238
196
|
}
|
|
239
197
|
}
|
|
240
198
|
else {
|
|
241
|
-
await runAgentConfig(
|
|
199
|
+
await runAgentConfig(result.value);
|
|
242
200
|
}
|
|
243
201
|
}
|
|
244
202
|
}
|
|
245
|
-
// ── Providers
|
|
203
|
+
// ── Manage → Providers ────────────────────────────────────
|
|
246
204
|
async function runProviders() {
|
|
205
|
+
let idx = 0;
|
|
247
206
|
while (true) {
|
|
248
207
|
const items = PROVIDERS.map((p) => {
|
|
249
208
|
const loggedIn = isProviderAuthenticated(p.id);
|
|
250
209
|
const prefix = loggedIn ? "→ " : " ";
|
|
251
210
|
const suffix = loggedIn ? " ✓ logged in" : "";
|
|
252
|
-
return {
|
|
253
|
-
value: p.id,
|
|
254
|
-
label: `${prefix}${p.label}${suffix}`,
|
|
255
|
-
description: "",
|
|
256
|
-
};
|
|
211
|
+
return { value: p.id, label: `${prefix}${p.label}${suffix}`, description: "" };
|
|
257
212
|
});
|
|
258
|
-
const
|
|
259
|
-
const screen = new MenuScreen(tui, "Providers", items);
|
|
260
|
-
screen.onSelect = (v) => resolve(v);
|
|
261
|
-
screen.onBack = () => resolve(null);
|
|
213
|
+
const result = await new Promise((resolve) => {
|
|
214
|
+
const screen = new MenuScreen(tui, "Providers", items, idx);
|
|
215
|
+
screen.onSelect = (v, i) => resolve({ value: v, index: i });
|
|
216
|
+
screen.onBack = () => resolve({ value: null, index: idx });
|
|
262
217
|
setScreen(screen);
|
|
263
218
|
});
|
|
264
|
-
if (!
|
|
219
|
+
if (!result.value)
|
|
265
220
|
return;
|
|
266
|
-
|
|
221
|
+
idx = result.index;
|
|
222
|
+
const provider = PROVIDERS.find((p) => p.id === result.value);
|
|
267
223
|
if (!provider)
|
|
268
224
|
continue;
|
|
269
225
|
if (!provider.keyBased) {
|
|
@@ -277,6 +233,33 @@ async function runProviders() {
|
|
|
277
233
|
}
|
|
278
234
|
}
|
|
279
235
|
}
|
|
236
|
+
// ── Manage (top) ──────────────────────────────────────────
|
|
237
|
+
async function runManageTop() {
|
|
238
|
+
let idx = 0;
|
|
239
|
+
while (true) {
|
|
240
|
+
const result = await new Promise((resolve) => {
|
|
241
|
+
const items = [
|
|
242
|
+
{ value: "swarm", label: "Swarm", description: "" },
|
|
243
|
+
{ value: "providers", label: "Providers", description: "" },
|
|
244
|
+
];
|
|
245
|
+
const screen = new MenuScreen(tui, "Manage", items, idx);
|
|
246
|
+
screen.onSelect = (v, i) => resolve({ value: v, index: i });
|
|
247
|
+
screen.onBack = () => resolve({ value: null, index: idx });
|
|
248
|
+
setScreen(screen);
|
|
249
|
+
});
|
|
250
|
+
if (!result.value)
|
|
251
|
+
return;
|
|
252
|
+
idx = result.index;
|
|
253
|
+
switch (result.value) {
|
|
254
|
+
case "swarm":
|
|
255
|
+
await runSwarm();
|
|
256
|
+
break;
|
|
257
|
+
case "providers":
|
|
258
|
+
await runProviders();
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
280
263
|
// ── Go online ─────────────────────────────────────────────
|
|
281
264
|
async function fetchCandidates(discovery) {
|
|
282
265
|
const config = getConfig();
|
|
@@ -309,7 +292,7 @@ async function goOnline() {
|
|
|
309
292
|
return;
|
|
310
293
|
const resolved = resolveModel(agent.modelPattern);
|
|
311
294
|
if (!resolved) {
|
|
312
|
-
await showMessage("No model", ["No provider API keys configured.", "Add one in
|
|
295
|
+
await showMessage("No model", ["No provider API keys configured.", "Add one in Manage → Providers."], "error");
|
|
313
296
|
return;
|
|
314
297
|
}
|
|
315
298
|
setActiveModel(resolved.provider, resolved.model);
|
|
@@ -317,19 +300,16 @@ async function goOnline() {
|
|
|
317
300
|
runningAgentId = agent.id;
|
|
318
301
|
const modelInfo = { provider: resolved.provider, id: resolved.model, name: resolved.model };
|
|
319
302
|
const config = getConfig();
|
|
320
|
-
const discovery = agent.discovery;
|
|
321
|
-
const limits = agent.limits;
|
|
322
303
|
const screen = new AgentRunningScreen(tui, modelInfo, config.serverUrl, agent.name);
|
|
323
304
|
const done = new Promise((resolve) => {
|
|
324
305
|
screen.onStop = () => { stopAgent(); runningAgentId = undefined; resolve(); };
|
|
325
306
|
});
|
|
326
307
|
setScreen(screen);
|
|
327
308
|
const log = (line) => screen.addLog(line);
|
|
328
|
-
startAgent(limits, agent.id, agent.name, log);
|
|
329
|
-
// Discovery
|
|
309
|
+
startAgent(agent.limits, agent.id, agent.name, log);
|
|
330
310
|
screen.setStatus("discovering candidates...");
|
|
331
311
|
try {
|
|
332
|
-
const candidates = await fetchCandidates(discovery);
|
|
312
|
+
const candidates = await fetchCandidates(agent.discovery);
|
|
333
313
|
if (candidates.length === 0) {
|
|
334
314
|
log(colors.gray("No candidates match your discovery filters."));
|
|
335
315
|
screen.setStatus("waiting for conversations...");
|
|
@@ -337,14 +317,14 @@ async function goOnline() {
|
|
|
337
317
|
else {
|
|
338
318
|
log(colors.lime(`📡 Found ${candidates.length} candidates, screening...`));
|
|
339
319
|
screen.setStatus(`screening ${candidates.length} profiles...`);
|
|
340
|
-
const results = await screenProfiles(candidates, agent.instructions, "", discovery.matchThreshold, discovery.maxScreenPerSession, (screened, total, result) => {
|
|
341
|
-
const icon = result.score >= discovery.matchThreshold ? colors.lime("✓") : colors.gray("⊘");
|
|
342
|
-
const scoreStr = result.score >= discovery.matchThreshold
|
|
320
|
+
const results = await screenProfiles(candidates, agent.instructions, "", agent.discovery.matchThreshold, agent.discovery.maxScreenPerSession, (screened, total, result) => {
|
|
321
|
+
const icon = result.score >= agent.discovery.matchThreshold ? colors.lime("✓") : colors.gray("⊘");
|
|
322
|
+
const scoreStr = result.score >= agent.discovery.matchThreshold
|
|
343
323
|
? colors.lime(`${result.score}/10`) : colors.gray(`${result.score}/10`);
|
|
344
324
|
log(` ${icon} @${result.profile.githubUsername} ${scoreStr} — ${colors.gray(result.reason)}`);
|
|
345
325
|
screen.setStatus(`screening ${screened}/${total}...`);
|
|
346
326
|
});
|
|
347
|
-
const matches = results.filter((r) => r.score >= discovery.matchThreshold);
|
|
327
|
+
const matches = results.filter((r) => r.score >= agent.discovery.matchThreshold);
|
|
348
328
|
log("");
|
|
349
329
|
if (matches.length === 0) {
|
|
350
330
|
log(colors.lime("No strong matches. Waiting for incoming conversations..."));
|
|
@@ -358,8 +338,8 @@ async function goOnline() {
|
|
|
358
338
|
}
|
|
359
339
|
log(colors.lime(` 🤝 @${match.profile.githubUsername} (${match.score}/10)`));
|
|
360
340
|
sendToServer({ type: "start_conversation", withUserId: match.profile.id });
|
|
361
|
-
if (limits.cooldownSeconds > 0)
|
|
362
|
-
await new Promise((r) => setTimeout(r, limits.cooldownSeconds * 1000));
|
|
341
|
+
if (agent.limits.cooldownSeconds > 0)
|
|
342
|
+
await new Promise((r) => setTimeout(r, agent.limits.cooldownSeconds * 1000));
|
|
363
343
|
}
|
|
364
344
|
}
|
|
365
345
|
screen.setStatus(`${matches.length} matches • waiting for conversations...`);
|
|
@@ -373,21 +353,23 @@ async function goOnline() {
|
|
|
373
353
|
}
|
|
374
354
|
// ── Dashboard loop ────────────────────────────────────────
|
|
375
355
|
async function runDashboard() {
|
|
356
|
+
let idx = 0;
|
|
376
357
|
while (true) {
|
|
377
|
-
const
|
|
378
|
-
const dashboard = new DashboardScreen(tui);
|
|
379
|
-
dashboard.onAction = resolve;
|
|
358
|
+
const result = await new Promise((resolve) => {
|
|
359
|
+
const dashboard = new DashboardScreen(tui, idx);
|
|
360
|
+
dashboard.onAction = (a, i) => resolve({ action: a, index: i });
|
|
380
361
|
setScreen(dashboard);
|
|
381
362
|
});
|
|
382
|
-
|
|
383
|
-
|
|
363
|
+
idx = result.index;
|
|
364
|
+
switch (result.action) {
|
|
365
|
+
case "start": {
|
|
384
366
|
const sure = await confirm("Go online?");
|
|
385
367
|
if (sure)
|
|
386
368
|
await goOnline();
|
|
387
369
|
break;
|
|
388
370
|
}
|
|
389
|
-
case "
|
|
390
|
-
await
|
|
371
|
+
case "manage":
|
|
372
|
+
await runManageTop();
|
|
391
373
|
break;
|
|
392
374
|
case "exit": {
|
|
393
375
|
const sure = await confirm("Exit?");
|
|
@@ -6,8 +6,8 @@ export declare class AgentConfigScreen implements Component {
|
|
|
6
6
|
private container;
|
|
7
7
|
private selectList;
|
|
8
8
|
private tui;
|
|
9
|
-
onAction?: (action: AgentConfigAction) => void;
|
|
10
|
-
constructor(tui: TUI, agent: AgentProfile);
|
|
9
|
+
onAction?: (action: AgentConfigAction, index: number) => void;
|
|
10
|
+
constructor(tui: TUI, agent: AgentProfile, initialIndex?: number);
|
|
11
11
|
handleInput(data: string): void;
|
|
12
12
|
render(width: number): string[];
|
|
13
13
|
invalidate(): void;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { Container, Text, Spacer, SelectList, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
1
|
+
import { Container, Text, Spacer, SelectList, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
2
|
import { colors, theme } from "../theme.js";
|
|
3
|
+
import { BannerComponent } from "./banner.js";
|
|
3
4
|
export class AgentConfigScreen {
|
|
4
5
|
container;
|
|
5
6
|
selectList;
|
|
6
7
|
tui;
|
|
7
8
|
onAction;
|
|
8
|
-
constructor(tui, agent) {
|
|
9
|
+
constructor(tui, agent, initialIndex = 0) {
|
|
9
10
|
this.tui = tui;
|
|
10
11
|
this.container = new Container();
|
|
11
|
-
|
|
12
|
-
this.container.addChild(new
|
|
12
|
+
// Banner
|
|
13
|
+
this.container.addChild(new BannerComponent());
|
|
13
14
|
this.container.addChild(new Text(theme.title(` Agent: ${agent.name}`), 1, 0));
|
|
14
15
|
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
15
16
|
this.container.addChild(new Spacer(1));
|
|
@@ -42,15 +43,28 @@ export class AgentConfigScreen {
|
|
|
42
43
|
scrollInfo: (t) => colors.gray(t),
|
|
43
44
|
noMatch: (t) => colors.gray(t),
|
|
44
45
|
});
|
|
46
|
+
if (initialIndex > 0) {
|
|
47
|
+
this.selectList.setSelectedIndex(initialIndex);
|
|
48
|
+
}
|
|
45
49
|
this.selectList.onSelect = (item) => {
|
|
46
|
-
|
|
50
|
+
const idx = items.findIndex((i) => i.value === item.value);
|
|
51
|
+
this.onAction?.(item.value, idx);
|
|
47
52
|
};
|
|
48
|
-
this.selectList.onCancel = () => this.onAction?.("back");
|
|
53
|
+
this.selectList.onCancel = () => this.onAction?.("back", -1);
|
|
49
54
|
this.container.addChild(this.selectList);
|
|
50
55
|
this.container.addChild(new Spacer(1));
|
|
51
|
-
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • enter select • esc back"), 1, 0));
|
|
56
|
+
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • →/enter select • ←/esc back"), 1, 0));
|
|
52
57
|
}
|
|
53
58
|
handleInput(data) {
|
|
59
|
+
if (matchesKey(data, Key.left)) {
|
|
60
|
+
this.onAction?.("back", -1);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (matchesKey(data, Key.right)) {
|
|
64
|
+
this.selectList.handleInput("\r");
|
|
65
|
+
this.tui.requestRender();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
54
68
|
this.selectList.handleInput(data);
|
|
55
69
|
this.tui.requestRender();
|
|
56
70
|
}
|
package/dist/screens/banner.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Container, Text, Spacer } from "@mariozechner/pi-tui";
|
|
2
2
|
import { colors } from "../theme.js";
|
|
3
3
|
const BANNER_ART = [
|
|
4
|
-
`
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
`
|
|
8
|
-
`
|
|
9
|
-
|
|
10
|
-
`
|
|
4
|
+
` █████████ ███`,
|
|
5
|
+
`███ ███ ███`,
|
|
6
|
+
`███ ███`,
|
|
7
|
+
` █████████ ███`,
|
|
8
|
+
` ███ ███`,
|
|
9
|
+
`███ ███ ███`,
|
|
10
|
+
` █████████ ███`,
|
|
11
11
|
];
|
|
12
12
|
export class BannerComponent extends Container {
|
|
13
13
|
constructor() {
|
|
@@ -21,7 +21,7 @@ export class BannerComponent extends Container {
|
|
|
21
21
|
this.addChild(new Text(colors.limeBold(` ${line}`), 1, 0));
|
|
22
22
|
}
|
|
23
23
|
this.addChild(new Spacer(0));
|
|
24
|
-
this.addChild(new Text(colors.gray(" LET THE SWARM BEGIN!"), 1, 0));
|
|
24
|
+
this.addChild(new Text(colors.gray(" [LET THE SWARM BEGIN!]"), 1, 0));
|
|
25
25
|
this.addChild(new Spacer(1));
|
|
26
26
|
}
|
|
27
27
|
invalidate() {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type Component } from "@mariozechner/pi-tui";
|
|
2
2
|
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
-
export type DashboardAction = "
|
|
3
|
+
export type DashboardAction = "start" | "manage" | "exit";
|
|
4
4
|
export declare class DashboardScreen implements Component {
|
|
5
5
|
private container;
|
|
6
6
|
private selectList;
|
|
7
7
|
private tui;
|
|
8
|
-
onAction?: (action: DashboardAction) => void;
|
|
9
|
-
constructor(tui: TUI);
|
|
8
|
+
onAction?: (action: DashboardAction, index: number) => void;
|
|
9
|
+
constructor(tui: TUI, initialIndex?: number);
|
|
10
10
|
handleInput(data: string): void;
|
|
11
11
|
render(width: number): string[];
|
|
12
12
|
invalidate(): void;
|
|
@@ -2,8 +2,8 @@ import { Container, Text, Spacer, SelectList, matchesKey, Key, } from "@mariozec
|
|
|
2
2
|
import { colors, theme } from "../theme.js";
|
|
3
3
|
import { BannerComponent } from "./banner.js";
|
|
4
4
|
const MENU_ITEMS = [
|
|
5
|
-
{ value: "
|
|
6
|
-
{ value: "
|
|
5
|
+
{ value: "start", label: "Start", description: "" },
|
|
6
|
+
{ value: "manage", label: "Manage", description: "" },
|
|
7
7
|
{ value: "exit", label: "Exit", description: "" },
|
|
8
8
|
];
|
|
9
9
|
export class DashboardScreen {
|
|
@@ -11,7 +11,7 @@ export class DashboardScreen {
|
|
|
11
11
|
selectList;
|
|
12
12
|
tui;
|
|
13
13
|
onAction;
|
|
14
|
-
constructor(tui) {
|
|
14
|
+
constructor(tui, initialIndex = 0) {
|
|
15
15
|
this.tui = tui;
|
|
16
16
|
this.container = new Container();
|
|
17
17
|
this.container.addChild(new BannerComponent());
|
|
@@ -22,8 +22,12 @@ export class DashboardScreen {
|
|
|
22
22
|
scrollInfo: (t) => colors.gray(t),
|
|
23
23
|
noMatch: (t) => colors.gray(t),
|
|
24
24
|
});
|
|
25
|
+
if (initialIndex > 0) {
|
|
26
|
+
this.selectList.setSelectedIndex(initialIndex);
|
|
27
|
+
}
|
|
25
28
|
this.selectList.onSelect = (item) => {
|
|
26
|
-
|
|
29
|
+
const idx = MENU_ITEMS.findIndex((i) => i.value === item.value);
|
|
30
|
+
this.onAction?.(item.value, idx);
|
|
27
31
|
};
|
|
28
32
|
this.container.addChild(this.selectList);
|
|
29
33
|
this.container.addChild(new Spacer(1));
|
|
@@ -31,7 +35,7 @@ export class DashboardScreen {
|
|
|
31
35
|
}
|
|
32
36
|
handleInput(data) {
|
|
33
37
|
if (matchesKey(data, "q")) {
|
|
34
|
-
this.onAction?.("exit");
|
|
38
|
+
this.onAction?.("exit", 2);
|
|
35
39
|
return;
|
|
36
40
|
}
|
|
37
41
|
if (matchesKey(data, Key.right)) {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { type SelectItem, type Component } from "@mariozechner/pi-tui";
|
|
2
2
|
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
3
|
/**
|
|
4
|
-
* Reusable menu screen with title, items, and left/right/enter/esc navigation.
|
|
4
|
+
* Reusable menu screen with banner, title, items, and left/right/enter/esc navigation.
|
|
5
5
|
* Enter or Right = select, Left or Esc = back.
|
|
6
6
|
*/
|
|
7
7
|
export declare class MenuScreen implements Component {
|
|
8
8
|
private container;
|
|
9
9
|
private selectList;
|
|
10
10
|
private tui;
|
|
11
|
-
|
|
11
|
+
private items;
|
|
12
|
+
onSelect?: (value: string, index: number) => void;
|
|
12
13
|
onBack?: () => void;
|
|
13
|
-
constructor(tui: TUI, title: string, items: SelectItem[],
|
|
14
|
+
constructor(tui: TUI, title: string, items: SelectItem[], initialIndex?: number);
|
|
14
15
|
handleInput(data: string): void;
|
|
15
16
|
render(width: number): string[];
|
|
16
17
|
invalidate(): void;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { Container, Text, Spacer, SelectList, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
2
|
import { colors, theme } from "../theme.js";
|
|
3
|
+
import { BannerComponent } from "./banner.js";
|
|
3
4
|
/**
|
|
4
|
-
* Reusable menu screen with title, items, and left/right/enter/esc navigation.
|
|
5
|
+
* Reusable menu screen with banner, title, items, and left/right/enter/esc navigation.
|
|
5
6
|
* Enter or Right = select, Left or Esc = back.
|
|
6
7
|
*/
|
|
7
8
|
export class MenuScreen {
|
|
8
9
|
container;
|
|
9
10
|
selectList;
|
|
10
11
|
tui;
|
|
12
|
+
items;
|
|
11
13
|
onSelect;
|
|
12
14
|
onBack;
|
|
13
|
-
constructor(tui, title, items,
|
|
15
|
+
constructor(tui, title, items, initialIndex = 0) {
|
|
14
16
|
this.tui = tui;
|
|
17
|
+
this.items = items;
|
|
15
18
|
this.container = new Container();
|
|
16
|
-
|
|
17
|
-
this.container.addChild(new
|
|
19
|
+
// Banner always shown
|
|
20
|
+
this.container.addChild(new BannerComponent());
|
|
18
21
|
this.container.addChild(new Text(theme.title(` ${title}`), 1, 0));
|
|
19
|
-
if (subtitle) {
|
|
20
|
-
this.container.addChild(new Text(colors.gray(` ${subtitle}`), 1, 0));
|
|
21
|
-
}
|
|
22
22
|
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
23
23
|
this.container.addChild(new Spacer(1));
|
|
24
24
|
this.selectList = new SelectList(items, Math.min(items.length, 15), {
|
|
@@ -28,21 +28,24 @@ export class MenuScreen {
|
|
|
28
28
|
scrollInfo: (t) => colors.gray(t),
|
|
29
29
|
noMatch: (t) => colors.gray(t),
|
|
30
30
|
});
|
|
31
|
-
|
|
31
|
+
if (initialIndex > 0) {
|
|
32
|
+
this.selectList.setSelectedIndex(initialIndex);
|
|
33
|
+
}
|
|
34
|
+
this.selectList.onSelect = (item) => {
|
|
35
|
+
const idx = this.items.findIndex((i) => i.value === item.value);
|
|
36
|
+
this.onSelect?.(item.value, idx);
|
|
37
|
+
};
|
|
32
38
|
this.selectList.onCancel = () => this.onBack?.();
|
|
33
39
|
this.container.addChild(this.selectList);
|
|
34
40
|
this.container.addChild(new Spacer(1));
|
|
35
41
|
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • →/enter select • ←/esc back"), 1, 0));
|
|
36
42
|
}
|
|
37
43
|
handleInput(data) {
|
|
38
|
-
// Right arrow = enter
|
|
39
44
|
if (matchesKey(data, Key.right)) {
|
|
40
|
-
// Simulate enter by triggering current selection
|
|
41
45
|
this.selectList.handleInput("\r");
|
|
42
46
|
this.tui.requestRender();
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
|
-
// Left arrow = back
|
|
46
49
|
if (matchesKey(data, Key.left)) {
|
|
47
50
|
this.onBack?.();
|
|
48
51
|
return;
|