@0xtiby/toby 0.0.1 → 1.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +469 -293
  2. package/package.json +8 -5
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.tsx
4
4
  import meow from "meow";
5
- import { render, Text as Text12 } from "ink";
5
+ import { render, Text as Text14 } from "ink";
6
6
 
7
7
  // src/commands/plan.tsx
8
8
  import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2 } from "react";
@@ -80,6 +80,7 @@ var LOCAL_TOBY_DIR = ".toby";
80
80
  var DEFAULT_SPECS_DIR = "specs";
81
81
  var STATUS_FILE = "status.json";
82
82
  var CONFIG_FILE = "config.json";
83
+ var TRANSCRIPTS_DIR = "transcripts";
83
84
  function getGlobalDir() {
84
85
  return path.join(os.homedir(), GLOBAL_TOBY_DIR);
85
86
  }
@@ -567,7 +568,6 @@ var AbortError = class extends Error {
567
568
  // src/lib/transcript.ts
568
569
  import fs6 from "fs";
569
570
  import path6 from "path";
570
- var TRANSCRIPTS_DIR = "transcripts";
571
571
  function formatTimestamp() {
572
572
  const now = /* @__PURE__ */ new Date();
573
573
  const pad2 = (n, len = 2) => String(n).padStart(len, "0");
@@ -1523,14 +1523,72 @@ function Build(flags2) {
1523
1523
  }
1524
1524
 
1525
1525
  // src/commands/init.tsx
1526
- import { useState as useState5, useEffect as useEffect4 } from "react";
1527
- import { Text as Text5, Box as Box5, useApp as useApp2 } from "ink";
1526
+ import { useState as useState6, useEffect as useEffect5 } from "react";
1527
+ import { Text as Text6, Box as Box6, useApp as useApp2 } from "ink";
1528
1528
  import SelectInput from "ink-select-input";
1529
1529
  import TextInput from "ink-text-input";
1530
1530
  import fs7 from "fs";
1531
1531
  import path7 from "path";
1532
- import { detectAll, getKnownModels } from "@0xtiby/spawner";
1532
+ import { detectAll } from "@0xtiby/spawner";
1533
+
1534
+ // src/hooks/useModels.ts
1535
+ import { useState as useState5, useEffect as useEffect4 } from "react";
1536
+ import { listModels } from "@0xtiby/spawner";
1537
+ var DEFAULT_ITEM = { label: "default", value: "default" };
1538
+ function useModels(cli2, options = {}) {
1539
+ const { enabled = true } = options;
1540
+ const [items, setItems] = useState5([DEFAULT_ITEM]);
1541
+ const [loading, setLoading] = useState5(enabled);
1542
+ const [error, setError] = useState5(null);
1543
+ useEffect4(() => {
1544
+ if (!enabled) {
1545
+ setLoading(false);
1546
+ return;
1547
+ }
1548
+ let cancelled = false;
1549
+ setLoading(true);
1550
+ setError(null);
1551
+ listModels({ cli: cli2, fallback: true }).then((models) => {
1552
+ if (!cancelled) {
1553
+ const mapped = models.map((m) => ({
1554
+ label: `${m.name} (${m.id})`,
1555
+ value: m.id
1556
+ }));
1557
+ setItems([DEFAULT_ITEM, ...mapped]);
1558
+ }
1559
+ }).catch((err) => {
1560
+ if (!cancelled) {
1561
+ const message = err instanceof Error ? err.message : String(err);
1562
+ console.warn(`Failed to load models for ${cli2}: ${message}`);
1563
+ setError(message);
1564
+ setItems([DEFAULT_ITEM]);
1565
+ }
1566
+ }).finally(() => {
1567
+ if (!cancelled) setLoading(false);
1568
+ });
1569
+ return () => {
1570
+ cancelled = true;
1571
+ };
1572
+ }, [cli2, enabled]);
1573
+ return { items, loading, error };
1574
+ }
1575
+
1576
+ // src/components/LoadingSpinner.tsx
1577
+ import { Text as Text5, Box as Box5 } from "ink";
1578
+ import Spinner from "ink-spinner";
1533
1579
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1580
+ function LoadingSpinner({ message = "Loading..." }) {
1581
+ return /* @__PURE__ */ jsxs4(Box5, { children: [
1582
+ /* @__PURE__ */ jsx5(Spinner, { type: "dots" }),
1583
+ /* @__PURE__ */ jsxs4(Text5, { children: [
1584
+ " ",
1585
+ message
1586
+ ] })
1587
+ ] });
1588
+ }
1589
+
1590
+ // src/commands/init.tsx
1591
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1534
1592
  function createProject(selections, cwd = process.cwd()) {
1535
1593
  const localDir = getLocalDir(cwd);
1536
1594
  const configPath = path7.join(localDir, CONFIG_FILE);
@@ -1589,15 +1647,15 @@ function getInstalledClis(result) {
1589
1647
  return Object.entries(result).filter(([, info]) => info.installed).map(([name]) => name);
1590
1648
  }
1591
1649
  function CliTable({ clis }) {
1592
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
1593
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Detected CLIs:" }),
1650
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginBottom: 1, children: [
1651
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Detected CLIs:" }),
1594
1652
  Object.entries(clis).map(
1595
- ([name, info]) => /* @__PURE__ */ jsxs4(Text5, { children: [
1653
+ ([name, info]) => /* @__PURE__ */ jsxs5(Text6, { children: [
1596
1654
  " ",
1597
- info.installed ? /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u2713" }) : /* @__PURE__ */ jsx5(Text5, { color: "red", children: "\u2717" }),
1655
+ info.installed ? /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713" }) : /* @__PURE__ */ jsx6(Text6, { color: "red", children: "\u2717" }),
1598
1656
  " ",
1599
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: name }),
1600
- info.installed && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1657
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: name }),
1658
+ info.installed && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1601
1659
  " ",
1602
1660
  info.version,
1603
1661
  info.authenticated ? " (authenticated)" : " (not authenticated)"
@@ -1606,13 +1664,6 @@ function CliTable({ clis }) {
1606
1664
  )
1607
1665
  ] });
1608
1666
  }
1609
- function modelItems(cli2) {
1610
- const models = getKnownModels(cli2);
1611
- return [
1612
- { label: "default", value: "default" },
1613
- ...models.map((m) => ({ label: `${m.name} (${m.id})`, value: m.id }))
1614
- ];
1615
- }
1616
1667
  function hasAllInitFlags(flags2) {
1617
1668
  return flags2.planCli !== void 0 && flags2.planModel !== void 0 && flags2.buildCli !== void 0 && flags2.buildModel !== void 0 && flags2.specsDir !== void 0;
1618
1669
  }
@@ -1623,8 +1674,8 @@ function NonInteractiveInit({ flags: flags2 }) {
1623
1674
  const invalidCli = [planCli, buildCli].find(
1624
1675
  (cli2) => !CLI_NAMES.includes(cli2)
1625
1676
  );
1626
- const [status, setStatus] = useState5(invalidCli ? { type: "invalid_cli", cli: invalidCli } : { type: "detecting" });
1627
- useEffect4(() => {
1677
+ const [status, setStatus] = useState6(invalidCli ? { type: "invalid_cli", cli: invalidCli } : { type: "detecting" });
1678
+ useEffect5(() => {
1628
1679
  if (invalidCli) {
1629
1680
  process.exitCode = 1;
1630
1681
  exit();
@@ -1661,23 +1712,23 @@ function NonInteractiveInit({ flags: flags2 }) {
1661
1712
  });
1662
1713
  }, []);
1663
1714
  if (status.type === "invalid_cli") {
1664
- return /* @__PURE__ */ jsx5(Text5, { color: "red", children: `\u2717 Unknown CLI: ${status.cli}. Must be one of: ${CLI_NAMES.join(", ")}` });
1715
+ return /* @__PURE__ */ jsx6(Text6, { color: "red", children: `\u2717 Unknown CLI: ${status.cli}. Must be one of: ${CLI_NAMES.join(", ")}` });
1665
1716
  }
1666
1717
  if (status.type === "detecting") {
1667
- return /* @__PURE__ */ jsx5(Text5, { children: "Detecting installed CLIs..." });
1718
+ return /* @__PURE__ */ jsx6(Text6, { children: "Detecting installed CLIs..." });
1668
1719
  }
1669
1720
  if (status.type === "error") {
1670
- return /* @__PURE__ */ jsx5(Text5, { color: "red", children: `\u2717 ${status.message}` });
1721
+ return /* @__PURE__ */ jsx6(Text6, { color: "red", children: `\u2717 ${status.message}` });
1671
1722
  }
1672
1723
  const { result, selections } = status;
1673
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1674
- /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2713 Project initialized!" }),
1675
- /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1724
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1725
+ /* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "\u2713 Project initialized!" }),
1726
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1676
1727
  " created ",
1677
1728
  path7.relative(process.cwd(), result.configPath)
1678
1729
  ] }),
1679
- result.statusCreated && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " created .toby/status.json" }),
1680
- result.specsDirCreated && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1730
+ result.statusCreated && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " created .toby/status.json" }),
1731
+ result.specsDirCreated && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1681
1732
  " created ",
1682
1733
  selections.specsDir,
1683
1734
  "/"
@@ -1686,16 +1737,16 @@ function NonInteractiveInit({ flags: flags2 }) {
1686
1737
  }
1687
1738
  function Init(flags2) {
1688
1739
  if (hasAllInitFlags(flags2)) {
1689
- return /* @__PURE__ */ jsx5(NonInteractiveInit, { flags: flags2 });
1740
+ return /* @__PURE__ */ jsx6(NonInteractiveInit, { flags: flags2 });
1690
1741
  }
1691
- return /* @__PURE__ */ jsx5(InteractiveInit, { version: flags2.version });
1742
+ return /* @__PURE__ */ jsx6(InteractiveInit, { version: flags2.version });
1692
1743
  }
1693
1744
  function InteractiveInit({ version: version2 }) {
1694
1745
  const { exit } = useApp2();
1695
- const [phase, setPhase] = useState5("detecting");
1696
- const [clis, setClis] = useState5(null);
1697
- const [installedClis, setInstalledClis] = useState5([]);
1698
- const [selections, setSelections] = useState5({
1746
+ const [phase, setPhase] = useState6("detecting");
1747
+ const [clis, setClis] = useState6(null);
1748
+ const [installedClis, setInstalledClis] = useState6([]);
1749
+ const [selections, setSelections] = useState6({
1699
1750
  planCli: "claude",
1700
1751
  planModel: "default",
1701
1752
  buildCli: "claude",
@@ -1703,10 +1754,12 @@ function InteractiveInit({ version: version2 }) {
1703
1754
  specsDir: DEFAULT_SPECS_DIR,
1704
1755
  verbose: false
1705
1756
  });
1706
- const [specsDirInput, setSpecsDirInput] = useState5(DEFAULT_SPECS_DIR);
1707
- const [result, setResult] = useState5(null);
1708
- const [error, setError] = useState5(null);
1709
- useEffect4(() => {
1757
+ const [specsDirInput, setSpecsDirInput] = useState6(DEFAULT_SPECS_DIR);
1758
+ const [result, setResult] = useState6(null);
1759
+ const [error, setError] = useState6(null);
1760
+ const planModels = useModels(selections.planCli, { enabled: phase === "plan_model" });
1761
+ const buildModels = useModels(selections.buildCli, { enabled: phase === "build_model" });
1762
+ useEffect5(() => {
1710
1763
  if (phase !== "detecting") return;
1711
1764
  detectAll().then((detectResult) => {
1712
1765
  setClis(detectResult);
@@ -1765,66 +1818,68 @@ function InteractiveInit({ version: version2 }) {
1765
1818
  });
1766
1819
  }
1767
1820
  if (phase === "detecting") {
1768
- return /* @__PURE__ */ jsx5(Text5, { children: "Detecting installed CLIs..." });
1821
+ return /* @__PURE__ */ jsx6(Text6, { children: "Detecting installed CLIs..." });
1769
1822
  }
1770
1823
  if (phase === "no_cli") {
1771
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1772
- clis && /* @__PURE__ */ jsx5(CliTable, { clis }),
1773
- /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "No AI CLIs found. Install one of the following:" }),
1774
- /* @__PURE__ */ jsx5(Text5, { children: " \u2022 claude \u2014 npm install -g @anthropic-ai/claude-code" }),
1775
- /* @__PURE__ */ jsx5(Text5, { children: " \u2022 codex \u2014 npm install -g @openai/codex" }),
1776
- /* @__PURE__ */ jsx5(Text5, { children: " \u2022 opencode \u2014 go install github.com/opencode-ai/opencode@latest" })
1824
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1825
+ clis && /* @__PURE__ */ jsx6(CliTable, { clis }),
1826
+ /* @__PURE__ */ jsx6(Text6, { color: "red", bold: true, children: "No AI CLIs found. Install one of the following:" }),
1827
+ /* @__PURE__ */ jsx6(Text6, { children: " \u2022 claude \u2014 npm install -g @anthropic-ai/claude-code" }),
1828
+ /* @__PURE__ */ jsx6(Text6, { children: " \u2022 codex \u2014 npm install -g @openai/codex" }),
1829
+ /* @__PURE__ */ jsx6(Text6, { children: " \u2022 opencode \u2014 go install github.com/opencode-ai/opencode@latest" })
1777
1830
  ] });
1778
1831
  }
1779
1832
  const cliItems = installedClis.map((name) => ({
1780
1833
  label: `${name} \u2014 ${clis?.[name]?.version ?? "unknown"}`,
1781
1834
  value: name
1782
1835
  }));
1783
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1784
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: `toby v${version2} \u2014 project setup
1836
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1837
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: `toby v${version2} \u2014 project setup
1785
1838
  ` }),
1786
- clis && /* @__PURE__ */ jsx5(CliTable, { clis }),
1787
- phase === "plan_cli" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1788
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Select CLI for planning:" }),
1789
- /* @__PURE__ */ jsx5(SelectInput, { items: cliItems, onSelect: handlePlanCliSelect })
1839
+ clis && /* @__PURE__ */ jsx6(CliTable, { clis }),
1840
+ phase === "plan_cli" && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1841
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select CLI for planning:" }),
1842
+ /* @__PURE__ */ jsx6(SelectInput, { items: cliItems, onSelect: handlePlanCliSelect })
1790
1843
  ] }),
1791
- phase === "plan_model" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1792
- /* @__PURE__ */ jsxs4(Text5, { bold: true, children: [
1844
+ phase === "plan_model" && planModels.loading && /* @__PURE__ */ jsx6(LoadingSpinner, { message: "Loading models..." }),
1845
+ phase === "plan_model" && !planModels.loading && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1846
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, children: [
1793
1847
  "Select model for planning (",
1794
1848
  selections.planCli,
1795
1849
  "):"
1796
1850
  ] }),
1797
- /* @__PURE__ */ jsx5(
1851
+ /* @__PURE__ */ jsx6(
1798
1852
  SelectInput,
1799
1853
  {
1800
- items: modelItems(selections.planCli),
1854
+ items: planModels.items,
1801
1855
  onSelect: handlePlanModelSelect
1802
1856
  }
1803
1857
  )
1804
1858
  ] }),
1805
- phase === "build_cli" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1806
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Select CLI for building:" }),
1807
- /* @__PURE__ */ jsx5(SelectInput, { items: cliItems, onSelect: handleBuildCliSelect })
1859
+ phase === "build_cli" && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1860
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select CLI for building:" }),
1861
+ /* @__PURE__ */ jsx6(SelectInput, { items: cliItems, onSelect: handleBuildCliSelect })
1808
1862
  ] }),
1809
- phase === "build_model" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1810
- /* @__PURE__ */ jsxs4(Text5, { bold: true, children: [
1863
+ phase === "build_model" && buildModels.loading && /* @__PURE__ */ jsx6(LoadingSpinner, { message: "Loading models..." }),
1864
+ phase === "build_model" && !buildModels.loading && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1865
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, children: [
1811
1866
  "Select model for building (",
1812
1867
  selections.buildCli,
1813
1868
  "):"
1814
1869
  ] }),
1815
- /* @__PURE__ */ jsx5(
1870
+ /* @__PURE__ */ jsx6(
1816
1871
  SelectInput,
1817
1872
  {
1818
- items: modelItems(selections.buildCli),
1873
+ items: buildModels.items,
1819
1874
  onSelect: handleBuildModelSelect
1820
1875
  }
1821
1876
  )
1822
1877
  ] }),
1823
- phase === "specs_dir" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1824
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Specs directory:" }),
1825
- /* @__PURE__ */ jsxs4(Box5, { children: [
1826
- /* @__PURE__ */ jsx5(Text5, { children: " > " }),
1827
- /* @__PURE__ */ jsx5(
1878
+ phase === "specs_dir" && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1879
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Specs directory:" }),
1880
+ /* @__PURE__ */ jsxs5(Box6, { children: [
1881
+ /* @__PURE__ */ jsx6(Text6, { children: " > " }),
1882
+ /* @__PURE__ */ jsx6(
1828
1883
  TextInput,
1829
1884
  {
1830
1885
  value: specsDirInput,
@@ -1834,10 +1889,10 @@ function InteractiveInit({ version: version2 }) {
1834
1889
  )
1835
1890
  ] })
1836
1891
  ] }),
1837
- phase === "verbose" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1838
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Verbose output:" }),
1839
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " Show full CLI output including tool use and system events" }),
1840
- /* @__PURE__ */ jsx5(
1892
+ phase === "verbose" && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1893
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Verbose output:" }),
1894
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " Show full CLI output including tool use and system events" }),
1895
+ /* @__PURE__ */ jsx6(
1841
1896
  SelectInput,
1842
1897
  {
1843
1898
  items: [
@@ -1848,38 +1903,38 @@ function InteractiveInit({ version: version2 }) {
1848
1903
  }
1849
1904
  )
1850
1905
  ] }),
1851
- phase === "done" && error && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, children: [
1852
- /* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "\u2717 Initialization failed" }),
1853
- /* @__PURE__ */ jsx5(Text5, { color: "red", children: ` ${error}` })
1906
+ phase === "done" && error && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
1907
+ /* @__PURE__ */ jsx6(Text6, { color: "red", bold: true, children: "\u2717 Initialization failed" }),
1908
+ /* @__PURE__ */ jsx6(Text6, { color: "red", children: ` ${error}` })
1854
1909
  ] }),
1855
- phase === "done" && result && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, children: [
1856
- /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2713 Project initialized!" }),
1857
- /* @__PURE__ */ jsx5(Text5, { children: "" }),
1858
- /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1910
+ phase === "done" && result && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
1911
+ /* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "\u2713 Project initialized!" }),
1912
+ /* @__PURE__ */ jsx6(Text6, { children: "" }),
1913
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1859
1914
  " created ",
1860
1915
  path7.relative(process.cwd(), result.configPath)
1861
1916
  ] }),
1862
- result.statusCreated && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " created .toby/status.json" }),
1863
- result.specsDirCreated && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1917
+ result.statusCreated && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " created .toby/status.json" }),
1918
+ result.specsDirCreated && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1864
1919
  " created ",
1865
1920
  selections.specsDir,
1866
1921
  "/"
1867
1922
  ] }),
1868
- /* @__PURE__ */ jsx5(Text5, { children: "" }),
1869
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Next steps:" }),
1870
- /* @__PURE__ */ jsxs4(Text5, { children: [
1923
+ /* @__PURE__ */ jsx6(Text6, { children: "" }),
1924
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Next steps:" }),
1925
+ /* @__PURE__ */ jsxs5(Text6, { children: [
1871
1926
  " 1. Add spec files to ",
1872
1927
  selections.specsDir,
1873
1928
  "/"
1874
1929
  ] }),
1875
- /* @__PURE__ */ jsxs4(Text5, { children: [
1930
+ /* @__PURE__ */ jsxs5(Text6, { children: [
1876
1931
  " 2. Run ",
1877
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "toby plan" }),
1932
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "toby plan" }),
1878
1933
  " to plan a spec"
1879
1934
  ] }),
1880
- /* @__PURE__ */ jsxs4(Text5, { children: [
1935
+ /* @__PURE__ */ jsxs5(Text6, { children: [
1881
1936
  " 3. Run ",
1882
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "toby build" }),
1937
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "toby build" }),
1883
1938
  " to build tasks"
1884
1939
  ] })
1885
1940
  ] })
@@ -1887,9 +1942,9 @@ function InteractiveInit({ version: version2 }) {
1887
1942
  }
1888
1943
 
1889
1944
  // src/commands/status.tsx
1890
- import { Text as Text6, Box as Box6 } from "ink";
1945
+ import { Text as Text7, Box as Box7 } from "ink";
1891
1946
  import fs8 from "fs";
1892
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1947
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1893
1948
  function formatDuration(startedAt, completedAt) {
1894
1949
  if (!completedAt) return "\u2014";
1895
1950
  const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime();
@@ -1937,15 +1992,15 @@ function StatusTable({ rows }) {
1937
1992
  };
1938
1993
  const separator = `${"\u2500".repeat(colWidths.name + 2)}\u253C${"\u2500".repeat(colWidths.status + 2)}\u253C${"\u2500".repeat(colWidths.iterations + 2)}\u253C${"\u2500".repeat(colWidths.tokens + 2)}`;
1939
1994
  const headerLine = ` ${pad(headers.name, colWidths.name)} \u2502 ${pad(headers.status, colWidths.status)} \u2502 ${pad(headers.iterations, colWidths.iterations)} \u2502 ${pad(headers.tokens, colWidths.tokens)} `;
1940
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1941
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: headerLine }),
1942
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: separator }),
1943
- rows.map((row) => /* @__PURE__ */ jsx6(Text6, { children: ` ${pad(row.name, colWidths.name)} \u2502 ${pad(row.status, colWidths.status)} \u2502 ${pad(String(row.iterations), colWidths.iterations)} \u2502 ${pad(String(row.tokens), colWidths.tokens)} ` }, row.name))
1995
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
1996
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: headerLine }),
1997
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: separator }),
1998
+ rows.map((row) => /* @__PURE__ */ jsx7(Text7, { children: ` ${pad(row.name, colWidths.name)} \u2502 ${pad(row.status, colWidths.status)} \u2502 ${pad(String(row.iterations), colWidths.iterations)} \u2502 ${pad(String(row.tokens), colWidths.tokens)} ` }, row.name))
1944
1999
  ] });
1945
2000
  }
1946
2001
  function IterationTable({ rows }) {
1947
2002
  if (rows.length === 0) {
1948
- return /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No iterations yet" });
2003
+ return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No iterations yet" });
1949
2004
  }
1950
2005
  const headers = { index: "#", type: "Type", cli: "CLI", tokens: "Tokens", duration: "Duration", exitCode: "Exit" };
1951
2006
  const w = {
@@ -1958,10 +2013,10 @@ function IterationTable({ rows }) {
1958
2013
  };
1959
2014
  const separator = `${"\u2500".repeat(w.index + 2)}\u253C${"\u2500".repeat(w.type + 2)}\u253C${"\u2500".repeat(w.cli + 2)}\u253C${"\u2500".repeat(w.tokens + 2)}\u253C${"\u2500".repeat(w.duration + 2)}\u253C${"\u2500".repeat(w.exitCode + 2)}`;
1960
2015
  const headerLine = ` ${pad(headers.index, w.index)} \u2502 ${pad(headers.type, w.type)} \u2502 ${pad(headers.cli, w.cli)} \u2502 ${pad(headers.tokens, w.tokens)} \u2502 ${pad(headers.duration, w.duration)} \u2502 ${pad(headers.exitCode, w.exitCode)} `;
1961
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1962
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: headerLine }),
1963
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: separator }),
1964
- rows.map((row) => /* @__PURE__ */ jsx6(Text6, { children: ` ${pad(row.index, w.index)} \u2502 ${pad(row.type, w.type)} \u2502 ${pad(row.cli, w.cli)} \u2502 ${pad(row.tokens, w.tokens)} \u2502 ${pad(row.duration, w.duration)} \u2502 ${pad(row.exitCode, w.exitCode)} ` }, row.index))
2016
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2017
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: headerLine }),
2018
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: separator }),
2019
+ rows.map((row) => /* @__PURE__ */ jsx7(Text7, { children: ` ${pad(row.index, w.index)} \u2502 ${pad(row.type, w.type)} \u2502 ${pad(row.cli, w.cli)} \u2502 ${pad(row.tokens, w.tokens)} \u2502 ${pad(row.duration, w.duration)} \u2502 ${pad(row.exitCode, w.exitCode)} ` }, row.index))
1965
2020
  ] });
1966
2021
  }
1967
2022
  function DetailedView({ specName, cwd }) {
@@ -1969,7 +2024,7 @@ function DetailedView({ specName, cwd }) {
1969
2024
  const specs = discoverSpecs(cwd, config);
1970
2025
  const spec = findSpec(specs, specName);
1971
2026
  if (!spec) {
1972
- return /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
2027
+ return /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
1973
2028
  "Spec not found: ",
1974
2029
  specName
1975
2030
  ] });
@@ -1995,21 +2050,21 @@ function DetailedView({ specName, cwd }) {
1995
2050
  (sum, iter) => sum + (iter.tokensUsed ?? 0),
1996
2051
  0
1997
2052
  );
1998
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1999
- statusWarning && /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: statusWarning }),
2000
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: spec.name }),
2001
- /* @__PURE__ */ jsxs5(Text6, { children: [
2053
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2054
+ statusWarning && /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: statusWarning }),
2055
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: spec.name }),
2056
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2002
2057
  "Status: ",
2003
2058
  entry.status
2004
2059
  ] }),
2005
- /* @__PURE__ */ jsx6(Text6, { children: "" }),
2006
- /* @__PURE__ */ jsx6(IterationTable, { rows: iterationRows }),
2007
- /* @__PURE__ */ jsx6(Text6, { children: "" }),
2008
- /* @__PURE__ */ jsxs5(Text6, { children: [
2060
+ /* @__PURE__ */ jsx7(Text7, { children: "" }),
2061
+ /* @__PURE__ */ jsx7(IterationTable, { rows: iterationRows }),
2062
+ /* @__PURE__ */ jsx7(Text7, { children: "" }),
2063
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2009
2064
  "Iterations: ",
2010
2065
  entry.iterations.length
2011
2066
  ] }),
2012
- /* @__PURE__ */ jsxs5(Text6, { children: [
2067
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2013
2068
  "Tokens used: ",
2014
2069
  totalTokens
2015
2070
  ] })
@@ -2019,43 +2074,43 @@ function Status({ spec, version: version2 }) {
2019
2074
  const cwd = process.cwd();
2020
2075
  const localDir = getLocalDir(cwd);
2021
2076
  if (!fs8.existsSync(localDir)) {
2022
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2023
- /* @__PURE__ */ jsx6(Text6, { color: "red", bold: true, children: "Toby not initialized" }),
2024
- /* @__PURE__ */ jsxs5(Text6, { children: [
2077
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2078
+ /* @__PURE__ */ jsx7(Text7, { color: "red", bold: true, children: "Toby not initialized" }),
2079
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2025
2080
  "Run ",
2026
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "toby init" }),
2081
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "toby init" }),
2027
2082
  " to set up your project."
2028
2083
  ] })
2029
2084
  ] });
2030
2085
  }
2031
2086
  if (spec) {
2032
- return /* @__PURE__ */ jsx6(DetailedView, { specName: spec, cwd });
2087
+ return /* @__PURE__ */ jsx7(DetailedView, { specName: spec, cwd });
2033
2088
  }
2034
2089
  const { rows, warnings } = buildRows(cwd);
2035
2090
  if (rows.length === 0) {
2036
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2037
- /* @__PURE__ */ jsx6(Text6, { children: `toby v${version2}` }),
2038
- warnings.map((w) => /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: w }, w)),
2039
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No specs found. Add .md files to your specs directory." })
2091
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2092
+ /* @__PURE__ */ jsx7(Text7, { children: `toby v${version2}` }),
2093
+ warnings.map((w) => /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: w }, w)),
2094
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No specs found. Add .md files to your specs directory." })
2040
2095
  ] });
2041
2096
  }
2042
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2043
- /* @__PURE__ */ jsx6(Text6, { children: `toby v${version2}` }),
2044
- warnings.map((w) => /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: w }, w)),
2045
- /* @__PURE__ */ jsx6(Text6, { children: "" }),
2046
- /* @__PURE__ */ jsx6(StatusTable, { rows })
2097
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2098
+ /* @__PURE__ */ jsx7(Text7, { children: `toby v${version2}` }),
2099
+ warnings.map((w) => /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: w }, w)),
2100
+ /* @__PURE__ */ jsx7(Text7, { children: "" }),
2101
+ /* @__PURE__ */ jsx7(StatusTable, { rows })
2047
2102
  ] });
2048
2103
  }
2049
2104
 
2050
2105
  // src/commands/config.tsx
2051
- import { useState as useState6, useEffect as useEffect5 } from "react";
2052
- import { Text as Text7, Box as Box7, useApp as useApp3 } from "ink";
2106
+ import { useState as useState7, useEffect as useEffect6 } from "react";
2107
+ import { Text as Text8, Box as Box8, useApp as useApp3 } from "ink";
2053
2108
  import SelectInput2 from "ink-select-input";
2054
2109
  import TextInput2 from "ink-text-input";
2055
2110
  import fs9 from "fs";
2056
2111
  import path8 from "path";
2057
- import { detectAll as detectAll2, getKnownModels as getKnownModels2 } from "@0xtiby/spawner";
2058
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2112
+ import { detectAll as detectAll2 } from "@0xtiby/spawner";
2113
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2059
2114
  var VALID_KEYS = {
2060
2115
  "plan.cli": "string",
2061
2116
  "plan.model": "string",
@@ -2122,28 +2177,28 @@ function readMergeWriteConfig(mutations) {
2122
2177
  }
2123
2178
  function ConfigGet({ configKey }) {
2124
2179
  if (!(configKey in VALID_KEYS)) {
2125
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Unknown config key: ${configKey}
2180
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Unknown config key: ${configKey}
2126
2181
  Valid keys: ${Object.keys(VALID_KEYS).join(", ")}` });
2127
2182
  }
2128
2183
  const cwd = process.cwd();
2129
2184
  const localDir = getLocalDir(cwd);
2130
2185
  if (!fs9.existsSync(localDir)) {
2131
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2132
- /* @__PURE__ */ jsx7(Text7, { color: "red", bold: true, children: "No config found" }),
2133
- /* @__PURE__ */ jsxs6(Text7, { children: [
2186
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2187
+ /* @__PURE__ */ jsx8(Text8, { color: "red", bold: true, children: "No config found" }),
2188
+ /* @__PURE__ */ jsxs7(Text8, { children: [
2134
2189
  "Run ",
2135
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "toby init" }),
2190
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "toby init" }),
2136
2191
  " to set up your project."
2137
2192
  ] })
2138
2193
  ] });
2139
2194
  }
2140
2195
  const config = loadConfig();
2141
2196
  const value = getNestedValue(config, configKey);
2142
- return /* @__PURE__ */ jsx7(Text7, { children: String(value) });
2197
+ return /* @__PURE__ */ jsx8(Text8, { children: String(value) });
2143
2198
  }
2144
2199
  function ConfigSet({ configKey, value }) {
2145
2200
  if (!(configKey in VALID_KEYS)) {
2146
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Unknown config key: ${configKey}
2201
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Unknown config key: ${configKey}
2147
2202
  Valid keys: ${Object.keys(VALID_KEYS).join(", ")}` });
2148
2203
  }
2149
2204
  const type = VALID_KEYS[configKey];
@@ -2151,7 +2206,7 @@ Valid keys: ${Object.keys(VALID_KEYS).join(", ")}` });
2151
2206
  try {
2152
2207
  parsed = parseValue(value, type);
2153
2208
  } catch (err) {
2154
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Invalid value for ${configKey}: ${err.message}` });
2209
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Invalid value for ${configKey}: ${err.message}` });
2155
2210
  }
2156
2211
  const partial = {};
2157
2212
  setNestedValue(partial, configKey, parsed);
@@ -2159,23 +2214,16 @@ Valid keys: ${Object.keys(VALID_KEYS).join(", ")}` });
2159
2214
  ConfigSchema.parse({ ...partial });
2160
2215
  } catch (err) {
2161
2216
  const msg = err instanceof Error ? err.message : String(err);
2162
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Validation error for ${configKey}: ${msg}` });
2217
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Validation error for ${configKey}: ${msg}` });
2163
2218
  }
2164
2219
  try {
2165
2220
  readMergeWriteConfig([{ key: configKey, value: parsed }]);
2166
2221
  } catch (err) {
2167
2222
  const code = err.code;
2168
2223
  const msg = code === "EACCES" ? `Permission denied writing to ${path8.join(getLocalDir(process.cwd()), CONFIG_FILE)}` : `Failed to write config: ${err.message}`;
2169
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: msg });
2224
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: msg });
2170
2225
  }
2171
- return /* @__PURE__ */ jsx7(Text7, { color: "green", children: `Set ${configKey} = ${String(parsed)}` });
2172
- }
2173
- function modelItems2(cli2) {
2174
- const models = getKnownModels2(cli2);
2175
- return [
2176
- { label: "default", value: "default" },
2177
- ...models.map((m) => ({ label: `${m.name} (${m.id})`, value: m.id }))
2178
- ];
2226
+ return /* @__PURE__ */ jsx8(Text8, { color: "green", children: `Set ${configKey} = ${String(parsed)}` });
2179
2227
  }
2180
2228
  function configToEditorValues(config) {
2181
2229
  return {
@@ -2222,9 +2270,9 @@ function pastPhase(current, target) {
2222
2270
  return PHASE_ORDER[current] > PHASE_ORDER[target];
2223
2271
  }
2224
2272
  function CompletedField({ label, value }) {
2225
- return /* @__PURE__ */ jsxs6(Text7, { children: [
2273
+ return /* @__PURE__ */ jsxs7(Text8, { children: [
2226
2274
  " ",
2227
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
2275
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
2228
2276
  label,
2229
2277
  ":"
2230
2278
  ] }),
@@ -2234,9 +2282,9 @@ function CompletedField({ label, value }) {
2234
2282
  }
2235
2283
  function ConfigEditor({ version: version2 }) {
2236
2284
  const { exit } = useApp3();
2237
- const [phase, setPhase] = useState6("loading");
2238
- const [installedClis, setInstalledClis] = useState6([]);
2239
- const [values, setValues] = useState6({
2285
+ const [phase, setPhase] = useState7("loading");
2286
+ const [installedClis, setInstalledClis] = useState7([]);
2287
+ const [values, setValues] = useState7({
2240
2288
  planCli: "claude",
2241
2289
  planModel: "default",
2242
2290
  planIterations: 2,
@@ -2246,10 +2294,12 @@ function ConfigEditor({ version: version2 }) {
2246
2294
  specsDir: "specs",
2247
2295
  verbose: false
2248
2296
  });
2249
- const [iterInput, setIterInput] = useState6("");
2250
- const [specsDirInput, setSpecsDirInput] = useState6("");
2251
- const [saveError, setSaveError] = useState6(null);
2252
- useEffect5(() => {
2297
+ const [iterInput, setIterInput] = useState7("");
2298
+ const [specsDirInput, setSpecsDirInput] = useState7("");
2299
+ const [saveError, setSaveError] = useState7(null);
2300
+ const planModels = useModels(values.planCli, { enabled: phase === "plan_model" });
2301
+ const buildModels = useModels(values.buildCli, { enabled: phase === "build_model" });
2302
+ useEffect6(() => {
2253
2303
  if (phase !== "loading") return;
2254
2304
  const config = loadConfig();
2255
2305
  const initial = configToEditorValues(config);
@@ -2288,17 +2338,17 @@ function ConfigEditor({ version: version2 }) {
2288
2338
  exit();
2289
2339
  }
2290
2340
  if (phase === "loading") {
2291
- return /* @__PURE__ */ jsx7(Text7, { children: "Loading configuration..." });
2341
+ return /* @__PURE__ */ jsx8(Text8, { children: "Loading configuration..." });
2292
2342
  }
2293
2343
  const planCliItems = cliItems();
2294
2344
  const buildCliItems = cliItems();
2295
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2296
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: `toby v${version2} \u2014 config editor
2345
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2346
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: `toby v${version2} \u2014 config editor
2297
2347
  ` }),
2298
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Plan" }),
2299
- phase === "plan_cli" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2300
- /* @__PURE__ */ jsx7(Text7, { children: " cli:" }),
2301
- /* @__PURE__ */ jsx7(
2348
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "Plan" }),
2349
+ phase === "plan_cli" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2350
+ /* @__PURE__ */ jsx8(Text8, { children: " cli:" }),
2351
+ /* @__PURE__ */ jsx8(
2302
2352
  SelectInput2,
2303
2353
  {
2304
2354
  items: planCliItems,
@@ -2310,14 +2360,15 @@ function ConfigEditor({ version: version2 }) {
2310
2360
  }
2311
2361
  )
2312
2362
  ] }),
2313
- phase !== "plan_cli" && /* @__PURE__ */ jsx7(CompletedField, { label: "cli", value: values.planCli }),
2314
- phase === "plan_model" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2315
- /* @__PURE__ */ jsx7(Text7, { children: " model:" }),
2316
- /* @__PURE__ */ jsx7(
2363
+ phase !== "plan_cli" && /* @__PURE__ */ jsx8(CompletedField, { label: "cli", value: values.planCli }),
2364
+ phase === "plan_model" && planModels.loading && /* @__PURE__ */ jsx8(LoadingSpinner, { message: "Loading models..." }),
2365
+ phase === "plan_model" && !planModels.loading && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2366
+ /* @__PURE__ */ jsx8(Text8, { children: " model:" }),
2367
+ /* @__PURE__ */ jsx8(
2317
2368
  SelectInput2,
2318
2369
  {
2319
- items: modelItems2(values.planCli),
2320
- initialIndex: initialIndex(modelItems2(values.planCli), values.planModel),
2370
+ items: planModels.items,
2371
+ initialIndex: initialIndex(planModels.items, values.planModel),
2321
2372
  onSelect: (item) => {
2322
2373
  setValues((v) => ({ ...v, planModel: item.value }));
2323
2374
  setIterInput(String(values.planIterations));
@@ -2326,10 +2377,10 @@ function ConfigEditor({ version: version2 }) {
2326
2377
  }
2327
2378
  )
2328
2379
  ] }),
2329
- pastPhase(phase, "plan_model") && /* @__PURE__ */ jsx7(CompletedField, { label: "model", value: values.planModel }),
2330
- phase === "plan_iterations" && /* @__PURE__ */ jsxs6(Box7, { children: [
2331
- /* @__PURE__ */ jsx7(Text7, { children: " iterations: " }),
2332
- /* @__PURE__ */ jsx7(
2380
+ pastPhase(phase, "plan_model") && /* @__PURE__ */ jsx8(CompletedField, { label: "model", value: values.planModel }),
2381
+ phase === "plan_iterations" && /* @__PURE__ */ jsxs7(Box8, { children: [
2382
+ /* @__PURE__ */ jsx8(Text8, { children: " iterations: " }),
2383
+ /* @__PURE__ */ jsx8(
2333
2384
  TextInput2,
2334
2385
  {
2335
2386
  value: iterInput,
@@ -2344,14 +2395,14 @@ function ConfigEditor({ version: version2 }) {
2344
2395
  }
2345
2396
  )
2346
2397
  ] }),
2347
- pastPhase(phase, "plan_iterations") && /* @__PURE__ */ jsx7(CompletedField, { label: "iterations", value: String(values.planIterations) }),
2348
- pastPhase(phase, "plan_iterations") && /* @__PURE__ */ jsxs6(Fragment2, { children: [
2349
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2350
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Build" })
2398
+ pastPhase(phase, "plan_iterations") && /* @__PURE__ */ jsx8(CompletedField, { label: "iterations", value: String(values.planIterations) }),
2399
+ pastPhase(phase, "plan_iterations") && /* @__PURE__ */ jsxs7(Fragment2, { children: [
2400
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2401
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "Build" })
2351
2402
  ] }),
2352
- phase === "build_cli" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2353
- /* @__PURE__ */ jsx7(Text7, { children: " cli:" }),
2354
- /* @__PURE__ */ jsx7(
2403
+ phase === "build_cli" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2404
+ /* @__PURE__ */ jsx8(Text8, { children: " cli:" }),
2405
+ /* @__PURE__ */ jsx8(
2355
2406
  SelectInput2,
2356
2407
  {
2357
2408
  items: buildCliItems,
@@ -2363,14 +2414,15 @@ function ConfigEditor({ version: version2 }) {
2363
2414
  }
2364
2415
  )
2365
2416
  ] }),
2366
- pastPhase(phase, "build_cli") && /* @__PURE__ */ jsx7(CompletedField, { label: "cli", value: values.buildCli }),
2367
- phase === "build_model" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2368
- /* @__PURE__ */ jsx7(Text7, { children: " model:" }),
2369
- /* @__PURE__ */ jsx7(
2417
+ pastPhase(phase, "build_cli") && /* @__PURE__ */ jsx8(CompletedField, { label: "cli", value: values.buildCli }),
2418
+ phase === "build_model" && buildModels.loading && /* @__PURE__ */ jsx8(LoadingSpinner, { message: "Loading models..." }),
2419
+ phase === "build_model" && !buildModels.loading && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2420
+ /* @__PURE__ */ jsx8(Text8, { children: " model:" }),
2421
+ /* @__PURE__ */ jsx8(
2370
2422
  SelectInput2,
2371
2423
  {
2372
- items: modelItems2(values.buildCli),
2373
- initialIndex: initialIndex(modelItems2(values.buildCli), values.buildModel),
2424
+ items: buildModels.items,
2425
+ initialIndex: initialIndex(buildModels.items, values.buildModel),
2374
2426
  onSelect: (item) => {
2375
2427
  setValues((v) => ({ ...v, buildModel: item.value }));
2376
2428
  setIterInput(String(values.buildIterations));
@@ -2379,10 +2431,10 @@ function ConfigEditor({ version: version2 }) {
2379
2431
  }
2380
2432
  )
2381
2433
  ] }),
2382
- pastPhase(phase, "build_model") && /* @__PURE__ */ jsx7(CompletedField, { label: "model", value: values.buildModel }),
2383
- phase === "build_iterations" && /* @__PURE__ */ jsxs6(Box7, { children: [
2384
- /* @__PURE__ */ jsx7(Text7, { children: " iterations: " }),
2385
- /* @__PURE__ */ jsx7(
2434
+ pastPhase(phase, "build_model") && /* @__PURE__ */ jsx8(CompletedField, { label: "model", value: values.buildModel }),
2435
+ phase === "build_iterations" && /* @__PURE__ */ jsxs7(Box8, { children: [
2436
+ /* @__PURE__ */ jsx8(Text8, { children: " iterations: " }),
2437
+ /* @__PURE__ */ jsx8(
2386
2438
  TextInput2,
2387
2439
  {
2388
2440
  value: iterInput,
@@ -2398,14 +2450,14 @@ function ConfigEditor({ version: version2 }) {
2398
2450
  }
2399
2451
  )
2400
2452
  ] }),
2401
- pastPhase(phase, "build_iterations") && /* @__PURE__ */ jsx7(CompletedField, { label: "iterations", value: String(values.buildIterations) }),
2402
- pastPhase(phase, "build_iterations") && /* @__PURE__ */ jsxs6(Fragment2, { children: [
2403
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2404
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "General" })
2453
+ pastPhase(phase, "build_iterations") && /* @__PURE__ */ jsx8(CompletedField, { label: "iterations", value: String(values.buildIterations) }),
2454
+ pastPhase(phase, "build_iterations") && /* @__PURE__ */ jsxs7(Fragment2, { children: [
2455
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2456
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: "General" })
2405
2457
  ] }),
2406
- phase === "specs_dir" && /* @__PURE__ */ jsxs6(Box7, { children: [
2407
- /* @__PURE__ */ jsx7(Text7, { children: " specsDir: " }),
2408
- /* @__PURE__ */ jsx7(
2458
+ phase === "specs_dir" && /* @__PURE__ */ jsxs7(Box8, { children: [
2459
+ /* @__PURE__ */ jsx8(Text8, { children: " specsDir: " }),
2460
+ /* @__PURE__ */ jsx8(
2409
2461
  TextInput2,
2410
2462
  {
2411
2463
  value: specsDirInput,
@@ -2418,10 +2470,10 @@ function ConfigEditor({ version: version2 }) {
2418
2470
  }
2419
2471
  )
2420
2472
  ] }),
2421
- pastPhase(phase, "specs_dir") && /* @__PURE__ */ jsx7(CompletedField, { label: "specsDir", value: values.specsDir }),
2422
- phase === "verbose" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2423
- /* @__PURE__ */ jsx7(Text7, { children: " verbose:" }),
2424
- /* @__PURE__ */ jsx7(
2473
+ pastPhase(phase, "specs_dir") && /* @__PURE__ */ jsx8(CompletedField, { label: "specsDir", value: values.specsDir }),
2474
+ phase === "verbose" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2475
+ /* @__PURE__ */ jsx8(Text8, { children: " verbose:" }),
2476
+ /* @__PURE__ */ jsx8(
2425
2477
  SelectInput2,
2426
2478
  {
2427
2479
  items: [
@@ -2436,13 +2488,13 @@ function ConfigEditor({ version: version2 }) {
2436
2488
  }
2437
2489
  )
2438
2490
  ] }),
2439
- phase === "done" && /* @__PURE__ */ jsxs6(Fragment2, { children: [
2440
- /* @__PURE__ */ jsx7(CompletedField, { label: "verbose", value: String(values.verbose) }),
2441
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2442
- saveError ? /* @__PURE__ */ jsxs6(Text7, { color: "red", bold: true, children: [
2491
+ phase === "done" && /* @__PURE__ */ jsxs7(Fragment2, { children: [
2492
+ /* @__PURE__ */ jsx8(CompletedField, { label: "verbose", value: String(values.verbose) }),
2493
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2494
+ saveError ? /* @__PURE__ */ jsxs7(Text8, { color: "red", bold: true, children: [
2443
2495
  "\u2717 ",
2444
2496
  saveError
2445
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: "green", bold: true, children: "\u2713 Config saved" })
2497
+ ] }) : /* @__PURE__ */ jsx8(Text8, { color: "green", bold: true, children: "\u2713 Config saved" })
2446
2498
  ] })
2447
2499
  ] });
2448
2500
  }
@@ -2470,7 +2522,7 @@ function ConfigSetBatch({ pairs }) {
2470
2522
  }
2471
2523
  if (errors.length > 0) {
2472
2524
  process.exitCode = 1;
2473
- return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: errors.map((e, i) => /* @__PURE__ */ jsx7(Text7, { color: "red", children: e }, i)) });
2525
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: errors.map((e, i) => /* @__PURE__ */ jsx8(Text8, { color: "red", children: e }, i)) });
2474
2526
  }
2475
2527
  const partial = {};
2476
2528
  for (const { key, value } of parsed) {
@@ -2481,7 +2533,7 @@ function ConfigSetBatch({ pairs }) {
2481
2533
  } catch (err) {
2482
2534
  process.exitCode = 1;
2483
2535
  const msg = err instanceof Error ? err.message : String(err);
2484
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Validation error: ${msg}` });
2536
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Validation error: ${msg}` });
2485
2537
  }
2486
2538
  try {
2487
2539
  readMergeWriteConfig(parsed);
@@ -2489,12 +2541,12 @@ function ConfigSetBatch({ pairs }) {
2489
2541
  process.exitCode = 1;
2490
2542
  const code = err.code;
2491
2543
  const msg = code === "EACCES" ? `Permission denied writing to ${path8.join(getLocalDir(process.cwd()), CONFIG_FILE)}` : `Failed to write config: ${err.message}`;
2492
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: msg });
2544
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: msg });
2493
2545
  }
2494
- return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: parsed.map(({ key, value }) => /* @__PURE__ */ jsx7(Text7, { color: "green", children: `Set ${key} = ${String(value)}` }, key)) });
2546
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: parsed.map(({ key, value }) => /* @__PURE__ */ jsx8(Text8, { color: "green", children: `Set ${key} = ${String(value)}` }, key)) });
2495
2547
  }
2496
2548
  function UnknownSubcommand({ subcommand }) {
2497
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Unknown config subcommand: ${subcommand}
2549
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Unknown config subcommand: ${subcommand}
2498
2550
  Usage: toby config [get <key> | set <key> <value>]` });
2499
2551
  }
2500
2552
  function Config({
@@ -2504,39 +2556,153 @@ function Config({
2504
2556
  version: version2
2505
2557
  }) {
2506
2558
  if (subcommand && subcommand !== "get" && subcommand !== "set") {
2507
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2508
- /* @__PURE__ */ jsx7(Text7, { children: `toby v${version2}` }),
2509
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2510
- /* @__PURE__ */ jsx7(UnknownSubcommand, { subcommand })
2559
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2560
+ /* @__PURE__ */ jsx8(Text8, { children: `toby v${version2}` }),
2561
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2562
+ /* @__PURE__ */ jsx8(UnknownSubcommand, { subcommand })
2511
2563
  ] });
2512
2564
  }
2513
2565
  if (subcommand === "get" && configKey) {
2514
- return /* @__PURE__ */ jsx7(ConfigGet, { configKey });
2566
+ return /* @__PURE__ */ jsx8(ConfigGet, { configKey });
2515
2567
  }
2516
2568
  if (subcommand === "set" && configKey && value) {
2517
- return /* @__PURE__ */ jsx7(ConfigSet, { configKey, value });
2569
+ return /* @__PURE__ */ jsx8(ConfigSet, { configKey, value });
2518
2570
  }
2519
2571
  if (subcommand === "set" && configKey && !value) {
2520
- return /* @__PURE__ */ jsx7(Text7, { color: "red", children: `Missing value for config set.
2572
+ return /* @__PURE__ */ jsx8(Text8, { color: "red", children: `Missing value for config set.
2521
2573
  Usage: toby config set <key> <value>` });
2522
2574
  }
2523
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2524
- /* @__PURE__ */ jsx7(Text7, { children: `toby v${version2}` }),
2525
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2526
- /* @__PURE__ */ jsx7(Text7, { children: "Usage: toby config [get <key> | set <key> <value>]" }),
2527
- /* @__PURE__ */ jsx7(Text7, { children: "" }),
2528
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Available keys:" }),
2529
- Object.entries(VALID_KEYS).map(([key, type]) => /* @__PURE__ */ jsx7(Text7, { children: ` ${key} (${type})` }, key))
2575
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2576
+ /* @__PURE__ */ jsx8(Text8, { children: `toby v${version2}` }),
2577
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2578
+ /* @__PURE__ */ jsx8(Text8, { children: "Usage: toby config [get <key> | set <key> <value>]" }),
2579
+ /* @__PURE__ */ jsx8(Text8, { children: "" }),
2580
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Available keys:" }),
2581
+ Object.entries(VALID_KEYS).map(([key, type]) => /* @__PURE__ */ jsx8(Text8, { children: ` ${key} (${type})` }, key))
2530
2582
  ] });
2531
2583
  }
2532
2584
 
2585
+ // src/commands/clean.tsx
2586
+ import { useState as useState8, useEffect as useEffect7, useMemo as useMemo4 } from "react";
2587
+ import { Text as Text9, useApp as useApp4, useInput as useInput2 } from "ink";
2588
+
2589
+ // src/lib/clean.ts
2590
+ import path9 from "path";
2591
+ import fs10 from "fs";
2592
+ function listTranscripts(cwd) {
2593
+ const dir = path9.join(getLocalDir(cwd), TRANSCRIPTS_DIR);
2594
+ let entries;
2595
+ try {
2596
+ entries = fs10.readdirSync(dir, { withFileTypes: true });
2597
+ } catch {
2598
+ return [];
2599
+ }
2600
+ return entries.filter((e) => e.isFile()).map((e) => path9.join(dir, e.name));
2601
+ }
2602
+ function deleteTranscripts(files) {
2603
+ let deleted = 0;
2604
+ for (const file of files) {
2605
+ try {
2606
+ fs10.unlinkSync(file);
2607
+ deleted++;
2608
+ } catch {
2609
+ }
2610
+ }
2611
+ return deleted;
2612
+ }
2613
+ function executeClean(cwd) {
2614
+ const files = listTranscripts(cwd);
2615
+ if (files.length === 0) {
2616
+ return { deleted: 0, failed: 0, total: 0 };
2617
+ }
2618
+ const deleted = deleteTranscripts(files);
2619
+ return { deleted, failed: files.length - deleted, total: files.length };
2620
+ }
2621
+
2622
+ // src/commands/clean.tsx
2623
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2624
+ function computeInitial(force) {
2625
+ const files = listTranscripts();
2626
+ if (files.length === 0) {
2627
+ return { phase: "empty", fileCount: 0, result: null };
2628
+ }
2629
+ if (!process.stdin.isTTY && !force) {
2630
+ return { phase: "error", fileCount: files.length, result: null };
2631
+ }
2632
+ if (force) {
2633
+ const r = executeClean();
2634
+ return { phase: "done", fileCount: files.length, result: r };
2635
+ }
2636
+ return { phase: "confirming", fileCount: files.length, result: null };
2637
+ }
2638
+ function Clean({ force }) {
2639
+ const { exit } = useApp4();
2640
+ const initial = useMemo4(() => computeInitial(force), [force]);
2641
+ useEffect7(() => {
2642
+ if (initial.phase === "error") {
2643
+ process.exitCode = 1;
2644
+ }
2645
+ }, [initial.phase]);
2646
+ const [phase, setPhase] = useState8(initial.phase);
2647
+ const [result, setResult] = useState8(initial.result);
2648
+ useEffect7(() => {
2649
+ if (phase === "empty" || phase === "done" || phase === "cancelled" || phase === "error") {
2650
+ const timer = setTimeout(() => exit(), 100);
2651
+ return () => clearTimeout(timer);
2652
+ }
2653
+ }, [phase, exit]);
2654
+ useInput2((input, key) => {
2655
+ if (phase !== "confirming") return;
2656
+ if (input === "y" || key.return) {
2657
+ const r = executeClean();
2658
+ setResult(r);
2659
+ setPhase("done");
2660
+ } else if (input === "n" || key.escape) {
2661
+ setPhase("cancelled");
2662
+ }
2663
+ });
2664
+ if (phase === "error") {
2665
+ return /* @__PURE__ */ jsx9(Text9, { color: "red", children: "Error: Use --force to clean transcripts in non-interactive mode." });
2666
+ }
2667
+ if (phase === "empty") {
2668
+ return /* @__PURE__ */ jsx9(Text9, { children: "No transcripts to clean." });
2669
+ }
2670
+ if (phase === "confirming") {
2671
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
2672
+ "Found ",
2673
+ initial.fileCount,
2674
+ " transcript files. Delete all? [Y/n]"
2675
+ ] });
2676
+ }
2677
+ if (phase === "done" && result) {
2678
+ if (result.failed > 0) {
2679
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
2680
+ "Deleted ",
2681
+ result.deleted,
2682
+ " transcript files. Failed to delete ",
2683
+ result.failed,
2684
+ " files."
2685
+ ] });
2686
+ }
2687
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
2688
+ "Deleted ",
2689
+ result.deleted,
2690
+ " transcript files."
2691
+ ] });
2692
+ }
2693
+ if (phase === "cancelled") {
2694
+ return /* @__PURE__ */ jsx9(Text9, { children: "Clean cancelled." });
2695
+ }
2696
+ return null;
2697
+ }
2698
+
2533
2699
  // src/components/Welcome.tsx
2534
- import { useState as useState8, useEffect as useEffect7, useMemo as useMemo5 } from "react";
2535
- import { Box as Box11, Text as Text11, useApp as useApp4, useStdout as useStdout2 } from "ink";
2700
+ import { useState as useState10, useEffect as useEffect9, useMemo as useMemo6 } from "react";
2701
+ import { Box as Box12, Text as Text13, useApp as useApp5, useStdout as useStdout2 } from "ink";
2536
2702
 
2537
2703
  // src/components/hamster/HamsterWheel.tsx
2538
- import { useState as useState7, useEffect as useEffect6, useMemo as useMemo4 } from "react";
2539
- import { Box as Box8, Text as Text8, useStdout } from "ink";
2704
+ import { useState as useState9, useEffect as useEffect8, useMemo as useMemo5 } from "react";
2705
+ import { Box as Box9, Text as Text10, useStdout } from "ink";
2540
2706
 
2541
2707
  // src/components/hamster/palette.ts
2542
2708
  var PALETTE = {
@@ -2695,7 +2861,7 @@ function generateWheelPixels(cx, cy, outerRadius, innerRadius, spokeAngle, aspec
2695
2861
  }
2696
2862
 
2697
2863
  // src/components/hamster/HamsterWheel.tsx
2698
- import { jsx as jsx8 } from "react/jsx-runtime";
2864
+ import { jsx as jsx10 } from "react/jsx-runtime";
2699
2865
  function buildGrid(width, height) {
2700
2866
  return Array.from(
2701
2867
  { length: height },
@@ -2778,9 +2944,9 @@ function HamsterWheel({
2778
2944
  heightProp,
2779
2945
  columns
2780
2946
  );
2781
- const [frame, setFrame] = useState7(0);
2782
- const [spokeAngle, setSpokeAngle] = useState7(0);
2783
- useEffect6(() => {
2947
+ const [frame, setFrame] = useState9(0);
2948
+ const [spokeAngle, setSpokeAngle] = useState9(0);
2949
+ useEffect8(() => {
2784
2950
  if (speed === 0 || isStatic) return;
2785
2951
  const interval = computeInterval(HAMSTER_BASE_INTERVAL, speed);
2786
2952
  const id = setInterval(() => {
@@ -2788,7 +2954,7 @@ function HamsterWheel({
2788
2954
  }, interval);
2789
2955
  return () => clearInterval(id);
2790
2956
  }, [speed, isStatic]);
2791
- useEffect6(() => {
2957
+ useEffect8(() => {
2792
2958
  if (speed === 0 || isStatic) return;
2793
2959
  const interval = computeInterval(WHEEL_BASE_INTERVAL, speed);
2794
2960
  const id = setInterval(() => {
@@ -2796,7 +2962,7 @@ function HamsterWheel({
2796
2962
  }, interval);
2797
2963
  return () => clearInterval(id);
2798
2964
  }, [speed, isStatic]);
2799
- const renderedRows = useMemo4(() => {
2965
+ const renderedRows = useMemo5(() => {
2800
2966
  if (isStatic) return [];
2801
2967
  const grid = buildGrid(resolvedWidth, resolvedHeight);
2802
2968
  const { cx, cy, outerRadius, innerRadius } = computeWheelGeometry(
@@ -2828,43 +2994,43 @@ function HamsterWheel({
2828
2994
  return buildColorRuns(grid, resolvedWidth, resolvedHeight);
2829
2995
  }, [resolvedWidth, resolvedHeight, frame, spokeAngle, isStatic]);
2830
2996
  if (isStatic) {
2831
- return /* @__PURE__ */ jsx8(Text8, { children: " \u{1F439} toby" });
2997
+ return /* @__PURE__ */ jsx10(Text10, { children: " \u{1F439} toby" });
2832
2998
  }
2833
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: renderedRows.map((runs, y) => /* @__PURE__ */ jsx8(Text8, { children: runs.map((run, i) => /* @__PURE__ */ jsx8(Text8, { color: run.fg, backgroundColor: run.bg, children: run.char.repeat(run.length) }, i)) }, y)) });
2999
+ return /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: renderedRows.map((runs, y) => /* @__PURE__ */ jsx10(Text10, { children: runs.map((run, i) => /* @__PURE__ */ jsx10(Text10, { color: run.fg, backgroundColor: run.bg, children: run.char.repeat(run.length) }, i)) }, y)) });
2834
3000
  }
2835
3001
 
2836
3002
  // src/components/InfoPanel.tsx
2837
- import { Box as Box9, Text as Text9 } from "ink";
2838
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3003
+ import { Box as Box10, Text as Text11 } from "ink";
3004
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2839
3005
  var formatTokens = (n) => new Intl.NumberFormat().format(n);
2840
3006
  function StatRow({ label, value }) {
2841
- return /* @__PURE__ */ jsxs7(Box9, { children: [
2842
- /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
3007
+ return /* @__PURE__ */ jsxs9(Box10, { children: [
3008
+ /* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
2843
3009
  String(label).padStart(9),
2844
3010
  " "
2845
3011
  ] }),
2846
- /* @__PURE__ */ jsx9(Text9, { children: value })
3012
+ /* @__PURE__ */ jsx11(Text11, { children: value })
2847
3013
  ] });
2848
3014
  }
2849
3015
  function InfoPanel({ version: version2, stats }) {
2850
- return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
2851
- /* @__PURE__ */ jsxs7(Text9, { bold: true, color: "#f0a030", children: [
3016
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
3017
+ /* @__PURE__ */ jsxs9(Text11, { bold: true, color: "#f0a030", children: [
2852
3018
  "toby v",
2853
3019
  version2
2854
3020
  ] }),
2855
- stats !== null && /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", marginTop: 1, children: [
2856
- /* @__PURE__ */ jsx9(StatRow, { label: "Specs", value: stats.totalSpecs }),
2857
- /* @__PURE__ */ jsx9(StatRow, { label: "Planned", value: stats.planned }),
2858
- /* @__PURE__ */ jsx9(StatRow, { label: "Done", value: stats.done }),
2859
- /* @__PURE__ */ jsx9(StatRow, { label: "Tokens", value: formatTokens(stats.totalTokens) })
3021
+ stats !== null && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
3022
+ /* @__PURE__ */ jsx11(StatRow, { label: "Specs", value: stats.totalSpecs }),
3023
+ /* @__PURE__ */ jsx11(StatRow, { label: "Planned", value: stats.planned }),
3024
+ /* @__PURE__ */ jsx11(StatRow, { label: "Done", value: stats.done }),
3025
+ /* @__PURE__ */ jsx11(StatRow, { label: "Tokens", value: formatTokens(stats.totalTokens) })
2860
3026
  ] })
2861
3027
  ] });
2862
3028
  }
2863
3029
 
2864
3030
  // src/components/MainMenu.tsx
2865
- import { Text as Text10, Box as Box10 } from "ink";
3031
+ import { Text as Text12, Box as Box11 } from "ink";
2866
3032
  import SelectInput3 from "ink-select-input";
2867
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
3033
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2868
3034
  var MENU_ITEMS = [
2869
3035
  { label: "plan", value: "plan", description: "Plan specs with AI loop engine" },
2870
3036
  { label: "build", value: "build", description: "Build tasks one-per-spawn with AI" },
@@ -2872,16 +3038,16 @@ var MENU_ITEMS = [
2872
3038
  { label: "config", value: "config", description: "Manage configuration" }
2873
3039
  ];
2874
3040
  function MenuItem({ isSelected = false, label, description }) {
2875
- return /* @__PURE__ */ jsxs8(Box10, { children: [
2876
- /* @__PURE__ */ jsx10(Text10, { color: isSelected ? "blue" : void 0, children: label.padEnd(10) }),
2877
- description && /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
3041
+ return /* @__PURE__ */ jsxs10(Box11, { children: [
3042
+ /* @__PURE__ */ jsx12(Text12, { color: isSelected ? "blue" : void 0, children: label.padEnd(10) }),
3043
+ description && /* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
2878
3044
  "\u2014 ",
2879
3045
  description
2880
3046
  ] })
2881
3047
  ] });
2882
3048
  }
2883
3049
  function MainMenu({ onSelect }) {
2884
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsx10(
3050
+ return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx12(
2885
3051
  SelectInput3,
2886
3052
  {
2887
3053
  items: MENU_ITEMS,
@@ -2892,9 +3058,9 @@ function MainMenu({ onSelect }) {
2892
3058
  }
2893
3059
 
2894
3060
  // src/lib/stats.ts
2895
- import fs10 from "fs";
3061
+ import fs11 from "fs";
2896
3062
  function computeProjectStats(cwd) {
2897
- if (!fs10.existsSync(getLocalDir(cwd))) {
3063
+ if (!fs11.existsSync(getLocalDir(cwd))) {
2898
3064
  return null;
2899
3065
  }
2900
3066
  let statusData;
@@ -2936,60 +3102,60 @@ function computeProjectStats(cwd) {
2936
3102
  }
2937
3103
 
2938
3104
  // src/components/Welcome.tsx
2939
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3105
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2940
3106
  var NARROW_THRESHOLD = 60;
2941
3107
  function Welcome({ version: version2 }) {
2942
- const { exit } = useApp4();
3108
+ const { exit } = useApp5();
2943
3109
  const { stdout } = useStdout2();
2944
- const [selectedCommand, setSelectedCommand] = useState8(null);
2945
- const stats = useMemo5(() => computeProjectStats(), []);
3110
+ const [selectedCommand, setSelectedCommand] = useState10(null);
3111
+ const stats = useMemo6(() => computeProjectStats(), []);
2946
3112
  const isNarrow = (stdout.columns ?? 80) < NARROW_THRESHOLD;
2947
- useEffect7(() => {
3113
+ useEffect9(() => {
2948
3114
  if (selectedCommand === "status") {
2949
3115
  const timer = setTimeout(() => exit(), 0);
2950
3116
  return () => clearTimeout(timer);
2951
3117
  }
2952
3118
  }, [selectedCommand, exit]);
2953
3119
  if (selectedCommand === "plan") {
2954
- return /* @__PURE__ */ jsx11(Plan, {});
3120
+ return /* @__PURE__ */ jsx13(Plan, {});
2955
3121
  }
2956
3122
  if (selectedCommand === "build") {
2957
- return /* @__PURE__ */ jsx11(Build, {});
3123
+ return /* @__PURE__ */ jsx13(Build, {});
2958
3124
  }
2959
3125
  if (selectedCommand === "status") {
2960
- return /* @__PURE__ */ jsx11(Status, { version: version2 });
3126
+ return /* @__PURE__ */ jsx13(Status, { version: version2 });
2961
3127
  }
2962
3128
  if (selectedCommand === "config") {
2963
- return /* @__PURE__ */ jsx11(ConfigEditor, { version: version2 });
3129
+ return /* @__PURE__ */ jsx13(ConfigEditor, { version: version2 });
2964
3130
  }
2965
- return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", gap: 1, children: [
2966
- isNarrow ? /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", children: [
2967
- /* @__PURE__ */ jsxs9(Text11, { bold: true, color: "#f0a030", children: [
3131
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
3132
+ isNarrow ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
3133
+ /* @__PURE__ */ jsxs11(Text13, { bold: true, color: "#f0a030", children: [
2968
3134
  "\u{1F439} toby v",
2969
3135
  version2
2970
3136
  ] }),
2971
- stats !== null && /* @__PURE__ */ jsxs9(Text11, { children: [
2972
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Specs: " }),
2973
- /* @__PURE__ */ jsx11(Text11, { children: stats.totalSpecs }),
2974
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \xB7 Planned: " }),
2975
- /* @__PURE__ */ jsx11(Text11, { children: stats.planned }),
2976
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \xB7 Done: " }),
2977
- /* @__PURE__ */ jsx11(Text11, { children: stats.done }),
2978
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " \xB7 Tokens: " }),
2979
- /* @__PURE__ */ jsx11(Text11, { children: formatTokens(stats.totalTokens) })
3137
+ stats !== null && /* @__PURE__ */ jsxs11(Text13, { children: [
3138
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Specs: " }),
3139
+ /* @__PURE__ */ jsx13(Text13, { children: stats.totalSpecs }),
3140
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Planned: " }),
3141
+ /* @__PURE__ */ jsx13(Text13, { children: stats.planned }),
3142
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Done: " }),
3143
+ /* @__PURE__ */ jsx13(Text13, { children: stats.done }),
3144
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Tokens: " }),
3145
+ /* @__PURE__ */ jsx13(Text13, { children: formatTokens(stats.totalTokens) })
2980
3146
  ] })
2981
- ] }) : /* @__PURE__ */ jsxs9(Box11, { flexDirection: "row", gap: 2, children: [
2982
- /* @__PURE__ */ jsx11(HamsterWheel, {}),
2983
- /* @__PURE__ */ jsx11(InfoPanel, { version: version2, stats })
3147
+ ] }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 2, children: [
3148
+ /* @__PURE__ */ jsx13(HamsterWheel, {}),
3149
+ /* @__PURE__ */ jsx13(InfoPanel, { version: version2, stats })
2984
3150
  ] }),
2985
- /* @__PURE__ */ jsx11(MainMenu, { onSelect: setSelectedCommand })
3151
+ /* @__PURE__ */ jsx13(MainMenu, { onSelect: setSelectedCommand })
2986
3152
  ] });
2987
3153
  }
2988
3154
 
2989
3155
  // src/cli.tsx
2990
- import { jsx as jsx12 } from "react/jsx-runtime";
3156
+ import { jsx as jsx14 } from "react/jsx-runtime";
2991
3157
  function Help({ version: version2 }) {
2992
- return /* @__PURE__ */ jsx12(Text12, { children: `toby v${version2} \u2014 AI-assisted development loop engine
3158
+ return /* @__PURE__ */ jsx14(Text14, { children: `toby v${version2} \u2014 AI-assisted development loop engine
2993
3159
 
2994
3160
  Usage
2995
3161
  $ toby <command> [options]
@@ -3000,6 +3166,7 @@ Commands
3000
3166
  init Initialize toby in current project
3001
3167
  status Show project status
3002
3168
  config Manage configuration
3169
+ clean Delete all transcript files
3003
3170
 
3004
3171
  Options
3005
3172
  --help Show this help
@@ -3010,7 +3177,7 @@ Spec Selection
3010
3177
  --specs=<names> Alias for --spec` });
3011
3178
  }
3012
3179
  function UnknownCommand({ command: command2 }) {
3013
- return /* @__PURE__ */ jsx12(Text12, { color: "red", children: `Unknown command: ${command2}
3180
+ return /* @__PURE__ */ jsx14(Text14, { color: "red", children: `Unknown command: ${command2}
3014
3181
  Run "toby --help" for available commands.` });
3015
3182
  }
3016
3183
  var cli = meow(
@@ -3024,6 +3191,7 @@ Commands
3024
3191
  init Initialize toby in current project
3025
3192
  status Show project status
3026
3193
  config Manage configuration
3194
+ clean Delete all transcript files
3027
3195
 
3028
3196
  Plan Options
3029
3197
  --spec=<query> Target spec(s) by name, slug, number, or comma-separated list
@@ -3061,6 +3229,9 @@ Config Subcommands
3061
3229
  config get <key> Show a config value (dot-notation)
3062
3230
  config set <key> <value> Set a config value
3063
3231
  config set <k>=<v> [<k>=<v>...] Batch set config values
3232
+
3233
+ Clean Options
3234
+ --force Skip confirmation prompt
3064
3235
  `,
3065
3236
  {
3066
3237
  importMeta: import.meta,
@@ -3077,7 +3248,8 @@ Config Subcommands
3077
3248
  buildCli: { type: "string" },
3078
3249
  buildModel: { type: "string" },
3079
3250
  specsDir: { type: "string" },
3080
- session: { type: "string" }
3251
+ session: { type: "string" },
3252
+ force: { type: "boolean", default: false }
3081
3253
  }
3082
3254
  }
3083
3255
  );
@@ -3086,7 +3258,7 @@ var resolvedSpec = cli.flags.specs ?? cli.flags.spec;
3086
3258
  var flags = { ...cli.flags, spec: resolvedSpec };
3087
3259
  var commands = {
3088
3260
  plan: {
3089
- render: (flags2) => /* @__PURE__ */ jsx12(
3261
+ render: (flags2) => /* @__PURE__ */ jsx14(
3090
3262
  Plan,
3091
3263
  {
3092
3264
  spec: flags2.spec,
@@ -3101,7 +3273,7 @@ var commands = {
3101
3273
  waitForExit: true
3102
3274
  },
3103
3275
  build: {
3104
- render: (flags2) => /* @__PURE__ */ jsx12(
3276
+ render: (flags2) => /* @__PURE__ */ jsx14(
3105
3277
  Build,
3106
3278
  {
3107
3279
  spec: flags2.spec,
@@ -3116,7 +3288,7 @@ var commands = {
3116
3288
  waitForExit: true
3117
3289
  },
3118
3290
  init: {
3119
- render: (flags2, _input, version2) => /* @__PURE__ */ jsx12(
3291
+ render: (flags2, _input, version2) => /* @__PURE__ */ jsx14(
3120
3292
  Init,
3121
3293
  {
3122
3294
  version: version2,
@@ -3131,17 +3303,21 @@ var commands = {
3131
3303
  waitForExit: true
3132
3304
  },
3133
3305
  status: {
3134
- render: (flags2, _input, version2) => /* @__PURE__ */ jsx12(Status, { spec: flags2.spec, version: version2 })
3306
+ render: (flags2, _input, version2) => /* @__PURE__ */ jsx14(Status, { spec: flags2.spec, version: version2 })
3307
+ },
3308
+ clean: {
3309
+ render: (flags2) => /* @__PURE__ */ jsx14(Clean, { force: flags2.force }),
3310
+ waitForExit: true
3135
3311
  },
3136
3312
  config: {
3137
3313
  render: (_flags, input, version2) => {
3138
3314
  const [, subcommand, ...rest] = input;
3139
- if (!subcommand) return /* @__PURE__ */ jsx12(ConfigEditor, { version: version2 });
3315
+ if (!subcommand) return /* @__PURE__ */ jsx14(ConfigEditor, { version: version2 });
3140
3316
  if (subcommand === "set" && rest.some((arg) => arg.includes("="))) {
3141
- return /* @__PURE__ */ jsx12(ConfigSetBatch, { pairs: rest.filter((arg) => arg.includes("=")) });
3317
+ return /* @__PURE__ */ jsx14(ConfigSetBatch, { pairs: rest.filter((arg) => arg.includes("=")) });
3142
3318
  }
3143
3319
  const [configKey, value] = rest;
3144
- return /* @__PURE__ */ jsx12(
3320
+ return /* @__PURE__ */ jsx14(
3145
3321
  Config,
3146
3322
  {
3147
3323
  subcommand,
@@ -3158,10 +3334,10 @@ var version = cli.pkg.version ?? "0.0.0";
3158
3334
  var [command] = cli.input;
3159
3335
  if (!command) {
3160
3336
  if (process.stdin.isTTY) {
3161
- const app = render(/* @__PURE__ */ jsx12(Welcome, { version }));
3337
+ const app = render(/* @__PURE__ */ jsx14(Welcome, { version }));
3162
3338
  await app.waitUntilExit();
3163
3339
  } else {
3164
- render(/* @__PURE__ */ jsx12(Help, { version })).unmount();
3340
+ render(/* @__PURE__ */ jsx14(Help, { version })).unmount();
3165
3341
  }
3166
3342
  } else if (command in commands) {
3167
3343
  const entry = commands[command];
@@ -3172,6 +3348,6 @@ if (!command) {
3172
3348
  app.unmount();
3173
3349
  }
3174
3350
  } else {
3175
- render(/* @__PURE__ */ jsx12(UnknownCommand, { command })).unmount();
3351
+ render(/* @__PURE__ */ jsx14(UnknownCommand, { command })).unmount();
3176
3352
  process.exitCode = 1;
3177
3353
  }