@zalify/cli 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.js +154 -52
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11312,9 +11312,10 @@ function migrate(raw) {
11312
11312
  if (!raw || typeof raw !== "object")
11313
11313
  return null;
11314
11314
  const obj = raw;
11315
- if (obj.version === 2 && obj.workspaces && obj.appUrl) {
11315
+ if (obj.version === 3 && obj.userKey && obj.appUrl)
11316
+ return obj;
11317
+ if (obj.version === 2 && obj.workspaces && obj.appUrl)
11316
11318
  return obj;
11317
- }
11318
11319
  if (typeof obj.key === "string" && typeof obj.workspaceId === "string") {
11319
11320
  return {
11320
11321
  version: 2,
@@ -11351,11 +11352,36 @@ function deleteConfig() {
11351
11352
  }
11352
11353
  function requireActive() {
11353
11354
  const config = readConfig();
11354
- if (!config || Object.keys(config.workspaces).length === 0) {
11355
+ if (!config) {
11355
11356
  throw new Error("Not logged in. Run `zalify login` first.");
11356
11357
  }
11357
- const workspaceId = config.activeWorkspaceId && config.workspaces[config.activeWorkspaceId] ? config.activeWorkspaceId : Object.keys(config.workspaces)[0];
11358
- return { appUrl: config.appUrl, workspaceId, entry: config.workspaces[workspaceId], config };
11358
+ if (config.version === 3) {
11359
+ if (!config.active) {
11360
+ throw new Error("No active workspace. Run `zalify workspace list` then `zalify workspace set <slug>`.");
11361
+ }
11362
+ return {
11363
+ appUrl: config.appUrl,
11364
+ key: config.userKey,
11365
+ workspaceId: config.active.id,
11366
+ workspaceName: config.active.name,
11367
+ workspaceSlug: config.active.slug,
11368
+ config
11369
+ };
11370
+ }
11371
+ const ids = Object.keys(config.workspaces);
11372
+ if (ids.length === 0) {
11373
+ throw new Error("Not logged in. Run `zalify login` first.");
11374
+ }
11375
+ const workspaceId = config.activeWorkspaceId && config.workspaces[config.activeWorkspaceId] ? config.activeWorkspaceId : ids[0];
11376
+ const entry = config.workspaces[workspaceId];
11377
+ return {
11378
+ appUrl: config.appUrl,
11379
+ key: entry.key,
11380
+ workspaceId,
11381
+ workspaceName: entry.name,
11382
+ workspaceSlug: entry.slug,
11383
+ config
11384
+ };
11359
11385
  }
11360
11386
 
11361
11387
  // src/auth.ts
@@ -11375,6 +11401,18 @@ function decodePayload(encoded) {
11375
11401
  const b64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
11376
11402
  return JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
11377
11403
  }
11404
+ async function fetchWorkspaces(appUrl, userKey) {
11405
+ try {
11406
+ const res = await fetch(`${appUrl}/api/cli/workspaces`, {
11407
+ headers: { Authorization: `Bearer ${userKey}` }
11408
+ });
11409
+ if (!res.ok)
11410
+ return [];
11411
+ return (await res.json()).workspaces;
11412
+ } catch {
11413
+ return [];
11414
+ }
11415
+ }
11378
11416
  async function login(options) {
11379
11417
  const base = (options.appUrl || readConfig()?.appUrl || DEFAULT_APP_URL).replace(/\/$/, "");
11380
11418
  const state = randomBytes(32).toString("hex");
@@ -11437,15 +11475,15 @@ async function login(options) {
11437
11475
  ]
11438
11476
  };
11439
11477
  }
11440
- if (!parsed || parsed.keys.length === 0) {
11441
- done("Login failed: no keys in callback.", () => {
11478
+ if (!parsed || !parsed.userKey && !parsed.keys?.length) {
11479
+ done("Login failed: no credentials in callback.", () => {
11442
11480
  teardown();
11443
- reject(new Error("Callback did not include any keys."));
11481
+ reject(new Error("Callback did not include credentials."));
11444
11482
  });
11445
11483
  return;
11446
11484
  }
11447
11485
  const result = parsed;
11448
- done(`Logged in to ${result.keys.length} workspace${result.keys.length === 1 ? "" : "s"}!`, () => {
11486
+ done("Logged in!", () => {
11449
11487
  teardown();
11450
11488
  resolvePromise(result);
11451
11489
  });
@@ -11461,15 +11499,40 @@ async function login(options) {
11461
11499
  });
11462
11500
  server.on("error", reject);
11463
11501
  });
11502
+ if (payload.userKey && payload.user) {
11503
+ const previous = readConfig();
11504
+ const previousActive = previous?.version === 3 ? previous.active : previous?.version === 2 && previous.activeWorkspaceId ? {
11505
+ id: previous.activeWorkspaceId,
11506
+ name: previous.workspaces[previous.activeWorkspaceId]?.name ?? "",
11507
+ slug: previous.workspaces[previous.activeWorkspaceId]?.slug ?? ""
11508
+ } : null;
11509
+ const workspaces = await fetchWorkspaces(base, payload.userKey);
11510
+ const active = previousActive && workspaces.find((w) => w.id === previousActive.id) || workspaces[0] || previousActive || null;
11511
+ writeConfig({
11512
+ version: 3,
11513
+ appUrl: base,
11514
+ userKey: payload.userKey,
11515
+ user: payload.user,
11516
+ active,
11517
+ workspacesCache: workspaces
11518
+ });
11519
+ console.log(`✓ Logged in as ${payload.user.name ?? payload.user.email ?? payload.user.id} — key covers ${workspaces.length || "your"} workspace(s), expires in 90 days.`);
11520
+ if (active)
11521
+ console.log(` Active workspace: ${active.name} (${active.slug})`);
11522
+ console.log(" Switch with `zalify workspace set <slug>`.");
11523
+ return;
11524
+ }
11464
11525
  const existing = readConfig();
11465
11526
  const config = {
11466
11527
  version: 2,
11467
11528
  appUrl: base,
11468
- activeWorkspaceId: existing?.activeWorkspaceId ?? null,
11469
- workspaces: { ...existing?.appUrl === base ? existing?.workspaces : {} }
11529
+ activeWorkspaceId: existing?.version === 2 ? existing.activeWorkspaceId : null,
11530
+ workspaces: {
11531
+ ...existing?.version === 2 && existing.appUrl === base ? existing.workspaces : {}
11532
+ }
11470
11533
  };
11471
11534
  const now = new Date().toISOString();
11472
- for (const k of payload.keys) {
11535
+ for (const k of payload.keys ?? []) {
11473
11536
  config.workspaces[k.workspaceId] = {
11474
11537
  key: k.key,
11475
11538
  name: k.workspaceName,
@@ -11478,23 +11541,15 @@ async function login(options) {
11478
11541
  };
11479
11542
  }
11480
11543
  if (!config.activeWorkspaceId || !config.workspaces[config.activeWorkspaceId]) {
11481
- config.activeWorkspaceId = payload.keys[0].workspaceId;
11544
+ config.activeWorkspaceId = payload.keys?.[0]?.workspaceId ?? null;
11482
11545
  }
11483
11546
  writeConfig(config);
11484
- console.log(`✓ Authorized ${payload.keys.length} workspace(s):`);
11485
- for (const k of payload.keys) {
11486
- const active = k.workspaceId === config.activeWorkspaceId ? " (active)" : "";
11487
- console.log(` - ${k.workspaceName} (${k.workspaceSlug})${active}`);
11488
- }
11489
- for (const f of payload.failed ?? []) {
11490
- console.log(` ✗ ${f.workspaceName}: ${f.error}`);
11491
- }
11492
- console.log("Switch with `zalify workspace set <slug>`.");
11547
+ console.log(`✓ Authorized ${payload.keys?.length ?? 0} workspace(s) (legacy keys).`);
11493
11548
  }
11494
11549
  function logout() {
11495
11550
  deleteConfig();
11496
11551
  console.log("✓ Logged out (local credentials deleted).");
11497
- console.log(" To revoke keys: each workspace's Settings Developer on app.zalify.com.");
11552
+ console.log(" CLI keys expire after 90 days; revoke sooner via app.zalify.com if needed.");
11498
11553
  }
11499
11554
  function ago(iso) {
11500
11555
  if (!iso)
@@ -11511,17 +11566,15 @@ function ago(iso) {
11511
11566
  return `${Math.round(hours / 24)}d ago`;
11512
11567
  }
11513
11568
  async function whoami() {
11514
- const { appUrl, workspaceId, entry, config } = requireActive();
11569
+ const auth = requireActive();
11515
11570
  let live = null;
11516
11571
  let liveError = null;
11517
11572
  try {
11518
- const res = await fetch(`${appUrl}/api/cli/whoami`, {
11519
- headers: { Authorization: `Bearer ${entry.key}` }
11520
- });
11573
+ const res = await fetch(`${auth.appUrl}/api/cli/whoami?workspaceId=${encodeURIComponent(auth.workspaceId)}`, { headers: { Authorization: `Bearer ${auth.key}` } });
11521
11574
  if (res.ok) {
11522
11575
  live = await res.json();
11523
11576
  } else if (res.status === 401) {
11524
- liveError = "key is invalid or was revoked — run `zalify login`";
11577
+ liveError = "key is invalid, expired, or revoked — run `zalify login`";
11525
11578
  } else {
11526
11579
  liveError = `server returned ${res.status}`;
11527
11580
  }
@@ -11529,19 +11582,21 @@ async function whoami() {
11529
11582
  liveError = "could not reach the server (offline?)";
11530
11583
  }
11531
11584
  const ws = live?.workspace;
11532
- const total = Object.keys(config.workspaces).length;
11533
- console.log(`workspace ${ws?.name ?? entry.name ?? "(unknown)"} (${ws?.slug ?? entry.slug ?? "?"}) [${ws?.id ?? workspaceId}]` + (total > 1 ? ` — 1 of ${total}, see \`zalify workspace list\`` : ""));
11585
+ const keyKind = auth.key.startsWith("zalify_cli_") ? "user key" : "workspace key";
11586
+ console.log(`workspace ${ws?.name ?? auth.workspaceName ?? "(unknown)"} (${ws?.slug ?? auth.workspaceSlug ?? "?"}) [${ws?.id ?? auth.workspaceId}]`);
11587
+ if (auth.config.version === 3) {
11588
+ console.log(`user ${auth.config.user.name ?? auth.config.user.email ?? auth.config.user.id}`);
11589
+ } else if (live?.key.createdByName) {
11590
+ console.log(`user ${live.key.createdByName}`);
11591
+ }
11534
11592
  if (live) {
11535
11593
  console.log(`plan ${live.plan.name} (${live.plan.type})`);
11536
- if (live.key.createdByName) {
11537
- console.log(`user ${live.key.createdByName}`);
11538
- }
11539
- console.log(`key ${live.key.name ?? entry.key.slice(0, 16) + "…"} — created ${live.key.createdAt ? new Date(live.key.createdAt).toISOString().slice(0, 10) : "?"}, last used ${ago(live.key.lastRequest)}`);
11594
+ console.log(`key ${live.key.name ?? auth.key.slice(0, 16) + "…"} (${keyKind}) — created ${live.key.createdAt ? new Date(live.key.createdAt).toISOString().slice(0, 10) : "?"}, last used ${ago(live.key.lastRequest)}`);
11540
11595
  } else {
11541
- console.log(`key ${entry.key.slice(0, 16)}…`);
11596
+ console.log(`key ${auth.key.slice(0, 16)}… (${keyKind})`);
11542
11597
  }
11543
- const slug = ws?.slug ?? entry.slug;
11544
- console.log(`app ${slug ? `${appUrl}/store/${slug}` : appUrl}`);
11598
+ const slug = ws?.slug ?? auth.workspaceSlug;
11599
+ console.log(`app ${slug ? `${auth.appUrl}/store/${slug}` : auth.appUrl}`);
11545
11600
  console.log(live ? "status ✓ key verified" : `status ! not verified — ${liveError}`);
11546
11601
  }
11547
11602
 
@@ -11586,7 +11641,7 @@ async function assetsPull(storeDir) {
11586
11641
  console.log(`Done: ${pulled} downloaded, ${Object.keys(index).length - pulled} already local.`);
11587
11642
  }
11588
11643
  async function assetsPush(storeDir) {
11589
- const { appUrl, workspaceId, entry } = requireActive();
11644
+ const { appUrl, workspaceId, key, workspaceName, workspaceSlug } = requireActive();
11590
11645
  const imagesDir = imagesDirFor(storeDir);
11591
11646
  const indexPath = join2(imagesDir, "assets.json");
11592
11647
  const index = readIndex(imagesDir);
@@ -11595,14 +11650,14 @@ async function assetsPush(storeDir) {
11595
11650
  method: "POST",
11596
11651
  headers: {
11597
11652
  "Content-Type": "application/json",
11598
- Authorization: `Bearer ${entry.key}`
11653
+ Authorization: `Bearer ${key}`
11599
11654
  },
11600
11655
  body: JSON.stringify(body)
11601
11656
  });
11602
11657
  const json = await res.json().catch(() => ({}));
11603
11658
  if (!res.ok) {
11604
11659
  if (json.code === "UPGRADE_REQUIRED") {
11605
- const billingUrl = entry.slug ? `${appUrl}/store/${entry.slug}/settings/billing` : `${appUrl} (Settings → Billing)`;
11660
+ const billingUrl = workspaceSlug ? `${appUrl}/store/${workspaceSlug}/settings/billing` : `${appUrl} (Settings → Billing)`;
11606
11661
  throw new Error(`${json.error ?? "This feature requires a paid plan."}
11607
11662
  ` + ` Upgrade this workspace: ${billingUrl}`);
11608
11663
  }
@@ -11618,7 +11673,7 @@ async function assetsPush(storeDir) {
11618
11673
  console.log("Everything already pushed.");
11619
11674
  return;
11620
11675
  }
11621
- console.log(`Pushing ${files.length} file(s) to "${entry.name || workspaceId}"`);
11676
+ console.log(`Pushing ${files.length} file(s) to "${workspaceName || workspaceId}"`);
11622
11677
  for (let i = 0;i < files.length; i += BATCH) {
11623
11678
  const batch = files.slice(i, i + BATCH);
11624
11679
  const { uploads } = await api("/api/assets/presign", {
@@ -11722,31 +11777,74 @@ Try manually: ${cmd} ${args.join(" ")}`);
11722
11777
  }
11723
11778
 
11724
11779
  // src/workspace.ts
11725
- function workspaceList() {
11780
+ async function fetchWorkspaces2(appUrl, userKey) {
11781
+ try {
11782
+ const res = await fetch(`${appUrl}/api/cli/workspaces`, {
11783
+ headers: { Authorization: `Bearer ${userKey}` }
11784
+ });
11785
+ if (!res.ok)
11786
+ return null;
11787
+ const json = await res.json();
11788
+ return json.workspaces;
11789
+ } catch {
11790
+ return null;
11791
+ }
11792
+ }
11793
+ async function workspaceList() {
11726
11794
  const config = readConfig();
11727
- const entries = Object.entries(config?.workspaces ?? {});
11728
- if (!config || entries.length === 0) {
11795
+ if (!config)
11729
11796
  throw new Error("Not logged in. Run `zalify login` first.");
11797
+ if (config.version === 3) {
11798
+ const live = await fetchWorkspaces2(config.appUrl, config.userKey);
11799
+ const list = live ?? config.workspacesCache;
11800
+ if (live) {
11801
+ config.workspacesCache = live;
11802
+ writeConfig(config);
11803
+ } else {
11804
+ console.log(`! could not fetch live list — showing cached workspaces
11805
+ `);
11806
+ }
11807
+ for (const w of list) {
11808
+ const marker = w.id === config.active?.id ? "* " : " ";
11809
+ console.log(`${marker}${w.slug.padEnd(36)} ${w.name} [${w.id}]`);
11810
+ }
11811
+ return;
11730
11812
  }
11813
+ const entries = Object.entries(config.workspaces);
11814
+ if (entries.length === 0)
11815
+ throw new Error("Not logged in. Run `zalify login` first.");
11731
11816
  for (const [id, w] of entries) {
11732
11817
  const marker = id === config.activeWorkspaceId ? "* " : " ";
11733
11818
  console.log(`${marker}${w.slug.padEnd(36)} ${w.name} [${id}]`);
11734
11819
  }
11735
11820
  }
11736
- function workspaceSet(slugOrId) {
11821
+ async function workspaceSet(slugOrId) {
11737
11822
  const config = readConfig();
11738
- if (!config || Object.keys(config.workspaces).length === 0) {
11823
+ if (!config)
11739
11824
  throw new Error("Not logged in. Run `zalify login` first.");
11825
+ if (config.version === 3) {
11826
+ const live = await fetchWorkspaces2(config.appUrl, config.userKey);
11827
+ const list = live ?? config.workspacesCache;
11828
+ if (live)
11829
+ config.workspacesCache = live;
11830
+ const match = list.find((w) => w.id === slugOrId || w.slug === slugOrId || w.name === slugOrId);
11831
+ if (!match) {
11832
+ throw new Error(`No workspace matches "${slugOrId}". Known: ${list.map((w) => w.slug).join(", ")}.`);
11833
+ }
11834
+ config.active = match;
11835
+ writeConfig(config);
11836
+ console.log(`✓ Active workspace: ${match.name} (${match.slug})`);
11837
+ return;
11740
11838
  }
11741
- const match = Object.entries(config.workspaces).find(([id, w]) => id === slugOrId || w.slug === slugOrId || w.name === slugOrId);
11742
- if (!match) {
11839
+ const found = Object.entries(config.workspaces).find(([id, w]) => id === slugOrId || w.slug === slugOrId || w.name === slugOrId);
11840
+ if (!found) {
11743
11841
  const known = Object.values(config.workspaces).map((w) => w.slug).join(", ");
11744
11842
  throw new Error(`No authorized workspace matches "${slugOrId}". Known: ${known}.
11745
11843
  ` + "Run `zalify login` to authorize more workspaces.");
11746
11844
  }
11747
- config.activeWorkspaceId = match[0];
11845
+ config.activeWorkspaceId = found[0];
11748
11846
  writeConfig(config);
11749
- console.log(`✓ Active workspace: ${match[1].name} (${match[1].slug})`);
11847
+ console.log(`✓ Active workspace: ${found[1].name} (${found[1].slug})`);
11750
11848
  }
11751
11849
 
11752
11850
  // src/cli.ts
@@ -11790,8 +11888,12 @@ program2.command("whoami").description("Verify the stored key and show workspace
11790
11888
  await whoami();
11791
11889
  });
11792
11890
  var workspace = program2.command("workspace").description("List authorized workspaces or switch the active one");
11793
- workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(() => workspaceList());
11794
- workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action((slugOrId) => workspaceSet(slugOrId));
11891
+ workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(async () => {
11892
+ await workspaceList();
11893
+ });
11894
+ workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
11895
+ await workspaceSet(slugOrId);
11896
+ });
11795
11897
  var assets = program2.command("assets").description("Sync images with the Zalify asset library");
11796
11898
  assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
11797
11899
  await assetsPush(storeDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",