@tokenbuddy/tb-admin 1.0.15 → 1.0.27

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 (43) hide show
  1. package/dist/src/cli.d.ts.map +1 -1
  2. package/dist/src/cli.js +286 -13
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/client.d.ts +12 -3
  5. package/dist/src/client.d.ts.map +1 -1
  6. package/dist/src/client.js +12 -8
  7. package/dist/src/client.js.map +1 -1
  8. package/dist/src/display-format.d.ts +39 -0
  9. package/dist/src/display-format.d.ts.map +1 -0
  10. package/dist/src/display-format.js +354 -0
  11. package/dist/src/display-format.js.map +1 -0
  12. package/dist/src/server-cmd.d.ts +3 -0
  13. package/dist/src/server-cmd.d.ts.map +1 -1
  14. package/dist/src/server-cmd.js +32 -9
  15. package/dist/src/server-cmd.js.map +1 -1
  16. package/dist/src/ui-actions.d.ts +2 -0
  17. package/dist/src/ui-actions.d.ts.map +1 -1
  18. package/dist/src/ui-actions.js +123 -63
  19. package/dist/src/ui-actions.js.map +1 -1
  20. package/dist/src/ui-command.js +1 -1
  21. package/dist/src/ui-command.js.map +1 -1
  22. package/dist/src/ui-server.d.ts +0 -1
  23. package/dist/src/ui-server.d.ts.map +1 -1
  24. package/dist/src/ui-server.js +25 -9
  25. package/dist/src/ui-server.js.map +1 -1
  26. package/dist/src/ui-state.d.ts +7 -1
  27. package/dist/src/ui-state.d.ts.map +1 -1
  28. package/dist/src/ui-state.js +55 -24
  29. package/dist/src/ui-state.js.map +1 -1
  30. package/dist/src/ui-static.d.ts.map +1 -1
  31. package/dist/src/ui-static.js +372 -47
  32. package/dist/src/ui-static.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/cli.ts +326 -13
  35. package/src/client.ts +13 -8
  36. package/src/display-format.ts +398 -0
  37. package/src/server-cmd.ts +35 -9
  38. package/src/ui-actions.ts +129 -72
  39. package/src/ui-command.ts +1 -1
  40. package/src/ui-server.ts +24 -10
  41. package/src/ui-state.ts +64 -25
  42. package/src/ui-static.ts +375 -47
  43. package/tests/admin.test.ts +573 -41
@@ -0,0 +1,398 @@
1
+ // Shared display formatters for TokenBuddy UI surfaces.
2
+ //
3
+ // This module is a portable copy of `packages/tb-ui/src/lib/display-format.ts`
4
+ // and the canonical formatter functions declared in `DESIGN.md`
5
+ // ("Formatter Implementation" section). Keep both files in sync — the
6
+ // rule from DESIGN.md is:
7
+ // "Do not add new per-page formatting helpers for these domains.
8
+ // If a page needs a different display, add an explicit option
9
+ // to the shared formatter."
10
+ //
11
+ // Differences vs. the React copy:
12
+ // 1. No React-only imports.
13
+ // 2. Adds `formatBalanceAmount` and `formatSellerStatus` for the
14
+ // admin surface, because the admin UI surfaces live balance
15
+ // numbers and node-status dots that the buyer UI never shows.
16
+
17
+ export const UNKNOWN_VALUE = "—";
18
+
19
+ const PAD2 = (n: number) => (n < 10 ? `0${n}` : `${n}`);
20
+
21
+ export function formatTokenCount(
22
+ value: number | undefined | null,
23
+ options: { compact?: boolean } = {}
24
+ ): string {
25
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
26
+ const numeric = value as number;
27
+ if (options.compact === false) return Math.round(numeric).toLocaleString("en-US");
28
+ if (numeric < 10_000) return Math.round(numeric).toLocaleString("en-US");
29
+ if (numeric < 1_000_000) return `${(numeric / 1_000).toFixed(1)}K`;
30
+ if (numeric < 1_000_000_000) return `${(numeric / 1_000_000).toFixed(1)}M`;
31
+ return `${(numeric / 1_000_000_000).toFixed(2)}B`;
32
+ }
33
+
34
+ export function formatTokenPair(
35
+ input: number | undefined | null,
36
+ output: number | undefined | null,
37
+ options: { compact?: boolean; separator?: string } = {}
38
+ ): string {
39
+ const separator = options.separator ?? " / ";
40
+ return `In ${formatTokenCount(input, options)}${separator}Out ${formatTokenCount(output, options)}`;
41
+ }
42
+
43
+ export function formatCount(value: number | undefined | null): string {
44
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
45
+ return Math.round(value as number).toLocaleString("en-US");
46
+ }
47
+
48
+ export function formatMoney(
49
+ micros: number | undefined | null,
50
+ options: { digits?: number; ledger?: boolean; signed?: boolean } = {}
51
+ ): string {
52
+ if (!Number.isFinite(micros)) return UNKNOWN_VALUE;
53
+ const amount = Math.abs((micros as number) / 1_000_000);
54
+ const digits = options.digits ?? (options.ledger && amount < 0.01 ? 6 : 4);
55
+ const formatted = `$${((micros as number) / 1_000_000).toFixed(digits)}`;
56
+ return options.signed && (micros as number) >= 0 ? `+${formatted}` : formatted;
57
+ }
58
+
59
+ export function formatMoneyPair(
60
+ actualMicros: number | undefined | null,
61
+ referenceMicros: number | undefined | null,
62
+ options: { digits?: number } = {}
63
+ ): string {
64
+ if (!Number.isFinite(actualMicros) && !Number.isFinite(referenceMicros)) return UNKNOWN_VALUE;
65
+ return `${formatMoney(actualMicros, options)} / ${formatMoney(referenceMicros, options)}`;
66
+ }
67
+
68
+ export function formatDuration(valueMs: number | undefined | null): string {
69
+ if (!Number.isFinite(valueMs)) return UNKNOWN_VALUE;
70
+ const ms = Math.max(0, Math.round(valueMs as number));
71
+ if (ms < 1000) return `${ms}ms`;
72
+ return `${(ms / 1000).toFixed(2)}s`;
73
+ }
74
+
75
+ export function formatPercent(value: number | undefined | null): string {
76
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
77
+ return `${Math.round((value as number) * 100)}%`;
78
+ }
79
+
80
+ export function formatDiscountRatio(discountRatio: number | undefined | null): string {
81
+ if (!Number.isFinite(discountRatio)) return UNKNOWN_VALUE;
82
+ const discount = Math.max(0, 1 - (discountRatio as number));
83
+ return formatPercent(discount);
84
+ }
85
+
86
+ export function formatPriceMicrosPerMillion(value: number | undefined | null): string {
87
+ return formatMoney(value, { digits: 4 });
88
+ }
89
+
90
+ export function formatPricePair(
91
+ inputMicros: number | undefined | null,
92
+ outputMicros: number | undefined | null
93
+ ): string {
94
+ return formatMoneyPair(inputMicros, outputMicros, { digits: 4 });
95
+ }
96
+
97
+ export function normalizeStatusLabel(status: string | undefined | null): string {
98
+ if (!status) return UNKNOWN_VALUE;
99
+ return status.trim().toLowerCase().replaceAll("_", " ");
100
+ }
101
+
102
+ export type StatusTone = "green" | "amber" | "red" | "blue" | "gray";
103
+
104
+ export function statusTone(status: string | undefined | null): StatusTone {
105
+ const normalized = normalizeStatusLabel(status);
106
+ if (
107
+ normalized === "ok" ||
108
+ normalized === "online" ||
109
+ normalized === "configured" ||
110
+ normalized === "settled" ||
111
+ normalized === "completed" ||
112
+ normalized === "success" ||
113
+ normalized === "active" ||
114
+ normalized === "healthy"
115
+ ) {
116
+ return "green";
117
+ }
118
+ if (
119
+ normalized === "fallback" ||
120
+ normalized === "pending" ||
121
+ normalized === "degraded" ||
122
+ normalized === "preview" ||
123
+ normalized === "draining" ||
124
+ normalized === "busy capacity" ||
125
+ normalized === "auth unknown"
126
+ ) {
127
+ return "amber";
128
+ }
129
+ if (
130
+ normalized === "failed" ||
131
+ normalized === "error" ||
132
+ normalized === "canceled" ||
133
+ normalized === "unhealthy" ||
134
+ normalized === "offline"
135
+ ) {
136
+ return "red";
137
+ }
138
+ if (normalized === "running") return "blue";
139
+ return "gray";
140
+ }
141
+
142
+ export function formatHash(value: string | undefined | null, length = 32): string {
143
+ if (!value) return UNKNOWN_VALUE;
144
+ return value.length > length ? `${value.slice(0, length)}...` : value;
145
+ }
146
+
147
+ export function formatSellerId(value: string | undefined | null): string {
148
+ if (!value) return UNKNOWN_VALUE;
149
+ if (value.startsWith("tbs-") && value.length > 10) return value.slice(0, 10);
150
+ if (value.length <= 12) return value;
151
+ return value.slice(0, 12);
152
+ }
153
+
154
+ export function formatTimeCompact(iso: string | undefined | null): string {
155
+ if (!iso) return UNKNOWN_VALUE;
156
+ const d = new Date(iso);
157
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
158
+ const now = new Date();
159
+ const sameDay =
160
+ d.getFullYear() === now.getFullYear() &&
161
+ d.getMonth() === now.getMonth() &&
162
+ d.getDate() === now.getDate();
163
+ const time = `${PAD2(d.getHours())}:${PAD2(d.getMinutes())}`;
164
+ if (sameDay) return time;
165
+ return `${PAD2(d.getMonth() + 1)}/${PAD2(d.getDate())} ${time}`;
166
+ }
167
+
168
+ export function formatTimeFull(iso: string | undefined | null): string {
169
+ if (!iso) return UNKNOWN_VALUE;
170
+ const d = new Date(iso);
171
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
172
+ return (
173
+ `${d.getFullYear()}-${PAD2(d.getMonth() + 1)}-${PAD2(d.getDate())} ` +
174
+ `${PAD2(d.getHours())}:${PAD2(d.getMinutes())}:${PAD2(d.getSeconds())}`
175
+ );
176
+ }
177
+
178
+ export function formatTimeLedger(iso: string | undefined | null): string {
179
+ if (!iso) return UNKNOWN_VALUE;
180
+ const d = new Date(iso);
181
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
182
+ return (
183
+ `${d.getFullYear()}/${PAD2(d.getMonth() + 1)}/${PAD2(d.getDate())} ` +
184
+ `${PAD2(d.getHours())}:${PAD2(d.getMinutes())}:${PAD2(d.getSeconds())}`
185
+ );
186
+ }
187
+
188
+ export function formatRouteSwitchTime(iso: string | undefined | null): string {
189
+ return formatTimeCompact(iso);
190
+ }
191
+
192
+ // --- admin-only extensions ---------------------------------------------------
193
+
194
+ export function formatBalanceAmount(
195
+ usdMicros: number | undefined | null,
196
+ currency: string | undefined | null
197
+ ): string {
198
+ if (!Number.isFinite(usdMicros)) return UNKNOWN_VALUE;
199
+ const amount = (usdMicros as number) / 1_000_000;
200
+ const digits = Math.abs(amount) >= 100 ? 0 : 2;
201
+ const code = (currency || "USD").toUpperCase();
202
+ return `${code} ${amount.toFixed(digits)}`;
203
+ }
204
+
205
+ export function formatSellerCapacity(
206
+ used: number | undefined | null,
207
+ limit: number | undefined | null
208
+ ): string {
209
+ if (!Number.isFinite(used) && !Number.isFinite(limit)) return UNKNOWN_VALUE;
210
+ const u = Number.isFinite(used) ? Math.round(used as number) : UNKNOWN_VALUE;
211
+ const l = Number.isFinite(limit) ? Math.round(limit as number) : UNKNOWN_VALUE;
212
+ return `${u} / ${l}`;
213
+ }
214
+
215
+ export function formatSpeed(value: number | undefined | null): string {
216
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
217
+ return `${(value as number).toFixed(1)} tok/s`;
218
+ }
219
+
220
+ export type CanonicalStatus =
221
+ | "ok"
222
+ | "online"
223
+ | "configured"
224
+ | "pending"
225
+ | "degraded"
226
+ | "error"
227
+ | "unknown";
228
+
229
+ const SELLER_STATUS_MAP: Record<string, CanonicalStatus> = {
230
+ active: "ok",
231
+ healthy: "ok",
232
+ online: "online",
233
+ configured: "configured",
234
+ pending: "pending",
235
+ draining: "degraded",
236
+ degraded: "degraded",
237
+ busy_capacity: "degraded",
238
+ offline: "error",
239
+ unhealthy: "error",
240
+ error: "error",
241
+ auth_unknown: "unknown",
242
+ unknown: "unknown"
243
+ };
244
+
245
+ export function formatSellerStatus(status: string | undefined | null): CanonicalStatus {
246
+ const key = String(status || "unknown")
247
+ .trim()
248
+ .toLowerCase()
249
+ .replace(/-/g, "_");
250
+ return SELLER_STATUS_MAP[key] || "unknown";
251
+ }
252
+
253
+ export function sellerStatusTone(status: string | undefined | null): StatusTone {
254
+ return statusTone(formatSellerStatus(status));
255
+ }
256
+
257
+ // Inlined browser bundle — emits a self-invoking function that
258
+ // attaches the same formatter API to `window.__tbFmt`. The HTML
259
+ // served by `tb-admin ui` includes this bundle verbatim so the
260
+ // page can use the shared spec-compliant helpers without an
261
+ // extra <script src> round-trip. The JS bodies below MUST stay
262
+ // in lockstep with the TS implementations above.
263
+ export function displayFormatBundle(): string {
264
+ return `(() => {
265
+ const UNKNOWN_VALUE = ${JSON.stringify(UNKNOWN_VALUE)};
266
+ const PAD2 = (n) => (n < 10 ? "0" + n : "" + n);
267
+ function formatTokenCount(value, options) {
268
+ options = options || {};
269
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
270
+ const numeric = value;
271
+ if (options.compact === false) return Math.round(numeric).toLocaleString("en-US");
272
+ if (numeric < 10000) return Math.round(numeric).toLocaleString("en-US");
273
+ if (numeric < 1000000) return (numeric / 1000).toFixed(1) + "K";
274
+ if (numeric < 1000000000) return (numeric / 1000000).toFixed(1) + "M";
275
+ return (numeric / 1000000000).toFixed(2) + "B";
276
+ }
277
+ function formatTokenPair(input, output, options) {
278
+ options = options || {};
279
+ const separator = options.separator == null ? " / " : options.separator;
280
+ return "In " + formatTokenCount(input, options) + separator + "Out " + formatTokenCount(output, options);
281
+ }
282
+ function formatCount(value) {
283
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
284
+ return Math.round(value).toLocaleString("en-US");
285
+ }
286
+ function formatMoney(micros, options) {
287
+ options = options || {};
288
+ if (!Number.isFinite(micros)) return UNKNOWN_VALUE;
289
+ const amount = Math.abs(micros / 1000000);
290
+ const digits = options.digits != null ? options.digits : (options.ledger && amount < 0.01 ? 6 : 4);
291
+ const formatted = "$" + (micros / 1000000).toFixed(digits);
292
+ return options.signed && micros >= 0 ? "+" + formatted : formatted;
293
+ }
294
+ function formatMoneyPair(actualMicros, referenceMicros, options) {
295
+ options = options || {};
296
+ if (!Number.isFinite(actualMicros) && !Number.isFinite(referenceMicros)) return UNKNOWN_VALUE;
297
+ return formatMoney(actualMicros, options) + " / " + formatMoney(referenceMicros, options);
298
+ }
299
+ function formatDuration(valueMs) {
300
+ if (!Number.isFinite(valueMs)) return UNKNOWN_VALUE;
301
+ const ms = Math.max(0, Math.round(valueMs));
302
+ if (ms < 1000) return ms + "ms";
303
+ return (ms / 1000).toFixed(2) + "s";
304
+ }
305
+ function formatPercent(value) {
306
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
307
+ return Math.round(value * 100) + "%";
308
+ }
309
+ function formatDiscountRatio(discountRatio) {
310
+ if (!Number.isFinite(discountRatio)) return UNKNOWN_VALUE;
311
+ const discount = Math.max(0, 1 - discountRatio);
312
+ return formatPercent(discount);
313
+ }
314
+ function formatPriceMicrosPerMillion(value) { return formatMoney(value, { digits: 4 }); }
315
+ function formatPricePair(inputMicros, outputMicros) { return formatMoneyPair(inputMicros, outputMicros, { digits: 4 }); }
316
+ function normalizeStatusLabel(status) {
317
+ if (!status) return UNKNOWN_VALUE;
318
+ return String(status).trim().toLowerCase().replaceAll("_", " ");
319
+ }
320
+ function statusTone(status) {
321
+ const n = normalizeStatusLabel(status);
322
+ if (n === "ok" || n === "online" || n === "configured" || n === "settled" || n === "completed" || n === "success" || n === "active" || n === "healthy") return "green";
323
+ if (n === "fallback" || n === "pending" || n === "degraded" || n === "preview" || n === "draining" || n === "busy capacity" || n === "auth unknown") return "amber";
324
+ if (n === "failed" || n === "error" || n === "canceled" || n === "unhealthy" || n === "offline") return "red";
325
+ if (n === "running") return "blue";
326
+ return "gray";
327
+ }
328
+ function formatHash(value, length) {
329
+ if (length == null) length = 32;
330
+ if (!value) return UNKNOWN_VALUE;
331
+ return value.length > length ? value.slice(0, length) + "..." : value;
332
+ }
333
+ function formatSellerId(value) {
334
+ if (!value) return UNKNOWN_VALUE;
335
+ if (value.startsWith("tbs-") && value.length > 10) return value.slice(0, 10);
336
+ if (value.length <= 12) return value;
337
+ return value.slice(0, 12);
338
+ }
339
+ function formatTimeCompact(iso) {
340
+ if (!iso) return UNKNOWN_VALUE;
341
+ const d = new Date(iso);
342
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
343
+ const now = new Date();
344
+ const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
345
+ const time = PAD2(d.getHours()) + ":" + PAD2(d.getMinutes());
346
+ if (sameDay) return time;
347
+ return PAD2(d.getMonth() + 1) + "/" + PAD2(d.getDate()) + " " + time;
348
+ }
349
+ function formatTimeFull(iso) {
350
+ if (!iso) return UNKNOWN_VALUE;
351
+ const d = new Date(iso);
352
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
353
+ return d.getFullYear() + "-" + PAD2(d.getMonth() + 1) + "-" + PAD2(d.getDate()) + " " + PAD2(d.getHours()) + ":" + PAD2(d.getMinutes()) + ":" + PAD2(d.getSeconds());
354
+ }
355
+ function formatTimeLedger(iso) {
356
+ if (!iso) return UNKNOWN_VALUE;
357
+ const d = new Date(iso);
358
+ if (Number.isNaN(d.getTime())) return UNKNOWN_VALUE;
359
+ return d.getFullYear() + "/" + PAD2(d.getMonth() + 1) + "/" + PAD2(d.getDate()) + " " + PAD2(d.getHours()) + ":" + PAD2(d.getMinutes()) + ":" + PAD2(d.getSeconds());
360
+ }
361
+ function formatRouteSwitchTime(iso) { return formatTimeCompact(iso); }
362
+ function formatBalanceAmount(usdMicros, currency) {
363
+ if (!Number.isFinite(usdMicros)) return UNKNOWN_VALUE;
364
+ const amount = usdMicros / 1000000;
365
+ const digits = Math.abs(amount) >= 100 ? 0 : 2;
366
+ const code = (currency || "USD").toUpperCase();
367
+ return code + " " + amount.toFixed(digits);
368
+ }
369
+ function formatSellerCapacity(used, limit) {
370
+ if (!Number.isFinite(used) && !Number.isFinite(limit)) return UNKNOWN_VALUE;
371
+ const u = Number.isFinite(used) ? Math.round(used) : UNKNOWN_VALUE;
372
+ const l = Number.isFinite(limit) ? Math.round(limit) : UNKNOWN_VALUE;
373
+ return u + " / " + l;
374
+ }
375
+ function formatSpeed(value) {
376
+ if (!Number.isFinite(value)) return UNKNOWN_VALUE;
377
+ return value.toFixed(1) + " tok/s";
378
+ }
379
+ const SELLER_STATUS_MAP = {
380
+ active: "ok", healthy: "ok", online: "online", configured: "configured",
381
+ pending: "pending", draining: "degraded", degraded: "degraded",
382
+ busy_capacity: "degraded", offline: "error", unhealthy: "error",
383
+ error: "error", auth_unknown: "unknown", unknown: "unknown"
384
+ };
385
+ function formatSellerStatus(status) {
386
+ const key = String(status || "unknown").trim().toLowerCase().replace(/-/g, "_");
387
+ return SELLER_STATUS_MAP[key] || "unknown";
388
+ }
389
+ function sellerStatusTone(status) { return statusTone(formatSellerStatus(status)); }
390
+ window.__tbFmt = {
391
+ UNKNOWN_VALUE, formatTokenCount, formatTokenPair, formatCount, formatMoney, formatMoneyPair,
392
+ formatDuration, formatPercent, formatDiscountRatio, formatPriceMicrosPerMillion, formatPricePair,
393
+ normalizeStatusLabel, statusTone, formatHash, formatSellerId, formatTimeCompact, formatTimeFull,
394
+ formatTimeLedger, formatRouteSwitchTime, formatBalanceAmount, formatSellerCapacity, formatSpeed,
395
+ formatSellerStatus, sellerStatusTone
396
+ };
397
+ })();`;
398
+ }
package/src/server-cmd.ts CHANGED
@@ -141,6 +141,32 @@ export class FlyProvider {
141
141
  return this.providerConfig?.flyctl_path || "flyctl";
142
142
  }
143
143
 
144
+ private flyExecOptions(options: Parameters<typeof execSync>[1] = {}): Parameters<typeof execSync>[1] {
145
+ return {
146
+ ...options,
147
+ env: this.flyEnv(options.env)
148
+ };
149
+ }
150
+
151
+ private flySpawnOptions(options: Parameters<typeof spawnSync>[2] = {}): Parameters<typeof spawnSync>[2] {
152
+ return {
153
+ ...options,
154
+ env: this.flyEnv(options.env)
155
+ };
156
+ }
157
+
158
+ private flyEnv(env: NodeJS.ProcessEnv | undefined): NodeJS.ProcessEnv {
159
+ const configuredToken = this.providerConfig?.token;
160
+ const merged = {
161
+ ...process.env,
162
+ ...(env || {})
163
+ };
164
+ if (configuredToken && !merged.FLY_API_TOKEN) {
165
+ merged.FLY_API_TOKEN = configuredToken;
166
+ }
167
+ return merged;
168
+ }
169
+
144
170
  /**
145
171
  * List apps on Fly.io
146
172
  */
@@ -148,7 +174,7 @@ export class FlyProvider {
148
174
  if (!this.runtime.checkFlyctlInstalled(this.flyctl)) {
149
175
  throw new Error(`\`${this.flyctl}\` is not installed on your system PATH.`);
150
176
  }
151
- return this.runtime.execSync(`${this.flyctl} apps list`, { encoding: "utf8" }) as string;
177
+ return this.runtime.execSync(`${this.flyctl} apps list`, this.flyExecOptions({ encoding: "utf8" })) as string;
152
178
  }
153
179
 
154
180
  /**
@@ -213,14 +239,14 @@ export class FlyProvider {
213
239
  requirePublishedDockerImage(targetImage, this.runtime.imageInspector);
214
240
 
215
241
  console.log(`[Fly.io] Creating app ${appName}...`);
216
- this.runtime.execSync(`${this.flyctl} apps create ${appName} --machines`, { stdio: "inherit" });
242
+ this.runtime.execSync(`${this.flyctl} apps create ${appName} --machines`, this.flyExecOptions({ stdio: "inherit" }));
217
243
 
218
244
  console.log(`[Fly.io] Setting secrets...`);
219
245
  this.importCreateSecrets(appName, operatorSecret, initialConfigPath);
220
246
 
221
247
  console.log(`[Fly.io] Deploying image ${targetImage}...`);
222
248
  const deployCmd = `${this.flyctl} deploy -c ${flyConfig} --image ${targetImage} --primary-region ${targetRegion} --app ${appName} --now`;
223
- this.runtime.execSync(deployCmd, { stdio: "inherit" });
249
+ this.runtime.execSync(deployCmd, this.flyExecOptions({ stdio: "inherit" }));
224
250
 
225
251
  return `Successfully deployed ${appName} on Fly.io`;
226
252
  }
@@ -234,10 +260,10 @@ export class FlyProvider {
234
260
  const configContent = fs.readFileSync(initialConfigPath, "utf8");
235
261
  lines.push(`TOKENBUDDY_SELLER_CONFIG_B64=${Buffer.from(configContent, "utf8").toString("base64")}`);
236
262
  }
237
- const result = this.runtime.spawnSync(this.flyctl, ["secrets", "import", "--stage", "--app", appName], {
263
+ const result = this.runtime.spawnSync(this.flyctl, ["secrets", "import", "--stage", "--app", appName], this.flySpawnOptions({
238
264
  input: `${lines.join("\n")}\n`,
239
265
  stdio: ["pipe", "inherit", "inherit"]
240
- });
266
+ }));
241
267
  if (result.error) {
242
268
  throw result.error;
243
269
  }
@@ -263,7 +289,7 @@ export class FlyProvider {
263
289
  }
264
290
 
265
291
  console.log(`[Fly.io] Destroying app ${appName}...`);
266
- this.runtime.execSync(`${this.flyctl} apps destroy ${appName} --yes`, { stdio: "inherit" });
292
+ this.runtime.execSync(`${this.flyctl} apps destroy ${appName} --yes`, this.flyExecOptions({ stdio: "inherit" }));
267
293
 
268
294
  return `Successfully destroyed ${appName} on Fly.io`;
269
295
  }
@@ -276,7 +302,7 @@ export class FlyProvider {
276
302
  if (!this.runtime.checkFlyctlInstalled(this.flyctl)) {
277
303
  throw new Error(`\`${this.flyctl}\` is not installed.`);
278
304
  }
279
- return this.runtime.execSync(`${this.flyctl} status --app ${appName}`, { encoding: "utf8" }) as string;
305
+ return this.runtime.execSync(`${this.flyctl} status --app ${appName}`, this.flyExecOptions({ encoding: "utf8" })) as string;
280
306
  }
281
307
 
282
308
  /**
@@ -310,14 +336,14 @@ export class FlyProvider {
310
336
  throw new Error(`\`${this.flyctl}\` is not installed.`);
311
337
  }
312
338
 
313
- const machinesJson = this.runtime.execSync(`${this.flyctl} machines list --app ${app} --json`, { encoding: "utf8" }) as string;
339
+ const machinesJson = this.runtime.execSync(`${this.flyctl} machines list --app ${app} --json`, this.flyExecOptions({ encoding: "utf8" })) as string;
314
340
  const machineIds = parseFlyMachineIds(machinesJson, app);
315
341
 
316
342
  console.log(`[Fly.io] Updating ${app} image on ${machineIds.length} machine(s)...`);
317
343
  for (const machineId of machineIds) {
318
344
  this.runtime.execSync(
319
345
  `${this.flyctl} machine update ${machineId} --app ${app} --image ${targetImage} --yes`,
320
- { stdio: "inherit" }
346
+ this.flyExecOptions({ stdio: "inherit" })
321
347
  );
322
348
  }
323
349
  return `Successfully updated ${app} image`;