@calimero-network/registry-cli 1.0.0 → 1.2.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/index.js CHANGED
@@ -1,20 +1,662 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import chalk from 'chalk';
4
- import ora from 'ora';
3
+ import chalk5 from 'chalk';
4
+ import ora6 from 'ora';
5
5
  import { table } from 'table';
6
6
  import { SSAppRegistryClient } from '@calimero-network/registry-client';
7
- import fs, { existsSync, writeFileSync } from 'fs';
7
+ import fs3, { existsSync, writeFileSync } from 'fs';
8
8
  import path from 'path';
9
+ import os from 'os';
10
+ import crypto from 'crypto';
11
+ import fastify from 'fastify';
12
+ import cors from '@fastify/cors';
13
+ import { Buffer } from 'buffer';
14
+ import fetch from 'node-fetch';
9
15
 
16
+ var LocalConfig = class {
17
+ constructor() {
18
+ this.configPath = path.join(
19
+ os.homedir(),
20
+ ".calimero-registry",
21
+ "config.json"
22
+ );
23
+ this.dataDir = path.join(os.homedir(), ".calimero-registry");
24
+ this.config = this.loadConfig();
25
+ }
26
+ loadConfig() {
27
+ const defaultConfig = {
28
+ server: {
29
+ port: 8082,
30
+ host: "localhost"
31
+ },
32
+ data: {
33
+ dir: path.join(os.homedir(), ".calimero-registry", "data"),
34
+ artifactsDir: path.join(
35
+ os.homedir(),
36
+ ".calimero-registry",
37
+ "artifacts"
38
+ )
39
+ },
40
+ artifacts: {
41
+ storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
42
+ serveLocal: true,
43
+ copyArtifacts: true,
44
+ maxFileSize: "100MB",
45
+ allowedTypes: ["wasm", "js", "html"]
46
+ }
47
+ };
48
+ if (fs3.existsSync(this.configPath)) {
49
+ try {
50
+ const existingConfig = JSON.parse(
51
+ fs3.readFileSync(this.configPath, "utf8")
52
+ );
53
+ return { ...defaultConfig, ...existingConfig };
54
+ } catch {
55
+ console.warn("Failed to load existing config, using defaults");
56
+ }
57
+ }
58
+ return defaultConfig;
59
+ }
60
+ saveConfig() {
61
+ const configDir = path.dirname(this.configPath);
62
+ if (!fs3.existsSync(configDir)) {
63
+ fs3.mkdirSync(configDir, { recursive: true });
64
+ }
65
+ fs3.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
66
+ }
67
+ // Server configuration
68
+ getPort() {
69
+ return this.config.server.port;
70
+ }
71
+ setPort(port) {
72
+ this.config.server.port = port;
73
+ this.saveConfig();
74
+ }
75
+ getHost() {
76
+ return this.config.server.host;
77
+ }
78
+ setHost(host) {
79
+ this.config.server.host = host;
80
+ this.saveConfig();
81
+ }
82
+ // Data directory configuration
83
+ getDataDir() {
84
+ return this.config.data.dir;
85
+ }
86
+ setDataDir(dir) {
87
+ this.config.data.dir = dir;
88
+ this.config.artifacts.storageDir = path.join(dir, "artifacts");
89
+ this.saveConfig();
90
+ }
91
+ getArtifactsDir() {
92
+ return this.config.data.artifactsDir;
93
+ }
94
+ setArtifactsDir(dir) {
95
+ this.config.data.artifactsDir = dir;
96
+ this.config.artifacts.storageDir = dir;
97
+ this.saveConfig();
98
+ }
99
+ // Artifact configuration
100
+ getArtifactsConfig() {
101
+ return this.config.artifacts;
102
+ }
103
+ setArtifactsConfig(config) {
104
+ this.config.artifacts = { ...this.config.artifacts, ...config };
105
+ this.saveConfig();
106
+ }
107
+ // Utility methods
108
+ ensureDirectories() {
109
+ const dirs = [
110
+ this.getDataDir(),
111
+ this.getArtifactsDir(),
112
+ path.join(this.getDataDir(), "backups"),
113
+ path.join(this.getDataDir(), "logs")
114
+ ];
115
+ dirs.forEach((dir) => {
116
+ if (!fs3.existsSync(dir)) {
117
+ fs3.mkdirSync(dir, { recursive: true });
118
+ }
119
+ });
120
+ }
121
+ getConfigPath() {
122
+ return this.configPath;
123
+ }
124
+ // Get full configuration
125
+ getConfig() {
126
+ return { ...this.config };
127
+ }
128
+ // Reset to defaults
129
+ reset() {
130
+ this.config = {
131
+ server: {
132
+ port: 8082,
133
+ host: "localhost"
134
+ },
135
+ data: {
136
+ dir: path.join(os.homedir(), ".calimero-registry", "data"),
137
+ artifactsDir: path.join(
138
+ os.homedir(),
139
+ ".calimero-registry",
140
+ "artifacts"
141
+ )
142
+ },
143
+ artifacts: {
144
+ storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
145
+ serveLocal: true,
146
+ copyArtifacts: true,
147
+ maxFileSize: "100MB",
148
+ allowedTypes: ["wasm", "js", "html"]
149
+ }
150
+ };
151
+ this.saveConfig();
152
+ }
153
+ };
154
+ var LocalDataStore = class {
155
+ constructor(config) {
156
+ this.config = config;
157
+ this.appsFile = path.join(config.getDataDir(), "apps.json");
158
+ this.manifestsFile = path.join(config.getDataDir(), "manifests.json");
159
+ this.artifactsFile = path.join(config.getDataDir(), "artifacts.json");
160
+ this.data = {
161
+ apps: /* @__PURE__ */ new Map(),
162
+ manifests: /* @__PURE__ */ new Map(),
163
+ artifacts: /* @__PURE__ */ new Map()
164
+ };
165
+ this.loadData();
166
+ }
167
+ loadData() {
168
+ try {
169
+ if (fs3.existsSync(this.appsFile)) {
170
+ const appsData = JSON.parse(fs3.readFileSync(this.appsFile, "utf8"));
171
+ this.data.apps = new Map(Object.entries(appsData));
172
+ }
173
+ if (fs3.existsSync(this.manifestsFile)) {
174
+ const manifestsData = JSON.parse(
175
+ fs3.readFileSync(this.manifestsFile, "utf8")
176
+ );
177
+ this.data.manifests = new Map(Object.entries(manifestsData));
178
+ }
179
+ if (fs3.existsSync(this.artifactsFile)) {
180
+ const artifactsData = JSON.parse(
181
+ fs3.readFileSync(this.artifactsFile, "utf8")
182
+ );
183
+ this.data.artifacts = new Map(Object.entries(artifactsData));
184
+ }
185
+ } catch {
186
+ console.warn("Failed to load existing data, starting fresh");
187
+ }
188
+ }
189
+ saveData() {
190
+ try {
191
+ this.config.ensureDirectories();
192
+ const appsObj = Object.fromEntries(this.data.apps);
193
+ fs3.writeFileSync(this.appsFile, JSON.stringify(appsObj, null, 2));
194
+ const manifestsObj = Object.fromEntries(this.data.manifests);
195
+ fs3.writeFileSync(
196
+ this.manifestsFile,
197
+ JSON.stringify(manifestsObj, null, 2)
198
+ );
199
+ const artifactsObj = Object.fromEntries(this.data.artifacts);
200
+ fs3.writeFileSync(
201
+ this.artifactsFile,
202
+ JSON.stringify(artifactsObj, null, 2)
203
+ );
204
+ } catch (error) {
205
+ throw new Error(
206
+ `Failed to save data: ${error instanceof Error ? error.message : "Unknown error"}`
207
+ );
208
+ }
209
+ }
210
+ // Apps management
211
+ getApps(filters) {
212
+ let apps = Array.from(this.data.apps.values());
213
+ if (filters?.dev) {
214
+ apps = apps.filter((app) => app.developer_pubkey === filters.dev);
215
+ }
216
+ if (filters?.name) {
217
+ apps = apps.filter((app) => app.name.includes(filters.name));
218
+ }
219
+ return apps;
220
+ }
221
+ getApp(appKey) {
222
+ return this.data.apps.get(appKey);
223
+ }
224
+ setApp(appKey, app) {
225
+ this.data.apps.set(appKey, app);
226
+ this.saveData();
227
+ }
228
+ // Versions management
229
+ getAppVersions(appId) {
230
+ const versions = [];
231
+ for (const [key, manifest] of this.data.manifests.entries()) {
232
+ if (key.startsWith(`${appId}/`)) {
233
+ const semver = key.split("/").pop();
234
+ versions.push({
235
+ semver,
236
+ cid: manifest.artifacts[0]?.cid || "",
237
+ yanked: false
238
+ // TODO: Implement yanking
239
+ });
240
+ }
241
+ }
242
+ return versions.sort(
243
+ (a, b) => a.semver.localeCompare(b.semver, void 0, { numeric: true })
244
+ );
245
+ }
246
+ // Manifest management
247
+ getManifest(appId, semver) {
248
+ const manifestKey = `${appId}/${semver}`;
249
+ return this.data.manifests.get(manifestKey);
250
+ }
251
+ setManifest(manifestKey, manifest) {
252
+ this.data.manifests.set(manifestKey, manifest);
253
+ this.saveData();
254
+ }
255
+ // Artifact management
256
+ getArtifactPath(hash) {
257
+ return this.data.artifacts.get(hash);
258
+ }
259
+ setArtifactPath(hash, filePath) {
260
+ this.data.artifacts.set(hash, filePath);
261
+ this.saveData();
262
+ }
263
+ // Statistics
264
+ getStats() {
265
+ return {
266
+ publishedApps: this.data.apps.size,
267
+ totalVersions: this.data.manifests.size,
268
+ totalArtifacts: this.data.artifacts.size
269
+ };
270
+ }
271
+ // Backup and restore
272
+ async backup(outputPath) {
273
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
274
+ const backupPath = outputPath || path.join(
275
+ this.config.getDataDir(),
276
+ "backups",
277
+ `backup-${timestamp}.json`
278
+ );
279
+ const backupDir = path.dirname(backupPath);
280
+ if (!fs3.existsSync(backupDir)) {
281
+ fs3.mkdirSync(backupDir, { recursive: true });
282
+ }
283
+ const backupData = {
284
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
285
+ apps: Object.fromEntries(this.data.apps),
286
+ manifests: Object.fromEntries(this.data.manifests),
287
+ artifacts: Object.fromEntries(this.data.artifacts)
288
+ };
289
+ fs3.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
290
+ return backupPath;
291
+ }
292
+ async restore(backupPath) {
293
+ if (!fs3.existsSync(backupPath)) {
294
+ throw new Error(`Backup file not found: ${backupPath}`);
295
+ }
296
+ const backupData = JSON.parse(fs3.readFileSync(backupPath, "utf8"));
297
+ this.data.apps = new Map(Object.entries(backupData.apps || {}));
298
+ this.data.manifests = new Map(Object.entries(backupData.manifests || {}));
299
+ this.data.artifacts = new Map(Object.entries(backupData.artifacts || {}));
300
+ this.saveData();
301
+ }
302
+ // Reset data
303
+ reset() {
304
+ this.data.apps.clear();
305
+ this.data.manifests.clear();
306
+ this.data.artifacts.clear();
307
+ this.saveData();
308
+ }
309
+ // Seed with sample data
310
+ async seed() {
311
+ const sampleApps = [
312
+ {
313
+ name: "sample-wallet",
314
+ developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
315
+ latest_version: "1.0.0",
316
+ latest_cid: "QmSampleWallet123",
317
+ alias: "Sample Wallet"
318
+ },
319
+ {
320
+ name: "demo-dex",
321
+ developer_pubkey: "ed25519:5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
322
+ latest_version: "2.1.0",
323
+ latest_cid: "QmDemoDex456",
324
+ alias: "Demo DEX"
325
+ }
326
+ ];
327
+ const sampleManifests = [
328
+ {
329
+ manifest_version: "1.0",
330
+ app: {
331
+ name: "sample-wallet",
332
+ namespace: "com.example",
333
+ developer_pubkey: "ed25519:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
334
+ id: "sample-wallet-id",
335
+ alias: "Sample Wallet"
336
+ },
337
+ version: {
338
+ semver: "1.0.0"
339
+ },
340
+ supported_chains: ["mainnet", "testnet"],
341
+ permissions: [
342
+ { cap: "wallet", bytes: 1024 },
343
+ { cap: "network", bytes: 512 }
344
+ ],
345
+ artifacts: [
346
+ {
347
+ type: "wasm",
348
+ target: "node",
349
+ path: "/artifacts/sample-wallet/1.0.0/app.wasm",
350
+ size: 1024e3,
351
+ sha256: "abc123def456"
352
+ }
353
+ ],
354
+ metadata: {
355
+ description: "A sample wallet application for testing",
356
+ author: "Sample Developer"
357
+ },
358
+ distribution: "local",
359
+ signature: {
360
+ alg: "ed25519",
361
+ sig: "sample-signature",
362
+ signed_at: (/* @__PURE__ */ new Date()).toISOString()
363
+ }
364
+ }
365
+ ];
366
+ sampleApps.forEach((app) => {
367
+ const appKey = `${app.developer_pubkey}/${app.name}`;
368
+ this.setApp(appKey, app);
369
+ });
370
+ sampleManifests.forEach((manifest) => {
371
+ const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
372
+ this.setManifest(manifestKey, manifest);
373
+ });
374
+ this.saveData();
375
+ }
376
+ };
377
+ var LocalArtifactServer = class {
378
+ constructor(config, dataStore) {
379
+ this.config = config;
380
+ this.dataStore = dataStore;
381
+ this.artifactsDir = config.getArtifactsDir();
382
+ this.ensureArtifactsDir();
383
+ }
384
+ ensureArtifactsDir() {
385
+ if (!fs3.existsSync(this.artifactsDir)) {
386
+ fs3.mkdirSync(this.artifactsDir, { recursive: true });
387
+ }
388
+ }
389
+ // Copy artifact to local storage
390
+ async copyArtifactToLocal(sourcePath, appId, version, filename) {
391
+ if (!fs3.existsSync(sourcePath)) {
392
+ throw new Error(`Source file not found: ${sourcePath}`);
393
+ }
394
+ const appVersionDir = path.join(this.artifactsDir, appId, version);
395
+ if (!fs3.existsSync(appVersionDir)) {
396
+ fs3.mkdirSync(appVersionDir, { recursive: true });
397
+ }
398
+ const targetPath = path.join(appVersionDir, filename);
399
+ fs3.copyFileSync(sourcePath, targetPath);
400
+ const fileHash = await this.calculateFileHash(targetPath);
401
+ this.dataStore.setArtifactPath(fileHash, targetPath);
402
+ return targetPath;
403
+ }
404
+ // Serve artifact by app ID, version, and filename
405
+ async serveArtifact(appId, version, filename) {
406
+ const artifactPath = path.join(this.artifactsDir, appId, version, filename);
407
+ if (!fs3.existsSync(artifactPath)) {
408
+ throw new Error(`Artifact not found: ${artifactPath}`);
409
+ }
410
+ return fs3.readFileSync(artifactPath);
411
+ }
412
+ // Serve artifact by hash
413
+ async serveByHash(hash) {
414
+ const artifactPath = this.dataStore.getArtifactPath(hash);
415
+ if (!artifactPath || !fs3.existsSync(artifactPath)) {
416
+ throw new Error(`Artifact not found for hash: ${hash}`);
417
+ }
418
+ return fs3.readFileSync(artifactPath);
419
+ }
420
+ // Validate artifact file
421
+ async validateArtifact(filePath) {
422
+ if (!fs3.existsSync(filePath)) {
423
+ throw new Error(`File not found: ${filePath}`);
424
+ }
425
+ const stats = fs3.statSync(filePath);
426
+ const fileBuffer = fs3.readFileSync(filePath);
427
+ const sha256 = crypto.createHash("sha256").update(fileBuffer).digest("hex");
428
+ return {
429
+ size: stats.size,
430
+ sha256
431
+ };
432
+ }
433
+ // Calculate file hash
434
+ async calculateFileHash(filePath) {
435
+ const fileBuffer = fs3.readFileSync(filePath);
436
+ return crypto.createHash("sha256").update(fileBuffer).digest("hex");
437
+ }
438
+ // Get artifact URL for local serving
439
+ getArtifactUrl(appId, version, filename) {
440
+ const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
441
+ return `${baseUrl}/artifacts/${appId}/${version}/${filename}`;
442
+ }
443
+ // Get artifact URL by hash
444
+ getArtifactUrlByHash(hash) {
445
+ const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
446
+ return `${baseUrl}/artifacts/${hash}`;
447
+ }
448
+ // Update manifest artifacts to use local URLs
449
+ updateManifestArtifacts(manifest) {
450
+ const updatedManifest = { ...manifest };
451
+ if (updatedManifest.artifacts) {
452
+ updatedManifest.artifacts = updatedManifest.artifacts.map(
453
+ (artifact) => {
454
+ const updatedArtifact = { ...artifact };
455
+ if (artifact.path) {
456
+ const filename = path.basename(artifact.path);
457
+ updatedArtifact.mirrors = [
458
+ this.getArtifactUrl(
459
+ manifest.app.id || manifest.app.name?.replace(/\s+/g, "-").toLowerCase(),
460
+ manifest.version.semver,
461
+ filename
462
+ )
463
+ ];
464
+ delete updatedArtifact.path;
465
+ }
466
+ return updatedArtifact;
467
+ }
468
+ );
469
+ }
470
+ return updatedManifest;
471
+ }
472
+ // Clean up old artifacts
473
+ async cleanupOldArtifacts(maxAge = 30 * 24 * 60 * 60 * 1e3) {
474
+ const now = Date.now();
475
+ const getAllFiles = (dir) => {
476
+ let files = [];
477
+ const items = fs3.readdirSync(dir);
478
+ for (const item of items) {
479
+ const fullPath = path.join(dir, item);
480
+ const stat = fs3.statSync(fullPath);
481
+ if (stat.isDirectory()) {
482
+ files = files.concat(getAllFiles(fullPath));
483
+ } else {
484
+ files.push(fullPath);
485
+ }
486
+ }
487
+ return files;
488
+ };
489
+ const allFiles = getAllFiles(this.artifactsDir);
490
+ for (const file of allFiles) {
491
+ const stats = fs3.statSync(file);
492
+ const age = now - stats.mtime.getTime();
493
+ if (age > maxAge) {
494
+ fs3.unlinkSync(file);
495
+ console.log(`Cleaned up old artifact: ${file}`);
496
+ }
497
+ }
498
+ }
499
+ // Get artifact statistics
500
+ getArtifactStats() {
501
+ let totalFiles = 0;
502
+ let totalSize = 0;
503
+ let oldestFile = null;
504
+ const getAllFiles = (dir) => {
505
+ let files = [];
506
+ const items = fs3.readdirSync(dir);
507
+ for (const item of items) {
508
+ const fullPath = path.join(dir, item);
509
+ const stat = fs3.statSync(fullPath);
510
+ if (stat.isDirectory()) {
511
+ files = files.concat(getAllFiles(fullPath));
512
+ } else {
513
+ files.push(fullPath);
514
+ }
515
+ }
516
+ return files;
517
+ };
518
+ if (fs3.existsSync(this.artifactsDir)) {
519
+ const allFiles = getAllFiles(this.artifactsDir);
520
+ for (const file of allFiles) {
521
+ const stats = fs3.statSync(file);
522
+ totalFiles++;
523
+ totalSize += stats.size;
524
+ if (!oldestFile || stats.mtime < oldestFile) {
525
+ oldestFile = stats.mtime;
526
+ }
527
+ }
528
+ }
529
+ return {
530
+ totalFiles,
531
+ totalSize,
532
+ oldestFile
533
+ };
534
+ }
535
+ };
536
+ var RemoteRegistryClient = class {
537
+ constructor(baseURL, timeout) {
538
+ this.client = new SSAppRegistryClient({
539
+ baseURL,
540
+ timeout
541
+ });
542
+ }
543
+ async getApps(filters) {
544
+ return await this.client.getApps(filters);
545
+ }
546
+ async getAppVersions(appId) {
547
+ return await this.client.getAppVersions(appId);
548
+ }
549
+ async getAppManifest(appId, semver) {
550
+ return await this.client.getAppManifest(appId, semver);
551
+ }
552
+ async submitAppManifest(manifest) {
553
+ return await this.client.submitAppManifest(manifest);
554
+ }
555
+ async healthCheck() {
556
+ return await this.client.healthCheck();
557
+ }
558
+ };
559
+ var LocalRegistryClient = class {
560
+ constructor() {
561
+ this.config = new LocalConfig();
562
+ this.dataStore = new LocalDataStore(this.config);
563
+ this.artifactServer = new LocalArtifactServer(this.config, this.dataStore);
564
+ }
565
+ async getApps(filters) {
566
+ return this.dataStore.getApps(filters);
567
+ }
568
+ async getAppVersions(appId) {
569
+ return this.dataStore.getAppVersions(appId);
570
+ }
571
+ async getAppManifest(appId, semver) {
572
+ const manifest = this.dataStore.getManifest(appId, semver);
573
+ if (!manifest) {
574
+ throw new Error("Manifest not found");
575
+ }
576
+ return this.artifactServer.updateManifestArtifacts(manifest);
577
+ }
578
+ async submitAppManifest(manifest) {
579
+ if (!this.validateManifest(manifest)) {
580
+ throw new Error("Invalid manifest structure");
581
+ }
582
+ const processedManifest = await this.processManifestArtifacts(manifest);
583
+ const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
584
+ this.dataStore.setManifest(manifestKey, processedManifest);
585
+ const appKey = manifest.app.app_id;
586
+ const appSummary = {
587
+ name: manifest.app.name,
588
+ developer_pubkey: manifest.app.developer_pubkey,
589
+ latest_version: manifest.version.semver,
590
+ latest_cid: manifest.artifacts[0]?.cid || "",
591
+ alias: manifest.app.alias
592
+ };
593
+ this.dataStore.setApp(appKey, appSummary);
594
+ return {
595
+ success: true,
596
+ message: "App version registered successfully"
597
+ };
598
+ }
599
+ async healthCheck() {
600
+ return { status: "ok" };
601
+ }
602
+ validateManifest(manifest) {
603
+ return !!(manifest.manifest_version && manifest.app && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
604
+ }
605
+ async processManifestArtifacts(manifest) {
606
+ const processedManifest = { ...manifest };
607
+ if (processedManifest.artifacts) {
608
+ processedManifest.artifacts = await Promise.all(
609
+ processedManifest.artifacts.map(async (artifact) => {
610
+ const processedArtifact = { ...artifact };
611
+ if (artifact.path && fs3.existsSync(artifact.path)) {
612
+ const filename = path.basename(artifact.path);
613
+ const appId = manifest.app.id || manifest.app.name?.replace(/\s+/g, "-").toLowerCase();
614
+ try {
615
+ await this.artifactServer.copyArtifactToLocal(
616
+ artifact.path,
617
+ appId,
618
+ manifest.version.semver,
619
+ filename
620
+ );
621
+ processedArtifact.mirrors = [
622
+ this.artifactServer.getArtifactUrl(
623
+ appId,
624
+ manifest.version.semver,
625
+ filename
626
+ )
627
+ ];
628
+ delete processedArtifact.path;
629
+ } catch (error) {
630
+ console.warn(`Failed to copy artifact ${artifact.path}:`, error);
631
+ }
632
+ }
633
+ return processedArtifact;
634
+ })
635
+ );
636
+ }
637
+ return processedManifest;
638
+ }
639
+ };
640
+ function createRegistryClient(useLocal, baseURL, timeout) {
641
+ if (useLocal) {
642
+ return new LocalRegistryClient();
643
+ } else {
644
+ return new RemoteRegistryClient(
645
+ baseURL || "http://localhost:8082",
646
+ timeout || 1e4
647
+ );
648
+ }
649
+ }
10
650
  var appsCommand = new Command("apps").description("Manage SSApp applications").addCommand(
11
651
  new Command("list").description("List all applications").option("-d, --dev <pubkey>", "Filter by developer public key").option("-n, --name <name>", "Filter by application name").action(async (options, command) => {
12
652
  const globalOpts = command.parent?.parent?.opts();
13
- const client = new SSAppRegistryClient({
14
- baseURL: globalOpts?.url || "http://localhost:8082",
15
- timeout: parseInt(globalOpts?.timeout || "10000")
16
- });
17
- const spinner2 = ora("Fetching applications...").start();
653
+ const useLocal = globalOpts?.local || false;
654
+ const client = createRegistryClient(
655
+ useLocal,
656
+ globalOpts?.url,
657
+ parseInt(globalOpts?.timeout || "10000")
658
+ );
659
+ const spinner2 = ora6("Fetching applications...").start();
18
660
  try {
19
661
  const apps = await client.getApps({
20
662
  dev: options.dev,
@@ -22,7 +664,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
22
664
  });
23
665
  spinner2.succeed(`Found ${apps.length} application(s)`);
24
666
  if (apps.length === 0) {
25
- console.log(chalk.yellow("No applications found"));
667
+ console.log(chalk5.yellow("No applications found"));
26
668
  return;
27
669
  }
28
670
  const tableData = [
@@ -39,7 +681,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
39
681
  } catch (error) {
40
682
  spinner2.fail("Failed to fetch applications");
41
683
  if (error instanceof Error) {
42
- console.error(chalk.red(`Error: ${error.message}`));
684
+ console.error(chalk5.red(`Error: ${error.message}`));
43
685
  }
44
686
  process.exit(1);
45
687
  }
@@ -47,16 +689,18 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
47
689
  ).addCommand(
48
690
  new Command("versions").description("List versions of a specific application").argument("<appId>", "Application ID").action(async (appId, options, command) => {
49
691
  const globalOpts = command.parent?.parent?.opts();
50
- const client = new SSAppRegistryClient({
51
- baseURL: globalOpts?.url || "http://localhost:8082",
52
- timeout: parseInt(globalOpts?.timeout || "10000")
53
- });
54
- const spinner2 = ora(`Fetching versions for ${appId}...`).start();
692
+ const useLocal = globalOpts?.local || false;
693
+ const client = createRegistryClient(
694
+ useLocal,
695
+ globalOpts?.url,
696
+ parseInt(globalOpts?.timeout || "10000")
697
+ );
698
+ const spinner2 = ora6(`Fetching versions for ${appId}...`).start();
55
699
  try {
56
700
  const versions = await client.getAppVersions(appId);
57
701
  spinner2.succeed(`Found ${versions.length} version(s)`);
58
702
  if (versions.length === 0) {
59
- console.log(chalk.yellow("No versions found"));
703
+ console.log(chalk5.yellow("No versions found"));
60
704
  return;
61
705
  }
62
706
  const tableData = [
@@ -64,14 +708,14 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
64
708
  ...versions.map((version) => [
65
709
  version.semver,
66
710
  version.cid.substring(0, 12) + "...",
67
- version.yanked ? chalk.red("Yes") : chalk.green("No")
711
+ version.yanked ? chalk5.red("Yes") : chalk5.green("No")
68
712
  ])
69
713
  ];
70
714
  console.log(table(tableData));
71
715
  } catch (error) {
72
716
  spinner2.fail("Failed to fetch versions");
73
717
  if (error instanceof Error) {
74
- console.error(chalk.red(`Error: ${error.message}`));
718
+ console.error(chalk5.red(`Error: ${error.message}`));
75
719
  }
76
720
  process.exit(1);
77
721
  }
@@ -79,22 +723,24 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
79
723
  ).addCommand(
80
724
  new Command("manifest").description("Get manifest for a specific application version").argument("<appId>", "Application ID").argument("<version>", "Application version").action(async (appId, version, options, command) => {
81
725
  const globalOpts = command.parent?.parent?.opts();
82
- const client = new SSAppRegistryClient({
83
- baseURL: globalOpts?.url || "http://localhost:8082",
84
- timeout: parseInt(globalOpts?.timeout || "10000")
85
- });
86
- const spinner2 = ora(
726
+ const useLocal = globalOpts?.local || false;
727
+ const client = createRegistryClient(
728
+ useLocal,
729
+ globalOpts?.url,
730
+ parseInt(globalOpts?.timeout || "10000")
731
+ );
732
+ const spinner2 = ora6(
87
733
  `Fetching manifest for ${appId}@${version}...`
88
734
  ).start();
89
735
  try {
90
736
  const manifest = await client.getAppManifest(appId, version);
91
737
  spinner2.succeed("Manifest fetched successfully");
92
- console.log(chalk.blue("\nApplication Manifest:"));
738
+ console.log(chalk5.blue("\nApplication Manifest:"));
93
739
  console.log(JSON.stringify(manifest, null, 2));
94
740
  } catch (error) {
95
741
  spinner2.fail("Failed to fetch manifest");
96
742
  if (error instanceof Error) {
97
- console.error(chalk.red(`Error: ${error.message}`));
743
+ console.error(chalk5.red(`Error: ${error.message}`));
98
744
  }
99
745
  process.exit(1);
100
746
  }
@@ -102,37 +748,39 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
102
748
  ).addCommand(
103
749
  new Command("submit").description("Submit a new application manifest").argument("<manifest-file>", "Path to the manifest JSON file").action(async (manifestFile, options, command) => {
104
750
  const globalOpts = command.parent?.parent?.opts();
105
- const client = new SSAppRegistryClient({
106
- baseURL: globalOpts?.url || "http://localhost:8082",
107
- timeout: parseInt(globalOpts?.timeout || "10000")
108
- });
109
- const spinner2 = ora("Reading manifest file...").start();
751
+ const useLocal = globalOpts?.local || false;
752
+ const client = createRegistryClient(
753
+ useLocal,
754
+ globalOpts?.url,
755
+ parseInt(globalOpts?.timeout || "10000")
756
+ );
757
+ const spinner2 = ora6("Reading manifest file...").start();
110
758
  try {
111
759
  const manifestPath = path.resolve(manifestFile);
112
- if (!fs.existsSync(manifestPath)) {
760
+ if (!fs3.existsSync(manifestPath)) {
113
761
  spinner2.fail("Manifest file not found");
114
- console.error(chalk.red(`File not found: ${manifestFile}`));
762
+ console.error(chalk5.red(`File not found: ${manifestFile}`));
115
763
  process.exit(1);
116
764
  }
117
- const manifestContent = fs.readFileSync(manifestPath, "utf8");
765
+ const manifestContent = fs3.readFileSync(manifestPath, "utf8");
118
766
  const manifest = JSON.parse(manifestContent);
119
767
  spinner2.text = "Submitting application manifest...";
120
768
  const result = await client.submitAppManifest(manifest);
121
769
  spinner2.succeed("Application submitted successfully");
122
- console.log(chalk.green(`
770
+ console.log(chalk5.green(`
123
771
  \u2705 ${result.message}`));
124
772
  if (manifest.app?.name) {
125
- console.log(chalk.blue(`
773
+ console.log(chalk5.blue(`
126
774
  \u{1F4F1} App: ${manifest.app.name}`));
127
775
  console.log(
128
- chalk.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
776
+ chalk5.blue(`\u{1F464} Developer: ${manifest.app.developer_pubkey}`)
129
777
  );
130
- console.log(chalk.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
778
+ console.log(chalk5.blue(`\u{1F4E6} Version: ${manifest.version?.semver}`));
131
779
  }
132
780
  } catch (error) {
133
781
  spinner2.fail("Failed to submit application");
134
782
  if (error instanceof Error) {
135
- console.error(chalk.red(`Error: ${error.message}`));
783
+ console.error(chalk5.red(`Error: ${error.message}`));
136
784
  }
137
785
  process.exit(1);
138
786
  }
@@ -145,33 +793,33 @@ var developersCommand = new Command("developers").description("Manage developer
145
793
  baseURL: globalOpts?.url || "http://localhost:8082",
146
794
  timeout: parseInt(globalOpts?.timeout || "10000")
147
795
  });
148
- const spinner2 = ora("Fetching developer profile...").start();
796
+ const spinner2 = ora6("Fetching developer profile...").start();
149
797
  try {
150
798
  const profile = await client.getDeveloper(pubkey);
151
799
  spinner2.succeed("Developer profile fetched successfully");
152
- console.log(chalk.blue("\nDeveloper Profile:"));
153
- console.log(chalk.green("Display Name:"), profile.display_name);
800
+ console.log(chalk5.blue("\nDeveloper Profile:"));
801
+ console.log(chalk5.green("Display Name:"), profile.display_name);
154
802
  if (profile.website) {
155
- console.log(chalk.green("Website:"), profile.website);
803
+ console.log(chalk5.green("Website:"), profile.website);
156
804
  }
157
805
  if (profile.proofs.length > 0) {
158
- console.log(chalk.green("\nProofs:"));
806
+ console.log(chalk5.green("\nProofs:"));
159
807
  const tableData = [
160
808
  ["Type", "Value", "Verified"],
161
809
  ...profile.proofs.map((proof) => [
162
810
  proof.type,
163
811
  proof.value.substring(0, 20) + "...",
164
- proof.verified ? chalk.green("Yes") : chalk.red("No")
812
+ proof.verified ? chalk5.green("Yes") : chalk5.red("No")
165
813
  ])
166
814
  ];
167
815
  console.log(table(tableData));
168
816
  } else {
169
- console.log(chalk.yellow("\nNo proofs found"));
817
+ console.log(chalk5.yellow("\nNo proofs found"));
170
818
  }
171
819
  } catch (error) {
172
820
  spinner2.fail("Failed to fetch developer profile");
173
821
  if (error instanceof Error) {
174
- console.error(chalk.red(`Error: ${error.message}`));
822
+ console.error(chalk5.red(`Error: ${error.message}`));
175
823
  }
176
824
  process.exit(1);
177
825
  }
@@ -183,7 +831,7 @@ var developersCommand = new Command("developers").description("Manage developer
183
831
  baseURL: globalOpts?.url || "http://localhost:8082",
184
832
  timeout: parseInt(globalOpts?.timeout || "10000")
185
833
  });
186
- const spinner2 = ora("Creating developer profile...").start();
834
+ const spinner2 = ora6("Creating developer profile...").start();
187
835
  try {
188
836
  let proofs = [];
189
837
  if (options.proofs) {
@@ -191,7 +839,7 @@ var developersCommand = new Command("developers").description("Manage developer
191
839
  proofs = JSON.parse(options.proofs);
192
840
  } catch {
193
841
  spinner2.fail("Invalid proofs JSON format");
194
- console.error(chalk.red("Proofs must be a valid JSON array"));
842
+ console.error(chalk5.red("Proofs must be a valid JSON array"));
195
843
  process.exit(1);
196
844
  }
197
845
  }
@@ -203,18 +851,18 @@ var developersCommand = new Command("developers").description("Manage developer
203
851
  };
204
852
  const result = await client.submitDeveloperProfile(pubkey, profile);
205
853
  spinner2.succeed("Developer profile created successfully");
206
- console.log(chalk.green(`
854
+ console.log(chalk5.green(`
207
855
  \u2705 ${result.message}`));
208
- console.log(chalk.blue(`
856
+ console.log(chalk5.blue(`
209
857
  \u{1F464} Developer: ${displayName}`));
210
- console.log(chalk.blue(`\u{1F511} Public Key: ${pubkey}`));
858
+ console.log(chalk5.blue(`\u{1F511} Public Key: ${pubkey}`));
211
859
  if (options.website) {
212
- console.log(chalk.blue(`\u{1F310} Website: ${options.website}`));
860
+ console.log(chalk5.blue(`\u{1F310} Website: ${options.website}`));
213
861
  }
214
862
  } catch (error) {
215
863
  spinner2.fail("Failed to create developer profile");
216
864
  if (error instanceof Error) {
217
- console.error(chalk.red(`Error: ${error.message}`));
865
+ console.error(chalk5.red(`Error: ${error.message}`));
218
866
  }
219
867
  process.exit(1);
220
868
  }
@@ -227,7 +875,7 @@ var attestationsCommand = new Command("attestations").description("Manage applic
227
875
  baseURL: globalOpts?.url || "http://localhost:8082",
228
876
  timeout: parseInt(globalOpts?.timeout || "10000")
229
877
  });
230
- const spinner2 = ora(
878
+ const spinner2 = ora6(
231
879
  `Fetching attestation for ${name}@${version}...`
232
880
  ).start();
233
881
  try {
@@ -237,16 +885,16 @@ var attestationsCommand = new Command("attestations").description("Manage applic
237
885
  version
238
886
  );
239
887
  spinner2.succeed("Attestation fetched successfully");
240
- console.log(chalk.blue("\nAttestation:"));
241
- console.log(chalk.green("Status:"), attestation.status);
242
- console.log(chalk.green("Timestamp:"), attestation.timestamp);
888
+ console.log(chalk5.blue("\nAttestation:"));
889
+ console.log(chalk5.green("Status:"), attestation.status);
890
+ console.log(chalk5.green("Timestamp:"), attestation.timestamp);
243
891
  if (attestation.comment) {
244
- console.log(chalk.green("Comment:"), attestation.comment);
892
+ console.log(chalk5.green("Comment:"), attestation.comment);
245
893
  }
246
894
  } catch (error) {
247
895
  spinner2.fail("Failed to fetch attestation");
248
896
  if (error instanceof Error) {
249
- console.error(chalk.red(`Error: ${error.message}`));
897
+ console.error(chalk5.red(`Error: ${error.message}`));
250
898
  }
251
899
  process.exit(1);
252
900
  }
@@ -254,19 +902,21 @@ var attestationsCommand = new Command("attestations").description("Manage applic
254
902
  );
255
903
  var healthCommand = new Command("health").description("Check the health of the SSApp Registry API").action(async (options, command) => {
256
904
  const globalOpts = command.parent?.opts();
257
- const client = new SSAppRegistryClient({
258
- baseURL: globalOpts?.url || "http://localhost:8082",
259
- timeout: parseInt(globalOpts?.timeout || "10000")
260
- });
261
- const spinner2 = ora("Checking API health...").start();
905
+ const useLocal = globalOpts?.local || false;
906
+ const client = createRegistryClient(
907
+ useLocal,
908
+ globalOpts?.url,
909
+ parseInt(globalOpts?.timeout || "10000")
910
+ );
911
+ const spinner2 = ora6("Checking API health...").start();
262
912
  try {
263
913
  const health = await client.healthCheck();
264
914
  spinner2.succeed("API is healthy");
265
- console.log(chalk.green(`Status: ${health.status}`));
915
+ console.log(chalk5.green(`Status: ${health.status}`));
266
916
  } catch (error) {
267
917
  spinner2.fail("API health check failed");
268
918
  if (error instanceof Error) {
269
- console.error(chalk.red(`Error: ${error.message}`));
919
+ console.error(chalk5.red(`Error: ${error.message}`));
270
920
  }
271
921
  process.exit(1);
272
922
  }
@@ -301,7 +951,7 @@ This is a demo file that would normally contain the actual application data.`;
301
951
  );
302
952
  }
303
953
  }
304
- var spinner = (text) => ora(text);
954
+ var spinner = (text) => ora6(text);
305
955
 
306
956
  // src/commands/ipfs.ts
307
957
  var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
@@ -339,6 +989,785 @@ var ipfsCommand = new Command("ipfs").description("IPFS operations").addCommand(
339
989
  }
340
990
  })
341
991
  );
992
+ var LocalRegistryServer = class {
993
+ constructor(config) {
994
+ this.server = null;
995
+ this.isRunning = false;
996
+ this.config = config;
997
+ this.dataStore = new LocalDataStore(config);
998
+ this.artifactServer = new LocalArtifactServer(config, this.dataStore);
999
+ }
1000
+ async start(port) {
1001
+ if (this.isRunning) {
1002
+ throw new Error("Local registry is already running");
1003
+ }
1004
+ const serverPort = port || this.config.getPort();
1005
+ const host = this.config.getHost();
1006
+ this.config.ensureDirectories();
1007
+ this.server = fastify({
1008
+ logger: {
1009
+ level: "info"
1010
+ }
1011
+ });
1012
+ await this.server.register(cors, {
1013
+ origin: true,
1014
+ credentials: true
1015
+ });
1016
+ await this.registerRoutes();
1017
+ await this.server.listen({ port: serverPort, host });
1018
+ this.isRunning = true;
1019
+ console.log(
1020
+ `Local registry server started on http://${host}:${serverPort}`
1021
+ );
1022
+ }
1023
+ async stop() {
1024
+ if (!this.server || !this.isRunning) {
1025
+ throw new Error("Local registry is not running");
1026
+ }
1027
+ await this.server.close();
1028
+ this.isRunning = false;
1029
+ this.server = null;
1030
+ }
1031
+ async getStatus() {
1032
+ const stats = this.dataStore.getStats();
1033
+ const artifactStats = this.artifactServer.getArtifactStats();
1034
+ return {
1035
+ running: this.isRunning,
1036
+ url: `http://${this.config.getHost()}:${this.config.getPort()}`,
1037
+ dataDir: this.config.getDataDir(),
1038
+ appsCount: stats.publishedApps,
1039
+ artifactsCount: artifactStats.totalFiles
1040
+ };
1041
+ }
1042
+ async reset() {
1043
+ this.dataStore.reset();
1044
+ const artifactsDir = this.config.getArtifactsDir();
1045
+ if (fs3.existsSync(artifactsDir)) {
1046
+ fs3.rmSync(artifactsDir, { recursive: true, force: true });
1047
+ }
1048
+ }
1049
+ async backup(outputPath) {
1050
+ return await this.dataStore.backup(outputPath);
1051
+ }
1052
+ async restore(backupPath) {
1053
+ await this.dataStore.restore(backupPath);
1054
+ }
1055
+ async seed() {
1056
+ await this.dataStore.seed();
1057
+ }
1058
+ async registerRoutes() {
1059
+ if (!this.server) {
1060
+ throw new Error("Server not initialized");
1061
+ }
1062
+ this.server.get("/healthz", async () => {
1063
+ return { status: "ok" };
1064
+ });
1065
+ this.server.get("/stats", async () => {
1066
+ const stats = this.dataStore.getStats();
1067
+ const artifactStats = this.artifactServer.getArtifactStats();
1068
+ return {
1069
+ publishedApps: stats.publishedApps,
1070
+ totalVersions: stats.totalVersions,
1071
+ totalArtifacts: artifactStats.totalFiles,
1072
+ totalSize: artifactStats.totalSize
1073
+ };
1074
+ });
1075
+ this.server.get("/apps", async (request) => {
1076
+ const query = request.query;
1077
+ return this.dataStore.getApps(query);
1078
+ });
1079
+ this.server.get("/apps/:appId", async (request) => {
1080
+ const { appId } = request.params;
1081
+ return this.dataStore.getAppVersions(appId);
1082
+ });
1083
+ this.server.get("/apps/:appId/:semver", async (request) => {
1084
+ const { appId, semver } = request.params;
1085
+ const manifest = this.dataStore.getManifest(appId, semver);
1086
+ if (!manifest) {
1087
+ return {
1088
+ statusCode: 404,
1089
+ error: "Not Found",
1090
+ message: "Manifest not found"
1091
+ };
1092
+ }
1093
+ return this.artifactServer.updateManifestArtifacts(manifest);
1094
+ });
1095
+ this.server.post("/apps", async (request) => {
1096
+ const manifest = request.body;
1097
+ if (!this.validateManifest(manifest)) {
1098
+ return {
1099
+ statusCode: 400,
1100
+ error: "Bad Request",
1101
+ message: "Invalid manifest structure"
1102
+ };
1103
+ }
1104
+ const processedManifest = await this.processManifestArtifacts(manifest);
1105
+ const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
1106
+ this.dataStore.setManifest(manifestKey, processedManifest);
1107
+ const appKey = manifest.app.app_id;
1108
+ const appSummary = {
1109
+ name: manifest.app.name,
1110
+ developer_pubkey: manifest.app.developer_pubkey,
1111
+ latest_version: manifest.version.semver,
1112
+ latest_cid: manifest.artifacts[0]?.cid || "",
1113
+ alias: manifest.app.name
1114
+ // Use name as alias
1115
+ };
1116
+ this.dataStore.setApp(appKey, appSummary);
1117
+ return {
1118
+ success: true,
1119
+ message: "App version registered successfully",
1120
+ manifest_key: manifestKey
1121
+ };
1122
+ });
1123
+ this.server.get(
1124
+ "/artifacts/:appId/:version/:filename",
1125
+ async (request, reply) => {
1126
+ const { appId, version, filename } = request.params;
1127
+ try {
1128
+ const artifactData = await this.artifactServer.serveArtifact(
1129
+ appId,
1130
+ version,
1131
+ filename
1132
+ );
1133
+ reply.header("Content-Type", "application/octet-stream");
1134
+ reply.header(
1135
+ "Content-Disposition",
1136
+ `attachment; filename="${filename}"`
1137
+ );
1138
+ return artifactData;
1139
+ } catch {
1140
+ return {
1141
+ statusCode: 404,
1142
+ error: "Not Found",
1143
+ message: "Artifact not found"
1144
+ };
1145
+ }
1146
+ }
1147
+ );
1148
+ this.server.get("/artifacts/:hash", async (request, reply) => {
1149
+ const { hash } = request.params;
1150
+ try {
1151
+ const artifactData = await this.artifactServer.serveByHash(hash);
1152
+ reply.header("Content-Type", "application/octet-stream");
1153
+ return artifactData;
1154
+ } catch {
1155
+ return {
1156
+ statusCode: 404,
1157
+ error: "Not Found",
1158
+ message: "Artifact not found"
1159
+ };
1160
+ }
1161
+ });
1162
+ this.server.get("/local/status", async () => {
1163
+ return await this.getStatus();
1164
+ });
1165
+ this.server.post("/local/reset", async () => {
1166
+ await this.reset();
1167
+ return { message: "Local registry data reset successfully" };
1168
+ });
1169
+ this.server.get("/local/backup", async (request) => {
1170
+ const query = request.query;
1171
+ const backupPath = await this.backup(query.output);
1172
+ return { backupPath };
1173
+ });
1174
+ this.server.post("/local/restore", async (request) => {
1175
+ const { backupPath } = request.body;
1176
+ await this.restore(backupPath);
1177
+ return { message: "Data restored successfully" };
1178
+ });
1179
+ this.server.post("/local/seed", async () => {
1180
+ await this.seed();
1181
+ return { message: "Sample data seeded successfully" };
1182
+ });
1183
+ this.server.post("/v1/apps", async (request, reply) => {
1184
+ const manifest = request.body;
1185
+ if (!manifest.manifest_version || !manifest.id || !manifest.name || !manifest.version) {
1186
+ return reply.code(400).send({
1187
+ error: "invalid_schema",
1188
+ details: "Missing required fields"
1189
+ });
1190
+ }
1191
+ try {
1192
+ const processedManifest = await this.processManifestArtifacts(manifest);
1193
+ const manifestKey = `${manifest.id}/${manifest.version}`;
1194
+ this.dataStore.setManifest(manifestKey, processedManifest);
1195
+ const appKey = manifest.id;
1196
+ const appSummary = {
1197
+ id: manifest.id,
1198
+ name: manifest.name,
1199
+ developer_pubkey: "local-dev-key",
1200
+ latest_version: manifest.version,
1201
+ latest_cid: manifest.artifact?.digest || ""
1202
+ };
1203
+ this.dataStore.setApp(appKey, appSummary);
1204
+ return reply.code(201).send({
1205
+ id: manifest.id,
1206
+ version: manifest.version,
1207
+ canonical_uri: `/v1/apps/${manifest.id}/${manifest.version}`
1208
+ });
1209
+ } catch {
1210
+ return reply.code(409).send({
1211
+ error: "already_exists",
1212
+ details: `${manifest.id}@${manifest.version}`
1213
+ });
1214
+ }
1215
+ });
1216
+ this.server.get("/v1/apps/:id", async (request, reply) => {
1217
+ const { id } = request.params;
1218
+ const versions = this.dataStore.getAppVersions(id);
1219
+ if (!versions || versions.length === 0) {
1220
+ return reply.code(404).send({ error: "not_found", message: "App not found" });
1221
+ }
1222
+ return {
1223
+ id,
1224
+ versions: versions.map((v) => v.semver)
1225
+ };
1226
+ });
1227
+ this.server.get("/v1/apps/:id/:version", async (request, reply) => {
1228
+ const { id, version } = request.params;
1229
+ const { canonical } = request.query;
1230
+ const oldManifest = this.dataStore.getManifest(id, version);
1231
+ if (!oldManifest) {
1232
+ return reply.code(404).send({ error: "not_found", message: "Manifest not found" });
1233
+ }
1234
+ const v1Manifest = {
1235
+ manifest_version: oldManifest.manifest_version,
1236
+ id: oldManifest.app.app_id,
1237
+ name: oldManifest.app.name,
1238
+ version: oldManifest.version.semver,
1239
+ chains: oldManifest.supported_chains,
1240
+ artifact: oldManifest.artifacts[0] ? {
1241
+ type: oldManifest.artifacts[0].type,
1242
+ target: oldManifest.artifacts[0].target,
1243
+ digest: oldManifest.artifacts[0].cid || `sha256:${"0".repeat(64)}`,
1244
+ uri: oldManifest.artifacts[0].path || oldManifest.artifacts[0].mirrors?.[0] || "https://example.com/artifact"
1245
+ } : {
1246
+ type: "wasm",
1247
+ target: "node",
1248
+ digest: `sha256:${"0".repeat(64)}`,
1249
+ uri: "https://example.com/artifact"
1250
+ },
1251
+ _warnings: []
1252
+ };
1253
+ if (canonical === "true") {
1254
+ const canonicalJCS = JSON.stringify(
1255
+ v1Manifest,
1256
+ Object.keys(v1Manifest).sort()
1257
+ );
1258
+ return {
1259
+ canonical_jcs: Buffer.from(canonicalJCS, "utf8").toString("base64")
1260
+ };
1261
+ }
1262
+ return v1Manifest;
1263
+ });
1264
+ this.server.get("/v1/search", async (request, reply) => {
1265
+ const { q } = request.query;
1266
+ if (!q) {
1267
+ return reply.code(400).send({
1268
+ error: "bad_request",
1269
+ message: 'Query parameter "q" is required'
1270
+ });
1271
+ }
1272
+ const apps = this.dataStore.getApps({});
1273
+ const results = apps.filter(
1274
+ (app) => app.name.toLowerCase().includes(q.toLowerCase()) || app.id?.toLowerCase().includes(q.toLowerCase())
1275
+ );
1276
+ return results.map((app) => ({
1277
+ id: app.id || app.name,
1278
+ versions: [app.latest_version]
1279
+ }));
1280
+ });
1281
+ this.server.post("/v1/resolve", async (request, reply) => {
1282
+ const { root } = request.body;
1283
+ if (!root || !root.id || !root.version) {
1284
+ return reply.code(400).send({
1285
+ error: "bad_request",
1286
+ message: "Root app ID and version are required"
1287
+ });
1288
+ }
1289
+ return {
1290
+ plan: [{ action: "install", id: root.id, version: root.version }],
1291
+ satisfies: [],
1292
+ missing: []
1293
+ };
1294
+ });
1295
+ }
1296
+ validateManifest(manifest) {
1297
+ return !!(manifest.manifest_version && manifest.app && manifest.app.app_id && manifest.app.name && manifest.app.developer_pubkey && manifest.version && manifest.version.semver && manifest.artifacts && manifest.artifacts.length > 0);
1298
+ }
1299
+ async processManifestArtifacts(manifest) {
1300
+ const processedManifest = { ...manifest };
1301
+ if (manifest.artifact && manifest.artifact.uri) {
1302
+ const artifact = manifest.artifact;
1303
+ if (artifact.uri.startsWith("file://")) {
1304
+ const filePath = artifact.uri.replace("file://", "");
1305
+ if (fs3.existsSync(filePath)) {
1306
+ const filename = path.basename(filePath);
1307
+ const appId = manifest.id;
1308
+ try {
1309
+ await this.artifactServer.copyArtifactToLocal(
1310
+ filePath,
1311
+ appId,
1312
+ manifest.version,
1313
+ filename
1314
+ );
1315
+ processedManifest.artifact = {
1316
+ ...artifact,
1317
+ uri: this.artifactServer.getArtifactUrl(
1318
+ appId,
1319
+ manifest.version,
1320
+ filename
1321
+ )
1322
+ };
1323
+ } catch (error) {
1324
+ console.warn(`Failed to copy artifact ${filePath}:`, error);
1325
+ }
1326
+ }
1327
+ }
1328
+ }
1329
+ return processedManifest;
1330
+ }
1331
+ };
1332
+ var localCommand = new Command("local").description(
1333
+ "Manage local registry for development"
1334
+ );
1335
+ localCommand.addCommand(
1336
+ new Command("start").description("Start local registry server").option("-p, --port <port>", "Port to run the server on", "8082").option("-h, --host <host>", "Host to bind the server to", "localhost").action(async (options) => {
1337
+ const spinner2 = ora6("Starting local registry...").start();
1338
+ try {
1339
+ const config = new LocalConfig();
1340
+ const server = new LocalRegistryServer(config);
1341
+ await server.start(parseInt(options.port));
1342
+ spinner2.succeed(
1343
+ `Local registry started on http://${options.host}:${options.port}`
1344
+ );
1345
+ console.log(chalk5.blue("\n\u{1F4F1} Local Registry Status:"));
1346
+ console.log(
1347
+ chalk5.green(`\u2705 Server: http://${options.host}:${options.port}`)
1348
+ );
1349
+ console.log(chalk5.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
1350
+ console.log(
1351
+ chalk5.green(
1352
+ `\u{1F4CB} Health: http://${options.host}:${options.port}/healthz`
1353
+ )
1354
+ );
1355
+ console.log(
1356
+ chalk5.green(`\u{1F4CA} Stats: http://${options.host}:${options.port}/stats`)
1357
+ );
1358
+ console.log(
1359
+ chalk5.blue(
1360
+ "\n\u{1F4A1} Use --local flag with other commands to use local registry"
1361
+ )
1362
+ );
1363
+ } catch (error) {
1364
+ spinner2.fail("Failed to start local registry");
1365
+ if (error instanceof Error) {
1366
+ console.error(chalk5.red(`Error: ${error.message}`));
1367
+ }
1368
+ process.exit(1);
1369
+ }
1370
+ })
1371
+ );
1372
+ localCommand.addCommand(
1373
+ new Command("stop").description("Stop local registry server").action(async () => {
1374
+ const spinner2 = ora6("Stopping local registry...").start();
1375
+ try {
1376
+ const config = new LocalConfig();
1377
+ const server = new LocalRegistryServer(config);
1378
+ await server.stop();
1379
+ spinner2.succeed("Local registry stopped");
1380
+ } catch (error) {
1381
+ spinner2.fail("Failed to stop local registry");
1382
+ if (error instanceof Error) {
1383
+ console.error(chalk5.red(`Error: ${error.message}`));
1384
+ }
1385
+ process.exit(1);
1386
+ }
1387
+ })
1388
+ );
1389
+ localCommand.addCommand(
1390
+ new Command("status").description("Check local registry status").action(async () => {
1391
+ const spinner2 = ora6("Checking local registry status...").start();
1392
+ try {
1393
+ const config = new LocalConfig();
1394
+ const server = new LocalRegistryServer(config);
1395
+ const status = await server.getStatus();
1396
+ if (status.running) {
1397
+ spinner2.succeed("Local registry is running");
1398
+ console.log(chalk5.green(`\u2705 Server: ${status.url}`));
1399
+ console.log(chalk5.green(`\u{1F4C1} Data: ${status.dataDir}`));
1400
+ console.log(chalk5.green(`\u{1F4CA} Apps: ${status.appsCount} applications`));
1401
+ console.log(
1402
+ chalk5.green(`\u{1F4E6} Artifacts: ${status.artifactsCount} artifacts`)
1403
+ );
1404
+ } else {
1405
+ spinner2.warn("Local registry is not running");
1406
+ console.log(
1407
+ chalk5.yellow(
1408
+ '\u{1F4A1} Run "calimero-registry local start" to start the local registry'
1409
+ )
1410
+ );
1411
+ }
1412
+ } catch (error) {
1413
+ spinner2.fail("Failed to check local registry status");
1414
+ if (error instanceof Error) {
1415
+ console.error(chalk5.red(`Error: ${error.message}`));
1416
+ }
1417
+ process.exit(1);
1418
+ }
1419
+ })
1420
+ );
1421
+ localCommand.addCommand(
1422
+ new Command("reset").description("Reset local registry data").option("-f, --force", "Force reset without confirmation").action(async (options) => {
1423
+ if (!options.force) {
1424
+ console.log(
1425
+ chalk5.yellow("\u26A0\uFE0F This will delete all local registry data!")
1426
+ );
1427
+ console.log(chalk5.yellow(" Use --force flag to confirm"));
1428
+ return;
1429
+ }
1430
+ const spinner2 = ora6("Resetting local registry data...").start();
1431
+ try {
1432
+ const config = new LocalConfig();
1433
+ const server = new LocalRegistryServer(config);
1434
+ await server.reset();
1435
+ spinner2.succeed("Local registry data reset");
1436
+ console.log(chalk5.green("\u2705 All local data has been cleared"));
1437
+ } catch (error) {
1438
+ spinner2.fail("Failed to reset local registry data");
1439
+ if (error instanceof Error) {
1440
+ console.error(chalk5.red(`Error: ${error.message}`));
1441
+ }
1442
+ process.exit(1);
1443
+ }
1444
+ })
1445
+ );
1446
+ localCommand.addCommand(
1447
+ new Command("backup").description("Backup local registry data").option("-o, --output <file>", "Output file path").action(async (options) => {
1448
+ const spinner2 = ora6("Creating backup...").start();
1449
+ try {
1450
+ const config = new LocalConfig();
1451
+ const server = new LocalRegistryServer(config);
1452
+ const backupPath = await server.backup(options.output);
1453
+ spinner2.succeed("Backup created successfully");
1454
+ console.log(chalk5.green(`\u{1F4E6} Backup saved to: ${backupPath}`));
1455
+ } catch (error) {
1456
+ spinner2.fail("Failed to create backup");
1457
+ if (error instanceof Error) {
1458
+ console.error(chalk5.red(`Error: ${error.message}`));
1459
+ }
1460
+ process.exit(1);
1461
+ }
1462
+ })
1463
+ );
1464
+ localCommand.addCommand(
1465
+ new Command("restore").description("Restore local registry data from backup").argument("<backup-file>", "Path to backup file").action(async (backupFile) => {
1466
+ const spinner2 = ora6("Restoring from backup...").start();
1467
+ try {
1468
+ if (!fs3.existsSync(backupFile)) {
1469
+ spinner2.fail("Backup file not found");
1470
+ console.error(chalk5.red(`File not found: ${backupFile}`));
1471
+ process.exit(1);
1472
+ }
1473
+ const config = new LocalConfig();
1474
+ const server = new LocalRegistryServer(config);
1475
+ await server.restore(backupFile);
1476
+ spinner2.succeed("Data restored successfully");
1477
+ console.log(chalk5.green(`\u2705 Restored from: ${backupFile}`));
1478
+ } catch (error) {
1479
+ spinner2.fail("Failed to restore from backup");
1480
+ if (error instanceof Error) {
1481
+ console.error(chalk5.red(`Error: ${error.message}`));
1482
+ }
1483
+ process.exit(1);
1484
+ }
1485
+ })
1486
+ );
1487
+ localCommand.addCommand(
1488
+ new Command("seed").description("Seed local registry with sample data").action(async () => {
1489
+ const spinner2 = ora6("Seeding local registry with sample data...").start();
1490
+ try {
1491
+ const config = new LocalConfig();
1492
+ const server = new LocalRegistryServer(config);
1493
+ await server.seed();
1494
+ spinner2.succeed("Sample data seeded successfully");
1495
+ console.log(
1496
+ chalk5.green("\u2705 Local registry populated with sample applications")
1497
+ );
1498
+ console.log(
1499
+ chalk5.blue(
1500
+ '\u{1F4A1} Run "calimero-registry apps list --local" to see the sample apps'
1501
+ )
1502
+ );
1503
+ } catch (error) {
1504
+ spinner2.fail("Failed to seed sample data");
1505
+ if (error instanceof Error) {
1506
+ console.error(chalk5.red(`Error: ${error.message}`));
1507
+ }
1508
+ process.exit(1);
1509
+ }
1510
+ })
1511
+ );
1512
+ var v1Command = new Command("v1").description("V1 API commands for Calimero SSApp Registry").addCommand(createPushCommand()).addCommand(createGetCommand()).addCommand(createListCommand()).addCommand(createResolveCommand()).addCommand(createVerifyCommand());
1513
+ function createPushCommand() {
1514
+ return new Command("push").description("Submit a v1 manifest to the registry").argument("<manifest-file>", "Path to manifest JSON file").option("--local", "Use local registry").action(
1515
+ async (manifestFile, options = {}, command) => {
1516
+ try {
1517
+ const globalOpts = command.parent?.parent?.opts();
1518
+ const useLocal = globalOpts?.local || options.local || false;
1519
+ if (!fs3.existsSync(manifestFile)) {
1520
+ console.error(`\u274C Manifest file not found: ${manifestFile}`);
1521
+ process.exit(1);
1522
+ }
1523
+ const manifestContent = fs3.readFileSync(manifestFile, "utf8");
1524
+ const manifest = JSON.parse(manifestContent);
1525
+ if (manifest.manifest_version !== "1.0") {
1526
+ console.error('\u274C Invalid manifest version. Must be "1.0"');
1527
+ process.exit(1);
1528
+ }
1529
+ if (!manifest.id || !manifest.name || !manifest.version) {
1530
+ console.error("\u274C Missing required fields: id, name, version");
1531
+ process.exit(1);
1532
+ }
1533
+ const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
1534
+ console.log(
1535
+ `\u{1F4E4} Submitting manifest: ${manifest.id}@${manifest.version}`
1536
+ );
1537
+ console.log(` Name: ${manifest.name}`);
1538
+ console.log(` Chains: ${manifest.chains?.join(", ")}`);
1539
+ console.log(
1540
+ ` Provides: ${manifest.provides?.join(", ") || "none"}`
1541
+ );
1542
+ console.log(
1543
+ ` Requires: ${manifest.requires?.join(", ") || "none"}`
1544
+ );
1545
+ const response = await fetch(`${baseUrl}/v1/apps`, {
1546
+ method: "POST",
1547
+ headers: { "Content-Type": "application/json" },
1548
+ body: JSON.stringify(manifest)
1549
+ });
1550
+ if (!response.ok) {
1551
+ const error = await response.json();
1552
+ throw new Error(`${error.error}: ${error.details}`);
1553
+ }
1554
+ const result = await response.json();
1555
+ console.log("\u2705 Manifest submitted successfully!");
1556
+ console.log(` ID: ${result.id}`);
1557
+ console.log(` Version: ${result.version}`);
1558
+ console.log(` URI: ${result.canonical_uri}`);
1559
+ } catch (error) {
1560
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1561
+ console.error("\u274C Failed to submit manifest:", errorMessage);
1562
+ process.exit(1);
1563
+ }
1564
+ }
1565
+ );
1566
+ }
1567
+ function createGetCommand() {
1568
+ return new Command("get").description("Get manifest from registry").argument("<app-id>", "Application ID").argument("[version]", "Specific version (optional)").option("--local", "Use local registry").option("--canonical", "Return canonical JCS format").action(
1569
+ async (appId, version, options = {}, command) => {
1570
+ try {
1571
+ const globalOpts = command.parent?.parent?.opts();
1572
+ const useLocal = globalOpts?.local || options.local || false;
1573
+ const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
1574
+ if (version) {
1575
+ console.log(`\u{1F4E5} Getting manifest: ${appId}@${version}`);
1576
+ const url = options.canonical ? `/v1/apps/${appId}/${version}?canonical=true` : `/v1/apps/${appId}/${version}`;
1577
+ const response = await fetch(`${baseUrl}${url}`);
1578
+ if (!response.ok) {
1579
+ if (response.status === 404) {
1580
+ console.error(`\u274C Manifest not found: ${appId}@${version}`);
1581
+ } else {
1582
+ console.error(
1583
+ `\u274C Error: ${response.status} ${response.statusText}`
1584
+ );
1585
+ }
1586
+ process.exit(1);
1587
+ }
1588
+ const manifest = await response.json();
1589
+ console.log(JSON.stringify(manifest, null, 2));
1590
+ } else {
1591
+ console.log(`\u{1F4E5} Getting versions for: ${appId}`);
1592
+ const response = await fetch(`${baseUrl}/v1/apps/${appId}`);
1593
+ if (!response.ok) {
1594
+ if (response.status === 404) {
1595
+ console.error(`\u274C App not found: ${appId}`);
1596
+ } else {
1597
+ console.error(
1598
+ `\u274C Error: ${response.status} ${response.statusText}`
1599
+ );
1600
+ }
1601
+ process.exit(1);
1602
+ }
1603
+ const result = await response.json();
1604
+ console.log(`\u{1F4F1} App: ${result.id}`);
1605
+ console.log(`\u{1F4CB} Versions: ${result.versions.join(", ")}`);
1606
+ }
1607
+ } catch (error) {
1608
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1609
+ console.error("\u274C Failed to get manifest:", errorMessage);
1610
+ process.exit(1);
1611
+ }
1612
+ }
1613
+ );
1614
+ }
1615
+ function createListCommand() {
1616
+ return new Command("ls").description("List applications in registry").option("--local", "Use local registry").option("--search <query>", "Search query").action(
1617
+ async (options = {}, command) => {
1618
+ try {
1619
+ const globalOpts = command.parent?.parent?.opts();
1620
+ const useLocal = globalOpts?.local || options.local || false;
1621
+ const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
1622
+ if (options.search) {
1623
+ console.log(`\u{1F50D} Searching for: ${options.search}`);
1624
+ const response = await fetch(
1625
+ `${baseUrl}/v1/search?q=${encodeURIComponent(options.search)}`
1626
+ );
1627
+ if (!response.ok) {
1628
+ console.error(
1629
+ `\u274C Error: ${response.status} ${response.statusText}`
1630
+ );
1631
+ process.exit(1);
1632
+ }
1633
+ const results = await response.json();
1634
+ if (results.length === 0) {
1635
+ console.log("No results found");
1636
+ return;
1637
+ }
1638
+ console.log(`Found ${results.length} result(s):`);
1639
+ results.forEach((result) => {
1640
+ console.log(
1641
+ ` \u{1F4F1} ${result.id}@${result.versions?.[0] || "unknown"}`
1642
+ );
1643
+ if (result.provides?.length > 0) {
1644
+ console.log(` Provides: ${result.provides.join(", ")}`);
1645
+ }
1646
+ if (result.requires?.length > 0) {
1647
+ console.log(` Requires: ${result.requires.join(", ")}`);
1648
+ }
1649
+ });
1650
+ } else {
1651
+ console.log("\u{1F4CB} Listing all applications...");
1652
+ console.log(
1653
+ "Note: Use --search <query> to search for specific apps"
1654
+ );
1655
+ }
1656
+ } catch (error) {
1657
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1658
+ console.error("\u274C Failed to list applications:", errorMessage);
1659
+ process.exit(1);
1660
+ }
1661
+ }
1662
+ );
1663
+ }
1664
+ function createResolveCommand() {
1665
+ return new Command("resolve").description("Resolve dependencies for an application").argument("<app-id>", "Application ID").argument("<version>", "Application version").option("--local", "Use local registry").action(
1666
+ async (appId, version, options = {}, command) => {
1667
+ try {
1668
+ const globalOpts = command.parent?.parent?.opts();
1669
+ const useLocal = globalOpts?.local || options.local || false;
1670
+ const baseUrl = useLocal ? "http://localhost:8082" : "http://localhost:8080";
1671
+ console.log(`\u{1F50D} Resolving dependencies for: ${appId}@${version}`);
1672
+ const resolveRequest = {
1673
+ root: { id: appId, version },
1674
+ installed: []
1675
+ // Could be extended to support pre-installed apps
1676
+ };
1677
+ const response = await fetch(`${baseUrl}/v1/resolve`, {
1678
+ method: "POST",
1679
+ headers: { "Content-Type": "application/json" },
1680
+ body: JSON.stringify(resolveRequest)
1681
+ });
1682
+ if (!response.ok) {
1683
+ const error = await response.json();
1684
+ console.error(`\u274C Resolution failed: ${error.error}`);
1685
+ console.error(` Details: ${error.details}`);
1686
+ process.exit(1);
1687
+ }
1688
+ const result = await response.json();
1689
+ console.log("\u2705 Dependencies resolved successfully!");
1690
+ console.log(`\u{1F4CB} Installation plan:`);
1691
+ result.plan.forEach(
1692
+ (item) => {
1693
+ console.log(` ${item.action}: ${item.id}@${item.version}`);
1694
+ }
1695
+ );
1696
+ if (result.satisfies?.length > 0) {
1697
+ console.log(`\u2705 Satisfies: ${result.satisfies.join(", ")}`);
1698
+ }
1699
+ if (result.missing?.length > 0) {
1700
+ console.log(`\u274C Missing: ${result.missing.join(", ")}`);
1701
+ }
1702
+ } catch (error) {
1703
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1704
+ console.error("\u274C Failed to resolve dependencies:", errorMessage);
1705
+ process.exit(1);
1706
+ }
1707
+ }
1708
+ );
1709
+ }
1710
+ function createVerifyCommand() {
1711
+ return new Command("verify").description("Verify manifest signature and integrity").argument("<manifest-file>", "Path to manifest JSON file").action(async (manifestFile) => {
1712
+ try {
1713
+ if (!fs3.existsSync(manifestFile)) {
1714
+ console.error(`\u274C Manifest file not found: ${manifestFile}`);
1715
+ process.exit(1);
1716
+ }
1717
+ const manifestContent = fs3.readFileSync(manifestFile, "utf8");
1718
+ const manifest = JSON.parse(manifestContent);
1719
+ console.log(
1720
+ `\u{1F50D} Verifying manifest: ${manifest.id}@${manifest.version}`
1721
+ );
1722
+ if (manifest.manifest_version !== "1.0") {
1723
+ console.error("\u274C Invalid manifest version");
1724
+ process.exit(1);
1725
+ }
1726
+ const requiredFields = ["id", "name", "version", "chains", "artifact"];
1727
+ const missingFields = requiredFields.filter(
1728
+ (field) => !manifest[field]
1729
+ );
1730
+ if (missingFields.length > 0) {
1731
+ console.error(
1732
+ `\u274C Missing required fields: ${missingFields.join(", ")}`
1733
+ );
1734
+ process.exit(1);
1735
+ }
1736
+ if (manifest.artifact.type !== "wasm" || manifest.artifact.target !== "node") {
1737
+ console.error("\u274C Invalid artifact type or target");
1738
+ process.exit(1);
1739
+ }
1740
+ if (!manifest.artifact.digest.match(/^sha256:[0-9a-f]{64}$/)) {
1741
+ console.error("\u274C Invalid artifact digest format");
1742
+ process.exit(1);
1743
+ }
1744
+ if (!manifest.artifact.uri.match(/^(https:\/\/|ipfs:\/\/)/)) {
1745
+ console.error("\u274C Invalid artifact URI format");
1746
+ process.exit(1);
1747
+ }
1748
+ if (manifest.signature) {
1749
+ console.log("\u{1F510} Verifying signature...");
1750
+ console.log("\u26A0\uFE0F Signature verification not implemented in CLI yet");
1751
+ }
1752
+ console.log("\u2705 Manifest verification passed!");
1753
+ console.log(` ID: ${manifest.id}`);
1754
+ console.log(` Name: ${manifest.name}`);
1755
+ console.log(` Version: ${manifest.version}`);
1756
+ console.log(` Chains: ${manifest.chains.join(", ")}`);
1757
+ console.log(` Artifact: ${manifest.artifact.uri}`);
1758
+ if (manifest.provides?.length > 0) {
1759
+ console.log(` Provides: ${manifest.provides.join(", ")}`);
1760
+ }
1761
+ if (manifest.requires?.length > 0) {
1762
+ console.log(` Requires: ${manifest.requires.join(", ")}`);
1763
+ }
1764
+ } catch (error) {
1765
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1766
+ console.error("\u274C Failed to verify manifest:", errorMessage);
1767
+ process.exit(1);
1768
+ }
1769
+ });
1770
+ }
342
1771
 
343
1772
  // src/index.ts
344
1773
  var program = new Command();
@@ -351,19 +1780,22 @@ program.option(
351
1780
  "Request timeout in milliseconds",
352
1781
  "10000"
353
1782
  );
1783
+ program.option("--local", "Use local registry instead of remote API");
354
1784
  program.addCommand(appsCommand);
355
1785
  program.addCommand(developersCommand);
356
1786
  program.addCommand(attestationsCommand);
357
1787
  program.addCommand(healthCommand);
358
1788
  program.addCommand(ipfsCommand);
1789
+ program.addCommand(localCommand);
1790
+ program.addCommand(v1Command);
359
1791
  program.exitOverride();
360
1792
  try {
361
1793
  program.parse();
362
1794
  } catch (err) {
363
1795
  if (err instanceof Error) {
364
- console.error(chalk.red("Error:"), err.message);
1796
+ console.error(chalk5.red("Error:"), err.message);
365
1797
  } else {
366
- console.error(chalk.red("Unknown error occurred"));
1798
+ console.error(chalk5.red("Unknown error occurred"));
367
1799
  }
368
1800
  process.exit(1);
369
1801
  }