swarmlancer-cli 0.6.0 → 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 +17 -21
- package/dist/index.js +5 -4
- package/dist/inference.d.ts +1 -2
- package/dist/inference.js +31 -42
- package/dist/providers.d.ts +40 -24
- package/dist/providers.js +101 -92
- package/dist/screens/name-editor.d.ts +2 -2
- package/dist/screens/name-editor.js +17 -25
- package/package.json +2 -1
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,
|
|
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
|
|
212
|
-
|
|
213
|
-
const prefix = loggedIn ? "→ " : " ";
|
|
214
|
-
const suffix = loggedIn ? " ✓
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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.
|
|
298
|
+
setActiveModel(resolved.provider, resolved.id);
|
|
303
299
|
setAgentInstructions(agent.instructions);
|
|
304
|
-
const modelInfo = { provider: resolved.provider, id: resolved.
|
|
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.
|
|
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.
|
|
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); });
|
package/dist/inference.d.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/providers.d.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export declare function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
11
|
+
function getRegistry() {
|
|
12
|
+
if (!modelRegistry) {
|
|
13
|
+
modelRegistry = new ModelRegistry(getAuth());
|
|
14
|
+
}
|
|
15
|
+
return modelRegistry;
|
|
63
16
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
17
|
+
// ── Public API ────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Get the AuthStorage instance (for login/logout flows).
|
|
20
|
+
*/
|
|
21
|
+
export function getAuthStorage() {
|
|
22
|
+
return getAuth();
|
|
68
23
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
124
|
+
return match;
|
|
115
125
|
}
|
|
116
|
-
// Default: first available
|
|
117
|
-
|
|
118
|
-
return { provider: m.provider, model: m.id };
|
|
126
|
+
// Default: first available
|
|
127
|
+
return models[0];
|
|
119
128
|
}
|
|
@@ -3,11 +3,11 @@ import type { TUI } from "@mariozechner/pi-tui";
|
|
|
3
3
|
export declare class NameEditorScreen implements Component {
|
|
4
4
|
private tui;
|
|
5
5
|
private container;
|
|
6
|
-
private
|
|
6
|
+
private input;
|
|
7
7
|
private cachedLines?;
|
|
8
8
|
onSave?: (name: string) => void;
|
|
9
9
|
onCancel?: () => void;
|
|
10
|
-
constructor(tui: TUI,
|
|
10
|
+
constructor(tui: TUI, currentValue: string, title?: string);
|
|
11
11
|
handleInput(data: string): void;
|
|
12
12
|
render(width: number): string[];
|
|
13
13
|
invalidate(): void;
|
|
@@ -1,42 +1,32 @@
|
|
|
1
|
-
import { Container,
|
|
1
|
+
import { Container, Input, matchesKey, Key, } from "@mariozechner/pi-tui";
|
|
2
2
|
import { colors } from "../theme.js";
|
|
3
3
|
import { BannerComponent } from "./banner.js";
|
|
4
|
-
const EDITOR_THEME = {
|
|
5
|
-
borderColor: (s) => colors.lime(s),
|
|
6
|
-
selectList: {
|
|
7
|
-
selectedPrefix: (t) => colors.lime(t),
|
|
8
|
-
selectedText: (t) => colors.lime(t),
|
|
9
|
-
description: (t) => colors.gray(t),
|
|
10
|
-
scrollInfo: (t) => colors.gray(t),
|
|
11
|
-
noMatch: (t) => colors.gray(t),
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
4
|
export class NameEditorScreen {
|
|
15
5
|
tui;
|
|
16
6
|
container;
|
|
17
|
-
|
|
7
|
+
input;
|
|
18
8
|
cachedLines;
|
|
19
9
|
onSave;
|
|
20
10
|
onCancel;
|
|
21
|
-
constructor(tui,
|
|
11
|
+
constructor(tui, currentValue, title = "Rename") {
|
|
22
12
|
this.tui = tui;
|
|
23
13
|
this.container = new Container();
|
|
24
14
|
this.container.addChild(new BannerComponent(title));
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
this.editor.disableSubmit = false;
|
|
28
|
-
this.editor.onSubmit = (text) => {
|
|
29
|
-
const trimmed = text.trim();
|
|
30
|
-
if (trimmed.length > 0)
|
|
31
|
-
this.onSave?.(trimmed);
|
|
32
|
-
};
|
|
15
|
+
this.input = new Input();
|
|
16
|
+
this.input.setValue(currentValue);
|
|
33
17
|
}
|
|
34
18
|
handleInput(data) {
|
|
35
19
|
if (matchesKey(data, Key.escape)) {
|
|
36
20
|
this.onCancel?.();
|
|
37
21
|
return;
|
|
38
22
|
}
|
|
39
|
-
|
|
23
|
+
if (matchesKey(data, Key.enter)) {
|
|
24
|
+
const trimmed = this.input.getValue().trim();
|
|
25
|
+
if (trimmed.length > 0)
|
|
26
|
+
this.onSave?.(trimmed);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.input.handleInput(data);
|
|
40
30
|
this.cachedLines = undefined;
|
|
41
31
|
this.tui.requestRender();
|
|
42
32
|
}
|
|
@@ -44,8 +34,11 @@ export class NameEditorScreen {
|
|
|
44
34
|
if (this.cachedLines)
|
|
45
35
|
return this.cachedLines;
|
|
46
36
|
const lines = this.container.render(width);
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
// Render the input with a prompt
|
|
38
|
+
const inputLines = this.input.render(width - 4);
|
|
39
|
+
for (const line of inputLines) {
|
|
40
|
+
lines.push(` ${colors.lime("▸")} ${line}`);
|
|
41
|
+
}
|
|
49
42
|
lines.push("");
|
|
50
43
|
lines.push(colors.gray(" enter save • esc cancel"));
|
|
51
44
|
this.cachedLines = lines;
|
|
@@ -53,6 +46,5 @@ export class NameEditorScreen {
|
|
|
53
46
|
}
|
|
54
47
|
invalidate() {
|
|
55
48
|
this.cachedLines = undefined;
|
|
56
|
-
this.editor.invalidate();
|
|
57
49
|
}
|
|
58
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarmlancer-cli",
|
|
3
|
-
"version": "0.
|
|
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"
|