struere 0.14.2 → 0.14.3

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.
@@ -680,7 +680,8 @@ async function _fireTrigger(options) {
680
680
  slug: options.slug,
681
681
  environment: options.environment,
682
682
  entityId: options.entityId,
683
- data: options.data
683
+ data: options.data,
684
+ ...options.organizationId && { organizationId: options.organizationId }
684
685
  }
685
686
  }),
686
687
  signal: AbortSignal.timeout(60000)
@@ -3633,7 +3634,10 @@ var devCommand = new Command6("dev").description("Watch files and sync to develo
3633
3634
  const apiKey = getApiKey();
3634
3635
  const nonInteractive = !isInteractive();
3635
3636
  if (nonInteractive) {
3636
- console.error("Error: struere dev is a long-running watch process. Use struere sync instead.");
3637
+ console.error(chalk7.red("Error:"), "`struere dev` requires an interactive terminal (TTY) to render its watch UI.");
3638
+ console.error(chalk7.gray(" - For a one-shot sync, use:"), chalk7.cyan("struere sync [--env development]"));
3639
+ console.error(chalk7.gray(" - For continuous deployment in CI/CD, use:"), chalk7.cyan("struere deploy"));
3640
+ console.error(chalk7.gray(" - To run dev in the foreground, attach a TTY (avoid backgrounding with `&` or piping output)."));
3637
3641
  process.exit(1);
3638
3642
  }
3639
3643
  console.log();
@@ -5849,7 +5853,8 @@ async function queryThreads(options) {
5849
5853
  environment: options.environment,
5850
5854
  ...options.agentId && { agentId: options.agentId },
5851
5855
  ...options.channel && { channel: options.channel },
5852
- ...options.limit && { limit: options.limit }
5856
+ ...options.limit && { limit: options.limit },
5857
+ ...options.organizationId && { organizationId: options.organizationId }
5853
5858
  });
5854
5859
  if (result.error || !result.data)
5855
5860
  return result;
@@ -5865,20 +5870,21 @@ async function queryThreads(options) {
5865
5870
  }
5866
5871
  return result;
5867
5872
  }
5868
- async function queryThreadDetail(threadId, messageLimit) {
5873
+ async function queryThreadDetail(threadId, messageLimit, organizationId) {
5869
5874
  return convexQuery2("threads:getWithMessages", {
5870
5875
  id: threadId,
5871
- ...messageLimit && { messageLimit }
5876
+ ...messageLimit && { messageLimit },
5877
+ ...organizationId && { organizationId }
5872
5878
  });
5873
5879
  }
5874
- async function queryThreadExecutions(threadId) {
5875
- return convexQuery2("executions:getByThread", { threadId });
5880
+ async function queryThreadExecutions(threadId, organizationId) {
5881
+ return convexQuery2("executions:getByThread", { threadId, ...organizationId && { organizationId } });
5876
5882
  }
5877
- async function resolveThreadId(shortId, environment) {
5883
+ async function resolveThreadId(shortId, environment, organizationId) {
5878
5884
  if (shortId.includes("|") || shortId.length > 20) {
5879
5885
  return { data: shortId };
5880
5886
  }
5881
- const result = await queryThreads({ environment: environment ?? "development", limit: 100 });
5887
+ const result = await queryThreads({ environment: environment ?? "development", limit: 100, organizationId });
5882
5888
  if (result.error || !result.data)
5883
5889
  return { error: result.error ?? "Could not fetch threads" };
5884
5890
  const threads = result.data;
@@ -5887,7 +5893,7 @@ async function resolveThreadId(shortId, environment) {
5887
5893
  for (const env of ["production", "development", "eval"]) {
5888
5894
  if (env === (environment ?? "development"))
5889
5895
  continue;
5890
- const envResult = await queryThreads({ environment: env, limit: 100 });
5896
+ const envResult = await queryThreads({ environment: env, limit: 100, organizationId });
5891
5897
  if (envResult.data) {
5892
5898
  const envThreads = envResult.data;
5893
5899
  const envMatch = envThreads.find((t) => t._id.endsWith(shortId));
@@ -5989,7 +5995,8 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
5989
5995
  agentId,
5990
5996
  channel: opts.channel,
5991
5997
  phone: opts.phone,
5992
- limit: parseInt(opts.limit, 10)
5998
+ limit: parseInt(opts.limit, 10),
5999
+ organizationId: orgId
5993
6000
  });
5994
6001
  if (error || !data) {
5995
6002
  spinner?.fail("Failed to fetch conversations");
@@ -6026,8 +6033,9 @@ logsCommand.command("list", { isDefault: true }).description("List recent conver
6026
6033
  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) => {
6027
6034
  await ensureAuth2();
6028
6035
  const spinner = opts.json ? null : ora12();
6036
+ const orgId = getOrgId2();
6029
6037
  spinner?.start("Resolving thread");
6030
- const resolved = await resolveThreadId(rawThreadId, opts.env);
6038
+ const resolved = await resolveThreadId(rawThreadId, opts.env, orgId);
6031
6039
  if (resolved.error || !resolved.data) {
6032
6040
  spinner?.fail("Thread not found");
6033
6041
  console.log(chalk16.red("Error:"), resolved.error || `No thread matched "${rawThreadId}"`);
@@ -6037,7 +6045,7 @@ logsCommand.command("view <thread-id>").description("View conversation messages"
6037
6045
  spinner?.stop();
6038
6046
  spinner?.start("Fetching conversation");
6039
6047
  const fetchLimit = opts.tail ? 1000 : parseInt(opts.limit, 10);
6040
- const { data, error } = await queryThreadDetail(threadId, fetchLimit);
6048
+ const { data, error } = await queryThreadDetail(threadId, fetchLimit, orgId);
6041
6049
  if (error || !data) {
6042
6050
  spinner?.fail("Failed to fetch conversation");
6043
6051
  console.log(chalk16.red("Error:"), error || "Thread not found");
@@ -6047,7 +6055,7 @@ logsCommand.command("view <thread-id>").description("View conversation messages"
6047
6055
  const result = data;
6048
6056
  let executions = [];
6049
6057
  if (opts.exec) {
6050
- const execResult = await queryThreadExecutions(threadId);
6058
+ const execResult = await queryThreadExecutions(threadId, orgId);
6051
6059
  if (execResult.data) {
6052
6060
  executions = execResult.data;
6053
6061
  }
@@ -7666,51 +7674,54 @@ async function convexMutation5(path, args) {
7666
7674
  }
7667
7675
  return json.value;
7668
7676
  }
7669
- async function listTriggers(environment) {
7670
- return convexQuery6("triggers:list", { environment });
7677
+ async function listTriggers(environment, organizationId) {
7678
+ return convexQuery6("triggers:list", { environment, ...organizationId && { organizationId } });
7671
7679
  }
7672
- async function getTrigger(slug, environment) {
7673
- return convexQuery6("triggers:getBySlug", { slug, environment });
7680
+ async function getTrigger(slug, environment, organizationId) {
7681
+ return convexQuery6("triggers:getBySlug", { slug, environment, ...organizationId && { organizationId } });
7674
7682
  }
7675
7683
  async function listTriggerRuns(options) {
7676
7684
  return convexQuery6("triggers:listRuns", {
7677
7685
  environment: options.environment,
7678
7686
  ...options.status && { status: options.status },
7679
7687
  ...options.triggerSlug && { triggerSlug: options.triggerSlug },
7680
- ...options.limit && { limit: options.limit }
7688
+ ...options.limit && { limit: options.limit },
7689
+ ...options.organizationId && { organizationId: options.organizationId }
7681
7690
  });
7682
7691
  }
7683
- async function getTriggerRunDetail(runId, environment) {
7692
+ async function getTriggerRunDetail(runId, environment, organizationId) {
7684
7693
  return convexQuery6("triggers:getRunDetail", {
7685
7694
  runId,
7686
- ...environment && { environment }
7695
+ ...environment && { environment },
7696
+ ...organizationId && { organizationId }
7687
7697
  });
7688
7698
  }
7689
- async function getTriggerRunStats(environment) {
7690
- return convexQuery6("triggers:getRunStats", { environment });
7699
+ async function getTriggerRunStats(environment, organizationId) {
7700
+ return convexQuery6("triggers:getRunStats", { environment, ...organizationId && { organizationId } });
7691
7701
  }
7692
- async function getLastRunStatuses(environment) {
7693
- return convexQuery6("triggers:getLastRunStatuses", { environment });
7702
+ async function getLastRunStatuses(environment, organizationId) {
7703
+ return convexQuery6("triggers:getLastRunStatuses", { environment, ...organizationId && { organizationId } });
7694
7704
  }
7695
- async function cancelTriggerRun(runId, environment) {
7696
- return convexMutation5("triggers:cancelRun", { runId, environment });
7705
+ async function cancelTriggerRun(runId, environment, organizationId) {
7706
+ return convexMutation5("triggers:cancelRun", { runId, environment, ...organizationId && { organizationId } });
7697
7707
  }
7698
- async function retryTriggerRun(runId, environment) {
7699
- return convexMutation5("triggers:retryRun", { runId, environment });
7708
+ async function retryTriggerRun(runId, environment, organizationId) {
7709
+ return convexMutation5("triggers:retryRun", { runId, environment, ...organizationId && { organizationId } });
7700
7710
  }
7701
- async function toggleTrigger(slug, enabled, environment) {
7711
+ async function toggleTrigger(slug, enabled, environment, organizationId) {
7702
7712
  const path = enabled ? "triggers:enable" : "triggers:disable";
7703
- return convexMutation5(path, { slug, environment });
7713
+ return convexMutation5(path, { slug, environment, ...organizationId && { organizationId } });
7704
7714
  }
7705
7715
  async function listTriggerExecutions(options) {
7706
7716
  return convexQuery6("triggers:listExecutions", {
7707
7717
  environment: options.environment,
7708
7718
  ...options.triggerSlug && { triggerSlug: options.triggerSlug },
7709
- ...options.limit && { limit: options.limit }
7719
+ ...options.limit && { limit: options.limit },
7720
+ ...options.organizationId && { organizationId: options.organizationId }
7710
7721
  });
7711
7722
  }
7712
- async function getTriggerExecutionDetail(eventId, environment) {
7713
- return convexQuery6("triggers:getExecutionDetail", { eventId, ...environment && { environment } });
7723
+ async function getTriggerExecutionDetail(eventId, environment, organizationId) {
7724
+ return convexQuery6("triggers:getExecutionDetail", { eventId, ...environment && { environment }, ...organizationId && { organizationId } });
7714
7725
  }
7715
7726
  async function convexAction3(path, args) {
7716
7727
  const token = getToken3();
@@ -7737,11 +7748,15 @@ async function convexAction3(path, args) {
7737
7748
  }
7738
7749
  return json.value;
7739
7750
  }
7740
- async function retryImmediateExecution(eventId, environment) {
7741
- return convexAction3("triggers:retryImmediateExecution", { eventId, environment });
7751
+ async function retryImmediateExecution(eventId, environment, organizationId) {
7752
+ return convexAction3("triggers:retryImmediateExecution", { eventId, environment, ...organizationId && { organizationId } });
7742
7753
  }
7743
7754
 
7744
7755
  // src/cli/commands/triggers.ts
7756
+ function getOrgId3() {
7757
+ const project = loadProject(process.cwd());
7758
+ return project?.organization.id;
7759
+ }
7745
7760
  async function withTriggerAuthRetry(fn) {
7746
7761
  try {
7747
7762
  return await fn();
@@ -7909,10 +7924,11 @@ triggersCommand.command("list", { isDefault: true }).description("List all trigg
7909
7924
  await ensureAuth5();
7910
7925
  const spinner = opts.json ? null : ora14();
7911
7926
  try {
7927
+ const orgId = getOrgId3();
7912
7928
  spinner?.start("Fetching triggers");
7913
7929
  const [triggers, statuses] = await Promise.all([
7914
- listTriggers(opts.env),
7915
- getLastRunStatuses(opts.env)
7930
+ listTriggers(opts.env, orgId),
7931
+ getLastRunStatuses(opts.env, orgId)
7916
7932
  ]);
7917
7933
  let filtered = triggers;
7918
7934
  if (opts.failed) {
@@ -7958,7 +7974,7 @@ triggersCommand.command("get <slug>").description("View trigger details").option
7958
7974
  const spinner = opts.json ? null : ora14();
7959
7975
  try {
7960
7976
  spinner?.start("Fetching trigger");
7961
- const trigger = await getTrigger(slug, opts.env);
7977
+ const trigger = await getTrigger(slug, opts.env, getOrgId3());
7962
7978
  if (!trigger) {
7963
7979
  spinner?.fail("Trigger not found");
7964
7980
  if (opts.json) {
@@ -8041,7 +8057,8 @@ triggersCommand.command("runs [slug]").description("List trigger runs").option("
8041
8057
  environment: opts.env,
8042
8058
  status: opts.status,
8043
8059
  triggerSlug: slug,
8044
- limit: parseInt(opts.limit, 10)
8060
+ limit: parseInt(opts.limit, 10),
8061
+ organizationId: getOrgId3()
8045
8062
  });
8046
8063
  spinner?.succeed(`Found ${runs.length} runs`);
8047
8064
  if (opts.json) {
@@ -8083,7 +8100,7 @@ triggersCommand.command("run <run-id>").description("View trigger run details").
8083
8100
  const spinner = opts.json ? null : ora14();
8084
8101
  try {
8085
8102
  spinner?.start("Fetching run");
8086
- const run = await getTriggerRunDetail(runId, opts.env);
8103
+ const run = await getTriggerRunDetail(runId, opts.env, getOrgId3());
8087
8104
  if (!run) {
8088
8105
  spinner?.fail("Run not found");
8089
8106
  if (opts.json) {
@@ -8127,8 +8144,9 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
8127
8144
  await ensureAuth5();
8128
8145
  const spinner = opts.json ? null : ora14();
8129
8146
  try {
8147
+ const orgId = getOrgId3();
8130
8148
  spinner?.start("Fetching statistics");
8131
- const stats = await getTriggerRunStats(opts.env);
8149
+ const stats = await getTriggerRunStats(opts.env, orgId);
8132
8150
  spinner?.succeed("Run statistics");
8133
8151
  if (opts.json) {
8134
8152
  console.log(JSON.stringify(stats, null, 2));
@@ -8153,7 +8171,7 @@ triggersCommand.command("stats").description("Show trigger run statistics").opti
8153
8171
  console.log(` ${chalk20.gray("\u2500".repeat(18))}`);
8154
8172
  console.log(` ${chalk20.bold("Total".padEnd(12))} ${chalk20.bold(String(total).padStart(6))}`);
8155
8173
  console.log();
8156
- const executions = await listTriggerExecutions({ environment: opts.env, limit: 50 });
8174
+ const executions = await listTriggerExecutions({ environment: opts.env, limit: 50, organizationId: orgId });
8157
8175
  const recentFailures = executions.filter((e) => e.eventType === "trigger.failed");
8158
8176
  const recentSuccesses = executions.length - recentFailures.length;
8159
8177
  console.log(chalk20.bold(` Recent Executions`));
@@ -8185,7 +8203,8 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
8185
8203
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8186
8204
  environment: opts.env,
8187
8205
  triggerSlug: slug,
8188
- limit: parseInt(opts.limit, 10)
8206
+ limit: parseInt(opts.limit, 10),
8207
+ organizationId: getOrgId3()
8189
8208
  }));
8190
8209
  spinner?.succeed(`Found ${executions.length} executions`);
8191
8210
  if (opts.json) {
@@ -8245,6 +8264,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8245
8264
  await ensureAuth5();
8246
8265
  const spinner = opts.json ? null : ora14();
8247
8266
  try {
8267
+ const orgId = getOrgId3();
8248
8268
  const nth = parseInt(opts.nth, 10);
8249
8269
  if (isNaN(nth) || nth < 1) {
8250
8270
  console.error(chalk20.red("Error:"), "--nth must be a positive integer (e.g., --nth 1 for most recent)");
@@ -8257,7 +8277,8 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8257
8277
  const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
8258
8278
  environment: opts.env,
8259
8279
  triggerSlug: identifier,
8260
- limit: nth
8280
+ limit: nth,
8281
+ organizationId: orgId
8261
8282
  }));
8262
8283
  if (!executions.length) {
8263
8284
  spinner?.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
@@ -8275,7 +8296,7 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
8275
8296
  spinner?.succeed(`Found execution for "${identifier}"`);
8276
8297
  }
8277
8298
  spinner?.start("Fetching execution detail");
8278
- const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env));
8299
+ const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env, orgId));
8279
8300
  if (!event) {
8280
8301
  spinner?.fail("Execution not found");
8281
8302
  if (opts.json) {
@@ -8337,7 +8358,7 @@ triggersCommand.command("retry <run-id>").description("Retry a failed or dead ru
8337
8358
  }
8338
8359
  try {
8339
8360
  spinner?.start("Retrying run...");
8340
- await retryTriggerRun(runId, environment);
8361
+ await retryTriggerRun(runId, environment, getOrgId3());
8341
8362
  spinner?.succeed(chalk20.green(`Run ${runId} queued for retry`));
8342
8363
  if (opts.json) {
8343
8364
  console.log(JSON.stringify({ success: true, runId }));
@@ -8368,7 +8389,7 @@ triggersCommand.command("cancel <run-id>").description("Cancel a pending run").o
8368
8389
  }
8369
8390
  try {
8370
8391
  spinner?.start("Cancelling run...");
8371
- await cancelTriggerRun(runId, environment);
8392
+ await cancelTriggerRun(runId, environment, getOrgId3());
8372
8393
  spinner?.succeed(chalk20.green(`Run ${runId} cancelled`));
8373
8394
  if (opts.json) {
8374
8395
  console.log(JSON.stringify({ success: true, runId }));
@@ -8399,7 +8420,7 @@ triggersCommand.command("enable <slug>").description("Enable a trigger").option(
8399
8420
  }
8400
8421
  try {
8401
8422
  spinner?.start("Enabling trigger...");
8402
- await toggleTrigger(slug, true, environment);
8423
+ await toggleTrigger(slug, true, environment, getOrgId3());
8403
8424
  spinner?.succeed(chalk20.green(`Trigger ${slug} enabled`));
8404
8425
  if (opts.json) {
8405
8426
  console.log(JSON.stringify({ success: true, slug }));
@@ -8430,7 +8451,7 @@ triggersCommand.command("disable <slug>").description("Disable a trigger").optio
8430
8451
  }
8431
8452
  try {
8432
8453
  spinner?.start("Disabling trigger...");
8433
- await toggleTrigger(slug, false, environment);
8454
+ await toggleTrigger(slug, false, environment, getOrgId3());
8434
8455
  spinner?.succeed(chalk20.green(`Trigger ${slug} disabled`));
8435
8456
  if (opts.json) {
8436
8457
  console.log(JSON.stringify({ success: true, slug }));
@@ -8461,7 +8482,7 @@ triggersCommand.command("retry-event <event-id>").description("Retry a failed im
8461
8482
  }
8462
8483
  try {
8463
8484
  spinner?.start("Retrying failed execution...");
8464
- const result = await retryImmediateExecution(eventId, environment);
8485
+ const result = await retryImmediateExecution(eventId, environment, getOrgId3());
8465
8486
  if (result.success) {
8466
8487
  spinner?.succeed(chalk20.green(`Execution retried successfully`));
8467
8488
  } else {
@@ -8509,7 +8530,8 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
8509
8530
  slug,
8510
8531
  environment,
8511
8532
  entityId: opts.entity,
8512
- data
8533
+ data,
8534
+ organizationId: getOrgId3()
8513
8535
  });
8514
8536
  if (error) {
8515
8537
  spinner?.fail("Trigger execution failed");
@@ -8667,12 +8689,14 @@ async function listThreads(options) {
8667
8689
  return convexQuery7("threads:listWithPreviews", {
8668
8690
  environment: options.environment,
8669
8691
  channel: options.channel,
8670
- limit: options.limit
8692
+ limit: options.limit,
8693
+ ...options.organizationId && { organizationId: options.organizationId }
8671
8694
  });
8672
8695
  }
8673
8696
  async function getThreadWithMessages(options) {
8674
8697
  return convexQuery7("threads:getWithMessages", {
8675
- id: options.threadId
8698
+ id: options.threadId,
8699
+ ...options.organizationId && { organizationId: options.organizationId }
8676
8700
  });
8677
8701
  }
8678
8702
  async function archiveThread(options) {
@@ -10450,8 +10474,8 @@ function checkModelConfig(resources) {
10450
10474
  hint: "Check agent model config - temperature should be 0-2"
10451
10475
  };
10452
10476
  }
10453
- async function checkStuckThreads(environment) {
10454
- const threads = await withAuthRetry2(() => listThreads({ environment, limit: 100 }));
10477
+ async function checkStuckThreads(environment, organizationId) {
10478
+ const threads = await withAuthRetry2(() => listThreads({ environment, limit: 100, organizationId }));
10455
10479
  const issues = [];
10456
10480
  const now = Date.now();
10457
10481
  const fiveMinutes = 5 * 60 * 1000;
@@ -10480,8 +10504,8 @@ async function checkStuckThreads(environment) {
10480
10504
  hint: "Run `struere threads archive <id>` to archive stuck threads"
10481
10505
  };
10482
10506
  }
10483
- async function checkTriggerHealth(environment) {
10484
- const executions = await withAuthRetry2(() => listTriggerExecutions({ environment, limit: 100 }));
10507
+ async function checkTriggerHealth(environment, organizationId) {
10508
+ const executions = await withAuthRetry2(() => listTriggerExecutions({ environment, limit: 100, organizationId }));
10485
10509
  if (executions.length === 0) {
10486
10510
  return {
10487
10511
  id: "trigger-health",
@@ -10528,8 +10552,8 @@ async function checkTriggerHealth(environment) {
10528
10552
  hint: "Run `struere triggers logs <slug>` to investigate failures"
10529
10553
  };
10530
10554
  }
10531
- async function checkSyncDrift(resources, environment) {
10532
- const { state, error } = await withAuthRetry2(() => getSyncState(undefined, environment));
10555
+ async function checkSyncDrift(resources, environment, organizationId) {
10556
+ const { state, error } = await withAuthRetry2(() => getSyncState(organizationId, environment));
10533
10557
  if (error || !state) {
10534
10558
  return {
10535
10559
  id: "sync-drift",
@@ -10638,6 +10662,8 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10638
10662
  const spinner = opts.json ? null : ora20();
10639
10663
  const cwd = process.cwd();
10640
10664
  const environment = opts.env;
10665
+ const project = loadProject(cwd);
10666
+ const organizationId = project?.organization.id;
10641
10667
  try {
10642
10668
  spinner?.start("Loading resources...");
10643
10669
  const resources = await loadAllResources(cwd);
@@ -10648,9 +10674,9 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10648
10674
  runCheck(() => checkTemplateApprovals(resources)),
10649
10675
  runCheck(() => checkEntityTypeReferences(resources)),
10650
10676
  runCheck(() => checkModelConfig(resources)),
10651
- runCheck(() => checkStuckThreads(environment)),
10652
- runCheck(() => checkTriggerHealth(environment)),
10653
- runCheck(() => checkSyncDrift(resources, environment))
10677
+ runCheck(() => checkStuckThreads(environment, organizationId)),
10678
+ runCheck(() => checkTriggerHealth(environment, organizationId)),
10679
+ runCheck(() => checkSyncDrift(resources, environment, organizationId))
10654
10680
  ]);
10655
10681
  spinner?.stop();
10656
10682
  const checks = results.map((r) => r.status === "fulfilled" ? r.value : { id: "unknown", label: "Unknown", status: "warn", message: `Check failed: ${r.reason}` });
@@ -10704,10 +10730,332 @@ var doctorCommand = new Command25("doctor").description("Run diagnostic checks o
10704
10730
  process.exit(1);
10705
10731
  }
10706
10732
  });
10733
+
10734
+ // src/cli/commands/keys.ts
10735
+ init_credentials();
10736
+ import { Command as Command26 } from "commander";
10737
+ import chalk29 from "chalk";
10738
+ import ora21 from "ora";
10739
+ import { input as input3, confirm as confirm8 } from "@inquirer/prompts";
10740
+ init_convex();
10741
+
10742
+ // src/cli/utils/apiKeys.ts
10743
+ init_credentials();
10744
+ init_config();
10745
+ function getToken5() {
10746
+ const credentials = loadCredentials();
10747
+ const apiKey = getApiKey();
10748
+ const token = apiKey || credentials?.token;
10749
+ if (!token)
10750
+ throw new Error("Not authenticated");
10751
+ return token;
10752
+ }
10753
+ async function convexQuery8(path, args) {
10754
+ const token = getToken5();
10755
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
10756
+ method: "POST",
10757
+ headers: {
10758
+ "Content-Type": "application/json",
10759
+ Authorization: `Bearer ${token}`
10760
+ },
10761
+ body: JSON.stringify({ path, args })
10762
+ });
10763
+ const text = await response.text();
10764
+ let json;
10765
+ try {
10766
+ json = JSON.parse(text);
10767
+ } catch {
10768
+ throw new Error(text || `HTTP ${response.status}`);
10769
+ }
10770
+ if (!response.ok) {
10771
+ throw new Error(json.errorData?.message || json.errorMessage || text);
10772
+ }
10773
+ if (json.status === "error") {
10774
+ throw new Error(json.errorMessage || "Unknown error from Convex");
10775
+ }
10776
+ return json.value;
10777
+ }
10778
+ async function convexMutation6(path, args) {
10779
+ const token = getToken5();
10780
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
10781
+ method: "POST",
10782
+ headers: {
10783
+ "Content-Type": "application/json",
10784
+ Authorization: `Bearer ${token}`
10785
+ },
10786
+ body: JSON.stringify({ path, args })
10787
+ });
10788
+ const text = await response.text();
10789
+ let json;
10790
+ try {
10791
+ json = JSON.parse(text);
10792
+ } catch {
10793
+ throw new Error(text || `HTTP ${response.status}`);
10794
+ }
10795
+ if (!response.ok) {
10796
+ throw new Error(json.errorData?.message || json.errorMessage || text);
10797
+ }
10798
+ if (json.status === "error") {
10799
+ throw new Error(json.errorMessage || "Unknown error from Convex");
10800
+ }
10801
+ return json.value;
10802
+ }
10803
+ async function listApiKeys() {
10804
+ return convexQuery8("apiKeys:list", {});
10805
+ }
10806
+ async function createApiKey(args) {
10807
+ return convexMutation6("apiKeys:create", {
10808
+ name: args.name,
10809
+ environment: args.environment,
10810
+ permissions: args.permissions ?? ["*"]
10811
+ });
10812
+ }
10813
+ async function removeApiKey(id) {
10814
+ return convexMutation6("apiKeys:remove", { id });
10815
+ }
10816
+
10817
+ // src/cli/commands/keys.ts
10818
+ async function withKeyAuthRetry(fn) {
10819
+ try {
10820
+ return await fn();
10821
+ } catch (err) {
10822
+ const msg = err instanceof Error ? err.message : String(err);
10823
+ if (msg.includes("Unauthenticated") || msg.includes("OIDC") || msg.includes("token") || msg.includes("expired")) {
10824
+ const refreshed = await refreshToken();
10825
+ if (!refreshed)
10826
+ throw err;
10827
+ return fn();
10828
+ }
10829
+ throw err;
10830
+ }
10831
+ }
10832
+ async function ensureAuth9() {
10833
+ const cwd = process.cwd();
10834
+ const nonInteractive = !isInteractive();
10835
+ if (!hasProject(cwd)) {
10836
+ if (nonInteractive) {
10837
+ console.error(chalk29.red("No struere.json found. Run struere init first."));
10838
+ process.exit(1);
10839
+ }
10840
+ console.log(chalk29.yellow("No struere.json found - initializing project..."));
10841
+ console.log();
10842
+ const success = await runInit(cwd);
10843
+ if (!success) {
10844
+ process.exit(1);
10845
+ }
10846
+ console.log();
10847
+ }
10848
+ let credentials = loadCredentials();
10849
+ const apiKey = getApiKey();
10850
+ if (!credentials && !apiKey) {
10851
+ if (nonInteractive) {
10852
+ console.error(chalk29.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
10853
+ process.exit(1);
10854
+ }
10855
+ console.log(chalk29.yellow("Not logged in - authenticating..."));
10856
+ console.log();
10857
+ credentials = await performLogin();
10858
+ if (!credentials) {
10859
+ console.log(chalk29.red("Authentication failed"));
10860
+ process.exit(1);
10861
+ }
10862
+ console.log();
10863
+ }
10864
+ return true;
10865
+ }
10866
+ function relativeTime4(ts) {
10867
+ const diff = Date.now() - ts;
10868
+ const seconds = Math.floor(diff / 1000);
10869
+ if (seconds < 60)
10870
+ return `${seconds}s ago`;
10871
+ const minutes = Math.floor(seconds / 60);
10872
+ if (minutes < 60)
10873
+ return `${minutes}m ago`;
10874
+ const hours = Math.floor(minutes / 60);
10875
+ if (hours < 24)
10876
+ return `${hours}h ago`;
10877
+ const days = Math.floor(hours / 24);
10878
+ return `${days}d ago`;
10879
+ }
10880
+ function maskedPrefix(prefix) {
10881
+ return `${prefix}****`;
10882
+ }
10883
+ function isValidEnvironment(value) {
10884
+ return value === "development" || value === "production" || value === "eval";
10885
+ }
10886
+ var keysCommand = new Command26("keys").description("Manage API keys");
10887
+ keysCommand.command("list", { isDefault: true }).description("List API keys").option("--env <environment>", "Filter by environment (development|production|eval)").option("--json", "Output raw JSON").action(async (opts) => {
10888
+ await ensureAuth9();
10889
+ const spinner = opts.json ? null : ora21();
10890
+ try {
10891
+ spinner?.start("Fetching API keys");
10892
+ let keys = await withKeyAuthRetry(() => listApiKeys());
10893
+ if (opts.env) {
10894
+ if (!isValidEnvironment(opts.env)) {
10895
+ spinner?.fail(`Invalid environment: ${opts.env}`);
10896
+ process.exit(1);
10897
+ }
10898
+ keys = keys.filter((k) => k.environment === opts.env);
10899
+ }
10900
+ spinner?.succeed(`Found ${keys.length} API key${keys.length === 1 ? "" : "s"}${opts.env ? ` (${opts.env})` : ""}`);
10901
+ if (opts.json) {
10902
+ console.log(JSON.stringify(keys, null, 2));
10903
+ return;
10904
+ }
10905
+ console.log();
10906
+ renderTable([
10907
+ { key: "id", label: "ID", width: 14 },
10908
+ { key: "name", label: "Name", width: 24 },
10909
+ { key: "prefix", label: "Prefix", width: 22 },
10910
+ { key: "environment", label: "Env", width: 12 },
10911
+ { key: "created", label: "Created", width: 12 },
10912
+ { key: "lastUsed", label: "Last Used", width: 12 }
10913
+ ], keys.map((k) => ({
10914
+ id: k.id.slice(-12),
10915
+ name: k.name,
10916
+ prefix: maskedPrefix(k.keyPrefix),
10917
+ environment: k.environment,
10918
+ created: relativeTime4(k.createdAt),
10919
+ lastUsed: k.lastUsedAt ? relativeTime4(k.lastUsedAt) : chalk29.gray("-")
10920
+ })));
10921
+ console.log();
10922
+ } catch (err) {
10923
+ const message = err instanceof Error ? err.message : String(err);
10924
+ spinner?.fail("Failed to fetch API keys");
10925
+ if (opts.json) {
10926
+ console.log(JSON.stringify({ success: false, error: message }));
10927
+ } else {
10928
+ console.log(chalk29.red("Error:"), message);
10929
+ }
10930
+ process.exit(1);
10931
+ }
10932
+ });
10933
+ keysCommand.command("create").description("Create a new API key").option("--name <name>", "Key name").option("--env <environment>", "Environment (development|production|eval)", "development").option("--json", "Output raw JSON").option("--confirm", "Skip production confirmation").action(async (opts) => {
10934
+ await ensureAuth9();
10935
+ if (!isValidEnvironment(opts.env)) {
10936
+ const message = `Invalid environment: ${opts.env}`;
10937
+ if (opts.json) {
10938
+ console.log(JSON.stringify({ success: false, error: message }));
10939
+ } else {
10940
+ console.log(chalk29.red("Error:"), message);
10941
+ }
10942
+ process.exit(1);
10943
+ }
10944
+ const environment = opts.env;
10945
+ let name = opts.name;
10946
+ if (!name || !name.trim()) {
10947
+ if (!isInteractive()) {
10948
+ const message = "--name is required in non-interactive mode";
10949
+ if (opts.json) {
10950
+ console.log(JSON.stringify({ success: false, error: message }));
10951
+ } else {
10952
+ console.log(chalk29.red("Error:"), message);
10953
+ }
10954
+ process.exit(1);
10955
+ }
10956
+ name = await input3({
10957
+ message: "API key name:",
10958
+ validate: (v) => v.trim().length > 0 || "Name is required"
10959
+ });
10960
+ }
10961
+ name = name.trim();
10962
+ if (environment === "production" && !opts.confirm && !opts.json && isInteractive()) {
10963
+ const ok = await confirm8({
10964
+ message: chalk29.yellow("Create API key in PRODUCTION environment?"),
10965
+ default: false
10966
+ });
10967
+ if (!ok) {
10968
+ console.log(chalk29.gray("Cancelled"));
10969
+ process.exit(0);
10970
+ }
10971
+ }
10972
+ const spinner = opts.json ? null : ora21();
10973
+ try {
10974
+ spinner?.start("Creating API key");
10975
+ const result = await withKeyAuthRetry(() => createApiKey({ name, environment }));
10976
+ spinner?.succeed(chalk29.green(`Created API key ${chalk29.cyan(result.name)}`));
10977
+ if (opts.json) {
10978
+ console.log(JSON.stringify({
10979
+ id: result.id,
10980
+ name: result.name,
10981
+ prefix: result.keyPrefix,
10982
+ key: result.key,
10983
+ environment,
10984
+ createdAt: result.createdAt
10985
+ }, null, 2));
10986
+ return;
10987
+ }
10988
+ console.log();
10989
+ console.log(chalk29.bold(" " + chalk29.yellow("Save this key now \u2014 it will not be shown again.")));
10990
+ console.log();
10991
+ console.log(` ${chalk29.gray("Key:")} ${chalk29.cyan(result.key)}`);
10992
+ console.log(` ${chalk29.gray("Name:")} ${result.name}`);
10993
+ console.log(` ${chalk29.gray("Prefix:")} ${result.keyPrefix}`);
10994
+ console.log(` ${chalk29.gray("Environment:")} ${environment}`);
10995
+ console.log(` ${chalk29.gray("ID:")} ${result.id}`);
10996
+ console.log();
10997
+ } catch (err) {
10998
+ const message = err instanceof Error ? err.message : String(err);
10999
+ spinner?.fail("Failed to create API key");
11000
+ if (opts.json) {
11001
+ console.log(JSON.stringify({ success: false, error: message }));
11002
+ } else {
11003
+ console.log(chalk29.red("Error:"), message);
11004
+ }
11005
+ process.exit(1);
11006
+ }
11007
+ });
11008
+ keysCommand.command("revoke <id-or-prefix>").description("Revoke an API key (delete)").option("--confirm", "Skip confirmation prompt").option("--json", "Output raw JSON").action(async (idOrPrefix, opts) => {
11009
+ await ensureAuth9();
11010
+ const spinner = opts.json ? null : ora21();
11011
+ try {
11012
+ spinner?.start("Looking up API key");
11013
+ const keys = await withKeyAuthRetry(() => listApiKeys());
11014
+ const target = keys.find((k) => k.id === idOrPrefix) ?? keys.find((k) => k.id.endsWith(idOrPrefix)) ?? keys.find((k) => k.keyPrefix === idOrPrefix) ?? keys.find((k) => k.keyPrefix.startsWith(idOrPrefix));
11015
+ if (!target) {
11016
+ spinner?.fail(`API key not found: ${idOrPrefix}`);
11017
+ if (opts.json) {
11018
+ console.log(JSON.stringify({ success: false, error: "API key not found" }));
11019
+ }
11020
+ process.exit(1);
11021
+ }
11022
+ spinner?.succeed(`Found ${chalk29.cyan(target.name)} (${maskedPrefix(target.keyPrefix)}, ${target.environment})`);
11023
+ if (!opts.confirm && !opts.json) {
11024
+ if (!isInteractive()) {
11025
+ console.log(chalk29.red("Error:"), "Use --confirm to revoke in non-interactive mode");
11026
+ process.exit(1);
11027
+ }
11028
+ const ok = await confirm8({
11029
+ message: `Revoke this key? This cannot be undone.`,
11030
+ default: false
11031
+ });
11032
+ if (!ok) {
11033
+ console.log(chalk29.gray("Cancelled"));
11034
+ process.exit(0);
11035
+ }
11036
+ }
11037
+ const revokeSpinner = opts.json ? null : ora21();
11038
+ revokeSpinner?.start("Revoking API key");
11039
+ await withKeyAuthRetry(() => removeApiKey(target.id));
11040
+ revokeSpinner?.succeed(chalk29.green(`Revoked ${chalk29.cyan(target.name)}`));
11041
+ if (opts.json) {
11042
+ console.log(JSON.stringify({ success: true, id: target.id, name: target.name }));
11043
+ }
11044
+ } catch (err) {
11045
+ const message = err instanceof Error ? err.message : String(err);
11046
+ spinner?.fail("Failed to revoke API key");
11047
+ if (opts.json) {
11048
+ console.log(JSON.stringify({ success: false, error: message }));
11049
+ } else {
11050
+ console.log(chalk29.red("Error:"), message);
11051
+ }
11052
+ process.exit(1);
11053
+ }
11054
+ });
10707
11055
  // package.json
10708
11056
  var package_default = {
10709
11057
  name: "struere",
10710
- version: "0.14.2",
11058
+ version: "0.14.3",
10711
11059
  description: "Build, test, and deploy AI agents",
10712
11060
  keywords: [
10713
11061
  "ai",
@@ -10742,13 +11090,17 @@ var package_default = {
10742
11090
  ".": {
10743
11091
  import: "./dist/index.js",
10744
11092
  types: "./dist/index.d.ts"
11093
+ },
11094
+ "./client": {
11095
+ import: "./dist/client/index.js",
11096
+ types: "./dist/client/index.d.ts"
10745
11097
  }
10746
11098
  },
10747
11099
  files: [
10748
11100
  "dist"
10749
11101
  ],
10750
11102
  scripts: {
10751
- build: "bun build ./src/cli/index.ts --outdir ./dist/cli --target bun --external commander --external chalk --external ora --external chokidar --external yaml --external @inquirer/prompts && bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/bin/struere.ts --outdir ./dist/bin --target bun --external commander --external chalk --external ora --external chokidar --external yaml --external @inquirer/prompts && tsc --emitDeclarationOnly && chmod +x ./dist/bin/struere.js",
11103
+ build: "bun build ./src/cli/index.ts --outdir ./dist/cli --target bun --external commander --external chalk --external ora --external chokidar --external yaml --external @inquirer/prompts && bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/client/index.ts --outdir ./dist/client --target browser && bun build ./src/bin/struere.ts --outdir ./dist/bin --target bun --external commander --external chalk --external ora --external chokidar --external yaml --external @inquirer/prompts && tsc --emitDeclarationOnly && chmod +x ./dist/bin/struere.js",
10752
11104
  dev: "tsc --watch",
10753
11105
  test: "bun test",
10754
11106
  prepublishOnly: "bun run build"
@@ -10832,4 +11184,5 @@ program.addCommand(chatCommand);
10832
11184
  program.addCommand(whatsappCommand);
10833
11185
  program.addCommand(diffCommand);
10834
11186
  program.addCommand(doctorCommand);
11187
+ program.addCommand(keysCommand);
10835
11188
  program.parse();