@tuttiai/cli 0.4.0 → 0.5.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 +200 -54
- package/dist/index.js.map +1 -1
- package/package.json +23 -5
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { config } from "dotenv";
|
|
5
|
+
import { createLogger as createLogger6 } from "@tuttiai/core";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
|
|
7
8
|
// src/commands/init.ts
|
|
@@ -9,6 +10,8 @@ import { mkdirSync, writeFileSync, existsSync } from "fs";
|
|
|
9
10
|
import { join } from "path";
|
|
10
11
|
import chalk from "chalk";
|
|
11
12
|
import Enquirer from "enquirer";
|
|
13
|
+
import { createLogger } from "@tuttiai/core";
|
|
14
|
+
var logger = createLogger("tutti-cli");
|
|
12
15
|
var { prompt } = Enquirer;
|
|
13
16
|
async function initCommand(projectName) {
|
|
14
17
|
if (!projectName) {
|
|
@@ -20,12 +23,12 @@ async function initCommand(projectName) {
|
|
|
20
23
|
projectName = response.projectName;
|
|
21
24
|
}
|
|
22
25
|
if (!projectName) {
|
|
23
|
-
|
|
26
|
+
logger.error("Project name is required");
|
|
24
27
|
process.exit(1);
|
|
25
28
|
}
|
|
26
29
|
const dir = join(process.cwd(), projectName);
|
|
27
30
|
if (existsSync(dir)) {
|
|
28
|
-
|
|
31
|
+
logger.error({ dir: `${projectName}/` }, "Directory already exists");
|
|
29
32
|
process.exit(1);
|
|
30
33
|
}
|
|
31
34
|
mkdirSync(dir, { recursive: true });
|
|
@@ -51,7 +54,7 @@ async function initCommand(projectName) {
|
|
|
51
54
|
null,
|
|
52
55
|
2
|
|
53
56
|
),
|
|
54
|
-
".env.example": "ANTHROPIC_API_KEY=your_key_here\n",
|
|
57
|
+
".env.example": "ANTHROPIC_API_KEY=your_key_here\n\n# Log level: debug | info | warn | error (default: info)\nTUTTI_LOG_LEVEL=info\n\n# OpenTelemetry (optional)\n# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\n# OTEL_SERVICE_NAME=tutti\n",
|
|
55
58
|
".gitignore": "node_modules\ndist\n.env\n",
|
|
56
59
|
"tsconfig.json": JSON.stringify(
|
|
57
60
|
{
|
|
@@ -129,25 +132,24 @@ import {
|
|
|
129
132
|
AnthropicProvider,
|
|
130
133
|
OpenAIProvider,
|
|
131
134
|
GeminiProvider,
|
|
132
|
-
SecretsManager
|
|
135
|
+
SecretsManager,
|
|
136
|
+
createLogger as createLogger2
|
|
133
137
|
} from "@tuttiai/core";
|
|
138
|
+
var logger2 = createLogger2("tutti-cli");
|
|
134
139
|
async function runCommand(scorePath) {
|
|
135
140
|
const file = resolve(scorePath ?? "./tutti.score.ts");
|
|
136
141
|
if (!existsSync2(file)) {
|
|
137
|
-
|
|
138
|
-
console.error(
|
|
139
|
-
chalk2.dim('Run "tutti-ai init" to create a new project.')
|
|
140
|
-
);
|
|
142
|
+
logger2.error({ file }, "Score file not found");
|
|
143
|
+
console.error(chalk2.dim('Run "tutti-ai init" to create a new project.'));
|
|
141
144
|
process.exit(1);
|
|
142
145
|
}
|
|
143
146
|
let score;
|
|
144
147
|
try {
|
|
145
148
|
score = await ScoreLoader.load(file);
|
|
146
149
|
} catch (err) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
150
|
+
logger2.error(
|
|
151
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
152
|
+
"Failed to load score"
|
|
151
153
|
);
|
|
152
154
|
process.exit(1);
|
|
153
155
|
}
|
|
@@ -160,12 +162,7 @@ async function runCommand(scorePath) {
|
|
|
160
162
|
if (score.provider instanceof ProviderClass) {
|
|
161
163
|
const key = SecretsManager.optional(envVar);
|
|
162
164
|
if (!key) {
|
|
163
|
-
|
|
164
|
-
chalk2.red(
|
|
165
|
-
`Missing API key: ${envVar}
|
|
166
|
-
Add it to your .env file: ${envVar}=your_value_here`
|
|
167
|
-
)
|
|
168
|
-
);
|
|
165
|
+
logger2.error({ envVar }, "Missing API key");
|
|
169
166
|
process.exit(1);
|
|
170
167
|
}
|
|
171
168
|
}
|
|
@@ -173,7 +170,7 @@ Add it to your .env file: ${envVar}=your_value_here`
|
|
|
173
170
|
const runtime = new TuttiRuntime(score);
|
|
174
171
|
const spinner = ora({ color: "cyan" });
|
|
175
172
|
runtime.events.on("agent:start", (e) => {
|
|
176
|
-
|
|
173
|
+
logger2.info({ agent: e.agent_name }, "Running agent");
|
|
177
174
|
});
|
|
178
175
|
runtime.events.on("llm:request", () => {
|
|
179
176
|
spinner.start("Thinking...");
|
|
@@ -182,26 +179,22 @@ Add it to your .env file: ${envVar}=your_value_here`
|
|
|
182
179
|
spinner.stop();
|
|
183
180
|
});
|
|
184
181
|
runtime.events.on("tool:start", (e) => {
|
|
185
|
-
|
|
182
|
+
logger2.info({ tool: e.tool_name }, "Using tool");
|
|
186
183
|
});
|
|
187
184
|
runtime.events.on("tool:end", (e) => {
|
|
188
|
-
|
|
185
|
+
logger2.debug({ tool: e.tool_name }, "Tool done");
|
|
189
186
|
});
|
|
190
187
|
runtime.events.on("tool:error", (e) => {
|
|
191
|
-
|
|
188
|
+
logger2.error({ tool: e.tool_name }, "Tool error");
|
|
192
189
|
});
|
|
193
190
|
runtime.events.on("security:injection_detected", (e) => {
|
|
194
|
-
|
|
195
|
-
chalk2.yellow(
|
|
196
|
-
` [security] Potential prompt injection detected in: ${e.tool_name}`
|
|
197
|
-
)
|
|
198
|
-
);
|
|
191
|
+
logger2.warn({ tool: e.tool_name }, "Potential prompt injection detected");
|
|
199
192
|
});
|
|
200
193
|
runtime.events.on("budget:warning", () => {
|
|
201
|
-
|
|
194
|
+
logger2.warn("Approaching token budget (80%)");
|
|
202
195
|
});
|
|
203
196
|
runtime.events.on("budget:exceeded", () => {
|
|
204
|
-
|
|
197
|
+
logger2.error("Token budget exceeded \u2014 stopping");
|
|
205
198
|
});
|
|
206
199
|
const rl = createInterface({
|
|
207
200
|
input: process.stdin,
|
|
@@ -228,15 +221,9 @@ ${result.output}
|
|
|
228
221
|
`);
|
|
229
222
|
} catch (err) {
|
|
230
223
|
spinner.stop();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
);
|
|
236
|
-
console.error(
|
|
237
|
-
chalk2.dim(
|
|
238
|
-
'Run "tutti-ai check" to validate your score file.'
|
|
239
|
-
)
|
|
224
|
+
logger2.error(
|
|
225
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
226
|
+
"Something went wrong"
|
|
240
227
|
);
|
|
241
228
|
}
|
|
242
229
|
}
|
|
@@ -253,6 +240,8 @@ import { resolve as resolve2 } from "path";
|
|
|
253
240
|
import { execSync } from "child_process";
|
|
254
241
|
import chalk3 from "chalk";
|
|
255
242
|
import ora2 from "ora";
|
|
243
|
+
import { createLogger as createLogger3 } from "@tuttiai/core";
|
|
244
|
+
var logger3 = createLogger3("tutti-cli");
|
|
256
245
|
var OFFICIAL_VOICES = {
|
|
257
246
|
filesystem: {
|
|
258
247
|
package: "@tuttiai/filesystem",
|
|
@@ -277,6 +266,20 @@ var OFFICIAL_VOICES = {
|
|
|
277
266
|
Add to your score:
|
|
278
267
|
${chalk3.cyan('import { PlaywrightVoice } from "@tuttiai/playwright"')}
|
|
279
268
|
${chalk3.cyan("voices: [new PlaywrightVoice()]")}`
|
|
269
|
+
},
|
|
270
|
+
postgres: {
|
|
271
|
+
package: "pg",
|
|
272
|
+
setup: ` Add ${chalk3.bold("DATABASE_URL")} to your .env file:
|
|
273
|
+
${chalk3.cyan("DATABASE_URL=postgres://user:pass@localhost:5432/tutti")}
|
|
274
|
+
|
|
275
|
+
Add to your score:
|
|
276
|
+
${chalk3.cyan("memory: { provider: 'postgres' }")}
|
|
277
|
+
|
|
278
|
+
Or with an explicit URL:
|
|
279
|
+
${chalk3.cyan("memory: { provider: 'postgres', url: process.env.DATABASE_URL }")}
|
|
280
|
+
|
|
281
|
+
Use the async factory for initialization:
|
|
282
|
+
${chalk3.cyan("const tutti = await TuttiRuntime.create(score)")}`
|
|
280
283
|
}
|
|
281
284
|
};
|
|
282
285
|
function resolvePackageName(input) {
|
|
@@ -303,12 +306,8 @@ async function addCommand(voiceName) {
|
|
|
303
306
|
const packageName = resolvePackageName(voiceName);
|
|
304
307
|
const pkgPath = resolve2(process.cwd(), "package.json");
|
|
305
308
|
if (!existsSync3(pkgPath)) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
);
|
|
309
|
-
console.error(
|
|
310
|
-
chalk3.dim('Run "tutti-ai init" to create a new project first.')
|
|
311
|
-
);
|
|
309
|
+
logger3.error("No package.json found in the current directory");
|
|
310
|
+
console.error(chalk3.dim('Run "tutti-ai init" to create a new project first.'));
|
|
312
311
|
process.exit(1);
|
|
313
312
|
}
|
|
314
313
|
if (isAlreadyInstalled(packageName)) {
|
|
@@ -325,7 +324,7 @@ async function addCommand(voiceName) {
|
|
|
325
324
|
} catch (error) {
|
|
326
325
|
spinner.fail(`Failed to install ${packageName}`);
|
|
327
326
|
const message = error instanceof Error ? error.message : String(error);
|
|
328
|
-
|
|
327
|
+
logger3.error({ error: message, package: packageName }, "Installation failed");
|
|
329
328
|
process.exit(1);
|
|
330
329
|
}
|
|
331
330
|
const official = OFFICIAL_VOICES[voiceName];
|
|
@@ -352,8 +351,10 @@ import {
|
|
|
352
351
|
AnthropicProvider as AnthropicProvider2,
|
|
353
352
|
OpenAIProvider as OpenAIProvider2,
|
|
354
353
|
GeminiProvider as GeminiProvider2,
|
|
355
|
-
SecretsManager as SecretsManager2
|
|
354
|
+
SecretsManager as SecretsManager2,
|
|
355
|
+
createLogger as createLogger4
|
|
356
356
|
} from "@tuttiai/core";
|
|
357
|
+
var logger4 = createLogger4("tutti-cli");
|
|
357
358
|
var ok = (msg) => console.log(chalk4.green(" \u2714 " + msg));
|
|
358
359
|
var fail = (msg) => console.log(chalk4.red(" \u2718 " + msg));
|
|
359
360
|
async function checkCommand(scorePath) {
|
|
@@ -371,10 +372,9 @@ Checking ${file}...
|
|
|
371
372
|
ok("Score file is valid");
|
|
372
373
|
} catch (err) {
|
|
373
374
|
fail("Score validation failed");
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
)
|
|
375
|
+
logger4.error(
|
|
376
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
377
|
+
"Score validation failed"
|
|
378
378
|
);
|
|
379
379
|
process.exit(1);
|
|
380
380
|
}
|
|
@@ -439,19 +439,162 @@ Checking ${file}...
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
// src/commands/studio.ts
|
|
443
|
+
import { existsSync as existsSync5 } from "fs";
|
|
444
|
+
import { resolve as resolve4 } from "path";
|
|
445
|
+
import { exec } from "child_process";
|
|
446
|
+
import express from "express";
|
|
447
|
+
import chalk5 from "chalk";
|
|
448
|
+
import {
|
|
449
|
+
TuttiRuntime as TuttiRuntime2,
|
|
450
|
+
ScoreLoader as ScoreLoader3,
|
|
451
|
+
createLogger as createLogger5
|
|
452
|
+
} from "@tuttiai/core";
|
|
453
|
+
var logger5 = createLogger5("tutti-studio");
|
|
454
|
+
var PORT = 4747;
|
|
455
|
+
function safeStringify(obj) {
|
|
456
|
+
return JSON.stringify(obj, (_key, value) => {
|
|
457
|
+
if (value instanceof Error) return { message: value.message, name: value.name };
|
|
458
|
+
if (typeof value === "function") return void 0;
|
|
459
|
+
return value;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function openBrowser(url) {
|
|
463
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
464
|
+
exec(cmd + " " + url);
|
|
465
|
+
}
|
|
466
|
+
async function studioCommand(scorePath) {
|
|
467
|
+
const file = resolve4(scorePath ?? "./tutti.score.ts");
|
|
468
|
+
if (!existsSync5(file)) {
|
|
469
|
+
logger5.error({ file }, "Score file not found");
|
|
470
|
+
console.error(chalk5.dim('Run "tutti-ai init" to create a new project.'));
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
let score;
|
|
474
|
+
try {
|
|
475
|
+
score = await ScoreLoader3.load(file);
|
|
476
|
+
} catch (err) {
|
|
477
|
+
logger5.error({ error: err instanceof Error ? err.message : String(err) }, "Failed to load score");
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
const runtime = new TuttiRuntime2(score);
|
|
481
|
+
const sessionRegistry = /* @__PURE__ */ new Map();
|
|
482
|
+
runtime.events.on("agent:start", (e) => {
|
|
483
|
+
if (!sessionRegistry.has(e.session_id)) {
|
|
484
|
+
sessionRegistry.set(e.session_id, { agent_name: e.agent_name, created_at: /* @__PURE__ */ new Date() });
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
488
|
+
runtime.events.onAny((event) => {
|
|
489
|
+
const data = safeStringify(event);
|
|
490
|
+
for (const client of sseClients) {
|
|
491
|
+
client.write("event: tutti\ndata: " + data + "\n\n");
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
const app = express();
|
|
495
|
+
app.use(express.json());
|
|
496
|
+
app.get("/events", (_req, res) => {
|
|
497
|
+
res.writeHead(200, {
|
|
498
|
+
"Content-Type": "text/event-stream",
|
|
499
|
+
"Cache-Control": "no-cache",
|
|
500
|
+
Connection: "keep-alive"
|
|
501
|
+
});
|
|
502
|
+
res.write(":\n\n");
|
|
503
|
+
sseClients.add(res);
|
|
504
|
+
_req.on("close", () => sseClients.delete(res));
|
|
505
|
+
});
|
|
506
|
+
app.get("/api/score", (_req, res) => {
|
|
507
|
+
const agents = Object.fromEntries(
|
|
508
|
+
Object.entries(runtime.score.agents).map(([id, agent]) => [
|
|
509
|
+
id,
|
|
510
|
+
{
|
|
511
|
+
name: agent.name,
|
|
512
|
+
description: agent.description,
|
|
513
|
+
model: agent.model,
|
|
514
|
+
role: agent.role,
|
|
515
|
+
delegates: agent.delegates,
|
|
516
|
+
voice_count: agent.voices.length,
|
|
517
|
+
voices: agent.voices.map((v) => v.name)
|
|
518
|
+
}
|
|
519
|
+
])
|
|
520
|
+
);
|
|
521
|
+
res.json({
|
|
522
|
+
name: runtime.score.name,
|
|
523
|
+
description: runtime.score.description,
|
|
524
|
+
default_model: runtime.score.default_model,
|
|
525
|
+
entry: runtime.score.entry,
|
|
526
|
+
agents
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
app.get("/api/sessions", (_req, res) => {
|
|
530
|
+
const sessions = Array.from(sessionRegistry.entries()).map(([id, meta]) => {
|
|
531
|
+
const session = runtime.getSession(id);
|
|
532
|
+
return {
|
|
533
|
+
id,
|
|
534
|
+
agent_name: meta.agent_name,
|
|
535
|
+
message_count: session?.messages.length ?? 0,
|
|
536
|
+
created_at: meta.created_at
|
|
537
|
+
};
|
|
538
|
+
});
|
|
539
|
+
res.json(sessions.reverse());
|
|
540
|
+
});
|
|
541
|
+
app.get("/api/sessions/:id", (req, res) => {
|
|
542
|
+
const session = runtime.getSession(req.params.id);
|
|
543
|
+
if (!session) {
|
|
544
|
+
res.status(404).json({ error: "Session not found" });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
res.json(session);
|
|
548
|
+
});
|
|
549
|
+
app.post("/api/run", async (req, res) => {
|
|
550
|
+
const { agent, input, session_id } = req.body;
|
|
551
|
+
if (!agent || !input) {
|
|
552
|
+
res.status(400).json({ error: "agent and input are required" });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const result = await runtime.run(agent, input, session_id);
|
|
557
|
+
res.json(result);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
app.get("/", (_req, res) => {
|
|
563
|
+
res.type("html").send(getStudioHtml());
|
|
564
|
+
});
|
|
565
|
+
app.listen(PORT, () => {
|
|
566
|
+
const url = "http://localhost:" + PORT;
|
|
567
|
+
console.log();
|
|
568
|
+
console.log(chalk5.bold(" Tutti Studio"));
|
|
569
|
+
console.log(chalk5.dim(" " + url));
|
|
570
|
+
console.log();
|
|
571
|
+
console.log(chalk5.dim(" Score: ") + (runtime.score.name ?? file));
|
|
572
|
+
console.log(chalk5.dim(" Agents: ") + Object.keys(runtime.score.agents).join(", "));
|
|
573
|
+
console.log();
|
|
574
|
+
openBrowser(url);
|
|
575
|
+
});
|
|
576
|
+
process.on("SIGINT", () => {
|
|
577
|
+
console.log(chalk5.dim("\nShutting down Tutti Studio..."));
|
|
578
|
+
process.exit(0);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function getStudioHtml() {
|
|
582
|
+
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Tutti Studio</title><style>*{margin:0;padding:0;box-sizing:border-box}:root{--bg:#0a0a0f;--panel:#12121a;--card:#1a1a26;--input:#0f0f17;--border:#2a2a3a;--text:#e2e8f0;--muted:#64748b;--purple:#8b5cf6;--teal:#14b8a6;--blue:#3b82f6;--green:#10b981;--red:#ef4444;--orange:#f97316;--amber:#f59e0b;--indigo:#6366f1;}html,body{height:100%;font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);font-size:13px}#app{display:flex;flex-direction:column;height:100vh}header{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--panel)}header .logo{font-weight:700;font-size:15px;letter-spacing:.5px}header .logo span{color:var(--purple)}header .meta{color:var(--muted);font-size:12px}header .status{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted)}header .dot{width:7px;height:7px;border-radius:50%;background:var(--green)}header .dot.off{background:var(--red)}main{display:grid;grid-template-columns:260px 1fr 280px;flex:1;overflow:hidden;border-bottom:1px solid var(--border)}.panel{display:flex;flex-direction:column;border-right:1px solid var(--border);overflow:hidden}.panel:last-child{border-right:none}.panel-title{padding:10px 14px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--muted);border-bottom:1px solid var(--border);background:var(--panel);flex-shrink:0}.panel-body{flex:1;overflow-y:auto;padding:10px}.panel-body::-webkit-scrollbar{width:5px}.panel-body::-webkit-scrollbar-track{background:transparent}.panel-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}#graph-panel .panel-body{padding:0;display:flex;align-items:center;justify-content:center}#graph-panel svg text{font-family:system-ui,-apple-system,sans-serif}#events-panel{display:flex;flex-direction:column}#event-stream{flex:1;overflow-y:auto;padding:10px}#event-stream::-webkit-scrollbar{width:5px}#event-stream::-webkit-scrollbar-track{background:transparent}#event-stream::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}.ev{padding:7px 10px;margin-bottom:6px;border-radius:6px;background:var(--card);border-left:3px solid var(--muted);font-size:12px;line-height:1.5}.ev .ev-head{display:flex;justify-content:space-between;align-items:center}.ev .ev-type{font-weight:600;font-family:"SF Mono",Menlo,monospace;font-size:11px}.ev .ev-time{color:var(--muted);font-size:10px;font-family:"SF Mono",Menlo,monospace}.ev .ev-detail{color:var(--muted);margin-top:3px;font-size:11px;word-break:break-all}.ev.agent{border-left-color:var(--purple)}.ev.agent .ev-type{color:var(--purple)}.ev.turn{border-left-color:var(--blue)}.ev.turn .ev-type{color:var(--blue)}.ev.llm{border-left-color:var(--green)}.ev.llm .ev-type{color:var(--green)}.ev.tool{border-left-color:var(--teal)}.ev.tool .ev-type{color:var(--teal)}.ev.tool-error{border-left-color:var(--red)}.ev.tool-error .ev-type{color:var(--red)}.ev.security{border-left-color:var(--orange)}.ev.security .ev-type{color:var(--orange)}.ev.budget-warn{border-left-color:var(--amber)}.ev.budget-warn .ev-type{color:var(--amber)}.ev.budget-exceed{border-left-color:var(--red)}.ev.budget-exceed .ev-type{color:var(--red)}.ev.delegate{border-left-color:var(--indigo)}.ev.delegate .ev-type{color:var(--indigo)}#input-bar{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--border);background:var(--panel);flex-shrink:0}#agent-select{background:var(--input);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:12px;outline:none;cursor:pointer;min-width:110px}#user-input{flex:1;background:var(--input);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 12px;font-size:13px;outline:none}#user-input:focus{border-color:var(--purple)}#send-btn{background:var(--purple);color:#fff;border:none;border-radius:6px;padding:6px 16px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}#send-btn:hover{opacity:.9}#send-btn:disabled{opacity:.4;cursor:default}.session-item{padding:8px 10px;margin-bottom:4px;border-radius:6px;background:var(--card);cursor:pointer;transition:background .15s}.session-item:hover{background:#22223a}.session-item.active{background:#22223a;border:1px solid var(--purple)}.session-id{font-family:"SF Mono",Menlo,monospace;font-size:11px;color:var(--purple)}.session-meta{font-size:11px;color:var(--muted);margin-top:2px}#session-detail{margin-top:10px;border-top:1px solid var(--border);padding-top:10px}.msg{padding:6px 8px;margin-bottom:4px;border-radius:5px;font-size:12px;line-height:1.5;word-break:break-word}.msg.user{background:#1c1c3a;border-left:2px solid var(--blue)}.msg.assistant{background:#1a2a1a;border-left:2px solid var(--green)}.msg .msg-role{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px}.msg.user .msg-role{color:var(--blue)}.msg.assistant .msg-role{color:var(--green)}footer{display:flex;align-items:center;gap:32px;padding:8px 20px;background:var(--panel);font-size:12px}.token-item{display:flex;align-items:center;gap:6px}.token-label{color:var(--muted)}.token-val{font-family:"SF Mono",Menlo,monospace;font-weight:600}.token-val.input{color:var(--blue)}.token-val.output{color:var(--green)}.token-val.cost{color:var(--amber)}.empty{color:var(--muted);text-align:center;padding:30px 10px;font-size:12px}</style></head><body><div id="app"><header> <div class="logo"><span>♫</span> Tutti Studio</div> <div class="meta" id="score-name"></div> <div class="status"><div class="dot" id="sse-dot"></div><span id="sse-label">connecting</span></div></header><main> <div class="panel" id="graph-panel"> <div class="panel-title">Agent Graph</div> <div class="panel-body" id="graph-body"></div> </div> <div class="panel" id="events-panel"> <div class="panel-title">Live Event Stream</div> <div id="event-stream"><div class="empty">Waiting for events…<br>Send a message below to start an agent run.</div></div> <div id="input-bar"> <select id="agent-select"></select> <input id="user-input" placeholder="Type a message…" autocomplete="off"> <button id="send-btn">Send</button> </div> </div> <div class="panel" id="sessions-panel"> <div class="panel-title">Sessions</div> <div class="panel-body" id="sessions-body"><div class="empty">No sessions yet</div></div> </div></main><footer> <div class="token-item"><span class="token-label">↓ Input</span><span class="token-val input" id="tok-in">0</span></div> <div class="token-item"><span class="token-label">↑ Output</span><span class="token-val output" id="tok-out">0</span></div> <div class="token-item"><span class="token-label">$ Est. cost</span><span class="token-val cost" id="tok-cost">0.0000</span></div></footer></div><script>(function(){var tokIn=0,tokOut=0;var sessionMap={};var activeSession=null;/* ---- helpers ---- */function esc(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML}function fmt(n){return n.toLocaleString()}function timeStr(){var d=new Date();return ("0"+d.getHours()).slice(-2)+":"+("0"+d.getMinutes()).slice(-2)+":"+("0"+d.getSeconds()).slice(-2)}function truncId(id){return id.slice(0,8)}/* ---- score + graph ---- */function loadScore(){ fetch("/api/score").then(function(r){return r.json()}).then(function(s){ document.getElementById("score-name").textContent=s.name||"tutti.score.ts"; var sel=document.getElementById("agent-select"); sel.innerHTML=""; Object.keys(s.agents).forEach(function(id){ var o=document.createElement("option");o.value=id;o.textContent=s.agents[id].name;sel.appendChild(o); }); renderGraph(s); });}function renderGraph(score){ var body=document.getElementById("graph-body"); var W=260,ids=Object.keys(score.agents),N=ids.length; if(N===0){body.innerHTML="<div class=\\"empty\\">No agents</div>";return} var hasDelegate=false; ids.forEach(function(id){if(score.agents[id].delegates&&score.agents[id].delegates.length)hasDelegate=true}); var nodeR=26,padY=90,padTop=50; var leftIds=[],rightIds=[]; if(hasDelegate){ ids.forEach(function(id){var a=score.agents[id];if(a.delegates&&a.delegates.length)leftIds.push(id);else rightIds.push(id)}); }else{leftIds=ids} var cols=hasDelegate?2:1; var cx1=cols===1?W/2:72,cx2=W-72; var H=Math.max(leftIds.length,rightIds.length)*padY+padTop*2; if(H<200)H=200; var pos={}; var svg='<svg xmlns="http://www.w3.org/2000/svg" width="'+W+'" height="'+H+'" viewBox="0 0 '+W+" "+H+'">'; svg+='<defs><marker id="ah" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="5" markerHeight="5" orient="auto"><path d="M0 0L10 5L0 10z" fill="#64748b"/></marker></defs>'; function drawNode(id,cx,cy){ var a=score.agents[id]; var col=a.role==="orchestrator"?"#8b5cf6":"#14b8a6"; pos[id]={x:cx,y:cy}; svg+='<circle cx="'+cx+'" cy="'+cy+'" r="'+nodeR+'" fill="'+col+'" fill-opacity="0.15" stroke="'+col+'" stroke-width="2"/>'; svg+='<text x="'+cx+'" y="'+(cy+4)+'" text-anchor="middle" fill="#e2e8f0" font-size="10" font-weight="600">'+esc(a.name)+'</text>'; var model=a.model||score.default_model||""; if(model){var sh=model.replace(/-\\d{8}$/,"");if(sh.length>18)sh=sh.slice(0,18)+"\\u2026";svg+='<text x="'+cx+'" y="'+(cy+nodeR+14)+'" text-anchor="middle" fill="#64748b" font-size="9">'+esc(sh)+'</text>'} svg+='<text x="'+cx+'" y="'+(cy+nodeR+26)+'" text-anchor="middle" fill="#64748b" font-size="9">'+a.voice_count+" voice"+(a.voice_count!==1?"s":"")+'</text>'; } leftIds.forEach(function(id,i){drawNode(id,cx1,padTop+i*padY)}); rightIds.forEach(function(id,i){drawNode(id,cx2,padTop+i*padY)}); ids.forEach(function(id){ var a=score.agents[id]; if(a.delegates)a.delegates.forEach(function(did){ if(pos[id]&&pos[did]){ var x1=pos[id].x+nodeR,y1=pos[id].y,x2=pos[did].x-nodeR,y2=pos[did].y; var mx=(x1+x2)/2; svg+='<path d="M'+x1+" "+y1+" C"+mx+" "+y1+" "+mx+" "+y2+" "+x2+" "+y2+'" fill="none" stroke="#64748b" stroke-width="1.5" stroke-dasharray="4 3" marker-end="url(#ah)"/>'; } }); }); svg+="</svg>"; body.innerHTML=svg;}/* ---- SSE ---- */function connectSSE(){ var es=new EventSource("/events"); es.addEventListener("tutti",function(e){ var ev=JSON.parse(e.data); addEvent(ev); if(ev.type==="llm:response"&&ev.response&&ev.response.usage){ tokIn+=ev.response.usage.input_tokens||0; tokOut+=ev.response.usage.output_tokens||0; document.getElementById("tok-in").textContent=fmt(tokIn); document.getElementById("tok-out").textContent=fmt(tokOut); document.getElementById("tok-cost").textContent=estimateCost(tokIn,tokOut); } if(ev.type==="agent:start"||ev.type==="agent:end")refreshSessions(); }); es.onopen=function(){document.getElementById("sse-dot").className="dot";document.getElementById("sse-label").textContent="connected"}; es.onerror=function(){document.getElementById("sse-dot").className="dot off";document.getElementById("sse-label").textContent="disconnected"};}function estimateCost(inp,out){ var c=(inp/1e6)*3+(out/1e6)*15; return c.toFixed(4);}function evClass(t){ if(t.indexOf("agent")===0)return "agent"; if(t.indexOf("turn")===0)return "turn"; if(t==="llm:request"||t==="llm:response")return "llm"; if(t==="tool:error")return "tool-error"; if(t.indexOf("tool")===0)return "tool"; if(t.indexOf("security")===0)return "security"; if(t==="budget:warning")return "budget-warn"; if(t==="budget:exceeded")return "budget-exceed"; if(t.indexOf("delegate")===0)return "delegate"; return "";}function evDetail(ev){ var parts=[]; if(ev.agent_name)parts.push("agent: "+ev.agent_name); if(ev.session_id)parts.push("session: "+truncId(ev.session_id)); if(ev.turn!==undefined)parts.push("turn: "+ev.turn); if(ev.tool_name)parts.push("tool: "+ev.tool_name); if(ev.from)parts.push("from: "+ev.from); if(ev.to)parts.push("to: "+ev.to); if(ev.tokens!==undefined)parts.push("tokens: "+fmt(ev.tokens)); if(ev.cost_usd!==undefined)parts.push("cost: $"+ev.cost_usd.toFixed(4)); if(ev.response&&ev.response.usage)parts.push("tokens: "+fmt(ev.response.usage.input_tokens)+" in / "+fmt(ev.response.usage.output_tokens)+" out"); if(ev.error){var em=typeof ev.error==="object"?ev.error.message||"":ev.error;if(em)parts.push("error: "+em)} if(ev.patterns)parts.push("patterns: "+ev.patterns.join(", ")); return parts.join(" · ");}var firstEvent=true;function addEvent(ev){ var stream=document.getElementById("event-stream"); if(firstEvent){stream.innerHTML="";firstEvent=false} var div=document.createElement("div"); div.className="ev "+evClass(ev.type); div.innerHTML='<div class="ev-head"><span class="ev-type">'+esc(ev.type)+'</span><span class="ev-time">'+timeStr()+'</span></div>'; var det=evDetail(ev); if(det)div.innerHTML+='<div class="ev-detail">'+det+"</div>"; stream.appendChild(div); stream.scrollTop=stream.scrollHeight;}/* ---- sessions ---- */function refreshSessions(){ fetch("/api/sessions").then(function(r){return r.json()}).then(function(list){ var body=document.getElementById("sessions-body"); if(!list.length){body.innerHTML='<div class="empty">No sessions yet</div>';return} var html=""; list.forEach(function(s){ var cls="session-item"+(activeSession===s.id?" active":""); html+='<div class="'+cls+'" data-id="'+s.id+'">'; html+='<div class="session-id">'+truncId(s.id)+"</div>"; html+='<div class="session-meta">'+esc(s.agent_name)+" · "+s.message_count+" msgs</div>"; html+="</div>"; }); if(activeSession)html+='<div id="session-detail"></div>'; body.innerHTML=html; body.querySelectorAll(".session-item").forEach(function(el){ el.addEventListener("click",function(){selectSession(el.getAttribute("data-id"))}); }); if(activeSession)loadSessionDetail(activeSession); });}function selectSession(id){ activeSession=activeSession===id?null:id; refreshSessions();}function loadSessionDetail(id){ var det=document.getElementById("session-detail"); if(!det)return; fetch("/api/sessions/"+id).then(function(r){return r.json()}).then(function(session){ if(!session||session.error){det.innerHTML='<div class="empty">Session not found</div>';return} var html=""; (session.messages||[]).forEach(function(m){ var role=m.role; var text=""; if(typeof m.content==="string")text=m.content; else if(Array.isArray(m.content)){ m.content.forEach(function(b){ if(b.type==="text")text+=b.text+"\\n"; else if(b.type==="tool_use")text+="[tool_use: "+b.name+"]\\n"; else if(b.type==="tool_result")text+="[tool_result]\\n"; }); } html+='<div class="msg '+role+'"><div class="msg-role">'+role+"</div>"+esc(text.trim())+"</div>"; }); det.innerHTML=html; });}/* ---- send ---- */function sendMessage(){ var agentSel=document.getElementById("agent-select"); var inputEl=document.getElementById("user-input"); var btn=document.getElementById("send-btn"); var agent=agentSel.value; var input=inputEl.value.trim(); if(!input)return; btn.disabled=true;btn.textContent="Running\\u2026"; inputEl.value=""; var sid=sessionMap[agent]||undefined; fetch("/api/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:agent,input:input,session_id:sid})}) .then(function(r){return r.json()}) .then(function(result){ if(result.session_id)sessionMap[agent]=result.session_id; if(result.output){ addEvent({type:"__output",agent_name:agent,output:result.output}); } refreshSessions(); }) .catch(function(err){addEvent({type:"__error",error:err.message||String(err)})}) .finally(function(){btn.disabled=false;btn.textContent="Send"});}document.getElementById("send-btn").addEventListener("click",sendMessage);document.getElementById("user-input").addEventListener("keydown",function(e){if(e.key==="Enter"&&!e.shiftKey){e.preventDefault();sendMessage()}});/* ---- init ---- */loadScore();connectSSE();})();</script></body></html>`;
|
|
583
|
+
}
|
|
584
|
+
|
|
442
585
|
// src/index.ts
|
|
443
586
|
config();
|
|
587
|
+
var logger6 = createLogger6("tutti-cli");
|
|
444
588
|
process.on("unhandledRejection", (reason) => {
|
|
445
|
-
|
|
446
|
-
console.error("Report at github.com/tuttiai/tutti/issues");
|
|
589
|
+
logger6.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
|
|
447
590
|
process.exit(1);
|
|
448
591
|
});
|
|
449
592
|
process.on("uncaughtException", (err) => {
|
|
450
|
-
|
|
593
|
+
logger6.error({ error: err.message }, "Fatal error");
|
|
451
594
|
process.exit(1);
|
|
452
595
|
});
|
|
453
596
|
var program = new Command();
|
|
454
|
-
program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.
|
|
597
|
+
program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.6.0");
|
|
455
598
|
program.command("init [project-name]").description("Create a new Tutti project").action(async (projectName) => {
|
|
456
599
|
await initCommand(projectName);
|
|
457
600
|
});
|
|
@@ -467,5 +610,8 @@ program.command("check [score]").description("Validate a score file without runn
|
|
|
467
610
|
program.command("doctor [score]").description("Alias for check \u2014 validate a score file").action(async (score) => {
|
|
468
611
|
await checkCommand(score);
|
|
469
612
|
});
|
|
613
|
+
program.command("studio [score]").description("Launch Tutti Studio \u2014 local web UI for inspecting agent runs").action(async (score) => {
|
|
614
|
+
await studioCommand(score);
|
|
615
|
+
});
|
|
470
616
|
program.parse();
|
|
471
617
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/run.ts","../src/commands/add.ts","../src/commands/check.ts"],"sourcesContent":["import { config } from \"dotenv\";\nconfig();\n\nprocess.on(\"unhandledRejection\", (reason) => {\n console.error(\"[tutti] Unhandled error:\", reason);\n console.error(\"Report at github.com/tuttiai/tutti/issues\");\n process.exit(1);\n});\n\nprocess.on(\"uncaughtException\", (err) => {\n console.error(\"[tutti] Fatal error:\", err.message);\n process.exit(1);\n});\n\nimport { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { addCommand } from \"./commands/add.js\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"tutti-ai\")\n .description(\"Tutti — multi-agent orchestration. All agents. All together.\")\n .version(\"0.4.0\");\n\nprogram\n .command(\"init [project-name]\")\n .description(\"Create a new Tutti project\")\n .action(async (projectName?: string) => {\n await initCommand(projectName);\n });\n\nprogram\n .command(\"run [score]\")\n .description(\"Run a Tutti score interactively\")\n .action(async (score?: string) => {\n await runCommand(score);\n });\n\nprogram\n .command(\"add <voice>\")\n .description(\"Add a voice to your project\")\n .action(async (voice: string) => {\n await addCommand(voice);\n });\n\nprogram\n .command(\"check [score]\")\n .description(\"Validate a score file without running it\")\n .action(async (score?: string) => {\n await checkCommand(score);\n });\n\nprogram\n .command(\"doctor [score]\")\n .description(\"Alias for check — validate a score file\")\n .action(async (score?: string) => {\n await checkCommand(score);\n });\n\nprogram.parse();\n","import { mkdirSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport Enquirer from \"enquirer\";\n\nconst { prompt } = Enquirer;\n\nexport async function initCommand(projectName?: string): Promise<void> {\n if (!projectName) {\n const response = await prompt<{ projectName: string }>({\n type: \"input\",\n name: \"projectName\",\n message: \"Project name?\",\n });\n projectName = response.projectName;\n }\n\n if (!projectName) {\n console.error(chalk.red(\"Project name is required.\"));\n process.exit(1);\n }\n\n const dir = join(process.cwd(), projectName);\n\n if (existsSync(dir)) {\n console.error(chalk.red(`Directory already exists: ${projectName}/`));\n process.exit(1);\n }\n\n mkdirSync(dir, { recursive: true });\n\n const files: Record<string, string> = {\n \"package.json\": JSON.stringify(\n {\n name: projectName,\n version: \"0.0.1\",\n type: \"module\",\n scripts: {\n dev: \"tsx watch tutti.score.ts\",\n start: \"tsx tutti.score.ts\",\n },\n dependencies: {\n \"@tuttiai/core\": \"*\",\n \"@tuttiai/types\": \"*\",\n },\n devDependencies: {\n tsx: \"^4.0.0\",\n typescript: \"^5.7.0\",\n },\n },\n null,\n 2,\n ),\n\n \".env.example\": \"ANTHROPIC_API_KEY=your_key_here\\n\",\n\n \".gitignore\": \"node_modules\\ndist\\n.env\\n\",\n\n \"tsconfig.json\": JSON.stringify(\n {\n compilerOptions: {\n strict: true,\n target: \"ES2022\",\n module: \"ES2022\",\n moduleResolution: \"bundler\",\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: \"dist\",\n rootDir: \".\",\n },\n include: [\".\"],\n },\n null,\n 2,\n ),\n\n \"tutti.score.ts\": `import { defineScore, AnthropicProvider } from \"@tuttiai/core\"\n\nexport default defineScore({\n provider: new AnthropicProvider(),\n default_model: \"claude-sonnet-4-20250514\",\n agents: {\n assistant: {\n name: \"Assistant\",\n system_prompt: \"You are a helpful assistant.\",\n voices: [],\n }\n }\n})\n`,\n\n \"README.md\": `# ${projectName}\n\nA Tutti agent project. All agents. All together.\n\n## Setup\n\n\\`\\`\\`bash\ncp .env.example .env\n# Add your ANTHROPIC_API_KEY to .env\nnpm install\n\\`\\`\\`\n\n## Run\n\n\\`\\`\\`bash\nnpm run dev\n\\`\\`\\`\n`,\n };\n\n for (const [filename, content] of Object.entries(files)) {\n writeFileSync(join(dir, filename), content);\n }\n\n console.log();\n console.log(chalk.green(` ✔ Created ${projectName}/`));\n console.log();\n console.log(\" Next steps:\");\n console.log(chalk.cyan(` cd ${projectName}`));\n console.log(chalk.cyan(\" cp .env.example .env\"));\n console.log(chalk.cyan(\" npm install\"));\n console.log(chalk.cyan(\" npm run dev\"));\n console.log();\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport {\n TuttiRuntime,\n ScoreLoader,\n AnthropicProvider,\n OpenAIProvider,\n GeminiProvider,\n SecretsManager,\n} from \"@tuttiai/core\";\n\nexport async function runCommand(scorePath?: string): Promise<void> {\n const file = resolve(scorePath ?? \"./tutti.score.ts\");\n\n if (!existsSync(file)) {\n console.error(chalk.red(`Score file not found: ${file}`));\n console.error(\n chalk.dim('Run \"tutti-ai init\" to create a new project.'),\n );\n process.exit(1);\n }\n\n let score;\n try {\n score = await ScoreLoader.load(file);\n } catch (err) {\n console.error(\n chalk.red(\n `Failed to load score: ${err instanceof Error ? err.message : err}`,\n ),\n );\n process.exit(1);\n }\n\n // Validate that the provider has a valid API key\n const providerKeyMap: [unknown, string][] = [\n [AnthropicProvider, \"ANTHROPIC_API_KEY\"],\n [OpenAIProvider, \"OPENAI_API_KEY\"],\n [GeminiProvider, \"GEMINI_API_KEY\"],\n ];\n\n for (const [ProviderClass, envVar] of providerKeyMap) {\n if (score.provider instanceof (ProviderClass as new (...args: unknown[]) => unknown)) {\n const key = SecretsManager.optional(envVar);\n if (!key) {\n console.error(\n chalk.red(\n `Missing API key: ${envVar}\\n` +\n `Add it to your .env file: ${envVar}=your_value_here`,\n ),\n );\n process.exit(1);\n }\n }\n }\n\n const runtime = new TuttiRuntime(score);\n const spinner = ora({ color: \"cyan\" });\n\n // Event-based execution trace\n runtime.events.on(\"agent:start\", (e) => {\n console.log(chalk.cyan(`Running agent: ${e.agent_name}`));\n });\n\n runtime.events.on(\"llm:request\", () => {\n spinner.start(\"Thinking...\");\n });\n\n runtime.events.on(\"llm:response\", () => {\n spinner.stop();\n });\n\n runtime.events.on(\"tool:start\", (e) => {\n console.log(chalk.dim(` Using tool: ${e.tool_name}`));\n });\n\n runtime.events.on(\"tool:end\", (e) => {\n console.log(chalk.dim(` Done: ${e.tool_name}`));\n });\n\n runtime.events.on(\"tool:error\", (e) => {\n console.log(chalk.red(` Error in tool: ${e.tool_name}`));\n });\n\n runtime.events.on(\"security:injection_detected\", (e) => {\n console.log(\n chalk.yellow(\n ` [security] Potential prompt injection detected in: ${e.tool_name}`,\n ),\n );\n });\n\n runtime.events.on(\"budget:warning\", () => {\n console.log(chalk.yellow(\" Approaching token budget (80%)\"));\n });\n\n runtime.events.on(\"budget:exceeded\", () => {\n console.log(chalk.red(\" Token budget exceeded. Stopping.\"));\n });\n\n // REPL\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log(chalk.dim('Tutti REPL — type \"exit\" to quit\\n'));\n\n let sessionId: string | undefined;\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.dim(\"\\nGoodbye!\"));\n rl.close();\n process.exit(0);\n });\n\n try {\n while (true) {\n const input = await rl.question(chalk.cyan(\"> \"));\n const trimmed = input.trim();\n\n if (!trimmed) continue;\n if (trimmed === \"exit\" || trimmed === \"quit\") break;\n\n try {\n const result = await runtime.run(\"assistant\", trimmed, sessionId);\n sessionId = result.session_id;\n console.log(`\\n${result.output}\\n`);\n } catch (err) {\n spinner.stop();\n console.error(\n chalk.red(\n `[tutti] Something went wrong: ${err instanceof Error ? err.message : err}`,\n ),\n );\n console.error(\n chalk.dim(\n 'Run \"tutti-ai check\" to validate your score file.',\n ),\n );\n }\n }\n } catch {\n // readline closed\n }\n\n console.log(chalk.dim(\"Goodbye!\"));\n rl.close();\n process.exit(0);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\n\nconst OFFICIAL_VOICES: Record<string, { package: string; setup: string }> = {\n filesystem: {\n package: \"@tuttiai/filesystem\",\n setup: ` Add to your score:\n ${chalk.cyan('import { FilesystemVoice } from \"@tuttiai/filesystem\"')}\n ${chalk.cyan(\"voices: [new FilesystemVoice()]\")}`,\n },\n github: {\n package: \"@tuttiai/github\",\n setup: ` Add ${chalk.bold(\"GITHUB_TOKEN\")} to your .env file:\n ${chalk.cyan(\"GITHUB_TOKEN=ghp_your_token_here\")}\n\n Add to your score:\n ${chalk.cyan('import { GitHubVoice } from \"@tuttiai/github\"')}\n ${chalk.cyan(\"voices: [new GitHubVoice()]\")}`,\n },\n playwright: {\n package: \"@tuttiai/playwright\",\n setup: ` Install the browser:\n ${chalk.cyan(\"npx playwright install chromium\")}\n\n Add to your score:\n ${chalk.cyan('import { PlaywrightVoice } from \"@tuttiai/playwright\"')}\n ${chalk.cyan(\"voices: [new PlaywrightVoice()]\")}`,\n },\n};\n\nfunction resolvePackageName(input: string): string {\n // Known official voice\n if (OFFICIAL_VOICES[input]) {\n return OFFICIAL_VOICES[input].package;\n }\n // Already a scoped package\n if (input.startsWith(\"@\")) {\n return input;\n }\n // Try @tuttiai/<name> convention\n return `@tuttiai/${input}`;\n}\n\nfunction isAlreadyInstalled(packageName: string): boolean {\n const pkgPath = resolve(process.cwd(), \"package.json\");\n if (!existsSync(pkgPath)) return false;\n\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n return packageName in deps;\n } catch {\n return false;\n }\n}\n\nexport async function addCommand(voiceName: string): Promise<void> {\n const packageName = resolvePackageName(voiceName);\n\n // Check if package.json exists in cwd\n const pkgPath = resolve(process.cwd(), \"package.json\");\n if (!existsSync(pkgPath)) {\n console.error(\n chalk.red(\"No package.json found in the current directory.\"),\n );\n console.error(\n chalk.dim('Run \"tutti-ai init\" to create a new project first.'),\n );\n process.exit(1);\n }\n\n // Check if already installed\n if (isAlreadyInstalled(packageName)) {\n console.log(chalk.green(` ✔ ${packageName} is already installed`));\n return;\n }\n\n // Install\n const spinner = ora(`Installing ${packageName}...`).start();\n\n try {\n execSync(`npm install ${packageName}`, {\n cwd: process.cwd(),\n stdio: \"pipe\",\n });\n spinner.succeed(`Installed ${packageName}`);\n } catch (error) {\n spinner.fail(`Failed to install ${packageName}`);\n const message =\n error instanceof Error ? error.message : String(error);\n console.error(chalk.red(message));\n process.exit(1);\n }\n\n // Print setup instructions\n const official = OFFICIAL_VOICES[voiceName];\n if (official) {\n console.log();\n console.log(\" Setup:\");\n console.log(official.setup);\n console.log();\n } else {\n console.log();\n console.log(\n chalk.dim(\" Check the package README for setup instructions.\"),\n );\n console.log();\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport {\n ScoreLoader,\n AnthropicProvider,\n OpenAIProvider,\n GeminiProvider,\n SecretsManager,\n} from \"@tuttiai/core\";\n\nconst ok = (msg: string) => console.log(chalk.green(\" \\u2714 \" + msg));\nconst fail = (msg: string) => console.log(chalk.red(\" \\u2718 \" + msg));\n\nexport async function checkCommand(scorePath?: string): Promise<void> {\n const file = resolve(scorePath ?? \"./tutti.score.ts\");\n\n console.log(chalk.cyan(`\\nChecking ${file}...\\n`));\n\n if (!existsSync(file)) {\n fail(\"Score file not found: \" + file);\n process.exit(1);\n }\n\n // 1. Load and validate\n let score;\n try {\n score = await ScoreLoader.load(file);\n ok(\"Score file is valid\");\n } catch (err) {\n fail(\"Score validation failed\");\n console.error(\n chalk.red(\n \" \" + (err instanceof Error ? err.message : String(err)),\n ),\n );\n process.exit(1);\n }\n\n let hasErrors = false;\n\n // 2. Check provider and API key\n const providerChecks: [unknown, string, string][] = [\n [AnthropicProvider, \"AnthropicProvider\", \"ANTHROPIC_API_KEY\"],\n [OpenAIProvider, \"OpenAIProvider\", \"OPENAI_API_KEY\"],\n [GeminiProvider, \"GeminiProvider\", \"GEMINI_API_KEY\"],\n ];\n\n let providerDetected = false;\n for (const [ProviderClass, name, envVar] of providerChecks) {\n if (\n score.provider instanceof\n (ProviderClass as new (...args: unknown[]) => unknown)\n ) {\n providerDetected = true;\n const key = SecretsManager.optional(envVar);\n if (key) {\n ok(\"Provider: \" + name + \" (\" + envVar + \" is set)\");\n } else {\n fail(\"Provider: \" + name + \" (\" + envVar + \" is NOT set)\");\n hasErrors = true;\n }\n }\n }\n\n if (!providerDetected) {\n ok(\"Provider: custom LLMProvider\");\n }\n\n // 3. Count agents\n const agentKeys = Object.keys(score.agents);\n ok(agentKeys.length + \" agent\" + (agentKeys.length === 1 ? \"\" : \"s\") + \" configured\");\n\n // 4. Check voices\n for (const [agentKey, agent] of Object.entries(score.agents)) {\n for (const voice of agent.voices) {\n const voiceName = voice.name;\n\n // Check for known voices and their env vars\n const voiceEnvMap: Record<string, string> = {\n github: \"GITHUB_TOKEN\",\n };\n\n const envVar = voiceEnvMap[voiceName];\n if (envVar) {\n const key = SecretsManager.optional(envVar);\n if (key) {\n ok(\n \"Voice: \" + voiceName + \" on \" + agentKey + \" (\" + envVar + \" is set)\",\n );\n } else {\n fail(\n \"Voice: \" + voiceName + \" on \" + agentKey + \" (\" + envVar + \" is NOT set)\",\n );\n hasErrors = true;\n }\n } else {\n ok(\"Voice: \" + voiceName + \" on \" + agentKey + \" (installed)\");\n }\n }\n }\n\n // Final summary\n console.log(\"\");\n if (hasErrors) {\n console.log(\n chalk.yellow(\"Some checks failed. Fix the issues above and re-run.\"),\n );\n process.exit(1);\n } else {\n console.log(\n chalk.green(\"All checks passed.\") +\n chalk.dim(\" Run tutti-ai run to start.\"),\n );\n }\n}\n"],"mappings":"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","names":["existsSync","chalk","existsSync","resolve","chalk","ora","existsSync","resolve","chalk","ScoreLoader","AnthropicProvider","OpenAIProvider","GeminiProvider","SecretsManager"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/run.ts","../src/commands/add.ts","../src/commands/check.ts","../src/commands/studio.ts"],"sourcesContent":["import { config } from \"dotenv\";\nconfig();\n\nimport { createLogger } from \"@tuttiai/core\";\nconst logger = createLogger(\"tutti-cli\");\n\nprocess.on(\"unhandledRejection\", (reason) => {\n logger.error({ error: reason instanceof Error ? reason.message : String(reason) }, \"Unhandled rejection\");\n process.exit(1);\n});\n\nprocess.on(\"uncaughtException\", (err) => {\n logger.error({ error: err.message }, \"Fatal error\");\n process.exit(1);\n});\n\nimport { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { addCommand } from \"./commands/add.js\";\nimport { checkCommand } from \"./commands/check.js\";\nimport { studioCommand } from \"./commands/studio.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"tutti-ai\")\n .description(\"Tutti — multi-agent orchestration. All agents. All together.\")\n .version(\"0.6.0\");\n\nprogram\n .command(\"init [project-name]\")\n .description(\"Create a new Tutti project\")\n .action(async (projectName?: string) => {\n await initCommand(projectName);\n });\n\nprogram\n .command(\"run [score]\")\n .description(\"Run a Tutti score interactively\")\n .action(async (score?: string) => {\n await runCommand(score);\n });\n\nprogram\n .command(\"add <voice>\")\n .description(\"Add a voice to your project\")\n .action(async (voice: string) => {\n await addCommand(voice);\n });\n\nprogram\n .command(\"check [score]\")\n .description(\"Validate a score file without running it\")\n .action(async (score?: string) => {\n await checkCommand(score);\n });\n\nprogram\n .command(\"doctor [score]\")\n .description(\"Alias for check — validate a score file\")\n .action(async (score?: string) => {\n await checkCommand(score);\n });\n\nprogram\n .command(\"studio [score]\")\n .description(\"Launch Tutti Studio — local web UI for inspecting agent runs\")\n .action(async (score?: string) => {\n await studioCommand(score);\n });\n\nprogram.parse();\n","import { mkdirSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport chalk from \"chalk\";\nimport Enquirer from \"enquirer\";\nimport { createLogger } from \"@tuttiai/core\";\n\nconst logger = createLogger(\"tutti-cli\");\n\nconst { prompt } = Enquirer;\n\nexport async function initCommand(projectName?: string): Promise<void> {\n if (!projectName) {\n const response = await prompt<{ projectName: string }>({\n type: \"input\",\n name: \"projectName\",\n message: \"Project name?\",\n });\n projectName = response.projectName;\n }\n\n if (!projectName) {\n logger.error(\"Project name is required\");\n process.exit(1);\n }\n\n const dir = join(process.cwd(), projectName);\n\n if (existsSync(dir)) {\n logger.error({ dir: `${projectName}/` }, \"Directory already exists\");\n process.exit(1);\n }\n\n mkdirSync(dir, { recursive: true });\n\n const files: Record<string, string> = {\n \"package.json\": JSON.stringify(\n {\n name: projectName,\n version: \"0.0.1\",\n type: \"module\",\n scripts: {\n dev: \"tsx watch tutti.score.ts\",\n start: \"tsx tutti.score.ts\",\n },\n dependencies: {\n \"@tuttiai/core\": \"*\",\n \"@tuttiai/types\": \"*\",\n },\n devDependencies: {\n tsx: \"^4.0.0\",\n typescript: \"^5.7.0\",\n },\n },\n null,\n 2,\n ),\n\n \".env.example\":\n \"ANTHROPIC_API_KEY=your_key_here\\n\\n\" +\n \"# Log level: debug | info | warn | error (default: info)\\n\" +\n \"TUTTI_LOG_LEVEL=info\\n\\n\" +\n \"# OpenTelemetry (optional)\\n\" +\n \"# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\\n\" +\n \"# OTEL_SERVICE_NAME=tutti\\n\",\n\n \".gitignore\": \"node_modules\\ndist\\n.env\\n\",\n\n \"tsconfig.json\": JSON.stringify(\n {\n compilerOptions: {\n strict: true,\n target: \"ES2022\",\n module: \"ES2022\",\n moduleResolution: \"bundler\",\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: \"dist\",\n rootDir: \".\",\n },\n include: [\".\"],\n },\n null,\n 2,\n ),\n\n \"tutti.score.ts\": `import { defineScore, AnthropicProvider } from \"@tuttiai/core\"\n\nexport default defineScore({\n provider: new AnthropicProvider(),\n default_model: \"claude-sonnet-4-20250514\",\n agents: {\n assistant: {\n name: \"Assistant\",\n system_prompt: \"You are a helpful assistant.\",\n voices: [],\n }\n }\n})\n`,\n\n \"README.md\": `# ${projectName}\n\nA Tutti agent project. All agents. All together.\n\n## Setup\n\n\\`\\`\\`bash\ncp .env.example .env\n# Add your ANTHROPIC_API_KEY to .env\nnpm install\n\\`\\`\\`\n\n## Run\n\n\\`\\`\\`bash\nnpm run dev\n\\`\\`\\`\n`,\n };\n\n for (const [filename, content] of Object.entries(files)) {\n writeFileSync(join(dir, filename), content);\n }\n\n console.log();\n console.log(chalk.green(` ✔ Created ${projectName}/`));\n console.log();\n console.log(\" Next steps:\");\n console.log(chalk.cyan(` cd ${projectName}`));\n console.log(chalk.cyan(\" cp .env.example .env\"));\n console.log(chalk.cyan(\" npm install\"));\n console.log(chalk.cyan(\" npm run dev\"));\n console.log();\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport {\n TuttiRuntime,\n ScoreLoader,\n AnthropicProvider,\n OpenAIProvider,\n GeminiProvider,\n SecretsManager,\n createLogger,\n} from \"@tuttiai/core\";\n\nconst logger = createLogger(\"tutti-cli\");\n\nexport async function runCommand(scorePath?: string): Promise<void> {\n const file = resolve(scorePath ?? \"./tutti.score.ts\");\n\n if (!existsSync(file)) {\n logger.error({ file }, \"Score file not found\");\n console.error(chalk.dim('Run \"tutti-ai init\" to create a new project.'));\n process.exit(1);\n }\n\n let score;\n try {\n score = await ScoreLoader.load(file);\n } catch (err) {\n logger.error(\n { error: err instanceof Error ? err.message : String(err) },\n \"Failed to load score\",\n );\n process.exit(1);\n }\n\n // Validate that the provider has a valid API key\n const providerKeyMap: [unknown, string][] = [\n [AnthropicProvider, \"ANTHROPIC_API_KEY\"],\n [OpenAIProvider, \"OPENAI_API_KEY\"],\n [GeminiProvider, \"GEMINI_API_KEY\"],\n ];\n\n for (const [ProviderClass, envVar] of providerKeyMap) {\n if (score.provider instanceof (ProviderClass as new (...args: unknown[]) => unknown)) {\n const key = SecretsManager.optional(envVar);\n if (!key) {\n logger.error({ envVar }, \"Missing API key\");\n process.exit(1);\n }\n }\n }\n\n const runtime = new TuttiRuntime(score);\n const spinner = ora({ color: \"cyan\" });\n\n // Event-based execution trace\n runtime.events.on(\"agent:start\", (e) => {\n logger.info({ agent: e.agent_name }, \"Running agent\");\n });\n\n runtime.events.on(\"llm:request\", () => {\n spinner.start(\"Thinking...\");\n });\n\n runtime.events.on(\"llm:response\", () => {\n spinner.stop();\n });\n\n runtime.events.on(\"tool:start\", (e) => {\n logger.info({ tool: e.tool_name }, \"Using tool\");\n });\n\n runtime.events.on(\"tool:end\", (e) => {\n logger.debug({ tool: e.tool_name }, \"Tool done\");\n });\n\n runtime.events.on(\"tool:error\", (e) => {\n logger.error({ tool: e.tool_name }, \"Tool error\");\n });\n\n runtime.events.on(\"security:injection_detected\", (e) => {\n logger.warn({ tool: e.tool_name }, \"Potential prompt injection detected\");\n });\n\n runtime.events.on(\"budget:warning\", () => {\n logger.warn(\"Approaching token budget (80%)\");\n });\n\n runtime.events.on(\"budget:exceeded\", () => {\n logger.error(\"Token budget exceeded — stopping\");\n });\n\n // REPL\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log(chalk.dim('Tutti REPL — type \"exit\" to quit\\n'));\n\n let sessionId: string | undefined;\n\n // Handle Ctrl+C\n process.on(\"SIGINT\", () => {\n console.log(chalk.dim(\"\\nGoodbye!\"));\n rl.close();\n process.exit(0);\n });\n\n try {\n while (true) {\n const input = await rl.question(chalk.cyan(\"> \"));\n const trimmed = input.trim();\n\n if (!trimmed) continue;\n if (trimmed === \"exit\" || trimmed === \"quit\") break;\n\n try {\n const result = await runtime.run(\"assistant\", trimmed, sessionId);\n sessionId = result.session_id;\n console.log(`\\n${result.output}\\n`);\n } catch (err) {\n spinner.stop();\n logger.error(\n { error: err instanceof Error ? err.message : String(err) },\n \"Something went wrong\",\n );\n }\n }\n } catch {\n // readline closed\n }\n\n console.log(chalk.dim(\"Goodbye!\"));\n rl.close();\n process.exit(0);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { createLogger } from \"@tuttiai/core\";\n\nconst logger = createLogger(\"tutti-cli\");\n\nconst OFFICIAL_VOICES: Record<string, { package: string; setup: string }> = {\n filesystem: {\n package: \"@tuttiai/filesystem\",\n setup: ` Add to your score:\n ${chalk.cyan('import { FilesystemVoice } from \"@tuttiai/filesystem\"')}\n ${chalk.cyan(\"voices: [new FilesystemVoice()]\")}`,\n },\n github: {\n package: \"@tuttiai/github\",\n setup: ` Add ${chalk.bold(\"GITHUB_TOKEN\")} to your .env file:\n ${chalk.cyan(\"GITHUB_TOKEN=ghp_your_token_here\")}\n\n Add to your score:\n ${chalk.cyan('import { GitHubVoice } from \"@tuttiai/github\"')}\n ${chalk.cyan(\"voices: [new GitHubVoice()]\")}`,\n },\n playwright: {\n package: \"@tuttiai/playwright\",\n setup: ` Install the browser:\n ${chalk.cyan(\"npx playwright install chromium\")}\n\n Add to your score:\n ${chalk.cyan('import { PlaywrightVoice } from \"@tuttiai/playwright\"')}\n ${chalk.cyan(\"voices: [new PlaywrightVoice()]\")}`,\n },\n postgres: {\n package: \"pg\",\n setup: ` Add ${chalk.bold(\"DATABASE_URL\")} to your .env file:\n ${chalk.cyan(\"DATABASE_URL=postgres://user:pass@localhost:5432/tutti\")}\n\n Add to your score:\n ${chalk.cyan(\"memory: { provider: 'postgres' }\")}\n\n Or with an explicit URL:\n ${chalk.cyan(\"memory: { provider: 'postgres', url: process.env.DATABASE_URL }\")}\n\n Use the async factory for initialization:\n ${chalk.cyan(\"const tutti = await TuttiRuntime.create(score)\")}`,\n },\n};\n\nfunction resolvePackageName(input: string): string {\n // Known official voice\n if (OFFICIAL_VOICES[input]) {\n return OFFICIAL_VOICES[input].package;\n }\n // Already a scoped package\n if (input.startsWith(\"@\")) {\n return input;\n }\n // Try @tuttiai/<name> convention\n return `@tuttiai/${input}`;\n}\n\nfunction isAlreadyInstalled(packageName: string): boolean {\n const pkgPath = resolve(process.cwd(), \"package.json\");\n if (!existsSync(pkgPath)) return false;\n\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n return packageName in deps;\n } catch {\n return false;\n }\n}\n\nexport async function addCommand(voiceName: string): Promise<void> {\n const packageName = resolvePackageName(voiceName);\n\n // Check if package.json exists in cwd\n const pkgPath = resolve(process.cwd(), \"package.json\");\n if (!existsSync(pkgPath)) {\n logger.error(\"No package.json found in the current directory\");\n console.error(chalk.dim('Run \"tutti-ai init\" to create a new project first.'));\n process.exit(1);\n }\n\n // Check if already installed\n if (isAlreadyInstalled(packageName)) {\n console.log(chalk.green(` ✔ ${packageName} is already installed`));\n return;\n }\n\n // Install\n const spinner = ora(`Installing ${packageName}...`).start();\n\n try {\n execSync(`npm install ${packageName}`, {\n cwd: process.cwd(),\n stdio: \"pipe\",\n });\n spinner.succeed(`Installed ${packageName}`);\n } catch (error) {\n spinner.fail(`Failed to install ${packageName}`);\n const message = error instanceof Error ? error.message : String(error);\n logger.error({ error: message, package: packageName }, \"Installation failed\");\n process.exit(1);\n }\n\n // Print setup instructions\n const official = OFFICIAL_VOICES[voiceName];\n if (official) {\n console.log();\n console.log(\" Setup:\");\n console.log(official.setup);\n console.log();\n } else {\n console.log();\n console.log(\n chalk.dim(\" Check the package README for setup instructions.\"),\n );\n console.log();\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport chalk from \"chalk\";\nimport {\n ScoreLoader,\n AnthropicProvider,\n OpenAIProvider,\n GeminiProvider,\n SecretsManager,\n createLogger,\n} from \"@tuttiai/core\";\n\nconst logger = createLogger(\"tutti-cli\");\n\nconst ok = (msg: string) => console.log(chalk.green(\" \\u2714 \" + msg));\nconst fail = (msg: string) => console.log(chalk.red(\" \\u2718 \" + msg));\n\nexport async function checkCommand(scorePath?: string): Promise<void> {\n const file = resolve(scorePath ?? \"./tutti.score.ts\");\n\n console.log(chalk.cyan(`\\nChecking ${file}...\\n`));\n\n if (!existsSync(file)) {\n fail(\"Score file not found: \" + file);\n process.exit(1);\n }\n\n // 1. Load and validate\n let score;\n try {\n score = await ScoreLoader.load(file);\n ok(\"Score file is valid\");\n } catch (err) {\n fail(\"Score validation failed\");\n logger.error(\n { error: err instanceof Error ? err.message : String(err) },\n \"Score validation failed\",\n );\n process.exit(1);\n }\n\n let hasErrors = false;\n\n // 2. Check provider and API key\n const providerChecks: [unknown, string, string][] = [\n [AnthropicProvider, \"AnthropicProvider\", \"ANTHROPIC_API_KEY\"],\n [OpenAIProvider, \"OpenAIProvider\", \"OPENAI_API_KEY\"],\n [GeminiProvider, \"GeminiProvider\", \"GEMINI_API_KEY\"],\n ];\n\n let providerDetected = false;\n for (const [ProviderClass, name, envVar] of providerChecks) {\n if (\n score.provider instanceof\n (ProviderClass as new (...args: unknown[]) => unknown)\n ) {\n providerDetected = true;\n const key = SecretsManager.optional(envVar);\n if (key) {\n ok(\"Provider: \" + name + \" (\" + envVar + \" is set)\");\n } else {\n fail(\"Provider: \" + name + \" (\" + envVar + \" is NOT set)\");\n hasErrors = true;\n }\n }\n }\n\n if (!providerDetected) {\n ok(\"Provider: custom LLMProvider\");\n }\n\n // 3. Count agents\n const agentKeys = Object.keys(score.agents);\n ok(agentKeys.length + \" agent\" + (agentKeys.length === 1 ? \"\" : \"s\") + \" configured\");\n\n // 4. Check voices\n for (const [agentKey, agent] of Object.entries(score.agents)) {\n for (const voice of agent.voices) {\n const voiceName = voice.name;\n\n // Check for known voices and their env vars\n const voiceEnvMap: Record<string, string> = {\n github: \"GITHUB_TOKEN\",\n };\n\n const envVar = voiceEnvMap[voiceName];\n if (envVar) {\n const key = SecretsManager.optional(envVar);\n if (key) {\n ok(\n \"Voice: \" + voiceName + \" on \" + agentKey + \" (\" + envVar + \" is set)\",\n );\n } else {\n fail(\n \"Voice: \" + voiceName + \" on \" + agentKey + \" (\" + envVar + \" is NOT set)\",\n );\n hasErrors = true;\n }\n } else {\n ok(\"Voice: \" + voiceName + \" on \" + agentKey + \" (installed)\");\n }\n }\n }\n\n // Final summary\n console.log(\"\");\n if (hasErrors) {\n console.log(\n chalk.yellow(\"Some checks failed. Fix the issues above and re-run.\"),\n );\n process.exit(1);\n } else {\n console.log(\n chalk.green(\"All checks passed.\") +\n chalk.dim(\" Run tutti-ai run to start.\"),\n );\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { exec } from \"node:child_process\";\nimport express from \"express\";\nimport chalk from \"chalk\";\nimport {\n TuttiRuntime,\n ScoreLoader,\n createLogger,\n} from \"@tuttiai/core\";\nimport type { Response as ExpressResponse } from \"express\";\n\nconst logger = createLogger(\"tutti-studio\");\nconst PORT = 4747;\n\nfunction safeStringify(obj: unknown): string {\n return JSON.stringify(obj, (_key, value) => {\n if (value instanceof Error) return { message: value.message, name: value.name };\n if (typeof value === \"function\") return undefined;\n return value;\n });\n}\n\nfunction openBrowser(url: string): void {\n const cmd =\n process.platform === \"darwin\" ? \"open\" :\n process.platform === \"win32\" ? \"start\" :\n \"xdg-open\";\n exec(cmd + \" \" + url);\n}\n\nexport async function studioCommand(scorePath?: string): Promise<void> {\n const file = resolve(scorePath ?? \"./tutti.score.ts\");\n\n if (!existsSync(file)) {\n logger.error({ file }, \"Score file not found\");\n console.error(chalk.dim('Run \"tutti-ai init\" to create a new project.'));\n process.exit(1);\n }\n\n let score;\n try {\n score = await ScoreLoader.load(file);\n } catch (err) {\n logger.error({ error: err instanceof Error ? err.message : String(err) }, \"Failed to load score\");\n process.exit(1);\n }\n\n const runtime = new TuttiRuntime(score);\n\n // Track sessions via events\n const sessionRegistry = new Map<string, { agent_name: string; created_at: Date }>();\n runtime.events.on(\"agent:start\", (e) => {\n if (!sessionRegistry.has(e.session_id)) {\n sessionRegistry.set(e.session_id, { agent_name: e.agent_name, created_at: new Date() });\n }\n });\n\n // SSE clients\n const sseClients = new Set<ExpressResponse>();\n runtime.events.onAny((event) => {\n const data = safeStringify(event);\n for (const client of sseClients) {\n client.write(\"event: tutti\\ndata: \" + data + \"\\n\\n\");\n }\n });\n\n // Express\n const app = express();\n app.use(express.json());\n\n // SSE endpoint\n app.get(\"/events\", (_req, res) => {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\":\\n\\n\");\n sseClients.add(res);\n _req.on(\"close\", () => sseClients.delete(res));\n });\n\n // REST API\n app.get(\"/api/score\", (_req, res) => {\n const agents = Object.fromEntries(\n Object.entries(runtime.score.agents).map(([id, agent]) => [\n id,\n {\n name: agent.name,\n description: agent.description,\n model: agent.model,\n role: agent.role,\n delegates: agent.delegates,\n voice_count: agent.voices.length,\n voices: agent.voices.map((v) => v.name),\n },\n ]),\n );\n res.json({\n name: runtime.score.name,\n description: runtime.score.description,\n default_model: runtime.score.default_model,\n entry: runtime.score.entry,\n agents,\n });\n });\n\n app.get(\"/api/sessions\", (_req, res) => {\n const sessions = Array.from(sessionRegistry.entries()).map(([id, meta]) => {\n const session = runtime.getSession(id);\n return {\n id,\n agent_name: meta.agent_name,\n message_count: session?.messages.length ?? 0,\n created_at: meta.created_at,\n };\n });\n res.json(sessions.reverse());\n });\n\n app.get(\"/api/sessions/:id\", (req, res) => {\n const session = runtime.getSession(req.params.id);\n if (!session) { res.status(404).json({ error: \"Session not found\" }); return; }\n res.json(session);\n });\n\n app.post(\"/api/run\", async (req, res) => {\n const { agent, input, session_id } = req.body as { agent: string; input: string; session_id?: string };\n if (!agent || !input) { res.status(400).json({ error: \"agent and input are required\" }); return; }\n try {\n const result = await runtime.run(agent, input, session_id);\n res.json(result);\n } catch (err) {\n res.status(500).json({ error: err instanceof Error ? err.message : String(err) });\n }\n });\n\n // Serve UI\n app.get(\"/\", (_req, res) => {\n res.type(\"html\").send(getStudioHtml());\n });\n\n app.listen(PORT, () => {\n const url = \"http://localhost:\" + PORT;\n console.log();\n console.log(chalk.bold(\" Tutti Studio\"));\n console.log(chalk.dim(\" \" + url));\n console.log();\n console.log(chalk.dim(\" Score: \") + (runtime.score.name ?? file));\n console.log(chalk.dim(\" Agents: \") + Object.keys(runtime.score.agents).join(\", \"));\n console.log();\n openBrowser(url);\n });\n\n process.on(\"SIGINT\", () => {\n console.log(chalk.dim(\"\\nShutting down Tutti Studio...\"));\n process.exit(0);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Inline HTML UI\n// ---------------------------------------------------------------------------\n\nfunction getStudioHtml(): string {\n return '<!DOCTYPE html>\\\n<html lang=\"en\">\\\n<head>\\\n<meta charset=\"utf-8\">\\\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\\\n<title>Tutti Studio</title>\\\n<style>\\\n*{margin:0;padding:0;box-sizing:border-box}\\\n:root{\\\n--bg:#0a0a0f;--panel:#12121a;--card:#1a1a26;--input:#0f0f17;\\\n--border:#2a2a3a;--text:#e2e8f0;--muted:#64748b;\\\n--purple:#8b5cf6;--teal:#14b8a6;--blue:#3b82f6;--green:#10b981;\\\n--red:#ef4444;--orange:#f97316;--amber:#f59e0b;--indigo:#6366f1;\\\n}\\\nhtml,body{height:100%;font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);font-size:13px}\\\n#app{display:flex;flex-direction:column;height:100vh}\\\n\\\nheader{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--panel)}\\\nheader .logo{font-weight:700;font-size:15px;letter-spacing:.5px}\\\nheader .logo span{color:var(--purple)}\\\nheader .meta{color:var(--muted);font-size:12px}\\\nheader .status{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--muted)}\\\nheader .dot{width:7px;height:7px;border-radius:50%;background:var(--green)}\\\nheader .dot.off{background:var(--red)}\\\n\\\nmain{display:grid;grid-template-columns:260px 1fr 280px;flex:1;overflow:hidden;border-bottom:1px solid var(--border)}\\\n\\\n.panel{display:flex;flex-direction:column;border-right:1px solid var(--border);overflow:hidden}\\\n.panel:last-child{border-right:none}\\\n.panel-title{padding:10px 14px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--muted);border-bottom:1px solid var(--border);background:var(--panel);flex-shrink:0}\\\n.panel-body{flex:1;overflow-y:auto;padding:10px}\\\n.panel-body::-webkit-scrollbar{width:5px}\\\n.panel-body::-webkit-scrollbar-track{background:transparent}\\\n.panel-body::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}\\\n\\\n#graph-panel .panel-body{padding:0;display:flex;align-items:center;justify-content:center}\\\n#graph-panel svg text{font-family:system-ui,-apple-system,sans-serif}\\\n\\\n#events-panel{display:flex;flex-direction:column}\\\n#event-stream{flex:1;overflow-y:auto;padding:10px}\\\n#event-stream::-webkit-scrollbar{width:5px}\\\n#event-stream::-webkit-scrollbar-track{background:transparent}\\\n#event-stream::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}\\\n\\\n.ev{padding:7px 10px;margin-bottom:6px;border-radius:6px;background:var(--card);border-left:3px solid var(--muted);font-size:12px;line-height:1.5}\\\n.ev .ev-head{display:flex;justify-content:space-between;align-items:center}\\\n.ev .ev-type{font-weight:600;font-family:\"SF Mono\",Menlo,monospace;font-size:11px}\\\n.ev .ev-time{color:var(--muted);font-size:10px;font-family:\"SF Mono\",Menlo,monospace}\\\n.ev .ev-detail{color:var(--muted);margin-top:3px;font-size:11px;word-break:break-all}\\\n.ev.agent{border-left-color:var(--purple)}.ev.agent .ev-type{color:var(--purple)}\\\n.ev.turn{border-left-color:var(--blue)}.ev.turn .ev-type{color:var(--blue)}\\\n.ev.llm{border-left-color:var(--green)}.ev.llm .ev-type{color:var(--green)}\\\n.ev.tool{border-left-color:var(--teal)}.ev.tool .ev-type{color:var(--teal)}\\\n.ev.tool-error{border-left-color:var(--red)}.ev.tool-error .ev-type{color:var(--red)}\\\n.ev.security{border-left-color:var(--orange)}.ev.security .ev-type{color:var(--orange)}\\\n.ev.budget-warn{border-left-color:var(--amber)}.ev.budget-warn .ev-type{color:var(--amber)}\\\n.ev.budget-exceed{border-left-color:var(--red)}.ev.budget-exceed .ev-type{color:var(--red)}\\\n.ev.delegate{border-left-color:var(--indigo)}.ev.delegate .ev-type{color:var(--indigo)}\\\n\\\n#input-bar{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--border);background:var(--panel);flex-shrink:0}\\\n#agent-select{background:var(--input);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:12px;outline:none;cursor:pointer;min-width:110px}\\\n#user-input{flex:1;background:var(--input);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 12px;font-size:13px;outline:none}\\\n#user-input:focus{border-color:var(--purple)}\\\n#send-btn{background:var(--purple);color:#fff;border:none;border-radius:6px;padding:6px 16px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}\\\n#send-btn:hover{opacity:.9}\\\n#send-btn:disabled{opacity:.4;cursor:default}\\\n\\\n.session-item{padding:8px 10px;margin-bottom:4px;border-radius:6px;background:var(--card);cursor:pointer;transition:background .15s}\\\n.session-item:hover{background:#22223a}\\\n.session-item.active{background:#22223a;border:1px solid var(--purple)}\\\n.session-id{font-family:\"SF Mono\",Menlo,monospace;font-size:11px;color:var(--purple)}\\\n.session-meta{font-size:11px;color:var(--muted);margin-top:2px}\\\n\\\n#session-detail{margin-top:10px;border-top:1px solid var(--border);padding-top:10px}\\\n.msg{padding:6px 8px;margin-bottom:4px;border-radius:5px;font-size:12px;line-height:1.5;word-break:break-word}\\\n.msg.user{background:#1c1c3a;border-left:2px solid var(--blue)}\\\n.msg.assistant{background:#1a2a1a;border-left:2px solid var(--green)}\\\n.msg .msg-role{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px}\\\n.msg.user .msg-role{color:var(--blue)}\\\n.msg.assistant .msg-role{color:var(--green)}\\\n\\\nfooter{display:flex;align-items:center;gap:32px;padding:8px 20px;background:var(--panel);font-size:12px}\\\n.token-item{display:flex;align-items:center;gap:6px}\\\n.token-label{color:var(--muted)}\\\n.token-val{font-family:\"SF Mono\",Menlo,monospace;font-weight:600}\\\n.token-val.input{color:var(--blue)}\\\n.token-val.output{color:var(--green)}\\\n.token-val.cost{color:var(--amber)}\\\n\\\n.empty{color:var(--muted);text-align:center;padding:30px 10px;font-size:12px}\\\n</style>\\\n</head>\\\n<body>\\\n<div id=\"app\">\\\n\\\n<header>\\\n <div class=\"logo\"><span>♫</span> Tutti Studio</div>\\\n <div class=\"meta\" id=\"score-name\"></div>\\\n <div class=\"status\"><div class=\"dot\" id=\"sse-dot\"></div><span id=\"sse-label\">connecting</span></div>\\\n</header>\\\n\\\n<main>\\\n <div class=\"panel\" id=\"graph-panel\">\\\n <div class=\"panel-title\">Agent Graph</div>\\\n <div class=\"panel-body\" id=\"graph-body\"></div>\\\n </div>\\\n\\\n <div class=\"panel\" id=\"events-panel\">\\\n <div class=\"panel-title\">Live Event Stream</div>\\\n <div id=\"event-stream\"><div class=\"empty\">Waiting for events…<br>Send a message below to start an agent run.</div></div>\\\n <div id=\"input-bar\">\\\n <select id=\"agent-select\"></select>\\\n <input id=\"user-input\" placeholder=\"Type a message…\" autocomplete=\"off\">\\\n <button id=\"send-btn\">Send</button>\\\n </div>\\\n </div>\\\n\\\n <div class=\"panel\" id=\"sessions-panel\">\\\n <div class=\"panel-title\">Sessions</div>\\\n <div class=\"panel-body\" id=\"sessions-body\"><div class=\"empty\">No sessions yet</div></div>\\\n </div>\\\n</main>\\\n\\\n<footer>\\\n <div class=\"token-item\"><span class=\"token-label\">↓ Input</span><span class=\"token-val input\" id=\"tok-in\">0</span></div>\\\n <div class=\"token-item\"><span class=\"token-label\">↑ Output</span><span class=\"token-val output\" id=\"tok-out\">0</span></div>\\\n <div class=\"token-item\"><span class=\"token-label\">$ Est. cost</span><span class=\"token-val cost\" id=\"tok-cost\">0.0000</span></div>\\\n</footer>\\\n\\\n</div>\\\n\\\n<script>\\\n(function(){\\\n\\\nvar tokIn=0,tokOut=0;\\\nvar sessionMap={};\\\nvar activeSession=null;\\\n\\\n/* ---- helpers ---- */\\\nfunction esc(s){var d=document.createElement(\"div\");d.textContent=s;return d.innerHTML}\\\nfunction fmt(n){return n.toLocaleString()}\\\nfunction timeStr(){var d=new Date();return (\"0\"+d.getHours()).slice(-2)+\":\"+(\"0\"+d.getMinutes()).slice(-2)+\":\"+(\"0\"+d.getSeconds()).slice(-2)}\\\nfunction truncId(id){return id.slice(0,8)}\\\n\\\n/* ---- score + graph ---- */\\\nfunction loadScore(){\\\n fetch(\"/api/score\").then(function(r){return r.json()}).then(function(s){\\\n document.getElementById(\"score-name\").textContent=s.name||\"tutti.score.ts\";\\\n var sel=document.getElementById(\"agent-select\");\\\n sel.innerHTML=\"\";\\\n Object.keys(s.agents).forEach(function(id){\\\n var o=document.createElement(\"option\");o.value=id;o.textContent=s.agents[id].name;sel.appendChild(o);\\\n });\\\n renderGraph(s);\\\n });\\\n}\\\n\\\nfunction renderGraph(score){\\\n var body=document.getElementById(\"graph-body\");\\\n var W=260,ids=Object.keys(score.agents),N=ids.length;\\\n if(N===0){body.innerHTML=\"<div class=\\\\\"empty\\\\\">No agents</div>\";return}\\\n var hasDelegate=false;\\\n ids.forEach(function(id){if(score.agents[id].delegates&&score.agents[id].delegates.length)hasDelegate=true});\\\n var nodeR=26,padY=90,padTop=50;\\\n var leftIds=[],rightIds=[];\\\n if(hasDelegate){\\\n ids.forEach(function(id){var a=score.agents[id];if(a.delegates&&a.delegates.length)leftIds.push(id);else rightIds.push(id)});\\\n }else{leftIds=ids}\\\n var cols=hasDelegate?2:1;\\\n var cx1=cols===1?W/2:72,cx2=W-72;\\\n var H=Math.max(leftIds.length,rightIds.length)*padY+padTop*2;\\\n if(H<200)H=200;\\\n var pos={};\\\n var svg=\\'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"\\'+W+\\'\" height=\"\\'+H+\\'\" viewBox=\"0 0 \\'+W+\" \"+H+\\'\">\\';\\\n svg+=\\'<defs><marker id=\"ah\" viewBox=\"0 0 10 10\" refX=\"10\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto\"><path d=\"M0 0L10 5L0 10z\" fill=\"#64748b\"/></marker></defs>\\';\\\n function drawNode(id,cx,cy){\\\n var a=score.agents[id];\\\n var col=a.role===\"orchestrator\"?\"#8b5cf6\":\"#14b8a6\";\\\n pos[id]={x:cx,y:cy};\\\n svg+=\\'<circle cx=\"\\'+cx+\\'\" cy=\"\\'+cy+\\'\" r=\"\\'+nodeR+\\'\" fill=\"\\'+col+\\'\" fill-opacity=\"0.15\" stroke=\"\\'+col+\\'\" stroke-width=\"2\"/>\\';\\\n svg+=\\'<text x=\"\\'+cx+\\'\" y=\"\\'+(cy+4)+\\'\" text-anchor=\"middle\" fill=\"#e2e8f0\" font-size=\"10\" font-weight=\"600\">\\'+esc(a.name)+\\'</text>\\';\\\n var model=a.model||score.default_model||\"\";\\\n if(model){var sh=model.replace(/-\\\\d{8}$/,\"\");if(sh.length>18)sh=sh.slice(0,18)+\"\\\\u2026\";svg+=\\'<text x=\"\\'+cx+\\'\" y=\"\\'+(cy+nodeR+14)+\\'\" text-anchor=\"middle\" fill=\"#64748b\" font-size=\"9\">\\'+esc(sh)+\\'</text>\\'}\\\n svg+=\\'<text x=\"\\'+cx+\\'\" y=\"\\'+(cy+nodeR+26)+\\'\" text-anchor=\"middle\" fill=\"#64748b\" font-size=\"9\">\\'+a.voice_count+\" voice\"+(a.voice_count!==1?\"s\":\"\")+\\'</text>\\';\\\n }\\\n leftIds.forEach(function(id,i){drawNode(id,cx1,padTop+i*padY)});\\\n rightIds.forEach(function(id,i){drawNode(id,cx2,padTop+i*padY)});\\\n ids.forEach(function(id){\\\n var a=score.agents[id];\\\n if(a.delegates)a.delegates.forEach(function(did){\\\n if(pos[id]&&pos[did]){\\\n var x1=pos[id].x+nodeR,y1=pos[id].y,x2=pos[did].x-nodeR,y2=pos[did].y;\\\n var mx=(x1+x2)/2;\\\n svg+=\\'<path d=\"M\\'+x1+\" \"+y1+\" C\"+mx+\" \"+y1+\" \"+mx+\" \"+y2+\" \"+x2+\" \"+y2+\\'\" fill=\"none\" stroke=\"#64748b\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" marker-end=\"url(#ah)\"/>\\';\\\n }\\\n });\\\n });\\\n svg+=\"</svg>\";\\\n body.innerHTML=svg;\\\n}\\\n\\\n/* ---- SSE ---- */\\\nfunction connectSSE(){\\\n var es=new EventSource(\"/events\");\\\n es.addEventListener(\"tutti\",function(e){\\\n var ev=JSON.parse(e.data);\\\n addEvent(ev);\\\n if(ev.type===\"llm:response\"&&ev.response&&ev.response.usage){\\\n tokIn+=ev.response.usage.input_tokens||0;\\\n tokOut+=ev.response.usage.output_tokens||0;\\\n document.getElementById(\"tok-in\").textContent=fmt(tokIn);\\\n document.getElementById(\"tok-out\").textContent=fmt(tokOut);\\\n document.getElementById(\"tok-cost\").textContent=estimateCost(tokIn,tokOut);\\\n }\\\n if(ev.type===\"agent:start\"||ev.type===\"agent:end\")refreshSessions();\\\n });\\\n es.onopen=function(){document.getElementById(\"sse-dot\").className=\"dot\";document.getElementById(\"sse-label\").textContent=\"connected\"};\\\n es.onerror=function(){document.getElementById(\"sse-dot\").className=\"dot off\";document.getElementById(\"sse-label\").textContent=\"disconnected\"};\\\n}\\\n\\\nfunction estimateCost(inp,out){\\\n var c=(inp/1e6)*3+(out/1e6)*15;\\\n return c.toFixed(4);\\\n}\\\n\\\nfunction evClass(t){\\\n if(t.indexOf(\"agent\")===0)return \"agent\";\\\n if(t.indexOf(\"turn\")===0)return \"turn\";\\\n if(t===\"llm:request\"||t===\"llm:response\")return \"llm\";\\\n if(t===\"tool:error\")return \"tool-error\";\\\n if(t.indexOf(\"tool\")===0)return \"tool\";\\\n if(t.indexOf(\"security\")===0)return \"security\";\\\n if(t===\"budget:warning\")return \"budget-warn\";\\\n if(t===\"budget:exceeded\")return \"budget-exceed\";\\\n if(t.indexOf(\"delegate\")===0)return \"delegate\";\\\n return \"\";\\\n}\\\n\\\nfunction evDetail(ev){\\\n var parts=[];\\\n if(ev.agent_name)parts.push(\"agent: \"+ev.agent_name);\\\n if(ev.session_id)parts.push(\"session: \"+truncId(ev.session_id));\\\n if(ev.turn!==undefined)parts.push(\"turn: \"+ev.turn);\\\n if(ev.tool_name)parts.push(\"tool: \"+ev.tool_name);\\\n if(ev.from)parts.push(\"from: \"+ev.from);\\\n if(ev.to)parts.push(\"to: \"+ev.to);\\\n if(ev.tokens!==undefined)parts.push(\"tokens: \"+fmt(ev.tokens));\\\n if(ev.cost_usd!==undefined)parts.push(\"cost: $\"+ev.cost_usd.toFixed(4));\\\n if(ev.response&&ev.response.usage)parts.push(\"tokens: \"+fmt(ev.response.usage.input_tokens)+\" in / \"+fmt(ev.response.usage.output_tokens)+\" out\");\\\n if(ev.error){var em=typeof ev.error===\"object\"?ev.error.message||\"\":ev.error;if(em)parts.push(\"error: \"+em)}\\\n if(ev.patterns)parts.push(\"patterns: \"+ev.patterns.join(\", \"));\\\n return parts.join(\" · \");\\\n}\\\n\\\nvar firstEvent=true;\\\nfunction addEvent(ev){\\\n var stream=document.getElementById(\"event-stream\");\\\n if(firstEvent){stream.innerHTML=\"\";firstEvent=false}\\\n var div=document.createElement(\"div\");\\\n div.className=\"ev \"+evClass(ev.type);\\\n div.innerHTML=\\'<div class=\"ev-head\"><span class=\"ev-type\">\\'+esc(ev.type)+\\'</span><span class=\"ev-time\">\\'+timeStr()+\\'</span></div>\\';\\\n var det=evDetail(ev);\\\n if(det)div.innerHTML+=\\'<div class=\"ev-detail\">\\'+det+\"</div>\";\\\n stream.appendChild(div);\\\n stream.scrollTop=stream.scrollHeight;\\\n}\\\n\\\n/* ---- sessions ---- */\\\nfunction refreshSessions(){\\\n fetch(\"/api/sessions\").then(function(r){return r.json()}).then(function(list){\\\n var body=document.getElementById(\"sessions-body\");\\\n if(!list.length){body.innerHTML=\\'<div class=\"empty\">No sessions yet</div>\\';return}\\\n var html=\"\";\\\n list.forEach(function(s){\\\n var cls=\"session-item\"+(activeSession===s.id?\" active\":\"\");\\\n html+=\\'<div class=\"\\'+cls+\\'\" data-id=\"\\'+s.id+\\'\">\\';\\\n html+=\\'<div class=\"session-id\">\\'+truncId(s.id)+\"</div>\";\\\n html+=\\'<div class=\"session-meta\">\\'+esc(s.agent_name)+\" · \"+s.message_count+\" msgs</div>\";\\\n html+=\"</div>\";\\\n });\\\n if(activeSession)html+=\\'<div id=\"session-detail\"></div>\\';\\\n body.innerHTML=html;\\\n body.querySelectorAll(\".session-item\").forEach(function(el){\\\n el.addEventListener(\"click\",function(){selectSession(el.getAttribute(\"data-id\"))});\\\n });\\\n if(activeSession)loadSessionDetail(activeSession);\\\n });\\\n}\\\n\\\nfunction selectSession(id){\\\n activeSession=activeSession===id?null:id;\\\n refreshSessions();\\\n}\\\n\\\nfunction loadSessionDetail(id){\\\n var det=document.getElementById(\"session-detail\");\\\n if(!det)return;\\\n fetch(\"/api/sessions/\"+id).then(function(r){return r.json()}).then(function(session){\\\n if(!session||session.error){det.innerHTML=\\'<div class=\"empty\">Session not found</div>\\';return}\\\n var html=\"\";\\\n (session.messages||[]).forEach(function(m){\\\n var role=m.role;\\\n var text=\"\";\\\n if(typeof m.content===\"string\")text=m.content;\\\n else if(Array.isArray(m.content)){\\\n m.content.forEach(function(b){\\\n if(b.type===\"text\")text+=b.text+\"\\\\n\";\\\n else if(b.type===\"tool_use\")text+=\"[tool_use: \"+b.name+\"]\\\\n\";\\\n else if(b.type===\"tool_result\")text+=\"[tool_result]\\\\n\";\\\n });\\\n }\\\n html+=\\'<div class=\"msg \\'+role+\\'\"><div class=\"msg-role\">\\'+role+\"</div>\"+esc(text.trim())+\"</div>\";\\\n });\\\n det.innerHTML=html;\\\n });\\\n}\\\n\\\n/* ---- send ---- */\\\nfunction sendMessage(){\\\n var agentSel=document.getElementById(\"agent-select\");\\\n var inputEl=document.getElementById(\"user-input\");\\\n var btn=document.getElementById(\"send-btn\");\\\n var agent=agentSel.value;\\\n var input=inputEl.value.trim();\\\n if(!input)return;\\\n btn.disabled=true;btn.textContent=\"Running\\\\u2026\";\\\n inputEl.value=\"\";\\\n var sid=sessionMap[agent]||undefined;\\\n fetch(\"/api/run\",{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({agent:agent,input:input,session_id:sid})})\\\n .then(function(r){return r.json()})\\\n .then(function(result){\\\n if(result.session_id)sessionMap[agent]=result.session_id;\\\n if(result.output){\\\n addEvent({type:\"__output\",agent_name:agent,output:result.output});\\\n }\\\n refreshSessions();\\\n })\\\n .catch(function(err){addEvent({type:\"__error\",error:err.message||String(err)})})\\\n .finally(function(){btn.disabled=false;btn.textContent=\"Send\"});\\\n}\\\n\\\ndocument.getElementById(\"send-btn\").addEventListener(\"click\",sendMessage);\\\ndocument.getElementById(\"user-input\").addEventListener(\"keydown\",function(e){if(e.key===\"Enter\"&&!e.shiftKey){e.preventDefault();sendMessage()}});\\\n\\\n/* ---- init ---- */\\\nloadScore();\\\nconnectSSE();\\\n\\\n})();\\\n</script>\\\n</body>\\\n</html>';\n}\n"],"mappings":"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","names":["createLogger","existsSync","chalk","createLogger","logger","existsSync","resolve","chalk","ora","createLogger","logger","existsSync","resolve","chalk","ScoreLoader","AnthropicProvider","OpenAIProvider","GeminiProvider","SecretsManager","createLogger","logger","existsSync","resolve","chalk","TuttiRuntime","ScoreLoader","createLogger","logger","logger","createLogger"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuttiai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Tutti CLI — scaffold and run multi-agent projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
|
-
"files": [
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
16
20
|
"scripts": {
|
|
17
21
|
"build": "tsup",
|
|
18
22
|
"dev": "tsup --watch",
|
|
@@ -27,14 +31,28 @@
|
|
|
27
31
|
"commander": "4.1.1",
|
|
28
32
|
"dotenv": "17.4.1",
|
|
29
33
|
"enquirer": "2.4.1",
|
|
30
|
-
"
|
|
34
|
+
"express": "^5.2.1",
|
|
35
|
+
"ora": "8.2.0",
|
|
36
|
+
"pino": "^10.3.1",
|
|
37
|
+
"pino-pretty": "^13.1.3"
|
|
31
38
|
},
|
|
32
|
-
"keywords": [
|
|
39
|
+
"keywords": [
|
|
40
|
+
"tutti",
|
|
41
|
+
"ai",
|
|
42
|
+
"agents",
|
|
43
|
+
"orchestration",
|
|
44
|
+
"cli",
|
|
45
|
+
"scaffold"
|
|
46
|
+
],
|
|
33
47
|
"license": "MIT",
|
|
34
48
|
"repository": {
|
|
35
49
|
"type": "git",
|
|
36
50
|
"url": "https://github.com/tuttiai/tutti",
|
|
37
51
|
"directory": "packages/cli"
|
|
38
52
|
},
|
|
39
|
-
"homepage": "https://tutti-ai.com"
|
|
53
|
+
"homepage": "https://tutti-ai.com",
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/express": "^5.0.6",
|
|
56
|
+
"@types/pino": "^7.0.4"
|
|
57
|
+
}
|
|
40
58
|
}
|