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
package/dist/app.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { ProcessTerminal, TUI, Container } from "@mariozechner/pi-tui";
|
|
2
|
+
import { getConfig, getAgents, getAgent, saveAgent, deleteAgent, createAgent, migrateLegacyAgent, } from "./config.js";
|
|
3
|
+
import { login } from "./login.js";
|
|
4
|
+
import { initInference, getAvailableModels, setAgentInstructions } from "./inference.js";
|
|
5
|
+
import { startAgent, stopAgent, sendToServer } from "./agent.js";
|
|
6
|
+
import { colors } from "./theme.js";
|
|
7
|
+
import { BannerComponent } from "./screens/banner.js";
|
|
8
|
+
import { SetupWizardScreen } from "./screens/setup-wizard.js";
|
|
9
|
+
import { DashboardScreen } from "./screens/dashboard.js";
|
|
10
|
+
import { AgentListScreen } from "./screens/agent-list.js";
|
|
11
|
+
import { AgentConfigScreen } from "./screens/agent-config.js";
|
|
12
|
+
import { AgentPickerScreen } from "./screens/agent-picker.js";
|
|
13
|
+
import { AgentEditorScreen } from "./screens/agent-editor.js";
|
|
14
|
+
import { AgentRunningScreen } from "./screens/agent-running.js";
|
|
15
|
+
import { SettingsScreen } from "./screens/settings.js";
|
|
16
|
+
import { DiscoverySettingsScreen } from "./screens/discovery-settings.js";
|
|
17
|
+
import { ModelPickerScreen } from "./screens/model-picker.js";
|
|
18
|
+
import { NameEditorScreen } from "./screens/name-editor.js";
|
|
19
|
+
import { SessionGoalScreen } from "./screens/session-goal.js";
|
|
20
|
+
import { MessageScreen } from "./screens/message.js";
|
|
21
|
+
import { screenProfiles } from "./screening.js";
|
|
22
|
+
let terminal;
|
|
23
|
+
let tui;
|
|
24
|
+
let detectedModel;
|
|
25
|
+
/**
|
|
26
|
+
* Switch the active screen by replacing the TUI's child + focus.
|
|
27
|
+
*/
|
|
28
|
+
function setScreen(component) {
|
|
29
|
+
tui.clear();
|
|
30
|
+
tui.addChild(component);
|
|
31
|
+
tui.setFocus(component);
|
|
32
|
+
tui.requestRender(true);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Show a temporary message and wait for any key.
|
|
36
|
+
*/
|
|
37
|
+
function showMessage(title, lines, style = "info") {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const screen = new MessageScreen(tui, title, lines, style);
|
|
40
|
+
screen.onClose = () => resolve();
|
|
41
|
+
setScreen(screen);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run the setup wizard: check auth, model, agents.
|
|
46
|
+
*/
|
|
47
|
+
async function runSetup() {
|
|
48
|
+
// Migrate legacy single-agent setup
|
|
49
|
+
migrateLegacyAgent();
|
|
50
|
+
const banner = new BannerComponent();
|
|
51
|
+
const wizard = new SetupWizardScreen(tui);
|
|
52
|
+
// Show banner + wizard together
|
|
53
|
+
const wrapper = new Container();
|
|
54
|
+
wrapper.addChild(banner);
|
|
55
|
+
wrapper.addChild(wizard);
|
|
56
|
+
tui.clear();
|
|
57
|
+
tui.addChild(wrapper);
|
|
58
|
+
tui.setFocus(wizard);
|
|
59
|
+
tui.requestRender(true);
|
|
60
|
+
// Step 1: Auth
|
|
61
|
+
const config = getConfig();
|
|
62
|
+
wizard.setStep(0, "running");
|
|
63
|
+
if (config.token) {
|
|
64
|
+
wizard.setStep(0, "done", "logged in");
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
wizard.setStep(0, "running", "not logged in — opening browser...");
|
|
68
|
+
try {
|
|
69
|
+
await login();
|
|
70
|
+
wizard.setStep(0, "done", "logged in");
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
wizard.setStep(0, "failed", err instanceof Error ? err.message : "login failed");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Step 2: Model
|
|
77
|
+
wizard.setStep(1, "running", "detecting pi credentials...");
|
|
78
|
+
try {
|
|
79
|
+
const { model } = await initInference();
|
|
80
|
+
detectedModel = model;
|
|
81
|
+
wizard.setStep(1, "done", `${model.provider}/${model.id}`);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
wizard.setStep(1, "failed", err instanceof Error ? err.message : "no models found");
|
|
85
|
+
}
|
|
86
|
+
// Step 3: Agents
|
|
87
|
+
wizard.setStep(2, "running");
|
|
88
|
+
const agents = getAgents();
|
|
89
|
+
if (agents.length > 0) {
|
|
90
|
+
wizard.setStep(2, "done", `${agents.length} agent${agents.length === 1 ? "" : "s"}`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
wizard.setStep(2, "skipped", "no agents yet");
|
|
94
|
+
const shouldCreate = await wizard.askConfirm("You have no agents. Create one now?");
|
|
95
|
+
if (shouldCreate) {
|
|
96
|
+
const name = await askName("New Agent Name", "Give your first agent a name.");
|
|
97
|
+
if (name) {
|
|
98
|
+
createAgent(name);
|
|
99
|
+
wizard.setStep(2, "done", "1 agent created");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
wizard.setStep(2, "skipped", "skipped");
|
|
103
|
+
}
|
|
104
|
+
// Re-show the wizard
|
|
105
|
+
tui.clear();
|
|
106
|
+
tui.addChild(wrapper);
|
|
107
|
+
tui.setFocus(wizard);
|
|
108
|
+
tui.requestRender(true);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Short pause then go to dashboard
|
|
112
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
113
|
+
}
|
|
114
|
+
// ── Sub-screens that return promises ──────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Ask the user for a name via the NameEditorScreen.
|
|
117
|
+
*/
|
|
118
|
+
function askName(title, subtitle, current = "") {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const screen = new NameEditorScreen(tui, current, title, subtitle);
|
|
121
|
+
screen.onSave = (name) => resolve(name);
|
|
122
|
+
screen.onCancel = () => resolve(null);
|
|
123
|
+
setScreen(screen);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Inline agent instructions editor.
|
|
128
|
+
*/
|
|
129
|
+
function editInstructions(agent) {
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
const screen = new AgentEditorScreen(tui, agent.instructions);
|
|
132
|
+
screen.onSave = async (newContent) => {
|
|
133
|
+
try {
|
|
134
|
+
agent.instructions = newContent;
|
|
135
|
+
saveAgent(agent);
|
|
136
|
+
await showMessage("Saved", ["Agent instructions updated."], "success");
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to save"], "error");
|
|
140
|
+
}
|
|
141
|
+
resolve();
|
|
142
|
+
};
|
|
143
|
+
screen.onCancel = () => resolve();
|
|
144
|
+
setScreen(screen);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Agent limits settings screen.
|
|
149
|
+
*/
|
|
150
|
+
function editLimits(agent) {
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const screen = new SettingsScreen(tui, agent.limits);
|
|
153
|
+
screen.onSave = async (newLimits) => {
|
|
154
|
+
try {
|
|
155
|
+
agent.limits = newLimits;
|
|
156
|
+
saveAgent(agent);
|
|
157
|
+
await showMessage("Saved", ["Agent limits updated."], "success");
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to save"], "error");
|
|
161
|
+
}
|
|
162
|
+
resolve();
|
|
163
|
+
};
|
|
164
|
+
screen.onCancel = () => resolve();
|
|
165
|
+
setScreen(screen);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Discovery settings screen.
|
|
170
|
+
*/
|
|
171
|
+
function editDiscovery(agent) {
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
const screen = new DiscoverySettingsScreen(tui, agent.discovery);
|
|
174
|
+
screen.onSave = async (newSettings) => {
|
|
175
|
+
try {
|
|
176
|
+
agent.discovery = newSettings;
|
|
177
|
+
saveAgent(agent);
|
|
178
|
+
await showMessage("Saved", ["Discovery settings updated."], "success");
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to save"], "error");
|
|
182
|
+
}
|
|
183
|
+
resolve();
|
|
184
|
+
};
|
|
185
|
+
screen.onCancel = () => resolve();
|
|
186
|
+
setScreen(screen);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Model picker screen — saves the model pattern to the agent.
|
|
191
|
+
*/
|
|
192
|
+
function editModel(agent) {
|
|
193
|
+
return new Promise(async (resolve) => {
|
|
194
|
+
try {
|
|
195
|
+
await initInference();
|
|
196
|
+
const models = await getAvailableModels();
|
|
197
|
+
if (models.length === 0) {
|
|
198
|
+
await showMessage("No models", ["No models available.", "Run `pi` and /login to authenticate a provider."], "error");
|
|
199
|
+
resolve();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const screen = new ModelPickerScreen(tui, models);
|
|
203
|
+
screen.onSelect = async (model) => {
|
|
204
|
+
agent.modelPattern = model.id;
|
|
205
|
+
saveAgent(agent);
|
|
206
|
+
await showMessage("Model selected", [`${model.provider}/${model.id}`], "success");
|
|
207
|
+
resolve();
|
|
208
|
+
};
|
|
209
|
+
screen.onCancel = () => resolve();
|
|
210
|
+
setScreen(screen);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to load models"], "error");
|
|
214
|
+
resolve();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Ask for an optional session goal before starting.
|
|
220
|
+
*/
|
|
221
|
+
function askSessionGoal() {
|
|
222
|
+
return new Promise((resolve) => {
|
|
223
|
+
const screen = new SessionGoalScreen(tui);
|
|
224
|
+
screen.onSubmit = (goal) => resolve(goal.trim());
|
|
225
|
+
screen.onSkip = () => resolve("");
|
|
226
|
+
setScreen(screen);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Pick which agent to start (or auto-select if only one).
|
|
231
|
+
*/
|
|
232
|
+
function pickAgent() {
|
|
233
|
+
const agents = getAgents();
|
|
234
|
+
if (agents.length === 0) {
|
|
235
|
+
return showMessage("No agents", ["Create an agent first in Manage agents."], "error").then(() => null);
|
|
236
|
+
}
|
|
237
|
+
if (agents.length === 1) {
|
|
238
|
+
return Promise.resolve(agents[0]);
|
|
239
|
+
}
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
const screen = new AgentPickerScreen(tui, agents);
|
|
242
|
+
screen.onSelect = (agent) => resolve(agent);
|
|
243
|
+
screen.onCancel = () => resolve(null);
|
|
244
|
+
setScreen(screen);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// ── Agent config sub-menu ─────────────────────────────────
|
|
248
|
+
async function runAgentConfig(agentId) {
|
|
249
|
+
while (true) {
|
|
250
|
+
const agent = getAgent(agentId);
|
|
251
|
+
if (!agent)
|
|
252
|
+
return; // deleted
|
|
253
|
+
const action = await new Promise((resolve) => {
|
|
254
|
+
const screen = new AgentConfigScreen(tui, agent);
|
|
255
|
+
screen.onAction = resolve;
|
|
256
|
+
setScreen(screen);
|
|
257
|
+
});
|
|
258
|
+
switch (action) {
|
|
259
|
+
case "edit-name": {
|
|
260
|
+
const newName = await askName("Rename Agent", "Enter a new name for this agent.", agent.name);
|
|
261
|
+
if (newName) {
|
|
262
|
+
agent.name = newName;
|
|
263
|
+
saveAgent(agent);
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "edit-instructions":
|
|
268
|
+
await editInstructions(agent);
|
|
269
|
+
break;
|
|
270
|
+
case "edit-discovery":
|
|
271
|
+
await editDiscovery(agent);
|
|
272
|
+
break;
|
|
273
|
+
case "edit-limits":
|
|
274
|
+
await editLimits(agent);
|
|
275
|
+
break;
|
|
276
|
+
case "edit-model":
|
|
277
|
+
await editModel(agent);
|
|
278
|
+
break;
|
|
279
|
+
case "delete": {
|
|
280
|
+
await showMessage("Deleted", [`Agent "${agent.name}" has been deleted.`], "info");
|
|
281
|
+
deleteAgent(agent.id);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
case "back":
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
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
|
+
// ── Running the agent ─────────────────────────────────────
|
|
317
|
+
/**
|
|
318
|
+
* Fetch candidate profiles from the server's /api/discover endpoint.
|
|
319
|
+
*/
|
|
320
|
+
async function fetchCandidates(discovery) {
|
|
321
|
+
const config = getConfig();
|
|
322
|
+
const params = new URLSearchParams();
|
|
323
|
+
if (discovery.onlineOnly)
|
|
324
|
+
params.set("onlineOnly", "true");
|
|
325
|
+
params.set("notContactedInDays", String(discovery.recontactAfterDays));
|
|
326
|
+
if (discovery.includeKeywords.length > 0) {
|
|
327
|
+
params.set("keywords", discovery.includeKeywords.join(","));
|
|
328
|
+
}
|
|
329
|
+
if (discovery.excludeKeywords.length > 0) {
|
|
330
|
+
params.set("excludeKeywords", discovery.excludeKeywords.join(","));
|
|
331
|
+
}
|
|
332
|
+
if (discovery.excludeUsers.length > 0) {
|
|
333
|
+
params.set("excludeUsers", discovery.excludeUsers.join(","));
|
|
334
|
+
}
|
|
335
|
+
params.set("limit", String(discovery.maxScreenPerSession));
|
|
336
|
+
const res = await fetch(`${config.serverUrl}/api/discover?${params}`, {
|
|
337
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
338
|
+
});
|
|
339
|
+
if (!res.ok)
|
|
340
|
+
throw new Error(`Discover API error: ${res.status}`);
|
|
341
|
+
const data = (await res.json());
|
|
342
|
+
return data.profiles;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Start the agent: pick agent → session goal → discover → screen → connect → run.
|
|
346
|
+
*/
|
|
347
|
+
async function runAgentSession() {
|
|
348
|
+
// 1. Pick agent
|
|
349
|
+
const agent = await pickAgent();
|
|
350
|
+
if (!agent)
|
|
351
|
+
return;
|
|
352
|
+
// 2. Init model for this agent
|
|
353
|
+
let activeModel;
|
|
354
|
+
try {
|
|
355
|
+
const { model } = await initInference(agent.modelPattern);
|
|
356
|
+
activeModel = model;
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
await showMessage("Model error", [err instanceof Error ? err.message : "Failed to initialize model"], "error");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// 3. Set agent instructions for inference
|
|
363
|
+
setAgentInstructions(agent.instructions);
|
|
364
|
+
// 4. Ask for session goal
|
|
365
|
+
const sessionGoal = await askSessionGoal();
|
|
366
|
+
const config = getConfig();
|
|
367
|
+
const discovery = agent.discovery;
|
|
368
|
+
const limits = agent.limits;
|
|
369
|
+
// 5. Show the running screen
|
|
370
|
+
const screen = new AgentRunningScreen(tui, activeModel, config.serverUrl, agent.name, sessionGoal);
|
|
371
|
+
const done = new Promise((resolve) => {
|
|
372
|
+
screen.onStop = () => {
|
|
373
|
+
stopAgent();
|
|
374
|
+
resolve();
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
setScreen(screen);
|
|
378
|
+
const log = (line) => screen.addLog(line);
|
|
379
|
+
// 6. Start the WebSocket agent (for incoming requests) with per-agent limits
|
|
380
|
+
startAgent(limits, agent.id, agent.name, log);
|
|
381
|
+
// 7. Discovery + screening (outbound)
|
|
382
|
+
screen.setStatus("discovering candidates...");
|
|
383
|
+
try {
|
|
384
|
+
const candidates = await fetchCandidates(discovery);
|
|
385
|
+
if (candidates.length === 0) {
|
|
386
|
+
log(colors.gray("No candidates match your discovery filters."));
|
|
387
|
+
log(colors.gray("Waiting for incoming conversations..."));
|
|
388
|
+
screen.setStatus("waiting for conversations...");
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
log(colors.cyan(`📡 Found ${candidates.length} candidates, screening...`));
|
|
392
|
+
screen.setStatus(`screening ${candidates.length} profiles...`);
|
|
393
|
+
const results = await screenProfiles(candidates, agent.instructions, sessionGoal, discovery.matchThreshold, discovery.maxScreenPerSession, (screened, total, result) => {
|
|
394
|
+
const icon = result.score >= discovery.matchThreshold
|
|
395
|
+
? colors.green("✓")
|
|
396
|
+
: colors.gray("⊘");
|
|
397
|
+
const name = `@${result.profile.githubUsername}`;
|
|
398
|
+
const scoreStr = result.score >= discovery.matchThreshold
|
|
399
|
+
? colors.green(`${result.score}/10`)
|
|
400
|
+
: colors.gray(`${result.score}/10`);
|
|
401
|
+
log(` ${icon} ${name} ${scoreStr} — ${colors.gray(result.reason)}`);
|
|
402
|
+
screen.setStatus(`screening ${screened}/${total}...`);
|
|
403
|
+
});
|
|
404
|
+
const matches = results.filter((r) => r.score >= discovery.matchThreshold);
|
|
405
|
+
log("");
|
|
406
|
+
if (matches.length === 0) {
|
|
407
|
+
log(colors.yellow("No strong matches found. Waiting for incoming conversations..."));
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
log(colors.green(`Found ${matches.length} match${matches.length === 1 ? "" : "es"}. Starting conversations...`));
|
|
411
|
+
log("");
|
|
412
|
+
// Initiate conversations with matches
|
|
413
|
+
for (const match of matches) {
|
|
414
|
+
if (!match.profile.online) {
|
|
415
|
+
log(colors.gray(` ⊘ @${match.profile.githubUsername} is offline, skipping`));
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
log(colors.cyan(` 🤝 Reaching out to @${match.profile.githubUsername} (${match.score}/10: ${match.reason})`));
|
|
419
|
+
// Send start_conversation via WebSocket
|
|
420
|
+
sendToServer({
|
|
421
|
+
type: "start_conversation",
|
|
422
|
+
withUserId: match.profile.id,
|
|
423
|
+
});
|
|
424
|
+
// Respect cooldown between outreach
|
|
425
|
+
if (limits.cooldownSeconds > 0) {
|
|
426
|
+
await new Promise((r) => setTimeout(r, limits.cooldownSeconds * 1000));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
screen.setStatus(`${matches.length} matches • waiting for conversations...`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
log(colors.red(`Discovery failed: ${err instanceof Error ? err.message : err}`));
|
|
435
|
+
screen.setStatus("waiting for conversations...");
|
|
436
|
+
}
|
|
437
|
+
return done;
|
|
438
|
+
}
|
|
439
|
+
// ── Dashboard loop ────────────────────────────────────────
|
|
440
|
+
async function runDashboard() {
|
|
441
|
+
while (true) {
|
|
442
|
+
const config = getConfig();
|
|
443
|
+
const agents = getAgents();
|
|
444
|
+
const action = await new Promise((resolve) => {
|
|
445
|
+
const dashboard = new DashboardScreen(tui, {
|
|
446
|
+
loggedIn: !!config.token,
|
|
447
|
+
model: detectedModel,
|
|
448
|
+
agentCount: agents.length,
|
|
449
|
+
serverUrl: config.serverUrl,
|
|
450
|
+
});
|
|
451
|
+
dashboard.onAction = resolve;
|
|
452
|
+
setScreen(dashboard);
|
|
453
|
+
});
|
|
454
|
+
switch (action) {
|
|
455
|
+
case "start": {
|
|
456
|
+
const conf = getConfig();
|
|
457
|
+
if (!conf.token) {
|
|
458
|
+
await showMessage("Not logged in", ["Run login first."], "error");
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
await runAgentSession();
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "agents":
|
|
465
|
+
await runAgentList();
|
|
466
|
+
break;
|
|
467
|
+
case "quit":
|
|
468
|
+
shutdown();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function shutdown() {
|
|
474
|
+
tui.stop();
|
|
475
|
+
terminal.clearScreen();
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Main entry point for interactive TUI mode.
|
|
480
|
+
*/
|
|
481
|
+
export async function runInteractive() {
|
|
482
|
+
terminal = new ProcessTerminal();
|
|
483
|
+
tui = new TUI(terminal, true);
|
|
484
|
+
tui.start();
|
|
485
|
+
// Handle Ctrl+C globally
|
|
486
|
+
tui.addInputListener((data) => {
|
|
487
|
+
if (data === "\x03") {
|
|
488
|
+
// Ctrl+C
|
|
489
|
+
shutdown();
|
|
490
|
+
return { consume: true };
|
|
491
|
+
}
|
|
492
|
+
return undefined;
|
|
493
|
+
});
|
|
494
|
+
await runSetup();
|
|
495
|
+
await runDashboard();
|
|
496
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type AgentLimits = {
|
|
2
|
+
maxConcurrentConversations: number;
|
|
3
|
+
maxMessagesPerConversation: number;
|
|
4
|
+
maxResponseLength: number;
|
|
5
|
+
cooldownSeconds: number;
|
|
6
|
+
maxConversationsPerSession: number;
|
|
7
|
+
autoStopIdleMinutes: number;
|
|
8
|
+
};
|
|
9
|
+
export declare const DEFAULT_LIMITS: AgentLimits;
|
|
10
|
+
export type DiscoverySettings = {
|
|
11
|
+
recontactAfterDays: number;
|
|
12
|
+
onlineOnly: boolean;
|
|
13
|
+
includeKeywords: string[];
|
|
14
|
+
excludeKeywords: string[];
|
|
15
|
+
excludeUsers: string[];
|
|
16
|
+
priorityUsers: string[];
|
|
17
|
+
matchThreshold: number;
|
|
18
|
+
maxScreenPerSession: number;
|
|
19
|
+
};
|
|
20
|
+
export declare const DEFAULT_DISCOVERY: DiscoverySettings;
|
|
21
|
+
/**
|
|
22
|
+
* A single agent profile with all its settings.
|
|
23
|
+
*/
|
|
24
|
+
export type AgentProfile = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
instructions: string;
|
|
28
|
+
discovery: DiscoverySettings;
|
|
29
|
+
limits: AgentLimits;
|
|
30
|
+
modelPattern?: string;
|
|
31
|
+
};
|
|
32
|
+
export type Config = {
|
|
33
|
+
token?: string;
|
|
34
|
+
serverUrl: string;
|
|
35
|
+
userId?: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function getConfigDir(): string;
|
|
38
|
+
export declare function getConfig(): Config;
|
|
39
|
+
export declare function saveConfig(config: Config): void;
|
|
40
|
+
/**
|
|
41
|
+
* Migrate legacy single-agent setup to new multi-agent format.
|
|
42
|
+
* Runs once: if ~/.swarmlancer/agent.md exists and no agents dir.
|
|
43
|
+
*/
|
|
44
|
+
export declare function migrateLegacyAgent(): void;
|
|
45
|
+
export declare function getAgents(): AgentProfile[];
|
|
46
|
+
export declare function getAgent(id: string): AgentProfile | undefined;
|
|
47
|
+
export declare function saveAgent(agent: AgentProfile): void;
|
|
48
|
+
export declare function deleteAgent(id: string): void;
|
|
49
|
+
export declare function createAgent(name: string): AgentProfile;
|