@tuttiai/cli 0.3.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 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
- console.error(chalk.red("Project name is required."));
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
- console.error(chalk.red(`Directory already exists: ${projectName}/`));
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
  {
@@ -123,31 +126,51 @@ import { resolve } from "path";
123
126
  import { createInterface } from "readline/promises";
124
127
  import chalk2 from "chalk";
125
128
  import ora from "ora";
126
- import { TuttiRuntime, ScoreLoader } from "@tuttiai/core";
129
+ import {
130
+ TuttiRuntime,
131
+ ScoreLoader,
132
+ AnthropicProvider,
133
+ OpenAIProvider,
134
+ GeminiProvider,
135
+ SecretsManager,
136
+ createLogger as createLogger2
137
+ } from "@tuttiai/core";
138
+ var logger2 = createLogger2("tutti-cli");
127
139
  async function runCommand(scorePath) {
128
140
  const file = resolve(scorePath ?? "./tutti.score.ts");
129
141
  if (!existsSync2(file)) {
130
- console.error(chalk2.red(`Score file not found: ${file}`));
131
- console.error(
132
- chalk2.dim('Run "tutti-ai init" to create a new project.')
133
- );
142
+ logger2.error({ file }, "Score file not found");
143
+ console.error(chalk2.dim('Run "tutti-ai init" to create a new project.'));
134
144
  process.exit(1);
135
145
  }
136
146
  let score;
137
147
  try {
138
148
  score = await ScoreLoader.load(file);
139
149
  } catch (err) {
140
- console.error(
141
- chalk2.red(
142
- `Failed to load score: ${err instanceof Error ? err.message : err}`
143
- )
150
+ logger2.error(
151
+ { error: err instanceof Error ? err.message : String(err) },
152
+ "Failed to load score"
144
153
  );
145
154
  process.exit(1);
146
155
  }
156
+ const providerKeyMap = [
157
+ [AnthropicProvider, "ANTHROPIC_API_KEY"],
158
+ [OpenAIProvider, "OPENAI_API_KEY"],
159
+ [GeminiProvider, "GEMINI_API_KEY"]
160
+ ];
161
+ for (const [ProviderClass, envVar] of providerKeyMap) {
162
+ if (score.provider instanceof ProviderClass) {
163
+ const key = SecretsManager.optional(envVar);
164
+ if (!key) {
165
+ logger2.error({ envVar }, "Missing API key");
166
+ process.exit(1);
167
+ }
168
+ }
169
+ }
147
170
  const runtime = new TuttiRuntime(score);
148
171
  const spinner = ora({ color: "cyan" });
149
172
  runtime.events.on("agent:start", (e) => {
150
- console.log(chalk2.cyan(`Running agent: ${e.agent_name}`));
173
+ logger2.info({ agent: e.agent_name }, "Running agent");
151
174
  });
152
175
  runtime.events.on("llm:request", () => {
153
176
  spinner.start("Thinking...");
@@ -156,13 +179,22 @@ async function runCommand(scorePath) {
156
179
  spinner.stop();
157
180
  });
158
181
  runtime.events.on("tool:start", (e) => {
159
- console.log(chalk2.dim(` Using tool: ${e.tool_name}`));
182
+ logger2.info({ tool: e.tool_name }, "Using tool");
160
183
  });
161
184
  runtime.events.on("tool:end", (e) => {
162
- console.log(chalk2.dim(` Done: ${e.tool_name}`));
185
+ logger2.debug({ tool: e.tool_name }, "Tool done");
163
186
  });
164
187
  runtime.events.on("tool:error", (e) => {
165
- console.log(chalk2.red(` Error in tool: ${e.tool_name}`));
188
+ logger2.error({ tool: e.tool_name }, "Tool error");
189
+ });
190
+ runtime.events.on("security:injection_detected", (e) => {
191
+ logger2.warn({ tool: e.tool_name }, "Potential prompt injection detected");
192
+ });
193
+ runtime.events.on("budget:warning", () => {
194
+ logger2.warn("Approaching token budget (80%)");
195
+ });
196
+ runtime.events.on("budget:exceeded", () => {
197
+ logger2.error("Token budget exceeded \u2014 stopping");
166
198
  });
167
199
  const rl = createInterface({
168
200
  input: process.stdin,
@@ -189,10 +221,9 @@ ${result.output}
189
221
  `);
190
222
  } catch (err) {
191
223
  spinner.stop();
192
- console.error(
193
- chalk2.red(
194
- `Error: ${err instanceof Error ? err.message : err}`
195
- )
224
+ logger2.error(
225
+ { error: err instanceof Error ? err.message : String(err) },
226
+ "Something went wrong"
196
227
  );
197
228
  }
198
229
  }
@@ -209,6 +240,8 @@ import { resolve as resolve2 } from "path";
209
240
  import { execSync } from "child_process";
210
241
  import chalk3 from "chalk";
211
242
  import ora2 from "ora";
243
+ import { createLogger as createLogger3 } from "@tuttiai/core";
244
+ var logger3 = createLogger3("tutti-cli");
212
245
  var OFFICIAL_VOICES = {
213
246
  filesystem: {
214
247
  package: "@tuttiai/filesystem",
@@ -233,6 +266,20 @@ var OFFICIAL_VOICES = {
233
266
  Add to your score:
234
267
  ${chalk3.cyan('import { PlaywrightVoice } from "@tuttiai/playwright"')}
235
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)")}`
236
283
  }
237
284
  };
238
285
  function resolvePackageName(input) {
@@ -259,12 +306,8 @@ async function addCommand(voiceName) {
259
306
  const packageName = resolvePackageName(voiceName);
260
307
  const pkgPath = resolve2(process.cwd(), "package.json");
261
308
  if (!existsSync3(pkgPath)) {
262
- console.error(
263
- chalk3.red("No package.json found in the current directory.")
264
- );
265
- console.error(
266
- chalk3.dim('Run "tutti-ai init" to create a new project first.')
267
- );
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.'));
268
311
  process.exit(1);
269
312
  }
270
313
  if (isAlreadyInstalled(packageName)) {
@@ -281,7 +324,7 @@ async function addCommand(voiceName) {
281
324
  } catch (error) {
282
325
  spinner.fail(`Failed to install ${packageName}`);
283
326
  const message = error instanceof Error ? error.message : String(error);
284
- console.error(chalk3.red(message));
327
+ logger3.error({ error: message, package: packageName }, "Installation failed");
285
328
  process.exit(1);
286
329
  }
287
330
  const official = OFFICIAL_VOICES[voiceName];
@@ -299,10 +342,259 @@ async function addCommand(voiceName) {
299
342
  }
300
343
  }
301
344
 
345
+ // src/commands/check.ts
346
+ import { existsSync as existsSync4 } from "fs";
347
+ import { resolve as resolve3 } from "path";
348
+ import chalk4 from "chalk";
349
+ import {
350
+ ScoreLoader as ScoreLoader2,
351
+ AnthropicProvider as AnthropicProvider2,
352
+ OpenAIProvider as OpenAIProvider2,
353
+ GeminiProvider as GeminiProvider2,
354
+ SecretsManager as SecretsManager2,
355
+ createLogger as createLogger4
356
+ } from "@tuttiai/core";
357
+ var logger4 = createLogger4("tutti-cli");
358
+ var ok = (msg) => console.log(chalk4.green(" \u2714 " + msg));
359
+ var fail = (msg) => console.log(chalk4.red(" \u2718 " + msg));
360
+ async function checkCommand(scorePath) {
361
+ const file = resolve3(scorePath ?? "./tutti.score.ts");
362
+ console.log(chalk4.cyan(`
363
+ Checking ${file}...
364
+ `));
365
+ if (!existsSync4(file)) {
366
+ fail("Score file not found: " + file);
367
+ process.exit(1);
368
+ }
369
+ let score;
370
+ try {
371
+ score = await ScoreLoader2.load(file);
372
+ ok("Score file is valid");
373
+ } catch (err) {
374
+ fail("Score validation failed");
375
+ logger4.error(
376
+ { error: err instanceof Error ? err.message : String(err) },
377
+ "Score validation failed"
378
+ );
379
+ process.exit(1);
380
+ }
381
+ let hasErrors = false;
382
+ const providerChecks = [
383
+ [AnthropicProvider2, "AnthropicProvider", "ANTHROPIC_API_KEY"],
384
+ [OpenAIProvider2, "OpenAIProvider", "OPENAI_API_KEY"],
385
+ [GeminiProvider2, "GeminiProvider", "GEMINI_API_KEY"]
386
+ ];
387
+ let providerDetected = false;
388
+ for (const [ProviderClass, name, envVar] of providerChecks) {
389
+ if (score.provider instanceof ProviderClass) {
390
+ providerDetected = true;
391
+ const key = SecretsManager2.optional(envVar);
392
+ if (key) {
393
+ ok("Provider: " + name + " (" + envVar + " is set)");
394
+ } else {
395
+ fail("Provider: " + name + " (" + envVar + " is NOT set)");
396
+ hasErrors = true;
397
+ }
398
+ }
399
+ }
400
+ if (!providerDetected) {
401
+ ok("Provider: custom LLMProvider");
402
+ }
403
+ const agentKeys = Object.keys(score.agents);
404
+ ok(agentKeys.length + " agent" + (agentKeys.length === 1 ? "" : "s") + " configured");
405
+ for (const [agentKey, agent] of Object.entries(score.agents)) {
406
+ for (const voice of agent.voices) {
407
+ const voiceName = voice.name;
408
+ const voiceEnvMap = {
409
+ github: "GITHUB_TOKEN"
410
+ };
411
+ const envVar = voiceEnvMap[voiceName];
412
+ if (envVar) {
413
+ const key = SecretsManager2.optional(envVar);
414
+ if (key) {
415
+ ok(
416
+ "Voice: " + voiceName + " on " + agentKey + " (" + envVar + " is set)"
417
+ );
418
+ } else {
419
+ fail(
420
+ "Voice: " + voiceName + " on " + agentKey + " (" + envVar + " is NOT set)"
421
+ );
422
+ hasErrors = true;
423
+ }
424
+ } else {
425
+ ok("Voice: " + voiceName + " on " + agentKey + " (installed)");
426
+ }
427
+ }
428
+ }
429
+ console.log("");
430
+ if (hasErrors) {
431
+ console.log(
432
+ chalk4.yellow("Some checks failed. Fix the issues above and re-run.")
433
+ );
434
+ process.exit(1);
435
+ } else {
436
+ console.log(
437
+ chalk4.green("All checks passed.") + chalk4.dim(" Run tutti-ai run to start.")
438
+ );
439
+ }
440
+ }
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>&#9835;</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&hellip;<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&hellip;" 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">&#x2193; Input</span><span class="token-val input" id="tok-in">0</span></div> <div class="token-item"><span class="token-label">&#x2191; 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(" &middot; ");}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)+" &middot; "+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
+
302
585
  // src/index.ts
303
586
  config();
587
+ var logger6 = createLogger6("tutti-cli");
588
+ process.on("unhandledRejection", (reason) => {
589
+ logger6.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
590
+ process.exit(1);
591
+ });
592
+ process.on("uncaughtException", (err) => {
593
+ logger6.error({ error: err.message }, "Fatal error");
594
+ process.exit(1);
595
+ });
304
596
  var program = new Command();
305
- program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.2.0");
597
+ program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.6.0");
306
598
  program.command("init [project-name]").description("Create a new Tutti project").action(async (projectName) => {
307
599
  await initCommand(projectName);
308
600
  });
@@ -312,5 +604,14 @@ program.command("run [score]").description("Run a Tutti score interactively").ac
312
604
  program.command("add <voice>").description("Add a voice to your project").action(async (voice) => {
313
605
  await addCommand(voice);
314
606
  });
607
+ program.command("check [score]").description("Validate a score file without running it").action(async (score) => {
608
+ await checkCommand(score);
609
+ });
610
+ program.command("doctor [score]").description("Alias for check \u2014 validate a score file").action(async (score) => {
611
+ await checkCommand(score);
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
+ });
315
616
  program.parse();
316
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"],"sourcesContent":["import { config } from \"dotenv\";\nconfig();\n\nimport { Command } from \"commander\";\nimport { initCommand } from \"./commands/init.js\";\nimport { runCommand } from \"./commands/run.js\";\nimport { addCommand } from \"./commands/add.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.2.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.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 { TuttiRuntime, ScoreLoader } 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 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 // 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 `Error: ${err instanceof Error ? err.message : err}`,\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"],"mappings":"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","names":["existsSync","chalk","existsSync","resolve","chalk","ora"]}
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>&#9835;</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&hellip;<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&hellip;\" 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\">&#x2193; Input</span><span class=\"token-val input\" id=\"tok-in\">0</span></div>\\\n <div class=\"token-item\"><span class=\"token-label\">&#x2191; 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(\" &middot; \");\\\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)+\" &middot; \"+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.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": ["dist", "README.md", "LICENSE"],
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
16
20
  "scripts": {
17
21
  "build": "tsup",
18
22
  "dev": "tsup --watch",
@@ -21,20 +25,34 @@
21
25
  "typecheck": "tsc --noEmit"
22
26
  },
23
27
  "dependencies": {
24
- "@tuttiai/core": "^0.3.0",
25
- "@tuttiai/types": "^0.2.0",
26
- "chalk": "^5.4.0",
27
- "commander": "^13.1.0",
28
- "dotenv": "^17.4.1",
29
- "enquirer": "^2.4.0",
30
- "ora": "^8.2.0"
28
+ "@tuttiai/core": "*",
29
+ "@tuttiai/types": "*",
30
+ "chalk": "5.6.2",
31
+ "commander": "4.1.1",
32
+ "dotenv": "17.4.1",
33
+ "enquirer": "2.4.1",
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": ["tutti", "ai", "agents", "orchestration", "cli", "scaffold"],
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
  }