swarmlancer-cli 0.2.0 → 0.3.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/agent.js CHANGED
@@ -21,7 +21,7 @@ function resetIdleTimer(limits, log) {
21
21
  if (idleTimer)
22
22
  clearTimeout(idleTimer);
23
23
  idleTimer = setTimeout(() => {
24
- log(colors.yellow(`⏱ Auto-stopping: idle for ${limits.autoStopIdleMinutes} minutes`));
24
+ log(colors.lime(`⏱ Auto-stopping: idle for ${limits.autoStopIdleMinutes} minutes`));
25
25
  stopAgent();
26
26
  }, limits.autoStopIdleMinutes * 60 * 1000);
27
27
  }
@@ -35,7 +35,7 @@ function resetIdleTimer(limits, log) {
35
35
  export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = console.log) {
36
36
  const config = getConfig();
37
37
  if (!config.token) {
38
- log(colors.red("Not logged in. Run: swarmlancer login"));
38
+ log(colors.lime("Not logged in. Run: swarmlancer login"));
39
39
  return;
40
40
  }
41
41
  resetCounters();
@@ -44,7 +44,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
44
44
  // Log limits
45
45
  log(colors.gray(`Limits: ${limits.maxConcurrentConversations} concurrent, ${limits.maxMessagesPerConversation} msgs/convo, ${limits.cooldownSeconds}s cooldown, ${limits.maxConversationsPerSession} max/session, ${limits.autoStopIdleMinutes}m idle`));
46
46
  function connect() {
47
- log(colors.cyan(`Connecting to ${wsUrl}...`));
47
+ log(colors.lime(`Connecting to ${wsUrl}...`));
48
48
  const ws = new WebSocket(wsUrl);
49
49
  activeWs = ws;
50
50
  // Start idle timer
@@ -56,7 +56,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
56
56
  const msg = JSON.parse(raw.toString());
57
57
  switch (msg.type) {
58
58
  case "authenticated":
59
- log(colors.green("Agent online"));
59
+ log(colors.lime("Agent online"));
60
60
  log("");
61
61
  log(colors.gray("Waiting for conversations..."));
62
62
  ws.send(JSON.stringify({ type: "get_online_users" }));
@@ -64,7 +64,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
64
64
  case "online_users": {
65
65
  const users = msg.users;
66
66
  if (users.length > 0) {
67
- log(colors.cyan(`📡 Online: ${users.map((u) => `${u.displayName} (@${u.githubUsername})`).join(", ")}`));
67
+ log(colors.lime(`📡 Online: ${users.map((u) => `${u.displayName} (@${u.githubUsername})`).join(", ")}`));
68
68
  }
69
69
  else {
70
70
  log(colors.gray("No other agents online right now."));
@@ -78,7 +78,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
78
78
  // Check concurrent conversation limit
79
79
  if (!activeConversations.has(conversationId) &&
80
80
  activeConversations.size >= limits.maxConcurrentConversations) {
81
- log(colors.yellow(`⚠ Skipping: ${limits.maxConcurrentConversations} concurrent conversations active`));
81
+ log(colors.lime(`⚠ Skipping: ${limits.maxConcurrentConversations} concurrent conversations active`));
82
82
  ws.send(JSON.stringify({
83
83
  type: "inference_error",
84
84
  requestId,
@@ -89,7 +89,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
89
89
  // Check session total limit
90
90
  if (!activeConversations.has(conversationId) &&
91
91
  totalConversations >= limits.maxConversationsPerSession) {
92
- log(colors.yellow(`⚠ Session limit reached (${limits.maxConversationsPerSession} conversations). Stopping.`));
92
+ log(colors.lime(`⚠ Session limit reached (${limits.maxConversationsPerSession} conversations). Stopping.`));
93
93
  stopAgent();
94
94
  break;
95
95
  }
@@ -99,7 +99,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
99
99
  const elapsed = (Date.now() - lastConversationTime) / 1000;
100
100
  if (lastConversationTime > 0 && elapsed < limits.cooldownSeconds) {
101
101
  const wait = Math.ceil(limits.cooldownSeconds - elapsed);
102
- log(colors.yellow(`⚠ Cooldown: ${wait}s remaining`));
102
+ log(colors.lime(`⚠ Cooldown: ${wait}s remaining`));
103
103
  ws.send(JSON.stringify({
104
104
  type: "inference_error",
105
105
  requestId,
@@ -119,7 +119,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
119
119
  const msgCount = (conversationMessageCounts.get(conversationId) ?? 0) + 1;
120
120
  conversationMessageCounts.set(conversationId, msgCount);
121
121
  if (msgCount > limits.maxMessagesPerConversation) {
122
- log(colors.yellow(`⚠ Conversation ${conversationId.slice(0, 8)} hit ${limits.maxMessagesPerConversation} message limit`));
122
+ log(colors.lime(`⚠ Conversation ${conversationId.slice(0, 8)} hit ${limits.maxMessagesPerConversation} message limit`));
123
123
  activeConversations.delete(conversationId);
124
124
  ws.send(JSON.stringify({
125
125
  type: "inference_response",
@@ -128,7 +128,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
128
128
  }));
129
129
  break;
130
130
  }
131
- log(colors.yellow(`💬 [${conversationId.slice(0, 8)}] msg ${msgCount}/${limits.maxMessagesPerConversation} (${activeConversations.size}/${limits.maxConcurrentConversations} active, ${totalConversations}/${limits.maxConversationsPerSession} total)`));
131
+ log(colors.lime(`💬 [${conversationId.slice(0, 8)}] msg ${msgCount}/${limits.maxMessagesPerConversation} (${activeConversations.size}/${limits.maxConcurrentConversations} active, ${totalConversations}/${limits.maxConversationsPerSession} total)`));
132
132
  try {
133
133
  let response = await runInference(systemPrompt, messages);
134
134
  // Enforce response length limit
@@ -137,7 +137,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
137
137
  log(colors.gray(` (truncated to ${limits.maxResponseLength} chars)`));
138
138
  }
139
139
  const preview = response.length > 80 ? response.slice(0, 80) + "..." : response;
140
- log(colors.green(`✓ Response: ${preview}`));
140
+ log(colors.lime(`✓ Response: ${preview}`));
141
141
  ws.send(JSON.stringify({
142
142
  type: "inference_response",
143
143
  requestId,
@@ -146,7 +146,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
146
146
  }
147
147
  catch (err) {
148
148
  const errorMsg = err instanceof Error ? err.message : "Unknown error";
149
- log(colors.red(`✗ Inference failed: ${errorMsg}`));
149
+ log(colors.lime(`✗ Inference failed: ${errorMsg}`));
150
150
  ws.send(JSON.stringify({
151
151
  type: "inference_error",
152
152
  requestId,
@@ -157,7 +157,7 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
157
157
  }
158
158
  case "conversation_started": {
159
159
  const { conversationId, withUser } = msg;
160
- log(colors.brightGreen(`🤝 Conversation started with ${withUser.displayName} (@${withUser.githubUsername})`));
160
+ log(colors.lime(`🤝 Conversation started with ${withUser.displayName} (@${withUser.githubUsername})`));
161
161
  resetIdleTimer(limits, log);
162
162
  break;
163
163
  }
@@ -169,18 +169,18 @@ export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = co
169
169
  break;
170
170
  }
171
171
  case "error":
172
- log(colors.red(`Server error: ${msg.message}`));
172
+ log(colors.lime(`Server error: ${msg.message}`));
173
173
  break;
174
174
  }
175
175
  });
176
176
  ws.on("close", () => {
177
177
  if (alive) {
178
- log(colors.yellow("Disconnected. Reconnecting in 5s..."));
178
+ log(colors.lime("Disconnected. Reconnecting in 5s..."));
179
179
  reconnectTimer = setTimeout(connect, 5000);
180
180
  }
181
181
  });
182
182
  ws.on("error", (err) => {
183
- log(colors.red(`WebSocket error: ${err.message}`));
183
+ log(colors.lime(`WebSocket error: ${err.message}`));
184
184
  });
185
185
  }
186
186
  connect();
package/dist/app.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { ProcessTerminal, TUI, Container } from "@mariozechner/pi-tui";
2
2
  import { getConfig, getAgents, getAgent, saveAgent, deleteAgent, createAgent, migrateLegacyAgent, } from "./config.js";
3
3
  import { login } from "./login.js";
4
- import { initInference, getAvailableModels, setAgentInstructions } from "./inference.js";
4
+ import { initInference, getAvailableModels, setAgentInstructions, getAuthenticatedProviders } from "./inference.js";
5
5
  import { startAgent, stopAgent, sendToServer } from "./agent.js";
6
6
  import { colors } from "./theme.js";
7
7
  import { BannerComponent } from "./screens/banner.js";
8
8
  import { SetupWizardScreen } from "./screens/setup-wizard.js";
9
9
  import { DashboardScreen } from "./screens/dashboard.js";
10
- import { AgentListScreen } from "./screens/agent-list.js";
11
10
  import { AgentConfigScreen } from "./screens/agent-config.js";
12
11
  import { AgentPickerScreen } from "./screens/agent-picker.js";
13
12
  import { AgentEditorScreen } from "./screens/agent-editor.js";
@@ -112,9 +111,6 @@ async function runSetup() {
112
111
  await new Promise((r) => setTimeout(r, 800));
113
112
  }
114
113
  // ── Sub-screens that return promises ──────────────────────
115
- /**
116
- * Ask the user for a name via the NameEditorScreen.
117
- */
118
114
  function askName(title, subtitle, current = "") {
119
115
  return new Promise((resolve) => {
120
116
  const screen = new NameEditorScreen(tui, current, title, subtitle);
@@ -123,9 +119,6 @@ function askName(title, subtitle, current = "") {
123
119
  setScreen(screen);
124
120
  });
125
121
  }
126
- /**
127
- * Inline agent instructions editor.
128
- */
129
122
  function editInstructions(agent) {
130
123
  return new Promise((resolve) => {
131
124
  const screen = new AgentEditorScreen(tui, agent.instructions);
@@ -144,9 +137,6 @@ function editInstructions(agent) {
144
137
  setScreen(screen);
145
138
  });
146
139
  }
147
- /**
148
- * Agent limits settings screen.
149
- */
150
140
  function editLimits(agent) {
151
141
  return new Promise((resolve) => {
152
142
  const screen = new SettingsScreen(tui, agent.limits);
@@ -165,9 +155,6 @@ function editLimits(agent) {
165
155
  setScreen(screen);
166
156
  });
167
157
  }
168
- /**
169
- * Discovery settings screen.
170
- */
171
158
  function editDiscovery(agent) {
172
159
  return new Promise((resolve) => {
173
160
  const screen = new DiscoverySettingsScreen(tui, agent.discovery);
@@ -186,16 +173,13 @@ function editDiscovery(agent) {
186
173
  setScreen(screen);
187
174
  });
188
175
  }
189
- /**
190
- * Model picker screen — saves the model pattern to the agent.
191
- */
192
176
  function editModel(agent) {
193
177
  return new Promise(async (resolve) => {
194
178
  try {
195
179
  await initInference();
196
180
  const models = await getAvailableModels();
197
181
  if (models.length === 0) {
198
- await showMessage("No models", ["No models available.", "Run `pi` and /login to authenticate a provider."], "error");
182
+ await showMessage("No models", ["No models available.", "Select a provider to authenticate."], "error");
199
183
  resolve();
200
184
  return;
201
185
  }
@@ -215,9 +199,6 @@ function editModel(agent) {
215
199
  }
216
200
  });
217
201
  }
218
- /**
219
- * Ask for an optional session goal before starting.
220
- */
221
202
  function askSessionGoal() {
222
203
  return new Promise((resolve) => {
223
204
  const screen = new SessionGoalScreen(tui);
@@ -226,13 +207,10 @@ function askSessionGoal() {
226
207
  setScreen(screen);
227
208
  });
228
209
  }
229
- /**
230
- * Pick which agent to start (or auto-select if only one).
231
- */
232
210
  function pickAgent() {
233
211
  const agents = getAgents();
234
212
  if (agents.length === 0) {
235
- return showMessage("No agents", ["Create an agent first in Manage agents."], "error").then(() => null);
213
+ return showMessage("No agents", ["Create an agent first."], "error").then(() => null);
236
214
  }
237
215
  if (agents.length === 1) {
238
216
  return Promise.resolve(agents[0]);
@@ -249,7 +227,7 @@ async function runAgentConfig(agentId) {
249
227
  while (true) {
250
228
  const agent = getAgent(agentId);
251
229
  if (!agent)
252
- return; // deleted
230
+ return;
253
231
  const action = await new Promise((resolve) => {
254
232
  const screen = new AgentConfigScreen(tui, agent);
255
233
  screen.onAction = resolve;
@@ -286,37 +264,7 @@ async function runAgentConfig(agentId) {
286
264
  }
287
265
  }
288
266
  }
289
- // ── Agent list ────────────────────────────────────────────
290
- async function runAgentList() {
291
- while (true) {
292
- const agents = getAgents();
293
- const action = await new Promise((resolve) => {
294
- const screen = new AgentListScreen(tui, agents);
295
- screen.onAction = resolve;
296
- setScreen(screen);
297
- });
298
- switch (action.type) {
299
- case "create": {
300
- const name = await askName("New Agent", "Give your agent a name.");
301
- if (name) {
302
- const agent = createAgent(name);
303
- // Go straight into editing the new agent
304
- await runAgentConfig(agent.id);
305
- }
306
- break;
307
- }
308
- case "select":
309
- await runAgentConfig(action.agent.id);
310
- break;
311
- case "back":
312
- return;
313
- }
314
- }
315
- }
316
267
  // ── Running the agent ─────────────────────────────────────
317
- /**
318
- * Fetch candidate profiles from the server's /api/discover endpoint.
319
- */
320
268
  async function fetchCandidates(discovery) {
321
269
  const config = getConfig();
322
270
  const params = new URLSearchParams();
@@ -341,15 +289,10 @@ async function fetchCandidates(discovery) {
341
289
  const data = (await res.json());
342
290
  return data.profiles;
343
291
  }
344
- /**
345
- * Start the agent: pick agent → session goal → discover → screen → connect → run.
346
- */
347
292
  async function runAgentSession() {
348
- // 1. Pick agent
349
293
  const agent = await pickAgent();
350
294
  if (!agent)
351
295
  return;
352
- // 2. Init model for this agent
353
296
  let activeModel;
354
297
  try {
355
298
  const { model } = await initInference(agent.modelPattern);
@@ -359,14 +302,11 @@ async function runAgentSession() {
359
302
  await showMessage("Model error", [err instanceof Error ? err.message : "Failed to initialize model"], "error");
360
303
  return;
361
304
  }
362
- // 3. Set agent instructions for inference
363
305
  setAgentInstructions(agent.instructions);
364
- // 4. Ask for session goal
365
306
  const sessionGoal = await askSessionGoal();
366
307
  const config = getConfig();
367
308
  const discovery = agent.discovery;
368
309
  const limits = agent.limits;
369
- // 5. Show the running screen
370
310
  const screen = new AgentRunningScreen(tui, activeModel, config.serverUrl, agent.name, sessionGoal);
371
311
  const done = new Promise((resolve) => {
372
312
  screen.onStop = () => {
@@ -376,9 +316,7 @@ async function runAgentSession() {
376
316
  });
377
317
  setScreen(screen);
378
318
  const log = (line) => screen.addLog(line);
379
- // 6. Start the WebSocket agent (for incoming requests) with per-agent limits
380
319
  startAgent(limits, agent.id, agent.name, log);
381
- // 7. Discovery + screening (outbound)
382
320
  screen.setStatus("discovering candidates...");
383
321
  try {
384
322
  const candidates = await fetchCandidates(discovery);
@@ -388,15 +326,15 @@ async function runAgentSession() {
388
326
  screen.setStatus("waiting for conversations...");
389
327
  }
390
328
  else {
391
- log(colors.cyan(`📡 Found ${candidates.length} candidates, screening...`));
329
+ log(colors.lime(`📡 Found ${candidates.length} candidates, screening...`));
392
330
  screen.setStatus(`screening ${candidates.length} profiles...`);
393
331
  const results = await screenProfiles(candidates, agent.instructions, sessionGoal, discovery.matchThreshold, discovery.maxScreenPerSession, (screened, total, result) => {
394
332
  const icon = result.score >= discovery.matchThreshold
395
- ? colors.green("✓")
333
+ ? colors.lime("✓")
396
334
  : colors.gray("⊘");
397
335
  const name = `@${result.profile.githubUsername}`;
398
336
  const scoreStr = result.score >= discovery.matchThreshold
399
- ? colors.green(`${result.score}/10`)
337
+ ? colors.lime(`${result.score}/10`)
400
338
  : colors.gray(`${result.score}/10`);
401
339
  log(` ${icon} ${name} ${scoreStr} — ${colors.gray(result.reason)}`);
402
340
  screen.setStatus(`screening ${screened}/${total}...`);
@@ -404,24 +342,21 @@ async function runAgentSession() {
404
342
  const matches = results.filter((r) => r.score >= discovery.matchThreshold);
405
343
  log("");
406
344
  if (matches.length === 0) {
407
- log(colors.yellow("No strong matches found. Waiting for incoming conversations..."));
345
+ log(colors.lime("No strong matches found. Waiting for incoming conversations..."));
408
346
  }
409
347
  else {
410
- log(colors.green(`Found ${matches.length} match${matches.length === 1 ? "" : "es"}. Starting conversations...`));
348
+ log(colors.lime(`Found ${matches.length} match${matches.length === 1 ? "" : "es"}. Starting conversations...`));
411
349
  log("");
412
- // Initiate conversations with matches
413
350
  for (const match of matches) {
414
351
  if (!match.profile.online) {
415
352
  log(colors.gray(` ⊘ @${match.profile.githubUsername} is offline, skipping`));
416
353
  continue;
417
354
  }
418
- log(colors.cyan(` 🤝 Reaching out to @${match.profile.githubUsername} (${match.score}/10: ${match.reason})`));
419
- // Send start_conversation via WebSocket
355
+ log(colors.lime(` 🤝 Reaching out to @${match.profile.githubUsername} (${match.score}/10: ${match.reason})`));
420
356
  sendToServer({
421
357
  type: "start_conversation",
422
358
  withUserId: match.profile.id,
423
359
  });
424
- // Respect cooldown between outreach
425
360
  if (limits.cooldownSeconds > 0) {
426
361
  await new Promise((r) => setTimeout(r, limits.cooldownSeconds * 1000));
427
362
  }
@@ -431,7 +366,7 @@ async function runAgentSession() {
431
366
  }
432
367
  }
433
368
  catch (err) {
434
- log(colors.red(`Discovery failed: ${err instanceof Error ? err.message : err}`));
369
+ log(colors.lime(`Discovery failed: ${err instanceof Error ? err.message : err}`));
435
370
  screen.setStatus("waiting for conversations...");
436
371
  }
437
372
  return done;
@@ -439,19 +374,17 @@ async function runAgentSession() {
439
374
  // ── Dashboard loop ────────────────────────────────────────
440
375
  async function runDashboard() {
441
376
  while (true) {
442
- const config = getConfig();
443
377
  const agents = getAgents();
378
+ const authenticatedProviders = await getAuthenticatedProviders();
444
379
  const action = await new Promise((resolve) => {
445
380
  const dashboard = new DashboardScreen(tui, {
446
- loggedIn: !!config.token,
447
- model: detectedModel,
448
- agentCount: agents.length,
449
- serverUrl: config.serverUrl,
381
+ agents,
382
+ authenticatedProviders,
450
383
  });
451
384
  dashboard.onAction = resolve;
452
385
  setScreen(dashboard);
453
386
  });
454
- switch (action) {
387
+ switch (action.type) {
455
388
  case "start": {
456
389
  const conf = getConfig();
457
390
  if (!conf.token) {
@@ -461,8 +394,27 @@ async function runDashboard() {
461
394
  await runAgentSession();
462
395
  break;
463
396
  }
464
- case "agents":
465
- await runAgentList();
397
+ case "create-agent": {
398
+ const name = await askName("New Agent", "Give your agent a name.");
399
+ if (name) {
400
+ const agent = createAgent(name);
401
+ await runAgentConfig(agent.id);
402
+ }
403
+ break;
404
+ }
405
+ case "edit-agent":
406
+ await runAgentConfig(action.agentId);
407
+ break;
408
+ case "provider":
409
+ await showMessage("Provider Login", [
410
+ `To authenticate with this provider, run:`,
411
+ ``,
412
+ ` pi`,
413
+ ` /login`,
414
+ ``,
415
+ `Then select the provider in pi's login flow.`,
416
+ `Your credentials are stored locally in ~/.pi/agent/auth.json`,
417
+ ], "info");
466
418
  break;
467
419
  case "quit":
468
420
  shutdown();
@@ -482,10 +434,8 @@ export async function runInteractive() {
482
434
  terminal = new ProcessTerminal();
483
435
  tui = new TUI(terminal, true);
484
436
  tui.start();
485
- // Handle Ctrl+C globally
486
437
  tui.addInputListener((data) => {
487
438
  if (data === "\x03") {
488
- // Ctrl+C
489
439
  shutdown();
490
440
  return { consume: true };
491
441
  }
@@ -3,6 +3,10 @@ export declare function initInference(modelPattern?: string): Promise<{
3
3
  model: Model<Api>;
4
4
  }>;
5
5
  export declare function getAvailableModels(): Model<Api>[];
6
+ /**
7
+ * Return unique provider names that have at least one authenticated model.
8
+ */
9
+ export declare function getAuthenticatedProviders(): Promise<string[]>;
6
10
  /**
7
11
  * Set the agent instructions that will be prepended to every inference call.
8
12
  */
package/dist/inference.js CHANGED
@@ -40,6 +40,19 @@ export async function initInference(modelPattern) {
40
40
  export function getAvailableModels() {
41
41
  return modelRegistry?.getAvailable() ?? Promise.resolve([]);
42
42
  }
43
+ /**
44
+ * Return unique provider names that have at least one authenticated model.
45
+ */
46
+ export async function getAuthenticatedProviders() {
47
+ try {
48
+ const models = await getAvailableModels();
49
+ const providers = new Set(models.map((m) => m.provider.toLowerCase()));
50
+ return Array.from(providers);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
43
56
  /**
44
57
  * Set the agent instructions that will be prepended to every inference call.
45
58
  */
@@ -28,20 +28,19 @@ export class AgentConfigScreen {
28
28
  this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
29
29
  this.container.addChild(new Spacer(1));
30
30
  const items = [
31
- { value: "edit-name", label: "Rename agent", description: `Currently: ${agent.name}` },
32
- { value: "edit-instructions", label: "Edit instructions", description: "How your agent behaves" },
33
- { value: "edit-discovery", label: "Discovery settings", description: "Keywords, match threshold, filters" },
34
- { value: "edit-limits", label: "Agent limits", description: "Max conversations, cooldown, auto-stop" },
35
- { value: "edit-model", label: "Change model", description: `Currently: ${modelStr}` },
36
- { value: "delete", label: "Delete agent", description: "Remove this agent permanently" },
37
- { value: "back", label: "← Back", description: "" },
31
+ { value: "edit-name", label: "Rename", description: agent.name },
32
+ { value: "edit-instructions", label: "Instructions", description: "How your agent behaves" },
33
+ { value: "edit-discovery", label: "Discovery", description: "Keywords, match threshold, filters" },
34
+ { value: "edit-limits", label: "Limits", description: "Max conversations, cooldown, auto-stop" },
35
+ { value: "edit-model", label: "Model", description: modelStr },
36
+ { value: "delete", label: "Delete", description: "" },
38
37
  ];
39
38
  this.selectList = new SelectList(items, items.length, {
40
39
  selectedPrefix: (t) => theme.accent(t),
41
40
  selectedText: (t) => theme.accent(t),
42
41
  description: (t) => colors.gray(t),
43
42
  scrollInfo: (t) => colors.gray(t),
44
- noMatch: (t) => colors.yellow(t),
43
+ noMatch: (t) => colors.gray(t),
45
44
  });
46
45
  this.selectList.onSelect = (item) => {
47
46
  this.onAction?.(item.value);
@@ -1,13 +1,13 @@
1
1
  import { Editor, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
2
  import { colors, theme } from "../theme.js";
3
3
  const EDITOR_THEME = {
4
- borderColor: (s) => colors.cyan(s),
4
+ borderColor: (s) => colors.lime(s),
5
5
  selectList: {
6
- selectedPrefix: (t) => colors.cyan(t),
7
- selectedText: (t) => colors.cyan(t),
6
+ selectedPrefix: (t) => colors.lime(t),
7
+ selectedText: (t) => colors.lime(t),
8
8
  description: (t) => colors.gray(t),
9
9
  scrollInfo: (t) => colors.gray(t),
10
- noMatch: (t) => colors.yellow(t),
10
+ noMatch: (t) => colors.gray(t),
11
11
  },
12
12
  };
13
13
  export class AgentEditorScreen {
@@ -43,17 +43,22 @@ export class AgentEditorScreen {
43
43
  return this.cachedLines;
44
44
  const lines = [];
45
45
  lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
46
- lines.push(truncateToWidth(` ${theme.title("Edit Agent Instructions")} ${colors.gray("~/.swarmlancer/agent.md")}`, width));
47
- lines.push(truncateToWidth(colors.gray(" This controls how your agent behaves in conversations."), width));
46
+ lines.push(truncateToWidth(` ${theme.title("Instructions")}`, width));
48
47
  lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
49
- lines.push("");
50
- // Editor takes most of the space
51
- for (const line of this.editor.render(width)) {
48
+ // Editor takes most of the space — pad with empty lines to double the height
49
+ const editorLines = this.editor.render(width);
50
+ for (const line of editorLines) {
52
51
  lines.push(line);
53
52
  }
54
- lines.push("");
53
+ // Add padding lines to give the editor more vertical space
54
+ const termHeight = this.tui.terminal?.rows ?? 40;
55
+ const usedLines = lines.length + 3; // +3 for footer
56
+ const remaining = Math.max(0, termHeight - usedLines);
57
+ for (let i = 0; i < remaining; i++) {
58
+ lines.push("");
59
+ }
55
60
  lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
56
- lines.push(truncateToWidth(colors.gray(" shift+enter new line • ctrl+s save • esc cancel"), width));
61
+ lines.push(truncateToWidth(colors.gray(" ctrl+s save • esc cancel"), width));
57
62
  this.cachedLines = lines;
58
63
  return lines;
59
64
  }
@@ -12,7 +12,7 @@ export class AgentListScreen {
12
12
  this.container = new Container();
13
13
  this.container.addChild(new Spacer(1));
14
14
  this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
15
- this.container.addChild(new Text(theme.title(" Manage Agents"), 1, 0));
15
+ this.container.addChild(new Text(theme.title(" Agents"), 1, 0));
16
16
  this.container.addChild(new Text(colors.gray(" Each agent has its own instructions, model, discovery & limits."), 1, 0));
17
17
  this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
18
18
  this.container.addChild(new Spacer(1));
@@ -20,14 +20,11 @@ export class AgentListScreen {
20
20
  // Create new agent always at top
21
21
  items.push({
22
22
  value: "__create__",
23
- label: "+ Create new agent",
23
+ label: "+ Create new",
24
24
  description: "",
25
25
  });
26
26
  // Existing agents
27
27
  for (const agent of agents) {
28
- const modelStr = agent.modelPattern
29
- ? colors.gray(` (${agent.modelPattern})`)
30
- : "";
31
28
  const instrPreview = agent.instructions.length > 0
32
29
  ? `${agent.instructions.slice(0, 50).replace(/\n/g, " ").trim()}…`
33
30
  : "(no instructions)";
@@ -43,7 +40,7 @@ export class AgentListScreen {
43
40
  selectedText: (t) => theme.accent(t),
44
41
  description: (t) => colors.gray(t),
45
42
  scrollInfo: (t) => colors.gray(t),
46
- noMatch: (t) => colors.yellow(t),
43
+ noMatch: (t) => colors.gray(t),
47
44
  });
48
45
  this.selectList.onSelect = (item) => {
49
46
  if (item.value === "__create__") {
@@ -25,7 +25,7 @@ export class AgentPickerScreen {
25
25
  selectedText: (t) => theme.accent(t),
26
26
  description: (t) => colors.gray(t),
27
27
  scrollInfo: (t) => colors.gray(t),
28
- noMatch: (t) => colors.yellow(t),
28
+ noMatch: (t) => colors.gray(t),
29
29
  });
30
30
  this.selectList.onSelect = (item) => {
31
31
  const agent = agents.find((a) => a.id === item.value);
@@ -45,9 +45,9 @@ export class AgentRunningScreen {
45
45
  // Header
46
46
  lines.push(theme.border("─".repeat(width)));
47
47
  const modelInfo = `${this.model.provider}/${this.model.id}`;
48
- lines.push(truncateToWidth(` ${theme.title("⚡ AGENT ONLINE")} ${colors.cyan(this.agentName)} ${colors.gray("model:")} ${colors.green(modelInfo)}`, width));
48
+ lines.push(truncateToWidth(` ${theme.title("⚡ AGENT ONLINE")} ${colors.lime(this.agentName)} ${colors.gray("model:")} ${colors.lime(modelInfo)}`, width));
49
49
  if (this.sessionGoal) {
50
- lines.push(truncateToWidth(` ${colors.gray("goal:")} ${colors.cyan(this.sessionGoal)}`, width));
50
+ lines.push(truncateToWidth(` ${colors.gray("goal:")} ${colors.lime(this.sessionGoal)}`, width));
51
51
  }
52
52
  lines.push(theme.border("─".repeat(width)));
53
53
  lines.push("");
@@ -1,10 +1,10 @@
1
1
  import { Container, Text, Spacer } from "@mariozechner/pi-tui";
2
2
  import { colors } from "../theme.js";
3
- // "LET THE SWARM BEGIN" — figlet Mini font, 70 chars wide, single horizontal block
4
3
  const BANNER_ART = [
5
- ` _ ___ ___ _ __ _ _ _ __ ___`,
6
- ` | |_ | | |_| |_ (_ \\ / /\\ |_) |\\/| |_) |_ /__ | |\\ |`,
7
- ` |_ |_ | | | | |_ __) \\/\\/ /--\\ | \\ | | |_) |_ \\_| _|_ | \\|`,
4
+ ` _______ ________ _______ ______ _______ _____ _______ _______ ______ _______ ______`,
5
+ ` | __| | | | _ | __ \\ | | |_| _ | | | | ___| __ \\`,
6
+ ` |__ | | | | | < | | | | ---| ___| <`,
7
+ ` |_______|________|___|___|___|__|__|_|__|_______|___|___|__|____|______|_______|___|__|`,
8
8
  ];
9
9
  export class BannerComponent extends Container {
10
10
  constructor() {
@@ -15,9 +15,10 @@ export class BannerComponent extends Container {
15
15
  this.clear();
16
16
  this.addChild(new Spacer(1));
17
17
  for (const line of BANNER_ART) {
18
- this.addChild(new Text(colors.bold(colors.lime(line)), 1, 0));
18
+ this.addChild(new Text(colors.limeBold(line), 1, 0));
19
19
  }
20
- this.addChild(new Text(colors.gray(" swarmlancer.com — let the swarm begin"), 1, 0));
20
+ this.addChild(new Spacer(0));
21
+ this.addChild(new Text(colors.gray(" Let the Swarm Begin"), 1, 0));
21
22
  this.addChild(new Spacer(1));
22
23
  }
23
24
  invalidate() {
@@ -1,15 +1,33 @@
1
1
  import { type Component } from "@mariozechner/pi-tui";
2
2
  import type { TUI } from "@mariozechner/pi-tui";
3
- import { type StatusInfo } from "./status-panel.js";
4
- export type MenuAction = "start" | "agents" | "quit";
3
+ import type { AgentProfile } from "../config.js";
4
+ export type MenuAction = {
5
+ type: "start";
6
+ } | {
7
+ type: "create-agent";
8
+ } | {
9
+ type: "edit-agent";
10
+ agentId: string;
11
+ } | {
12
+ type: "provider";
13
+ providerId: string;
14
+ } | {
15
+ type: "quit";
16
+ };
17
+ export interface DashboardData {
18
+ agents: AgentProfile[];
19
+ authenticatedProviders: string[];
20
+ }
5
21
  export declare class DashboardScreen implements Component {
6
- private container;
7
- private statusPanel;
8
- private selectList;
9
22
  private tui;
23
+ private banner;
24
+ private items;
25
+ private selectableIndices;
26
+ private cursor;
27
+ private cachedRender?;
10
28
  onAction?: (action: MenuAction) => void;
11
- constructor(tui: TUI, status: StatusInfo);
12
- updateStatus(info: Partial<StatusInfo>): void;
29
+ constructor(tui: TUI, data: DashboardData);
30
+ private buildItems;
13
31
  handleInput(data: string): void;
14
32
  render(width: number): string[];
15
33
  invalidate(): void;
@@ -1,59 +1,128 @@
1
- import { Container, Text, Spacer, SelectList, matchesKey, } from "@mariozechner/pi-tui";
1
+ import { matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
2
  import { colors, theme } from "../theme.js";
3
- import { StatusPanel } from "./status-panel.js";
4
3
  import { BannerComponent } from "./banner.js";
5
- const MENU_ITEMS = [
6
- { value: "start", label: "Start agent", description: "Pick an agent → set goal → discover → connect" },
7
- { value: "agents", label: "Manage agents", description: "Create, edit, or delete agents" },
8
- { value: "quit", label: "Quit", description: "Exit swarmlancer" },
4
+ const KNOWN_PROVIDERS = [
5
+ { id: "anthropic", label: "Anthropic (Claude Pro/Max)", match: ["anthropic"] },
6
+ { id: "copilot", label: "GitHub Copilot", match: ["copilot", "github"] },
7
+ { id: "google", label: "Google Cloud Code Assist (Gemini CLI)", match: ["google", "vertex"] },
8
+ { id: "antigravity", label: "Antigravity (Gemini 3, Claude, GPT-OSS)", match: ["antigravity"] },
9
+ { id: "openai", label: "ChatGPT Plus/Pro (Codex Subscription)", match: ["openai", "chatgpt"] },
9
10
  ];
10
11
  export class DashboardScreen {
11
- container;
12
- statusPanel;
13
- selectList;
14
12
  tui;
13
+ banner;
14
+ items;
15
+ selectableIndices;
16
+ cursor = 0;
17
+ cachedRender;
15
18
  onAction;
16
- constructor(tui, status) {
19
+ constructor(tui, data) {
17
20
  this.tui = tui;
18
- this.container = new Container();
19
- // Banner
20
- this.container.addChild(new BannerComponent());
21
- // Status
22
- this.statusPanel = new StatusPanel(status);
23
- this.container.addChild(this.statusPanel);
24
- this.container.addChild(new Spacer(1));
25
- // Menu
26
- this.container.addChild(new Text(colors.bold(" What do you want to do?"), 1, 0));
27
- this.container.addChild(new Spacer(1));
28
- this.selectList = new SelectList(MENU_ITEMS, MENU_ITEMS.length, {
29
- selectedPrefix: (t) => theme.accent(t),
30
- selectedText: (t) => theme.accent(t),
31
- description: (t) => colors.gray(t),
32
- scrollInfo: (t) => colors.gray(t),
33
- noMatch: (t) => colors.yellow(t),
34
- });
35
- this.selectList.onSelect = (item) => {
36
- this.onAction?.(item.value);
37
- };
38
- this.container.addChild(this.selectList);
39
- this.container.addChild(new Spacer(1));
40
- this.container.addChild(new Text(colors.gray(" ↑↓ navigate • enter select • q quit"), 1, 0));
21
+ this.banner = new BannerComponent();
22
+ this.items = this.buildItems(data);
23
+ this.selectableIndices = this.items
24
+ .map((item, i) => (item.kind === "action" ? i : -1))
25
+ .filter((i) => i >= 0);
41
26
  }
42
- updateStatus(info) {
43
- this.statusPanel.update(info);
27
+ buildItems(data) {
28
+ const items = [];
29
+ items.push({ kind: "action", text: "Go online", indent: 0, action: { type: "start" } });
30
+ items.push({ kind: "spacer", text: "", indent: 0 });
31
+ items.push({ kind: "header", text: "Swarm", indent: 0 });
32
+ // Manage
33
+ items.push({ kind: "header", text: "Manage", indent: 1 });
34
+ items.push({ kind: "action", text: "+ Create", indent: 2, action: { type: "create-agent" } });
35
+ for (const agent of data.agents) {
36
+ const ready = agent.instructions.length > 0;
37
+ const suffix = ready ? " [⚡]" : "";
38
+ items.push({
39
+ kind: "action",
40
+ text: `${agent.name}${suffix}`,
41
+ indent: 2,
42
+ action: { type: "edit-agent", agentId: agent.id },
43
+ });
44
+ }
45
+ // Providers
46
+ items.push({ kind: "header", text: "Providers", indent: 1 });
47
+ items.push({ kind: "label", text: "Select provider to login:", indent: 2 });
48
+ for (const p of KNOWN_PROVIDERS) {
49
+ const loggedIn = p.match.some((m) => data.authenticatedProviders.some((ap) => ap.includes(m)));
50
+ const prefix = loggedIn ? "→ " : " ";
51
+ const suffix = loggedIn ? " ✓ logged in" : "";
52
+ items.push({
53
+ kind: "action",
54
+ text: `${prefix}${p.label}${suffix}`,
55
+ indent: 3,
56
+ action: { type: "provider", providerId: p.id },
57
+ });
58
+ }
59
+ items.push({ kind: "spacer", text: "", indent: 0 });
60
+ items.push({ kind: "action", text: "Exit", indent: 0, action: { type: "quit" } });
61
+ return items;
44
62
  }
45
63
  handleInput(data) {
46
64
  if (matchesKey(data, "q")) {
47
- this.onAction?.("quit");
65
+ this.onAction?.({ type: "quit" });
66
+ return;
67
+ }
68
+ if (matchesKey(data, Key.up)) {
69
+ if (this.cursor > 0)
70
+ this.cursor--;
71
+ this.cachedRender = undefined;
72
+ this.tui.requestRender();
73
+ return;
74
+ }
75
+ if (matchesKey(data, Key.down)) {
76
+ if (this.cursor < this.selectableIndices.length - 1)
77
+ this.cursor++;
78
+ this.cachedRender = undefined;
79
+ this.tui.requestRender();
80
+ return;
81
+ }
82
+ if (matchesKey(data, Key.enter)) {
83
+ const itemIndex = this.selectableIndices[this.cursor];
84
+ const item = this.items[itemIndex];
85
+ if (item.action) {
86
+ this.onAction?.(item.action);
87
+ }
48
88
  return;
49
89
  }
50
- this.selectList.handleInput(data);
51
- this.tui.requestRender();
52
90
  }
53
91
  render(width) {
54
- return this.container.render(width);
92
+ if (this.cachedRender)
93
+ return this.cachedRender;
94
+ const lines = this.banner.render(width);
95
+ const selectedItemIndex = this.selectableIndices[this.cursor];
96
+ for (let i = 0; i < this.items.length; i++) {
97
+ const item = this.items[i];
98
+ const pad = " ".repeat(item.indent);
99
+ if (item.kind === "spacer") {
100
+ lines.push("");
101
+ continue;
102
+ }
103
+ if (item.kind === "header") {
104
+ lines.push(truncateToWidth(` ${pad}${colors.bold(item.text)}`, width));
105
+ continue;
106
+ }
107
+ if (item.kind === "label") {
108
+ lines.push(truncateToWidth(` ${pad}${colors.gray(item.text)}`, width));
109
+ continue;
110
+ }
111
+ // Selectable action
112
+ const isSelected = i === selectedItemIndex;
113
+ if (isSelected) {
114
+ lines.push(truncateToWidth(` ${pad}${theme.accent(`▸ ${item.text}`)}`, width));
115
+ }
116
+ else {
117
+ lines.push(truncateToWidth(` ${pad} ${item.text}`, width));
118
+ }
119
+ }
120
+ lines.push("");
121
+ lines.push(truncateToWidth(colors.gray(" ↑↓ navigate • enter select • q quit"), width));
122
+ this.cachedRender = lines;
123
+ return lines;
55
124
  }
56
125
  invalidate() {
57
- this.container.invalidate();
126
+ this.cachedRender = undefined;
58
127
  }
59
128
  }
@@ -146,14 +146,14 @@ export class DiscoverySettingsScreen {
146
146
  const pct = ((value - (f.min ?? 0)) / ((f.max ?? 100) - (f.min ?? 0)));
147
147
  const barW = 16;
148
148
  const filled = Math.round(pct * barW);
149
- const bar = colors.cyan("█".repeat(filled)) + colors.gray("░".repeat(barW - filled));
149
+ const bar = colors.lime("█".repeat(filled)) + colors.gray("░".repeat(barW - filled));
150
150
  const valStr = `${value}${f.suffix ?? ""}`;
151
151
  lines.push(truncateToWidth(`${prefix}${label}`, width));
152
- lines.push(truncateToWidth(` ${colors.gray("◀")} ${bar} ${colors.gray("▶")} ${isActive ? colors.brightCyan(valStr) : colors.gray(valStr)}`, width));
152
+ lines.push(truncateToWidth(` ${colors.gray("◀")} ${bar} ${colors.gray("▶")} ${isActive ? colors.lime(valStr) : colors.gray(valStr)}`, width));
153
153
  }
154
154
  else if (f.type === "boolean") {
155
155
  const value = this.settings[f.key];
156
- const valStr = value ? colors.green("ON") : colors.red("OFF");
156
+ const valStr = value ? colors.lime("ON") : colors.gray("OFF");
157
157
  lines.push(truncateToWidth(`${prefix}${label} ${colors.gray("◀")} ${valStr} ${colors.gray("▶")}`, width));
158
158
  }
159
159
  else if (f.type === "keywords") {
@@ -8,7 +8,7 @@ export declare class MessageScreen implements Component {
8
8
  private container;
9
9
  private tui;
10
10
  onClose?: () => void;
11
- constructor(tui: TUI, title: string, lines: string[], style?: "info" | "error" | "success");
11
+ constructor(tui: TUI, title: string, lines: string[], _style?: "info" | "error" | "success");
12
12
  handleInput(_data: string): void;
13
13
  render(width: number): string[];
14
14
  invalidate(): void;
@@ -8,17 +8,12 @@ export class MessageScreen {
8
8
  container;
9
9
  tui;
10
10
  onClose;
11
- constructor(tui, title, lines, style = "info") {
11
+ constructor(tui, title, lines, _style = "info") {
12
12
  this.tui = tui;
13
13
  this.container = new Container();
14
- const colorFn = style === "error"
15
- ? colors.red
16
- : style === "success"
17
- ? colors.green
18
- : colors.cyan;
19
14
  this.container.addChild(new Spacer(1));
20
15
  this.container.addChild(new Text(theme.border("─".repeat(50)), 0, 0));
21
- this.container.addChild(new Text(colorFn(colors.bold(` ${title}`)), 1, 0));
16
+ this.container.addChild(new Text(colors.bold(colors.lime(` ${title}`)), 1, 0));
22
17
  this.container.addChild(new Spacer(1));
23
18
  for (const line of lines) {
24
19
  this.container.addChild(new Text(` ${line}`, 0, 0));
@@ -23,7 +23,7 @@ export class ModelPickerScreen {
23
23
  selectedText: (t) => theme.accent(t),
24
24
  description: (t) => colors.gray(t),
25
25
  scrollInfo: (t) => colors.gray(t),
26
- noMatch: (t) => colors.yellow(t),
26
+ noMatch: (t) => colors.gray(t),
27
27
  });
28
28
  this.selectList.onSelect = (item) => {
29
29
  const model = models.find((m) => m.id === item.value);
@@ -1,13 +1,13 @@
1
1
  import { Editor, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
2
  import { colors, theme } from "../theme.js";
3
3
  const EDITOR_THEME = {
4
- borderColor: (s) => colors.cyan(s),
4
+ borderColor: (s) => colors.lime(s),
5
5
  selectList: {
6
- selectedPrefix: (t) => colors.cyan(t),
7
- selectedText: (t) => colors.cyan(t),
6
+ selectedPrefix: (t) => colors.lime(t),
7
+ selectedText: (t) => colors.lime(t),
8
8
  description: (t) => colors.gray(t),
9
9
  scrollInfo: (t) => colors.gray(t),
10
- noMatch: (t) => colors.yellow(t),
10
+ noMatch: (t) => colors.gray(t),
11
11
  },
12
12
  };
13
13
  export class NameEditorScreen {
@@ -1,13 +1,13 @@
1
1
  import { Editor, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
2
  import { colors, theme } from "../theme.js";
3
3
  const EDITOR_THEME = {
4
- borderColor: (s) => colors.cyan(s),
4
+ borderColor: (s) => colors.lime(s),
5
5
  selectList: {
6
- selectedPrefix: (t) => colors.cyan(t),
7
- selectedText: (t) => colors.cyan(t),
6
+ selectedPrefix: (t) => colors.lime(t),
7
+ selectedText: (t) => colors.lime(t),
8
8
  description: (t) => colors.gray(t),
9
9
  scrollInfo: (t) => colors.gray(t),
10
- noMatch: (t) => colors.yellow(t),
10
+ noMatch: (t) => colors.gray(t),
11
11
  },
12
12
  };
13
13
  export class SessionGoalScreen {
@@ -107,9 +107,9 @@ export class SettingsScreen {
107
107
  const pct = (value - s.min) / (s.max - s.min);
108
108
  const barWidth = 20;
109
109
  const filled = Math.round(pct * barWidth);
110
- const bar = colors.cyan("█".repeat(filled)) + colors.gray("░".repeat(barWidth - filled));
110
+ const bar = colors.lime("█".repeat(filled)) + colors.gray("░".repeat(barWidth - filled));
111
111
  lines.push(truncateToWidth(`${prefix}${label}`, width));
112
- lines.push(truncateToWidth(` ${colors.gray("◀")} ${bar} ${colors.gray("▶")} ${isActive ? colors.brightCyan(valueStr) : colors.gray(valueStr)}`, width));
112
+ lines.push(truncateToWidth(` ${colors.gray("◀")} ${bar} ${colors.gray("▶")} ${isActive ? colors.lime(valueStr) : colors.gray(valueStr)}`, width));
113
113
  if (isActive) {
114
114
  lines.push(truncateToWidth(` ${colors.gray(s.description)}`, width));
115
115
  }
@@ -39,7 +39,7 @@ export class SetupWizardScreen {
39
39
  selectedText: (t) => theme.accent(t),
40
40
  description: (t) => colors.gray(t),
41
41
  scrollInfo: (t) => colors.gray(t),
42
- noMatch: (t) => colors.yellow(t),
42
+ noMatch: (t) => colors.gray(t),
43
43
  });
44
44
  this.promptList.onSelect = (item) => {
45
45
  this.promptActive = false;
@@ -81,20 +81,20 @@ export class SetupWizardScreen {
81
81
  line = colors.gray(step.label);
82
82
  break;
83
83
  case "running":
84
- icon = colors.cyan("◉");
85
- line = colors.cyan(step.label + "...");
84
+ icon = colors.lime("◉");
85
+ line = colors.lime(step.label + "...");
86
86
  break;
87
87
  case "done":
88
- icon = colors.green("✓");
89
- line = colors.green(step.label);
88
+ icon = colors.lime("✓");
89
+ line = colors.lime(step.label);
90
90
  break;
91
91
  case "failed":
92
- icon = colors.red("✗");
93
- line = colors.red(step.label);
92
+ icon = colors.lime("✗");
93
+ line = colors.lime(step.label);
94
94
  break;
95
95
  case "skipped":
96
- icon = colors.yellow("⊘");
97
- line = colors.yellow(step.label);
96
+ icon = colors.gray("⊘");
97
+ line = colors.gray(step.label);
98
98
  break;
99
99
  }
100
100
  lines.push(truncateToWidth(` ${icon} ${line}`, width));
@@ -14,7 +14,7 @@ export class StatusPanel extends Container {
14
14
  rebuild() {
15
15
  this.clear();
16
16
  const line = (label, value, ok) => {
17
- const icon = ok ? colors.green("✓") : colors.red("✗");
17
+ const icon = ok ? colors.lime("✓") : colors.gray("✗");
18
18
  const labelStr = colors.gray(label.padEnd(10));
19
19
  return ` ${icon} ${labelStr} ${value}`;
20
20
  };
@@ -24,7 +24,7 @@ export class StatusPanel extends Container {
24
24
  ? `${this.info.model.provider}/${this.info.model.id}`
25
25
  : "(not detected)", !!this.info.model), 0, 0));
26
26
  const agentStr = this.info.agentCount === 0
27
- ? "none — create one in Manage agents"
27
+ ? "none — create one in Agents"
28
28
  : `${this.info.agentCount} agent${this.info.agentCount === 1 ? "" : "s"}`;
29
29
  this.addChild(new Text(line("Agents", agentStr, this.info.agentCount > 0), 0, 0));
30
30
  this.addChild(new Text(line("Server", this.info.serverUrl, true), 0, 0));
package/dist/theme.d.ts CHANGED
@@ -3,24 +3,13 @@ export declare const colors: {
3
3
  bold: (s: string) => string;
4
4
  dim: (s: string) => string;
5
5
  italic: (s: string) => string;
6
- cyan: (s: string) => string;
7
- green: (s: string) => string;
8
- red: (s: string) => string;
9
- yellow: (s: string) => string;
10
- magenta: (s: string) => string;
11
- blue: (s: string) => string;
12
6
  white: (s: string) => string;
13
7
  gray: (s: string) => string;
14
- brightCyan: (s: string) => string;
15
- brightGreen: (s: string) => string;
16
- brightYellow: (s: string) => string;
17
8
  brightWhite: (s: string) => string;
18
9
  lime: (s: string) => string;
19
10
  limeBold: (s: string) => string;
20
11
  bgLime: (s: string) => string;
21
12
  bgGray: (s: string) => string;
22
- bgCyan: (s: string) => string;
23
- bgBlue: (s: string) => string;
24
13
  inverse: (s: string) => string;
25
14
  };
26
15
  export declare const theme: {
package/dist/theme.js CHANGED
@@ -11,46 +11,34 @@ export const colors = {
11
11
  bold: (s) => `${ESC}1m${s}${ESC}22m`,
12
12
  dim: (s) => `${ESC}2m${s}${ESC}22m`,
13
13
  italic: (s) => `${ESC}3m${s}${ESC}23m`,
14
- // Named colors
15
- cyan: (s) => `${ESC}36m${s}${ESC}39m`,
16
- green: (s) => `${ESC}32m${s}${ESC}39m`,
17
- red: (s) => `${ESC}31m${s}${ESC}39m`,
18
- yellow: (s) => `${ESC}33m${s}${ESC}39m`,
19
- magenta: (s) => `${ESC}35m${s}${ESC}39m`,
20
- blue: (s) => `${ESC}34m${s}${ESC}39m`,
14
+ // Named colors — kept for internal use but UI uses only lime
21
15
  white: (s) => `${ESC}37m${s}${ESC}39m`,
22
16
  gray: (s) => `${ESC}90m${s}${ESC}39m`,
23
- // Bright
24
- brightCyan: (s) => `${ESC}96m${s}${ESC}39m`,
25
- brightGreen: (s) => `${ESC}92m${s}${ESC}39m`,
26
- brightYellow: (s) => `${ESC}93m${s}${ESC}39m`,
27
17
  brightWhite: (s) => `${ESC}97m${s}${ESC}39m`,
28
- // Logo lime green — #C8F135
18
+ // Logo lime green — #C8F135 — THE accent color
29
19
  lime: (s) => `${LIME_FG}${s}${ESC}39m`,
30
20
  limeBold: (s) => `${LIME_FG}${ESC}1m${s}${ESC}22m${ESC}39m`,
31
21
  bgLime: (s) => `${LIME_BG}${DARK_FG}${s}${RESET}`,
32
22
  // Background
33
23
  bgGray: (s) => `${ESC}48;5;236m${s}${ESC}49m`,
34
- bgCyan: (s) => `${ESC}46m${s}${ESC}49m`,
35
- bgBlue: (s) => `${ESC}44m${s}${ESC}49m`,
36
24
  // Inverse
37
25
  inverse: (s) => `${ESC}7m${s}${ESC}27m`,
38
26
  };
39
- // Semantic aliases — accent is now the logo lime green
27
+ // Semantic aliases — everything colorful is lime
40
28
  export const theme = {
41
29
  accent: colors.lime,
42
- success: colors.green,
43
- error: colors.red,
44
- warning: colors.yellow,
30
+ success: colors.lime,
31
+ error: colors.lime,
32
+ warning: colors.lime,
45
33
  muted: colors.gray,
46
34
  highlight: colors.limeBold,
47
35
  border: colors.lime,
48
36
  title: (s) => colors.bold(colors.lime(s)),
49
37
  subtitle: (s) => colors.bold(colors.white(s)),
50
38
  status: {
51
- ok: (s) => colors.green(`✓ ${s}`),
52
- fail: (s) => colors.red(`✗ ${s}`),
53
- warn: (s) => colors.yellow(`⚠ ${s}`),
39
+ ok: (s) => colors.lime(`✓ ${s}`),
40
+ fail: (s) => colors.lime(`✗ ${s}`),
41
+ warn: (s) => colors.lime(`⚠ ${s}`),
54
42
  info: (s) => colors.lime(`▸ ${s}`),
55
43
  },
56
44
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarmlancer-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Swarmlancer CLI — let the swarm begin. Connect your AI agent to a network of other agents.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",