@calimero-network/registry-cli 1.2.0 → 1.4.2

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
@@ -5,40 +5,40 @@ import ora6 from 'ora';
5
5
  import { table } from 'table';
6
6
  import { SSAppRegistryClient } from '@calimero-network/registry-client';
7
7
  import fs3, { existsSync, writeFileSync } from 'fs';
8
- import path from 'path';
8
+ import path6 from 'path';
9
9
  import os from 'os';
10
10
  import crypto from 'crypto';
11
11
  import fastify from 'fastify';
12
12
  import cors from '@fastify/cors';
13
- import { Buffer } from 'buffer';
14
- import fetch from 'node-fetch';
13
+ import * as tar from 'tar';
15
14
 
16
15
  var LocalConfig = class {
17
16
  constructor() {
18
- this.configPath = path.join(
17
+ this.configPath = path6.join(
19
18
  os.homedir(),
20
19
  ".calimero-registry",
21
20
  "config.json"
22
21
  );
23
- this.dataDir = path.join(os.homedir(), ".calimero-registry");
22
+ this.dataDir = path6.join(os.homedir(), ".calimero-registry");
24
23
  this.config = this.loadConfig();
25
24
  }
26
25
  loadConfig() {
27
26
  const defaultConfig = {
28
27
  server: {
29
28
  port: 8082,
30
- host: "localhost"
29
+ host: "0.0.0.0",
30
+ publicHost: "host.docker.internal"
31
31
  },
32
32
  data: {
33
- dir: path.join(os.homedir(), ".calimero-registry", "data"),
34
- artifactsDir: path.join(
33
+ dir: path6.join(os.homedir(), ".calimero-registry", "data"),
34
+ artifactsDir: path6.join(
35
35
  os.homedir(),
36
36
  ".calimero-registry",
37
37
  "artifacts"
38
38
  )
39
39
  },
40
40
  artifacts: {
41
- storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
41
+ storageDir: path6.join(os.homedir(), ".calimero-registry", "artifacts"),
42
42
  serveLocal: true,
43
43
  copyArtifacts: true,
44
44
  maxFileSize: "100MB",
@@ -50,7 +50,20 @@ var LocalConfig = class {
50
50
  const existingConfig = JSON.parse(
51
51
  fs3.readFileSync(this.configPath, "utf8")
52
52
  );
53
- return { ...defaultConfig, ...existingConfig };
53
+ return {
54
+ server: {
55
+ ...defaultConfig.server,
56
+ ...existingConfig.server || {}
57
+ },
58
+ data: {
59
+ ...defaultConfig.data,
60
+ ...existingConfig.data || {}
61
+ },
62
+ artifacts: {
63
+ ...defaultConfig.artifacts,
64
+ ...existingConfig.artifacts || {}
65
+ }
66
+ };
54
67
  } catch {
55
68
  console.warn("Failed to load existing config, using defaults");
56
69
  }
@@ -58,7 +71,7 @@ var LocalConfig = class {
58
71
  return defaultConfig;
59
72
  }
60
73
  saveConfig() {
61
- const configDir = path.dirname(this.configPath);
74
+ const configDir = path6.dirname(this.configPath);
62
75
  if (!fs3.existsSync(configDir)) {
63
76
  fs3.mkdirSync(configDir, { recursive: true });
64
77
  }
@@ -79,13 +92,20 @@ var LocalConfig = class {
79
92
  this.config.server.host = host;
80
93
  this.saveConfig();
81
94
  }
95
+ getPublicHost() {
96
+ return this.config.server.publicHost;
97
+ }
98
+ setPublicHost(host) {
99
+ this.config.server.publicHost = host;
100
+ this.saveConfig();
101
+ }
82
102
  // Data directory configuration
83
103
  getDataDir() {
84
104
  return this.config.data.dir;
85
105
  }
86
106
  setDataDir(dir) {
87
107
  this.config.data.dir = dir;
88
- this.config.artifacts.storageDir = path.join(dir, "artifacts");
108
+ this.config.artifacts.storageDir = path6.join(dir, "artifacts");
89
109
  this.saveConfig();
90
110
  }
91
111
  getArtifactsDir() {
@@ -109,8 +129,8 @@ var LocalConfig = class {
109
129
  const dirs = [
110
130
  this.getDataDir(),
111
131
  this.getArtifactsDir(),
112
- path.join(this.getDataDir(), "backups"),
113
- path.join(this.getDataDir(), "logs")
132
+ path6.join(this.getDataDir(), "backups"),
133
+ path6.join(this.getDataDir(), "logs")
114
134
  ];
115
135
  dirs.forEach((dir) => {
116
136
  if (!fs3.existsSync(dir)) {
@@ -133,15 +153,15 @@ var LocalConfig = class {
133
153
  host: "localhost"
134
154
  },
135
155
  data: {
136
- dir: path.join(os.homedir(), ".calimero-registry", "data"),
137
- artifactsDir: path.join(
156
+ dir: path6.join(os.homedir(), ".calimero-registry", "data"),
157
+ artifactsDir: path6.join(
138
158
  os.homedir(),
139
159
  ".calimero-registry",
140
160
  "artifacts"
141
161
  )
142
162
  },
143
163
  artifacts: {
144
- storageDir: path.join(os.homedir(), ".calimero-registry", "artifacts"),
164
+ storageDir: path6.join(os.homedir(), ".calimero-registry", "artifacts"),
145
165
  serveLocal: true,
146
166
  copyArtifacts: true,
147
167
  maxFileSize: "100MB",
@@ -154,12 +174,15 @@ var LocalConfig = class {
154
174
  var LocalDataStore = class {
155
175
  constructor(config) {
156
176
  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");
177
+ this.appsFile = path6.join(config.getDataDir(), "apps.json");
178
+ this.bundleManifestsFile = path6.join(
179
+ config.getDataDir(),
180
+ "bundle_manifests.json"
181
+ );
182
+ this.artifactsFile = path6.join(config.getDataDir(), "artifacts.json");
160
183
  this.data = {
161
184
  apps: /* @__PURE__ */ new Map(),
162
- manifests: /* @__PURE__ */ new Map(),
185
+ bundleManifests: /* @__PURE__ */ new Map(),
163
186
  artifacts: /* @__PURE__ */ new Map()
164
187
  };
165
188
  this.loadData();
@@ -170,11 +193,13 @@ var LocalDataStore = class {
170
193
  const appsData = JSON.parse(fs3.readFileSync(this.appsFile, "utf8"));
171
194
  this.data.apps = new Map(Object.entries(appsData));
172
195
  }
173
- if (fs3.existsSync(this.manifestsFile)) {
174
- const manifestsData = JSON.parse(
175
- fs3.readFileSync(this.manifestsFile, "utf8")
196
+ if (fs3.existsSync(this.bundleManifestsFile)) {
197
+ const bundleManifestsData = JSON.parse(
198
+ fs3.readFileSync(this.bundleManifestsFile, "utf8")
199
+ );
200
+ this.data.bundleManifests = new Map(
201
+ Object.entries(bundleManifestsData)
176
202
  );
177
- this.data.manifests = new Map(Object.entries(manifestsData));
178
203
  }
179
204
  if (fs3.existsSync(this.artifactsFile)) {
180
205
  const artifactsData = JSON.parse(
@@ -191,10 +216,10 @@ var LocalDataStore = class {
191
216
  this.config.ensureDirectories();
192
217
  const appsObj = Object.fromEntries(this.data.apps);
193
218
  fs3.writeFileSync(this.appsFile, JSON.stringify(appsObj, null, 2));
194
- const manifestsObj = Object.fromEntries(this.data.manifests);
219
+ const bundleManifestsObj = Object.fromEntries(this.data.bundleManifests);
195
220
  fs3.writeFileSync(
196
- this.manifestsFile,
197
- JSON.stringify(manifestsObj, null, 2)
221
+ this.bundleManifestsFile,
222
+ JSON.stringify(bundleManifestsObj, null, 2)
198
223
  );
199
224
  const artifactsObj = Object.fromEntries(this.data.artifacts);
200
225
  fs3.writeFileSync(
@@ -210,12 +235,12 @@ var LocalDataStore = class {
210
235
  // Apps management
211
236
  getApps(filters) {
212
237
  let apps = Array.from(this.data.apps.values());
213
- if (filters?.dev) {
214
- apps = apps.filter((app) => app.developer_pubkey === filters.dev);
215
- }
216
238
  if (filters?.name) {
217
239
  apps = apps.filter((app) => app.name.includes(filters.name));
218
240
  }
241
+ if (filters?.dev) {
242
+ apps = apps.filter((app) => app.developer_pubkey === filters.dev);
243
+ }
219
244
  return apps;
220
245
  }
221
246
  getApp(appKey) {
@@ -225,17 +250,19 @@ var LocalDataStore = class {
225
250
  this.data.apps.set(appKey, app);
226
251
  this.saveData();
227
252
  }
228
- // Versions management
229
- getAppVersions(appId) {
253
+ // Versions management (bundles only)
254
+ getAppVersions(packageId) {
230
255
  const versions = [];
231
- for (const [key, manifest] of this.data.manifests.entries()) {
232
- if (key.startsWith(`${appId}/`)) {
256
+ for (const [key, manifest] of this.data.bundleManifests.entries()) {
257
+ if (key.startsWith(`${packageId}/`)) {
233
258
  const semver = key.split("/").pop();
259
+ const digest = manifest.wasm?.hash || "";
234
260
  versions.push({
235
261
  semver,
236
- cid: manifest.artifacts[0]?.cid || "",
262
+ digest,
263
+ cid: digest,
264
+ // Compatibility
237
265
  yanked: false
238
- // TODO: Implement yanking
239
266
  });
240
267
  }
241
268
  }
@@ -243,13 +270,14 @@ var LocalDataStore = class {
243
270
  (a, b) => a.semver.localeCompare(b.semver, void 0, { numeric: true })
244
271
  );
245
272
  }
246
- // Manifest management
247
- getManifest(appId, semver) {
248
- const manifestKey = `${appId}/${semver}`;
249
- return this.data.manifests.get(manifestKey);
273
+ // Manifest management (V2 / Bundles)
274
+ getBundleManifest(pkg, version) {
275
+ const key = `${pkg}/${version}`;
276
+ return this.data.bundleManifests.get(key);
250
277
  }
251
- setManifest(manifestKey, manifest) {
252
- this.data.manifests.set(manifestKey, manifest);
278
+ setBundleManifest(pkg, version, manifest) {
279
+ const key = `${pkg}/${version}`;
280
+ this.data.bundleManifests.set(key, manifest);
253
281
  this.saveData();
254
282
  }
255
283
  // Artifact management
@@ -264,26 +292,26 @@ var LocalDataStore = class {
264
292
  getStats() {
265
293
  return {
266
294
  publishedApps: this.data.apps.size,
267
- totalVersions: this.data.manifests.size,
295
+ totalVersions: this.data.bundleManifests.size,
268
296
  totalArtifacts: this.data.artifacts.size
269
297
  };
270
298
  }
271
299
  // Backup and restore
272
300
  async backup(outputPath) {
273
301
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
274
- const backupPath = outputPath || path.join(
302
+ const backupPath = outputPath || path6.join(
275
303
  this.config.getDataDir(),
276
304
  "backups",
277
305
  `backup-${timestamp}.json`
278
306
  );
279
- const backupDir = path.dirname(backupPath);
307
+ const backupDir = path6.dirname(backupPath);
280
308
  if (!fs3.existsSync(backupDir)) {
281
309
  fs3.mkdirSync(backupDir, { recursive: true });
282
310
  }
283
311
  const backupData = {
284
312
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
285
313
  apps: Object.fromEntries(this.data.apps),
286
- manifests: Object.fromEntries(this.data.manifests),
314
+ bundleManifests: Object.fromEntries(this.data.bundleManifests),
287
315
  artifacts: Object.fromEntries(this.data.artifacts)
288
316
  };
289
317
  fs3.writeFileSync(backupPath, JSON.stringify(backupData, null, 2));
@@ -295,83 +323,62 @@ var LocalDataStore = class {
295
323
  }
296
324
  const backupData = JSON.parse(fs3.readFileSync(backupPath, "utf8"));
297
325
  this.data.apps = new Map(Object.entries(backupData.apps || {}));
298
- this.data.manifests = new Map(Object.entries(backupData.manifests || {}));
326
+ this.data.bundleManifests = new Map(
327
+ Object.entries(backupData.bundleManifests || {})
328
+ );
299
329
  this.data.artifacts = new Map(Object.entries(backupData.artifacts || {}));
300
330
  this.saveData();
301
331
  }
302
332
  // Reset data
303
333
  reset() {
304
334
  this.data.apps.clear();
305
- this.data.manifests.clear();
335
+ this.data.bundleManifests.clear();
306
336
  this.data.artifacts.clear();
307
337
  this.saveData();
308
338
  }
339
+ /**
340
+ * Get all bundle manifests
341
+ */
342
+ getAllBundles() {
343
+ return Array.from(this.data.bundleManifests.values());
344
+ }
309
345
  // Seed with sample data
310
346
  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"
347
+ const bundleManifest = {
348
+ version: "1.0",
349
+ package: "com.calimero.sample-bundle",
350
+ appVersion: "1.0.0",
351
+ metadata: {
352
+ name: "Sample Bundle App",
353
+ description: "A sample application using V2 Bundle Manifest",
354
+ tags: ["sample", "v2"],
355
+ license: "MIT"
318
356
  },
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();
357
+ interfaces: {
358
+ exports: ["com.calimero.sample.v1"],
359
+ uses: []
360
+ },
361
+ links: {
362
+ frontend: "https://example.com/app",
363
+ github: "https://github.com/example/app"
364
+ },
365
+ wasm: {
366
+ path: "app.wasm",
367
+ size: 1024,
368
+ hash: null
369
+ },
370
+ abi: {
371
+ path: "abi.json",
372
+ size: 512,
373
+ hash: null
374
+ },
375
+ migrations: []
376
+ };
377
+ this.setBundleManifest(
378
+ bundleManifest.package,
379
+ bundleManifest.appVersion,
380
+ bundleManifest
381
+ );
375
382
  }
376
383
  };
377
384
  var LocalArtifactServer = class {
@@ -391,11 +398,11 @@ var LocalArtifactServer = class {
391
398
  if (!fs3.existsSync(sourcePath)) {
392
399
  throw new Error(`Source file not found: ${sourcePath}`);
393
400
  }
394
- const appVersionDir = path.join(this.artifactsDir, appId, version);
401
+ const appVersionDir = path6.join(this.artifactsDir, appId, version);
395
402
  if (!fs3.existsSync(appVersionDir)) {
396
403
  fs3.mkdirSync(appVersionDir, { recursive: true });
397
404
  }
398
- const targetPath = path.join(appVersionDir, filename);
405
+ const targetPath = path6.join(appVersionDir, filename);
399
406
  fs3.copyFileSync(sourcePath, targetPath);
400
407
  const fileHash = await this.calculateFileHash(targetPath);
401
408
  this.dataStore.setArtifactPath(fileHash, targetPath);
@@ -403,7 +410,7 @@ var LocalArtifactServer = class {
403
410
  }
404
411
  // Serve artifact by app ID, version, and filename
405
412
  async serveArtifact(appId, version, filename) {
406
- const artifactPath = path.join(this.artifactsDir, appId, version, filename);
413
+ const artifactPath = path6.join(this.artifactsDir, appId, version, filename);
407
414
  if (!fs3.existsSync(artifactPath)) {
408
415
  throw new Error(`Artifact not found: ${artifactPath}`);
409
416
  }
@@ -437,35 +444,33 @@ var LocalArtifactServer = class {
437
444
  }
438
445
  // Get artifact URL for local serving
439
446
  getArtifactUrl(appId, version, filename) {
440
- const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
447
+ const baseUrl = `http://${this.config.getPublicHost()}:${this.config.getPort()}`;
441
448
  return `${baseUrl}/artifacts/${appId}/${version}/${filename}`;
442
449
  }
443
450
  // Get artifact URL by hash
444
451
  getArtifactUrlByHash(hash) {
445
- const baseUrl = `http://${this.config.getHost()}:${this.config.getPort()}`;
452
+ const baseUrl = `http://${this.config.getPublicHost()}:${this.config.getPort()}`;
446
453
  return `${baseUrl}/artifacts/${hash}`;
447
454
  }
448
- // Update manifest artifacts to use local URLs
449
- updateManifestArtifacts(manifest) {
455
+ // Update manifest artifact to use local URLs (and preserve HTTP access)
456
+ updateManifestArtifact(manifest) {
450
457
  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
- );
458
+ if (!manifest.artifact?.uri) {
459
+ return updatedManifest;
460
+ }
461
+ const uri = manifest.artifact.uri;
462
+ if (uri.startsWith("/")) {
463
+ updatedManifest.artifact = {
464
+ ...manifest.artifact,
465
+ uri: `${this.getArtifactUrl(manifest.id, manifest.version, path6.basename(manifest.artifact.uri))}`
466
+ };
467
+ } else if (uri.startsWith("file://")) {
468
+ const filePath = uri.replace("file://", "");
469
+ const filename = path6.basename(filePath);
470
+ updatedManifest.artifact = {
471
+ ...manifest.artifact,
472
+ uri: this.getArtifactUrl(manifest.id, manifest.version, filename)
473
+ };
469
474
  }
470
475
  return updatedManifest;
471
476
  }
@@ -476,7 +481,7 @@ var LocalArtifactServer = class {
476
481
  let files = [];
477
482
  const items = fs3.readdirSync(dir);
478
483
  for (const item of items) {
479
- const fullPath = path.join(dir, item);
484
+ const fullPath = path6.join(dir, item);
480
485
  const stat = fs3.statSync(fullPath);
481
486
  if (stat.isDirectory()) {
482
487
  files = files.concat(getAllFiles(fullPath));
@@ -505,7 +510,7 @@ var LocalArtifactServer = class {
505
510
  let files = [];
506
511
  const items = fs3.readdirSync(dir);
507
512
  for (const item of items) {
508
- const fullPath = path.join(dir, item);
513
+ const fullPath = path6.join(dir, item);
509
514
  const stat = fs3.statSync(fullPath);
510
515
  if (stat.isDirectory()) {
511
516
  files = files.concat(getAllFiles(fullPath));
@@ -573,24 +578,22 @@ var LocalRegistryClient = class {
573
578
  if (!manifest) {
574
579
  throw new Error("Manifest not found");
575
580
  }
576
- return this.artifactServer.updateManifestArtifacts(manifest);
581
+ return this.artifactServer.updateManifestArtifact(manifest);
577
582
  }
578
583
  async submitAppManifest(manifest) {
579
584
  if (!this.validateManifest(manifest)) {
580
585
  throw new Error("Invalid manifest structure");
581
586
  }
582
587
  const processedManifest = await this.processManifestArtifacts(manifest);
583
- const manifestKey = `${manifest.app.app_id}/${manifest.version.semver}`;
588
+ const manifestKey = `${processedManifest.id}/${processedManifest.version}`;
584
589
  this.dataStore.setManifest(manifestKey, processedManifest);
585
- const appKey = manifest.app.app_id;
586
590
  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
591
+ id: processedManifest.id,
592
+ name: processedManifest.name,
593
+ latest_version: processedManifest.version,
594
+ latest_digest: processedManifest.artifact.digest
592
595
  };
593
- this.dataStore.setApp(appKey, appSummary);
596
+ this.dataStore.setApp(processedManifest.id, appSummary);
594
597
  return {
595
598
  success: true,
596
599
  message: "App version registered successfully"
@@ -600,39 +603,33 @@ var LocalRegistryClient = class {
600
603
  return { status: "ok" };
601
604
  }
602
605
  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);
606
+ return !!(manifest.manifest_version && manifest.id && manifest.name && manifest.version && manifest.artifact && manifest.artifact.type && manifest.artifact.target && manifest.artifact.digest && manifest.artifact.uri);
604
607
  }
605
608
  async processManifestArtifacts(manifest) {
606
609
  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
- );
610
+ if (manifest.artifact?.uri?.startsWith("file://")) {
611
+ const filePath = manifest.artifact.uri.replace("file://", "");
612
+ if (fs3.existsSync(filePath)) {
613
+ const filename = path6.basename(filePath);
614
+ try {
615
+ await this.artifactServer.copyArtifactToLocal(
616
+ filePath,
617
+ manifest.id,
618
+ manifest.version,
619
+ filename
620
+ );
621
+ processedManifest.artifact = {
622
+ ...manifest.artifact,
623
+ uri: this.artifactServer.getArtifactUrl(
624
+ manifest.id,
625
+ manifest.version,
626
+ filename
627
+ )
628
+ };
629
+ } catch (error) {
630
+ console.warn(`Failed to copy artifact ${filePath}:`, error);
631
+ }
632
+ }
636
633
  }
637
634
  return processedManifest;
638
635
  }
@@ -648,7 +645,7 @@ function createRegistryClient(useLocal, baseURL, timeout) {
648
645
  }
649
646
  }
650
647
  var appsCommand = new Command("apps").description("Manage SSApp applications").addCommand(
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) => {
648
+ new Command("list").description("List all applications").option("-n, --name <name>", "Filter by application name").action(async (options, command) => {
652
649
  const globalOpts = command.parent?.parent?.opts();
653
650
  const useLocal = globalOpts?.local || false;
654
651
  const client = createRegistryClient(
@@ -659,7 +656,6 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
659
656
  const spinner2 = ora6("Fetching applications...").start();
660
657
  try {
661
658
  const apps = await client.getApps({
662
- dev: options.dev,
663
659
  name: options.name
664
660
  });
665
661
  spinner2.succeed(`Found ${apps.length} application(s)`);
@@ -668,13 +664,13 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
668
664
  return;
669
665
  }
670
666
  const tableData = [
671
- ["Name", "Developer", "Latest Version", "Latest CID", "Alias"],
667
+ ["ID", "Name", "Latest Version", "Digest"],
672
668
  ...apps.map((app) => [
669
+ app.id,
673
670
  app.name,
674
- app.developer_pubkey?.substring(0, 12) + "..." || "Unknown",
675
- app.latest_version || "Unknown",
676
- app.latest_cid?.substring(0, 12) + "..." || "N/A",
677
- app.alias || "-"
671
+ app.latest_version,
672
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
673
+ (app.latest_digest || app.latest_cid || "N/A").toString().substring(0, 12) + "..."
678
674
  ])
679
675
  ];
680
676
  console.log(table(tableData));
@@ -756,7 +752,7 @@ var appsCommand = new Command("apps").description("Manage SSApp applications").a
756
752
  );
757
753
  const spinner2 = ora6("Reading manifest file...").start();
758
754
  try {
759
- const manifestPath = path.resolve(manifestFile);
755
+ const manifestPath = path6.resolve(manifestFile);
760
756
  if (!fs3.existsSync(manifestPath)) {
761
757
  spinner2.fail("Manifest file not found");
762
758
  console.error(chalk5.red(`File not found: ${manifestFile}`));
@@ -1072,54 +1068,6 @@ var LocalRegistryServer = class {
1072
1068
  totalSize: artifactStats.totalSize
1073
1069
  };
1074
1070
  });
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
1071
  this.server.get(
1124
1072
  "/artifacts/:appId/:version/:filename",
1125
1073
  async (request, reply) => {
@@ -1180,180 +1128,109 @@ var LocalRegistryServer = class {
1180
1128
  await this.seed();
1181
1129
  return { message: "Sample data seeded successfully" };
1182
1130
  });
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
- };
1131
+ this.server.get("/api/v2/bundles", async (request, reply) => {
1132
+ const query = request.query;
1133
+ const { package: pkg, version, developer } = query;
1134
+ if (pkg && version) {
1135
+ const manifest = this.dataStore.getBundleManifest(pkg, version);
1136
+ if (!manifest) {
1137
+ return reply.code(404).send({
1138
+ error: "bundle_not_found",
1139
+ message: `Bundle ${pkg}@${version} not found`
1140
+ });
1141
+ }
1142
+ return [manifest];
1261
1143
  }
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
- });
1144
+ const allBundles = this.dataStore.getAllBundles();
1145
+ const bundles = [];
1146
+ for (const bundle of allBundles) {
1147
+ if (pkg && bundle.package !== pkg) {
1148
+ continue;
1149
+ }
1150
+ if (developer) {
1151
+ const bundlePubkey = bundle.signature?.pubkey;
1152
+ if (!bundlePubkey || bundlePubkey !== developer) {
1153
+ continue;
1154
+ }
1155
+ }
1156
+ bundles.push(bundle);
1271
1157
  }
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"
1158
+ bundles.sort((a, b) => {
1159
+ const pkgCompare = a.package.localeCompare(b.package);
1160
+ if (pkgCompare !== 0) return pkgCompare;
1161
+ return b.appVersion.localeCompare(a.appVersion, void 0, {
1162
+ numeric: true
1287
1163
  });
1164
+ });
1165
+ if (pkg) {
1166
+ return bundles;
1167
+ }
1168
+ const latestByPackage = /* @__PURE__ */ new Map();
1169
+ for (const bundle of bundles) {
1170
+ const existing = latestByPackage.get(bundle.package);
1171
+ if (!existing || bundle.appVersion > existing.appVersion) {
1172
+ latestByPackage.set(bundle.package, bundle);
1173
+ }
1288
1174
  }
1289
- return {
1290
- plan: [{ action: "install", id: root.id, version: root.version }],
1291
- satisfies: [],
1292
- missing: []
1293
- };
1175
+ return Array.from(latestByPackage.values());
1294
1176
  });
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
- }
1177
+ this.server.get(
1178
+ "/api/v2/bundles/:package/:version",
1179
+ async (request, reply) => {
1180
+ const { package: pkg, version } = request.params;
1181
+ const manifest = this.dataStore.getBundleManifest(pkg, version);
1182
+ if (!manifest) {
1183
+ return reply.code(404).send({
1184
+ error: "manifest_not_found",
1185
+ message: `Manifest not found for ${pkg}@${version}`
1186
+ });
1326
1187
  }
1188
+ return manifest;
1327
1189
  }
1328
- }
1329
- return processedManifest;
1190
+ );
1330
1191
  }
1331
1192
  };
1332
1193
  var localCommand = new Command("local").description(
1333
1194
  "Manage local registry for development"
1334
1195
  );
1335
1196
  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) => {
1197
+ 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", "0.0.0.0").option(
1198
+ "--public-host <host>",
1199
+ "Public host exposed in manifest artifact URLs",
1200
+ "host.docker.internal"
1201
+ ).action(async (options) => {
1337
1202
  const spinner2 = ora6("Starting local registry...").start();
1338
1203
  try {
1339
1204
  const config = new LocalConfig();
1205
+ config.setHost(options.host);
1206
+ config.setPublicHost(options.publicHost);
1207
+ config.setPort(parseInt(options.port, 10));
1340
1208
  const server = new LocalRegistryServer(config);
1341
1209
  await server.start(parseInt(options.port));
1342
1210
  spinner2.succeed(
1343
- `Local registry started on http://${options.host}:${options.port}`
1211
+ `Local registry started on http://${config.getHost()}:${config.getPort()}`
1344
1212
  );
1345
1213
  console.log(chalk5.blue("\n\u{1F4F1} Local Registry Status:"));
1346
1214
  console.log(
1347
- chalk5.green(`\u2705 Server: http://${options.host}:${options.port}`)
1215
+ chalk5.green(
1216
+ `\u2705 Server: http://${config.getHost()}:${config.getPort()}`
1217
+ )
1218
+ );
1219
+ console.log(
1220
+ chalk5.green(
1221
+ `\u{1F310} Public URL: http://${config.getPublicHost()}:${config.getPort()}`
1222
+ )
1348
1223
  );
1349
1224
  console.log(chalk5.green(`\u{1F4C1} Data: ${config.getDataDir()}`));
1350
1225
  console.log(
1351
1226
  chalk5.green(
1352
- `\u{1F4CB} Health: http://${options.host}:${options.port}/healthz`
1227
+ `\u{1F4CB} Health: http://${config.getHost()}:${config.getPort()}/healthz`
1353
1228
  )
1354
1229
  );
1355
1230
  console.log(
1356
- chalk5.green(`\u{1F4CA} Stats: http://${options.host}:${options.port}/stats`)
1231
+ chalk5.green(
1232
+ `\u{1F4CA} Stats: http://${config.getHost()}:${config.getPort()}/stats`
1233
+ )
1357
1234
  );
1358
1235
  console.log(
1359
1236
  chalk5.blue(
@@ -1509,271 +1386,245 @@ localCommand.addCommand(
1509
1386
  }
1510
1387
  })
1511
1388
  );
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
- }
1389
+ var bundleCommand = new Command("bundle").description("Manage application bundles (V2)").addCommand(createCreateCommand()).addCommand(createPushCommand()).addCommand(createGetCommand());
1390
+ function createCreateCommand() {
1391
+ return new Command("create").description("Create an MPK bundle from a WASM file").argument("<wasm-file>", "Path to the WASM file").argument("<package>", "Package name (e.g. com.calimero.myapp)").argument("<version>", "Version (e.g. 1.0.0)").option("-o, --output <path>", "Output path for the MPK file").option("--name <name>", "Application name").option("--description <description>", "Application description").option("--author <author>", "Application author", "Calimero Team").option("--frontend <url>", "Frontend URL").option("--github <url>", "GitHub URL").option("--docs <url>", "Documentation URL").option(
1392
+ "--export <interface>",
1393
+ "Export interface (can be specified multiple times)",
1394
+ (value, prev) => {
1395
+ return [...prev || [], value];
1564
1396
  }
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);
1397
+ ).option(
1398
+ "--use <interface>",
1399
+ "Use interface (can be specified multiple times)",
1400
+ (value, prev) => {
1401
+ return [...prev || [], value];
1402
+ }
1403
+ ).action(async (wasmFile, pkg, version, options) => {
1404
+ try {
1405
+ const wasmPath = path6.resolve(wasmFile);
1406
+ if (!fs3.existsSync(wasmPath)) {
1407
+ console.error(`\u274C WASM file not found: ${wasmFile}`);
1610
1408
  process.exit(1);
1611
1409
  }
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
- );
1410
+ console.log(`\u{1F4E6} Creating MPK bundle from: ${path6.basename(wasmPath)}`);
1411
+ const wasmContent = fs3.readFileSync(wasmPath);
1412
+ const wasmSize = wasmContent.length;
1413
+ const hash = crypto.createHash("sha256").update(wasmContent).digest("hex");
1414
+ const metadata = {
1415
+ name: options.name || pkg,
1416
+ description: options.description || "",
1417
+ author: options.author || "Calimero Team"
1418
+ };
1419
+ const links = {};
1420
+ if (options.frontend) links.frontend = options.frontend;
1421
+ if (options.github) links.github = options.github;
1422
+ if (options.docs) links.docs = options.docs;
1423
+ const interfaces = {
1424
+ exports: options.export || [],
1425
+ uses: options.use || []
1426
+ };
1427
+ const manifest = {
1428
+ version: "1.0",
1429
+ package: pkg,
1430
+ appVersion: version,
1431
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
1432
+ interfaces: interfaces.exports.length > 0 || interfaces.uses.length > 0 ? interfaces : void 0,
1433
+ wasm: {
1434
+ path: "app.wasm",
1435
+ hash,
1436
+ size: wasmSize
1437
+ },
1438
+ abi: null,
1439
+ migrations: [],
1440
+ links: Object.keys(links).length > 0 ? links : void 0,
1441
+ signature: void 0
1442
+ };
1443
+ let outputPath = options.output;
1444
+ if (!outputPath) {
1445
+ const outputDir = path6.join(process.cwd(), pkg, version);
1446
+ if (!fs3.existsSync(outputDir)) {
1447
+ fs3.mkdirSync(outputDir, { recursive: true });
1448
+ }
1449
+ outputPath = path6.join(outputDir, `${pkg}-${version}.mpk`);
1450
+ } else {
1451
+ outputPath = path6.resolve(outputPath);
1452
+ const outputDir = path6.dirname(outputPath);
1453
+ if (!fs3.existsSync(outputDir)) {
1454
+ fs3.mkdirSync(outputDir, { recursive: true });
1655
1455
  }
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
1456
  }
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) => {
1457
+ const tempDir = path6.join(
1458
+ path6.dirname(outputPath),
1459
+ `.temp-bundle-${Date.now()}`
1460
+ );
1461
+ fs3.mkdirSync(tempDir, { recursive: true });
1667
1462
  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
- }
1463
+ fs3.writeFileSync(
1464
+ path6.join(tempDir, "manifest.json"),
1465
+ JSON.stringify(manifest, null, 2)
1695
1466
  );
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(", ")}`);
1467
+ fs3.writeFileSync(path6.join(tempDir, "app.wasm"), wasmContent);
1468
+ await tar.create(
1469
+ {
1470
+ gzip: true,
1471
+ file: outputPath,
1472
+ cwd: tempDir
1473
+ },
1474
+ ["manifest.json", "app.wasm"]
1475
+ );
1476
+ const outputSize = fs3.statSync(outputPath).size;
1477
+ console.log(`\u2705 Created MPK bundle: ${outputPath}`);
1478
+ console.log(` Package: ${pkg}`);
1479
+ console.log(` Version: ${version}`);
1480
+ console.log(` Size: ${outputSize} bytes`);
1481
+ console.log(` WASM Hash: ${hash}`);
1482
+ } finally {
1483
+ if (fs3.existsSync(tempDir)) {
1484
+ fs3.rmSync(tempDir, { recursive: true, force: true });
1701
1485
  }
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
1486
  }
1487
+ } catch (error) {
1488
+ const message = error instanceof Error ? error.message : "Unknown error";
1489
+ console.error("\u274C Failed to create bundle:", message);
1490
+ process.exit(1);
1707
1491
  }
1708
- );
1492
+ });
1709
1493
  }
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) => {
1494
+ function createPushCommand() {
1495
+ return new Command("push").description("Push a bundle (.mpk) to the registry").argument("<bundle-file>", "Path to the .mpk bundle file").option("--local", "Push to local registry instance", true).action(async (bundleFile, options) => {
1712
1496
  try {
1713
- if (!fs3.existsSync(manifestFile)) {
1714
- console.error(`\u274C Manifest file not found: ${manifestFile}`);
1497
+ const fullPath = path6.resolve(bundleFile);
1498
+ if (!fs3.existsSync(fullPath)) {
1499
+ console.error(`\u274C File not found: ${bundleFile}`);
1715
1500
  process.exit(1);
1716
1501
  }
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
- );
1502
+ console.log(`\u{1F4E6} Processing bundle: ${path6.basename(fullPath)}`);
1503
+ const manifest = await extractManifest(fullPath);
1504
+ if (!manifest) {
1505
+ console.error("\u274C manifest.json not found in bundle");
1734
1506
  process.exit(1);
1735
1507
  }
1736
- if (manifest.artifact.type !== "wasm" || manifest.artifact.target !== "node") {
1737
- console.error("\u274C Invalid artifact type or target");
1508
+ if (!manifest.package || !manifest.appVersion) {
1509
+ console.error("\u274C Invalid manifest: missing package or appVersion");
1738
1510
  process.exit(1);
1739
1511
  }
1740
- if (!manifest.artifact.digest.match(/^sha256:[0-9a-f]{64}$/)) {
1741
- console.error("\u274C Invalid artifact digest format");
1512
+ console.log(` Package: ${manifest.package}`);
1513
+ console.log(` Version: ${manifest.appVersion}`);
1514
+ if (manifest.metadata) {
1515
+ console.log(` Name: ${manifest.metadata.name}`);
1516
+ }
1517
+ if (options.local) {
1518
+ const config = new LocalConfig();
1519
+ const store = new LocalDataStore(config);
1520
+ const artifactServer = new LocalArtifactServer(config, store);
1521
+ const bundleFilename = `${manifest.package}-${manifest.appVersion}.mpk`;
1522
+ const targetPath = await artifactServer.copyArtifactToLocal(
1523
+ fullPath,
1524
+ manifest.package,
1525
+ manifest.appVersion,
1526
+ bundleFilename
1527
+ );
1528
+ console.log(` Artifact stored: ${targetPath}`);
1529
+ store.setBundleManifest(
1530
+ manifest.package,
1531
+ manifest.appVersion,
1532
+ manifest
1533
+ );
1534
+ console.log("\u2705 Bundle manifest registered locally");
1535
+ console.log(
1536
+ ` Run 'calimero-registry bundle get ${manifest.package} ${manifest.appVersion}' to verify`
1537
+ );
1538
+ } else {
1539
+ console.error("\u274C Remote push not implemented yet");
1742
1540
  process.exit(1);
1743
1541
  }
1744
- if (!manifest.artifact.uri.match(/^(https:\/\/|ipfs:\/\/)/)) {
1745
- console.error("\u274C Invalid artifact URI format");
1542
+ } catch (error) {
1543
+ console.error("\u274C Failed to push bundle:", error);
1544
+ process.exit(1);
1545
+ }
1546
+ });
1547
+ }
1548
+ function createGetCommand() {
1549
+ return new Command("get").description("Get bundle manifest").argument("<package>", "Package name (e.g. com.calimero.kv)").argument("<version>", "Version (e.g. 1.0.0)").option("--local", "Use local registry", true).action(async (pkg, version, options) => {
1550
+ try {
1551
+ if (options.local) {
1552
+ const config = new LocalConfig();
1553
+ const store = new LocalDataStore(config);
1554
+ const manifest = store.getBundleManifest(pkg, version);
1555
+ if (manifest) {
1556
+ console.log(JSON.stringify(manifest, null, 2));
1557
+ } else {
1558
+ console.error(`\u274C Manifest not found: ${pkg}@${version}`);
1559
+ process.exit(1);
1560
+ }
1561
+ } else {
1562
+ console.error("\u274C Remote get not implemented yet");
1746
1563
  process.exit(1);
1747
1564
  }
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
1565
  } catch (error) {
1765
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1766
- console.error("\u274C Failed to verify manifest:", errorMessage);
1566
+ console.error("\u274C Error:", error);
1767
1567
  process.exit(1);
1768
1568
  }
1769
1569
  });
1770
1570
  }
1571
+ async function extractManifest(bundlePath) {
1572
+ let manifestContent = null;
1573
+ await tar.t({
1574
+ file: bundlePath,
1575
+ onentry: (entry) => {
1576
+ if (entry.path === "manifest.json") ;
1577
+ }
1578
+ });
1579
+ const extractDir = path6.join(path6.dirname(bundlePath), `.temp-${Date.now()}`);
1580
+ if (!fs3.existsSync(extractDir)) {
1581
+ fs3.mkdirSync(extractDir);
1582
+ }
1583
+ try {
1584
+ await tar.x({
1585
+ file: bundlePath,
1586
+ cwd: extractDir,
1587
+ filter: (path7) => path7 === "manifest.json"
1588
+ });
1589
+ const manifestPath = path6.join(extractDir, "manifest.json");
1590
+ if (fs3.existsSync(manifestPath)) {
1591
+ const content = fs3.readFileSync(manifestPath, "utf8");
1592
+ manifestContent = content;
1593
+ }
1594
+ } catch {
1595
+ } finally {
1596
+ if (fs3.existsSync(extractDir)) {
1597
+ fs3.rmSync(extractDir, { recursive: true, force: true });
1598
+ }
1599
+ }
1600
+ if (manifestContent) {
1601
+ try {
1602
+ return JSON.parse(manifestContent);
1603
+ } catch {
1604
+ console.error("Failed to parse manifest JSON");
1605
+ return null;
1606
+ }
1607
+ }
1608
+ return null;
1609
+ }
1771
1610
 
1772
1611
  // src/index.ts
1773
1612
  var program = new Command();
1774
1613
  program.name("calimero-registry").description(
1775
1614
  "Calimero Network App Registry CLI - Command-line interface for the App Registry"
1776
1615
  ).version("1.0.0");
1616
+ program.addHelpText(
1617
+ "after",
1618
+ `
1619
+ Examples:
1620
+ $ calimero-registry apps list
1621
+ $ calimero-registry apps create --file manifest.json
1622
+ $ calimero-registry local start
1623
+ $ calimero-registry health --local
1624
+
1625
+ For more information, visit: https://github.com/calimero-network/app-registry
1626
+ `
1627
+ );
1777
1628
  program.option("-u, --url <url>", "Registry API URL", "http://localhost:8082");
1778
1629
  program.option(
1779
1630
  "-t, --timeout <timeout>",
@@ -1787,7 +1638,7 @@ program.addCommand(attestationsCommand);
1787
1638
  program.addCommand(healthCommand);
1788
1639
  program.addCommand(ipfsCommand);
1789
1640
  program.addCommand(localCommand);
1790
- program.addCommand(v1Command);
1641
+ program.addCommand(bundleCommand);
1791
1642
  program.exitOverride();
1792
1643
  try {
1793
1644
  program.parse();