swarmlancer-cli 0.2.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/README.md +15 -0
- package/dist/agent.d.ts +13 -0
- package/dist/agent.js +202 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +496 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +175 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +129 -0
- package/dist/inference.d.ts +13 -0
- package/dist/inference.js +105 -0
- package/dist/login.d.ts +1 -0
- package/dist/login.js +57 -0
- package/dist/screening.d.ts +22 -0
- package/dist/screening.js +101 -0
- package/dist/screens/agent-config.d.ts +14 -0
- package/dist/screens/agent-config.js +64 -0
- package/dist/screens/agent-editor.d.ts +13 -0
- package/dist/screens/agent-editor.js +64 -0
- package/dist/screens/agent-list.d.ts +22 -0
- package/dist/screens/agent-list.js +73 -0
- package/dist/screens/agent-picker.d.ts +15 -0
- package/dist/screens/agent-picker.js +51 -0
- package/dist/screens/agent-running.d.ts +20 -0
- package/dist/screens/agent-running.js +68 -0
- package/dist/screens/banner.d.ts +6 -0
- package/dist/screens/banner.js +27 -0
- package/dist/screens/dashboard.d.ts +16 -0
- package/dist/screens/dashboard.js +59 -0
- package/dist/screens/discovery-settings.d.ts +17 -0
- package/dist/screens/discovery-settings.js +189 -0
- package/dist/screens/message.d.ts +15 -0
- package/dist/screens/message.js +39 -0
- package/dist/screens/model-picker.d.ts +14 -0
- package/dist/screens/model-picker.js +49 -0
- package/dist/screens/name-editor.d.ts +15 -0
- package/dist/screens/name-editor.js +67 -0
- package/dist/screens/session-goal.d.ts +13 -0
- package/dist/screens/session-goal.js +61 -0
- package/dist/screens/settings.d.ts +15 -0
- package/dist/screens/settings.js +126 -0
- package/dist/screens/setup-wizard.d.ts +20 -0
- package/dist/screens/setup-wizard.js +120 -0
- package/dist/screens/status-panel.d.ts +15 -0
- package/dist/screens/status-panel.js +37 -0
- package/dist/theme.d.ts +42 -0
- package/dist/theme.js +56 -0
- package/package.json +49 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Container, Text, Spacer, SelectList, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors, theme } from "../theme.js";
|
|
3
|
+
export class AgentConfigScreen {
|
|
4
|
+
container;
|
|
5
|
+
selectList;
|
|
6
|
+
tui;
|
|
7
|
+
onAction;
|
|
8
|
+
constructor(tui, agent) {
|
|
9
|
+
this.tui = tui;
|
|
10
|
+
this.container = new Container();
|
|
11
|
+
this.container.addChild(new Spacer(1));
|
|
12
|
+
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
13
|
+
this.container.addChild(new Text(theme.title(` Agent: ${agent.name}`), 1, 0));
|
|
14
|
+
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
15
|
+
this.container.addChild(new Spacer(1));
|
|
16
|
+
// Status summary
|
|
17
|
+
const modelStr = agent.modelPattern || "(auto — cheapest)";
|
|
18
|
+
const instrStr = agent.instructions.length > 0
|
|
19
|
+
? `${agent.instructions.split("\n").length} lines`
|
|
20
|
+
: "empty";
|
|
21
|
+
const discoveryStr = `threshold ${agent.discovery.matchThreshold}/10, ${agent.discovery.maxScreenPerSession} max`;
|
|
22
|
+
const limitsStr = `${agent.limits.maxConcurrentConversations} concurrent, ${agent.limits.maxMessagesPerConversation} msgs`;
|
|
23
|
+
this.container.addChild(new Text(` ${colors.gray("Model:")} ${modelStr}`, 0, 0));
|
|
24
|
+
this.container.addChild(new Text(` ${colors.gray("Instructions:")} ${instrStr}`, 0, 0));
|
|
25
|
+
this.container.addChild(new Text(` ${colors.gray("Discovery:")} ${discoveryStr}`, 0, 0));
|
|
26
|
+
this.container.addChild(new Text(` ${colors.gray("Limits:")} ${limitsStr}`, 0, 0));
|
|
27
|
+
this.container.addChild(new Spacer(1));
|
|
28
|
+
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
29
|
+
this.container.addChild(new Spacer(1));
|
|
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: "" },
|
|
38
|
+
];
|
|
39
|
+
this.selectList = new SelectList(items, items.length, {
|
|
40
|
+
selectedPrefix: (t) => theme.accent(t),
|
|
41
|
+
selectedText: (t) => theme.accent(t),
|
|
42
|
+
description: (t) => colors.gray(t),
|
|
43
|
+
scrollInfo: (t) => colors.gray(t),
|
|
44
|
+
noMatch: (t) => colors.yellow(t),
|
|
45
|
+
});
|
|
46
|
+
this.selectList.onSelect = (item) => {
|
|
47
|
+
this.onAction?.(item.value);
|
|
48
|
+
};
|
|
49
|
+
this.selectList.onCancel = () => this.onAction?.("back");
|
|
50
|
+
this.container.addChild(this.selectList);
|
|
51
|
+
this.container.addChild(new Spacer(1));
|
|
52
|
+
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • enter select • esc back"), 1, 0));
|
|
53
|
+
}
|
|
54
|
+
handleInput(data) {
|
|
55
|
+
this.selectList.handleInput(data);
|
|
56
|
+
this.tui.requestRender();
|
|
57
|
+
}
|
|
58
|
+
render(width) {
|
|
59
|
+
return this.container.render(width);
|
|
60
|
+
}
|
|
61
|
+
invalidate() {
|
|
62
|
+
this.container.invalidate();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
export declare class AgentEditorScreen implements Component {
|
|
4
|
+
private tui;
|
|
5
|
+
private editor;
|
|
6
|
+
private cachedLines?;
|
|
7
|
+
onSave?: (content: string) => void;
|
|
8
|
+
onCancel?: () => void;
|
|
9
|
+
constructor(tui: TUI, content: string);
|
|
10
|
+
handleInput(data: string): void;
|
|
11
|
+
render(width: number): string[];
|
|
12
|
+
invalidate(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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.cyan(s),
|
|
5
|
+
selectList: {
|
|
6
|
+
selectedPrefix: (t) => colors.cyan(t),
|
|
7
|
+
selectedText: (t) => colors.cyan(t),
|
|
8
|
+
description: (t) => colors.gray(t),
|
|
9
|
+
scrollInfo: (t) => colors.gray(t),
|
|
10
|
+
noMatch: (t) => colors.yellow(t),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export class AgentEditorScreen {
|
|
14
|
+
tui;
|
|
15
|
+
editor;
|
|
16
|
+
cachedLines;
|
|
17
|
+
onSave;
|
|
18
|
+
onCancel;
|
|
19
|
+
constructor(tui, content) {
|
|
20
|
+
this.tui = tui;
|
|
21
|
+
this.editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
|
|
22
|
+
this.editor.setText(content);
|
|
23
|
+
this.editor.disableSubmit = true; // Enter = new line, not submit
|
|
24
|
+
}
|
|
25
|
+
handleInput(data) {
|
|
26
|
+
// Ctrl+S to save
|
|
27
|
+
if (matchesKey(data, Key.ctrl("s"))) {
|
|
28
|
+
this.onSave?.(this.editor.getText());
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Escape to cancel
|
|
32
|
+
if (matchesKey(data, Key.escape)) {
|
|
33
|
+
this.onCancel?.();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Everything else goes to the editor
|
|
37
|
+
this.editor.handleInput(data);
|
|
38
|
+
this.cachedLines = undefined;
|
|
39
|
+
this.tui.requestRender();
|
|
40
|
+
}
|
|
41
|
+
render(width) {
|
|
42
|
+
if (this.cachedLines)
|
|
43
|
+
return this.cachedLines;
|
|
44
|
+
const lines = [];
|
|
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));
|
|
48
|
+
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)) {
|
|
52
|
+
lines.push(line);
|
|
53
|
+
}
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push(truncateToWidth(theme.border("─".repeat(width)), width));
|
|
56
|
+
lines.push(truncateToWidth(colors.gray(" shift+enter new line • ctrl+s save • esc cancel"), width));
|
|
57
|
+
this.cachedLines = lines;
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
60
|
+
invalidate() {
|
|
61
|
+
this.cachedLines = undefined;
|
|
62
|
+
this.editor.invalidate();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { AgentProfile } from "../config.js";
|
|
4
|
+
export type AgentListAction = {
|
|
5
|
+
type: "select";
|
|
6
|
+
agent: AgentProfile;
|
|
7
|
+
} | {
|
|
8
|
+
type: "create";
|
|
9
|
+
} | {
|
|
10
|
+
type: "back";
|
|
11
|
+
};
|
|
12
|
+
export declare class AgentListScreen implements Component {
|
|
13
|
+
private container;
|
|
14
|
+
private selectList;
|
|
15
|
+
private tui;
|
|
16
|
+
private agents;
|
|
17
|
+
onAction?: (action: AgentListAction) => void;
|
|
18
|
+
constructor(tui: TUI, agents: AgentProfile[]);
|
|
19
|
+
handleInput(data: string): void;
|
|
20
|
+
render(width: number): string[];
|
|
21
|
+
invalidate(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Container, Text, Spacer, SelectList, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors, theme } from "../theme.js";
|
|
3
|
+
export class AgentListScreen {
|
|
4
|
+
container;
|
|
5
|
+
selectList;
|
|
6
|
+
tui;
|
|
7
|
+
agents;
|
|
8
|
+
onAction;
|
|
9
|
+
constructor(tui, agents) {
|
|
10
|
+
this.tui = tui;
|
|
11
|
+
this.agents = agents;
|
|
12
|
+
this.container = new Container();
|
|
13
|
+
this.container.addChild(new Spacer(1));
|
|
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));
|
|
16
|
+
this.container.addChild(new Text(colors.gray(" Each agent has its own instructions, model, discovery & limits."), 1, 0));
|
|
17
|
+
this.container.addChild(new Text(truncateToWidth(theme.border("─".repeat(60)), 60), 0, 0));
|
|
18
|
+
this.container.addChild(new Spacer(1));
|
|
19
|
+
const items = [];
|
|
20
|
+
// Create new agent always at top
|
|
21
|
+
items.push({
|
|
22
|
+
value: "__create__",
|
|
23
|
+
label: "+ Create new agent",
|
|
24
|
+
description: "",
|
|
25
|
+
});
|
|
26
|
+
// Existing agents
|
|
27
|
+
for (const agent of agents) {
|
|
28
|
+
const modelStr = agent.modelPattern
|
|
29
|
+
? colors.gray(` (${agent.modelPattern})`)
|
|
30
|
+
: "";
|
|
31
|
+
const instrPreview = agent.instructions.length > 0
|
|
32
|
+
? `${agent.instructions.slice(0, 50).replace(/\n/g, " ").trim()}…`
|
|
33
|
+
: "(no instructions)";
|
|
34
|
+
items.push({
|
|
35
|
+
value: agent.id,
|
|
36
|
+
label: agent.name,
|
|
37
|
+
description: instrPreview,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const maxVisible = Math.min(items.length, 15);
|
|
41
|
+
this.selectList = new SelectList(items, maxVisible, {
|
|
42
|
+
selectedPrefix: (t) => theme.accent(t),
|
|
43
|
+
selectedText: (t) => theme.accent(t),
|
|
44
|
+
description: (t) => colors.gray(t),
|
|
45
|
+
scrollInfo: (t) => colors.gray(t),
|
|
46
|
+
noMatch: (t) => colors.yellow(t),
|
|
47
|
+
});
|
|
48
|
+
this.selectList.onSelect = (item) => {
|
|
49
|
+
if (item.value === "__create__") {
|
|
50
|
+
this.onAction?.({ type: "create" });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const agent = agents.find((a) => a.id === item.value);
|
|
54
|
+
if (agent)
|
|
55
|
+
this.onAction?.({ type: "select", agent });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
this.selectList.onCancel = () => this.onAction?.({ type: "back" });
|
|
59
|
+
this.container.addChild(this.selectList);
|
|
60
|
+
this.container.addChild(new Spacer(1));
|
|
61
|
+
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • enter select • esc back"), 1, 0));
|
|
62
|
+
}
|
|
63
|
+
handleInput(data) {
|
|
64
|
+
this.selectList.handleInput(data);
|
|
65
|
+
this.tui.requestRender();
|
|
66
|
+
}
|
|
67
|
+
render(width) {
|
|
68
|
+
return this.container.render(width);
|
|
69
|
+
}
|
|
70
|
+
invalidate() {
|
|
71
|
+
this.container.invalidate();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { AgentProfile } from "../config.js";
|
|
4
|
+
export declare class AgentPickerScreen implements Component {
|
|
5
|
+
private container;
|
|
6
|
+
private selectList;
|
|
7
|
+
private tui;
|
|
8
|
+
private agents;
|
|
9
|
+
onSelect?: (agent: AgentProfile) => void;
|
|
10
|
+
onCancel?: () => void;
|
|
11
|
+
constructor(tui: TUI, agents: AgentProfile[]);
|
|
12
|
+
handleInput(data: string): void;
|
|
13
|
+
render(width: number): string[];
|
|
14
|
+
invalidate(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Container, Text, Spacer, SelectList, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors, theme } from "../theme.js";
|
|
3
|
+
export class AgentPickerScreen {
|
|
4
|
+
container;
|
|
5
|
+
selectList;
|
|
6
|
+
tui;
|
|
7
|
+
agents;
|
|
8
|
+
onSelect;
|
|
9
|
+
onCancel;
|
|
10
|
+
constructor(tui, agents) {
|
|
11
|
+
this.tui = tui;
|
|
12
|
+
this.agents = agents;
|
|
13
|
+
this.container = new Container();
|
|
14
|
+
this.container.addChild(new Spacer(1));
|
|
15
|
+
this.container.addChild(new Text(theme.border("─".repeat(50)), 0, 0));
|
|
16
|
+
this.container.addChild(new Text(theme.title(" Pick an agent to start"), 1, 0));
|
|
17
|
+
this.container.addChild(new Spacer(1));
|
|
18
|
+
const items = agents.map((a) => ({
|
|
19
|
+
value: a.id,
|
|
20
|
+
label: a.name,
|
|
21
|
+
description: a.modelPattern || "(auto model)",
|
|
22
|
+
}));
|
|
23
|
+
this.selectList = new SelectList(items, Math.min(items.length, 15), {
|
|
24
|
+
selectedPrefix: (t) => theme.accent(t),
|
|
25
|
+
selectedText: (t) => theme.accent(t),
|
|
26
|
+
description: (t) => colors.gray(t),
|
|
27
|
+
scrollInfo: (t) => colors.gray(t),
|
|
28
|
+
noMatch: (t) => colors.yellow(t),
|
|
29
|
+
});
|
|
30
|
+
this.selectList.onSelect = (item) => {
|
|
31
|
+
const agent = agents.find((a) => a.id === item.value);
|
|
32
|
+
if (agent)
|
|
33
|
+
this.onSelect?.(agent);
|
|
34
|
+
};
|
|
35
|
+
this.selectList.onCancel = () => this.onCancel?.();
|
|
36
|
+
this.container.addChild(this.selectList);
|
|
37
|
+
this.container.addChild(new Spacer(1));
|
|
38
|
+
this.container.addChild(new Text(colors.gray(" ↑↓ navigate • enter select • esc back"), 1, 0));
|
|
39
|
+
this.container.addChild(new Text(theme.border("─".repeat(50)), 0, 0));
|
|
40
|
+
}
|
|
41
|
+
handleInput(data) {
|
|
42
|
+
this.selectList.handleInput(data);
|
|
43
|
+
this.tui.requestRender();
|
|
44
|
+
}
|
|
45
|
+
render(width) {
|
|
46
|
+
return this.container.render(width);
|
|
47
|
+
}
|
|
48
|
+
invalidate() {
|
|
49
|
+
this.container.invalidate();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { Model, Api } from "@mariozechner/pi-ai";
|
|
4
|
+
export declare class AgentRunningScreen implements Component {
|
|
5
|
+
private tui;
|
|
6
|
+
private logLines;
|
|
7
|
+
private cachedRender?;
|
|
8
|
+
private model;
|
|
9
|
+
private serverUrl;
|
|
10
|
+
private agentName;
|
|
11
|
+
private sessionGoal;
|
|
12
|
+
private statusLine;
|
|
13
|
+
onStop?: () => void;
|
|
14
|
+
constructor(tui: TUI, model: Model<Api>, serverUrl: string, agentName: string, sessionGoal?: string);
|
|
15
|
+
setStatus(status: string): void;
|
|
16
|
+
addLog(line: string): void;
|
|
17
|
+
handleInput(data: string): void;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
invalidate(): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { matchesKey, Key, truncateToWidth, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors, theme } from "../theme.js";
|
|
3
|
+
const MAX_LOG_LINES = 200;
|
|
4
|
+
export class AgentRunningScreen {
|
|
5
|
+
tui;
|
|
6
|
+
logLines = [];
|
|
7
|
+
cachedRender;
|
|
8
|
+
model;
|
|
9
|
+
serverUrl;
|
|
10
|
+
agentName;
|
|
11
|
+
sessionGoal;
|
|
12
|
+
statusLine = "starting...";
|
|
13
|
+
onStop;
|
|
14
|
+
constructor(tui, model, serverUrl, agentName, sessionGoal = "") {
|
|
15
|
+
this.tui = tui;
|
|
16
|
+
this.model = model;
|
|
17
|
+
this.serverUrl = serverUrl;
|
|
18
|
+
this.agentName = agentName;
|
|
19
|
+
this.sessionGoal = sessionGoal;
|
|
20
|
+
}
|
|
21
|
+
setStatus(status) {
|
|
22
|
+
this.statusLine = status;
|
|
23
|
+
this.cachedRender = undefined;
|
|
24
|
+
this.tui.requestRender();
|
|
25
|
+
}
|
|
26
|
+
addLog(line) {
|
|
27
|
+
this.logLines.push(line);
|
|
28
|
+
if (this.logLines.length > MAX_LOG_LINES) {
|
|
29
|
+
this.logLines.shift();
|
|
30
|
+
}
|
|
31
|
+
this.cachedRender = undefined;
|
|
32
|
+
this.tui.requestRender();
|
|
33
|
+
}
|
|
34
|
+
handleInput(data) {
|
|
35
|
+
if (matchesKey(data, Key.escape) ||
|
|
36
|
+
matchesKey(data, "q") ||
|
|
37
|
+
matchesKey(data, Key.ctrl("c"))) {
|
|
38
|
+
this.onStop?.();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
render(width) {
|
|
42
|
+
if (this.cachedRender)
|
|
43
|
+
return this.cachedRender;
|
|
44
|
+
const lines = [];
|
|
45
|
+
// Header
|
|
46
|
+
lines.push(theme.border("─".repeat(width)));
|
|
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));
|
|
49
|
+
if (this.sessionGoal) {
|
|
50
|
+
lines.push(truncateToWidth(` ${colors.gray("goal:")} ${colors.cyan(this.sessionGoal)}`, width));
|
|
51
|
+
}
|
|
52
|
+
lines.push(theme.border("─".repeat(width)));
|
|
53
|
+
lines.push("");
|
|
54
|
+
// Log
|
|
55
|
+
for (const logLine of this.logLines) {
|
|
56
|
+
lines.push(truncateToWidth(` ${logLine}`, width));
|
|
57
|
+
}
|
|
58
|
+
// Footer
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(theme.border("─".repeat(width)));
|
|
61
|
+
lines.push(truncateToWidth(` ${colors.gray(this.statusLine)} ${colors.gray("• esc/q stop")}`, width));
|
|
62
|
+
this.cachedRender = lines;
|
|
63
|
+
return lines;
|
|
64
|
+
}
|
|
65
|
+
invalidate() {
|
|
66
|
+
this.cachedRender = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Container, Text, Spacer } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors } from "../theme.js";
|
|
3
|
+
// "LET THE SWARM BEGIN" — figlet Mini font, 70 chars wide, single horizontal block
|
|
4
|
+
const BANNER_ART = [
|
|
5
|
+
` _ ___ ___ _ __ _ _ _ __ ___`,
|
|
6
|
+
` | |_ | | |_| |_ (_ \\ / /\\ |_) |\\/| |_) |_ /__ | |\\ |`,
|
|
7
|
+
` |_ |_ | | | | |_ __) \\/\\/ /--\\ | \\ | | |_) |_ \\_| _|_ | \\|`,
|
|
8
|
+
];
|
|
9
|
+
export class BannerComponent extends Container {
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.rebuild();
|
|
13
|
+
}
|
|
14
|
+
rebuild() {
|
|
15
|
+
this.clear();
|
|
16
|
+
this.addChild(new Spacer(1));
|
|
17
|
+
for (const line of BANNER_ART) {
|
|
18
|
+
this.addChild(new Text(colors.bold(colors.lime(line)), 1, 0));
|
|
19
|
+
}
|
|
20
|
+
this.addChild(new Text(colors.gray(" swarmlancer.com — let the swarm begin"), 1, 0));
|
|
21
|
+
this.addChild(new Spacer(1));
|
|
22
|
+
}
|
|
23
|
+
invalidate() {
|
|
24
|
+
super.invalidate();
|
|
25
|
+
this.rebuild();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
import { type StatusInfo } from "./status-panel.js";
|
|
4
|
+
export type MenuAction = "start" | "agents" | "quit";
|
|
5
|
+
export declare class DashboardScreen implements Component {
|
|
6
|
+
private container;
|
|
7
|
+
private statusPanel;
|
|
8
|
+
private selectList;
|
|
9
|
+
private tui;
|
|
10
|
+
onAction?: (action: MenuAction) => void;
|
|
11
|
+
constructor(tui: TUI, status: StatusInfo);
|
|
12
|
+
updateStatus(info: Partial<StatusInfo>): void;
|
|
13
|
+
handleInput(data: string): void;
|
|
14
|
+
render(width: number): string[];
|
|
15
|
+
invalidate(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Container, Text, Spacer, SelectList, matchesKey, } from "@mariozechner/pi-tui";
|
|
2
|
+
import { colors, theme } from "../theme.js";
|
|
3
|
+
import { StatusPanel } from "./status-panel.js";
|
|
4
|
+
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" },
|
|
9
|
+
];
|
|
10
|
+
export class DashboardScreen {
|
|
11
|
+
container;
|
|
12
|
+
statusPanel;
|
|
13
|
+
selectList;
|
|
14
|
+
tui;
|
|
15
|
+
onAction;
|
|
16
|
+
constructor(tui, status) {
|
|
17
|
+
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));
|
|
41
|
+
}
|
|
42
|
+
updateStatus(info) {
|
|
43
|
+
this.statusPanel.update(info);
|
|
44
|
+
}
|
|
45
|
+
handleInput(data) {
|
|
46
|
+
if (matchesKey(data, "q")) {
|
|
47
|
+
this.onAction?.("quit");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.selectList.handleInput(data);
|
|
51
|
+
this.tui.requestRender();
|
|
52
|
+
}
|
|
53
|
+
render(width) {
|
|
54
|
+
return this.container.render(width);
|
|
55
|
+
}
|
|
56
|
+
invalidate() {
|
|
57
|
+
this.container.invalidate();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
2
|
+
import type { TUI } from "@mariozechner/pi-tui";
|
|
3
|
+
import type { DiscoverySettings } from "../config.js";
|
|
4
|
+
export declare class DiscoverySettingsScreen implements Component {
|
|
5
|
+
private tui;
|
|
6
|
+
private settings;
|
|
7
|
+
private selectedIndex;
|
|
8
|
+
private editingKeywords;
|
|
9
|
+
private keywordInput;
|
|
10
|
+
private cachedRender?;
|
|
11
|
+
onSave?: (settings: DiscoverySettings) => void;
|
|
12
|
+
onCancel?: () => void;
|
|
13
|
+
constructor(tui: TUI, settings: DiscoverySettings);
|
|
14
|
+
handleInput(data: string): void;
|
|
15
|
+
render(width: number): string[];
|
|
16
|
+
invalidate(): void;
|
|
17
|
+
}
|