@layr-labs/ecloud-cli 0.0.1-dev → 0.0.1-rfc.1
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/README.md +2 -6
- package/dist/commands/app/create.js +29 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/deploy.js +142 -0
- package/dist/commands/app/deploy.js.map +1 -0
- package/dist/commands/app/logs.js +108 -0
- package/dist/commands/app/logs.js.map +1 -0
- package/dist/commands/app/start.js +121 -0
- package/dist/commands/app/start.js.map +1 -0
- package/dist/commands/app/stop.js +121 -0
- package/dist/commands/app/stop.js.map +1 -0
- package/dist/commands/app/terminate.js +128 -0
- package/dist/commands/app/terminate.js.map +1 -0
- package/dist/commands/app/upgrade.js +142 -0
- package/dist/commands/app/upgrade.js.map +1 -0
- package/dist/commands/auth/generate.js +10 -116
- package/dist/commands/auth/generate.js.map +1 -1
- package/dist/commands/auth/login.js +35 -37
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +8 -2
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/migrate.js +37 -32
- package/dist/commands/auth/migrate.js.map +1 -1
- package/dist/commands/auth/whoami.js +21 -53
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/cancel.js +22 -83
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +29 -92
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +31 -86
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/keys/mainnet-alpha/prod/kms-encryption-public-key.pem +14 -0
- package/dist/keys/mainnet-alpha/prod/kms-signing-public-key.pem +4 -0
- package/dist/keys/sepolia/dev/kms-encryption-public-key.pem +14 -0
- package/dist/keys/sepolia/dev/kms-signing-public-key.pem +4 -0
- package/dist/keys/sepolia/prod/kms-encryption-public-key.pem +14 -0
- package/dist/keys/sepolia/prod/kms-signing-public-key.pem +4 -0
- package/dist/templates/Dockerfile.layered.tmpl +58 -0
- package/dist/templates/compute-source-env.sh.tmpl +110 -0
- package/package.json +4 -29
- package/VERSION +0 -2
- package/dist/commands/compute/app/configure/tls.js +0 -150
- package/dist/commands/compute/app/configure/tls.js.map +0 -1
- package/dist/commands/compute/app/create.js +0 -134
- package/dist/commands/compute/app/create.js.map +0 -1
- package/dist/commands/compute/app/deploy.js +0 -1081
- package/dist/commands/compute/app/deploy.js.map +0 -1
- package/dist/commands/compute/app/info.js +0 -809
- package/dist/commands/compute/app/info.js.map +0 -1
- package/dist/commands/compute/app/list.js +0 -570
- package/dist/commands/compute/app/list.js.map +0 -1
- package/dist/commands/compute/app/logs.js +0 -629
- package/dist/commands/compute/app/logs.js.map +0 -1
- package/dist/commands/compute/app/profile/set.js +0 -1072
- package/dist/commands/compute/app/profile/set.js.map +0 -1
- package/dist/commands/compute/app/start.js +0 -665
- package/dist/commands/compute/app/start.js.map +0 -1
- package/dist/commands/compute/app/stop.js +0 -665
- package/dist/commands/compute/app/stop.js.map +0 -1
- package/dist/commands/compute/app/terminate.js +0 -671
- package/dist/commands/compute/app/terminate.js.map +0 -1
- package/dist/commands/compute/app/upgrade.js +0 -1063
- package/dist/commands/compute/app/upgrade.js.map +0 -1
- package/dist/commands/compute/environment/list.js +0 -89
- package/dist/commands/compute/environment/list.js.map +0 -1
- package/dist/commands/compute/environment/set.js +0 -215
- package/dist/commands/compute/environment/set.js.map +0 -1
- package/dist/commands/compute/environment/show.js +0 -96
- package/dist/commands/compute/environment/show.js.map +0 -1
- package/dist/commands/compute/undelegate.js +0 -250
- package/dist/commands/compute/undelegate.js.map +0 -1
- package/dist/commands/upgrade.js +0 -91
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/version.js +0 -65
- package/dist/commands/version.js.map +0 -1
|
@@ -1,1072 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/commands/compute/app/profile/set.ts
|
|
4
|
-
import { Command, Args, Flags as Flags2 } from "@oclif/core";
|
|
5
|
-
import { getEnvironmentConfig as getEnvironmentConfig2, UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
|
|
6
|
-
|
|
7
|
-
// src/flags.ts
|
|
8
|
-
import { Flags } from "@oclif/core";
|
|
9
|
-
|
|
10
|
-
// src/utils/prompts.ts
|
|
11
|
-
import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
|
|
12
|
-
import fs3 from "fs";
|
|
13
|
-
import path3 from "path";
|
|
14
|
-
import os3 from "os";
|
|
15
|
-
import { isAddress as isAddress2 } from "viem";
|
|
16
|
-
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
17
|
-
import {
|
|
18
|
-
getEnvironmentConfig,
|
|
19
|
-
getAvailableEnvironments,
|
|
20
|
-
isEnvironmentAvailable,
|
|
21
|
-
getAllAppsByDeveloper as getAllAppsByDeveloper2,
|
|
22
|
-
getCategoryDescriptions,
|
|
23
|
-
fetchTemplateCatalog,
|
|
24
|
-
PRIMARY_LANGUAGES,
|
|
25
|
-
validateAppName,
|
|
26
|
-
validateImageReference,
|
|
27
|
-
validateFilePath,
|
|
28
|
-
validatePrivateKeyFormat,
|
|
29
|
-
extractAppNameFromImage,
|
|
30
|
-
UserApiClient as UserApiClient2
|
|
31
|
-
} from "@layr-labs/ecloud-sdk";
|
|
32
|
-
|
|
33
|
-
// src/utils/appResolver.ts
|
|
34
|
-
import { isAddress } from "viem";
|
|
35
|
-
import { privateKeyToAccount } from "viem/accounts";
|
|
36
|
-
import {
|
|
37
|
-
UserApiClient,
|
|
38
|
-
getAllAppsByDeveloper
|
|
39
|
-
} from "@layr-labs/ecloud-sdk";
|
|
40
|
-
|
|
41
|
-
// src/utils/globalConfig.ts
|
|
42
|
-
import * as fs from "fs";
|
|
43
|
-
import * as path from "path";
|
|
44
|
-
import * as os from "os";
|
|
45
|
-
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
|
|
46
|
-
import { getBuildType } from "@layr-labs/ecloud-sdk";
|
|
47
|
-
var GLOBAL_CONFIG_FILE = "config.yaml";
|
|
48
|
-
var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
49
|
-
function getGlobalConfigDir() {
|
|
50
|
-
const configHome = process.env.XDG_CONFIG_HOME;
|
|
51
|
-
let baseDir;
|
|
52
|
-
if (configHome && path.isAbsolute(configHome)) {
|
|
53
|
-
baseDir = configHome;
|
|
54
|
-
} else {
|
|
55
|
-
baseDir = path.join(os.homedir(), ".config");
|
|
56
|
-
}
|
|
57
|
-
const buildType = getBuildType();
|
|
58
|
-
const buildSuffix = buildType === "dev" ? "-dev" : "";
|
|
59
|
-
const configDirName = `ecloud${buildSuffix}`;
|
|
60
|
-
return path.join(baseDir, configDirName);
|
|
61
|
-
}
|
|
62
|
-
function getGlobalConfigPath() {
|
|
63
|
-
return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
|
|
64
|
-
}
|
|
65
|
-
function loadGlobalConfig() {
|
|
66
|
-
const configPath = getGlobalConfigPath();
|
|
67
|
-
if (!fs.existsSync(configPath)) {
|
|
68
|
-
return {
|
|
69
|
-
first_run: true
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
74
|
-
const config = loadYaml(content);
|
|
75
|
-
return config || { first_run: true };
|
|
76
|
-
} catch {
|
|
77
|
-
return {
|
|
78
|
-
first_run: true
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function saveGlobalConfig(config) {
|
|
83
|
-
const configPath = getGlobalConfigPath();
|
|
84
|
-
const configDir = path.dirname(configPath);
|
|
85
|
-
fs.mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
86
|
-
const content = dumpYaml(config, { lineWidth: -1 });
|
|
87
|
-
fs.writeFileSync(configPath, content, { mode: 420 });
|
|
88
|
-
}
|
|
89
|
-
function getProfileCache(environment) {
|
|
90
|
-
const config = loadGlobalConfig();
|
|
91
|
-
const cacheEntry = config.profile_cache?.[environment];
|
|
92
|
-
if (!cacheEntry) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
const now = Date.now();
|
|
96
|
-
if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return cacheEntry.profiles;
|
|
100
|
-
}
|
|
101
|
-
function setProfileCache(environment, profiles) {
|
|
102
|
-
const config = loadGlobalConfig();
|
|
103
|
-
if (!config.profile_cache) {
|
|
104
|
-
config.profile_cache = {};
|
|
105
|
-
}
|
|
106
|
-
config.profile_cache[environment] = {
|
|
107
|
-
updated_at: Date.now(),
|
|
108
|
-
profiles
|
|
109
|
-
};
|
|
110
|
-
saveGlobalConfig(config);
|
|
111
|
-
}
|
|
112
|
-
function invalidateProfileCache(environment) {
|
|
113
|
-
const config = loadGlobalConfig();
|
|
114
|
-
if (!config.profile_cache) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (environment) {
|
|
118
|
-
delete config.profile_cache[environment];
|
|
119
|
-
} else {
|
|
120
|
-
config.profile_cache = {};
|
|
121
|
-
}
|
|
122
|
-
saveGlobalConfig(config);
|
|
123
|
-
}
|
|
124
|
-
function updateProfileCacheEntry(environment, appId, profileName) {
|
|
125
|
-
const config = loadGlobalConfig();
|
|
126
|
-
if (!config.profile_cache) {
|
|
127
|
-
config.profile_cache = {};
|
|
128
|
-
}
|
|
129
|
-
if (!config.profile_cache[environment]) {
|
|
130
|
-
config.profile_cache[environment] = {
|
|
131
|
-
updated_at: Date.now(),
|
|
132
|
-
profiles: {}
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
const normalizedAppId = appId.toLowerCase();
|
|
136
|
-
config.profile_cache[environment].profiles[normalizedAppId] = profileName;
|
|
137
|
-
config.profile_cache[environment].updated_at = Date.now();
|
|
138
|
-
saveGlobalConfig(config);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/utils/appNames.ts
|
|
142
|
-
import * as fs2 from "fs";
|
|
143
|
-
import * as path2 from "path";
|
|
144
|
-
import * as os2 from "os";
|
|
145
|
-
import { load as loadYaml2, dump as dumpYaml2 } from "js-yaml";
|
|
146
|
-
var CONFIG_DIR = path2.join(os2.homedir(), ".eigenx");
|
|
147
|
-
var APPS_DIR = path2.join(CONFIG_DIR, "apps");
|
|
148
|
-
var APP_REGISTRY_VERSION = "1.0.0";
|
|
149
|
-
function getAppRegistryPath(environment) {
|
|
150
|
-
return path2.join(APPS_DIR, `${environment}.yaml`);
|
|
151
|
-
}
|
|
152
|
-
function loadAppRegistry(environment) {
|
|
153
|
-
const filePath = getAppRegistryPath(environment);
|
|
154
|
-
if (!fs2.existsSync(filePath)) {
|
|
155
|
-
return {
|
|
156
|
-
version: APP_REGISTRY_VERSION,
|
|
157
|
-
apps: {}
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
162
|
-
const registry = loadYaml2(content);
|
|
163
|
-
if (!registry.apps) {
|
|
164
|
-
registry.apps = {};
|
|
165
|
-
}
|
|
166
|
-
return registry;
|
|
167
|
-
} catch {
|
|
168
|
-
return {
|
|
169
|
-
version: APP_REGISTRY_VERSION,
|
|
170
|
-
apps: {}
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function resolveAppIDFromRegistry(environment, appIDOrName) {
|
|
175
|
-
if (/^0x[a-fA-F0-9]{40}$/.test(appIDOrName)) {
|
|
176
|
-
return appIDOrName;
|
|
177
|
-
}
|
|
178
|
-
const registry = loadAppRegistry(environment);
|
|
179
|
-
const app = registry.apps[appIDOrName];
|
|
180
|
-
if (app) {
|
|
181
|
-
return app.app_id;
|
|
182
|
-
}
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
function getAppName(environment, appID) {
|
|
186
|
-
const registry = loadAppRegistry(environment);
|
|
187
|
-
const normalizedAppID = appID.toLowerCase();
|
|
188
|
-
for (const [name, app] of Object.entries(registry.apps)) {
|
|
189
|
-
if (app?.app_id && String(app.app_id).toLowerCase() === normalizedAppID) {
|
|
190
|
-
return name;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return "";
|
|
194
|
-
}
|
|
195
|
-
function listApps(environment) {
|
|
196
|
-
const registry = loadAppRegistry(environment);
|
|
197
|
-
const result = {};
|
|
198
|
-
for (const [name, app] of Object.entries(registry.apps)) {
|
|
199
|
-
if (app?.app_id) {
|
|
200
|
-
result[name] = String(app.app_id);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return result;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// src/utils/appResolver.ts
|
|
207
|
-
var CHUNK_SIZE = 10;
|
|
208
|
-
async function getAppInfosChunked(userApiClient, appIds, addressCount) {
|
|
209
|
-
if (appIds.length === 0) {
|
|
210
|
-
return [];
|
|
211
|
-
}
|
|
212
|
-
const chunks = [];
|
|
213
|
-
for (let i = 0; i < appIds.length; i += CHUNK_SIZE) {
|
|
214
|
-
chunks.push(appIds.slice(i, i + CHUNK_SIZE));
|
|
215
|
-
}
|
|
216
|
-
const chunkResults = await Promise.all(
|
|
217
|
-
chunks.map((chunk) => userApiClient.getInfos(chunk, addressCount))
|
|
218
|
-
);
|
|
219
|
-
return chunkResults.flat();
|
|
220
|
-
}
|
|
221
|
-
var AppResolver = class {
|
|
222
|
-
constructor(environment, environmentConfig, privateKey, rpcUrl) {
|
|
223
|
-
this.environment = environment;
|
|
224
|
-
this.environmentConfig = environmentConfig;
|
|
225
|
-
this.privateKey = privateKey;
|
|
226
|
-
this.rpcUrl = rpcUrl;
|
|
227
|
-
}
|
|
228
|
-
profileNames = {};
|
|
229
|
-
// appId (lowercase) -> name
|
|
230
|
-
cacheInitialized = false;
|
|
231
|
-
/**
|
|
232
|
-
* Resolve app name or ID to a valid Address
|
|
233
|
-
* @param appIDOrName - App ID (hex address) or app name
|
|
234
|
-
* @returns Resolved app address
|
|
235
|
-
* @throws Error if app cannot be resolved
|
|
236
|
-
*/
|
|
237
|
-
async resolveAppID(appIDOrName) {
|
|
238
|
-
if (!appIDOrName) {
|
|
239
|
-
throw new Error("App ID or name is required");
|
|
240
|
-
}
|
|
241
|
-
const normalized = appIDOrName.startsWith("0x") ? appIDOrName : `0x${appIDOrName}`;
|
|
242
|
-
if (isAddress(normalized)) {
|
|
243
|
-
return normalized;
|
|
244
|
-
}
|
|
245
|
-
await this.ensureCacheInitialized();
|
|
246
|
-
const searchName = appIDOrName.toLowerCase();
|
|
247
|
-
for (const [appId, name] of Object.entries(this.profileNames)) {
|
|
248
|
-
if (name.toLowerCase() === searchName) {
|
|
249
|
-
return appId;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const localAppId = resolveAppIDFromRegistry(this.environment, appIDOrName);
|
|
253
|
-
if (localAppId) {
|
|
254
|
-
return localAppId;
|
|
255
|
-
}
|
|
256
|
-
throw new Error(`App '${appIDOrName}' not found in environment '${this.environment}'`);
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Get app name from app ID
|
|
260
|
-
* @param appID - App address
|
|
261
|
-
* @returns Profile name if found, empty string otherwise
|
|
262
|
-
*/
|
|
263
|
-
async getAppName(appID) {
|
|
264
|
-
const normalizedId = String(appID).toLowerCase();
|
|
265
|
-
await this.ensureCacheInitialized();
|
|
266
|
-
const profileName = this.profileNames[normalizedId];
|
|
267
|
-
if (profileName) {
|
|
268
|
-
return profileName;
|
|
269
|
-
}
|
|
270
|
-
return getAppName(this.environment, appID);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Check if an app name is available (not used by any existing app)
|
|
274
|
-
* @param name - Name to check
|
|
275
|
-
* @returns true if available, false if taken
|
|
276
|
-
*/
|
|
277
|
-
async isAppNameAvailable(name) {
|
|
278
|
-
await this.ensureCacheInitialized();
|
|
279
|
-
const searchName = name.toLowerCase();
|
|
280
|
-
for (const profileName of Object.values(this.profileNames)) {
|
|
281
|
-
if (profileName.toLowerCase() === searchName) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const localApps = listApps(this.environment);
|
|
286
|
-
return !localApps[name];
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Find an available app name by appending numbers if needed
|
|
290
|
-
* @param baseName - Base name to start with
|
|
291
|
-
* @returns Available name (may have number suffix)
|
|
292
|
-
*/
|
|
293
|
-
async findAvailableName(baseName) {
|
|
294
|
-
if (await this.isAppNameAvailable(baseName)) {
|
|
295
|
-
return baseName;
|
|
296
|
-
}
|
|
297
|
-
for (let i = 2; i <= 100; i++) {
|
|
298
|
-
const candidate = `${baseName}-${i}`;
|
|
299
|
-
if (await this.isAppNameAvailable(candidate)) {
|
|
300
|
-
return candidate;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return `${baseName}-${Date.now()}`;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Get all profile names (for display/listing purposes)
|
|
307
|
-
* @returns Map of appId -> name
|
|
308
|
-
*/
|
|
309
|
-
async getAllProfileNames() {
|
|
310
|
-
await this.ensureCacheInitialized();
|
|
311
|
-
return { ...this.profileNames };
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Update cache with a new profile name (call after deploy or profile set)
|
|
315
|
-
*/
|
|
316
|
-
updateCacheEntry(appId, profileName) {
|
|
317
|
-
const normalizedId = appId.toLowerCase();
|
|
318
|
-
this.profileNames[normalizedId] = profileName;
|
|
319
|
-
updateProfileCacheEntry(this.environment, appId, profileName);
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Ensure the profile cache is initialized
|
|
323
|
-
* Loads from disk cache if valid, otherwise fetches from API
|
|
324
|
-
*/
|
|
325
|
-
async ensureCacheInitialized() {
|
|
326
|
-
if (this.cacheInitialized) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const cachedProfiles = getProfileCache(this.environment);
|
|
330
|
-
if (cachedProfiles) {
|
|
331
|
-
this.profileNames = cachedProfiles;
|
|
332
|
-
this.cacheInitialized = true;
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
await this.fetchProfilesFromAPI();
|
|
336
|
-
this.cacheInitialized = true;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Fetch profile names from the remote API and update cache
|
|
340
|
-
*/
|
|
341
|
-
async fetchProfilesFromAPI() {
|
|
342
|
-
if (!this.privateKey || !this.rpcUrl) {
|
|
343
|
-
this.profileNames = {};
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
try {
|
|
347
|
-
const account = privateKeyToAccount(this.privateKey);
|
|
348
|
-
const { apps } = await getAllAppsByDeveloper(
|
|
349
|
-
this.rpcUrl,
|
|
350
|
-
this.environmentConfig,
|
|
351
|
-
account.address
|
|
352
|
-
);
|
|
353
|
-
if (apps.length === 0) {
|
|
354
|
-
this.profileNames = {};
|
|
355
|
-
setProfileCache(this.environment, {});
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
const userApiClient = new UserApiClient(this.environmentConfig, this.privateKey, this.rpcUrl);
|
|
359
|
-
const appInfos = await getAppInfosChunked(userApiClient, apps);
|
|
360
|
-
const profiles = {};
|
|
361
|
-
for (const info of appInfos) {
|
|
362
|
-
if (info.profile?.name) {
|
|
363
|
-
const normalizedId = String(info.address).toLowerCase();
|
|
364
|
-
profiles[normalizedId] = info.profile.name;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
this.profileNames = profiles;
|
|
368
|
-
setProfileCache(this.environment, profiles);
|
|
369
|
-
} catch (error) {
|
|
370
|
-
console.debug?.("Failed to fetch profiles from API:", error);
|
|
371
|
-
this.profileNames = {};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
function createAppResolver(environment, environmentConfig, privateKey, rpcUrl) {
|
|
376
|
-
return new AppResolver(environment, environmentConfig, privateKey, rpcUrl);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// src/utils/prompts.ts
|
|
380
|
-
function addHexPrefix(value) {
|
|
381
|
-
if (value.startsWith("0x")) {
|
|
382
|
-
return value;
|
|
383
|
-
}
|
|
384
|
-
return `0x${value}`;
|
|
385
|
-
}
|
|
386
|
-
var ContractAppStatusStarted = 1;
|
|
387
|
-
var ContractAppStatusStopped = 2;
|
|
388
|
-
var ContractAppStatusTerminated = 3;
|
|
389
|
-
var ContractAppStatusSuspended = 4;
|
|
390
|
-
function getContractStatusString(status) {
|
|
391
|
-
switch (status) {
|
|
392
|
-
case ContractAppStatusStarted:
|
|
393
|
-
return "Started";
|
|
394
|
-
case ContractAppStatusStopped:
|
|
395
|
-
return "Stopped";
|
|
396
|
-
case ContractAppStatusTerminated:
|
|
397
|
-
return "Terminated";
|
|
398
|
-
case ContractAppStatusSuspended:
|
|
399
|
-
return "Suspended";
|
|
400
|
-
default:
|
|
401
|
-
return "Unknown";
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
function getStatusPriority(status, isExited) {
|
|
405
|
-
if (isExited) {
|
|
406
|
-
return 1;
|
|
407
|
-
}
|
|
408
|
-
switch (status) {
|
|
409
|
-
case ContractAppStatusStarted:
|
|
410
|
-
return 0;
|
|
411
|
-
case ContractAppStatusStopped:
|
|
412
|
-
return 2;
|
|
413
|
-
case ContractAppStatusTerminated:
|
|
414
|
-
return 3;
|
|
415
|
-
default:
|
|
416
|
-
return 4;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function formatAppDisplay(environmentName, appID, profileName) {
|
|
420
|
-
if (profileName) {
|
|
421
|
-
return `${profileName} (${environmentName}:${appID})`;
|
|
422
|
-
}
|
|
423
|
-
return `${environmentName}:${appID}`;
|
|
424
|
-
}
|
|
425
|
-
async function getOrPromptAppID(appIDOrOptions, environment) {
|
|
426
|
-
let options;
|
|
427
|
-
if (environment !== void 0) {
|
|
428
|
-
options = {
|
|
429
|
-
appID: appIDOrOptions,
|
|
430
|
-
environment
|
|
431
|
-
};
|
|
432
|
-
} else if (appIDOrOptions && typeof appIDOrOptions === "object" && "environment" in appIDOrOptions) {
|
|
433
|
-
options = appIDOrOptions;
|
|
434
|
-
} else {
|
|
435
|
-
options = {
|
|
436
|
-
appID: appIDOrOptions,
|
|
437
|
-
environment: "sepolia"
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
if (options.appID) {
|
|
441
|
-
const normalized = typeof options.appID === "string" ? addHexPrefix(options.appID) : options.appID;
|
|
442
|
-
if (isAddress2(normalized)) {
|
|
443
|
-
return normalized;
|
|
444
|
-
}
|
|
445
|
-
const profileCache = getProfileCache(options.environment);
|
|
446
|
-
if (profileCache) {
|
|
447
|
-
const searchName = options.appID.toLowerCase();
|
|
448
|
-
for (const [appId, name] of Object.entries(profileCache)) {
|
|
449
|
-
if (name.toLowerCase() === searchName) {
|
|
450
|
-
return appId;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
const apps = listApps(options.environment);
|
|
455
|
-
const foundAppID = apps[options.appID];
|
|
456
|
-
if (foundAppID) {
|
|
457
|
-
return addHexPrefix(foundAppID);
|
|
458
|
-
}
|
|
459
|
-
throw new Error(
|
|
460
|
-
`App name '${options.appID}' not found in environment '${options.environment}'`
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
return getAppIDInteractive(options);
|
|
464
|
-
}
|
|
465
|
-
async function getAppIDInteractive(options) {
|
|
466
|
-
const action = options.action || "view";
|
|
467
|
-
const environment = options.environment || "sepolia";
|
|
468
|
-
const environmentConfig = getEnvironmentConfig(environment);
|
|
469
|
-
if (!options.privateKey || !options.rpcUrl) {
|
|
470
|
-
return getAppIDInteractiveFromRegistry(environment, action);
|
|
471
|
-
}
|
|
472
|
-
console.log(`
|
|
473
|
-
Select an app to ${action}:
|
|
474
|
-
`);
|
|
475
|
-
const privateKeyHex = addHexPrefix(options.privateKey);
|
|
476
|
-
const account = privateKeyToAccount2(privateKeyHex);
|
|
477
|
-
const developerAddr = account.address;
|
|
478
|
-
const { apps, appConfigs } = await getAllAppsByDeveloper2(
|
|
479
|
-
options.rpcUrl,
|
|
480
|
-
environmentConfig,
|
|
481
|
-
developerAddr
|
|
482
|
-
);
|
|
483
|
-
if (apps.length === 0) {
|
|
484
|
-
throw new Error("no apps found for your address");
|
|
485
|
-
}
|
|
486
|
-
const profileNames = {};
|
|
487
|
-
let cachedProfiles = getProfileCache(environment);
|
|
488
|
-
if (!cachedProfiles) {
|
|
489
|
-
try {
|
|
490
|
-
const userApiClient = new UserApiClient2(
|
|
491
|
-
environmentConfig,
|
|
492
|
-
options.privateKey,
|
|
493
|
-
options.rpcUrl
|
|
494
|
-
);
|
|
495
|
-
const appInfos = await getAppInfosChunked(userApiClient, apps);
|
|
496
|
-
const freshProfiles = {};
|
|
497
|
-
for (const info of appInfos) {
|
|
498
|
-
if (info.profile?.name) {
|
|
499
|
-
const normalizedId = String(info.address).toLowerCase();
|
|
500
|
-
freshProfiles[normalizedId] = info.profile.name;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
setProfileCache(environment, freshProfiles);
|
|
504
|
-
cachedProfiles = freshProfiles;
|
|
505
|
-
} catch {
|
|
506
|
-
cachedProfiles = {};
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
for (const [appId, name] of Object.entries(cachedProfiles)) {
|
|
510
|
-
profileNames[appId.toLowerCase()] = name;
|
|
511
|
-
}
|
|
512
|
-
const localApps = listApps(environment);
|
|
513
|
-
for (const [name, appID] of Object.entries(localApps)) {
|
|
514
|
-
const normalizedID = String(appID).toLowerCase();
|
|
515
|
-
if (!profileNames[normalizedID]) {
|
|
516
|
-
profileNames[normalizedID] = name;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const isEligible = (status) => {
|
|
520
|
-
switch (action) {
|
|
521
|
-
case "view":
|
|
522
|
-
case "view info for":
|
|
523
|
-
case "set profile for":
|
|
524
|
-
return true;
|
|
525
|
-
case "start":
|
|
526
|
-
return status === ContractAppStatusStopped || status === ContractAppStatusSuspended;
|
|
527
|
-
case "stop":
|
|
528
|
-
return status === ContractAppStatusStarted;
|
|
529
|
-
default:
|
|
530
|
-
return status !== ContractAppStatusTerminated && status !== ContractAppStatusSuspended;
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
const appItems = [];
|
|
534
|
-
for (let i = 0; i < apps.length; i++) {
|
|
535
|
-
const appAddr = apps[i];
|
|
536
|
-
const config = appConfigs[i];
|
|
537
|
-
const status = config.status;
|
|
538
|
-
if (!isEligible(status)) {
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
const statusStr = getContractStatusString(status);
|
|
542
|
-
const profileName = profileNames[String(appAddr).toLowerCase()] || "";
|
|
543
|
-
const displayName = formatAppDisplay(environmentConfig.name, appAddr, profileName);
|
|
544
|
-
appItems.push({
|
|
545
|
-
addr: appAddr,
|
|
546
|
-
display: `${displayName} - ${statusStr}`,
|
|
547
|
-
status,
|
|
548
|
-
index: i
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
appItems.sort((a, b) => {
|
|
552
|
-
const aPriority = getStatusPriority(a.status, false);
|
|
553
|
-
const bPriority = getStatusPriority(b.status, false);
|
|
554
|
-
if (aPriority !== bPriority) {
|
|
555
|
-
return aPriority - bPriority;
|
|
556
|
-
}
|
|
557
|
-
return b.index - a.index;
|
|
558
|
-
});
|
|
559
|
-
if (appItems.length === 0) {
|
|
560
|
-
switch (action) {
|
|
561
|
-
case "start":
|
|
562
|
-
throw new Error("no startable apps found - only Stopped apps can be started");
|
|
563
|
-
case "stop":
|
|
564
|
-
throw new Error("no running apps found - only Running apps can be stopped");
|
|
565
|
-
default:
|
|
566
|
-
throw new Error("no active apps found");
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
const choices = appItems.map((item) => ({
|
|
570
|
-
name: item.display,
|
|
571
|
-
value: item.addr
|
|
572
|
-
}));
|
|
573
|
-
const selected = await select({
|
|
574
|
-
message: "Select app:",
|
|
575
|
-
choices
|
|
576
|
-
});
|
|
577
|
-
return selected;
|
|
578
|
-
}
|
|
579
|
-
async function getAppIDInteractiveFromRegistry(environment, action) {
|
|
580
|
-
const allApps = {};
|
|
581
|
-
const cachedProfiles = getProfileCache(environment);
|
|
582
|
-
if (cachedProfiles) {
|
|
583
|
-
for (const [appId, name] of Object.entries(cachedProfiles)) {
|
|
584
|
-
allApps[name] = appId;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const localApps = listApps(environment);
|
|
588
|
-
for (const [name, appId] of Object.entries(localApps)) {
|
|
589
|
-
if (!allApps[name]) {
|
|
590
|
-
allApps[name] = appId;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (Object.keys(allApps).length === 0) {
|
|
594
|
-
console.log("\nNo apps found in registry.");
|
|
595
|
-
console.log("You can enter an app ID (address) or app name.");
|
|
596
|
-
console.log();
|
|
597
|
-
const appIDInput = await input({
|
|
598
|
-
message: "Enter app ID or name:",
|
|
599
|
-
default: "",
|
|
600
|
-
validate: (value) => {
|
|
601
|
-
if (!value) {
|
|
602
|
-
return "App ID or name cannot be empty";
|
|
603
|
-
}
|
|
604
|
-
const normalized2 = addHexPrefix(value);
|
|
605
|
-
if (isAddress2(normalized2)) {
|
|
606
|
-
return true;
|
|
607
|
-
}
|
|
608
|
-
return "Invalid app ID address";
|
|
609
|
-
}
|
|
610
|
-
});
|
|
611
|
-
const normalized = addHexPrefix(appIDInput);
|
|
612
|
-
if (isAddress2(normalized)) {
|
|
613
|
-
return normalized;
|
|
614
|
-
}
|
|
615
|
-
throw new Error(`Invalid app ID address: ${appIDInput}`);
|
|
616
|
-
}
|
|
617
|
-
const choices = Object.entries(allApps).map(([name, appID]) => {
|
|
618
|
-
const displayName = `${name} (${appID})`;
|
|
619
|
-
return { name: displayName, value: appID };
|
|
620
|
-
});
|
|
621
|
-
choices.push({ name: "Enter custom app ID or name", value: "custom" });
|
|
622
|
-
console.log(`
|
|
623
|
-
Select an app to ${action}:`);
|
|
624
|
-
const selected = await select({
|
|
625
|
-
message: "Choose app:",
|
|
626
|
-
choices
|
|
627
|
-
});
|
|
628
|
-
if (selected === "custom") {
|
|
629
|
-
const appIDInput = await input({
|
|
630
|
-
message: "Enter app ID or name:",
|
|
631
|
-
default: "",
|
|
632
|
-
validate: (value) => {
|
|
633
|
-
if (!value) {
|
|
634
|
-
return "App ID or name cannot be empty";
|
|
635
|
-
}
|
|
636
|
-
const normalized2 = addHexPrefix(value);
|
|
637
|
-
if (isAddress2(normalized2)) {
|
|
638
|
-
return true;
|
|
639
|
-
}
|
|
640
|
-
if (allApps[value]) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
return "Invalid app ID or name not found";
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
const normalized = addHexPrefix(appIDInput);
|
|
647
|
-
if (isAddress2(normalized)) {
|
|
648
|
-
return normalized;
|
|
649
|
-
}
|
|
650
|
-
const foundAppID = allApps[appIDInput];
|
|
651
|
-
if (foundAppID) {
|
|
652
|
-
return addHexPrefix(foundAppID);
|
|
653
|
-
}
|
|
654
|
-
throw new Error(`Failed to resolve app ID from input: ${appIDInput}`);
|
|
655
|
-
}
|
|
656
|
-
return addHexPrefix(selected);
|
|
657
|
-
}
|
|
658
|
-
async function getPrivateKeyInteractive(privateKey) {
|
|
659
|
-
if (privateKey) {
|
|
660
|
-
if (!validatePrivateKeyFormat(privateKey)) {
|
|
661
|
-
throw new Error("Invalid private key format");
|
|
662
|
-
}
|
|
663
|
-
return privateKey;
|
|
664
|
-
}
|
|
665
|
-
const { getPrivateKeyWithSource } = await import("@layr-labs/ecloud-sdk");
|
|
666
|
-
const result = await getPrivateKeyWithSource({ privateKey: void 0 });
|
|
667
|
-
if (result) {
|
|
668
|
-
return result.key;
|
|
669
|
-
}
|
|
670
|
-
const key = await password({
|
|
671
|
-
message: "Enter private key:",
|
|
672
|
-
mask: true,
|
|
673
|
-
validate: (value) => {
|
|
674
|
-
if (!value.trim()) {
|
|
675
|
-
return "Private key is required";
|
|
676
|
-
}
|
|
677
|
-
if (!validatePrivateKeyFormat(value)) {
|
|
678
|
-
return "Invalid private key format (must be 64 hex characters, optionally prefixed with 0x)";
|
|
679
|
-
}
|
|
680
|
-
return true;
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
return key.trim();
|
|
684
|
-
}
|
|
685
|
-
var MAX_DESCRIPTION_LENGTH = 1e3;
|
|
686
|
-
var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
|
|
687
|
-
var VALID_IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png"];
|
|
688
|
-
var VALID_X_HOSTS = ["twitter.com", "www.twitter.com", "x.com", "www.x.com"];
|
|
689
|
-
function validateURL(rawURL) {
|
|
690
|
-
if (!rawURL.trim()) {
|
|
691
|
-
return "URL cannot be empty";
|
|
692
|
-
}
|
|
693
|
-
try {
|
|
694
|
-
const url = new URL(rawURL);
|
|
695
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
696
|
-
return "URL scheme must be http or https";
|
|
697
|
-
}
|
|
698
|
-
} catch {
|
|
699
|
-
return "Invalid URL format";
|
|
700
|
-
}
|
|
701
|
-
return void 0;
|
|
702
|
-
}
|
|
703
|
-
function validateXURL(rawURL) {
|
|
704
|
-
const urlErr = validateURL(rawURL);
|
|
705
|
-
if (urlErr) {
|
|
706
|
-
return urlErr;
|
|
707
|
-
}
|
|
708
|
-
try {
|
|
709
|
-
const url = new URL(rawURL);
|
|
710
|
-
const host = url.hostname.toLowerCase();
|
|
711
|
-
if (!VALID_X_HOSTS.includes(host)) {
|
|
712
|
-
return "URL must be a valid X/Twitter URL (x.com or twitter.com)";
|
|
713
|
-
}
|
|
714
|
-
if (!url.pathname || url.pathname === "/") {
|
|
715
|
-
return "X URL must include a username or profile path";
|
|
716
|
-
}
|
|
717
|
-
} catch {
|
|
718
|
-
return "Invalid X URL format";
|
|
719
|
-
}
|
|
720
|
-
return void 0;
|
|
721
|
-
}
|
|
722
|
-
function validateDescription(description) {
|
|
723
|
-
if (!description.trim()) {
|
|
724
|
-
return "Description cannot be empty";
|
|
725
|
-
}
|
|
726
|
-
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
727
|
-
return `Description cannot exceed ${MAX_DESCRIPTION_LENGTH} characters`;
|
|
728
|
-
}
|
|
729
|
-
return void 0;
|
|
730
|
-
}
|
|
731
|
-
function validateImagePath(filePath) {
|
|
732
|
-
const cleanedPath = filePath.trim().replace(/^["']|["']$/g, "");
|
|
733
|
-
if (!cleanedPath) {
|
|
734
|
-
return "Image path cannot be empty";
|
|
735
|
-
}
|
|
736
|
-
if (!fs3.existsSync(cleanedPath)) {
|
|
737
|
-
return `Image file not found: ${cleanedPath}`;
|
|
738
|
-
}
|
|
739
|
-
const stats = fs3.statSync(cleanedPath);
|
|
740
|
-
if (stats.isDirectory()) {
|
|
741
|
-
return "Path is a directory, not a file";
|
|
742
|
-
}
|
|
743
|
-
if (stats.size > MAX_IMAGE_SIZE) {
|
|
744
|
-
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
745
|
-
return `Image file size (${sizeMB} MB) exceeds maximum allowed size of 4 MB`;
|
|
746
|
-
}
|
|
747
|
-
const ext = path3.extname(cleanedPath).toLowerCase();
|
|
748
|
-
if (!VALID_IMAGE_EXTENSIONS.includes(ext)) {
|
|
749
|
-
return "Image must be JPG or PNG format";
|
|
750
|
-
}
|
|
751
|
-
return void 0;
|
|
752
|
-
}
|
|
753
|
-
function validateAppProfile(profile) {
|
|
754
|
-
if (!profile.name || !profile.name.trim()) {
|
|
755
|
-
return "Profile name is required";
|
|
756
|
-
}
|
|
757
|
-
try {
|
|
758
|
-
validateAppName(profile.name);
|
|
759
|
-
} catch (err) {
|
|
760
|
-
return `Invalid profile name: ${err.message}`;
|
|
761
|
-
}
|
|
762
|
-
if (profile.website) {
|
|
763
|
-
const websiteErr = validateURL(profile.website);
|
|
764
|
-
if (websiteErr) {
|
|
765
|
-
return `Invalid website: ${websiteErr}`;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
if (profile.description) {
|
|
769
|
-
const descErr = validateDescription(profile.description);
|
|
770
|
-
if (descErr) {
|
|
771
|
-
return `Invalid description: ${descErr}`;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (profile.xURL) {
|
|
775
|
-
const xURLErr = validateXURL(profile.xURL);
|
|
776
|
-
if (xURLErr) {
|
|
777
|
-
return `Invalid X URL: ${xURLErr}`;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (profile.imagePath) {
|
|
781
|
-
const imageErr = validateImagePath(profile.imagePath);
|
|
782
|
-
if (imageErr) {
|
|
783
|
-
return `Invalid image: ${imageErr}`;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
return void 0;
|
|
787
|
-
}
|
|
788
|
-
async function getAppProfileInteractive(defaultName = "", allowRetry = true) {
|
|
789
|
-
while (true) {
|
|
790
|
-
const name = await getAppNameForProfile(defaultName);
|
|
791
|
-
const website = await getAppWebsiteInteractive();
|
|
792
|
-
const description = await getAppDescriptionInteractive();
|
|
793
|
-
const xURL = await getAppXURLInteractive();
|
|
794
|
-
const imagePath = await getAppImageInteractive();
|
|
795
|
-
const profile = {
|
|
796
|
-
name,
|
|
797
|
-
website,
|
|
798
|
-
description,
|
|
799
|
-
xURL,
|
|
800
|
-
imagePath
|
|
801
|
-
};
|
|
802
|
-
console.log("\n" + formatProfileForDisplay(profile));
|
|
803
|
-
const confirmed = await inquirerConfirm({
|
|
804
|
-
message: "Continue with this profile?",
|
|
805
|
-
default: true
|
|
806
|
-
});
|
|
807
|
-
if (confirmed) {
|
|
808
|
-
return profile;
|
|
809
|
-
}
|
|
810
|
-
if (!allowRetry) {
|
|
811
|
-
throw new Error("Profile confirmation cancelled");
|
|
812
|
-
}
|
|
813
|
-
const retry = await inquirerConfirm({
|
|
814
|
-
message: "Would you like to re-enter the information?",
|
|
815
|
-
default: true
|
|
816
|
-
});
|
|
817
|
-
if (!retry) {
|
|
818
|
-
return void 0;
|
|
819
|
-
}
|
|
820
|
-
defaultName = name;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
async function getAppNameForProfile(defaultName) {
|
|
824
|
-
if (defaultName) {
|
|
825
|
-
validateAppName(defaultName);
|
|
826
|
-
return defaultName;
|
|
827
|
-
}
|
|
828
|
-
return await input({
|
|
829
|
-
message: "App name:",
|
|
830
|
-
default: "",
|
|
831
|
-
validate: (value) => {
|
|
832
|
-
if (!value.trim()) {
|
|
833
|
-
return "Name is required";
|
|
834
|
-
}
|
|
835
|
-
try {
|
|
836
|
-
validateAppName(value);
|
|
837
|
-
return true;
|
|
838
|
-
} catch (err) {
|
|
839
|
-
return err.message;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
async function getAppWebsiteInteractive() {
|
|
845
|
-
const website = await input({
|
|
846
|
-
message: "Website URL (optional):",
|
|
847
|
-
default: "",
|
|
848
|
-
validate: (value) => {
|
|
849
|
-
if (!value.trim()) {
|
|
850
|
-
return true;
|
|
851
|
-
}
|
|
852
|
-
const err = validateURL(value);
|
|
853
|
-
return err ? err : true;
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
|
-
if (!website.trim()) {
|
|
857
|
-
return void 0;
|
|
858
|
-
}
|
|
859
|
-
return website;
|
|
860
|
-
}
|
|
861
|
-
async function getAppDescriptionInteractive() {
|
|
862
|
-
const description = await input({
|
|
863
|
-
message: "Description (optional):",
|
|
864
|
-
default: "",
|
|
865
|
-
validate: (value) => {
|
|
866
|
-
if (!value.trim()) {
|
|
867
|
-
return true;
|
|
868
|
-
}
|
|
869
|
-
const err = validateDescription(value);
|
|
870
|
-
return err ? err : true;
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
if (!description.trim()) {
|
|
874
|
-
return void 0;
|
|
875
|
-
}
|
|
876
|
-
return description;
|
|
877
|
-
}
|
|
878
|
-
async function getAppXURLInteractive() {
|
|
879
|
-
const xURL = await input({
|
|
880
|
-
message: "X (Twitter) URL (optional):",
|
|
881
|
-
default: "",
|
|
882
|
-
validate: (value) => {
|
|
883
|
-
if (!value.trim()) {
|
|
884
|
-
return true;
|
|
885
|
-
}
|
|
886
|
-
const err = validateXURL(value);
|
|
887
|
-
return err ? err : true;
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
if (!xURL.trim()) {
|
|
891
|
-
return void 0;
|
|
892
|
-
}
|
|
893
|
-
return xURL;
|
|
894
|
-
}
|
|
895
|
-
async function getAppImageInteractive() {
|
|
896
|
-
const wantsImage = await inquirerConfirm({
|
|
897
|
-
message: "Would you like to upload an app icon/logo?",
|
|
898
|
-
default: false
|
|
899
|
-
});
|
|
900
|
-
if (!wantsImage) {
|
|
901
|
-
return void 0;
|
|
902
|
-
}
|
|
903
|
-
const imagePath = await input({
|
|
904
|
-
message: "Image path (drag & drop image file or enter path - JPG/PNG, max 4MB, square recommended):",
|
|
905
|
-
default: "",
|
|
906
|
-
validate: (value) => {
|
|
907
|
-
if (!value.trim()) {
|
|
908
|
-
return true;
|
|
909
|
-
}
|
|
910
|
-
const err = validateImagePath(value);
|
|
911
|
-
return err ? err : true;
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
if (!imagePath.trim()) {
|
|
915
|
-
return void 0;
|
|
916
|
-
}
|
|
917
|
-
return imagePath.trim().replace(/^["']|["']$/g, "");
|
|
918
|
-
}
|
|
919
|
-
function formatProfileForDisplay(profile) {
|
|
920
|
-
let output = "\n\u{1F4CB} Profile Summary:\n";
|
|
921
|
-
output += ` Name: ${profile.name}
|
|
922
|
-
`;
|
|
923
|
-
if (profile.website) {
|
|
924
|
-
output += ` Website: ${profile.website}
|
|
925
|
-
`;
|
|
926
|
-
}
|
|
927
|
-
if (profile.description) {
|
|
928
|
-
output += ` Description: ${profile.description}
|
|
929
|
-
`;
|
|
930
|
-
}
|
|
931
|
-
if (profile.xURL) {
|
|
932
|
-
output += ` X URL: ${profile.xURL}
|
|
933
|
-
`;
|
|
934
|
-
}
|
|
935
|
-
if (profile.imagePath) {
|
|
936
|
-
output += ` Image: ${profile.imagePath}
|
|
937
|
-
`;
|
|
938
|
-
}
|
|
939
|
-
return output;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// src/flags.ts
|
|
943
|
-
var commonFlags = {
|
|
944
|
-
environment: Flags.string({
|
|
945
|
-
required: false,
|
|
946
|
-
description: "Deployment environment to use",
|
|
947
|
-
env: "ECLOUD_ENV"
|
|
948
|
-
}),
|
|
949
|
-
"private-key": Flags.string({
|
|
950
|
-
required: false,
|
|
951
|
-
description: "Private key for signing transactions",
|
|
952
|
-
env: "ECLOUD_PRIVATE_KEY"
|
|
953
|
-
}),
|
|
954
|
-
"rpc-url": Flags.string({
|
|
955
|
-
required: false,
|
|
956
|
-
description: "RPC URL to connect to blockchain",
|
|
957
|
-
env: "ECLOUD_RPC_URL"
|
|
958
|
-
}),
|
|
959
|
-
verbose: Flags.boolean({
|
|
960
|
-
required: false,
|
|
961
|
-
description: "Enable verbose logging (default: false)",
|
|
962
|
-
default: false
|
|
963
|
-
})
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
// src/commands/compute/app/profile/set.ts
|
|
967
|
-
import chalk from "chalk";
|
|
968
|
-
var ProfileSet = class _ProfileSet extends Command {
|
|
969
|
-
static description = "Set public profile information for an app";
|
|
970
|
-
static args = {
|
|
971
|
-
"app-id": Args.string({
|
|
972
|
-
description: "App ID or name to set profile for",
|
|
973
|
-
required: false
|
|
974
|
-
})
|
|
975
|
-
};
|
|
976
|
-
static flags = {
|
|
977
|
-
...commonFlags,
|
|
978
|
-
name: Flags2.string({
|
|
979
|
-
description: "Profile name for the app",
|
|
980
|
-
required: false
|
|
981
|
-
}),
|
|
982
|
-
website: Flags2.string({
|
|
983
|
-
description: "Website URL",
|
|
984
|
-
required: false
|
|
985
|
-
}),
|
|
986
|
-
description: Flags2.string({
|
|
987
|
-
description: "App description",
|
|
988
|
-
required: false
|
|
989
|
-
}),
|
|
990
|
-
"x-url": Flags2.string({
|
|
991
|
-
description: "X (Twitter) URL",
|
|
992
|
-
required: false
|
|
993
|
-
}),
|
|
994
|
-
image: Flags2.string({
|
|
995
|
-
description: "Path to profile image (JPG/PNG, max 4MB)",
|
|
996
|
-
required: false
|
|
997
|
-
})
|
|
998
|
-
};
|
|
999
|
-
async run() {
|
|
1000
|
-
const { args, flags } = await this.parse(_ProfileSet);
|
|
1001
|
-
const environment = flags.environment || "sepolia";
|
|
1002
|
-
const environmentConfig = getEnvironmentConfig2(environment);
|
|
1003
|
-
const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
|
|
1004
|
-
const privateKey = await getPrivateKeyInteractive(flags["private-key"]);
|
|
1005
|
-
const resolver = createAppResolver(environment, environmentConfig, privateKey, rpcUrl);
|
|
1006
|
-
const appId = await getOrPromptAppID({
|
|
1007
|
-
appID: args["app-id"],
|
|
1008
|
-
environment,
|
|
1009
|
-
privateKey,
|
|
1010
|
-
rpcUrl,
|
|
1011
|
-
action: "set profile for"
|
|
1012
|
-
});
|
|
1013
|
-
this.log(`
|
|
1014
|
-
Setting profile for app: ${chalk.cyan(appId)}`);
|
|
1015
|
-
let profile;
|
|
1016
|
-
if (flags.name) {
|
|
1017
|
-
profile = {
|
|
1018
|
-
name: flags.name,
|
|
1019
|
-
website: flags.website,
|
|
1020
|
-
description: flags.description,
|
|
1021
|
-
xURL: flags["x-url"],
|
|
1022
|
-
imagePath: flags.image
|
|
1023
|
-
};
|
|
1024
|
-
const validationError = validateAppProfile(profile);
|
|
1025
|
-
if (validationError) {
|
|
1026
|
-
this.error(validationError);
|
|
1027
|
-
}
|
|
1028
|
-
this.log("\n\u{1F4CB} Profile Summary:");
|
|
1029
|
-
this.log(` Name: ${profile.name}`);
|
|
1030
|
-
if (profile.website) this.log(` Website: ${profile.website}`);
|
|
1031
|
-
if (profile.description) this.log(` Description: ${profile.description}`);
|
|
1032
|
-
if (profile.xURL) this.log(` X URL: ${profile.xURL}`);
|
|
1033
|
-
if (profile.imagePath) this.log(` Image: ${profile.imagePath}`);
|
|
1034
|
-
} else {
|
|
1035
|
-
this.log("\nEnter profile information:");
|
|
1036
|
-
profile = await getAppProfileInteractive("", true);
|
|
1037
|
-
if (!profile) {
|
|
1038
|
-
this.log(`
|
|
1039
|
-
${chalk.gray("Profile setup cancelled")}`);
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
this.log("\nUploading app profile...");
|
|
1044
|
-
const userApiClient = new UserApiClient3(environmentConfig, privateKey, rpcUrl);
|
|
1045
|
-
try {
|
|
1046
|
-
const response = await userApiClient.uploadAppProfile(
|
|
1047
|
-
appId,
|
|
1048
|
-
profile.name,
|
|
1049
|
-
profile.website,
|
|
1050
|
-
profile.description,
|
|
1051
|
-
profile.xURL,
|
|
1052
|
-
profile.imagePath
|
|
1053
|
-
);
|
|
1054
|
-
resolver.updateCacheEntry(appId, response.name);
|
|
1055
|
-
invalidateProfileCache(environment);
|
|
1056
|
-
this.log(`
|
|
1057
|
-
\u2705 ${chalk.green(`Profile updated successfully for app '${response.name}'`)}`);
|
|
1058
|
-
this.log("\nUploaded Profile:");
|
|
1059
|
-
this.log(` Name: ${response.name}`);
|
|
1060
|
-
if (response.website) this.log(` Website: ${response.website}`);
|
|
1061
|
-
if (response.description) this.log(` Description: ${response.description}`);
|
|
1062
|
-
if (response.xURL) this.log(` X URL: ${response.xURL}`);
|
|
1063
|
-
if (response.imageURL) this.log(` Image URL: ${response.imageURL}`);
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
this.error(`Failed to upload profile: ${error.message}`);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
export {
|
|
1070
|
-
ProfileSet as default
|
|
1071
|
-
};
|
|
1072
|
-
//# sourceMappingURL=set.js.map
|