struere 0.14.0 → 0.14.2

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;
@@ -3110,6 +3126,11 @@ function extractAgentPayload(agent, customToolsMap) {
3110
3126
  const tools = (agent.tools || []).map((toolName) => {
3111
3127
  const isBuiltin = BUILTIN_TOOLS.includes(toolName);
3112
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
+ }
3113
3134
  const available = customToolsMap.size > 0 ? `Available custom tools: ${Array.from(customToolsMap.keys()).join(", ")}` : "No custom tools were loaded from tools/index.ts";
3114
3135
  throw new Error(`Agent "${agent.name}" references tool "${toolName}" but it was not found. ${available}`);
3115
3136
  }
@@ -3205,6 +3226,42 @@ function validateResources(payload, resources) {
3205
3226
  }
3206
3227
 
3207
3228
  // src/cli/commands/sync.ts
3229
+ class SyncValidationFailureError extends Error {
3230
+ validationErrors;
3231
+ constructor(validationErrors) {
3232
+ super("Server-side validation failed for one or more resources");
3233
+ this.name = "SyncValidationFailureError";
3234
+ this.validationErrors = validationErrors;
3235
+ }
3236
+ }
3237
+ function renderValidationErrors(errors) {
3238
+ console.log();
3239
+ console.log(chalk6.red.bold("Validation failed \u2014 no changes were applied."));
3240
+ console.log();
3241
+ const labels = {
3242
+ agents: "agent",
3243
+ entityTypes: "entity type",
3244
+ roles: "role",
3245
+ evalSuites: "eval suite",
3246
+ triggers: "trigger",
3247
+ fixtures: "fixture",
3248
+ routers: "router",
3249
+ tools: "tool"
3250
+ };
3251
+ const order = ["agents", "entityTypes", "roles", "evalSuites", "triggers", "fixtures", "routers", "tools"];
3252
+ for (const key of order) {
3253
+ const items = errors[key];
3254
+ if (!items || items.length === 0)
3255
+ continue;
3256
+ const label = labels[key] || key;
3257
+ console.log(chalk6.red.bold(` ${key}:`), chalk6.red(`${items.length} invalid ${label}${items.length === 1 ? "" : "s"}`));
3258
+ for (const item of items) {
3259
+ const id = item.slug || item.name || "(unidentified)";
3260
+ console.log(chalk6.red(` - ${id}: ${item.error}`));
3261
+ }
3262
+ }
3263
+ console.log();
3264
+ }
3208
3265
  async function performDevSync(cwd, organizationId) {
3209
3266
  generateTypeDeclarations(cwd);
3210
3267
  const resources = await loadAllResources(cwd);
@@ -3235,6 +3292,8 @@ ${resources.errors.join(`
3235
3292
  environment: "development"
3236
3293
  });
3237
3294
  if (!devResult.success) {
3295
+ if (devResult.validationErrors)
3296
+ throw new SyncValidationFailureError(devResult.validationErrors);
3238
3297
  throw new Error(devResult.error || "Dev sync failed");
3239
3298
  }
3240
3299
  const hasEvalContent = payload.evalSuites && payload.evalSuites.length > 0 || payload.fixtures && payload.fixtures.length > 0;
@@ -3250,6 +3309,8 @@ ${resources.errors.join(`
3250
3309
  environment: "eval"
3251
3310
  });
3252
3311
  if (!evalResult.success) {
3312
+ if (evalResult.validationErrors)
3313
+ throw new SyncValidationFailureError(evalResult.validationErrors);
3253
3314
  throw new Error(evalResult.error || "Eval sync failed");
3254
3315
  }
3255
3316
  }
@@ -3322,8 +3383,11 @@ ${resources.errors.join(`
3322
3383
  organizationId,
3323
3384
  environment: "eval"
3324
3385
  });
3325
- if (!result.success)
3386
+ if (!result.success) {
3387
+ if (result.validationErrors)
3388
+ throw new SyncValidationFailureError(result.validationErrors);
3326
3389
  throw new Error(result.error || "Eval sync failed");
3390
+ }
3327
3391
  return result;
3328
3392
  }
3329
3393
  if (environment === "production") {
@@ -3332,8 +3396,11 @@ ${resources.errors.join(`
3332
3396
  organizationId,
3333
3397
  environment: "production"
3334
3398
  });
3335
- if (!result.success)
3399
+ if (!result.success) {
3400
+ if (result.validationErrors)
3401
+ throw new SyncValidationFailureError(result.validationErrors);
3336
3402
  throw new Error(result.error || "Production sync failed");
3403
+ }
3337
3404
  return result;
3338
3405
  }
3339
3406
  return performDevSync(cwd, organizationId);
@@ -3481,8 +3548,42 @@ var syncCommand = new Command5("sync").description("Sync resources to Convex and
3481
3548
  output.start("Syncing to Convex");
3482
3549
  try {
3483
3550
  const result = await syncToEnvironment(cwd, project.organization.id, environment);
3484
- if (!jsonMode)
3551
+ if (!jsonMode) {
3485
3552
  output.succeed(`Synced to ${environment}`);
3553
+ console.log();
3554
+ const types = [
3555
+ { label: "agent", data: result.agents },
3556
+ { label: "entity type", data: result.entityTypes },
3557
+ { label: "role", data: result.roles },
3558
+ { label: "trigger", data: result.triggers },
3559
+ { label: "router", data: result.routers },
3560
+ { label: "eval suite", data: result.evalSuites }
3561
+ ];
3562
+ let hasChanges = false;
3563
+ for (const { label, data } of types) {
3564
+ if (!data)
3565
+ continue;
3566
+ for (const name of data.created) {
3567
+ console.log(chalk6.green(` + Created ${label}: ${name}`));
3568
+ hasChanges = true;
3569
+ }
3570
+ for (const name of data.updated) {
3571
+ console.log(chalk6.blue(` ~ Updated ${label}: ${name}`));
3572
+ hasChanges = true;
3573
+ }
3574
+ for (const name of data.deleted) {
3575
+ console.log(chalk6.red(` - Deleted ${label}: ${name}`));
3576
+ hasChanges = true;
3577
+ }
3578
+ }
3579
+ if (!hasChanges) {
3580
+ console.log(chalk6.gray(" No changes detected"));
3581
+ }
3582
+ if (result.evalSuites?.skipped && result.evalSuites.skipped.length > 0) {
3583
+ console.log(chalk6.yellow(` Skipped eval suites (agent not found): ${result.evalSuites.skipped.join(", ")}`));
3584
+ }
3585
+ console.log();
3586
+ }
3486
3587
  if (jsonMode) {
3487
3588
  console.log(JSON.stringify({
3488
3589
  success: true,
@@ -3505,6 +3606,15 @@ var syncCommand = new Command5("sync").description("Sync resources to Convex and
3505
3606
  }));
3506
3607
  }
3507
3608
  } catch (error) {
3609
+ if (error instanceof SyncValidationFailureError) {
3610
+ if (jsonMode) {
3611
+ console.log(JSON.stringify({ success: false, validationErrors: error.validationErrors }));
3612
+ } else {
3613
+ output.fail("Sync failed");
3614
+ renderValidationErrors(error.validationErrors);
3615
+ }
3616
+ process.exit(1);
3617
+ }
3508
3618
  if (jsonMode) {
3509
3619
  console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
3510
3620
  } else {
@@ -3635,6 +3745,9 @@ var devCommand = new Command6("dev").description("Watch files and sync to develo
3635
3745
  console.log(chalk7.gray(" 2."), "Or run", chalk7.cyan("struere init"), "to select a different organization");
3636
3746
  console.log();
3637
3747
  process.exit(1);
3748
+ } else if (error instanceof SyncValidationFailureError) {
3749
+ spinner.fail("Sync failed");
3750
+ renderValidationErrors(error.validationErrors);
3638
3751
  } else {
3639
3752
  spinner.fail("Sync failed");
3640
3753
  console.log(chalk7.red("Error:"), error instanceof Error ? error.message : String(error));
@@ -3675,8 +3788,13 @@ var devCommand = new Command6("dev").description("Watch files and sync to develo
3675
3788
  await performDevSync(cwd, project.organization.id);
3676
3789
  syncSpinner.succeed("Synced");
3677
3790
  } catch (error) {
3678
- syncSpinner.fail("Sync failed");
3679
- console.log(chalk7.red("Error:"), error instanceof Error ? error.message : String(error));
3791
+ if (error instanceof SyncValidationFailureError) {
3792
+ syncSpinner.fail("Sync failed");
3793
+ renderValidationErrors(error.validationErrors);
3794
+ } else {
3795
+ syncSpinner.fail("Sync failed");
3796
+ console.log(chalk7.red("Error:"), error instanceof Error ? error.message : String(error));
3797
+ }
3680
3798
  }
3681
3799
  };
3682
3800
  const handleFileChange = (path, action) => {
@@ -3943,6 +4061,8 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
3943
4061
  environment: "production"
3944
4062
  });
3945
4063
  if (!syncResult.success) {
4064
+ if (syncResult.validationErrors)
4065
+ throw new SyncValidationFailureError(syncResult.validationErrors);
3946
4066
  throw new Error(syncResult.error || "Deploy failed");
3947
4067
  }
3948
4068
  const elapsed = Date.now() - startTime;
@@ -4052,6 +4172,14 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
4052
4172
  console.log();
4053
4173
  }
4054
4174
  process.exit(1);
4175
+ } else if (error instanceof SyncValidationFailureError) {
4176
+ if (jsonMode) {
4177
+ console.log(JSON.stringify({ success: false, validationErrors: error.validationErrors }));
4178
+ } else {
4179
+ spinner.fail("Deployment failed");
4180
+ renderValidationErrors(error.validationErrors);
4181
+ }
4182
+ process.exit(1);
4055
4183
  } else {
4056
4184
  if (jsonMode) {
4057
4185
  console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
@@ -5338,17 +5466,17 @@ function flattenEntityForTable(entity) {
5338
5466
  var entitiesCommand = new Command13("data").description("Manage data records");
5339
5467
  entitiesCommand.command("types").description("List available data types").option("--env <environment>", "Environment (development|production)", "development").option("--json", "Output raw JSON").action(async (opts) => {
5340
5468
  await ensureAuth();
5341
- const spinner = ora11();
5469
+ const spinner = opts.json ? null : ora11();
5342
5470
  const env = opts.env;
5343
5471
  const orgId = getOrgId();
5344
- spinner.start("Fetching data types");
5472
+ spinner?.start("Fetching data types");
5345
5473
  const { data, error } = await queryEntityTypes(env, orgId);
5346
5474
  if (error || !data) {
5347
- spinner.fail("Failed to fetch data types");
5475
+ spinner?.fail("Failed to fetch data types");
5348
5476
  console.log(chalk15.red("Error:"), error);
5349
5477
  process.exit(1);
5350
5478
  }
5351
- spinner.succeed("Data types loaded");
5479
+ spinner?.succeed("Data types loaded");
5352
5480
  const types = data;
5353
5481
  if (opts.json) {
5354
5482
  console.log(JSON.stringify(types, null, 2));
@@ -5373,10 +5501,10 @@ entitiesCommand.command("types").description("List available data types").option
5373
5501
  });
5374
5502
  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) => {
5375
5503
  await ensureAuth();
5376
- const spinner = ora11();
5504
+ const spinner = opts.json ? null : ora11();
5377
5505
  const env = opts.env;
5378
5506
  const orgId = getOrgId();
5379
- spinner.start(`Fetching ${type} records`);
5507
+ spinner?.start(`Fetching ${type} records`);
5380
5508
  const [entitiesResult, typeResult] = await Promise.all([
5381
5509
  queryEntities(type, env, {
5382
5510
  status: opts.status,
@@ -5385,12 +5513,12 @@ entitiesCommand.command("list <type>").description("List records of a type").opt
5385
5513
  queryEntityTypeBySlug(type, env, orgId)
5386
5514
  ]);
5387
5515
  if (entitiesResult.error || !entitiesResult.data) {
5388
- spinner.fail(`Failed to fetch ${type} records`);
5516
+ spinner?.fail(`Failed to fetch ${type} records`);
5389
5517
  console.log(chalk15.red("Error:"), entitiesResult.error);
5390
5518
  process.exit(1);
5391
5519
  }
5392
5520
  const entities = entitiesResult.data;
5393
- spinner.succeed(`Found ${entities.length} ${type} records`);
5521
+ spinner?.succeed(`Found ${entities.length} ${type} records`);
5394
5522
  if (opts.json) {
5395
5523
  console.log(JSON.stringify(entities, null, 2));
5396
5524
  return;
@@ -5405,25 +5533,26 @@ entitiesCommand.command("list <type>").description("List records of a type").opt
5405
5533
  });
5406
5534
  entitiesCommand.command("get <id>").description("Get record details").option("--env <environment>", "Environment (development|production)", "development").option("--json", "Output raw JSON").action(async (rawId, opts) => {
5407
5535
  await ensureAuth();
5408
- const spinner = ora11();
5536
+ const spinner = opts.json ? null : ora11();
5409
5537
  const env = opts.env;
5410
5538
  const orgId = getOrgId();
5411
- spinner.start("Resolving record ID");
5539
+ spinner?.start("Resolving record ID");
5412
5540
  const resolved = await resolveEntityId(rawId, env, orgId);
5413
5541
  if (resolved.error || !resolved.data) {
5414
- spinner.fail("Record not found");
5542
+ spinner?.fail("Record not found");
5415
5543
  console.log(chalk15.red("Error:"), resolved.error || `No record matched "${rawId}"`);
5416
5544
  process.exit(1);
5417
5545
  }
5418
5546
  const id = resolved.data;
5419
- spinner.text = "Fetching record";
5547
+ if (spinner)
5548
+ spinner.text = "Fetching record";
5420
5549
  const { data, error } = await queryEntity(id, env, orgId);
5421
5550
  if (error || !data) {
5422
- spinner.fail("Failed to fetch record");
5551
+ spinner?.fail("Failed to fetch record");
5423
5552
  console.log(chalk15.red("Error:"), error || "Record not found");
5424
5553
  process.exit(1);
5425
5554
  }
5426
- spinner.succeed("Record loaded");
5555
+ spinner?.succeed("Record loaded");
5427
5556
  const result = data;
5428
5557
  if (opts.json) {
5429
5558
  console.log(JSON.stringify(result, null, 2));
@@ -5453,7 +5582,7 @@ entitiesCommand.command("get <id>").description("Get record details").option("--
5453
5582
  });
5454
5583
  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) => {
5455
5584
  await ensureAuth();
5456
- const spinner = ora11();
5585
+ const spinner = opts.json ? null : ora11();
5457
5586
  const env = opts.env;
5458
5587
  const orgId = getOrgId();
5459
5588
  let data;
@@ -5468,14 +5597,14 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5468
5597
  console.log(chalk15.red("--data <json> is required in non-interactive mode"));
5469
5598
  process.exit(1);
5470
5599
  } else {
5471
- spinner.start(`Fetching ${type} schema`);
5600
+ spinner?.start(`Fetching ${type} schema`);
5472
5601
  const { data: typeData, error: error2 } = await queryEntityTypeBySlug(type, env, orgId);
5473
5602
  if (error2 || !typeData) {
5474
- spinner.fail(`Data type not found: ${type}`);
5603
+ spinner?.fail(`Data type not found: ${type}`);
5475
5604
  console.log(chalk15.red("Error:"), error2 || "Not found");
5476
5605
  process.exit(1);
5477
5606
  }
5478
- spinner.succeed(`Schema loaded for ${type}`);
5607
+ spinner?.succeed(`Schema loaded for ${type}`);
5479
5608
  console.log();
5480
5609
  const entityType = typeData;
5481
5610
  const schema = entityType.schema;
@@ -5509,14 +5638,14 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5509
5638
  }
5510
5639
  console.log();
5511
5640
  }
5512
- spinner.start(`Creating ${type} record`);
5641
+ spinner?.start(`Creating ${type} record`);
5513
5642
  const { data: result, error } = await createEntity(type, data, env, opts.status, orgId);
5514
5643
  if (error) {
5515
- spinner.fail("Failed to create record");
5644
+ spinner?.fail("Failed to create record");
5516
5645
  console.log(chalk15.red("Error:"), error);
5517
5646
  process.exit(1);
5518
5647
  }
5519
- spinner.succeed(`Record created`);
5648
+ spinner?.succeed(`Record created`);
5520
5649
  if (opts.json) {
5521
5650
  console.log(JSON.stringify({ id: result }, null, 2));
5522
5651
  } else {
@@ -5527,7 +5656,7 @@ entitiesCommand.command("create <type>").description("Create a new record").opti
5527
5656
  });
5528
5657
  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) => {
5529
5658
  await ensureAuth();
5530
- const spinner = ora11();
5659
+ const spinner = opts.json ? null : ora11();
5531
5660
  const env = opts.env;
5532
5661
  const orgId = getOrgId();
5533
5662
  if (!opts.data && !opts.status) {
@@ -5543,22 +5672,23 @@ entitiesCommand.command("update <id>").description("Update a record").option("--
5543
5672
  process.exit(1);
5544
5673
  }
5545
5674
  }
5546
- spinner.start("Resolving record ID");
5675
+ spinner?.start("Resolving record ID");
5547
5676
  const resolved = await resolveEntityId(rawId, env, orgId);
5548
5677
  if (resolved.error || !resolved.data) {
5549
- spinner.fail("Record not found");
5678
+ spinner?.fail("Record not found");
5550
5679
  console.log(chalk15.red("Error:"), resolved.error || `No record matched "${rawId}"`);
5551
5680
  process.exit(1);
5552
5681
  }
5553
5682
  const id = resolved.data;
5554
- spinner.text = "Updating record";
5683
+ if (spinner)
5684
+ spinner.text = "Updating record";
5555
5685
  const { data: result, error } = await updateEntity(id, data, env, opts.status, orgId);
5556
5686
  if (error) {
5557
- spinner.fail("Failed to update record");
5687
+ spinner?.fail("Failed to update record");
5558
5688
  console.log(chalk15.red("Error:"), error);
5559
5689
  process.exit(1);
5560
5690
  }
5561
- spinner.succeed("Record updated");
5691
+ spinner?.succeed("Record updated");
5562
5692
  if (opts.json) {
5563
5693
  console.log(JSON.stringify(result, null, 2));
5564
5694
  } else {
@@ -5646,21 +5776,21 @@ entitiesCommand.command("delete <id>").description("Delete a record").option("--
5646
5776
  });
5647
5777
  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) => {
5648
5778
  await ensureAuth();
5649
- const spinner = ora11();
5779
+ const spinner = opts.json ? null : ora11();
5650
5780
  const env = opts.env;
5651
5781
  const orgId = getOrgId();
5652
- spinner.start(`Searching ${type} for "${query}"`);
5782
+ spinner?.start(`Searching ${type} for "${query}"`);
5653
5783
  const [searchResult, typeResult] = await Promise.all([
5654
5784
  searchEntities(type, query, env, parseInt(opts.limit, 10), orgId),
5655
5785
  queryEntityTypeBySlug(type, env, orgId)
5656
5786
  ]);
5657
5787
  if (searchResult.error || !searchResult.data) {
5658
- spinner.fail("Search failed");
5788
+ spinner?.fail("Search failed");
5659
5789
  console.log(chalk15.red("Error:"), searchResult.error);
5660
5790
  process.exit(1);
5661
5791
  }
5662
5792
  const entities = searchResult.data;
5663
- spinner.succeed(`Found ${entities.length} results`);
5793
+ spinner?.succeed(`Found ${entities.length} results`);
5664
5794
  if (opts.json) {
5665
5795
  console.log(JSON.stringify(entities, null, 2));
5666
5796
  return;
@@ -5839,21 +5969,21 @@ function formatTimestamp(ts) {
5839
5969
  var logsCommand = new Command14("logs").description("View and debug agent conversations");
5840
5970
  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) => {
5841
5971
  await ensureAuth2();
5842
- const spinner = ora12();
5972
+ const spinner = opts.json ? null : ora12();
5843
5973
  const orgId = getOrgId2();
5844
5974
  let agentId;
5845
5975
  if (opts.agent) {
5846
- spinner.start(`Resolving agent "${opts.agent}"`);
5976
+ spinner?.start(`Resolving agent "${opts.agent}"`);
5847
5977
  const resolved = await resolveAgentSlug(opts.agent, orgId);
5848
5978
  if (resolved.error || !resolved.data) {
5849
- spinner.fail("Agent not found");
5979
+ spinner?.fail("Agent not found");
5850
5980
  console.log(chalk16.red("Error:"), resolved.error || `No agent matched "${opts.agent}"`);
5851
5981
  process.exit(1);
5852
5982
  }
5853
5983
  agentId = resolved.data;
5854
- spinner.succeed(`Agent resolved: ${opts.agent}`);
5984
+ spinner?.succeed(`Agent resolved: ${opts.agent}`);
5855
5985
  }
5856
- spinner.start("Fetching conversations");
5986
+ spinner?.start("Fetching conversations");
5857
5987
  const { data, error } = await queryThreads({
5858
5988
  environment: opts.env,
5859
5989
  agentId,
@@ -5862,17 +5992,16 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
5862
5992
  limit: parseInt(opts.limit, 10)
5863
5993
  });
5864
5994
  if (error || !data) {
5865
- spinner.fail("Failed to fetch conversations");
5995
+ spinner?.fail("Failed to fetch conversations");
5866
5996
  console.log(chalk16.red("Error:"), error);
5867
5997
  process.exit(1);
5868
5998
  }
5869
5999
  const threads = data;
5870
6000
  if (opts.json) {
5871
- spinner.stop();
5872
6001
  console.log(JSON.stringify(threads, null, 2));
5873
6002
  return;
5874
6003
  }
5875
- spinner.succeed(`Found ${threads.length} conversations`);
6004
+ spinner?.succeed(`Found ${threads.length} conversations`);
5876
6005
  console.log();
5877
6006
  renderTable([
5878
6007
  { key: "id", label: "ID", width: 14 },
@@ -5896,25 +6025,25 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
5896
6025
  });
5897
6026
  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) => {
5898
6027
  await ensureAuth2();
5899
- const spinner = ora12();
5900
- spinner.start("Resolving thread");
6028
+ const spinner = opts.json ? null : ora12();
6029
+ spinner?.start("Resolving thread");
5901
6030
  const resolved = await resolveThreadId(rawThreadId, opts.env);
5902
6031
  if (resolved.error || !resolved.data) {
5903
- spinner.fail("Thread not found");
6032
+ spinner?.fail("Thread not found");
5904
6033
  console.log(chalk16.red("Error:"), resolved.error || `No thread matched "${rawThreadId}"`);
5905
6034
  process.exit(1);
5906
6035
  }
5907
6036
  const threadId = resolved.data;
5908
- spinner.stop();
5909
- spinner.start("Fetching conversation");
6037
+ spinner?.stop();
6038
+ spinner?.start("Fetching conversation");
5910
6039
  const fetchLimit = opts.tail ? 1000 : parseInt(opts.limit, 10);
5911
6040
  const { data, error } = await queryThreadDetail(threadId, fetchLimit);
5912
6041
  if (error || !data) {
5913
- spinner.fail("Failed to fetch conversation");
6042
+ spinner?.fail("Failed to fetch conversation");
5914
6043
  console.log(chalk16.red("Error:"), error || "Thread not found");
5915
6044
  process.exit(1);
5916
6045
  }
5917
- spinner.stop();
6046
+ spinner?.stop();
5918
6047
  const result = data;
5919
6048
  let executions = [];
5920
6049
  if (opts.exec) {
@@ -6795,7 +6924,7 @@ var templatesCommand = new Command16("templates").description("Manage WhatsApp m
6795
6924
  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) => {
6796
6925
  await ensureAuth3();
6797
6926
  const connectionId = await resolveConnectionId(opts.env ?? "production");
6798
- const out = createOutput();
6927
+ const out = opts.json ? createSilentOutput() : createOutput();
6799
6928
  out.start("Fetching templates");
6800
6929
  const { data, error } = await listTemplates(connectionId);
6801
6930
  if (error) {
@@ -6857,7 +6986,7 @@ templatesCommand.command("create <name>").description("Create a new message temp
6857
6986
  console.log(chalk18.red("Components must be a JSON array"));
6858
6987
  process.exit(1);
6859
6988
  }
6860
- const out = createOutput();
6989
+ const out = opts.json ? createSilentOutput() : createOutput();
6861
6990
  out.start(`Creating template "${name}"`);
6862
6991
  const { data, error } = await createTemplate(connectionId, name, opts.language, opts.category.toUpperCase(), components, opts.allowCategoryChange);
6863
6992
  const result = data;
@@ -6904,7 +7033,7 @@ templatesCommand.command("delete <name>").description("Delete a message template
6904
7033
  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) => {
6905
7034
  await ensureAuth3();
6906
7035
  const connectionId = await resolveConnectionId(opts.env ?? "production", opts.connection);
6907
- const out = createOutput();
7036
+ const out = opts.json ? createSilentOutput() : createOutput();
6908
7037
  out.start(`Checking status for "${name}"`);
6909
7038
  const { data, error } = await getTemplateStatus(connectionId, name);
6910
7039
  if (error) {
@@ -6942,7 +7071,7 @@ templatesCommand.command("status <name>").description("Check template approval s
6942
7071
  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) => {
6943
7072
  await ensureAuth3();
6944
7073
  const connectionId = await resolveConnectionId(opts.env ?? "production", opts.connection);
6945
- const out = createOutput();
7074
+ const out = opts.json ? createSilentOutput() : createOutput();
6946
7075
  out.start(`Fetching template "${name}"`);
6947
7076
  const { data: statusData, error: statusError } = await getTemplateStatus(connectionId, name);
6948
7077
  if (statusError) {
@@ -7262,7 +7391,7 @@ function buildConfigFromOpts(provider, opts) {
7262
7391
  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) => {
7263
7392
  await ensureAuth4();
7264
7393
  const env = opts.env;
7265
- const out = createOutput();
7394
+ const out = opts.json ? createSilentOutput() : createOutput();
7266
7395
  if (!provider || provider === "list") {
7267
7396
  out.start("Fetching integrations");
7268
7397
  const { data, error } = await listIntegrationConfigs(env);
@@ -7778,9 +7907,9 @@ function renderExecutionLog(executionLog, verbose) {
7778
7907
  var triggersCommand = new Command18("triggers").description("Manage triggers and automation runs");
7779
7908
  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) => {
7780
7909
  await ensureAuth5();
7781
- const spinner = ora14();
7910
+ const spinner = opts.json ? null : ora14();
7782
7911
  try {
7783
- spinner.start("Fetching triggers");
7912
+ spinner?.start("Fetching triggers");
7784
7913
  const [triggers, statuses] = await Promise.all([
7785
7914
  listTriggers(opts.env),
7786
7915
  getLastRunStatuses(opts.env)
@@ -7789,7 +7918,7 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7789
7918
  if (opts.failed) {
7790
7919
  filtered = triggers.filter((t) => statuses[t.slug]?.status === "failed");
7791
7920
  }
7792
- spinner.succeed(`Found ${filtered.length} triggers${opts.failed ? " (failed only)" : ""}`);
7921
+ spinner?.succeed(`Found ${filtered.length} triggers${opts.failed ? " (failed only)" : ""}`);
7793
7922
  if (opts.json) {
7794
7923
  console.log(JSON.stringify(filtered, null, 2));
7795
7924
  return;
@@ -7815,7 +7944,7 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7815
7944
  console.log();
7816
7945
  } catch (err) {
7817
7946
  const message = err instanceof Error ? err.message : String(err);
7818
- spinner.fail("Failed to fetch triggers");
7947
+ spinner?.fail("Failed to fetch triggers");
7819
7948
  if (opts.json) {
7820
7949
  console.log(JSON.stringify({ success: false, error: message }));
7821
7950
  } else {
@@ -7826,18 +7955,18 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7826
7955
  });
7827
7956
  triggersCommand.command("get <slug>").description("View trigger details").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (slug, opts) => {
7828
7957
  await ensureAuth5();
7829
- const spinner = ora14();
7958
+ const spinner = opts.json ? null : ora14();
7830
7959
  try {
7831
- spinner.start("Fetching trigger");
7960
+ spinner?.start("Fetching trigger");
7832
7961
  const trigger = await getTrigger(slug, opts.env);
7833
7962
  if (!trigger) {
7834
- spinner.fail("Trigger not found");
7963
+ spinner?.fail("Trigger not found");
7835
7964
  if (opts.json) {
7836
7965
  console.log(JSON.stringify({ success: false, error: "Trigger not found" }));
7837
7966
  }
7838
7967
  process.exit(1);
7839
7968
  }
7840
- spinner.succeed("Trigger loaded");
7969
+ spinner?.succeed("Trigger loaded");
7841
7970
  if (opts.json) {
7842
7971
  console.log(JSON.stringify(trigger, null, 2));
7843
7972
  return;
@@ -7894,7 +8023,7 @@ triggersCommand.command("get <slug>").description("View trigger details").option
7894
8023
  console.log();
7895
8024
  } catch (err) {
7896
8025
  const message = err instanceof Error ? err.message : String(err);
7897
- spinner.fail("Failed to fetch trigger");
8026
+ spinner?.fail("Failed to fetch trigger");
7898
8027
  if (opts.json) {
7899
8028
  console.log(JSON.stringify({ success: false, error: message }));
7900
8029
  } else {
@@ -7905,16 +8034,16 @@ triggersCommand.command("get <slug>").description("View trigger details").option
7905
8034
  });
7906
8035
  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) => {
7907
8036
  await ensureAuth5();
7908
- const spinner = ora14();
8037
+ const spinner = opts.json ? null : ora14();
7909
8038
  try {
7910
- spinner.start("Fetching runs");
8039
+ spinner?.start("Fetching runs");
7911
8040
  const runs = await listTriggerRuns({
7912
8041
  environment: opts.env,
7913
8042
  status: opts.status,
7914
8043
  triggerSlug: slug,
7915
8044
  limit: parseInt(opts.limit, 10)
7916
8045
  });
7917
- spinner.succeed(`Found ${runs.length} runs`);
8046
+ spinner?.succeed(`Found ${runs.length} runs`);
7918
8047
  if (opts.json) {
7919
8048
  console.log(JSON.stringify(runs, null, 2));
7920
8049
  return;
@@ -7940,7 +8069,7 @@ triggersCommand.command("runs [slug]").description("List trigger runs").option("
7940
8069
  console.log();
7941
8070
  } catch (err) {
7942
8071
  const message = err instanceof Error ? err.message : String(err);
7943
- spinner.fail("Failed to fetch runs");
8072
+ spinner?.fail("Failed to fetch runs");
7944
8073
  if (opts.json) {
7945
8074
  console.log(JSON.stringify({ success: false, error: message }));
7946
8075
  } else {
@@ -7951,18 +8080,18 @@ triggersCommand.command("runs [slug]").description("List trigger runs").option("
7951
8080
  });
7952
8081
  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) => {
7953
8082
  await ensureAuth5();
7954
- const spinner = ora14();
8083
+ const spinner = opts.json ? null : ora14();
7955
8084
  try {
7956
- spinner.start("Fetching run");
8085
+ spinner?.start("Fetching run");
7957
8086
  const run = await getTriggerRunDetail(runId, opts.env);
7958
8087
  if (!run) {
7959
- spinner.fail("Run not found");
8088
+ spinner?.fail("Run not found");
7960
8089
  if (opts.json) {
7961
8090
  console.log(JSON.stringify({ success: false, error: "Run not found" }));
7962
8091
  }
7963
8092
  process.exit(1);
7964
8093
  }
7965
- spinner.succeed("Run loaded");
8094
+ spinner?.succeed("Run loaded");
7966
8095
  if (opts.json) {
7967
8096
  console.log(JSON.stringify(run, null, 2));
7968
8097
  return;
@@ -7985,7 +8114,7 @@ triggersCommand.command("run <run-id>").description("View trigger run details").
7985
8114
  console.log();
7986
8115
  } catch (err) {
7987
8116
  const message = err instanceof Error ? err.message : String(err);
7988
- spinner.fail("Failed to fetch run");
8117
+ spinner?.fail("Failed to fetch run");
7989
8118
  if (opts.json) {
7990
8119
  console.log(JSON.stringify({ success: false, error: message }));
7991
8120
  } else {
@@ -7996,11 +8125,11 @@ triggersCommand.command("run <run-id>").description("View trigger run details").
7996
8125
  });
7997
8126
  triggersCommand.command("stats").description("Show trigger run statistics").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (opts) => {
7998
8127
  await ensureAuth5();
7999
- const spinner = ora14();
8128
+ const spinner = opts.json ? null : ora14();
8000
8129
  try {
8001
- spinner.start("Fetching statistics");
8130
+ spinner?.start("Fetching statistics");
8002
8131
  const stats = await getTriggerRunStats(opts.env);
8003
- spinner.succeed("Run statistics");
8132
+ spinner?.succeed("Run statistics");
8004
8133
  if (opts.json) {
8005
8134
  console.log(JSON.stringify(stats, null, 2));
8006
8135
  return;
@@ -8039,7 +8168,7 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
8039
8168
  console.log();
8040
8169
  } catch (err) {
8041
8170
  const message = err instanceof Error ? err.message : String(err);
8042
- spinner.fail("Failed to fetch statistics");
8171
+ spinner?.fail("Failed to fetch statistics");
8043
8172
  if (opts.json) {
8044
8173
  console.log(JSON.stringify({ success: false, error: message }));
8045
8174
  } else {
@@ -8050,15 +8179,15 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
8050
8179
  });
8051
8180
  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) => {
8052
8181
  await ensureAuth5();
8053
- const spinner = ora14();
8182
+ const spinner = opts.json ? null : ora14();
8054
8183
  try {
8055
- spinner.start("Fetching execution logs");
8184
+ spinner?.start("Fetching execution logs");
8056
8185
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8057
8186
  environment: opts.env,
8058
8187
  triggerSlug: slug,
8059
8188
  limit: parseInt(opts.limit, 10)
8060
8189
  }));
8061
- spinner.succeed(`Found ${executions.length} executions`);
8190
+ spinner?.succeed(`Found ${executions.length} executions`);
8062
8191
  if (opts.json) {
8063
8192
  console.log(JSON.stringify(executions, null, 2));
8064
8193
  return;
@@ -8103,7 +8232,7 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
8103
8232
  console.log();
8104
8233
  } catch (err) {
8105
8234
  const message = err instanceof Error ? err.message : String(err);
8106
- spinner.fail("Failed to fetch execution logs");
8235
+ spinner?.fail("Failed to fetch execution logs");
8107
8236
  if (opts.json) {
8108
8237
  console.log(JSON.stringify({ success: false, error: message }));
8109
8238
  } else {
@@ -8114,7 +8243,7 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
8114
8243
  });
8115
8244
  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) => {
8116
8245
  await ensureAuth5();
8117
- const spinner = ora14();
8246
+ const spinner = opts.json ? null : ora14();
8118
8247
  try {
8119
8248
  const nth = parseInt(opts.nth, 10);
8120
8249
  if (isNaN(nth) || nth < 1) {
@@ -8124,14 +8253,14 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8124
8253
  let eventId = identifier;
8125
8254
  const isConvexId = /^[0-9a-zA-Z]{20,}$/.test(identifier);
8126
8255
  if (!isConvexId) {
8127
- spinner.start("Resolving trigger slug to latest execution");
8256
+ spinner?.start("Resolving trigger slug to latest execution");
8128
8257
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8129
8258
  environment: opts.env,
8130
8259
  triggerSlug: identifier,
8131
8260
  limit: nth
8132
8261
  }));
8133
8262
  if (!executions.length) {
8134
- spinner.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
8263
+ spinner?.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
8135
8264
  if (opts.json) {
8136
8265
  console.log(JSON.stringify({ success: false, error: `No executions found for trigger "${identifier}"` }));
8137
8266
  }
@@ -8139,22 +8268,22 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8139
8268
  }
8140
8269
  const idx = nth - 1;
8141
8270
  if (idx >= executions.length) {
8142
- spinner.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
8271
+ spinner?.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
8143
8272
  process.exit(1);
8144
8273
  }
8145
8274
  eventId = executions[idx]._id;
8146
- spinner.succeed(`Found execution for "${identifier}"`);
8275
+ spinner?.succeed(`Found execution for "${identifier}"`);
8147
8276
  }
8148
- spinner.start("Fetching execution detail");
8277
+ spinner?.start("Fetching execution detail");
8149
8278
  const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env));
8150
8279
  if (!event) {
8151
- spinner.fail("Execution not found");
8280
+ spinner?.fail("Execution not found");
8152
8281
  if (opts.json) {
8153
8282
  console.log(JSON.stringify({ success: false, error: "Execution not found" }));
8154
8283
  }
8155
8284
  process.exit(1);
8156
8285
  }
8157
- spinner.succeed("Execution loaded");
8286
+ spinner?.succeed("Execution loaded");
8158
8287
  if (opts.json) {
8159
8288
  console.log(JSON.stringify(event, null, 2));
8160
8289
  return;
@@ -8184,7 +8313,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8184
8313
  }
8185
8314
  } catch (err) {
8186
8315
  const message = err instanceof Error ? err.message : String(err);
8187
- spinner.fail("Failed to fetch execution detail");
8316
+ spinner?.fail("Failed to fetch execution detail");
8188
8317
  if (opts.json) {
8189
8318
  console.log(JSON.stringify({ success: false, error: message }));
8190
8319
  } else {
@@ -8195,7 +8324,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8195
8324
  });
8196
8325
  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) => {
8197
8326
  await ensureAuth5();
8198
- const spinner = ora14();
8327
+ const spinner = opts.json ? null : ora14();
8199
8328
  const environment = opts.env;
8200
8329
  if (environment === "production" && !opts.confirm && isInteractive()) {
8201
8330
  const readline = await import("readline");
@@ -8207,15 +8336,15 @@ triggersCommand.command("retry <run-id>").description("Retry a failed or dead ru
8207
8336
  rl.close();
8208
8337
  }
8209
8338
  try {
8210
- spinner.start("Retrying run...");
8339
+ spinner?.start("Retrying run...");
8211
8340
  await retryTriggerRun(runId, environment);
8212
- spinner.succeed(chalk20.green(`Run ${runId} queued for retry`));
8341
+ spinner?.succeed(chalk20.green(`Run ${runId} queued for retry`));
8213
8342
  if (opts.json) {
8214
8343
  console.log(JSON.stringify({ success: true, runId }));
8215
8344
  }
8216
8345
  } catch (err) {
8217
8346
  const message = err instanceof Error ? err.message : String(err);
8218
- spinner.fail("Failed to retry run");
8347
+ spinner?.fail("Failed to retry run");
8219
8348
  if (opts.json) {
8220
8349
  console.log(JSON.stringify({ success: false, error: message }));
8221
8350
  } else {
@@ -8226,7 +8355,7 @@ triggersCommand.command("retry <run-id>").description("Retry a failed or dead ru
8226
8355
  });
8227
8356
  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) => {
8228
8357
  await ensureAuth5();
8229
- const spinner = ora14();
8358
+ const spinner = opts.json ? null : ora14();
8230
8359
  const environment = opts.env;
8231
8360
  if (environment === "production" && !opts.confirm && isInteractive()) {
8232
8361
  const readline = await import("readline");
@@ -8238,15 +8367,15 @@ triggersCommand.command("cancel <run-id>").description("Cancel a pending run").o
8238
8367
  rl.close();
8239
8368
  }
8240
8369
  try {
8241
- spinner.start("Cancelling run...");
8370
+ spinner?.start("Cancelling run...");
8242
8371
  await cancelTriggerRun(runId, environment);
8243
- spinner.succeed(chalk20.green(`Run ${runId} cancelled`));
8372
+ spinner?.succeed(chalk20.green(`Run ${runId} cancelled`));
8244
8373
  if (opts.json) {
8245
8374
  console.log(JSON.stringify({ success: true, runId }));
8246
8375
  }
8247
8376
  } catch (err) {
8248
8377
  const message = err instanceof Error ? err.message : String(err);
8249
- spinner.fail("Failed to cancel run");
8378
+ spinner?.fail("Failed to cancel run");
8250
8379
  if (opts.json) {
8251
8380
  console.log(JSON.stringify({ success: false, error: message }));
8252
8381
  } else {
@@ -8257,7 +8386,7 @@ triggersCommand.command("cancel <run-id>").description("Cancel a pending run").o
8257
8386
  });
8258
8387
  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) => {
8259
8388
  await ensureAuth5();
8260
- const spinner = ora14();
8389
+ const spinner = opts.json ? null : ora14();
8261
8390
  const environment = opts.env;
8262
8391
  if (environment === "production" && !opts.confirm && isInteractive()) {
8263
8392
  const readline = await import("readline");
@@ -8269,15 +8398,15 @@ triggersCommand.command("enable <slug>").description("Enable a trigger").option(
8269
8398
  rl.close();
8270
8399
  }
8271
8400
  try {
8272
- spinner.start("Enabling trigger...");
8401
+ spinner?.start("Enabling trigger...");
8273
8402
  await toggleTrigger(slug, true, environment);
8274
- spinner.succeed(chalk20.green(`Trigger ${slug} enabled`));
8403
+ spinner?.succeed(chalk20.green(`Trigger ${slug} enabled`));
8275
8404
  if (opts.json) {
8276
8405
  console.log(JSON.stringify({ success: true, slug }));
8277
8406
  }
8278
8407
  } catch (err) {
8279
8408
  const message = err instanceof Error ? err.message : String(err);
8280
- spinner.fail("Failed to enable trigger");
8409
+ spinner?.fail("Failed to enable trigger");
8281
8410
  if (opts.json) {
8282
8411
  console.log(JSON.stringify({ success: false, error: message }));
8283
8412
  } else {
@@ -8288,7 +8417,7 @@ triggersCommand.command("enable <slug>").description("Enable a trigger").option(
8288
8417
  });
8289
8418
  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) => {
8290
8419
  await ensureAuth5();
8291
- const spinner = ora14();
8420
+ const spinner = opts.json ? null : ora14();
8292
8421
  const environment = opts.env;
8293
8422
  if (environment === "production" && !opts.confirm && isInteractive()) {
8294
8423
  const readline = await import("readline");
@@ -8300,15 +8429,15 @@ triggersCommand.command("disable <slug>").description("Disable a trigger").optio
8300
8429
  rl.close();
8301
8430
  }
8302
8431
  try {
8303
- spinner.start("Disabling trigger...");
8432
+ spinner?.start("Disabling trigger...");
8304
8433
  await toggleTrigger(slug, false, environment);
8305
- spinner.succeed(chalk20.green(`Trigger ${slug} disabled`));
8434
+ spinner?.succeed(chalk20.green(`Trigger ${slug} disabled`));
8306
8435
  if (opts.json) {
8307
8436
  console.log(JSON.stringify({ success: true, slug }));
8308
8437
  }
8309
8438
  } catch (err) {
8310
8439
  const message = err instanceof Error ? err.message : String(err);
8311
- spinner.fail("Failed to disable trigger");
8440
+ spinner?.fail("Failed to disable trigger");
8312
8441
  if (opts.json) {
8313
8442
  console.log(JSON.stringify({ success: false, error: message }));
8314
8443
  } else {
@@ -8319,7 +8448,7 @@ triggersCommand.command("disable <slug>").description("Disable a trigger").optio
8319
8448
  });
8320
8449
  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) => {
8321
8450
  await ensureAuth5();
8322
- const spinner = ora14();
8451
+ const spinner = opts.json ? null : ora14();
8323
8452
  const environment = opts.env;
8324
8453
  if (environment === "production" && !opts.confirm && isInteractive()) {
8325
8454
  const readline = await import("readline");
@@ -8331,19 +8460,19 @@ triggersCommand.command("retry-event <event-id>").description("Retry a failed im
8331
8460
  rl.close();
8332
8461
  }
8333
8462
  try {
8334
- spinner.start("Retrying failed execution...");
8463
+ spinner?.start("Retrying failed execution...");
8335
8464
  const result = await retryImmediateExecution(eventId, environment);
8336
8465
  if (result.success) {
8337
- spinner.succeed(chalk20.green(`Execution retried successfully`));
8466
+ spinner?.succeed(chalk20.green(`Execution retried successfully`));
8338
8467
  } else {
8339
- spinner.fail(chalk20.red(`Execution retry failed again`));
8468
+ spinner?.fail(chalk20.red(`Execution retry failed again`));
8340
8469
  }
8341
8470
  if (opts.json) {
8342
8471
  console.log(JSON.stringify(result));
8343
8472
  }
8344
8473
  } catch (err) {
8345
8474
  const message = err instanceof Error ? err.message : String(err);
8346
- spinner.fail("Failed to retry execution");
8475
+ spinner?.fail("Failed to retry execution");
8347
8476
  if (opts.json) {
8348
8477
  console.log(JSON.stringify({ success: false, error: message }));
8349
8478
  } else {
@@ -8354,7 +8483,7 @@ triggersCommand.command("retry-event <event-id>").description("Retry a failed im
8354
8483
  });
8355
8484
  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) => {
8356
8485
  await ensureAuth5();
8357
- const spinner = ora14();
8486
+ const spinner = opts.json ? null : ora14();
8358
8487
  const environment = opts.env;
8359
8488
  if (environment === "production" && !opts.confirm && isInteractive()) {
8360
8489
  const readline = await import("readline");
@@ -8375,7 +8504,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8375
8504
  }
8376
8505
  }
8377
8506
  try {
8378
- spinner.start(`Firing trigger ${chalk20.cyan(slug)}...`);
8507
+ spinner?.start(`Firing trigger ${chalk20.cyan(slug)}...`);
8379
8508
  const { result, error } = await fireTrigger({
8380
8509
  slug,
8381
8510
  environment,
@@ -8383,7 +8512,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8383
8512
  data
8384
8513
  });
8385
8514
  if (error) {
8386
- spinner.fail("Trigger execution failed");
8515
+ spinner?.fail("Trigger execution failed");
8387
8516
  if (opts.json) {
8388
8517
  console.log(JSON.stringify({ success: false, error }));
8389
8518
  } else {
@@ -8392,13 +8521,13 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8392
8521
  process.exit(1);
8393
8522
  }
8394
8523
  if (!result) {
8395
- spinner.fail("No result returned");
8524
+ spinner?.fail("No result returned");
8396
8525
  process.exit(1);
8397
8526
  }
8398
8527
  if (result.success) {
8399
- spinner.succeed(chalk20.green(`Trigger ${chalk20.cyan(slug)} fired successfully`));
8528
+ spinner?.succeed(chalk20.green(`Trigger ${chalk20.cyan(slug)} fired successfully`));
8400
8529
  } else {
8401
- spinner.fail(chalk20.red(`Trigger ${chalk20.cyan(slug)} execution failed`));
8530
+ spinner?.fail(chalk20.red(`Trigger ${chalk20.cyan(slug)} execution failed`));
8402
8531
  }
8403
8532
  if (opts.json) {
8404
8533
  console.log(JSON.stringify(result, null, 2));
@@ -8416,7 +8545,7 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8416
8545
  }
8417
8546
  } catch (err) {
8418
8547
  const message = err instanceof Error ? err.message : String(err);
8419
- spinner.fail("Failed to fire trigger");
8548
+ spinner?.fail("Failed to fire trigger");
8420
8549
  if (opts.json) {
8421
8550
  console.log(JSON.stringify({ success: false, error: message }));
8422
8551
  } else {
@@ -8654,6 +8783,8 @@ function channelColor(channel) {
8654
8783
  return chalk21.magenta(channel);
8655
8784
  case "dashboard":
8656
8785
  return chalk21.cyan(channel);
8786
+ case "voice":
8787
+ return chalk21.yellow(channel);
8657
8788
  default:
8658
8789
  return chalk21.gray(channel ?? "-");
8659
8790
  }
@@ -8673,17 +8804,17 @@ function roleColor(role) {
8673
8804
  }
8674
8805
  }
8675
8806
  var threadsCommand = new Command19("threads").description("Manage conversation threads");
8676
- 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) => {
8807
+ 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) => {
8677
8808
  await ensureAuth6();
8678
- const spinner = ora15();
8809
+ const spinner = opts.json ? null : ora15();
8679
8810
  try {
8680
- spinner.start("Fetching threads");
8811
+ spinner?.start("Fetching threads");
8681
8812
  const threads = await listThreads({
8682
8813
  environment: opts.env,
8683
8814
  channel: opts.channel,
8684
8815
  limit: parseInt(opts.limit, 10)
8685
8816
  });
8686
- spinner.succeed(`Found ${threads.length} threads`);
8817
+ spinner?.succeed(`Found ${threads.length} threads`);
8687
8818
  if (opts.json) {
8688
8819
  console.log(JSON.stringify(threads, null, 2));
8689
8820
  return;
@@ -8707,7 +8838,7 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
8707
8838
  console.log();
8708
8839
  } catch (err) {
8709
8840
  const message = err instanceof Error ? err.message : String(err);
8710
- spinner.fail("Failed to fetch threads");
8841
+ spinner?.fail("Failed to fetch threads");
8711
8842
  if (opts.json) {
8712
8843
  console.log(JSON.stringify({ success: false, error: message }));
8713
8844
  } else {
@@ -8718,21 +8849,21 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
8718
8849
  });
8719
8850
  threadsCommand.command("view <id>").description("View thread details and messages").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (id, opts) => {
8720
8851
  await ensureAuth6();
8721
- const spinner = ora15();
8852
+ const spinner = opts.json ? null : ora15();
8722
8853
  try {
8723
- spinner.start("Fetching thread");
8854
+ spinner?.start("Fetching thread");
8724
8855
  const thread = await getThreadWithMessages({
8725
8856
  threadId: id,
8726
8857
  environment: opts.env
8727
8858
  });
8728
8859
  if (!thread) {
8729
- spinner.fail("Thread not found");
8860
+ spinner?.fail("Thread not found");
8730
8861
  if (opts.json) {
8731
8862
  console.log(JSON.stringify({ success: false, error: "Thread not found" }));
8732
8863
  }
8733
8864
  process.exit(1);
8734
8865
  }
8735
- spinner.succeed("Thread loaded");
8866
+ spinner?.succeed("Thread loaded");
8736
8867
  if (opts.json) {
8737
8868
  console.log(JSON.stringify(thread, null, 2));
8738
8869
  return;
@@ -8765,7 +8896,7 @@ threadsCommand.command("view <id>").description("View thread details and message
8765
8896
  console.log();
8766
8897
  } catch (err) {
8767
8898
  const message = err instanceof Error ? err.message : String(err);
8768
- spinner.fail("Failed to fetch thread");
8899
+ spinner?.fail("Failed to fetch thread");
8769
8900
  if (opts.json) {
8770
8901
  console.log(JSON.stringify({ success: false, error: message }));
8771
8902
  } else {
@@ -8776,7 +8907,7 @@ threadsCommand.command("view <id>").description("View thread details and message
8776
8907
  });
8777
8908
  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) => {
8778
8909
  await ensureAuth6();
8779
- const spinner = ora15();
8910
+ const spinner = opts.json ? null : ora15();
8780
8911
  const environment = opts.env;
8781
8912
  if (environment === "production" && !opts.confirm && isInteractive()) {
8782
8913
  const readline = await import("readline");
@@ -8788,18 +8919,18 @@ threadsCommand.command("archive <id>").description("Archive a thread (frees its
8788
8919
  rl.close();
8789
8920
  }
8790
8921
  try {
8791
- spinner.start("Archiving thread...");
8922
+ spinner?.start("Archiving thread...");
8792
8923
  const result = await archiveThread({
8793
8924
  threadId: id,
8794
8925
  environment
8795
8926
  });
8796
- spinner.succeed(chalk21.green(`Thread ${id} archived`));
8927
+ spinner?.succeed(chalk21.green(`Thread ${id} archived`));
8797
8928
  if (opts.json) {
8798
8929
  console.log(JSON.stringify({ success: true, threadId: id }));
8799
8930
  }
8800
8931
  } catch (err) {
8801
8932
  const message = err instanceof Error ? err.message : String(err);
8802
- spinner.fail("Failed to archive thread");
8933
+ spinner?.fail("Failed to archive thread");
8803
8934
  if (opts.json) {
8804
8935
  console.log(JSON.stringify({ success: false, error: message }));
8805
8936
  } else {
@@ -8810,7 +8941,7 @@ threadsCommand.command("archive <id>").description("Archive a thread (frees its
8810
8941
  });
8811
8942
  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) => {
8812
8943
  await ensureAuth6();
8813
- const spinner = ora15();
8944
+ const spinner = opts.json ? null : ora15();
8814
8945
  const environment = opts.env;
8815
8946
  if (environment === "production" && !opts.confirm && isInteractive()) {
8816
8947
  const readline = await import("readline");
@@ -8822,30 +8953,31 @@ threadsCommand.command("reset").description("Archive a thread by phone number").
8822
8953
  rl.close();
8823
8954
  }
8824
8955
  try {
8825
- spinner.start(`Finding thread for phone ${opts.phone}...`);
8956
+ spinner?.start(`Finding thread for phone ${opts.phone}...`);
8826
8957
  const thread = await findThreadByPhone({
8827
8958
  phone: opts.phone,
8828
8959
  environment
8829
8960
  });
8830
8961
  if (!thread) {
8831
- spinner.fail(`No thread found for phone number ${opts.phone}`);
8962
+ spinner?.fail(`No thread found for phone number ${opts.phone}`);
8832
8963
  if (opts.json) {
8833
8964
  console.log(JSON.stringify({ success: false, error: "Thread not found for phone number" }));
8834
8965
  }
8835
8966
  process.exit(1);
8836
8967
  }
8837
- spinner.text = `Archiving thread ${thread._id}...`;
8968
+ if (spinner)
8969
+ spinner.text = `Archiving thread ${thread._id}...`;
8838
8970
  await archiveThread({
8839
8971
  threadId: thread._id,
8840
8972
  environment
8841
8973
  });
8842
- spinner.succeed(chalk21.green(`Thread ${thread._id} archived (phone: ${opts.phone})`));
8974
+ spinner?.succeed(chalk21.green(`Thread ${thread._id} archived (phone: ${opts.phone})`));
8843
8975
  if (opts.json) {
8844
8976
  console.log(JSON.stringify({ success: true, threadId: thread._id, phone: opts.phone }));
8845
8977
  }
8846
8978
  } catch (err) {
8847
8979
  const message = err instanceof Error ? err.message : String(err);
8848
- spinner.fail("Failed to reset thread");
8980
+ spinner?.fail("Failed to reset thread");
8849
8981
  if (opts.json) {
8850
8982
  console.log(JSON.stringify({ success: false, error: message }));
8851
8983
  } else {
@@ -9557,7 +9689,7 @@ var whatsappCommand = new Command23("whatsapp").description("Manage WhatsApp con
9557
9689
  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) => {
9558
9690
  await ensureAuth7();
9559
9691
  const env = opts.env;
9560
- const out = createOutput();
9692
+ const out = opts.json ? createSilentOutput() : createOutput();
9561
9693
  out.start("Fetching WhatsApp connections");
9562
9694
  const { data, error } = await listWhatsAppConnections(env);
9563
9695
  if (error || !data) {
@@ -10503,14 +10635,14 @@ async function runCheck(fn) {
10503
10635
  }
10504
10636
  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) => {
10505
10637
  await ensureAuth8();
10506
- const spinner = ora20();
10638
+ const spinner = opts.json ? null : ora20();
10507
10639
  const cwd = process.cwd();
10508
10640
  const environment = opts.env;
10509
10641
  try {
10510
- spinner.start("Loading resources...");
10642
+ spinner?.start("Loading resources...");
10511
10643
  const resources = await loadAllResources(cwd);
10512
- spinner.succeed("Resources loaded");
10513
- spinner.start("Running diagnostics...");
10644
+ spinner?.succeed("Resources loaded");
10645
+ spinner?.start("Running diagnostics...");
10514
10646
  const results = await Promise.allSettled([
10515
10647
  runCheck(() => checkWhatsAppConnection(resources)),
10516
10648
  runCheck(() => checkTemplateApprovals(resources)),
@@ -10520,7 +10652,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10520
10652
  runCheck(() => checkTriggerHealth(environment)),
10521
10653
  runCheck(() => checkSyncDrift(resources, environment))
10522
10654
  ]);
10523
- spinner.stop();
10655
+ spinner?.stop();
10524
10656
  const checks = results.map((r) => r.status === "fulfilled" ? r.value : { id: "unknown", label: "Unknown", status: "warn", message: `Check failed: ${r.reason}` });
10525
10657
  const summary = {
10526
10658
  ok: checks.filter((c) => c.status === "ok").length,
@@ -10563,7 +10695,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10563
10695
  }
10564
10696
  } catch (err) {
10565
10697
  const message = err instanceof Error ? err.message : String(err);
10566
- spinner.fail("Diagnostics failed");
10698
+ spinner?.fail("Diagnostics failed");
10567
10699
  if (opts.json) {
10568
10700
  console.log(JSON.stringify({ success: false, error: message }));
10569
10701
  } else {
@@ -10575,7 +10707,7 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10575
10707
  // package.json
10576
10708
  var package_default = {
10577
10709
  name: "struere",
10578
- version: "0.14.0",
10710
+ version: "0.14.2",
10579
10711
  description: "Build, test, and deploy AI agents",
10580
10712
  keywords: [
10581
10713
  "ai",