@stacksjs/buddy 0.70.258 → 0.70.260

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.
Files changed (112) hide show
  1. package/dist/cli.d.ts +11 -1
  2. package/dist/cli.js +5 -164
  3. package/dist/commands/about.js +1 -35
  4. package/dist/commands/add.js +1 -25
  5. package/dist/commands/ai-context.js +4 -27
  6. package/dist/commands/auth.js +1 -68
  7. package/dist/commands/build.js +1 -221
  8. package/dist/commands/cd.js +8 -39
  9. package/dist/commands/changelog.js +1 -32
  10. package/dist/commands/clean.js +1 -44
  11. package/dist/commands/cloud.js +2 -519
  12. package/dist/commands/commit.js +1 -16
  13. package/dist/commands/completion.js +6 -42
  14. package/dist/commands/config-migrate.js +6 -94
  15. package/dist/commands/configure.js +2 -45
  16. package/dist/commands/create.js +1 -162
  17. package/dist/commands/deploy.js +13 -1676
  18. package/dist/commands/desktop-apple.js +12 -399
  19. package/dist/commands/dev.js +6 -1083
  20. package/dist/commands/dns.js +3 -103
  21. package/dist/commands/docs/buddy-commands.js +10 -132
  22. package/dist/commands/docs/buddy-commands.test.js +1 -38
  23. package/dist/commands/docs/framework-repo.js +1 -14
  24. package/dist/commands/docs/framework-repo.test.js +1 -68
  25. package/dist/commands/docs/generated-artifacts.js +3 -70
  26. package/dist/commands/docs/generated-artifacts.test.js +1 -19
  27. package/dist/commands/docs/links.js +2 -85
  28. package/dist/commands/docs/links.test.js +4 -66
  29. package/dist/commands/docs.js +1 -24
  30. package/dist/commands/doctor.js +2 -478
  31. package/dist/commands/domains.js +2 -152
  32. package/dist/commands/email.js +31 -503
  33. package/dist/commands/env.js +1 -312
  34. package/dist/commands/extension.js +1 -239
  35. package/dist/commands/features.js +9 -345
  36. package/dist/commands/fresh.js +1 -46
  37. package/dist/commands/generate.js +2 -152
  38. package/dist/commands/http.js +1 -33
  39. package/dist/commands/index.js +1 -57
  40. package/dist/commands/install.js +1 -22
  41. package/dist/commands/key.js +1 -23
  42. package/dist/commands/link.js +2 -147
  43. package/dist/commands/lint.js +2 -84
  44. package/dist/commands/list.js +1 -140
  45. package/dist/commands/mail.js +15 -1477
  46. package/dist/commands/maintenance.js +1 -141
  47. package/dist/commands/make.js +1 -392
  48. package/dist/commands/migrate-project.js +1 -49
  49. package/dist/commands/migrate.js +27 -625
  50. package/dist/commands/outdated.js +1 -19
  51. package/dist/commands/package.js +1 -17
  52. package/dist/commands/phone.js +17 -181
  53. package/dist/commands/ports.js +2 -118
  54. package/dist/commands/prepublish.js +1 -17
  55. package/dist/commands/projects.js +1 -29
  56. package/dist/commands/protocol/craft-evidence.js +4 -98
  57. package/dist/commands/protocol/craft-evidence.test.js +1 -20
  58. package/dist/commands/protocol/desktop-lifecycle-report.js +3 -57
  59. package/dist/commands/protocol/desktop-lifecycle-report.test.js +1 -32
  60. package/dist/commands/protocol/desktop-support.js +2 -32
  61. package/dist/commands/protocol/desktop-workflow.test.js +3 -35
  62. package/dist/commands/protocol/driver-registry.js +2 -71
  63. package/dist/commands/protocol/pantry-evidence.js +4 -144
  64. package/dist/commands/protocol/pantry-evidence.test.js +1 -29
  65. package/dist/commands/protocol/release-manifest.js +3 -102
  66. package/dist/commands/protocol/release-manifest.test.js +2 -34
  67. package/dist/commands/protocol/run-conformance.js +4 -346
  68. package/dist/commands/protocol/run-conformance.test.js +1 -52
  69. package/dist/commands/protocol/run-driver-contracts.js +4 -83
  70. package/dist/commands/protocol/run-driver-contracts.test.js +1 -18
  71. package/dist/commands/protocol/source-manifest.js +3 -113
  72. package/dist/commands/protocol/sync-suite.js +3 -136
  73. package/dist/commands/protocol/sync-suite.test.js +1 -44
  74. package/dist/commands/protocol.js +1 -60
  75. package/dist/commands/publish.js +8 -500
  76. package/dist/commands/queue.js +1 -248
  77. package/dist/commands/release.js +1 -42
  78. package/dist/commands/route.js +1 -21
  79. package/dist/commands/run-tool.js +1 -9
  80. package/dist/commands/saas.js +1 -25
  81. package/dist/commands/schedule.js +1 -61
  82. package/dist/commands/search.js +1 -84
  83. package/dist/commands/seed.js +1 -47
  84. package/dist/commands/serve.js +1 -55
  85. package/dist/commands/setup-ai.js +2 -121
  86. package/dist/commands/setup.js +2 -305
  87. package/dist/commands/share.js +1 -214
  88. package/dist/commands/sms.js +32 -319
  89. package/dist/commands/stacks.js +1 -72
  90. package/dist/commands/telemetry.js +1 -74
  91. package/dist/commands/test.js +1 -130
  92. package/dist/commands/tinker.js +1 -37
  93. package/dist/commands/types.js +1 -18
  94. package/dist/commands/upgrade.js +1 -98
  95. package/dist/commands/user.js +3 -84
  96. package/dist/commands/version.js +1 -14
  97. package/dist/config.js +1 -231
  98. package/dist/custom-cli.js +1 -23
  99. package/dist/database-preflight.js +1 -88
  100. package/dist/global-options.js +1 -4
  101. package/dist/index.js +1 -1
  102. package/dist/lazy-commands.js +1 -241
  103. package/dist/migrators/index.js +2 -62
  104. package/dist/migrators/laravel/index.js +1 -148
  105. package/dist/migrators/laravel/migrations.js +4 -231
  106. package/dist/migrators/laravel/models.js +8 -111
  107. package/dist/migrators/rails/index.js +1 -11
  108. package/dist/production-server.js +1 -257
  109. package/dist/project-setup.js +1 -36
  110. package/dist/serve-entry.js +1 -5
  111. package/dist/version-info.js +1 -5
  112. package/package.json +53 -46
@@ -1,214 +1 @@
1
- import { getErrorMessage } from "@stacksjs/utils";
2
- import process from "node:process";
3
- import {
4
- runApiDevServer,
5
- runDashboardDevServer,
6
- runDesktopDevServer,
7
- runDocsDevServer,
8
- runFrontendDevServer
9
- } from "@stacksjs/actions";
10
- import { bold, cyan, dim, green, intro, log, outro, spinner } from "@stacksjs/cli";
11
- import { ports as configPorts } from "@stacksjs/config";
12
- import { ExitCode } from "@stacksjs/types";
13
- const originalStdoutWrite = process.stdout.write.bind(process.stdout), originalStderrWrite = process.stderr.write.bind(process.stderr), originalConsoleLog = console.log, originalConsoleError = console.error, originalConsoleWarn = console.warn, originalConsoleInfo = console.info;
14
- let _muted = !1, _verboseBuffer = [];
15
- function muteOutput() {
16
- _muted = !0;
17
- _verboseBuffer = [];
18
- const filter = (fn) => {
19
- return function(chunk, ...args) {
20
- if (_muted) {
21
- _verboseBuffer.push(String(chunk));
22
- return !0;
23
- }
24
- return fn(chunk, ...args);
25
- };
26
- };
27
- process.stdout.write = filter(originalStdoutWrite);
28
- process.stderr.write = filter(originalStderrWrite);
29
- console.log = (...args) => {
30
- if (!_muted)
31
- originalConsoleLog(...args);
32
- };
33
- console.error = (...args) => {
34
- if (!_muted)
35
- originalConsoleError(...args);
36
- };
37
- console.warn = (...args) => {
38
- if (!_muted)
39
- originalConsoleWarn(...args);
40
- };
41
- console.info = (...args) => {
42
- if (!_muted)
43
- originalConsoleInfo(...args);
44
- };
45
- }
46
- function unmuteOutput() {
47
- _muted = !1;
48
- process.stdout.write = originalStdoutWrite;
49
- process.stderr.write = originalStderrWrite;
50
- console.log = originalConsoleLog;
51
- console.error = originalConsoleError;
52
- console.warn = originalConsoleWarn;
53
- console.info = originalConsoleInfo;
54
- }
55
- async function waitForPort(port, host = "localhost", timeoutMs = 30000) {
56
- const start = Date.now();
57
- while (Date.now() - start < timeoutMs)
58
- try {
59
- await (await fetch(`http://${host}:${port}`, { signal: AbortSignal.timeout(1000) })).arrayBuffer();
60
- return;
61
- } catch {
62
- await Bun.sleep(300);
63
- }
64
- throw Error(`Timed out waiting for server on port ${port}`);
65
- }
66
- const devServerRunners = {
67
- frontend: runFrontendDevServer,
68
- api: runApiDevServer,
69
- backend: runApiDevServer,
70
- admin: runDashboardDevServer,
71
- dashboard: runDashboardDevServer,
72
- desktop: runDesktopDevServer,
73
- docs: runDocsDevServer
74
- }, companionServices = {
75
- frontend: [
76
- { port: configPorts?.api || 3008, suffix: "api", label: "API", runner: runApiDevServer },
77
- { port: configPorts?.docs || 3006, suffix: "docs", label: "Docs", runner: runDocsDevServer }
78
- ]
79
- };
80
- function capitalize(s) {
81
- return s.charAt(0).toUpperCase() + s.slice(1);
82
- }
83
- export function share(buddy) {
84
- buddy.command("share [type]", "Share your local development server via a public tunnel").option("-p, --port <port>", "Local port to share").option("--server <url>", "Tunnel server URL", { default: "api.localtunnel.dev" }).option("--subdomain <name>", "Request a specific subdomain").option("--verbose", "Enable verbose output", { default: !1 }).action(async (type, options) => {
85
- const perf = await intro("buddy share"), serviceType = type || "frontend", defaultPorts = {
86
- frontend: configPorts?.frontend || 3000,
87
- api: configPorts?.api || 3008,
88
- backend: configPorts?.backend || 3001,
89
- admin: configPorts?.admin || 3002,
90
- dashboard: configPorts?.admin || 3002,
91
- library: configPorts?.library || 3003,
92
- desktop: configPorts?.desktop || 3004,
93
- email: configPorts?.email || 3005,
94
- docs: configPorts?.docs || 3006,
95
- inspect: configPorts?.inspect || 3007
96
- }, port = options.port ? Number.parseInt(options.port, 10) : defaultPorts[serviceType] || 3000;
97
- if (Number.isNaN(port) || port < 1 || port > 65535) {
98
- log.error(`Invalid port: ${options.port}`);
99
- process.exit(ExitCode.InvalidArgument);
100
- }
101
- const server = options.server || "api.localtunnel.dev", tunnels = [], companions = companionServices[serviceType] || [], s = spinner();
102
- try {
103
- const { localTunnel } = await import("@stacksjs/tunnel");
104
- console.log();
105
- const runner = devServerRunners[serviceType];
106
- if (runner) {
107
- s.start(`Starting ${serviceType} dev server...`);
108
- muteOutput();
109
- runner({ verbose: options.verbose ?? !1 }).catch(() => {});
110
- await waitForPort(port);
111
- unmuteOutput();
112
- s.succeed(`${bold(capitalize(serviceType))} ready ${dim(`on :${port}`)}`);
113
- }
114
- const startedCompanions = [];
115
- if (companions.length > 0) {
116
- s.start("Starting companion services...");
117
- muteOutput();
118
- for (const companion of companions)
119
- companion.runner({ verbose: options.verbose ?? !1 }).catch(() => {});
120
- const results = await Promise.allSettled(companions.map((c) => waitForPort(c.port, "localhost", 60000)));
121
- unmuteOutput();
122
- for (let i = 0;i < companions.length; i++) {
123
- const result = results[i], companion = companions[i];
124
- if (!result || !companion)
125
- continue;
126
- if (result.status === "fulfilled") {
127
- startedCompanions.push(companion);
128
- s.succeed(`${bold(companion.label)} ready ${dim(`on :${companion.port}`)}`);
129
- } else
130
- s.fail(`${companion.label} failed to start ${dim(`on :${companion.port}`)}`);
131
- }
132
- }
133
- console.log();
134
- s.start("Creating tunnel...");
135
- const primaryTunnel = await localTunnel({
136
- port,
137
- server,
138
- subdomain: options.subdomain,
139
- verbose: options.verbose,
140
- onRequest: (req) => {
141
- if (options.verbose)
142
- console.log(` ${dim(`${req.method} ${req.url}`)}`);
143
- },
144
- onReconnecting: (info) => {
145
- s.start(`Reconnecting... (attempt ${info.attempt})`);
146
- }
147
- });
148
- tunnels.push(primaryTunnel);
149
- const baseSubdomain = primaryTunnel.subdomain;
150
- for (const companion of startedCompanions)
151
- try {
152
- const companionTunnel = await localTunnel({
153
- port: companion.port,
154
- server,
155
- subdomain: `${baseSubdomain}-${companion.suffix}`,
156
- verbose: options.verbose,
157
- onReconnecting: (info) => {
158
- s.start(`${companion.label}: reconnecting... (attempt ${info.attempt})`);
159
- }
160
- });
161
- tunnels.push(companionTunnel);
162
- } catch {}
163
- s.succeed(`Connected ${dim(`to ${server}`)}`);
164
- const entries = [
165
- { label: capitalize(serviceType), url: primaryTunnel.url, local: `localhost:${port}` }
166
- ];
167
- for (const companion of startedCompanions) {
168
- const tunnel = tunnels.find((t) => t.subdomain === `${baseSubdomain}-${companion.suffix}`);
169
- if (tunnel)
170
- entries.push({ label: companion.label, url: tunnel.url, local: `localhost:${companion.port}` });
171
- }
172
- const maxLabel = Math.max(...entries.map((e) => e.label.length));
173
- console.log();
174
- for (const entry of entries) {
175
- const padding = " ".repeat(maxLabel - entry.label.length + 1);
176
- console.log(` ${green("\u279C")} ${bold(entry.label)}${padding} ${cyan(entry.url)}`);
177
- }
178
- console.log();
179
- for (const entry of entries) {
180
- const padding = " ".repeat(maxLabel - entry.label.length + 1);
181
- console.log(` ${dim(entry.label)}${dim(padding)}${dim(entry.url)} ${dim("\u2192")} ${dim(entry.local)}`);
182
- }
183
- console.log();
184
- console.log(` ${dim("press Ctrl+C to stop")}`);
185
- console.log();
186
- const cleanup = async () => {
187
- console.log();
188
- s.start("Closing tunnels...");
189
- await Promise.all(tunnels.map((t) => t.close()));
190
- s.succeed("Tunnels closed");
191
- outro("Stopped sharing", { startTime: perf, useSeconds: !0 });
192
- process.exit(ExitCode.Success);
193
- };
194
- process.on("SIGINT", () => cleanup());
195
- process.on("SIGTERM", () => cleanup());
196
- await new Promise(() => {});
197
- } catch (error) {
198
- unmuteOutput();
199
- s.fail(getErrorMessage(error));
200
- const caught = error instanceof Error ? error : Error(String(error));
201
- if (caught.message.includes("timeout") || caught.message.includes("ECONNREFUSED")) {
202
- log.error(`Could not reach tunnel server at ${server}`);
203
- log.info(`Verify with: curl -sk https://${server}/status`);
204
- } else
205
- log.error(`Failed to create tunnel: ${caught.message}`);
206
- if (options.verbose)
207
- log.error(caught.stack);
208
- for (const t of tunnels)
209
- t.close();
210
- await outro("Share failed", { startTime: perf, useSeconds: !0 });
211
- process.exit(ExitCode.FatalError);
212
- }
213
- });
214
- }
1
+ import{getErrorMessage}from"@stacksjs/utils";import process from"node:process";import{runApiDevServer,runDashboardDevServer,runDesktopDevServer,runDocsDevServer,runFrontendDevServer}from"@stacksjs/actions";import{bold,cyan,dim,green,intro,log,outro,spinner}from"@stacksjs/cli";import{ports as configPorts}from"@stacksjs/config";import{ExitCode}from"@stacksjs/types";const originalStdoutWrite=process.stdout.write.bind(process.stdout),originalStderrWrite=process.stderr.write.bind(process.stderr),originalConsoleLog=console.log,originalConsoleError=console.error,originalConsoleWarn=console.warn,originalConsoleInfo=console.info;let _muted=!1,_verboseBuffer=[];function muteOutput(){_muted=!0;_verboseBuffer=[];const filter=(fn)=>{return function(chunk,...args){if(_muted){_verboseBuffer.push(String(chunk));return!0}return fn(chunk,...args)}};process.stdout.write=filter(originalStdoutWrite);process.stderr.write=filter(originalStderrWrite);console.log=(...args)=>{if(!_muted)originalConsoleLog(...args)};console.error=(...args)=>{if(!_muted)originalConsoleError(...args)};console.warn=(...args)=>{if(!_muted)originalConsoleWarn(...args)};console.info=(...args)=>{if(!_muted)originalConsoleInfo(...args)}}function unmuteOutput(){_muted=!1;process.stdout.write=originalStdoutWrite;process.stderr.write=originalStderrWrite;console.log=originalConsoleLog;console.error=originalConsoleError;console.warn=originalConsoleWarn;console.info=originalConsoleInfo}async function waitForPort(port,host="localhost",timeoutMs=30000){const start=Date.now();while(Date.now()-start<timeoutMs)try{await(await fetch(`http://${host}:${port}`,{signal:AbortSignal.timeout(1000)})).arrayBuffer();return}catch{await Bun.sleep(300)}throw Error(`Timed out waiting for server on port ${port}`)}const devServerRunners={frontend:runFrontendDevServer,api:runApiDevServer,backend:runApiDevServer,admin:runDashboardDevServer,dashboard:runDashboardDevServer,desktop:runDesktopDevServer,docs:runDocsDevServer},companionServices={frontend:[{port:configPorts?.api||3008,suffix:"api",label:"API",runner:runApiDevServer},{port:configPorts?.docs||3006,suffix:"docs",label:"Docs",runner:runDocsDevServer}]};function capitalize(s){return s.charAt(0).toUpperCase()+s.slice(1)}export function share(buddy){buddy.command("share [type]","Share your local development server via a public tunnel").option("-p, --port <port>","Local port to share").option("--server <url>","Tunnel server URL",{default:"api.localtunnel.dev"}).option("--subdomain <name>","Request a specific subdomain").option("--verbose","Enable verbose output",{default:!1}).action(async(type,options)=>{const perf=await intro("buddy share"),serviceType=type||"frontend",defaultPorts={frontend:configPorts?.frontend||3000,api:configPorts?.api||3008,backend:configPorts?.backend||3001,admin:configPorts?.admin||3002,dashboard:configPorts?.admin||3002,library:configPorts?.library||3003,desktop:configPorts?.desktop||3004,email:configPorts?.email||3005,docs:configPorts?.docs||3006,inspect:configPorts?.inspect||3007},port=options.port?Number.parseInt(options.port,10):defaultPorts[serviceType]||3000;if(Number.isNaN(port)||port<1||port>65535){log.error(`Invalid port: ${options.port}`);process.exit(ExitCode.InvalidArgument)}const server=options.server||"api.localtunnel.dev",tunnels=[],companions=companionServices[serviceType]||[],s=spinner();try{const{localTunnel}=await import("@stacksjs/tunnel");console.log();const runner=devServerRunners[serviceType];if(runner){s.start(`Starting ${serviceType} dev server...`);muteOutput();runner({verbose:options.verbose??!1}).catch(()=>{});await waitForPort(port);unmuteOutput();s.succeed(`${bold(capitalize(serviceType))} ready ${dim(`on :${port}`)}`)}const startedCompanions=[];if(companions.length>0){s.start("Starting companion services...");muteOutput();for(const companion of companions)companion.runner({verbose:options.verbose??!1}).catch(()=>{});const results=await Promise.allSettled(companions.map((c)=>waitForPort(c.port,"localhost",60000)));unmuteOutput();for(let i=0;i<companions.length;i++){const result=results[i],companion=companions[i];if(!result||!companion)continue;if(result.status==="fulfilled"){startedCompanions.push(companion);s.succeed(`${bold(companion.label)} ready ${dim(`on :${companion.port}`)}`)}else s.fail(`${companion.label} failed to start ${dim(`on :${companion.port}`)}`)}}console.log();s.start("Creating tunnel...");const primaryTunnel=await localTunnel({port,server,subdomain:options.subdomain,verbose:options.verbose,onRequest:(req)=>{if(options.verbose)console.log(` ${dim(`${req.method} ${req.url}`)}`)},onReconnecting:(info)=>{s.start(`Reconnecting... (attempt ${info.attempt})`)}});tunnels.push(primaryTunnel);const baseSubdomain=primaryTunnel.subdomain;for(const companion of startedCompanions)try{const companionTunnel=await localTunnel({port:companion.port,server,subdomain:`${baseSubdomain}-${companion.suffix}`,verbose:options.verbose,onReconnecting:(info)=>{s.start(`${companion.label}: reconnecting... (attempt ${info.attempt})`)}});tunnels.push(companionTunnel)}catch{}s.succeed(`Connected ${dim(`to ${server}`)}`);const entries=[{label:capitalize(serviceType),url:primaryTunnel.url,local:`localhost:${port}`}];for(const companion of startedCompanions){const tunnel=tunnels.find((t)=>t.subdomain===`${baseSubdomain}-${companion.suffix}`);if(tunnel)entries.push({label:companion.label,url:tunnel.url,local:`localhost:${companion.port}`})}const maxLabel=Math.max(...entries.map((e)=>e.label.length));console.log();for(const entry of entries){const padding=" ".repeat(maxLabel-entry.label.length+1);console.log(` ${green("\u279C")} ${bold(entry.label)}${padding} ${cyan(entry.url)}`)}console.log();for(const entry of entries){const padding=" ".repeat(maxLabel-entry.label.length+1);console.log(` ${dim(entry.label)}${dim(padding)}${dim(entry.url)} ${dim("\u2192")} ${dim(entry.local)}`)}console.log();console.log(` ${dim("press Ctrl+C to stop")}`);console.log();const cleanup=async()=>{console.log();s.start("Closing tunnels...");await Promise.all(tunnels.map((t)=>t.close()));s.succeed("Tunnels closed");outro("Stopped sharing",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)};process.on("SIGINT",()=>cleanup());process.on("SIGTERM",()=>cleanup());await new Promise(()=>{})}catch(error){unmuteOutput();s.fail(getErrorMessage(error));const caught=error instanceof Error?error:Error(String(error));if(caught.message.includes("timeout")||caught.message.includes("ECONNREFUSED")){log.error(`Could not reach tunnel server at ${server}`);log.info(`Verify with: curl -sk https://${server}/status`)}else log.error(`Failed to create tunnel: ${caught.message}`);if(options.verbose)log.error(caught.stack);for(const t of tunnels)t.close();await outro("Share failed",{startTime:perf,useSeconds:!0});process.exit(ExitCode.FatalError)}})}
@@ -1,328 +1,41 @@
1
- import { getErrorMessage } from "@stacksjs/utils";
2
- import { readFileSync, existsSync } from "node:fs";
3
- const TIMEOUT_MS = 30000;
4
- async function withTimeout(promise, ms = TIMEOUT_MS) {
5
- let timeoutId;
6
- const timeoutPromise = new Promise((_, reject) => {
7
- timeoutId = setTimeout(() => reject(Error(`Operation timed out after ${ms}ms`)), ms);
8
- });
9
- try {
10
- return await Promise.race([promise, timeoutPromise]);
11
- } finally {
12
- clearTimeout(timeoutId);
13
- }
14
- }
15
- function loadAwsCredentials() {
16
- const envPath = ".env.production";
17
- if (existsSync(envPath)) {
18
- const content = readFileSync(envPath, "utf-8");
19
- for (const line of content.split(`
20
- `)) {
21
- const eq = line.indexOf("=");
22
- if (eq > 0 && !line.startsWith("#"))
23
- process.env[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
24
- }
25
- }
26
- }
27
- const descriptions = {
28
- sms: "SMS service management commands",
29
- status: "Show SMS service status",
30
- send: "Send a test SMS",
31
- setup: "Initialize SMS infrastructure",
32
- verify: "Verify a sandbox phone number",
33
- "add-phone": "Add a phone to sandbox for testing"
34
- };
35
- export function sms(buddy) {
36
- buddy.command("sms", descriptions.sms).action(async () => {
37
- console.log(`
1
+ import{getErrorMessage}from"@stacksjs/utils";import{readFileSync,existsSync}from"node:fs";const TIMEOUT_MS=30000;async function withTimeout(promise,ms=TIMEOUT_MS){let timeoutId;const timeoutPromise=new Promise((_,reject)=>{timeoutId=setTimeout(()=>reject(Error(`Operation timed out after ${ms}ms`)),ms)});try{return await Promise.race([promise,timeoutPromise])}finally{clearTimeout(timeoutId)}}function loadAwsCredentials(){const envPath=".env.production";if(existsSync(envPath)){const content=readFileSync(envPath,"utf-8");for(const line of content.split(`
2
+ `)){const eq=line.indexOf("=");if(eq>0&&!line.startsWith("#"))process.env[line.slice(0,eq).trim()]=line.slice(eq+1).trim()}}}const descriptions={sms:"SMS service management commands",status:"Show SMS service status",send:"Send a test SMS",setup:"Initialize SMS infrastructure",verify:"Verify a sandbox phone number","add-phone":"Add a phone to sandbox for testing"};export function sms(buddy){buddy.command("sms",descriptions.sms).action(async()=>{console.log(`
38
3
  \uD83D\uDCF1 SMS Service Commands
39
- `);
40
- console.log(" buddy sms:status - Show SMS service status");
41
- console.log(" buddy sms:send - Send a test SMS");
42
- console.log(" buddy sms:setup - Initialize SMS infrastructure");
43
- console.log(" buddy sms:add-phone - Add a phone to sandbox for testing");
44
- console.log(" buddy sms:verify - Verify a sandbox phone number with OTP");
45
- console.log("");
46
- process.exit(0);
47
- });
48
- buddy.command("sms:status", descriptions.status).action(async () => {
49
- console.log(`
4
+ `);console.log(" buddy sms:status - Show SMS service status");console.log(" buddy sms:send - Send a test SMS");console.log(" buddy sms:setup - Initialize SMS infrastructure");console.log(" buddy sms:add-phone - Add a phone to sandbox for testing");console.log(" buddy sms:verify - Verify a sandbox phone number with OTP");console.log("");process.exit(0)});buddy.command("sms:status",descriptions.status).action(async()=>{console.log(`
50
5
  \uD83D\uDCF1 SMS Service Status
51
- `);
52
- loadAwsCredentials();
53
- try {
54
- const { getSmsInfrastructureStatus } = await import("@stacksjs/ts-cloud"), status = await withTimeout(getSmsInfrastructureStatus({
55
- region: process.env.AWS_REGION || "us-east-1",
56
- accountName: "stacks"
57
- }), 30000);
58
- console.log("Phone Numbers:");
59
- if (status.phoneNumbers.length === 0) {
60
- console.log(" No phone numbers provisioned");
61
- console.log(" Run `buddy sms:setup` to provision a phone number");
62
- } else
63
- for (const phone of status.phoneNumbers) {
64
- console.log(` ${phone.phoneNumber}`);
65
- console.log(` Status: ${phone.status}`);
66
- console.log(` Two-Way: ${phone.twoWayEnabled ? "Enabled" : "Disabled"}`);
67
- console.log(` Capabilities: ${phone.capabilities.join(", ") || "N/A"}`);
68
- }
69
- console.log(`
70
- Account:`);
71
- console.log(` Sandbox Status: ${status.sandboxStatus === "IN_SANDBOX" ? "\u26A0\uFE0F In Sandbox" : "\u2705 Production"}`);
72
- console.log(` Spending Limit: $${status.spendingLimit}/month`);
73
- console.log(`
74
- SNS Topics:`);
75
- if (status.topics.length === 0)
76
- console.log(" No SMS topics configured");
77
- else
78
- for (const topic of status.topics)
79
- console.log(` ${topic.name}`);
80
- if (status.sandboxStatus === "IN_SANDBOX") {
81
- console.log(`
82
- \uD83D\uDCA1 To exit sandbox mode:`);
83
- console.log(" Run `buddy sms:setup` to file a support request");
84
- console.log(" Or visit AWS Console > End User Messaging > SMS > Request production access");
85
- }
86
- if (status.phoneNumbers.some((p) => p.status === "PENDING"))
87
- console.log(`
88
- \u23F3 Note: Phone number verification can take 24-72 hours for toll-free numbers`);
89
- } catch (error) {
90
- console.error("Error checking status:", getErrorMessage(error));
91
- try {
92
- const { PinpointSmsVoiceClient } = await import("@stacksjs/ts-cloud"), smsClient = new PinpointSmsVoiceClient(process.env.AWS_REGION || "us-east-1"), activeNumbers = (await withTimeout(smsClient.describePhoneNumbers({}))).PhoneNumbers?.filter((p) => p.Status === "ACTIVE" || p.Status === "PENDING");
93
- if (activeNumbers && activeNumbers.length > 0) {
94
- console.log(`
95
- \u2705 SMS Service Active (End User Messaging)`);
96
- for (const phone of activeNumbers)
97
- console.log(` Phone: ${phone.PhoneNumber} (${phone.Status})`);
98
- } else {
99
- console.log(`
100
- \u274C SMS service not deployed.`);
101
- console.log(" Run `buddy sms:setup` to initialize");
102
- }
103
- } catch {
104
- console.log(`
105
- \u274C Could not connect to AWS. Check credentials.`);
106
- }
107
- }
108
- process.exit(0);
109
- });
110
- buddy.command("sms:send <phone> [message]", descriptions.send).option("--template <name>", "Use a message template").action(async (phone, message, options) => {
111
- const msg = message || "Test message from Stacks";
112
- console.log(`
6
+ `);loadAwsCredentials();try{const{getSmsInfrastructureStatus}=await import("@stacksjs/ts-cloud"),status=await withTimeout(getSmsInfrastructureStatus({region:process.env.AWS_REGION||"us-east-1",accountName:"stacks"}),30000);console.log("Phone Numbers:");if(status.phoneNumbers.length===0){console.log(" No phone numbers provisioned");console.log(" Run `buddy sms:setup` to provision a phone number")}else for(const phone of status.phoneNumbers){console.log(` ${phone.phoneNumber}`);console.log(` Status: ${phone.status}`);console.log(` Two-Way: ${phone.twoWayEnabled?"Enabled":"Disabled"}`);console.log(` Capabilities: ${phone.capabilities.join(", ")||"N/A"}`)}console.log(`
7
+ Account:`);console.log(` Sandbox Status: ${status.sandboxStatus==="IN_SANDBOX"?"\u26A0\uFE0F In Sandbox":"\u2705 Production"}`);console.log(` Spending Limit: $${status.spendingLimit}/month`);console.log(`
8
+ SNS Topics:`);if(status.topics.length===0)console.log(" No SMS topics configured");else for(const topic of status.topics)console.log(` ${topic.name}`);if(status.sandboxStatus==="IN_SANDBOX"){console.log(`
9
+ \uD83D\uDCA1 To exit sandbox mode:`);console.log(" Run `buddy sms:setup` to file a support request");console.log(" Or visit AWS Console > End User Messaging > SMS > Request production access")}if(status.phoneNumbers.some((p)=>p.status==="PENDING"))console.log(`
10
+ \u23F3 Note: Phone number verification can take 24-72 hours for toll-free numbers`)}catch(error){console.error("Error checking status:",getErrorMessage(error));try{const{PinpointSmsVoiceClient}=await import("@stacksjs/ts-cloud"),smsClient=new PinpointSmsVoiceClient(process.env.AWS_REGION||"us-east-1"),activeNumbers=(await withTimeout(smsClient.describePhoneNumbers({}))).PhoneNumbers?.filter((p)=>p.Status==="ACTIVE"||p.Status==="PENDING");if(activeNumbers&&activeNumbers.length>0){console.log(`
11
+ \u2705 SMS Service Active (End User Messaging)`);for(const phone of activeNumbers)console.log(` Phone: ${phone.PhoneNumber} (${phone.Status})`)}else{console.log(`
12
+ \u274C SMS service not deployed.`);console.log(" Run `buddy sms:setup` to initialize")}}catch{console.log(`
13
+ \u274C Could not connect to AWS. Check credentials.`)}}process.exit(0)});buddy.command("sms:send <phone> [message]",descriptions.send).option("--template <name>","Use a message template").action(async(phone,message,options)=>{const msg=message||"Test message from Stacks";console.log(`
113
14
  \uD83D\uDCF1 Sending SMS to ${phone}...
114
- `);
115
- loadAwsCredentials();
116
- try {
117
- const { SMSClient } = await import("@stacksjs/ts-cloud"), smsClient = new SMSClient({
118
- region: process.env.AWS_REGION || "us-east-1"
119
- }), smsConfig = await import("@stacksjs/config").then((m) => m.config?.sms);
120
- let originationNumber = smsConfig?.originationNumber;
121
- if (!originationNumber) {
122
- const { PinpointSmsVoiceClient } = await import("@stacksjs/ts-cloud"), pinpointV2 = new PinpointSmsVoiceClient(process.env.AWS_REGION || "us-east-1");
123
- originationNumber = (await withTimeout(pinpointV2.describePhoneNumbers({}))).PhoneNumbers?.find((p) => p.Status === "ACTIVE")?.PhoneNumber;
124
- }
125
- let finalMessage = msg;
126
- if (options?.template && smsConfig?.templates) {
127
- const template = smsConfig.templates.find((t) => t.name === options.template);
128
- if (template) {
129
- finalMessage = template.body;
130
- console.log(`Using template: ${options.template}`);
131
- } else
132
- console.log(`Template "${options.template}" not found. Using default message.`);
133
- }
134
- const result = await withTimeout(smsClient.send({
135
- to: phone,
136
- message: finalMessage,
137
- originationNumber
138
- }), 30000);
139
- if (result.messageId) {
140
- console.log("\u2705 SMS sent successfully!");
141
- console.log(` Message ID: ${result.messageId}`);
142
- if (originationNumber)
143
- console.log(` From: ${originationNumber}`);
144
- } else
145
- console.log("\u274C SMS send failed");
146
- } catch (error) {
147
- console.error("Error sending SMS:", getErrorMessage(error));
148
- if (getErrorMessage(error).includes("not found") || getErrorMessage(error).includes("No phone")) {
149
- console.log(`
150
- \uD83D\uDCA1 No origination phone number available.`);
151
- console.log(" Run `buddy sms:setup` to provision a phone number.");
152
- } else if (getErrorMessage(error).includes("not authorized"))
153
- console.log(`
154
- \uD83D\uDCA1 Make sure your AWS account has SMS permissions.`);
155
- else if (getErrorMessage(error).includes("sandbox"))
156
- console.log("\n\uD83D\uDCA1 Your account is in SMS sandbox. Run `buddy sms:setup` to request production access.");
157
- else if (getErrorMessage(error).includes("PENDING")) {
158
- console.log(`
159
- \uD83D\uDCA1 Phone number is still pending verification (24-72 hours for toll-free).`);
160
- console.log(" Run `buddy sms:status` to check current status.");
161
- }
162
- }
163
- process.exit(0);
164
- });
165
- buddy.command("sms:setup", descriptions.setup).option("--dry-run", "Show what would be done without making changes").action(async (options) => {
166
- console.log(`
15
+ `);loadAwsCredentials();try{const{SMSClient}=await import("@stacksjs/ts-cloud"),smsClient=new SMSClient({region:process.env.AWS_REGION||"us-east-1"}),smsConfig=await import("@stacksjs/config").then((m)=>m.config?.sms);let originationNumber=smsConfig?.originationNumber;if(!originationNumber){const{PinpointSmsVoiceClient}=await import("@stacksjs/ts-cloud"),pinpointV2=new PinpointSmsVoiceClient(process.env.AWS_REGION||"us-east-1");originationNumber=(await withTimeout(pinpointV2.describePhoneNumbers({}))).PhoneNumbers?.find((p)=>p.Status==="ACTIVE")?.PhoneNumber}let finalMessage=msg;if(options?.template&&smsConfig?.templates){const template=smsConfig.templates.find((t)=>t.name===options.template);if(template){finalMessage=template.body;console.log(`Using template: ${options.template}`)}else console.log(`Template "${options.template}" not found. Using default message.`)}const result=await withTimeout(smsClient.send({to:phone,message:finalMessage,originationNumber}),30000);if(result.messageId){console.log("\u2705 SMS sent successfully!");console.log(` Message ID: ${result.messageId}`);if(originationNumber)console.log(` From: ${originationNumber}`)}else console.log("\u274C SMS send failed")}catch(error){console.error("Error sending SMS:",getErrorMessage(error));if(getErrorMessage(error).includes("not found")||getErrorMessage(error).includes("No phone")){console.log(`
16
+ \uD83D\uDCA1 No origination phone number available.`);console.log(" Run `buddy sms:setup` to provision a phone number.")}else if(getErrorMessage(error).includes("not authorized"))console.log(`
17
+ \uD83D\uDCA1 Make sure your AWS account has SMS permissions.`);else if(getErrorMessage(error).includes("sandbox"))console.log("\n\uD83D\uDCA1 Your account is in SMS sandbox. Run `buddy sms:setup` to request production access.");else if(getErrorMessage(error).includes("PENDING")){console.log(`
18
+ \uD83D\uDCA1 Phone number is still pending verification (24-72 hours for toll-free).`);console.log(" Run `buddy sms:status` to check current status.")}}process.exit(0)});buddy.command("sms:setup",descriptions.setup).option("--dry-run","Show what would be done without making changes").action(async(options)=>{console.log(`
167
19
  \uD83D\uDCF1 SMS Service Setup
168
- `);
169
- loadAwsCredentials();
170
- try {
171
- const smsConfig = await import("@stacksjs/config").then((m) => m.config?.sms);
172
- if (!smsConfig?.enabled) {
173
- console.log("SMS is disabled in config/sms.ts");
174
- console.log(`
175
- To enable SMS:`);
176
- console.log("1. Edit config/sms.ts and set enabled: true");
177
- console.log("2. Run `buddy sms:setup` again");
178
- process.exit(0);
179
- return;
180
- }
181
- if (options.dryRun) {
182
- console.log("[Dry Run] Would set up SMS infrastructure with config:");
183
- console.log(` Provider: ${smsConfig.provider}`);
184
- console.log(` Country: ${smsConfig.defaultCountryCode}`);
185
- console.log(` Message Type: ${smsConfig.messageType}`);
186
- console.log(` Monthly Limit: $${smsConfig.maxSpendPerMonth || 1}`);
187
- console.log(` Two-Way: ${smsConfig.twoWay?.enabled ? "Enabled" : "Disabled"}`);
188
- console.log(` Inbox: ${smsConfig.inbox?.enabled ? "Enabled" : "Disabled"}`);
189
- process.exit(0);
190
- return;
191
- }
192
- console.log(`Setting up SMS infrastructure...
193
- `);
194
- const { createSmsInfrastructure } = await import("@stacksjs/ts-cloud"), result = await withTimeout(createSmsInfrastructure({
195
- enabled: smsConfig.enabled,
196
- provider: smsConfig.provider || "pinpoint",
197
- originationNumber: smsConfig.originationNumber,
198
- defaultCountryCode: smsConfig.defaultCountryCode || "US",
199
- messageType: smsConfig.messageType || "TRANSACTIONAL",
200
- maxSpendPerMonth: smsConfig.maxSpendPerMonth,
201
- inbox: smsConfig.inbox,
202
- twoWay: smsConfig.twoWay,
203
- optOut: smsConfig.optOut || { enabled: !0, keywords: ["STOP"] }
204
- }), 120000);
205
- if (result.success) {
206
- console.log(`
20
+ `);loadAwsCredentials();try{const smsConfig=await import("@stacksjs/config").then((m)=>m.config?.sms);if(!smsConfig?.enabled){console.log("SMS is disabled in config/sms.ts");console.log(`
21
+ To enable SMS:`);console.log("1. Edit config/sms.ts and set enabled: true");console.log("2. Run `buddy sms:setup` again");process.exit(0);return}if(options.dryRun){console.log("[Dry Run] Would set up SMS infrastructure with config:");console.log(` Provider: ${smsConfig.provider}`);console.log(` Country: ${smsConfig.defaultCountryCode}`);console.log(` Message Type: ${smsConfig.messageType}`);console.log(` Monthly Limit: $${smsConfig.maxSpendPerMonth||1}`);console.log(` Two-Way: ${smsConfig.twoWay?.enabled?"Enabled":"Disabled"}`);console.log(` Inbox: ${smsConfig.inbox?.enabled?"Enabled":"Disabled"}`);process.exit(0);return}console.log(`Setting up SMS infrastructure...
22
+ `);const{createSmsInfrastructure}=await import("@stacksjs/ts-cloud"),result=await withTimeout(createSmsInfrastructure({enabled:smsConfig.enabled,provider:smsConfig.provider||"pinpoint",originationNumber:smsConfig.originationNumber,defaultCountryCode:smsConfig.defaultCountryCode||"US",messageType:smsConfig.messageType||"TRANSACTIONAL",maxSpendPerMonth:smsConfig.maxSpendPerMonth,inbox:smsConfig.inbox,twoWay:smsConfig.twoWay,optOut:smsConfig.optOut||{enabled:!0,keywords:["STOP"]}}),120000);if(result.success){console.log(`
207
23
  \u2705 SMS infrastructure setup complete!
208
- `);
209
- if (result.phoneNumber)
210
- console.log(` Phone Number: ${result.phoneNumber} (${result.phoneNumberStatus})`);
211
- if (result.twoWayTopicArn)
212
- console.log(` Two-Way Topic: ${result.twoWayTopicArn}`);
213
- if (result.inboxBucket)
214
- console.log(` Inbox: s3://${result.inboxBucket}/${result.inboxPrefix}`);
215
- console.log(` Spending Limit: $${result.spendingLimit}/month`);
216
- console.log(` Sandbox Status: ${result.sandboxStatus}`);
217
- if (result.supportCaseId) {
218
- console.log(`
219
- \uD83D\uDCCB Support Case: ${result.supportCaseId}`);
220
- console.log(" A support ticket has been filed for sandbox exit.");
221
- }
222
- if (result.warnings.length > 0) {
223
- console.log(`
224
- \u26A0\uFE0F Warnings:`);
225
- for (const warning of result.warnings)
226
- console.log(` - ${warning}`);
227
- }
228
- console.log(`
229
- \uD83D\uDCA1 Next steps:`);
230
- console.log(' - Run `buddy sms:send +1234567890 "Test"` to send a test SMS');
231
- console.log(" - Run `buddy sms:status` to check current status");
232
- } else {
233
- console.log(`
24
+ `);if(result.phoneNumber)console.log(` Phone Number: ${result.phoneNumber} (${result.phoneNumberStatus})`);if(result.twoWayTopicArn)console.log(` Two-Way Topic: ${result.twoWayTopicArn}`);if(result.inboxBucket)console.log(` Inbox: s3://${result.inboxBucket}/${result.inboxPrefix}`);console.log(` Spending Limit: $${result.spendingLimit}/month`);console.log(` Sandbox Status: ${result.sandboxStatus}`);if(result.supportCaseId){console.log(`
25
+ \uD83D\uDCCB Support Case: ${result.supportCaseId}`);console.log(" A support ticket has been filed for sandbox exit.")}if(result.warnings.length>0){console.log(`
26
+ \u26A0\uFE0F Warnings:`);for(const warning of result.warnings)console.log(` - ${warning}`)}console.log(`
27
+ \uD83D\uDCA1 Next steps:`);console.log(' - Run `buddy sms:send +1234567890 "Test"` to send a test SMS');console.log(" - Run `buddy sms:status` to check current status")}else{console.log(`
234
28
  \u274C SMS setup completed with errors:
235
- `);
236
- for (const error of result.errors)
237
- console.log(` - ${error}`);
238
- }
239
- } catch (error) {
240
- console.error("Error setting up SMS:", getErrorMessage(error));
241
- if (getErrorMessage(error).includes("timeout")) {
242
- console.log(`
243
- \uD83D\uDCA1 Setup timed out. Some operations may have completed.`);
244
- console.log(" Run `buddy sms:status` to check current status.");
245
- }
246
- }
247
- process.exit(0);
248
- });
249
- buddy.command("sms:add-phone <phone>", descriptions["add-phone"]).action(async (phone) => {
250
- console.log(`
29
+ `);for(const error of result.errors)console.log(` - ${error}`)}}catch(error){console.error("Error setting up SMS:",getErrorMessage(error));if(getErrorMessage(error).includes("timeout")){console.log(`
30
+ \uD83D\uDCA1 Setup timed out. Some operations may have completed.`);console.log(" Run `buddy sms:status` to check current status.")}}process.exit(0)});buddy.command("sms:add-phone <phone>",descriptions["add-phone"]).action(async(phone)=>{console.log(`
251
31
  \uD83D\uDCF1 Adding ${phone} to SMS sandbox...
252
- `);
253
- loadAwsCredentials();
254
- try {
255
- const { PinpointSmsVoiceClient } = await import("@stacksjs/ts-cloud"), smsClient = new PinpointSmsVoiceClient(process.env.AWS_REGION || "us-east-1"), result = await withTimeout(smsClient.addSandboxPhone(phone), 30000);
256
- if (result.status === "VERIFIED") {
257
- console.log("\u2705 Phone is already verified!");
258
- console.log(` ID: ${result.verifiedDestinationNumberId}`);
259
- } else if (result.status === "PENDING") {
260
- console.log("\uD83D\uDCE8 Verification code sent to phone!");
261
- console.log(` ID: ${result.verifiedDestinationNumberId}`);
262
- console.log(`
263
- \uD83D\uDCA1 Next step:`);
264
- console.log(` Run: buddy sms:verify ${result.verifiedDestinationNumberId} <code>`);
265
- console.log(" Replace <code> with the 6-digit code you received");
266
- } else {
267
- console.log("\u2753 Unexpected status:", result.status);
268
- console.log(" Message:", result.message);
269
- }
270
- } catch (error) {
271
- console.error("Error adding phone:", getErrorMessage(error));
272
- if (getErrorMessage(error).includes("already exists")) {
273
- console.log(`
274
- \uD83D\uDCA1 Phone number may already be in the sandbox.`);
275
- console.log(" Run `buddy sms:status` to check sandbox numbers.");
276
- }
277
- }
278
- process.exit(0);
279
- });
280
- buddy.command("sms:verify <id> <code>", descriptions.verify).action(async (id, code) => {
281
- console.log(`
32
+ `);loadAwsCredentials();try{const{PinpointSmsVoiceClient}=await import("@stacksjs/ts-cloud"),smsClient=new PinpointSmsVoiceClient(process.env.AWS_REGION||"us-east-1"),result=await withTimeout(smsClient.addSandboxPhone(phone),30000);if(result.status==="VERIFIED"){console.log("\u2705 Phone is already verified!");console.log(` ID: ${result.verifiedDestinationNumberId}`)}else if(result.status==="PENDING"){console.log("\uD83D\uDCE8 Verification code sent to phone!");console.log(` ID: ${result.verifiedDestinationNumberId}`);console.log(`
33
+ \uD83D\uDCA1 Next step:`);console.log(` Run: buddy sms:verify ${result.verifiedDestinationNumberId} <code>`);console.log(" Replace <code> with the 6-digit code you received")}else{console.log("\u2753 Unexpected status:",result.status);console.log(" Message:",result.message)}}catch(error){console.error("Error adding phone:",getErrorMessage(error));if(getErrorMessage(error).includes("already exists")){console.log(`
34
+ \uD83D\uDCA1 Phone number may already be in the sandbox.`);console.log(" Run `buddy sms:status` to check sandbox numbers.")}}process.exit(0)});buddy.command("sms:verify <id> <code>",descriptions.verify).action(async(id,code)=>{console.log(`
282
35
  \uD83D\uDCF1 Verifying sandbox phone...
283
- `);
284
- loadAwsCredentials();
285
- try {
286
- const { PinpointSmsVoiceClient } = await import("@stacksjs/ts-cloud"), smsClient = new PinpointSmsVoiceClient(process.env.AWS_REGION || "us-east-1"), result = await withTimeout(smsClient.verifySandboxPhone(id, code), 30000);
287
- if (result.success) {
288
- console.log("\u2705 Phone verified successfully!");
289
- console.log(` Phone: ${result.phoneNumber}`);
290
- console.log(`
291
- \uD83D\uDCA1 You can now send SMS to this phone while in sandbox mode.`);
292
- console.log(` Run: buddy sms:send ${result.phoneNumber} "Test message"`);
293
- } else {
294
- console.log("\u274C Verification failed");
295
- console.log(` Error: ${result.message}`);
296
- console.log(`
297
- \uD83D\uDCA1 Make sure you entered the correct 6-digit code.`);
298
- console.log(" Run `buddy sms:add-phone <phone>` to resend the code.");
299
- }
300
- } catch (error) {
301
- console.error("Error verifying phone:", getErrorMessage(error));
302
- }
303
- process.exit(0);
304
- });
305
- buddy.command("sms:sandbox-list", "List sandbox verified phone numbers").action(async () => {
306
- console.log(`
36
+ `);loadAwsCredentials();try{const{PinpointSmsVoiceClient}=await import("@stacksjs/ts-cloud"),smsClient=new PinpointSmsVoiceClient(process.env.AWS_REGION||"us-east-1"),result=await withTimeout(smsClient.verifySandboxPhone(id,code),30000);if(result.success){console.log("\u2705 Phone verified successfully!");console.log(` Phone: ${result.phoneNumber}`);console.log(`
37
+ \uD83D\uDCA1 You can now send SMS to this phone while in sandbox mode.`);console.log(` Run: buddy sms:send ${result.phoneNumber} "Test message"`)}else{console.log("\u274C Verification failed");console.log(` Error: ${result.message}`);console.log(`
38
+ \uD83D\uDCA1 Make sure you entered the correct 6-digit code.`);console.log(" Run `buddy sms:add-phone <phone>` to resend the code.")}}catch(error){console.error("Error verifying phone:",getErrorMessage(error))}process.exit(0)});buddy.command("sms:sandbox-list","List sandbox verified phone numbers").action(async()=>{console.log(`
307
39
  \uD83D\uDCF1 Sandbox Verified Numbers
308
- `);
309
- loadAwsCredentials();
310
- try {
311
- const { PinpointSmsVoiceClient } = await import("@stacksjs/ts-cloud"), smsClient = new PinpointSmsVoiceClient(process.env.AWS_REGION || "us-east-1"), phones = await withTimeout(smsClient.listSandboxPhones(), 30000);
312
- if (phones.length === 0) {
313
- console.log("No verified sandbox numbers yet.");
314
- console.log(`
315
- To add a phone:`);
316
- console.log(" buddy sms:add-phone +1234567890");
317
- } else
318
- for (const phone of phones) {
319
- const statusIcon = phone.status === "VERIFIED" ? "\u2705" : "\u23F3";
320
- console.log(` ${statusIcon} ${phone.phoneNumber} (${phone.status})`);
321
- console.log(` ID: ${phone.id}`);
322
- }
323
- } catch (error) {
324
- console.error("Error listing sandbox phones:", getErrorMessage(error));
325
- }
326
- process.exit(0);
327
- });
328
- }
40
+ `);loadAwsCredentials();try{const{PinpointSmsVoiceClient}=await import("@stacksjs/ts-cloud"),smsClient=new PinpointSmsVoiceClient(process.env.AWS_REGION||"us-east-1"),phones=await withTimeout(smsClient.listSandboxPhones(),30000);if(phones.length===0){console.log("No verified sandbox numbers yet.");console.log(`
41
+ To add a phone:`);console.log(" buddy sms:add-phone +1234567890")}else for(const phone of phones){const statusIcon=phone.status==="VERIFIED"?"\u2705":"\u23F3";console.log(` ${statusIcon} ${phone.phoneNumber} (${phone.status})`);console.log(` ID: ${phone.id}`)}}catch(error){console.error("Error listing sandbox phones:",getErrorMessage(error))}process.exit(0)})}