@zalify/cli 0.2.4 → 0.3.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 +138 -34
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11308,11 +11308,35 @@ import { homedir } from "node:os";
11308
11308
  import { join } from "node:path";
11309
11309
  var dir = join(homedir(), ".zalify");
11310
11310
  var file = join(dir, "config.json");
11311
+ function migrate(raw) {
11312
+ if (!raw || typeof raw !== "object")
11313
+ return null;
11314
+ const obj = raw;
11315
+ if (obj.version === 2 && obj.workspaces && obj.appUrl) {
11316
+ return obj;
11317
+ }
11318
+ if (typeof obj.key === "string" && typeof obj.workspaceId === "string") {
11319
+ return {
11320
+ version: 2,
11321
+ appUrl: obj.appUrl ?? "https://app.zalify.com",
11322
+ activeWorkspaceId: obj.workspaceId,
11323
+ workspaces: {
11324
+ [obj.workspaceId]: {
11325
+ key: obj.key,
11326
+ name: obj.workspaceName ?? "",
11327
+ slug: obj.workspaceSlug ?? "",
11328
+ createdAt: obj.createdAt ?? new Date().toISOString()
11329
+ }
11330
+ }
11331
+ };
11332
+ }
11333
+ return null;
11334
+ }
11311
11335
  function readConfig() {
11312
11336
  if (!existsSync(file))
11313
11337
  return null;
11314
11338
  try {
11315
- return JSON.parse(readFileSync(file, "utf8"));
11339
+ return migrate(JSON.parse(readFileSync(file, "utf8")));
11316
11340
  } catch {
11317
11341
  return null;
11318
11342
  }
@@ -11325,12 +11349,13 @@ function writeConfig(config) {
11325
11349
  function deleteConfig() {
11326
11350
  rmSync(file, { force: true });
11327
11351
  }
11328
- function requireConfig() {
11352
+ function requireActive() {
11329
11353
  const config = readConfig();
11330
- if (!config?.key) {
11354
+ if (!config || Object.keys(config.workspaces).length === 0) {
11331
11355
  throw new Error("Not logged in. Run `zalify login` first.");
11332
11356
  }
11333
- return config;
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 };
11334
11359
  }
11335
11360
 
11336
11361
  // src/auth.ts
@@ -11346,10 +11371,14 @@ function openBrowser(url) {
11346
11371
  detached: true
11347
11372
  }).unref();
11348
11373
  }
11374
+ function decodePayload(encoded) {
11375
+ const b64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
11376
+ return JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
11377
+ }
11349
11378
  async function login(options) {
11350
11379
  const base = (options.appUrl || readConfig()?.appUrl || DEFAULT_APP_URL).replace(/\/$/, "");
11351
11380
  const state = randomBytes(32).toString("hex");
11352
- const result = await new Promise((resolvePromise, reject) => {
11381
+ const payload = await new Promise((resolvePromise, reject) => {
11353
11382
  const timer = setTimeout(() => {
11354
11383
  server.close();
11355
11384
  server.closeAllConnections();
@@ -11387,22 +11416,38 @@ async function login(options) {
11387
11416
  });
11388
11417
  return;
11389
11418
  }
11390
- const key = url.searchParams.get("key");
11391
- if (!key) {
11392
- done("Login failed: no key in callback.", () => {
11419
+ const encoded = url.searchParams.get("payload");
11420
+ const legacyKey = url.searchParams.get("key");
11421
+ let parsed = null;
11422
+ if (encoded) {
11423
+ try {
11424
+ parsed = decodePayload(encoded);
11425
+ } catch {
11426
+ parsed = null;
11427
+ }
11428
+ } else if (legacyKey) {
11429
+ parsed = {
11430
+ keys: [
11431
+ {
11432
+ workspaceId: url.searchParams.get("workspace_id") ?? "",
11433
+ workspaceName: url.searchParams.get("workspace_name") ?? "",
11434
+ workspaceSlug: url.searchParams.get("workspace_slug") ?? "",
11435
+ key: legacyKey
11436
+ }
11437
+ ]
11438
+ };
11439
+ }
11440
+ if (!parsed || parsed.keys.length === 0) {
11441
+ done("Login failed: no keys in callback.", () => {
11393
11442
  teardown();
11394
- reject(new Error("Callback did not include a key."));
11443
+ reject(new Error("Callback did not include any keys."));
11395
11444
  });
11396
11445
  return;
11397
11446
  }
11398
- done("Logged in!", () => {
11447
+ const result = parsed;
11448
+ done(`Logged in to ${result.keys.length} workspace${result.keys.length === 1 ? "" : "s"}!`, () => {
11399
11449
  teardown();
11400
- resolvePromise({
11401
- key,
11402
- workspaceId: url.searchParams.get("workspace_id") ?? "",
11403
- workspaceName: url.searchParams.get("workspace_name") ?? "",
11404
- workspaceSlug: url.searchParams.get("workspace_slug") ?? ""
11405
- });
11450
+ resolvePromise(result);
11406
11451
  });
11407
11452
  });
11408
11453
  server.listen(0, "127.0.0.1", () => {
@@ -11416,13 +11461,40 @@ async function login(options) {
11416
11461
  });
11417
11462
  server.on("error", reject);
11418
11463
  });
11419
- writeConfig({ appUrl: base, ...result, createdAt: new Date().toISOString() });
11420
- console.log(`✓ Logged in to workspace "${result.workspaceName || result.workspaceId}" (key stored in ~/.zalify/config.json)`);
11464
+ const existing = readConfig();
11465
+ const config = {
11466
+ version: 2,
11467
+ appUrl: base,
11468
+ activeWorkspaceId: existing?.activeWorkspaceId ?? null,
11469
+ workspaces: { ...existing?.appUrl === base ? existing?.workspaces : {} }
11470
+ };
11471
+ const now = new Date().toISOString();
11472
+ for (const k of payload.keys) {
11473
+ config.workspaces[k.workspaceId] = {
11474
+ key: k.key,
11475
+ name: k.workspaceName,
11476
+ slug: k.workspaceSlug,
11477
+ createdAt: now
11478
+ };
11479
+ }
11480
+ if (!config.activeWorkspaceId || !config.workspaces[config.activeWorkspaceId]) {
11481
+ config.activeWorkspaceId = payload.keys[0].workspaceId;
11482
+ }
11483
+ 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>`.");
11421
11493
  }
11422
11494
  function logout() {
11423
11495
  deleteConfig();
11424
11496
  console.log("✓ Logged out (local credentials deleted).");
11425
- console.log(" To revoke the key itself: app.zalify.com Settings → Developer.");
11497
+ console.log(" To revoke keys: each workspace's Settings → Developer on app.zalify.com.");
11426
11498
  }
11427
11499
  function ago(iso) {
11428
11500
  if (!iso)
@@ -11439,12 +11511,12 @@ function ago(iso) {
11439
11511
  return `${Math.round(hours / 24)}d ago`;
11440
11512
  }
11441
11513
  async function whoami() {
11442
- const config = requireConfig();
11514
+ const { appUrl, workspaceId, entry, config } = requireActive();
11443
11515
  let live = null;
11444
11516
  let liveError = null;
11445
11517
  try {
11446
- const res = await fetch(`${config.appUrl}/api/cli/whoami`, {
11447
- headers: { Authorization: `Bearer ${config.key}` }
11518
+ const res = await fetch(`${appUrl}/api/cli/whoami`, {
11519
+ headers: { Authorization: `Bearer ${entry.key}` }
11448
11520
  });
11449
11521
  if (res.ok) {
11450
11522
  live = await res.json();
@@ -11457,18 +11529,19 @@ async function whoami() {
11457
11529
  liveError = "could not reach the server (offline?)";
11458
11530
  }
11459
11531
  const ws = live?.workspace;
11460
- console.log(`workspace ${ws?.name ?? config.workspaceName ?? "(unknown)"} (${ws?.slug ?? config.workspaceSlug ?? "?"}) [${ws?.id ?? config.workspaceId}]`);
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\`` : ""));
11461
11534
  if (live) {
11462
11535
  console.log(`plan ${live.plan.name} (${live.plan.type})`);
11463
11536
  if (live.key.createdByName) {
11464
11537
  console.log(`user ${live.key.createdByName}`);
11465
11538
  }
11466
- console.log(`key ${live.key.name ?? config.key.slice(0, 16) + "…"} — created ${live.key.createdAt ? new Date(live.key.createdAt).toISOString().slice(0, 10) : "?"}, last used ${ago(live.key.lastRequest)}`);
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)}`);
11467
11540
  } else {
11468
- console.log(`key ${config.key.slice(0, 16)}…`);
11541
+ console.log(`key ${entry.key.slice(0, 16)}…`);
11469
11542
  }
11470
- const slug = ws?.slug ?? config.workspaceSlug;
11471
- console.log(`app ${slug ? `${config.appUrl}/store/${slug}` : config.appUrl}`);
11543
+ const slug = ws?.slug ?? entry.slug;
11544
+ console.log(`app ${slug ? `${appUrl}/store/${slug}` : appUrl}`);
11472
11545
  console.log(live ? "status ✓ key verified" : `status ! not verified — ${liveError}`);
11473
11546
  }
11474
11547
 
@@ -11513,23 +11586,23 @@ async function assetsPull(storeDir) {
11513
11586
  console.log(`Done: ${pulled} downloaded, ${Object.keys(index).length - pulled} already local.`);
11514
11587
  }
11515
11588
  async function assetsPush(storeDir) {
11516
- const config = requireConfig();
11589
+ const { appUrl, workspaceId, entry } = requireActive();
11517
11590
  const imagesDir = imagesDirFor(storeDir);
11518
11591
  const indexPath = join2(imagesDir, "assets.json");
11519
11592
  const index = readIndex(imagesDir);
11520
11593
  async function api(path9, body) {
11521
- const res = await fetch(`${config.appUrl}${path9}`, {
11594
+ const res = await fetch(`${appUrl}${path9}`, {
11522
11595
  method: "POST",
11523
11596
  headers: {
11524
11597
  "Content-Type": "application/json",
11525
- Authorization: `Bearer ${config.key}`
11598
+ Authorization: `Bearer ${entry.key}`
11526
11599
  },
11527
11600
  body: JSON.stringify(body)
11528
11601
  });
11529
11602
  const json = await res.json().catch(() => ({}));
11530
11603
  if (!res.ok) {
11531
11604
  if (json.code === "UPGRADE_REQUIRED") {
11532
- const billingUrl = config.workspaceSlug ? `${config.appUrl}/store/${config.workspaceSlug}/settings/billing` : `${config.appUrl} (Settings → Billing)`;
11605
+ const billingUrl = entry.slug ? `${appUrl}/store/${entry.slug}/settings/billing` : `${appUrl} (Settings → Billing)`;
11533
11606
  throw new Error(`${json.error ?? "This feature requires a paid plan."}
11534
11607
  ` + ` Upgrade this workspace: ${billingUrl}`);
11535
11608
  }
@@ -11545,11 +11618,11 @@ async function assetsPush(storeDir) {
11545
11618
  console.log("Everything already pushed.");
11546
11619
  return;
11547
11620
  }
11548
- console.log(`Pushing ${files.length} file(s) to "${config.workspaceName || config.workspaceId}"`);
11621
+ console.log(`Pushing ${files.length} file(s) to "${entry.name || workspaceId}"`);
11549
11622
  for (let i = 0;i < files.length; i += BATCH) {
11550
11623
  const batch = files.slice(i, i + BATCH);
11551
11624
  const { uploads } = await api("/api/assets/presign", {
11552
- workspaceId: config.workspaceId,
11625
+ workspaceId,
11553
11626
  folderId: process.env.ZALIFY_FOLDER_ID ?? null,
11554
11627
  files: batch.map((f) => ({
11555
11628
  id: f.name,
@@ -11589,7 +11662,7 @@ async function assetsPush(storeDir) {
11589
11662
  }
11590
11663
  if (toFinalize.length) {
11591
11664
  const { assets, pending } = await api("/api/assets/finalize", {
11592
- workspaceId: config.workspaceId,
11665
+ workspaceId,
11593
11666
  items: toFinalize.map(({ file: file2, up }) => ({
11594
11667
  assetId: up.assetId,
11595
11668
  checksum: file2.checksum,
@@ -11648,6 +11721,34 @@ Try manually: ${cmd} ${args.join(" ")}`);
11648
11721
  ✓ Done${updated ? ` (version: ${updated})` : ""}`);
11649
11722
  }
11650
11723
 
11724
+ // src/workspace.ts
11725
+ function workspaceList() {
11726
+ const config = readConfig();
11727
+ const entries = Object.entries(config?.workspaces ?? {});
11728
+ if (!config || entries.length === 0) {
11729
+ throw new Error("Not logged in. Run `zalify login` first.");
11730
+ }
11731
+ for (const [id, w] of entries) {
11732
+ const marker = id === config.activeWorkspaceId ? "* " : " ";
11733
+ console.log(`${marker}${w.slug.padEnd(36)} ${w.name} [${id}]`);
11734
+ }
11735
+ }
11736
+ function workspaceSet(slugOrId) {
11737
+ const config = readConfig();
11738
+ if (!config || Object.keys(config.workspaces).length === 0) {
11739
+ throw new Error("Not logged in. Run `zalify login` first.");
11740
+ }
11741
+ const match = Object.entries(config.workspaces).find(([id, w]) => id === slugOrId || w.slug === slugOrId || w.name === slugOrId);
11742
+ if (!match) {
11743
+ const known = Object.values(config.workspaces).map((w) => w.slug).join(", ");
11744
+ throw new Error(`No authorized workspace matches "${slugOrId}". Known: ${known}.
11745
+ ` + "Run `zalify login` to authorize more workspaces.");
11746
+ }
11747
+ config.activeWorkspaceId = match[0];
11748
+ writeConfig(config);
11749
+ console.log(`✓ Active workspace: ${match[1].name} (${match[1].slug})`);
11750
+ }
11751
+
11651
11752
  // src/cli.ts
11652
11753
  var __dirname4 = dirname(fileURLToPath3(import.meta.url));
11653
11754
  var require2 = createRequire2(import.meta.url);
@@ -11688,6 +11789,9 @@ program2.command("logout").description("Delete the stored CLI credentials").acti
11688
11789
  program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
11689
11790
  await whoami();
11690
11791
  });
11792
+ 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));
11691
11795
  var assets = program2.command("assets").description("Sync images with the Zalify asset library");
11692
11796
  assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
11693
11797
  await assetsPush(storeDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",