@tuttiai/cli 0.13.0 → 0.14.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 createLogger14 } from "@tuttiai/core";
5
+ import { createLogger as createLogger15 } from "@tuttiai/core";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/init.ts
@@ -718,7 +718,8 @@ async function resumeCommand(sessionId, opts) {
718
718
  }
719
719
  }
720
720
  const agentName = resolveAgentName(score, opts.agent);
721
- const agent = score.agents[agentName];
721
+ const agentMap = new Map(Object.entries(score.agents));
722
+ const agent = agentMap.get(agentName);
722
723
  if (!agent) {
723
724
  logger3.error(
724
725
  { agent: agentName, available: Object.keys(score.agents) },
@@ -938,14 +939,14 @@ import chalk4 from "chalk";
938
939
  import ora3 from "ora";
939
940
  import { createLogger as createLogger4 } from "@tuttiai/core";
940
941
  var logger4 = createLogger4("tutti-cli");
941
- var OFFICIAL_VOICES = {
942
- filesystem: {
942
+ var OFFICIAL_VOICES = /* @__PURE__ */ new Map([
943
+ ["filesystem", {
943
944
  package: "@tuttiai/filesystem",
944
945
  setup: ` Add to your score:
945
946
  ${chalk4.cyan('import { FilesystemVoice } from "@tuttiai/filesystem"')}
946
947
  ${chalk4.cyan("voices: [new FilesystemVoice()]")}`
947
- },
948
- github: {
948
+ }],
949
+ ["github", {
949
950
  package: "@tuttiai/github",
950
951
  setup: ` Add ${chalk4.bold("GITHUB_TOKEN")} to your .env file:
951
952
  ${chalk4.cyan("GITHUB_TOKEN=ghp_your_token_here")}
@@ -953,8 +954,8 @@ var OFFICIAL_VOICES = {
953
954
  Add to your score:
954
955
  ${chalk4.cyan('import { GitHubVoice } from "@tuttiai/github"')}
955
956
  ${chalk4.cyan("voices: [new GitHubVoice()]")}`
956
- },
957
- playwright: {
957
+ }],
958
+ ["playwright", {
958
959
  package: "@tuttiai/playwright",
959
960
  setup: ` Install the browser:
960
961
  ${chalk4.cyan("npx playwright install chromium")}
@@ -962,8 +963,8 @@ var OFFICIAL_VOICES = {
962
963
  Add to your score:
963
964
  ${chalk4.cyan('import { PlaywrightVoice } from "@tuttiai/playwright"')}
964
965
  ${chalk4.cyan("voices: [new PlaywrightVoice()]")}`
965
- },
966
- postgres: {
966
+ }],
967
+ ["postgres", {
967
968
  package: "pg",
968
969
  setup: ` Add ${chalk4.bold("DATABASE_URL")} to your .env file:
969
970
  ${chalk4.cyan("DATABASE_URL=postgres://user:pass@localhost:5432/tutti")}
@@ -976,11 +977,12 @@ var OFFICIAL_VOICES = {
976
977
 
977
978
  Use the async factory for initialization:
978
979
  ${chalk4.cyan("const tutti = await TuttiRuntime.create(score)")}`
979
- }
980
- };
980
+ }]
981
+ ]);
981
982
  function resolvePackageName(input) {
982
- if (OFFICIAL_VOICES[input]) {
983
- return OFFICIAL_VOICES[input].package;
983
+ const voice = OFFICIAL_VOICES.get(input);
984
+ if (voice) {
985
+ return voice.package;
984
986
  }
985
987
  if (input.startsWith("@")) {
986
988
  return input;
@@ -1023,7 +1025,7 @@ function addCommand(voiceName) {
1023
1025
  logger4.error({ error: message, package: packageName }, "Installation failed");
1024
1026
  process.exit(1);
1025
1027
  }
1026
- const official = OFFICIAL_VOICES[voiceName];
1028
+ const official = OFFICIAL_VOICES.get(voiceName);
1027
1029
  if (official) {
1028
1030
  console.log();
1029
1031
  console.log(" Setup:");
@@ -1051,8 +1053,12 @@ import {
1051
1053
  createLogger as createLogger5
1052
1054
  } from "@tuttiai/core";
1053
1055
  var logger5 = createLogger5("tutti-cli");
1054
- var ok = (msg) => console.log(chalk5.green(" \u2714 " + msg));
1055
- var fail = (msg) => console.log(chalk5.red(" \u2718 " + msg));
1056
+ var ok = (msg) => {
1057
+ console.log(chalk5.green(" \u2714 " + msg));
1058
+ };
1059
+ var fail = (msg) => {
1060
+ console.log(chalk5.red(" \u2718 " + msg));
1061
+ };
1056
1062
  async function checkCommand(scorePath) {
1057
1063
  const file = resolve5(scorePath ?? "./tutti.score.ts");
1058
1064
  console.log(chalk5.cyan(`
@@ -1101,10 +1107,10 @@ Checking ${file}...
1101
1107
  for (const [agentKey, agent] of Object.entries(score.agents)) {
1102
1108
  for (const voice of agent.voices) {
1103
1109
  const voiceName = voice.name;
1104
- const voiceEnvMap = {
1105
- github: "GITHUB_TOKEN"
1106
- };
1107
- const envVar = voiceEnvMap[voiceName];
1110
+ const voiceEnvMap = /* @__PURE__ */ new Map([
1111
+ ["github", "GITHUB_TOKEN"]
1112
+ ]);
1113
+ const envVar = voiceEnvMap.get(voiceName);
1108
1114
  if (envVar) {
1109
1115
  const key = SecretsManager3.optional(envVar);
1110
1116
  if (key) {
@@ -1360,8 +1366,8 @@ async function fetchRegistry() {
1360
1366
  }
1361
1367
  }
1362
1368
  function toolCount(name) {
1363
- const counts = { filesystem: 7, github: 10, playwright: 12 };
1364
- return counts[name] ?? 0;
1369
+ const counts = /* @__PURE__ */ new Map([["filesystem", 7], ["github", 10], ["playwright", 12]]);
1370
+ return counts.get(name) ?? 0;
1365
1371
  }
1366
1372
  function matchesQuery(voice, query) {
1367
1373
  const q = query.toLowerCase();
@@ -1464,7 +1470,9 @@ function fail2(msg) {
1464
1470
  console.error(chalk8.red(" " + msg));
1465
1471
  process.exit(1);
1466
1472
  }
1467
- var ok2 = (msg) => console.log(chalk8.green(" \u2714 " + msg));
1473
+ var ok2 = (msg) => {
1474
+ console.log(chalk8.green(" \u2714 " + msg));
1475
+ };
1468
1476
  async function publishCommand(opts) {
1469
1477
  const cwd = process.cwd();
1470
1478
  const pkg = readPkg(cwd);
@@ -1481,8 +1489,8 @@ async function publishCommand(opts) {
1481
1489
  if (!pkg.license) missing.push("license");
1482
1490
  if (!pkg.exports) missing.push("exports");
1483
1491
  if (missing.length > 0) fail2("package.json is missing: " + missing.join(", "));
1484
- const name = pkg.name;
1485
- const version = pkg.version;
1492
+ const name = pkg.name ?? "";
1493
+ const version = pkg.version ?? "";
1486
1494
  const validName = name.startsWith("@tuttiai/") || name.startsWith("tutti");
1487
1495
  if (!validName) fail2("Package name must start with @tuttiai/ or tutti \u2014 got: " + name);
1488
1496
  const src = readFileSync3(resolve8(cwd, "src/index.ts"), "utf-8");
@@ -1527,7 +1535,7 @@ async function publishCommand(opts) {
1527
1535
  console.error(chalk8.dim(" " + msg));
1528
1536
  process.exit(1);
1529
1537
  }
1530
- const fileLines = packOutput.split("\n").filter((l) => l.includes("npm notice") && /\d+(\.\d+)?\s*[kM]?B\s/.test(l)).map((l) => l.replace(/npm notice\s*/, ""));
1538
+ const fileLines = packOutput.split("\n").filter((l) => l.includes("npm notice") && /\d+(?:\.\d+)?\s*[kM]?B\s/.test(l)).map((l) => l.replace(/npm notice\s*/, ""));
1531
1539
  if (fileLines.length > 0) {
1532
1540
  console.log(chalk8.dim(" Files:"));
1533
1541
  for (const line of fileLines) {
@@ -1613,13 +1621,19 @@ async function openRegistryPR(packageName, version, description, token) {
1613
1621
  author: isOfficial ? "tuttiai" : packageName.split("/")[0]?.replace("@", "") ?? "community",
1614
1622
  tags: [shortName]
1615
1623
  };
1616
- if (!registry[section]) registry[section] = [];
1617
- const exists = registry[section].some((v) => v.package === packageName);
1618
- if (exists) {
1619
- const idx = registry[section].findIndex((v) => v.package === packageName);
1620
- registry[section][idx] = { ...registry[section][idx], ...entry };
1624
+ const sectionList = section === "official" ? registry.official : registry.community;
1625
+ const list = sectionList ?? [];
1626
+ if (section === "official") {
1627
+ registry.official = list;
1621
1628
  } else {
1622
- registry[section].push(entry);
1629
+ registry.community = list;
1630
+ }
1631
+ const existingIdx = list.findIndex((v) => v.package === packageName);
1632
+ if (existingIdx >= 0) {
1633
+ const existing = list.at(existingIdx);
1634
+ list.splice(existingIdx, 1, { ...existing, ...entry });
1635
+ } else {
1636
+ list.push(entry);
1623
1637
  }
1624
1638
  const updatedContent = Buffer.from(JSON.stringify(registry, null, 2) + "\n").toString("base64");
1625
1639
  const mainRes = await fetch(
@@ -1764,7 +1778,7 @@ async function serveCommand(scorePath, options = {}) {
1764
1778
  }
1765
1779
  const agentNames = Object.keys(score.agents);
1766
1780
  const agentName = options.agent ?? (typeof score.entry === "string" ? score.entry : void 0) ?? agentNames[0];
1767
- if (!agentName || !score.agents[agentName]) {
1781
+ if (!agentName || !Object.hasOwn(score.agents, agentName)) {
1768
1782
  logger10.error(
1769
1783
  { requested: agentName, available: agentNames },
1770
1784
  "Agent not found in score"
@@ -1785,7 +1799,8 @@ async function serveCommand(scorePath, options = {}) {
1785
1799
  reactive.on("reloaded", () => {
1786
1800
  void (async () => {
1787
1801
  try {
1788
- const nextScore = reactive.current;
1802
+ const nextScore = reactive?.current;
1803
+ if (!nextScore) return;
1789
1804
  const nextRuntime = buildRuntime2(nextScore, sharedSessions);
1790
1805
  const nextApp = await buildApp(nextRuntime, agentName, port, host, options.apiKey);
1791
1806
  await app.close();
@@ -1960,21 +1975,472 @@ async function scheduleCommand(scorePath) {
1960
1975
  await new Promise(() => void 0);
1961
1976
  }
1962
1977
 
1963
- // src/commands/replay.ts
1964
- import { existsSync as existsSync12 } from "fs";
1965
- import { writeFile } from "fs/promises";
1978
+ // src/commands/update.ts
1979
+ import { execSync as execSync3 } from "child_process";
1980
+ import { existsSync as existsSync12, readFileSync as readFileSync5 } from "fs";
1966
1981
  import { resolve as resolve12 } from "path";
1967
- import { createInterface as createInterface3 } from "readline/promises";
1968
1982
  import chalk12 from "chalk";
1969
1983
  import ora7 from "ora";
1984
+ var TUTTI_PACKAGES = [
1985
+ "@tuttiai/core",
1986
+ "@tuttiai/cli",
1987
+ "@tuttiai/types",
1988
+ "@tuttiai/server",
1989
+ "@tuttiai/filesystem",
1990
+ "@tuttiai/github",
1991
+ "@tuttiai/playwright",
1992
+ "@tuttiai/mcp",
1993
+ "@tuttiai/web",
1994
+ "@tuttiai/sandbox",
1995
+ "@tuttiai/rag"
1996
+ ];
1997
+ function getInstalledVersion(pkg) {
1998
+ try {
1999
+ const out = execSync3(`npm list ${pkg} --depth=0 --json 2>/dev/null`, {
2000
+ encoding: "utf-8",
2001
+ stdio: ["pipe", "pipe", "pipe"]
2002
+ });
2003
+ const data = JSON.parse(out);
2004
+ const deps = data.dependencies;
2005
+ if (!deps) return null;
2006
+ for (const [name, info] of Object.entries(deps)) {
2007
+ if (name === pkg && info.version) return info.version;
2008
+ }
2009
+ return null;
2010
+ } catch {
2011
+ return null;
2012
+ }
2013
+ }
2014
+ function getLatestVersion(pkg) {
2015
+ try {
2016
+ return execSync3(`npm view ${pkg} version 2>/dev/null`, {
2017
+ encoding: "utf-8",
2018
+ stdio: ["pipe", "pipe", "pipe"]
2019
+ }).trim();
2020
+ } catch {
2021
+ return null;
2022
+ }
2023
+ }
2024
+ function detectPackageManager() {
2025
+ const cwd = process.cwd();
2026
+ if (existsSync12(resolve12(cwd, "yarn.lock"))) return "yarn";
2027
+ if (existsSync12(resolve12(cwd, "pnpm-lock.yaml"))) return "pnpm";
2028
+ return "npm";
2029
+ }
2030
+ function isGlobalInstall() {
2031
+ try {
2032
+ const globalPrefix = execSync3("npm prefix -g", {
2033
+ encoding: "utf-8",
2034
+ stdio: ["pipe", "pipe", "pipe"]
2035
+ }).trim();
2036
+ return process.argv[1]?.startsWith(globalPrefix) ?? false;
2037
+ } catch {
2038
+ return false;
2039
+ }
2040
+ }
2041
+ function updateCommand() {
2042
+ console.log();
2043
+ console.log(chalk12.cyan.bold(" Tutti Update"));
2044
+ console.log();
2045
+ const spinner = ora7("Checking for updates...").start();
2046
+ const cliCurrent = getInstalledVersion("@tuttiai/cli") ?? "unknown";
2047
+ const cliLatest = getLatestVersion("@tuttiai/cli");
2048
+ spinner.stop();
2049
+ if (cliLatest && cliCurrent !== cliLatest) {
2050
+ console.log(
2051
+ chalk12.yellow(" CLI update available: ") + chalk12.dim(cliCurrent) + " \u2192 " + chalk12.green(cliLatest)
2052
+ );
2053
+ if (isGlobalInstall()) {
2054
+ const updateSpinner2 = ora7("Updating global CLI...").start();
2055
+ try {
2056
+ execSync3("npm install -g tutti-ai@latest", { stdio: "pipe" });
2057
+ updateSpinner2.succeed("CLI updated to " + cliLatest);
2058
+ } catch {
2059
+ updateSpinner2.fail("Failed to update global CLI");
2060
+ console.log(chalk12.dim(" Run manually: npm install -g tutti-ai@latest"));
2061
+ }
2062
+ } else {
2063
+ console.log(chalk12.dim(" Global: npm install -g tutti-ai@latest"));
2064
+ }
2065
+ } else {
2066
+ console.log(chalk12.green(" CLI is up to date") + chalk12.dim(" (" + cliCurrent + ")"));
2067
+ }
2068
+ const pkgPath = resolve12(process.cwd(), "package.json");
2069
+ if (!existsSync12(pkgPath)) {
2070
+ console.log();
2071
+ console.log(chalk12.dim(" No package.json found \u2014 skipping project dependency check."));
2072
+ console.log();
2073
+ return;
2074
+ }
2075
+ let pkg;
2076
+ try {
2077
+ pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
2078
+ } catch {
2079
+ console.log(chalk12.dim(" Could not read package.json"));
2080
+ return;
2081
+ }
2082
+ const allDeps = /* @__PURE__ */ new Map();
2083
+ if (pkg.dependencies) {
2084
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
2085
+ allDeps.set(name, version);
2086
+ }
2087
+ }
2088
+ if (pkg.devDependencies) {
2089
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
2090
+ allDeps.set(name, version);
2091
+ }
2092
+ }
2093
+ const installed = TUTTI_PACKAGES.filter((p) => allDeps.has(p));
2094
+ if (installed.length === 0) {
2095
+ console.log();
2096
+ console.log(chalk12.dim(" No @tuttiai packages found in this project."));
2097
+ console.log();
2098
+ return;
2099
+ }
2100
+ console.log();
2101
+ console.log(" " + chalk12.bold("Project packages:"));
2102
+ const toUpdate = [];
2103
+ for (const name of installed) {
2104
+ const current = allDeps.get(name) ?? "?";
2105
+ const latest = getLatestVersion(name);
2106
+ if (!latest) {
2107
+ console.log(" " + chalk12.dim(name) + " " + current + chalk12.dim(" (could not check)"));
2108
+ continue;
2109
+ }
2110
+ const cleanCurrent = current.replace(/^[\^~]/, "");
2111
+ if (cleanCurrent === latest) {
2112
+ console.log(" " + chalk12.green("\u2714") + " " + name + " " + chalk12.dim(latest));
2113
+ } else {
2114
+ console.log(
2115
+ " " + chalk12.yellow("\u2191") + " " + name + " " + chalk12.dim(cleanCurrent) + " \u2192 " + chalk12.green(latest)
2116
+ );
2117
+ toUpdate.push(name + "@latest");
2118
+ }
2119
+ }
2120
+ if (toUpdate.length === 0) {
2121
+ console.log();
2122
+ console.log(chalk12.green(" All packages are up to date."));
2123
+ console.log();
2124
+ return;
2125
+ }
2126
+ console.log();
2127
+ const pm = detectPackageManager();
2128
+ 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();
2130
+ try {
2131
+ execSync3(installCmd, { cwd: process.cwd(), stdio: "pipe" });
2132
+ updateSpinner.succeed("Updated " + toUpdate.length + " package(s)");
2133
+ } catch {
2134
+ updateSpinner.fail("Update failed");
2135
+ console.log(chalk12.dim(" Run manually: " + installCmd));
2136
+ }
2137
+ console.log();
2138
+ }
2139
+
2140
+ // src/commands/outdated.ts
2141
+ import { execSync as execSync4 } from "child_process";
2142
+ 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";
2146
+ function getLatestVersion2(pkg) {
2147
+ try {
2148
+ return execSync4(`npm view ${pkg} version 2>/dev/null`, {
2149
+ encoding: "utf-8",
2150
+ stdio: ["pipe", "pipe", "pipe"]
2151
+ }).trim();
2152
+ } catch {
2153
+ return null;
2154
+ }
2155
+ }
2156
+ function pad(s, len) {
2157
+ return s.length >= len ? s : s + " ".repeat(len - s.length);
2158
+ }
2159
+ function outdatedCommand() {
2160
+ const pkgPath = resolve13(process.cwd(), "package.json");
2161
+ 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.'));
2164
+ process.exit(1);
2165
+ }
2166
+ let pkg;
2167
+ try {
2168
+ pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2169
+ } catch {
2170
+ console.error(chalk13.red("Could not parse package.json"));
2171
+ process.exit(1);
2172
+ }
2173
+ const allDeps = /* @__PURE__ */ new Map();
2174
+ if (pkg.dependencies) {
2175
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
2176
+ allDeps.set(name, version);
2177
+ }
2178
+ }
2179
+ if (pkg.devDependencies) {
2180
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
2181
+ allDeps.set(name, version);
2182
+ }
2183
+ }
2184
+ const tuttiDeps = [...allDeps.entries()].filter(([name]) => name.startsWith("@tuttiai/"));
2185
+ if (tuttiDeps.length === 0) {
2186
+ console.log(chalk13.dim("No @tuttiai packages found in this project."));
2187
+ return;
2188
+ }
2189
+ const spinner = ora8("Checking npm registry...").start();
2190
+ const results = [];
2191
+ for (const [name, version] of tuttiDeps) {
2192
+ const latest = getLatestVersion2(name);
2193
+ const current = version.replace(/^[\^~]/, "");
2194
+ results.push({
2195
+ name,
2196
+ current,
2197
+ latest: latest ?? "?",
2198
+ outdated: latest !== null && current !== latest
2199
+ });
2200
+ }
2201
+ spinner.stop();
2202
+ console.log();
2203
+ console.log(
2204
+ chalk13.dim(
2205
+ " " + pad("PACKAGE", 28) + pad("CURRENT", 12) + pad("LATEST", 12) + "STATUS"
2206
+ )
2207
+ );
2208
+ console.log(chalk13.dim(" " + "\u2500".repeat(64)));
2209
+ let outdatedCount = 0;
2210
+ for (const r of results) {
2211
+ const status = r.outdated ? chalk13.yellow("update available") : chalk13.green("up to date");
2212
+ if (r.outdated) outdatedCount++;
2213
+ console.log(
2214
+ " " + pad(r.name, 28) + pad(r.current, 12) + pad(r.latest, 12) + status
2215
+ );
2216
+ }
2217
+ console.log();
2218
+ if (outdatedCount > 0) {
2219
+ console.log(
2220
+ chalk13.yellow(" " + outdatedCount + " package(s) can be updated.") + chalk13.dim(" Run: tutti-ai update")
2221
+ );
2222
+ } else {
2223
+ console.log(chalk13.green(" All packages are up to date."));
2224
+ }
2225
+ console.log();
2226
+ }
2227
+
2228
+ // src/commands/info.ts
2229
+ 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) {
2235
+ return s.length >= len ? s : s + " ".repeat(len - s.length);
2236
+ }
2237
+ async function infoCommand(scorePath) {
2238
+ const pkgPath = resolve14(process.cwd(), "package.json");
2239
+ let projectName = "(unknown)";
2240
+ let projectVersion = "(unknown)";
2241
+ const installedDeps = /* @__PURE__ */ new Map();
2242
+ if (existsSync14(pkgPath)) {
2243
+ try {
2244
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
2245
+ projectName = pkg.name ?? "(unnamed)";
2246
+ projectVersion = pkg.version ?? "0.0.0";
2247
+ if (pkg.dependencies) {
2248
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
2249
+ installedDeps.set(name, version);
2250
+ }
2251
+ }
2252
+ if (pkg.devDependencies) {
2253
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
2254
+ installedDeps.set(name, version);
2255
+ }
2256
+ }
2257
+ } catch {
2258
+ }
2259
+ }
2260
+ console.log();
2261
+ console.log(chalk14.cyan.bold(" Tutti Project Info"));
2262
+ console.log();
2263
+ console.log(" " + chalk14.dim("Project:") + " " + chalk14.bold(projectName) + " " + chalk14.dim(projectVersion));
2264
+ const tuttiPkgs = [...installedDeps.entries()].filter(([name]) => name.startsWith("@tuttiai/"));
2265
+ if (tuttiPkgs.length > 0) {
2266
+ console.log();
2267
+ console.log(" " + chalk14.bold("Packages:"));
2268
+ for (const [name, version] of tuttiPkgs) {
2269
+ console.log(" " + pad2(name, 28) + chalk14.dim(version));
2270
+ }
2271
+ }
2272
+ const scoreFile = resolve14(scorePath ?? "./tutti.score.ts");
2273
+ if (!existsSync14(scoreFile)) {
2274
+ 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.'));
2277
+ console.log();
2278
+ return;
2279
+ }
2280
+ let score;
2281
+ try {
2282
+ score = await ScoreLoader8.load(scoreFile);
2283
+ } catch (err) {
2284
+ logger12.error(
2285
+ { error: err instanceof Error ? err.message : String(err) },
2286
+ "Failed to load score"
2287
+ );
2288
+ console.log(chalk14.dim(" Score file found but failed to load."));
2289
+ console.log();
2290
+ return;
2291
+ }
2292
+ console.log(" " + chalk14.dim("Score:") + " " + (score.name ?? scoreFile));
2293
+ const agentEntries = Object.entries(score.agents);
2294
+ console.log();
2295
+ console.log(" " + chalk14.bold("Agents:") + chalk14.dim(" (" + agentEntries.length + ")"));
2296
+ for (const [id, agent] of agentEntries) {
2297
+ const voiceNames = agent.voices.map((v) => v.name).join(", ") || "none";
2298
+ const model = agent.model ?? score.default_model ?? "(default)";
2299
+ const flags = [];
2300
+ if (agent.streaming) flags.push("streaming");
2301
+ if (agent.allow_human_input) flags.push("hitl");
2302
+ if (agent.durable) flags.push("durable");
2303
+ if (agent.schedule) flags.push("scheduled");
2304
+ if (agent.outputSchema) flags.push("structured");
2305
+ if (agent.beforeRun ?? agent.afterRun) flags.push("guardrails");
2306
+ 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);
2310
+ if (flags.length > 0) {
2311
+ console.log(" " + chalk14.dim("Flags: ") + flags.join(", "));
2312
+ }
2313
+ if (agent.schedule) {
2314
+ const sched = agent.schedule;
2315
+ const trigger = sched.cron ?? sched.every ?? sched.at ?? "?";
2316
+ console.log(" " + chalk14.dim("Schedule: ") + trigger);
2317
+ }
2318
+ }
2319
+ if (score.entry) {
2320
+ const entry = typeof score.entry === "string" ? score.entry : "parallel";
2321
+ console.log();
2322
+ console.log(" " + chalk14.dim("Entry:") + " " + entry);
2323
+ }
2324
+ console.log();
2325
+ }
2326
+
2327
+ // src/commands/upgrade.ts
2328
+ import { execSync as execSync5 } from "child_process";
2329
+ 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";
2333
+ function detectPackageManager2() {
2334
+ const cwd = process.cwd();
2335
+ if (existsSync15(resolve15(cwd, "yarn.lock"))) return "yarn";
2336
+ if (existsSync15(resolve15(cwd, "pnpm-lock.yaml"))) return "pnpm";
2337
+ return "npm";
2338
+ }
2339
+ function resolvePackageName2(input) {
2340
+ const KNOWN = /* @__PURE__ */ new Map([
2341
+ ["filesystem", "@tuttiai/filesystem"],
2342
+ ["github", "@tuttiai/github"],
2343
+ ["playwright", "@tuttiai/playwright"],
2344
+ ["mcp", "@tuttiai/mcp"],
2345
+ ["web", "@tuttiai/web"],
2346
+ ["sandbox", "@tuttiai/sandbox"],
2347
+ ["rag", "@tuttiai/rag"],
2348
+ ["core", "@tuttiai/core"],
2349
+ ["types", "@tuttiai/types"],
2350
+ ["server", "@tuttiai/server"]
2351
+ ]);
2352
+ return KNOWN.get(input) ?? (input.startsWith("@") ? input : `@tuttiai/${input}`);
2353
+ }
2354
+ function getInstalledTuttiPackages() {
2355
+ const pkgPath = resolve15(process.cwd(), "package.json");
2356
+ if (!existsSync15(pkgPath)) return /* @__PURE__ */ new Map();
2357
+ try {
2358
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
2359
+ const result = /* @__PURE__ */ new Map();
2360
+ if (pkg.dependencies) {
2361
+ for (const [name, version] of Object.entries(pkg.dependencies)) {
2362
+ if (name.startsWith("@tuttiai/")) result.set(name, version);
2363
+ }
2364
+ }
2365
+ if (pkg.devDependencies) {
2366
+ for (const [name, version] of Object.entries(pkg.devDependencies)) {
2367
+ if (name.startsWith("@tuttiai/")) result.set(name, version);
2368
+ }
2369
+ }
2370
+ return result;
2371
+ } catch {
2372
+ return /* @__PURE__ */ new Map();
2373
+ }
2374
+ }
2375
+ function upgradeCommand(target) {
2376
+ const pkgPath = resolve15(process.cwd(), "package.json");
2377
+ 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.'));
2380
+ process.exit(1);
2381
+ }
2382
+ const pm = detectPackageManager2();
2383
+ const installed = getInstalledTuttiPackages();
2384
+ if (installed.size === 0) {
2385
+ console.log(chalk15.dim("No @tuttiai packages found in this project."));
2386
+ return;
2387
+ }
2388
+ let packages;
2389
+ if (target) {
2390
+ const resolved = resolvePackageName2(target);
2391
+ 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(", ")));
2394
+ process.exit(1);
2395
+ }
2396
+ packages = [resolved + "@latest"];
2397
+ console.log(chalk15.cyan(" Upgrading " + resolved + "..."));
2398
+ } else {
2399
+ packages = [...installed.keys()].map((p) => p + "@latest");
2400
+ console.log(chalk15.cyan(" Upgrading all " + packages.length + " @tuttiai packages..."));
2401
+ }
2402
+ const installCmd = pm === "yarn" ? "yarn add " + packages.join(" ") : pm === "pnpm" ? "pnpm add " + packages.join(" ") : "npm install " + packages.join(" ");
2403
+ const spinner = ora9("Installing...").start();
2404
+ try {
2405
+ execSync5(installCmd, { cwd: process.cwd(), stdio: "pipe" });
2406
+ spinner.succeed("Upgraded " + packages.length + " package(s)");
2407
+ } catch {
2408
+ spinner.fail("Upgrade failed");
2409
+ console.log(chalk15.dim(" Run manually: " + installCmd));
2410
+ process.exit(1);
2411
+ }
2412
+ console.log();
2413
+ const newInstalled = getInstalledTuttiPackages();
2414
+ for (const [name, oldVersion] of installed) {
2415
+ const newVersion = newInstalled.get(name) ?? oldVersion;
2416
+ const oldClean = oldVersion.replace(/^[\^~]/, "");
2417
+ const newClean = newVersion.replace(/^[\^~]/, "");
2418
+ if (oldClean !== newClean) {
2419
+ console.log(" " + chalk15.green("\u2191") + " " + name + " " + chalk15.dim(oldClean) + " \u2192 " + chalk15.green(newClean));
2420
+ } else {
2421
+ console.log(" " + chalk15.dim("=") + " " + name + " " + chalk15.dim(newClean) + chalk15.dim(" (already latest)"));
2422
+ }
2423
+ }
2424
+ console.log();
2425
+ }
2426
+
2427
+ // src/commands/replay.ts
2428
+ import { existsSync as existsSync16 } from "fs";
2429
+ import { writeFile } from "fs/promises";
2430
+ import { resolve as resolve16 } from "path";
2431
+ import { createInterface as createInterface3 } from "readline/promises";
2432
+ import chalk17 from "chalk";
2433
+ import ora10 from "ora";
1970
2434
  import {
1971
2435
  PostgresSessionStore,
1972
- ScoreLoader as ScoreLoader8,
2436
+ ScoreLoader as ScoreLoader9,
1973
2437
  TuttiRuntime as TuttiRuntime5,
1974
2438
  SecretsManager as SecretsManager7,
1975
- createLogger as createLogger12
2439
+ createLogger as createLogger13
1976
2440
  } from "@tuttiai/core";
1977
- var logger12 = createLogger12("tutti-cli");
2441
+
2442
+ // src/commands/replay-render.ts
2443
+ import chalk16 from "chalk";
1978
2444
  function messageToText2(msg) {
1979
2445
  if (typeof msg.content === "string") return msg.content;
1980
2446
  const parts = [];
@@ -1997,22 +2463,24 @@ function excerpt2(text, max) {
1997
2463
  function renderList(messages) {
1998
2464
  const lines = [];
1999
2465
  for (let i = 0; i < messages.length; i++) {
2000
- const msg = messages[i];
2001
- const role = msg.role === "user" ? chalk12.blue("user ") : chalk12.green("assistant");
2466
+ const msg = messages.at(i);
2467
+ if (!msg) continue;
2468
+ const role = msg.role === "user" ? chalk16.blue("user ") : chalk16.green("assistant");
2002
2469
  const text = excerpt2(messageToText2(msg), 80);
2003
2470
  lines.push(
2004
- chalk12.dim(String(i).padStart(3)) + " " + role + " " + text
2471
+ chalk16.dim(String(i).padStart(3)) + " " + role + " " + text
2005
2472
  );
2006
2473
  }
2007
2474
  return lines.join("\n");
2008
2475
  }
2009
2476
  function renderShow(messages, index) {
2010
2477
  if (index < 0 || index >= messages.length) {
2011
- return chalk12.red("Index out of range. Valid: 0\u2013" + (messages.length - 1));
2478
+ return chalk16.red("Index out of range. Valid: 0\u2013" + (messages.length - 1));
2012
2479
  }
2013
- const msg = messages[index];
2480
+ const msg = messages.at(index);
2481
+ if (!msg) return chalk16.red("Index out of range.");
2014
2482
  const lines = [];
2015
- lines.push(chalk12.cyan.bold("Turn " + index) + " " + chalk12.dim("[" + msg.role + "]"));
2483
+ lines.push(chalk16.cyan.bold("Turn " + index) + " " + chalk16.dim("[" + msg.role + "]"));
2016
2484
  lines.push("");
2017
2485
  if (typeof msg.content === "string") {
2018
2486
  lines.push(msg.content);
@@ -2021,13 +2489,13 @@ function renderShow(messages, index) {
2021
2489
  if (block.type === "text") {
2022
2490
  lines.push(block.text);
2023
2491
  } else if (block.type === "tool_use") {
2024
- lines.push(chalk12.yellow(" tool_use: " + block.name));
2025
- lines.push(chalk12.dim(" id: " + block.id));
2026
- lines.push(chalk12.dim(" input: " + JSON.stringify(block.input, null, 2)));
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)));
2027
2495
  } else if (block.type === "tool_result") {
2028
- const label = block.is_error ? chalk12.red(" tool_result (error):") : chalk12.green(" tool_result:");
2496
+ const label = block.is_error ? chalk16.red(" tool_result (error):") : chalk16.green(" tool_result:");
2029
2497
  lines.push(label);
2030
- lines.push(chalk12.dim(" tool_use_id: " + block.tool_use_id));
2498
+ lines.push(chalk16.dim(" tool_use_id: " + block.tool_use_id));
2031
2499
  lines.push(" " + block.content);
2032
2500
  }
2033
2501
  }
@@ -2036,9 +2504,9 @@ function renderShow(messages, index) {
2036
2504
  }
2037
2505
  function renderInspect(messages, index) {
2038
2506
  if (index < 0 || index >= messages.length) {
2039
- return chalk12.red("Index out of range.");
2507
+ return chalk16.red("Index out of range.");
2040
2508
  }
2041
- return JSON.stringify(messages[index], null, 2);
2509
+ return JSON.stringify(messages.at(index), null, 2);
2042
2510
  }
2043
2511
  function exportJSON(session) {
2044
2512
  return JSON.stringify(
@@ -2063,7 +2531,8 @@ function exportMarkdown(session) {
2063
2531
  lines.push("---");
2064
2532
  lines.push("");
2065
2533
  for (let i = 0; i < session.messages.length; i++) {
2066
- const msg = session.messages[i];
2534
+ const msg = session.messages.at(i);
2535
+ if (!msg) continue;
2067
2536
  lines.push("## Turn " + i + " (" + msg.role + ")");
2068
2537
  lines.push("");
2069
2538
  if (typeof msg.content === "string") {
@@ -2086,25 +2555,28 @@ function exportMarkdown(session) {
2086
2555
  }
2087
2556
  return lines.join("\n");
2088
2557
  }
2558
+
2559
+ // src/commands/replay.ts
2560
+ var logger13 = createLogger13("tutti-cli");
2089
2561
  async function replayCommand(sessionId, opts = {}) {
2090
2562
  const pgUrl = SecretsManager7.optional("TUTTI_PG_URL");
2091
2563
  if (!pgUrl) {
2092
- console.error(chalk12.red("TUTTI_PG_URL is not set."));
2564
+ console.error(chalk17.red("TUTTI_PG_URL is not set."));
2093
2565
  console.error(
2094
- chalk12.dim(
2566
+ chalk17.dim(
2095
2567
  "The replay command requires PostgreSQL for session persistence.\nSet TUTTI_PG_URL=postgres://user:pass@host/db in your environment."
2096
2568
  )
2097
2569
  );
2098
2570
  process.exit(1);
2099
2571
  }
2100
2572
  const store = new PostgresSessionStore(pgUrl);
2101
- const spinner = ora7({ color: "cyan" }).start("Loading session...");
2573
+ const spinner = ora10({ color: "cyan" }).start("Loading session...");
2102
2574
  let session;
2103
2575
  try {
2104
2576
  session = await store.getAsync(sessionId);
2105
2577
  } catch (err) {
2106
2578
  spinner.fail("Failed to load session");
2107
- logger12.error(
2579
+ logger13.error(
2108
2580
  { error: err instanceof Error ? err.message : String(err) },
2109
2581
  "Session store error"
2110
2582
  );
@@ -2112,26 +2584,26 @@ async function replayCommand(sessionId, opts = {}) {
2112
2584
  }
2113
2585
  spinner.stop();
2114
2586
  if (!session) {
2115
- console.error(chalk12.red("Session not found: " + sessionId));
2116
- console.error(chalk12.dim("Check the session ID and ensure TUTTI_PG_URL points to the correct database."));
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."));
2117
2589
  await store.close();
2118
2590
  process.exit(1);
2119
2591
  }
2120
2592
  const messages = session.messages;
2121
2593
  console.log("");
2122
- console.log(chalk12.cyan.bold(" Tutti Replay"));
2123
- console.log(chalk12.dim(" Session: " + session.id));
2124
- console.log(chalk12.dim(" Agent: " + session.agent_name));
2125
- console.log(chalk12.dim(" Created: " + session.created_at.toISOString()));
2126
- console.log(chalk12.dim(" Messages: " + messages.length));
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));
2127
2599
  console.log("");
2128
- console.log(chalk12.dim(" Commands: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit"));
2600
+ console.log(chalk17.dim(" Commands: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit"));
2129
2601
  console.log("");
2130
2602
  let cursor = 0;
2131
2603
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2132
2604
  try {
2133
2605
  while (true) {
2134
- const prompt3 = chalk12.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2606
+ const prompt3 = chalk17.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2135
2607
  const raw = await rl.question(prompt3);
2136
2608
  const input = raw.trim();
2137
2609
  if (!input) continue;
@@ -2140,7 +2612,7 @@ async function replayCommand(sessionId, opts = {}) {
2140
2612
  case "quit":
2141
2613
  case "exit":
2142
2614
  case "q":
2143
- console.log(chalk12.dim("Bye."));
2615
+ console.log(chalk17.dim("Bye."));
2144
2616
  return;
2145
2617
  case "list":
2146
2618
  console.log(renderList(messages));
@@ -2156,7 +2628,7 @@ async function replayCommand(sessionId, opts = {}) {
2156
2628
  cursor++;
2157
2629
  console.log(renderShow(messages, cursor));
2158
2630
  } else {
2159
- console.log(chalk12.dim("Already at last message."));
2631
+ console.log(chalk17.dim("Already at last message."));
2160
2632
  }
2161
2633
  break;
2162
2634
  case "prev":
@@ -2164,7 +2636,7 @@ async function replayCommand(sessionId, opts = {}) {
2164
2636
  cursor--;
2165
2637
  console.log(renderShow(messages, cursor));
2166
2638
  } else {
2167
- console.log(chalk12.dim("Already at first message."));
2639
+ console.log(chalk17.dim("Already at first message."));
2168
2640
  }
2169
2641
  break;
2170
2642
  case "inspect":
@@ -2173,7 +2645,7 @@ async function replayCommand(sessionId, opts = {}) {
2173
2645
  case "replay-from": {
2174
2646
  const turn = parseInt(args[0] ?? "", 10);
2175
2647
  if (isNaN(turn) || turn < 0 || turn >= messages.length) {
2176
- console.log(chalk12.red("Usage: replay-from <turn-number>"));
2648
+ console.log(chalk17.red("Usage: replay-from <turn-number>"));
2177
2649
  break;
2178
2650
  }
2179
2651
  await handleReplayFrom(turn, session, rl, opts);
@@ -2184,19 +2656,19 @@ async function replayCommand(sessionId, opts = {}) {
2184
2656
  if (format === "json") {
2185
2657
  const filename = "session-" + session.id.slice(0, 8) + ".json";
2186
2658
  await writeFile(filename, exportJSON(session));
2187
- console.log(chalk12.green("Exported to " + filename));
2659
+ console.log(chalk17.green("Exported to " + filename));
2188
2660
  } else if (format === "md" || format === "markdown") {
2189
2661
  const filename = "session-" + session.id.slice(0, 8) + ".md";
2190
2662
  await writeFile(filename, exportMarkdown(session));
2191
- console.log(chalk12.green("Exported to " + filename));
2663
+ console.log(chalk17.green("Exported to " + filename));
2192
2664
  } else {
2193
- console.log(chalk12.dim("Usage: export <json|md>"));
2665
+ console.log(chalk17.dim("Usage: export <json|md>"));
2194
2666
  }
2195
2667
  break;
2196
2668
  }
2197
2669
  default:
2198
2670
  console.log(
2199
- chalk12.dim("Unknown command. Available: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit")
2671
+ chalk17.dim("Unknown command. Available: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit")
2200
2672
  );
2201
2673
  }
2202
2674
  }
@@ -2206,29 +2678,29 @@ async function replayCommand(sessionId, opts = {}) {
2206
2678
  }
2207
2679
  }
2208
2680
  async function handleReplayFrom(turn, session, rl, opts) {
2209
- const scoreFile = resolve12(opts.score ?? "./tutti.score.ts");
2210
- if (!existsSync12(scoreFile)) {
2211
- console.log(chalk12.red("Score file not found: " + scoreFile));
2212
- console.log(chalk12.dim("Use --score to specify the score file."));
2681
+ const scoreFile = resolve16(opts.score ?? "./tutti.score.ts");
2682
+ 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."));
2213
2685
  return;
2214
2686
  }
2215
- const originalMsg = session.messages[turn];
2687
+ const originalMsg = session.messages.at(turn);
2216
2688
  const originalInput = originalMsg ? messageToText2(originalMsg) : "";
2217
2689
  const answer = await rl.question(
2218
- chalk12.cyan("Replay from turn " + turn + " with original input? ") + chalk12.dim("(y / enter new input) ")
2690
+ chalk17.cyan("Replay from turn " + turn + " with original input? ") + chalk17.dim("(y / enter new input) ")
2219
2691
  );
2220
2692
  const input = answer.trim().toLowerCase() === "y" || answer.trim() === "" ? originalInput : answer.trim();
2221
2693
  if (!input) {
2222
- console.log(chalk12.dim("No input provided. Cancelled."));
2694
+ console.log(chalk17.dim("No input provided. Cancelled."));
2223
2695
  return;
2224
2696
  }
2225
- const spinnerLoad = ora7({ color: "cyan" }).start("Loading score...");
2697
+ const spinnerLoad = ora10({ color: "cyan" }).start("Loading score...");
2226
2698
  let score;
2227
2699
  try {
2228
- score = await ScoreLoader8.load(scoreFile);
2700
+ score = await ScoreLoader9.load(scoreFile);
2229
2701
  } catch (err) {
2230
2702
  spinnerLoad.fail("Failed to load score");
2231
- logger12.error({ error: err instanceof Error ? err.message : String(err) }, "Score load error");
2703
+ logger13.error({ error: err instanceof Error ? err.message : String(err) }, "Score load error");
2232
2704
  return;
2233
2705
  }
2234
2706
  spinnerLoad.stop();
@@ -2245,12 +2717,13 @@ async function handleReplayFrom(turn, session, rl, opts) {
2245
2717
  });
2246
2718
  }
2247
2719
  const agentName = session.agent_name;
2248
- const agent = score.agents[agentName];
2720
+ const agentMap = new Map(Object.entries(score.agents));
2721
+ const agent = agentMap.get(agentName);
2249
2722
  if (!agent) {
2250
- console.log(chalk12.red('Agent "' + agentName + '" not found in score.'));
2723
+ console.log(chalk17.red('Agent "' + agentName + '" not found in score.'));
2251
2724
  return;
2252
2725
  }
2253
- const spinnerRun = ora7({ color: "cyan" }).start("Running from turn " + turn + "...");
2726
+ const spinnerRun = ora10({ color: "cyan" }).start("Running from turn " + turn + "...");
2254
2727
  runtime.events.on("token:stream", (e) => {
2255
2728
  spinnerRun.stop();
2256
2729
  process.stdout.write(e.text);
@@ -2259,23 +2732,23 @@ async function handleReplayFrom(turn, session, rl, opts) {
2259
2732
  const result = await runtime.run(agentName, input, session.id);
2260
2733
  spinnerRun.stop();
2261
2734
  console.log("");
2262
- console.log(chalk12.green("Replay complete."));
2263
- console.log(chalk12.dim(" Turns: " + result.turns));
2264
- console.log(chalk12.dim(" Tokens: " + result.usage.input_tokens + " in / " + result.usage.output_tokens + " out"));
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"));
2265
2738
  console.log("");
2266
2739
  console.log(result.output);
2267
2740
  } catch (err) {
2268
2741
  spinnerRun.fail("Replay failed");
2269
- logger12.error({ error: err instanceof Error ? err.message : String(err) }, "Replay error");
2742
+ logger13.error({ error: err instanceof Error ? err.message : String(err) }, "Replay error");
2270
2743
  }
2271
2744
  }
2272
2745
 
2273
2746
  // src/commands/schedules.ts
2274
- import { existsSync as existsSync13 } from "fs";
2275
- import { resolve as resolve13 } from "path";
2276
- import chalk13 from "chalk";
2747
+ import { existsSync as existsSync17 } from "fs";
2748
+ import { resolve as resolve17 } from "path";
2749
+ import chalk18 from "chalk";
2277
2750
  import {
2278
- ScoreLoader as ScoreLoader9,
2751
+ ScoreLoader as ScoreLoader10,
2279
2752
  SchedulerEngine as SchedulerEngine2,
2280
2753
  PostgresScheduleStore as PostgresScheduleStore2,
2281
2754
  MemoryScheduleStore as MemoryScheduleStore2,
@@ -2283,15 +2756,15 @@ import {
2283
2756
  EventBus as EventBus2,
2284
2757
  InMemorySessionStore as InMemorySessionStore4,
2285
2758
  SecretsManager as SecretsManager8,
2286
- createLogger as createLogger13
2759
+ createLogger as createLogger14
2287
2760
  } from "@tuttiai/core";
2288
- var logger13 = createLogger13("tutti-cli");
2761
+ var logger14 = createLogger14("tutti-cli");
2289
2762
  function resolveStore2() {
2290
2763
  const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
2291
2764
  if (pgUrl) {
2292
2765
  return new PostgresScheduleStore2({ connection_string: pgUrl });
2293
2766
  }
2294
- logger13.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
2767
+ logger14.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
2295
2768
  return new MemoryScheduleStore2();
2296
2769
  }
2297
2770
  async function closeStore(store) {
@@ -2305,7 +2778,7 @@ function formatTrigger(r) {
2305
2778
  if (r.config.at) return "at " + r.config.at;
2306
2779
  return "?";
2307
2780
  }
2308
- function pad(s, len) {
2781
+ function pad3(s, len) {
2309
2782
  return s.length >= len ? s : s + " ".repeat(len - s.length);
2310
2783
  }
2311
2784
  async function schedulesListCommand() {
@@ -2313,22 +2786,22 @@ async function schedulesListCommand() {
2313
2786
  try {
2314
2787
  const records = await store.list();
2315
2788
  if (records.length === 0) {
2316
- console.log(chalk13.dim("No schedules found."));
2317
- console.log(chalk13.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
2789
+ console.log(chalk18.dim("No schedules found."));
2790
+ console.log(chalk18.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
2318
2791
  return;
2319
2792
  }
2320
2793
  console.log("");
2321
2794
  console.log(
2322
- chalk13.dim(
2323
- " " + pad("ID", 20) + pad("AGENT", 16) + pad("TRIGGER", 22) + pad("ENABLED", 10) + pad("RUNS", 8) + "CREATED"
2795
+ chalk18.dim(
2796
+ " " + pad3("ID", 20) + pad3("AGENT", 16) + pad3("TRIGGER", 22) + pad3("ENABLED", 10) + pad3("RUNS", 8) + "CREATED"
2324
2797
  )
2325
2798
  );
2326
- console.log(chalk13.dim(" " + "\u2500".repeat(90)));
2799
+ console.log(chalk18.dim(" " + "\u2500".repeat(90)));
2327
2800
  for (const r of records) {
2328
- const enabled = r.enabled ? chalk13.green("yes") : chalk13.red("no") + " ";
2801
+ const enabled = r.enabled ? chalk18.green("yes") : chalk18.red("no") + " ";
2329
2802
  const maxLabel = r.config.max_runs ? r.run_count + "/" + r.config.max_runs : String(r.run_count);
2330
2803
  console.log(
2331
- " " + chalk13.bold(pad(r.id, 20)) + pad(r.agent_id, 16) + pad(formatTrigger(r), 22) + pad(enabled, 10) + pad(maxLabel, 8) + chalk13.dim(r.created_at.toISOString().slice(0, 10))
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))
2332
2805
  );
2333
2806
  }
2334
2807
  console.log("");
@@ -2341,11 +2814,11 @@ async function schedulesEnableCommand(id) {
2341
2814
  try {
2342
2815
  const record = await store.get(id);
2343
2816
  if (!record) {
2344
- console.error(chalk13.red('Schedule "' + id + '" not found.'));
2817
+ console.error(chalk18.red('Schedule "' + id + '" not found.'));
2345
2818
  process.exit(1);
2346
2819
  }
2347
2820
  await store.setEnabled(id, true);
2348
- console.log(chalk13.green('Schedule "' + id + '" enabled.'));
2821
+ console.log(chalk18.green('Schedule "' + id + '" enabled.'));
2349
2822
  } finally {
2350
2823
  await closeStore(store);
2351
2824
  }
@@ -2355,22 +2828,22 @@ async function schedulesDisableCommand(id) {
2355
2828
  try {
2356
2829
  const record = await store.get(id);
2357
2830
  if (!record) {
2358
- console.error(chalk13.red('Schedule "' + id + '" not found.'));
2831
+ console.error(chalk18.red('Schedule "' + id + '" not found.'));
2359
2832
  process.exit(1);
2360
2833
  }
2361
2834
  await store.setEnabled(id, false);
2362
- console.log(chalk13.yellow('Schedule "' + id + '" disabled.'));
2835
+ console.log(chalk18.yellow('Schedule "' + id + '" disabled.'));
2363
2836
  } finally {
2364
2837
  await closeStore(store);
2365
2838
  }
2366
2839
  }
2367
2840
  async function schedulesTriggerCommand(id, scorePath) {
2368
- const file = resolve13(scorePath ?? "./tutti.score.ts");
2369
- if (!existsSync13(file)) {
2370
- console.error(chalk13.red("Score file not found: " + file));
2841
+ const file = resolve17(scorePath ?? "./tutti.score.ts");
2842
+ if (!existsSync17(file)) {
2843
+ console.error(chalk18.red("Score file not found: " + file));
2371
2844
  process.exit(1);
2372
2845
  }
2373
- const score = await ScoreLoader9.load(file);
2846
+ const score = await ScoreLoader10.load(file);
2374
2847
  const events = new EventBus2();
2375
2848
  const sessions = new InMemorySessionStore4();
2376
2849
  const runner = new AgentRunner2(score.provider, events, sessions);
@@ -2378,30 +2851,30 @@ async function schedulesTriggerCommand(id, scorePath) {
2378
2851
  try {
2379
2852
  const record = await store.get(id);
2380
2853
  if (!record) {
2381
- console.error(chalk13.red('Schedule "' + id + '" not found.'));
2854
+ console.error(chalk18.red('Schedule "' + id + '" not found.'));
2382
2855
  process.exit(1);
2383
2856
  }
2384
2857
  const agent = score.agents[record.agent_id];
2385
2858
  if (!agent) {
2386
- console.error(chalk13.red('Agent "' + record.agent_id + '" not found in score.'));
2859
+ console.error(chalk18.red('Agent "' + record.agent_id + '" not found in score.'));
2387
2860
  process.exit(1);
2388
2861
  }
2389
2862
  const resolvedAgent = agent.model ? agent : { ...agent, model: score.default_model ?? "claude-sonnet-4-20250514" };
2390
2863
  const engine = new SchedulerEngine2(store, runner, events);
2391
2864
  await engine.schedule(id, resolvedAgent, record.config);
2392
2865
  engine.start();
2393
- console.log(chalk13.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
2866
+ console.log(chalk18.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
2394
2867
  const run2 = await engine.trigger(id);
2395
2868
  engine.stop();
2396
2869
  if (run2.error) {
2397
- console.log(chalk13.red(" Error: " + run2.error));
2870
+ console.log(chalk18.red(" Error: " + run2.error));
2398
2871
  process.exit(1);
2399
2872
  }
2400
2873
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() : 0;
2401
- console.log(chalk13.green(" Completed in " + duration + "ms"));
2874
+ console.log(chalk18.green(" Completed in " + duration + "ms"));
2402
2875
  if (run2.result) {
2403
2876
  const preview = run2.result.length > 200 ? run2.result.slice(0, 200) + "..." : run2.result;
2404
- console.log(chalk13.dim(" Output: " + preview));
2877
+ console.log(chalk18.dim(" Output: " + preview));
2405
2878
  }
2406
2879
  } finally {
2407
2880
  await closeStore(store);
@@ -2412,46 +2885,312 @@ async function schedulesRunsCommand(id) {
2412
2885
  try {
2413
2886
  const record = await store.get(id);
2414
2887
  if (!record) {
2415
- console.error(chalk13.red('Schedule "' + id + '" not found.'));
2888
+ console.error(chalk18.red('Schedule "' + id + '" not found.'));
2416
2889
  process.exit(1);
2417
2890
  }
2418
2891
  if ("getRuns" in store && typeof store.getRuns === "function") {
2419
2892
  const runs = store.getRuns(id);
2420
2893
  if (runs.length === 0) {
2421
- console.log(chalk13.dim("No runs recorded for this schedule."));
2894
+ console.log(chalk18.dim("No runs recorded for this schedule."));
2422
2895
  return;
2423
2896
  }
2424
2897
  const recent = runs.slice(-20);
2425
2898
  console.log("");
2426
- console.log(chalk13.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
2899
+ console.log(chalk18.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
2427
2900
  console.log("");
2428
2901
  for (const run2 of recent) {
2429
2902
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() + "ms" : "?";
2430
- const status = run2.error ? chalk13.red("error") : chalk13.green("ok");
2903
+ const status = run2.error ? chalk18.red("error") : chalk18.green("ok");
2431
2904
  const preview = run2.error ? run2.error.slice(0, 80) : (run2.result ?? "").slice(0, 80);
2432
2905
  console.log(
2433
- " " + chalk13.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk13.dim(duration) + " " + preview
2906
+ " " + chalk18.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk18.dim(duration) + " " + preview
2434
2907
  );
2435
2908
  }
2436
2909
  console.log("");
2437
2910
  } else {
2438
- console.log(chalk13.dim('Schedule "' + id + '" has completed ' + record.run_count + " runs."));
2439
- console.log(chalk13.dim("Full run history requires the MemoryScheduleStore or a future tutti_schedule_runs table."));
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."));
2440
2913
  }
2441
2914
  } finally {
2442
2915
  await closeStore(store);
2443
2916
  }
2444
2917
  }
2445
2918
 
2919
+ // src/commands/traces.ts
2920
+ import chalk20 from "chalk";
2921
+ import { SecretsManager as SecretsManager9 } from "@tuttiai/core";
2922
+
2923
+ // src/commands/traces-render.ts
2924
+ import chalk19 from "chalk";
2925
+ function visibleLen(s) {
2926
+ return s.replace(/\u001b\[[0-9;]*m/g, "").length;
2927
+ }
2928
+ function pad4(s, len) {
2929
+ const v = visibleLen(s);
2930
+ return v >= len ? s : s + " ".repeat(len - v);
2931
+ }
2932
+ function colorStatus(status) {
2933
+ if (status === "ok") return chalk19.green("ok");
2934
+ if (status === "error") return chalk19.red("error");
2935
+ return chalk19.yellow("running");
2936
+ }
2937
+ function formatCost(cost) {
2938
+ if (cost === null) return chalk19.dim("\u2014");
2939
+ if (cost === 0) return "$0";
2940
+ return "$" + cost.toFixed(6);
2941
+ }
2942
+ function formatTokens(n) {
2943
+ return n > 0 ? String(n) : chalk19.dim("\u2014");
2944
+ }
2945
+ function formatDuration(ms) {
2946
+ if (ms === null) return chalk19.dim("\u2014");
2947
+ return ms + "ms";
2948
+ }
2949
+ function renderTracesList(traces) {
2950
+ if (traces.length === 0) {
2951
+ return chalk19.dim("No traces found.");
2952
+ }
2953
+ const lines = [];
2954
+ lines.push("");
2955
+ 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"
2958
+ )
2959
+ );
2960
+ lines.push(chalk19.dim(" " + "\u2500".repeat(80)));
2961
+ for (const t of traces) {
2962
+ const traceShort = t.trace_id.slice(0, 8);
2963
+ const startedShort = t.started_at.slice(11, 19);
2964
+ 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)
2966
+ );
2967
+ }
2968
+ lines.push("");
2969
+ return lines.join("\n");
2970
+ }
2971
+ var SPAN_ICONS = {
2972
+ agent: "\u25B6",
2973
+ llm: "\u25C6",
2974
+ tool: "\u2699",
2975
+ guardrail: "\u{1F6E1}",
2976
+ checkpoint: "\u{1F4BE}"
2977
+ };
2978
+ function renderSpanLine(span, indent) {
2979
+ const icon = SPAN_ICONS[span.kind];
2980
+ const indentStr = " ".repeat(indent);
2981
+ const dur = span.duration_ms !== void 0 ? chalk19.dim(" " + span.duration_ms + "ms ") : chalk19.dim(" (running) ");
2982
+ const status = colorStatus(span.status);
2983
+ const attrs = formatAttrs(span);
2984
+ const attrSuffix = attrs ? chalk19.dim(" \xB7 " + attrs) : "";
2985
+ return indentStr + icon + " " + chalk19.bold(span.name) + dur + status + attrSuffix;
2986
+ }
2987
+ function formatAttrs(span) {
2988
+ const a = span.attributes;
2989
+ const parts = [];
2990
+ if (span.kind === "agent") {
2991
+ if (a.agent_id !== void 0) parts.push("agent=" + a.agent_id);
2992
+ if (a.model !== void 0) parts.push("model=" + a.model);
2993
+ } else if (span.kind === "llm") {
2994
+ if (a.model !== void 0) parts.push("model=" + a.model);
2995
+ if (a.total_tokens !== void 0) parts.push(a.total_tokens + " tok");
2996
+ if (a.cost_usd !== void 0) parts.push(formatCost(a.cost_usd));
2997
+ } else if (span.kind === "tool") {
2998
+ if (a.tool_name !== void 0) parts.push(a.tool_name);
2999
+ } else if (span.kind === "guardrail") {
3000
+ if (a.guardrail_name !== void 0) parts.push(a.guardrail_name);
3001
+ if (a.guardrail_action !== void 0) parts.push("\u2192 " + a.guardrail_action);
3002
+ } else if (span.kind === "checkpoint") {
3003
+ if (a.session_id !== void 0) parts.push("session=" + a.session_id.slice(0, 8));
3004
+ }
3005
+ if (span.error?.message) {
3006
+ parts.push(chalk19.red("error: " + span.error.message));
3007
+ }
3008
+ return parts.join(" \xB7 ");
3009
+ }
3010
+ function renderTraceShow(spans) {
3011
+ if (spans.length === 0) {
3012
+ return chalk19.dim("No spans found for this trace.");
3013
+ }
3014
+ const childrenByParent = /* @__PURE__ */ new Map();
3015
+ const presentSpanIds = new Set(spans.map((s) => s.span_id));
3016
+ const roots = [];
3017
+ for (const span of spans) {
3018
+ const parent = span.parent_span_id;
3019
+ if (parent === void 0 || !presentSpanIds.has(parent)) {
3020
+ roots.push(span);
3021
+ continue;
3022
+ }
3023
+ const arr = childrenByParent.get(parent) ?? [];
3024
+ arr.push(span);
3025
+ childrenByParent.set(parent, arr);
3026
+ }
3027
+ roots.sort((a, b) => a.started_at.getTime() - b.started_at.getTime());
3028
+ const lines = [""];
3029
+ function walk(span, indent) {
3030
+ lines.push(renderSpanLine(span, indent));
3031
+ const kids = childrenByParent.get(span.span_id);
3032
+ if (!kids) return;
3033
+ for (const child of kids) {
3034
+ walk(child, indent + 1);
3035
+ }
3036
+ }
3037
+ for (const root of roots) walk(root, 0);
3038
+ let total_tokens = 0;
3039
+ let total_cost = 0;
3040
+ let any_cost = false;
3041
+ for (const s of spans) {
3042
+ if (s.name !== "llm.completion") continue;
3043
+ total_tokens += s.attributes.total_tokens ?? 0;
3044
+ if (s.attributes.cost_usd !== void 0) {
3045
+ total_cost += s.attributes.cost_usd;
3046
+ any_cost = true;
3047
+ }
3048
+ }
3049
+ const wall_ms = roots.map((r) => r.duration_ms).find((d) => d !== void 0);
3050
+ lines.push("");
3051
+ lines.push(chalk19.dim("\u2500".repeat(60)));
3052
+ 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")
3054
+ );
3055
+ lines.push("");
3056
+ return lines.join("\n");
3057
+ }
3058
+
3059
+ // src/commands/traces.ts
3060
+ var DEFAULT_SERVER_URL = "http://127.0.0.1:3847";
3061
+ function resolveUrl(opts) {
3062
+ return opts.url ?? SecretsManager9.optional("TUTTI_SERVER_URL") ?? DEFAULT_SERVER_URL;
3063
+ }
3064
+ function resolveAuthHeader(opts) {
3065
+ const key = opts.apiKey ?? SecretsManager9.optional("TUTTI_API_KEY");
3066
+ return key ? { Authorization: "Bearer " + key } : {};
3067
+ }
3068
+ function explainConnectionError(err, baseUrl) {
3069
+ 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."));
3073
+ process.exit(1);
3074
+ }
3075
+ async function tracesListCommand(opts) {
3076
+ const baseUrl = resolveUrl(opts);
3077
+ const url = baseUrl.replace(/\/$/, "") + "/traces";
3078
+ let res;
3079
+ try {
3080
+ res = await fetch(url, { headers: resolveAuthHeader(opts) });
3081
+ } catch (err) {
3082
+ explainConnectionError(err, baseUrl);
3083
+ }
3084
+ if (res.status === 401) {
3085
+ console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3086
+ process.exit(1);
3087
+ }
3088
+ if (!res.ok) {
3089
+ console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3090
+ process.exit(1);
3091
+ }
3092
+ const body = await res.json();
3093
+ console.log(renderTracesList(body.traces));
3094
+ }
3095
+ async function tracesShowCommand(traceId, opts) {
3096
+ const baseUrl = resolveUrl(opts);
3097
+ const url = baseUrl.replace(/\/$/, "") + "/traces/" + encodeURIComponent(traceId);
3098
+ let res;
3099
+ try {
3100
+ res = await fetch(url, { headers: resolveAuthHeader(opts) });
3101
+ } catch (err) {
3102
+ explainConnectionError(err, baseUrl);
3103
+ }
3104
+ if (res.status === 404) {
3105
+ console.error(chalk20.red('Trace "' + traceId + '" not found.'));
3106
+ process.exit(1);
3107
+ }
3108
+ if (res.status === 401) {
3109
+ console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3110
+ process.exit(1);
3111
+ }
3112
+ if (!res.ok) {
3113
+ console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3114
+ process.exit(1);
3115
+ }
3116
+ const body = await res.json();
3117
+ const spans = body.spans.map(reviveSpanDates);
3118
+ console.log(renderTraceShow(spans));
3119
+ }
3120
+ async function tracesTailCommand(opts) {
3121
+ const baseUrl = resolveUrl(opts);
3122
+ const url = baseUrl.replace(/\/$/, "") + "/traces/stream";
3123
+ console.error(chalk20.dim("Tailing traces from " + baseUrl + " \u2014 Ctrl+C to exit"));
3124
+ console.error("");
3125
+ const controller = new AbortController();
3126
+ process.once("SIGINT", () => {
3127
+ controller.abort();
3128
+ console.error("");
3129
+ console.error(chalk20.dim("Disconnected."));
3130
+ process.exit(0);
3131
+ });
3132
+ let res;
3133
+ try {
3134
+ res = await fetch(url, {
3135
+ headers: { ...resolveAuthHeader(opts), Accept: "text/event-stream" },
3136
+ signal: controller.signal
3137
+ });
3138
+ } catch (err) {
3139
+ if (controller.signal.aborted) return;
3140
+ explainConnectionError(err, baseUrl);
3141
+ }
3142
+ if (res.status === 401) {
3143
+ console.error(chalk20.red("Unauthorized \u2014 set --api-key or TUTTI_API_KEY."));
3144
+ process.exit(1);
3145
+ }
3146
+ if (!res.ok || !res.body) {
3147
+ console.error(chalk20.red("Server returned " + res.status + " " + res.statusText));
3148
+ process.exit(1);
3149
+ }
3150
+ const reader = res.body.getReader();
3151
+ const decoder = new TextDecoder();
3152
+ let buffer = "";
3153
+ for (; ; ) {
3154
+ let chunk;
3155
+ try {
3156
+ chunk = await reader.read();
3157
+ } catch {
3158
+ return;
3159
+ }
3160
+ if (chunk.done) break;
3161
+ buffer += decoder.decode(chunk.value, { stream: true });
3162
+ let frameEnd;
3163
+ while ((frameEnd = buffer.indexOf("\n\n")) !== -1) {
3164
+ const frame = buffer.slice(0, frameEnd);
3165
+ buffer = buffer.slice(frameEnd + 2);
3166
+ const dataLine = frame.split("\n").find((l) => l.startsWith("data: "));
3167
+ if (!dataLine) continue;
3168
+ try {
3169
+ const span = reviveSpanDates(JSON.parse(dataLine.slice(6)));
3170
+ console.log(renderSpanLine(span, 0));
3171
+ } catch (err) {
3172
+ console.error(chalk20.red("Bad SSE frame: " + (err instanceof Error ? err.message : String(err))));
3173
+ }
3174
+ }
3175
+ }
3176
+ }
3177
+ function reviveSpanDates(span) {
3178
+ return {
3179
+ ...span,
3180
+ started_at: new Date(span.started_at),
3181
+ ...span.ended_at !== void 0 ? { ended_at: new Date(span.ended_at) } : {}
3182
+ };
3183
+ }
3184
+
2446
3185
  // src/index.ts
2447
3186
  config();
2448
- var logger14 = createLogger14("tutti-cli");
3187
+ var logger15 = createLogger15("tutti-cli");
2449
3188
  process.on("unhandledRejection", (reason) => {
2450
- logger14.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
3189
+ logger15.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
2451
3190
  process.exit(1);
2452
3191
  });
2453
3192
  process.on("uncaughtException", (err) => {
2454
- logger14.error({ error: err.message }, "Fatal error");
3193
+ logger15.error({ error: err.message }, "Fatal error");
2455
3194
  process.exit(1);
2456
3195
  });
2457
3196
  var program = new Command();
@@ -2511,6 +3250,18 @@ program.command("publish").description("Publish the current voice to npm and the
2511
3250
  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) => {
2512
3251
  await evalCommand(suitePath, opts);
2513
3252
  });
3253
+ program.command("update").description("Update @tuttiai packages to their latest versions").action(() => {
3254
+ updateCommand();
3255
+ });
3256
+ program.command("outdated").description("Check installed @tuttiai packages for newer versions").action(() => {
3257
+ outdatedCommand();
3258
+ });
3259
+ program.command("info [score]").description("Show project info \u2014 agents, voices, models, and package versions").action(async (score) => {
3260
+ await infoCommand(score);
3261
+ });
3262
+ program.command("upgrade [voice]").description("Upgrade a specific voice or all @tuttiai packages to latest").action((voice) => {
3263
+ upgradeCommand(voice);
3264
+ });
2514
3265
  program.command("replay <session-id>").description("Time-travel debugger \u2014 navigate and replay a session from PostgreSQL").option("-s, --score <path>", "Path to score file for replay-from (default: ./tutti.score.ts)").action(async (sessionId, opts) => {
2515
3266
  await replayCommand(sessionId, { score: opts.score });
2516
3267
  });
@@ -2533,5 +3284,27 @@ schedulesCmd.command("trigger <id>").description("Manually trigger a scheduled r
2533
3284
  schedulesCmd.command("runs <id>").description("Show run history for a schedule (last 20 runs)").action(async (id) => {
2534
3285
  await schedulesRunsCommand(id);
2535
3286
  });
3287
+ var tracesCmd = program.command("traces").description("Inspect spans emitted by a running tutti-ai serve process");
3288
+ tracesCmd.command("list").description("Show the last 20 traces (most recent first)").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (opts) => {
3289
+ const resolved = {
3290
+ ...opts.url !== void 0 ? { url: opts.url } : {},
3291
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {}
3292
+ };
3293
+ await tracesListCommand(resolved);
3294
+ });
3295
+ tracesCmd.command("show <trace-id>").description("Render every span in a trace as an indented tree").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (traceId, opts) => {
3296
+ const resolved = {
3297
+ ...opts.url !== void 0 ? { url: opts.url } : {},
3298
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {}
3299
+ };
3300
+ await tracesShowCommand(traceId, resolved);
3301
+ });
3302
+ tracesCmd.command("tail").description("Live-tail spans as they are emitted (Ctrl+C to exit)").option("-u, --url <url>", "Server URL (default: http://127.0.0.1:3847)").option("-k, --api-key <key>", "Bearer token (default: TUTTI_API_KEY env)").action(async (opts) => {
3303
+ const resolved = {
3304
+ ...opts.url !== void 0 ? { url: opts.url } : {},
3305
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {}
3306
+ };
3307
+ await tracesTailCommand(resolved);
3308
+ });
2536
3309
  program.parse();
2537
3310
  //# sourceMappingURL=index.js.map