@0xtiby/toby 1.2.0 → 1.3.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/cli.js +355 -134
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -65,11 +65,18 @@ var SpecStatusEntrySchema = z.object({
|
|
|
65
65
|
iterations: z.array(IterationSchema),
|
|
66
66
|
stopReason: StopReasonSchema.optional()
|
|
67
67
|
});
|
|
68
|
+
var SessionStateSchema = z.enum(["active", "interrupted"]);
|
|
69
|
+
var SessionSchema = z.object({
|
|
70
|
+
name: z.string(),
|
|
71
|
+
cli: z.string(),
|
|
72
|
+
specs: z.array(z.string()),
|
|
73
|
+
state: SessionStateSchema,
|
|
74
|
+
startedAt: z.string().datetime()
|
|
75
|
+
});
|
|
68
76
|
var StatusSchema = z.object({
|
|
69
77
|
specs: z.record(z.string(), SpecStatusEntrySchema),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
78
|
+
session: SessionSchema.optional()
|
|
79
|
+
}).strip();
|
|
73
80
|
|
|
74
81
|
// src/lib/paths.ts
|
|
75
82
|
import path from "path";
|
|
@@ -162,6 +169,29 @@ var commandHelp = {
|
|
|
162
169
|
}
|
|
163
170
|
]
|
|
164
171
|
},
|
|
172
|
+
resume: {
|
|
173
|
+
summary: "Resume an interrupted build session",
|
|
174
|
+
usage: ["$ toby resume [options]"],
|
|
175
|
+
flags: [
|
|
176
|
+
{ name: "--iterations=<n>", description: "Override iteration count" },
|
|
177
|
+
{ name: "--verbose", description: "Show full CLI output" },
|
|
178
|
+
{ name: "--transcript", description: "Save session transcript to file" }
|
|
179
|
+
],
|
|
180
|
+
examples: [
|
|
181
|
+
{
|
|
182
|
+
command: "toby resume",
|
|
183
|
+
description: "Resume the most recent interrupted session from where it left off"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
command: "toby resume --iterations=10 --verbose",
|
|
187
|
+
description: "Resume with 10 iterations per spec and full CLI output"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
command: "toby resume --transcript",
|
|
191
|
+
description: "Resume and save a transcript of the resumed session"
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
165
195
|
init: {
|
|
166
196
|
summary: "Initialize toby in current project",
|
|
167
197
|
usage: ["$ toby init [options]"],
|
|
@@ -759,6 +789,28 @@ function addIteration(status, specName, iteration) {
|
|
|
759
789
|
}
|
|
760
790
|
};
|
|
761
791
|
}
|
|
792
|
+
function createSession(name, cli2, specs) {
|
|
793
|
+
return {
|
|
794
|
+
name,
|
|
795
|
+
cli: cli2,
|
|
796
|
+
specs,
|
|
797
|
+
state: "active",
|
|
798
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function updateSessionState(status, state) {
|
|
802
|
+
if (!status.session) return status;
|
|
803
|
+
return {
|
|
804
|
+
...status,
|
|
805
|
+
session: { ...status.session, state }
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function clearSession(status) {
|
|
809
|
+
return { specs: status.specs };
|
|
810
|
+
}
|
|
811
|
+
function hasResumableSession(status) {
|
|
812
|
+
return status.session?.state === "active" || status.session?.state === "interrupted";
|
|
813
|
+
}
|
|
762
814
|
function updateSpecStatus(status, specName, newStatus) {
|
|
763
815
|
const entry = getSpecStatus(status, specName);
|
|
764
816
|
return {
|
|
@@ -959,16 +1011,19 @@ function useCommandRunner(options) {
|
|
|
959
1011
|
if (filtered.length === 0) {
|
|
960
1012
|
setErrorMessage(emptyMessage ?? "No specs found.");
|
|
961
1013
|
setPhase("error");
|
|
962
|
-
exit();
|
|
963
1014
|
return;
|
|
964
1015
|
}
|
|
965
1016
|
setSpecs(filtered);
|
|
966
1017
|
} catch (err) {
|
|
967
1018
|
setErrorMessage(err.message);
|
|
968
1019
|
setPhase("error");
|
|
969
|
-
exit(new Error(err.message));
|
|
970
1020
|
}
|
|
971
1021
|
}, [phase]);
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
if (phase === "error" && errorMessage) {
|
|
1024
|
+
exit(new Error(errorMessage));
|
|
1025
|
+
}
|
|
1026
|
+
}, [phase, errorMessage]);
|
|
972
1027
|
useEffect(() => {
|
|
973
1028
|
if (phase !== "multi" || selectedSpecs.length > 0) return;
|
|
974
1029
|
if (!flags2.spec) return;
|
|
@@ -1400,14 +1455,10 @@ function Plan(flags2) {
|
|
|
1400
1455
|
import { useState as useState4, useEffect as useEffect3, useMemo as useMemo3 } from "react";
|
|
1401
1456
|
import { Text as Text4, Box as Box4 } from "ink";
|
|
1402
1457
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1403
|
-
function
|
|
1458
|
+
function resolveResumeSessionId(specEntry, currentCli, sessionCli) {
|
|
1459
|
+
if (currentCli !== sessionCli) return void 0;
|
|
1404
1460
|
const lastIteration = specEntry?.iterations.at(-1);
|
|
1405
|
-
|
|
1406
|
-
const isExhaustedResume = !!(specEntry?.status !== "done" && specEntry?.stopReason === "max_iterations");
|
|
1407
|
-
const needsResume = isCrashResume || isExhaustedResume;
|
|
1408
|
-
const isSameCli = currentCli === lastCli;
|
|
1409
|
-
const sessionId = isSameCli && isCrashResume ? lastIteration?.sessionId ?? void 0 : void 0;
|
|
1410
|
-
return { isCrashResume, isExhaustedResume, needsResume, sessionId };
|
|
1461
|
+
return lastIteration?.sessionId ?? void 0;
|
|
1411
1462
|
}
|
|
1412
1463
|
async function runSpecBuild(options) {
|
|
1413
1464
|
const { spec, iterations, cli: cli2, model, cwd, callbacks } = options;
|
|
@@ -1456,7 +1507,6 @@ async function runSpecBuild(options) {
|
|
|
1456
1507
|
tokensUsed: null
|
|
1457
1508
|
};
|
|
1458
1509
|
status = addIteration(status, spec.name, iterationRecord);
|
|
1459
|
-
status = { ...status, sessionName: options.session, lastCli: cli2 };
|
|
1460
1510
|
writeStatus(status, cwd);
|
|
1461
1511
|
},
|
|
1462
1512
|
onIterationComplete: (iterResult) => {
|
|
@@ -1534,20 +1584,22 @@ async function executeBuild(flags2, callbacks = {}, cwd = process.cwd(), abortSi
|
|
|
1534
1584
|
if (!found) {
|
|
1535
1585
|
throw new Error(`Spec '${flags2.spec}' not found`);
|
|
1536
1586
|
}
|
|
1537
|
-
|
|
1587
|
+
let status = readStatus(cwd);
|
|
1538
1588
|
const specEntry = status.specs[found.name];
|
|
1589
|
+
if (specEntry?.status === "done") {
|
|
1590
|
+
throw new Error(`Spec '${found.name}' is already done. Reset its status in .toby/status.json to rebuild.`);
|
|
1591
|
+
}
|
|
1539
1592
|
if (!specEntry || specEntry.status !== "planned" && specEntry.status !== "building") {
|
|
1540
1593
|
throw new Error(`No plan found for ${found.name}. Run 'toby plan --spec=${flags2.spec}' first.`);
|
|
1541
1594
|
}
|
|
1542
1595
|
const existingIterations = specEntry.iterations.length;
|
|
1543
|
-
const
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
callbacks.onOutput?.(`\u26A0 Previous build exhausted iterations without completing. Resuming in worktree "${session}"...`);
|
|
1596
|
+
const session = flags2.session || status.session?.name || computeSpecSlug(found.name);
|
|
1597
|
+
const sessionCli = status.session?.cli ?? commandConfig.cli;
|
|
1598
|
+
const resumeSessionId = resolveResumeSessionId(specEntry, commandConfig.cli, sessionCli);
|
|
1599
|
+
if (!status.session) {
|
|
1600
|
+
const sessionObj = createSession(session, commandConfig.cli, [found.name]);
|
|
1601
|
+
status = { ...status, session: sessionObj };
|
|
1602
|
+
writeStatus(status, cwd);
|
|
1551
1603
|
}
|
|
1552
1604
|
return withTranscript(
|
|
1553
1605
|
{ flags: flags2, config, command: "build", specName: found.name },
|
|
@@ -1563,7 +1615,7 @@ async function executeBuild(flags2, callbacks = {}, cwd = process.cwd(), abortSi
|
|
|
1563
1615
|
templateVars: config.templateVars,
|
|
1564
1616
|
specsDir: config.specsDir,
|
|
1565
1617
|
session,
|
|
1566
|
-
sessionId:
|
|
1618
|
+
sessionId: resumeSessionId,
|
|
1567
1619
|
specIndex: 1,
|
|
1568
1620
|
specCount: 1,
|
|
1569
1621
|
specs: [found.name],
|
|
@@ -1572,7 +1624,14 @@ async function executeBuild(flags2, callbacks = {}, cwd = process.cwd(), abortSi
|
|
|
1572
1624
|
callbacks,
|
|
1573
1625
|
writer
|
|
1574
1626
|
});
|
|
1575
|
-
|
|
1627
|
+
let finalStatus = readStatus(cwd);
|
|
1628
|
+
if (result.specDone) {
|
|
1629
|
+
finalStatus = clearSession(finalStatus);
|
|
1630
|
+
} else {
|
|
1631
|
+
finalStatus = updateSessionState(finalStatus, "interrupted");
|
|
1632
|
+
}
|
|
1633
|
+
writeStatus(finalStatus, cwd);
|
|
1634
|
+
return result;
|
|
1576
1635
|
}
|
|
1577
1636
|
);
|
|
1578
1637
|
}
|
|
@@ -1594,62 +1653,99 @@ async function executeBuildAll(flags2, callbacks = {}, cwd = process.cwd(), abor
|
|
|
1594
1653
|
}
|
|
1595
1654
|
const built = [];
|
|
1596
1655
|
const specNames = planned.map((s) => s.name);
|
|
1597
|
-
|
|
1656
|
+
let status = readStatus(cwd);
|
|
1657
|
+
const buildable = planned.filter((spec) => {
|
|
1658
|
+
const entry = status.specs[spec.name];
|
|
1659
|
+
return entry?.status !== "done";
|
|
1660
|
+
});
|
|
1598
1661
|
const commandConfig = resolveCommandConfig(config, "build", {
|
|
1599
1662
|
cli: flags2.cli,
|
|
1600
1663
|
iterations: flags2.iterations
|
|
1601
1664
|
});
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1665
|
+
const session = flags2.session || status.session?.name || generateSessionName();
|
|
1666
|
+
const sessionCli = status.session?.cli ?? commandConfig.cli;
|
|
1667
|
+
const existingSession = status.session;
|
|
1668
|
+
if (!existingSession) {
|
|
1669
|
+
const sessionObj2 = createSession(session, commandConfig.cli, specNames);
|
|
1670
|
+
status = { ...status, session: sessionObj2 };
|
|
1671
|
+
writeStatus(status, cwd);
|
|
1672
|
+
} else {
|
|
1673
|
+
status = updateSessionState(status, "active");
|
|
1674
|
+
writeStatus(status, cwd);
|
|
1675
|
+
}
|
|
1676
|
+
const sessionObj = status.session;
|
|
1606
1677
|
return withTranscript(
|
|
1607
1678
|
{ flags: { ...flags2, session: flags2.session ?? session }, config, command: "build" },
|
|
1608
1679
|
void 0,
|
|
1609
1680
|
async (writer) => {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1681
|
+
try {
|
|
1682
|
+
for (let i = 0; i < buildable.length; i++) {
|
|
1683
|
+
const spec = buildable[i];
|
|
1684
|
+
const specIndex = planned.indexOf(spec) + 1;
|
|
1685
|
+
writer?.writeSpecHeader(specIndex, planned.length, spec.name);
|
|
1686
|
+
callbacks.onSpecStart?.(spec.name, specIndex - 1, planned.length);
|
|
1687
|
+
const specEntry = status.specs[spec.name];
|
|
1688
|
+
const existingIterations = specEntry?.iterations.length ?? 0;
|
|
1689
|
+
const resumeSessionId = resolveResumeSessionId(specEntry, commandConfig.cli, sessionCli);
|
|
1690
|
+
const { result } = await runSpecBuild({
|
|
1691
|
+
spec,
|
|
1692
|
+
promptName: "PROMPT_BUILD",
|
|
1693
|
+
existingIterations,
|
|
1694
|
+
iterations: commandConfig.iterations,
|
|
1695
|
+
cli: commandConfig.cli,
|
|
1696
|
+
model: commandConfig.model,
|
|
1697
|
+
templateVars: config.templateVars,
|
|
1698
|
+
specsDir: config.specsDir,
|
|
1699
|
+
session,
|
|
1700
|
+
sessionId: resumeSessionId,
|
|
1701
|
+
specIndex,
|
|
1702
|
+
specCount: planned.length,
|
|
1703
|
+
specs: specNames,
|
|
1704
|
+
cwd,
|
|
1705
|
+
abortSignal,
|
|
1706
|
+
callbacks: {
|
|
1707
|
+
onPhase: callbacks.onPhase,
|
|
1708
|
+
onIteration: callbacks.onIteration,
|
|
1709
|
+
onEvent: callbacks.onEvent,
|
|
1710
|
+
onOutput: callbacks.onOutput
|
|
1711
|
+
},
|
|
1712
|
+
writer
|
|
1713
|
+
});
|
|
1714
|
+
built.push(result);
|
|
1715
|
+
callbacks.onSpecComplete?.(result);
|
|
1716
|
+
if (!result.specDone) {
|
|
1717
|
+
let currentStatus = readStatus(cwd);
|
|
1718
|
+
currentStatus = updateSessionState(currentStatus, "interrupted");
|
|
1719
|
+
writeStatus(currentStatus, cwd);
|
|
1720
|
+
const allSpecNames = sessionObj.specs;
|
|
1721
|
+
const doneSpecs = allSpecNames.filter((name) => {
|
|
1722
|
+
return currentStatus.specs[name]?.status === "done";
|
|
1723
|
+
});
|
|
1724
|
+
const remainingSpecs = allSpecNames.filter((name) => !doneSpecs.includes(name));
|
|
1725
|
+
callbacks.onOutput?.(
|
|
1726
|
+
`Session "${sessionObj.name}" interrupted at ${spec.name} (${result.error ? "error" : "incomplete"}).`
|
|
1727
|
+
);
|
|
1728
|
+
callbacks.onOutput?.(
|
|
1729
|
+
`Completed: ${doneSpecs.join(", ") || "none"} (${doneSpecs.length}/${allSpecNames.length})`
|
|
1730
|
+
);
|
|
1731
|
+
callbacks.onOutput?.(
|
|
1732
|
+
`Remaining: ${remainingSpecs.join(", ")} (${remainingSpecs.length}/${allSpecNames.length})`
|
|
1733
|
+
);
|
|
1734
|
+
callbacks.onOutput?.("Run 'toby resume' to continue.");
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1626
1737
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
specIndex: i + 1,
|
|
1639
|
-
specCount: planned.length,
|
|
1640
|
-
specs: specNames,
|
|
1641
|
-
cwd,
|
|
1642
|
-
abortSignal,
|
|
1643
|
-
callbacks: {
|
|
1644
|
-
onPhase: callbacks.onPhase,
|
|
1645
|
-
onIteration: callbacks.onIteration,
|
|
1646
|
-
onEvent: callbacks.onEvent,
|
|
1647
|
-
onOutput: callbacks.onOutput
|
|
1648
|
-
},
|
|
1649
|
-
writer
|
|
1650
|
-
});
|
|
1651
|
-
built.push({ ...result, needsResume: resume.needsResume });
|
|
1652
|
-
callbacks.onSpecComplete?.({ ...result, needsResume: resume.needsResume });
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
if (err instanceof AbortError) {
|
|
1740
|
+
const currentStatus = readStatus(cwd);
|
|
1741
|
+
writeStatus(updateSessionState(currentStatus, "interrupted"), cwd);
|
|
1742
|
+
}
|
|
1743
|
+
throw err;
|
|
1744
|
+
}
|
|
1745
|
+
const finalStatus = readStatus(cwd);
|
|
1746
|
+
const allDone = sessionObj.specs.every((name) => finalStatus.specs[name]?.status === "done");
|
|
1747
|
+
if (allDone) {
|
|
1748
|
+
writeStatus(clearSession(finalStatus), cwd);
|
|
1653
1749
|
}
|
|
1654
1750
|
return { built };
|
|
1655
1751
|
}
|
|
@@ -2916,13 +3012,123 @@ function Clean({ force }) {
|
|
|
2916
3012
|
return null;
|
|
2917
3013
|
}
|
|
2918
3014
|
|
|
3015
|
+
// src/commands/resume.tsx
|
|
3016
|
+
import { useState as useState9, useEffect as useEffect8, useRef as useRef2 } from "react";
|
|
3017
|
+
import { Box as Box9, Text as Text10, useApp as useApp5 } from "ink";
|
|
3018
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3019
|
+
async function executeResume(flags2, callbacks = {}, cwd = process.cwd(), abortSignal) {
|
|
3020
|
+
const status = readStatus(cwd);
|
|
3021
|
+
if (!hasResumableSession(status)) {
|
|
3022
|
+
throw new Error(
|
|
3023
|
+
"No active session to resume. Use 'toby build --spec=<name>' to start a new build."
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
const session = status.session;
|
|
3027
|
+
const config = loadConfig(cwd);
|
|
3028
|
+
const commandConfig = resolveCommandConfig(config, "build", {
|
|
3029
|
+
iterations: flags2.iterations
|
|
3030
|
+
});
|
|
3031
|
+
const allSpecs = discoverSpecs(cwd, config);
|
|
3032
|
+
const incompleteNames = [];
|
|
3033
|
+
const missingNames = [];
|
|
3034
|
+
for (const specName of session.specs) {
|
|
3035
|
+
const entry = status.specs[specName];
|
|
3036
|
+
if (entry?.status === "done") {
|
|
3037
|
+
callbacks.onOutput?.(` \u2713 ${specName} (done, skipping)`);
|
|
3038
|
+
continue;
|
|
3039
|
+
}
|
|
3040
|
+
const found = findSpec(allSpecs, specName);
|
|
3041
|
+
if (!found) {
|
|
3042
|
+
missingNames.push(specName);
|
|
3043
|
+
callbacks.onOutput?.(` \u26A0 ${specName} (not found in specs/, skipping)`);
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
incompleteNames.push(specName);
|
|
3047
|
+
}
|
|
3048
|
+
if (missingNames.length === session.specs.length) {
|
|
3049
|
+
throw new Error(
|
|
3050
|
+
"All session specs are missing from specs/ directory. Cannot resume."
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
if (incompleteNames.length === 0) {
|
|
3054
|
+
throw new Error(
|
|
3055
|
+
missingNames.length > 0 ? "All remaining session specs are missing from specs/. Nothing to resume." : "All specs in this session are already done. Nothing to resume."
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
const specsToResume = incompleteNames.map((name) => findSpec(allSpecs, name));
|
|
3059
|
+
callbacks.onOutput?.(`Resuming session "${session.name}" with ${specsToResume.length} spec(s):`);
|
|
3060
|
+
for (const spec of specsToResume) {
|
|
3061
|
+
callbacks.onOutput?.(` \u2192 ${spec.name}`);
|
|
3062
|
+
}
|
|
3063
|
+
const updatedStatus = updateSessionState(status, "active");
|
|
3064
|
+
writeStatus(updatedStatus, cwd);
|
|
3065
|
+
const buildFlags = {
|
|
3066
|
+
spec: void 0,
|
|
3067
|
+
all: true,
|
|
3068
|
+
iterations: flags2.iterations ?? commandConfig.iterations,
|
|
3069
|
+
verbose: flags2.verbose ?? false,
|
|
3070
|
+
transcript: flags2.transcript,
|
|
3071
|
+
cli: commandConfig.cli,
|
|
3072
|
+
session: session.name
|
|
3073
|
+
};
|
|
3074
|
+
return executeBuildAll(buildFlags, callbacks, cwd, abortSignal, specsToResume);
|
|
3075
|
+
}
|
|
3076
|
+
function Resume(props) {
|
|
3077
|
+
const { exit } = useApp5();
|
|
3078
|
+
const [phase, setPhase] = useState9("loading");
|
|
3079
|
+
const [messages, setMessages] = useState9([]);
|
|
3080
|
+
const [result, setResult] = useState9(null);
|
|
3081
|
+
const [errorMessage, setErrorMessage] = useState9("");
|
|
3082
|
+
const abortController = useRef2(new AbortController());
|
|
3083
|
+
useEffect8(() => {
|
|
3084
|
+
const callbacks = {
|
|
3085
|
+
onOutput: (msg) => setMessages((prev) => [...prev, msg])
|
|
3086
|
+
};
|
|
3087
|
+
setPhase("building");
|
|
3088
|
+
executeResume(props, callbacks, void 0, abortController.current.signal).then((r) => {
|
|
3089
|
+
setResult(r);
|
|
3090
|
+
setPhase("done");
|
|
3091
|
+
}).catch((err) => {
|
|
3092
|
+
if (abortController.current.signal.aborted) return;
|
|
3093
|
+
setErrorMessage(err.message);
|
|
3094
|
+
setPhase("error");
|
|
3095
|
+
});
|
|
3096
|
+
return () => {
|
|
3097
|
+
abortController.current.abort();
|
|
3098
|
+
};
|
|
3099
|
+
}, []);
|
|
3100
|
+
useEffect8(() => {
|
|
3101
|
+
if (phase === "done" || phase === "error") {
|
|
3102
|
+
const timer = setTimeout(() => exit(), 100);
|
|
3103
|
+
return () => clearTimeout(timer);
|
|
3104
|
+
}
|
|
3105
|
+
}, [phase, exit]);
|
|
3106
|
+
if (phase === "error") {
|
|
3107
|
+
return /* @__PURE__ */ jsx10(Text10, { color: "red", children: errorMessage });
|
|
3108
|
+
}
|
|
3109
|
+
if (phase === "done" && result) {
|
|
3110
|
+
const totalIter = result.built.reduce((s, r) => s + r.totalIterations, 0);
|
|
3111
|
+
const totalTok = result.built.reduce((s, r) => s + r.totalTokens, 0);
|
|
3112
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3113
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: msg }, i)),
|
|
3114
|
+
/* @__PURE__ */ jsx10(Text10, { color: "green", children: `\u2713 Resume complete (${result.built.length} spec(s) built)` }),
|
|
3115
|
+
result.built.map((r) => /* @__PURE__ */ jsx10(Text10, { children: ` ${r.specName}: ${r.totalIterations} iterations, ${r.totalTokens} tokens${r.specDone ? " [done]" : ""}` }, r.specName)),
|
|
3116
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: ` Total: ${totalIter} iterations, ${totalTok} tokens` })
|
|
3117
|
+
] });
|
|
3118
|
+
}
|
|
3119
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3120
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: msg }, i)),
|
|
3121
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Resuming build..." })
|
|
3122
|
+
] });
|
|
3123
|
+
}
|
|
3124
|
+
|
|
2919
3125
|
// src/components/Welcome.tsx
|
|
2920
|
-
import { useState as
|
|
2921
|
-
import { Box as
|
|
3126
|
+
import { useState as useState11, useEffect as useEffect10, useMemo as useMemo6 } from "react";
|
|
3127
|
+
import { Box as Box13, Text as Text14, useApp as useApp6, useStdout as useStdout2 } from "ink";
|
|
2922
3128
|
|
|
2923
3129
|
// src/components/hamster/HamsterWheel.tsx
|
|
2924
|
-
import { useState as
|
|
2925
|
-
import { Box as
|
|
3130
|
+
import { useState as useState10, useEffect as useEffect9, useMemo as useMemo5 } from "react";
|
|
3131
|
+
import { Box as Box10, Text as Text11, useStdout } from "ink";
|
|
2926
3132
|
|
|
2927
3133
|
// src/components/hamster/palette.ts
|
|
2928
3134
|
var PALETTE = {
|
|
@@ -3081,7 +3287,7 @@ function generateWheelPixels(cx, cy, outerRadius, innerRadius, spokeAngle, aspec
|
|
|
3081
3287
|
}
|
|
3082
3288
|
|
|
3083
3289
|
// src/components/hamster/HamsterWheel.tsx
|
|
3084
|
-
import { jsx as
|
|
3290
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
3085
3291
|
function buildGrid(width, height) {
|
|
3086
3292
|
return Array.from(
|
|
3087
3293
|
{ length: height },
|
|
@@ -3164,9 +3370,9 @@ function HamsterWheel({
|
|
|
3164
3370
|
heightProp,
|
|
3165
3371
|
columns
|
|
3166
3372
|
);
|
|
3167
|
-
const [frame, setFrame] =
|
|
3168
|
-
const [spokeAngle, setSpokeAngle] =
|
|
3169
|
-
|
|
3373
|
+
const [frame, setFrame] = useState10(0);
|
|
3374
|
+
const [spokeAngle, setSpokeAngle] = useState10(0);
|
|
3375
|
+
useEffect9(() => {
|
|
3170
3376
|
if (speed === 0 || isStatic) return;
|
|
3171
3377
|
const interval = computeInterval(HAMSTER_BASE_INTERVAL, speed);
|
|
3172
3378
|
const id = setInterval(() => {
|
|
@@ -3174,7 +3380,7 @@ function HamsterWheel({
|
|
|
3174
3380
|
}, interval);
|
|
3175
3381
|
return () => clearInterval(id);
|
|
3176
3382
|
}, [speed, isStatic]);
|
|
3177
|
-
|
|
3383
|
+
useEffect9(() => {
|
|
3178
3384
|
if (speed === 0 || isStatic) return;
|
|
3179
3385
|
const interval = computeInterval(WHEEL_BASE_INTERVAL, speed);
|
|
3180
3386
|
const id = setInterval(() => {
|
|
@@ -3214,60 +3420,61 @@ function HamsterWheel({
|
|
|
3214
3420
|
return buildColorRuns(grid, resolvedWidth, resolvedHeight);
|
|
3215
3421
|
}, [resolvedWidth, resolvedHeight, frame, spokeAngle, isStatic]);
|
|
3216
3422
|
if (isStatic) {
|
|
3217
|
-
return /* @__PURE__ */
|
|
3423
|
+
return /* @__PURE__ */ jsx11(Text11, { children: " \u{1F439} toby" });
|
|
3218
3424
|
}
|
|
3219
|
-
return /* @__PURE__ */
|
|
3425
|
+
return /* @__PURE__ */ jsx11(Box10, { flexDirection: "column", children: renderedRows.map((runs, y) => /* @__PURE__ */ jsx11(Text11, { children: runs.map((run, i) => /* @__PURE__ */ jsx11(Text11, { color: run.fg, backgroundColor: run.bg, children: run.char.repeat(run.length) }, i)) }, y)) });
|
|
3220
3426
|
}
|
|
3221
3427
|
|
|
3222
3428
|
// src/components/InfoPanel.tsx
|
|
3223
|
-
import { Box as
|
|
3224
|
-
import { jsx as
|
|
3429
|
+
import { Box as Box11, Text as Text12 } from "ink";
|
|
3430
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3225
3431
|
var formatTokens = (n) => new Intl.NumberFormat().format(n);
|
|
3226
3432
|
function StatRow({ label, value }) {
|
|
3227
|
-
return /* @__PURE__ */
|
|
3228
|
-
/* @__PURE__ */
|
|
3433
|
+
return /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3434
|
+
/* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
|
|
3229
3435
|
String(label).padStart(9),
|
|
3230
3436
|
" "
|
|
3231
3437
|
] }),
|
|
3232
|
-
/* @__PURE__ */
|
|
3438
|
+
/* @__PURE__ */ jsx12(Text12, { children: value })
|
|
3233
3439
|
] });
|
|
3234
3440
|
}
|
|
3235
3441
|
function InfoPanel({ version: version2, stats }) {
|
|
3236
|
-
return /* @__PURE__ */
|
|
3237
|
-
/* @__PURE__ */
|
|
3442
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
3443
|
+
/* @__PURE__ */ jsxs10(Text12, { bold: true, color: "#f0a030", children: [
|
|
3238
3444
|
"toby v",
|
|
3239
3445
|
version2
|
|
3240
3446
|
] }),
|
|
3241
|
-
stats !== null && /* @__PURE__ */
|
|
3242
|
-
/* @__PURE__ */
|
|
3243
|
-
/* @__PURE__ */
|
|
3244
|
-
/* @__PURE__ */
|
|
3245
|
-
/* @__PURE__ */
|
|
3447
|
+
stats !== null && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
3448
|
+
/* @__PURE__ */ jsx12(StatRow, { label: "Specs", value: stats.totalSpecs }),
|
|
3449
|
+
/* @__PURE__ */ jsx12(StatRow, { label: "Planned", value: stats.planned }),
|
|
3450
|
+
/* @__PURE__ */ jsx12(StatRow, { label: "Done", value: stats.done }),
|
|
3451
|
+
/* @__PURE__ */ jsx12(StatRow, { label: "Tokens", value: formatTokens(stats.totalTokens) })
|
|
3246
3452
|
] })
|
|
3247
3453
|
] });
|
|
3248
3454
|
}
|
|
3249
3455
|
|
|
3250
3456
|
// src/components/MainMenu.tsx
|
|
3251
|
-
import { Text as
|
|
3457
|
+
import { Text as Text13, Box as Box12 } from "ink";
|
|
3252
3458
|
import SelectInput3 from "ink-select-input";
|
|
3253
|
-
import { jsx as
|
|
3459
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3254
3460
|
var MENU_ITEMS = [
|
|
3255
3461
|
{ label: "plan", value: "plan", description: "Plan specs with AI loop engine" },
|
|
3256
3462
|
{ label: "build", value: "build", description: "Build tasks one-per-spawn with AI" },
|
|
3463
|
+
{ label: "resume", value: "resume", description: "Resume an interrupted build session" },
|
|
3257
3464
|
{ label: "status", value: "status", description: "Show project status" },
|
|
3258
3465
|
{ label: "config", value: "config", description: "Manage configuration" }
|
|
3259
3466
|
];
|
|
3260
3467
|
function MenuItem({ isSelected = false, label, description }) {
|
|
3261
|
-
return /* @__PURE__ */
|
|
3262
|
-
/* @__PURE__ */
|
|
3263
|
-
description && /* @__PURE__ */
|
|
3468
|
+
return /* @__PURE__ */ jsxs11(Box12, { children: [
|
|
3469
|
+
/* @__PURE__ */ jsx13(Text13, { color: isSelected ? "blue" : void 0, children: label.padEnd(10) }),
|
|
3470
|
+
description && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
|
|
3264
3471
|
"\u2014 ",
|
|
3265
3472
|
description
|
|
3266
3473
|
] })
|
|
3267
3474
|
] });
|
|
3268
3475
|
}
|
|
3269
3476
|
function MainMenu({ onSelect }) {
|
|
3270
|
-
return /* @__PURE__ */
|
|
3477
|
+
return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(
|
|
3271
3478
|
SelectInput3,
|
|
3272
3479
|
{
|
|
3273
3480
|
items: MENU_ITEMS,
|
|
@@ -3322,53 +3529,56 @@ function computeProjectStats(cwd) {
|
|
|
3322
3529
|
}
|
|
3323
3530
|
|
|
3324
3531
|
// src/components/Welcome.tsx
|
|
3325
|
-
import { jsx as
|
|
3532
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3326
3533
|
var NARROW_THRESHOLD = 60;
|
|
3327
3534
|
function Welcome({ version: version2 }) {
|
|
3328
|
-
const { exit } =
|
|
3535
|
+
const { exit } = useApp6();
|
|
3329
3536
|
const { stdout } = useStdout2();
|
|
3330
|
-
const [selectedCommand, setSelectedCommand] =
|
|
3537
|
+
const [selectedCommand, setSelectedCommand] = useState11(null);
|
|
3331
3538
|
const stats = useMemo6(() => computeProjectStats(), []);
|
|
3332
3539
|
const isNarrow = (stdout.columns ?? 80) < NARROW_THRESHOLD;
|
|
3333
|
-
|
|
3540
|
+
useEffect10(() => {
|
|
3334
3541
|
if (selectedCommand === "status") {
|
|
3335
3542
|
const timer = setTimeout(() => exit(), 0);
|
|
3336
3543
|
return () => clearTimeout(timer);
|
|
3337
3544
|
}
|
|
3338
3545
|
}, [selectedCommand, exit]);
|
|
3339
3546
|
if (selectedCommand === "plan") {
|
|
3340
|
-
return /* @__PURE__ */
|
|
3547
|
+
return /* @__PURE__ */ jsx14(Plan, {});
|
|
3341
3548
|
}
|
|
3342
3549
|
if (selectedCommand === "build") {
|
|
3343
|
-
return /* @__PURE__ */
|
|
3550
|
+
return /* @__PURE__ */ jsx14(Build, {});
|
|
3551
|
+
}
|
|
3552
|
+
if (selectedCommand === "resume") {
|
|
3553
|
+
return /* @__PURE__ */ jsx14(Resume, {});
|
|
3344
3554
|
}
|
|
3345
3555
|
if (selectedCommand === "status") {
|
|
3346
|
-
return /* @__PURE__ */
|
|
3556
|
+
return /* @__PURE__ */ jsx14(Status, { version: version2 });
|
|
3347
3557
|
}
|
|
3348
3558
|
if (selectedCommand === "config") {
|
|
3349
|
-
return /* @__PURE__ */
|
|
3559
|
+
return /* @__PURE__ */ jsx14(ConfigEditor, { version: version2 });
|
|
3350
3560
|
}
|
|
3351
|
-
return /* @__PURE__ */
|
|
3352
|
-
isNarrow ? /* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3561
|
+
return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", gap: 1, children: [
|
|
3562
|
+
isNarrow ? /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
|
|
3563
|
+
/* @__PURE__ */ jsxs12(Text14, { bold: true, color: "#f0a030", children: [
|
|
3354
3564
|
"\u{1F439} toby v",
|
|
3355
3565
|
version2
|
|
3356
3566
|
] }),
|
|
3357
|
-
stats !== null && /* @__PURE__ */
|
|
3358
|
-
/* @__PURE__ */
|
|
3359
|
-
/* @__PURE__ */
|
|
3360
|
-
/* @__PURE__ */
|
|
3361
|
-
/* @__PURE__ */
|
|
3362
|
-
/* @__PURE__ */
|
|
3363
|
-
/* @__PURE__ */
|
|
3364
|
-
/* @__PURE__ */
|
|
3365
|
-
/* @__PURE__ */
|
|
3567
|
+
stats !== null && /* @__PURE__ */ jsxs12(Text14, { children: [
|
|
3568
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "Specs: " }),
|
|
3569
|
+
/* @__PURE__ */ jsx14(Text14, { children: stats.totalSpecs }),
|
|
3570
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 Planned: " }),
|
|
3571
|
+
/* @__PURE__ */ jsx14(Text14, { children: stats.planned }),
|
|
3572
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 Done: " }),
|
|
3573
|
+
/* @__PURE__ */ jsx14(Text14, { children: stats.done }),
|
|
3574
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 Tokens: " }),
|
|
3575
|
+
/* @__PURE__ */ jsx14(Text14, { children: formatTokens(stats.totalTokens) })
|
|
3366
3576
|
] })
|
|
3367
|
-
] }) : /* @__PURE__ */
|
|
3368
|
-
/* @__PURE__ */
|
|
3369
|
-
/* @__PURE__ */
|
|
3577
|
+
] }) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "row", gap: 2, children: [
|
|
3578
|
+
/* @__PURE__ */ jsx14(HamsterWheel, {}),
|
|
3579
|
+
/* @__PURE__ */ jsx14(InfoPanel, { version: version2, stats })
|
|
3370
3580
|
] }),
|
|
3371
|
-
/* @__PURE__ */
|
|
3581
|
+
/* @__PURE__ */ jsx14(MainMenu, { onSelect: setSelectedCommand })
|
|
3372
3582
|
] });
|
|
3373
3583
|
}
|
|
3374
3584
|
|
|
@@ -3394,7 +3604,7 @@ var MEOW_FLAGS = {
|
|
|
3394
3604
|
var MEOW_FLAG_NAMES = Object.keys(MEOW_FLAGS);
|
|
3395
3605
|
|
|
3396
3606
|
// src/cli.tsx
|
|
3397
|
-
import { jsx as
|
|
3607
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
3398
3608
|
function writeUnknownCommandError(command2) {
|
|
3399
3609
|
process.stderr.write(
|
|
3400
3610
|
formatErrorWithHint(
|
|
@@ -3414,7 +3624,7 @@ var resolvedSpec = cli.flags.specs ?? cli.flags.spec;
|
|
|
3414
3624
|
var flags = { ...cli.flags, spec: resolvedSpec };
|
|
3415
3625
|
var commands = {
|
|
3416
3626
|
plan: {
|
|
3417
|
-
render: (flags2) => /* @__PURE__ */
|
|
3627
|
+
render: (flags2) => /* @__PURE__ */ jsx15(
|
|
3418
3628
|
Plan,
|
|
3419
3629
|
{
|
|
3420
3630
|
spec: flags2.spec,
|
|
@@ -3429,7 +3639,7 @@ var commands = {
|
|
|
3429
3639
|
waitForExit: true
|
|
3430
3640
|
},
|
|
3431
3641
|
build: {
|
|
3432
|
-
render: (flags2) => /* @__PURE__ */
|
|
3642
|
+
render: (flags2) => /* @__PURE__ */ jsx15(
|
|
3433
3643
|
Build,
|
|
3434
3644
|
{
|
|
3435
3645
|
spec: flags2.spec,
|
|
@@ -3444,7 +3654,7 @@ var commands = {
|
|
|
3444
3654
|
waitForExit: true
|
|
3445
3655
|
},
|
|
3446
3656
|
init: {
|
|
3447
|
-
render: (flags2, _input, version2) => /* @__PURE__ */
|
|
3657
|
+
render: (flags2, _input, version2) => /* @__PURE__ */ jsx15(
|
|
3448
3658
|
Init,
|
|
3449
3659
|
{
|
|
3450
3660
|
version: version2,
|
|
@@ -3459,21 +3669,32 @@ var commands = {
|
|
|
3459
3669
|
waitForExit: true
|
|
3460
3670
|
},
|
|
3461
3671
|
status: {
|
|
3462
|
-
render: (flags2, _input, version2) => /* @__PURE__ */
|
|
3672
|
+
render: (flags2, _input, version2) => /* @__PURE__ */ jsx15(Status, { spec: flags2.spec, version: version2 })
|
|
3673
|
+
},
|
|
3674
|
+
resume: {
|
|
3675
|
+
render: (flags2) => /* @__PURE__ */ jsx15(
|
|
3676
|
+
Resume,
|
|
3677
|
+
{
|
|
3678
|
+
iterations: flags2.iterations,
|
|
3679
|
+
verbose: flags2.verbose,
|
|
3680
|
+
transcript: flags2.transcript
|
|
3681
|
+
}
|
|
3682
|
+
),
|
|
3683
|
+
waitForExit: true
|
|
3463
3684
|
},
|
|
3464
3685
|
clean: {
|
|
3465
|
-
render: (flags2) => /* @__PURE__ */
|
|
3686
|
+
render: (flags2) => /* @__PURE__ */ jsx15(Clean, { force: flags2.force }),
|
|
3466
3687
|
waitForExit: true
|
|
3467
3688
|
},
|
|
3468
3689
|
config: {
|
|
3469
3690
|
render: (_flags, input, version2) => {
|
|
3470
3691
|
const [, subcommand, ...rest] = input;
|
|
3471
|
-
if (!subcommand) return /* @__PURE__ */
|
|
3692
|
+
if (!subcommand) return /* @__PURE__ */ jsx15(ConfigEditor, { version: version2 });
|
|
3472
3693
|
if (subcommand === "set" && rest.some((arg) => arg.includes("="))) {
|
|
3473
|
-
return /* @__PURE__ */
|
|
3694
|
+
return /* @__PURE__ */ jsx15(ConfigSetBatch, { pairs: rest.filter((arg) => arg.includes("=")) });
|
|
3474
3695
|
}
|
|
3475
3696
|
const [configKey, value] = rest;
|
|
3476
|
-
return /* @__PURE__ */
|
|
3697
|
+
return /* @__PURE__ */ jsx15(
|
|
3477
3698
|
Config,
|
|
3478
3699
|
{
|
|
3479
3700
|
subcommand,
|
|
@@ -3502,7 +3723,7 @@ if (cli.flags.help) {
|
|
|
3502
3723
|
}
|
|
3503
3724
|
} else if (!command) {
|
|
3504
3725
|
if (process.stdin.isTTY) {
|
|
3505
|
-
const app = render(/* @__PURE__ */
|
|
3726
|
+
const app = render(/* @__PURE__ */ jsx15(Welcome, { version }));
|
|
3506
3727
|
await app.waitUntilExit();
|
|
3507
3728
|
} else {
|
|
3508
3729
|
process.stdout.write(formatGlobalHelp(version));
|