struere 0.13.1 → 0.14.1

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.
@@ -2549,6 +2549,22 @@ import chalk3 from "chalk";
2549
2549
  function isInteractive() {
2550
2550
  return process.stdout.isTTY === true && !process.env.CI && !getApiKey();
2551
2551
  }
2552
+ function createSilentOutput() {
2553
+ return {
2554
+ start() {},
2555
+ succeed() {},
2556
+ fail() {},
2557
+ stop() {},
2558
+ info() {},
2559
+ warn() {},
2560
+ error(msg) {
2561
+ console.error(msg);
2562
+ },
2563
+ json(data) {
2564
+ console.log(JSON.stringify(data, null, 2));
2565
+ }
2566
+ };
2567
+ }
2552
2568
  function createOutput() {
2553
2569
  if (isInteractive()) {
2554
2570
  const spinner = ora3();
@@ -2658,15 +2674,15 @@ orgCommand.command("list").description("List your organizations").option("--json
2658
2674
  await refreshToken();
2659
2675
  const fresh = loadCredentials();
2660
2676
  const token = fresh?.token || credentials.token;
2661
- const spinner = ora4();
2662
- spinner.start("Fetching organizations");
2677
+ const spinner = options.json ? null : ora4();
2678
+ spinner?.start("Fetching organizations");
2663
2679
  const { organizations, error } = await listMyOrganizations(token);
2664
2680
  if (error) {
2665
- spinner.fail("Failed to fetch organizations");
2681
+ spinner?.fail("Failed to fetch organizations");
2666
2682
  console.log(chalk4.red(error));
2667
2683
  process.exit(1);
2668
2684
  }
2669
- spinner.stop();
2685
+ spinner?.stop();
2670
2686
  if (options.json) {
2671
2687
  console.log(JSON.stringify(organizations, null, 2));
2672
2688
  return;
@@ -2706,15 +2722,15 @@ orgCommand.command("create").argument("[name]", "Organization name").description
2706
2722
  }
2707
2723
  name = name.trim();
2708
2724
  const slug = options.slug || slugify(name);
2709
- const spinner = ora4();
2710
- spinner.start("Creating organization");
2725
+ const spinner = options.json ? null : ora4();
2726
+ spinner?.start("Creating organization");
2711
2727
  const { organization, error } = await createOrganization(token, name, slug);
2712
2728
  if (error || !organization) {
2713
- spinner.fail("Failed to create organization");
2729
+ spinner?.fail("Failed to create organization");
2714
2730
  console.log(chalk4.red(error || "Unknown error"));
2715
2731
  process.exit(1);
2716
2732
  }
2717
- spinner.stop();
2733
+ spinner?.stop();
2718
2734
  if (options.json) {
2719
2735
  console.log(JSON.stringify(organization, null, 2));
2720
2736
  return;
@@ -2964,7 +2980,9 @@ var BUILTIN_TOOLS = [
2964
2980
  "payment.create",
2965
2981
  "payment.getStatus",
2966
2982
  "web.search",
2967
- "web.fetch"
2983
+ "web.fetch",
2984
+ "voice.call",
2985
+ "voice.inject"
2968
2986
  ];
2969
2987
  function extractSyncPayload(resources) {
2970
2988
  const customToolsMap = new Map;
@@ -3073,7 +3091,8 @@ function extractSyncPayload(resources) {
3073
3091
  classifyModel: r.classifyModel,
3074
3092
  contextMessages: r.contextMessages,
3075
3093
  maxTransfers: r.maxTransfers,
3076
- inactivityResetMs: r.inactivityResetMs
3094
+ inactivityResetMs: r.inactivityResetMs,
3095
+ voiceConfig: r.voiceConfig
3077
3096
  })) : undefined;
3078
3097
  const fixtures = resources.fixtures.length > 0 ? resources.fixtures.map((f) => ({
3079
3098
  name: f.name,
@@ -3107,6 +3126,11 @@ function extractAgentPayload(agent, customToolsMap) {
3107
3126
  const tools = (agent.tools || []).map((toolName) => {
3108
3127
  const isBuiltin = BUILTIN_TOOLS.includes(toolName);
3109
3128
  if (!isBuiltin && !customToolsMap.has(toolName)) {
3129
+ const builtinPrefixes = ["entity.", "calendar.", "whatsapp.", "agent.", "airtable.", "email.", "payment.", "web.", "voice.", "router."];
3130
+ const looksBuiltin = builtinPrefixes.some((prefix) => toolName.startsWith(prefix));
3131
+ if (looksBuiltin) {
3132
+ throw new Error(`Agent "${agent.name}" references tool "${toolName}" which looks like a built-in tool but is not recognized. Try updating the CLI: npm install -g struere@latest`);
3133
+ }
3110
3134
  const available = customToolsMap.size > 0 ? `Available custom tools: ${Array.from(customToolsMap.keys()).join(", ")}` : "No custom tools were loaded from tools/index.ts";
3111
3135
  throw new Error(`Agent "${agent.name}" references tool "${toolName}" but it was not found. ${available}`);
3112
3136
  }
@@ -3152,7 +3176,8 @@ var INTEGRATION_PREFIXES = {
3152
3176
  "calendar.": "Google Calendar",
3153
3177
  "airtable.": "Airtable",
3154
3178
  "email.": "Resend",
3155
- "payment.": "Flow / Polar"
3179
+ "payment.": "Flow / Polar",
3180
+ "voice.": "Twilio Voice"
3156
3181
  };
3157
3182
  function validateResources(payload, resources) {
3158
3183
  const warnings = [];
@@ -3477,8 +3502,42 @@ var syncCommand = new Command5("sync").description("Sync resources to Convex and
3477
3502
  output.start("Syncing to Convex");
3478
3503
  try {
3479
3504
  const result = await syncToEnvironment(cwd, project.organization.id, environment);
3480
- if (!jsonMode)
3505
+ if (!jsonMode) {
3481
3506
  output.succeed(`Synced to ${environment}`);
3507
+ console.log();
3508
+ const types = [
3509
+ { label: "agent", data: result.agents },
3510
+ { label: "entity type", data: result.entityTypes },
3511
+ { label: "role", data: result.roles },
3512
+ { label: "trigger", data: result.triggers },
3513
+ { label: "router", data: result.routers },
3514
+ { label: "eval suite", data: result.evalSuites }
3515
+ ];
3516
+ let hasChanges = false;
3517
+ for (const { label, data } of types) {
3518
+ if (!data)
3519
+ continue;
3520
+ for (const name of data.created) {
3521
+ console.log(chalk6.green(` + Created ${label}: ${name}`));
3522
+ hasChanges = true;
3523
+ }
3524
+ for (const name of data.updated) {
3525
+ console.log(chalk6.blue(` ~ Updated ${label}: ${name}`));
3526
+ hasChanges = true;
3527
+ }
3528
+ for (const name of data.deleted) {
3529
+ console.log(chalk6.red(` - Deleted ${label}: ${name}`));
3530
+ hasChanges = true;
3531
+ }
3532
+ }
3533
+ if (!hasChanges) {
3534
+ console.log(chalk6.gray(" No changes detected"));
3535
+ }
3536
+ if (result.evalSuites?.skipped && result.evalSuites.skipped.length > 0) {
3537
+ console.log(chalk6.yellow(` Skipped eval suites (agent not found): ${result.evalSuites.skipped.join(", ")}`));
3538
+ }
3539
+ console.log();
3540
+ }
3482
3541
  if (jsonMode) {
3483
3542
  console.log(JSON.stringify({
3484
3543
  success: true,
@@ -5334,17 +5393,17 @@ function flattenEntityForTable(entity) {
5334
5393
  var entitiesCommand = new Command13("data").description("Manage data records");
5335
5394
  entitiesCommand.command("types").description("List available data types").option("--env <environment>", "Environment (development|production)", "development").option("--json", "Output raw JSON").action(async (opts) => {
5336
5395
  await ensureAuth();
5337
- const spinner = ora11();
5396
+ const spinner = opts.json ? null : ora11();
5338
5397
  const env = opts.env;
5339
5398
  const orgId = getOrgId();
5340
- spinner.start("Fetching data types");
5399
+ spinner?.start("Fetching data types");
5341
5400
  const { data, error } = await queryEntityTypes(env, orgId);
5342
5401
  if (error || !data) {
5343
- spinner.fail("Failed to fetch data types");
5402
+ spinner?.fail("Failed to fetch data types");
5344
5403
  console.log(chalk15.red("Error:"), error);
5345
5404
  process.exit(1);
5346
5405
  }
5347
- spinner.succeed("Data types loaded");
5406
+ spinner?.succeed("Data types loaded");
5348
5407
  const types = data;
5349
5408
  if (opts.json) {
5350
5409
  console.log(JSON.stringify(types, null, 2));
@@ -5369,10 +5428,10 @@ entitiesCommand.command("types").description("List available data types").option
5369
5428
  });
5370
5429
  entitiesCommand.command("list <type>").description("List records of a type").option("--env <environment>", "Environment (development|production)", "development").option("--status <status>", "Filter by status").option("--limit <n>", "Maximum results", "50").option("--json", "Output raw JSON").action(async (type, opts) => {
5371
5430
  await ensureAuth();
5372
- const spinner = ora11();
5431
+ const spinner = opts.json ? null : ora11();
5373
5432
  const env = opts.env;
5374
5433
  const orgId = getOrgId();
5375
- spinner.start(`Fetching ${type} records`);
5434
+ spinner?.start(`Fetching ${type} records`);
5376
5435
  const [entitiesResult, typeResult] = await Promise.all([
5377
5436
  queryEntities(type, env, {
5378
5437
  status: opts.status,
@@ -5381,12 +5440,12 @@ entitiesCommand.command("list <type>").description("List records of a type").opt
5381
5440
  queryEntityTypeBySlug(type, env, orgId)
5382
5441
  ]);
5383
5442
  if (entitiesResult.error || !entitiesResult.data) {
5384
- spinner.fail(`Failed to fetch ${type} records`);
5443
+ spinner?.fail(`Failed to fetch ${type} records`);
5385
5444
  console.log(chalk15.red("Error:"), entitiesResult.error);
5386
5445
  process.exit(1);
5387
5446
  }
5388
5447
  const entities = entitiesResult.data;
5389
- spinner.succeed(`Found ${entities.length} ${type} records`);
5448
+ spinner?.succeed(`Found ${entities.length} ${type} records`);
5390
5449
  if (opts.json) {
5391
5450
  console.log(JSON.stringify(entities, null, 2));
5392
5451
  return;
@@ -5401,25 +5460,26 @@ entitiesCommand.command("list <type>").description("List records of a type").opt
5401
5460
  });
5402
5461
  entitiesCommand.command("get <id>").description("Get record details").option("--env <environment>", "Environment (development|production)", "development").option("--json", "Output raw JSON").action(async (rawId, opts) => {
5403
5462
  await ensureAuth();
5404
- const spinner = ora11();
5463
+ const spinner = opts.json ? null : ora11();
5405
5464
  const env = opts.env;
5406
5465
  const orgId = getOrgId();
5407
- spinner.start("Resolving record ID");
5466
+ spinner?.start("Resolving record ID");
5408
5467
  const resolved = await resolveEntityId(rawId, env, orgId);
5409
5468
  if (resolved.error || !resolved.data) {
5410
- spinner.fail("Record not found");
5469
+ spinner?.fail("Record not found");
5411
5470
  console.log(chalk15.red("Error:"), resolved.error || `No record matched "${rawId}"`);
5412
5471
  process.exit(1);
5413
5472
  }
5414
5473
  const id = resolved.data;
5415
- spinner.text = "Fetching record";
5474
+ if (spinner)
5475
+ spinner.text = "Fetching record";
5416
5476
  const { data, error } = await queryEntity(id, env, orgId);
5417
5477
  if (error || !data) {
5418
- spinner.fail("Failed to fetch record");
5478
+ spinner?.fail("Failed to fetch record");
5419
5479
  console.log(chalk15.red("Error:"), error || "Record not found");
5420
5480
  process.exit(1);
5421
5481
  }
5422
- spinner.succeed("Record loaded");
5482
+ spinner?.succeed("Record loaded");
5423
5483
  const result = data;
5424
5484
  if (opts.json) {
5425
5485
  console.log(JSON.stringify(result, null, 2));
@@ -5449,7 +5509,7 @@ entitiesCommand.command("get <id>").description("Get record details").option("--
5449
5509
  });
5450
5510
  entitiesCommand.command("create <type>").description("Create a new record").option("--env <environment>", "Environment (development|production)", "development").option("--data <json>", "Record data as JSON").option("--status <status>", "Initial status").option("--json", "Output raw JSON").action(async (type, opts) => {
5451
5511
  await ensureAuth();
5452
- const spinner = ora11();
5512
+ const spinner = opts.json ? null : ora11();
5453
5513
  const env = opts.env;
5454
5514
  const orgId = getOrgId();
5455
5515
  let data;
@@ -5464,14 +5524,14 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5464
5524
  console.log(chalk15.red("--data <json> is required in non-interactive mode"));
5465
5525
  process.exit(1);
5466
5526
  } else {
5467
- spinner.start(`Fetching ${type} schema`);
5527
+ spinner?.start(`Fetching ${type} schema`);
5468
5528
  const { data: typeData, error: error2 } = await queryEntityTypeBySlug(type, env, orgId);
5469
5529
  if (error2 || !typeData) {
5470
- spinner.fail(`Data type not found: ${type}`);
5530
+ spinner?.fail(`Data type not found: ${type}`);
5471
5531
  console.log(chalk15.red("Error:"), error2 || "Not found");
5472
5532
  process.exit(1);
5473
5533
  }
5474
- spinner.succeed(`Schema loaded for ${type}`);
5534
+ spinner?.succeed(`Schema loaded for ${type}`);
5475
5535
  console.log();
5476
5536
  const entityType = typeData;
5477
5537
  const schema = entityType.schema;
@@ -5505,14 +5565,14 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5505
5565
  }
5506
5566
  console.log();
5507
5567
  }
5508
- spinner.start(`Creating ${type} record`);
5568
+ spinner?.start(`Creating ${type} record`);
5509
5569
  const { data: result, error } = await createEntity(type, data, env, opts.status, orgId);
5510
5570
  if (error) {
5511
- spinner.fail("Failed to create record");
5571
+ spinner?.fail("Failed to create record");
5512
5572
  console.log(chalk15.red("Error:"), error);
5513
5573
  process.exit(1);
5514
5574
  }
5515
- spinner.succeed(`Record created`);
5575
+ spinner?.succeed(`Record created`);
5516
5576
  if (opts.json) {
5517
5577
  console.log(JSON.stringify({ id: result }, null, 2));
5518
5578
  } else {
@@ -5523,7 +5583,7 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5523
5583
  });
5524
5584
  entitiesCommand.command("update <id>").description("Update a record").option("--env <environment>", "Environment (development|production)", "development").option("--data <json>", "Update data as JSON").option("--status <status>", "New status").option("--json", "Output raw JSON").action(async (rawId, opts) => {
5525
5585
  await ensureAuth();
5526
- const spinner = ora11();
5586
+ const spinner = opts.json ? null : ora11();
5527
5587
  const env = opts.env;
5528
5588
  const orgId = getOrgId();
5529
5589
  if (!opts.data && !opts.status) {
@@ -5539,22 +5599,23 @@ entitiesCommand.command("update <id>").description("Update a record").option("--
5539
5599
  process.exit(1);
5540
5600
  }
5541
5601
  }
5542
- spinner.start("Resolving record ID");
5602
+ spinner?.start("Resolving record ID");
5543
5603
  const resolved = await resolveEntityId(rawId, env, orgId);
5544
5604
  if (resolved.error || !resolved.data) {
5545
- spinner.fail("Record not found");
5605
+ spinner?.fail("Record not found");
5546
5606
  console.log(chalk15.red("Error:"), resolved.error || `No record matched "${rawId}"`);
5547
5607
  process.exit(1);
5548
5608
  }
5549
5609
  const id = resolved.data;
5550
- spinner.text = "Updating record";
5610
+ if (spinner)
5611
+ spinner.text = "Updating record";
5551
5612
  const { data: result, error } = await updateEntity(id, data, env, opts.status, orgId);
5552
5613
  if (error) {
5553
- spinner.fail("Failed to update record");
5614
+ spinner?.fail("Failed to update record");
5554
5615
  console.log(chalk15.red("Error:"), error);
5555
5616
  process.exit(1);
5556
5617
  }
5557
- spinner.succeed("Record updated");
5618
+ spinner?.succeed("Record updated");
5558
5619
  if (opts.json) {
5559
5620
  console.log(JSON.stringify(result, null, 2));
5560
5621
  } else {
@@ -5642,21 +5703,21 @@ entitiesCommand.command("delete <id>").description("Delete a record").option("--
5642
5703
  });
5643
5704
  entitiesCommand.command("search <type> <query>").description("Search records").option("--env <environment>", "Environment (development|production)", "development").option("--limit <n>", "Maximum results", "25").option("--json", "Output raw JSON").action(async (type, query, opts) => {
5644
5705
  await ensureAuth();
5645
- const spinner = ora11();
5706
+ const spinner = opts.json ? null : ora11();
5646
5707
  const env = opts.env;
5647
5708
  const orgId = getOrgId();
5648
- spinner.start(`Searching ${type} for "${query}"`);
5709
+ spinner?.start(`Searching ${type} for "${query}"`);
5649
5710
  const [searchResult, typeResult] = await Promise.all([
5650
5711
  searchEntities(type, query, env, parseInt(opts.limit, 10), orgId),
5651
5712
  queryEntityTypeBySlug(type, env, orgId)
5652
5713
  ]);
5653
5714
  if (searchResult.error || !searchResult.data) {
5654
- spinner.fail("Search failed");
5715
+ spinner?.fail("Search failed");
5655
5716
  console.log(chalk15.red("Error:"), searchResult.error);
5656
5717
  process.exit(1);
5657
5718
  }
5658
5719
  const entities = searchResult.data;
5659
- spinner.succeed(`Found ${entities.length} results`);
5720
+ spinner?.succeed(`Found ${entities.length} results`);
5660
5721
  if (opts.json) {
5661
5722
  console.log(JSON.stringify(entities, null, 2));
5662
5723
  return;
@@ -5835,21 +5896,21 @@ function formatTimestamp(ts) {
5835
5896
  var logsCommand = new Command14("logs").description("View and debug agent conversations");
5836
5897
  logsCommand.command("list", { isDefault: true }).description("List recent conversations").option("--env <environment>", "Environment (development|production|eval)", "development").option("--agent <slug>", "Filter by agent slug").option("--channel <channel>", "Filter by channel (api|whatsapp|widget|dashboard)").option("--phone <number>", "Filter by phone number").option("--limit <n>", "Maximum results", "20").option("--json", "Output raw JSON").action(async (opts) => {
5837
5898
  await ensureAuth2();
5838
- const spinner = ora12();
5899
+ const spinner = opts.json ? null : ora12();
5839
5900
  const orgId = getOrgId2();
5840
5901
  let agentId;
5841
5902
  if (opts.agent) {
5842
- spinner.start(`Resolving agent "${opts.agent}"`);
5903
+ spinner?.start(`Resolving agent "${opts.agent}"`);
5843
5904
  const resolved = await resolveAgentSlug(opts.agent, orgId);
5844
5905
  if (resolved.error || !resolved.data) {
5845
- spinner.fail("Agent not found");
5906
+ spinner?.fail("Agent not found");
5846
5907
  console.log(chalk16.red("Error:"), resolved.error || `No agent matched "${opts.agent}"`);
5847
5908
  process.exit(1);
5848
5909
  }
5849
5910
  agentId = resolved.data;
5850
- spinner.succeed(`Agent resolved: ${opts.agent}`);
5911
+ spinner?.succeed(`Agent resolved: ${opts.agent}`);
5851
5912
  }
5852
- spinner.start("Fetching conversations");
5913
+ spinner?.start("Fetching conversations");
5853
5914
  const { data, error } = await queryThreads({
5854
5915
  environment: opts.env,
5855
5916
  agentId,
@@ -5858,17 +5919,16 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
5858
5919
  limit: parseInt(opts.limit, 10)
5859
5920
  });
5860
5921
  if (error || !data) {
5861
- spinner.fail("Failed to fetch conversations");
5922
+ spinner?.fail("Failed to fetch conversations");
5862
5923
  console.log(chalk16.red("Error:"), error);
5863
5924
  process.exit(1);
5864
5925
  }
5865
5926
  const threads = data;
5866
5927
  if (opts.json) {
5867
- spinner.stop();
5868
5928
  console.log(JSON.stringify(threads, null, 2));
5869
5929
  return;
5870
5930
  }
5871
- spinner.succeed(`Found ${threads.length} conversations`);
5931
+ spinner?.succeed(`Found ${threads.length} conversations`);
5872
5932
  console.log();
5873
5933
  renderTable([
5874
5934
  { key: "id", label: "ID", width: 14 },
@@ -5892,25 +5952,25 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
5892
5952
  });
5893
5953
  logsCommand.command("view <thread-id>").description("View conversation messages").option("--env <environment>", "Environment hint for resolving short IDs").option("--exec", "Include execution details").option("--verbose", "Show full tool call arguments and results").option("--tail", "Show most recent messages (use with --limit)").option("--json", "Output raw JSON").option("--limit <n>", "Message limit", "100").action(async (rawThreadId, opts) => {
5894
5954
  await ensureAuth2();
5895
- const spinner = ora12();
5896
- spinner.start("Resolving thread");
5955
+ const spinner = opts.json ? null : ora12();
5956
+ spinner?.start("Resolving thread");
5897
5957
  const resolved = await resolveThreadId(rawThreadId, opts.env);
5898
5958
  if (resolved.error || !resolved.data) {
5899
- spinner.fail("Thread not found");
5959
+ spinner?.fail("Thread not found");
5900
5960
  console.log(chalk16.red("Error:"), resolved.error || `No thread matched "${rawThreadId}"`);
5901
5961
  process.exit(1);
5902
5962
  }
5903
5963
  const threadId = resolved.data;
5904
- spinner.stop();
5905
- spinner.start("Fetching conversation");
5964
+ spinner?.stop();
5965
+ spinner?.start("Fetching conversation");
5906
5966
  const fetchLimit = opts.tail ? 1000 : parseInt(opts.limit, 10);
5907
5967
  const { data, error } = await queryThreadDetail(threadId, fetchLimit);
5908
5968
  if (error || !data) {
5909
- spinner.fail("Failed to fetch conversation");
5969
+ spinner?.fail("Failed to fetch conversation");
5910
5970
  console.log(chalk16.red("Error:"), error || "Thread not found");
5911
5971
  process.exit(1);
5912
5972
  }
5913
- spinner.stop();
5973
+ spinner?.stop();
5914
5974
  const result = data;
5915
5975
  let executions = [];
5916
5976
  if (opts.exec) {
@@ -6791,7 +6851,7 @@ var templatesCommand = new Command16("templates").description("Manage WhatsApp m
6791
6851
  templatesCommand.command("list").description("List all message templates").option("--env <environment>", "Environment to find connection (development|production|eval)").option("--json", "Output raw JSON").action(async (opts) => {
6792
6852
  await ensureAuth3();
6793
6853
  const connectionId = await resolveConnectionId(opts.env ?? "production");
6794
- const out = createOutput();
6854
+ const out = opts.json ? createSilentOutput() : createOutput();
6795
6855
  out.start("Fetching templates");
6796
6856
  const { data, error } = await listTemplates(connectionId);
6797
6857
  if (error) {
@@ -6853,22 +6913,22 @@ templatesCommand.command("create <name>").description("Create a new message temp
6853
6913
  console.log(chalk18.red("Components must be a JSON array"));
6854
6914
  process.exit(1);
6855
6915
  }
6856
- const out = createOutput();
6916
+ const out = opts.json ? createSilentOutput() : createOutput();
6857
6917
  out.start(`Creating template "${name}"`);
6858
6918
  const { data, error } = await createTemplate(connectionId, name, opts.language, opts.category.toUpperCase(), components, opts.allowCategoryChange);
6859
- if (error) {
6919
+ const result = data;
6920
+ if (error || result?.error) {
6860
6921
  out.fail("Failed to create template");
6861
- out.error(error);
6922
+ out.error(error ?? result?.error ?? "Unknown error");
6862
6923
  process.exit(1);
6863
6924
  }
6864
- const result = data;
6865
6925
  out.succeed(`Template "${name}" created`);
6866
6926
  if (opts.json) {
6867
6927
  console.log(JSON.stringify(result, null, 2));
6868
6928
  } else {
6869
6929
  console.log();
6870
6930
  console.log(` ${chalk18.gray("ID:")} ${result.id}`);
6871
- console.log(` ${chalk18.gray("Status:")} ${statusColor2(result.status)}`);
6931
+ console.log(` ${chalk18.gray("Status:")} ${statusColor2(result.status ?? "")}`);
6872
6932
  console.log(` ${chalk18.gray("Category:")} ${result.category}`);
6873
6933
  console.log();
6874
6934
  }
@@ -6900,7 +6960,7 @@ templatesCommand.command("delete <name>").description("Delete a message template
6900
6960
  templatesCommand.command("status <name>").description("Check template approval status").option("--env <environment>", "Environment to find connection").option("--connection <id>", "WhatsApp connection ID").option("--json", "Output raw JSON").action(async (name, opts) => {
6901
6961
  await ensureAuth3();
6902
6962
  const connectionId = await resolveConnectionId(opts.env ?? "production", opts.connection);
6903
- const out = createOutput();
6963
+ const out = opts.json ? createSilentOutput() : createOutput();
6904
6964
  out.start(`Checking status for "${name}"`);
6905
6965
  const { data, error } = await getTemplateStatus(connectionId, name);
6906
6966
  if (error) {
@@ -6938,7 +6998,7 @@ templatesCommand.command("status <name>").description("Check template approval s
6938
6998
  templatesCommand.command("edit <name>").description("Edit a message template").option("--env <environment>", "Environment to find connection").option("--connection <id>", "WhatsApp connection ID").option("--components <json>", "New components as JSON string").option("--file <path>", "Read components from a JSON file").option("--category <cat>", "New category (UTILITY|MARKETING|AUTHENTICATION)").option("--language <code>", "Language code").option("--json", "Output raw JSON").action(async (name, opts) => {
6939
6999
  await ensureAuth3();
6940
7000
  const connectionId = await resolveConnectionId(opts.env ?? "production", opts.connection);
6941
- const out = createOutput();
7001
+ const out = opts.json ? createSilentOutput() : createOutput();
6942
7002
  out.start(`Fetching template "${name}"`);
6943
7003
  const { data: statusData, error: statusError } = await getTemplateStatus(connectionId, name);
6944
7004
  if (statusError) {
@@ -7137,9 +7197,15 @@ async function listIntegrationConfigs(env) {
7137
7197
  async function setIntegrationStatus(provider, env, status) {
7138
7198
  return convexMutation4("integrations:setConfigStatus", { provider, environment: env, status });
7139
7199
  }
7200
+ async function createVoiceConnection(args) {
7201
+ return convexMutation4("voiceConnections:createConnection", args);
7202
+ }
7203
+ async function setVoicePhoneAgent(args) {
7204
+ return convexMutation4("voiceConnections:setPhoneAgentBySlug", args);
7205
+ }
7140
7206
 
7141
7207
  // src/cli/commands/integration.ts
7142
- var VALID_PROVIDERS = ["airtable", "resend", "flow"];
7208
+ var VALID_PROVIDERS = ["airtable", "resend", "flow", "twilio"];
7143
7209
  async function ensureAuth4() {
7144
7210
  const cwd = process.cwd();
7145
7211
  const nonInteractive = !isInteractive();
@@ -7194,6 +7260,8 @@ function getProviderHelp(provider) {
7194
7260
  return `Usage: struere integration resend --from-email <email> [--from-name <name>] [--reply-to <email>]`;
7195
7261
  case "flow":
7196
7262
  return `Usage: struere integration flow --api-url <url> --api-key <key> --secret-key <secret> [--return-url <url>]`;
7263
+ case "twilio":
7264
+ return `Usage: struere integration twilio --account-sid <sid> --auth-token <token> --phone-number <number> [--router <slug>] [--agent <slug>]`;
7197
7265
  default:
7198
7266
  return "";
7199
7267
  }
@@ -7235,12 +7303,22 @@ function buildConfigFromOpts(provider, opts) {
7235
7303
  return null;
7236
7304
  return config;
7237
7305
  }
7306
+ if (provider === "twilio") {
7307
+ const config = {};
7308
+ if (opts.accountSid)
7309
+ config.accountSid = opts.accountSid;
7310
+ if (opts.authToken)
7311
+ config.authToken = opts.authToken;
7312
+ if (Object.keys(config).length === 0)
7313
+ return null;
7314
+ return config;
7315
+ }
7238
7316
  return null;
7239
7317
  }
7240
- var integrationCommand = new Command17("integration").description("Manage integrations").argument("[provider]", "Integration provider (airtable, resend, flow)").option("--env <environment>", "Environment (development|production)", "development").option("--token <pat>", "Personal access token (airtable)").option("--base-id <id>", "Default base ID (airtable)").option("--from-email <email>", "From email address (resend)").option("--from-name <name>", "From display name (resend)").option("--reply-to <email>", "Reply-to address (resend)").option("--api-url <url>", "API URL (flow)").option("--api-key <key>", "API key (flow)").option("--secret-key <secret>", "Secret key (flow)").option("--return-url <url>", "Return URL after payment (flow)").option("--test", "Test the connection after saving").option("--remove", "Remove integration config").option("--enable", "Enable integration").option("--disable", "Disable integration").option("--status", "Show current config status").option("--yes", "Skip confirmation prompts").option("--json", "Output raw JSON").action(async (provider, opts) => {
7318
+ var integrationCommand = new Command17("integration").description("Manage integrations").argument("[provider]", "Integration provider (airtable, resend, flow)").option("--env <environment>", "Environment (development|production)", "development").option("--token <pat>", "Personal access token (airtable)").option("--base-id <id>", "Default base ID (airtable)").option("--from-email <email>", "From email address (resend)").option("--from-name <name>", "From display name (resend)").option("--reply-to <email>", "Reply-to address (resend)").option("--api-url <url>", "API URL (flow)").option("--api-key <key>", "API key (flow)").option("--secret-key <secret>", "Secret key (flow)").option("--return-url <url>", "Return URL after payment (flow)").option("--account-sid <sid>", "Twilio Account SID (twilio)").option("--auth-token <token>", "Twilio Auth Token (twilio)").option("--phone-number <number>", "Twilio phone number in E.164 format (twilio)").option("--router <slug>", "Router slug to assign to phone number (twilio)").option("--agent <slug>", "Agent slug to assign to phone number (twilio)").option("--test", "Test the connection after saving").option("--remove", "Remove integration config").option("--enable", "Enable integration").option("--disable", "Disable integration").option("--status", "Show current config status").option("--yes", "Skip confirmation prompts").option("--json", "Output raw JSON").action(async (provider, opts) => {
7241
7319
  await ensureAuth4();
7242
7320
  const env = opts.env;
7243
- const out = createOutput();
7321
+ const out = opts.json ? createSilentOutput() : createOutput();
7244
7322
  if (!provider || provider === "list") {
7245
7323
  out.start("Fetching integrations");
7246
7324
  const { data, error } = await listIntegrationConfigs(env);
@@ -7336,6 +7414,39 @@ var integrationCommand = new Command17("integration").description("Manage integr
7336
7414
  process.exit(1);
7337
7415
  }
7338
7416
  out.succeed(`${provider} config saved`);
7417
+ if (provider === "twilio" && opts.phoneNumber) {
7418
+ const phoneNumber = opts.phoneNumber.startsWith("+") ? opts.phoneNumber : `+${opts.phoneNumber}`;
7419
+ out.start(`Adding voice connection for ${phoneNumber}`);
7420
+ const { data: connData, error: connError } = await createVoiceConnection({
7421
+ twilioAccountSid: config.accountSid,
7422
+ twilioPhoneNumber: phoneNumber,
7423
+ label: `Twilio ${phoneNumber}`,
7424
+ environment: env
7425
+ });
7426
+ if (connError) {
7427
+ out.fail(`Failed to create voice connection`);
7428
+ out.error(connError);
7429
+ } else {
7430
+ out.succeed(`Voice connection created for ${phoneNumber}`);
7431
+ const conn = connData;
7432
+ if (connData && (opts.router || opts.agent)) {
7433
+ out.start(`Assigning ${opts.router ? "router" : "agent"}`);
7434
+ const connectionId = connData;
7435
+ const assignArgs = { connectionId };
7436
+ if (opts.router)
7437
+ assignArgs.routerSlug = opts.router;
7438
+ if (opts.agent)
7439
+ assignArgs.agentSlug = opts.agent;
7440
+ const { error: assignError } = await setVoicePhoneAgent(assignArgs);
7441
+ if (assignError) {
7442
+ out.fail(`Failed to assign`);
7443
+ out.error(assignError);
7444
+ } else {
7445
+ out.succeed(`Assigned to ${opts.router ? `router: ${opts.router}` : `agent: ${opts.agent}`}`);
7446
+ }
7447
+ }
7448
+ }
7449
+ }
7339
7450
  if (opts.test) {
7340
7451
  out.start(`Testing ${provider} connection`);
7341
7452
  const { data: testData, error: testError } = await testIntegrationConnection(provider, env);
@@ -7723,9 +7834,9 @@ function renderExecutionLog(executionLog, verbose) {
7723
7834
  var triggersCommand = new Command18("triggers").description("Manage triggers and automation runs");
7724
7835
  triggersCommand.command("list", { isDefault: true }).description("List all triggers").option("--env <environment>", "Environment (development|production|eval)", "development").option("--json", "Output raw JSON").option("--failed", "Show only triggers with recent failures").action(async (opts) => {
7725
7836
  await ensureAuth5();
7726
- const spinner = ora14();
7837
+ const spinner = opts.json ? null : ora14();
7727
7838
  try {
7728
- spinner.start("Fetching triggers");
7839
+ spinner?.start("Fetching triggers");
7729
7840
  const [triggers, statuses] = await Promise.all([
7730
7841
  listTriggers(opts.env),
7731
7842
  getLastRunStatuses(opts.env)
@@ -7734,7 +7845,7 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7734
7845
  if (opts.failed) {
7735
7846
  filtered = triggers.filter((t) => statuses[t.slug]?.status === "failed");
7736
7847
  }
7737
- spinner.succeed(`Found ${filtered.length} triggers${opts.failed ? " (failed only)" : ""}`);
7848
+ spinner?.succeed(`Found ${filtered.length} triggers${opts.failed ? " (failed only)" : ""}`);
7738
7849
  if (opts.json) {
7739
7850
  console.log(JSON.stringify(filtered, null, 2));
7740
7851
  return;
@@ -7760,7 +7871,7 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7760
7871
  console.log();
7761
7872
  } catch (err) {
7762
7873
  const message = err instanceof Error ? err.message : String(err);
7763
- spinner.fail("Failed to fetch triggers");
7874
+ spinner?.fail("Failed to fetch triggers");
7764
7875
  if (opts.json) {
7765
7876
  console.log(JSON.stringify({ success: false, error: message }));
7766
7877
  } else {
@@ -7771,18 +7882,18 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7771
7882
  });
7772
7883
  triggersCommand.command("get <slug>").description("View trigger details").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (slug, opts) => {
7773
7884
  await ensureAuth5();
7774
- const spinner = ora14();
7885
+ const spinner = opts.json ? null : ora14();
7775
7886
  try {
7776
- spinner.start("Fetching trigger");
7887
+ spinner?.start("Fetching trigger");
7777
7888
  const trigger = await getTrigger(slug, opts.env);
7778
7889
  if (!trigger) {
7779
- spinner.fail("Trigger not found");
7890
+ spinner?.fail("Trigger not found");
7780
7891
  if (opts.json) {
7781
7892
  console.log(JSON.stringify({ success: false, error: "Trigger not found" }));
7782
7893
  }
7783
7894
  process.exit(1);
7784
7895
  }
7785
- spinner.succeed("Trigger loaded");
7896
+ spinner?.succeed("Trigger loaded");
7786
7897
  if (opts.json) {
7787
7898
  console.log(JSON.stringify(trigger, null, 2));
7788
7899
  return;
@@ -7839,7 +7950,7 @@ triggersCommand.command("get <slug>").description("View trigger details").option
7839
7950
  console.log();
7840
7951
  } catch (err) {
7841
7952
  const message = err instanceof Error ? err.message : String(err);
7842
- spinner.fail("Failed to fetch trigger");
7953
+ spinner?.fail("Failed to fetch trigger");
7843
7954
  if (opts.json) {
7844
7955
  console.log(JSON.stringify({ success: false, error: message }));
7845
7956
  } else {
@@ -7850,16 +7961,16 @@ triggersCommand.command("get <slug>").description("View trigger details").option
7850
7961
  });
7851
7962
  triggersCommand.command("runs [slug]").description("List trigger runs").option("--env <environment>", "Environment", "development").option("--status <status>", "Filter by status (pending|running|completed|failed|dead)").option("--limit <n>", "Maximum results", "20").option("--json", "Output raw JSON").action(async (slug, opts) => {
7852
7963
  await ensureAuth5();
7853
- const spinner = ora14();
7964
+ const spinner = opts.json ? null : ora14();
7854
7965
  try {
7855
- spinner.start("Fetching runs");
7966
+ spinner?.start("Fetching runs");
7856
7967
  const runs = await listTriggerRuns({
7857
7968
  environment: opts.env,
7858
7969
  status: opts.status,
7859
7970
  triggerSlug: slug,
7860
7971
  limit: parseInt(opts.limit, 10)
7861
7972
  });
7862
- spinner.succeed(`Found ${runs.length} runs`);
7973
+ spinner?.succeed(`Found ${runs.length} runs`);
7863
7974
  if (opts.json) {
7864
7975
  console.log(JSON.stringify(runs, null, 2));
7865
7976
  return;
@@ -7885,7 +7996,7 @@ triggersCommand.command("runs [slug]").description("List trigger runs").option("
7885
7996
  console.log();
7886
7997
  } catch (err) {
7887
7998
  const message = err instanceof Error ? err.message : String(err);
7888
- spinner.fail("Failed to fetch runs");
7999
+ spinner?.fail("Failed to fetch runs");
7889
8000
  if (opts.json) {
7890
8001
  console.log(JSON.stringify({ success: false, error: message }));
7891
8002
  } else {
@@ -7896,18 +8007,18 @@ triggersCommand.command("runs [slug]").description("List trigger runs").option("
7896
8007
  });
7897
8008
  triggersCommand.command("run <run-id>").description("View trigger run details").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").option("-v, --verbose", "Show detailed agent tool calls").action(async (runId, opts) => {
7898
8009
  await ensureAuth5();
7899
- const spinner = ora14();
8010
+ const spinner = opts.json ? null : ora14();
7900
8011
  try {
7901
- spinner.start("Fetching run");
8012
+ spinner?.start("Fetching run");
7902
8013
  const run = await getTriggerRunDetail(runId, opts.env);
7903
8014
  if (!run) {
7904
- spinner.fail("Run not found");
8015
+ spinner?.fail("Run not found");
7905
8016
  if (opts.json) {
7906
8017
  console.log(JSON.stringify({ success: false, error: "Run not found" }));
7907
8018
  }
7908
8019
  process.exit(1);
7909
8020
  }
7910
- spinner.succeed("Run loaded");
8021
+ spinner?.succeed("Run loaded");
7911
8022
  if (opts.json) {
7912
8023
  console.log(JSON.stringify(run, null, 2));
7913
8024
  return;
@@ -7930,7 +8041,7 @@ triggersCommand.command("run <run-id>").description("View trigger run details").
7930
8041
  console.log();
7931
8042
  } catch (err) {
7932
8043
  const message = err instanceof Error ? err.message : String(err);
7933
- spinner.fail("Failed to fetch run");
8044
+ spinner?.fail("Failed to fetch run");
7934
8045
  if (opts.json) {
7935
8046
  console.log(JSON.stringify({ success: false, error: message }));
7936
8047
  } else {
@@ -7941,11 +8052,11 @@ triggersCommand.command("run <run-id>").description("View trigger run details").
7941
8052
  });
7942
8053
  triggersCommand.command("stats").description("Show trigger run statistics").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (opts) => {
7943
8054
  await ensureAuth5();
7944
- const spinner = ora14();
8055
+ const spinner = opts.json ? null : ora14();
7945
8056
  try {
7946
- spinner.start("Fetching statistics");
8057
+ spinner?.start("Fetching statistics");
7947
8058
  const stats = await getTriggerRunStats(opts.env);
7948
- spinner.succeed("Run statistics");
8059
+ spinner?.succeed("Run statistics");
7949
8060
  if (opts.json) {
7950
8061
  console.log(JSON.stringify(stats, null, 2));
7951
8062
  return;
@@ -7984,7 +8095,7 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
7984
8095
  console.log();
7985
8096
  } catch (err) {
7986
8097
  const message = err instanceof Error ? err.message : String(err);
7987
- spinner.fail("Failed to fetch statistics");
8098
+ spinner?.fail("Failed to fetch statistics");
7988
8099
  if (opts.json) {
7989
8100
  console.log(JSON.stringify({ success: false, error: message }));
7990
8101
  } else {
@@ -7995,15 +8106,15 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
7995
8106
  });
7996
8107
  triggersCommand.command("logs [slug]").description("View trigger execution history").option("--env <environment>", "Environment", "development").option("--limit <n>", "Maximum results", "10").option("--json", "Output raw JSON").option("-v, --verbose", "Show full error messages").action(async (slug, opts) => {
7997
8108
  await ensureAuth5();
7998
- const spinner = ora14();
8109
+ const spinner = opts.json ? null : ora14();
7999
8110
  try {
8000
- spinner.start("Fetching execution logs");
8111
+ spinner?.start("Fetching execution logs");
8001
8112
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8002
8113
  environment: opts.env,
8003
8114
  triggerSlug: slug,
8004
8115
  limit: parseInt(opts.limit, 10)
8005
8116
  }));
8006
- spinner.succeed(`Found ${executions.length} executions`);
8117
+ spinner?.succeed(`Found ${executions.length} executions`);
8007
8118
  if (opts.json) {
8008
8119
  console.log(JSON.stringify(executions, null, 2));
8009
8120
  return;
@@ -8048,7 +8159,7 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
8048
8159
  console.log();
8049
8160
  } catch (err) {
8050
8161
  const message = err instanceof Error ? err.message : String(err);
8051
- spinner.fail("Failed to fetch execution logs");
8162
+ spinner?.fail("Failed to fetch execution logs");
8052
8163
  if (opts.json) {
8053
8164
  console.log(JSON.stringify({ success: false, error: message }));
8054
8165
  } else {
@@ -8059,7 +8170,7 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
8059
8170
  });
8060
8171
  triggersCommand.command("log <identifier>").description("View detailed trigger execution log (by event ID or trigger slug)").option("--env <environment>", "Environment", "development").option("--nth <n>", "Show nth most recent execution (when using slug)", "1").option("--json", "Output raw JSON").option("-v, --verbose", "Show detailed agent tool calls").action(async (identifier, opts) => {
8061
8172
  await ensureAuth5();
8062
- const spinner = ora14();
8173
+ const spinner = opts.json ? null : ora14();
8063
8174
  try {
8064
8175
  const nth = parseInt(opts.nth, 10);
8065
8176
  if (isNaN(nth) || nth < 1) {
@@ -8069,14 +8180,14 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8069
8180
  let eventId = identifier;
8070
8181
  const isConvexId = /^[0-9a-zA-Z]{20,}$/.test(identifier);
8071
8182
  if (!isConvexId) {
8072
- spinner.start("Resolving trigger slug to latest execution");
8183
+ spinner?.start("Resolving trigger slug to latest execution");
8073
8184
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8074
8185
  environment: opts.env,
8075
8186
  triggerSlug: identifier,
8076
8187
  limit: nth
8077
8188
  }));
8078
8189
  if (!executions.length) {
8079
- spinner.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
8190
+ spinner?.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
8080
8191
  if (opts.json) {
8081
8192
  console.log(JSON.stringify({ success: false, error: `No executions found for trigger "${identifier}"` }));
8082
8193
  }
@@ -8084,22 +8195,22 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8084
8195
  }
8085
8196
  const idx = nth - 1;
8086
8197
  if (idx >= executions.length) {
8087
- spinner.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
8198
+ spinner?.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
8088
8199
  process.exit(1);
8089
8200
  }
8090
8201
  eventId = executions[idx]._id;
8091
- spinner.succeed(`Found execution for "${identifier}"`);
8202
+ spinner?.succeed(`Found execution for "${identifier}"`);
8092
8203
  }
8093
- spinner.start("Fetching execution detail");
8204
+ spinner?.start("Fetching execution detail");
8094
8205
  const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env));
8095
8206
  if (!event) {
8096
- spinner.fail("Execution not found");
8207
+ spinner?.fail("Execution not found");
8097
8208
  if (opts.json) {
8098
8209
  console.log(JSON.stringify({ success: false, error: "Execution not found" }));
8099
8210
  }
8100
8211
  process.exit(1);
8101
8212
  }
8102
- spinner.succeed("Execution loaded");
8213
+ spinner?.succeed("Execution loaded");
8103
8214
  if (opts.json) {
8104
8215
  console.log(JSON.stringify(event, null, 2));
8105
8216
  return;
@@ -8129,7 +8240,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8129
8240
  }
8130
8241
  } catch (err) {
8131
8242
  const message = err instanceof Error ? err.message : String(err);
8132
- spinner.fail("Failed to fetch execution detail");
8243
+ spinner?.fail("Failed to fetch execution detail");
8133
8244
  if (opts.json) {
8134
8245
  console.log(JSON.stringify({ success: false, error: message }));
8135
8246
  } else {
@@ -8140,7 +8251,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8140
8251
  });
8141
8252
  triggersCommand.command("retry <run-id>").description("Retry a failed or dead run").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (runId, opts) => {
8142
8253
  await ensureAuth5();
8143
- const spinner = ora14();
8254
+ const spinner = opts.json ? null : ora14();
8144
8255
  const environment = opts.env;
8145
8256
  if (environment === "production" && !opts.confirm && isInteractive()) {
8146
8257
  const readline = await import("readline");
@@ -8152,15 +8263,15 @@ triggersCommand.command("retry <run-id>").description("Retry a failed or dead ru
8152
8263
  rl.close();
8153
8264
  }
8154
8265
  try {
8155
- spinner.start("Retrying run...");
8266
+ spinner?.start("Retrying run...");
8156
8267
  await retryTriggerRun(runId, environment);
8157
- spinner.succeed(chalk20.green(`Run ${runId} queued for retry`));
8268
+ spinner?.succeed(chalk20.green(`Run ${runId} queued for retry`));
8158
8269
  if (opts.json) {
8159
8270
  console.log(JSON.stringify({ success: true, runId }));
8160
8271
  }
8161
8272
  } catch (err) {
8162
8273
  const message = err instanceof Error ? err.message : String(err);
8163
- spinner.fail("Failed to retry run");
8274
+ spinner?.fail("Failed to retry run");
8164
8275
  if (opts.json) {
8165
8276
  console.log(JSON.stringify({ success: false, error: message }));
8166
8277
  } else {
@@ -8171,7 +8282,7 @@ triggersCommand.command("retry <run-id>").description("Retry a failed or dead ru
8171
8282
  });
8172
8283
  triggersCommand.command("cancel <run-id>").description("Cancel a pending run").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (runId, opts) => {
8173
8284
  await ensureAuth5();
8174
- const spinner = ora14();
8285
+ const spinner = opts.json ? null : ora14();
8175
8286
  const environment = opts.env;
8176
8287
  if (environment === "production" && !opts.confirm && isInteractive()) {
8177
8288
  const readline = await import("readline");
@@ -8183,15 +8294,15 @@ triggersCommand.command("cancel <run-id>").description("Cancel a pending run").o
8183
8294
  rl.close();
8184
8295
  }
8185
8296
  try {
8186
- spinner.start("Cancelling run...");
8297
+ spinner?.start("Cancelling run...");
8187
8298
  await cancelTriggerRun(runId, environment);
8188
- spinner.succeed(chalk20.green(`Run ${runId} cancelled`));
8299
+ spinner?.succeed(chalk20.green(`Run ${runId} cancelled`));
8189
8300
  if (opts.json) {
8190
8301
  console.log(JSON.stringify({ success: true, runId }));
8191
8302
  }
8192
8303
  } catch (err) {
8193
8304
  const message = err instanceof Error ? err.message : String(err);
8194
- spinner.fail("Failed to cancel run");
8305
+ spinner?.fail("Failed to cancel run");
8195
8306
  if (opts.json) {
8196
8307
  console.log(JSON.stringify({ success: false, error: message }));
8197
8308
  } else {
@@ -8202,7 +8313,7 @@ triggersCommand.command("cancel <run-id>").description("Cancel a pending run").o
8202
8313
  });
8203
8314
  triggersCommand.command("enable <slug>").description("Enable a trigger").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (slug, opts) => {
8204
8315
  await ensureAuth5();
8205
- const spinner = ora14();
8316
+ const spinner = opts.json ? null : ora14();
8206
8317
  const environment = opts.env;
8207
8318
  if (environment === "production" && !opts.confirm && isInteractive()) {
8208
8319
  const readline = await import("readline");
@@ -8214,15 +8325,15 @@ triggersCommand.command("enable <slug>").description("Enable a trigger").option(
8214
8325
  rl.close();
8215
8326
  }
8216
8327
  try {
8217
- spinner.start("Enabling trigger...");
8328
+ spinner?.start("Enabling trigger...");
8218
8329
  await toggleTrigger(slug, true, environment);
8219
- spinner.succeed(chalk20.green(`Trigger ${slug} enabled`));
8330
+ spinner?.succeed(chalk20.green(`Trigger ${slug} enabled`));
8220
8331
  if (opts.json) {
8221
8332
  console.log(JSON.stringify({ success: true, slug }));
8222
8333
  }
8223
8334
  } catch (err) {
8224
8335
  const message = err instanceof Error ? err.message : String(err);
8225
- spinner.fail("Failed to enable trigger");
8336
+ spinner?.fail("Failed to enable trigger");
8226
8337
  if (opts.json) {
8227
8338
  console.log(JSON.stringify({ success: false, error: message }));
8228
8339
  } else {
@@ -8233,7 +8344,7 @@ triggersCommand.command("enable <slug>").description("Enable a trigger").option(
8233
8344
  });
8234
8345
  triggersCommand.command("disable <slug>").description("Disable a trigger").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (slug, opts) => {
8235
8346
  await ensureAuth5();
8236
- const spinner = ora14();
8347
+ const spinner = opts.json ? null : ora14();
8237
8348
  const environment = opts.env;
8238
8349
  if (environment === "production" && !opts.confirm && isInteractive()) {
8239
8350
  const readline = await import("readline");
@@ -8245,15 +8356,15 @@ triggersCommand.command("disable <slug>").description("Disable a trigger").optio
8245
8356
  rl.close();
8246
8357
  }
8247
8358
  try {
8248
- spinner.start("Disabling trigger...");
8359
+ spinner?.start("Disabling trigger...");
8249
8360
  await toggleTrigger(slug, false, environment);
8250
- spinner.succeed(chalk20.green(`Trigger ${slug} disabled`));
8361
+ spinner?.succeed(chalk20.green(`Trigger ${slug} disabled`));
8251
8362
  if (opts.json) {
8252
8363
  console.log(JSON.stringify({ success: true, slug }));
8253
8364
  }
8254
8365
  } catch (err) {
8255
8366
  const message = err instanceof Error ? err.message : String(err);
8256
- spinner.fail("Failed to disable trigger");
8367
+ spinner?.fail("Failed to disable trigger");
8257
8368
  if (opts.json) {
8258
8369
  console.log(JSON.stringify({ success: false, error: message }));
8259
8370
  } else {
@@ -8264,7 +8375,7 @@ triggersCommand.command("disable <slug>").description("Disable a trigger").optio
8264
8375
  });
8265
8376
  triggersCommand.command("retry-event <event-id>").description("Retry a failed immediate trigger execution").option("--env <environment>", "Environment (development|production|eval)", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (eventId, opts) => {
8266
8377
  await ensureAuth5();
8267
- const spinner = ora14();
8378
+ const spinner = opts.json ? null : ora14();
8268
8379
  const environment = opts.env;
8269
8380
  if (environment === "production" && !opts.confirm && isInteractive()) {
8270
8381
  const readline = await import("readline");
@@ -8276,19 +8387,19 @@ triggersCommand.command("retry-event <event-id>").description("Retry a failed im
8276
8387
  rl.close();
8277
8388
  }
8278
8389
  try {
8279
- spinner.start("Retrying failed execution...");
8390
+ spinner?.start("Retrying failed execution...");
8280
8391
  const result = await retryImmediateExecution(eventId, environment);
8281
8392
  if (result.success) {
8282
- spinner.succeed(chalk20.green(`Execution retried successfully`));
8393
+ spinner?.succeed(chalk20.green(`Execution retried successfully`));
8283
8394
  } else {
8284
- spinner.fail(chalk20.red(`Execution retry failed again`));
8395
+ spinner?.fail(chalk20.red(`Execution retry failed again`));
8285
8396
  }
8286
8397
  if (opts.json) {
8287
8398
  console.log(JSON.stringify(result));
8288
8399
  }
8289
8400
  } catch (err) {
8290
8401
  const message = err instanceof Error ? err.message : String(err);
8291
- spinner.fail("Failed to retry execution");
8402
+ spinner?.fail("Failed to retry execution");
8292
8403
  if (opts.json) {
8293
8404
  console.log(JSON.stringify({ success: false, error: message }));
8294
8405
  } else {
@@ -8299,7 +8410,7 @@ triggersCommand.command("retry-event <event-id>").description("Retry a failed im
8299
8410
  });
8300
8411
  triggersCommand.command("fire <slug>").description("Manually fire a trigger").option("--env <environment>", "Environment (development|production|eval)", "development").option("--entity <entityId>", "Entity ID to provide as context").option("--data <json>", "JSON data for template context").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").option("-v, --verbose", "Show detailed agent tool calls").action(async (slug, opts) => {
8301
8412
  await ensureAuth5();
8302
- const spinner = ora14();
8413
+ const spinner = opts.json ? null : ora14();
8303
8414
  const environment = opts.env;
8304
8415
  if (environment === "production" && !opts.confirm && isInteractive()) {
8305
8416
  const readline = await import("readline");
@@ -8320,7 +8431,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8320
8431
  }
8321
8432
  }
8322
8433
  try {
8323
- spinner.start(`Firing trigger ${chalk20.cyan(slug)}...`);
8434
+ spinner?.start(`Firing trigger ${chalk20.cyan(slug)}...`);
8324
8435
  const { result, error } = await fireTrigger({
8325
8436
  slug,
8326
8437
  environment,
@@ -8328,7 +8439,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8328
8439
  data
8329
8440
  });
8330
8441
  if (error) {
8331
- spinner.fail("Trigger execution failed");
8442
+ spinner?.fail("Trigger execution failed");
8332
8443
  if (opts.json) {
8333
8444
  console.log(JSON.stringify({ success: false, error }));
8334
8445
  } else {
@@ -8337,13 +8448,13 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8337
8448
  process.exit(1);
8338
8449
  }
8339
8450
  if (!result) {
8340
- spinner.fail("No result returned");
8451
+ spinner?.fail("No result returned");
8341
8452
  process.exit(1);
8342
8453
  }
8343
8454
  if (result.success) {
8344
- spinner.succeed(chalk20.green(`Trigger ${chalk20.cyan(slug)} fired successfully`));
8455
+ spinner?.succeed(chalk20.green(`Trigger ${chalk20.cyan(slug)} fired successfully`));
8345
8456
  } else {
8346
- spinner.fail(chalk20.red(`Trigger ${chalk20.cyan(slug)} execution failed`));
8457
+ spinner?.fail(chalk20.red(`Trigger ${chalk20.cyan(slug)} execution failed`));
8347
8458
  }
8348
8459
  if (opts.json) {
8349
8460
  console.log(JSON.stringify(result, null, 2));
@@ -8361,7 +8472,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8361
8472
  }
8362
8473
  } catch (err) {
8363
8474
  const message = err instanceof Error ? err.message : String(err);
8364
- spinner.fail("Failed to fire trigger");
8475
+ spinner?.fail("Failed to fire trigger");
8365
8476
  if (opts.json) {
8366
8477
  console.log(JSON.stringify({ success: false, error: message }));
8367
8478
  } else {
@@ -8599,6 +8710,8 @@ function channelColor(channel) {
8599
8710
  return chalk21.magenta(channel);
8600
8711
  case "dashboard":
8601
8712
  return chalk21.cyan(channel);
8713
+ case "voice":
8714
+ return chalk21.yellow(channel);
8602
8715
  default:
8603
8716
  return chalk21.gray(channel ?? "-");
8604
8717
  }
@@ -8618,17 +8731,17 @@ function roleColor(role) {
8618
8731
  }
8619
8732
  }
8620
8733
  var threadsCommand = new Command19("threads").description("Manage conversation threads");
8621
- threadsCommand.command("list", { isDefault: true }).description("List conversation threads").option("--env <environment>", "Environment (development|production|eval)", "development").option("--channel <channel>", "Filter by channel (whatsapp|api|widget|dashboard)").option("--limit <n>", "Maximum results", "25").option("--json", "Output raw JSON").action(async (opts) => {
8734
+ threadsCommand.command("list", { isDefault: true }).description("List conversation threads").option("--env <environment>", "Environment (development|production|eval)", "development").option("--channel <channel>", "Filter by channel (whatsapp|api|widget|dashboard|voice)").option("--limit <n>", "Maximum results", "25").option("--json", "Output raw JSON").action(async (opts) => {
8622
8735
  await ensureAuth6();
8623
- const spinner = ora15();
8736
+ const spinner = opts.json ? null : ora15();
8624
8737
  try {
8625
- spinner.start("Fetching threads");
8738
+ spinner?.start("Fetching threads");
8626
8739
  const threads = await listThreads({
8627
8740
  environment: opts.env,
8628
8741
  channel: opts.channel,
8629
8742
  limit: parseInt(opts.limit, 10)
8630
8743
  });
8631
- spinner.succeed(`Found ${threads.length} threads`);
8744
+ spinner?.succeed(`Found ${threads.length} threads`);
8632
8745
  if (opts.json) {
8633
8746
  console.log(JSON.stringify(threads, null, 2));
8634
8747
  return;
@@ -8652,7 +8765,7 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
8652
8765
  console.log();
8653
8766
  } catch (err) {
8654
8767
  const message = err instanceof Error ? err.message : String(err);
8655
- spinner.fail("Failed to fetch threads");
8768
+ spinner?.fail("Failed to fetch threads");
8656
8769
  if (opts.json) {
8657
8770
  console.log(JSON.stringify({ success: false, error: message }));
8658
8771
  } else {
@@ -8663,21 +8776,21 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
8663
8776
  });
8664
8777
  threadsCommand.command("view <id>").description("View thread details and messages").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (id, opts) => {
8665
8778
  await ensureAuth6();
8666
- const spinner = ora15();
8779
+ const spinner = opts.json ? null : ora15();
8667
8780
  try {
8668
- spinner.start("Fetching thread");
8781
+ spinner?.start("Fetching thread");
8669
8782
  const thread = await getThreadWithMessages({
8670
8783
  threadId: id,
8671
8784
  environment: opts.env
8672
8785
  });
8673
8786
  if (!thread) {
8674
- spinner.fail("Thread not found");
8787
+ spinner?.fail("Thread not found");
8675
8788
  if (opts.json) {
8676
8789
  console.log(JSON.stringify({ success: false, error: "Thread not found" }));
8677
8790
  }
8678
8791
  process.exit(1);
8679
8792
  }
8680
- spinner.succeed("Thread loaded");
8793
+ spinner?.succeed("Thread loaded");
8681
8794
  if (opts.json) {
8682
8795
  console.log(JSON.stringify(thread, null, 2));
8683
8796
  return;
@@ -8710,7 +8823,7 @@ threadsCommand.command("view <id>").description("View thread details and message
8710
8823
  console.log();
8711
8824
  } catch (err) {
8712
8825
  const message = err instanceof Error ? err.message : String(err);
8713
- spinner.fail("Failed to fetch thread");
8826
+ spinner?.fail("Failed to fetch thread");
8714
8827
  if (opts.json) {
8715
8828
  console.log(JSON.stringify({ success: false, error: message }));
8716
8829
  } else {
@@ -8721,7 +8834,7 @@ threadsCommand.command("view <id>").description("View thread details and message
8721
8834
  });
8722
8835
  threadsCommand.command("archive <id>").description("Archive a thread (frees its externalId)").option("--env <environment>", "Environment", "development").option("--confirm", "Skip production confirmation").option("--json", "Output raw JSON").action(async (id, opts) => {
8723
8836
  await ensureAuth6();
8724
- const spinner = ora15();
8837
+ const spinner = opts.json ? null : ora15();
8725
8838
  const environment = opts.env;
8726
8839
  if (environment === "production" && !opts.confirm && isInteractive()) {
8727
8840
  const readline = await import("readline");
@@ -8733,18 +8846,18 @@ threadsCommand.command("archive <id>").description("Archive a thread (frees its
8733
8846
  rl.close();
8734
8847
  }
8735
8848
  try {
8736
- spinner.start("Archiving thread...");
8849
+ spinner?.start("Archiving thread...");
8737
8850
  const result = await archiveThread({
8738
8851
  threadId: id,
8739
8852
  environment
8740
8853
  });
8741
- spinner.succeed(chalk21.green(`Thread ${id} archived`));
8854
+ spinner?.succeed(chalk21.green(`Thread ${id} archived`));
8742
8855
  if (opts.json) {
8743
8856
  console.log(JSON.stringify({ success: true, threadId: id }));
8744
8857
  }
8745
8858
  } catch (err) {
8746
8859
  const message = err instanceof Error ? err.message : String(err);
8747
- spinner.fail("Failed to archive thread");
8860
+ spinner?.fail("Failed to archive thread");
8748
8861
  if (opts.json) {
8749
8862
  console.log(JSON.stringify({ success: false, error: message }));
8750
8863
  } else {
@@ -8755,7 +8868,7 @@ threadsCommand.command("archive <id>").description("Archive a thread (frees its
8755
8868
  });
8756
8869
  threadsCommand.command("reset").description("Archive a thread by phone number").requiredOption("--phone <number>", "Phone number to find and archive").option("--env <environment>", "Environment", "production").option("--confirm", "Skip production confirmation").option("--json", "Output raw JSON").action(async (opts) => {
8757
8870
  await ensureAuth6();
8758
- const spinner = ora15();
8871
+ const spinner = opts.json ? null : ora15();
8759
8872
  const environment = opts.env;
8760
8873
  if (environment === "production" && !opts.confirm && isInteractive()) {
8761
8874
  const readline = await import("readline");
@@ -8767,30 +8880,31 @@ threadsCommand.command("reset").description("Archive a thread by phone number").
8767
8880
  rl.close();
8768
8881
  }
8769
8882
  try {
8770
- spinner.start(`Finding thread for phone ${opts.phone}...`);
8883
+ spinner?.start(`Finding thread for phone ${opts.phone}...`);
8771
8884
  const thread = await findThreadByPhone({
8772
8885
  phone: opts.phone,
8773
8886
  environment
8774
8887
  });
8775
8888
  if (!thread) {
8776
- spinner.fail(`No thread found for phone number ${opts.phone}`);
8889
+ spinner?.fail(`No thread found for phone number ${opts.phone}`);
8777
8890
  if (opts.json) {
8778
8891
  console.log(JSON.stringify({ success: false, error: "Thread not found for phone number" }));
8779
8892
  }
8780
8893
  process.exit(1);
8781
8894
  }
8782
- spinner.text = `Archiving thread ${thread._id}...`;
8895
+ if (spinner)
8896
+ spinner.text = `Archiving thread ${thread._id}...`;
8783
8897
  await archiveThread({
8784
8898
  threadId: thread._id,
8785
8899
  environment
8786
8900
  });
8787
- spinner.succeed(chalk21.green(`Thread ${thread._id} archived (phone: ${opts.phone})`));
8901
+ spinner?.succeed(chalk21.green(`Thread ${thread._id} archived (phone: ${opts.phone})`));
8788
8902
  if (opts.json) {
8789
8903
  console.log(JSON.stringify({ success: true, threadId: thread._id, phone: opts.phone }));
8790
8904
  }
8791
8905
  } catch (err) {
8792
8906
  const message = err instanceof Error ? err.message : String(err);
8793
- spinner.fail("Failed to reset thread");
8907
+ spinner?.fail("Failed to reset thread");
8794
8908
  if (opts.json) {
8795
8909
  console.log(JSON.stringify({ success: false, error: message }));
8796
8910
  } else {
@@ -9502,7 +9616,7 @@ var whatsappCommand = new Command23("whatsapp").description("Manage WhatsApp con
9502
9616
  whatsappCommand.command("list").description("List WhatsApp connections with routing assignments").option("--env <environment>", "Environment (development|production)", "production").option("--json", "Output raw JSON").action(async (opts) => {
9503
9617
  await ensureAuth7();
9504
9618
  const env = opts.env;
9505
- const out = createOutput();
9619
+ const out = opts.json ? createSilentOutput() : createOutput();
9506
9620
  out.start("Fetching WhatsApp connections");
9507
9621
  const { data, error } = await listWhatsAppConnections(env);
9508
9622
  if (error || !data) {
@@ -10448,14 +10562,14 @@ async function runCheck(fn) {
10448
10562
  }
10449
10563
  var doctorCommand = new Command25("doctor").description("Run diagnostic checks on your Struere project").option("--env <environment>", "Environment to check", "development").option("--json", "Output raw JSON").action(async (opts) => {
10450
10564
  await ensureAuth8();
10451
- const spinner = ora20();
10565
+ const spinner = opts.json ? null : ora20();
10452
10566
  const cwd = process.cwd();
10453
10567
  const environment = opts.env;
10454
10568
  try {
10455
- spinner.start("Loading resources...");
10569
+ spinner?.start("Loading resources...");
10456
10570
  const resources = await loadAllResources(cwd);
10457
- spinner.succeed("Resources loaded");
10458
- spinner.start("Running diagnostics...");
10571
+ spinner?.succeed("Resources loaded");
10572
+ spinner?.start("Running diagnostics...");
10459
10573
  const results = await Promise.allSettled([
10460
10574
  runCheck(() => checkWhatsAppConnection(resources)),
10461
10575
  runCheck(() => checkTemplateApprovals(resources)),
@@ -10465,7 +10579,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10465
10579
  runCheck(() => checkTriggerHealth(environment)),
10466
10580
  runCheck(() => checkSyncDrift(resources, environment))
10467
10581
  ]);
10468
- spinner.stop();
10582
+ spinner?.stop();
10469
10583
  const checks = results.map((r) => r.status === "fulfilled" ? r.value : { id: "unknown", label: "Unknown", status: "warn", message: `Check failed: ${r.reason}` });
10470
10584
  const summary = {
10471
10585
  ok: checks.filter((c) => c.status === "ok").length,
@@ -10508,7 +10622,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10508
10622
  }
10509
10623
  } catch (err) {
10510
10624
  const message = err instanceof Error ? err.message : String(err);
10511
- spinner.fail("Diagnostics failed");
10625
+ spinner?.fail("Diagnostics failed");
10512
10626
  if (opts.json) {
10513
10627
  console.log(JSON.stringify({ success: false, error: message }));
10514
10628
  } else {
@@ -10520,7 +10634,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10520
10634
  // package.json
10521
10635
  var package_default = {
10522
10636
  name: "struere",
10523
- version: "0.13.1",
10637
+ version: "0.14.1",
10524
10638
  description: "Build, test, and deploy AI agents",
10525
10639
  keywords: [
10526
10640
  "ai",