@agentuity/cli 1.0.20 → 1.0.22

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 (44) hide show
  1. package/dist/cmd/build/vite/static-renderer.d.ts +27 -0
  2. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -0
  3. package/dist/cmd/build/vite/static-renderer.js +182 -0
  4. package/dist/cmd/build/vite/static-renderer.js.map +1 -0
  5. package/dist/cmd/build/vite/vite-builder.d.ts +5 -0
  6. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  7. package/dist/cmd/build/vite/vite-builder.js +16 -0
  8. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  9. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  10. package/dist/cmd/build/vite-bundler.js +3 -0
  11. package/dist/cmd/build/vite-bundler.js.map +1 -1
  12. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  13. package/dist/cmd/cloud/deploy.js +1 -1
  14. package/dist/cmd/cloud/deploy.js.map +1 -1
  15. package/dist/cmd/project/add/domain.d.ts.map +1 -1
  16. package/dist/cmd/project/add/domain.js +6 -4
  17. package/dist/cmd/project/add/domain.js.map +1 -1
  18. package/dist/cmd/project/domain/check.d.ts.map +1 -1
  19. package/dist/cmd/project/domain/check.js +10 -5
  20. package/dist/cmd/project/domain/check.js.map +1 -1
  21. package/dist/cmd/project/reconcile.d.ts.map +1 -1
  22. package/dist/cmd/project/reconcile.js +19 -7
  23. package/dist/cmd/project/reconcile.js.map +1 -1
  24. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  25. package/dist/cmd/project/template-flow.js +2 -1
  26. package/dist/cmd/project/template-flow.js.map +1 -1
  27. package/dist/domain.d.ts +3 -2
  28. package/dist/domain.d.ts.map +1 -1
  29. package/dist/domain.js +90 -21
  30. package/dist/domain.js.map +1 -1
  31. package/dist/types.d.ts +7 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/package.json +6 -6
  35. package/src/cmd/build/vite/static-renderer.ts +236 -0
  36. package/src/cmd/build/vite/vite-builder.ts +18 -0
  37. package/src/cmd/build/vite-bundler.ts +7 -0
  38. package/src/cmd/cloud/deploy.ts +1 -0
  39. package/src/cmd/project/add/domain.ts +10 -4
  40. package/src/cmd/project/domain/check.ts +16 -5
  41. package/src/cmd/project/reconcile.ts +31 -13
  42. package/src/cmd/project/template-flow.ts +2 -1
  43. package/src/domain.ts +99 -21
  44. package/src/types.ts +8 -0
package/src/domain.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import type { Config } from './types';
2
2
  import { StructuredError } from '@agentuity/core';
3
+ import { getIONHost } from './config';
3
4
  import * as tui from './tui';
4
5
 
5
6
  interface BaseDNSResult {
6
7
  domain: string;
7
8
  target: string;
8
9
  recordType: string;
10
+ aRecordTarget?: string;
9
11
  }
10
12
 
11
13
  interface DNSSuccess extends BaseDNSResult {
@@ -68,23 +70,29 @@ interface CFRecord {
68
70
  }[];
69
71
  }
70
72
 
71
- async function fetchDNSRecord(name: string, type: string): Promise<string | null> {
73
+ async function fetchDNSRecords(name: string, type: string): Promise<string[]> {
72
74
  const params = new URLSearchParams();
73
75
  params.set('name', name);
74
76
  params.set('type', type);
77
+ params.set('_', Date.now().toString());
75
78
  const res = await fetch(`https://cloudflare-dns.com/dns-query?${params.toString()}`, {
76
79
  headers: {
77
80
  Accept: 'application/dns-json',
78
81
  },
82
+ // @ts-expect-error - cache is supported by Bun's fetch at runtime but missing from type definitions
83
+ cache: 'no-store',
79
84
  });
80
85
  if (res.ok) {
81
86
  const result = (await res.json()) as CFRecord;
82
- const firstAnswer = result?.Answer?.[0];
83
- if (firstAnswer) {
84
- return firstAnswer.data.replace(/\.$/, ''); // DNS records end with . so we remove that
85
- }
87
+ // DNS records end with . so we remove that
88
+ return (result?.Answer ?? []).map((a) => a.data.replace(/\.$/, ''));
86
89
  }
87
- return null;
90
+ return [];
91
+ }
92
+
93
+ async function fetchDNSRecord(name: string, type: string): Promise<string | null> {
94
+ const records = await fetchDNSRecords(name, type);
95
+ return records[0] ?? null;
88
96
  }
89
97
 
90
98
  const LOCAL_DNS = 'agentuity.io';
@@ -102,12 +110,24 @@ const PRODUCTION_DNS = 'agentuity.run';
102
110
  export async function checkCustomDomainForDNS(
103
111
  projectId: string,
104
112
  domains: string[],
113
+ region: string,
105
114
  config?: Config | null
106
115
  ): Promise<DNSResult[]> {
107
116
  const suffix = config?.overrides?.api_url?.includes('agentuity.io') ? LOCAL_DNS : PRODUCTION_DNS;
108
117
  const id = Bun.hash.xxHash64(projectId).toString(16).padStart(16, '0');
109
118
  const proxy = `p${id}.${suffix}`;
110
119
 
120
+ // Resolve the ION host A record(s) so we can validate A records
121
+ // and show the user what IP to point their A record to
122
+ const ionHost = getIONHost(config ?? null, region);
123
+ let ionIPs: string[] = [];
124
+ try {
125
+ ionIPs = await fetchDNSRecords(ionHost, 'A');
126
+ } catch {
127
+ // If we can't resolve the ION host, A record validation will be skipped
128
+ }
129
+ const aRecordTarget = ionIPs[0] ?? undefined;
130
+
111
131
  return Promise.all(
112
132
  domains.map(async (domain) => {
113
133
  // Detect if user passed a URL instead of a domain name
@@ -117,6 +137,7 @@ export async function checkCustomDomainForDNS(
117
137
  return {
118
138
  domain,
119
139
  target: proxy,
140
+ aRecordTarget,
120
141
  recordType: 'CNAME',
121
142
  success: false,
122
143
  error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name: "${url.hostname}"`,
@@ -125,6 +146,7 @@ export async function checkCustomDomainForDNS(
125
146
  return {
126
147
  domain,
127
148
  target: proxy,
149
+ aRecordTarget,
128
150
  recordType: 'CNAME',
129
151
  success: false,
130
152
  error: `Invalid domain format: "${domain}" appears to be a URL. Use just the domain name without the protocol (e.g., "example.com" not "https://example.com")`,
@@ -132,6 +154,7 @@ export async function checkCustomDomainForDNS(
132
154
  }
133
155
  }
134
156
 
157
+ // Step 1: Check CNAME record
135
158
  try {
136
159
  let timeoutId: Timer | undefined;
137
160
 
@@ -153,6 +176,7 @@ export async function checkCustomDomainForDNS(
153
176
  return {
154
177
  domain,
155
178
  target: proxy,
179
+ aRecordTarget,
156
180
  recordType: 'CNAME',
157
181
  success: true,
158
182
  } as DNSSuccess;
@@ -160,9 +184,10 @@ export async function checkCustomDomainForDNS(
160
184
  return {
161
185
  domain,
162
186
  target: proxy,
187
+ aRecordTarget,
163
188
  recordType: 'CNAME',
164
189
  success: false,
165
- misconfigured: `CNAME record is ${result}`,
190
+ misconfigured: `CNAME record points to ${result}`,
166
191
  } as DNSMisconfigured;
167
192
  }
168
193
  } catch (ex) {
@@ -171,6 +196,7 @@ export async function checkCustomDomainForDNS(
171
196
  return {
172
197
  domain,
173
198
  target: proxy,
199
+ aRecordTarget,
174
200
  recordType: 'CNAME',
175
201
  success: false,
176
202
  error: `DNS lookup timed out after 5 seconds. Please check your DNS configuration.`,
@@ -186,16 +212,63 @@ export async function checkCustomDomainForDNS(
186
212
  return {
187
213
  domain,
188
214
  target: proxy,
215
+ aRecordTarget,
189
216
  recordType: 'CNAME',
190
217
  success: false,
191
218
  error: errMsg,
192
219
  } as DNSError;
193
220
  }
221
+ // ENOTFOUND: no CNAME record exists, fall through to A record check
194
222
  }
223
+
224
+ // Step 2: Check A record (supports apex domains and ALIAS/ANAME/CNAME-flattening)
225
+ if (ionIPs.length > 0) {
226
+ try {
227
+ let aTimeoutId: Timer | undefined;
228
+
229
+ const aTimeoutPromise = new Promise<never>((_, reject) => {
230
+ aTimeoutId = setTimeout(() => {
231
+ reject(new DNSTimeoutError());
232
+ }, timeoutMs);
233
+ });
234
+
235
+ const domainARecords = await Promise.race([
236
+ fetchDNSRecords(domain, 'A'),
237
+ aTimeoutPromise,
238
+ ]).finally(() => {
239
+ if (aTimeoutId) clearTimeout(aTimeoutId);
240
+ });
241
+
242
+ if (domainARecords.length > 0) {
243
+ const matching = domainARecords.some((a) => ionIPs.includes(a));
244
+ if (matching) {
245
+ return {
246
+ domain,
247
+ target: proxy,
248
+ aRecordTarget,
249
+ recordType: 'A',
250
+ success: true,
251
+ } as DNSSuccess;
252
+ }
253
+ return {
254
+ domain,
255
+ target: proxy,
256
+ aRecordTarget,
257
+ recordType: 'A',
258
+ success: false,
259
+ misconfigured: `A record points to ${domainARecords[0]}, expected ${aRecordTarget}`,
260
+ } as DNSMisconfigured;
261
+ }
262
+ } catch {
263
+ // A record check failed, fall through to missing
264
+ }
265
+ }
266
+
195
267
  return {
196
268
  domain,
197
269
  success: false,
198
270
  target: proxy,
271
+ aRecordTarget,
199
272
  recordType: 'CNAME',
200
273
  pending: false,
201
274
  } as DNSMissing;
@@ -206,27 +279,28 @@ export async function checkCustomDomainForDNS(
206
279
  export async function promptForDNS(
207
280
  projectId: string,
208
281
  domains: string[],
282
+ region: string,
209
283
  config?: Config,
210
284
  resumeFn?: () => () => void
211
285
  ) {
212
286
  let paused = false;
213
287
  let resume: (() => void) | undefined;
214
288
  for (;;) {
215
- const result = await checkCustomDomainForDNS(projectId, domains, config);
289
+ const result = await checkCustomDomainForDNS(projectId, domains, region, config);
216
290
  const failed = result.filter((x): x is DNSFailed => !isSuccess(x));
217
291
  if (failed.length) {
218
292
  const records: {
219
293
  domain: string;
220
- type: string;
221
- target: string;
294
+ cnameTarget: string;
295
+ aRecordTarget?: string;
222
296
  status: string;
223
297
  }[] = [];
224
298
  result.forEach((r) => {
225
299
  if (isSuccess(r)) {
226
300
  records.push({
227
301
  domain: r.domain,
228
- type: r.recordType,
229
- target: r.target,
302
+ cnameTarget: r.target,
303
+ aRecordTarget: r.aRecordTarget,
230
304
  status: tui.colorSuccess(`${tui.ICONS.success} Configured`),
231
305
  });
232
306
  }
@@ -245,22 +319,22 @@ export async function promptForDNS(
245
319
  } else if (isMisconfigured(r)) {
246
320
  records.push({
247
321
  domain: r.domain,
248
- type: r.recordType,
249
- target: r.target,
322
+ cnameTarget: r.target,
323
+ aRecordTarget: r.aRecordTarget,
250
324
  status: tui.colorWarning(`${tui.ICONS.error} ${r.misconfigured}`),
251
325
  });
252
326
  } else if (isPending(r)) {
253
327
  records.push({
254
328
  domain: r.domain,
255
- type: r.recordType,
256
- target: r.target,
329
+ cnameTarget: r.target,
330
+ aRecordTarget: r.aRecordTarget,
257
331
  status: tui.colorWarning('⌛️ Pending'),
258
332
  });
259
333
  } else if (isMissing(r)) {
260
334
  records.push({
261
335
  domain: r.domain,
262
- type: r.recordType,
263
- target: r.target,
336
+ cnameTarget: r.target,
337
+ aRecordTarget: r.aRecordTarget,
264
338
  status: tui.colorError(`${tui.ICONS.error} Missing`),
265
339
  });
266
340
  }
@@ -270,11 +344,15 @@ export async function promptForDNS(
270
344
  for (const record of records) {
271
345
  console.log();
272
346
  console.log(`${tui.colorInfo('Domain:')} ${tui.colorPrimary(record.domain)}`);
273
- console.log(`${tui.colorInfo('Type:')} ${tui.colorPrimary(record.type)}`);
274
- console.log(`${tui.colorInfo('Target:')} ${tui.colorPrimary(record.target)}`);
347
+ console.log(`${tui.colorInfo('CNAME:')} ${tui.colorPrimary(record.cnameTarget)}`);
348
+ if (record.aRecordTarget) {
349
+ console.log(
350
+ `${tui.colorInfo('A:')} ${tui.colorPrimary(record.aRecordTarget)}`
351
+ );
352
+ }
275
353
  console.log(`${tui.colorInfo('Status:')} ${tui.colorPrimary(record.status)}`);
276
354
  console.log();
277
- linesShown += 6;
355
+ linesShown += record.aRecordTarget ? 6 : 5;
278
356
  }
279
357
 
280
358
  // await tui.waitForAnyKey('Press any key to check again or ctrl+c to cancel...');
package/src/types.ts CHANGED
@@ -215,6 +215,14 @@ export interface AnalyticsConfig {
215
215
  * Agentuity project configuration (declarative)
216
216
  */
217
217
  export interface AgentuityConfig {
218
+ /**
219
+ * Rendering mode for the web frontend.
220
+ * - 'spa' (default): Single-page application with client-side routing
221
+ * - 'static': Pre-renders all routes to static HTML at build time (SSG).
222
+ * Requires src/web/entry-server.tsx exporting render(url) and getStaticPaths()
223
+ */
224
+ render?: 'spa' | 'static';
225
+
218
226
  /**
219
227
  * Workbench configuration
220
228
  */