@gaberrb/polypus 0.4.0 → 0.4.2
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 +5 -0
- package/dist/index.js +840 -587
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143,6 +143,7 @@ var en = {
|
|
|
143
143
|
"repl.allowShow": "mode={mode} allow=[{allow}]",
|
|
144
144
|
"repl.historyCleared": "history cleared",
|
|
145
145
|
"repl.unknown": "Unknown command /{cmd}. Type /help.",
|
|
146
|
+
"repl.pasted": "[Pasted text #{id} +{lines} lines]",
|
|
146
147
|
"repl.agentSwitched": "active agent \u2192 {name}",
|
|
147
148
|
"repl.switchedTo": "active agent is now {name}",
|
|
148
149
|
"repl.noAgentsLeft": "No agents left. Use /add to create one.",
|
|
@@ -156,6 +157,7 @@ var en = {
|
|
|
156
157
|
" /plan switch to plan mode (read-only)",
|
|
157
158
|
" /review switch to review mode (confirm each action)",
|
|
158
159
|
" /bypass switch to bypass mode (auto-approve)",
|
|
160
|
+
" /swarm <task> run a task as a parallel swarm (needs 3+ agents)",
|
|
159
161
|
" /allow <glob> add a path glob to the allow-list",
|
|
160
162
|
" /allow show the current allow-list and mode",
|
|
161
163
|
" /reset clear the conversation history",
|
|
@@ -359,6 +361,7 @@ var ptBR = {
|
|
|
359
361
|
"repl.allowShow": "modo={mode} allow=[{allow}]",
|
|
360
362
|
"repl.historyCleared": "hist\xF3rico limpo",
|
|
361
363
|
"repl.unknown": "Comando desconhecido /{cmd}. Digite /help.",
|
|
364
|
+
"repl.pasted": "[Texto colado #{id} +{lines} linhas]",
|
|
362
365
|
"repl.agentSwitched": "agente ativo \u2192 {name}",
|
|
363
366
|
"repl.switchedTo": "agente ativo agora \xE9 {name}",
|
|
364
367
|
"repl.noAgentsLeft": "Nenhum agente restante. Use /add para criar um.",
|
|
@@ -372,6 +375,7 @@ var ptBR = {
|
|
|
372
375
|
" /plan muda para o modo plan (somente leitura)",
|
|
373
376
|
" /review muda para o modo review (confirma cada a\xE7\xE3o)",
|
|
374
377
|
" /bypass muda para o modo bypass (aprova automaticamente)",
|
|
378
|
+
" /swarm <task> roda a tarefa como swarm paralelo (requer 3+ agentes)",
|
|
375
379
|
" /allow <glob> adiciona um glob de caminho \xE0 allow-list",
|
|
376
380
|
" /allow mostra a allow-list e o modo atuais",
|
|
377
381
|
" /reset limpa o hist\xF3rico da conversa",
|
|
@@ -688,7 +692,7 @@ async function listAgents() {
|
|
|
688
692
|
}
|
|
689
693
|
|
|
690
694
|
// src/cli/commands/run.ts
|
|
691
|
-
import
|
|
695
|
+
import pc8 from "picocolors";
|
|
692
696
|
import * as p2 from "@clack/prompts";
|
|
693
697
|
|
|
694
698
|
// src/core/providers/anthropic.ts
|
|
@@ -1431,6 +1435,116 @@ function clamp(s) {
|
|
|
1431
1435
|
return s.length > MAX_OUTPUT ? s.slice(0, MAX_OUTPUT) + "\n\u2026[truncated]" : s;
|
|
1432
1436
|
}
|
|
1433
1437
|
|
|
1438
|
+
// src/core/tools/search-file.ts
|
|
1439
|
+
import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
|
|
1440
|
+
import { join as join2, resolve as resolve5 } from "path";
|
|
1441
|
+
import { z as z6 } from "zod";
|
|
1442
|
+
var Args5 = z6.object({
|
|
1443
|
+
query: z6.string().min(1),
|
|
1444
|
+
path: z6.string().optional(),
|
|
1445
|
+
glob: z6.string().optional(),
|
|
1446
|
+
max_results: z6.number().int().positive().max(1e3).optional()
|
|
1447
|
+
});
|
|
1448
|
+
var DEFAULT_MAX_RESULTS = 50;
|
|
1449
|
+
var MAX_OUTPUT2 = 2e4;
|
|
1450
|
+
var MAX_FILE_BYTES = 2e6;
|
|
1451
|
+
var SNIPPET_CHARS = 200;
|
|
1452
|
+
var NUL = String.fromCharCode(0);
|
|
1453
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "coverage", ".turbo"]);
|
|
1454
|
+
var searchTool = {
|
|
1455
|
+
mutating: false,
|
|
1456
|
+
spec: {
|
|
1457
|
+
name: "search",
|
|
1458
|
+
description: "Search file contents by regular expression across the workspace (like grep/ripgrep). Returns matches as 'path:line: snippet'. Respects the allow/deny-list and skips node_modules/.git. Use this to find where a symbol is defined or used instead of reading files blindly.",
|
|
1459
|
+
parameters: {
|
|
1460
|
+
type: "object",
|
|
1461
|
+
properties: {
|
|
1462
|
+
query: { type: "string", description: "Regular expression to match against each line" },
|
|
1463
|
+
path: { type: "string", description: "Workspace-relative directory to search in (default '.')" },
|
|
1464
|
+
glob: {
|
|
1465
|
+
type: "string",
|
|
1466
|
+
description: "Optional glob to limit files, e.g. 'src/**/*.ts'"
|
|
1467
|
+
},
|
|
1468
|
+
max_results: {
|
|
1469
|
+
type: "number",
|
|
1470
|
+
description: `Maximum number of matches to return (default ${DEFAULT_MAX_RESULTS})`
|
|
1471
|
+
}
|
|
1472
|
+
},
|
|
1473
|
+
required: ["query"]
|
|
1474
|
+
}
|
|
1475
|
+
},
|
|
1476
|
+
async run(rawArgs, ctx) {
|
|
1477
|
+
const parsed = Args5.safeParse(rawArgs);
|
|
1478
|
+
if (!parsed.success) return { ok: false, output: "Invalid args: 'query' is required." };
|
|
1479
|
+
const { query, path = ".", glob, max_results } = parsed.data;
|
|
1480
|
+
let regex;
|
|
1481
|
+
try {
|
|
1482
|
+
regex = new RegExp(query);
|
|
1483
|
+
} catch (err) {
|
|
1484
|
+
return { ok: false, output: `Invalid regular expression: ${err.message}` };
|
|
1485
|
+
}
|
|
1486
|
+
if (path !== ".") {
|
|
1487
|
+
const decision = ctx.permissions.authorizeRead(path);
|
|
1488
|
+
if (!decision.allowed) return { ok: false, output: `Search denied: ${decision.reason}` };
|
|
1489
|
+
}
|
|
1490
|
+
const globRe = glob ? globToRegExp(glob) : void 0;
|
|
1491
|
+
const limit = max_results ?? DEFAULT_MAX_RESULTS;
|
|
1492
|
+
const root = resolve5(ctx.workspace, path);
|
|
1493
|
+
const matches = [];
|
|
1494
|
+
let truncated = false;
|
|
1495
|
+
const walk = async (dir) => {
|
|
1496
|
+
if (matches.length >= limit) return;
|
|
1497
|
+
let entries;
|
|
1498
|
+
try {
|
|
1499
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
1500
|
+
} catch {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
for (const entry of entries) {
|
|
1504
|
+
if (matches.length >= limit) return;
|
|
1505
|
+
const abs = join2(dir, entry.name);
|
|
1506
|
+
const rel = toPosix(abs.slice(ctx.workspace.length + 1));
|
|
1507
|
+
if (entry.isDirectory()) {
|
|
1508
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1509
|
+
await walk(abs);
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
if (!entry.isFile()) continue;
|
|
1513
|
+
if (globRe && !globRe.test(rel)) continue;
|
|
1514
|
+
if (!ctx.permissions.authorizeRead(rel).allowed) continue;
|
|
1515
|
+
try {
|
|
1516
|
+
const info = await stat(abs);
|
|
1517
|
+
if (info.size > MAX_FILE_BYTES) continue;
|
|
1518
|
+
const content = await readFile4(abs, "utf8");
|
|
1519
|
+
if (content.includes(NUL)) continue;
|
|
1520
|
+
const lines = content.split("\n");
|
|
1521
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1522
|
+
if (matches.length >= limit) {
|
|
1523
|
+
truncated = true;
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
if (regex.test(lines[i])) {
|
|
1527
|
+
const snippet = lines[i].trim().slice(0, SNIPPET_CHARS);
|
|
1528
|
+
matches.push(`${rel}:${i + 1}: ${snippet}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
await walk(root);
|
|
1536
|
+
if (matches.length === 0) {
|
|
1537
|
+
return { ok: true, output: `No matches for /${query}/${glob ? ` in ${glob}` : ""}.` };
|
|
1538
|
+
}
|
|
1539
|
+
const header = `${matches.length}${truncated ? "+" : ""} match(es) for /${query}/:`;
|
|
1540
|
+
const body = [header, ...matches].join("\n");
|
|
1541
|
+
return {
|
|
1542
|
+
ok: true,
|
|
1543
|
+
output: body.length > MAX_OUTPUT2 ? body.slice(0, MAX_OUTPUT2) + "\n\u2026[truncated]" : body
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1434
1548
|
// src/core/tools/types.ts
|
|
1435
1549
|
var FINISH_TOOL = {
|
|
1436
1550
|
name: "finish",
|
|
@@ -1446,9 +1560,9 @@ var FINISH_TOOL = {
|
|
|
1446
1560
|
|
|
1447
1561
|
// src/core/tools/write-file.ts
|
|
1448
1562
|
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1449
|
-
import { dirname, resolve as
|
|
1450
|
-
import { z as
|
|
1451
|
-
var
|
|
1563
|
+
import { dirname, resolve as resolve6 } from "path";
|
|
1564
|
+
import { z as z7 } from "zod";
|
|
1565
|
+
var Args6 = z7.object({ path: z7.string().min(1), content: z7.string() });
|
|
1452
1566
|
var writeFileTool = {
|
|
1453
1567
|
mutating: true,
|
|
1454
1568
|
spec: {
|
|
@@ -1464,7 +1578,7 @@ var writeFileTool = {
|
|
|
1464
1578
|
}
|
|
1465
1579
|
},
|
|
1466
1580
|
async run(rawArgs, ctx) {
|
|
1467
|
-
const args =
|
|
1581
|
+
const args = Args6.safeParse(rawArgs);
|
|
1468
1582
|
if (!args.success) {
|
|
1469
1583
|
const got = Object.keys(rawArgs ?? {});
|
|
1470
1584
|
return {
|
|
@@ -1476,7 +1590,7 @@ var writeFileTool = {
|
|
|
1476
1590
|
const decision = await ctx.permissions.authorizeWrite(args.data.path, preview);
|
|
1477
1591
|
if (!decision.allowed) return { ok: false, output: `Write denied: ${decision.reason}` };
|
|
1478
1592
|
try {
|
|
1479
|
-
const abs =
|
|
1593
|
+
const abs = resolve6(ctx.workspace, args.data.path);
|
|
1480
1594
|
await mkdir2(dirname(abs), { recursive: true });
|
|
1481
1595
|
await writeFile3(abs, args.data.content, "utf8");
|
|
1482
1596
|
const lines = args.data.content.split("\n").length;
|
|
@@ -1495,6 +1609,7 @@ function previewContent(content) {
|
|
|
1495
1609
|
var TOOLS = {
|
|
1496
1610
|
[readFileTool.spec.name]: readFileTool,
|
|
1497
1611
|
[listDirTool.spec.name]: listDirTool,
|
|
1612
|
+
[searchTool.spec.name]: searchTool,
|
|
1498
1613
|
[writeFileTool.spec.name]: writeFileTool,
|
|
1499
1614
|
[editFileTool.spec.name]: editFileTool,
|
|
1500
1615
|
[runCommandTool.spec.name]: runCommandTool
|
|
@@ -1507,8 +1622,8 @@ function getTool(name) {
|
|
|
1507
1622
|
}
|
|
1508
1623
|
|
|
1509
1624
|
// src/core/agent/correction.ts
|
|
1510
|
-
import { readFile as
|
|
1511
|
-
import { dirname as dirname2, resolve as
|
|
1625
|
+
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
1626
|
+
import { dirname as dirname2, resolve as resolve7 } from "path";
|
|
1512
1627
|
function truncationGuidance(toolName) {
|
|
1513
1628
|
const fileHint = toolName === "write_file" || toolName === "edit_file" ? " Write large files in parts: create the file with the first chunk via write_file, then append the rest with edit_file in the next steps." : "";
|
|
1514
1629
|
return [
|
|
@@ -1608,7 +1723,7 @@ ${text2}` : null;
|
|
|
1608
1723
|
}
|
|
1609
1724
|
async function readWorkspaceFile(workspace, path) {
|
|
1610
1725
|
try {
|
|
1611
|
-
return await
|
|
1726
|
+
return await readFile5(resolve7(workspace, path), "utf8");
|
|
1612
1727
|
} catch {
|
|
1613
1728
|
return null;
|
|
1614
1729
|
}
|
|
@@ -1666,11 +1781,11 @@ async function occurrenceLines(workspace, path, search) {
|
|
|
1666
1781
|
return out;
|
|
1667
1782
|
}
|
|
1668
1783
|
async function listNearest(workspace, path) {
|
|
1669
|
-
let dir = dirname2(
|
|
1784
|
+
let dir = dirname2(resolve7(workspace, path));
|
|
1670
1785
|
for (let i = 0; i < 8; i++) {
|
|
1671
1786
|
try {
|
|
1672
|
-
const entries = await
|
|
1673
|
-
const rel = dir ===
|
|
1787
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1788
|
+
const rel = dir === resolve7(workspace) ? "." : dir;
|
|
1674
1789
|
const names = entries.slice(0, 40).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
1675
1790
|
return `${rel}:
|
|
1676
1791
|
${names.join(" ") || "(empty)"}`;
|
|
@@ -1692,14 +1807,14 @@ function formatSchema(spec) {
|
|
|
1692
1807
|
}
|
|
1693
1808
|
|
|
1694
1809
|
// src/core/agent/project-context.ts
|
|
1695
|
-
import { readFile as
|
|
1696
|
-
import { join as
|
|
1697
|
-
var INSTRUCTION_FILES = [
|
|
1810
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1811
|
+
import { join as join3 } from "path";
|
|
1812
|
+
var INSTRUCTION_FILES = [join3(".poly", "agents.md"), "AGENTS.md"];
|
|
1698
1813
|
var MAX_CHARS2 = 8e3;
|
|
1699
1814
|
async function loadProjectInstructions(workspace) {
|
|
1700
1815
|
for (const rel of INSTRUCTION_FILES) {
|
|
1701
1816
|
try {
|
|
1702
|
-
const raw = (await
|
|
1817
|
+
const raw = (await readFile6(join3(workspace, rel), "utf8")).trim();
|
|
1703
1818
|
if (!raw) continue;
|
|
1704
1819
|
return raw.length > MAX_CHARS2 ? raw.slice(0, MAX_CHARS2) + "\n\u2026(truncated)" : raw;
|
|
1705
1820
|
} catch {
|
|
@@ -1860,8 +1975,6 @@ ${guidance}`;
|
|
|
1860
1975
|
}
|
|
1861
1976
|
|
|
1862
1977
|
// src/ui/repl.ts
|
|
1863
|
-
import * as readline from "readline/promises";
|
|
1864
|
-
import { stdin, stdout } from "process";
|
|
1865
1978
|
import pc6 from "picocolors";
|
|
1866
1979
|
|
|
1867
1980
|
// src/ui/wizard.ts
|
|
@@ -2383,6 +2496,136 @@ function promptLabel(mode) {
|
|
|
2383
2496
|
return c2("\u{1F419} polypus") + pc5.dim(`(${mode})`) + c3(" \u203A ");
|
|
2384
2497
|
}
|
|
2385
2498
|
|
|
2499
|
+
// src/ui/line-reader.ts
|
|
2500
|
+
import * as readline from "readline/promises";
|
|
2501
|
+
import { PassThrough } from "stream";
|
|
2502
|
+
import { stdin, stdout } from "process";
|
|
2503
|
+
|
|
2504
|
+
// src/ui/paste.ts
|
|
2505
|
+
var PASTE_START = "\x1B[200~";
|
|
2506
|
+
var PASTE_END = "\x1B[201~";
|
|
2507
|
+
var PasteStore = class {
|
|
2508
|
+
/** `format(id, lines)` builds the placeholder (localized by the caller). */
|
|
2509
|
+
constructor(format) {
|
|
2510
|
+
this.format = format;
|
|
2511
|
+
}
|
|
2512
|
+
format;
|
|
2513
|
+
seq = 0;
|
|
2514
|
+
map = /* @__PURE__ */ new Map();
|
|
2515
|
+
/** Register pasted text, returning the placeholder to display in its place. */
|
|
2516
|
+
add(text2) {
|
|
2517
|
+
const lines = text2.split(/\r\n|\r|\n/).length;
|
|
2518
|
+
const placeholder = this.format(++this.seq, lines);
|
|
2519
|
+
this.map.set(placeholder, text2);
|
|
2520
|
+
return placeholder;
|
|
2521
|
+
}
|
|
2522
|
+
/** Replace any known placeholders in `line` with their full pasted text. */
|
|
2523
|
+
expand(line) {
|
|
2524
|
+
let out = line;
|
|
2525
|
+
for (const [placeholder, full] of this.map) {
|
|
2526
|
+
if (out.includes(placeholder)) out = out.split(placeholder).join(full);
|
|
2527
|
+
}
|
|
2528
|
+
return out;
|
|
2529
|
+
}
|
|
2530
|
+
get size() {
|
|
2531
|
+
return this.map.size;
|
|
2532
|
+
}
|
|
2533
|
+
};
|
|
2534
|
+
var PasteFilter = class {
|
|
2535
|
+
constructor(store) {
|
|
2536
|
+
this.store = store;
|
|
2537
|
+
}
|
|
2538
|
+
store;
|
|
2539
|
+
buf = "";
|
|
2540
|
+
inPaste = false;
|
|
2541
|
+
pasteBuf = "";
|
|
2542
|
+
push(chunk) {
|
|
2543
|
+
this.buf += chunk;
|
|
2544
|
+
let out = "";
|
|
2545
|
+
for (; ; ) {
|
|
2546
|
+
if (!this.inPaste) {
|
|
2547
|
+
const i = this.buf.indexOf(PASTE_START);
|
|
2548
|
+
if (i === -1) {
|
|
2549
|
+
const keep = partialSuffix(this.buf, PASTE_START);
|
|
2550
|
+
out += this.buf.slice(0, this.buf.length - keep);
|
|
2551
|
+
this.buf = this.buf.slice(this.buf.length - keep);
|
|
2552
|
+
return out;
|
|
2553
|
+
}
|
|
2554
|
+
out += this.buf.slice(0, i);
|
|
2555
|
+
this.buf = this.buf.slice(i + PASTE_START.length);
|
|
2556
|
+
this.inPaste = true;
|
|
2557
|
+
} else {
|
|
2558
|
+
const j = this.buf.indexOf(PASTE_END);
|
|
2559
|
+
if (j === -1) {
|
|
2560
|
+
const keep = partialSuffix(this.buf, PASTE_END);
|
|
2561
|
+
this.pasteBuf += this.buf.slice(0, this.buf.length - keep);
|
|
2562
|
+
this.buf = this.buf.slice(this.buf.length - keep);
|
|
2563
|
+
return out;
|
|
2564
|
+
}
|
|
2565
|
+
this.pasteBuf += this.buf.slice(0, j);
|
|
2566
|
+
this.buf = this.buf.slice(j + PASTE_END.length);
|
|
2567
|
+
this.inPaste = false;
|
|
2568
|
+
out += this.emit(this.pasteBuf);
|
|
2569
|
+
this.pasteBuf = "";
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
/** Multi-line pastes become a placeholder; single-line pastes pass through. */
|
|
2574
|
+
emit(text2) {
|
|
2575
|
+
return /\r|\n/.test(text2) ? this.store.add(text2) : text2;
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
function partialSuffix(s, marker) {
|
|
2579
|
+
const max = Math.min(s.length, marker.length - 1);
|
|
2580
|
+
for (let n = max; n > 0; n--) {
|
|
2581
|
+
if (s.slice(s.length - n) === marker.slice(0, n)) return n;
|
|
2582
|
+
}
|
|
2583
|
+
return 0;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
// src/ui/line-reader.ts
|
|
2587
|
+
var ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
|
|
2588
|
+
var DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
|
|
2589
|
+
async function readLine(prompt) {
|
|
2590
|
+
if (!stdin.isTTY) {
|
|
2591
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
2592
|
+
try {
|
|
2593
|
+
return await rl.question(prompt);
|
|
2594
|
+
} catch {
|
|
2595
|
+
return null;
|
|
2596
|
+
} finally {
|
|
2597
|
+
rl.close();
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return readLineTTY(prompt);
|
|
2601
|
+
}
|
|
2602
|
+
async function readLineTTY(prompt) {
|
|
2603
|
+
const store = new PasteStore((id, lines) => t("repl.pasted", { id, lines }));
|
|
2604
|
+
const filter = new PasteFilter(store);
|
|
2605
|
+
const proxy = new PassThrough();
|
|
2606
|
+
const rl = readline.createInterface({ input: proxy, output: stdout, terminal: true });
|
|
2607
|
+
const onData = (buf) => {
|
|
2608
|
+
proxy.write(filter.push(buf.toString("utf8")));
|
|
2609
|
+
};
|
|
2610
|
+
stdout.write(ENABLE_BRACKETED_PASTE);
|
|
2611
|
+
stdin.setRawMode(true);
|
|
2612
|
+
stdin.resume();
|
|
2613
|
+
stdin.on("data", onData);
|
|
2614
|
+
try {
|
|
2615
|
+
const line = await new Promise((resolve9) => {
|
|
2616
|
+
rl.question(prompt).then(resolve9, () => resolve9(null));
|
|
2617
|
+
rl.on("SIGINT", () => resolve9(null));
|
|
2618
|
+
rl.on("close", () => resolve9(null));
|
|
2619
|
+
});
|
|
2620
|
+
return line === null ? null : store.expand(line);
|
|
2621
|
+
} finally {
|
|
2622
|
+
stdin.off("data", onData);
|
|
2623
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
2624
|
+
stdout.write(DISABLE_BRACKETED_PASTE);
|
|
2625
|
+
rl.close();
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2386
2629
|
// src/ui/repl.ts
|
|
2387
2630
|
async function startRepl(ctx) {
|
|
2388
2631
|
for (; ; ) {
|
|
@@ -2412,16 +2655,6 @@ async function startRepl(ctx) {
|
|
|
2412
2655
|
await handleCommand(cmd, arg, ctx);
|
|
2413
2656
|
}
|
|
2414
2657
|
}
|
|
2415
|
-
async function readLine(prompt) {
|
|
2416
|
-
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
2417
|
-
try {
|
|
2418
|
-
return await rl.question(prompt);
|
|
2419
|
-
} catch {
|
|
2420
|
-
return null;
|
|
2421
|
-
} finally {
|
|
2422
|
-
rl.close();
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
2658
|
async function handleCommand(cmd, arg, ctx) {
|
|
2426
2659
|
const { session } = ctx;
|
|
2427
2660
|
switch (cmd) {
|
|
@@ -2446,6 +2679,18 @@ async function handleCommand(cmd, arg, ctx) {
|
|
|
2446
2679
|
session.history = [];
|
|
2447
2680
|
console.log(pc6.dim(t("repl.historyCleared")));
|
|
2448
2681
|
return;
|
|
2682
|
+
case "swarm": {
|
|
2683
|
+
if (!arg) {
|
|
2684
|
+
console.log(pc6.yellow(t("repl.needName", { usage: "/swarm <task>" })));
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
try {
|
|
2688
|
+
await ctx.runSwarm(arg);
|
|
2689
|
+
} catch (e) {
|
|
2690
|
+
console.log(pc6.red(`\u2717 ${e.message}`));
|
|
2691
|
+
}
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2449
2694
|
case "agents":
|
|
2450
2695
|
printAgents(ctx.getConfig(), session.agentName);
|
|
2451
2696
|
return;
|
|
@@ -2509,113 +2754,525 @@ async function removeAgent2(name, ctx) {
|
|
|
2509
2754
|
}
|
|
2510
2755
|
}
|
|
2511
2756
|
|
|
2512
|
-
// src/
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
/** Extra dim text appended after the elapsed time (e.g. token count). */
|
|
2525
|
-
setSuffix(suffix) {
|
|
2526
|
-
this.suffix = suffix;
|
|
2757
|
+
// src/cli/commands/swarm.ts
|
|
2758
|
+
import pc7 from "picocolors";
|
|
2759
|
+
|
|
2760
|
+
// src/core/git/worktree.ts
|
|
2761
|
+
import { mkdtemp } from "fs/promises";
|
|
2762
|
+
import { tmpdir } from "os";
|
|
2763
|
+
import { join as join4 } from "path";
|
|
2764
|
+
import { simpleGit } from "simple-git";
|
|
2765
|
+
async function ensureRepo(workspace) {
|
|
2766
|
+
const git = simpleGit(workspace);
|
|
2767
|
+
if (!await git.checkIsRepo()) {
|
|
2768
|
+
await git.init();
|
|
2527
2769
|
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
if (this.timer) return;
|
|
2533
|
-
this.startedAt = Date.now();
|
|
2534
|
-
this.render();
|
|
2535
|
-
this.timer = setInterval(() => this.render(), 90);
|
|
2536
|
-
this.timer.unref?.();
|
|
2770
|
+
const identity = await identityArgs(git);
|
|
2771
|
+
const hasHead = await git.raw(["rev-parse", "--verify", "HEAD"]).then(() => true).catch(() => false);
|
|
2772
|
+
if (!hasHead) {
|
|
2773
|
+
await git.raw([...identity, "commit", "--allow-empty", "-m", "polypus: initial commit"]);
|
|
2537
2774
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2775
|
+
return git;
|
|
2776
|
+
}
|
|
2777
|
+
async function identityArgs(git) {
|
|
2778
|
+
const email = await git.raw(["config", "user.email"]).catch(() => "");
|
|
2779
|
+
if (email.trim()) return [];
|
|
2780
|
+
return ["-c", "user.email=polypus@local", "-c", "user.name=Polypus"];
|
|
2781
|
+
}
|
|
2782
|
+
async function createWorktree(git, label) {
|
|
2783
|
+
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
2784
|
+
const path = await mkdtemp(join4(tmpdir(), "polypus-wt-"));
|
|
2785
|
+
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
2786
|
+
return { path, branch };
|
|
2787
|
+
}
|
|
2788
|
+
async function commitWorktree(wt, message) {
|
|
2789
|
+
const wtGit = simpleGit(wt.path);
|
|
2790
|
+
await wtGit.add(["-A"]);
|
|
2791
|
+
const status = await wtGit.status();
|
|
2792
|
+
if (status.staged.length === 0 && status.files.length === 0) return false;
|
|
2793
|
+
const identity = await identityArgs(wtGit);
|
|
2794
|
+
await wtGit.raw([...identity, "commit", "-m", message]);
|
|
2795
|
+
return true;
|
|
2796
|
+
}
|
|
2797
|
+
async function mergeWorktreeBranch(git, branch) {
|
|
2798
|
+
try {
|
|
2799
|
+
const identity = await identityArgs(git);
|
|
2800
|
+
await git.raw([...identity, "merge", "--no-edit", branch]);
|
|
2801
|
+
return { branch, ok: true, conflicts: [] };
|
|
2802
|
+
} catch (err) {
|
|
2803
|
+
const status = await git.status().catch(() => void 0);
|
|
2804
|
+
const conflicts = status?.conflicted ?? [];
|
|
2805
|
+
await git.raw(["merge", "--abort"]).catch(() => void 0);
|
|
2806
|
+
if (conflicts.length === 0) {
|
|
2807
|
+
throw err;
|
|
2543
2808
|
}
|
|
2544
|
-
|
|
2545
|
-
}
|
|
2546
|
-
render() {
|
|
2547
|
-
const f = violet(FRAMES[this.frame = (this.frame + 1) % FRAMES.length]);
|
|
2548
|
-
const secs = Math.floor((Date.now() - this.startedAt) / 1e3);
|
|
2549
|
-
const time = secs > 0 ? dim(` (${secs}s)`) : "";
|
|
2550
|
-
const suffix = this.suffix ? dim(` \xB7 ${this.suffix}`) : "";
|
|
2551
|
-
process.stdout.write(`\r\x1B[K${f} \u{1F419} ${dim(this.label + "\u2026")}${time}${suffix}`);
|
|
2809
|
+
return { branch, ok: false, conflicts };
|
|
2552
2810
|
}
|
|
2553
|
-
}
|
|
2811
|
+
}
|
|
2812
|
+
async function removeWorktree(git, wt) {
|
|
2813
|
+
await git.raw(["worktree", "remove", wt.path, "--force"]).catch(() => void 0);
|
|
2814
|
+
await git.raw(["branch", "-D", wt.branch]).catch(() => void 0);
|
|
2815
|
+
}
|
|
2554
2816
|
|
|
2555
|
-
// src/
|
|
2556
|
-
async function
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
agentName: agentConfig.name,
|
|
2562
|
-
mode: opts.mode ?? config.permissions.mode,
|
|
2563
|
-
allow: config.permissions.allow,
|
|
2564
|
-
deny: config.permissions.deny,
|
|
2565
|
-
allowedCommands: config.permissions.allowedCommands,
|
|
2566
|
-
maxSteps: opts.maxSteps ? Number(opts.maxSteps) : void 0,
|
|
2567
|
-
history: []
|
|
2568
|
-
};
|
|
2569
|
-
const runTask = async (taskText) => {
|
|
2570
|
-
const active = resolveAgent(config, session.agentName);
|
|
2571
|
-
const resolved2 = createProvider(active);
|
|
2572
|
-
await executeTask(taskText, resolved2, workspace, session);
|
|
2573
|
-
};
|
|
2574
|
-
if (task) {
|
|
2575
|
-
const resolved2 = createProvider(agentConfig);
|
|
2576
|
-
console.log(
|
|
2577
|
-
pc7.dim(
|
|
2578
|
-
t("run.status", {
|
|
2579
|
-
name: resolved2.config.name,
|
|
2580
|
-
provider: resolved2.config.provider,
|
|
2581
|
-
model: resolved2.config.model,
|
|
2582
|
-
toolMode: resolved2.toolMode,
|
|
2583
|
-
mode: session.mode
|
|
2584
|
-
})
|
|
2585
|
-
)
|
|
2586
|
-
);
|
|
2587
|
-
await executeTask(task, resolved2, workspace, session);
|
|
2588
|
-
return;
|
|
2589
|
-
}
|
|
2590
|
-
const resolved = createProvider(agentConfig);
|
|
2591
|
-
await printWelcome({
|
|
2592
|
-
agentName: resolved.config.name,
|
|
2593
|
-
provider: resolved.config.provider,
|
|
2594
|
-
model: resolved.config.model,
|
|
2595
|
-
toolMode: resolved.toolMode,
|
|
2596
|
-
mode: session.mode,
|
|
2597
|
-
workspace
|
|
2817
|
+
// src/core/agent/worker.ts
|
|
2818
|
+
async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
2819
|
+
const permissions = new PermissionEngine({
|
|
2820
|
+
mode: "bypass",
|
|
2821
|
+
policy: { workspace: wt.path, allow, deny },
|
|
2822
|
+
allowedCommands: []
|
|
2598
2823
|
});
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2824
|
+
const result = await runAgent({
|
|
2825
|
+
task: subtask.brief,
|
|
2826
|
+
workspace: wt.path,
|
|
2827
|
+
agent,
|
|
2828
|
+
permissions,
|
|
2829
|
+
promptContext: { workspace: wt.path, mode: "bypass", allow, briefing: subtask.brief },
|
|
2830
|
+
events
|
|
2831
|
+
});
|
|
2832
|
+
const committed = await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
2833
|
+
return {
|
|
2834
|
+
subtask,
|
|
2835
|
+
agentName: agent.config.name,
|
|
2836
|
+
branch: wt.branch,
|
|
2837
|
+
finished: result.finished,
|
|
2838
|
+
summary: result.summary,
|
|
2839
|
+
committed,
|
|
2840
|
+
steps: result.steps
|
|
2606
2841
|
};
|
|
2607
|
-
await startRepl(ctx);
|
|
2608
2842
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2843
|
+
|
|
2844
|
+
// src/core/agent/orchestrator.ts
|
|
2845
|
+
async function runSwarm(opts) {
|
|
2846
|
+
const lead = opts.agents[0];
|
|
2847
|
+
if (!lead) throw new Error("Swarm requires at least one agent.");
|
|
2848
|
+
const maxSubtasks = opts.maxSubtasks ?? Math.max(opts.agents.length, 2);
|
|
2849
|
+
const git = await ensureRepo(opts.workspace);
|
|
2850
|
+
const subtasks = await decompose(lead, opts.task, maxSubtasks);
|
|
2851
|
+
opts.events?.onDecomposed?.(subtasks);
|
|
2852
|
+
const worktrees = [];
|
|
2853
|
+
for (const subtask of subtasks) {
|
|
2854
|
+
worktrees.push(await createWorktree(git, subtask.id));
|
|
2855
|
+
}
|
|
2856
|
+
const outcomes = await Promise.all(
|
|
2857
|
+
subtasks.map(async (subtask, i) => {
|
|
2858
|
+
const agent = opts.agents[i % opts.agents.length];
|
|
2859
|
+
const wt = worktrees[i];
|
|
2860
|
+
opts.events?.onWorkerStart?.(subtask, agent.config.name);
|
|
2861
|
+
const outcome = await runWorker(
|
|
2862
|
+
subtask,
|
|
2863
|
+
agent,
|
|
2864
|
+
wt,
|
|
2865
|
+
opts.allow,
|
|
2866
|
+
opts.deny,
|
|
2867
|
+
opts.events?.workerEvents?.(subtask)
|
|
2868
|
+
);
|
|
2869
|
+
opts.events?.onWorkerDone?.(outcome);
|
|
2870
|
+
return outcome;
|
|
2871
|
+
})
|
|
2872
|
+
);
|
|
2873
|
+
const merges = [];
|
|
2874
|
+
for (const outcome of outcomes) {
|
|
2875
|
+
if (!outcome.committed) continue;
|
|
2876
|
+
const merge = await mergeWorktreeBranch(git, outcome.branch);
|
|
2877
|
+
merges.push(merge);
|
|
2878
|
+
opts.events?.onMerge?.(merge);
|
|
2879
|
+
}
|
|
2880
|
+
const conflicted = new Set(merges.filter((m) => !m.ok).map((m) => m.branch));
|
|
2881
|
+
for (const wt of worktrees) {
|
|
2882
|
+
if (conflicted.has(wt.branch)) {
|
|
2883
|
+
await git.raw(["worktree", "remove", wt.path, "--force"]).catch(() => void 0);
|
|
2884
|
+
} else {
|
|
2885
|
+
await removeWorktree(git, wt);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return { subtasks, outcomes, merges };
|
|
2889
|
+
}
|
|
2890
|
+
var DECOMPOSE_SYSTEM = [
|
|
2891
|
+
"You are a tech lead splitting a coding task into independent subtasks that can be done in parallel.",
|
|
2892
|
+
'Return ONLY a JSON array. Each item: {"title": string, "brief": string}.',
|
|
2893
|
+
"Make subtasks touch DIFFERENT files/areas to minimize merge conflicts.",
|
|
2894
|
+
"Keep the list small (prefer 2-4 items). Each brief must be self-contained and actionable."
|
|
2895
|
+
].join("\n");
|
|
2896
|
+
async function decompose(lead, task, maxSubtasks) {
|
|
2897
|
+
try {
|
|
2898
|
+
const res = await lead.provider.chat({
|
|
2899
|
+
messages: [
|
|
2900
|
+
{ role: "system", content: DECOMPOSE_SYSTEM },
|
|
2901
|
+
{ role: "user", content: `Task:
|
|
2902
|
+
${task}
|
|
2903
|
+
|
|
2904
|
+
Return at most ${maxSubtasks} subtasks as a JSON array.` }
|
|
2905
|
+
],
|
|
2906
|
+
params: { temperature: 0 }
|
|
2907
|
+
});
|
|
2908
|
+
const parsed = extractJsonArray(res.content);
|
|
2909
|
+
if (parsed && parsed.length > 0) {
|
|
2910
|
+
return parsed.slice(0, maxSubtasks).map((item, i) => ({
|
|
2911
|
+
id: `t${i + 1}`,
|
|
2912
|
+
title: String(item.title ?? `subtask ${i + 1}`),
|
|
2913
|
+
brief: String(item.brief ?? item.title ?? task)
|
|
2914
|
+
}));
|
|
2915
|
+
}
|
|
2916
|
+
} catch {
|
|
2917
|
+
}
|
|
2918
|
+
return [{ id: "t1", title: "task", brief: task }];
|
|
2919
|
+
}
|
|
2920
|
+
function extractJsonArray(text2) {
|
|
2921
|
+
const start = text2.indexOf("[");
|
|
2922
|
+
const end = text2.lastIndexOf("]");
|
|
2923
|
+
if (start === -1 || end <= start) return null;
|
|
2924
|
+
try {
|
|
2925
|
+
const parsed = JSON.parse(text2.slice(start, end + 1));
|
|
2926
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
2927
|
+
} catch {
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
// src/ui/swarm-view.ts
|
|
2933
|
+
var RESET2 = "\x1B[0m";
|
|
2934
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2935
|
+
function describeToolCall(call) {
|
|
2936
|
+
const raw = call.name === "run_command" ? call.arguments.command : call.arguments.path;
|
|
2937
|
+
const arg = typeof raw === "string" ? raw : "";
|
|
2938
|
+
const short = arg.length > 40 ? arg.slice(0, 39) + "\u2026" : arg;
|
|
2939
|
+
return short ? `${call.name} ${short}` : call.name;
|
|
2940
|
+
}
|
|
2941
|
+
var SwarmView = class {
|
|
2942
|
+
constructor(leadName, opts = {}) {
|
|
2943
|
+
this.leadName = leadName;
|
|
2944
|
+
this.tty = opts.tty ?? (Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
|
|
2945
|
+
this.color = opts.color ?? this.tty;
|
|
2946
|
+
this.write = opts.sink ?? ((s) => process.stdout.write(s));
|
|
2947
|
+
}
|
|
2948
|
+
leadName;
|
|
2949
|
+
tty;
|
|
2950
|
+
color;
|
|
2951
|
+
write;
|
|
2952
|
+
workers = /* @__PURE__ */ new Map();
|
|
2953
|
+
order = [];
|
|
2954
|
+
phase = "decomposing";
|
|
2955
|
+
frame = 0;
|
|
2956
|
+
lastLines = 0;
|
|
2957
|
+
timer;
|
|
2958
|
+
start() {
|
|
2959
|
+
if (!this.tty) {
|
|
2960
|
+
this.write(`\u{1F419} ${t("swarm.view.header", { lead: this.leadName })} \u2014 ${t("swarm.view.decomposing")}
|
|
2961
|
+
`);
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
this.flush();
|
|
2965
|
+
this.timer = setInterval(() => {
|
|
2966
|
+
this.frame = (this.frame + 1) % FRAMES.length;
|
|
2967
|
+
this.flush();
|
|
2968
|
+
}, 110);
|
|
2969
|
+
this.timer.unref?.();
|
|
2970
|
+
}
|
|
2971
|
+
setSubtasks(subtasks) {
|
|
2972
|
+
this.phase = "running";
|
|
2973
|
+
for (const s of subtasks) {
|
|
2974
|
+
this.workers.set(s.id, { id: s.id, title: s.title, agent: "", status: "pending", action: "", steps: 0 });
|
|
2975
|
+
this.order.push(s.id);
|
|
2976
|
+
}
|
|
2977
|
+
if (!this.tty) {
|
|
2978
|
+
this.write(` ${t("swarm.decomposed", { n: subtasks.length })}
|
|
2979
|
+
`);
|
|
2980
|
+
for (const s of subtasks) this.write(` ${s.id}: ${s.title}
|
|
2981
|
+
`);
|
|
2982
|
+
}
|
|
2983
|
+
this.flush();
|
|
2984
|
+
}
|
|
2985
|
+
workerStart(id, agent) {
|
|
2986
|
+
const w = this.workers.get(id);
|
|
2987
|
+
if (!w) return;
|
|
2988
|
+
w.agent = agent;
|
|
2989
|
+
w.status = "running";
|
|
2990
|
+
if (!this.tty) this.write(` \u25B6 ${id} [${agent}] ${w.title}
|
|
2991
|
+
`);
|
|
2992
|
+
this.flush();
|
|
2993
|
+
}
|
|
2994
|
+
workerAction(id, action) {
|
|
2995
|
+
const w = this.workers.get(id);
|
|
2996
|
+
if (!w) return;
|
|
2997
|
+
w.action = action;
|
|
2998
|
+
this.flush();
|
|
2999
|
+
}
|
|
3000
|
+
workerStep(id, n) {
|
|
3001
|
+
const w = this.workers.get(id);
|
|
3002
|
+
if (!w) return;
|
|
3003
|
+
w.steps = n;
|
|
3004
|
+
this.flush();
|
|
3005
|
+
}
|
|
3006
|
+
workerDone(o) {
|
|
3007
|
+
const w = this.workers.get(o.subtask.id);
|
|
3008
|
+
if (!w) return;
|
|
3009
|
+
w.status = o.finished ? "done" : "stopped";
|
|
3010
|
+
w.steps = o.steps;
|
|
3011
|
+
w.branch = o.branch;
|
|
3012
|
+
w.action = "";
|
|
3013
|
+
if (!this.tty) {
|
|
3014
|
+
const tag = o.finished ? "\u2713" : "\u25A0";
|
|
3015
|
+
const changes = o.committed ? t("swarm.changesCommitted") : t("swarm.noChanges");
|
|
3016
|
+
this.write(` ${tag} ${o.subtask.id} (${t("swarm.view.steps", { n: o.steps })}, ${changes})
|
|
3017
|
+
`);
|
|
3018
|
+
}
|
|
3019
|
+
this.flush();
|
|
3020
|
+
}
|
|
3021
|
+
merge(r) {
|
|
3022
|
+
for (const w of this.workers.values()) {
|
|
3023
|
+
if (w.branch === r.branch) w.merge = r.ok ? "ok" : "conflict";
|
|
3024
|
+
}
|
|
3025
|
+
if (!this.tty) {
|
|
3026
|
+
this.write(r.ok ? ` \u2935 ${t("swarm.merged", { branch: r.branch })}
|
|
3027
|
+
` : ` \u2717 ${t("swarm.mergeConflict", { branch: r.branch })}
|
|
3028
|
+
`);
|
|
3029
|
+
}
|
|
3030
|
+
this.flush();
|
|
3031
|
+
}
|
|
3032
|
+
stop() {
|
|
3033
|
+
this.phase = "done";
|
|
3034
|
+
if (this.timer) {
|
|
3035
|
+
clearInterval(this.timer);
|
|
3036
|
+
this.timer = void 0;
|
|
3037
|
+
}
|
|
3038
|
+
this.flush();
|
|
3039
|
+
}
|
|
3040
|
+
/** Content lines of the dashboard (no cursor control). Exposed for tests. */
|
|
3041
|
+
frameLines() {
|
|
3042
|
+
const spin = this.dim(FRAMES[this.frame]);
|
|
3043
|
+
const lead = `\u{1F419} ${t("swarm.view.header", { lead: this.leadName })}`;
|
|
3044
|
+
const lines = [];
|
|
3045
|
+
if (this.phase === "decomposing") {
|
|
3046
|
+
lines.push(`${spin} ${lead}`);
|
|
3047
|
+
lines.push(" " + this.dim(t("swarm.view.decomposing")));
|
|
3048
|
+
return lines;
|
|
3049
|
+
}
|
|
3050
|
+
lines.push(`${this.phase === "running" ? spin : " "} ${lead}`);
|
|
3051
|
+
lines.push("");
|
|
3052
|
+
for (const id of this.order) {
|
|
3053
|
+
const w = this.workers.get(id);
|
|
3054
|
+
lines.push(this.row(w, spin));
|
|
3055
|
+
}
|
|
3056
|
+
return lines;
|
|
3057
|
+
}
|
|
3058
|
+
// -------------------------------------------------------------------------
|
|
3059
|
+
row(w, spin) {
|
|
3060
|
+
const icon = w.status === "running" ? spin : w.status === "done" ? this.c("\u2713", "32") : w.status === "stopped" ? this.c("\u25A0", "33") : this.dim("\xB7");
|
|
3061
|
+
const status = this.statusLabel(w);
|
|
3062
|
+
const meta = w.steps > 0 ? this.dim(" \xB7 " + (w.status === "running" ? t("swarm.view.step", { n: w.steps }) : t("swarm.view.steps", { n: w.steps }))) : "";
|
|
3063
|
+
const action = w.action ? w.action : this.dim("\u2014");
|
|
3064
|
+
return ` ${icon} ${pad(w.id, 4)} ${pad(status, 12)} ${pad(`[${w.agent}]`, 14)} ${action}${meta}`;
|
|
3065
|
+
}
|
|
3066
|
+
statusLabel(w) {
|
|
3067
|
+
if (w.merge === "conflict") return this.c(t("swarm.view.conflict"), "31");
|
|
3068
|
+
if (w.status === "running") return this.c(t("swarm.view.running"), "36");
|
|
3069
|
+
if (w.status === "done") return this.c(t("swarm.view.done"), "32");
|
|
3070
|
+
if (w.status === "stopped") return this.c(t("swarm.view.stopped"), "33");
|
|
3071
|
+
return this.dim(t("swarm.view.pending"));
|
|
3072
|
+
}
|
|
3073
|
+
/** Redraw the block in place (TTY) by clearing the previous frame first. */
|
|
3074
|
+
flush() {
|
|
3075
|
+
if (!this.tty) return;
|
|
3076
|
+
const lines = this.frameLines();
|
|
3077
|
+
let s = "";
|
|
3078
|
+
if (this.lastLines > 0) s += `\x1B[${this.lastLines}A`;
|
|
3079
|
+
s += "\x1B[0J";
|
|
3080
|
+
s += lines.join("\n") + "\n";
|
|
3081
|
+
this.write(s);
|
|
3082
|
+
this.lastLines = lines.length;
|
|
3083
|
+
}
|
|
3084
|
+
c(s, code) {
|
|
3085
|
+
return this.color ? `\x1B[${code}m${s}${RESET2}` : s;
|
|
3086
|
+
}
|
|
3087
|
+
dim(s) {
|
|
3088
|
+
return this.color ? `\x1B[2m${s}${RESET2}` : s;
|
|
3089
|
+
}
|
|
3090
|
+
};
|
|
3091
|
+
function pad(s, n) {
|
|
3092
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
// src/cli/commands/swarm.ts
|
|
3096
|
+
var MIN_SWARM_AGENTS = 3;
|
|
3097
|
+
function canSwarm(agentCount) {
|
|
3098
|
+
return agentCount >= MIN_SWARM_AGENTS;
|
|
3099
|
+
}
|
|
3100
|
+
async function runSwarmSession(task, config, opts = {}) {
|
|
3101
|
+
if (!canSwarm(config.agents.length)) {
|
|
3102
|
+
throw new Error(t("swarm.needsAgents", { min: MIN_SWARM_AGENTS, have: config.agents.length }));
|
|
3103
|
+
}
|
|
3104
|
+
const workspace = opts.workspace ?? process.cwd();
|
|
3105
|
+
const selected = opts.agents?.length ? opts.agents : config.agents.map((a) => a.name);
|
|
3106
|
+
if (selected.length === 0) {
|
|
3107
|
+
throw new Error(t("swarm.noAgents"));
|
|
3108
|
+
}
|
|
3109
|
+
const resolved = selected.map((name) => {
|
|
3110
|
+
const a = config.agents.find((x) => x.name === name);
|
|
3111
|
+
if (!a) throw new Error(t("agent.notFound", { name }));
|
|
3112
|
+
return createProvider(a);
|
|
3113
|
+
});
|
|
3114
|
+
console.log(
|
|
3115
|
+
pc7.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace }))
|
|
3116
|
+
);
|
|
3117
|
+
console.log(pc7.yellow(t("swarm.bypassNote") + "\n"));
|
|
3118
|
+
const view = new SwarmView(resolved[0].config.name);
|
|
3119
|
+
view.start();
|
|
3120
|
+
let result;
|
|
3121
|
+
try {
|
|
3122
|
+
result = await runSwarm({
|
|
3123
|
+
task,
|
|
3124
|
+
workspace,
|
|
3125
|
+
agents: resolved,
|
|
3126
|
+
allow: config.permissions.allow,
|
|
3127
|
+
deny: config.permissions.deny,
|
|
3128
|
+
maxSubtasks: opts.maxSubtasks,
|
|
3129
|
+
events: {
|
|
3130
|
+
onDecomposed: (subtasks) => view.setSubtasks(subtasks),
|
|
3131
|
+
onWorkerStart: (subtask, agentName) => view.workerStart(subtask.id, agentName),
|
|
3132
|
+
onWorkerDone: (outcome) => view.workerDone(outcome),
|
|
3133
|
+
onMerge: (merge) => view.merge(merge),
|
|
3134
|
+
workerEvents: (subtask) => ({
|
|
3135
|
+
onToolCall: (call) => view.workerAction(subtask.id, describeToolCall(call)),
|
|
3136
|
+
onStep: (step) => view.workerStep(subtask.id, step)
|
|
3137
|
+
})
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
} finally {
|
|
3141
|
+
view.stop();
|
|
3142
|
+
}
|
|
3143
|
+
console.log("");
|
|
3144
|
+
console.log(pc7.bold("\n" + t("swarm.summary")));
|
|
3145
|
+
for (const o of result.outcomes) {
|
|
3146
|
+
const status = o.finished ? pc7.green(t("swarm.statusDone")) : pc7.yellow(t("swarm.statusIncomplete"));
|
|
3147
|
+
const committed = o.committed ? "" : pc7.dim(` (${t("swarm.noChanges")})`);
|
|
3148
|
+
console.log(` ${pc7.bold(o.subtask.id)} [${o.agentName}] ${status}${committed} \u2014 ${o.subtask.title}`);
|
|
3149
|
+
}
|
|
3150
|
+
const conflicts = result.merges.filter((m) => !m.ok);
|
|
3151
|
+
if (conflicts.length > 0) {
|
|
3152
|
+
console.log(pc7.red("\n" + t("swarm.conflictsHeader", { n: conflicts.length })));
|
|
3153
|
+
for (const m of conflicts) {
|
|
3154
|
+
console.log(pc7.red(` ${m.branch}: ${m.conflicts.join(", ")}`));
|
|
3155
|
+
}
|
|
3156
|
+
} else {
|
|
3157
|
+
console.log(pc7.green("\n" + t("swarm.allMerged")));
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
async function swarm(task, opts) {
|
|
3161
|
+
const config = await loadConfig();
|
|
3162
|
+
await runSwarmSession(task, config, {
|
|
3163
|
+
agents: opts.agents ? opts.agents.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
3164
|
+
maxSubtasks: opts.maxSubtasks ? Number(opts.maxSubtasks) : void 0
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
// src/ui/spinner.ts
|
|
3169
|
+
var RESET3 = "\x1B[0m";
|
|
3170
|
+
var isTTY = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
3171
|
+
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3172
|
+
var violet = (s) => isTTY ? `\x1B[38;2;167;139;250m${s}${RESET3}` : s;
|
|
3173
|
+
var dim = (s) => isTTY ? `\x1B[2m${s}${RESET3}` : s;
|
|
3174
|
+
var Spinner = class {
|
|
3175
|
+
timer;
|
|
3176
|
+
frame = 0;
|
|
3177
|
+
startedAt = 0;
|
|
3178
|
+
label = "";
|
|
3179
|
+
suffix = "";
|
|
3180
|
+
/** Extra dim text appended after the elapsed time (e.g. token count). */
|
|
3181
|
+
setSuffix(suffix) {
|
|
3182
|
+
this.suffix = suffix;
|
|
3183
|
+
}
|
|
3184
|
+
/** Start (or, if already running, just update the label). */
|
|
3185
|
+
start(label) {
|
|
3186
|
+
this.label = label;
|
|
3187
|
+
if (!isTTY) return;
|
|
3188
|
+
if (this.timer) return;
|
|
3189
|
+
this.startedAt = Date.now();
|
|
3190
|
+
this.render();
|
|
3191
|
+
this.timer = setInterval(() => this.render(), 90);
|
|
3192
|
+
this.timer.unref?.();
|
|
3193
|
+
}
|
|
3194
|
+
/** Erase the spinner line and stop animating. */
|
|
3195
|
+
stop() {
|
|
3196
|
+
if (this.timer) {
|
|
3197
|
+
clearInterval(this.timer);
|
|
3198
|
+
this.timer = void 0;
|
|
3199
|
+
}
|
|
3200
|
+
if (isTTY) process.stdout.write("\r\x1B[K");
|
|
3201
|
+
}
|
|
3202
|
+
render() {
|
|
3203
|
+
const f = violet(FRAMES2[this.frame = (this.frame + 1) % FRAMES2.length]);
|
|
3204
|
+
const secs = Math.floor((Date.now() - this.startedAt) / 1e3);
|
|
3205
|
+
const time = secs > 0 ? dim(` (${secs}s)`) : "";
|
|
3206
|
+
const suffix = this.suffix ? dim(` \xB7 ${this.suffix}`) : "";
|
|
3207
|
+
process.stdout.write(`\r\x1B[K${f} \u{1F419} ${dim(this.label + "\u2026")}${time}${suffix}`);
|
|
3208
|
+
}
|
|
3209
|
+
};
|
|
3210
|
+
|
|
3211
|
+
// src/cli/commands/run.ts
|
|
3212
|
+
async function run(task, opts) {
|
|
3213
|
+
let config = await loadConfig();
|
|
3214
|
+
const agentConfig = resolveAgent(config, opts.agent);
|
|
3215
|
+
const workspace = process.cwd();
|
|
3216
|
+
const session = {
|
|
3217
|
+
agentName: agentConfig.name,
|
|
3218
|
+
mode: opts.mode ?? config.permissions.mode,
|
|
3219
|
+
allow: config.permissions.allow,
|
|
3220
|
+
deny: config.permissions.deny,
|
|
3221
|
+
allowedCommands: config.permissions.allowedCommands,
|
|
3222
|
+
maxSteps: opts.maxSteps ? Number(opts.maxSteps) : void 0,
|
|
3223
|
+
history: []
|
|
3224
|
+
};
|
|
3225
|
+
const runTask = async (taskText) => {
|
|
3226
|
+
const active = resolveAgent(config, session.agentName);
|
|
3227
|
+
const resolved2 = createProvider(active);
|
|
3228
|
+
await executeTask(taskText, resolved2, workspace, session);
|
|
3229
|
+
};
|
|
3230
|
+
if (task) {
|
|
3231
|
+
const resolved2 = createProvider(agentConfig);
|
|
3232
|
+
console.log(
|
|
3233
|
+
pc8.dim(
|
|
3234
|
+
t("run.status", {
|
|
3235
|
+
name: resolved2.config.name,
|
|
3236
|
+
provider: resolved2.config.provider,
|
|
3237
|
+
model: resolved2.config.model,
|
|
3238
|
+
toolMode: resolved2.toolMode,
|
|
3239
|
+
mode: session.mode
|
|
3240
|
+
})
|
|
3241
|
+
)
|
|
3242
|
+
);
|
|
3243
|
+
await executeTask(task, resolved2, workspace, session);
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
const resolved = createProvider(agentConfig);
|
|
3247
|
+
await printWelcome({
|
|
3248
|
+
agentName: resolved.config.name,
|
|
3249
|
+
provider: resolved.config.provider,
|
|
3250
|
+
model: resolved.config.model,
|
|
3251
|
+
toolMode: resolved.toolMode,
|
|
3252
|
+
mode: session.mode,
|
|
3253
|
+
workspace
|
|
3254
|
+
});
|
|
3255
|
+
const ctx = {
|
|
3256
|
+
session,
|
|
3257
|
+
runTask,
|
|
3258
|
+
runSwarm: (taskText) => runSwarmSession(taskText, config, { workspace }),
|
|
3259
|
+
getConfig: () => config,
|
|
3260
|
+
reload: async () => {
|
|
3261
|
+
config = await loadConfig();
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
await startRepl(ctx);
|
|
3265
|
+
}
|
|
3266
|
+
async function executeTask(task, resolved, workspace, session) {
|
|
3267
|
+
const spinner3 = new Spinner();
|
|
3268
|
+
const controller = new AbortController();
|
|
3269
|
+
const cancel2 = listenForCancel(controller);
|
|
3270
|
+
const permissions = new PermissionEngine({
|
|
3271
|
+
mode: session.mode,
|
|
3272
|
+
policy: { workspace, allow: session.allow, deny: session.deny },
|
|
3273
|
+
allowedCommands: session.allowedCommands,
|
|
3274
|
+
confirm: async (req) => {
|
|
3275
|
+
spinner3.stop();
|
|
2619
3276
|
cancel2.pause();
|
|
2620
3277
|
const ok = await confirmAction(req);
|
|
2621
3278
|
cancel2.resume();
|
|
@@ -2642,16 +3299,16 @@ async function executeTask(task, resolved, workspace, session) {
|
|
|
2642
3299
|
}
|
|
2643
3300
|
session.history = result.messages;
|
|
2644
3301
|
if (result.reason === "finished") {
|
|
2645
|
-
console.log(
|
|
3302
|
+
console.log(pc8.green("\n" + t("run.done", { steps: result.steps })) + (result.summary ? ` ${result.summary}` : ""));
|
|
2646
3303
|
} else if (result.reason === "cancelled") {
|
|
2647
|
-
console.log(
|
|
3304
|
+
console.log(pc8.dim("\n" + t("run.cancelled")));
|
|
2648
3305
|
} else if (result.reason === "stalled" || result.reason === "maxsteps") {
|
|
2649
|
-
console.log(
|
|
3306
|
+
console.log(pc8.yellow("\n" + t("run.stopped", { steps: result.steps })));
|
|
2650
3307
|
}
|
|
2651
3308
|
if (result.usage.promptTokens || result.usage.completionTokens) {
|
|
2652
3309
|
const total = result.usage.promptTokens + result.usage.completionTokens;
|
|
2653
3310
|
console.log(
|
|
2654
|
-
|
|
3311
|
+
pc8.dim(
|
|
2655
3312
|
"\u21B3 " + t("ui.tokens", {
|
|
2656
3313
|
total: fmtTokens(total),
|
|
2657
3314
|
in: fmtTokens(result.usage.promptTokens),
|
|
@@ -2692,7 +3349,7 @@ function listenForCancel(controller) {
|
|
|
2692
3349
|
return { pause: detach, resume: attach, dispose: detach };
|
|
2693
3350
|
}
|
|
2694
3351
|
async function confirmAction(req) {
|
|
2695
|
-
if (req.preview) console.log(
|
|
3352
|
+
if (req.preview) console.log(pc8.dim(req.preview));
|
|
2696
3353
|
const answer = await p2.confirm({ message: t("run.confirm", { summary: req.summary }) });
|
|
2697
3354
|
if (p2.isCancel(answer)) return false;
|
|
2698
3355
|
return answer === true;
|
|
@@ -2708,26 +3365,26 @@ function renderEvents(spinner3) {
|
|
|
2708
3365
|
},
|
|
2709
3366
|
onAssistantText(text2) {
|
|
2710
3367
|
spinner3.stop();
|
|
2711
|
-
if (text2.trim()) console.log(
|
|
3368
|
+
if (text2.trim()) console.log(pc8.cyan(text2.trim()));
|
|
2712
3369
|
},
|
|
2713
3370
|
onToolCall(call) {
|
|
2714
3371
|
spinner3.stop();
|
|
2715
3372
|
const arg = call.name === "run_command" ? call.arguments.command : call.arguments.path;
|
|
2716
|
-
console.log(
|
|
3373
|
+
console.log(pc8.dim(` \u2192 ${call.name}${arg ? ` ${String(arg)}` : ""}`));
|
|
2717
3374
|
spinner3.start(t("ui.running", { tool: call.name }));
|
|
2718
3375
|
},
|
|
2719
3376
|
onToolResult(_call, result) {
|
|
2720
3377
|
spinner3.stop();
|
|
2721
3378
|
const head = result.output.split("\n")[0] ?? "";
|
|
2722
|
-
console.log((result.ok ?
|
|
3379
|
+
console.log((result.ok ? pc8.green(" \u2713 ") : pc8.red(" \u2717 ")) + pc8.dim(head.slice(0, 120)));
|
|
2723
3380
|
},
|
|
2724
3381
|
onReprompt(attempt) {
|
|
2725
3382
|
spinner3.stop();
|
|
2726
|
-
console.log(
|
|
3383
|
+
console.log(pc8.yellow(" " + t("run.reprompt", { attempt })));
|
|
2727
3384
|
},
|
|
2728
3385
|
onCorrection() {
|
|
2729
3386
|
spinner3.stop();
|
|
2730
|
-
console.log(
|
|
3387
|
+
console.log(pc8.yellow(" \u21BB " + t("run.autocorrect")));
|
|
2731
3388
|
}
|
|
2732
3389
|
};
|
|
2733
3390
|
}
|
|
@@ -2738,11 +3395,11 @@ async function setup() {
|
|
|
2738
3395
|
}
|
|
2739
3396
|
|
|
2740
3397
|
// src/cli/commands/init.ts
|
|
2741
|
-
import
|
|
3398
|
+
import pc9 from "picocolors";
|
|
2742
3399
|
|
|
2743
3400
|
// src/core/scaffold/init.ts
|
|
2744
3401
|
import { mkdir as mkdir3, writeFile as writeFile4, access } from "fs/promises";
|
|
2745
|
-
import { dirname as dirname3, join as
|
|
3402
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
2746
3403
|
|
|
2747
3404
|
// src/core/scaffold/templates.ts
|
|
2748
3405
|
function polyTemplates(locale) {
|
|
@@ -2976,458 +3633,54 @@ A mudan\xE7a em termos simples \u2014 o comportamento que o usu\xE1rio vai realm
|
|
|
2976
3633
|
|
|
2977
3634
|
- \u2026
|
|
2978
3635
|
`
|
|
2979
|
-
};
|
|
2980
|
-
|
|
2981
|
-
// src/core/scaffold/init.ts
|
|
2982
|
-
async function scaffoldPoly(workspace, opts) {
|
|
2983
|
-
const templates = polyTemplates(opts.locale);
|
|
2984
|
-
const created = [];
|
|
2985
|
-
const skipped = [];
|
|
2986
|
-
for (const [rel, content] of Object.entries(templates)) {
|
|
2987
|
-
const display = `.poly/${rel}`;
|
|
2988
|
-
const abs = join3(workspace, ".poly", ...rel.split("/"));
|
|
2989
|
-
if (!opts.force && await exists(abs)) {
|
|
2990
|
-
skipped.push(display);
|
|
2991
|
-
continue;
|
|
2992
|
-
}
|
|
2993
|
-
await mkdir3(dirname3(abs), { recursive: true });
|
|
2994
|
-
await writeFile4(abs, content, "utf8");
|
|
2995
|
-
created.push(display);
|
|
2996
|
-
}
|
|
2997
|
-
return { created, skipped };
|
|
2998
|
-
}
|
|
2999
|
-
async function exists(path) {
|
|
3000
|
-
try {
|
|
3001
|
-
await access(path);
|
|
3002
|
-
return true;
|
|
3003
|
-
} catch {
|
|
3004
|
-
return false;
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
|
|
3008
|
-
// src/cli/commands/init.ts
|
|
3009
|
-
async function init(opts) {
|
|
3010
|
-
const { created, skipped } = await scaffoldPoly(process.cwd(), {
|
|
3011
|
-
force: Boolean(opts.force),
|
|
3012
|
-
locale: getLocale()
|
|
3013
|
-
});
|
|
3014
|
-
if (created.length === 0) {
|
|
3015
|
-
console.log(pc8.yellow(t("init.allExist")));
|
|
3016
|
-
for (const f of skipped) console.log(pc8.dim(` ${f}`));
|
|
3017
|
-
console.log(pc8.dim(t("init.forceHint")));
|
|
3018
|
-
return;
|
|
3019
|
-
}
|
|
3020
|
-
console.log(pc8.green(t("init.created")));
|
|
3021
|
-
for (const f of created) console.log(pc8.dim(` ${f}`));
|
|
3022
|
-
if (skipped.length > 0) {
|
|
3023
|
-
console.log(pc8.dim(t("init.skipped")));
|
|
3024
|
-
for (const f of skipped) console.log(pc8.dim(` ${f}`));
|
|
3025
|
-
}
|
|
3026
|
-
console.log("\n" + t("init.tip"));
|
|
3027
|
-
}
|
|
3028
|
-
|
|
3029
|
-
// src/cli/commands/swarm.ts
|
|
3030
|
-
import pc9 from "picocolors";
|
|
3031
|
-
|
|
3032
|
-
// src/core/git/worktree.ts
|
|
3033
|
-
import { mkdtemp } from "fs/promises";
|
|
3034
|
-
import { tmpdir } from "os";
|
|
3035
|
-
import { join as join4 } from "path";
|
|
3036
|
-
import { simpleGit } from "simple-git";
|
|
3037
|
-
async function ensureRepo(workspace) {
|
|
3038
|
-
const git = simpleGit(workspace);
|
|
3039
|
-
if (!await git.checkIsRepo()) {
|
|
3040
|
-
await git.init();
|
|
3041
|
-
}
|
|
3042
|
-
const identity = await identityArgs(git);
|
|
3043
|
-
const hasHead = await git.raw(["rev-parse", "--verify", "HEAD"]).then(() => true).catch(() => false);
|
|
3044
|
-
if (!hasHead) {
|
|
3045
|
-
await git.raw([...identity, "commit", "--allow-empty", "-m", "polypus: initial commit"]);
|
|
3046
|
-
}
|
|
3047
|
-
return git;
|
|
3048
|
-
}
|
|
3049
|
-
async function identityArgs(git) {
|
|
3050
|
-
const email = await git.raw(["config", "user.email"]).catch(() => "");
|
|
3051
|
-
if (email.trim()) return [];
|
|
3052
|
-
return ["-c", "user.email=polypus@local", "-c", "user.name=Polypus"];
|
|
3053
|
-
}
|
|
3054
|
-
async function createWorktree(git, label) {
|
|
3055
|
-
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
3056
|
-
const path = await mkdtemp(join4(tmpdir(), "polypus-wt-"));
|
|
3057
|
-
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3058
|
-
return { path, branch };
|
|
3059
|
-
}
|
|
3060
|
-
async function commitWorktree(wt, message) {
|
|
3061
|
-
const wtGit = simpleGit(wt.path);
|
|
3062
|
-
await wtGit.add(["-A"]);
|
|
3063
|
-
const status = await wtGit.status();
|
|
3064
|
-
if (status.staged.length === 0 && status.files.length === 0) return false;
|
|
3065
|
-
const identity = await identityArgs(wtGit);
|
|
3066
|
-
await wtGit.raw([...identity, "commit", "-m", message]);
|
|
3067
|
-
return true;
|
|
3068
|
-
}
|
|
3069
|
-
async function mergeWorktreeBranch(git, branch) {
|
|
3070
|
-
try {
|
|
3071
|
-
const identity = await identityArgs(git);
|
|
3072
|
-
await git.raw([...identity, "merge", "--no-edit", branch]);
|
|
3073
|
-
return { branch, ok: true, conflicts: [] };
|
|
3074
|
-
} catch (err) {
|
|
3075
|
-
const status = await git.status().catch(() => void 0);
|
|
3076
|
-
const conflicts = status?.conflicted ?? [];
|
|
3077
|
-
await git.raw(["merge", "--abort"]).catch(() => void 0);
|
|
3078
|
-
if (conflicts.length === 0) {
|
|
3079
|
-
throw err;
|
|
3080
|
-
}
|
|
3081
|
-
return { branch, ok: false, conflicts };
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
async function removeWorktree(git, wt) {
|
|
3085
|
-
await git.raw(["worktree", "remove", wt.path, "--force"]).catch(() => void 0);
|
|
3086
|
-
await git.raw(["branch", "-D", wt.branch]).catch(() => void 0);
|
|
3087
|
-
}
|
|
3088
|
-
|
|
3089
|
-
// src/core/agent/worker.ts
|
|
3090
|
-
async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
3091
|
-
const permissions = new PermissionEngine({
|
|
3092
|
-
mode: "bypass",
|
|
3093
|
-
policy: { workspace: wt.path, allow, deny },
|
|
3094
|
-
allowedCommands: []
|
|
3095
|
-
});
|
|
3096
|
-
const result = await runAgent({
|
|
3097
|
-
task: subtask.brief,
|
|
3098
|
-
workspace: wt.path,
|
|
3099
|
-
agent,
|
|
3100
|
-
permissions,
|
|
3101
|
-
promptContext: { workspace: wt.path, mode: "bypass", allow, briefing: subtask.brief },
|
|
3102
|
-
events
|
|
3103
|
-
});
|
|
3104
|
-
const committed = await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
3105
|
-
return {
|
|
3106
|
-
subtask,
|
|
3107
|
-
agentName: agent.config.name,
|
|
3108
|
-
branch: wt.branch,
|
|
3109
|
-
finished: result.finished,
|
|
3110
|
-
summary: result.summary,
|
|
3111
|
-
committed,
|
|
3112
|
-
steps: result.steps
|
|
3113
|
-
};
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
// src/core/agent/orchestrator.ts
|
|
3117
|
-
async function runSwarm(opts) {
|
|
3118
|
-
const lead = opts.agents[0];
|
|
3119
|
-
if (!lead) throw new Error("Swarm requires at least one agent.");
|
|
3120
|
-
const maxSubtasks = opts.maxSubtasks ?? Math.max(opts.agents.length, 2);
|
|
3121
|
-
const git = await ensureRepo(opts.workspace);
|
|
3122
|
-
const subtasks = await decompose(lead, opts.task, maxSubtasks);
|
|
3123
|
-
opts.events?.onDecomposed?.(subtasks);
|
|
3124
|
-
const worktrees = [];
|
|
3125
|
-
for (const subtask of subtasks) {
|
|
3126
|
-
worktrees.push(await createWorktree(git, subtask.id));
|
|
3127
|
-
}
|
|
3128
|
-
const outcomes = await Promise.all(
|
|
3129
|
-
subtasks.map(async (subtask, i) => {
|
|
3130
|
-
const agent = opts.agents[i % opts.agents.length];
|
|
3131
|
-
const wt = worktrees[i];
|
|
3132
|
-
opts.events?.onWorkerStart?.(subtask, agent.config.name);
|
|
3133
|
-
const outcome = await runWorker(
|
|
3134
|
-
subtask,
|
|
3135
|
-
agent,
|
|
3136
|
-
wt,
|
|
3137
|
-
opts.allow,
|
|
3138
|
-
opts.deny,
|
|
3139
|
-
opts.events?.workerEvents?.(subtask)
|
|
3140
|
-
);
|
|
3141
|
-
opts.events?.onWorkerDone?.(outcome);
|
|
3142
|
-
return outcome;
|
|
3143
|
-
})
|
|
3144
|
-
);
|
|
3145
|
-
const merges = [];
|
|
3146
|
-
for (const outcome of outcomes) {
|
|
3147
|
-
if (!outcome.committed) continue;
|
|
3148
|
-
const merge = await mergeWorktreeBranch(git, outcome.branch);
|
|
3149
|
-
merges.push(merge);
|
|
3150
|
-
opts.events?.onMerge?.(merge);
|
|
3151
|
-
}
|
|
3152
|
-
const conflicted = new Set(merges.filter((m) => !m.ok).map((m) => m.branch));
|
|
3153
|
-
for (const wt of worktrees) {
|
|
3154
|
-
if (conflicted.has(wt.branch)) {
|
|
3155
|
-
await git.raw(["worktree", "remove", wt.path, "--force"]).catch(() => void 0);
|
|
3156
|
-
} else {
|
|
3157
|
-
await removeWorktree(git, wt);
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
return { subtasks, outcomes, merges };
|
|
3161
|
-
}
|
|
3162
|
-
var DECOMPOSE_SYSTEM = [
|
|
3163
|
-
"You are a tech lead splitting a coding task into independent subtasks that can be done in parallel.",
|
|
3164
|
-
'Return ONLY a JSON array. Each item: {"title": string, "brief": string}.',
|
|
3165
|
-
"Make subtasks touch DIFFERENT files/areas to minimize merge conflicts.",
|
|
3166
|
-
"Keep the list small (prefer 2-4 items). Each brief must be self-contained and actionable."
|
|
3167
|
-
].join("\n");
|
|
3168
|
-
async function decompose(lead, task, maxSubtasks) {
|
|
3169
|
-
try {
|
|
3170
|
-
const res = await lead.provider.chat({
|
|
3171
|
-
messages: [
|
|
3172
|
-
{ role: "system", content: DECOMPOSE_SYSTEM },
|
|
3173
|
-
{ role: "user", content: `Task:
|
|
3174
|
-
${task}
|
|
3636
|
+
};
|
|
3175
3637
|
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3638
|
+
// src/core/scaffold/init.ts
|
|
3639
|
+
async function scaffoldPoly(workspace, opts) {
|
|
3640
|
+
const templates = polyTemplates(opts.locale);
|
|
3641
|
+
const created = [];
|
|
3642
|
+
const skipped = [];
|
|
3643
|
+
for (const [rel, content] of Object.entries(templates)) {
|
|
3644
|
+
const display = `.poly/${rel}`;
|
|
3645
|
+
const abs = join5(workspace, ".poly", ...rel.split("/"));
|
|
3646
|
+
if (!opts.force && await exists(abs)) {
|
|
3647
|
+
skipped.push(display);
|
|
3648
|
+
continue;
|
|
3187
3649
|
}
|
|
3188
|
-
|
|
3650
|
+
await mkdir3(dirname3(abs), { recursive: true });
|
|
3651
|
+
await writeFile4(abs, content, "utf8");
|
|
3652
|
+
created.push(display);
|
|
3189
3653
|
}
|
|
3190
|
-
return
|
|
3654
|
+
return { created, skipped };
|
|
3191
3655
|
}
|
|
3192
|
-
function
|
|
3193
|
-
const start = text2.indexOf("[");
|
|
3194
|
-
const end = text2.lastIndexOf("]");
|
|
3195
|
-
if (start === -1 || end <= start) return null;
|
|
3656
|
+
async function exists(path) {
|
|
3196
3657
|
try {
|
|
3197
|
-
|
|
3198
|
-
return
|
|
3658
|
+
await access(path);
|
|
3659
|
+
return true;
|
|
3199
3660
|
} catch {
|
|
3200
|
-
return
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
// src/ui/swarm-view.ts
|
|
3205
|
-
var RESET3 = "\x1B[0m";
|
|
3206
|
-
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3207
|
-
function describeToolCall(call) {
|
|
3208
|
-
const raw = call.name === "run_command" ? call.arguments.command : call.arguments.path;
|
|
3209
|
-
const arg = typeof raw === "string" ? raw : "";
|
|
3210
|
-
const short = arg.length > 40 ? arg.slice(0, 39) + "\u2026" : arg;
|
|
3211
|
-
return short ? `${call.name} ${short}` : call.name;
|
|
3212
|
-
}
|
|
3213
|
-
var SwarmView = class {
|
|
3214
|
-
constructor(leadName, opts = {}) {
|
|
3215
|
-
this.leadName = leadName;
|
|
3216
|
-
this.tty = opts.tty ?? (Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
|
|
3217
|
-
this.color = opts.color ?? this.tty;
|
|
3218
|
-
this.write = opts.sink ?? ((s) => process.stdout.write(s));
|
|
3219
|
-
}
|
|
3220
|
-
leadName;
|
|
3221
|
-
tty;
|
|
3222
|
-
color;
|
|
3223
|
-
write;
|
|
3224
|
-
workers = /* @__PURE__ */ new Map();
|
|
3225
|
-
order = [];
|
|
3226
|
-
phase = "decomposing";
|
|
3227
|
-
frame = 0;
|
|
3228
|
-
lastLines = 0;
|
|
3229
|
-
timer;
|
|
3230
|
-
start() {
|
|
3231
|
-
if (!this.tty) {
|
|
3232
|
-
this.write(`\u{1F419} ${t("swarm.view.header", { lead: this.leadName })} \u2014 ${t("swarm.view.decomposing")}
|
|
3233
|
-
`);
|
|
3234
|
-
return;
|
|
3235
|
-
}
|
|
3236
|
-
this.flush();
|
|
3237
|
-
this.timer = setInterval(() => {
|
|
3238
|
-
this.frame = (this.frame + 1) % FRAMES2.length;
|
|
3239
|
-
this.flush();
|
|
3240
|
-
}, 110);
|
|
3241
|
-
this.timer.unref?.();
|
|
3242
|
-
}
|
|
3243
|
-
setSubtasks(subtasks) {
|
|
3244
|
-
this.phase = "running";
|
|
3245
|
-
for (const s of subtasks) {
|
|
3246
|
-
this.workers.set(s.id, { id: s.id, title: s.title, agent: "", status: "pending", action: "", steps: 0 });
|
|
3247
|
-
this.order.push(s.id);
|
|
3248
|
-
}
|
|
3249
|
-
if (!this.tty) {
|
|
3250
|
-
this.write(` ${t("swarm.decomposed", { n: subtasks.length })}
|
|
3251
|
-
`);
|
|
3252
|
-
for (const s of subtasks) this.write(` ${s.id}: ${s.title}
|
|
3253
|
-
`);
|
|
3254
|
-
}
|
|
3255
|
-
this.flush();
|
|
3256
|
-
}
|
|
3257
|
-
workerStart(id, agent) {
|
|
3258
|
-
const w = this.workers.get(id);
|
|
3259
|
-
if (!w) return;
|
|
3260
|
-
w.agent = agent;
|
|
3261
|
-
w.status = "running";
|
|
3262
|
-
if (!this.tty) this.write(` \u25B6 ${id} [${agent}] ${w.title}
|
|
3263
|
-
`);
|
|
3264
|
-
this.flush();
|
|
3265
|
-
}
|
|
3266
|
-
workerAction(id, action) {
|
|
3267
|
-
const w = this.workers.get(id);
|
|
3268
|
-
if (!w) return;
|
|
3269
|
-
w.action = action;
|
|
3270
|
-
this.flush();
|
|
3271
|
-
}
|
|
3272
|
-
workerStep(id, n) {
|
|
3273
|
-
const w = this.workers.get(id);
|
|
3274
|
-
if (!w) return;
|
|
3275
|
-
w.steps = n;
|
|
3276
|
-
this.flush();
|
|
3277
|
-
}
|
|
3278
|
-
workerDone(o) {
|
|
3279
|
-
const w = this.workers.get(o.subtask.id);
|
|
3280
|
-
if (!w) return;
|
|
3281
|
-
w.status = o.finished ? "done" : "stopped";
|
|
3282
|
-
w.steps = o.steps;
|
|
3283
|
-
w.branch = o.branch;
|
|
3284
|
-
w.action = "";
|
|
3285
|
-
if (!this.tty) {
|
|
3286
|
-
const tag = o.finished ? "\u2713" : "\u25A0";
|
|
3287
|
-
const changes = o.committed ? t("swarm.changesCommitted") : t("swarm.noChanges");
|
|
3288
|
-
this.write(` ${tag} ${o.subtask.id} (${t("swarm.view.steps", { n: o.steps })}, ${changes})
|
|
3289
|
-
`);
|
|
3290
|
-
}
|
|
3291
|
-
this.flush();
|
|
3292
|
-
}
|
|
3293
|
-
merge(r) {
|
|
3294
|
-
for (const w of this.workers.values()) {
|
|
3295
|
-
if (w.branch === r.branch) w.merge = r.ok ? "ok" : "conflict";
|
|
3296
|
-
}
|
|
3297
|
-
if (!this.tty) {
|
|
3298
|
-
this.write(r.ok ? ` \u2935 ${t("swarm.merged", { branch: r.branch })}
|
|
3299
|
-
` : ` \u2717 ${t("swarm.mergeConflict", { branch: r.branch })}
|
|
3300
|
-
`);
|
|
3301
|
-
}
|
|
3302
|
-
this.flush();
|
|
3303
|
-
}
|
|
3304
|
-
stop() {
|
|
3305
|
-
this.phase = "done";
|
|
3306
|
-
if (this.timer) {
|
|
3307
|
-
clearInterval(this.timer);
|
|
3308
|
-
this.timer = void 0;
|
|
3309
|
-
}
|
|
3310
|
-
this.flush();
|
|
3311
|
-
}
|
|
3312
|
-
/** Content lines of the dashboard (no cursor control). Exposed for tests. */
|
|
3313
|
-
frameLines() {
|
|
3314
|
-
const spin = this.dim(FRAMES2[this.frame]);
|
|
3315
|
-
const lead = `\u{1F419} ${t("swarm.view.header", { lead: this.leadName })}`;
|
|
3316
|
-
const lines = [];
|
|
3317
|
-
if (this.phase === "decomposing") {
|
|
3318
|
-
lines.push(`${spin} ${lead}`);
|
|
3319
|
-
lines.push(" " + this.dim(t("swarm.view.decomposing")));
|
|
3320
|
-
return lines;
|
|
3321
|
-
}
|
|
3322
|
-
lines.push(`${this.phase === "running" ? spin : " "} ${lead}`);
|
|
3323
|
-
lines.push("");
|
|
3324
|
-
for (const id of this.order) {
|
|
3325
|
-
const w = this.workers.get(id);
|
|
3326
|
-
lines.push(this.row(w, spin));
|
|
3327
|
-
}
|
|
3328
|
-
return lines;
|
|
3329
|
-
}
|
|
3330
|
-
// -------------------------------------------------------------------------
|
|
3331
|
-
row(w, spin) {
|
|
3332
|
-
const icon = w.status === "running" ? spin : w.status === "done" ? this.c("\u2713", "32") : w.status === "stopped" ? this.c("\u25A0", "33") : this.dim("\xB7");
|
|
3333
|
-
const status = this.statusLabel(w);
|
|
3334
|
-
const meta = w.steps > 0 ? this.dim(" \xB7 " + (w.status === "running" ? t("swarm.view.step", { n: w.steps }) : t("swarm.view.steps", { n: w.steps }))) : "";
|
|
3335
|
-
const action = w.action ? w.action : this.dim("\u2014");
|
|
3336
|
-
return ` ${icon} ${pad(w.id, 4)} ${pad(status, 12)} ${pad(`[${w.agent}]`, 14)} ${action}${meta}`;
|
|
3337
|
-
}
|
|
3338
|
-
statusLabel(w) {
|
|
3339
|
-
if (w.merge === "conflict") return this.c(t("swarm.view.conflict"), "31");
|
|
3340
|
-
if (w.status === "running") return this.c(t("swarm.view.running"), "36");
|
|
3341
|
-
if (w.status === "done") return this.c(t("swarm.view.done"), "32");
|
|
3342
|
-
if (w.status === "stopped") return this.c(t("swarm.view.stopped"), "33");
|
|
3343
|
-
return this.dim(t("swarm.view.pending"));
|
|
3344
|
-
}
|
|
3345
|
-
/** Redraw the block in place (TTY) by clearing the previous frame first. */
|
|
3346
|
-
flush() {
|
|
3347
|
-
if (!this.tty) return;
|
|
3348
|
-
const lines = this.frameLines();
|
|
3349
|
-
let s = "";
|
|
3350
|
-
if (this.lastLines > 0) s += `\x1B[${this.lastLines}A`;
|
|
3351
|
-
s += "\x1B[0J";
|
|
3352
|
-
s += lines.join("\n") + "\n";
|
|
3353
|
-
this.write(s);
|
|
3354
|
-
this.lastLines = lines.length;
|
|
3355
|
-
}
|
|
3356
|
-
c(s, code) {
|
|
3357
|
-
return this.color ? `\x1B[${code}m${s}${RESET3}` : s;
|
|
3358
|
-
}
|
|
3359
|
-
dim(s) {
|
|
3360
|
-
return this.color ? `\x1B[2m${s}${RESET3}` : s;
|
|
3661
|
+
return false;
|
|
3361
3662
|
}
|
|
3362
|
-
};
|
|
3363
|
-
function pad(s, n) {
|
|
3364
|
-
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3365
3663
|
}
|
|
3366
3664
|
|
|
3367
|
-
// src/cli/commands/
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
async function swarm(task, opts) {
|
|
3373
|
-
const config = await loadConfig();
|
|
3374
|
-
if (!canSwarm(config.agents.length)) {
|
|
3375
|
-
throw new Error(t("swarm.needsAgents", { min: MIN_SWARM_AGENTS, have: config.agents.length }));
|
|
3376
|
-
}
|
|
3377
|
-
const selected = opts.agents ? opts.agents.split(",").map((s) => s.trim()).filter(Boolean) : config.agents.map((a) => a.name);
|
|
3378
|
-
if (selected.length === 0) {
|
|
3379
|
-
throw new Error(t("swarm.noAgents"));
|
|
3380
|
-
}
|
|
3381
|
-
const resolved = selected.map((name) => {
|
|
3382
|
-
const a = config.agents.find((x) => x.name === name);
|
|
3383
|
-
if (!a) throw new Error(t("agent.notFound", { name }));
|
|
3384
|
-
return createProvider(a);
|
|
3665
|
+
// src/cli/commands/init.ts
|
|
3666
|
+
async function init(opts) {
|
|
3667
|
+
const { created, skipped } = await scaffoldPoly(process.cwd(), {
|
|
3668
|
+
force: Boolean(opts.force),
|
|
3669
|
+
locale: getLocale()
|
|
3385
3670
|
});
|
|
3386
|
-
|
|
3387
|
-
pc9.
|
|
3388
|
-
);
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
view.start();
|
|
3392
|
-
let result;
|
|
3393
|
-
try {
|
|
3394
|
-
result = await runSwarm({
|
|
3395
|
-
task,
|
|
3396
|
-
workspace: process.cwd(),
|
|
3397
|
-
agents: resolved,
|
|
3398
|
-
allow: config.permissions.allow,
|
|
3399
|
-
deny: config.permissions.deny,
|
|
3400
|
-
maxSubtasks: opts.maxSubtasks ? Number(opts.maxSubtasks) : void 0,
|
|
3401
|
-
events: {
|
|
3402
|
-
onDecomposed: (subtasks) => view.setSubtasks(subtasks),
|
|
3403
|
-
onWorkerStart: (subtask, agentName) => view.workerStart(subtask.id, agentName),
|
|
3404
|
-
onWorkerDone: (outcome) => view.workerDone(outcome),
|
|
3405
|
-
onMerge: (merge) => view.merge(merge),
|
|
3406
|
-
workerEvents: (subtask) => ({
|
|
3407
|
-
onToolCall: (call) => view.workerAction(subtask.id, describeToolCall(call)),
|
|
3408
|
-
onStep: (step) => view.workerStep(subtask.id, step)
|
|
3409
|
-
})
|
|
3410
|
-
}
|
|
3411
|
-
});
|
|
3412
|
-
} finally {
|
|
3413
|
-
view.stop();
|
|
3414
|
-
}
|
|
3415
|
-
console.log("");
|
|
3416
|
-
console.log(pc9.bold("\n" + t("swarm.summary")));
|
|
3417
|
-
for (const o of result.outcomes) {
|
|
3418
|
-
const status = o.finished ? pc9.green(t("swarm.statusDone")) : pc9.yellow(t("swarm.statusIncomplete"));
|
|
3419
|
-
const committed = o.committed ? "" : pc9.dim(` (${t("swarm.noChanges")})`);
|
|
3420
|
-
console.log(` ${pc9.bold(o.subtask.id)} [${o.agentName}] ${status}${committed} \u2014 ${o.subtask.title}`);
|
|
3671
|
+
if (created.length === 0) {
|
|
3672
|
+
console.log(pc9.yellow(t("init.allExist")));
|
|
3673
|
+
for (const f of skipped) console.log(pc9.dim(` ${f}`));
|
|
3674
|
+
console.log(pc9.dim(t("init.forceHint")));
|
|
3675
|
+
return;
|
|
3421
3676
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
}
|
|
3428
|
-
} else {
|
|
3429
|
-
console.log(pc9.green("\n" + t("swarm.allMerged")));
|
|
3677
|
+
console.log(pc9.green(t("init.created")));
|
|
3678
|
+
for (const f of created) console.log(pc9.dim(` ${f}`));
|
|
3679
|
+
if (skipped.length > 0) {
|
|
3680
|
+
console.log(pc9.dim(t("init.skipped")));
|
|
3681
|
+
for (const f of skipped) console.log(pc9.dim(` ${f}`));
|
|
3430
3682
|
}
|
|
3683
|
+
console.log("\n" + t("init.tip"));
|
|
3431
3684
|
}
|
|
3432
3685
|
|
|
3433
3686
|
// src/cli/commands/models.ts
|
|
@@ -3489,7 +3742,7 @@ async function resolveOpenRouterKey() {
|
|
|
3489
3742
|
}
|
|
3490
3743
|
|
|
3491
3744
|
// src/cli/commands/prd.ts
|
|
3492
|
-
import { writeFile as writeFile5, readFile as
|
|
3745
|
+
import { writeFile as writeFile5, readFile as readFile7 } from "fs/promises";
|
|
3493
3746
|
import { execFile } from "child_process";
|
|
3494
3747
|
import { promisify as promisify2 } from "util";
|
|
3495
3748
|
import pc11 from "picocolors";
|
|
@@ -3581,13 +3834,13 @@ async function withRetry(fn, opts = {}) {
|
|
|
3581
3834
|
|
|
3582
3835
|
// src/cli/commands/cli-io.ts
|
|
3583
3836
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
3584
|
-
import { resolve as
|
|
3837
|
+
import { resolve as resolve8 } from "path";
|
|
3585
3838
|
var GUIDE_MAX = 12e3;
|
|
3586
3839
|
function readProjectGuide(files) {
|
|
3587
3840
|
const parts = [];
|
|
3588
3841
|
for (const file of files) {
|
|
3589
3842
|
try {
|
|
3590
|
-
const path =
|
|
3843
|
+
const path = resolve8(process.cwd(), file);
|
|
3591
3844
|
if (existsSync2(path)) parts.push(`# ${file}
|
|
3592
3845
|
${readFileSync(path, "utf8").trim()}`);
|
|
3593
3846
|
} catch {
|
|
@@ -3628,7 +3881,7 @@ async function prd(issueRef, opts) {
|
|
|
3628
3881
|
}
|
|
3629
3882
|
async function loadIssue(issueRef, input) {
|
|
3630
3883
|
if (input) {
|
|
3631
|
-
const raw = input === "-" ? await readStdin() : await
|
|
3884
|
+
const raw = input === "-" ? await readStdin() : await readFile7(input, "utf8");
|
|
3632
3885
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
3633
3886
|
}
|
|
3634
3887
|
const num = numericRef(issueRef);
|
|
@@ -3647,7 +3900,7 @@ function normalize2(raw) {
|
|
|
3647
3900
|
}
|
|
3648
3901
|
|
|
3649
3902
|
// src/cli/commands/review.ts
|
|
3650
|
-
import { writeFile as writeFile6, readFile as
|
|
3903
|
+
import { writeFile as writeFile6, readFile as readFile8 } from "fs/promises";
|
|
3651
3904
|
import { execFile as execFile2 } from "child_process";
|
|
3652
3905
|
import { promisify as promisify3 } from "util";
|
|
3653
3906
|
import pc12 from "picocolors";
|
|
@@ -3723,7 +3976,7 @@ async function review(prRef, opts) {
|
|
|
3723
3976
|
}
|
|
3724
3977
|
}
|
|
3725
3978
|
async function loadDiff(num, input) {
|
|
3726
|
-
if (input) return input === "-" ? readStdin() :
|
|
3979
|
+
if (input) return input === "-" ? readStdin() : readFile8(input, "utf8");
|
|
3727
3980
|
const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
|
|
3728
3981
|
return stdout2;
|
|
3729
3982
|
}
|
|
@@ -3735,7 +3988,7 @@ async function loadMeta(num, input) {
|
|
|
3735
3988
|
}
|
|
3736
3989
|
|
|
3737
3990
|
// src/cli/index.ts
|
|
3738
|
-
import { join as
|
|
3991
|
+
import { join as join6 } from "path";
|
|
3739
3992
|
|
|
3740
3993
|
// src/core/config/dotenv.ts
|
|
3741
3994
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -3804,7 +4057,7 @@ function buildProgram() {
|
|
|
3804
4057
|
}
|
|
3805
4058
|
async function main() {
|
|
3806
4059
|
try {
|
|
3807
|
-
loadDotenv([
|
|
4060
|
+
loadDotenv([join6(configDir(), ".env"), join6(process.cwd(), ".env")]);
|
|
3808
4061
|
await resolveLocale();
|
|
3809
4062
|
await buildProgram().parseAsync(process.argv);
|
|
3810
4063
|
} catch (err) {
|