@tuttiai/cli 0.16.0 → 0.17.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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { config } from "dotenv";
5
- import { createLogger as createLogger16 } from "@tuttiai/core";
5
+ import { createLogger as createLogger18 } from "@tuttiai/core";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/init.ts
@@ -1729,34 +1729,568 @@ async function evalCommand(suitePath, opts) {
1729
1729
  }
1730
1730
  }
1731
1731
 
1732
+ // src/commands/eval-record.ts
1733
+ import { readFile } from "fs/promises";
1734
+ import { join as join2, resolve as resolve10 } from "path";
1735
+ import chalk11 from "chalk";
1736
+ import Enquirer3 from "enquirer";
1737
+ import {
1738
+ JsonFileGoldenStore,
1739
+ PostgresSessionStore,
1740
+ SecretsManager as SecretsManager5,
1741
+ createLogger as createLogger10
1742
+ } from "@tuttiai/core";
1743
+
1744
+ // src/commands/eval-record-render.ts
1745
+ import chalk10 from "chalk";
1746
+ function extractSessionDraft(session) {
1747
+ const firstUser = session.messages.find((m) => m.role === "user");
1748
+ const lastAssistant = [...session.messages].reverse().find((m) => m.role === "assistant");
1749
+ return {
1750
+ input: firstUser ? messageText(firstUser) : "",
1751
+ output: lastAssistant ? messageText(lastAssistant) : "",
1752
+ tool_sequence: collectToolSequence(session.messages)
1753
+ };
1754
+ }
1755
+ function messageText(msg) {
1756
+ if (typeof msg.content === "string") return msg.content;
1757
+ return msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
1758
+ }
1759
+ function collectToolSequence(messages) {
1760
+ const seq = [];
1761
+ for (const msg of messages) {
1762
+ if (msg.role !== "assistant" || typeof msg.content === "string") continue;
1763
+ for (const block of msg.content) {
1764
+ if (block.type === "tool_use") seq.push(block.name);
1765
+ }
1766
+ }
1767
+ return seq;
1768
+ }
1769
+ function truncate200(text) {
1770
+ const compact = text.replace(/\s+/g, " ").trim();
1771
+ return compact.length > 200 ? compact.slice(0, 199) + "\u2026" : compact;
1772
+ }
1773
+ function renderSessionSummary(session, draft, tokens) {
1774
+ const lines = [];
1775
+ lines.push(chalk10.cyan.bold(" Session summary"));
1776
+ lines.push(chalk10.dim(" Session: ") + session.id);
1777
+ lines.push(chalk10.dim(" Agent: ") + session.agent_name);
1778
+ lines.push(chalk10.dim(" Created: ") + session.created_at.toISOString());
1779
+ lines.push(chalk10.dim(" Tokens: ") + (tokens !== void 0 ? String(tokens) : "\u2014"));
1780
+ lines.push("");
1781
+ lines.push(chalk10.dim(" Input: ") + truncate200(draft.input || "(empty)"));
1782
+ lines.push(chalk10.dim(" Output: ") + truncate200(draft.output || "(empty)"));
1783
+ lines.push(
1784
+ chalk10.dim(" Tools: ") + (draft.tool_sequence.length === 0 ? chalk10.dim("(none)") : draft.tool_sequence.map((t) => chalk10.cyan(t)).join(chalk10.dim(" \u2192 ")))
1785
+ );
1786
+ return lines.join("\n");
1787
+ }
1788
+ function deriveDefaultCaseName(input) {
1789
+ const compact = input.replace(/\s+/g, " ").trim();
1790
+ return compact.length > 40 ? compact.slice(0, 40) : compact;
1791
+ }
1792
+ function parseTagInput(raw) {
1793
+ return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1794
+ }
1795
+ function parseToolSequenceInput(raw) {
1796
+ return raw.split(/[,\u2192]|->/).map((s) => s.trim()).filter((s) => s.length > 0);
1797
+ }
1798
+ function buildGoldenCase(session, draft, answers, now = /* @__PURE__ */ new Date()) {
1799
+ const expected_output = resolveExpectedOutput(draft, answers);
1800
+ return {
1801
+ id: "",
1802
+ name: answers.name,
1803
+ agent_id: session.agent_name,
1804
+ input: draft.input,
1805
+ ...expected_output !== void 0 ? { expected_output } : {},
1806
+ ...answers.tool_sequence.length > 0 ? { expected_tool_sequence: answers.tool_sequence } : {},
1807
+ scorers: answers.scorers,
1808
+ ...answers.tags.length > 0 ? { tags: answers.tags } : {},
1809
+ promoted_from_session: session.id,
1810
+ created_at: now
1811
+ };
1812
+ }
1813
+ function resolveExpectedOutput(draft, answers) {
1814
+ if (answers.expected_mode === "actual") return draft.output;
1815
+ if (answers.expected_mode === "custom") return answers.expected_output_custom ?? "";
1816
+ return void 0;
1817
+ }
1818
+ function renderRecordedConfirmation(stored) {
1819
+ return chalk10.green("\u2713") + " Golden case saved: " + chalk10.bold(stored.name) + chalk10.dim(" (" + stored.id + "). Run `tutti-ai eval run` to test against it.");
1820
+ }
1821
+
1822
+ // src/commands/eval-record.ts
1823
+ var logger10 = createLogger10("tutti-cli");
1824
+ var { prompt: prompt3 } = Enquirer3;
1825
+ var LOCAL_SESSION_DIR = ".tutti/sessions";
1826
+ async function evalRecordCommand(sessionId) {
1827
+ const { session, close } = await resolveSession(sessionId);
1828
+ try {
1829
+ const draft = extractSessionDraft(session);
1830
+ console.log(renderSessionSummary(session, draft, void 0));
1831
+ console.log("");
1832
+ const answers = await collectAnswers(draft);
1833
+ const goldenCase = buildGoldenCase(session, draft, answers);
1834
+ const store = new JsonFileGoldenStore();
1835
+ const stored = await store.saveCase(goldenCase);
1836
+ console.log(renderRecordedConfirmation(stored));
1837
+ } finally {
1838
+ await close();
1839
+ }
1840
+ }
1841
+ async function resolveSession(sessionId) {
1842
+ const pgUrl = SecretsManager5.optional("TUTTI_PG_URL");
1843
+ if (pgUrl) {
1844
+ return loadFromPostgres(sessionId, pgUrl);
1845
+ }
1846
+ return loadFromLocalLog(sessionId);
1847
+ }
1848
+ async function loadFromPostgres(sessionId, pgUrl) {
1849
+ const store = new PostgresSessionStore(pgUrl);
1850
+ let session;
1851
+ try {
1852
+ session = await store.getAsync(sessionId);
1853
+ } catch (err) {
1854
+ await store.close();
1855
+ logger10.error(
1856
+ { error: err instanceof Error ? err.message : String(err) },
1857
+ "Session store error"
1858
+ );
1859
+ process.exit(1);
1860
+ }
1861
+ if (!session) {
1862
+ await store.close();
1863
+ exitSessionNotFound(sessionId, "postgres");
1864
+ }
1865
+ return { session, close: () => store.close() };
1866
+ }
1867
+ async function loadFromLocalLog(sessionId) {
1868
+ const path = resolve10(join2(LOCAL_SESSION_DIR, sessionId + ".json"));
1869
+ let raw;
1870
+ try {
1871
+ raw = await readFile(path, "utf8");
1872
+ } catch {
1873
+ exitSessionNotFound(sessionId, "local");
1874
+ }
1875
+ const parsed = JSON.parse(raw);
1876
+ const session = reviveLocalSession(parsed);
1877
+ return { session, close: () => Promise.resolve() };
1878
+ }
1879
+ function reviveLocalSession(raw) {
1880
+ const obj = raw;
1881
+ return {
1882
+ ...obj,
1883
+ created_at: new Date(obj.created_at),
1884
+ updated_at: new Date(obj.updated_at)
1885
+ };
1886
+ }
1887
+ function exitSessionNotFound(sessionId, source) {
1888
+ console.error(chalk11.red("Session not found: " + sessionId));
1889
+ if (source === "postgres") {
1890
+ console.error(
1891
+ chalk11.dim(
1892
+ " Checked the Postgres session store at TUTTI_PG_URL. Verify the id and that the env var points at the right database."
1893
+ )
1894
+ );
1895
+ } else {
1896
+ console.error(
1897
+ chalk11.dim(
1898
+ " TUTTI_PG_URL is unset and no local log exists at " + LOCAL_SESSION_DIR + "/" + sessionId + ".json.\n Set TUTTI_PG_URL to pull the session from Postgres."
1899
+ )
1900
+ );
1901
+ }
1902
+ process.exit(1);
1903
+ }
1904
+ async function collectAnswers(draft) {
1905
+ const { name } = await prompt3({
1906
+ type: "input",
1907
+ name: "name",
1908
+ message: "Case name?",
1909
+ initial: deriveDefaultCaseName(draft.input) || "unnamed-case"
1910
+ });
1911
+ const expected_mode = await promptExpectedMode();
1912
+ const expected_output_custom = expected_mode === "custom" ? await promptCustomExpected() : void 0;
1913
+ const tool_sequence = await promptToolSequence(draft.tool_sequence);
1914
+ const scorers = await promptScorers(expected_mode, tool_sequence.length > 0);
1915
+ const { tagsRaw } = await prompt3({
1916
+ type: "input",
1917
+ name: "tagsRaw",
1918
+ message: "Tags (comma-separated, optional)?",
1919
+ initial: ""
1920
+ });
1921
+ return {
1922
+ name: name.trim() === "" ? deriveDefaultCaseName(draft.input) : name.trim(),
1923
+ expected_mode,
1924
+ ...expected_output_custom !== void 0 ? { expected_output_custom } : {},
1925
+ tool_sequence,
1926
+ scorers,
1927
+ tags: parseTagInput(tagsRaw)
1928
+ };
1929
+ }
1930
+ async function promptExpectedMode() {
1931
+ const { choice } = await prompt3({
1932
+ type: "select",
1933
+ name: "choice",
1934
+ message: "Expected output?",
1935
+ choices: [
1936
+ { name: "actual", message: "Use the actual output from this run (exact match)" },
1937
+ { name: "custom", message: "Enter a custom expected output" },
1938
+ { name: "skip", message: "Skip \u2014 rely on tool-sequence / custom scorers" }
1939
+ ]
1940
+ });
1941
+ if (choice === "actual" || choice === "custom" || choice === "skip") return choice;
1942
+ return "skip";
1943
+ }
1944
+ async function promptCustomExpected() {
1945
+ const { expected } = await prompt3({
1946
+ type: "input",
1947
+ name: "expected",
1948
+ message: "Custom expected output:"
1949
+ });
1950
+ return expected;
1951
+ }
1952
+ async function promptToolSequence(actual) {
1953
+ const initial = actual.join(", ");
1954
+ const { edited } = await prompt3({
1955
+ type: "input",
1956
+ name: "edited",
1957
+ message: "Expected tool sequence (comma-separated, empty to skip)?",
1958
+ initial
1959
+ });
1960
+ return parseToolSequenceInput(edited);
1961
+ }
1962
+ async function promptScorers(expectedMode, hasToolSequence) {
1963
+ const defaults = [];
1964
+ if (expectedMode !== "skip") defaults.push("exact");
1965
+ if (hasToolSequence) defaults.push("tool-sequence");
1966
+ const multiselectOpts = {
1967
+ type: "multiselect",
1968
+ name: "picked",
1969
+ message: "Scorers to attach?",
1970
+ choices: [
1971
+ { name: "exact", message: "Exact output match" },
1972
+ { name: "similarity", message: "Cosine similarity" },
1973
+ { name: "tool-sequence", message: "Tool sequence match" },
1974
+ { name: "custom", message: "Custom scorer module" }
1975
+ ],
1976
+ initial: defaults
1977
+ };
1978
+ const { picked } = await prompt3(multiselectOpts);
1979
+ const refs = [];
1980
+ for (const kind of picked) {
1981
+ if (kind === "custom") {
1982
+ const { path } = await prompt3({
1983
+ type: "input",
1984
+ name: "path",
1985
+ message: "Custom scorer module path (relative to CWD)?"
1986
+ });
1987
+ refs.push({ type: "custom", path: path.trim() });
1988
+ } else if (kind === "exact" || kind === "similarity" || kind === "tool-sequence") {
1989
+ refs.push({ type: kind });
1990
+ }
1991
+ }
1992
+ return refs;
1993
+ }
1994
+
1995
+ // src/commands/eval-list.ts
1996
+ import { JsonFileGoldenStore as JsonFileGoldenStore2 } from "@tuttiai/core";
1997
+
1998
+ // src/commands/eval-list-render.ts
1999
+ import chalk12 from "chalk";
2000
+ function visibleLen(s) {
2001
+ return s.replace(/\u001b\[[0-9;]*m/g, "").length;
2002
+ }
2003
+ function pad(s, len) {
2004
+ const v = visibleLen(s);
2005
+ return v >= len ? s : s + " ".repeat(len - v);
2006
+ }
2007
+ function truncate(text, max) {
2008
+ const oneLine = text.replace(/\s+/g, " ").trim();
2009
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
2010
+ }
2011
+ function formatIsoShort(d) {
2012
+ const iso = d.toISOString();
2013
+ return iso.slice(0, 10) + " " + iso.slice(11, 16);
2014
+ }
2015
+ function scorersCell(scorers) {
2016
+ if (scorers.length === 0) return chalk12.dim("(none)");
2017
+ return scorers.map((s) => s.type).join(",");
2018
+ }
2019
+ function renderStatus(status) {
2020
+ if (status === "pass") return chalk12.green("pass");
2021
+ if (status === "fail") return chalk12.red("FAIL");
2022
+ return chalk12.dim("never");
2023
+ }
2024
+ function deriveLastRunStatus(latest) {
2025
+ if (!latest) return "never";
2026
+ return latest.passed ? "pass" : "fail";
2027
+ }
2028
+ function renderGoldenCasesTable(cases, latestByCaseId) {
2029
+ if (cases.length === 0) {
2030
+ return chalk12.dim(
2031
+ "No golden cases recorded. Run `tutti-ai eval record <session-id>` to pin one."
2032
+ );
2033
+ }
2034
+ const rows = cases.map((c) => ({
2035
+ id: c.id.slice(0, 8),
2036
+ name: truncate(c.name, 36),
2037
+ agent: truncate(c.agent_id, 16),
2038
+ scorers: truncate(scorersCell(c.scorers), 32),
2039
+ status: deriveLastRunStatus(latestByCaseId.get(c.id)),
2040
+ created: formatIsoShort(c.created_at)
2041
+ }));
2042
+ const widths = {
2043
+ id: 8,
2044
+ name: Math.max(4, ...rows.map((r) => visibleLen(r.name))),
2045
+ agent: Math.max(5, ...rows.map((r) => visibleLen(r.agent))),
2046
+ scorers: Math.max(7, ...rows.map((r) => visibleLen(r.scorers))),
2047
+ status: 6,
2048
+ created: 16
2049
+ };
2050
+ const header = chalk12.dim.bold(
2051
+ pad("ID", widths.id) + " " + pad("NAME", widths.name) + " " + pad("AGENT", widths.agent) + " " + pad("SCORERS", widths.scorers) + " " + pad("STATUS", widths.status) + " " + pad("CREATED", widths.created)
2052
+ );
2053
+ const body = rows.map(
2054
+ (r) => pad(chalk12.dim(r.id), widths.id) + " " + pad(r.name, widths.name) + " " + pad(chalk12.cyan(r.agent), widths.agent) + " " + pad(r.scorers, widths.scorers) + " " + pad(renderStatus(r.status), widths.status) + " " + pad(chalk12.dim(r.created), widths.created)
2055
+ ).join("\n");
2056
+ return header + "\n" + body;
2057
+ }
2058
+
2059
+ // src/commands/eval-list.ts
2060
+ async function evalListCommand() {
2061
+ const store = new JsonFileGoldenStore2();
2062
+ const cases = await store.listCases();
2063
+ const latest = await Promise.all(
2064
+ cases.map(async (c) => [c.id, await store.latestRun(c.id)])
2065
+ );
2066
+ const latestByCaseId = new Map(latest);
2067
+ console.log(renderGoldenCasesTable(cases, latestByCaseId));
2068
+ }
2069
+
2070
+ // src/commands/eval-run.ts
2071
+ import { mkdir, writeFile } from "fs/promises";
2072
+ import { dirname as dirname2, resolve as resolve11 } from "path";
2073
+ import chalk14 from "chalk";
2074
+ import ora7 from "ora";
2075
+ import {
2076
+ GoldenRunner,
2077
+ JsonFileGoldenStore as JsonFileGoldenStore3,
2078
+ ScoreLoader as ScoreLoader6,
2079
+ createLogger as createLogger11
2080
+ } from "@tuttiai/core";
2081
+
2082
+ // src/commands/eval-run-render.ts
2083
+ import chalk13 from "chalk";
2084
+ var DIFF_PREVIEW_LINES = 20;
2085
+ function filterCases(cases, filters) {
2086
+ return cases.filter((c) => {
2087
+ if (filters.case !== void 0 && !c.id.startsWith(filters.case)) return false;
2088
+ if (filters.tag !== void 0) {
2089
+ const tags = c.tags ?? [];
2090
+ if (!tags.includes(filters.tag)) return false;
2091
+ }
2092
+ return true;
2093
+ });
2094
+ }
2095
+ function scoreSummary(scores) {
2096
+ const parts = Object.values(scores).map(
2097
+ (s) => s.scorer + ":" + s.score.toFixed(2)
2098
+ );
2099
+ return parts.length > 0 ? parts.join(", ") : "(no scorers)";
2100
+ }
2101
+ function firstFailure(scores) {
2102
+ return Object.values(scores).find((s) => !s.passed);
2103
+ }
2104
+ function truncateDiffPreview(diff, maxLines = DIFF_PREVIEW_LINES) {
2105
+ const lines = diff.split("\n");
2106
+ if (lines.length <= maxLines) return diff;
2107
+ const kept = lines.slice(0, maxLines);
2108
+ const dropped = lines.length - maxLines;
2109
+ return kept.join("\n") + "\n\u2026 (" + dropped + " more lines)";
2110
+ }
2111
+ function renderCaseLine(goldenCase, run2) {
2112
+ if (run2.passed) {
2113
+ return chalk13.green("\u2713 ") + chalk13.bold(goldenCase.name) + chalk13.dim(" \u2014 " + scoreSummary(run2.scores));
2114
+ }
2115
+ const failure = firstFailure(run2.scores);
2116
+ const why = failure ? failure.scorer + (failure.detail ? ": " + failure.detail : " failed") : "run failed";
2117
+ const head = chalk13.red("\u2717 ") + chalk13.bold(goldenCase.name) + chalk13.dim(" \u2014 " + why);
2118
+ if (run2.diff) {
2119
+ return head + "\n" + chalk13.dim(truncateDiffPreview(run2.diff));
2120
+ }
2121
+ return head;
2122
+ }
2123
+ function renderCaseLineCI(goldenCase, run2) {
2124
+ const status = run2.passed ? "PASS" : "FAIL";
2125
+ const id8 = goldenCase.id.slice(0, 8);
2126
+ const scoreStr = scoreSummary(run2.scores);
2127
+ const tokens = "tokens=" + run2.tokens;
2128
+ const cost = run2.cost_usd !== void 0 ? " cost=$" + run2.cost_usd.toFixed(4) : "";
2129
+ const why = !run2.passed && firstFailure(run2.scores) ? " why=" + (firstFailure(run2.scores)?.scorer ?? "") : "";
2130
+ return status + " " + id8 + " " + goldenCase.name + " [" + scoreStr + "] " + tokens + cost + why;
2131
+ }
2132
+ function computeStats(runs) {
2133
+ const passed = runs.filter((r) => r.passed).length;
2134
+ const totalTokens = runs.reduce((sum, r) => sum + r.tokens, 0);
2135
+ const totalCostUsd = runs.reduce((sum, r) => sum + (r.cost_usd ?? 0), 0);
2136
+ return {
2137
+ passed,
2138
+ failed: runs.length - passed,
2139
+ total: runs.length,
2140
+ totalTokens,
2141
+ totalCostUsd
2142
+ };
2143
+ }
2144
+ function renderSummary(stats, colors) {
2145
+ const avgTokens = stats.total > 0 ? Math.round(stats.totalTokens / stats.total) : 0;
2146
+ const passStr = stats.passed + " passed";
2147
+ const failStr = stats.failed + " failed";
2148
+ const passOut = colors ? chalk13.green(passStr) : passStr;
2149
+ const failOut = colors && stats.failed > 0 ? chalk13.red(failStr) : failStr;
2150
+ return passOut + ", " + failOut + " out of " + stats.total + " cases | avg tokens: " + avgTokens + " | total cost: $" + stats.totalCostUsd.toFixed(2);
2151
+ }
2152
+
2153
+ // src/commands/eval-run-junit.ts
2154
+ function escapeXmlText(s) {
2155
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2156
+ }
2157
+ function escapeXmlAttr(s) {
2158
+ return escapeXmlText(s).replace(/"/g, "&quot;").replace(/\n/g, "&#10;");
2159
+ }
2160
+ function firstFailure2(scores) {
2161
+ return Object.values(scores).find((s) => !s.passed);
2162
+ }
2163
+ function toJunitXml(rows, suiteName = "tutti-eval") {
2164
+ const totalTimeSec = rows.reduce((s, r) => s + r.durationMs / 1e3, 0);
2165
+ const failures = rows.filter((r) => !r.run.passed).length;
2166
+ const attrs = 'name="' + escapeXmlAttr(suiteName) + '" tests="' + rows.length + '" failures="' + failures + '" errors="0" time="' + totalTimeSec.toFixed(3) + '"';
2167
+ const body = rows.map(renderTestCase).join("");
2168
+ return '<?xml version="1.0" encoding="UTF-8"?>\n<testsuites ' + attrs + ">\n <testsuite " + attrs + ">\n" + body + " </testsuite>\n</testsuites>\n";
2169
+ }
2170
+ function renderTestCase({ goldenCase, run: run2, durationMs }) {
2171
+ const timeSec = (durationMs / 1e3).toFixed(3);
2172
+ const caseAttrs = ' classname="' + escapeXmlAttr(goldenCase.agent_id) + '" name="' + escapeXmlAttr(goldenCase.name) + '" time="' + timeSec + '"';
2173
+ if (run2.passed) {
2174
+ return " <testcase" + caseAttrs + "/>\n";
2175
+ }
2176
+ const failure = firstFailure2(run2.scores);
2177
+ const message = failure ? failure.scorer + (failure.detail ? ": " + failure.detail : " failed") : "case failed";
2178
+ const parts = [];
2179
+ if (failure?.detail) parts.push(failure.detail);
2180
+ if (run2.diff) parts.push(run2.diff);
2181
+ parts.push("---- output ----\n" + run2.output);
2182
+ const bodyRaw = parts.join("\n\n").replace(/]]>/g, "]]]]><![CDATA[>");
2183
+ return " <testcase" + caseAttrs + '>\n <failure message="' + escapeXmlAttr(message) + '" type="ScorerFailed"><![CDATA[' + bodyRaw + "]]></failure>\n </testcase>\n";
2184
+ }
2185
+
2186
+ // src/commands/eval-run.ts
2187
+ var logger11 = createLogger11("tutti-cli");
2188
+ var DEFAULT_JUNIT_PATH = ".tutti/eval-results.xml";
2189
+ async function runEvalRun(opts, deps = {}) {
2190
+ const store = deps.store ?? new JsonFileGoldenStore3();
2191
+ const all = await store.listCases();
2192
+ const cases = filterCases(all, {
2193
+ ...opts.case !== void 0 ? { case: opts.case } : {},
2194
+ ...opts.tag !== void 0 ? { tag: opts.tag } : {}
2195
+ });
2196
+ if (cases.length === 0) {
2197
+ console.log(
2198
+ chalk14.dim(
2199
+ "No golden cases match the filter. Run `tutti-ai eval record <session-id>` first."
2200
+ )
2201
+ );
2202
+ return { passed: 0, failed: 0, total: 0, totalTokens: 0, totalCostUsd: 0 };
2203
+ }
2204
+ const score = deps.score ?? await loadScore(opts.score);
2205
+ const runner = new GoldenRunner({ score, store });
2206
+ const rows = [];
2207
+ for (const c of cases) {
2208
+ const start = Date.now();
2209
+ const run2 = await runner.runGoldenCase(c);
2210
+ const durationMs = Date.now() - start;
2211
+ rows.push({ goldenCase: c, run: run2, durationMs });
2212
+ printCaseResult(c, run2, opts.ci === true);
2213
+ }
2214
+ const runs = rows.map((r) => r.run);
2215
+ const stats = computeStats(runs);
2216
+ console.log(renderSummary(stats, !opts.ci));
2217
+ let xmlPath;
2218
+ if (opts.ci) {
2219
+ xmlPath = resolve11(deps.junitPath ?? DEFAULT_JUNIT_PATH);
2220
+ await writeJunitFile(xmlPath, rows);
2221
+ }
2222
+ return {
2223
+ passed: stats.passed,
2224
+ failed: stats.failed,
2225
+ total: stats.total,
2226
+ totalTokens: stats.totalTokens,
2227
+ totalCostUsd: stats.totalCostUsd,
2228
+ ...xmlPath !== void 0 ? { xmlPath } : {}
2229
+ };
2230
+ }
2231
+ async function evalRunCommand(opts) {
2232
+ const result = await runEvalRun(opts);
2233
+ if (opts.ci && result.failed > 0) {
2234
+ process.exit(1);
2235
+ }
2236
+ }
2237
+ async function loadScore(scorePath) {
2238
+ const path = resolve11(scorePath ?? "./tutti.score.ts");
2239
+ const spinner = ora7({ color: "cyan" }).start("Loading score...");
2240
+ try {
2241
+ const score = await ScoreLoader6.load(path);
2242
+ spinner.stop();
2243
+ return score;
2244
+ } catch (err) {
2245
+ spinner.fail("Failed to load score");
2246
+ logger11.error(
2247
+ { error: err instanceof Error ? err.message : String(err) },
2248
+ "Score load error"
2249
+ );
2250
+ process.exit(1);
2251
+ }
2252
+ }
2253
+ function printCaseResult(goldenCase, run2, ci) {
2254
+ if (ci) {
2255
+ console.log(renderCaseLineCI(goldenCase, run2));
2256
+ } else {
2257
+ console.log(renderCaseLine(goldenCase, run2));
2258
+ }
2259
+ }
2260
+ async function writeJunitFile(xmlPath, rows) {
2261
+ const xml = toJunitXml(rows);
2262
+ await mkdir(dirname2(xmlPath), { recursive: true });
2263
+ await writeFile(xmlPath, xml, "utf8");
2264
+ }
2265
+
1732
2266
  // src/commands/serve.ts
1733
2267
  import { existsSync as existsSync10 } from "fs";
1734
- import { resolve as resolve10 } from "path";
1735
- import chalk10 from "chalk";
2268
+ import { resolve as resolve12 } from "path";
2269
+ import chalk15 from "chalk";
1736
2270
  import {
1737
2271
  TuttiRuntime as TuttiRuntime4,
1738
- ScoreLoader as ScoreLoader6,
2272
+ ScoreLoader as ScoreLoader7,
1739
2273
  AnthropicProvider as AnthropicProvider4,
1740
2274
  OpenAIProvider as OpenAIProvider4,
1741
2275
  GeminiProvider as GeminiProvider4,
1742
- SecretsManager as SecretsManager5,
2276
+ SecretsManager as SecretsManager6,
1743
2277
  InMemorySessionStore as InMemorySessionStore2,
1744
- createLogger as createLogger10
2278
+ createLogger as createLogger12
1745
2279
  } from "@tuttiai/core";
1746
2280
  import { createServer, DEFAULT_PORT, SERVER_VERSION } from "@tuttiai/server";
1747
- var logger10 = createLogger10("tutti-serve");
2281
+ var logger12 = createLogger12("tutti-serve");
1748
2282
  async function serveCommand(scorePath, options = {}) {
1749
- const file = resolve10(scorePath ?? "./tutti.score.ts");
2283
+ const file = resolve12(scorePath ?? "./tutti.score.ts");
1750
2284
  if (!existsSync10(file)) {
1751
- logger10.error({ file }, "Score file not found");
1752
- console.error(chalk10.dim('Run "tutti-ai init" to create a new project.'));
2285
+ logger12.error({ file }, "Score file not found");
2286
+ console.error(chalk15.dim('Run "tutti-ai init" to create a new project.'));
1753
2287
  process.exit(1);
1754
2288
  }
1755
2289
  let score;
1756
2290
  try {
1757
- score = await ScoreLoader6.load(file);
2291
+ score = await ScoreLoader7.load(file);
1758
2292
  } catch (err) {
1759
- logger10.error(
2293
+ logger12.error(
1760
2294
  { error: err instanceof Error ? err.message : String(err) },
1761
2295
  "Failed to load score"
1762
2296
  );
@@ -1769,9 +2303,9 @@ async function serveCommand(scorePath, options = {}) {
1769
2303
  ];
1770
2304
  for (const [ProviderClass, envVar] of providerKeyMap) {
1771
2305
  if (score.provider instanceof ProviderClass) {
1772
- const key = SecretsManager5.optional(envVar);
2306
+ const key = SecretsManager6.optional(envVar);
1773
2307
  if (!key) {
1774
- logger10.error({ envVar }, "Missing API key");
2308
+ logger12.error({ envVar }, "Missing API key");
1775
2309
  process.exit(1);
1776
2310
  }
1777
2311
  }
@@ -1779,7 +2313,7 @@ async function serveCommand(scorePath, options = {}) {
1779
2313
  const agentNames = Object.keys(score.agents);
1780
2314
  const agentName = options.agent ?? (typeof score.entry === "string" ? score.entry : void 0) ?? agentNames[0];
1781
2315
  if (!agentName || !Object.hasOwn(score.agents, agentName)) {
1782
- logger10.error(
2316
+ logger12.error(
1783
2317
  { requested: agentName, available: agentNames },
1784
2318
  "Agent not found in score"
1785
2319
  );
@@ -1794,7 +2328,7 @@ async function serveCommand(scorePath, options = {}) {
1794
2328
  if (options.watch) {
1795
2329
  reactive = new ReactiveScore(score, file);
1796
2330
  reactive.on("file-change", () => {
1797
- console.log(chalk10.cyan("\n[tutti] Score changed, reloading..."));
2331
+ console.log(chalk15.cyan("\n[tutti] Score changed, reloading..."));
1798
2332
  });
1799
2333
  reactive.on("reloaded", () => {
1800
2334
  void (async () => {
@@ -1807,9 +2341,9 @@ async function serveCommand(scorePath, options = {}) {
1807
2341
  runtime = nextRuntime;
1808
2342
  app = nextApp;
1809
2343
  await app.listen({ port, host });
1810
- console.log(chalk10.green("[tutti] Score reloaded. Server restarted."));
2344
+ console.log(chalk15.green("[tutti] Score reloaded. Server restarted."));
1811
2345
  } catch (err) {
1812
- logger10.error(
2346
+ logger12.error(
1813
2347
  { error: err instanceof Error ? err.message : String(err) },
1814
2348
  "[tutti] Reload failed \u2014 server continues with previous config"
1815
2349
  );
@@ -1817,7 +2351,7 @@ async function serveCommand(scorePath, options = {}) {
1817
2351
  })();
1818
2352
  });
1819
2353
  reactive.on("reload-failed", (err) => {
1820
- logger10.error(
2354
+ logger12.error(
1821
2355
  { error: err instanceof Error ? err.message : String(err) },
1822
2356
  "[tutti] Reload failed \u2014 server continues with previous config"
1823
2357
  );
@@ -1826,7 +2360,7 @@ async function serveCommand(scorePath, options = {}) {
1826
2360
  await app.listen({ port, host });
1827
2361
  printBanner(port, host, agentName, score, file, options.watch);
1828
2362
  const shutdown = async (signal) => {
1829
- console.log(chalk10.dim("\n" + signal + " received \u2014 shutting down..."));
2363
+ console.log(chalk15.dim("\n" + signal + " received \u2014 shutting down..."));
1830
2364
  if (reactive) await reactive.close();
1831
2365
  await app.close();
1832
2366
  process.exit(0);
@@ -1838,7 +2372,7 @@ function parsePort(raw) {
1838
2372
  if (raw === void 0) return DEFAULT_PORT;
1839
2373
  const n = Number.parseInt(raw, 10);
1840
2374
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
1841
- logger10.error({ port: raw }, "Invalid port number");
2375
+ logger12.error({ port: raw }, "Invalid port number");
1842
2376
  process.exit(1);
1843
2377
  }
1844
2378
  return n;
@@ -1862,56 +2396,56 @@ function printBanner(port, host, agentName, score, file, watch) {
1862
2396
  const display = host === "0.0.0.0" || host === "::" ? "localhost" : host;
1863
2397
  const url = "http://" + display + ":" + port;
1864
2398
  console.log();
1865
- console.log(chalk10.bold(" Tutti Server v" + SERVER_VERSION));
1866
- console.log(chalk10.dim(" " + url));
2399
+ console.log(chalk15.bold(" Tutti Server v" + SERVER_VERSION));
2400
+ console.log(chalk15.dim(" " + url));
1867
2401
  console.log();
1868
- console.log(chalk10.dim(" Score: ") + (score.name ?? file));
1869
- console.log(chalk10.dim(" Agent: ") + agentName);
1870
- console.log(chalk10.dim(" Agents: ") + Object.keys(score.agents).join(", "));
2402
+ console.log(chalk15.dim(" Score: ") + (score.name ?? file));
2403
+ console.log(chalk15.dim(" Agent: ") + agentName);
2404
+ console.log(chalk15.dim(" Agents: ") + Object.keys(score.agents).join(", "));
1871
2405
  if (watch) {
1872
- console.log(chalk10.dim(" Watch: ") + chalk10.cyan("enabled"));
2406
+ console.log(chalk15.dim(" Watch: ") + chalk15.cyan("enabled"));
1873
2407
  }
1874
2408
  console.log();
1875
- console.log(chalk10.dim(" Endpoints:"));
1876
- console.log(chalk10.dim(" POST ") + url + "/run");
1877
- console.log(chalk10.dim(" POST ") + url + "/run/stream");
1878
- console.log(chalk10.dim(" GET ") + url + "/sessions/:id");
1879
- console.log(chalk10.dim(" GET ") + url + "/health");
2409
+ console.log(chalk15.dim(" Endpoints:"));
2410
+ console.log(chalk15.dim(" POST ") + url + "/run");
2411
+ console.log(chalk15.dim(" POST ") + url + "/run/stream");
2412
+ console.log(chalk15.dim(" GET ") + url + "/sessions/:id");
2413
+ console.log(chalk15.dim(" GET ") + url + "/health");
1880
2414
  console.log();
1881
2415
  }
1882
2416
 
1883
2417
  // src/commands/schedule.ts
1884
2418
  import { existsSync as existsSync11 } from "fs";
1885
- import { resolve as resolve11 } from "path";
1886
- import chalk11 from "chalk";
2419
+ import { resolve as resolve13 } from "path";
2420
+ import chalk16 from "chalk";
1887
2421
  import {
1888
- ScoreLoader as ScoreLoader7,
2422
+ ScoreLoader as ScoreLoader8,
1889
2423
  SchedulerEngine,
1890
2424
  PostgresScheduleStore,
1891
2425
  MemoryScheduleStore,
1892
2426
  AgentRunner,
1893
2427
  EventBus,
1894
2428
  InMemorySessionStore as InMemorySessionStore3,
1895
- SecretsManager as SecretsManager6,
1896
- createLogger as createLogger11
2429
+ SecretsManager as SecretsManager7,
2430
+ createLogger as createLogger13
1897
2431
  } from "@tuttiai/core";
1898
- var logger11 = createLogger11("tutti-cli");
2432
+ var logger13 = createLogger13("tutti-cli");
1899
2433
  function resolveStore() {
1900
- const pgUrl = SecretsManager6.optional("TUTTI_PG_URL");
2434
+ const pgUrl = SecretsManager7.optional("TUTTI_PG_URL");
1901
2435
  if (pgUrl) {
1902
2436
  return new PostgresScheduleStore({ connection_string: pgUrl });
1903
2437
  }
1904
- logger11.warn("TUTTI_PG_URL not set \u2014 using in-memory store (not durable across restarts)");
2438
+ logger13.warn("TUTTI_PG_URL not set \u2014 using in-memory store (not durable across restarts)");
1905
2439
  return new MemoryScheduleStore();
1906
2440
  }
1907
2441
  async function scheduleCommand(scorePath) {
1908
- const file = resolve11(scorePath ?? "./tutti.score.ts");
2442
+ const file = resolve13(scorePath ?? "./tutti.score.ts");
1909
2443
  if (!existsSync11(file)) {
1910
- console.error(chalk11.red("Score file not found: " + file));
1911
- console.error(chalk11.dim('Run "tutti-ai init" to create a new project.'));
2444
+ console.error(chalk16.red("Score file not found: " + file));
2445
+ console.error(chalk16.dim('Run "tutti-ai init" to create a new project.'));
1912
2446
  process.exit(1);
1913
2447
  }
1914
- const score = await ScoreLoader7.load(file);
2448
+ const score = await ScoreLoader8.load(file);
1915
2449
  const events = new EventBus();
1916
2450
  const sessions = new InMemorySessionStore3();
1917
2451
  const runner = new AgentRunner(
@@ -1929,41 +2463,41 @@ async function scheduleCommand(scorePath) {
1929
2463
  registered++;
1930
2464
  }
1931
2465
  if (registered === 0) {
1932
- console.log(chalk11.yellow("No agents have a schedule config. Nothing to run."));
1933
- console.log(chalk11.dim("Add schedule: { cron: '...', input: '...' } to an agent in your score."));
2466
+ console.log(chalk16.yellow("No agents have a schedule config. Nothing to run."));
2467
+ console.log(chalk16.dim("Add schedule: { cron: '...', input: '...' } to an agent in your score."));
1934
2468
  process.exit(0);
1935
2469
  }
1936
2470
  events.onAny((e) => {
1937
2471
  if (e.type === "schedule:triggered") {
1938
2472
  const ev = e;
1939
2473
  console.log(
1940
- chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.cyan("triggered") + " " + chalk11.bold(ev.schedule_id) + " \u2192 " + ev.agent_name
2474
+ chalk16.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk16.cyan("triggered") + " " + chalk16.bold(ev.schedule_id) + " \u2192 " + ev.agent_name
1941
2475
  );
1942
2476
  }
1943
2477
  if (e.type === "schedule:completed") {
1944
2478
  const ev = e;
1945
2479
  console.log(
1946
- chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.green("completed") + " " + chalk11.bold(ev.schedule_id) + " " + chalk11.dim("(" + ev.duration_ms + "ms)")
2480
+ chalk16.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk16.green("completed") + " " + chalk16.bold(ev.schedule_id) + " " + chalk16.dim("(" + ev.duration_ms + "ms)")
1947
2481
  );
1948
2482
  }
1949
2483
  if (e.type === "schedule:error") {
1950
2484
  const ev = e;
1951
2485
  console.log(
1952
- chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.red("error") + " " + chalk11.bold(ev.schedule_id) + " \u2014 " + ev.error.message
2486
+ chalk16.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk16.red("error") + " " + chalk16.bold(ev.schedule_id) + " \u2014 " + ev.error.message
1953
2487
  );
1954
2488
  }
1955
2489
  });
1956
2490
  engine.start();
1957
2491
  console.log("");
1958
- console.log(chalk11.cyan.bold(" Tutti Scheduler"));
1959
- console.log(chalk11.dim(" Score: " + (score.name ?? file)));
1960
- console.log(chalk11.dim(" Schedules: " + registered));
1961
- console.log(chalk11.dim(" Store: " + (SecretsManager6.optional("TUTTI_PG_URL") ? "postgres" : "memory")));
2492
+ console.log(chalk16.cyan.bold(" Tutti Scheduler"));
2493
+ console.log(chalk16.dim(" Score: " + (score.name ?? file)));
2494
+ console.log(chalk16.dim(" Schedules: " + registered));
2495
+ console.log(chalk16.dim(" Store: " + (SecretsManager7.optional("TUTTI_PG_URL") ? "postgres" : "memory")));
1962
2496
  console.log("");
1963
- console.log(chalk11.dim(" Press Ctrl+C to stop."));
2497
+ console.log(chalk16.dim(" Press Ctrl+C to stop."));
1964
2498
  console.log("");
1965
2499
  const shutdown = () => {
1966
- console.log(chalk11.dim("\n Shutting down scheduler..."));
2500
+ console.log(chalk16.dim("\n Shutting down scheduler..."));
1967
2501
  engine.stop();
1968
2502
  if ("close" in store && typeof store.close === "function") {
1969
2503
  void store.close();
@@ -1978,9 +2512,9 @@ async function scheduleCommand(scorePath) {
1978
2512
  // src/commands/update.ts
1979
2513
  import { execSync as execSync3 } from "child_process";
1980
2514
  import { existsSync as existsSync12, readFileSync as readFileSync5 } from "fs";
1981
- import { resolve as resolve12 } from "path";
1982
- import chalk12 from "chalk";
1983
- import ora7 from "ora";
2515
+ import { resolve as resolve14 } from "path";
2516
+ import chalk17 from "chalk";
2517
+ import ora8 from "ora";
1984
2518
  var TUTTI_PACKAGES = [
1985
2519
  "@tuttiai/core",
1986
2520
  "@tuttiai/cli",
@@ -2023,8 +2557,8 @@ function getLatestVersion(pkg) {
2023
2557
  }
2024
2558
  function detectPackageManager() {
2025
2559
  const cwd = process.cwd();
2026
- if (existsSync12(resolve12(cwd, "yarn.lock"))) return "yarn";
2027
- if (existsSync12(resolve12(cwd, "pnpm-lock.yaml"))) return "pnpm";
2560
+ if (existsSync12(resolve14(cwd, "yarn.lock"))) return "yarn";
2561
+ if (existsSync12(resolve14(cwd, "pnpm-lock.yaml"))) return "pnpm";
2028
2562
  return "npm";
2029
2563
  }
2030
2564
  function isGlobalInstall() {
@@ -2040,35 +2574,35 @@ function isGlobalInstall() {
2040
2574
  }
2041
2575
  function updateCommand() {
2042
2576
  console.log();
2043
- console.log(chalk12.cyan.bold(" Tutti Update"));
2577
+ console.log(chalk17.cyan.bold(" Tutti Update"));
2044
2578
  console.log();
2045
- const spinner = ora7("Checking for updates...").start();
2579
+ const spinner = ora8("Checking for updates...").start();
2046
2580
  const cliCurrent = getInstalledVersion("@tuttiai/cli") ?? "unknown";
2047
2581
  const cliLatest = getLatestVersion("@tuttiai/cli");
2048
2582
  spinner.stop();
2049
2583
  if (cliLatest && cliCurrent !== cliLatest) {
2050
2584
  console.log(
2051
- chalk12.yellow(" CLI update available: ") + chalk12.dim(cliCurrent) + " \u2192 " + chalk12.green(cliLatest)
2585
+ chalk17.yellow(" CLI update available: ") + chalk17.dim(cliCurrent) + " \u2192 " + chalk17.green(cliLatest)
2052
2586
  );
2053
2587
  if (isGlobalInstall()) {
2054
- const updateSpinner2 = ora7("Updating global CLI...").start();
2588
+ const updateSpinner2 = ora8("Updating global CLI...").start();
2055
2589
  try {
2056
2590
  execSync3("npm install -g tutti-ai@latest", { stdio: "pipe" });
2057
2591
  updateSpinner2.succeed("CLI updated to " + cliLatest);
2058
2592
  } catch {
2059
2593
  updateSpinner2.fail("Failed to update global CLI");
2060
- console.log(chalk12.dim(" Run manually: npm install -g tutti-ai@latest"));
2594
+ console.log(chalk17.dim(" Run manually: npm install -g tutti-ai@latest"));
2061
2595
  }
2062
2596
  } else {
2063
- console.log(chalk12.dim(" Global: npm install -g tutti-ai@latest"));
2597
+ console.log(chalk17.dim(" Global: npm install -g tutti-ai@latest"));
2064
2598
  }
2065
2599
  } else {
2066
- console.log(chalk12.green(" CLI is up to date") + chalk12.dim(" (" + cliCurrent + ")"));
2600
+ console.log(chalk17.green(" CLI is up to date") + chalk17.dim(" (" + cliCurrent + ")"));
2067
2601
  }
2068
- const pkgPath = resolve12(process.cwd(), "package.json");
2602
+ const pkgPath = resolve14(process.cwd(), "package.json");
2069
2603
  if (!existsSync12(pkgPath)) {
2070
2604
  console.log();
2071
- console.log(chalk12.dim(" No package.json found \u2014 skipping project dependency check."));
2605
+ console.log(chalk17.dim(" No package.json found \u2014 skipping project dependency check."));
2072
2606
  console.log();
2073
2607
  return;
2074
2608
  }
@@ -2076,7 +2610,7 @@ function updateCommand() {
2076
2610
  try {
2077
2611
  pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
2078
2612
  } catch {
2079
- console.log(chalk12.dim(" Could not read package.json"));
2613
+ console.log(chalk17.dim(" Could not read package.json"));
2080
2614
  return;
2081
2615
  }
2082
2616
  const allDeps = /* @__PURE__ */ new Map();
@@ -2093,46 +2627,46 @@ function updateCommand() {
2093
2627
  const installed = TUTTI_PACKAGES.filter((p) => allDeps.has(p));
2094
2628
  if (installed.length === 0) {
2095
2629
  console.log();
2096
- console.log(chalk12.dim(" No @tuttiai packages found in this project."));
2630
+ console.log(chalk17.dim(" No @tuttiai packages found in this project."));
2097
2631
  console.log();
2098
2632
  return;
2099
2633
  }
2100
2634
  console.log();
2101
- console.log(" " + chalk12.bold("Project packages:"));
2635
+ console.log(" " + chalk17.bold("Project packages:"));
2102
2636
  const toUpdate = [];
2103
2637
  for (const name of installed) {
2104
2638
  const current = allDeps.get(name) ?? "?";
2105
2639
  const latest = getLatestVersion(name);
2106
2640
  if (!latest) {
2107
- console.log(" " + chalk12.dim(name) + " " + current + chalk12.dim(" (could not check)"));
2641
+ console.log(" " + chalk17.dim(name) + " " + current + chalk17.dim(" (could not check)"));
2108
2642
  continue;
2109
2643
  }
2110
2644
  const cleanCurrent = current.replace(/^[\^~]/, "");
2111
2645
  if (cleanCurrent === latest) {
2112
- console.log(" " + chalk12.green("\u2714") + " " + name + " " + chalk12.dim(latest));
2646
+ console.log(" " + chalk17.green("\u2714") + " " + name + " " + chalk17.dim(latest));
2113
2647
  } else {
2114
2648
  console.log(
2115
- " " + chalk12.yellow("\u2191") + " " + name + " " + chalk12.dim(cleanCurrent) + " \u2192 " + chalk12.green(latest)
2649
+ " " + chalk17.yellow("\u2191") + " " + name + " " + chalk17.dim(cleanCurrent) + " \u2192 " + chalk17.green(latest)
2116
2650
  );
2117
2651
  toUpdate.push(name + "@latest");
2118
2652
  }
2119
2653
  }
2120
2654
  if (toUpdate.length === 0) {
2121
2655
  console.log();
2122
- console.log(chalk12.green(" All packages are up to date."));
2656
+ console.log(chalk17.green(" All packages are up to date."));
2123
2657
  console.log();
2124
2658
  return;
2125
2659
  }
2126
2660
  console.log();
2127
2661
  const pm = detectPackageManager();
2128
2662
  const installCmd = pm === "yarn" ? "yarn add " + toUpdate.join(" ") : pm === "pnpm" ? "pnpm add " + toUpdate.join(" ") : "npm install " + toUpdate.join(" ");
2129
- const updateSpinner = ora7("Updating " + toUpdate.length + " package(s)...").start();
2663
+ const updateSpinner = ora8("Updating " + toUpdate.length + " package(s)...").start();
2130
2664
  try {
2131
2665
  execSync3(installCmd, { cwd: process.cwd(), stdio: "pipe" });
2132
2666
  updateSpinner.succeed("Updated " + toUpdate.length + " package(s)");
2133
2667
  } catch {
2134
2668
  updateSpinner.fail("Update failed");
2135
- console.log(chalk12.dim(" Run manually: " + installCmd));
2669
+ console.log(chalk17.dim(" Run manually: " + installCmd));
2136
2670
  }
2137
2671
  console.log();
2138
2672
  }
@@ -2140,9 +2674,9 @@ function updateCommand() {
2140
2674
  // src/commands/outdated.ts
2141
2675
  import { execSync as execSync4 } from "child_process";
2142
2676
  import { existsSync as existsSync13, readFileSync as readFileSync6 } from "fs";
2143
- import { resolve as resolve13 } from "path";
2144
- import chalk13 from "chalk";
2145
- import ora8 from "ora";
2677
+ import { resolve as resolve15 } from "path";
2678
+ import chalk18 from "chalk";
2679
+ import ora9 from "ora";
2146
2680
  function getLatestVersion2(pkg) {
2147
2681
  try {
2148
2682
  return execSync4(`npm view ${pkg} version 2>/dev/null`, {
@@ -2153,21 +2687,21 @@ function getLatestVersion2(pkg) {
2153
2687
  return null;
2154
2688
  }
2155
2689
  }
2156
- function pad(s, len) {
2690
+ function pad2(s, len) {
2157
2691
  return s.length >= len ? s : s + " ".repeat(len - s.length);
2158
2692
  }
2159
2693
  function outdatedCommand() {
2160
- const pkgPath = resolve13(process.cwd(), "package.json");
2694
+ const pkgPath = resolve15(process.cwd(), "package.json");
2161
2695
  if (!existsSync13(pkgPath)) {
2162
- console.error(chalk13.red("No package.json found in the current directory."));
2163
- console.error(chalk13.dim('Run "tutti-ai init" to create a new project.'));
2696
+ console.error(chalk18.red("No package.json found in the current directory."));
2697
+ console.error(chalk18.dim('Run "tutti-ai init" to create a new project.'));
2164
2698
  process.exit(1);
2165
2699
  }
2166
2700
  let pkg;
2167
2701
  try {
2168
2702
  pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2169
2703
  } catch {
2170
- console.error(chalk13.red("Could not parse package.json"));
2704
+ console.error(chalk18.red("Could not parse package.json"));
2171
2705
  process.exit(1);
2172
2706
  }
2173
2707
  const allDeps = /* @__PURE__ */ new Map();
@@ -2183,10 +2717,10 @@ function outdatedCommand() {
2183
2717
  }
2184
2718
  const tuttiDeps = [...allDeps.entries()].filter(([name]) => name.startsWith("@tuttiai/"));
2185
2719
  if (tuttiDeps.length === 0) {
2186
- console.log(chalk13.dim("No @tuttiai packages found in this project."));
2720
+ console.log(chalk18.dim("No @tuttiai packages found in this project."));
2187
2721
  return;
2188
2722
  }
2189
- const spinner = ora8("Checking npm registry...").start();
2723
+ const spinner = ora9("Checking npm registry...").start();
2190
2724
  const results = [];
2191
2725
  for (const [name, version] of tuttiDeps) {
2192
2726
  const latest = getLatestVersion2(name);
@@ -2201,41 +2735,41 @@ function outdatedCommand() {
2201
2735
  spinner.stop();
2202
2736
  console.log();
2203
2737
  console.log(
2204
- chalk13.dim(
2205
- " " + pad("PACKAGE", 28) + pad("CURRENT", 12) + pad("LATEST", 12) + "STATUS"
2738
+ chalk18.dim(
2739
+ " " + pad2("PACKAGE", 28) + pad2("CURRENT", 12) + pad2("LATEST", 12) + "STATUS"
2206
2740
  )
2207
2741
  );
2208
- console.log(chalk13.dim(" " + "\u2500".repeat(64)));
2742
+ console.log(chalk18.dim(" " + "\u2500".repeat(64)));
2209
2743
  let outdatedCount = 0;
2210
2744
  for (const r of results) {
2211
- const status = r.outdated ? chalk13.yellow("update available") : chalk13.green("up to date");
2745
+ const status = r.outdated ? chalk18.yellow("update available") : chalk18.green("up to date");
2212
2746
  if (r.outdated) outdatedCount++;
2213
2747
  console.log(
2214
- " " + pad(r.name, 28) + pad(r.current, 12) + pad(r.latest, 12) + status
2748
+ " " + pad2(r.name, 28) + pad2(r.current, 12) + pad2(r.latest, 12) + status
2215
2749
  );
2216
2750
  }
2217
2751
  console.log();
2218
2752
  if (outdatedCount > 0) {
2219
2753
  console.log(
2220
- chalk13.yellow(" " + outdatedCount + " package(s) can be updated.") + chalk13.dim(" Run: tutti-ai update")
2754
+ chalk18.yellow(" " + outdatedCount + " package(s) can be updated.") + chalk18.dim(" Run: tutti-ai update")
2221
2755
  );
2222
2756
  } else {
2223
- console.log(chalk13.green(" All packages are up to date."));
2757
+ console.log(chalk18.green(" All packages are up to date."));
2224
2758
  }
2225
2759
  console.log();
2226
2760
  }
2227
2761
 
2228
2762
  // src/commands/info.ts
2229
2763
  import { existsSync as existsSync14, readFileSync as readFileSync7 } from "fs";
2230
- import { resolve as resolve14 } from "path";
2231
- import chalk14 from "chalk";
2232
- import { ScoreLoader as ScoreLoader8, createLogger as createLogger12 } from "@tuttiai/core";
2233
- var logger12 = createLogger12("tutti-cli");
2234
- function pad2(s, len) {
2764
+ import { resolve as resolve16 } from "path";
2765
+ import chalk19 from "chalk";
2766
+ import { ScoreLoader as ScoreLoader9, createLogger as createLogger14 } from "@tuttiai/core";
2767
+ var logger14 = createLogger14("tutti-cli");
2768
+ function pad3(s, len) {
2235
2769
  return s.length >= len ? s : s + " ".repeat(len - s.length);
2236
2770
  }
2237
2771
  async function infoCommand(scorePath) {
2238
- const pkgPath = resolve14(process.cwd(), "package.json");
2772
+ const pkgPath = resolve16(process.cwd(), "package.json");
2239
2773
  let projectName = "(unknown)";
2240
2774
  let projectVersion = "(unknown)";
2241
2775
  const installedDeps = /* @__PURE__ */ new Map();
@@ -2258,41 +2792,41 @@ async function infoCommand(scorePath) {
2258
2792
  }
2259
2793
  }
2260
2794
  console.log();
2261
- console.log(chalk14.cyan.bold(" Tutti Project Info"));
2795
+ console.log(chalk19.cyan.bold(" Tutti Project Info"));
2262
2796
  console.log();
2263
- console.log(" " + chalk14.dim("Project:") + " " + chalk14.bold(projectName) + " " + chalk14.dim(projectVersion));
2797
+ console.log(" " + chalk19.dim("Project:") + " " + chalk19.bold(projectName) + " " + chalk19.dim(projectVersion));
2264
2798
  const tuttiPkgs = [...installedDeps.entries()].filter(([name]) => name.startsWith("@tuttiai/"));
2265
2799
  if (tuttiPkgs.length > 0) {
2266
2800
  console.log();
2267
- console.log(" " + chalk14.bold("Packages:"));
2801
+ console.log(" " + chalk19.bold("Packages:"));
2268
2802
  for (const [name, version] of tuttiPkgs) {
2269
- console.log(" " + pad2(name, 28) + chalk14.dim(version));
2803
+ console.log(" " + pad3(name, 28) + chalk19.dim(version));
2270
2804
  }
2271
2805
  }
2272
- const scoreFile = resolve14(scorePath ?? "./tutti.score.ts");
2806
+ const scoreFile = resolve16(scorePath ?? "./tutti.score.ts");
2273
2807
  if (!existsSync14(scoreFile)) {
2274
2808
  console.log();
2275
- console.log(chalk14.dim(" No score file found at " + scoreFile));
2276
- console.log(chalk14.dim(' Run "tutti-ai init" to create a new project.'));
2809
+ console.log(chalk19.dim(" No score file found at " + scoreFile));
2810
+ console.log(chalk19.dim(' Run "tutti-ai init" to create a new project.'));
2277
2811
  console.log();
2278
2812
  return;
2279
2813
  }
2280
2814
  let score;
2281
2815
  try {
2282
- score = await ScoreLoader8.load(scoreFile);
2816
+ score = await ScoreLoader9.load(scoreFile);
2283
2817
  } catch (err) {
2284
- logger12.error(
2818
+ logger14.error(
2285
2819
  { error: err instanceof Error ? err.message : String(err) },
2286
2820
  "Failed to load score"
2287
2821
  );
2288
- console.log(chalk14.dim(" Score file found but failed to load."));
2822
+ console.log(chalk19.dim(" Score file found but failed to load."));
2289
2823
  console.log();
2290
2824
  return;
2291
2825
  }
2292
- console.log(" " + chalk14.dim("Score:") + " " + (score.name ?? scoreFile));
2826
+ console.log(" " + chalk19.dim("Score:") + " " + (score.name ?? scoreFile));
2293
2827
  const agentEntries = Object.entries(score.agents);
2294
2828
  console.log();
2295
- console.log(" " + chalk14.bold("Agents:") + chalk14.dim(" (" + agentEntries.length + ")"));
2829
+ console.log(" " + chalk19.bold("Agents:") + chalk19.dim(" (" + agentEntries.length + ")"));
2296
2830
  for (const [id, agent] of agentEntries) {
2297
2831
  const voiceNames = agent.voices.map((v) => v.name).join(", ") || "none";
2298
2832
  const model = agent.model ?? score.default_model ?? "(default)";
@@ -2304,22 +2838,22 @@ async function infoCommand(scorePath) {
2304
2838
  if (agent.outputSchema) flags.push("structured");
2305
2839
  if (agent.beforeRun ?? agent.afterRun) flags.push("guardrails");
2306
2840
  console.log();
2307
- console.log(" " + chalk14.bold(id) + chalk14.dim(" (" + agent.name + ")"));
2308
- console.log(" " + chalk14.dim("Model: ") + model);
2309
- console.log(" " + chalk14.dim("Voices: ") + voiceNames);
2841
+ console.log(" " + chalk19.bold(id) + chalk19.dim(" (" + agent.name + ")"));
2842
+ console.log(" " + chalk19.dim("Model: ") + model);
2843
+ console.log(" " + chalk19.dim("Voices: ") + voiceNames);
2310
2844
  if (flags.length > 0) {
2311
- console.log(" " + chalk14.dim("Flags: ") + flags.join(", "));
2845
+ console.log(" " + chalk19.dim("Flags: ") + flags.join(", "));
2312
2846
  }
2313
2847
  if (agent.schedule) {
2314
2848
  const sched = agent.schedule;
2315
2849
  const trigger = sched.cron ?? sched.every ?? sched.at ?? "?";
2316
- console.log(" " + chalk14.dim("Schedule: ") + trigger);
2850
+ console.log(" " + chalk19.dim("Schedule: ") + trigger);
2317
2851
  }
2318
2852
  }
2319
2853
  if (score.entry) {
2320
2854
  const entry = typeof score.entry === "string" ? score.entry : "parallel";
2321
2855
  console.log();
2322
- console.log(" " + chalk14.dim("Entry:") + " " + entry);
2856
+ console.log(" " + chalk19.dim("Entry:") + " " + entry);
2323
2857
  }
2324
2858
  console.log();
2325
2859
  }
@@ -2327,13 +2861,13 @@ async function infoCommand(scorePath) {
2327
2861
  // src/commands/upgrade.ts
2328
2862
  import { execSync as execSync5 } from "child_process";
2329
2863
  import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
2330
- import { resolve as resolve15 } from "path";
2331
- import chalk15 from "chalk";
2332
- import ora9 from "ora";
2864
+ import { resolve as resolve17 } from "path";
2865
+ import chalk20 from "chalk";
2866
+ import ora10 from "ora";
2333
2867
  function detectPackageManager2() {
2334
2868
  const cwd = process.cwd();
2335
- if (existsSync15(resolve15(cwd, "yarn.lock"))) return "yarn";
2336
- if (existsSync15(resolve15(cwd, "pnpm-lock.yaml"))) return "pnpm";
2869
+ if (existsSync15(resolve17(cwd, "yarn.lock"))) return "yarn";
2870
+ if (existsSync15(resolve17(cwd, "pnpm-lock.yaml"))) return "pnpm";
2337
2871
  return "npm";
2338
2872
  }
2339
2873
  function resolvePackageName2(input) {
@@ -2352,7 +2886,7 @@ function resolvePackageName2(input) {
2352
2886
  return KNOWN.get(input) ?? (input.startsWith("@") ? input : `@tuttiai/${input}`);
2353
2887
  }
2354
2888
  function getInstalledTuttiPackages() {
2355
- const pkgPath = resolve15(process.cwd(), "package.json");
2889
+ const pkgPath = resolve17(process.cwd(), "package.json");
2356
2890
  if (!existsSync15(pkgPath)) return /* @__PURE__ */ new Map();
2357
2891
  try {
2358
2892
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
@@ -2373,40 +2907,40 @@ function getInstalledTuttiPackages() {
2373
2907
  }
2374
2908
  }
2375
2909
  function upgradeCommand(target) {
2376
- const pkgPath = resolve15(process.cwd(), "package.json");
2910
+ const pkgPath = resolve17(process.cwd(), "package.json");
2377
2911
  if (!existsSync15(pkgPath)) {
2378
- console.error(chalk15.red("No package.json found in the current directory."));
2379
- console.error(chalk15.dim('Run "tutti-ai init" to create a new project.'));
2912
+ console.error(chalk20.red("No package.json found in the current directory."));
2913
+ console.error(chalk20.dim('Run "tutti-ai init" to create a new project.'));
2380
2914
  process.exit(1);
2381
2915
  }
2382
2916
  const pm = detectPackageManager2();
2383
2917
  const installed = getInstalledTuttiPackages();
2384
2918
  if (installed.size === 0) {
2385
- console.log(chalk15.dim("No @tuttiai packages found in this project."));
2919
+ console.log(chalk20.dim("No @tuttiai packages found in this project."));
2386
2920
  return;
2387
2921
  }
2388
2922
  let packages;
2389
2923
  if (target) {
2390
2924
  const resolved = resolvePackageName2(target);
2391
2925
  if (!installed.has(resolved)) {
2392
- console.error(chalk15.red(resolved + " is not installed in this project."));
2393
- console.error(chalk15.dim("Installed: " + [...installed.keys()].join(", ")));
2926
+ console.error(chalk20.red(resolved + " is not installed in this project."));
2927
+ console.error(chalk20.dim("Installed: " + [...installed.keys()].join(", ")));
2394
2928
  process.exit(1);
2395
2929
  }
2396
2930
  packages = [resolved + "@latest"];
2397
- console.log(chalk15.cyan(" Upgrading " + resolved + "..."));
2931
+ console.log(chalk20.cyan(" Upgrading " + resolved + "..."));
2398
2932
  } else {
2399
2933
  packages = [...installed.keys()].map((p) => p + "@latest");
2400
- console.log(chalk15.cyan(" Upgrading all " + packages.length + " @tuttiai packages..."));
2934
+ console.log(chalk20.cyan(" Upgrading all " + packages.length + " @tuttiai packages..."));
2401
2935
  }
2402
2936
  const installCmd = pm === "yarn" ? "yarn add " + packages.join(" ") : pm === "pnpm" ? "pnpm add " + packages.join(" ") : "npm install " + packages.join(" ");
2403
- const spinner = ora9("Installing...").start();
2937
+ const spinner = ora10("Installing...").start();
2404
2938
  try {
2405
2939
  execSync5(installCmd, { cwd: process.cwd(), stdio: "pipe" });
2406
2940
  spinner.succeed("Upgraded " + packages.length + " package(s)");
2407
2941
  } catch {
2408
2942
  spinner.fail("Upgrade failed");
2409
- console.log(chalk15.dim(" Run manually: " + installCmd));
2943
+ console.log(chalk20.dim(" Run manually: " + installCmd));
2410
2944
  process.exit(1);
2411
2945
  }
2412
2946
  console.log();
@@ -2416,9 +2950,9 @@ function upgradeCommand(target) {
2416
2950
  const oldClean = oldVersion.replace(/^[\^~]/, "");
2417
2951
  const newClean = newVersion.replace(/^[\^~]/, "");
2418
2952
  if (oldClean !== newClean) {
2419
- console.log(" " + chalk15.green("\u2191") + " " + name + " " + chalk15.dim(oldClean) + " \u2192 " + chalk15.green(newClean));
2953
+ console.log(" " + chalk20.green("\u2191") + " " + name + " " + chalk20.dim(oldClean) + " \u2192 " + chalk20.green(newClean));
2420
2954
  } else {
2421
- console.log(" " + chalk15.dim("=") + " " + name + " " + chalk15.dim(newClean) + chalk15.dim(" (already latest)"));
2955
+ console.log(" " + chalk20.dim("=") + " " + name + " " + chalk20.dim(newClean) + chalk20.dim(" (already latest)"));
2422
2956
  }
2423
2957
  }
2424
2958
  console.log();
@@ -2426,21 +2960,21 @@ function upgradeCommand(target) {
2426
2960
 
2427
2961
  // src/commands/replay.ts
2428
2962
  import { existsSync as existsSync16 } from "fs";
2429
- import { writeFile } from "fs/promises";
2430
- import { resolve as resolve16 } from "path";
2963
+ import { writeFile as writeFile2 } from "fs/promises";
2964
+ import { resolve as resolve18 } from "path";
2431
2965
  import { createInterface as createInterface3 } from "readline/promises";
2432
- import chalk17 from "chalk";
2433
- import ora10 from "ora";
2966
+ import chalk22 from "chalk";
2967
+ import ora11 from "ora";
2434
2968
  import {
2435
- PostgresSessionStore,
2436
- ScoreLoader as ScoreLoader9,
2969
+ PostgresSessionStore as PostgresSessionStore2,
2970
+ ScoreLoader as ScoreLoader10,
2437
2971
  TuttiRuntime as TuttiRuntime5,
2438
- SecretsManager as SecretsManager7,
2439
- createLogger as createLogger13
2972
+ SecretsManager as SecretsManager8,
2973
+ createLogger as createLogger15
2440
2974
  } from "@tuttiai/core";
2441
2975
 
2442
2976
  // src/commands/replay-render.ts
2443
- import chalk16 from "chalk";
2977
+ import chalk21 from "chalk";
2444
2978
  function messageToText2(msg) {
2445
2979
  if (typeof msg.content === "string") return msg.content;
2446
2980
  const parts = [];
@@ -2465,22 +2999,22 @@ function renderList(messages) {
2465
2999
  for (let i = 0; i < messages.length; i++) {
2466
3000
  const msg = messages.at(i);
2467
3001
  if (!msg) continue;
2468
- const role = msg.role === "user" ? chalk16.blue("user ") : chalk16.green("assistant");
3002
+ const role = msg.role === "user" ? chalk21.blue("user ") : chalk21.green("assistant");
2469
3003
  const text = excerpt2(messageToText2(msg), 80);
2470
3004
  lines.push(
2471
- chalk16.dim(String(i).padStart(3)) + " " + role + " " + text
3005
+ chalk21.dim(String(i).padStart(3)) + " " + role + " " + text
2472
3006
  );
2473
3007
  }
2474
3008
  return lines.join("\n");
2475
3009
  }
2476
3010
  function renderShow(messages, index) {
2477
3011
  if (index < 0 || index >= messages.length) {
2478
- return chalk16.red("Index out of range. Valid: 0\u2013" + (messages.length - 1));
3012
+ return chalk21.red("Index out of range. Valid: 0\u2013" + (messages.length - 1));
2479
3013
  }
2480
3014
  const msg = messages.at(index);
2481
- if (!msg) return chalk16.red("Index out of range.");
3015
+ if (!msg) return chalk21.red("Index out of range.");
2482
3016
  const lines = [];
2483
- lines.push(chalk16.cyan.bold("Turn " + index) + " " + chalk16.dim("[" + msg.role + "]"));
3017
+ lines.push(chalk21.cyan.bold("Turn " + index) + " " + chalk21.dim("[" + msg.role + "]"));
2484
3018
  lines.push("");
2485
3019
  if (typeof msg.content === "string") {
2486
3020
  lines.push(msg.content);
@@ -2489,13 +3023,13 @@ function renderShow(messages, index) {
2489
3023
  if (block.type === "text") {
2490
3024
  lines.push(block.text);
2491
3025
  } else if (block.type === "tool_use") {
2492
- lines.push(chalk16.yellow(" tool_use: " + block.name));
2493
- lines.push(chalk16.dim(" id: " + block.id));
2494
- lines.push(chalk16.dim(" input: " + JSON.stringify(block.input, null, 2)));
3026
+ lines.push(chalk21.yellow(" tool_use: " + block.name));
3027
+ lines.push(chalk21.dim(" id: " + block.id));
3028
+ lines.push(chalk21.dim(" input: " + JSON.stringify(block.input, null, 2)));
2495
3029
  } else if (block.type === "tool_result") {
2496
- const label = block.is_error ? chalk16.red(" tool_result (error):") : chalk16.green(" tool_result:");
3030
+ const label = block.is_error ? chalk21.red(" tool_result (error):") : chalk21.green(" tool_result:");
2497
3031
  lines.push(label);
2498
- lines.push(chalk16.dim(" tool_use_id: " + block.tool_use_id));
3032
+ lines.push(chalk21.dim(" tool_use_id: " + block.tool_use_id));
2499
3033
  lines.push(" " + block.content);
2500
3034
  }
2501
3035
  }
@@ -2504,7 +3038,7 @@ function renderShow(messages, index) {
2504
3038
  }
2505
3039
  function renderInspect(messages, index) {
2506
3040
  if (index < 0 || index >= messages.length) {
2507
- return chalk16.red("Index out of range.");
3041
+ return chalk21.red("Index out of range.");
2508
3042
  }
2509
3043
  return JSON.stringify(messages.at(index), null, 2);
2510
3044
  }
@@ -2557,26 +3091,26 @@ function exportMarkdown(session) {
2557
3091
  }
2558
3092
 
2559
3093
  // src/commands/replay.ts
2560
- var logger13 = createLogger13("tutti-cli");
3094
+ var logger15 = createLogger15("tutti-cli");
2561
3095
  async function replayCommand(sessionId, opts = {}) {
2562
- const pgUrl = SecretsManager7.optional("TUTTI_PG_URL");
3096
+ const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
2563
3097
  if (!pgUrl) {
2564
- console.error(chalk17.red("TUTTI_PG_URL is not set."));
3098
+ console.error(chalk22.red("TUTTI_PG_URL is not set."));
2565
3099
  console.error(
2566
- chalk17.dim(
3100
+ chalk22.dim(
2567
3101
  "The replay command requires PostgreSQL for session persistence.\nSet TUTTI_PG_URL=postgres://user:pass@host/db in your environment."
2568
3102
  )
2569
3103
  );
2570
3104
  process.exit(1);
2571
3105
  }
2572
- const store = new PostgresSessionStore(pgUrl);
2573
- const spinner = ora10({ color: "cyan" }).start("Loading session...");
3106
+ const store = new PostgresSessionStore2(pgUrl);
3107
+ const spinner = ora11({ color: "cyan" }).start("Loading session...");
2574
3108
  let session;
2575
3109
  try {
2576
3110
  session = await store.getAsync(sessionId);
2577
3111
  } catch (err) {
2578
3112
  spinner.fail("Failed to load session");
2579
- logger13.error(
3113
+ logger15.error(
2580
3114
  { error: err instanceof Error ? err.message : String(err) },
2581
3115
  "Session store error"
2582
3116
  );
@@ -2584,27 +3118,27 @@ async function replayCommand(sessionId, opts = {}) {
2584
3118
  }
2585
3119
  spinner.stop();
2586
3120
  if (!session) {
2587
- console.error(chalk17.red("Session not found: " + sessionId));
2588
- console.error(chalk17.dim("Check the session ID and ensure TUTTI_PG_URL points to the correct database."));
3121
+ console.error(chalk22.red("Session not found: " + sessionId));
3122
+ console.error(chalk22.dim("Check the session ID and ensure TUTTI_PG_URL points to the correct database."));
2589
3123
  await store.close();
2590
3124
  process.exit(1);
2591
3125
  }
2592
3126
  const messages = session.messages;
2593
3127
  console.log("");
2594
- console.log(chalk17.cyan.bold(" Tutti Replay"));
2595
- console.log(chalk17.dim(" Session: " + session.id));
2596
- console.log(chalk17.dim(" Agent: " + session.agent_name));
2597
- console.log(chalk17.dim(" Created: " + session.created_at.toISOString()));
2598
- console.log(chalk17.dim(" Messages: " + messages.length));
3128
+ console.log(chalk22.cyan.bold(" Tutti Replay"));
3129
+ console.log(chalk22.dim(" Session: " + session.id));
3130
+ console.log(chalk22.dim(" Agent: " + session.agent_name));
3131
+ console.log(chalk22.dim(" Created: " + session.created_at.toISOString()));
3132
+ console.log(chalk22.dim(" Messages: " + messages.length));
2599
3133
  console.log("");
2600
- console.log(chalk17.dim(" Commands: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit"));
3134
+ console.log(chalk22.dim(" Commands: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit"));
2601
3135
  console.log("");
2602
3136
  let cursor = 0;
2603
3137
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2604
3138
  try {
2605
3139
  while (true) {
2606
- const prompt5 = chalk17.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2607
- const raw = await rl.question(prompt5);
3140
+ const prompt6 = chalk22.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
3141
+ const raw = await rl.question(prompt6);
2608
3142
  const input = raw.trim();
2609
3143
  if (!input) continue;
2610
3144
  const [cmd, ...args] = input.split(/\s+/);
@@ -2612,7 +3146,7 @@ async function replayCommand(sessionId, opts = {}) {
2612
3146
  case "quit":
2613
3147
  case "exit":
2614
3148
  case "q":
2615
- console.log(chalk17.dim("Bye."));
3149
+ console.log(chalk22.dim("Bye."));
2616
3150
  return;
2617
3151
  case "list":
2618
3152
  console.log(renderList(messages));
@@ -2628,7 +3162,7 @@ async function replayCommand(sessionId, opts = {}) {
2628
3162
  cursor++;
2629
3163
  console.log(renderShow(messages, cursor));
2630
3164
  } else {
2631
- console.log(chalk17.dim("Already at last message."));
3165
+ console.log(chalk22.dim("Already at last message."));
2632
3166
  }
2633
3167
  break;
2634
3168
  case "prev":
@@ -2636,7 +3170,7 @@ async function replayCommand(sessionId, opts = {}) {
2636
3170
  cursor--;
2637
3171
  console.log(renderShow(messages, cursor));
2638
3172
  } else {
2639
- console.log(chalk17.dim("Already at first message."));
3173
+ console.log(chalk22.dim("Already at first message."));
2640
3174
  }
2641
3175
  break;
2642
3176
  case "inspect":
@@ -2645,7 +3179,7 @@ async function replayCommand(sessionId, opts = {}) {
2645
3179
  case "replay-from": {
2646
3180
  const turn = parseInt(args[0] ?? "", 10);
2647
3181
  if (isNaN(turn) || turn < 0 || turn >= messages.length) {
2648
- console.log(chalk17.red("Usage: replay-from <turn-number>"));
3182
+ console.log(chalk22.red("Usage: replay-from <turn-number>"));
2649
3183
  break;
2650
3184
  }
2651
3185
  await handleReplayFrom(turn, session, rl, opts);
@@ -2655,20 +3189,20 @@ async function replayCommand(sessionId, opts = {}) {
2655
3189
  const format = args[0];
2656
3190
  if (format === "json") {
2657
3191
  const filename = "session-" + session.id.slice(0, 8) + ".json";
2658
- await writeFile(filename, exportJSON(session));
2659
- console.log(chalk17.green("Exported to " + filename));
3192
+ await writeFile2(filename, exportJSON(session));
3193
+ console.log(chalk22.green("Exported to " + filename));
2660
3194
  } else if (format === "md" || format === "markdown") {
2661
3195
  const filename = "session-" + session.id.slice(0, 8) + ".md";
2662
- await writeFile(filename, exportMarkdown(session));
2663
- console.log(chalk17.green("Exported to " + filename));
3196
+ await writeFile2(filename, exportMarkdown(session));
3197
+ console.log(chalk22.green("Exported to " + filename));
2664
3198
  } else {
2665
- console.log(chalk17.dim("Usage: export <json|md>"));
3199
+ console.log(chalk22.dim("Usage: export <json|md>"));
2666
3200
  }
2667
3201
  break;
2668
3202
  }
2669
3203
  default:
2670
3204
  console.log(
2671
- chalk17.dim("Unknown command. Available: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit")
3205
+ chalk22.dim("Unknown command. Available: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit")
2672
3206
  );
2673
3207
  }
2674
3208
  }
@@ -2678,29 +3212,29 @@ async function replayCommand(sessionId, opts = {}) {
2678
3212
  }
2679
3213
  }
2680
3214
  async function handleReplayFrom(turn, session, rl, opts) {
2681
- const scoreFile = resolve16(opts.score ?? "./tutti.score.ts");
3215
+ const scoreFile = resolve18(opts.score ?? "./tutti.score.ts");
2682
3216
  if (!existsSync16(scoreFile)) {
2683
- console.log(chalk17.red("Score file not found: " + scoreFile));
2684
- console.log(chalk17.dim("Use --score to specify the score file."));
3217
+ console.log(chalk22.red("Score file not found: " + scoreFile));
3218
+ console.log(chalk22.dim("Use --score to specify the score file."));
2685
3219
  return;
2686
3220
  }
2687
3221
  const originalMsg = session.messages.at(turn);
2688
3222
  const originalInput = originalMsg ? messageToText2(originalMsg) : "";
2689
3223
  const answer = await rl.question(
2690
- chalk17.cyan("Replay from turn " + turn + " with original input? ") + chalk17.dim("(y / enter new input) ")
3224
+ chalk22.cyan("Replay from turn " + turn + " with original input? ") + chalk22.dim("(y / enter new input) ")
2691
3225
  );
2692
3226
  const input = answer.trim().toLowerCase() === "y" || answer.trim() === "" ? originalInput : answer.trim();
2693
3227
  if (!input) {
2694
- console.log(chalk17.dim("No input provided. Cancelled."));
3228
+ console.log(chalk22.dim("No input provided. Cancelled."));
2695
3229
  return;
2696
3230
  }
2697
- const spinnerLoad = ora10({ color: "cyan" }).start("Loading score...");
3231
+ const spinnerLoad = ora11({ color: "cyan" }).start("Loading score...");
2698
3232
  let score;
2699
3233
  try {
2700
- score = await ScoreLoader9.load(scoreFile);
3234
+ score = await ScoreLoader10.load(scoreFile);
2701
3235
  } catch (err) {
2702
3236
  spinnerLoad.fail("Failed to load score");
2703
- logger13.error({ error: err instanceof Error ? err.message : String(err) }, "Score load error");
3237
+ logger15.error({ error: err instanceof Error ? err.message : String(err) }, "Score load error");
2704
3238
  return;
2705
3239
  }
2706
3240
  spinnerLoad.stop();
@@ -2720,10 +3254,10 @@ async function handleReplayFrom(turn, session, rl, opts) {
2720
3254
  const agentMap = new Map(Object.entries(score.agents));
2721
3255
  const agent = agentMap.get(agentName);
2722
3256
  if (!agent) {
2723
- console.log(chalk17.red('Agent "' + agentName + '" not found in score.'));
3257
+ console.log(chalk22.red('Agent "' + agentName + '" not found in score.'));
2724
3258
  return;
2725
3259
  }
2726
- const spinnerRun = ora10({ color: "cyan" }).start("Running from turn " + turn + "...");
3260
+ const spinnerRun = ora11({ color: "cyan" }).start("Running from turn " + turn + "...");
2727
3261
  runtime.events.on("token:stream", (e) => {
2728
3262
  spinnerRun.stop();
2729
3263
  process.stdout.write(e.text);
@@ -2732,39 +3266,39 @@ async function handleReplayFrom(turn, session, rl, opts) {
2732
3266
  const result = await runtime.run(agentName, input, session.id);
2733
3267
  spinnerRun.stop();
2734
3268
  console.log("");
2735
- console.log(chalk17.green("Replay complete."));
2736
- console.log(chalk17.dim(" Turns: " + result.turns));
2737
- console.log(chalk17.dim(" Tokens: " + result.usage.input_tokens + " in / " + result.usage.output_tokens + " out"));
3269
+ console.log(chalk22.green("Replay complete."));
3270
+ console.log(chalk22.dim(" Turns: " + result.turns));
3271
+ console.log(chalk22.dim(" Tokens: " + result.usage.input_tokens + " in / " + result.usage.output_tokens + " out"));
2738
3272
  console.log("");
2739
3273
  console.log(result.output);
2740
3274
  } catch (err) {
2741
3275
  spinnerRun.fail("Replay failed");
2742
- logger13.error({ error: err instanceof Error ? err.message : String(err) }, "Replay error");
3276
+ logger15.error({ error: err instanceof Error ? err.message : String(err) }, "Replay error");
2743
3277
  }
2744
3278
  }
2745
3279
 
2746
3280
  // src/commands/schedules.ts
2747
3281
  import { existsSync as existsSync17 } from "fs";
2748
- import { resolve as resolve17 } from "path";
2749
- import chalk18 from "chalk";
3282
+ import { resolve as resolve19 } from "path";
3283
+ import chalk23 from "chalk";
2750
3284
  import {
2751
- ScoreLoader as ScoreLoader10,
3285
+ ScoreLoader as ScoreLoader11,
2752
3286
  SchedulerEngine as SchedulerEngine2,
2753
3287
  PostgresScheduleStore as PostgresScheduleStore2,
2754
3288
  MemoryScheduleStore as MemoryScheduleStore2,
2755
3289
  AgentRunner as AgentRunner2,
2756
3290
  EventBus as EventBus2,
2757
3291
  InMemorySessionStore as InMemorySessionStore4,
2758
- SecretsManager as SecretsManager8,
2759
- createLogger as createLogger14
3292
+ SecretsManager as SecretsManager9,
3293
+ createLogger as createLogger16
2760
3294
  } from "@tuttiai/core";
2761
- var logger14 = createLogger14("tutti-cli");
3295
+ var logger16 = createLogger16("tutti-cli");
2762
3296
  function resolveStore2() {
2763
- const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
3297
+ const pgUrl = SecretsManager9.optional("TUTTI_PG_URL");
2764
3298
  if (pgUrl) {
2765
3299
  return new PostgresScheduleStore2({ connection_string: pgUrl });
2766
3300
  }
2767
- logger14.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
3301
+ logger16.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
2768
3302
  return new MemoryScheduleStore2();
2769
3303
  }
2770
3304
  async function closeStore(store) {
@@ -2778,7 +3312,7 @@ function formatTrigger(r) {
2778
3312
  if (r.config.at) return "at " + r.config.at;
2779
3313
  return "?";
2780
3314
  }
2781
- function pad3(s, len) {
3315
+ function pad4(s, len) {
2782
3316
  return s.length >= len ? s : s + " ".repeat(len - s.length);
2783
3317
  }
2784
3318
  async function schedulesListCommand() {
@@ -2786,22 +3320,22 @@ async function schedulesListCommand() {
2786
3320
  try {
2787
3321
  const records = await store.list();
2788
3322
  if (records.length === 0) {
2789
- console.log(chalk18.dim("No schedules found."));
2790
- console.log(chalk18.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
3323
+ console.log(chalk23.dim("No schedules found."));
3324
+ console.log(chalk23.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
2791
3325
  return;
2792
3326
  }
2793
3327
  console.log("");
2794
3328
  console.log(
2795
- chalk18.dim(
2796
- " " + pad3("ID", 20) + pad3("AGENT", 16) + pad3("TRIGGER", 22) + pad3("ENABLED", 10) + pad3("RUNS", 8) + "CREATED"
3329
+ chalk23.dim(
3330
+ " " + pad4("ID", 20) + pad4("AGENT", 16) + pad4("TRIGGER", 22) + pad4("ENABLED", 10) + pad4("RUNS", 8) + "CREATED"
2797
3331
  )
2798
3332
  );
2799
- console.log(chalk18.dim(" " + "\u2500".repeat(90)));
3333
+ console.log(chalk23.dim(" " + "\u2500".repeat(90)));
2800
3334
  for (const r of records) {
2801
- const enabled = r.enabled ? chalk18.green("yes") : chalk18.red("no") + " ";
3335
+ const enabled = r.enabled ? chalk23.green("yes") : chalk23.red("no") + " ";
2802
3336
  const maxLabel = r.config.max_runs ? r.run_count + "/" + r.config.max_runs : String(r.run_count);
2803
3337
  console.log(
2804
- " " + chalk18.bold(pad3(r.id, 20)) + pad3(r.agent_id, 16) + pad3(formatTrigger(r), 22) + pad3(enabled, 10) + pad3(maxLabel, 8) + chalk18.dim(r.created_at.toISOString().slice(0, 10))
3338
+ " " + chalk23.bold(pad4(r.id, 20)) + pad4(r.agent_id, 16) + pad4(formatTrigger(r), 22) + pad4(enabled, 10) + pad4(maxLabel, 8) + chalk23.dim(r.created_at.toISOString().slice(0, 10))
2805
3339
  );
2806
3340
  }
2807
3341
  console.log("");
@@ -2814,11 +3348,11 @@ async function schedulesEnableCommand(id) {
2814
3348
  try {
2815
3349
  const record = await store.get(id);
2816
3350
  if (!record) {
2817
- console.error(chalk18.red('Schedule "' + id + '" not found.'));
3351
+ console.error(chalk23.red('Schedule "' + id + '" not found.'));
2818
3352
  process.exit(1);
2819
3353
  }
2820
3354
  await store.setEnabled(id, true);
2821
- console.log(chalk18.green('Schedule "' + id + '" enabled.'));
3355
+ console.log(chalk23.green('Schedule "' + id + '" enabled.'));
2822
3356
  } finally {
2823
3357
  await closeStore(store);
2824
3358
  }
@@ -2828,22 +3362,22 @@ async function schedulesDisableCommand(id) {
2828
3362
  try {
2829
3363
  const record = await store.get(id);
2830
3364
  if (!record) {
2831
- console.error(chalk18.red('Schedule "' + id + '" not found.'));
3365
+ console.error(chalk23.red('Schedule "' + id + '" not found.'));
2832
3366
  process.exit(1);
2833
3367
  }
2834
3368
  await store.setEnabled(id, false);
2835
- console.log(chalk18.yellow('Schedule "' + id + '" disabled.'));
3369
+ console.log(chalk23.yellow('Schedule "' + id + '" disabled.'));
2836
3370
  } finally {
2837
3371
  await closeStore(store);
2838
3372
  }
2839
3373
  }
2840
3374
  async function schedulesTriggerCommand(id, scorePath) {
2841
- const file = resolve17(scorePath ?? "./tutti.score.ts");
3375
+ const file = resolve19(scorePath ?? "./tutti.score.ts");
2842
3376
  if (!existsSync17(file)) {
2843
- console.error(chalk18.red("Score file not found: " + file));
3377
+ console.error(chalk23.red("Score file not found: " + file));
2844
3378
  process.exit(1);
2845
3379
  }
2846
- const score = await ScoreLoader10.load(file);
3380
+ const score = await ScoreLoader11.load(file);
2847
3381
  const events = new EventBus2();
2848
3382
  const sessions = new InMemorySessionStore4();
2849
3383
  const runner = new AgentRunner2(score.provider, events, sessions);
@@ -2851,30 +3385,30 @@ async function schedulesTriggerCommand(id, scorePath) {
2851
3385
  try {
2852
3386
  const record = await store.get(id);
2853
3387
  if (!record) {
2854
- console.error(chalk18.red('Schedule "' + id + '" not found.'));
3388
+ console.error(chalk23.red('Schedule "' + id + '" not found.'));
2855
3389
  process.exit(1);
2856
3390
  }
2857
3391
  const agent = score.agents[record.agent_id];
2858
3392
  if (!agent) {
2859
- console.error(chalk18.red('Agent "' + record.agent_id + '" not found in score.'));
3393
+ console.error(chalk23.red('Agent "' + record.agent_id + '" not found in score.'));
2860
3394
  process.exit(1);
2861
3395
  }
2862
3396
  const resolvedAgent = agent.model ? agent : { ...agent, model: score.default_model ?? "claude-sonnet-4-20250514" };
2863
3397
  const engine = new SchedulerEngine2(store, runner, events);
2864
3398
  await engine.schedule(id, resolvedAgent, record.config);
2865
3399
  engine.start();
2866
- console.log(chalk18.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
3400
+ console.log(chalk23.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
2867
3401
  const run2 = await engine.trigger(id);
2868
3402
  engine.stop();
2869
3403
  if (run2.error) {
2870
- console.log(chalk18.red(" Error: " + run2.error));
3404
+ console.log(chalk23.red(" Error: " + run2.error));
2871
3405
  process.exit(1);
2872
3406
  }
2873
3407
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() : 0;
2874
- console.log(chalk18.green(" Completed in " + duration + "ms"));
3408
+ console.log(chalk23.green(" Completed in " + duration + "ms"));
2875
3409
  if (run2.result) {
2876
3410
  const preview = run2.result.length > 200 ? run2.result.slice(0, 200) + "..." : run2.result;
2877
- console.log(chalk18.dim(" Output: " + preview));
3411
+ console.log(chalk23.dim(" Output: " + preview));
2878
3412
  }
2879
3413
  } finally {
2880
3414
  await closeStore(store);
@@ -2885,31 +3419,31 @@ async function schedulesRunsCommand(id) {
2885
3419
  try {
2886
3420
  const record = await store.get(id);
2887
3421
  if (!record) {
2888
- console.error(chalk18.red('Schedule "' + id + '" not found.'));
3422
+ console.error(chalk23.red('Schedule "' + id + '" not found.'));
2889
3423
  process.exit(1);
2890
3424
  }
2891
3425
  if ("getRuns" in store && typeof store.getRuns === "function") {
2892
3426
  const runs = store.getRuns(id);
2893
3427
  if (runs.length === 0) {
2894
- console.log(chalk18.dim("No runs recorded for this schedule."));
3428
+ console.log(chalk23.dim("No runs recorded for this schedule."));
2895
3429
  return;
2896
3430
  }
2897
3431
  const recent = runs.slice(-20);
2898
3432
  console.log("");
2899
- console.log(chalk18.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
3433
+ console.log(chalk23.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
2900
3434
  console.log("");
2901
3435
  for (const run2 of recent) {
2902
3436
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() + "ms" : "?";
2903
- const status = run2.error ? chalk18.red("error") : chalk18.green("ok");
3437
+ const status = run2.error ? chalk23.red("error") : chalk23.green("ok");
2904
3438
  const preview = run2.error ? run2.error.slice(0, 80) : (run2.result ?? "").slice(0, 80);
2905
3439
  console.log(
2906
- " " + chalk18.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk18.dim(duration) + " " + preview
3440
+ " " + chalk23.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk23.dim(duration) + " " + preview
2907
3441
  );
2908
3442
  }
2909
3443
  console.log("");
2910
3444
  } else {
2911
- console.log(chalk18.dim('Schedule "' + id + '" has completed ' + record.run_count + " runs."));
2912
- console.log(chalk18.dim("Full run history requires the MemoryScheduleStore or a future tutti_schedule_runs table."));
3445
+ console.log(chalk23.dim('Schedule "' + id + '" has completed ' + record.run_count + " runs."));
3446
+ console.log(chalk23.dim("Full run history requires the MemoryScheduleStore or a future tutti_schedule_runs table."));
2913
3447
  }
2914
3448
  } finally {
2915
3449
  await closeStore(store);
@@ -2917,52 +3451,52 @@ async function schedulesRunsCommand(id) {
2917
3451
  }
2918
3452
 
2919
3453
  // src/commands/traces.ts
2920
- import chalk20 from "chalk";
2921
- import { SecretsManager as SecretsManager9 } from "@tuttiai/core";
3454
+ import chalk25 from "chalk";
3455
+ import { SecretsManager as SecretsManager10 } from "@tuttiai/core";
2922
3456
 
2923
3457
  // src/commands/traces-render.ts
2924
- import chalk19 from "chalk";
2925
- function visibleLen(s) {
3458
+ import chalk24 from "chalk";
3459
+ function visibleLen2(s) {
2926
3460
  return s.replace(/\u001b\[[0-9;]*m/g, "").length;
2927
3461
  }
2928
- function pad4(s, len) {
2929
- const v = visibleLen(s);
3462
+ function pad5(s, len) {
3463
+ const v = visibleLen2(s);
2930
3464
  return v >= len ? s : s + " ".repeat(len - v);
2931
3465
  }
2932
3466
  function colorStatus(status) {
2933
- if (status === "ok") return chalk19.green("ok");
2934
- if (status === "error") return chalk19.red("error");
2935
- return chalk19.yellow("running");
3467
+ if (status === "ok") return chalk24.green("ok");
3468
+ if (status === "error") return chalk24.red("error");
3469
+ return chalk24.yellow("running");
2936
3470
  }
2937
3471
  function formatCost(cost) {
2938
- if (cost === null) return chalk19.dim("\u2014");
3472
+ if (cost === null) return chalk24.dim("\u2014");
2939
3473
  if (cost === 0) return "$0";
2940
3474
  return "$" + cost.toFixed(6);
2941
3475
  }
2942
3476
  function formatTokens(n) {
2943
- return n > 0 ? String(n) : chalk19.dim("\u2014");
3477
+ return n > 0 ? String(n) : chalk24.dim("\u2014");
2944
3478
  }
2945
3479
  function formatDuration(ms) {
2946
- if (ms === null) return chalk19.dim("\u2014");
3480
+ if (ms === null) return chalk24.dim("\u2014");
2947
3481
  return ms + "ms";
2948
3482
  }
2949
3483
  function renderTracesList(traces) {
2950
3484
  if (traces.length === 0) {
2951
- return chalk19.dim("No traces found.");
3485
+ return chalk24.dim("No traces found.");
2952
3486
  }
2953
3487
  const lines = [];
2954
3488
  lines.push("");
2955
3489
  lines.push(
2956
- chalk19.dim(
2957
- " " + pad4("TRACE", 10) + pad4("AGENT", 18) + pad4("STARTED", 12) + pad4("DURATION", 12) + pad4("STATUS", 12) + pad4("TOKENS", 10) + "COST"
3490
+ chalk24.dim(
3491
+ " " + pad5("TRACE", 10) + pad5("AGENT", 18) + pad5("STARTED", 12) + pad5("DURATION", 12) + pad5("STATUS", 12) + pad5("TOKENS", 10) + "COST"
2958
3492
  )
2959
3493
  );
2960
- lines.push(chalk19.dim(" " + "\u2500".repeat(80)));
3494
+ lines.push(chalk24.dim(" " + "\u2500".repeat(80)));
2961
3495
  for (const t of traces) {
2962
3496
  const traceShort = t.trace_id.slice(0, 8);
2963
3497
  const startedShort = t.started_at.slice(11, 19);
2964
3498
  lines.push(
2965
- " " + chalk19.bold(pad4(traceShort, 10)) + pad4(t.agent_id ?? chalk19.dim("\u2014"), 18) + pad4(startedShort, 12) + pad4(formatDuration(t.duration_ms), 12) + pad4(colorStatus(t.status), 12) + pad4(formatTokens(t.total_tokens), 10) + formatCost(t.cost_usd)
3499
+ " " + chalk24.bold(pad5(traceShort, 10)) + pad5(t.agent_id ?? chalk24.dim("\u2014"), 18) + pad5(startedShort, 12) + pad5(formatDuration(t.duration_ms), 12) + pad5(colorStatus(t.status), 12) + pad5(formatTokens(t.total_tokens), 10) + formatCost(t.cost_usd)
2966
3500
  );
2967
3501
  }
2968
3502
  lines.push("");
@@ -2978,11 +3512,11 @@ var SPAN_ICONS = {
2978
3512
  function renderSpanLine(span, indent) {
2979
3513
  const icon = SPAN_ICONS[span.kind];
2980
3514
  const indentStr = " ".repeat(indent);
2981
- const dur = span.duration_ms !== void 0 ? chalk19.dim(" " + span.duration_ms + "ms ") : chalk19.dim(" (running) ");
3515
+ const dur = span.duration_ms !== void 0 ? chalk24.dim(" " + span.duration_ms + "ms ") : chalk24.dim(" (running) ");
2982
3516
  const status = colorStatus(span.status);
2983
3517
  const attrs = formatAttrs(span);
2984
- const attrSuffix = attrs ? chalk19.dim(" \xB7 " + attrs) : "";
2985
- return indentStr + icon + " " + chalk19.bold(span.name) + dur + status + attrSuffix;
3518
+ const attrSuffix = attrs ? chalk24.dim(" \xB7 " + attrs) : "";
3519
+ return indentStr + icon + " " + chalk24.bold(span.name) + dur + status + attrSuffix;
2986
3520
  }
2987
3521
  function formatAttrs(span) {
2988
3522
  const a = span.attributes;
@@ -3003,13 +3537,13 @@ function formatAttrs(span) {
3003
3537
  if (a.session_id !== void 0) parts.push("session=" + a.session_id.slice(0, 8));
3004
3538
  }
3005
3539
  if (span.error?.message) {
3006
- parts.push(chalk19.red("error: " + span.error.message));
3540
+ parts.push(chalk24.red("error: " + span.error.message));
3007
3541
  }
3008
3542
  return parts.join(" \xB7 ");
3009
3543
  }
3010
3544
  function renderTraceShow(spans) {
3011
3545
  if (spans.length === 0) {
3012
- return chalk19.dim("No spans found for this trace.");
3546
+ return chalk24.dim("No spans found for this trace.");
3013
3547
  }
3014
3548
  const childrenByParent = /* @__PURE__ */ new Map();
3015
3549
  const presentSpanIds = new Set(spans.map((s) => s.span_id));
@@ -3048,9 +3582,9 @@ function renderTraceShow(spans) {
3048
3582
  }
3049
3583
  const wall_ms = roots.map((r) => r.duration_ms).find((d) => d !== void 0);
3050
3584
  lines.push("");
3051
- lines.push(chalk19.dim("\u2500".repeat(60)));
3585
+ lines.push(chalk24.dim("\u2500".repeat(60)));
3052
3586
  lines.push(
3053
- chalk19.dim("Total: ") + chalk19.bold(formatTokens(total_tokens)) + chalk19.dim(" tokens \xB7 ") + chalk19.bold(any_cost ? formatCost(total_cost) : chalk19.dim("\u2014")) + chalk19.dim(" cost \xB7 ") + chalk19.bold(wall_ms !== void 0 ? wall_ms + "ms" : chalk19.dim("\u2014")) + chalk19.dim(" wall")
3587
+ chalk24.dim("Total: ") + chalk24.bold(formatTokens(total_tokens)) + chalk24.dim(" tokens \xB7 ") + chalk24.bold(any_cost ? formatCost(total_cost) : chalk24.dim("\u2014")) + chalk24.dim(" cost \xB7 ") + chalk24.bold(wall_ms !== void 0 ? wall_ms + "ms" : chalk24.dim("\u2014")) + chalk24.dim(" wall")
3054
3588
  );
3055
3589
  lines.push("");
3056
3590
  return lines.join("\n");
@@ -3059,17 +3593,17 @@ function renderTraceShow(spans) {
3059
3593
  // src/commands/traces.ts
3060
3594
  var DEFAULT_SERVER_URL = "http://127.0.0.1:3847";
3061
3595
  function resolveUrl(opts) {
3062
- return opts.url ?? SecretsManager9.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL;
3596
+ return opts.url ?? SecretsManager10.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL;
3063
3597
  }
3064
3598
  function resolveAuthHeader(opts) {
3065
- const key = opts.apiKey ?? SecretsManager9.optional("TUTTI_API_KEY");
3599
+ const key = opts.apiKey ?? SecretsManager10.optional("TUTTI_API_KEY");
3066
3600
  return key ? { Authorization: "Bearer " + key } : {};
3067
3601
  }
3068
3602
  function explainConnectionError(err, baseUrl) {
3069
3603
  const msg = err instanceof Error ? err.message : String(err);
3070
- console.error(chalk20.red("Failed to reach Tutti server at " + baseUrl));
3071
- console.error(chalk20.dim(" " + msg));
3072
- console.error(chalk20.dim(" Is `tutti-ai serve` running? Set --url or TUTTI_SERVER_URL to override."));
3604
+ console.error(chalk25.red("Failed to reach Tutti server at " + baseUrl));
3605
+ console.error(chalk25.dim(" " + msg));
3606
+ console.error(chalk25.dim(" Is `tutti-ai serve` running? Set --url or TUTTI_SERVER_URL to override."));
3073
3607
  process.exit(1);
3074
3608
  }
3075
3609
  async function tracesListCommand(opts) {
@@ -3082,11 +3616,11 @@ async function tracesListCommand(opts) {
3082
3616
  explainConnectionError(err, baseUrl);
3083
3617
  }
3084
3618
  if (res.status === 401) {
3085
- console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3619
+ console.error(chalk25.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3086
3620
  process.exit(1);
3087
3621
  }
3088
3622
  if (!res.ok) {
3089
- console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3623
+ console.error(chalk25.red("Server returned " + res.status + " " + res.statusText));
3090
3624
  process.exit(1);
3091
3625
  }
3092
3626
  const body = await res.json();
@@ -3102,15 +3636,15 @@ async function tracesShowCommand(traceId, opts) {
3102
3636
  explainConnectionError(err, baseUrl);
3103
3637
  }
3104
3638
  if (res.status === 404) {
3105
- console.error(chalk20.red('Trace "' + traceId + '" not found.'));
3639
+ console.error(chalk25.red('Trace "' + traceId + '" not found.'));
3106
3640
  process.exit(1);
3107
3641
  }
3108
3642
  if (res.status === 401) {
3109
- console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3643
+ console.error(chalk25.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3110
3644
  process.exit(1);
3111
3645
  }
3112
3646
  if (!res.ok) {
3113
- console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3647
+ console.error(chalk25.red("Server returned " + res.status + " " + res.statusText));
3114
3648
  process.exit(1);
3115
3649
  }
3116
3650
  const body = await res.json();
@@ -3120,13 +3654,13 @@ async function tracesShowCommand(traceId, opts) {
3120
3654
  async function tracesTailCommand(opts) {
3121
3655
  const baseUrl = resolveUrl(opts);
3122
3656
  const url = baseUrl.replace(/\/$/, "") + "/traces/stream";
3123
- console.error(chalk20.dim("Tailing traces from " + baseUrl + " \u2014 Ctrl+C to exit"));
3657
+ console.error(chalk25.dim("Tailing traces from " + baseUrl + " \u2014 Ctrl+C to exit"));
3124
3658
  console.error("");
3125
3659
  const controller = new AbortController();
3126
3660
  process.once("SIGINT", () => {
3127
3661
  controller.abort();
3128
3662
  console.error("");
3129
- console.error(chalk20.dim("Disconnected."));
3663
+ console.error(chalk25.dim("Disconnected."));
3130
3664
  process.exit(0);
3131
3665
  });
3132
3666
  let res;
@@ -3140,11 +3674,11 @@ async function tracesTailCommand(opts) {
3140
3674
  explainConnectionError(err, baseUrl);
3141
3675
  }
3142
3676
  if (res.status === 401) {
3143
- console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3677
+ console.error(chalk25.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3144
3678
  process.exit(1);
3145
3679
  }
3146
3680
  if (!res.ok || !res.body) {
3147
- console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3681
+ console.error(chalk25.red("Server returned " + res.status + " " + res.statusText));
3148
3682
  process.exit(1);
3149
3683
  }
3150
3684
  const reader = res.body.getReader();
@@ -3169,7 +3703,7 @@ async function tracesTailCommand(opts) {
3169
3703
  const span = reviveSpanDates(JSON.parse(dataLine.slice(6)));
3170
3704
  console.log(renderSpanLine(span, 0));
3171
3705
  } catch (err) {
3172
- console.error(chalk20.red("Bad SSE frame: " + (err instanceof Error ? err.message : String(err))));
3706
+ console.error(chalk25.red("Bad SSE frame: " + (err instanceof Error ? err.message : String(err))));
3173
3707
  }
3174
3708
  }
3175
3709
  }
@@ -3184,25 +3718,25 @@ function reviveSpanDates(span) {
3184
3718
 
3185
3719
  // src/commands/memory.ts
3186
3720
  import { writeFileSync as writeFileSync2 } from "fs";
3187
- import chalk22 from "chalk";
3188
- import Enquirer3 from "enquirer";
3721
+ import chalk27 from "chalk";
3722
+ import Enquirer4 from "enquirer";
3189
3723
  import {
3190
3724
  MemoryUserMemoryStore,
3191
3725
  PostgresUserMemoryStore,
3192
- SecretsManager as SecretsManager10,
3193
- createLogger as createLogger15
3726
+ SecretsManager as SecretsManager11,
3727
+ createLogger as createLogger17
3194
3728
  } from "@tuttiai/core";
3195
3729
 
3196
3730
  // src/commands/memory-render.ts
3197
- import chalk21 from "chalk";
3198
- function visibleLen2(s) {
3731
+ import chalk26 from "chalk";
3732
+ function visibleLen3(s) {
3199
3733
  return s.replace(/\u001b\[[0-9;]*m/g, "").length;
3200
3734
  }
3201
- function pad5(s, len) {
3202
- const v = visibleLen2(s);
3735
+ function pad6(s, len) {
3736
+ const v = visibleLen3(s);
3203
3737
  return v >= len ? s : s + " ".repeat(len - v);
3204
3738
  }
3205
- function truncate(text, max) {
3739
+ function truncate2(text, max) {
3206
3740
  const oneLine = text.replace(/\s+/g, " ").trim();
3207
3741
  return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
3208
3742
  }
@@ -3222,23 +3756,23 @@ function sortMemoriesForList(memories) {
3222
3756
  });
3223
3757
  }
3224
3758
  function renderTable(memories, emptyMessage) {
3225
- if (memories.length === 0) return chalk21.dim(emptyMessage);
3759
+ if (memories.length === 0) return chalk26.dim(emptyMessage);
3226
3760
  const lines = [];
3227
3761
  lines.push("");
3228
3762
  lines.push(
3229
- chalk21.dim(
3230
- " " + pad5("ID", 10) + pad5("CONTENT", 62) + pad5("SOURCE", 12) + pad5("IMPORTANCE", 14) + "CREATED"
3763
+ chalk26.dim(
3764
+ " " + pad6("ID", 10) + pad6("CONTENT", 62) + pad6("SOURCE", 12) + pad6("IMPORTANCE", 14) + "CREATED"
3231
3765
  )
3232
3766
  );
3233
- lines.push(chalk21.dim(" " + "\u2500".repeat(110)));
3767
+ lines.push(chalk26.dim(" " + "\u2500".repeat(110)));
3234
3768
  for (const m of memories) {
3235
3769
  const idShort = m.id.slice(0, 8);
3236
- const content = truncate(m.content, 60);
3237
- const sourceColored = m.source === "explicit" ? chalk21.green("explicit") : chalk21.yellow("inferred");
3770
+ const content = truncate2(m.content, 60);
3771
+ const sourceColored = m.source === "explicit" ? chalk26.green("explicit") : chalk26.yellow("inferred");
3238
3772
  const importance = importanceStars(m.importance);
3239
3773
  const created = formatDate(m.created_at);
3240
3774
  lines.push(
3241
- " " + chalk21.bold(pad5(idShort, 10)) + pad5(content, 62) + pad5(sourceColored, 12) + pad5(importance, 14) + chalk21.dim(created)
3775
+ " " + chalk26.bold(pad6(idShort, 10)) + pad6(content, 62) + pad6(sourceColored, 12) + pad6(importance, 14) + chalk26.dim(created)
3242
3776
  );
3243
3777
  }
3244
3778
  lines.push("");
@@ -3251,8 +3785,8 @@ function renderMemoryList(memories, userId) {
3251
3785
  );
3252
3786
  }
3253
3787
  function renderMemorySearch(memories, userId, query) {
3254
- const header = chalk21.dim(
3255
- "Search for " + chalk21.bold('"' + query + '"') + ' in user "' + userId + '" \u2014 ' + memories.length + " result" + (memories.length === 1 ? "" : "s")
3788
+ const header = chalk26.dim(
3789
+ "Search for " + chalk26.bold('"' + query + '"') + ' in user "' + userId + '" \u2014 ' + memories.length + " result" + (memories.length === 1 ? "" : "s")
3256
3790
  );
3257
3791
  const body = renderTable(
3258
3792
  memories,
@@ -3261,13 +3795,13 @@ function renderMemorySearch(memories, userId, query) {
3261
3795
  return header + body;
3262
3796
  }
3263
3797
  function renderMemoryAdded(memory) {
3264
- return chalk21.green("\u2713") + " Stored memory " + chalk21.bold(memory.id.slice(0, 8)) + chalk21.dim(" (" + memory.source + ", " + importanceStars(memory.importance) + ")");
3798
+ return chalk26.green("\u2713") + " Stored memory " + chalk26.bold(memory.id.slice(0, 8)) + chalk26.dim(" (" + memory.source + ", " + importanceStars(memory.importance) + ")");
3265
3799
  }
3266
3800
  function renderMemoryDeleted(memoryId) {
3267
- return chalk21.green("\u2713") + " Deleted memory " + chalk21.bold(memoryId.slice(0, 8));
3801
+ return chalk26.green("\u2713") + " Deleted memory " + chalk26.bold(memoryId.slice(0, 8));
3268
3802
  }
3269
3803
  function renderMemoryCleared(userId, count) {
3270
- return chalk21.green("\u2713") + " Deleted " + chalk21.bold(String(count)) + " memor" + (count === 1 ? "y" : "ies") + ' for user "' + userId + '"';
3804
+ return chalk26.green("\u2713") + " Deleted " + chalk26.bold(String(count)) + " memor" + (count === 1 ? "y" : "ies") + ' for user "' + userId + '"';
3271
3805
  }
3272
3806
  function exportMemoriesJson(memories) {
3273
3807
  return JSON.stringify(memories, null, 2) + "\n";
@@ -3310,14 +3844,14 @@ function csvEscape(field) {
3310
3844
  }
3311
3845
 
3312
3846
  // src/commands/memory.ts
3313
- var logger15 = createLogger15("tutti-cli");
3314
- var { prompt: prompt3 } = Enquirer3;
3847
+ var logger17 = createLogger17("tutti-cli");
3848
+ var { prompt: prompt4 } = Enquirer4;
3315
3849
  function resolveStore3() {
3316
- const pgUrl = SecretsManager10.optional("TUTTI_PG_URL");
3850
+ const pgUrl = SecretsManager11.optional("TUTTI_PG_URL");
3317
3851
  if (pgUrl) {
3318
3852
  return new PostgresUserMemoryStore({ connection_string: pgUrl });
3319
3853
  }
3320
- logger15.warn(
3854
+ logger17.warn(
3321
3855
  "TUTTI_PG_URL not set \u2014 using in-memory store (memories are ephemeral; this command will appear to do nothing useful)"
3322
3856
  );
3323
3857
  return new MemoryUserMemoryStore();
@@ -3329,7 +3863,7 @@ async function closeStore2(store) {
3329
3863
  }
3330
3864
  function requireUser(opts) {
3331
3865
  if (!opts.user || opts.user.trim() === "") {
3332
- console.error(chalk22.red("--user <user-id> is required"));
3866
+ console.error(chalk27.red("--user <user-id> is required"));
3333
3867
  process.exit(1);
3334
3868
  }
3335
3869
  return opts.user.trim();
@@ -3339,7 +3873,7 @@ function parseImportance(raw) {
3339
3873
  if (raw === "1") return 1;
3340
3874
  if (raw === "2") return 2;
3341
3875
  if (raw === "3") return 3;
3342
- console.error(chalk22.red("--importance must be 1, 2, or 3"));
3876
+ console.error(chalk27.red("--importance must be 1, 2, or 3"));
3343
3877
  process.exit(1);
3344
3878
  }
3345
3879
  async function memoryListCommand(opts) {
@@ -3355,7 +3889,7 @@ async function memoryListCommand(opts) {
3355
3889
  async function memorySearchCommand(query, opts) {
3356
3890
  const userId = requireUser(opts);
3357
3891
  if (query.trim() === "") {
3358
- console.error(chalk22.red("Search query is required"));
3892
+ console.error(chalk27.red("Search query is required"));
3359
3893
  process.exit(1);
3360
3894
  }
3361
3895
  const store = resolveStore3();
@@ -3369,7 +3903,7 @@ async function memorySearchCommand(query, opts) {
3369
3903
  async function memoryAddCommand(content, opts) {
3370
3904
  const userId = requireUser(opts);
3371
3905
  if (content.trim() === "") {
3372
- console.error(chalk22.red("Memory content is required"));
3906
+ console.error(chalk27.red("Memory content is required"));
3373
3907
  process.exit(1);
3374
3908
  }
3375
3909
  const importance = parseImportance(opts.importance);
@@ -3400,17 +3934,17 @@ async function memoryClearCommand(opts) {
3400
3934
  try {
3401
3935
  const memories = await store.list(userId);
3402
3936
  if (memories.length === 0) {
3403
- console.log(chalk22.dim('No memories stored for user "' + userId + '".'));
3937
+ console.log(chalk27.dim('No memories stored for user "' + userId + '".'));
3404
3938
  return;
3405
3939
  }
3406
- const { confirm } = await prompt3({
3940
+ const { confirm } = await prompt4({
3407
3941
  type: "confirm",
3408
3942
  name: "confirm",
3409
3943
  message: "Delete all " + memories.length + ' memories for user "' + userId + '"?',
3410
3944
  initial: false
3411
3945
  });
3412
3946
  if (!confirm) {
3413
- console.log(chalk22.dim(" Cancelled."));
3947
+ console.log(chalk27.dim(" Cancelled."));
3414
3948
  return;
3415
3949
  }
3416
3950
  await store.deleteAll(userId);
@@ -3423,7 +3957,7 @@ async function memoryExportCommand(opts) {
3423
3957
  const userId = requireUser(opts);
3424
3958
  const format = (opts.format ?? "json").toLowerCase();
3425
3959
  if (format !== "json" && format !== "csv") {
3426
- console.error(chalk22.red("--format must be 'json' or 'csv'"));
3960
+ console.error(chalk27.red("--format must be 'json' or 'csv'"));
3427
3961
  process.exit(1);
3428
3962
  }
3429
3963
  const store = resolveStore3();
@@ -3433,7 +3967,7 @@ async function memoryExportCommand(opts) {
3433
3967
  if (opts.out) {
3434
3968
  writeFileSync2(opts.out, body, "utf8");
3435
3969
  console.log(
3436
- chalk22.green("\u2713") + " Wrote " + chalk22.bold(String(memories.length)) + " memor" + (memories.length === 1 ? "y" : "ies") + " to " + chalk22.bold(opts.out)
3970
+ chalk27.green("\u2713") + " Wrote " + chalk27.bold(String(memories.length)) + " memor" + (memories.length === 1 ? "y" : "ies") + " to " + chalk27.bold(opts.out)
3437
3971
  );
3438
3972
  } else {
3439
3973
  process.stdout.write(body);
@@ -3444,24 +3978,24 @@ async function memoryExportCommand(opts) {
3444
3978
  }
3445
3979
 
3446
3980
  // src/commands/interrupts.ts
3447
- import chalk24 from "chalk";
3448
- import Enquirer4 from "enquirer";
3449
- import { SecretsManager as SecretsManager11 } from "@tuttiai/core";
3981
+ import chalk29 from "chalk";
3982
+ import Enquirer5 from "enquirer";
3983
+ import { SecretsManager as SecretsManager12 } from "@tuttiai/core";
3450
3984
 
3451
3985
  // src/commands/interrupts-render.ts
3452
- import chalk23 from "chalk";
3453
- function visibleLen3(s) {
3986
+ import chalk28 from "chalk";
3987
+ function visibleLen4(s) {
3454
3988
  return s.replace(/\u001b\[[0-9;]*m/g, "").length;
3455
3989
  }
3456
- function pad6(s, len) {
3457
- const v = visibleLen3(s);
3990
+ function pad7(s, len) {
3991
+ const v = visibleLen4(s);
3458
3992
  return v >= len ? s : s + " ".repeat(len - v);
3459
3993
  }
3460
- function truncate2(text, max) {
3994
+ function truncate3(text, max) {
3461
3995
  const oneLine = text.replace(/\s+/g, " ").trim();
3462
3996
  return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
3463
3997
  }
3464
- function formatIsoShort(d) {
3998
+ function formatIsoShort2(d) {
3465
3999
  const iso = d.toISOString();
3466
4000
  return iso.slice(0, 10) + " " + iso.slice(11, 19);
3467
4001
  }
@@ -3485,28 +4019,28 @@ function truncateArgs(tool_args, max = 80) {
3485
4019
  json = String(tool_args);
3486
4020
  }
3487
4021
  if (json === void 0) return "";
3488
- return truncate2(json, max);
4022
+ return truncate3(json, max);
3489
4023
  }
3490
4024
  function renderInterruptsList(interrupts, now = /* @__PURE__ */ new Date()) {
3491
4025
  if (interrupts.length === 0) {
3492
- return chalk23.dim("No pending interrupts.");
4026
+ return chalk28.dim("No pending interrupts.");
3493
4027
  }
3494
4028
  const lines = [];
3495
4029
  lines.push("");
3496
4030
  lines.push(
3497
- chalk23.dim(
3498
- " " + pad6("ID", 10) + pad6("SESSION", 14) + pad6("TOOL", 22) + pad6("ARGS", 52) + "AGE"
4031
+ chalk28.dim(
4032
+ " " + pad7("ID", 10) + pad7("SESSION", 14) + pad7("TOOL", 22) + pad7("ARGS", 52) + "AGE"
3499
4033
  )
3500
4034
  );
3501
- lines.push(chalk23.dim(" " + "\u2500".repeat(110)));
4035
+ lines.push(chalk28.dim(" " + "\u2500".repeat(110)));
3502
4036
  for (const r of interrupts) {
3503
4037
  const idShort = r.interrupt_id.slice(0, 8);
3504
4038
  const sessionShort = r.session_id.slice(0, 12);
3505
- const toolName = truncate2(r.tool_name, 20);
4039
+ const toolName = truncate3(r.tool_name, 20);
3506
4040
  const argsPreview = truncateArgs(r.tool_args, 50);
3507
4041
  const age = formatRelativeTime(r.requested_at, now);
3508
4042
  lines.push(
3509
- " " + chalk23.bold(pad6(idShort, 10)) + pad6(sessionShort, 14) + pad6(chalk23.cyan(toolName), 22) + pad6(chalk23.dim(argsPreview), 52) + chalk23.dim(age)
4043
+ " " + chalk28.bold(pad7(idShort, 10)) + pad7(sessionShort, 14) + pad7(chalk28.cyan(toolName), 22) + pad7(chalk28.dim(argsPreview), 52) + chalk28.dim(age)
3510
4044
  );
3511
4045
  }
3512
4046
  lines.push("");
@@ -3515,25 +4049,25 @@ function renderInterruptsList(interrupts, now = /* @__PURE__ */ new Date()) {
3515
4049
  function renderInterruptDetail(interrupt, now = /* @__PURE__ */ new Date()) {
3516
4050
  const lines = [];
3517
4051
  lines.push("");
3518
- lines.push(chalk23.bold("Interrupt ") + chalk23.dim(interrupt.interrupt_id));
3519
- lines.push(chalk23.dim("\u2500".repeat(60)));
3520
- lines.push(chalk23.dim("Session: ") + interrupt.session_id);
3521
- lines.push(chalk23.dim("Tool: ") + chalk23.cyan(interrupt.tool_name));
4052
+ lines.push(chalk28.bold("Interrupt ") + chalk28.dim(interrupt.interrupt_id));
4053
+ lines.push(chalk28.dim("\u2500".repeat(60)));
4054
+ lines.push(chalk28.dim("Session: ") + interrupt.session_id);
4055
+ lines.push(chalk28.dim("Tool: ") + chalk28.cyan(interrupt.tool_name));
3522
4056
  lines.push(
3523
- chalk23.dim("Requested: ") + formatIsoShort(interrupt.requested_at) + chalk23.dim(" (" + formatRelativeTime(interrupt.requested_at, now) + ")")
4057
+ chalk28.dim("Requested: ") + formatIsoShort2(interrupt.requested_at) + chalk28.dim(" (" + formatRelativeTime(interrupt.requested_at, now) + ")")
3524
4058
  );
3525
- lines.push(chalk23.dim("Status: ") + colorStatus2(interrupt.status));
4059
+ lines.push(chalk28.dim("Status: ") + colorStatus2(interrupt.status));
3526
4060
  if (interrupt.resolved_at) {
3527
- lines.push(chalk23.dim("Resolved: ") + formatIsoShort(interrupt.resolved_at));
4061
+ lines.push(chalk28.dim("Resolved: ") + formatIsoShort2(interrupt.resolved_at));
3528
4062
  }
3529
4063
  if (interrupt.resolved_by) {
3530
- lines.push(chalk23.dim("Resolved by: ") + interrupt.resolved_by);
4064
+ lines.push(chalk28.dim("Resolved by: ") + interrupt.resolved_by);
3531
4065
  }
3532
4066
  if (interrupt.denial_reason) {
3533
- lines.push(chalk23.dim("Reason: ") + interrupt.denial_reason);
4067
+ lines.push(chalk28.dim("Reason: ") + interrupt.denial_reason);
3534
4068
  }
3535
4069
  lines.push("");
3536
- lines.push(chalk23.dim("Arguments:"));
4070
+ lines.push(chalk28.dim("Arguments:"));
3537
4071
  lines.push(prettyJson(interrupt.tool_args));
3538
4072
  lines.push("");
3539
4073
  return lines.join("\n");
@@ -3546,34 +4080,34 @@ function prettyJson(value) {
3546
4080
  }
3547
4081
  }
3548
4082
  function colorStatus2(status) {
3549
- if (status === "approved") return chalk23.green("approved");
3550
- if (status === "denied") return chalk23.red("denied");
3551
- return chalk23.yellow("pending");
4083
+ if (status === "approved") return chalk28.green("approved");
4084
+ if (status === "denied") return chalk28.red("denied");
4085
+ return chalk28.yellow("pending");
3552
4086
  }
3553
4087
  function renderApproved(interrupt) {
3554
- return chalk23.green("\u2713") + " Approved " + chalk23.bold(interrupt.interrupt_id.slice(0, 8)) + chalk23.dim(" (" + interrupt.tool_name + ")") + (interrupt.resolved_by ? chalk23.dim(" by " + interrupt.resolved_by) : "");
4088
+ return chalk28.green("\u2713") + " Approved " + chalk28.bold(interrupt.interrupt_id.slice(0, 8)) + chalk28.dim(" (" + interrupt.tool_name + ")") + (interrupt.resolved_by ? chalk28.dim(" by " + interrupt.resolved_by) : "");
3555
4089
  }
3556
4090
  function renderDenied(interrupt) {
3557
- return chalk23.red("\u2717") + " Denied " + chalk23.bold(interrupt.interrupt_id.slice(0, 8)) + chalk23.dim(" (" + interrupt.tool_name + ")") + (interrupt.denial_reason ? chalk23.dim(' \u2014 "' + interrupt.denial_reason + '"') : "");
4091
+ return chalk28.red("\u2717") + " Denied " + chalk28.bold(interrupt.interrupt_id.slice(0, 8)) + chalk28.dim(" (" + interrupt.tool_name + ")") + (interrupt.denial_reason ? chalk28.dim(' \u2014 "' + interrupt.denial_reason + '"') : "");
3558
4092
  }
3559
4093
 
3560
4094
  // src/commands/interrupts.ts
3561
- var { prompt: prompt4 } = Enquirer4;
4095
+ var { prompt: prompt5 } = Enquirer5;
3562
4096
  var DEFAULT_SERVER_URL2 = "http://127.0.0.1:3847";
3563
4097
  var POLL_INTERVAL_MS = 2e3;
3564
4098
  function resolveUrl2(opts) {
3565
- return opts.url ?? SecretsManager11.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL2;
4099
+ return opts.url ?? SecretsManager12.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL2;
3566
4100
  }
3567
4101
  function resolveAuthHeader2(opts) {
3568
- const key = opts.apiKey ?? SecretsManager11.optional("TUTTI_API_KEY");
4102
+ const key = opts.apiKey ?? SecretsManager12.optional("TUTTI_API_KEY");
3569
4103
  return key ? { Authorization: "Bearer " + key } : {};
3570
4104
  }
3571
4105
  function explainConnectionError2(err, baseUrl) {
3572
4106
  const msg = err instanceof Error ? err.message : String(err);
3573
- console.error(chalk24.red("Failed to reach Tutti server at " + baseUrl));
3574
- console.error(chalk24.dim(" " + msg));
4107
+ console.error(chalk29.red("Failed to reach Tutti server at " + baseUrl));
4108
+ console.error(chalk29.dim(" " + msg));
3575
4109
  console.error(
3576
- chalk24.dim(" Is `tutti-ai serve` running? Set --url or TUTTI_SERVER_URL to override.")
4110
+ chalk29.dim(" Is `tutti-ai serve` running? Set --url or TUTTI_SERVER_URL to override.")
3577
4111
  );
3578
4112
  process.exit(1);
3579
4113
  }
@@ -3624,19 +4158,19 @@ async function fetchPending(opts) {
3624
4158
  "/interrupts/pending"
3625
4159
  );
3626
4160
  if (status === 401) {
3627
- console.error(chalk24.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
4161
+ console.error(chalk29.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3628
4162
  process.exit(1);
3629
4163
  }
3630
4164
  if (status === 503) {
3631
4165
  console.error(
3632
- chalk24.red(
4166
+ chalk29.red(
3633
4167
  "The server has no InterruptStore configured. Start `tutti-ai serve` with an interrupt store attached."
3634
4168
  )
3635
4169
  );
3636
4170
  process.exit(1);
3637
4171
  }
3638
4172
  if (status < 200 || status >= 300 || !("interrupts" in body)) {
3639
- console.error(chalk24.red("Unexpected server response: " + status));
4173
+ console.error(chalk29.red("Unexpected server response: " + status));
3640
4174
  process.exit(1);
3641
4175
  }
3642
4176
  return body.interrupts.map(reviveInterrupt);
@@ -3649,23 +4183,23 @@ async function postResolve(opts, interruptId, action, payload) {
3649
4183
  payload
3650
4184
  );
3651
4185
  if (status === 401) {
3652
- console.error(chalk24.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
4186
+ console.error(chalk29.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3653
4187
  process.exit(1);
3654
4188
  }
3655
4189
  if (status === 404) {
3656
- console.error(chalk24.red('Interrupt "' + interruptId + '" not found.'));
4190
+ console.error(chalk29.red('Interrupt "' + interruptId + '" not found.'));
3657
4191
  process.exit(1);
3658
4192
  }
3659
4193
  if (status === 409) {
3660
4194
  const current = body.current;
3661
4195
  const currentStatus = current && typeof current["status"] === "string" ? current["status"] : "resolved";
3662
4196
  console.error(
3663
- chalk24.red("Interrupt already " + currentStatus + " \u2014 refusing to override.")
4197
+ chalk29.red("Interrupt already " + currentStatus + " \u2014 refusing to override.")
3664
4198
  );
3665
4199
  process.exit(1);
3666
4200
  }
3667
4201
  if (status < 200 || status >= 300) {
3668
- console.error(chalk24.red("Unexpected server response: " + status));
4202
+ console.error(chalk29.red("Unexpected server response: " + status));
3669
4203
  process.exit(1);
3670
4204
  }
3671
4205
  return reviveInterrupt(body);
@@ -3693,7 +4227,7 @@ function clearScreen() {
3693
4227
  function readKey() {
3694
4228
  const input = process.stdin;
3695
4229
  if (!input.isTTY) return Promise.resolve(null);
3696
- return new Promise((resolve18) => {
4230
+ return new Promise((resolve20) => {
3697
4231
  input.setRawMode(true);
3698
4232
  input.resume();
3699
4233
  input.setEncoding("utf8");
@@ -3701,15 +4235,15 @@ function readKey() {
3701
4235
  input.removeListener("data", onData);
3702
4236
  input.setRawMode(false);
3703
4237
  input.pause();
3704
- resolve18(data);
4238
+ resolve20(data);
3705
4239
  };
3706
4240
  input.on("data", onData);
3707
4241
  });
3708
4242
  }
3709
4243
  async function readKeyOrTimeout(ms) {
3710
4244
  let timer;
3711
- const timeout = new Promise((resolve18) => {
3712
- timer = setTimeout(() => resolve18(null), ms);
4245
+ const timeout = new Promise((resolve20) => {
4246
+ timer = setTimeout(() => resolve20(null), ms);
3713
4247
  });
3714
4248
  try {
3715
4249
  const winner = await Promise.race([readKey(), timeout]);
@@ -3734,17 +4268,17 @@ async function interruptsTUICommand(opts) {
3734
4268
  const pending = await fetchPending(opts);
3735
4269
  clearScreen();
3736
4270
  console.log(
3737
- chalk24.bold("Tutti \u2014 pending interrupts") + chalk24.dim(" (auto-refresh every " + POLL_INTERVAL_MS / 1e3 + "s)")
4271
+ chalk29.bold("Tutti \u2014 pending interrupts") + chalk29.dim(" (auto-refresh every " + POLL_INTERVAL_MS / 1e3 + "s)")
3738
4272
  );
3739
4273
  console.log(renderInterruptsList(pending));
3740
4274
  if (pending.length > 0) {
3741
4275
  console.log(
3742
- chalk24.dim(
4276
+ chalk29.dim(
3743
4277
  "Press a number to inspect, 'r' to refresh, 'q' to quit."
3744
4278
  )
3745
4279
  );
3746
4280
  } else {
3747
- console.log(chalk24.dim("Press 'r' to refresh, 'q' to quit."));
4281
+ console.log(chalk29.dim("Press 'r' to refresh, 'q' to quit."));
3748
4282
  }
3749
4283
  const indexed = pending.slice(0, 9);
3750
4284
  const key = await readKeyOrTimeout(POLL_INTERVAL_MS);
@@ -3767,7 +4301,7 @@ async function runDetailView(opts, interrupt) {
3767
4301
  clearScreen();
3768
4302
  console.log(renderInterruptDetail(interrupt));
3769
4303
  console.log(
3770
- chalk24.dim(
4304
+ chalk29.dim(
3771
4305
  "Press 'a' to approve, 'd' to deny, 'q' to go back to the list."
3772
4306
  )
3773
4307
  );
@@ -3781,7 +4315,7 @@ async function runDetailView(opts, interrupt) {
3781
4315
  return true;
3782
4316
  }
3783
4317
  if (key === "d") {
3784
- const { reason } = await prompt4({
4318
+ const { reason } = await prompt5({
3785
4319
  type: "input",
3786
4320
  name: "reason",
3787
4321
  message: "Reason (optional):"
@@ -3802,19 +4336,19 @@ async function runDetailView(opts, interrupt) {
3802
4336
  return true;
3803
4337
  }
3804
4338
  async function pause() {
3805
- console.log(chalk24.dim("\nPress any key to continue..."));
4339
+ console.log(chalk29.dim("\nPress any key to continue..."));
3806
4340
  await readKey();
3807
4341
  }
3808
4342
 
3809
4343
  // src/index.ts
3810
4344
  config();
3811
- var logger16 = createLogger16("tutti-cli");
4345
+ var logger18 = createLogger18("tutti-cli");
3812
4346
  process.on("unhandledRejection", (reason) => {
3813
- logger16.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
4347
+ logger18.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
3814
4348
  process.exit(1);
3815
4349
  });
3816
4350
  process.on("uncaughtException", (err) => {
3817
- logger16.error({ error: err.message }, "Fatal error");
4351
+ logger18.error({ error: err.message }, "Fatal error");
3818
4352
  process.exit(1);
3819
4353
  });
3820
4354
  var program = new Command();
@@ -3871,9 +4405,30 @@ program.command("voices").description("List all available official voices and in
3871
4405
  program.command("publish").description("Publish the current voice to npm and the voice registry").option("--dry-run", "Run all checks without publishing").action(async (opts) => {
3872
4406
  await publishCommand(opts);
3873
4407
  });
3874
- program.command("eval <suite-file>").description("Run an evaluation suite against a score").option("--ci", "Exit with code 1 if any case fails").option("-s, --score <path>", "Path to score file (default: ./tutti.score.ts)").action(async (suitePath, opts) => {
4408
+ var evalCmd = program.command("eval [suite-file]").description("Run an evaluation suite against a score").option("--ci", "Exit with code 1 if any case fails").option("-s, --score <path>", "Path to score file (default: ./tutti.score.ts)").action(async (suitePath, opts) => {
4409
+ if (!suitePath) {
4410
+ console.error(
4411
+ "Usage: tutti-ai eval <suite-file>\n tutti-ai eval record <session-id>\n tutti-ai eval list"
4412
+ );
4413
+ process.exit(1);
4414
+ }
3875
4415
  await evalCommand(suitePath, opts);
3876
4416
  });
4417
+ evalCmd.command("record <session-id>").description("Promote a past session run to a golden eval case").action(async (sessionId) => {
4418
+ await evalRecordCommand(sessionId);
4419
+ });
4420
+ evalCmd.command("list").description("Show every golden case + latest-run status").action(async () => {
4421
+ await evalListCommand();
4422
+ });
4423
+ evalCmd.command("run").description("Run every golden case (optionally filtered) and report pass/fail").option("--case <id>", "Run only the case with this id (full or 8-char prefix)").option("--tag <tag>", "Run only cases carrying this tag").option("--ci", "Write JUnit XML + exit 1 on failure + drop colors").option("-s, --score <path>", "Path to score file (default: ./tutti.score.ts)").action(async (opts) => {
4424
+ const resolved = {
4425
+ ...opts.case !== void 0 ? { case: opts.case } : {},
4426
+ ...opts.tag !== void 0 ? { tag: opts.tag } : {},
4427
+ ...opts.ci !== void 0 ? { ci: opts.ci } : {},
4428
+ ...opts.score !== void 0 ? { score: opts.score } : {}
4429
+ };
4430
+ await evalRunCommand(resolved);
4431
+ });
3877
4432
  program.command("update").description("Update @tuttiai packages to their latest versions").action(() => {
3878
4433
  updateCommand();
3879
4434
  });