@tuttiai/cli 0.11.1 → 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/README.md +90 -0
- package/dist/index.js +605 -5
- 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
|
|
@@ -1865,19 +1865,597 @@ function printBanner(port, host, agentName, score, file, watch) {
|
|
|
1865
1865
|
console.log();
|
|
1866
1866
|
}
|
|
1867
1867
|
|
|
1868
|
+
// src/commands/schedule.ts
|
|
1869
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1870
|
+
import { resolve as resolve11 } from "path";
|
|
1871
|
+
import chalk11 from "chalk";
|
|
1872
|
+
import {
|
|
1873
|
+
ScoreLoader as ScoreLoader7,
|
|
1874
|
+
SchedulerEngine,
|
|
1875
|
+
PostgresScheduleStore,
|
|
1876
|
+
MemoryScheduleStore,
|
|
1877
|
+
AgentRunner,
|
|
1878
|
+
EventBus,
|
|
1879
|
+
InMemorySessionStore as InMemorySessionStore3,
|
|
1880
|
+
SecretsManager as SecretsManager6,
|
|
1881
|
+
createLogger as createLogger11
|
|
1882
|
+
} from "@tuttiai/core";
|
|
1883
|
+
var logger11 = createLogger11("tutti-cli");
|
|
1884
|
+
function resolveStore() {
|
|
1885
|
+
const pgUrl = SecretsManager6.optional("TUTTI_PG_URL");
|
|
1886
|
+
if (pgUrl) {
|
|
1887
|
+
return new PostgresScheduleStore({ connection_string: pgUrl });
|
|
1888
|
+
}
|
|
1889
|
+
logger11.warn("TUTTI_PG_URL not set \u2014 using in-memory store (not durable across restarts)");
|
|
1890
|
+
return new MemoryScheduleStore();
|
|
1891
|
+
}
|
|
1892
|
+
async function scheduleCommand(scorePath) {
|
|
1893
|
+
const file = resolve11(scorePath ?? "./tutti.score.ts");
|
|
1894
|
+
if (!existsSync11(file)) {
|
|
1895
|
+
console.error(chalk11.red("Score file not found: " + file));
|
|
1896
|
+
console.error(chalk11.dim('Run "tutti-ai init" to create a new project.'));
|
|
1897
|
+
process.exit(1);
|
|
1898
|
+
}
|
|
1899
|
+
const score = await ScoreLoader7.load(file);
|
|
1900
|
+
const events = new EventBus();
|
|
1901
|
+
const sessions = new InMemorySessionStore3();
|
|
1902
|
+
const runner = new AgentRunner(
|
|
1903
|
+
score.provider,
|
|
1904
|
+
events,
|
|
1905
|
+
sessions
|
|
1906
|
+
);
|
|
1907
|
+
const store = resolveStore();
|
|
1908
|
+
const engine = new SchedulerEngine(store, runner, events);
|
|
1909
|
+
let registered = 0;
|
|
1910
|
+
for (const [agentId, agent] of Object.entries(score.agents)) {
|
|
1911
|
+
if (!agent.schedule) continue;
|
|
1912
|
+
const resolvedAgent = agent.model ? agent : { ...agent, model: score.default_model ?? "claude-sonnet-4-20250514" };
|
|
1913
|
+
await engine.schedule(agentId, resolvedAgent, agent.schedule);
|
|
1914
|
+
registered++;
|
|
1915
|
+
}
|
|
1916
|
+
if (registered === 0) {
|
|
1917
|
+
console.log(chalk11.yellow("No agents have a schedule config. Nothing to run."));
|
|
1918
|
+
console.log(chalk11.dim("Add schedule: { cron: '...', input: '...' } to an agent in your score."));
|
|
1919
|
+
process.exit(0);
|
|
1920
|
+
}
|
|
1921
|
+
events.onAny((e) => {
|
|
1922
|
+
if (e.type === "schedule:triggered") {
|
|
1923
|
+
const ev = e;
|
|
1924
|
+
console.log(
|
|
1925
|
+
chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.cyan("triggered") + " " + chalk11.bold(ev.schedule_id) + " \u2192 " + ev.agent_name
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
if (e.type === "schedule:completed") {
|
|
1929
|
+
const ev = e;
|
|
1930
|
+
console.log(
|
|
1931
|
+
chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.green("completed") + " " + chalk11.bold(ev.schedule_id) + " " + chalk11.dim("(" + ev.duration_ms + "ms)")
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
if (e.type === "schedule:error") {
|
|
1935
|
+
const ev = e;
|
|
1936
|
+
console.log(
|
|
1937
|
+
chalk11.dim((/* @__PURE__ */ new Date()).toISOString()) + " " + chalk11.red("error") + " " + chalk11.bold(ev.schedule_id) + " \u2014 " + ev.error.message
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
engine.start();
|
|
1942
|
+
console.log("");
|
|
1943
|
+
console.log(chalk11.cyan.bold(" Tutti Scheduler"));
|
|
1944
|
+
console.log(chalk11.dim(" Score: " + (score.name ?? file)));
|
|
1945
|
+
console.log(chalk11.dim(" Schedules: " + registered));
|
|
1946
|
+
console.log(chalk11.dim(" Store: " + (SecretsManager6.optional("TUTTI_PG_URL") ? "postgres" : "memory")));
|
|
1947
|
+
console.log("");
|
|
1948
|
+
console.log(chalk11.dim(" Press Ctrl+C to stop."));
|
|
1949
|
+
console.log("");
|
|
1950
|
+
const shutdown = () => {
|
|
1951
|
+
console.log(chalk11.dim("\n Shutting down scheduler..."));
|
|
1952
|
+
engine.stop();
|
|
1953
|
+
if ("close" in store && typeof store.close === "function") {
|
|
1954
|
+
void store.close();
|
|
1955
|
+
}
|
|
1956
|
+
process.exit(0);
|
|
1957
|
+
};
|
|
1958
|
+
process.on("SIGINT", shutdown);
|
|
1959
|
+
process.on("SIGTERM", shutdown);
|
|
1960
|
+
await new Promise(() => void 0);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/commands/replay.ts
|
|
1964
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1965
|
+
import { writeFile } from "fs/promises";
|
|
1966
|
+
import { resolve as resolve12 } from "path";
|
|
1967
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
1968
|
+
import chalk12 from "chalk";
|
|
1969
|
+
import ora7 from "ora";
|
|
1970
|
+
import {
|
|
1971
|
+
PostgresSessionStore,
|
|
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,
|
|
2279
|
+
SchedulerEngine as SchedulerEngine2,
|
|
2280
|
+
PostgresScheduleStore as PostgresScheduleStore2,
|
|
2281
|
+
MemoryScheduleStore as MemoryScheduleStore2,
|
|
2282
|
+
AgentRunner as AgentRunner2,
|
|
2283
|
+
EventBus as EventBus2,
|
|
2284
|
+
InMemorySessionStore as InMemorySessionStore4,
|
|
2285
|
+
SecretsManager as SecretsManager8,
|
|
2286
|
+
createLogger as createLogger13
|
|
2287
|
+
} from "@tuttiai/core";
|
|
2288
|
+
var logger13 = createLogger13("tutti-cli");
|
|
2289
|
+
function resolveStore2() {
|
|
2290
|
+
const pgUrl = SecretsManager8.optional("TUTTI_PG_URL");
|
|
2291
|
+
if (pgUrl) {
|
|
2292
|
+
return new PostgresScheduleStore2({ connection_string: pgUrl });
|
|
2293
|
+
}
|
|
2294
|
+
logger13.warn("TUTTI_PG_URL not set \u2014 using in-memory store (schedules are ephemeral)");
|
|
2295
|
+
return new MemoryScheduleStore2();
|
|
2296
|
+
}
|
|
2297
|
+
async function closeStore(store) {
|
|
2298
|
+
if ("close" in store && typeof store.close === "function") {
|
|
2299
|
+
await store.close();
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function formatTrigger(r) {
|
|
2303
|
+
if (r.config.cron) return "cron: " + r.config.cron;
|
|
2304
|
+
if (r.config.every) return "every " + r.config.every;
|
|
2305
|
+
if (r.config.at) return "at " + r.config.at;
|
|
2306
|
+
return "?";
|
|
2307
|
+
}
|
|
2308
|
+
function pad(s, len) {
|
|
2309
|
+
return s.length >= len ? s : s + " ".repeat(len - s.length);
|
|
2310
|
+
}
|
|
2311
|
+
async function schedulesListCommand() {
|
|
2312
|
+
const store = resolveStore2();
|
|
2313
|
+
try {
|
|
2314
|
+
const records = await store.list();
|
|
2315
|
+
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.'));
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
console.log("");
|
|
2321
|
+
console.log(
|
|
2322
|
+
chalk13.dim(
|
|
2323
|
+
" " + pad("ID", 20) + pad("AGENT", 16) + pad("TRIGGER", 22) + pad("ENABLED", 10) + pad("RUNS", 8) + "CREATED"
|
|
2324
|
+
)
|
|
2325
|
+
);
|
|
2326
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(90)));
|
|
2327
|
+
for (const r of records) {
|
|
2328
|
+
const enabled = r.enabled ? chalk13.green("yes") : chalk13.red("no") + " ";
|
|
2329
|
+
const maxLabel = r.config.max_runs ? r.run_count + "/" + r.config.max_runs : String(r.run_count);
|
|
2330
|
+
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))
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
console.log("");
|
|
2335
|
+
} finally {
|
|
2336
|
+
await closeStore(store);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
async function schedulesEnableCommand(id) {
|
|
2340
|
+
const store = resolveStore2();
|
|
2341
|
+
try {
|
|
2342
|
+
const record = await store.get(id);
|
|
2343
|
+
if (!record) {
|
|
2344
|
+
console.error(chalk13.red('Schedule "' + id + '" not found.'));
|
|
2345
|
+
process.exit(1);
|
|
2346
|
+
}
|
|
2347
|
+
await store.setEnabled(id, true);
|
|
2348
|
+
console.log(chalk13.green('Schedule "' + id + '" enabled.'));
|
|
2349
|
+
} finally {
|
|
2350
|
+
await closeStore(store);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
async function schedulesDisableCommand(id) {
|
|
2354
|
+
const store = resolveStore2();
|
|
2355
|
+
try {
|
|
2356
|
+
const record = await store.get(id);
|
|
2357
|
+
if (!record) {
|
|
2358
|
+
console.error(chalk13.red('Schedule "' + id + '" not found.'));
|
|
2359
|
+
process.exit(1);
|
|
2360
|
+
}
|
|
2361
|
+
await store.setEnabled(id, false);
|
|
2362
|
+
console.log(chalk13.yellow('Schedule "' + id + '" disabled.'));
|
|
2363
|
+
} finally {
|
|
2364
|
+
await closeStore(store);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
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));
|
|
2371
|
+
process.exit(1);
|
|
2372
|
+
}
|
|
2373
|
+
const score = await ScoreLoader9.load(file);
|
|
2374
|
+
const events = new EventBus2();
|
|
2375
|
+
const sessions = new InMemorySessionStore4();
|
|
2376
|
+
const runner = new AgentRunner2(score.provider, events, sessions);
|
|
2377
|
+
const store = resolveStore2();
|
|
2378
|
+
try {
|
|
2379
|
+
const record = await store.get(id);
|
|
2380
|
+
if (!record) {
|
|
2381
|
+
console.error(chalk13.red('Schedule "' + id + '" not found.'));
|
|
2382
|
+
process.exit(1);
|
|
2383
|
+
}
|
|
2384
|
+
const agent = score.agents[record.agent_id];
|
|
2385
|
+
if (!agent) {
|
|
2386
|
+
console.error(chalk13.red('Agent "' + record.agent_id + '" not found in score.'));
|
|
2387
|
+
process.exit(1);
|
|
2388
|
+
}
|
|
2389
|
+
const resolvedAgent = agent.model ? agent : { ...agent, model: score.default_model ?? "claude-sonnet-4-20250514" };
|
|
2390
|
+
const engine = new SchedulerEngine2(store, runner, events);
|
|
2391
|
+
await engine.schedule(id, resolvedAgent, record.config);
|
|
2392
|
+
engine.start();
|
|
2393
|
+
console.log(chalk13.cyan('Triggering "' + id + '" (' + record.agent_id + ")..."));
|
|
2394
|
+
const run2 = await engine.trigger(id);
|
|
2395
|
+
engine.stop();
|
|
2396
|
+
if (run2.error) {
|
|
2397
|
+
console.log(chalk13.red(" Error: " + run2.error));
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
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"));
|
|
2402
|
+
if (run2.result) {
|
|
2403
|
+
const preview = run2.result.length > 200 ? run2.result.slice(0, 200) + "..." : run2.result;
|
|
2404
|
+
console.log(chalk13.dim(" Output: " + preview));
|
|
2405
|
+
}
|
|
2406
|
+
} finally {
|
|
2407
|
+
await closeStore(store);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
async function schedulesRunsCommand(id) {
|
|
2411
|
+
const store = resolveStore2();
|
|
2412
|
+
try {
|
|
2413
|
+
const record = await store.get(id);
|
|
2414
|
+
if (!record) {
|
|
2415
|
+
console.error(chalk13.red('Schedule "' + id + '" not found.'));
|
|
2416
|
+
process.exit(1);
|
|
2417
|
+
}
|
|
2418
|
+
if ("getRuns" in store && typeof store.getRuns === "function") {
|
|
2419
|
+
const runs = store.getRuns(id);
|
|
2420
|
+
if (runs.length === 0) {
|
|
2421
|
+
console.log(chalk13.dim("No runs recorded for this schedule."));
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const recent = runs.slice(-20);
|
|
2425
|
+
console.log("");
|
|
2426
|
+
console.log(chalk13.dim(" Showing last " + recent.length + " of " + runs.length + " runs:"));
|
|
2427
|
+
console.log("");
|
|
2428
|
+
for (const run2 of recent) {
|
|
2429
|
+
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");
|
|
2431
|
+
const preview = run2.error ? run2.error.slice(0, 80) : (run2.result ?? "").slice(0, 80);
|
|
2432
|
+
console.log(
|
|
2433
|
+
" " + chalk13.dim(run2.triggered_at.toISOString()) + " " + status + " " + chalk13.dim(duration) + " " + preview
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
console.log("");
|
|
2437
|
+
} 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."));
|
|
2440
|
+
}
|
|
2441
|
+
} finally {
|
|
2442
|
+
await closeStore(store);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
1868
2446
|
// src/index.ts
|
|
1869
2447
|
config();
|
|
1870
|
-
var
|
|
2448
|
+
var logger14 = createLogger14("tutti-cli");
|
|
1871
2449
|
process.on("unhandledRejection", (reason) => {
|
|
1872
|
-
|
|
2450
|
+
logger14.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
|
|
1873
2451
|
process.exit(1);
|
|
1874
2452
|
});
|
|
1875
2453
|
process.on("uncaughtException", (err) => {
|
|
1876
|
-
|
|
2454
|
+
logger14.error({ error: err.message }, "Fatal error");
|
|
1877
2455
|
process.exit(1);
|
|
1878
2456
|
});
|
|
1879
2457
|
var program = new Command();
|
|
1880
|
-
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");
|
|
1881
2459
|
program.command("init [project-name]").description("Create a new Tutti project").option("-t, --template <id>", "Project template to use").action(async (projectName, opts) => {
|
|
1882
2460
|
await initCommand(projectName, opts.template);
|
|
1883
2461
|
});
|
|
@@ -1933,5 +2511,27 @@ program.command("publish").description("Publish the current voice to npm and the
|
|
|
1933
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) => {
|
|
1934
2512
|
await evalCommand(suitePath, opts);
|
|
1935
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
|
+
});
|
|
2517
|
+
program.command("schedule [score]").description("Start the scheduler daemon \u2014 runs agents on their configured schedules").action(async (score) => {
|
|
2518
|
+
await scheduleCommand(score);
|
|
2519
|
+
});
|
|
2520
|
+
var schedulesCmd = program.command("schedules").description("Manage scheduled agents");
|
|
2521
|
+
schedulesCmd.command("list").description("Show all registered schedules").action(async () => {
|
|
2522
|
+
await schedulesListCommand();
|
|
2523
|
+
});
|
|
2524
|
+
schedulesCmd.command("enable <id>").description("Enable a disabled schedule").action(async (id) => {
|
|
2525
|
+
await schedulesEnableCommand(id);
|
|
2526
|
+
});
|
|
2527
|
+
schedulesCmd.command("disable <id>").description("Disable a schedule without deleting it").action(async (id) => {
|
|
2528
|
+
await schedulesDisableCommand(id);
|
|
2529
|
+
});
|
|
2530
|
+
schedulesCmd.command("trigger <id>").description("Manually trigger a scheduled run immediately").option("-s, --score <path>", "Path to score file (default: ./tutti.score.ts)").action(async (id, opts) => {
|
|
2531
|
+
await schedulesTriggerCommand(id, opts.score);
|
|
2532
|
+
});
|
|
2533
|
+
schedulesCmd.command("runs <id>").description("Show run history for a schedule (last 20 runs)").action(async (id) => {
|
|
2534
|
+
await schedulesRunsCommand(id);
|
|
2535
|
+
});
|
|
1936
2536
|
program.parse();
|
|
1937
2537
|
//# sourceMappingURL=index.js.map
|