swarmlancer-cli 0.6.1 → 0.7.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/app.js CHANGED
@@ -2,7 +2,8 @@ import { ProcessTerminal, TUI } 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
4
  import { setActiveModel, setAgentInstructions } from "./inference.js";
5
- import { resolveModel, getAvailableModels, isProviderAuthenticated, saveProviderKey, PROVIDERS, } from "./providers.js";
5
+ import { resolveModel, getAvailableModels, getProviderList, } from "./providers.js";
6
+ import { LoginDialogComponent } from "@mariozechner/pi-coding-agent";
6
7
  import { startAgent, stopAgent, sendToServer } from "./agent.js";
7
8
  import { colors } from "./theme.js";
8
9
  import { DashboardScreen } from "./screens/dashboard.js";
@@ -91,7 +92,7 @@ function editDiscovery(agent) {
91
92
  }
92
93
  function editModel(agent) {
93
94
  return new Promise(async (resolve) => {
94
- const models = getAvailableModels();
95
+ const models = await getAvailableModels();
95
96
  if (models.length === 0) {
96
97
  await showMessage("No models", ["No provider API keys configured.", "Add one in Manage → Providers."], "error");
97
98
  resolve();
@@ -208,10 +209,10 @@ async function runSwarm() {
208
209
  async function runProviders() {
209
210
  let idx = 0;
210
211
  while (true) {
211
- const items = PROVIDERS.map((p) => {
212
- const loggedIn = isProviderAuthenticated(p.id);
213
- const prefix = loggedIn ? "→ " : " ";
214
- const suffix = loggedIn ? " ✓ logged in" : "";
212
+ const providers = await getProviderList();
213
+ const items = providers.map((p) => {
214
+ const prefix = p.loggedIn ? "→ " : " ";
215
+ const suffix = p.loggedIn ? " ✓" : "";
215
216
  return { value: p.id, label: `${prefix}${p.label}${suffix}`, description: "" };
216
217
  });
217
218
  const result = await new Promise((resolve) => {
@@ -223,18 +224,13 @@ async function runProviders() {
223
224
  if (!result.value)
224
225
  return;
225
226
  idx = result.index;
226
- const provider = PROVIDERS.find((p) => p.id === result.value);
227
- if (!provider)
228
- continue;
229
- if (!provider.keyBased) {
230
- await showMessage("Coming soon", [`${provider.label} is not yet supported.`], "info");
231
- continue;
232
- }
233
- const key = await askName(provider.label);
234
- if (key) {
235
- saveProviderKey(provider.id, key);
236
- await showMessage("Saved", [`${provider.label} API key saved.`], "success");
237
- }
227
+ // Use pi's LoginDialogComponent for the OAuth/login flow
228
+ await new Promise((resolve) => {
229
+ const dialog = new LoginDialogComponent(tui, result.value, (success, message) => {
230
+ resolve();
231
+ });
232
+ setScreen(dialog);
233
+ });
238
234
  }
239
235
  }
240
236
  // ── Manage (top) ──────────────────────────────────────────
@@ -294,14 +290,14 @@ async function goOnline() {
294
290
  const agent = await pickAgent();
295
291
  if (!agent)
296
292
  return;
297
- const resolved = resolveModel(agent.modelPattern);
293
+ const resolved = await resolveModel(agent.modelPattern);
298
294
  if (!resolved) {
299
295
  await showMessage("No model", ["No provider API keys configured.", "Add one in Manage → Providers."], "error");
300
296
  return;
301
297
  }
302
- setActiveModel(resolved.provider, resolved.model);
298
+ setActiveModel(resolved.provider, resolved.id);
303
299
  setAgentInstructions(agent.instructions);
304
- const modelInfo = { provider: resolved.provider, id: resolved.model, name: resolved.model };
300
+ const modelInfo = { provider: resolved.provider, id: resolved.id, name: resolved.id };
305
301
  const config = getConfig();
306
302
  const screen = new AgentRunningScreen(tui, modelInfo, config.serverUrl, agent.name);
307
303
  const done = new Promise((resolve) => {
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { runInteractive } from "./app.js";
3
3
  import { login } from "./login.js";
4
4
  import { getConfig, getAgents, migrateLegacyAgent } from "./config.js";
5
5
  import { resolveModel, getAvailableModels } from "./providers.js";
6
+ // Note: resolveModel and getAvailableModels are now async
6
7
  import { setActiveModel, setAgentInstructions } from "./inference.js";
7
8
  import { startAgent } from "./agent.js";
8
9
  async function main() {
@@ -47,7 +48,7 @@ async function main() {
47
48
  break;
48
49
  }
49
50
  case "models": {
50
- const models = getAvailableModels();
51
+ const models = await getAvailableModels();
51
52
  if (models.length === 0) {
52
53
  console.log("No models available. Add a provider API key: swarmlancer");
53
54
  }
@@ -82,15 +83,15 @@ async function main() {
82
83
  agent = match;
83
84
  }
84
85
  const modelPattern = flags.model || agent.modelPattern;
85
- const resolved = resolveModel(modelPattern);
86
+ const resolved = await resolveModel(modelPattern);
86
87
  if (!resolved) {
87
88
  console.error("No model available. Add a provider API key: swarmlancer");
88
89
  process.exit(1);
89
90
  }
90
- setActiveModel(resolved.provider, resolved.model);
91
+ setActiveModel(resolved.provider, resolved.id);
91
92
  setAgentInstructions(agent.instructions);
92
93
  console.log(`Agent: ${agent.name}`);
93
- console.log(`Model: ${resolved.provider}/${resolved.model}`);
94
+ console.log(`Model: ${resolved.provider}/${resolved.id}`);
94
95
  console.log(`Server: ${config.serverUrl}`);
95
96
  startAgent(agent.limits, agent.id, agent.name);
96
97
  process.on("SIGINT", () => { console.log("\nAgent shutting down..."); process.exit(0); });
@@ -1,6 +1,5 @@
1
- import { type ProviderId } from "./providers.js";
2
1
  export declare function setAgentInstructions(instructions: string): void;
3
- export declare function setActiveModel(provider: ProviderId, model: string): void;
2
+ export declare function setActiveModel(provider: string, model: string): void;
4
3
  type Message = {
5
4
  role: "user" | "assistant";
6
5
  content: string;
package/dist/inference.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getProviderKey } from "./providers.js";
1
+ import { getApiKey } from "./providers.js";
2
2
  let currentProvider;
3
3
  let currentModel;
4
4
  let currentAgentInstructions = "";
@@ -11,27 +11,33 @@ export function setActiveModel(provider, model) {
11
11
  }
12
12
  export async function runInference(systemPrompt, messages) {
13
13
  if (!currentProvider || !currentModel) {
14
- throw new Error("No model configured. Add a provider API key and select a model.");
14
+ throw new Error("No model configured. Add a provider and select a model.");
15
15
  }
16
16
  const fullSystemPrompt = currentAgentInstructions
17
17
  ? `${currentAgentInstructions}\n\n---\n\n${systemPrompt}`
18
18
  : systemPrompt;
19
- switch (currentProvider) {
20
- case "anthropic":
21
- return callAnthropic(fullSystemPrompt, messages, currentModel);
22
- case "openai":
23
- return callOpenAI(fullSystemPrompt, messages, currentModel);
24
- case "google":
25
- return callGoogle(fullSystemPrompt, messages, currentModel);
26
- default:
27
- throw new Error(`Provider "${currentProvider}" does not support direct API calls yet.`);
19
+ const apiKey = await getApiKey(currentProvider);
20
+ if (!apiKey) {
21
+ throw new Error(`No API key for provider "${currentProvider}". Login first.`);
28
22
  }
23
+ // Route to the correct API based on provider
24
+ if (currentProvider === "anthropic" || currentProvider === "claude-pro-max") {
25
+ return callAnthropic(fullSystemPrompt, messages, currentModel, apiKey);
26
+ }
27
+ if (currentProvider === "openai" || currentProvider === "openai-codex") {
28
+ return callOpenAI(fullSystemPrompt, messages, currentModel, apiKey);
29
+ }
30
+ if (currentProvider === "google" || currentProvider === "google-gemini-cli" || currentProvider === "google-antigravity") {
31
+ return callGoogle(fullSystemPrompt, messages, currentModel, apiKey);
32
+ }
33
+ if (currentProvider === "github-copilot") {
34
+ return callOpenAI(fullSystemPrompt, messages, currentModel, apiKey);
35
+ }
36
+ // Fallback: try OpenAI-compatible API
37
+ return callOpenAI(fullSystemPrompt, messages, currentModel, apiKey);
29
38
  }
30
39
  // ── Anthropic ─────────────────────────────────────────────
31
- async function callAnthropic(system, messages, model) {
32
- const apiKey = getProviderKey("anthropic");
33
- if (!apiKey)
34
- throw new Error("Anthropic API key not configured");
40
+ async function callAnthropic(system, messages, model, apiKey) {
35
41
  const res = await fetch("https://api.anthropic.com/v1/messages", {
36
42
  method: "POST",
37
43
  headers: {
@@ -39,25 +45,15 @@ async function callAnthropic(system, messages, model) {
39
45
  "x-api-key": apiKey,
40
46
  "anthropic-version": "2023-06-01",
41
47
  },
42
- body: JSON.stringify({
43
- model,
44
- max_tokens: 2048,
45
- system,
46
- messages,
47
- }),
48
+ body: JSON.stringify({ model, max_tokens: 2048, system, messages }),
48
49
  });
49
- if (!res.ok) {
50
- const body = await res.text();
51
- throw new Error(`Anthropic ${res.status}: ${body.slice(0, 200)}`);
52
- }
50
+ if (!res.ok)
51
+ throw new Error(`Anthropic ${res.status}: ${(await res.text()).slice(0, 200)}`);
53
52
  const data = (await res.json());
54
53
  return data.content?.[0]?.text || "(no response)";
55
54
  }
56
- // ── OpenAI ────────────────────────────────────────────────
57
- async function callOpenAI(system, messages, model) {
58
- const apiKey = getProviderKey("openai");
59
- if (!apiKey)
60
- throw new Error("OpenAI API key not configured");
55
+ // ── OpenAI / Copilot ──────────────────────────────────────
56
+ async function callOpenAI(system, messages, model, apiKey) {
61
57
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
62
58
  method: "POST",
63
59
  headers: {
@@ -69,18 +65,13 @@ async function callOpenAI(system, messages, model) {
69
65
  messages: [{ role: "system", content: system }, ...messages],
70
66
  }),
71
67
  });
72
- if (!res.ok) {
73
- const body = await res.text();
74
- throw new Error(`OpenAI ${res.status}: ${body.slice(0, 200)}`);
75
- }
68
+ if (!res.ok)
69
+ throw new Error(`OpenAI ${res.status}: ${(await res.text()).slice(0, 200)}`);
76
70
  const data = (await res.json());
77
71
  return data.choices?.[0]?.message?.content || "(no response)";
78
72
  }
79
73
  // ── Google Gemini ─────────────────────────────────────────
80
- async function callGoogle(system, messages, model) {
81
- const apiKey = getProviderKey("google");
82
- if (!apiKey)
83
- throw new Error("Google API key not configured");
74
+ async function callGoogle(system, messages, model, apiKey) {
84
75
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
85
76
  const contents = messages.map((m) => ({
86
77
  role: m.role === "assistant" ? "model" : "user",
@@ -94,10 +85,8 @@ async function callGoogle(system, messages, model) {
94
85
  contents,
95
86
  }),
96
87
  });
97
- if (!res.ok) {
98
- const body = await res.text();
99
- throw new Error(`Google ${res.status}: ${body.slice(0, 200)}`);
100
- }
88
+ if (!res.ok)
89
+ throw new Error(`Google ${res.status}: ${(await res.text()).slice(0, 200)}`);
101
90
  const data = (await res.json());
102
91
  return data.candidates?.[0]?.content?.parts?.[0]?.text || "(no response)";
103
92
  }
@@ -1,30 +1,46 @@
1
+ import { AuthStorage } from "@mariozechner/pi-coding-agent";
1
2
  export type ModelInfo = {
2
3
  provider: string;
3
4
  id: string;
4
5
  name: string;
5
6
  };
6
- export type ProviderId = "anthropic" | "openai" | "google" | "copilot" | "antigravity";
7
- export type ProviderDef = {
8
- id: ProviderId;
7
+ /**
8
+ * Get the AuthStorage instance (for login/logout flows).
9
+ */
10
+ export declare function getAuthStorage(): AuthStorage;
11
+ /**
12
+ * Get all available models (from providers the user has credentials for).
13
+ */
14
+ export declare function getAvailableModels(): Promise<ModelInfo[]>;
15
+ /**
16
+ * Get all known models (regardless of auth status).
17
+ */
18
+ export declare function getAllModels(): ModelInfo[];
19
+ /**
20
+ * Get all OAuth provider IDs that the user can /login to.
21
+ */
22
+ export declare function getOAuthProviders(): {
23
+ id: string;
24
+ name: string;
25
+ loggedIn: boolean;
26
+ }[];
27
+ /**
28
+ * Get list of providers with auth status for display.
29
+ */
30
+ export declare function getProviderList(): Promise<{
31
+ id: string;
9
32
  label: string;
10
- keyBased: boolean;
11
- models: {
12
- id: string;
13
- name: string;
14
- }[];
15
- };
16
- export declare const PROVIDERS: ProviderDef[];
17
- export declare function getProviderKey(id: ProviderId): string | undefined;
18
- export declare function saveProviderKey(id: ProviderId, apiKey: string): void;
19
- export declare function removeProviderKey(id: ProviderId): void;
20
- export declare function getAuthenticatedProviderIds(): ProviderId[];
21
- export declare function isProviderAuthenticated(id: ProviderId): boolean;
22
- export declare function getAvailableModels(): ModelInfo[];
23
- /**
24
- * Resolve a model pattern like "anthropic/claude-haiku-4-20250514" or just "claude-haiku-4-20250514"
25
- * into a { provider, model } pair. Falls back to the cheapest available model.
26
- */
27
- export declare function resolveModel(modelPattern?: string): {
28
- provider: ProviderId;
29
- model: string;
30
- } | undefined;
33
+ loggedIn: boolean;
34
+ }[]>;
35
+ /**
36
+ * Check if a provider has any auth configured.
37
+ */
38
+ export declare function isProviderAuthenticated(providerId: string): boolean;
39
+ /**
40
+ * Get the API key for a provider (resolves OAuth tokens, env vars, auth.json).
41
+ */
42
+ export declare function getApiKey(providerId: string): Promise<string | undefined>;
43
+ /**
44
+ * Resolve a model pattern to a specific available model.
45
+ */
46
+ export declare function resolveModel(modelPattern?: string): Promise<ModelInfo | undefined>;
package/dist/providers.js CHANGED
@@ -1,102 +1,112 @@
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 PROVIDERS_FILE = join(CONFIG_DIR, "providers.json");
6
- // ── Known providers ───────────────────────────────────────
7
- export const PROVIDERS = [
8
- {
9
- id: "anthropic",
10
- label: "Anthropic (Claude Pro/Max)",
11
- keyBased: true,
12
- models: [
13
- { id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4" },
14
- { id: "claude-haiku-4-20250514", name: "Claude Haiku 4" },
15
- ],
16
- },
17
- {
18
- id: "openai",
19
- label: "ChatGPT Plus/Pro (Codex Subscription)",
20
- keyBased: true,
21
- models: [
22
- { id: "gpt-4o", name: "GPT-4o" },
23
- { id: "gpt-4o-mini", name: "GPT-4o Mini" },
24
- ],
25
- },
26
- {
27
- id: "google",
28
- label: "Google Cloud Code Assist (Gemini CLI)",
29
- keyBased: true,
30
- models: [
31
- { id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
32
- { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
33
- ],
34
- },
35
- {
36
- id: "copilot",
37
- label: "GitHub Copilot",
38
- keyBased: false,
39
- models: [],
40
- },
41
- {
42
- id: "antigravity",
43
- label: "Antigravity (Gemini 3, Claude, GPT-OSS)",
44
- keyBased: false,
45
- models: [],
46
- },
47
- ];
48
- function readAll() {
49
- try {
50
- if (existsSync(PROVIDERS_FILE)) {
51
- return JSON.parse(readFileSync(PROVIDERS_FILE, "utf-8"));
52
- }
1
+ import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
2
+ // ── Singleton instances ───────────────────────────────────
3
+ let authStorage;
4
+ let modelRegistry;
5
+ function getAuth() {
6
+ if (!authStorage) {
7
+ authStorage = AuthStorage.create();
53
8
  }
54
- catch { }
55
- return {};
56
- }
57
- function writeAll(data) {
58
- mkdirSync(CONFIG_DIR, { recursive: true });
59
- writeFileSync(PROVIDERS_FILE, JSON.stringify(data, null, 2));
9
+ return authStorage;
60
10
  }
61
- export function getProviderKey(id) {
62
- return readAll()[id]?.apiKey || undefined;
11
+ function getRegistry() {
12
+ if (!modelRegistry) {
13
+ modelRegistry = new ModelRegistry(getAuth());
14
+ }
15
+ return modelRegistry;
63
16
  }
64
- export function saveProviderKey(id, apiKey) {
65
- const all = readAll();
66
- all[id] = { apiKey };
67
- writeAll(all);
17
+ // ── Public API ────────────────────────────────────────────
18
+ /**
19
+ * Get the AuthStorage instance (for login/logout flows).
20
+ */
21
+ export function getAuthStorage() {
22
+ return getAuth();
68
23
  }
69
- export function removeProviderKey(id) {
70
- const all = readAll();
71
- delete all[id];
72
- writeAll(all);
24
+ /**
25
+ * Get all available models (from providers the user has credentials for).
26
+ */
27
+ export async function getAvailableModels() {
28
+ const registry = getRegistry();
29
+ const models = await registry.getAvailable();
30
+ return models.map((m) => ({
31
+ provider: m.provider,
32
+ id: m.id,
33
+ name: m.name,
34
+ }));
73
35
  }
74
- // ── Queries ───────────────────────────────────────────────
75
- export function getAuthenticatedProviderIds() {
76
- const all = readAll();
77
- return Object.keys(all).filter((k) => all[k]?.apiKey);
36
+ /**
37
+ * Get all known models (regardless of auth status).
38
+ */
39
+ export function getAllModels() {
40
+ const registry = getRegistry();
41
+ return registry.getAll().map((m) => ({
42
+ provider: m.provider,
43
+ id: m.id,
44
+ name: m.name,
45
+ }));
78
46
  }
79
- export function isProviderAuthenticated(id) {
80
- return !!getProviderKey(id);
47
+ /**
48
+ * Get all OAuth provider IDs that the user can /login to.
49
+ */
50
+ export function getOAuthProviders() {
51
+ const auth = getAuth();
52
+ const oauthProviders = auth.getOAuthProviders();
53
+ return oauthProviders.map((p) => ({
54
+ id: p.id,
55
+ name: p.id, // will be overridden by display name below
56
+ loggedIn: auth.has(p.id),
57
+ }));
81
58
  }
82
- export function getAvailableModels() {
83
- const authed = getAuthenticatedProviderIds();
84
- const models = [];
85
- for (const p of PROVIDERS) {
86
- if (authed.includes(p.id)) {
87
- for (const m of p.models) {
88
- models.push({ provider: p.id, id: m.id, name: m.name });
89
- }
59
+ /**
60
+ * Get list of providers with auth status for display.
61
+ */
62
+ export async function getProviderList() {
63
+ const auth = getAuth();
64
+ const registry = getRegistry();
65
+ // Get OAuth providers
66
+ const oauthProviders = auth.getOAuthProviders();
67
+ // Get all unique providers from all models
68
+ const allModels = registry.getAll();
69
+ const providerIds = [...new Set(allModels.map((m) => m.provider))];
70
+ // Build display list
71
+ const result = [];
72
+ // OAuth providers first (subscription-based)
73
+ for (const op of oauthProviders) {
74
+ result.push({
75
+ id: op.id,
76
+ label: op.id,
77
+ loggedIn: auth.has(op.id),
78
+ });
79
+ }
80
+ // Then API key providers not already in the OAuth list
81
+ const oauthIds = new Set(oauthProviders.map((p) => p.id));
82
+ for (const pid of providerIds) {
83
+ if (!oauthIds.has(pid)) {
84
+ result.push({
85
+ id: pid,
86
+ label: pid,
87
+ loggedIn: auth.hasAuth(pid),
88
+ });
90
89
  }
91
90
  }
92
- return models;
91
+ return result;
92
+ }
93
+ /**
94
+ * Check if a provider has any auth configured.
95
+ */
96
+ export function isProviderAuthenticated(providerId) {
97
+ return getAuth().hasAuth(providerId);
98
+ }
99
+ /**
100
+ * Get the API key for a provider (resolves OAuth tokens, env vars, auth.json).
101
+ */
102
+ export async function getApiKey(providerId) {
103
+ return getAuth().getApiKey(providerId);
93
104
  }
94
105
  /**
95
- * Resolve a model pattern like "anthropic/claude-haiku-4-20250514" or just "claude-haiku-4-20250514"
96
- * into a { provider, model } pair. Falls back to the cheapest available model.
106
+ * Resolve a model pattern to a specific available model.
97
107
  */
98
- export function resolveModel(modelPattern) {
99
- const models = getAvailableModels();
108
+ export async function resolveModel(modelPattern) {
109
+ const models = await getAvailableModels();
100
110
  if (models.length === 0)
101
111
  return undefined;
102
112
  if (modelPattern) {
@@ -105,15 +115,14 @@ export function resolveModel(modelPattern) {
105
115
  const [prov, mod] = modelPattern.split("/", 2);
106
116
  const match = models.find((m) => m.provider === prov && m.id === mod);
107
117
  if (match)
108
- return { provider: match.provider, model: match.id };
118
+ return match;
109
119
  }
110
120
  // Fuzzy match
111
121
  const match = models.find((m) => m.id.includes(modelPattern) ||
112
122
  m.name.toLowerCase().includes(modelPattern.toLowerCase()));
113
123
  if (match)
114
- return { provider: match.provider, model: match.id };
124
+ return match;
115
125
  }
116
- // Default: first available model (cheapest tends to be listed last per provider, but first model overall)
117
- const m = models[0];
118
- return { provider: m.provider, model: m.id };
126
+ // Default: first available
127
+ return models[0];
119
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarmlancer-cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.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",
@@ -41,6 +41,7 @@
41
41
  "typescript": "^5.8.0"
42
42
  },
43
43
  "dependencies": {
44
+ "@mariozechner/pi-coding-agent": "^0.58.4",
44
45
  "@mariozechner/pi-tui": "^0.58.4",
45
46
  "open": "^11.0.0",
46
47
  "ws": "^8.19.0"