@agentuity/cli 1.0.28 → 1.0.30
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.
- package/dist/agent-detection.d.ts.map +1 -1
- package/dist/agent-detection.js +23 -3
- package/dist/agent-detection.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/repl.js +1 -1
- package/dist/cmd/cloud/keyvalue/repl.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/search.js +2 -2
- package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
- package/dist/cmd/cloud/storage/config.d.ts +2 -0
- package/dist/cmd/cloud/storage/config.d.ts.map +1 -0
- package/dist/cmd/cloud/storage/config.js +202 -0
- package/dist/cmd/cloud/storage/config.js.map +1 -0
- package/dist/cmd/cloud/storage/index.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/index.js +2 -0
- package/dist/cmd/cloud/storage/index.js.map +1 -1
- package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/list.js +19 -0
- package/dist/cmd/cloud/storage/list.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +30 -2
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +48 -0
- package/dist/domain.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +3 -1
- package/dist/tui.js.map +1 -1
- package/package.json +6 -6
- package/src/agent-detection.ts +23 -3
- package/src/cli.ts +1 -0
- package/src/cmd/cloud/keyvalue/repl.ts +1 -1
- package/src/cmd/cloud/keyvalue/search.ts +2 -2
- package/src/cmd/cloud/storage/config.ts +238 -0
- package/src/cmd/cloud/storage/index.ts +2 -0
- package/src/cmd/cloud/storage/list.ts +18 -0
- package/src/cmd/project/template-flow.ts +31 -1
- package/src/domain.ts +52 -4
- package/src/tui.ts +2 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
getBucketConfig,
|
|
4
|
+
updateBucketConfig,
|
|
5
|
+
deleteBucketConfig,
|
|
6
|
+
BucketConfigResponseError,
|
|
7
|
+
listOrgResources,
|
|
8
|
+
type BucketConfigUpdate,
|
|
9
|
+
type BucketConfig,
|
|
10
|
+
StorageTierSchema,
|
|
11
|
+
} from '@agentuity/server';
|
|
12
|
+
import { createSubcommand } from '../../../types';
|
|
13
|
+
import * as tui from '../../../tui';
|
|
14
|
+
import { getCatalystAPIClient, getGlobalCatalystAPIClient } from '../../../config';
|
|
15
|
+
import { getCommand } from '../../../command-prefix';
|
|
16
|
+
import { getResourceInfo, setResourceInfo } from '../../../cache';
|
|
17
|
+
|
|
18
|
+
function displayConfig(config: BucketConfig) {
|
|
19
|
+
tui.newline();
|
|
20
|
+
console.log(tui.bold('Bucket: ') + config.bucket_name);
|
|
21
|
+
console.log(
|
|
22
|
+
tui.bold('Storage Tier: ') + (config.storage_tier ?? tui.muted('default'))
|
|
23
|
+
);
|
|
24
|
+
console.log(
|
|
25
|
+
tui.bold('TTL: ') +
|
|
26
|
+
(config.ttl != null ? `${config.ttl}s` : tui.muted('default'))
|
|
27
|
+
);
|
|
28
|
+
console.log(
|
|
29
|
+
tui.bold('Public: ') +
|
|
30
|
+
(config.public != null ? String(config.public) : tui.muted('default'))
|
|
31
|
+
);
|
|
32
|
+
console.log(
|
|
33
|
+
tui.bold('Cache Control: ') + (config.cache_control ?? tui.muted('default'))
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (config.cors) {
|
|
37
|
+
console.log(tui.bold('CORS:'));
|
|
38
|
+
if (config.cors.allowed_origins?.length) {
|
|
39
|
+
console.log(' Origins: ' + config.cors.allowed_origins.join(', '));
|
|
40
|
+
}
|
|
41
|
+
if (config.cors.allowed_methods?.length) {
|
|
42
|
+
console.log(' Methods: ' + config.cors.allowed_methods.join(', '));
|
|
43
|
+
}
|
|
44
|
+
if (config.cors.allowed_headers?.length) {
|
|
45
|
+
console.log(' Headers: ' + config.cors.allowed_headers.join(', '));
|
|
46
|
+
}
|
|
47
|
+
if (config.cors.expose_headers?.length) {
|
|
48
|
+
console.log(' Expose: ' + config.cors.expose_headers.join(', '));
|
|
49
|
+
}
|
|
50
|
+
if (config.cors.max_age_seconds != null) {
|
|
51
|
+
console.log(' Max Age: ' + config.cors.max_age_seconds + 's');
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
console.log(tui.bold('CORS: ') + tui.muted('default'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.additional_headers && Object.keys(config.additional_headers).length > 0) {
|
|
58
|
+
console.log(tui.bold('Headers:'));
|
|
59
|
+
for (const [key, value] of Object.entries(config.additional_headers)) {
|
|
60
|
+
console.log(` ${key}: ${value}`);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
console.log(tui.bold('Headers: ') + tui.muted('default'));
|
|
64
|
+
}
|
|
65
|
+
tui.newline();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const configSubcommand = createSubcommand({
|
|
69
|
+
name: 'config',
|
|
70
|
+
description: 'View or update bucket configuration',
|
|
71
|
+
tags: ['slow', 'requires-auth'],
|
|
72
|
+
requires: { auth: true },
|
|
73
|
+
optional: { org: true },
|
|
74
|
+
idempotent: true,
|
|
75
|
+
examples: [
|
|
76
|
+
{
|
|
77
|
+
command: `${getCommand('cloud storage config')} my-bucket`,
|
|
78
|
+
description: 'View bucket configuration',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
command: `${getCommand('cloud storage config')} my-bucket --ttl 3600 --public`,
|
|
82
|
+
description: 'Update bucket TTL and make it public',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
command: `${getCommand('cloud storage config')} my-bucket --storage-tier ARCHIVE`,
|
|
86
|
+
description: 'Change the storage tier',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
command: `${getCommand('cloud storage config')} my-bucket --reset`,
|
|
90
|
+
description: 'Reset all configuration to system defaults',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
schema: {
|
|
94
|
+
args: z.object({
|
|
95
|
+
name: z.string().describe('The name of the storage bucket'),
|
|
96
|
+
}),
|
|
97
|
+
options: z.object({
|
|
98
|
+
reset: z.boolean().optional().describe('Reset all configuration to system defaults'),
|
|
99
|
+
storageTier: StorageTierSchema.optional().describe('Storage tier'),
|
|
100
|
+
ttl: z.coerce.number().optional().describe('Object TTL in seconds (0 to clear)'),
|
|
101
|
+
public: z.boolean().optional().describe('Make bucket publicly accessible'),
|
|
102
|
+
cacheControl: z.string().optional().describe('Cache-Control header value'),
|
|
103
|
+
cors: z.string().optional().describe('CORS configuration as JSON string'),
|
|
104
|
+
additionalHeaders: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe('Additional headers as JSON key-value pairs'),
|
|
108
|
+
}),
|
|
109
|
+
response: z.object({
|
|
110
|
+
bucket_name: z.string(),
|
|
111
|
+
storage_tier: z.string().nullable().optional(),
|
|
112
|
+
ttl: z.number().nullable().optional(),
|
|
113
|
+
public: z.boolean().nullable().optional(),
|
|
114
|
+
cache_control: z.string().nullable().optional(),
|
|
115
|
+
cors: z.any().nullable().optional(),
|
|
116
|
+
additional_headers: z.record(z.string(), z.string()).nullable().optional(),
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async handler(ctx) {
|
|
121
|
+
const { logger, args, opts, options, auth, config } = ctx;
|
|
122
|
+
const { name: bucketName } = args;
|
|
123
|
+
|
|
124
|
+
const profileName = config?.name ?? 'production';
|
|
125
|
+
const catalystClient = await getGlobalCatalystAPIClient(logger, auth, profileName);
|
|
126
|
+
|
|
127
|
+
// Look up bucket to get cloud_region
|
|
128
|
+
const cachedInfo = await getResourceInfo('bucket', profileName, bucketName);
|
|
129
|
+
const orgId = ctx.orgId ?? cachedInfo?.orgId;
|
|
130
|
+
|
|
131
|
+
const resources = await tui.spinner({
|
|
132
|
+
message: 'Looking up bucket...',
|
|
133
|
+
clearOnSuccess: true,
|
|
134
|
+
callback: () => listOrgResources(catalystClient, { type: 's3', orgId }),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const bucket = resources.s3.find((s3) => s3.bucket_name === bucketName);
|
|
138
|
+
if (!bucket) {
|
|
139
|
+
throw new BucketConfigResponseError({ message: `Bucket "${bucketName}" not found` });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Cache the bucket info for future lookups
|
|
143
|
+
if (bucket.cloud_region && bucket.org_id) {
|
|
144
|
+
await setResourceInfo(
|
|
145
|
+
'bucket',
|
|
146
|
+
profileName,
|
|
147
|
+
bucket.bucket_name,
|
|
148
|
+
bucket.cloud_region,
|
|
149
|
+
bucket.org_id
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!bucket.cloud_region) {
|
|
154
|
+
throw new BucketConfigResponseError({
|
|
155
|
+
message: `Bucket "${bucketName}" is missing region information`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create regional client for bucket config operations (orgId required for CLI auth)
|
|
160
|
+
const regionalClient = getCatalystAPIClient(logger, auth, bucket.cloud_region, bucket.org_id);
|
|
161
|
+
|
|
162
|
+
// Handle --reset flag (DELETE)
|
|
163
|
+
if (opts.reset) {
|
|
164
|
+
await tui.spinner({
|
|
165
|
+
message: 'Resetting bucket configuration...',
|
|
166
|
+
clearOnSuccess: true,
|
|
167
|
+
callback: () => deleteBucketConfig(regionalClient, bucketName),
|
|
168
|
+
});
|
|
169
|
+
if (!options.json) {
|
|
170
|
+
tui.success(`Configuration reset to defaults for bucket "${bucketName}"`);
|
|
171
|
+
}
|
|
172
|
+
return { bucket_name: bucketName };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if any update flags are present
|
|
176
|
+
const hasUpdateFlags =
|
|
177
|
+
opts.storageTier !== undefined ||
|
|
178
|
+
opts.ttl !== undefined ||
|
|
179
|
+
opts.public !== undefined ||
|
|
180
|
+
opts.cacheControl !== undefined ||
|
|
181
|
+
opts.cors !== undefined ||
|
|
182
|
+
opts.additionalHeaders !== undefined;
|
|
183
|
+
|
|
184
|
+
if (hasUpdateFlags) {
|
|
185
|
+
// Build update payload
|
|
186
|
+
const update: BucketConfigUpdate = {};
|
|
187
|
+
|
|
188
|
+
if (opts.storageTier !== undefined) update.storage_tier = opts.storageTier;
|
|
189
|
+
if (opts.ttl !== undefined) update.ttl = opts.ttl === 0 ? null : opts.ttl;
|
|
190
|
+
if (opts.public !== undefined) update.public = opts.public;
|
|
191
|
+
if (opts.cacheControl !== undefined) update.cache_control = opts.cacheControl;
|
|
192
|
+
|
|
193
|
+
// Parse JSON flags
|
|
194
|
+
if (opts.cors !== undefined) {
|
|
195
|
+
try {
|
|
196
|
+
update.cors = JSON.parse(opts.cors);
|
|
197
|
+
} catch {
|
|
198
|
+
throw new BucketConfigResponseError({
|
|
199
|
+
message: 'Invalid JSON for --cors flag',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (opts.additionalHeaders !== undefined) {
|
|
204
|
+
try {
|
|
205
|
+
update.additional_headers = JSON.parse(opts.additionalHeaders);
|
|
206
|
+
} catch {
|
|
207
|
+
throw new BucketConfigResponseError({
|
|
208
|
+
message: 'Invalid JSON for --additionalHeaders flag',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = await tui.spinner({
|
|
214
|
+
message: 'Updating bucket configuration...',
|
|
215
|
+
clearOnSuccess: true,
|
|
216
|
+
callback: () => updateBucketConfig(regionalClient, bucketName, update),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (!options.json) {
|
|
220
|
+
displayConfig(result);
|
|
221
|
+
tui.success(`Configuration updated for bucket "${bucketName}"`);
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// No update flags — GET and display
|
|
227
|
+
const getResult = await tui.spinner({
|
|
228
|
+
message: 'Fetching bucket configuration...',
|
|
229
|
+
clearOnSuccess: true,
|
|
230
|
+
callback: () => getBucketConfig(regionalClient, bucketName),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (!options.json) {
|
|
234
|
+
displayConfig(getResult);
|
|
235
|
+
}
|
|
236
|
+
return getResult;
|
|
237
|
+
},
|
|
238
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCommand } from '../../../types';
|
|
2
|
+
import { configSubcommand } from './config';
|
|
2
3
|
import { createSubcommand } from './create';
|
|
3
4
|
import { listSubcommand } from './list';
|
|
4
5
|
import { deleteSubcommand } from './delete';
|
|
@@ -20,6 +21,7 @@ export const storageCommand = createCommand({
|
|
|
20
21
|
},
|
|
21
22
|
],
|
|
22
23
|
subcommands: [
|
|
24
|
+
configSubcommand,
|
|
23
25
|
createSubcommand,
|
|
24
26
|
listSubcommand,
|
|
25
27
|
getSubcommand,
|
|
@@ -23,6 +23,9 @@ const StorageListResponseSchema = z.object({
|
|
|
23
23
|
bucket_type: z.string().optional().describe('Bucket type (user or snapshots)'),
|
|
24
24
|
internal: z.boolean().optional().describe('Whether this is a system-managed bucket'),
|
|
25
25
|
description: z.string().optional().describe('Optional description of the bucket'),
|
|
26
|
+
object_count: z.number().int().optional().describe('Number of objects in this bucket'),
|
|
27
|
+
total_size: z.number().int().optional().describe('Total size of objects in bytes'),
|
|
28
|
+
last_event_at: z.string().optional().describe('Last activity timestamp'),
|
|
26
29
|
})
|
|
27
30
|
)
|
|
28
31
|
.optional()
|
|
@@ -256,6 +259,18 @@ export const listSubcommand = createSubcommand({
|
|
|
256
259
|
}
|
|
257
260
|
if (s3.region) console.log(` Region: ${tui.muted(s3.region)}`);
|
|
258
261
|
if (s3.endpoint) console.log(` Endpoint: ${tui.muted(s3.endpoint)}`);
|
|
262
|
+
if (s3.object_count != null) {
|
|
263
|
+
const sizeStr = s3.total_size != null ? tui.formatBytes(s3.total_size) : 'unknown';
|
|
264
|
+
console.log(` Objects: ${tui.muted(`${s3.object_count.toLocaleString()} (${sizeStr})`)}`);
|
|
265
|
+
}
|
|
266
|
+
if (s3.last_event_at) {
|
|
267
|
+
const date = new Date(s3.last_event_at);
|
|
268
|
+
if (Number.isNaN(date.getTime())) {
|
|
269
|
+
console.log(` Activity: ${tui.muted('unknown')}`);
|
|
270
|
+
} else {
|
|
271
|
+
console.log(` Activity: ${tui.muted(date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' }))}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
259
274
|
tui.newline();
|
|
260
275
|
}
|
|
261
276
|
}
|
|
@@ -274,6 +289,9 @@ export const listSubcommand = createSubcommand({
|
|
|
274
289
|
bucket_type: s3.bucket_type,
|
|
275
290
|
internal: s3.internal,
|
|
276
291
|
description: s3.description ?? undefined,
|
|
292
|
+
object_count: s3.object_count ?? undefined,
|
|
293
|
+
total_size: s3.total_size ?? undefined,
|
|
294
|
+
last_event_at: s3.last_event_at ?? undefined,
|
|
277
295
|
})),
|
|
278
296
|
};
|
|
279
297
|
},
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
APIClient as ServerAPIClient,
|
|
12
12
|
createResources,
|
|
13
13
|
validateDatabaseName,
|
|
14
|
+
validateBucketName,
|
|
14
15
|
} from '@agentuity/server';
|
|
15
16
|
import type { Logger } from '@agentuity/core';
|
|
16
17
|
import * as tui from '../../tui';
|
|
@@ -440,11 +441,40 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<CreateF
|
|
|
440
441
|
// Process storage action
|
|
441
442
|
switch (s3_action) {
|
|
442
443
|
case 'Create New': {
|
|
444
|
+
let bucketName: string | undefined;
|
|
445
|
+
let bucketDescription: string | undefined;
|
|
446
|
+
|
|
447
|
+
// Only prompt for name/description in interactive mode
|
|
448
|
+
if (isInteractive) {
|
|
449
|
+
const bucketNameInput = await prompt.text({
|
|
450
|
+
message: 'Bucket name',
|
|
451
|
+
hint: 'Optional - lowercase letters, digits, hyphens only',
|
|
452
|
+
validate: (value: string) => {
|
|
453
|
+
const trimmed = value.trim();
|
|
454
|
+
if (trimmed === '') return true;
|
|
455
|
+
const result = validateBucketName(trimmed);
|
|
456
|
+
return result.valid ? true : result.error!;
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
bucketName = bucketNameInput.trim() || undefined;
|
|
460
|
+
bucketDescription =
|
|
461
|
+
(await prompt.text({
|
|
462
|
+
message: 'Bucket description',
|
|
463
|
+
hint: 'Optional - press Enter to skip',
|
|
464
|
+
})) || undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
443
467
|
const created = await tui.spinner({
|
|
444
468
|
message: 'Provisioning New Bucket',
|
|
445
469
|
clearOnSuccess: true,
|
|
446
470
|
callback: async () => {
|
|
447
|
-
return createResources(catalystClient!, orgId!, region!, [
|
|
471
|
+
return createResources(catalystClient!, orgId!, region!, [
|
|
472
|
+
{
|
|
473
|
+
type: 's3',
|
|
474
|
+
name: bucketName,
|
|
475
|
+
description: bucketDescription,
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
448
478
|
},
|
|
449
479
|
});
|
|
450
480
|
// Collect env vars from newly created resource
|
package/src/domain.ts
CHANGED
|
@@ -95,6 +95,30 @@ async function fetchDNSRecord(name: string, type: string): Promise<string | null
|
|
|
95
95
|
return records[0] ?? null;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Check if a domain has a valid TLS certificate by making a HEAD request.
|
|
100
|
+
* This also triggers Let's Encrypt certificate provisioning on first access.
|
|
101
|
+
* Returns true if the TLS certificate is valid (any HTTP status code received).
|
|
102
|
+
* Returns false if the certificate is not yet provisioned (timeout or TLS error).
|
|
103
|
+
*/
|
|
104
|
+
async function checkTLSCertificate(domain: string): Promise<boolean> {
|
|
105
|
+
try {
|
|
106
|
+
await fetch(`https://${domain}`, {
|
|
107
|
+
method: 'HEAD',
|
|
108
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
109
|
+
redirect: 'manual',
|
|
110
|
+
// @ts-expect-error - cache is supported by Bun's fetch at runtime but missing from type definitions
|
|
111
|
+
cache: 'no-store',
|
|
112
|
+
});
|
|
113
|
+
// Any HTTP response means TLS handshake succeeded and certificate is valid
|
|
114
|
+
return true;
|
|
115
|
+
} catch {
|
|
116
|
+
// Timeout, TLS certificate error, connection refused, etc.
|
|
117
|
+
// All indicate the certificate is not yet provisioned
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
const LOCAL_DNS = 'agentuity.io';
|
|
99
123
|
const PRODUCTION_DNS = 'agentuity.run';
|
|
100
124
|
|
|
@@ -171,15 +195,27 @@ export async function checkCustomDomainForDNS(
|
|
|
171
195
|
if (timeoutId) clearTimeout(timeoutId);
|
|
172
196
|
});
|
|
173
197
|
|
|
174
|
-
|
|
175
|
-
|
|
198
|
+
if (result) {
|
|
199
|
+
if (result === proxy) {
|
|
200
|
+
// DNS is correct — verify TLS certificate (also triggers Let's Encrypt provisioning)
|
|
201
|
+
const tlsValid = await checkTLSCertificate(domain);
|
|
202
|
+
if (tlsValid) {
|
|
203
|
+
return {
|
|
204
|
+
domain,
|
|
205
|
+
target: proxy,
|
|
206
|
+
aRecordTarget,
|
|
207
|
+
recordType: 'CNAME',
|
|
208
|
+
success: true,
|
|
209
|
+
} as DNSSuccess;
|
|
210
|
+
}
|
|
176
211
|
return {
|
|
177
212
|
domain,
|
|
178
213
|
target: proxy,
|
|
179
214
|
aRecordTarget,
|
|
180
215
|
recordType: 'CNAME',
|
|
181
216
|
success: true,
|
|
182
|
-
|
|
217
|
+
pending: true,
|
|
218
|
+
} as DNSPending;
|
|
183
219
|
}
|
|
184
220
|
return {
|
|
185
221
|
domain,
|
|
@@ -242,13 +278,25 @@ export async function checkCustomDomainForDNS(
|
|
|
242
278
|
if (domainARecords.length > 0) {
|
|
243
279
|
const matching = domainARecords.some((a) => ionIPs.includes(a));
|
|
244
280
|
if (matching) {
|
|
281
|
+
// DNS is correct — verify TLS certificate (also triggers Let's Encrypt provisioning)
|
|
282
|
+
const tlsValid = await checkTLSCertificate(domain);
|
|
283
|
+
if (tlsValid) {
|
|
284
|
+
return {
|
|
285
|
+
domain,
|
|
286
|
+
target: proxy,
|
|
287
|
+
aRecordTarget,
|
|
288
|
+
recordType: 'A',
|
|
289
|
+
success: true,
|
|
290
|
+
} as DNSSuccess;
|
|
291
|
+
}
|
|
245
292
|
return {
|
|
246
293
|
domain,
|
|
247
294
|
target: proxy,
|
|
248
295
|
aRecordTarget,
|
|
249
296
|
recordType: 'A',
|
|
250
297
|
success: true,
|
|
251
|
-
|
|
298
|
+
pending: true,
|
|
299
|
+
} as DNSPending;
|
|
252
300
|
}
|
|
253
301
|
return {
|
|
254
302
|
domain,
|
package/src/tui.ts
CHANGED
|
@@ -2235,7 +2235,8 @@ export function formatBytes(bytes: number): string {
|
|
|
2235
2235
|
if (bytes < 1024) return `${bytes} B`;
|
|
2236
2236
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
2237
2237
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
2238
|
-
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
2238
|
+
if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
2239
|
+
return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
|
|
2239
2240
|
}
|
|
2240
2241
|
|
|
2241
2242
|
export function clearLastLines(n: number, s?: (v: string) => void) {
|