swarmlancer 0.1.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 +4 -0
- package/dist/agent.js +95 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +303 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +110 -0
- package/dist/inference.d.ts +9 -0
- package/dist/inference.js +100 -0
- package/dist/login.d.ts +1 -0
- package/dist/login.js +57 -0
- package/dist/profile.d.ts +3 -0
- package/dist/profile.js +32 -0
- package/dist/screens/agent-editor.d.ts +13 -0
- package/dist/screens/agent-editor.js +64 -0
- package/dist/screens/agent-running.d.ts +17 -0
- package/dist/screens/agent-running.js +62 -0
- package/dist/screens/banner.d.ts +6 -0
- package/dist/screens/banner.js +28 -0
- package/dist/screens/dashboard.d.ts +16 -0
- package/dist/screens/dashboard.js +62 -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/profile-editor.d.ts +16 -0
- package/dist/screens/profile-editor.js +129 -0
- package/dist/screens/profile-view.d.ts +11 -0
- package/dist/screens/profile-view.js +38 -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 +34 -0
- package/dist/theme.d.ts +39 -0
- package/dist/theme.js +47 -0
- package/package.json +49 -0
package/README.md
ADDED
package/dist/agent.d.ts
ADDED
package/dist/agent.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { getConfig, getAgentFilePath } from "./config.js";
|
|
3
|
+
import { runInference } from "./inference.js";
|
|
4
|
+
import { colors } from "./theme.js";
|
|
5
|
+
let activeWs = null;
|
|
6
|
+
let alive = false;
|
|
7
|
+
let reconnectTimer = null;
|
|
8
|
+
export function startAgent(log = console.log) {
|
|
9
|
+
const config = getConfig();
|
|
10
|
+
if (!config.token) {
|
|
11
|
+
log(colors.red("Not logged in. Run: swarmlancer login"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const wsUrl = config.serverUrl.replace(/^http/, "ws") + "/ws";
|
|
15
|
+
alive = true;
|
|
16
|
+
function connect() {
|
|
17
|
+
log(colors.cyan(`Connecting to ${wsUrl}...`));
|
|
18
|
+
const ws = new WebSocket(wsUrl);
|
|
19
|
+
activeWs = ws;
|
|
20
|
+
ws.on("open", () => {
|
|
21
|
+
ws.send(JSON.stringify({ type: "auth", token: config.token }));
|
|
22
|
+
});
|
|
23
|
+
ws.on("message", async (raw) => {
|
|
24
|
+
const msg = JSON.parse(raw.toString());
|
|
25
|
+
switch (msg.type) {
|
|
26
|
+
case "authenticated":
|
|
27
|
+
log(colors.green("Agent online"));
|
|
28
|
+
log(colors.gray(`Agent instructions: ${getAgentFilePath()}`));
|
|
29
|
+
log("");
|
|
30
|
+
log(colors.gray("Waiting for conversations..."));
|
|
31
|
+
ws.send(JSON.stringify({ type: "get_online_users" }));
|
|
32
|
+
break;
|
|
33
|
+
case "online_users": {
|
|
34
|
+
const users = msg.users;
|
|
35
|
+
if (users.length > 0) {
|
|
36
|
+
log(colors.cyan(`📡 Online: ${users.map((u) => `${u.displayName} (@${u.githubUsername})`).join(", ")}`));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
log(colors.gray("No other agents online right now."));
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "inference_request": {
|
|
44
|
+
const { requestId, conversationId, systemPrompt, messages } = msg;
|
|
45
|
+
log(colors.yellow(`💬 Inference request for conversation ${conversationId.slice(0, 8)}...`));
|
|
46
|
+
try {
|
|
47
|
+
const response = await runInference(systemPrompt, messages);
|
|
48
|
+
const preview = response.length > 80 ? response.slice(0, 80) + "..." : response;
|
|
49
|
+
log(colors.green(`✓ Response: ${preview}`));
|
|
50
|
+
ws.send(JSON.stringify({
|
|
51
|
+
type: "inference_response",
|
|
52
|
+
requestId,
|
|
53
|
+
content: response,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
58
|
+
log(colors.red(`✗ Inference failed: ${errorMsg}`));
|
|
59
|
+
ws.send(JSON.stringify({
|
|
60
|
+
type: "inference_error",
|
|
61
|
+
requestId,
|
|
62
|
+
error: errorMsg,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "conversation_started": {
|
|
68
|
+
const { conversationId, withUser } = msg;
|
|
69
|
+
log(colors.brightGreen(`🤝 Conversation started with ${withUser.displayName} (@${withUser.githubUsername})`));
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "error":
|
|
73
|
+
log(colors.red(`Server error: ${msg.message}`));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
ws.on("close", () => {
|
|
78
|
+
if (alive) {
|
|
79
|
+
log(colors.yellow("Disconnected. Reconnecting in 5s..."));
|
|
80
|
+
reconnectTimer = setTimeout(connect, 5000);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
ws.on("error", (err) => {
|
|
84
|
+
log(colors.red(`WebSocket error: ${err.message}`));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
connect();
|
|
88
|
+
}
|
|
89
|
+
export function stopAgent() {
|
|
90
|
+
alive = false;
|
|
91
|
+
if (reconnectTimer)
|
|
92
|
+
clearTimeout(reconnectTimer);
|
|
93
|
+
activeWs?.close();
|
|
94
|
+
activeWs = null;
|
|
95
|
+
}
|
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { ProcessTerminal, TUI, Container } from "@mariozechner/pi-tui";
|
|
2
|
+
import { getConfig, ensureAgentFile, getAgentFilePath, getAgentInstructions, saveAgentInstructions, } from "./config.js";
|
|
3
|
+
import { login } from "./login.js";
|
|
4
|
+
import { initInference, getAvailableModels } from "./inference.js";
|
|
5
|
+
import { getProfile, isProfileComplete, saveProfile, } from "./profile.js";
|
|
6
|
+
import { startAgent, stopAgent } from "./agent.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 { ModelPickerScreen } from "./screens/model-picker.js";
|
|
11
|
+
import { ProfileEditorScreen } from "./screens/profile-editor.js";
|
|
12
|
+
import { ProfileViewScreen } from "./screens/profile-view.js";
|
|
13
|
+
import { AgentEditorScreen } from "./screens/agent-editor.js";
|
|
14
|
+
import { AgentRunningScreen } from "./screens/agent-running.js";
|
|
15
|
+
import { MessageScreen } from "./screens/message.js";
|
|
16
|
+
let terminal;
|
|
17
|
+
let tui;
|
|
18
|
+
let selectedModel;
|
|
19
|
+
/**
|
|
20
|
+
* Switch the active screen by replacing the TUI's child + focus.
|
|
21
|
+
*/
|
|
22
|
+
function setScreen(component) {
|
|
23
|
+
tui.clear();
|
|
24
|
+
tui.addChild(component);
|
|
25
|
+
tui.setFocus(component);
|
|
26
|
+
tui.requestRender(true);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Show a temporary message and wait for any key.
|
|
30
|
+
*/
|
|
31
|
+
function showMessage(title, lines, style = "info") {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const screen = new MessageScreen(tui, title, lines, style);
|
|
34
|
+
screen.onClose = () => resolve();
|
|
35
|
+
setScreen(screen);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run the setup wizard: check auth, model, profile, agent.
|
|
40
|
+
*/
|
|
41
|
+
async function runSetup() {
|
|
42
|
+
const banner = new BannerComponent();
|
|
43
|
+
const wizard = new SetupWizardScreen(tui);
|
|
44
|
+
// Show banner + wizard together
|
|
45
|
+
const wrapper = new Container();
|
|
46
|
+
wrapper.addChild(banner);
|
|
47
|
+
wrapper.addChild(wizard);
|
|
48
|
+
tui.clear();
|
|
49
|
+
tui.addChild(wrapper);
|
|
50
|
+
tui.setFocus(wizard);
|
|
51
|
+
tui.requestRender(true);
|
|
52
|
+
// Step 1: Auth
|
|
53
|
+
const config = getConfig();
|
|
54
|
+
wizard.setStep(0, "running");
|
|
55
|
+
if (config.token) {
|
|
56
|
+
wizard.setStep(0, "done", "logged in");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
wizard.setStep(0, "running", "not logged in — opening browser...");
|
|
60
|
+
try {
|
|
61
|
+
await login();
|
|
62
|
+
wizard.setStep(0, "done", "logged in");
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
wizard.setStep(0, "failed", err instanceof Error ? err.message : "login failed");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Step 2: Model
|
|
69
|
+
wizard.setStep(1, "running", "detecting pi credentials...");
|
|
70
|
+
try {
|
|
71
|
+
const { model } = await initInference();
|
|
72
|
+
selectedModel = model;
|
|
73
|
+
wizard.setStep(1, "done", `${model.provider}/${model.id}`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
wizard.setStep(1, "failed", err instanceof Error ? err.message : "no models found");
|
|
77
|
+
}
|
|
78
|
+
// Step 3: Profile
|
|
79
|
+
wizard.setStep(2, "running");
|
|
80
|
+
const profile = await getProfile();
|
|
81
|
+
if (profile && isProfileComplete(profile)) {
|
|
82
|
+
wizard.setStep(2, "done", "complete");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
wizard.setStep(2, "skipped", "incomplete");
|
|
86
|
+
const shouldEdit = await wizard.askConfirm("Your profile is incomplete. Set it up now?");
|
|
87
|
+
if (shouldEdit) {
|
|
88
|
+
await runProfileEditor();
|
|
89
|
+
wizard.setStep(2, "done", "updated");
|
|
90
|
+
// Re-show the wizard
|
|
91
|
+
tui.clear();
|
|
92
|
+
tui.addChild(wrapper);
|
|
93
|
+
tui.setFocus(wizard);
|
|
94
|
+
tui.requestRender(true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Step 4: Agent instructions
|
|
98
|
+
wizard.setStep(3, "running");
|
|
99
|
+
ensureAgentFile();
|
|
100
|
+
const agentMd = getAgentInstructions();
|
|
101
|
+
if (agentMd && !agentMd.includes("(Describe yourself")) {
|
|
102
|
+
wizard.setStep(3, "done", "configured");
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
wizard.setStep(3, "skipped", "using defaults");
|
|
106
|
+
const shouldEdit = await wizard.askConfirm("Edit your agent instructions now?");
|
|
107
|
+
if (shouldEdit) {
|
|
108
|
+
await runAgentEditor();
|
|
109
|
+
wizard.setStep(3, "done", "updated");
|
|
110
|
+
// Re-show the wizard
|
|
111
|
+
tui.clear();
|
|
112
|
+
tui.addChild(wrapper);
|
|
113
|
+
tui.setFocus(wizard);
|
|
114
|
+
tui.requestRender(true);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Short pause then go to dashboard
|
|
118
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Inline agent instructions editor.
|
|
122
|
+
*/
|
|
123
|
+
function runAgentEditor() {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
ensureAgentFile();
|
|
126
|
+
const content = getAgentInstructions();
|
|
127
|
+
const screen = new AgentEditorScreen(tui, content);
|
|
128
|
+
screen.onSave = async (newContent) => {
|
|
129
|
+
try {
|
|
130
|
+
saveAgentInstructions(newContent);
|
|
131
|
+
await showMessage("Saved", ["Agent instructions updated."], "success");
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to save"], "error");
|
|
135
|
+
}
|
|
136
|
+
resolve();
|
|
137
|
+
};
|
|
138
|
+
screen.onCancel = () => resolve();
|
|
139
|
+
setScreen(screen);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Profile editor screen.
|
|
144
|
+
*/
|
|
145
|
+
function runProfileEditor() {
|
|
146
|
+
return new Promise(async (resolve) => {
|
|
147
|
+
const profile = await getProfile();
|
|
148
|
+
if (!profile) {
|
|
149
|
+
await showMessage("Error", ["Could not load profile."], "error");
|
|
150
|
+
resolve();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const screen = new ProfileEditorScreen(tui, profile);
|
|
154
|
+
screen.onSave = async (data) => {
|
|
155
|
+
try {
|
|
156
|
+
await saveProfile(data);
|
|
157
|
+
await showMessage("Success", ["Profile 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
|
+
* Model picker screen.
|
|
170
|
+
*/
|
|
171
|
+
function runModelPicker() {
|
|
172
|
+
return new Promise(async (resolve) => {
|
|
173
|
+
try {
|
|
174
|
+
await initInference();
|
|
175
|
+
const models = await getAvailableModels();
|
|
176
|
+
if (models.length === 0) {
|
|
177
|
+
await showMessage("No models", ["No models available.", "Run `pi` and /login to authenticate a provider."], "error");
|
|
178
|
+
resolve();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const screen = new ModelPickerScreen(tui, models);
|
|
182
|
+
screen.onSelect = async (model) => {
|
|
183
|
+
selectedModel = model;
|
|
184
|
+
await initInference(model.id);
|
|
185
|
+
await showMessage("Model selected", [`${model.provider}/${model.id}`], "success");
|
|
186
|
+
resolve();
|
|
187
|
+
};
|
|
188
|
+
screen.onCancel = () => resolve();
|
|
189
|
+
setScreen(screen);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
await showMessage("Error", [err instanceof Error ? err.message : "Failed to load models"], "error");
|
|
193
|
+
resolve();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Profile view screen.
|
|
199
|
+
*/
|
|
200
|
+
function runProfileView() {
|
|
201
|
+
return new Promise(async (resolve) => {
|
|
202
|
+
const profile = await getProfile();
|
|
203
|
+
if (!profile) {
|
|
204
|
+
await showMessage("Error", ["Could not load profile."], "error");
|
|
205
|
+
resolve();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const screen = new ProfileViewScreen(tui, profile);
|
|
209
|
+
screen.onClose = () => resolve();
|
|
210
|
+
setScreen(screen);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Start the agent and show the live log screen.
|
|
215
|
+
*/
|
|
216
|
+
function runAgent() {
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
const config = getConfig();
|
|
219
|
+
const screen = new AgentRunningScreen(tui, selectedModel, config.serverUrl, getAgentFilePath());
|
|
220
|
+
screen.onStop = () => {
|
|
221
|
+
stopAgent();
|
|
222
|
+
resolve();
|
|
223
|
+
};
|
|
224
|
+
setScreen(screen);
|
|
225
|
+
// Start the agent with the log callback
|
|
226
|
+
startAgent((line) => {
|
|
227
|
+
screen.addLog(line);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Main dashboard loop.
|
|
233
|
+
*/
|
|
234
|
+
async function runDashboard() {
|
|
235
|
+
while (true) {
|
|
236
|
+
const config = getConfig();
|
|
237
|
+
const agentMd = getAgentInstructions();
|
|
238
|
+
const action = await new Promise((resolve) => {
|
|
239
|
+
const dashboard = new DashboardScreen(tui, {
|
|
240
|
+
loggedIn: !!config.token,
|
|
241
|
+
model: selectedModel,
|
|
242
|
+
agentConfigured: !!(agentMd && !agentMd.includes("(Describe yourself")),
|
|
243
|
+
serverUrl: config.serverUrl,
|
|
244
|
+
});
|
|
245
|
+
dashboard.onAction = resolve;
|
|
246
|
+
setScreen(dashboard);
|
|
247
|
+
});
|
|
248
|
+
switch (action) {
|
|
249
|
+
case "start": {
|
|
250
|
+
if (!selectedModel) {
|
|
251
|
+
await showMessage("No model", ["Select a model first."], "error");
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
const conf = getConfig();
|
|
255
|
+
if (!conf.token) {
|
|
256
|
+
await showMessage("Not logged in", ["Run login first."], "error");
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
await runAgent();
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case "profile-edit":
|
|
263
|
+
await runProfileEditor();
|
|
264
|
+
break;
|
|
265
|
+
case "agent-edit":
|
|
266
|
+
await runAgentEditor();
|
|
267
|
+
break;
|
|
268
|
+
case "model-pick":
|
|
269
|
+
await runModelPicker();
|
|
270
|
+
break;
|
|
271
|
+
case "profile-view":
|
|
272
|
+
await runProfileView();
|
|
273
|
+
break;
|
|
274
|
+
case "quit":
|
|
275
|
+
shutdown();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function shutdown() {
|
|
281
|
+
tui.stop();
|
|
282
|
+
terminal.clearScreen();
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Main entry point for interactive TUI mode.
|
|
287
|
+
*/
|
|
288
|
+
export async function runInteractive() {
|
|
289
|
+
terminal = new ProcessTerminal();
|
|
290
|
+
tui = new TUI(terminal, true);
|
|
291
|
+
tui.start();
|
|
292
|
+
// Handle Ctrl+C globally
|
|
293
|
+
tui.addInputListener((data) => {
|
|
294
|
+
if (data === "\x03") {
|
|
295
|
+
// Ctrl+C
|
|
296
|
+
shutdown();
|
|
297
|
+
return { consume: true };
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
300
|
+
});
|
|
301
|
+
await runSetup();
|
|
302
|
+
await runDashboard();
|
|
303
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Config = {
|
|
2
|
+
token?: string;
|
|
3
|
+
serverUrl: string;
|
|
4
|
+
userId?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function getConfigDir(): string;
|
|
7
|
+
export declare function getConfig(): Config;
|
|
8
|
+
export declare function saveConfig(config: Config): void;
|
|
9
|
+
export declare function getAgentInstructions(): string;
|
|
10
|
+
export declare function getAgentFilePath(): string;
|
|
11
|
+
export declare function saveAgentInstructions(content: string): void;
|
|
12
|
+
export declare function ensureAgentFile(): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
const CONFIG_DIR = join(homedir(), ".swarmlancer");
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
6
|
+
const AGENT_FILE = join(CONFIG_DIR, "agent.md");
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
serverUrl: "https://swarmlancer.com",
|
|
9
|
+
};
|
|
10
|
+
export function getConfigDir() {
|
|
11
|
+
return CONFIG_DIR;
|
|
12
|
+
}
|
|
13
|
+
export function getConfig() {
|
|
14
|
+
try {
|
|
15
|
+
if (existsSync(CONFIG_FILE)) {
|
|
16
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
return { ...DEFAULT_CONFIG };
|
|
21
|
+
}
|
|
22
|
+
export function saveConfig(config) {
|
|
23
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
25
|
+
}
|
|
26
|
+
export function getAgentInstructions() {
|
|
27
|
+
try {
|
|
28
|
+
if (existsSync(AGENT_FILE)) {
|
|
29
|
+
return readFileSync(AGENT_FILE, "utf-8").trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
export function getAgentFilePath() {
|
|
36
|
+
return AGENT_FILE;
|
|
37
|
+
}
|
|
38
|
+
export function saveAgentInstructions(content) {
|
|
39
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
40
|
+
writeFileSync(AGENT_FILE, content);
|
|
41
|
+
}
|
|
42
|
+
export function ensureAgentFile() {
|
|
43
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
44
|
+
if (!existsSync(AGENT_FILE)) {
|
|
45
|
+
writeFileSync(AGENT_FILE, `# My Agent Instructions
|
|
46
|
+
|
|
47
|
+
## About me
|
|
48
|
+
(Describe yourself — what you do, what you're building)
|
|
49
|
+
|
|
50
|
+
## What I'm looking for
|
|
51
|
+
(What kind of people or collaborations interest you?)
|
|
52
|
+
|
|
53
|
+
## How my agent should behave
|
|
54
|
+
- Be direct and genuine
|
|
55
|
+
- If there's no real connection, wrap up quickly
|
|
56
|
+
- If there's potential, suggest connecting on GitHub
|
|
57
|
+
- Keep responses concise
|
|
58
|
+
- Max 8-10 messages per conversation
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInteractive } from "./app.js";
|
|
3
|
+
import { login } from "./login.js";
|
|
4
|
+
import { getConfig, ensureAgentFile, getAgentFilePath } from "./config.js";
|
|
5
|
+
import { initInference, getAvailableModels } from "./inference.js";
|
|
6
|
+
import { startAgent } from "./agent.js";
|
|
7
|
+
import { getProfile } from "./profile.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0];
|
|
11
|
+
// No args → full TUI interactive mode
|
|
12
|
+
if (!command) {
|
|
13
|
+
await runInteractive();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Parse flags for direct commands
|
|
17
|
+
const flags = {};
|
|
18
|
+
for (let i = 1; i < args.length; i++) {
|
|
19
|
+
if (args[i].startsWith("--") && args[i + 1]) {
|
|
20
|
+
flags[args[i].slice(2)] = args[i + 1];
|
|
21
|
+
i++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Direct commands for scripting / CI
|
|
25
|
+
switch (command) {
|
|
26
|
+
case "login":
|
|
27
|
+
await login();
|
|
28
|
+
break;
|
|
29
|
+
case "profile":
|
|
30
|
+
if (args[1] === "edit") {
|
|
31
|
+
console.log("Use interactive mode for profile editing: swarmlancer");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const profile = await getProfile();
|
|
35
|
+
if (profile) {
|
|
36
|
+
console.log(`Name: ${profile.displayName || "(not set)"}`);
|
|
37
|
+
console.log(`GitHub: @${profile.githubUsername}`);
|
|
38
|
+
console.log(`Bio: ${profile.bio || "(not set)"}`);
|
|
39
|
+
console.log(`Skills: ${profile.skills || "(not set)"}`);
|
|
40
|
+
console.log(`Projects: ${profile.projects || "(not set)"}`);
|
|
41
|
+
console.log(`Looking for: ${profile.lookingFor || "(not set)"}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log("Could not load profile.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case "agent":
|
|
49
|
+
console.log(`Agent instructions file: ${getAgentFilePath()}`);
|
|
50
|
+
console.log(`Use interactive mode to edit: swarmlancer`);
|
|
51
|
+
break;
|
|
52
|
+
case "models": {
|
|
53
|
+
console.log("Available models (from pi credentials):\n");
|
|
54
|
+
try {
|
|
55
|
+
await initInference();
|
|
56
|
+
const models = await getAvailableModels();
|
|
57
|
+
for (const m of models)
|
|
58
|
+
console.log(` ${m.provider}/${m.id}`);
|
|
59
|
+
console.log(`\nUse: swarmlancer start --model <pattern>`);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error(err instanceof Error ? err.message : err);
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "start": {
|
|
67
|
+
ensureAgentFile();
|
|
68
|
+
const config = getConfig();
|
|
69
|
+
if (!config.token) {
|
|
70
|
+
console.error("Not logged in. Run: swarmlancer login");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const { model } = await initInference(flags.model);
|
|
75
|
+
console.log(`Model: ${model.provider}/${model.id}`);
|
|
76
|
+
console.log(`Server: ${config.serverUrl}`);
|
|
77
|
+
console.log(`Agent: ${getAgentFilePath()}`);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error(err instanceof Error ? err.message : err);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
startAgent();
|
|
84
|
+
// Keep running until Ctrl+C
|
|
85
|
+
process.on("SIGINT", () => {
|
|
86
|
+
console.log("\nAgent shutting down...");
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
console.log(`
|
|
93
|
+
swarmlancer — your agent, your rules
|
|
94
|
+
|
|
95
|
+
Usage: swarmlancer [command]
|
|
96
|
+
|
|
97
|
+
(no command) Interactive TUI — guided setup and menu
|
|
98
|
+
login Sign in with GitHub
|
|
99
|
+
profile View your public profile
|
|
100
|
+
agent Edit agent instructions
|
|
101
|
+
models List available LLM models
|
|
102
|
+
start Start your agent
|
|
103
|
+
start --model <pattern> Start with a specific model
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
main().catch((err) => {
|
|
108
|
+
console.error(err);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Model, Api } from "@mariozechner/pi-ai";
|
|
2
|
+
export declare function initInference(modelPattern?: string): Promise<{
|
|
3
|
+
model: Model<Api>;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function getAvailableModels(): Model<Api>[];
|
|
6
|
+
export declare function runInference(systemPrompt: string, messages: {
|
|
7
|
+
role: "user" | "assistant";
|
|
8
|
+
content: string;
|
|
9
|
+
}[]): Promise<string>;
|