@zalify/cli 0.2.3 → 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 +139 -33
  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,17 +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
- console.log(`app ${config.appUrl}`);
11543
+ const slug = ws?.slug ?? entry.slug;
11544
+ console.log(`app ${slug ? `${appUrl}/store/${slug}` : appUrl}`);
11471
11545
  console.log(live ? "status ✓ key verified" : `status ! not verified — ${liveError}`);
11472
11546
  }
11473
11547
 
@@ -11512,24 +11586,25 @@ async function assetsPull(storeDir) {
11512
11586
  console.log(`Done: ${pulled} downloaded, ${Object.keys(index).length - pulled} already local.`);
11513
11587
  }
11514
11588
  async function assetsPush(storeDir) {
11515
- const config = requireConfig();
11589
+ const { appUrl, workspaceId, entry } = requireActive();
11516
11590
  const imagesDir = imagesDirFor(storeDir);
11517
11591
  const indexPath = join2(imagesDir, "assets.json");
11518
11592
  const index = readIndex(imagesDir);
11519
11593
  async function api(path9, body) {
11520
- const res = await fetch(`${config.appUrl}${path9}`, {
11594
+ const res = await fetch(`${appUrl}${path9}`, {
11521
11595
  method: "POST",
11522
11596
  headers: {
11523
11597
  "Content-Type": "application/json",
11524
- Authorization: `Bearer ${config.key}`
11598
+ Authorization: `Bearer ${entry.key}`
11525
11599
  },
11526
11600
  body: JSON.stringify(body)
11527
11601
  });
11528
11602
  const json = await res.json().catch(() => ({}));
11529
11603
  if (!res.ok) {
11530
11604
  if (json.code === "UPGRADE_REQUIRED") {
11605
+ const billingUrl = entry.slug ? `${appUrl}/store/${entry.slug}/settings/billing` : `${appUrl} (Settings → Billing)`;
11531
11606
  throw new Error(`${json.error ?? "This feature requires a paid plan."}
11532
- ` + ` Upgrade this workspace at ${config.appUrl} (Settings → Billing).`);
11607
+ ` + ` Upgrade this workspace: ${billingUrl}`);
11533
11608
  }
11534
11609
  throw new Error(`${path9} ${res.status}: ${JSON.stringify(json)}`);
11535
11610
  }
@@ -11543,11 +11618,11 @@ async function assetsPush(storeDir) {
11543
11618
  console.log("Everything already pushed.");
11544
11619
  return;
11545
11620
  }
11546
- console.log(`Pushing ${files.length} file(s) to "${config.workspaceName || config.workspaceId}"`);
11621
+ console.log(`Pushing ${files.length} file(s) to "${entry.name || workspaceId}"`);
11547
11622
  for (let i = 0;i < files.length; i += BATCH) {
11548
11623
  const batch = files.slice(i, i + BATCH);
11549
11624
  const { uploads } = await api("/api/assets/presign", {
11550
- workspaceId: config.workspaceId,
11625
+ workspaceId,
11551
11626
  folderId: process.env.ZALIFY_FOLDER_ID ?? null,
11552
11627
  files: batch.map((f) => ({
11553
11628
  id: f.name,
@@ -11587,7 +11662,7 @@ async function assetsPush(storeDir) {
11587
11662
  }
11588
11663
  if (toFinalize.length) {
11589
11664
  const { assets, pending } = await api("/api/assets/finalize", {
11590
- workspaceId: config.workspaceId,
11665
+ workspaceId,
11591
11666
  items: toFinalize.map(({ file: file2, up }) => ({
11592
11667
  assetId: up.assetId,
11593
11668
  checksum: file2.checksum,
@@ -11646,6 +11721,34 @@ Try manually: ${cmd} ${args.join(" ")}`);
11646
11721
  ✓ Done${updated ? ` (version: ${updated})` : ""}`);
11647
11722
  }
11648
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
+
11649
11752
  // src/cli.ts
11650
11753
  var __dirname4 = dirname(fileURLToPath3(import.meta.url));
11651
11754
  var require2 = createRequire2(import.meta.url);
@@ -11686,6 +11789,9 @@ program2.command("logout").description("Delete the stored CLI credentials").acti
11686
11789
  program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
11687
11790
  await whoami();
11688
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));
11689
11795
  var assets = program2.command("assets").description("Sync images with the Zalify asset library");
11690
11796
  assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
11691
11797
  await assetsPush(storeDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.2.3",
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",