@tuttiai/cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { config } from "dotenv";
5
- import { createLogger as createLogger9 } from "@tuttiai/core";
5
+ import { createLogger as createLogger10 } from "@tuttiai/core";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/init.ts
@@ -514,52 +514,312 @@ async function runCommand(scorePath) {
514
514
  process.exit(0);
515
515
  }
516
516
 
517
- // src/commands/add.ts
518
- import { existsSync as existsSync3, readFileSync } from "fs";
517
+ // src/commands/resume.ts
518
+ import { existsSync as existsSync3 } from "fs";
519
519
  import { resolve as resolve2 } from "path";
520
- import { execSync } from "child_process";
520
+ import { createInterface as createInterface2 } from "readline/promises";
521
521
  import chalk3 from "chalk";
522
522
  import ora2 from "ora";
523
- import { createLogger as createLogger3 } from "@tuttiai/core";
523
+ import {
524
+ AnthropicProvider as AnthropicProvider2,
525
+ GeminiProvider as GeminiProvider2,
526
+ OpenAIProvider as OpenAIProvider2,
527
+ ScoreLoader as ScoreLoader2,
528
+ SecretsManager as SecretsManager2,
529
+ TuttiRuntime as TuttiRuntime2,
530
+ createCheckpointStore,
531
+ createLogger as createLogger3
532
+ } from "@tuttiai/core";
524
533
  var logger3 = createLogger3("tutti-cli");
534
+ async function resumeCommand(sessionId, opts) {
535
+ const scoreFile = resolve2(opts.score ?? "./tutti.score.ts");
536
+ if (!existsSync3(scoreFile)) {
537
+ logger3.error({ file: scoreFile }, "Score file not found");
538
+ console.error(chalk3.dim('Run "tutti-ai init" to create a new project.'));
539
+ process.exit(1);
540
+ }
541
+ let score;
542
+ try {
543
+ score = await ScoreLoader2.load(scoreFile);
544
+ } catch (err) {
545
+ logger3.error(
546
+ { error: err instanceof Error ? err.message : String(err) },
547
+ "Failed to load score"
548
+ );
549
+ process.exit(1);
550
+ }
551
+ const providerKeyMap = [
552
+ [AnthropicProvider2, "ANTHROPIC_API_KEY"],
553
+ [OpenAIProvider2, "OPENAI_API_KEY"],
554
+ [GeminiProvider2, "GEMINI_API_KEY"]
555
+ ];
556
+ for (const [ProviderClass, envVar] of providerKeyMap) {
557
+ if (score.provider instanceof ProviderClass) {
558
+ if (!SecretsManager2.optional(envVar)) {
559
+ logger3.error({ envVar }, "Missing API key");
560
+ process.exit(1);
561
+ }
562
+ }
563
+ }
564
+ const agentName = resolveAgentName(score, opts.agent);
565
+ const agent = score.agents[agentName];
566
+ if (!agent) {
567
+ logger3.error(
568
+ { agent: agentName, available: Object.keys(score.agents) },
569
+ "Agent not found in score"
570
+ );
571
+ process.exit(1);
572
+ }
573
+ if (!agent.durable) {
574
+ console.error(
575
+ chalk3.yellow(
576
+ "Agent '" + agentName + "' does not have `durable: true` set \u2014 resume has nothing to restore."
577
+ )
578
+ );
579
+ console.error(
580
+ chalk3.dim(
581
+ "Enable durable checkpointing on the agent before the run that created this session."
582
+ )
583
+ );
584
+ process.exit(1);
585
+ }
586
+ const spinner = ora2({ color: "cyan" }).start("Loading checkpoint...");
587
+ let checkpointStore;
588
+ let checkpoint;
589
+ try {
590
+ checkpointStore = createCheckpointStore({ store: opts.store });
591
+ checkpoint = await checkpointStore.loadLatest(sessionId);
592
+ } catch (err) {
593
+ spinner.fail("Failed to load checkpoint");
594
+ logger3.error(
595
+ { error: err instanceof Error ? err.message : String(err), store: opts.store },
596
+ "Checkpoint store error"
597
+ );
598
+ process.exit(1);
599
+ }
600
+ spinner.stop();
601
+ if (!checkpoint) {
602
+ console.error(
603
+ chalk3.red("No checkpoint found for session " + sessionId + ".")
604
+ );
605
+ console.error(
606
+ chalk3.dim(
607
+ "Verify TUTTI_" + (opts.store === "redis" ? "REDIS" : "PG") + "_URL points to the same " + opts.store + " the original run used."
608
+ )
609
+ );
610
+ process.exit(1);
611
+ }
612
+ printSummary(checkpoint);
613
+ if (!opts.yes && !await confirmResume(checkpoint.turn)) {
614
+ console.log(chalk3.dim("Cancelled."));
615
+ process.exit(0);
616
+ }
617
+ const runtime = new TuttiRuntime2(score, { checkpointStore });
618
+ const sessions = runtime.sessions;
619
+ if ("save" in sessions && typeof sessions.save === "function") {
620
+ sessions.save({
621
+ id: sessionId,
622
+ agent_name: agentName,
623
+ messages: [...checkpoint.messages],
624
+ created_at: checkpoint.saved_at,
625
+ updated_at: /* @__PURE__ */ new Date()
626
+ });
627
+ } else {
628
+ console.error(
629
+ chalk3.red(
630
+ "Session store does not support resume seeding. Use the default InMemorySessionStore or PostgresSessionStore."
631
+ )
632
+ );
633
+ process.exit(1);
634
+ }
635
+ wireProgress(runtime);
636
+ try {
637
+ const result = await runtime.run(agentName, "[resume]", sessionId);
638
+ console.log();
639
+ console.log(chalk3.green("\u2713 Resumed run complete."));
640
+ console.log(chalk3.dim(" Final turn: " + result.turns));
641
+ console.log(chalk3.dim(" Session ID: " + result.session_id));
642
+ console.log(
643
+ chalk3.dim(
644
+ " Token usage: " + result.usage.input_tokens + " in / " + result.usage.output_tokens + " out"
645
+ )
646
+ );
647
+ console.log();
648
+ console.log(result.output);
649
+ } catch (err) {
650
+ logger3.error(
651
+ { error: err instanceof Error ? err.message : String(err) },
652
+ "Resume failed"
653
+ );
654
+ process.exit(1);
655
+ }
656
+ }
657
+ function resolveAgentName(score, override) {
658
+ if (override) return override;
659
+ if (typeof score.entry === "string") return score.entry;
660
+ const first = Object.keys(score.agents)[0];
661
+ if (!first) {
662
+ console.error(chalk3.red("Score has no agents defined."));
663
+ process.exit(1);
664
+ }
665
+ return first;
666
+ }
667
+ function printSummary(checkpoint) {
668
+ console.log();
669
+ console.log(chalk3.cyan.bold("Checkpoint summary"));
670
+ console.log(
671
+ chalk3.dim(" Session ID: ") + checkpoint.session_id
672
+ );
673
+ console.log(
674
+ chalk3.dim(" Last turn: ") + String(checkpoint.turn)
675
+ );
676
+ console.log(
677
+ chalk3.dim(" Saved at: ") + checkpoint.saved_at.toISOString()
678
+ );
679
+ console.log(
680
+ chalk3.dim(" Messages: ") + String(checkpoint.messages.length) + " total"
681
+ );
682
+ console.log();
683
+ console.log(chalk3.cyan("First messages"));
684
+ const preview = checkpoint.messages.slice(0, 3);
685
+ for (const msg of preview) {
686
+ const text = excerpt(messageToText(msg), 200);
687
+ console.log(chalk3.dim(" [" + msg.role + "] ") + text);
688
+ }
689
+ if (checkpoint.messages.length > preview.length) {
690
+ console.log(
691
+ chalk3.dim(
692
+ " \u2026 " + String(checkpoint.messages.length - preview.length) + " more"
693
+ )
694
+ );
695
+ }
696
+ console.log();
697
+ }
698
+ function messageToText(msg) {
699
+ if (typeof msg.content === "string") return msg.content;
700
+ const parts = [];
701
+ for (const block of msg.content) {
702
+ if (block.type === "text") {
703
+ parts.push(block.text);
704
+ } else if (block.type === "tool_use") {
705
+ parts.push("[tool_use " + block.name + "]");
706
+ } else if (block.type === "tool_result") {
707
+ parts.push("[tool_result " + excerpt(block.content, 80) + "]");
708
+ }
709
+ }
710
+ return parts.join(" ");
711
+ }
712
+ function excerpt(text, max) {
713
+ const oneLine = text.replace(/\s+/g, " ").trim();
714
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
715
+ }
716
+ async function confirmResume(turn) {
717
+ const rl = createInterface2({
718
+ input: process.stdin,
719
+ output: process.stdout
720
+ });
721
+ try {
722
+ const answer = (await rl.question(
723
+ chalk3.cyan("Resume from turn " + turn + "? ") + chalk3.dim("(y/n) ")
724
+ )).trim().toLowerCase();
725
+ return answer === "y" || answer === "yes";
726
+ } finally {
727
+ rl.close();
728
+ }
729
+ }
730
+ function wireProgress(runtime) {
731
+ const spinner = ora2({ color: "cyan" });
732
+ let streaming = false;
733
+ runtime.events.on("checkpoint:restored", (e) => {
734
+ console.log(
735
+ chalk3.dim("\u21BB Restored from turn " + e.turn) + chalk3.dim(" (session " + e.session_id.slice(0, 8) + "\u2026)")
736
+ );
737
+ });
738
+ runtime.events.on("checkpoint:saved", (e) => {
739
+ console.log(chalk3.dim("\xB7 Checkpoint saved at turn " + e.turn));
740
+ });
741
+ runtime.events.on("llm:request", () => {
742
+ spinner.start("Thinking...");
743
+ });
744
+ runtime.events.on("token:stream", (e) => {
745
+ if (!streaming) {
746
+ spinner.stop();
747
+ streaming = true;
748
+ }
749
+ process.stdout.write(e.text);
750
+ });
751
+ runtime.events.on("llm:response", () => {
752
+ if (streaming) {
753
+ process.stdout.write("\n");
754
+ } else {
755
+ spinner.stop();
756
+ }
757
+ });
758
+ runtime.events.on("tool:start", (e) => {
759
+ if (streaming) {
760
+ process.stdout.write(chalk3.dim("\n [using: " + e.tool_name + "]"));
761
+ } else {
762
+ spinner.stop();
763
+ console.log(chalk3.dim(" [using: " + e.tool_name + "]"));
764
+ }
765
+ });
766
+ runtime.events.on("tool:end", (e) => {
767
+ if (streaming) {
768
+ process.stdout.write(chalk3.dim(" [done: " + e.tool_name + "]\n"));
769
+ }
770
+ });
771
+ runtime.events.on("tool:error", (e) => {
772
+ spinner.stop();
773
+ logger3.error({ tool: e.tool_name }, "Tool error");
774
+ });
775
+ }
776
+
777
+ // src/commands/add.ts
778
+ import { existsSync as existsSync4, readFileSync } from "fs";
779
+ import { resolve as resolve3 } from "path";
780
+ import { execSync } from "child_process";
781
+ import chalk4 from "chalk";
782
+ import ora3 from "ora";
783
+ import { createLogger as createLogger4 } from "@tuttiai/core";
784
+ var logger4 = createLogger4("tutti-cli");
525
785
  var OFFICIAL_VOICES = {
526
786
  filesystem: {
527
787
  package: "@tuttiai/filesystem",
528
788
  setup: ` Add to your score:
529
- ${chalk3.cyan('import { FilesystemVoice } from "@tuttiai/filesystem"')}
530
- ${chalk3.cyan("voices: [new FilesystemVoice()]")}`
789
+ ${chalk4.cyan('import { FilesystemVoice } from "@tuttiai/filesystem"')}
790
+ ${chalk4.cyan("voices: [new FilesystemVoice()]")}`
531
791
  },
532
792
  github: {
533
793
  package: "@tuttiai/github",
534
- setup: ` Add ${chalk3.bold("GITHUB_TOKEN")} to your .env file:
535
- ${chalk3.cyan("GITHUB_TOKEN=ghp_your_token_here")}
794
+ setup: ` Add ${chalk4.bold("GITHUB_TOKEN")} to your .env file:
795
+ ${chalk4.cyan("GITHUB_TOKEN=ghp_your_token_here")}
536
796
 
537
797
  Add to your score:
538
- ${chalk3.cyan('import { GitHubVoice } from "@tuttiai/github"')}
539
- ${chalk3.cyan("voices: [new GitHubVoice()]")}`
798
+ ${chalk4.cyan('import { GitHubVoice } from "@tuttiai/github"')}
799
+ ${chalk4.cyan("voices: [new GitHubVoice()]")}`
540
800
  },
541
801
  playwright: {
542
802
  package: "@tuttiai/playwright",
543
803
  setup: ` Install the browser:
544
- ${chalk3.cyan("npx playwright install chromium")}
804
+ ${chalk4.cyan("npx playwright install chromium")}
545
805
 
546
806
  Add to your score:
547
- ${chalk3.cyan('import { PlaywrightVoice } from "@tuttiai/playwright"')}
548
- ${chalk3.cyan("voices: [new PlaywrightVoice()]")}`
807
+ ${chalk4.cyan('import { PlaywrightVoice } from "@tuttiai/playwright"')}
808
+ ${chalk4.cyan("voices: [new PlaywrightVoice()]")}`
549
809
  },
550
810
  postgres: {
551
811
  package: "pg",
552
- setup: ` Add ${chalk3.bold("DATABASE_URL")} to your .env file:
553
- ${chalk3.cyan("DATABASE_URL=postgres://user:pass@localhost:5432/tutti")}
812
+ setup: ` Add ${chalk4.bold("DATABASE_URL")} to your .env file:
813
+ ${chalk4.cyan("DATABASE_URL=postgres://user:pass@localhost:5432/tutti")}
554
814
 
555
815
  Add to your score:
556
- ${chalk3.cyan("memory: { provider: 'postgres' }")}
816
+ ${chalk4.cyan("memory: { provider: 'postgres' }")}
557
817
 
558
818
  Or with an explicit URL:
559
- ${chalk3.cyan("memory: { provider: 'postgres', url: process.env.DATABASE_URL }")}
819
+ ${chalk4.cyan("memory: { provider: 'postgres', url: process.env.DATABASE_URL }")}
560
820
 
561
821
  Use the async factory for initialization:
562
- ${chalk3.cyan("const tutti = await TuttiRuntime.create(score)")}`
822
+ ${chalk4.cyan("const tutti = await TuttiRuntime.create(score)")}`
563
823
  }
564
824
  };
565
825
  function resolvePackageName(input) {
@@ -572,8 +832,8 @@ function resolvePackageName(input) {
572
832
  return `@tuttiai/${input}`;
573
833
  }
574
834
  function isAlreadyInstalled(packageName) {
575
- const pkgPath = resolve2(process.cwd(), "package.json");
576
- if (!existsSync3(pkgPath)) return false;
835
+ const pkgPath = resolve3(process.cwd(), "package.json");
836
+ if (!existsSync4(pkgPath)) return false;
577
837
  try {
578
838
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
579
839
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -584,17 +844,17 @@ function isAlreadyInstalled(packageName) {
584
844
  }
585
845
  function addCommand(voiceName) {
586
846
  const packageName = resolvePackageName(voiceName);
587
- const pkgPath = resolve2(process.cwd(), "package.json");
588
- if (!existsSync3(pkgPath)) {
589
- logger3.error("No package.json found in the current directory");
590
- console.error(chalk3.dim('Run "tutti-ai init" to create a new project first.'));
847
+ const pkgPath = resolve3(process.cwd(), "package.json");
848
+ if (!existsSync4(pkgPath)) {
849
+ logger4.error("No package.json found in the current directory");
850
+ console.error(chalk4.dim('Run "tutti-ai init" to create a new project first.'));
591
851
  process.exit(1);
592
852
  }
593
853
  if (isAlreadyInstalled(packageName)) {
594
- console.log(chalk3.green(` \u2714 ${packageName} is already installed`));
854
+ console.log(chalk4.green(` \u2714 ${packageName} is already installed`));
595
855
  return;
596
856
  }
597
- const spinner = ora2(`Installing ${packageName}...`).start();
857
+ const spinner = ora3(`Installing ${packageName}...`).start();
598
858
  try {
599
859
  execSync(`npm install ${packageName}`, {
600
860
  cwd: process.cwd(),
@@ -604,7 +864,7 @@ function addCommand(voiceName) {
604
864
  } catch (error) {
605
865
  spinner.fail(`Failed to install ${packageName}`);
606
866
  const message = error instanceof Error ? error.message : String(error);
607
- logger3.error({ error: message, package: packageName }, "Installation failed");
867
+ logger4.error({ error: message, package: packageName }, "Installation failed");
608
868
  process.exit(1);
609
869
  }
610
870
  const official = OFFICIAL_VOICES[voiceName];
@@ -616,43 +876,43 @@ function addCommand(voiceName) {
616
876
  } else {
617
877
  console.log();
618
878
  console.log(
619
- chalk3.dim(" Check the package README for setup instructions.")
879
+ chalk4.dim(" Check the package README for setup instructions.")
620
880
  );
621
881
  console.log();
622
882
  }
623
883
  }
624
884
 
625
885
  // src/commands/check.ts
626
- import { existsSync as existsSync4 } from "fs";
627
- import { resolve as resolve3 } from "path";
628
- import chalk4 from "chalk";
886
+ import { existsSync as existsSync5 } from "fs";
887
+ import { resolve as resolve4 } from "path";
888
+ import chalk5 from "chalk";
629
889
  import {
630
- ScoreLoader as ScoreLoader2,
631
- AnthropicProvider as AnthropicProvider2,
632
- OpenAIProvider as OpenAIProvider2,
633
- GeminiProvider as GeminiProvider2,
634
- SecretsManager as SecretsManager2,
635
- createLogger as createLogger4
890
+ ScoreLoader as ScoreLoader3,
891
+ AnthropicProvider as AnthropicProvider3,
892
+ OpenAIProvider as OpenAIProvider3,
893
+ GeminiProvider as GeminiProvider3,
894
+ SecretsManager as SecretsManager3,
895
+ createLogger as createLogger5
636
896
  } from "@tuttiai/core";
637
- var logger4 = createLogger4("tutti-cli");
638
- var ok = (msg) => console.log(chalk4.green(" \u2714 " + msg));
639
- var fail = (msg) => console.log(chalk4.red(" \u2718 " + msg));
897
+ var logger5 = createLogger5("tutti-cli");
898
+ var ok = (msg) => console.log(chalk5.green(" \u2714 " + msg));
899
+ var fail = (msg) => console.log(chalk5.red(" \u2718 " + msg));
640
900
  async function checkCommand(scorePath) {
641
- const file = resolve3(scorePath ?? "./tutti.score.ts");
642
- console.log(chalk4.cyan(`
901
+ const file = resolve4(scorePath ?? "./tutti.score.ts");
902
+ console.log(chalk5.cyan(`
643
903
  Checking ${file}...
644
904
  `));
645
- if (!existsSync4(file)) {
905
+ if (!existsSync5(file)) {
646
906
  fail("Score file not found: " + file);
647
907
  process.exit(1);
648
908
  }
649
909
  let score;
650
910
  try {
651
- score = await ScoreLoader2.load(file);
911
+ score = await ScoreLoader3.load(file);
652
912
  ok("Score file is valid");
653
913
  } catch (err) {
654
914
  fail("Score validation failed");
655
- logger4.error(
915
+ logger5.error(
656
916
  { error: err instanceof Error ? err.message : String(err) },
657
917
  "Score validation failed"
658
918
  );
@@ -660,15 +920,15 @@ Checking ${file}...
660
920
  }
661
921
  let hasErrors = false;
662
922
  const providerChecks = [
663
- [AnthropicProvider2, "AnthropicProvider", "ANTHROPIC_API_KEY"],
664
- [OpenAIProvider2, "OpenAIProvider", "OPENAI_API_KEY"],
665
- [GeminiProvider2, "GeminiProvider", "GEMINI_API_KEY"]
923
+ [AnthropicProvider3, "AnthropicProvider", "ANTHROPIC_API_KEY"],
924
+ [OpenAIProvider3, "OpenAIProvider", "OPENAI_API_KEY"],
925
+ [GeminiProvider3, "GeminiProvider", "GEMINI_API_KEY"]
666
926
  ];
667
927
  let providerDetected = false;
668
928
  for (const [ProviderClass, name, envVar] of providerChecks) {
669
929
  if (score.provider instanceof ProviderClass) {
670
930
  providerDetected = true;
671
- const key = SecretsManager2.optional(envVar);
931
+ const key = SecretsManager3.optional(envVar);
672
932
  if (key) {
673
933
  ok("Provider: " + name + " (" + envVar + " is set)");
674
934
  } else {
@@ -690,7 +950,7 @@ Checking ${file}...
690
950
  };
691
951
  const envVar = voiceEnvMap[voiceName];
692
952
  if (envVar) {
693
- const key = SecretsManager2.optional(envVar);
953
+ const key = SecretsManager3.optional(envVar);
694
954
  if (key) {
695
955
  ok(
696
956
  "Voice: " + voiceName + " on " + agentKey + " (" + envVar + " is set)"
@@ -709,28 +969,28 @@ Checking ${file}...
709
969
  console.log("");
710
970
  if (hasErrors) {
711
971
  console.log(
712
- chalk4.yellow("Some checks failed. Fix the issues above and re-run.")
972
+ chalk5.yellow("Some checks failed. Fix the issues above and re-run.")
713
973
  );
714
974
  process.exit(1);
715
975
  } else {
716
976
  console.log(
717
- chalk4.green("All checks passed.") + chalk4.dim(" Run tutti-ai run to start.")
977
+ chalk5.green("All checks passed.") + chalk5.dim(" Run tutti-ai run to start.")
718
978
  );
719
979
  }
720
980
  }
721
981
 
722
982
  // src/commands/studio.ts
723
- import { existsSync as existsSync5 } from "fs";
724
- import { resolve as resolve4 } from "path";
983
+ import { existsSync as existsSync6 } from "fs";
984
+ import { resolve as resolve5 } from "path";
725
985
  import { execFile } from "child_process";
726
986
  import express from "express";
727
- import chalk5 from "chalk";
987
+ import chalk6 from "chalk";
728
988
  import {
729
- TuttiRuntime as TuttiRuntime2,
730
- ScoreLoader as ScoreLoader3,
731
- createLogger as createLogger5
989
+ TuttiRuntime as TuttiRuntime3,
990
+ ScoreLoader as ScoreLoader4,
991
+ createLogger as createLogger6
732
992
  } from "@tuttiai/core";
733
- var logger5 = createLogger5("tutti-studio");
993
+ var logger6 = createLogger6("tutti-studio");
734
994
  var envPort = Number.parseInt(process.env.PORT ?? "", 10);
735
995
  var PORT = Number.isInteger(envPort) && envPort > 0 && envPort <= 65535 ? envPort : 4747;
736
996
  function safeStringify(obj) {
@@ -749,20 +1009,20 @@ function openBrowser(url) {
749
1009
  execFile(cmd, [url]);
750
1010
  }
751
1011
  async function studioCommand(scorePath) {
752
- const file = resolve4(scorePath ?? "./tutti.score.ts");
753
- if (!existsSync5(file)) {
754
- logger5.error({ file }, "Score file not found");
755
- console.error(chalk5.dim('Run "tutti-ai init" to create a new project.'));
1012
+ const file = resolve5(scorePath ?? "./tutti.score.ts");
1013
+ if (!existsSync6(file)) {
1014
+ logger6.error({ file }, "Score file not found");
1015
+ console.error(chalk6.dim('Run "tutti-ai init" to create a new project.'));
756
1016
  process.exit(1);
757
1017
  }
758
1018
  let score;
759
1019
  try {
760
- score = await ScoreLoader3.load(file);
1020
+ score = await ScoreLoader4.load(file);
761
1021
  } catch (err) {
762
- logger5.error({ error: err instanceof Error ? err.message : String(err) }, "Failed to load score");
1022
+ logger6.error({ error: err instanceof Error ? err.message : String(err) }, "Failed to load score");
763
1023
  process.exit(1);
764
1024
  }
765
- const runtime = new TuttiRuntime2(score);
1025
+ const runtime = new TuttiRuntime3(score);
766
1026
  const sessionRegistry = /* @__PURE__ */ new Map();
767
1027
  runtime.events.on("agent:start", (e) => {
768
1028
  if (!sessionRegistry.has(e.session_id)) {
@@ -865,16 +1125,16 @@ async function studioCommand(scorePath) {
865
1125
  app.listen(PORT, () => {
866
1126
  const url = "http://localhost:" + PORT;
867
1127
  console.log();
868
- console.log(chalk5.bold(" Tutti Studio"));
869
- console.log(chalk5.dim(" " + url));
1128
+ console.log(chalk6.bold(" Tutti Studio"));
1129
+ console.log(chalk6.dim(" " + url));
870
1130
  console.log();
871
- console.log(chalk5.dim(" Score: ") + (runtime.score.name ?? file));
872
- console.log(chalk5.dim(" Agents: ") + Object.keys(runtime.score.agents).join(", "));
1131
+ console.log(chalk6.dim(" Score: ") + (runtime.score.name ?? file));
1132
+ console.log(chalk6.dim(" Agents: ") + Object.keys(runtime.score.agents).join(", "));
873
1133
  console.log();
874
1134
  openBrowser(url);
875
1135
  });
876
1136
  process.on("SIGINT", () => {
877
- console.log(chalk5.dim("\nShutting down Tutti Studio..."));
1137
+ console.log(chalk6.dim("\nShutting down Tutti Studio..."));
878
1138
  process.exit(0);
879
1139
  });
880
1140
  }
@@ -883,12 +1143,12 @@ function getStudioHtml() {
883
1143
  }
884
1144
 
885
1145
  // src/commands/search.ts
886
- import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
887
- import { resolve as resolve5 } from "path";
888
- import chalk6 from "chalk";
889
- import ora3 from "ora";
890
- import { createLogger as createLogger6 } from "@tuttiai/core";
891
- var logger6 = createLogger6("tutti-cli");
1146
+ import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
1147
+ import { resolve as resolve6 } from "path";
1148
+ import chalk7 from "chalk";
1149
+ import ora4 from "ora";
1150
+ import { createLogger as createLogger7 } from "@tuttiai/core";
1151
+ var logger7 = createLogger7("tutti-cli");
892
1152
  var REGISTRY_URL = "https://raw.githubusercontent.com/tuttiai/voices/main/voices.json";
893
1153
  var BUILTIN_VOICES = [
894
1154
  {
@@ -939,7 +1199,7 @@ async function fetchRegistry() {
939
1199
  if (voices.length === 0) throw new Error("Empty registry");
940
1200
  return voices;
941
1201
  } catch {
942
- logger6.debug("Registry unreachable, using built-in voice list");
1202
+ logger7.debug("Registry unreachable, using built-in voice list");
943
1203
  return BUILTIN_VOICES;
944
1204
  }
945
1205
  }
@@ -955,8 +1215,8 @@ function matchesQuery(voice, query) {
955
1215
  return false;
956
1216
  }
957
1217
  function isInstalled(packageName) {
958
- const pkgPath = resolve5(process.cwd(), "package.json");
959
- if (!existsSync6(pkgPath)) return false;
1218
+ const pkgPath = resolve6(process.cwd(), "package.json");
1219
+ if (!existsSync7(pkgPath)) return false;
960
1220
  try {
961
1221
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
962
1222
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -966,35 +1226,35 @@ function isInstalled(packageName) {
966
1226
  }
967
1227
  }
968
1228
  function printVoice(voice, showInstallStatus) {
969
- const badge = voice.official ? chalk6.green(" [official]") : chalk6.blue(" [community]");
1229
+ const badge = voice.official ? chalk7.green(" [official]") : chalk7.blue(" [community]");
970
1230
  const installed = showInstallStatus && isInstalled(voice.package);
971
- const status = showInstallStatus ? installed ? chalk6.green(" \u2714 installed") : chalk6.dim(" not installed") : "";
1231
+ const status = showInstallStatus ? installed ? chalk7.green(" \u2714 installed") : chalk7.dim(" not installed") : "";
972
1232
  console.log();
973
- console.log(" " + chalk6.bold(voice.package) + badge + status);
1233
+ console.log(" " + chalk7.bold(voice.package) + badge + status);
974
1234
  console.log(" " + voice.description);
975
1235
  const installCmd = voice.official && voice.name !== "postgres" ? "tutti-ai add " + voice.name : "npm install " + voice.package;
976
- console.log(" " + chalk6.dim("Install: ") + chalk6.cyan(installCmd));
1236
+ console.log(" " + chalk7.dim("Install: ") + chalk7.cyan(installCmd));
977
1237
  if (voice.tags.length > 0) {
978
- console.log(" " + chalk6.dim("Tags: ") + voice.tags.join(", "));
1238
+ console.log(" " + chalk7.dim("Tags: ") + voice.tags.join(", "));
979
1239
  }
980
1240
  }
981
1241
  async function searchCommand(query) {
982
- const spinner = ora3("Searching the Repertoire...").start();
1242
+ const spinner = ora4("Searching the Repertoire...").start();
983
1243
  const voices = await fetchRegistry();
984
1244
  const results = voices.filter((v) => matchesQuery(v, query));
985
1245
  spinner.stop();
986
1246
  if (results.length === 0) {
987
1247
  console.log();
988
- console.log(chalk6.yellow(' No voices found for "' + query + '"'));
1248
+ console.log(chalk7.yellow(' No voices found for "' + query + '"'));
989
1249
  console.log();
990
- console.log(chalk6.dim(" Browse all: https://tutti-ai.com/voices"));
991
- console.log(chalk6.dim(" Build your own: tutti-ai create voice <name>"));
1250
+ console.log(chalk7.dim(" Browse all: https://tutti-ai.com/voices"));
1251
+ console.log(chalk7.dim(" Build your own: tutti-ai create voice <name>"));
992
1252
  console.log();
993
1253
  return;
994
1254
  }
995
1255
  console.log();
996
1256
  console.log(
997
- " Found " + chalk6.bold(String(results.length)) + " voice" + (results.length !== 1 ? "s" : "") + " matching " + chalk6.cyan("'" + query + "'") + ":"
1257
+ " Found " + chalk7.bold(String(results.length)) + " voice" + (results.length !== 1 ? "s" : "") + " matching " + chalk7.cyan("'" + query + "'") + ":"
998
1258
  );
999
1259
  for (const voice of results) {
1000
1260
  printVoice(voice, false);
@@ -1002,12 +1262,12 @@ async function searchCommand(query) {
1002
1262
  console.log();
1003
1263
  }
1004
1264
  async function voicesCommand() {
1005
- const spinner = ora3("Loading voices...").start();
1265
+ const spinner = ora4("Loading voices...").start();
1006
1266
  const voices = await fetchRegistry();
1007
1267
  const official = voices.filter((v) => v.official);
1008
1268
  spinner.stop();
1009
1269
  console.log();
1010
- console.log(" " + chalk6.bold("Official Tutti Voices"));
1270
+ console.log(" " + chalk7.bold("Official Tutti Voices"));
1011
1271
  console.log();
1012
1272
  for (const voice of official) {
1013
1273
  printVoice(voice, true);
@@ -1015,49 +1275,49 @@ async function voicesCommand() {
1015
1275
  const community = voices.filter((v) => !v.official);
1016
1276
  if (community.length > 0) {
1017
1277
  console.log();
1018
- console.log(" " + chalk6.bold("Community Voices"));
1278
+ console.log(" " + chalk7.bold("Community Voices"));
1019
1279
  for (const voice of community) {
1020
1280
  printVoice(voice, true);
1021
1281
  }
1022
1282
  }
1023
1283
  console.log();
1024
- console.log(chalk6.dim(" Search: tutti-ai search <query>"));
1025
- console.log(chalk6.dim(" Browse: https://tutti-ai.com/voices"));
1284
+ console.log(chalk7.dim(" Search: tutti-ai search <query>"));
1285
+ console.log(chalk7.dim(" Browse: https://tutti-ai.com/voices"));
1026
1286
  console.log();
1027
1287
  }
1028
1288
 
1029
1289
  // src/commands/publish.ts
1030
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
1031
- import { resolve as resolve6 } from "path";
1290
+ import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
1291
+ import { resolve as resolve7 } from "path";
1032
1292
  import { execSync as execSync2 } from "child_process";
1033
- import chalk7 from "chalk";
1034
- import ora4 from "ora";
1293
+ import chalk8 from "chalk";
1294
+ import ora5 from "ora";
1035
1295
  import Enquirer2 from "enquirer";
1036
- import { createLogger as createLogger7, SecretsManager as SecretsManager3 } from "@tuttiai/core";
1296
+ import { createLogger as createLogger8, SecretsManager as SecretsManager4 } from "@tuttiai/core";
1037
1297
  var { prompt: prompt2 } = Enquirer2;
1038
- var logger7 = createLogger7("tutti-cli");
1298
+ var logger8 = createLogger8("tutti-cli");
1039
1299
  function readPkg(dir) {
1040
- const p = resolve6(dir, "package.json");
1041
- if (!existsSync7(p)) return void 0;
1300
+ const p = resolve7(dir, "package.json");
1301
+ if (!existsSync8(p)) return void 0;
1042
1302
  return JSON.parse(readFileSync3(p, "utf-8"));
1043
1303
  }
1044
1304
  function run(cmd, cwd) {
1045
1305
  return execSync2(cmd, { cwd, stdio: "pipe", encoding: "utf-8" });
1046
1306
  }
1047
1307
  function fail2(msg) {
1048
- console.error(chalk7.red(" " + msg));
1308
+ console.error(chalk8.red(" " + msg));
1049
1309
  process.exit(1);
1050
1310
  }
1051
- var ok2 = (msg) => console.log(chalk7.green(" \u2714 " + msg));
1311
+ var ok2 = (msg) => console.log(chalk8.green(" \u2714 " + msg));
1052
1312
  async function publishCommand(opts) {
1053
1313
  const cwd = process.cwd();
1054
1314
  const pkg = readPkg(cwd);
1055
1315
  console.log();
1056
- console.log(chalk7.bold(" Tutti Voice Publisher"));
1316
+ console.log(chalk8.bold(" Tutti Voice Publisher"));
1057
1317
  console.log();
1058
- const spinner = ora4("Running pre-flight checks...").start();
1318
+ const spinner = ora5("Running pre-flight checks...").start();
1059
1319
  if (!pkg) fail2("No package.json found in the current directory.");
1060
- if (!existsSync7(resolve6(cwd, "src/index.ts"))) fail2("No src/index.ts found \u2014 are you inside a voice directory?");
1320
+ if (!existsSync8(resolve7(cwd, "src/index.ts"))) fail2("No src/index.ts found \u2014 are you inside a voice directory?");
1061
1321
  const missing = [];
1062
1322
  if (!pkg.name) missing.push("name");
1063
1323
  if (!pkg.version) missing.push("version");
@@ -1069,22 +1329,22 @@ async function publishCommand(opts) {
1069
1329
  const version = pkg.version;
1070
1330
  const validName = name.startsWith("@tuttiai/") || name.startsWith("tutti");
1071
1331
  if (!validName) fail2("Package name must start with @tuttiai/ or tutti \u2014 got: " + name);
1072
- const src = readFileSync3(resolve6(cwd, "src/index.ts"), "utf-8");
1332
+ const src = readFileSync3(resolve7(cwd, "src/index.ts"), "utf-8");
1073
1333
  if (!src.includes("required_permissions")) {
1074
1334
  fail2("Voice class must declare required_permissions in src/index.ts");
1075
1335
  }
1076
1336
  spinner.succeed("Pre-flight checks passed");
1077
- const buildSpinner = ora4("Building...").start();
1337
+ const buildSpinner = ora5("Building...").start();
1078
1338
  try {
1079
1339
  run("npm run build", cwd);
1080
1340
  buildSpinner.succeed("Build succeeded");
1081
1341
  } catch (err) {
1082
1342
  buildSpinner.fail("Build failed");
1083
1343
  const msg = err instanceof Error ? err.message : String(err);
1084
- console.error(chalk7.dim(" " + msg.split("\n").slice(0, 5).join("\n ")));
1344
+ console.error(chalk8.dim(" " + msg.split("\n").slice(0, 5).join("\n ")));
1085
1345
  process.exit(1);
1086
1346
  }
1087
- const testSpinner = ora4("Running tests...").start();
1347
+ const testSpinner = ora5("Running tests...").start();
1088
1348
  try {
1089
1349
  run("npx vitest run", cwd);
1090
1350
  testSpinner.succeed("Tests passed");
@@ -1092,15 +1352,15 @@ async function publishCommand(opts) {
1092
1352
  testSpinner.fail("Tests failed");
1093
1353
  process.exit(1);
1094
1354
  }
1095
- const auditSpinner = ora4("Checking vulnerabilities...").start();
1355
+ const auditSpinner = ora5("Checking vulnerabilities...").start();
1096
1356
  try {
1097
1357
  run("npm audit --audit-level=high", cwd);
1098
1358
  auditSpinner.succeed("No high/critical vulnerabilities");
1099
1359
  } catch {
1100
- auditSpinner.stopAndPersist({ symbol: chalk7.yellow("\u26A0"), text: "Vulnerabilities found (npm audit)" });
1360
+ auditSpinner.stopAndPersist({ symbol: chalk8.yellow("\u26A0"), text: "Vulnerabilities found (npm audit)" });
1101
1361
  }
1102
1362
  console.log();
1103
- const drySpinner = ora4("Packing (dry run)...").start();
1363
+ const drySpinner = ora5("Packing (dry run)...").start();
1104
1364
  let packOutput;
1105
1365
  try {
1106
1366
  packOutput = run("npm pack --dry-run 2>&1", cwd);
@@ -1108,24 +1368,24 @@ async function publishCommand(opts) {
1108
1368
  } catch (err) {
1109
1369
  drySpinner.fail("Pack dry-run failed");
1110
1370
  const msg = err instanceof Error ? err.message : String(err);
1111
- console.error(chalk7.dim(" " + msg));
1371
+ console.error(chalk8.dim(" " + msg));
1112
1372
  process.exit(1);
1113
1373
  }
1114
1374
  const fileLines = packOutput.split("\n").filter((l) => l.includes("npm notice") && /\d+(\.\d+)?\s*[kM]?B\s/.test(l)).map((l) => l.replace(/npm notice\s*/, ""));
1115
1375
  if (fileLines.length > 0) {
1116
- console.log(chalk7.dim(" Files:"));
1376
+ console.log(chalk8.dim(" Files:"));
1117
1377
  for (const line of fileLines) {
1118
- console.log(chalk7.dim(" " + line.trim()));
1378
+ console.log(chalk8.dim(" " + line.trim()));
1119
1379
  }
1120
1380
  }
1121
1381
  const sizeLine = packOutput.split("\n").find((l) => l.includes("package size"));
1122
1382
  const totalLine = packOutput.split("\n").find((l) => l.includes("total files"));
1123
- if (sizeLine) console.log(chalk7.dim(" " + sizeLine.replace(/npm notice\s*/, "").trim()));
1124
- if (totalLine) console.log(chalk7.dim(" " + totalLine.replace(/npm notice\s*/, "").trim()));
1383
+ if (sizeLine) console.log(chalk8.dim(" " + sizeLine.replace(/npm notice\s*/, "").trim()));
1384
+ if (totalLine) console.log(chalk8.dim(" " + totalLine.replace(/npm notice\s*/, "").trim()));
1125
1385
  if (opts.dryRun) {
1126
1386
  console.log();
1127
1387
  ok2("Dry run complete \u2014 no packages were published");
1128
- console.log(chalk7.dim(" Run without --dry-run to publish for real."));
1388
+ console.log(chalk8.dim(" Run without --dry-run to publish for real."));
1129
1389
  console.log();
1130
1390
  return;
1131
1391
  }
@@ -1133,38 +1393,38 @@ async function publishCommand(opts) {
1133
1393
  const { confirm } = await prompt2({
1134
1394
  type: "confirm",
1135
1395
  name: "confirm",
1136
- message: "Publish " + chalk7.cyan(name + "@" + version) + "?"
1396
+ message: "Publish " + chalk8.cyan(name + "@" + version) + "?"
1137
1397
  });
1138
1398
  if (!confirm) {
1139
- console.log(chalk7.dim(" Cancelled."));
1399
+ console.log(chalk8.dim(" Cancelled."));
1140
1400
  return;
1141
1401
  }
1142
- const pubSpinner = ora4("Publishing to npm...").start();
1402
+ const pubSpinner = ora5("Publishing to npm...").start();
1143
1403
  try {
1144
1404
  run("npm publish --access public", cwd);
1145
- pubSpinner.succeed("Published " + chalk7.cyan(name + "@" + version));
1405
+ pubSpinner.succeed("Published " + chalk8.cyan(name + "@" + version));
1146
1406
  } catch (err) {
1147
1407
  pubSpinner.fail("Publish failed");
1148
1408
  const msg = err instanceof Error ? err.message : String(err);
1149
- logger7.error({ error: msg }, "npm publish failed");
1409
+ logger8.error({ error: msg }, "npm publish failed");
1150
1410
  process.exit(1);
1151
1411
  }
1152
- const ghToken = SecretsManager3.optional("GITHUB_TOKEN");
1412
+ const ghToken = SecretsManager4.optional("GITHUB_TOKEN");
1153
1413
  let prUrl;
1154
1414
  if (ghToken) {
1155
- const prSpinner = ora4("Opening PR to voice registry...").start();
1415
+ const prSpinner = ora5("Opening PR to voice registry...").start();
1156
1416
  try {
1157
1417
  prUrl = await openRegistryPR(name, version, pkg.description ?? "", ghToken);
1158
1418
  prSpinner.succeed("PR opened: " + prUrl);
1159
1419
  } catch (err) {
1160
1420
  prSpinner.fail("Failed to open PR");
1161
1421
  const msg = err instanceof Error ? err.message : String(err);
1162
- logger7.error({ error: msg }, "Registry PR failed");
1422
+ logger8.error({ error: msg }, "Registry PR failed");
1163
1423
  }
1164
1424
  } else {
1165
1425
  console.log();
1166
- console.log(chalk7.dim(" To list in the Repertoire, set GITHUB_TOKEN and re-run"));
1167
- console.log(chalk7.dim(" Or open a PR manually: github.com/tuttiai/voices"));
1426
+ console.log(chalk8.dim(" To list in the Repertoire, set GITHUB_TOKEN and re-run"));
1427
+ console.log(chalk8.dim(" Or open a PR manually: github.com/tuttiai/voices"));
1168
1428
  }
1169
1429
  console.log();
1170
1430
  ok2(name + "@" + version + " published to npm");
@@ -1249,69 +1509,69 @@ async function openRegistryPR(packageName, version, description, token) {
1249
1509
  }
1250
1510
 
1251
1511
  // src/commands/eval.ts
1252
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
1253
- import { resolve as resolve7 } from "path";
1254
- import chalk8 from "chalk";
1255
- import ora5 from "ora";
1512
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
1513
+ import { resolve as resolve8 } from "path";
1514
+ import chalk9 from "chalk";
1515
+ import ora6 from "ora";
1256
1516
  import {
1257
- ScoreLoader as ScoreLoader4,
1517
+ ScoreLoader as ScoreLoader5,
1258
1518
  EvalRunner,
1259
1519
  printEvalTable,
1260
- createLogger as createLogger8
1520
+ createLogger as createLogger9
1261
1521
  } from "@tuttiai/core";
1262
- var logger8 = createLogger8("tutti-cli");
1522
+ var logger9 = createLogger9("tutti-cli");
1263
1523
  async function evalCommand(suitePath, opts) {
1264
- const suiteFile = resolve7(suitePath);
1265
- if (!existsSync8(suiteFile)) {
1266
- logger8.error({ file: suiteFile }, "Suite file not found");
1524
+ const suiteFile = resolve8(suitePath);
1525
+ if (!existsSync9(suiteFile)) {
1526
+ logger9.error({ file: suiteFile }, "Suite file not found");
1267
1527
  process.exit(1);
1268
1528
  }
1269
1529
  let suite;
1270
1530
  try {
1271
1531
  suite = JSON.parse(readFileSync4(suiteFile, "utf-8"));
1272
1532
  } catch (err) {
1273
- logger8.error({ error: err instanceof Error ? err.message : String(err) }, "Failed to parse suite file");
1533
+ logger9.error({ error: err instanceof Error ? err.message : String(err) }, "Failed to parse suite file");
1274
1534
  process.exit(1);
1275
1535
  }
1276
- const scoreFile = resolve7(opts.score ?? "./tutti.score.ts");
1277
- if (!existsSync8(scoreFile)) {
1278
- logger8.error({ file: scoreFile }, "Score file not found");
1536
+ const scoreFile = resolve8(opts.score ?? "./tutti.score.ts");
1537
+ if (!existsSync9(scoreFile)) {
1538
+ logger9.error({ file: scoreFile }, "Score file not found");
1279
1539
  process.exit(1);
1280
1540
  }
1281
- const spinner = ora5("Loading score...").start();
1541
+ const spinner = ora6("Loading score...").start();
1282
1542
  let score;
1283
1543
  try {
1284
- score = await ScoreLoader4.load(scoreFile);
1544
+ score = await ScoreLoader5.load(scoreFile);
1285
1545
  } catch (err) {
1286
1546
  spinner.fail("Failed to load score");
1287
- logger8.error({ error: err instanceof Error ? err.message : String(err) }, "Score load failed");
1547
+ logger9.error({ error: err instanceof Error ? err.message : String(err) }, "Score load failed");
1288
1548
  process.exit(1);
1289
1549
  }
1290
1550
  spinner.succeed("Score loaded");
1291
- const evalSpinner = ora5("Running " + suite.cases.length + " eval cases...").start();
1551
+ const evalSpinner = ora6("Running " + suite.cases.length + " eval cases...").start();
1292
1552
  const runner = new EvalRunner(score);
1293
1553
  const report = await runner.run(suite);
1294
1554
  evalSpinner.stop();
1295
1555
  printEvalTable(report);
1296
1556
  if (opts.ci && report.summary.failed > 0) {
1297
- console.error(chalk8.red(" CI mode: " + report.summary.failed + " case(s) failed"));
1557
+ console.error(chalk9.red(" CI mode: " + report.summary.failed + " case(s) failed"));
1298
1558
  process.exit(1);
1299
1559
  }
1300
1560
  }
1301
1561
 
1302
1562
  // src/index.ts
1303
1563
  config();
1304
- var logger9 = createLogger9("tutti-cli");
1564
+ var logger10 = createLogger10("tutti-cli");
1305
1565
  process.on("unhandledRejection", (reason) => {
1306
- logger9.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
1566
+ logger10.error({ error: reason instanceof Error ? reason.message : String(reason) }, "Unhandled rejection");
1307
1567
  process.exit(1);
1308
1568
  });
1309
1569
  process.on("uncaughtException", (err) => {
1310
- logger9.error({ error: err.message }, "Fatal error");
1570
+ logger10.error({ error: err.message }, "Fatal error");
1311
1571
  process.exit(1);
1312
1572
  });
1313
1573
  var program = new Command();
1314
- program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.8.0");
1574
+ program.name("tutti-ai").description("Tutti \u2014 multi-agent orchestration. All agents. All together.").version("0.9.0");
1315
1575
  program.command("init [project-name]").description("Create a new Tutti project").option("-t, --template <id>", "Project template to use").action(async (projectName, opts) => {
1316
1576
  await initCommand(projectName, opts.template);
1317
1577
  });
@@ -1321,6 +1581,25 @@ program.command("templates").description("List all available project templates")
1321
1581
  program.command("run [score]").description("Run a Tutti score interactively").action(async (score) => {
1322
1582
  await runCommand(score);
1323
1583
  });
1584
+ program.command("resume <session-id>").description("Resume a crashed or interrupted run from its last checkpoint").option(
1585
+ "--store <backend>",
1586
+ "Durable store the checkpoint was written to (redis | postgres)",
1587
+ "redis"
1588
+ ).option("-s, --score <path>", "Path to score file (default: ./tutti.score.ts)").option("-a, --agent <name>", "Agent key to resume (default: score.entry or the first agent)").option("-y, --yes", "Skip the confirmation prompt").action(
1589
+ async (sessionId, opts) => {
1590
+ if (opts.store !== "redis" && opts.store !== "postgres") {
1591
+ console.error("--store must be 'redis' or 'postgres'");
1592
+ process.exit(1);
1593
+ }
1594
+ const resolved = {
1595
+ store: opts.store,
1596
+ ...opts.score !== void 0 ? { score: opts.score } : {},
1597
+ ...opts.agent !== void 0 ? { agent: opts.agent } : {},
1598
+ ...opts.yes !== void 0 ? { yes: opts.yes } : {}
1599
+ };
1600
+ await resumeCommand(sessionId, resolved);
1601
+ }
1602
+ );
1324
1603
  program.command("add <voice>").description("Add a voice to your project").action((voice) => {
1325
1604
  addCommand(voice);
1326
1605
  });