@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 +351 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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/
|
|
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
|
|
1976
|
-
createLogger as
|
|
2285
|
+
SecretsManager as SecretsManager8,
|
|
2286
|
+
createLogger as createLogger13
|
|
1977
2287
|
} from "@tuttiai/core";
|
|
1978
|
-
var
|
|
2288
|
+
var logger13 = createLogger13("tutti-cli");
|
|
1979
2289
|
function resolveStore2() {
|
|
1980
|
-
const pgUrl =
|
|
2290
|
+
const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
|
|
1981
2291
|
if (pgUrl) {
|
|
1982
2292
|
return new PostgresScheduleStore2({ connection_string: pgUrl });
|
|
1983
2293
|
}
|
|
1984
|
-
|
|
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(
|
|
2007
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
2326
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(90)));
|
|
2017
2327
|
for (const r of records) {
|
|
2018
|
-
const enabled = r.enabled ?
|
|
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
|
-
" " +
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
2059
|
-
if (!
|
|
2060
|
-
console.error(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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
|
-
" " +
|
|
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(
|
|
2129
|
-
console.log(
|
|
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
|
|
2448
|
+
var logger14 = createLogger14("tutti-cli");
|
|
2139
2449
|
process.on("unhandledRejection", (reason) => {
|
|
2140
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
});
|