@layr-labs/ecloud-cli 0.3.4 → 0.4.0-dev.0
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/commands/auth/whoami.js +1 -0
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/__tests__/status.test.js +552 -0
- package/dist/commands/billing/__tests__/status.test.js.map +1 -0
- package/dist/commands/billing/__tests__/subscribe.test.js +580 -0
- package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -0
- package/dist/commands/billing/__tests__/top-up.test.js +729 -0
- package/dist/commands/billing/__tests__/top-up.test.js.map +1 -0
- package/dist/commands/billing/cancel.js +8 -7
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +25 -19
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +43 -14
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/billing/top-up.js +491 -0
- package/dist/commands/billing/top-up.js.map +1 -0
- package/dist/commands/compute/app/create.js +1 -0
- package/dist/commands/compute/app/create.js.map +1 -1
- package/dist/commands/compute/app/deploy.js +62 -23
- package/dist/commands/compute/app/deploy.js.map +1 -1
- package/dist/commands/compute/app/info.js +32 -31
- package/dist/commands/compute/app/info.js.map +1 -1
- package/dist/commands/compute/app/list.js +31 -30
- package/dist/commands/compute/app/list.js.map +1 -1
- package/dist/commands/compute/app/logs.js +2 -1
- package/dist/commands/compute/app/logs.js.map +1 -1
- package/dist/commands/compute/app/profile/set.js +6 -5
- package/dist/commands/compute/app/profile/set.js.map +1 -1
- package/dist/commands/compute/app/releases.js +18 -17
- package/dist/commands/compute/app/releases.js.map +1 -1
- package/dist/commands/compute/app/start.js +6 -5
- package/dist/commands/compute/app/start.js.map +1 -1
- package/dist/commands/compute/app/stop.js +6 -5
- package/dist/commands/compute/app/stop.js.map +1 -1
- package/dist/commands/compute/app/terminate.js +6 -5
- package/dist/commands/compute/app/terminate.js.map +1 -1
- package/dist/commands/compute/app/upgrade.js +51 -22
- package/dist/commands/compute/app/upgrade.js.map +1 -1
- package/dist/commands/compute/build/info.js +16 -15
- package/dist/commands/compute/build/info.js.map +1 -1
- package/dist/commands/compute/build/list.js +13 -12
- package/dist/commands/compute/build/list.js.map +1 -1
- package/dist/commands/compute/build/logs.js +4 -3
- package/dist/commands/compute/build/logs.js.map +1 -1
- package/dist/commands/compute/build/status.js +9 -8
- package/dist/commands/compute/build/status.js.map +1 -1
- package/dist/commands/compute/build/submit.js +16 -15
- package/dist/commands/compute/build/submit.js.map +1 -1
- package/dist/commands/compute/build/verify.js +10 -9
- package/dist/commands/compute/build/verify.js.map +1 -1
- package/dist/commands/compute/environment/set.js +1 -0
- package/dist/commands/compute/environment/set.js.map +1 -1
- package/dist/commands/compute/undelegate.js +6 -5
- package/dist/commands/compute/undelegate.js.map +1 -1
- package/package.json +3 -2
- package/VERSION +0 -2
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/utils/viemClients.ts
|
|
13
|
+
import {
|
|
14
|
+
createPublicClient,
|
|
15
|
+
http
|
|
16
|
+
} from "viem";
|
|
17
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
18
|
+
import {
|
|
19
|
+
getEnvironmentConfig,
|
|
20
|
+
addHexPrefix,
|
|
21
|
+
createViemClients as sdkCreateViemClients,
|
|
22
|
+
getChainFromID
|
|
23
|
+
} from "@layr-labs/ecloud-sdk";
|
|
24
|
+
var init_viemClients = __esm({
|
|
25
|
+
"src/utils/viemClients.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/utils/globalConfig.ts
|
|
31
|
+
import * as fs from "fs";
|
|
32
|
+
import * as path from "path";
|
|
33
|
+
import * as os from "os";
|
|
34
|
+
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
|
|
35
|
+
import { getBuildType } from "@layr-labs/ecloud-sdk";
|
|
36
|
+
import * as crypto from "crypto";
|
|
37
|
+
function getGlobalConfigDir() {
|
|
38
|
+
const configHome = process.env.XDG_CONFIG_HOME;
|
|
39
|
+
let baseDir;
|
|
40
|
+
if (configHome && path.isAbsolute(configHome)) {
|
|
41
|
+
baseDir = configHome;
|
|
42
|
+
} else {
|
|
43
|
+
baseDir = path.join(os.homedir(), ".config");
|
|
44
|
+
}
|
|
45
|
+
const buildType = getBuildType();
|
|
46
|
+
const buildSuffix = buildType === "dev" ? "-dev" : "";
|
|
47
|
+
const configDirName = `ecloud${buildSuffix}`;
|
|
48
|
+
return path.join(baseDir, configDirName);
|
|
49
|
+
}
|
|
50
|
+
function getGlobalConfigPath() {
|
|
51
|
+
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
|
|
52
|
+
}
|
|
53
|
+
function loadGlobalConfig() {
|
|
54
|
+
const configPath = getGlobalConfigPath();
|
|
55
|
+
if (!fs.existsSync(configPath)) {
|
|
56
|
+
return {
|
|
57
|
+
first_run: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
62
|
+
const config = loadYaml(content);
|
|
63
|
+
return config || { first_run: true };
|
|
64
|
+
} catch {
|
|
65
|
+
return {
|
|
66
|
+
first_run: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function saveGlobalConfig(config) {
|
|
71
|
+
const configPath = getGlobalConfigPath();
|
|
72
|
+
const configDir = path.dirname(configPath);
|
|
73
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
74
|
+
const content = dumpYaml(config, { lineWidth: -1 });
|
|
75
|
+
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
76
|
+
}
|
|
77
|
+
function getDefaultEnvironment() {
|
|
78
|
+
const config = loadGlobalConfig();
|
|
79
|
+
return config.default_environment;
|
|
80
|
+
}
|
|
81
|
+
function getGlobalTelemetryPreference() {
|
|
82
|
+
const config = loadGlobalConfig();
|
|
83
|
+
return config.telemetry_enabled;
|
|
84
|
+
}
|
|
85
|
+
function getOrCreateUserUUID() {
|
|
86
|
+
const config = loadGlobalConfig();
|
|
87
|
+
if (config.user_uuid) {
|
|
88
|
+
return config.user_uuid;
|
|
89
|
+
}
|
|
90
|
+
const uuid = generateUUID();
|
|
91
|
+
config.user_uuid = uuid;
|
|
92
|
+
config.first_run = false;
|
|
93
|
+
saveGlobalConfig(config);
|
|
94
|
+
return uuid;
|
|
95
|
+
}
|
|
96
|
+
function generateUUID() {
|
|
97
|
+
const bytes = crypto.randomBytes(16);
|
|
98
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
99
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
100
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
|
101
|
+
return hex.slice(0, 4).join("") + hex.slice(4, 6).join("") + "-" + hex.slice(6, 8).join("") + "-" + hex.slice(8, 10).join("") + "-" + hex.slice(10, 12).join("") + "-" + hex.slice(12, 16).join("");
|
|
102
|
+
}
|
|
103
|
+
var GLOBAL_CONFIG_FILE, PROFILE_CACHE_TTL_MS;
|
|
104
|
+
var init_globalConfig = __esm({
|
|
105
|
+
"src/utils/globalConfig.ts"() {
|
|
106
|
+
"use strict";
|
|
107
|
+
GLOBAL_CONFIG_FILE = "config.yaml";
|
|
108
|
+
PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// src/utils/appNames.ts
|
|
113
|
+
import * as fs2 from "fs";
|
|
114
|
+
import * as path2 from "path";
|
|
115
|
+
import * as os2 from "os";
|
|
116
|
+
import { load as loadYaml2, dump as dumpYaml2 } from "js-yaml";
|
|
117
|
+
var CONFIG_DIR, APPS_DIR;
|
|
118
|
+
var init_appNames = __esm({
|
|
119
|
+
"src/utils/appNames.ts"() {
|
|
120
|
+
"use strict";
|
|
121
|
+
CONFIG_DIR = path2.join(os2.homedir(), ".eigenx");
|
|
122
|
+
APPS_DIR = path2.join(CONFIG_DIR, "apps");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// src/utils/version.ts
|
|
127
|
+
var init_version = __esm({
|
|
128
|
+
"src/utils/version.ts"() {
|
|
129
|
+
"use strict";
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// src/utils/appResolver.ts
|
|
134
|
+
import { isAddress } from "viem";
|
|
135
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
136
|
+
import {
|
|
137
|
+
UserApiClient,
|
|
138
|
+
getAllAppsByDeveloper
|
|
139
|
+
} from "@layr-labs/ecloud-sdk";
|
|
140
|
+
var init_appResolver = __esm({
|
|
141
|
+
"src/utils/appResolver.ts"() {
|
|
142
|
+
"use strict";
|
|
143
|
+
init_viemClients();
|
|
144
|
+
init_globalConfig();
|
|
145
|
+
init_appNames();
|
|
146
|
+
init_version();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// src/utils/prompts.ts
|
|
151
|
+
import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
152
|
+
import chalk from "chalk";
|
|
153
|
+
import fs3 from "fs";
|
|
154
|
+
import path3 from "path";
|
|
155
|
+
import os3 from "os";
|
|
156
|
+
import { isAddress as isAddress2 } from "viem";
|
|
157
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
158
|
+
import {
|
|
159
|
+
getEnvironmentConfig as getEnvironmentConfig2,
|
|
160
|
+
getAvailableEnvironments,
|
|
161
|
+
isEnvironmentAvailable,
|
|
162
|
+
getAllAppsByDeveloper as getAllAppsByDeveloper2,
|
|
163
|
+
getCategoryDescriptions,
|
|
164
|
+
fetchTemplateCatalog,
|
|
165
|
+
PRIMARY_LANGUAGES,
|
|
166
|
+
validateAppName,
|
|
167
|
+
validateImageReference,
|
|
168
|
+
validateFilePath,
|
|
169
|
+
validatePrivateKeyFormat,
|
|
170
|
+
extractAppNameFromImage,
|
|
171
|
+
UserApiClient as UserApiClient2
|
|
172
|
+
} from "@layr-labs/ecloud-sdk";
|
|
173
|
+
async function getPrivateKeyInteractive(privateKey) {
|
|
174
|
+
if (privateKey) {
|
|
175
|
+
if (!validatePrivateKeyFormat(privateKey)) {
|
|
176
|
+
throw new Error("Invalid private key format");
|
|
177
|
+
}
|
|
178
|
+
return privateKey;
|
|
179
|
+
}
|
|
180
|
+
const { getPrivateKeyWithSource: getPrivateKeyWithSource2 } = await import("@layr-labs/ecloud-sdk");
|
|
181
|
+
const result = await getPrivateKeyWithSource2({ privateKey: void 0 });
|
|
182
|
+
if (result) {
|
|
183
|
+
return result.key;
|
|
184
|
+
}
|
|
185
|
+
const key = await password({
|
|
186
|
+
message: "Enter private key:",
|
|
187
|
+
mask: true,
|
|
188
|
+
validate: (value) => {
|
|
189
|
+
if (!value.trim()) {
|
|
190
|
+
return "Private key is required";
|
|
191
|
+
}
|
|
192
|
+
if (!validatePrivateKeyFormat(value)) {
|
|
193
|
+
return "Invalid private key format (must be 64 hex characters, optionally prefixed with 0x)";
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return key.trim();
|
|
199
|
+
}
|
|
200
|
+
var MAX_IMAGE_SIZE;
|
|
201
|
+
var init_prompts = __esm({
|
|
202
|
+
"src/utils/prompts.ts"() {
|
|
203
|
+
"use strict";
|
|
204
|
+
init_appResolver();
|
|
205
|
+
init_viemClients();
|
|
206
|
+
init_globalConfig();
|
|
207
|
+
init_appNames();
|
|
208
|
+
init_version();
|
|
209
|
+
MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// src/flags.ts
|
|
214
|
+
import { Flags } from "@oclif/core";
|
|
215
|
+
import { getBuildType as getBuildType2 } from "@layr-labs/ecloud-sdk";
|
|
216
|
+
var commonFlags;
|
|
217
|
+
var init_flags = __esm({
|
|
218
|
+
"src/flags.ts"() {
|
|
219
|
+
"use strict";
|
|
220
|
+
init_prompts();
|
|
221
|
+
init_globalConfig();
|
|
222
|
+
commonFlags = {
|
|
223
|
+
environment: Flags.string({
|
|
224
|
+
required: false,
|
|
225
|
+
description: "Deployment environment to use",
|
|
226
|
+
env: "ECLOUD_ENV",
|
|
227
|
+
default: async () => getDefaultEnvironment() || (getBuildType2() === "dev" ? "sepolia-dev" : "sepolia")
|
|
228
|
+
}),
|
|
229
|
+
"private-key": Flags.string({
|
|
230
|
+
required: false,
|
|
231
|
+
description: "Private key for signing transactions",
|
|
232
|
+
env: "ECLOUD_PRIVATE_KEY"
|
|
233
|
+
}),
|
|
234
|
+
"rpc-url": Flags.string({
|
|
235
|
+
required: false,
|
|
236
|
+
description: "RPC URL to connect to blockchain",
|
|
237
|
+
env: "ECLOUD_RPC_URL"
|
|
238
|
+
}),
|
|
239
|
+
verbose: Flags.boolean({
|
|
240
|
+
required: false,
|
|
241
|
+
description: "Enable verbose logging (default: false)",
|
|
242
|
+
default: false
|
|
243
|
+
})
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// src/client.ts
|
|
249
|
+
import {
|
|
250
|
+
createComputeModule,
|
|
251
|
+
createBillingModule,
|
|
252
|
+
createBuildModule,
|
|
253
|
+
getEnvironmentConfig as getEnvironmentConfig3,
|
|
254
|
+
requirePrivateKey,
|
|
255
|
+
getPrivateKeyWithSource,
|
|
256
|
+
addHexPrefix as addHexPrefix2
|
|
257
|
+
} from "@layr-labs/ecloud-sdk";
|
|
258
|
+
import { createWalletClient, custom } from "viem";
|
|
259
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
260
|
+
async function createBillingClient(flags) {
|
|
261
|
+
const result = await getPrivateKeyWithSource({
|
|
262
|
+
privateKey: flags["private-key"]
|
|
263
|
+
});
|
|
264
|
+
const privateKey = await getPrivateKeyInteractive(result?.key);
|
|
265
|
+
const account = privateKeyToAccount4(addHexPrefix2(privateKey));
|
|
266
|
+
const walletClient = createWalletClient({
|
|
267
|
+
account,
|
|
268
|
+
transport: custom({
|
|
269
|
+
async request() {
|
|
270
|
+
throw new Error("RPC not available - billing uses local signing only");
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
return createBillingModule({
|
|
275
|
+
verbose: flags.verbose ?? false,
|
|
276
|
+
walletClient,
|
|
277
|
+
skipTelemetry: true
|
|
278
|
+
// CLI already has telemetry, skip SDK telemetry
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
var init_client = __esm({
|
|
282
|
+
"src/client.ts"() {
|
|
283
|
+
"use strict";
|
|
284
|
+
init_flags();
|
|
285
|
+
init_prompts();
|
|
286
|
+
init_version();
|
|
287
|
+
init_viemClients();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// src/telemetry.ts
|
|
292
|
+
import {
|
|
293
|
+
createTelemetryClient,
|
|
294
|
+
createAppEnvironment,
|
|
295
|
+
createMetricsContext,
|
|
296
|
+
addMetric,
|
|
297
|
+
addMetricWithDimensions,
|
|
298
|
+
emitMetrics,
|
|
299
|
+
getBuildType as getBuildType3
|
|
300
|
+
} from "@layr-labs/ecloud-sdk";
|
|
301
|
+
function createCLITelemetryClient() {
|
|
302
|
+
const userUUID = getOrCreateUserUUID();
|
|
303
|
+
const environment = createAppEnvironment(userUUID);
|
|
304
|
+
const telemetryEnabled = getGlobalTelemetryPreference();
|
|
305
|
+
return createTelemetryClient(environment, "ecloud-cli", {
|
|
306
|
+
telemetryEnabled: telemetryEnabled !== false
|
|
307
|
+
// Enabled by default, disabled only if explicitly set to false
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async function withTelemetry(command, action) {
|
|
311
|
+
const client = createCLITelemetryClient();
|
|
312
|
+
const metrics = createMetricsContext();
|
|
313
|
+
metrics.properties["source"] = "ecloud-cli";
|
|
314
|
+
metrics.properties["command"] = command.id || command.constructor.name;
|
|
315
|
+
const environment = getDefaultEnvironment() || "sepolia";
|
|
316
|
+
metrics.properties["environment"] = environment;
|
|
317
|
+
const buildType = getBuildType3() || "prod";
|
|
318
|
+
metrics.properties["build_type"] = buildType;
|
|
319
|
+
const cliVersion = command.config.version;
|
|
320
|
+
if (cliVersion) {
|
|
321
|
+
metrics.properties["cli_version"] = cliVersion;
|
|
322
|
+
}
|
|
323
|
+
addMetric(metrics, "Count", 1);
|
|
324
|
+
let actionError;
|
|
325
|
+
let result;
|
|
326
|
+
try {
|
|
327
|
+
result = await action();
|
|
328
|
+
return result;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
actionError = err instanceof Error ? err : new Error(String(err));
|
|
331
|
+
throw err;
|
|
332
|
+
} finally {
|
|
333
|
+
const resultValue = actionError ? "Failure" : "Success";
|
|
334
|
+
const dimensions = {};
|
|
335
|
+
if (actionError) {
|
|
336
|
+
dimensions["error"] = actionError.message;
|
|
337
|
+
}
|
|
338
|
+
addMetricWithDimensions(metrics, resultValue, 1, dimensions);
|
|
339
|
+
const duration = Date.now() - metrics.startTime.getTime();
|
|
340
|
+
addMetric(metrics, "DurationMilliseconds", duration);
|
|
341
|
+
try {
|
|
342
|
+
await emitMetrics(client, metrics);
|
|
343
|
+
await client.close();
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
var init_telemetry = __esm({
|
|
349
|
+
"src/telemetry.ts"() {
|
|
350
|
+
"use strict";
|
|
351
|
+
init_globalConfig();
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// src/commands/billing/status.ts
|
|
356
|
+
var status_exports = {};
|
|
357
|
+
__export(status_exports, {
|
|
358
|
+
default: () => BillingStatus
|
|
359
|
+
});
|
|
360
|
+
import { Command, Flags as Flags2 } from "@oclif/core";
|
|
361
|
+
import chalk2 from "chalk";
|
|
362
|
+
var BillingStatus;
|
|
363
|
+
var init_status = __esm({
|
|
364
|
+
"src/commands/billing/status.ts"() {
|
|
365
|
+
"use strict";
|
|
366
|
+
init_client();
|
|
367
|
+
init_flags();
|
|
368
|
+
init_telemetry();
|
|
369
|
+
BillingStatus = class _BillingStatus extends Command {
|
|
370
|
+
static description = "Show subscription status";
|
|
371
|
+
static flags = {
|
|
372
|
+
"private-key": commonFlags["private-key"],
|
|
373
|
+
verbose: commonFlags.verbose,
|
|
374
|
+
product: Flags2.string({
|
|
375
|
+
required: false,
|
|
376
|
+
description: "Product ID",
|
|
377
|
+
default: "compute",
|
|
378
|
+
options: ["compute"],
|
|
379
|
+
env: "ECLOUD_PRODUCT_ID"
|
|
380
|
+
})
|
|
381
|
+
};
|
|
382
|
+
async run() {
|
|
383
|
+
return withTelemetry(this, async () => {
|
|
384
|
+
const { flags } = await this.parse(_BillingStatus);
|
|
385
|
+
const billing = await createBillingClient(flags);
|
|
386
|
+
const result = await billing.getStatus({
|
|
387
|
+
productId: flags.product
|
|
388
|
+
});
|
|
389
|
+
const formatExpiry = (timestamp) => timestamp ? ` (expires ${new Date(timestamp * 1e3).toLocaleDateString()})` : "";
|
|
390
|
+
const formatStatus = (status) => {
|
|
391
|
+
switch (status) {
|
|
392
|
+
case "active":
|
|
393
|
+
return `${chalk2.green("\u2713 Active")}`;
|
|
394
|
+
case "trialing":
|
|
395
|
+
return `${chalk2.green("\u2713 Trial")}`;
|
|
396
|
+
case "past_due":
|
|
397
|
+
return `${chalk2.yellow("\u26A0 Past Due")}`;
|
|
398
|
+
case "canceled":
|
|
399
|
+
return `${chalk2.red("\u2717 Canceled")}`;
|
|
400
|
+
case "inactive":
|
|
401
|
+
return `${chalk2.gray("\u2717 Inactive")}`;
|
|
402
|
+
case "incomplete":
|
|
403
|
+
return `${chalk2.yellow("\u26A0 Incomplete")}`;
|
|
404
|
+
case "incomplete_expired":
|
|
405
|
+
return `${chalk2.red("\u2717 Expired")}`;
|
|
406
|
+
case "unpaid":
|
|
407
|
+
return `${chalk2.yellow("\u26A0 Unpaid")}`;
|
|
408
|
+
case "paused":
|
|
409
|
+
return `${chalk2.yellow("\u26A0 Paused")}`;
|
|
410
|
+
default:
|
|
411
|
+
return status;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
this.log(`
|
|
415
|
+
${chalk2.bold("Subscription Status:")}`);
|
|
416
|
+
this.log(` Wallet: ${billing.address}`);
|
|
417
|
+
this.log(` Status: ${formatStatus(result.subscriptionStatus)}`);
|
|
418
|
+
this.log(` Product: ${result.productId}`);
|
|
419
|
+
if (result.currentPeriodStart && result.currentPeriodEnd) {
|
|
420
|
+
const startDate = new Date(result.currentPeriodStart).toLocaleDateString();
|
|
421
|
+
const endDate = new Date(result.currentPeriodEnd).toLocaleDateString();
|
|
422
|
+
this.log(` Current Period: ${startDate} - ${endDate}`);
|
|
423
|
+
}
|
|
424
|
+
if (result.lineItems && result.lineItems.length > 0) {
|
|
425
|
+
this.log(`
|
|
426
|
+
${chalk2.bold(" Line Items:")}`);
|
|
427
|
+
for (const item of result.lineItems) {
|
|
428
|
+
const product = `${flags.product.charAt(0).toUpperCase()}${flags.product.slice(1)}`;
|
|
429
|
+
const chain = item.description.toLowerCase().includes("sepolia") ? "Sepolia" : "Mainnet";
|
|
430
|
+
this.log(
|
|
431
|
+
` \u2022 ${product} (${chain}): $${item.subtotal.toFixed(2)} (${item.quantity} vCPU hours \xD7 $${item.price.toFixed(3)}/vCPU hour)`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (result.creditsApplied !== void 0 && result.creditsApplied > 0) {
|
|
436
|
+
this.log(`
|
|
437
|
+
${chalk2.bold(" Invoice Summary:")}`);
|
|
438
|
+
const subtotal = result.upcomingInvoiceSubtotal ?? result.upcomingInvoiceTotal ?? 0;
|
|
439
|
+
this.log(` Subtotal: $${subtotal.toFixed(2)}`);
|
|
440
|
+
this.log(` Credits Applied: ${chalk2.green(`-$${result.creditsApplied.toFixed(2)}`)}`);
|
|
441
|
+
this.log(` ${"\u2500".repeat(21)}`);
|
|
442
|
+
this.log(` Total Due: $${(result.upcomingInvoiceTotal ?? 0).toFixed(2)}`);
|
|
443
|
+
if (result.remainingCredits !== void 0) {
|
|
444
|
+
this.log(
|
|
445
|
+
`
|
|
446
|
+
${chalk2.bold("Remaining Credits:")} ${chalk2.cyan(`$${result.remainingCredits.toFixed(2)}`)}${formatExpiry(result.nextCreditExpiry)}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
} else if (result.upcomingInvoiceTotal !== void 0) {
|
|
450
|
+
this.log(`
|
|
451
|
+
Upcoming Invoice: $${result.upcomingInvoiceTotal.toFixed(2)}`);
|
|
452
|
+
if (result.remainingCredits !== void 0 && result.remainingCredits > 0) {
|
|
453
|
+
this.log(
|
|
454
|
+
` ${chalk2.bold("Available Credits:")} ${chalk2.cyan(`$${result.remainingCredits.toFixed(2)}`)}${formatExpiry(result.nextCreditExpiry)}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (result.cancelAtPeriodEnd) {
|
|
459
|
+
this.log(`
|
|
460
|
+
${chalk2.yellow("\u26A0 Subscription will cancel at period end")}`);
|
|
461
|
+
}
|
|
462
|
+
if (result.canceledAt) {
|
|
463
|
+
const cancelDate = new Date(result.canceledAt).toLocaleDateString();
|
|
464
|
+
this.log(` Canceled On: ${cancelDate}`);
|
|
465
|
+
}
|
|
466
|
+
if (result.portalUrl) {
|
|
467
|
+
this.log(`
|
|
468
|
+
${chalk2.bold("Payment & Invoices:")}`);
|
|
469
|
+
this.log(` ${chalk2.cyan(result.portalUrl)}`);
|
|
470
|
+
}
|
|
471
|
+
if (result.subscriptionStatus === "inactive" || result.remainingCredits !== void 0 && result.remainingCredits < 10) {
|
|
472
|
+
this.log(`
|
|
473
|
+
${chalk2.bold("Need more credits?")}`);
|
|
474
|
+
this.log(` Run ${chalk2.cyan("ecloud billing top-up")} to purchase credits.`);
|
|
475
|
+
}
|
|
476
|
+
this.log();
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// src/commands/billing/__tests__/status.test.ts
|
|
484
|
+
init_client();
|
|
485
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
486
|
+
vi.mock("../../../client", () => ({
|
|
487
|
+
createBillingClient: vi.fn()
|
|
488
|
+
}));
|
|
489
|
+
vi.mock("../../../telemetry", () => ({
|
|
490
|
+
withTelemetry: vi.fn((_cmd, fn) => fn())
|
|
491
|
+
}));
|
|
492
|
+
describe("ecloud billing status \u2014 top-up hint", () => {
|
|
493
|
+
let logOutput;
|
|
494
|
+
let mockBilling;
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
logOutput = [];
|
|
497
|
+
mockBilling = {
|
|
498
|
+
address: "0xabcdef1234567890abcdef1234567890abcdef12",
|
|
499
|
+
getStatus: vi.fn()
|
|
500
|
+
};
|
|
501
|
+
createBillingClient.mockResolvedValue(mockBilling);
|
|
502
|
+
});
|
|
503
|
+
async function runStatusCommand(statusResult) {
|
|
504
|
+
const { default: BillingStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
505
|
+
mockBilling.getStatus.mockResolvedValue(statusResult);
|
|
506
|
+
const cmd = new BillingStatus2([], {});
|
|
507
|
+
cmd.parse = vi.fn().mockResolvedValue({
|
|
508
|
+
flags: { product: "compute", verbose: false }
|
|
509
|
+
});
|
|
510
|
+
cmd.log = vi.fn((...args) => logOutput.push(args.join(" ")));
|
|
511
|
+
cmd.debug = vi.fn();
|
|
512
|
+
await cmd.run();
|
|
513
|
+
return logOutput;
|
|
514
|
+
}
|
|
515
|
+
it("shows top-up hint when subscription is inactive", async () => {
|
|
516
|
+
const output = await runStatusCommand({
|
|
517
|
+
subscriptionStatus: "inactive",
|
|
518
|
+
productId: "compute"
|
|
519
|
+
});
|
|
520
|
+
const fullOutput = output.join("\n");
|
|
521
|
+
expect(fullOutput).toContain("ecloud billing top-up");
|
|
522
|
+
expect(fullOutput).toContain("Need more credits?");
|
|
523
|
+
});
|
|
524
|
+
it("shows top-up hint when credits are low (< $10)", async () => {
|
|
525
|
+
const output = await runStatusCommand({
|
|
526
|
+
subscriptionStatus: "active",
|
|
527
|
+
productId: "compute",
|
|
528
|
+
remainingCredits: 5
|
|
529
|
+
});
|
|
530
|
+
const fullOutput = output.join("\n");
|
|
531
|
+
expect(fullOutput).toContain("ecloud billing top-up");
|
|
532
|
+
});
|
|
533
|
+
it("does NOT show top-up hint when credits are healthy", async () => {
|
|
534
|
+
const output = await runStatusCommand({
|
|
535
|
+
subscriptionStatus: "active",
|
|
536
|
+
productId: "compute",
|
|
537
|
+
remainingCredits: 50,
|
|
538
|
+
upcomingInvoiceTotal: 12
|
|
539
|
+
});
|
|
540
|
+
const fullOutput = output.join("\n");
|
|
541
|
+
expect(fullOutput).not.toContain("Need more credits?");
|
|
542
|
+
});
|
|
543
|
+
it("does NOT show top-up hint when subscription is active with no credit info", async () => {
|
|
544
|
+
const output = await runStatusCommand({
|
|
545
|
+
subscriptionStatus: "active",
|
|
546
|
+
productId: "compute"
|
|
547
|
+
});
|
|
548
|
+
const fullOutput = output.join("\n");
|
|
549
|
+
expect(fullOutput).not.toContain("Need more credits?");
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
//# sourceMappingURL=status.test.js.map
|