swarmlancer-cli 0.5.3 → 0.6.1

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 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
+ // Runtime state for currently running agent
23
23
  function setScreen(component) {
24
24
  tui.clear();
25
25
  tui.addChild(component);
@@ -57,9 +57,9 @@ async function silentSetup() {
57
57
  }
58
58
  }
59
59
  // ── Sub-screens ───────────────────────────────────────────
60
- function askName(title, subtitle, current = "") {
60
+ function askName(title, current = "") {
61
61
  return new Promise((resolve) => {
62
- const screen = new NameEditorScreen(tui, current, title, subtitle);
62
+ const screen = new NameEditorScreen(tui, current, title);
63
63
  screen.onSave = (name) => resolve(name);
64
64
  screen.onCancel = () => resolve(null);
65
65
  setScreen(screen);
@@ -134,8 +134,13 @@ async function runAgentConfig(agentId) {
134
134
  });
135
135
  idx = action.index >= 0 ? action.index : idx;
136
136
  switch (action.action) {
137
+ case "toggle-active": {
138
+ agent.active = !agent.active;
139
+ saveAgent(agent);
140
+ break;
141
+ }
137
142
  case "edit-name": {
138
- const n = await askName("Rename Agent", "Enter a new name.", agent.name);
143
+ const n = await askName("Rename", agent.name);
139
144
  if (n) {
140
145
  agent.name = n;
141
146
  saveAgent(agent);
@@ -175,8 +180,7 @@ async function runSwarm() {
175
180
  { value: "__create__", label: "+ Create", description: "" },
176
181
  ];
177
182
  for (const agent of agents) {
178
- const isRunning = agent.id === runningAgentId;
179
- const suffix = isRunning ? " [⚡]" : "";
183
+ const suffix = agent.active ? " (ON)" : "";
180
184
  items.push({ value: agent.id, label: `${agent.name}${suffix}`, description: "" });
181
185
  }
182
186
  const result = await new Promise((resolve) => {
@@ -189,7 +193,7 @@ async function runSwarm() {
189
193
  return;
190
194
  idx = result.index;
191
195
  if (result.value === "__create__") {
192
- const name = await askName("New Agent", "Give your agent a name.");
196
+ const name = await askName("+ Create");
193
197
  if (name) {
194
198
  const a = createAgent(name);
195
199
  await runAgentConfig(a.id);
@@ -226,7 +230,7 @@ async function runProviders() {
226
230
  await showMessage("Coming soon", [`${provider.label} is not yet supported.`], "info");
227
231
  continue;
228
232
  }
229
- const key = await askName(provider.label, "Paste your API key:");
233
+ const key = await askName(provider.label);
230
234
  if (key) {
231
235
  saveProviderKey(provider.id, key);
232
236
  await showMessage("Saved", [`${provider.label} API key saved.`], "success");
@@ -297,12 +301,11 @@ async function goOnline() {
297
301
  }
298
302
  setActiveModel(resolved.provider, resolved.model);
299
303
  setAgentInstructions(agent.instructions);
300
- runningAgentId = agent.id;
301
304
  const modelInfo = { provider: resolved.provider, id: resolved.model, name: resolved.model };
302
305
  const config = getConfig();
303
306
  const screen = new AgentRunningScreen(tui, modelInfo, config.serverUrl, agent.name);
304
307
  const done = new Promise((resolve) => {
305
- screen.onStop = () => { stopAgent(); runningAgentId = undefined; resolve(); };
308
+ screen.onStop = () => { stopAgent(); resolve(); };
306
309
  });
307
310
  setScreen(screen);
308
311
  const log = (line) => screen.addLog(line);
package/dist/config.d.ts CHANGED
@@ -28,6 +28,7 @@ export type AgentProfile = {
28
28
  discovery: DiscoverySettings;
29
29
  limits: AgentLimits;
30
30
  modelPattern?: string;
31
+ active?: boolean;
31
32
  };
32
33
  export type Config = {
33
34
  token?: string;
@@ -1,7 +1,7 @@
1
1
  import { type Component } from "@mariozechner/pi-tui";
2
2
  import type { TUI } from "@mariozechner/pi-tui";
3
3
  import type { AgentProfile } from "../config.js";
4
- export type AgentConfigAction = "edit-name" | "edit-instructions" | "edit-discovery" | "edit-limits" | "edit-model" | "delete" | "back";
4
+ export type AgentConfigAction = "toggle-active" | "edit-name" | "edit-instructions" | "edit-discovery" | "edit-limits" | "edit-model" | "delete" | "back";
5
5
  export declare class AgentConfigScreen implements Component {
6
6
  private container;
7
7
  private selectList;
@@ -1,4 +1,4 @@
1
- import { Container, Text, Spacer, SelectList, matchesKey, Key, } from "@mariozechner/pi-tui";
1
+ import { Container, Spacer, SelectList, Text, matchesKey, Key, } from "@mariozechner/pi-tui";
2
2
  import { colors, theme } from "../theme.js";
3
3
  import { BannerComponent } from "./banner.js";
4
4
  export class AgentConfigScreen {
@@ -9,25 +9,15 @@ export class AgentConfigScreen {
9
9
  constructor(tui, agent, initialIndex = 0) {
10
10
  this.tui = tui;
11
11
  this.container = new Container();
12
- this.container.addChild(new BannerComponent(`Agent: ${agent.name}`));
13
- // Status summary
14
- const modelStr = agent.modelPattern || "(auto — cheapest)";
15
- const instrStr = agent.instructions.length > 0
16
- ? `${agent.instructions.split("\n").length} lines`
17
- : "empty";
18
- const discoveryStr = `threshold ${agent.discovery.matchThreshold}/10, ${agent.discovery.maxScreenPerSession} max`;
19
- const limitsStr = `${agent.limits.maxConcurrentConversations} concurrent, ${agent.limits.maxMessagesPerConversation} msgs`;
20
- this.container.addChild(new Text(` ${colors.gray("Model:")} ${modelStr}`, 0, 0));
21
- this.container.addChild(new Text(` ${colors.gray("Instructions:")} ${instrStr}`, 0, 0));
22
- this.container.addChild(new Text(` ${colors.gray("Discovery:")} ${discoveryStr}`, 0, 0));
23
- this.container.addChild(new Text(` ${colors.gray("Limits:")} ${limitsStr}`, 0, 0));
24
- this.container.addChild(new Spacer(1));
12
+ this.container.addChild(new BannerComponent(agent.name));
13
+ const activeLabel = agent.active ? "ON" : "OFF";
25
14
  const items = [
26
- { value: "edit-name", label: "Rename", description: agent.name },
27
- { value: "edit-instructions", label: "Instructions", description: "How your agent behaves" },
28
- { value: "edit-discovery", label: "Discovery", description: "Keywords, match threshold, filters" },
29
- { value: "edit-limits", label: "Limits", description: "Max conversations, cooldown, auto-stop" },
30
- { value: "edit-model", label: "Model", description: modelStr },
15
+ { value: "toggle-active", label: `Active: ${activeLabel}`, description: "" },
16
+ { value: "edit-name", label: "Rename", description: "" },
17
+ { value: "edit-instructions", label: "Instructions", description: "" },
18
+ { value: "edit-discovery", label: "Discovery", description: "" },
19
+ { value: "edit-limits", label: "Limits", description: "" },
20
+ { value: "edit-model", label: "Model", description: "" },
31
21
  { value: "delete", label: "Delete", description: "" },
32
22
  ];
33
23
  this.selectList = new SelectList(items, items.length, {
@@ -2,6 +2,7 @@ import { type Component } from "@mariozechner/pi-tui";
2
2
  import type { TUI } from "@mariozechner/pi-tui";
3
3
  export declare class AgentEditorScreen implements Component {
4
4
  private tui;
5
+ private banner;
5
6
  private editor;
6
7
  private cachedLines?;
7
8
  onSave?: (content: string) => void;
@@ -1,5 +1,6 @@
1
- import { Editor, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
- import { colors, theme } from "../theme.js";
1
+ import { Editor, matchesKey, Key, } from "@mariozechner/pi-tui";
2
+ import { colors } from "../theme.js";
3
+ import { BannerComponent } from "./banner.js";
3
4
  const EDITOR_THEME = {
4
5
  borderColor: (s) => colors.lime(s),
5
6
  selectList: {
@@ -12,28 +13,27 @@ const EDITOR_THEME = {
12
13
  };
13
14
  export class AgentEditorScreen {
14
15
  tui;
16
+ banner;
15
17
  editor;
16
18
  cachedLines;
17
19
  onSave;
18
20
  onCancel;
19
21
  constructor(tui, content) {
20
22
  this.tui = tui;
23
+ this.banner = new BannerComponent("Instructions");
21
24
  this.editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
22
25
  this.editor.setText(content);
23
- this.editor.disableSubmit = true; // Enter = new line, not submit
26
+ this.editor.disableSubmit = true;
24
27
  }
25
28
  handleInput(data) {
26
- // Ctrl+S to save
27
29
  if (matchesKey(data, Key.ctrl("s"))) {
28
30
  this.onSave?.(this.editor.getText());
29
31
  return;
30
32
  }
31
- // Escape to cancel
32
33
  if (matchesKey(data, Key.escape)) {
33
34
  this.onCancel?.();
34
35
  return;
35
36
  }
36
- // Everything else goes to the editor
37
37
  this.editor.handleInput(data);
38
38
  this.cachedLines = undefined;
39
39
  this.tui.requestRender();
@@ -41,24 +41,18 @@ export class AgentEditorScreen {
41
41
  render(width) {
42
42
  if (this.cachedLines)
43
43
  return this.cachedLines;
44
- const lines = [];
45
- lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
46
- lines.push(truncateToWidth(` ${theme.title("Instructions")}`, width));
47
- lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
48
- // Editor takes most of the space — pad with empty lines to double the height
44
+ const lines = this.banner.render(width);
45
+ // Editor content
49
46
  const editorLines = this.editor.render(width);
50
- for (const line of editorLines) {
47
+ for (const line of editorLines)
51
48
  lines.push(line);
52
- }
53
- // Add padding lines to give the editor more vertical space
49
+ // Fill remaining terminal height to give editor 2x visual space
54
50
  const termHeight = this.tui.terminal?.rows ?? 40;
55
- const usedLines = lines.length + 3; // +3 for footer
51
+ const usedLines = lines.length + 2;
56
52
  const remaining = Math.max(0, termHeight - usedLines);
57
- for (let i = 0; i < remaining; i++) {
53
+ for (let i = 0; i < remaining; i++)
58
54
  lines.push("");
59
- }
60
- lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
61
- lines.push(truncateToWidth(colors.gray(" ctrl+s save • esc cancel"), width));
55
+ lines.push(colors.gray(" ctrl+s save • esc cancel"));
62
56
  this.cachedLines = lines;
63
57
  return lines;
64
58
  }
@@ -2,13 +2,12 @@ import { type Component } from "@mariozechner/pi-tui";
2
2
  import type { TUI } from "@mariozechner/pi-tui";
3
3
  export declare class NameEditorScreen implements Component {
4
4
  private tui;
5
- private editor;
5
+ private container;
6
+ private input;
6
7
  private cachedLines?;
7
- private title;
8
- private subtitle;
9
8
  onSave?: (name: string) => void;
10
9
  onCancel?: () => void;
11
- constructor(tui: TUI, currentName: string, title?: string, subtitle?: string);
10
+ constructor(tui: TUI, currentValue: string, title?: string);
12
11
  handleInput(data: string): void;
13
12
  render(width: number): string[];
14
13
  invalidate(): void;
@@ -1,67 +1,50 @@
1
- import { Editor, matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
2
- import { colors, theme } from "../theme.js";
3
- const EDITOR_THEME = {
4
- borderColor: (s) => colors.lime(s),
5
- selectList: {
6
- selectedPrefix: (t) => colors.lime(t),
7
- selectedText: (t) => colors.lime(t),
8
- description: (t) => colors.gray(t),
9
- scrollInfo: (t) => colors.gray(t),
10
- noMatch: (t) => colors.gray(t),
11
- },
12
- };
1
+ import { Container, Input, matchesKey, Key, } from "@mariozechner/pi-tui";
2
+ import { colors } from "../theme.js";
3
+ import { BannerComponent } from "./banner.js";
13
4
  export class NameEditorScreen {
14
5
  tui;
15
- editor;
6
+ container;
7
+ input;
16
8
  cachedLines;
17
- title;
18
- subtitle;
19
9
  onSave;
20
10
  onCancel;
21
- constructor(tui, currentName, title = "Agent Name", subtitle = "Give your agent a name to identify it.") {
11
+ constructor(tui, currentValue, title = "Rename") {
22
12
  this.tui = tui;
23
- this.title = title;
24
- this.subtitle = subtitle;
25
- this.editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
26
- this.editor.setText(currentName);
27
- this.editor.disableSubmit = false; // Enter submits
28
- this.editor.onSubmit = (text) => {
29
- const trimmed = text.trim();
30
- if (trimmed.length > 0) {
31
- this.onSave?.(trimmed);
32
- }
33
- };
13
+ this.container = new Container();
14
+ this.container.addChild(new BannerComponent(title));
15
+ this.input = new Input();
16
+ this.input.setValue(currentValue);
34
17
  }
35
18
  handleInput(data) {
36
19
  if (matchesKey(data, Key.escape)) {
37
20
  this.onCancel?.();
38
21
  return;
39
22
  }
40
- this.editor.handleInput(data);
23
+ if (matchesKey(data, Key.enter)) {
24
+ const trimmed = this.input.getValue().trim();
25
+ if (trimmed.length > 0)
26
+ this.onSave?.(trimmed);
27
+ return;
28
+ }
29
+ this.input.handleInput(data);
41
30
  this.cachedLines = undefined;
42
31
  this.tui.requestRender();
43
32
  }
44
33
  render(width) {
45
34
  if (this.cachedLines)
46
35
  return this.cachedLines;
47
- const lines = [];
48
- lines.push("");
49
- lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
50
- lines.push(truncateToWidth(theme.title(` ${this.title}`), width));
51
- lines.push(truncateToWidth(colors.gray(` ${this.subtitle}`), width));
52
- lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
53
- lines.push("");
54
- for (const line of this.editor.render(width)) {
55
- lines.push(line);
36
+ const lines = this.container.render(width);
37
+ // Render the input with a prompt
38
+ const inputLines = this.input.render(width - 4);
39
+ for (const line of inputLines) {
40
+ lines.push(` ${colors.lime("▸")} ${line}`);
56
41
  }
57
42
  lines.push("");
58
- lines.push(truncateToWidth(theme.border("".repeat(width)), width));
59
- lines.push(truncateToWidth(colors.gray(" enter save • esc cancel"), width));
43
+ lines.push(colors.gray(" enter save • esc cancel"));
60
44
  this.cachedLines = lines;
61
45
  return lines;
62
46
  }
63
47
  invalidate() {
64
48
  this.cachedLines = undefined;
65
- this.editor.invalidate();
66
49
  }
67
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarmlancer-cli",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
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",