@calimero-network/registry-cli 1.11.0 → 1.12.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
@@ -12,9 +12,6 @@ import crypto from 'crypto';
12
12
  import fastify from 'fastify';
13
13
  import cors from '@fastify/cors';
14
14
  import * as tar from 'tar';
15
- import * as ed25519 from '@noble/ed25519';
16
- import canonicalize from 'canonicalize';
17
- import bs58 from 'bs58';
18
15
 
19
16
  var CalimeroRegistryClient = class {
20
17
  /**
@@ -2380,9 +2377,8 @@ Examples:
2380
2377
  console.log(` Package: ${body.package || pkg}`);
2381
2378
  console.log(` Version: ${body.version || version2}`);
2382
2379
  } else {
2383
- console.error(
2384
- `\u274C PATCH failed (${res2.status}): ${body.error ?? body.message ?? text}`
2385
- );
2380
+ const errMsg = body.message && String(body.message).trim() || body.error || text;
2381
+ console.error(`\u274C PATCH failed (${res2.status}): ${errMsg}`);
2386
2382
  process.exit(1);
2387
2383
  }
2388
2384
  return;
@@ -2641,7 +2637,7 @@ async function extractManifest(bundlePath) {
2641
2637
  await tar.x({
2642
2638
  file: bundlePath,
2643
2639
  cwd: extractDir,
2644
- filter: (path9) => path9 === "manifest.json"
2640
+ filter: (path8) => path8 === "manifest.json"
2645
2641
  });
2646
2642
  const manifestPath = path7.join(extractDir, "manifest.json");
2647
2643
  if (fs9.existsSync(manifestPath)) {
@@ -2814,96 +2810,6 @@ function createConfigResetCommand() {
2814
2810
  }
2815
2811
  });
2816
2812
  }
2817
- var ENV_KEYPAIR = "CALIMERO_REGISTRY_KEYPAIR";
2818
- function base64urlEncode(bytes) {
2819
- const base64 = Buffer.from(bytes).toString("base64");
2820
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2821
- }
2822
- function buildSignedPayload(method, pathname, body) {
2823
- const bodyObj = body != null && typeof body === "object" ? body : {};
2824
- const bodyStr = Object.keys(bodyObj).length > 0 ? canonicalize(bodyObj) : "";
2825
- return `${method}
2826
- ${pathname}
2827
- ${bodyStr}`;
2828
- }
2829
- function base64urlDecode(str) {
2830
- const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
2831
- const padded = base64.padEnd(
2832
- base64.length + (4 - base64.length % 4) % 4,
2833
- "="
2834
- );
2835
- return new Uint8Array(Buffer.from(padded, "base64"));
2836
- }
2837
- function loadKeypair(options) {
2838
- if (options?.keypairPath) {
2839
- const resolved = path7.resolve(options.keypairPath);
2840
- if (!fs9.existsSync(resolved)) {
2841
- throw new Error(`Keypair file not found: ${resolved}`);
2842
- }
2843
- const raw = fs9.readFileSync(resolved, "utf8");
2844
- const parsed = JSON.parse(raw);
2845
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.private_key) {
2846
- const secretKey2 = base64urlDecode(parsed.private_key);
2847
- const publicKey2 = base64urlDecode(parsed.public_key);
2848
- if (secretKey2.length !== 32 || publicKey2.length !== 32) {
2849
- throw new Error(
2850
- "Invalid key file: private_key and public_key must be 32 bytes each"
2851
- );
2852
- }
2853
- return { publicKey: publicKey2, secretKey: secretKey2 };
2854
- }
2855
- if (Array.isArray(parsed) && parsed.length === 64) {
2856
- const bytes2 = new Uint8Array(parsed);
2857
- const secretKey2 = bytes2.slice(0, 32);
2858
- const publicKey2 = bytes2.slice(32, 64);
2859
- return { publicKey: publicKey2, secretKey: secretKey2 };
2860
- }
2861
- throw new Error(
2862
- "Unrecognised key file format. Expected mero-sign JSON ({ private_key, public_key, signer_id }) or a legacy array of 64 numbers."
2863
- );
2864
- }
2865
- const env = process.env[ENV_KEYPAIR];
2866
- if (!env || typeof env !== "string" || !env.trim()) {
2867
- throw new Error(
2868
- `Missing keypair: set ${ENV_KEYPAIR} (base58 64-byte keypair) or use -k <path>`
2869
- );
2870
- }
2871
- const decoded = bs58.decode(env.trim());
2872
- if (decoded.length !== 64) {
2873
- throw new Error(
2874
- `Invalid keypair: ${ENV_KEYPAIR} must decode to 64 bytes (got ${decoded.length})`
2875
- );
2876
- }
2877
- const bytes = new Uint8Array(decoded);
2878
- const secretKey = bytes.slice(0, 32);
2879
- const publicKey = bytes.slice(32, 64);
2880
- return { publicKey, secretKey };
2881
- }
2882
- async function signPayload(payload, secretKey, publicKey) {
2883
- const message = new TextEncoder().encode(payload);
2884
- const signature = await ed25519.signAsync(message, secretKey);
2885
- return {
2886
- pubkeyBase64url: base64urlEncode(publicKey),
2887
- signatureBase64url: base64urlEncode(signature)
2888
- };
2889
- }
2890
- async function getSignedHeaders(method, pathname, body, keypair) {
2891
- const payload = buildSignedPayload(method, pathname, body);
2892
- const { pubkeyBase64url, signatureBase64url } = await signPayload(
2893
- payload,
2894
- keypair.secretKey,
2895
- keypair.publicKey
2896
- );
2897
- return {
2898
- "X-Pubkey": pubkeyBase64url,
2899
- "X-Signature": signatureBase64url
2900
- };
2901
- }
2902
- function publicKeyToBase64url(publicKey) {
2903
- return base64urlEncode(publicKey);
2904
- }
2905
-
2906
- // src/commands/org.ts
2907
2813
  var DEFAULT_TIMEOUT = 1e4;
2908
2814
  function getGlobalOpts(command) {
2909
2815
  const opts = command.parent?.parent?.opts() ?? {};
@@ -2914,9 +2820,15 @@ function getGlobalOpts(command) {
2914
2820
  timeout: parseInt(String(opts.timeout ?? DEFAULT_TIMEOUT), 10) || DEFAULT_TIMEOUT
2915
2821
  };
2916
2822
  }
2917
- function getKeypairPath(command) {
2918
- const orgCmd = command.parent?.name() === "org" ? command.parent : command.parent?.parent;
2919
- return orgCmd?.opts()?.keypair;
2823
+ function getAuthHeaders() {
2824
+ const remoteConfig = new RemoteConfig();
2825
+ const apiKey = remoteConfig.getApiKey();
2826
+ if (!apiKey) {
2827
+ throw new Error(
2828
+ "API token required for org write operations.\nGet a token from the Organizations page in the web UI, then run:\n calimero-registry config set api-key <token>"
2829
+ );
2830
+ }
2831
+ return { Authorization: `Bearer ${apiKey}` };
2920
2832
  }
2921
2833
  async function fetchJson(url, options = {}) {
2922
2834
  const { timeout = DEFAULT_TIMEOUT, ...init } = options;
@@ -2935,31 +2847,49 @@ async function fetchJson(url, options = {}) {
2935
2847
  const data = text ? JSON.parse(text) : void 0;
2936
2848
  return { data, status: res.status };
2937
2849
  }
2938
- var orgCommand = new Command("org").description("Manage organizations (create, list, members, packages)").option(
2939
- "-k, --keypair <path>",
2940
- "Path to Ed25519 keypair JSON (64 bytes). Overrides CALIMERO_REGISTRY_KEYPAIR."
2941
- ).addCommand(
2850
+ var orgCommand = new Command("org").description("Manage organizations (create, list, members, packages)").addCommand(
2942
2851
  new Command("list").description(
2943
- "List organizations you belong to (uses keypair pubkey as member)"
2852
+ "List organizations you belong to (uses API token to resolve identity)"
2944
2853
  ).action(async (_options, command) => {
2945
2854
  const { url, timeout } = getGlobalOpts(command);
2946
- const keypairPath = getKeypairPath(command);
2947
- const spinner2 = ora7("Loading keypair...").start();
2948
- let pubkey;
2855
+ const spinner2 = ora7("Resolving identity...").start();
2856
+ let authHeaders;
2949
2857
  try {
2950
- const kp = loadKeypair({ keypairPath });
2951
- pubkey = publicKeyToBase64url(kp.publicKey);
2952
- spinner2.text = "Fetching organizations...";
2858
+ authHeaders = getAuthHeaders();
2953
2859
  } catch (e) {
2954
- spinner2.fail("Keypair required");
2860
+ spinner2.fail("API token required");
2955
2861
  console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
2956
2862
  process.exit(1);
2957
2863
  }
2958
2864
  const base = url.replace(/\/$/, "");
2959
- const apiUrl = `${base}/api/v2/orgs?member=${encodeURIComponent(pubkey)}`;
2865
+ let myEmail;
2866
+ try {
2867
+ const { data, status } = await fetchJson(`${base}/api/auth/me`, {
2868
+ method: "GET",
2869
+ headers: authHeaders,
2870
+ timeout
2871
+ });
2872
+ if (status !== 200 || !data?.user?.email) {
2873
+ spinner2.fail("Could not resolve identity from API token");
2874
+ console.error(
2875
+ chalk7.red(
2876
+ "Ensure your API token is valid. Get a new one from the web UI."
2877
+ )
2878
+ );
2879
+ process.exit(1);
2880
+ }
2881
+ myEmail = data.user.email;
2882
+ spinner2.text = `Fetching organizations for ${myEmail}...`;
2883
+ } catch (e) {
2884
+ spinner2.fail("Request failed");
2885
+ console.error(chalk7.red(e instanceof Error ? e.message : String(e)));
2886
+ process.exit(1);
2887
+ }
2888
+ const apiUrl = `${base}/api/v2/orgs?member=${encodeURIComponent(myEmail)}`;
2960
2889
  try {
2961
2890
  const { data, status } = await fetchJson(apiUrl, {
2962
2891
  method: "GET",
2892
+ headers: authHeaders,
2963
2893
  timeout
2964
2894
  });
2965
2895
  if (status !== 200) {
@@ -2977,13 +2907,13 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
2977
2907
  orgs.map(
2978
2908
  (o) => fetchJson(
2979
2909
  `${base}/api/v2/orgs/${encodeURIComponent(o.id)}/members`,
2980
- { method: "GET", timeout }
2910
+ { method: "GET", headers: authHeaders, timeout }
2981
2911
  ).catch(() => ({ data: { members: [] }, status: 0 }))
2982
2912
  )
2983
2913
  );
2984
2914
  const lines = orgs.map((o, i) => {
2985
2915
  const members = memberResults[i].data?.members ?? [];
2986
- const myMember = members.find((m) => m.pubkey === pubkey);
2916
+ const myMember = members.find((m) => m.email === myEmail);
2987
2917
  const role = myMember?.role ?? "?";
2988
2918
  const count = members.length;
2989
2919
  const roleLabel = role === "admin" ? chalk7.yellow("admin") : chalk7.gray(role);
@@ -2997,25 +2927,23 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
2997
2927
  }
2998
2928
  })
2999
2929
  ).addCommand(
3000
- new Command("create").description("Create a new organization").requiredOption("-n, --name <name>", "Organization display name").requiredOption("-s, --slug <slug>", "Organization slug (e.g. my-org)").action(
2930
+ new Command("create").description("Create a new organization (requires API token)").requiredOption("-n, --name <name>", "Organization display name").requiredOption("-s, --slug <slug>", "Organization slug (e.g. my-org)").action(
3001
2931
  async (options, command) => {
3002
2932
  const { url, timeout } = getGlobalOpts(command);
3003
- const keypairPath = getKeypairPath(command);
3004
2933
  const spinner2 = ora7("Creating organization...").start();
3005
2934
  try {
3006
- const kp = loadKeypair({ keypairPath });
2935
+ const authHeaders = getAuthHeaders();
3007
2936
  const pathname = "/api/v2/orgs";
3008
2937
  const body = {
3009
2938
  name: options.name.trim(),
3010
2939
  slug: options.slug.trim().toLowerCase()
3011
2940
  };
3012
- const headers = await getSignedHeaders("POST", pathname, body, kp);
3013
2941
  const base = url.replace(/\/$/, "");
3014
2942
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3015
2943
  method: "POST",
3016
2944
  body: JSON.stringify(body),
3017
2945
  timeout,
3018
- headers: { ...headers }
2946
+ headers: authHeaders
3019
2947
  });
3020
2948
  if (status >= 400) {
3021
2949
  const err = data;
@@ -3063,10 +2991,9 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3063
2991
  new Command("update").description("Update organization (name, metadata)").argument("<orgId>", "Organization id or slug").option("-n, --name <name>", "New display name").option("-m, --metadata <json>", "Metadata JSON object").action(
3064
2992
  async (orgId, options, command) => {
3065
2993
  const { url, timeout } = getGlobalOpts(command);
3066
- const keypairPath = getKeypairPath(command);
3067
2994
  const spinner2 = ora7("Updating organization...").start();
3068
2995
  try {
3069
- const kp = loadKeypair({ keypairPath });
2996
+ const authHeaders = getAuthHeaders();
3070
2997
  const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
3071
2998
  const body = {};
3072
2999
  if (options.name !== void 0) body.name = options.name;
@@ -3078,13 +3005,12 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3078
3005
  process.exit(1);
3079
3006
  }
3080
3007
  }
3081
- const headers = await getSignedHeaders("PATCH", pathname, body, kp);
3082
3008
  const base = url.replace(/\/$/, "");
3083
3009
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3084
3010
  method: "PATCH",
3085
3011
  body: Object.keys(body).length > 0 ? JSON.stringify(body) : void 0,
3086
3012
  timeout,
3087
- headers: { ...headers }
3013
+ headers: authHeaders
3088
3014
  });
3089
3015
  if (status >= 400) {
3090
3016
  spinner2.fail(
@@ -3108,7 +3034,6 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3108
3034
  new Command("delete").description("Delete an organization and all its data (irreversible)").argument("<orgId>", "Organization id or slug").option("-y, --yes", "Skip confirmation prompt").action(
3109
3035
  async (orgId, options, command) => {
3110
3036
  const { url, timeout } = getGlobalOpts(command);
3111
- const keypairPath = getKeypairPath(command);
3112
3037
  if (!options.yes) {
3113
3038
  const { createInterface } = await import('readline');
3114
3039
  const rl = createInterface({
@@ -3133,19 +3058,13 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3133
3058
  }
3134
3059
  const spinner2 = ora7("Deleting organization...").start();
3135
3060
  try {
3136
- const kp = loadKeypair({ keypairPath });
3061
+ const authHeaders = getAuthHeaders();
3137
3062
  const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}`;
3138
- const headers = await getSignedHeaders(
3139
- "DELETE",
3140
- pathname,
3141
- void 0,
3142
- kp
3143
- );
3144
3063
  const base = url.replace(/\/$/, "");
3145
3064
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3146
3065
  method: "DELETE",
3147
3066
  timeout,
3148
- headers: { ...headers }
3067
+ headers: authHeaders
3149
3068
  });
3150
3069
  if (status >= 400) {
3151
3070
  spinner2.fail(
@@ -3190,30 +3109,23 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3190
3109
  }
3191
3110
  })
3192
3111
  ).addCommand(
3193
- new Command("add").description("Add a member by pubkey").argument("<orgId>", "Organization id or slug").argument("<pubkey>", "Member public key").option("-r, --role <role>", "Role: member or admin", "member").action(
3194
- async (orgId, pubkey, options, command) => {
3112
+ new Command("add").description("Add a member by email").argument("<orgId>", "Organization id or slug").argument("<email>", "Member email address").option("-r, --role <role>", "Role: member or admin", "member").action(
3113
+ async (orgId, email, options, command) => {
3195
3114
  const { url, timeout } = getGlobalOpts(command);
3196
- const keypairPath = getKeypairPath(command);
3197
3115
  const spinner2 = ora7("Adding member...").start();
3198
3116
  try {
3199
- const kp = loadKeypair({ keypairPath });
3117
+ const authHeaders = getAuthHeaders();
3200
3118
  const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members`;
3201
3119
  const body = {
3202
- pubkey: pubkey.trim(),
3120
+ email: email.trim(),
3203
3121
  role: options.role === "admin" ? "admin" : "member"
3204
3122
  };
3205
- const headers = await getSignedHeaders(
3206
- "POST",
3207
- pathname,
3208
- body,
3209
- kp
3210
- );
3211
3123
  const base = url.replace(/\/$/, "");
3212
3124
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3213
3125
  method: "POST",
3214
3126
  body: JSON.stringify(body),
3215
3127
  timeout,
3216
- headers: { ...headers }
3128
+ headers: authHeaders
3217
3129
  });
3218
3130
  if (status >= 400) {
3219
3131
  spinner2.fail(
@@ -3233,28 +3145,21 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3233
3145
  }
3234
3146
  )
3235
3147
  ).addCommand(
3236
- new Command("update").description("Update a member role (admin or member)").argument("<orgId>", "Organization id or slug").argument("<pubkey>", "Member public key").requiredOption("-r, --role <role>", "New role: admin or member").action(
3237
- async (orgId, pubkey, options, command) => {
3148
+ new Command("update").description("Update a member role (admin or member)").argument("<orgId>", "Organization id or slug").argument("<email>", "Member email address").requiredOption("-r, --role <role>", "New role: admin or member").action(
3149
+ async (orgId, email, options, command) => {
3238
3150
  const { url, timeout } = getGlobalOpts(command);
3239
- const keypairPath = getKeypairPath(command);
3240
3151
  const role = options.role === "admin" ? "admin" : "member";
3241
3152
  const spinner2 = ora7("Updating member role...").start();
3242
3153
  try {
3243
- const kp = loadKeypair({ keypairPath });
3244
- const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(pubkey.trim())}`;
3154
+ const authHeaders = getAuthHeaders();
3155
+ const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
3245
3156
  const body = { role };
3246
- const headers = await getSignedHeaders(
3247
- "PATCH",
3248
- pathname,
3249
- body,
3250
- kp
3251
- );
3252
3157
  const base = url.replace(/\/$/, "");
3253
3158
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3254
3159
  method: "PATCH",
3255
3160
  body: JSON.stringify(body),
3256
3161
  timeout,
3257
- headers: { ...headers }
3162
+ headers: authHeaders
3258
3163
  });
3259
3164
  if (status >= 400) {
3260
3165
  spinner2.fail(
@@ -3274,25 +3179,18 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3274
3179
  }
3275
3180
  )
3276
3181
  ).addCommand(
3277
- new Command("remove").description("Remove a member by pubkey").argument("<orgId>", "Organization id or slug").argument("<pubkey>", "Member public key").action(
3278
- async (orgId, pubkey, _options, command) => {
3182
+ new Command("remove").description("Remove a member by email").argument("<orgId>", "Organization id or slug").argument("<email>", "Member email address").action(
3183
+ async (orgId, email, _options, command) => {
3279
3184
  const { url, timeout } = getGlobalOpts(command);
3280
- const keypairPath = getKeypairPath(command);
3281
3185
  const spinner2 = ora7("Removing member...").start();
3282
3186
  try {
3283
- const kp = loadKeypair({ keypairPath });
3284
- const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(pubkey.trim())}`;
3285
- const headers = await getSignedHeaders(
3286
- "DELETE",
3287
- pathname,
3288
- void 0,
3289
- kp
3290
- );
3187
+ const authHeaders = getAuthHeaders();
3188
+ const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(email.trim())}`;
3291
3189
  const base = url.replace(/\/$/, "");
3292
3190
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3293
3191
  method: "DELETE",
3294
3192
  timeout,
3295
- headers: { ...headers }
3193
+ headers: authHeaders
3296
3194
  });
3297
3195
  if (status >= 400) {
3298
3196
  spinner2.fail(
@@ -3317,24 +3215,17 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3317
3215
  new Command("link").description("Link a package to the organization").argument("<orgId>", "Organization id or slug").argument("<package>", "Package name").action(
3318
3216
  async (orgId, pkg, _options, command) => {
3319
3217
  const { url, timeout } = getGlobalOpts(command);
3320
- const keypairPath = getKeypairPath(command);
3321
3218
  const spinner2 = ora7("Linking package...").start();
3322
3219
  try {
3323
- const kp = loadKeypair({ keypairPath });
3220
+ const authHeaders = getAuthHeaders();
3324
3221
  const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages`;
3325
3222
  const body = { package: pkg.trim() };
3326
- const headers = await getSignedHeaders(
3327
- "POST",
3328
- pathname,
3329
- body,
3330
- kp
3331
- );
3332
3223
  const base = url.replace(/\/$/, "");
3333
3224
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3334
3225
  method: "POST",
3335
3226
  body: JSON.stringify(body),
3336
3227
  timeout,
3337
- headers: { ...headers }
3228
+ headers: authHeaders
3338
3229
  });
3339
3230
  if (status >= 400) {
3340
3231
  spinner2.fail(
@@ -3357,22 +3248,15 @@ var orgCommand = new Command("org").description("Manage organizations (create, l
3357
3248
  new Command("unlink").description("Unlink a package from the organization").argument("<orgId>", "Organization id or slug").argument("<package>", "Package name").action(
3358
3249
  async (orgId, pkg, _options, command) => {
3359
3250
  const { url, timeout } = getGlobalOpts(command);
3360
- const keypairPath = getKeypairPath(command);
3361
3251
  const spinner2 = ora7("Unlinking package...").start();
3362
3252
  try {
3363
- const kp = loadKeypair({ keypairPath });
3253
+ const authHeaders = getAuthHeaders();
3364
3254
  const pathname = `/api/v2/orgs/${encodeURIComponent(orgId)}/packages/${encodeURIComponent(pkg.trim())}`;
3365
- const headers = await getSignedHeaders(
3366
- "DELETE",
3367
- pathname,
3368
- void 0,
3369
- kp
3370
- );
3371
3255
  const base = url.replace(/\/$/, "");
3372
3256
  const { data, status } = await fetchJson(`${base}${pathname}`, {
3373
3257
  method: "DELETE",
3374
3258
  timeout,
3375
- headers: { ...headers }
3259
+ headers: authHeaders
3376
3260
  });
3377
3261
  if (status >= 400) {
3378
3262
  spinner2.fail(