@vosjs/cli 0.86.0 → 0.87.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/README.md CHANGED
@@ -300,7 +300,7 @@ take/
300
300
  join.json a joined take's inputs: each one's path, footage sha256 and media id, read by `vos join --keep-edits`
301
301
  outputs/ the files `download` steps kept, listed in meta.json's outputs
302
302
  page-sound/ the sound the page played during the take, listed in meta.json's media
303
- vos.json hosted tracking (vos id + base version), written by push, fetch and pull
303
+ vos.json hosted tracking (vos id + base version, the origin and the account it belongs to), written by push, fetch and pull
304
304
  stills/ `vos frames` output + stills.json
305
305
  digest/ `vos digest` output: digest.json, <id>.full.png, <id>.crop.png, sheet.png
306
306
  kit/ `vos deliver` output: kit.json + the cuts
@@ -532,7 +532,7 @@ vos brand https://your.app --out BRAND.md # the brand kit,
532
532
 
533
533
  ## The vos.so loop
534
534
 
535
- Everything that runs on your machine is open source; [vos.so](https://vos.so) is the hosted platform: a studio for the human half of an edit, version history with a typed changelog, a shelf of projects and recipes. All vos.so traffic lives behind one client: one origin (`VOS_ORIGIN`, default `https://vos.so`; `--origin` per call), one credential ladder (`--key`, then `--account <handle>`, then `VOS_API_KEY`, then `VOS_ACCOUNT`, then the account in use at `~/.config/vos/credentials`; a `vos_rg_` remix grant is just a key), one tracking file (`vos.json` beside the artifact). **A person is many accounts** (themselves, and every organization they act for), so `vos login` is additive: the approval page asks which account the terminal is signed in as, every account signed in is stored by name under `~/.config/vos/accounts/<host>/<id>`, the one in use is copied to `~/.config/vos/credentials` (its first line is still the key, for every older reader), `vos switch [<handle>]` changes which, `vos whoami` says which is in use and what else is stored, and `--account <handle>` or `VOS_ACCOUNT` picks a stored one for a command without switching; a name nothing is stored under is refused in words, never answered with another account. A key acts for exactly the account it belongs to, so switching is choosing a key. Credentials are never printed, and a stored one is sent only to the origin it was minted for (a file written before the origin line is `https://vos.so`'s): a command aimed at another origin sends no key and says so, so pass `--key` or `VOS_API_KEY` for it, or `vos login --origin <it>`. Keys can never publish: pushes are private, and humans publish on vos.so.
535
+ Everything that runs on your machine is open source; [vos.so](https://vos.so) is the hosted platform: a studio for the human half of an edit, version history with a typed changelog, a shelf of projects and recipes. All vos.so traffic lives behind one client: one origin (`VOS_ORIGIN`, default `https://vos.so`; `--origin` per call), one credential ladder (`--key`, then `--account <handle>`, then `VOS_API_KEY`, then `VOS_ACCOUNT`, then the account in use at `~/.config/vos/credentials`; a `vos_rg_` remix grant is just a key), one tracking file (`vos.json` beside the artifact). **A person is many accounts** (themselves, and every organization they act for), so `vos login` is additive: the approval page asks which account the terminal is signed in as, every account signed in is stored by name under `~/.config/vos/accounts/<host>/<id>`, the one in use is copied to `~/.config/vos/credentials` (its first line is still the key, for every older reader), `vos switch [<handle>]` changes which, `vos whoami` says which is in use and what else is stored, and `--account <handle>` or `VOS_ACCOUNT` picks a stored one for a command without switching; a name nothing is stored under is refused in words, never answered with another account. A DIRECTORY remembers whose work it is: `vos.json` carries the `origin` and the `account` of its last push or fetch, and a verb on it resolves the stored credential for that account before the active one, so switching is rarely needed; a directory whose account is not stored here says so before any request. A push to someone else's project address (`--folder <handle>/<slug>`) asks before starting your own copy of it (`--yes` headless). A key acts for exactly the account it belongs to, so switching is choosing a key. Credentials are never printed, and a stored one is sent only to the origin it was minted for (a file written before the origin line is `https://vos.so`'s): a command aimed at another origin sends no key and says so, so pass `--key` or `VOS_API_KEY` for it, or `vos login --origin <it>`. Keys can never publish: pushes are private, and humans publish on vos.so.
536
536
 
537
537
  ```bash
538
538
  vos login [--key <k>] [--label <name>] [--no-browser] # browser sign-in: a code and a vos.so/cli/auth URL, a human approves (as themselves or an org), the key stores itself beside the ones already stored
@@ -4,12 +4,12 @@ import {
4
4
  localManifestRefs,
5
5
  pullManifest,
6
6
  uploadManifest
7
- } from "./chunk-V3RUNUNP.js";
8
- import "./chunk-PZP76YJI.js";
7
+ } from "./chunk-2UFNOKVY.js";
8
+ import "./chunk-PEW5GIZF.js";
9
9
  export {
10
10
  hostedLiteralWarnings,
11
11
  localManifestRefs,
12
12
  pullManifest,
13
13
  uploadManifest
14
14
  };
15
- //# sourceMappingURL=assetManifest-QFJ7X6TT.js.map
15
+ //# sourceMappingURL=assetManifest-UUKFWTLO.js.map
@@ -14,7 +14,7 @@ import {
14
14
  writeFileSafe,
15
15
  writeFileSafeSync,
16
16
  writeSyncState
17
- } from "./chunk-PZP76YJI.js";
17
+ } from "./chunk-PEW5GIZF.js";
18
18
 
19
19
  // src/plugin/assetManifest.ts
20
20
  import { existsSync as existsSync4, readFileSync, realpathSync, rmSync } from "fs";
@@ -958,4 +958,4 @@ export {
958
958
  pullManifest,
959
959
  hostedLiteralWarnings
960
960
  };
961
- //# sourceMappingURL=chunk-V3RUNUNP.js.map
961
+ //# sourceMappingURL=chunk-2UFNOKVY.js.map
@@ -10,7 +10,7 @@ import {
10
10
  reencodeStill,
11
11
  renderStill,
12
12
  videoFormat
13
- } from "./chunk-2LAXJZCG.js";
13
+ } from "./chunk-EXDMMAPT.js";
14
14
  import {
15
15
  PREV_DOC_NAME,
16
16
  PasteFileRefusal,
@@ -46,7 +46,7 @@ import {
46
46
  uploadDocRefs,
47
47
  uploadManifest,
48
48
  writeJson
49
- } from "./chunk-V3RUNUNP.js";
49
+ } from "./chunk-2UFNOKVY.js";
50
50
  import {
51
51
  ACCOUNT_ENV,
52
52
  CREDENTIALS_PATH,
@@ -62,6 +62,7 @@ import {
62
62
  bearerFor,
63
63
  clientId,
64
64
  containerMismatch,
65
+ credentialAccount,
65
66
  credentialFlags,
66
67
  deriveSlug,
67
68
  detailLine,
@@ -96,12 +97,13 @@ import {
96
97
  sniffMediaType,
97
98
  strFlag,
98
99
  switchTarget,
100
+ syncIdentity,
99
101
  writeCredential,
100
102
  writeFileSafe,
101
103
  writeOutput,
102
104
  writeOutputSync,
103
105
  writeSyncState
104
- } from "./chunk-PZP76YJI.js";
106
+ } from "./chunk-PEW5GIZF.js";
105
107
  import {
106
108
  deadLine,
107
109
  deadWarns
@@ -7466,7 +7468,7 @@ async function planTake(dir, opts = {}) {
7466
7468
  import { createHash } from "crypto";
7467
7469
  import { existsSync as existsSync13, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
7468
7470
  import { readFile as readFile11 } from "fs/promises";
7469
- import { createInterface as createInterface2 } from "readline/promises";
7471
+ import { createInterface as createInterface3 } from "readline/promises";
7470
7472
  import { basename as basename3, join as join14 } from "path";
7471
7473
  import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
7472
7474
 
@@ -7547,6 +7549,7 @@ import { mkdir as mkdir6 } from "fs/promises";
7547
7549
  import { join as join13 } from "path";
7548
7550
  import { recipeHints } from "@vosjs/shared/frontmatter";
7549
7551
  import { migrateHostedDoc } from "@vosjs/studio-core";
7552
+ import { createInterface as createInterface2 } from "readline/promises";
7550
7553
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
7551
7554
  async function listFolders(origin, key) {
7552
7555
  const r = await apiJson(origin, "/api/folders", { key });
@@ -7567,7 +7570,18 @@ function projectAddress(ref) {
7567
7570
  );
7568
7571
  return m ? { handle: m[1], slug: m[2].toLowerCase() } : null;
7569
7572
  }
7570
- async function resolvePushFolder(origin, key, ref) {
7573
+ function startFromVerdict(at2, mine) {
7574
+ const owner = at2.handle.toLowerCase();
7575
+ const asks = mine !== null && (!mine.handle || mine.handle.toLowerCase() !== owner);
7576
+ const who = mine ? accountLabel(mine) : "this credential";
7577
+ const address = `@${at2.handle}/${at2.slug}`;
7578
+ return {
7579
+ asks,
7580
+ question: `Pushing to ${address} as ${who} would START your own copy of it (its recipes copied in), not continue it. Continue?`,
7581
+ refusal: `vos push --folder ${address} as ${who} would start your own copy of it, not continue it: run it as @${at2.handle} (vos switch ${at2.handle}, or --account ${at2.handle}), or pass --yes to start the copy`
7582
+ };
7583
+ }
7584
+ async function resolvePushFolder(origin, key, ref, opts = {}) {
7571
7585
  const at2 = projectAddress(ref);
7572
7586
  if (!at2) {
7573
7587
  return {
@@ -7575,6 +7589,15 @@ async function resolvePushFolder(origin, key, ref) {
7575
7589
  note: null
7576
7590
  };
7577
7591
  }
7592
+ const mine = await credentialAccount(origin, key);
7593
+ const start = startFromVerdict(at2, mine);
7594
+ if (start.asks && !opts.yes) {
7595
+ if (!process.stdin.isTTY) throw new UsageError(start.refusal);
7596
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
7597
+ const answer = await rl.question(`${start.question} [y/N] `);
7598
+ rl.close();
7599
+ if (!/^y(es)?$/i.test(answer.trim())) throw new UsageError(start.refusal);
7600
+ }
7578
7601
  const r = await apiJson(
7579
7602
  origin,
7580
7603
  `/api/folders/public/${encodeURIComponent(at2.handle)}/${encodeURIComponent(at2.slug)}/push-target`,
@@ -7812,7 +7835,8 @@ async function cmdPull(argv) {
7812
7835
  vosId: v.id,
7813
7836
  versionId: v.currentVersionId ?? null,
7814
7837
  title: v.title,
7815
- ...v.slug ? { slug: v.slug } : {}
7838
+ ...v.slug ? { slug: v.slug } : {},
7839
+ ...await syncIdentity(origin, key)
7816
7840
  });
7817
7841
  members.push({ dir, id: v.id, title: v.title, take });
7818
7842
  }
@@ -7952,11 +7976,16 @@ function hostOf(origin) {
7952
7976
  }
7953
7977
 
7954
7978
  // src/plugin/sync.ts
7955
- function apiContext(flags) {
7979
+ function apiContext(flags, dir) {
7956
7980
  const origin = platformOrigin(flags);
7957
7981
  return {
7958
7982
  origin,
7959
- key: requireCredential({ key: flags.key, account: flags.account, origin })
7983
+ key: requireCredential({
7984
+ key: flags.key,
7985
+ account: flags.account,
7986
+ origin,
7987
+ dir
7988
+ })
7960
7989
  };
7961
7990
  }
7962
7991
  async function api(ctx, path, init) {
@@ -8048,7 +8077,7 @@ async function pushTake(dir, flags, r) {
8048
8077
  await readScript(dir) ?? take.doc.source.script,
8049
8078
  dir
8050
8079
  );
8051
- const ctx = apiContext(flags);
8080
+ const ctx = apiContext(flags, dir);
8052
8081
  const tracked = readSyncState(dir);
8053
8082
  const firstPush = tracked === null;
8054
8083
  if (firstPush && !flags.yes) {
@@ -8057,7 +8086,7 @@ async function pushTake(dir, flags, r) {
8057
8086
  "first push of this take uploads its recording \u2014 pass --yes to consent headlessly"
8058
8087
  );
8059
8088
  }
8060
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
8089
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
8061
8090
  const answer = await rl.question(
8062
8091
  `Push this take (recording + doc.json) to ${ctx.origin}? [y/N] `
8063
8092
  );
@@ -8093,7 +8122,9 @@ async function pushTake(dir, flags, r) {
8093
8122
  "--folder applies when CREATING a vos \u2014 this take is already pushed; file it with: vos folder move <vosId> --to <folder>"
8094
8123
  );
8095
8124
  }
8096
- const target = await resolvePushFolder(ctx.origin, ctx.key, flags.folder);
8125
+ const target = await resolvePushFolder(ctx.origin, ctx.key, flags.folder, {
8126
+ yes: flags.yes === true
8127
+ });
8097
8128
  folderId = target.id;
8098
8129
  if (target.note) r.log(target.note);
8099
8130
  }
@@ -8186,7 +8217,8 @@ async function pushTake(dir, flags, r) {
8186
8217
  versionId,
8187
8218
  pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
8188
8219
  title,
8189
- slug
8220
+ slug,
8221
+ ...await syncIdentity(ctx.origin, ctx.key)
8190
8222
  });
8191
8223
  return { vosId: vos.id, versionId, versionNumber: 1 };
8192
8224
  }
@@ -8236,7 +8268,8 @@ async function pushTake(dir, flags, r) {
8236
8268
  writeSyncState(dir, {
8237
8269
  vosId: state.vosId,
8238
8270
  versionId: version.id,
8239
- pushedAt: (/* @__PURE__ */ new Date()).toISOString()
8271
+ pushedAt: (/* @__PURE__ */ new Date()).toISOString(),
8272
+ ...await syncIdentity(ctx.origin, ctx.key)
8240
8273
  });
8241
8274
  return {
8242
8275
  vosId: state.vosId,
@@ -8245,7 +8278,7 @@ async function pushTake(dir, flags, r) {
8245
8278
  };
8246
8279
  }
8247
8280
  async function pullTake(dir, flags, r) {
8248
- const ctx = apiContext(flags);
8281
+ const ctx = apiContext(flags, dir);
8249
8282
  const state = readSyncState(dir);
8250
8283
  const vosId = flags.vos ?? state?.vosId;
8251
8284
  if (!vosId) {
@@ -8355,7 +8388,8 @@ async function pullTake(dir, flags, r) {
8355
8388
  writeSyncState(dir, {
8356
8389
  vosId,
8357
8390
  versionId: head,
8358
- pushedAt: state?.pushedAt ?? (/* @__PURE__ */ new Date()).toISOString()
8391
+ pushedAt: state?.pushedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8392
+ ...await syncIdentity(ctx.origin, ctx.key)
8359
8393
  });
8360
8394
  return {
8361
8395
  vosId,
@@ -8470,7 +8504,7 @@ async function pushTakeClaimable(dir, flags, r) {
8470
8504
  // src/plugin/program.ts
8471
8505
  import { existsSync as existsSync15 } from "fs";
8472
8506
  import { mkdir as mkdir7, readFile as readFile12 } from "fs/promises";
8473
- import { createInterface as createInterface3 } from "readline/promises";
8507
+ import { createInterface as createInterface4 } from "readline/promises";
8474
8508
  import { basename as basename5, dirname as dirname2, join as join15 } from "path";
8475
8509
  import {
8476
8510
  CURRENT_CONFIG_VERSION,
@@ -8699,7 +8733,8 @@ async function cmdFetch(argv) {
8699
8733
  vosId,
8700
8734
  versionId: head,
8701
8735
  ...title ? { title } : {},
8702
- slug
8736
+ slug,
8737
+ ...await syncIdentity(origin, key)
8703
8738
  });
8704
8739
  let take = false;
8705
8740
  let mediaLine = "";
@@ -8966,8 +9001,8 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
8966
9001
  );
8967
9002
  return EXIT_OK;
8968
9003
  }
8969
- const key = requireCredential(credentialFlags(flags));
8970
- void flags.yes;
9004
+ const key = requireCredential({ ...credentialFlags(flags), dir });
9005
+ const yes = flags.yes === true;
8971
9006
  const files = await collectFiles(config, programDoc, dir, r);
8972
9007
  await files.upload({ origin, key });
8973
9008
  const overrides = [
@@ -9051,7 +9086,11 @@ with their key: vos push ${source} --vos ${String(created2.id ?? "<id>")}`
9051
9086
  }
9052
9087
  const version = res.body.version ?? {};
9053
9088
  if (typeof version.id === "string") {
9054
- writeSyncState(dir, { vosId, versionId: version.id });
9089
+ writeSyncState(dir, {
9090
+ vosId,
9091
+ versionId: version.id,
9092
+ ...await syncIdentity(origin, key)
9093
+ });
9055
9094
  }
9056
9095
  const watchUrl2 = `${origin}/vos/${vosId}`;
9057
9096
  const studioUrl2 = `${origin}/studio?vos=${vosId}`;
@@ -9086,7 +9125,7 @@ ${landedLines(landed2)}` : "")
9086
9125
  const remixOfId = pushTarget.kind === "create" ? pushTarget.remixOfId : void 0;
9087
9126
  const fallbackTitle = state?.title ? `${state.title} remix` : basename5(source).replace(/\.json$/i, "") || "vos remix";
9088
9127
  const title = (strFlag(flags, "title") ?? fallbackTitle).slice(0, 100);
9089
- const pushFolder = folderRef ? await resolvePushFolder(origin, key, folderRef) : void 0;
9128
+ const pushFolder = folderRef ? await resolvePushFolder(origin, key, folderRef, { yes }) : void 0;
9090
9129
  const folderId = pushFolder?.id;
9091
9130
  if (pushFolder?.note) r.log(pushFolder.note);
9092
9131
  const created = await createProgramVos({
@@ -9114,7 +9153,8 @@ ${landedLines(landed2)}` : "")
9114
9153
  versionId: created.currentVersionId,
9115
9154
  title,
9116
9155
  slug: created.slug,
9117
- ...remixOfId ? { remixOfId } : {}
9156
+ ...remixOfId ? { remixOfId } : {},
9157
+ ...await syncIdentity(origin, key)
9118
9158
  });
9119
9159
  const watchUrl = `${origin}/vos/${created.id}`;
9120
9160
  const studioUrl = `${origin}/studio?vos=${created.id}`;
@@ -9181,7 +9221,7 @@ async function cmdPullProgram(argv) {
9181
9221
  `no base version in ${dir}/vos.json \u2014 pass --since <versionId>`
9182
9222
  );
9183
9223
  }
9184
- const key = requireCredential(credentialFlags(flags));
9224
+ const key = requireCredential({ ...credentialFlags(flags), dir });
9185
9225
  const res = await apiJson(
9186
9226
  origin,
9187
9227
  `/api/vos/${vosId}/changes?since=${encodeURIComponent(since)}`,
@@ -9253,7 +9293,8 @@ async function cmdPullProgram(argv) {
9253
9293
  await writeFileSafe(configPath, JSON.stringify(headConfig, null, 2));
9254
9294
  writeSyncState(dir, {
9255
9295
  vosId,
9256
- versionId: typeof head.id === "string" ? head.id : since
9296
+ versionId: typeof head.id === "string" ? head.id : since,
9297
+ ...await syncIdentity(origin, key)
9257
9298
  });
9258
9299
  r.done(
9259
9300
  {
@@ -9320,7 +9361,7 @@ async function cmdLogin(argv) {
9320
9361
  `vos login --key <vos_sk_\u2026> (headless), or run interactively \u2014 mint a key at ${mintAt(origin)}`
9321
9362
  );
9322
9363
  }
9323
- const rl = createInterface3({ input: process.stdin, output: process.stderr });
9364
+ const rl = createInterface4({ input: process.stdin, output: process.stderr });
9324
9365
  const key = (await rl.question(`Paste a content key from ${origin}/app/api: `)).trim();
9325
9366
  rl.close();
9326
9367
  if (!key) throw new UsageError("no key given");
@@ -9987,7 +10028,7 @@ async function cmdAsset(argv) {
9987
10028
  // src/plugin/remove.ts
9988
10029
  import { existsSync as existsSync16, rmSync as rmSync2, statSync as statSync3 } from "fs";
9989
10030
  import { join as join16 } from "path";
9990
- import { createInterface as createInterface4 } from "readline/promises";
10031
+ import { createInterface as createInterface5 } from "readline/promises";
9991
10032
 
9992
10033
  // src/plugin/trash.ts
9993
10034
  var BOOLEAN_FLAGS4 = /* @__PURE__ */ new Set(["json", "help"]);
@@ -10187,7 +10228,7 @@ async function cmdDelete(argv) {
10187
10228
  throw new UsageError(
10188
10229
  `"${title}" (${vosId}) would move to Trash: re-run with --yes to confirm, or --dry-run to only look`
10189
10230
  );
10190
- const rl = createInterface4({ input: process.stdin, output: process.stderr });
10231
+ const rl = createInterface5({ input: process.stdin, output: process.stderr });
10191
10232
  const answer = await rl.question(
10192
10233
  `Move "${title}" (${vosId}) to Trash? It stays restorable there for a while (vos restore ${vosId}). [y/N] `
10193
10234
  );
@@ -19188,7 +19229,9 @@ via VOS_ORIGIN, default https://vos.so). A take push uploads the recording ONCE
19188
19229
  lowers doc.json, and creates a private vos (first push asks; --yes for
19189
19230
  headless) or a new version against the base in vos.json \u2014 a stale base
19190
19231
  prints the human's typed changelog and exits 1 (run pull, re-apply, push
19191
- again). A protected conflict means the human touched those nodes in the
19232
+ again). vos.json also names the ACCOUNT the directory belongs to, and a
19233
+ verb on it uses the stored credential for that account before the active
19234
+ one, so a take fetched as an organization pushes as it with no switch. A protected conflict means the human touched those nodes in the
19192
19235
  studio: keep their values, or --override <id> ONLY when the user asked for
19193
19236
  that exact change. ALWAYS pass --label (what you did, one line) and --note
19194
19237
  (why: the user's ask) \u2014 the version history reads as a conversation, and an
@@ -21399,4 +21442,4 @@ export {
21399
21442
  verbHelp,
21400
21443
  run
21401
21444
  };
21402
- //# sourceMappingURL=chunk-ZXFO32KW.js.map
21445
+ //# sourceMappingURL=chunk-ACWV3Y6Y.js.map