swarm-code 0.1.2 → 0.1.4
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/interactive.js +172 -104
- package/package.json +1 -1
package/dist/interactive.js
CHANGED
|
@@ -363,6 +363,26 @@ async function installOllama() {
|
|
|
363
363
|
console.log(` ${c.dim}Download Ollama from: https://ollama.com/download${c.reset}`);
|
|
364
364
|
return false;
|
|
365
365
|
}
|
|
366
|
+
function isOllamaServing() {
|
|
367
|
+
try {
|
|
368
|
+
execFileSync("curl", ["-sf", "http://127.0.0.1:11434/api/tags"], {
|
|
369
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
370
|
+
timeout: 3000,
|
|
371
|
+
});
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function startOllamaServe() {
|
|
379
|
+
// Start ollama serve in the background (detached)
|
|
380
|
+
const child = spawnChild("ollama", ["serve"], {
|
|
381
|
+
stdio: "ignore",
|
|
382
|
+
detached: true,
|
|
383
|
+
});
|
|
384
|
+
child.unref();
|
|
385
|
+
}
|
|
366
386
|
async function pullOllamaModel(model) {
|
|
367
387
|
console.log(`\n ${c.bold}Pulling ${model}...${c.reset} ${c.dim}(this may take a few minutes)${c.reset}\n`);
|
|
368
388
|
return new Promise((resolve) => {
|
|
@@ -372,6 +392,7 @@ async function pullOllamaModel(model) {
|
|
|
372
392
|
});
|
|
373
393
|
}
|
|
374
394
|
async function ensureOllamaSetup(rl, model) {
|
|
395
|
+
const shortModel = model.replace("ollama/", "");
|
|
375
396
|
// 1. Check if Ollama is installed
|
|
376
397
|
if (!isOllamaInstalled()) {
|
|
377
398
|
console.log(`\n ${c.dim}Ollama is not installed. It's needed to run open-source models locally.${c.reset}`);
|
|
@@ -383,26 +404,45 @@ async function ensureOllamaSetup(rl, model) {
|
|
|
383
404
|
console.log(` ${c.dim}Install manually from https://ollama.com/download${c.reset}\n`);
|
|
384
405
|
return false;
|
|
385
406
|
}
|
|
386
|
-
console.log(
|
|
407
|
+
console.log(` ${c.green}✓${c.reset} Ollama installed`);
|
|
387
408
|
}
|
|
388
409
|
else {
|
|
389
|
-
console.log(`\n ${c.dim}
|
|
390
|
-
console.log(` ${c.dim}Install later from https://ollama.com/download${c.reset}\n`);
|
|
410
|
+
console.log(`\n ${c.dim}Install later from https://ollama.com/download${c.reset}\n`);
|
|
391
411
|
return false;
|
|
392
412
|
}
|
|
393
413
|
}
|
|
394
414
|
else {
|
|
395
|
-
console.log(
|
|
415
|
+
console.log(` ${c.green}✓${c.reset} Ollama installed`);
|
|
396
416
|
}
|
|
397
|
-
// 2.
|
|
398
|
-
|
|
417
|
+
// 2. Ensure ollama serve is running
|
|
418
|
+
if (!isOllamaServing()) {
|
|
419
|
+
console.log(` ${c.dim}Starting Ollama server...${c.reset}`);
|
|
420
|
+
startOllamaServe();
|
|
421
|
+
// Give it a moment to start
|
|
422
|
+
let retries = 10;
|
|
423
|
+
while (retries > 0 && !isOllamaServing()) {
|
|
424
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
425
|
+
retries--;
|
|
426
|
+
}
|
|
427
|
+
if (isOllamaServing()) {
|
|
428
|
+
console.log(` ${c.green}✓${c.reset} Ollama server running`);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.log(` ${c.yellow}⚠${c.reset} Could not start Ollama server. Run ${c.bold}ollama serve${c.reset} manually.`);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
console.log(` ${c.green}✓${c.reset} Ollama server running`);
|
|
437
|
+
}
|
|
438
|
+
// 3. Check if model is already pulled
|
|
399
439
|
if (isOllamaModelAvailable(shortModel)) {
|
|
400
440
|
console.log(` ${c.green}✓${c.reset} Model ${c.bold}${shortModel}${c.reset} ready\n`);
|
|
401
441
|
return true;
|
|
402
442
|
}
|
|
403
|
-
//
|
|
443
|
+
// 4. Pull the model
|
|
404
444
|
console.log(` ${c.dim}Model ${shortModel} not found locally.${c.reset}`);
|
|
405
|
-
const pull = await questionWithEsc(rl,
|
|
445
|
+
const pull = await questionWithEsc(rl, `\n ${c.cyan}Pull ${shortModel} now? [Y/n]:${c.reset} `);
|
|
406
446
|
if (pull !== null && pull.toLowerCase() !== "n" && pull.toLowerCase() !== "no") {
|
|
407
447
|
const ok = await pullOllamaModel(shortModel);
|
|
408
448
|
if (!ok) {
|
|
@@ -410,12 +450,73 @@ async function ensureOllamaSetup(rl, model) {
|
|
|
410
450
|
console.log(` ${c.dim}Try manually: ollama pull ${shortModel}${c.reset}\n`);
|
|
411
451
|
return false;
|
|
412
452
|
}
|
|
413
|
-
console.log(
|
|
453
|
+
console.log(` ${c.green}✓${c.reset} Model ${c.bold}${shortModel}${c.reset} ready\n`);
|
|
414
454
|
return true;
|
|
415
455
|
}
|
|
416
456
|
console.log(`\n ${c.dim}Run later: ollama pull ${shortModel}${c.reset}\n`);
|
|
417
457
|
return false;
|
|
418
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Interactive checkbox selector — arrow keys to navigate, space to toggle, enter to confirm.
|
|
461
|
+
* Returns indices of selected items.
|
|
462
|
+
*/
|
|
463
|
+
function checkboxSelect(items) {
|
|
464
|
+
return new Promise((resolve) => {
|
|
465
|
+
let cursor = 0;
|
|
466
|
+
const render = () => {
|
|
467
|
+
// Move cursor up to overwrite previous render (except first time)
|
|
468
|
+
if (rendered) {
|
|
469
|
+
process.stdout.write(`\x1b[${items.length}A`);
|
|
470
|
+
}
|
|
471
|
+
for (let i = 0; i < items.length; i++) {
|
|
472
|
+
const item = items[i];
|
|
473
|
+
const pointer = i === cursor ? `${c.cyan}❯${c.reset}` : " ";
|
|
474
|
+
const box = item.checked ? `${c.green}◼${c.reset}` : `${c.dim}◻${c.reset}`;
|
|
475
|
+
const label = i === cursor ? `${c.bold}${item.label}${c.reset}` : item.label;
|
|
476
|
+
const desc = `${c.dim}${item.desc}${c.reset}`;
|
|
477
|
+
process.stdout.write(`\x1b[2K ${pointer} ${box} ${label} ${desc}\n`);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
let rendered = false;
|
|
481
|
+
process.stdout.write("\x1b[?25l"); // Hide cursor
|
|
482
|
+
render();
|
|
483
|
+
rendered = true;
|
|
484
|
+
const wasRaw = stdin.isRaw;
|
|
485
|
+
if (stdin.isTTY)
|
|
486
|
+
stdin.setRawMode(true);
|
|
487
|
+
const cleanup = () => {
|
|
488
|
+
stdin.removeListener("data", onData);
|
|
489
|
+
if (stdin.isTTY)
|
|
490
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
491
|
+
process.stdout.write("\x1b[?25h"); // Show cursor
|
|
492
|
+
};
|
|
493
|
+
const onData = (data) => {
|
|
494
|
+
const key = data.toString();
|
|
495
|
+
if (key === "\x1b[A" || key === "k") {
|
|
496
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
497
|
+
render();
|
|
498
|
+
}
|
|
499
|
+
else if (key === "\x1b[B" || key === "j") {
|
|
500
|
+
cursor = (cursor + 1) % items.length;
|
|
501
|
+
render();
|
|
502
|
+
}
|
|
503
|
+
else if (key === " ") {
|
|
504
|
+
items[cursor].checked = !items[cursor].checked;
|
|
505
|
+
render();
|
|
506
|
+
}
|
|
507
|
+
else if (key === "\r" || key === "\n") {
|
|
508
|
+
cleanup();
|
|
509
|
+
const selected = items.map((item, i) => (item.checked ? i : -1)).filter((i) => i >= 0);
|
|
510
|
+
resolve(selected);
|
|
511
|
+
}
|
|
512
|
+
else if (key === "\x1b" || key === "\x03") {
|
|
513
|
+
cleanup();
|
|
514
|
+
resolve([]);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
stdin.on("data", onData);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
419
520
|
// ── Banner ──────────────────────────────────────────────────────────────────
|
|
420
521
|
function printBanner() {
|
|
421
522
|
console.log(`
|
|
@@ -1434,111 +1535,78 @@ async function interactive() {
|
|
|
1434
1535
|
requiresKey: true,
|
|
1435
1536
|
},
|
|
1436
1537
|
];
|
|
1538
|
+
// ── Step 1: Checkbox agent selection ─────────────────────────
|
|
1539
|
+
console.log(` ${c.bold}Select your coding agent(s):${c.reset}`);
|
|
1540
|
+
console.log(` ${c.dim}↑/↓ navigate · space toggle · enter confirm${c.reset}\n`);
|
|
1541
|
+
const checkboxItems = AGENT_CHOICES.map((a) => ({
|
|
1542
|
+
label: a.name,
|
|
1543
|
+
desc: a.desc,
|
|
1544
|
+
checked: false,
|
|
1545
|
+
}));
|
|
1546
|
+
const selectedIndices = await checkboxSelect(checkboxItems);
|
|
1547
|
+
if (selectedIndices.length === 0) {
|
|
1548
|
+
console.log(`\n ${c.dim}No agents selected. Exiting.${c.reset}\n`);
|
|
1549
|
+
process.exit(0);
|
|
1550
|
+
}
|
|
1551
|
+
const selectedAgents = selectedIndices.map((i) => AGENT_CHOICES[i]);
|
|
1552
|
+
console.log();
|
|
1553
|
+
for (const agent of selectedAgents) {
|
|
1554
|
+
console.log(` ${c.green}✓${c.reset} ${c.bold}${agent.name}${c.reset}`);
|
|
1555
|
+
}
|
|
1556
|
+
console.log();
|
|
1557
|
+
// ── Step 2: Configure each agent ─────────────────────────────
|
|
1437
1558
|
const setupRl = readline.createInterface({ input: stdin, output: stdout, terminal: true });
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
console.log(` ${c.
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
if (choice === null) {
|
|
1449
|
-
console.log(`\n ${c.dim}Exiting.${c.reset}\n`);
|
|
1450
|
-
setupRl.close();
|
|
1451
|
-
process.exit(0);
|
|
1452
|
-
}
|
|
1453
|
-
// Parse comma-separated selections
|
|
1454
|
-
const indices = choice
|
|
1455
|
-
.split(",")
|
|
1456
|
-
.map((s) => parseInt(s.trim(), 10) - 1)
|
|
1457
|
-
.filter((i) => !Number.isNaN(i) && i >= 0 && i < AGENT_CHOICES.length);
|
|
1458
|
-
// Deduplicate
|
|
1459
|
-
const uniqueIndices = [...new Set(indices)];
|
|
1460
|
-
if (uniqueIndices.length === 0) {
|
|
1461
|
-
console.log(`\n ${c.dim}Invalid choice.${c.reset}\n`);
|
|
1462
|
-
continue;
|
|
1463
|
-
}
|
|
1464
|
-
const selectedAgents = uniqueIndices.map((i) => AGENT_CHOICES[i]);
|
|
1465
|
-
console.log();
|
|
1466
|
-
for (const agent of selectedAgents) {
|
|
1467
|
-
console.log(` ${c.green}✓${c.reset} ${c.bold}${agent.name}${c.reset}`);
|
|
1468
|
-
}
|
|
1469
|
-
console.log();
|
|
1470
|
-
// Walk through each selected agent and configure keys
|
|
1471
|
-
const configuredProviders = new Set();
|
|
1472
|
-
for (const agent of selectedAgents) {
|
|
1473
|
-
if (agent.requiresKey) {
|
|
1474
|
-
console.log(` ${c.bold}${agent.name}${c.reset} ${c.dim}requires an API key. Configure one now:${c.reset}\n`);
|
|
1475
|
-
for (const provider of agent.keys) {
|
|
1476
|
-
// Skip providers already configured in this session
|
|
1477
|
-
if (configuredProviders.has(provider.env)) {
|
|
1478
|
-
console.log(` ${c.green}✓${c.reset} ${provider.name} ${c.dim}(already configured)${c.reset}`);
|
|
1479
|
-
continue;
|
|
1480
|
-
}
|
|
1481
|
-
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
1482
|
-
if (gotKey === null)
|
|
1483
|
-
break; // ESC — skip remaining
|
|
1484
|
-
if (gotKey) {
|
|
1485
|
-
configuredProviders.add(provider.env);
|
|
1486
|
-
break; // Got one — enough for this agent
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
// Check if this agent still has no key
|
|
1490
|
-
const agentHasKey = agent.keys.some((p) => configuredProviders.has(p.env) || process.env[p.env]);
|
|
1491
|
-
if (!agentHasKey) {
|
|
1492
|
-
console.log(`\n ${c.dim}No key for ${agent.name} — it won't be available until a key is set in .env${c.reset}\n`);
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
else {
|
|
1496
|
-
// Agent works without keys (e.g. OpenCode with open-source models)
|
|
1497
|
-
console.log(` ${c.bold}${agent.name}${c.reset} ${c.dim}works with open-source models (no API key needed).${c.reset}`);
|
|
1498
|
-
console.log(` ${c.dim}You can run locally with Ollama, or add a cloud API key.${c.reset}\n`);
|
|
1499
|
-
const setupChoice = await questionWithEsc(setupRl, ` ${c.cyan}Set up with: [1] Ollama (open-source) [2] API key [1]:${c.reset} `);
|
|
1500
|
-
const wantsApiKey = setupChoice !== null && setupChoice.trim() === "2";
|
|
1501
|
-
if (wantsApiKey) {
|
|
1502
|
-
for (const provider of agent.keys) {
|
|
1503
|
-
if (configuredProviders.has(provider.env)) {
|
|
1504
|
-
console.log(` ${c.green}✓${c.reset} ${provider.name} ${c.dim}(already configured)${c.reset}`);
|
|
1505
|
-
continue;
|
|
1506
|
-
}
|
|
1507
|
-
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
1508
|
-
if (gotKey === null)
|
|
1509
|
-
break;
|
|
1510
|
-
if (gotKey) {
|
|
1511
|
-
configuredProviders.add(provider.env);
|
|
1512
|
-
break;
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1559
|
+
const configuredProviders = new Set();
|
|
1560
|
+
let usesOllama = false;
|
|
1561
|
+
for (const agent of selectedAgents) {
|
|
1562
|
+
if (agent.requiresKey) {
|
|
1563
|
+
// Agent needs at least one API key
|
|
1564
|
+
console.log(` ${c.bold}${agent.name}${c.reset} ${c.dim}— configure API key:${c.reset}\n`);
|
|
1565
|
+
for (const provider of agent.keys) {
|
|
1566
|
+
if (configuredProviders.has(provider.env) || process.env[provider.env]) {
|
|
1567
|
+
console.log(` ${c.green}✓${c.reset} ${provider.name} ${c.dim}(already configured)${c.reset}`);
|
|
1568
|
+
continue;
|
|
1515
1569
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1570
|
+
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
1571
|
+
if (gotKey === null)
|
|
1572
|
+
break;
|
|
1573
|
+
if (gotKey) {
|
|
1574
|
+
configuredProviders.add(provider.env);
|
|
1575
|
+
break;
|
|
1519
1576
|
}
|
|
1520
|
-
console.log();
|
|
1521
1577
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (activeProvider) {
|
|
1526
|
-
currentProviderName = activeProvider;
|
|
1527
|
-
const defaultModel = getDefaultModelForProvider(activeProvider);
|
|
1528
|
-
if (defaultModel) {
|
|
1529
|
-
currentModelId = defaultModel;
|
|
1530
|
-
saveModelPreference(currentModelId);
|
|
1531
|
-
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
1578
|
+
const agentHasKey = agent.keys.some((p) => configuredProviders.has(p.env) || process.env[p.env]);
|
|
1579
|
+
if (!agentHasKey) {
|
|
1580
|
+
console.log(` ${c.dim}No key for ${agent.name} — set one in .env later${c.reset}`);
|
|
1532
1581
|
}
|
|
1582
|
+
console.log();
|
|
1533
1583
|
}
|
|
1534
1584
|
else {
|
|
1535
|
-
|
|
1585
|
+
// Agent works without keys (e.g. OpenCode) — set up Ollama directly
|
|
1586
|
+
console.log(` ${c.bold}${agent.name}${c.reset} ${c.dim}— setting up Ollama for local models:${c.reset}\n`);
|
|
1587
|
+
const ok = await ensureOllamaSetup(setupRl, "ollama/deepseek-coder-v2");
|
|
1588
|
+
if (ok)
|
|
1589
|
+
usesOllama = true;
|
|
1590
|
+
console.log();
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
// ── Step 3: Set default model ────────────────────────────────
|
|
1594
|
+
const activeProvider = Object.keys(PROVIDER_KEYS).find((p) => process.env[providerEnvKey(p)]);
|
|
1595
|
+
if (activeProvider) {
|
|
1596
|
+
currentProviderName = activeProvider;
|
|
1597
|
+
const defaultModel = getDefaultModelForProvider(activeProvider);
|
|
1598
|
+
if (defaultModel) {
|
|
1599
|
+
currentModelId = defaultModel;
|
|
1536
1600
|
saveModelPreference(currentModelId);
|
|
1537
|
-
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}
|
|
1601
|
+
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
1538
1602
|
}
|
|
1539
|
-
console.log();
|
|
1540
|
-
setupDone = true;
|
|
1541
1603
|
}
|
|
1604
|
+
else if (usesOllama) {
|
|
1605
|
+
currentModelId = "ollama/deepseek-coder-v2";
|
|
1606
|
+
saveModelPreference(currentModelId);
|
|
1607
|
+
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset} ${c.dim}(local)${c.reset}`);
|
|
1608
|
+
}
|
|
1609
|
+
console.log();
|
|
1542
1610
|
setupRl.close();
|
|
1543
1611
|
}
|
|
1544
1612
|
// Resolve model — ensure the resolved provider actually has an API key
|
package/package.json
CHANGED