struere 0.16.1 → 0.16.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.
@@ -73596,6 +73596,144 @@ async function findThreadByPhone(options) {
73596
73596
  return false;
73597
73597
  }) ?? null;
73598
73598
  }
73599
+ async function replyToThread(options) {
73600
+ const apiKey = getApiKey();
73601
+ const credentials = loadCredentials();
73602
+ if (apiKey && !credentials?.token) {
73603
+ return threadsApiCall("/v1/threads/reply", {
73604
+ threadId: options.threadId,
73605
+ message: options.message
73606
+ });
73607
+ }
73608
+ const token = getToken5();
73609
+ const response = await fetch(`${CONVEX_URL}/api/action`, {
73610
+ method: "POST",
73611
+ headers: {
73612
+ "Content-Type": "application/json",
73613
+ Authorization: `Bearer ${token}`
73614
+ },
73615
+ body: JSON.stringify({
73616
+ path: "chat:replyToThreadCli",
73617
+ args: { threadId: options.threadId, message: options.message }
73618
+ }),
73619
+ signal: AbortSignal.timeout(30000)
73620
+ });
73621
+ const text = await response.text();
73622
+ let json;
73623
+ try {
73624
+ json = JSON.parse(text);
73625
+ } catch {
73626
+ throw new Error(text || `HTTP ${response.status}`);
73627
+ }
73628
+ if (!response.ok) {
73629
+ throw new Error(json.errorData?.message || json.errorMessage || text);
73630
+ }
73631
+ if (json.status === "error") {
73632
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
73633
+ }
73634
+ return json.value;
73635
+ }
73636
+ async function pauseThread(options) {
73637
+ const apiKey = getApiKey();
73638
+ const credentials = loadCredentials();
73639
+ if (apiKey && !credentials?.token) {
73640
+ return threadsApiCall("/v1/threads/pause", {
73641
+ threadId: options.threadId
73642
+ });
73643
+ }
73644
+ const token = getToken5();
73645
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
73646
+ method: "POST",
73647
+ headers: {
73648
+ "Content-Type": "application/json",
73649
+ Authorization: `Bearer ${token}`
73650
+ },
73651
+ body: JSON.stringify({
73652
+ path: "threads:setAgentPaused",
73653
+ args: { threadId: options.threadId, paused: true }
73654
+ }),
73655
+ signal: AbortSignal.timeout(30000)
73656
+ });
73657
+ const text = await response.text();
73658
+ let json;
73659
+ try {
73660
+ json = JSON.parse(text);
73661
+ } catch {
73662
+ throw new Error(text || `HTTP ${response.status}`);
73663
+ }
73664
+ if (!response.ok) {
73665
+ throw new Error(json.errorData?.message || json.errorMessage || text);
73666
+ }
73667
+ if (json.status === "error") {
73668
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
73669
+ }
73670
+ return { success: true, paused: true };
73671
+ }
73672
+ async function resumeThread(options) {
73673
+ const apiKey = getApiKey();
73674
+ const credentials = loadCredentials();
73675
+ if (apiKey && !credentials?.token) {
73676
+ return threadsApiCall("/v1/threads/resume", {
73677
+ threadId: options.threadId
73678
+ });
73679
+ }
73680
+ const token = getToken5();
73681
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
73682
+ method: "POST",
73683
+ headers: {
73684
+ "Content-Type": "application/json",
73685
+ Authorization: `Bearer ${token}`
73686
+ },
73687
+ body: JSON.stringify({
73688
+ path: "threads:setAgentPaused",
73689
+ args: { threadId: options.threadId, paused: false }
73690
+ }),
73691
+ signal: AbortSignal.timeout(30000)
73692
+ });
73693
+ const text = await response.text();
73694
+ let json;
73695
+ try {
73696
+ json = JSON.parse(text);
73697
+ } catch {
73698
+ throw new Error(text || `HTTP ${response.status}`);
73699
+ }
73700
+ if (!response.ok) {
73701
+ throw new Error(json.errorData?.message || json.errorMessage || text);
73702
+ }
73703
+ if (json.status === "error") {
73704
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
73705
+ }
73706
+ return { success: true, paused: false };
73707
+ }
73708
+ async function resolveActiveWhatsAppThread(options) {
73709
+ const threads = await listThreads({
73710
+ environment: options.environment,
73711
+ organizationId: options.organizationId,
73712
+ limit: 100
73713
+ });
73714
+ const isPhone = options.target.startsWith("+") || /^\d+$/.test(options.target);
73715
+ let matches;
73716
+ if (isPhone) {
73717
+ const normalize = (p) => p.replace(/\D/g, "");
73718
+ const normalizedInput = normalize(options.target);
73719
+ matches = threads.filter((t2) => {
73720
+ const phoneNumber = t2.channelParams?.phoneNumber;
73721
+ if (phoneNumber && normalize(phoneNumber) === normalizedInput)
73722
+ return true;
73723
+ if (t2.externalId && t2.externalId.includes(normalizedInput))
73724
+ return true;
73725
+ return false;
73726
+ });
73727
+ } else {
73728
+ matches = threads.filter((t2) => t2._id === options.target);
73729
+ }
73730
+ const active = matches.filter((t2) => !t2.externalId?.includes(":archived:"));
73731
+ if (active.length === 0)
73732
+ return { kind: "none" };
73733
+ if (active.length === 1)
73734
+ return { kind: "ok", thread: active[0] };
73735
+ return { kind: "multiple", candidates: active };
73736
+ }
73599
73737
 
73600
73738
  // src/cli/commands/threads.ts
73601
73739
  async function ensureAuth7() {
@@ -73676,6 +73814,16 @@ function roleColor(role) {
73676
73814
  return chalk22.gray(role);
73677
73815
  }
73678
73816
  }
73817
+ function threadPhone(thread) {
73818
+ const phoneNumber = thread.channelParams?.phoneNumber;
73819
+ if (phoneNumber)
73820
+ return phoneNumber;
73821
+ if (thread.externalId) {
73822
+ const parts = thread.externalId.split(":");
73823
+ return parts[parts.length - 1];
73824
+ }
73825
+ return "unknown";
73826
+ }
73679
73827
  var threadsCommand = new Command20("threads").description("Manage conversation threads");
73680
73828
  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) => {
73681
73829
  await ensureAuth7();
@@ -73703,7 +73851,7 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
73703
73851
  ], threads.map((t2) => ({
73704
73852
  id: t2._id,
73705
73853
  channel: channelColor(t2.channel),
73706
- participant: t2.participantName ?? "Unknown",
73854
+ participant: t2.agentPaused ? `${t2.participantName ?? "Unknown"} ${chalk22.yellow("PAUSED")}` : t2.participantName ?? "Unknown",
73707
73855
  agent: t2.agentName ?? "Unknown",
73708
73856
  lastMessage: t2.lastMessage ? t2.lastMessage.content.slice(0, 40) : chalk22.gray("-"),
73709
73857
  time: relativeTime4(t2.updatedAt ?? t2.createdAt)
@@ -73859,6 +74007,138 @@ threadsCommand.command("reset").description("Archive a thread by phone number").
73859
74007
  process.exit(1);
73860
74008
  }
73861
74009
  });
74010
+ async function resolveOrExit(target, environment, json, spinner) {
74011
+ const project = loadProject(process.cwd());
74012
+ const resolution = await resolveActiveWhatsAppThread({
74013
+ target,
74014
+ environment,
74015
+ organizationId: project?.organization.id
74016
+ });
74017
+ if (resolution.kind === "none") {
74018
+ const error = `No active WhatsApp thread found for "${target}" in ${environment}. Did you mean --env production?`;
74019
+ spinner?.fail(error);
74020
+ if (json) {
74021
+ console.log(JSON.stringify({ success: false, error }));
74022
+ } else {
74023
+ console.log(chalk22.red("Error:"), error);
74024
+ }
74025
+ process.exit(1);
74026
+ }
74027
+ if (resolution.kind === "multiple") {
74028
+ spinner?.stop();
74029
+ if (json) {
74030
+ console.log(JSON.stringify({ success: false, error: `Multiple active threads match ${target}` }));
74031
+ } else {
74032
+ console.log(chalk22.yellow(`Multiple active threads match ${target}:`));
74033
+ for (const candidate of resolution.candidates) {
74034
+ console.log(` ${chalk22.cyan(candidate._id)} ${candidate.agentName ?? "Unknown"} ${threadPhone(candidate)}`);
74035
+ }
74036
+ console.log(chalk22.gray("Re-run with an explicit thread id."));
74037
+ }
74038
+ process.exit(1);
74039
+ }
74040
+ const thread = resolution.thread;
74041
+ if (!thread.externalId?.startsWith("whatsapp:")) {
74042
+ const error = `Thread ${thread._id} is not a WhatsApp conversation (reply/pause/resume only support WhatsApp).`;
74043
+ spinner?.fail(error);
74044
+ if (json) {
74045
+ console.log(JSON.stringify({ success: false, error }));
74046
+ } else {
74047
+ console.log(chalk22.red("Error:"), error);
74048
+ }
74049
+ process.exit(1);
74050
+ }
74051
+ return thread;
74052
+ }
74053
+ threadsCommand.command("reply").description("Send a WhatsApp message directly to a user as a human operator (does not pause the agent)").argument("<target>", "Thread id or phone number").requiredOption("-m, --message <text>", "Message to send to the user").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
74054
+ await ensureAuth7();
74055
+ const spinner = opts.json ? null : ora16();
74056
+ const environment = opts.env;
74057
+ try {
74058
+ spinner?.start(`Resolving thread for ${target}...`);
74059
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
74060
+ const phone = threadPhone(thread);
74061
+ if (spinner)
74062
+ spinner.text = `Sending to ${phone}...`;
74063
+ const result = await replyToThread({
74064
+ threadId: thread._id,
74065
+ message: opts.message,
74066
+ environment
74067
+ });
74068
+ if (result.whatsappStatus === "failed") {
74069
+ spinner?.fail(`Failed to send to ${phone} (env: ${environment})`);
74070
+ if (opts.json) {
74071
+ console.log(JSON.stringify({ success: false, threadId: thread._id, whatsappStatus: result.whatsappStatus, environment }));
74072
+ } else {
74073
+ console.log(chalk22.gray(`Tip: the 24h window may be closed or the connection is not connected. Re-open with a template: struere run-tool whatsapp.sendTemplate --args '{"to":"${phone}","templateName":"...","language":"..."}'`));
74074
+ }
74075
+ process.exit(1);
74076
+ }
74077
+ spinner?.succeed(chalk22.green(`Sent to ${phone} (env: ${environment})`));
74078
+ if (opts.json) {
74079
+ console.log(JSON.stringify({ success: true, threadId: thread._id, whatsappStatus: result.whatsappStatus, environment }));
74080
+ }
74081
+ } catch (err) {
74082
+ const message = err instanceof Error ? err.message : String(err);
74083
+ spinner?.fail("Failed to reply to thread");
74084
+ if (opts.json) {
74085
+ console.log(JSON.stringify({ success: false, error: message }));
74086
+ } else {
74087
+ console.log(chalk22.red("Error:"), message);
74088
+ }
74089
+ process.exit(1);
74090
+ }
74091
+ });
74092
+ threadsCommand.command("pause").description("Pause the agent on a conversation so it stops auto-responding").argument("<target>", "Thread id or phone number").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
74093
+ await ensureAuth7();
74094
+ const spinner = opts.json ? null : ora16();
74095
+ const environment = opts.env;
74096
+ try {
74097
+ spinner?.start(`Resolving thread for ${target}...`);
74098
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
74099
+ if (spinner)
74100
+ spinner.text = `Pausing agent for thread ${thread._id}...`;
74101
+ await pauseThread({ threadId: thread._id, environment });
74102
+ spinner?.succeed(chalk22.green(`Agent paused for thread ${thread._id} (env: ${environment})`));
74103
+ if (opts.json) {
74104
+ console.log(JSON.stringify({ success: true, threadId: thread._id, paused: true, environment }));
74105
+ }
74106
+ } catch (err) {
74107
+ const message = err instanceof Error ? err.message : String(err);
74108
+ spinner?.fail("Failed to pause thread");
74109
+ if (opts.json) {
74110
+ console.log(JSON.stringify({ success: false, error: message }));
74111
+ } else {
74112
+ console.log(chalk22.red("Error:"), message);
74113
+ }
74114
+ process.exit(1);
74115
+ }
74116
+ });
74117
+ threadsCommand.command("resume").description("Resume the agent on a paused conversation").argument("<target>", "Thread id or phone number").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
74118
+ await ensureAuth7();
74119
+ const spinner = opts.json ? null : ora16();
74120
+ const environment = opts.env;
74121
+ try {
74122
+ spinner?.start(`Resolving thread for ${target}...`);
74123
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
74124
+ if (spinner)
74125
+ spinner.text = `Resuming agent for thread ${thread._id}...`;
74126
+ await resumeThread({ threadId: thread._id, environment });
74127
+ spinner?.succeed(chalk22.green(`Agent resumed for thread ${thread._id} (env: ${environment})`));
74128
+ if (opts.json) {
74129
+ console.log(JSON.stringify({ success: true, threadId: thread._id, paused: false, environment }));
74130
+ }
74131
+ } catch (err) {
74132
+ const message = err instanceof Error ? err.message : String(err);
74133
+ spinner?.fail("Failed to resume thread");
74134
+ if (opts.json) {
74135
+ console.log(JSON.stringify({ success: false, error: message }));
74136
+ } else {
74137
+ console.log(chalk22.red("Error:"), message);
74138
+ }
74139
+ process.exit(1);
74140
+ }
74141
+ });
73862
74142
 
73863
74143
  // src/cli/commands/compile-prompt.ts
73864
74144
  init_credentials();
@@ -75914,7 +76194,7 @@ keysCommand.command("revoke <id-or-prefix>").description("Revoke an API key (del
75914
76194
  // package.json
75915
76195
  var package_default = {
75916
76196
  name: "struere",
75917
- version: "0.16.1",
76197
+ version: "0.16.3",
75918
76198
  description: "Build, test, and deploy AI agents",
75919
76199
  keywords: [
75920
76200
  "ai",
@@ -1 +1 @@
1
- {"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/threads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA2FnC,eAAO,MAAM,cAAc,SACkB,CAAA"}
1
+ {"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/threads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA0GnC,eAAO,MAAM,cAAc,SACkB,CAAA"}
package/dist/cli/index.js CHANGED
@@ -74586,6 +74586,144 @@ async function findThreadByPhone(options) {
74586
74586
  return false;
74587
74587
  }) ?? null;
74588
74588
  }
74589
+ async function replyToThread(options) {
74590
+ const apiKey = getApiKey();
74591
+ const credentials = loadCredentials();
74592
+ if (apiKey && !credentials?.token) {
74593
+ return threadsApiCall("/v1/threads/reply", {
74594
+ threadId: options.threadId,
74595
+ message: options.message
74596
+ });
74597
+ }
74598
+ const token = getToken5();
74599
+ const response = await fetch(`${CONVEX_URL}/api/action`, {
74600
+ method: "POST",
74601
+ headers: {
74602
+ "Content-Type": "application/json",
74603
+ Authorization: `Bearer ${token}`
74604
+ },
74605
+ body: JSON.stringify({
74606
+ path: "chat:replyToThreadCli",
74607
+ args: { threadId: options.threadId, message: options.message }
74608
+ }),
74609
+ signal: AbortSignal.timeout(30000)
74610
+ });
74611
+ const text = await response.text();
74612
+ let json;
74613
+ try {
74614
+ json = JSON.parse(text);
74615
+ } catch {
74616
+ throw new Error(text || `HTTP ${response.status}`);
74617
+ }
74618
+ if (!response.ok) {
74619
+ throw new Error(json.errorData?.message || json.errorMessage || text);
74620
+ }
74621
+ if (json.status === "error") {
74622
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
74623
+ }
74624
+ return json.value;
74625
+ }
74626
+ async function pauseThread(options) {
74627
+ const apiKey = getApiKey();
74628
+ const credentials = loadCredentials();
74629
+ if (apiKey && !credentials?.token) {
74630
+ return threadsApiCall("/v1/threads/pause", {
74631
+ threadId: options.threadId
74632
+ });
74633
+ }
74634
+ const token = getToken5();
74635
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
74636
+ method: "POST",
74637
+ headers: {
74638
+ "Content-Type": "application/json",
74639
+ Authorization: `Bearer ${token}`
74640
+ },
74641
+ body: JSON.stringify({
74642
+ path: "threads:setAgentPaused",
74643
+ args: { threadId: options.threadId, paused: true }
74644
+ }),
74645
+ signal: AbortSignal.timeout(30000)
74646
+ });
74647
+ const text = await response.text();
74648
+ let json;
74649
+ try {
74650
+ json = JSON.parse(text);
74651
+ } catch {
74652
+ throw new Error(text || `HTTP ${response.status}`);
74653
+ }
74654
+ if (!response.ok) {
74655
+ throw new Error(json.errorData?.message || json.errorMessage || text);
74656
+ }
74657
+ if (json.status === "error") {
74658
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
74659
+ }
74660
+ return { success: true, paused: true };
74661
+ }
74662
+ async function resumeThread(options) {
74663
+ const apiKey = getApiKey();
74664
+ const credentials = loadCredentials();
74665
+ if (apiKey && !credentials?.token) {
74666
+ return threadsApiCall("/v1/threads/resume", {
74667
+ threadId: options.threadId
74668
+ });
74669
+ }
74670
+ const token = getToken5();
74671
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
74672
+ method: "POST",
74673
+ headers: {
74674
+ "Content-Type": "application/json",
74675
+ Authorization: `Bearer ${token}`
74676
+ },
74677
+ body: JSON.stringify({
74678
+ path: "threads:setAgentPaused",
74679
+ args: { threadId: options.threadId, paused: false }
74680
+ }),
74681
+ signal: AbortSignal.timeout(30000)
74682
+ });
74683
+ const text = await response.text();
74684
+ let json;
74685
+ try {
74686
+ json = JSON.parse(text);
74687
+ } catch {
74688
+ throw new Error(text || `HTTP ${response.status}`);
74689
+ }
74690
+ if (!response.ok) {
74691
+ throw new Error(json.errorData?.message || json.errorMessage || text);
74692
+ }
74693
+ if (json.status === "error") {
74694
+ throw new Error(json.errorData?.message || json.errorMessage || "Unknown error from Convex");
74695
+ }
74696
+ return { success: true, paused: false };
74697
+ }
74698
+ async function resolveActiveWhatsAppThread(options) {
74699
+ const threads = await listThreads({
74700
+ environment: options.environment,
74701
+ organizationId: options.organizationId,
74702
+ limit: 100
74703
+ });
74704
+ const isPhone = options.target.startsWith("+") || /^\d+$/.test(options.target);
74705
+ let matches;
74706
+ if (isPhone) {
74707
+ const normalize = (p) => p.replace(/\D/g, "");
74708
+ const normalizedInput = normalize(options.target);
74709
+ matches = threads.filter((t2) => {
74710
+ const phoneNumber = t2.channelParams?.phoneNumber;
74711
+ if (phoneNumber && normalize(phoneNumber) === normalizedInput)
74712
+ return true;
74713
+ if (t2.externalId && t2.externalId.includes(normalizedInput))
74714
+ return true;
74715
+ return false;
74716
+ });
74717
+ } else {
74718
+ matches = threads.filter((t2) => t2._id === options.target);
74719
+ }
74720
+ const active = matches.filter((t2) => !t2.externalId?.includes(":archived:"));
74721
+ if (active.length === 0)
74722
+ return { kind: "none" };
74723
+ if (active.length === 1)
74724
+ return { kind: "ok", thread: active[0] };
74725
+ return { kind: "multiple", candidates: active };
74726
+ }
74589
74727
 
74590
74728
  // src/cli/commands/threads.ts
74591
74729
  async function ensureAuth7() {
@@ -74666,6 +74804,16 @@ function roleColor(role) {
74666
74804
  return chalk27.gray(role);
74667
74805
  }
74668
74806
  }
74807
+ function threadPhone(thread) {
74808
+ const phoneNumber = thread.channelParams?.phoneNumber;
74809
+ if (phoneNumber)
74810
+ return phoneNumber;
74811
+ if (thread.externalId) {
74812
+ const parts = thread.externalId.split(":");
74813
+ return parts[parts.length - 1];
74814
+ }
74815
+ return "unknown";
74816
+ }
74669
74817
  var threadsCommand = new Command25("threads").description("Manage conversation threads");
74670
74818
  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) => {
74671
74819
  await ensureAuth7();
@@ -74693,7 +74841,7 @@ threadsCommand.command("list", { isDefault: true }).description("List conversati
74693
74841
  ], threads.map((t2) => ({
74694
74842
  id: t2._id,
74695
74843
  channel: channelColor(t2.channel),
74696
- participant: t2.participantName ?? "Unknown",
74844
+ participant: t2.agentPaused ? `${t2.participantName ?? "Unknown"} ${chalk27.yellow("PAUSED")}` : t2.participantName ?? "Unknown",
74697
74845
  agent: t2.agentName ?? "Unknown",
74698
74846
  lastMessage: t2.lastMessage ? t2.lastMessage.content.slice(0, 40) : chalk27.gray("-"),
74699
74847
  time: relativeTime4(t2.updatedAt ?? t2.createdAt)
@@ -74849,6 +74997,138 @@ threadsCommand.command("reset").description("Archive a thread by phone number").
74849
74997
  process.exit(1);
74850
74998
  }
74851
74999
  });
75000
+ async function resolveOrExit(target, environment, json, spinner) {
75001
+ const project = loadProject(process.cwd());
75002
+ const resolution = await resolveActiveWhatsAppThread({
75003
+ target,
75004
+ environment,
75005
+ organizationId: project?.organization.id
75006
+ });
75007
+ if (resolution.kind === "none") {
75008
+ const error = `No active WhatsApp thread found for "${target}" in ${environment}. Did you mean --env production?`;
75009
+ spinner?.fail(error);
75010
+ if (json) {
75011
+ console.log(JSON.stringify({ success: false, error }));
75012
+ } else {
75013
+ console.log(chalk27.red("Error:"), error);
75014
+ }
75015
+ process.exit(1);
75016
+ }
75017
+ if (resolution.kind === "multiple") {
75018
+ spinner?.stop();
75019
+ if (json) {
75020
+ console.log(JSON.stringify({ success: false, error: `Multiple active threads match ${target}` }));
75021
+ } else {
75022
+ console.log(chalk27.yellow(`Multiple active threads match ${target}:`));
75023
+ for (const candidate of resolution.candidates) {
75024
+ console.log(` ${chalk27.cyan(candidate._id)} ${candidate.agentName ?? "Unknown"} ${threadPhone(candidate)}`);
75025
+ }
75026
+ console.log(chalk27.gray("Re-run with an explicit thread id."));
75027
+ }
75028
+ process.exit(1);
75029
+ }
75030
+ const thread = resolution.thread;
75031
+ if (!thread.externalId?.startsWith("whatsapp:")) {
75032
+ const error = `Thread ${thread._id} is not a WhatsApp conversation (reply/pause/resume only support WhatsApp).`;
75033
+ spinner?.fail(error);
75034
+ if (json) {
75035
+ console.log(JSON.stringify({ success: false, error }));
75036
+ } else {
75037
+ console.log(chalk27.red("Error:"), error);
75038
+ }
75039
+ process.exit(1);
75040
+ }
75041
+ return thread;
75042
+ }
75043
+ threadsCommand.command("reply").description("Send a WhatsApp message directly to a user as a human operator (does not pause the agent)").argument("<target>", "Thread id or phone number").requiredOption("-m, --message <text>", "Message to send to the user").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
75044
+ await ensureAuth7();
75045
+ const spinner = opts.json ? null : ora20();
75046
+ const environment = opts.env;
75047
+ try {
75048
+ spinner?.start(`Resolving thread for ${target}...`);
75049
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
75050
+ const phone = threadPhone(thread);
75051
+ if (spinner)
75052
+ spinner.text = `Sending to ${phone}...`;
75053
+ const result = await replyToThread({
75054
+ threadId: thread._id,
75055
+ message: opts.message,
75056
+ environment
75057
+ });
75058
+ if (result.whatsappStatus === "failed") {
75059
+ spinner?.fail(`Failed to send to ${phone} (env: ${environment})`);
75060
+ if (opts.json) {
75061
+ console.log(JSON.stringify({ success: false, threadId: thread._id, whatsappStatus: result.whatsappStatus, environment }));
75062
+ } else {
75063
+ console.log(chalk27.gray(`Tip: the 24h window may be closed or the connection is not connected. Re-open with a template: struere run-tool whatsapp.sendTemplate --args '{"to":"${phone}","templateName":"...","language":"..."}'`));
75064
+ }
75065
+ process.exit(1);
75066
+ }
75067
+ spinner?.succeed(chalk27.green(`Sent to ${phone} (env: ${environment})`));
75068
+ if (opts.json) {
75069
+ console.log(JSON.stringify({ success: true, threadId: thread._id, whatsappStatus: result.whatsappStatus, environment }));
75070
+ }
75071
+ } catch (err) {
75072
+ const message = err instanceof Error ? err.message : String(err);
75073
+ spinner?.fail("Failed to reply to thread");
75074
+ if (opts.json) {
75075
+ console.log(JSON.stringify({ success: false, error: message }));
75076
+ } else {
75077
+ console.log(chalk27.red("Error:"), message);
75078
+ }
75079
+ process.exit(1);
75080
+ }
75081
+ });
75082
+ threadsCommand.command("pause").description("Pause the agent on a conversation so it stops auto-responding").argument("<target>", "Thread id or phone number").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
75083
+ await ensureAuth7();
75084
+ const spinner = opts.json ? null : ora20();
75085
+ const environment = opts.env;
75086
+ try {
75087
+ spinner?.start(`Resolving thread for ${target}...`);
75088
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
75089
+ if (spinner)
75090
+ spinner.text = `Pausing agent for thread ${thread._id}...`;
75091
+ await pauseThread({ threadId: thread._id, environment });
75092
+ spinner?.succeed(chalk27.green(`Agent paused for thread ${thread._id} (env: ${environment})`));
75093
+ if (opts.json) {
75094
+ console.log(JSON.stringify({ success: true, threadId: thread._id, paused: true, environment }));
75095
+ }
75096
+ } catch (err) {
75097
+ const message = err instanceof Error ? err.message : String(err);
75098
+ spinner?.fail("Failed to pause thread");
75099
+ if (opts.json) {
75100
+ console.log(JSON.stringify({ success: false, error: message }));
75101
+ } else {
75102
+ console.log(chalk27.red("Error:"), message);
75103
+ }
75104
+ process.exit(1);
75105
+ }
75106
+ });
75107
+ threadsCommand.command("resume").description("Resume the agent on a paused conversation").argument("<target>", "Thread id or phone number").option("--env <environment>", "Environment: development | production | eval", "development").option("--json", "Output raw JSON").action(async (target, opts) => {
75108
+ await ensureAuth7();
75109
+ const spinner = opts.json ? null : ora20();
75110
+ const environment = opts.env;
75111
+ try {
75112
+ spinner?.start(`Resolving thread for ${target}...`);
75113
+ const thread = await resolveOrExit(target, environment, !!opts.json, spinner);
75114
+ if (spinner)
75115
+ spinner.text = `Resuming agent for thread ${thread._id}...`;
75116
+ await resumeThread({ threadId: thread._id, environment });
75117
+ spinner?.succeed(chalk27.green(`Agent resumed for thread ${thread._id} (env: ${environment})`));
75118
+ if (opts.json) {
75119
+ console.log(JSON.stringify({ success: true, threadId: thread._id, paused: false, environment }));
75120
+ }
75121
+ } catch (err) {
75122
+ const message = err instanceof Error ? err.message : String(err);
75123
+ spinner?.fail("Failed to resume thread");
75124
+ if (opts.json) {
75125
+ console.log(JSON.stringify({ success: false, error: message }));
75126
+ } else {
75127
+ console.log(chalk27.red("Error:"), message);
75128
+ }
75129
+ process.exit(1);
75130
+ }
75131
+ });
74852
75132
 
74853
75133
  // src/cli/commands/compile-prompt.ts
74854
75134
  init_credentials();
@@ -76904,7 +77184,7 @@ keysCommand.command("revoke <id-or-prefix>").description("Revoke an API key (del
76904
77184
  // package.json
76905
77185
  var package_default = {
76906
77186
  name: "struere",
76907
- version: "0.16.1",
77187
+ version: "0.16.3",
76908
77188
  description: "Build, test, and deploy AI agents",
76909
77189
  keywords: [
76910
77190
  "ai",
@@ -12,6 +12,7 @@ export interface ThreadPreview {
12
12
  } | null;
13
13
  participantName: string;
14
14
  messageCount: number;
15
+ agentPaused?: boolean;
15
16
  createdAt: number;
16
17
  updatedAt: number;
17
18
  }
@@ -58,4 +59,40 @@ export declare function findThreadByPhone(options: {
58
59
  environment: string;
59
60
  organizationId?: string;
60
61
  }): Promise<ThreadPreview | null>;
62
+ export declare function replyToThread(options: {
63
+ threadId: string;
64
+ message: string;
65
+ environment: string;
66
+ }): Promise<{
67
+ success: boolean;
68
+ whatsappStatus: 'sent' | 'failed' | 'skipped';
69
+ }>;
70
+ export declare function pauseThread(options: {
71
+ threadId: string;
72
+ environment: string;
73
+ }): Promise<{
74
+ success: boolean;
75
+ paused: boolean;
76
+ }>;
77
+ export declare function resumeThread(options: {
78
+ threadId: string;
79
+ environment: string;
80
+ }): Promise<{
81
+ success: boolean;
82
+ paused: boolean;
83
+ }>;
84
+ export type ResolveResult = {
85
+ kind: 'ok';
86
+ thread: ThreadPreview;
87
+ } | {
88
+ kind: 'none';
89
+ } | {
90
+ kind: 'multiple';
91
+ candidates: ThreadPreview[];
92
+ };
93
+ export declare function resolveActiveWhatsAppThread(options: {
94
+ target: string;
95
+ environment: string;
96
+ organizationId?: string;
97
+ }): Promise<ResolveResult>;
61
98
  //# sourceMappingURL=threads.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/threads.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACzE,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AA4GD,wBAAsB,WAAW,CAAC,OAAO,EAAE;IACzC,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB3B;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC;IACV,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC;QACd,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KACpE,CAAC,CAAA;CACH,GAAG,IAAI,CAAC,CAKR;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAmChC;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAgBhC"}
1
+ {"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/threads.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "struere",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "description": "Build, test, and deploy AI agents",
5
5
  "keywords": [
6
6
  "ai",