@tuttiai/cli 0.12.0 → 0.13.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 createLogger13 } from "@tuttiai/core";
5
+ import { createLogger as createLogger14 } from "@tuttiai/core";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/init.ts
@@ -1960,28 +1960,338 @@ async function scheduleCommand(scorePath) {
1960
1960
  await new Promise(() => void 0);
1961
1961
  }
1962
1962
 
1963
- // src/commands/schedules.ts
1963
+ // src/commands/replay.ts
1964
1964
  import { existsSync as existsSync12 } from "fs";
1965
+ import { writeFile } from "fs/promises";
1965
1966
  import { resolve as resolve12 } from "path";
1967
+ import { createInterface as createInterface3 } from "readline/promises";
1966
1968
  import chalk12 from "chalk";
1969
+ import ora7 from "ora";
1967
1970
  import {
1971
+ PostgresSessionStore,
1968
1972
  ScoreLoader as ScoreLoader8,
1973
+ TuttiRuntime as TuttiRuntime5,
1974
+ SecretsManager as SecretsManager7,
1975
+ createLogger as createLogger12
1976
+ } from "@tuttiai/core";
1977
+ var logger12 = createLogger12("tutti-cli");
1978
+ function messageToText2(msg) {
1979
+ if (typeof msg.content === "string") return msg.content;
1980
+ const parts = [];
1981
+ for (const block of msg.content) {
1982
+ if (block.type === "text") {
1983
+ parts.push(block.text);
1984
+ } else if (block.type === "tool_use") {
1985
+ parts.push("[tool_use " + block.name + "]");
1986
+ } else if (block.type === "tool_result") {
1987
+ const preview = block.content.replace(/\s+/g, " ").trim();
1988
+ parts.push("[tool_result " + (preview.length > 60 ? preview.slice(0, 59) + "\u2026" : preview) + "]");
1989
+ }
1990
+ }
1991
+ return parts.join(" ");
1992
+ }
1993
+ function excerpt2(text, max) {
1994
+ const oneLine = text.replace(/\s+/g, " ").trim();
1995
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
1996
+ }
1997
+ function renderList(messages) {
1998
+ const lines = [];
1999
+ 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");
2002
+ const text = excerpt2(messageToText2(msg), 80);
2003
+ lines.push(
2004
+ chalk12.dim(String(i).padStart(3)) + " " + role + " " + text
2005
+ );
2006
+ }
2007
+ return lines.join("\n");
2008
+ }
2009
+ function renderShow(messages, index) {
2010
+ if (index < 0 || index >= messages.length) {
2011
+ return chalk12.red("Index out of range. Valid: 0\u2013" + (messages.length - 1));
2012
+ }
2013
+ const msg = messages[index];
2014
+ const lines = [];
2015
+ lines.push(chalk12.cyan.bold("Turn " + index) + " " + chalk12.dim("[" + msg.role + "]"));
2016
+ lines.push("");
2017
+ if (typeof msg.content === "string") {
2018
+ lines.push(msg.content);
2019
+ } else {
2020
+ for (const block of msg.content) {
2021
+ if (block.type === "text") {
2022
+ lines.push(block.text);
2023
+ } 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)));
2027
+ } else if (block.type === "tool_result") {
2028
+ const label = block.is_error ? chalk12.red(" tool_result (error):") : chalk12.green(" tool_result:");
2029
+ lines.push(label);
2030
+ lines.push(chalk12.dim(" tool_use_id: " + block.tool_use_id));
2031
+ lines.push(" " + block.content);
2032
+ }
2033
+ }
2034
+ }
2035
+ return lines.join("\n");
2036
+ }
2037
+ function renderInspect(messages, index) {
2038
+ if (index < 0 || index >= messages.length) {
2039
+ return chalk12.red("Index out of range.");
2040
+ }
2041
+ return JSON.stringify(messages[index], null, 2);
2042
+ }
2043
+ function exportJSON(session) {
2044
+ return JSON.stringify(
2045
+ {
2046
+ id: session.id,
2047
+ agent_name: session.agent_name,
2048
+ created_at: session.created_at,
2049
+ messages: session.messages
2050
+ },
2051
+ null,
2052
+ 2
2053
+ );
2054
+ }
2055
+ function exportMarkdown(session) {
2056
+ const lines = [];
2057
+ lines.push("# Session " + session.id);
2058
+ lines.push("");
2059
+ lines.push("**Agent:** " + session.agent_name);
2060
+ lines.push("**Created:** " + session.created_at.toISOString());
2061
+ lines.push("**Messages:** " + session.messages.length);
2062
+ lines.push("");
2063
+ lines.push("---");
2064
+ lines.push("");
2065
+ for (let i = 0; i < session.messages.length; i++) {
2066
+ const msg = session.messages[i];
2067
+ lines.push("## Turn " + i + " (" + msg.role + ")");
2068
+ lines.push("");
2069
+ if (typeof msg.content === "string") {
2070
+ lines.push(msg.content);
2071
+ } else {
2072
+ for (const block of msg.content) {
2073
+ if (block.type === "text") {
2074
+ lines.push(block.text);
2075
+ } else if (block.type === "tool_use") {
2076
+ lines.push("**Tool call:** `" + block.name + "`");
2077
+ lines.push("```json\n" + JSON.stringify(block.input, null, 2) + "\n```");
2078
+ } else if (block.type === "tool_result") {
2079
+ const label = block.is_error ? "**Tool error:**" : "**Tool result:**";
2080
+ lines.push(label);
2081
+ lines.push("```\n" + block.content + "\n```");
2082
+ }
2083
+ }
2084
+ }
2085
+ lines.push("");
2086
+ }
2087
+ return lines.join("\n");
2088
+ }
2089
+ async function replayCommand(sessionId, opts = {}) {
2090
+ const pgUrl = SecretsManager7.optional("TUTTI_PG_URL");
2091
+ if (!pgUrl) {
2092
+ console.error(chalk12.red("TUTTI_PG_URL is not set."));
2093
+ console.error(
2094
+ chalk12.dim(
2095
+ "The replay command requires PostgreSQL for session persistence.\nSet TUTTI_PG_URL=postgres://user:pass@host/db in your environment."
2096
+ )
2097
+ );
2098
+ process.exit(1);
2099
+ }
2100
+ const store = new PostgresSessionStore(pgUrl);
2101
+ const spinner = ora7({ color: "cyan" }).start("Loading session...");
2102
+ let session;
2103
+ try {
2104
+ session = await store.getAsync(sessionId);
2105
+ } catch (err) {
2106
+ spinner.fail("Failed to load session");
2107
+ logger12.error(
2108
+ { error: err instanceof Error ? err.message : String(err) },
2109
+ "Session store error"
2110
+ );
2111
+ process.exit(1);
2112
+ }
2113
+ spinner.stop();
2114
+ 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."));
2117
+ await store.close();
2118
+ process.exit(1);
2119
+ }
2120
+ const messages = session.messages;
2121
+ 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));
2127
+ console.log("");
2128
+ console.log(chalk12.dim(" Commands: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit"));
2129
+ console.log("");
2130
+ let cursor = 0;
2131
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
2132
+ try {
2133
+ while (true) {
2134
+ const prompt3 = chalk12.cyan("replay [" + cursor + "/" + (messages.length - 1) + "]> ");
2135
+ const raw = await rl.question(prompt3);
2136
+ const input = raw.trim();
2137
+ if (!input) continue;
2138
+ const [cmd, ...args] = input.split(/\s+/);
2139
+ switch (cmd) {
2140
+ case "quit":
2141
+ case "exit":
2142
+ case "q":
2143
+ console.log(chalk12.dim("Bye."));
2144
+ return;
2145
+ case "list":
2146
+ console.log(renderList(messages));
2147
+ break;
2148
+ case "show": {
2149
+ const n = parseInt(args[0] ?? String(cursor), 10);
2150
+ console.log(renderShow(messages, n));
2151
+ if (n >= 0 && n < messages.length) cursor = n;
2152
+ break;
2153
+ }
2154
+ case "next":
2155
+ if (cursor < messages.length - 1) {
2156
+ cursor++;
2157
+ console.log(renderShow(messages, cursor));
2158
+ } else {
2159
+ console.log(chalk12.dim("Already at last message."));
2160
+ }
2161
+ break;
2162
+ case "prev":
2163
+ if (cursor > 0) {
2164
+ cursor--;
2165
+ console.log(renderShow(messages, cursor));
2166
+ } else {
2167
+ console.log(chalk12.dim("Already at first message."));
2168
+ }
2169
+ break;
2170
+ case "inspect":
2171
+ console.log(renderInspect(messages, cursor));
2172
+ break;
2173
+ case "replay-from": {
2174
+ const turn = parseInt(args[0] ?? "", 10);
2175
+ if (isNaN(turn) || turn < 0 || turn >= messages.length) {
2176
+ console.log(chalk12.red("Usage: replay-from <turn-number>"));
2177
+ break;
2178
+ }
2179
+ await handleReplayFrom(turn, session, rl, opts);
2180
+ break;
2181
+ }
2182
+ case "export": {
2183
+ const format = args[0];
2184
+ if (format === "json") {
2185
+ const filename = "session-" + session.id.slice(0, 8) + ".json";
2186
+ await writeFile(filename, exportJSON(session));
2187
+ console.log(chalk12.green("Exported to " + filename));
2188
+ } else if (format === "md" || format === "markdown") {
2189
+ const filename = "session-" + session.id.slice(0, 8) + ".md";
2190
+ await writeFile(filename, exportMarkdown(session));
2191
+ console.log(chalk12.green("Exported to " + filename));
2192
+ } else {
2193
+ console.log(chalk12.dim("Usage: export <json|md>"));
2194
+ }
2195
+ break;
2196
+ }
2197
+ default:
2198
+ console.log(
2199
+ chalk12.dim("Unknown command. Available: list, show <n>, next, prev, inspect, replay-from <n>, export <json|md>, quit")
2200
+ );
2201
+ }
2202
+ }
2203
+ } finally {
2204
+ rl.close();
2205
+ await store.close();
2206
+ }
2207
+ }
2208
+ 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."));
2213
+ return;
2214
+ }
2215
+ const originalMsg = session.messages[turn];
2216
+ const originalInput = originalMsg ? messageToText2(originalMsg) : "";
2217
+ const answer = await rl.question(
2218
+ chalk12.cyan("Replay from turn " + turn + " with original input? ") + chalk12.dim("(y / enter new input) ")
2219
+ );
2220
+ const input = answer.trim().toLowerCase() === "y" || answer.trim() === "" ? originalInput : answer.trim();
2221
+ if (!input) {
2222
+ console.log(chalk12.dim("No input provided. Cancelled."));
2223
+ return;
2224
+ }
2225
+ const spinnerLoad = ora7({ color: "cyan" }).start("Loading score...");
2226
+ let score;
2227
+ try {
2228
+ score = await ScoreLoader8.load(scoreFile);
2229
+ } catch (err) {
2230
+ spinnerLoad.fail("Failed to load score");
2231
+ logger12.error({ error: err instanceof Error ? err.message : String(err) }, "Score load error");
2232
+ return;
2233
+ }
2234
+ spinnerLoad.stop();
2235
+ const restoredMessages = session.messages.slice(0, turn);
2236
+ const runtime = new TuttiRuntime5(score);
2237
+ const sessions = runtime.sessions;
2238
+ if ("save" in sessions && typeof sessions.save === "function") {
2239
+ sessions.save({
2240
+ id: session.id,
2241
+ agent_name: session.agent_name,
2242
+ messages: restoredMessages,
2243
+ created_at: session.created_at,
2244
+ updated_at: /* @__PURE__ */ new Date()
2245
+ });
2246
+ }
2247
+ const agentName = session.agent_name;
2248
+ const agent = score.agents[agentName];
2249
+ if (!agent) {
2250
+ console.log(chalk12.red('Agent "' + agentName + '" not found in score.'));
2251
+ return;
2252
+ }
2253
+ const spinnerRun = ora7({ color: "cyan" }).start("Running from turn " + turn + "...");
2254
+ runtime.events.on("token:stream", (e) => {
2255
+ spinnerRun.stop();
2256
+ process.stdout.write(e.text);
2257
+ });
2258
+ try {
2259
+ const result = await runtime.run(agentName, input, session.id);
2260
+ spinnerRun.stop();
2261
+ 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"));
2265
+ console.log("");
2266
+ console.log(result.output);
2267
+ } catch (err) {
2268
+ spinnerRun.fail("Replay failed");
2269
+ logger12.error({ error: err instanceof Error ? err.message : String(err) }, "Replay error");
2270
+ }
2271
+ }
2272
+
2273
+ // src/commands/schedules.ts
2274
+ import { existsSync as existsSync13 } from "fs";
2275
+ import { resolve as resolve13 } from "path";
2276
+ import chalk13 from "chalk";
2277
+ import {
2278
+ ScoreLoader as ScoreLoader9,
1969
2279
  SchedulerEngine as SchedulerEngine2,
1970
2280
  PostgresScheduleStore as PostgresScheduleStore2,
1971
2281
  MemoryScheduleStore as MemoryScheduleStore2,
1972
2282
  AgentRunner as AgentRunner2,
1973
2283
  EventBus as EventBus2,
1974
2284
  InMemorySessionStore as InMemorySessionStore4,
1975
- SecretsManager as SecretsManager7,
1976
- createLogger as createLogger12
2285
+ SecretsManager as SecretsManager8,
2286
+ createLogger as createLogger13
1977
2287
  } from "@tuttiai/core";
1978
- var logger12 = createLogger12("tutti-cli");
2288
+ var logger13 = createLogger13("tutti-cli");
1979
2289
  function resolveStore2() {
1980
- const pgUrl = SecretsManager7.optional("TUTTI_PG_URL");
2290
+ const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
1981
2291
  if (pgUrl) {
1982
2292
  return new PostgresScheduleStore2({ connection_string: pgUrl });
1983
2293
  }
1984
- logger12.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
2294
+ logger13.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
1985
2295
  return new MemoryScheduleStore2();
1986
2296
  }
1987
2297
  async function closeStore(store) {
@@ -2003,22 +2313,22 @@ async function schedulesListCommand() {
2003
2313
  try {
2004
2314
  const records = await store.list();
2005
2315
  if (records.length === 0) {
2006
- console.log(chalk12.dim("No schedules found."));
2007
- console.log(chalk12.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
2316
+ console.log(chalk13.dim("No schedules found."));
2317
+ console.log(chalk13.dim('Run "tutti-ai schedule" to start the scheduler daemon.'));
2008
2318
  return;
2009
2319
  }
2010
2320
  console.log("");
2011
2321
  console.log(
2012
- chalk12.dim(
2322
+ chalk13.dim(
2013
2323
  " " + pad("ID", 20) + pad("AGENT", 16) + pad("TRIGGER", 22) + pad("ENABLED", 10) + pad("RUNS", 8) + "CREATED"
2014
2324
  )
2015
2325
  );
2016
- console.log(chalk12.dim(" " + "\u2500".repeat(90)));
2326
+ console.log(chalk13.dim(" " + "\u2500".repeat(90)));
2017
2327
  for (const r of records) {
2018
- const enabled = r.enabled ? chalk12.green("yes") : chalk12.red("no") + " ";
2328
+ const enabled = r.enabled ? chalk13.green("yes") : chalk13.red("no") + " ";
2019
2329
  const maxLabel = r.config.max_runs ? r.run_count + "/" + r.config.max_runs : String(r.run_count);
2020
2330
  console.log(
2021
- " " + chalk12.bold(pad(r.id, 20)) + pad(r.agent_id, 16) + pad(formatTrigger(r), 22) + pad(enabled, 10) + pad(maxLabel, 8) + chalk12.dim(r.created_at.toISOString().slice(0, 10))
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))
2022
2332
  );
2023
2333
  }
2024
2334
  console.log("");
@@ -2031,11 +2341,11 @@ async function schedulesEnableCommand(id) {
2031
2341
  try {
2032
2342
  const record = await store.get(id);
2033
2343
  if (!record) {
2034
- console.error(chalk12.red('Schedule "' + id + '" not found.'));
2344
+ console.error(chalk13.red('Schedule "' + id + '" not found.'));
2035
2345
  process.exit(1);
2036
2346
  }
2037
2347
  await store.setEnabled(id, true);
2038
- console.log(chalk12.green('Schedule "' + id + '" enabled.'));
2348
+ console.log(chalk13.green('Schedule "' + id + '" enabled.'));
2039
2349
  } finally {
2040
2350
  await closeStore(store);
2041
2351
  }
@@ -2045,22 +2355,22 @@ async function schedulesDisableCommand(id) {
2045
2355
  try {
2046
2356
  const record = await store.get(id);
2047
2357
  if (!record) {
2048
- console.error(chalk12.red('Schedule "' + id + '" not found.'));
2358
+ console.error(chalk13.red('Schedule "' + id + '" not found.'));
2049
2359
  process.exit(1);
2050
2360
  }
2051
2361
  await store.setEnabled(id, false);
2052
- console.log(chalk12.yellow('Schedule "' + id + '" disabled.'));
2362
+ console.log(chalk13.yellow('Schedule "' + id + '" disabled.'));
2053
2363
  } finally {
2054
2364
  await closeStore(store);
2055
2365
  }
2056
2366
  }
2057
2367
  async function schedulesTriggerCommand(id, scorePath) {
2058
- const file = resolve12(scorePath ?? "./tutti.score.ts");
2059
- if (!existsSync12(file)) {
2060
- console.error(chalk12.red("Score file not found: " + file));
2368
+ const file = resolve13(scorePath ?? "./tutti.score.ts");
2369
+ if (!existsSync13(file)) {
2370
+ console.error(chalk13.red("Score file not found: " + file));
2061
2371
  process.exit(1);
2062
2372
  }
2063
- const score = await ScoreLoader8.load(file);
2373
+ const score = await ScoreLoader9.load(file);
2064
2374
  const events = new EventBus2();
2065
2375
  const sessions = new InMemorySessionStore4();
2066
2376
  const runner = new AgentRunner2(score.provider, events, sessions);
@@ -2068,30 +2378,30 @@ async function schedulesTriggerCommand(id, scorePath) {
2068
2378
  try {
2069
2379
  const record = await store.get(id);
2070
2380
  if (!record) {
2071
- console.error(chalk12.red('Schedule "' + id + '" not found.'));
2381
+ console.error(chalk13.red('Schedule "' + id + '" not found.'));
2072
2382
  process.exit(1);
2073
2383
  }
2074
2384
  const agent = score.agents[record.agent_id];
2075
2385
  if (!agent) {
2076
- console.error(chalk12.red('Agent "' + record.agent_id + '" not found in score.'));
2386
+ console.error(chalk13.red('Agent "' + record.agent_id + '" not found in score.'));
2077
2387
  process.exit(1);
2078
2388
  }
2079
2389
  const resolvedAgent = agent.model ? agent : { ...agent, model: score.default_model ?? "claude-sonnet-4-20250514" };
2080
2390
  const engine = new SchedulerEngine2(store, runner, events);
2081
2391
  await engine.schedule(id, resolvedAgent, record.config);
2082
2392
  engine.start();
2083
- console.log(chalk12.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
2393
+ console.log(chalk13.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
2084
2394
  const run2 = await engine.trigger(id);
2085
2395
  engine.stop();
2086
2396
  if (run2.error) {
2087
- console.log(chalk12.red(" Error: " + run2.error));
2397
+ console.log(chalk13.red(" Error: " + run2.error));
2088
2398
  process.exit(1);
2089
2399
  }
2090
2400
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() : 0;
2091
- console.log(chalk12.green(" Completed in " + duration + "ms"));
2401
+ console.log(chalk13.green(" Completed in " + duration + "ms"));
2092
2402
  if (run2.result) {
2093
2403
  const preview = run2.result.length > 200 ? run2.result.slice(0, 200) + "..." : run2.result;
2094
- console.log(chalk12.dim(" Output: " + preview));
2404
+ console.log(chalk13.dim(" Output: " + preview));
2095
2405
  }
2096
2406
  } finally {
2097
2407
  await closeStore(store);
@@ -2102,31 +2412,31 @@ async function schedulesRunsCommand(id) {
2102
2412
  try {
2103
2413
  const record = await store.get(id);
2104
2414
  if (!record) {
2105
- console.error(chalk12.red('Schedule "' + id + '" not found.'));
2415
+ console.error(chalk13.red('Schedule "' + id + '" not found.'));
2106
2416
  process.exit(1);
2107
2417
  }
2108
2418
  if ("getRuns" in store && typeof store.getRuns === "function") {
2109
2419
  const runs = store.getRuns(id);
2110
2420
  if (runs.length === 0) {
2111
- console.log(chalk12.dim("No runs recorded for this schedule."));
2421
+ console.log(chalk13.dim("No runs recorded for this schedule."));
2112
2422
  return;
2113
2423
  }
2114
2424
  const recent = runs.slice(-20);
2115
2425
  console.log("");
2116
- console.log(chalk12.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
2426
+ console.log(chalk13.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
2117
2427
  console.log("");
2118
2428
  for (const run2 of recent) {
2119
2429
  const duration = run2.completed_at && run2.triggered_at ? run2.completed_at.getTime() - run2.triggered_at.getTime() + "ms" : "?";
2120
- const status = run2.error ? chalk12.red("error") : chalk12.green("ok");
2430
+ const status = run2.error ? chalk13.red("error") : chalk13.green("ok");
2121
2431
  const preview = run2.error ? run2.error.slice(0, 80) : (run2.result ?? "").slice(0, 80);
2122
2432
  console.log(
2123
- " " + chalk12.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk12.dim(duration) + " " + preview
2433
+ " " + chalk13.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk13.dim(duration) + " " + preview
2124
2434
  );
2125
2435
  }
2126
2436
  console.log("");
2127
2437
  } else {
2128
- console.log(chalk12.dim('Schedule "' + id + '" has completed ' + record.run_count + " runs."));
2129
- console.log(chalk12.dim("Full run history requires the MemoryScheduleStore or a future tutti_schedule_runs table."));
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."));
2130
2440
  }
2131
2441
  } finally {
2132
2442
  await closeStore(store);
@@ -2135,17 +2445,17 @@ async function schedulesRunsCommand(id) {
2135
2445
 
2136
2446
  // src/index.ts
2137
2447
  config();
2138
- var logger13 = createLogger13("tutti-cli");
2448
+ var logger14 = createLogger14("tutti-cli");
2139
2449
  process.on("unhandledRejection", (reason) => {
2140
- logger13.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
2450
+ logger14.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
2141
2451
  process.exit(1);
2142
2452
  });
2143
2453
  process.on("uncaughtException", (err) => {
2144
- logger13.error({ error: err.message }, "Fatal error");
2454
+ logger14.error({ error: err.message }, "Fatal error");
2145
2455
  process.exit(1);
2146
2456
  });
2147
2457
  var program = new Command();
2148
- program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.12.0");
2458
+ program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.13.0");
2149
2459
  program.command("init [project-name]").description("Create a new Tutti project").option("-t, --template <id>", "Project template to use").action(async (projectName, opts) => {
2150
2460
  await initCommand(projectName, opts.template);
2151
2461
  });
@@ -2201,6 +2511,9 @@ program.command("publish").description("Publish the current voice to npm and the
2201
2511
  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) => {
2202
2512
  await evalCommand(suitePath, opts);
2203
2513
  });
2514
+ 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
+ await replayCommand(sessionId, { score: opts.score });
2516
+ });
2204
2517
  program.command("schedule [score]").description("Start the scheduler daemon \u2014 runs agents on their configured schedules").action(async (score) => {
2205
2518
  await scheduleCommand(score);
2206
2519
  });