@vosjs/cli 0.85.0 → 0.86.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.
@@ -10,7 +10,7 @@ import {
10
10
  reencodeStill,
11
11
  renderStill,
12
12
  videoFormat
13
- } from "./chunk-Q47FUCYN.js";
13
+ } from "./chunk-2LAXJZCG.js";
14
14
  import {
15
15
  PREV_DOC_NAME,
16
16
  PasteFileRefusal,
@@ -46,47 +46,62 @@ import {
46
46
  uploadDocRefs,
47
47
  uploadManifest,
48
48
  writeJson
49
- } from "./chunk-M5ZBQFPG.js";
49
+ } from "./chunk-V3RUNUNP.js";
50
50
  import {
51
+ ACCOUNT_ENV,
51
52
  CREDENTIALS_PATH,
53
+ DEFAULT_STORE,
52
54
  MEDIA_HEAD_BYTES,
53
55
  SYNC_STATE_NAME,
54
56
  UsageError,
57
+ accountFrom,
58
+ accountLabel,
59
+ activate,
55
60
  apiError,
56
61
  apiJson,
57
62
  bearerFor,
58
63
  clientId,
59
64
  containerMismatch,
65
+ credentialFlags,
60
66
  deriveSlug,
61
67
  detailLine,
68
+ findStored,
62
69
  formatChanges,
63
70
  hasFlag,
64
71
  hostedAssetId,
72
+ isActive,
73
+ lastCredential,
74
+ listStored,
65
75
  localFile,
66
76
  manifestRefs,
67
77
  mintAt,
68
78
  nameForType,
69
79
  numFlag2 as numFlag,
80
+ originOf,
70
81
  parseArgs2 as parseArgs,
71
82
  parseVosId,
72
83
  platformOrigin,
73
84
  prepareOutput,
74
85
  programAssets,
75
86
  promptHint,
87
+ readActive,
76
88
  readSyncState,
77
89
  refuseSymlink,
90
+ removeAll,
91
+ removeStored,
78
92
  requireCredential,
79
93
  resolveCredential,
80
94
  resolveMediaType,
81
95
  serverWarnings,
82
96
  sniffMediaType,
83
97
  strFlag,
98
+ switchTarget,
84
99
  writeCredential,
85
100
  writeFileSafe,
86
101
  writeOutput,
87
102
  writeOutputSync,
88
103
  writeSyncState
89
- } from "./chunk-EPSYIF2S.js";
104
+ } from "./chunk-PZP76YJI.js";
90
105
  import {
91
106
  deadLine,
92
107
  deadWarns
@@ -4856,6 +4871,7 @@ async function deliverTake(browser, dir, opts) {
4856
4871
 
4857
4872
  // src/plugin/login.ts
4858
4873
  import { hostname } from "os";
4874
+ var storeWith = (store) => store ?? ((key, origin, account) => writeCredential(key, origin, void 0, account));
4859
4875
  var LoginUnsupportedError = class extends Error {
4860
4876
  constructor() {
4861
4877
  super("this origin does not support browser login");
@@ -4932,12 +4948,14 @@ async function browserLogin(origin, r, opts) {
4932
4948
  continue;
4933
4949
  }
4934
4950
  if (res.status === 200 && status === "ok") {
4935
- const path = (opts.store ?? writeCredential)(
4951
+ const account = accountFrom(res.body.user);
4952
+ const path = storeWith(opts.store)(
4936
4953
  res.body.key,
4937
- origin
4954
+ origin,
4955
+ account
4938
4956
  );
4939
4957
  const user = typeof res.body.user === "object" && res.body.user !== null ? res.body.user.name ?? "" : "";
4940
- return { path, user };
4958
+ return { path, user, account };
4941
4959
  }
4942
4960
  if (res.status === 429) {
4943
4961
  if (status === "key_limit" && !capNoted) {
@@ -4975,16 +4993,19 @@ async function handoffLogin(url, r, opts = {}) {
4975
4993
  body: { token: parsed.token, hostname: hostname().slice(0, 64) }
4976
4994
  });
4977
4995
  if (res.status === 200 && res.body.status === "ok") {
4978
- const path = (opts.store ?? writeCredential)(
4996
+ const account = accountFrom(res.body.user);
4997
+ const path = storeWith(opts.store)(
4979
4998
  res.body.key,
4980
- parsed.origin
4999
+ parsed.origin,
5000
+ account
4981
5001
  );
4982
5002
  const user = typeof res.body.user === "object" && res.body.user !== null ? res.body.user.name ?? "" : "";
4983
5003
  return {
4984
5004
  path,
4985
5005
  user,
4986
5006
  keyName: String(res.body.keyName ?? ""),
4987
- origin: parsed.origin
5007
+ origin: parsed.origin,
5008
+ account
4988
5009
  };
4989
5010
  }
4990
5011
  if (res.status === 404 && !res.body.status) {
@@ -7603,7 +7624,7 @@ async function cmdList(argv) {
7603
7624
  origin: strFlag(flags, "origin"),
7604
7625
  api: strFlag(flags, "api")
7605
7626
  });
7606
- const key = requireCredential(strFlag(flags, "key"));
7627
+ const key = requireCredential(credentialFlags(flags));
7607
7628
  const rows = await listFolders(origin, key);
7608
7629
  if (r.json) {
7609
7630
  r.done({ folders: rows }, "");
@@ -7628,7 +7649,7 @@ async function cmdCreate(argv) {
7628
7649
  origin: strFlag(flags, "origin"),
7629
7650
  api: strFlag(flags, "api")
7630
7651
  });
7631
- const key = requireCredential(strFlag(flags, "key"));
7652
+ const key = requireCredential(credentialFlags(flags));
7632
7653
  const parentRef = strFlag(flags, "parent");
7633
7654
  let parentId;
7634
7655
  if (parentRef) {
@@ -7663,7 +7684,7 @@ async function cmdMove(argv) {
7663
7684
  origin: strFlag(flags, "origin"),
7664
7685
  api: strFlag(flags, "api")
7665
7686
  });
7666
- const key = requireCredential(strFlag(flags, "key"));
7687
+ const key = requireCredential(credentialFlags(flags));
7667
7688
  const folderId = toRef === "none" ? null : resolveFolder(await listFolders(origin, key), toRef).id;
7668
7689
  let failed = 0;
7669
7690
  const moved = [];
@@ -7723,7 +7744,7 @@ async function cmdPull(argv) {
7723
7744
  origin: strFlag(flags, "origin"),
7724
7745
  api: strFlag(flags, "api")
7725
7746
  });
7726
- const key = requireCredential(strFlag(flags, "key"));
7747
+ const key = requireCredential(credentialFlags(flags));
7727
7748
  const folder = resolveFolder(await listFolders(origin, key), ref);
7728
7749
  const res = await apiJson(origin, `/api/folders/${folder.id}`, { key });
7729
7750
  if (res.status !== 200)
@@ -7932,9 +7953,10 @@ function hostOf(origin) {
7932
7953
 
7933
7954
  // src/plugin/sync.ts
7934
7955
  function apiContext(flags) {
7956
+ const origin = platformOrigin(flags);
7935
7957
  return {
7936
- origin: platformOrigin(flags),
7937
- key: requireCredential(flags.key, platformOrigin(flags))
7958
+ origin,
7959
+ key: requireCredential({ key: flags.key, account: flags.account, origin })
7938
7960
  };
7939
7961
  }
7940
7962
  async function api(ctx, path, init) {
@@ -8944,7 +8966,7 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8944
8966
  );
8945
8967
  return EXIT_OK;
8946
8968
  }
8947
- const key = requireCredential(strFlag(flags, "key"));
8969
+ const key = requireCredential(credentialFlags(flags));
8948
8970
  void flags.yes;
8949
8971
  const files = await collectFiles(config, programDoc, dir, r);
8950
8972
  await files.upload({ origin, key });
@@ -9159,7 +9181,7 @@ async function cmdPullProgram(argv) {
9159
9181
  `no base version in ${dir}/vos.json \u2014 pass --since <versionId>`
9160
9182
  );
9161
9183
  }
9162
- const key = requireCredential(strFlag(flags, "key"));
9184
+ const key = requireCredential(credentialFlags(flags));
9163
9185
  const res = await apiJson(
9164
9186
  origin,
9165
9187
  `/api/vos/${vosId}/changes?since=${encodeURIComponent(since)}`,
@@ -9258,16 +9280,21 @@ async function cmdLogin(argv) {
9258
9280
  });
9259
9281
  const handoffUrl = strFlag(flags, "handoff");
9260
9282
  if (handoffUrl) {
9283
+ const before = await nameActiveCredential(
9284
+ parseHandoffUrl(handoffUrl)?.origin ?? origin
9285
+ );
9261
9286
  const done = await handoffLogin(handoffUrl, r);
9262
9287
  r.done(
9263
9288
  {
9264
9289
  path: done.path,
9265
9290
  origin: done.origin,
9266
9291
  user: done.user,
9292
+ account: done.account,
9267
9293
  keyName: done.keyName,
9294
+ ...setAsideFacts(before, done.account),
9268
9295
  next_step: "the link is spent; vos push when you want the work hosted"
9269
9296
  },
9270
- `Signed in${done.user ? ` as ${done.user}` : ""} \u2014 key "${done.keyName}" stored at ${done.path} (used by every vos platform verb). The link is spent.`
9297
+ `Signed in${signedAs(done.user, done.account)} \u2014 key "${done.keyName}" stored at ${done.path} (used by every vos platform verb). The link is spent.${setAsideLine(before, done.account)}`
9271
9298
  );
9272
9299
  return EXIT_OK;
9273
9300
  }
@@ -9275,13 +9302,14 @@ async function cmdLogin(argv) {
9275
9302
  if (explicit) return storeKey(explicit, origin, r);
9276
9303
  try {
9277
9304
  const openBrowser = process.stdout.isTTY === true && !process.env.SSH_CONNECTION && !process.env.SSH_TTY && flags.json !== true && flags["no-browser"] !== true;
9278
- const { path, user } = await browserLogin(origin, r, {
9305
+ const before = await nameActiveCredential(origin);
9306
+ const { path, user, account } = await browserLogin(origin, r, {
9279
9307
  label: strFlag(flags, "label"),
9280
9308
  openBrowser
9281
9309
  });
9282
9310
  r.done(
9283
- { path, origin, user },
9284
- `Signed in${user ? ` as ${user}` : ""} \u2014 key stored at ${path} (used by every vos platform verb)`
9311
+ { path, origin, user, account, ...setAsideFacts(before, account) },
9312
+ `Signed in${signedAs(user, account)} \u2014 key stored at ${path} (used by every vos platform verb).${setAsideLine(before, account)}`
9285
9313
  );
9286
9314
  return EXIT_OK;
9287
9315
  } catch (e) {
@@ -9299,17 +9327,57 @@ async function cmdLogin(argv) {
9299
9327
  return storeKey(key, origin, r);
9300
9328
  }
9301
9329
  async function storeKey(key, origin, r) {
9302
- const probe3 = await apiJson(origin, "/api/folders", { key });
9303
- if (probe3.status === 401 || probe3.status === 403) {
9304
- throw new Error(apiError("validate key", probe3));
9330
+ const who = await apiJson(origin, "/api/user/whoami", { key });
9331
+ if (who.status === 401 || who.status === 403) {
9332
+ throw new Error(apiError("validate key", who));
9305
9333
  }
9306
- const path = writeCredential(key, origin);
9334
+ let account = null;
9335
+ if (who.status === 200) account = accountFrom(who.body.user);
9336
+ else {
9337
+ const probe3 = await apiJson(origin, "/api/folders", { key });
9338
+ if (probe3.status === 401 || probe3.status === 403) {
9339
+ throw new Error(apiError("validate key", probe3));
9340
+ }
9341
+ }
9342
+ const before = await nameActiveCredential(origin);
9343
+ const path = writeCredential(key, origin, void 0, account);
9307
9344
  r.done(
9308
- { path, origin },
9309
- `Signed in \u2014 key stored at ${path} (used by every vos platform verb)`
9345
+ { path, origin, account, ...setAsideFacts(before, account) },
9346
+ `Signed in${signedAs("", account)} \u2014 key stored at ${path} (used by every vos platform verb).${setAsideLine(before, account)}`
9310
9347
  );
9311
9348
  return EXIT_OK;
9312
9349
  }
9350
+ function signedAs(user, account) {
9351
+ if (account) return ` as ${accountLabel(account)}`;
9352
+ return user ? ` as ${user}` : "";
9353
+ }
9354
+ async function nameActiveCredential(origin) {
9355
+ const active = readActive(DEFAULT_STORE);
9356
+ if (!active || active.origin !== originOf(origin)) return active;
9357
+ if (active.account) return active;
9358
+ try {
9359
+ const who = await apiJson(origin, "/api/user/whoami", { key: active.key });
9360
+ const account = who.status === 200 ? accountFrom(who.body.user) : null;
9361
+ if (!account) return active;
9362
+ const named = { ...active, account };
9363
+ activate(DEFAULT_STORE, named);
9364
+ return named;
9365
+ } catch {
9366
+ return active;
9367
+ }
9368
+ }
9369
+ function setAsideFacts(before, account) {
9370
+ if (!before || account && before.account?.id === account.id) return {};
9371
+ return before.account ? { setAside: before.account } : { replaced: true };
9372
+ }
9373
+ function setAsideLine(before, account) {
9374
+ const facts = setAsideFacts(before, account);
9375
+ if (facts.setAside)
9376
+ return ` ${accountLabel(facts.setAside)} stays stored; vos switch ${facts.setAside.handle ?? facts.setAside.id} when you need it.`;
9377
+ if (facts.replaced)
9378
+ return " The credential stored before had no name on file and was replaced.";
9379
+ return "";
9380
+ }
9313
9381
  function isTakeDir(target) {
9314
9382
  return directoryKind(target) === "take";
9315
9383
  }
@@ -9625,7 +9693,7 @@ async function cmdRename(argv) {
9625
9693
  origin: strFlag(flags, "origin"),
9626
9694
  api: strFlag(flags, "api")
9627
9695
  });
9628
- const key = requireCredential(strFlag(flags, "key"));
9696
+ const key = requireCredential(credentialFlags(flags));
9629
9697
  const res = await apiJson(origin, `/api/assets/${assetId}`, {
9630
9698
  method: "PATCH",
9631
9699
  key,
@@ -9648,7 +9716,7 @@ async function cmdPush(argv) {
9648
9716
  origin: strFlag(flags, "origin"),
9649
9717
  api: strFlag(flags, "api")
9650
9718
  });
9651
- const key = requireCredential(strFlag(flags, "key"));
9719
+ const key = requireCredential(credentialFlags(flags));
9652
9720
  const folderRef = strFlag(flags, "folder");
9653
9721
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9654
9722
  const uploaded = [];
@@ -9729,7 +9797,7 @@ async function cmdImport(argv) {
9729
9797
  origin: strFlag(flags, "origin"),
9730
9798
  api: strFlag(flags, "api")
9731
9799
  });
9732
- const key = requireCredential(strFlag(flags, "key"));
9800
+ const key = requireCredential(credentialFlags(flags));
9733
9801
  const folderRef = strFlag(flags, "folder");
9734
9802
  const folder = folderRef ? resolveFolder(await listFolders(origin, key), folderRef) : null;
9735
9803
  const refused = fetchRefusal(url);
@@ -9813,7 +9881,7 @@ async function cmdList2(argv) {
9813
9881
  origin: strFlag(flags, "origin"),
9814
9882
  api: strFlag(flags, "api")
9815
9883
  });
9816
- const key = requireCredential(strFlag(flags, "key"));
9884
+ const key = requireCredential(credentialFlags(flags));
9817
9885
  const params = new URLSearchParams({
9818
9886
  limit: String(Math.min(200, Math.max(1, numFlag(flags, "limit", 50))))
9819
9887
  });
@@ -9846,7 +9914,7 @@ async function cmdUsage(argv) {
9846
9914
  origin: strFlag(flags, "origin"),
9847
9915
  api: strFlag(flags, "api")
9848
9916
  });
9849
- const key = requireCredential(strFlag(flags, "key"));
9917
+ const key = requireCredential(credentialFlags(flags));
9850
9918
  const res = await apiJson(origin, "/api/user/usage", { key });
9851
9919
  if (res.status !== 200) throw new Error(apiError("read usage", res));
9852
9920
  const usage = res.body.usage ?? {};
@@ -9873,7 +9941,7 @@ async function cmdWhy(argv) {
9873
9941
  origin: strFlag(flags, "origin"),
9874
9942
  api: strFlag(flags, "api")
9875
9943
  });
9876
- const key = requireCredential(strFlag(flags, "key"));
9944
+ const key = requireCredential(credentialFlags(flags));
9877
9945
  const res = await apiJson(origin, `/api/assets/${assetId}`, { key });
9878
9946
  if (res.status !== 200) throw new Error(apiError(`read ${assetId}`, res));
9879
9947
  const asset = hostedFile(res.body.asset);
@@ -9968,7 +10036,7 @@ function context(flags) {
9968
10036
  origin: strFlag(flags, "origin"),
9969
10037
  api: strFlag(flags, "api")
9970
10038
  }).replace(/\/+$/, "");
9971
- return { origin, key: requireCredential(strFlag(flags, "key")) };
10039
+ return { origin, key: requireCredential(credentialFlags(flags)) };
9972
10040
  }
9973
10041
  async function readTrash(ctx) {
9974
10042
  const res = await apiJson(ctx.origin, "/api/trash", { key: ctx.key });
@@ -10096,7 +10164,7 @@ async function cmdDelete(argv) {
10096
10164
  origin: strFlag(flags, "origin"),
10097
10165
  api: strFlag(flags, "api")
10098
10166
  }).replace(/\/+$/, "");
10099
- const key = requireCredential(strFlag(flags, "key"));
10167
+ const key = requireCredential(credentialFlags(flags));
10100
10168
  const dir = existsSync16(target) && statSync3(target).isDirectory() ? target : null;
10101
10169
  const vosId = dir ? readSyncState(dir)?.vosId : parseVosId(target);
10102
10170
  if (!vosId)
@@ -12149,7 +12217,7 @@ async function cmdPush2(argv) {
12149
12217
  origin: strFlag(flags, "origin"),
12150
12218
  api: strFlag(flags, "api")
12151
12219
  });
12152
- const key = requireCredential(strFlag(flags, "key"));
12220
+ const key = requireCredential(credentialFlags(flags));
12153
12221
  if (assetId) {
12154
12222
  const res = await apiJson(origin, `/api/assets/${assetId}/file`, {
12155
12223
  method: "PUT",
@@ -18021,7 +18089,6 @@ import {
18021
18089
  existsSync as existsSync30,
18022
18090
  mkdirSync as mkdirSync6,
18023
18091
  readFileSync as readFileSync12,
18024
- rmSync as rmSync3,
18025
18092
  writeFileSync as writeFileSync6
18026
18093
  } from "fs";
18027
18094
  import { homedir as homedir2 } from "os";
@@ -18115,7 +18182,7 @@ var RULES_BODY = `## Product videos (vosso)
18115
18182
  - Hand back: vos open take serves the take into the studio at vos.so, doc.json intact
18116
18183
  - Local by default: vos push only when asked for a link, the shelf or a cloud render
18117
18184
  - Deleting: vos delete moves a vos to Trash and vos restore brings it back; if you deleted by mistake, restore it and say so. Only the person can delete forever, at vos.so/app/trash
18118
- - Credentials: VOS_API_KEY, then ~/.config/vos/credentials, then vos login; never print one
18185
+ - Credentials: VOS_API_KEY, then the account in use at ~/.config/vos/credentials (vos whoami says which; vos switch <handle> changes it), then vos login; never print one
18119
18186
  - Reference: https://vos.so/llms-full.txt`;
18120
18187
  var RULES_BLOCK = `${RULES_BEGIN}
18121
18188
  ${RULES_BODY}
@@ -18195,6 +18262,18 @@ var NODE_MIN_MAJOR = 18;
18195
18262
  function major(version) {
18196
18263
  return parseInt(version.split(".")[0] ?? "0", 10);
18197
18264
  }
18265
+ function credentialLine(facts) {
18266
+ const accounts = facts.accounts ?? [];
18267
+ const active = accounts.find((a) => a.active);
18268
+ const others = accounts.filter((a) => !a.active).map((a) => a.label);
18269
+ if (active) {
18270
+ return `${active.label} \u2713${others.length ? ` (also ${others.join(", ")})` : ""}`;
18271
+ }
18272
+ if (facts.credential) {
18273
+ return `present \u2713${others.length ? ` (stored: ${others.join(", ")}; vos switch to use one)` : ""}`;
18274
+ }
18275
+ return `absent (vos login when you need hosting)${others.length ? `; stored: ${others.join(", ")}, vos switch to use one` : ""}`;
18276
+ }
18198
18277
  function doctorReport(facts) {
18199
18278
  const lines = [];
18200
18279
  const nodeOk = major(facts.node) >= NODE_MIN_MAJOR;
@@ -18220,9 +18299,7 @@ function doctorReport(facts) {
18220
18299
  } else {
18221
18300
  lines.push("skills none \u2717 run: npx vos setup");
18222
18301
  }
18223
- lines.push(
18224
- `credential ${facts.credential ? "present \u2713" : "absent (vos login when you need hosting)"}`
18225
- );
18302
+ lines.push(`credential ${credentialLine(facts)}`);
18226
18303
  if (facts.devServer) {
18227
18304
  const d = facts.devServer;
18228
18305
  lines.push(
@@ -18280,6 +18357,7 @@ var BOOLEAN_FLAGS15 = /* @__PURE__ */ new Set([
18280
18357
  "no-rules",
18281
18358
  "no-browser"
18282
18359
  ]);
18360
+ var LOGOUT_FLAGS = /* @__PURE__ */ new Set([...BOOLEAN_FLAGS15, "all"]);
18283
18361
  var SKILLS_SOURCE = "vosjs/skills";
18284
18362
  var SKILLS_CLI_VERSION = "1.7.2";
18285
18363
  function skillsCatalogAt(version) {
@@ -18349,10 +18427,24 @@ async function doctorFacts(opts) {
18349
18427
  browser: probe2(),
18350
18428
  ffmpeg: ffmpegVersion(),
18351
18429
  skills: skillsFacts(opts.homes),
18352
- credential: resolveCredential() !== null,
18430
+ credential: safeResolve() !== null,
18431
+ accounts: storedAccounts(platformOrigin()),
18353
18432
  ...opts.url ? { devServer: await devServerFact(opts.url) } : {}
18354
18433
  };
18355
18434
  }
18435
+ function safeResolve() {
18436
+ try {
18437
+ return resolveCredential();
18438
+ } catch {
18439
+ return null;
18440
+ }
18441
+ }
18442
+ function storedAccounts(origin, store = DEFAULT_STORE) {
18443
+ return listStored(store, origin).map((c) => ({
18444
+ label: accountLabel(c.account),
18445
+ active: isActive(c, store)
18446
+ }));
18447
+ }
18356
18448
  async function cmdSetup(argv) {
18357
18449
  const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18358
18450
  const r = reporterFor(flags);
@@ -18512,29 +18604,42 @@ async function cmdWhoami(argv) {
18512
18604
  origin: strFlag(flags, "origin"),
18513
18605
  api: strFlag(flags, "api")
18514
18606
  });
18515
- const key = resolveCredential(strFlag(flags, "key"));
18607
+ const stored = storedAccounts(origin);
18608
+ const others = stored.filter((a) => !a.active).map((a) => a.label);
18609
+ const alsoStored = others.length ? ` Also stored: ${others.join(", ")}; vos switch <name> to use one.` : "";
18610
+ const key = resolveCredential({
18611
+ key: strFlag(flags, "key"),
18612
+ account: strFlag(flags, "account"),
18613
+ origin
18614
+ });
18516
18615
  if (!key) {
18517
18616
  r.done(
18518
18617
  {
18519
18618
  signedIn: false,
18520
18619
  origin,
18620
+ accounts: stored,
18521
18621
  next_step: "vos login (only needed for hosting, sharing or cloud rendering)"
18522
18622
  },
18523
- `Not signed in to ${origin}. Recording and rendering need no account; vos login when you want a link.`
18623
+ `Not signed in to ${origin}. Recording and rendering need no account; vos login when you want a link.${alsoStored}`
18524
18624
  );
18525
18625
  return EXIT_ERROR;
18526
18626
  }
18627
+ const resolved = lastCredential();
18628
+ const via = resolved?.via ?? "stored";
18629
+ const viaLine = via === "flag" ? " (the key passed as --key)" : via === "env" ? " (from VOS_API_KEY, which outranks the stored credential)" : via === "account-flag" ? " (--account)" : via === "account-env" ? ` (${ACCOUNT_ENV})` : "";
18527
18630
  const res = await apiJson(origin, "/api/user/whoami", { key });
18528
18631
  if (res.status === 404) {
18529
18632
  r.done(
18530
18633
  {
18531
18634
  signedIn: true,
18532
18635
  origin,
18636
+ via,
18533
18637
  user: null,
18534
18638
  key: null,
18639
+ accounts: stored,
18535
18640
  next_step: "vos push when you want the work hosted"
18536
18641
  },
18537
- `A credential is present for ${origin} (this origin cannot say whose).`
18642
+ `A credential is present for ${origin}${viaLine} (this origin cannot say whose).${alsoStored}`
18538
18643
  );
18539
18644
  return EXIT_OK;
18540
18645
  }
@@ -18543,44 +18648,150 @@ async function cmdWhoami(argv) {
18543
18648
  {
18544
18649
  signedIn: false,
18545
18650
  origin,
18651
+ via,
18546
18652
  error: apiError("whoami", res),
18653
+ accounts: stored,
18547
18654
  next_step: "vos login"
18548
18655
  },
18549
- `The stored credential is refused by ${origin}: ${apiError("whoami", res)}. Run vos login.`
18656
+ `The stored credential is refused by ${origin}${viaLine}: ${apiError("whoami", res)}. Run vos login.${alsoStored}`
18550
18657
  );
18551
18658
  return EXIT_ERROR;
18552
18659
  }
18553
18660
  if (res.status !== 200) throw new Error(apiError("whoami", res));
18554
18661
  const user = res.body.user;
18662
+ const actor = res.body.actor;
18555
18663
  const k = res.body.key;
18556
18664
  const who = user?.username ? `@${user.username}` : user?.name ?? "an account";
18665
+ const orgLine = user?.kind === "org" ? ` (an organization${actor ? `, ${actor.username ? `@${actor.username}` : actor.name ?? "you"} recorded as the person behind it` : ""})` : "";
18557
18666
  const keyLine = k ? `key "${k.name ?? ""}" (${k.prefix ?? "\u2026"}, ${(k.scopes ?? []).join(", ") || "no scopes"})` : "a session, not a key";
18667
+ if (via === "stored" && resolved && !resolved.account && user?.id) {
18668
+ const active = readActive(DEFAULT_STORE);
18669
+ if (active && active.key === key) {
18670
+ activate(DEFAULT_STORE, {
18671
+ ...active,
18672
+ account: {
18673
+ id: user.id,
18674
+ kind: user.kind === "org" ? "org" : "person",
18675
+ handle: user.username ?? null
18676
+ }
18677
+ });
18678
+ }
18679
+ }
18558
18680
  r.done(
18559
18681
  {
18560
18682
  signedIn: true,
18561
18683
  origin,
18684
+ via,
18562
18685
  user,
18686
+ actor: actor ?? null,
18563
18687
  key: k,
18688
+ accounts: storedAccounts(origin),
18564
18689
  next_step: "vos push when you want the work hosted"
18565
18690
  },
18566
- `Signed in to ${origin} as ${who}, with ${keyLine}.`
18691
+ `Signed in to ${origin} as ${who}${orgLine}, with ${keyLine}${viaLine}.${alsoStored}`
18692
+ );
18693
+ return EXIT_OK;
18694
+ }
18695
+ function cmdSwitch(argv) {
18696
+ const { flags, positionals } = parseArgs(argv, BOOLEAN_FLAGS15);
18697
+ const r = reporterFor(flags);
18698
+ const origin = platformOrigin({
18699
+ origin: strFlag(flags, "origin"),
18700
+ api: strFlag(flags, "api")
18701
+ });
18702
+ const store = DEFAULT_STORE;
18703
+ const stored = listStored(store, origin);
18704
+ const active = readActive(store);
18705
+ const target = switchTarget(
18706
+ stored,
18707
+ active?.key ?? null,
18708
+ positionals[0],
18709
+ origin
18710
+ );
18711
+ if (!target.ok) throw new UsageError(target.error);
18712
+ const to = target.to;
18713
+ if (active && active.key === to.key) {
18714
+ r.done(
18715
+ {
18716
+ origin,
18717
+ account: to.account,
18718
+ changed: false,
18719
+ accounts: storedAccounts(origin, store),
18720
+ next_step: "vos push when you want the work hosted"
18721
+ },
18722
+ `${accountLabel(to.account)} is already the account in use on ${origin}.`
18723
+ );
18724
+ return EXIT_OK;
18725
+ }
18726
+ activate(store, to);
18727
+ const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18728
+ r.done(
18729
+ {
18730
+ origin,
18731
+ account: to.account,
18732
+ changed: true,
18733
+ accounts: storedAccounts(origin, store),
18734
+ next_step: envKey ? "unset VOS_API_KEY; it outranks the stored credential" : "vos push when you want the work hosted"
18735
+ },
18736
+ `Now ${accountLabel(to.account)} on ${origin}${active?.account ? ` (${accountLabel(active.account)} stays stored)` : ""}.${envKey ? " VOS_API_KEY is set in this shell and still outranks it." : ""}`
18567
18737
  );
18568
18738
  return EXIT_OK;
18569
18739
  }
18570
18740
  function cmdLogout(argv) {
18571
- const { flags } = parseArgs(argv, BOOLEAN_FLAGS15);
18741
+ const { flags, positionals } = parseArgs(argv, LOGOUT_FLAGS);
18572
18742
  const r = reporterFor(flags);
18573
- const had = existsSync30(CREDENTIALS_PATH);
18574
- rmSync3(CREDENTIALS_PATH, { force: true });
18743
+ const origin = platformOrigin({
18744
+ origin: strFlag(flags, "origin"),
18745
+ api: strFlag(flags, "api")
18746
+ });
18747
+ const store = DEFAULT_STORE;
18575
18748
  const envKey = Boolean(process.env.VOS_API_KEY?.trim());
18749
+ const envNote = envKey ? " VOS_API_KEY is still set in this shell and still resolves." : "";
18750
+ const envStep = envKey ? "unset VOS_API_KEY as well; it still resolves" : "vos login when you next need hosting";
18751
+ if (flags.all === true) {
18752
+ const { had } = removeAll(store);
18753
+ r.done(
18754
+ {
18755
+ path: CREDENTIALS_PATH,
18756
+ removed: had,
18757
+ all: true,
18758
+ envCredential: envKey,
18759
+ next_step: envStep
18760
+ },
18761
+ (had ? "Removed every stored credential." : "Nothing was stored.") + envNote
18762
+ );
18763
+ return EXIT_OK;
18764
+ }
18765
+ const ref = positionals[0];
18766
+ const target = ref ? findStored(store, origin, ref) : readActive(store);
18767
+ if (!target) {
18768
+ const have = storedAccounts(origin, store);
18769
+ r.done(
18770
+ {
18771
+ path: CREDENTIALS_PATH,
18772
+ removed: false,
18773
+ envCredential: envKey,
18774
+ accounts: have,
18775
+ next_step: envStep
18776
+ },
18777
+ (ref ? `Nothing stored for ${ref.startsWith("@") ? ref : `@${ref}`} on ${origin}.` : `Nothing stored at ${CREDENTIALS_PATH}.`) + (have.length ? ` Stored: ${have.map((a) => a.label).join(", ")}.` : "") + envNote
18778
+ );
18779
+ return EXIT_OK;
18780
+ }
18781
+ const { wasActive } = removeStored(store, target);
18782
+ const left = storedAccounts(origin, store);
18783
+ const leftLine = left.length ? wasActive ? ` Still stored: ${left.map((a) => a.label).join(", ")}; vos switch <name> to use one.` : ` ${left.find((a) => a.active)?.label ?? "another account"} stays in use.` : "";
18576
18784
  r.done(
18577
18785
  {
18578
18786
  path: CREDENTIALS_PATH,
18579
- removed: had,
18787
+ removed: true,
18788
+ account: target.account,
18789
+ wasActive,
18580
18790
  envCredential: envKey,
18581
- next_step: envKey ? "unset VOS_API_KEY as well; it still resolves" : "vos login when you next need hosting"
18791
+ accounts: left,
18792
+ next_step: left.length && wasActive ? "vos switch <name>" : envStep
18582
18793
  },
18583
- (had ? `Removed ${CREDENTIALS_PATH}.` : `Nothing stored at ${CREDENTIALS_PATH}.`) + (envKey ? " VOS_API_KEY is still set in this shell and still resolves." : "")
18794
+ `Removed ${accountLabel(target.account)} (${target.origin}).${leftLine}${envNote}`
18584
18795
  );
18585
18796
  return EXIT_OK;
18586
18797
  }
@@ -18689,15 +18900,22 @@ This machine (no account)
18689
18900
  agent, a credential (present or absent, never printed), and the
18690
18901
  dev server when --url names one. Exit 3 when no browser
18691
18902
  vos whoami [--json]
18692
- the key's name and the account it belongs to, never the key
18693
- vos logout [--json]
18694
- remove ~/.config/vos/credentials (VOS_API_KEY still resolves)
18903
+ the account in use (its key's name, which rung resolved it) and
18904
+ every other account stored for the origin, never a key
18905
+ vos switch [<handle>] [--json]
18906
+ use another stored account (with two stored, the other one);
18907
+ vos login once per account, approving as it, stores them all
18908
+ vos logout [<handle>] [--all] [--json]
18909
+ forget the account in use, a named one, or every stored
18910
+ credential (VOS_API_KEY still resolves)
18695
18911
 
18696
18912
  Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
18697
18913
  vos login [--key <vos_sk_\u2026>] [--handoff <url>] [--label <name>] [--no-browser]
18698
18914
  sign in via the browser (approve at vos.so/cli/auth; works
18699
18915
  headless \u2014 print the URL, a human approves, the key stores
18700
18916
  itself). --key skips the browser (mint at vos.so/app/api).
18917
+ Additive: the person picks the account on the approval page,
18918
+ and every account signed in stays stored (vos switch).
18701
18919
  --handoff https://vos.so/h/\u2026 exchanges the setup link a
18702
18920
  signed-in person's line carries for a key, once, no click
18703
18921
  vos fetch <vosId|watch-url> [--out dir] [--media] [--json]
@@ -18761,7 +18979,8 @@ Platform (vos.so) \u2014 fetch, edit, push, pull, repeat
18761
18979
  kept) or the take's doc.json. ALWAYS pull before editing pushed work.
18762
18980
  --media also downloads the recording (+ mic/cam sidecars) beside
18763
18981
  the doc and re-anchors it, so digest/frames/render run locally
18764
- auth: --key, VOS_API_KEY, or ~/.config/vos/credentials; keys can
18982
+ auth: --key, --account <handle>, VOS_API_KEY, VOS_ACCOUNT, or the
18983
+ stored account in use (~/.config/vos/credentials); keys can
18765
18984
  never publish (visibility stays private; humans publish on vos.so)
18766
18985
  vos folder list
18767
18986
  vos folder create <name> [--parent <folderId|slug>] [--desc <text>]
@@ -18962,9 +19181,10 @@ doc.json edits arrive intact; a human can drag every zoom span. Keeps
18962
19181
  serving until Ctrl-C. The studio is https://vos.so by default (VOS_ORIGIN
18963
19182
  moves it; --studio http://localhost:6060 for a dev server).
18964
19183
 
18965
- push/pull \u2014 the hosted iteration loop (credentials: --key, VOS_API_KEY, or
18966
- ~/.config/vos/credentials via vos login; origin via VOS_ORIGIN, default
18967
- https://vos.so). A take push uploads the recording ONCE (content-addressed),
19184
+ push/pull \u2014 the hosted iteration loop (credentials: --key, --account <handle>,
19185
+ VOS_API_KEY, VOS_ACCOUNT, or the account in use at ~/.config/vos/credentials
19186
+ via vos login, one sign-in per account and vos switch between them; origin
19187
+ via VOS_ORIGIN, default https://vos.so). A take push uploads the recording ONCE (content-addressed),
18968
19188
  lowers doc.json, and creates a private vos (first push asks; --yes for
18969
19189
  headless) or a new version against the base in vos.json \u2014 a stale base
18970
19190
  prints the human's typed changelog and exits 1 (run pull, re-apply, push
@@ -20794,7 +21014,7 @@ reference and the only credential.`
20794
21014
  }).replace(/\/+$/, "");
20795
21015
  const landed = flags.wait === true ? await waitForLanded({
20796
21016
  origin: pushedTo,
20797
- key: requireCredential(strFlag(flags, "key")),
21017
+ key: requireCredential(credentialFlags(flags)),
20798
21018
  vosId: result.vosId,
20799
21019
  versionId: result.versionId ?? null,
20800
21020
  log: (l) => r.log(l)
@@ -20915,6 +21135,8 @@ async function run(argv) {
20915
21135
  return await cmdWhoami(rest);
20916
21136
  case "logout":
20917
21137
  return cmdLogout(rest);
21138
+ case "switch":
21139
+ return cmdSwitch(rest);
20918
21140
  case "push": {
20919
21141
  const target = rest.find((a) => !a.startsWith("-"));
20920
21142
  if (target && isTakeDir(target)) return await cmdPush3(rest);
@@ -21177,4 +21399,4 @@ export {
21177
21399
  verbHelp,
21178
21400
  run
21179
21401
  };
21180
- //# sourceMappingURL=chunk-NJBUBXFE.js.map
21402
+ //# sourceMappingURL=chunk-ZXFO32KW.js.map