@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.
package/README.md CHANGED
@@ -39,8 +39,9 @@ Node 18 or newer and a Chromium-family browser. A system Chrome is used when pre
39
39
  ```bash
40
40
  vos setup [--agent claude,cursor,codex,copilot|all] [--global] [--no-skills] [--skills-from-catalog] [--no-rules] [--no-browser] [--url <dev server>]
41
41
  vos doctor [--url <dev server>] # what is ready, in words; exit 3 when no browser
42
- vos whoami # the key's name and the account it belongs to, never the key
43
- vos logout # remove ~/.config/vos/credentials
42
+ vos whoami # the account in use, its key's name, and every other account stored; never a key
43
+ vos switch [<handle>] # use another stored account (with two stored, the other one)
44
+ vos logout [<handle>] [--all] # forget the account in use, a named one, or every stored credential
44
45
  ```
45
46
 
46
47
  `vos setup` detects the agents present by their directories (`.claude/`, `.cursor/`, `.codex/` or `.agents/`, `.github/copilot`; the home forms under `--global`), installs the skills into them from the copy of the catalog this package ships (`skills/`, the catalog at the tag this release was cut from; `--skills-from-catalog` fetches that same tag through skills.sh instead, with the bundled copy as the fallback when it cannot be reached), finds a browser in the recorder's own order or installs Chromium, writes one block between `<!-- vos:begin -->` and `<!-- vos:end -->` markers into `AGENTS.md` (or `CLAUDE.md` when only that exists), so a second run replaces its own text and never yours, and ends with `doctor`. These four verbs print NDJSON whenever stdout is not a TTY, and every `done` event carries `next_step`.
@@ -531,10 +532,10 @@ vos brand https://your.app --out BRAND.md # the brand kit,
531
532
 
532
533
  ## The vos.so loop
533
534
 
534
- 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 `VOS_API_KEY`, then `~/.config/vos/credentials`, written by `vos login` with the origin it was minted for; a `vos_rg_` remix grant is just a key), one tracking file (`vos.json` beside the artifact). Credentials are never printed, and the 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 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
536
 
536
537
  ```bash
537
- vos login [--key <k>] [--label <name>] [--no-browser] # browser sign-in: a code and a vos.so/cli/auth URL, a human approves, the key stores itself
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
538
539
  vos login --handoff https://vos.so/h/<token> # the setup link a signed-in person's line carries: exchanged once for a key, no click
539
540
  vos fetch <vosId|url> [--out <slug>] [--media] # a program writes config.json + vos.json; a take writes doc.json (+ the recording with --media)
540
541
  vos check bright-loop/config.json # full local validation
@@ -593,7 +594,7 @@ vos push /tmp/take --label "$TAG launch" --yes --json # VOS_API_KEY as a r
593
594
  - **`--max-duration <s>`** on `record` and `create` defaults to the hosted recording cap, read live from `GET /api/limits` (2 s, fail-open to 30 min when the origin is unreachable): the capture stops there and the done event says so. Cut the flow rather than raising the cap; the platform refuses a longer take.
594
595
  - **`create`** is the one-shot verb: record, auto-plan and render in one command and one browser session. The take directory still lands on disk, so the full loop (frames, edit `doc.json`, re-render) stays open afterwards.
595
596
  - **`vos validate <thing>`** takes an `actions.json`, a take directory, a program directory (`config.json`, plus its program document when present), or a `kit.json`; exit 1 on any problem.
596
- - **Environment.** `VOS_ORIGIN` (the platform, and the studio `vos open` opens), `VOS_API_KEY`, `VOS_BROWSER_PATH`, `VOS_CLIENT` (the client string a push self-reports; unset, a push names the coding agent that spawned the CLI, read from its environment the way `@vercel/detect-agent` reads it, e.g. `claude-code vos-cli`, else `vos-cli`).
597
+ - **Environment.** `VOS_ORIGIN` (the platform, and the studio `vos open` opens), `VOS_API_KEY` (a key, as a CI secret; it outranks the store), `VOS_ACCOUNT` (a stored account by handle, for a shell or a runner that never holds a key), `VOS_BROWSER_PATH`, `VOS_CLIENT` (the client string a push self-reports; unset, a push names the coding agent that spawned the CLI, read from its environment the way `@vercel/detect-agent` reads it, e.g. `claude-code vos-cli`, else `vos-cli`).
597
598
 
598
599
  ## Programmatic use
599
600
 
@@ -4,12 +4,12 @@ import {
4
4
  localManifestRefs,
5
5
  pullManifest,
6
6
  uploadManifest
7
- } from "./chunk-M5ZBQFPG.js";
8
- import "./chunk-EPSYIF2S.js";
7
+ } from "./chunk-V3RUNUNP.js";
8
+ import "./chunk-PZP76YJI.js";
9
9
  export {
10
10
  hostedLiteralWarnings,
11
11
  localManifestRefs,
12
12
  pullManifest,
13
13
  uploadManifest
14
14
  };
15
- //# sourceMappingURL=assetManifest-32VI7OK7.js.map
15
+ //# sourceMappingURL=assetManifest-QFJ7X6TT.js.map
@@ -3,7 +3,7 @@ import {
3
3
  ASSET_ROUTE,
4
4
  UsageError,
5
5
  serveFile
6
- } from "./chunk-EPSYIF2S.js";
6
+ } from "./chunk-PZP76YJI.js";
7
7
 
8
8
  // src/loadConfig.ts
9
9
  import { readFile } from "fs/promises";
@@ -573,4 +573,4 @@ export {
573
573
  setFlags,
574
574
  applyDataSets
575
575
  };
576
- //# sourceMappingURL=chunk-Q47FUCYN.js.map
576
+ //# sourceMappingURL=chunk-2LAXJZCG.js.map
@@ -128,13 +128,20 @@ var manifest = {
128
128
  },
129
129
  {
130
130
  name: "whoami",
131
- summary: "the key\u2019s name and the account it belongs to, never the key"
131
+ summary: "the account in use and every other one stored for the origin, never a key"
132
132
  },
133
- { name: "logout", summary: "remove ~/.config/vos/credentials" }
133
+ {
134
+ name: "switch",
135
+ summary: "use another stored account (vos login once per account stores them all)"
136
+ },
137
+ {
138
+ name: "logout",
139
+ summary: "forget the account in use, a named one (vos logout <handle>), or all (--all)"
140
+ }
134
141
  ]
135
142
  };
136
143
 
137
144
  export {
138
145
  manifest
139
146
  };
140
- //# sourceMappingURL=chunk-PF2NEZYU.js.map
147
+ //# sourceMappingURL=chunk-7A3PZARL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin/manifest.ts"],"sourcesContent":["/**\n * The verb manifest of the take pipeline and the vos.so verbs — what\n * `vos help` lists under the engine verbs. It kept the shape a separately\n * installed plugin once handed the host (name + host range), so a script\n * reading it keeps working.\n */\nexport interface PluginVerb {\n name: string\n summary: string\n}\n\nexport interface PluginManifest {\n name: string\n /** Host versions this plugin speaks the run(argv) contract with. */\n hostRange: string\n verbs: PluginVerb[]\n}\n\nexport const manifest: PluginManifest = {\n name: '@vosjs/cli',\n hostRange: '>=0.9.0',\n verbs: [\n {\n name: 'create',\n summary: 'record + auto-plan + render, one command (--strict)',\n },\n {\n name: 'record',\n summary: 'drive actions.json into a take (screencast + cursor track)',\n },\n {\n name: 'look',\n summary:\n 'see a page before scripting it (signed in, like record): a screenshot + every control in view with one proposed stable selector',\n },\n {\n name: 'plan',\n summary:\n 'plan zoom/cursor effects into doc.json (wand contract); --reuse re-times a previous cut onto a re-recording',\n },\n {\n name: 'render',\n summary: 'render a take directory (engine configs render in the host)',\n },\n {\n name: 'frames',\n summary:\n 'PNG stills at output times / zoom apexes (each checked against its zoom-off twin) / moments / exact sizes',\n },\n {\n name: 'check-video',\n summary:\n 'check a rendered file: facts, scene changes, loudness, first and last frames',\n },\n {\n name: 'deliver',\n summary:\n 'render a take to release video destinations (YouTube, X, Shorts/LinkedIn, GitHub) + verified kit.json',\n },\n {\n name: 'digest',\n summary:\n 'see a recording before cutting it: moments (clicks, typing, scrolls, idle, scenes) + footage frames + crops, in doc units',\n },\n { name: 'open', summary: 'serve the take into the vos.so studio' },\n {\n name: 'validate',\n summary:\n 'lint actions.json, a take dir (doc.json semantics), or re-measure a kit.json against the channel specs',\n },\n {\n name: 'actions',\n summary:\n 'from-agent-browser: turn an agent-browser walk (steps.jsonl) into actions.json; what cannot follow is named',\n },\n {\n name: 'ingest',\n summary:\n 'a take from a recording someone else made, with its cursor track from a trace beside it (Playwright trace.zip, stamped records, CSV); without one nothing is planned',\n },\n {\n name: 'join',\n summary:\n 'several takes and video files as one take: each next input a media, its spans tagged, its layers moved to where it starts; --captions anchors every step caption',\n },\n {\n name: 'score',\n summary:\n \"a music track as the film's score, its strongest swell landed on a step or a second (--dry-run prints the loudness envelope and the plan)\",\n },\n {\n name: 'audio',\n summary:\n 'measure: every audio clip’s loudness (integrated LUFS, sample peak) written beside it, which a clip’s normalize target plays it to',\n },\n {\n name: 'music',\n summary:\n 'ls: the library one line per track (tempo, vocals, loops, the sentence); pick: the beds that fit this film, best first, with reasons and the vos score line that places each',\n },\n {\n name: 'port',\n summary:\n 'bring a page, a HyperFrames or a Remotion piece: inventory (its words, colours, faces, media, scenes) then scaffold (a data-first program.mjs to translate the motion into)',\n },\n {\n name: 'build',\n summary:\n 'program.mjs (real functions) → config.json, refusing what cannot run on its own (module-scope reads, syntax the page cannot parse, non-JSON data)',\n },\n {\n name: 'compare',\n summary:\n \"a program against the source's own render: per-frame SSIM and source | vos | difference sheets; exits 1 when any frame is under the threshold\",\n },\n {\n name: 'brand',\n summary:\n \"write a product's BRAND.md, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)\",\n },\n {\n name: 'fetch',\n summary: 'download a hosted program: config.json + vos.json tracking',\n },\n {\n name: 'push',\n summary: 'push a config.json or take to vos.so (private; versioned)',\n },\n {\n name: 'duplicate',\n summary:\n 'a private sibling of your OWN vos (someone else\\u2019s is remixed: fetch + push --remix-of)',\n },\n {\n name: 'pull',\n summary:\n 'sync what changed on vos.so since your base (--media brings a take’s footage home)',\n },\n {\n name: 'folder',\n summary:\n 'list/create/pull folders, move voses and assets into them (pull = the context package on disk)',\n },\n {\n name: 'asset',\n summary:\n 'your files: push or import one, list them (assets ls), see storage (assets usage) and what uses a file (assets why)',\n },\n {\n name: 'recipe',\n summary:\n 'push a recipe .md into a folder, or replace one in place (prior body kept)',\n },\n {\n name: 'login',\n summary: 'sign in via the browser (or --key); stores a content key',\n },\n {\n name: 'setup',\n summary:\n 'ready this machine: the vos skills into your agent, a browser, one rules block, then doctor',\n },\n {\n name: 'doctor',\n summary:\n 'what is ready, in words: node, browser, ffmpeg, skills, a credential (never printed), the dev server',\n },\n {\n name: 'whoami',\n summary: 'the key’s name and the account it belongs to, never the key',\n },\n { name: 'logout', summary: 'remove ~/.config/vos/credentials' },\n ],\n}\n"],"mappings":";;;AAkBO,IAAM,WAA2B;AAAA,EACtC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA,EAAE,MAAM,QAAQ,SAAS,wCAAwC;AAAA,IACjE;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,EAAE,MAAM,UAAU,SAAS,mCAAmC;AAAA,EAChE;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/plugin/manifest.ts"],"sourcesContent":["/**\n * The verb manifest of the take pipeline and the vos.so verbs — what\n * `vos help` lists under the engine verbs. It kept the shape a separately\n * installed plugin once handed the host (name + host range), so a script\n * reading it keeps working.\n */\nexport interface PluginVerb {\n name: string\n summary: string\n}\n\nexport interface PluginManifest {\n name: string\n /** Host versions this plugin speaks the run(argv) contract with. */\n hostRange: string\n verbs: PluginVerb[]\n}\n\nexport const manifest: PluginManifest = {\n name: '@vosjs/cli',\n hostRange: '>=0.9.0',\n verbs: [\n {\n name: 'create',\n summary: 'record + auto-plan + render, one command (--strict)',\n },\n {\n name: 'record',\n summary: 'drive actions.json into a take (screencast + cursor track)',\n },\n {\n name: 'look',\n summary:\n 'see a page before scripting it (signed in, like record): a screenshot + every control in view with one proposed stable selector',\n },\n {\n name: 'plan',\n summary:\n 'plan zoom/cursor effects into doc.json (wand contract); --reuse re-times a previous cut onto a re-recording',\n },\n {\n name: 'render',\n summary: 'render a take directory (engine configs render in the host)',\n },\n {\n name: 'frames',\n summary:\n 'PNG stills at output times / zoom apexes (each checked against its zoom-off twin) / moments / exact sizes',\n },\n {\n name: 'check-video',\n summary:\n 'check a rendered file: facts, scene changes, loudness, first and last frames',\n },\n {\n name: 'deliver',\n summary:\n 'render a take to release video destinations (YouTube, X, Shorts/LinkedIn, GitHub) + verified kit.json',\n },\n {\n name: 'digest',\n summary:\n 'see a recording before cutting it: moments (clicks, typing, scrolls, idle, scenes) + footage frames + crops, in doc units',\n },\n { name: 'open', summary: 'serve the take into the vos.so studio' },\n {\n name: 'validate',\n summary:\n 'lint actions.json, a take dir (doc.json semantics), or re-measure a kit.json against the channel specs',\n },\n {\n name: 'actions',\n summary:\n 'from-agent-browser: turn an agent-browser walk (steps.jsonl) into actions.json; what cannot follow is named',\n },\n {\n name: 'ingest',\n summary:\n 'a take from a recording someone else made, with its cursor track from a trace beside it (Playwright trace.zip, stamped records, CSV); without one nothing is planned',\n },\n {\n name: 'join',\n summary:\n 'several takes and video files as one take: each next input a media, its spans tagged, its layers moved to where it starts; --captions anchors every step caption',\n },\n {\n name: 'score',\n summary:\n \"a music track as the film's score, its strongest swell landed on a step or a second (--dry-run prints the loudness envelope and the plan)\",\n },\n {\n name: 'audio',\n summary:\n 'measure: every audio clip’s loudness (integrated LUFS, sample peak) written beside it, which a clip’s normalize target plays it to',\n },\n {\n name: 'music',\n summary:\n 'ls: the library one line per track (tempo, vocals, loops, the sentence); pick: the beds that fit this film, best first, with reasons and the vos score line that places each',\n },\n {\n name: 'port',\n summary:\n 'bring a page, a HyperFrames or a Remotion piece: inventory (its words, colours, faces, media, scenes) then scaffold (a data-first program.mjs to translate the motion into)',\n },\n {\n name: 'build',\n summary:\n 'program.mjs (real functions) → config.json, refusing what cannot run on its own (module-scope reads, syntax the page cannot parse, non-JSON data)',\n },\n {\n name: 'compare',\n summary:\n \"a program against the source's own render: per-frame SSIM and source | vos | difference sheets; exits 1 when any frame is under the threshold\",\n },\n {\n name: 'brand',\n summary:\n \"write a product's BRAND.md, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)\",\n },\n {\n name: 'fetch',\n summary: 'download a hosted program: config.json + vos.json tracking',\n },\n {\n name: 'push',\n summary: 'push a config.json or take to vos.so (private; versioned)',\n },\n {\n name: 'duplicate',\n summary:\n 'a private sibling of your OWN vos (someone else\\u2019s is remixed: fetch + push --remix-of)',\n },\n {\n name: 'pull',\n summary:\n 'sync what changed on vos.so since your base (--media brings a take’s footage home)',\n },\n {\n name: 'folder',\n summary:\n 'list/create/pull folders, move voses and assets into them (pull = the context package on disk)',\n },\n {\n name: 'asset',\n summary:\n 'your files: push or import one, list them (assets ls), see storage (assets usage) and what uses a file (assets why)',\n },\n {\n name: 'recipe',\n summary:\n 'push a recipe .md into a folder, or replace one in place (prior body kept)',\n },\n {\n name: 'login',\n summary: 'sign in via the browser (or --key); stores a content key',\n },\n {\n name: 'setup',\n summary:\n 'ready this machine: the vos skills into your agent, a browser, one rules block, then doctor',\n },\n {\n name: 'doctor',\n summary:\n 'what is ready, in words: node, browser, ffmpeg, skills, a credential (never printed), the dev server',\n },\n {\n name: 'whoami',\n summary:\n 'the account in use and every other one stored for the origin, never a key',\n },\n {\n name: 'switch',\n summary:\n 'use another stored account (vos login once per account stores them all)',\n },\n {\n name: 'logout',\n summary:\n 'forget the account in use, a named one (vos logout <handle>), or all (--all)',\n },\n ],\n}\n"],"mappings":";;;AAkBO,IAAM,WAA2B;AAAA,EACtC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA,EAAE,MAAM,QAAQ,SAAS,wCAAwC;AAAA,IACjE;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,EACF;AACF;","names":[]}
@@ -341,9 +341,8 @@ function hasFlag(flags, name) {
341
341
  }
342
342
 
343
343
  // src/plugin/platform.ts
344
- import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
345
- import { homedir } from "os";
346
- import { dirname as dirname2, join } from "path";
344
+ import { readFileSync as readFileSync2 } from "fs";
345
+ import { join as join2 } from "path";
347
346
 
348
347
  // src/plugin/hostAgent.ts
349
348
  import { existsSync } from "fs";
@@ -389,6 +388,204 @@ function detectHostAgent(env = process.env, exists = existsSync) {
389
388
  return null;
390
389
  }
391
390
 
391
+ // src/plugin/accounts.ts
392
+ import {
393
+ existsSync as existsSync2,
394
+ mkdirSync as mkdirSync2,
395
+ readFileSync,
396
+ readdirSync,
397
+ rmSync,
398
+ writeFileSync as writeFileSync2
399
+ } from "fs";
400
+ import { homedir } from "os";
401
+ import { dirname as dirname2, join } from "path";
402
+ var CREDENTIALS_PATH = join(homedir(), ".config", "vos", "credentials");
403
+ var ACCOUNTS_DIR = join(homedir(), ".config", "vos", "accounts");
404
+ var DEFAULT_ORIGIN = "https://vos.so";
405
+ var ACCOUNT_ENV = "VOS_ACCOUNT";
406
+ var DEFAULT_STORE = {
407
+ credentialsPath: CREDENTIALS_PATH,
408
+ accountsDir: ACCOUNTS_DIR
409
+ };
410
+ function originOf(url) {
411
+ try {
412
+ return new URL(url).origin;
413
+ } catch {
414
+ return url.replace(/\/+$/, "");
415
+ }
416
+ }
417
+ function accountFrom(value) {
418
+ if (!value || typeof value !== "object") return null;
419
+ const u = value;
420
+ if (typeof u.id !== "string" || !u.id) return null;
421
+ return {
422
+ id: u.id,
423
+ kind: u.kind === "org" ? "org" : "person",
424
+ handle: typeof u.username === "string" && u.username ? u.username : null
425
+ };
426
+ }
427
+ function formatCredential(c) {
428
+ const account = c.account ? `account=${c.account.id} ${c.account.kind}${c.account.handle ? ` ${c.account.handle}` : ""}
429
+ ` : "";
430
+ return `${c.key.trim()}
431
+ origin=${originOf(c.origin)}
432
+ ${account}`;
433
+ }
434
+ function parseCredential(text) {
435
+ const [first, ...rest] = text.split("\n");
436
+ const key = first.trim();
437
+ if (!key) return null;
438
+ let origin = "";
439
+ let account = null;
440
+ for (const raw of rest) {
441
+ const line = raw.trim();
442
+ if (line.startsWith("origin=")) origin = originOf(line.slice(7).trim());
443
+ else if (line.startsWith("account=")) {
444
+ const [id, kind, handle] = line.slice(8).trim().split(/\s+/);
445
+ if (id)
446
+ account = {
447
+ id,
448
+ kind: kind === "org" ? "org" : "person",
449
+ handle: handle || null
450
+ };
451
+ }
452
+ }
453
+ return { key, origin: origin || DEFAULT_ORIGIN, account };
454
+ }
455
+ function readFile(path) {
456
+ try {
457
+ return parseCredential(readFileSync(path, "utf8"));
458
+ } catch {
459
+ return null;
460
+ }
461
+ }
462
+ function writeFile2(path, c) {
463
+ mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
464
+ writeFileSync2(path, formatCredential(c), { mode: 384 });
465
+ }
466
+ function hostDir(origin) {
467
+ let host;
468
+ try {
469
+ host = new URL(origin).host;
470
+ } catch {
471
+ host = origin.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
472
+ }
473
+ return host.replace(/[^A-Za-z0-9.-]/g, "_") || "unknown";
474
+ }
475
+ function accountPath(store, origin, accountId) {
476
+ return join(store.accountsDir, hostDir(origin), accountId);
477
+ }
478
+ function readActive(store = DEFAULT_STORE) {
479
+ return readFile(store.credentialsPath);
480
+ }
481
+ function listStored(store = DEFAULT_STORE, origin) {
482
+ const active = readActive(store);
483
+ const want = origin ? originOf(origin) : null;
484
+ const out = [];
485
+ const seen = /* @__PURE__ */ new Set();
486
+ const push = (c) => {
487
+ if (!c || want && c.origin !== want || seen.has(c.key)) return;
488
+ seen.add(c.key);
489
+ out.push(c);
490
+ };
491
+ push(active);
492
+ let hosts = [];
493
+ try {
494
+ hosts = readdirSync(store.accountsDir);
495
+ } catch {
496
+ return out;
497
+ }
498
+ for (const host of hosts.sort()) {
499
+ let files = [];
500
+ try {
501
+ files = readdirSync(join(store.accountsDir, host));
502
+ } catch {
503
+ continue;
504
+ }
505
+ for (const file of files.sort()) {
506
+ push(readFile(join(store.accountsDir, host, file)));
507
+ }
508
+ }
509
+ return out;
510
+ }
511
+ function isActive(c, store = DEFAULT_STORE) {
512
+ return readActive(store)?.key === c.key;
513
+ }
514
+ function accountLabel(a) {
515
+ if (!a) return "an unnamed account";
516
+ return a.handle ? `@${a.handle}` : a.id;
517
+ }
518
+ function matchesAccount(a, ref) {
519
+ if (!a) return false;
520
+ const want = ref.trim().replace(/^@/, "");
521
+ if (!want) return false;
522
+ if (a.id === want) return true;
523
+ return !!a.handle && a.handle.toLowerCase() === want.toLowerCase();
524
+ }
525
+ function findStored(store, origin, ref) {
526
+ return listStored(store, origin).find((c) => matchesAccount(c.account, ref)) ?? null;
527
+ }
528
+ function activate(store, c) {
529
+ writeFile2(store.credentialsPath, c);
530
+ return store.credentialsPath;
531
+ }
532
+ function storeCredential(store, c) {
533
+ const previous = readActive(store);
534
+ let setAside = null;
535
+ if (previous && previous.key !== c.key) {
536
+ if (previous.account) {
537
+ writeFile2(
538
+ accountPath(store, previous.origin, previous.account.id),
539
+ previous
540
+ );
541
+ setAside = previous;
542
+ }
543
+ }
544
+ if (c.account) writeFile2(accountPath(store, c.origin, c.account.id), c);
545
+ return { path: activate(store, c), setAside };
546
+ }
547
+ function removeStored(store, c) {
548
+ const wasActive = isActive(c, store);
549
+ if (c.account) {
550
+ rmSync(accountPath(store, c.origin, c.account.id), { force: true });
551
+ }
552
+ if (wasActive) rmSync(store.credentialsPath, { force: true });
553
+ return { wasActive };
554
+ }
555
+ function removeAll(store) {
556
+ const had = existsSync2(store.credentialsPath) || existsSync2(store.accountsDir);
557
+ rmSync(store.credentialsPath, { force: true });
558
+ rmSync(store.accountsDir, { recursive: true, force: true });
559
+ return { had };
560
+ }
561
+ function switchTarget(stored, activeKey, ref, origin) {
562
+ if (ref) {
563
+ const to = stored.find((c) => matchesAccount(c.account, ref));
564
+ if (!to)
565
+ return {
566
+ ok: false,
567
+ error: `no credential stored for ${ref.startsWith("@") ? ref : `@${ref.replace(/^@/, "")}`} on ${origin}; run vos login and approve as it${stored.length ? `, or vos switch ${stored.map((c) => accountLabel(c.account)).join(" | ")}` : ""}`
568
+ };
569
+ return { ok: true, to };
570
+ }
571
+ const others = stored.filter((c) => c.key !== activeKey);
572
+ if (stored.length === 0)
573
+ return {
574
+ ok: false,
575
+ error: `nothing is stored for ${origin}; run vos login`
576
+ };
577
+ if (stored.length === 1)
578
+ return {
579
+ ok: false,
580
+ error: `only ${accountLabel(stored[0].account)} is stored for ${origin}; run vos login and approve as another account to add one`
581
+ };
582
+ if (others.length === 1) return { ok: true, to: others[0] };
583
+ return {
584
+ ok: false,
585
+ error: `say which: vos switch ${stored.map((c) => accountLabel(c.account)).join(" | ")}`
586
+ };
587
+ }
588
+
392
589
  // src/plugin/platform.ts
393
590
  function platformOrigin(flags = {}) {
394
591
  const legacyEnv = process.env.VOS_API_BASE?.trim();
@@ -422,40 +619,63 @@ function parseVosId(input) {
422
619
  function deriveSlug(title) {
423
620
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
424
621
  }
425
- var CREDENTIALS_PATH = join(homedir(), ".config", "vos", "credentials");
426
- var DEFAULT_ORIGIN = "https://vos.so";
427
- function originOf(url) {
428
- try {
429
- return new URL(url).origin;
430
- } catch {
431
- return url.replace(/\/+$/, "");
432
- }
433
- }
434
- function readStoredCredential(path = CREDENTIALS_PATH) {
435
- let text;
436
- try {
437
- text = readFileSync(path, "utf8");
438
- } catch {
439
- return null;
440
- }
441
- const [first, ...rest] = text.split("\n");
442
- const key = first.trim();
443
- if (!key) return null;
444
- const line = rest.map((l) => l.trim()).find((l) => l.startsWith("origin="));
445
- const origin = line ? originOf(line.slice("origin=".length).trim()) : "";
446
- return { key, origin: origin || DEFAULT_ORIGIN };
447
- }
448
622
  var storedFor = /* @__PURE__ */ new Map();
449
623
  var warned = /* @__PURE__ */ new Set();
624
+ var lastResolved = null;
625
+ function lastCredential() {
626
+ return lastResolved;
627
+ }
628
+ function credentialFlags(flags) {
629
+ const str = (name) => typeof flags[name] === "string" ? flags[name] : void 0;
630
+ return {
631
+ key: str("key"),
632
+ account: str("account"),
633
+ origin: platformOrigin({ origin: str("origin"), api: str("api") })
634
+ };
635
+ }
636
+ function fromStore(c, via) {
637
+ if (!c) return null;
638
+ storedFor.set(c.key, c.origin);
639
+ lastResolved = { key: c.key, via, account: c.account, origin: c.origin };
640
+ return c.key;
641
+ }
450
642
  function resolveCredential(explicit, path = CREDENTIALS_PATH) {
451
- const flag = explicit?.trim();
452
- if (flag) return flag;
643
+ const ask = typeof explicit === "string" || explicit === void 0 ? { key: explicit } : explicit;
644
+ const store = ask.store ?? {
645
+ ...DEFAULT_STORE,
646
+ ...path !== CREDENTIALS_PATH ? { credentialsPath: path } : {}
647
+ };
648
+ const origin = ask.origin ?? platformOrigin();
649
+ const flag = ask.key?.trim();
650
+ if (flag) {
651
+ lastResolved = { key: flag, via: "flag", account: null, origin: null };
652
+ return flag;
653
+ }
654
+ const named = ask.account?.trim();
655
+ if (named) {
656
+ const found = findStored(store, origin, named);
657
+ if (!found)
658
+ throw new Error(noSuchAccount(store, origin, named, "--account"));
659
+ return fromStore(found, "account-flag");
660
+ }
453
661
  const env = process.env.VOS_API_KEY?.trim();
454
- if (env) return env;
455
- const stored = readStoredCredential(path);
456
- if (!stored) return null;
457
- storedFor.set(stored.key, stored.origin);
458
- return stored.key;
662
+ if (env) {
663
+ lastResolved = { key: env, via: "env", account: null, origin: null };
664
+ return env;
665
+ }
666
+ const envName = process.env[ACCOUNT_ENV]?.trim();
667
+ if (envName) {
668
+ const found = findStored(store, origin, envName);
669
+ if (!found)
670
+ throw new Error(noSuchAccount(store, origin, envName, ACCOUNT_ENV));
671
+ return fromStore(found, "account-env");
672
+ }
673
+ return fromStore(readActive(store), "stored");
674
+ }
675
+ function noSuchAccount(store, origin, ref, how) {
676
+ const have = listStored(store, origin).map((c) => accountLabel(c.account));
677
+ const name = ref.startsWith("@") ? ref : `@${ref}`;
678
+ return `${how} names ${name}, and no credential for it is stored for ${origin}: run vos login and approve as ${name}` + (have.length ? ` (stored: ${have.join(", ")})` : "");
459
679
  }
460
680
  function bearerFor(key, url) {
461
681
  if (!key) return {};
@@ -477,20 +697,25 @@ var mintAt = (origin = DEFAULT_ORIGIN) => `${originOf(origin)}/app/api`;
477
697
  function requireCredential(explicit, origin) {
478
698
  const key = resolveCredential(explicit);
479
699
  if (!key) {
700
+ const at = origin ?? (typeof explicit === "object" ? explicit.origin : void 0) ?? platformOrigin();
480
701
  throw new Error(
481
- `no credential found \u2014 pass --key, set VOS_API_KEY, or run \`vos login\` (mint a content key at ${mintAt(origin ?? platformOrigin())}; a vos_rg_ remix grant works too)`
702
+ `no credential found \u2014 pass --key, set VOS_API_KEY, or run \`vos login\` (mint a content key at ${mintAt(at)}; a vos_rg_ remix grant works too)`
482
703
  );
483
704
  }
484
705
  return key;
485
706
  }
486
- function writeCredential(key, origin = DEFAULT_ORIGIN, path = CREDENTIALS_PATH) {
487
- mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
488
- writeFileSync2(path, `${key.trim()}
489
- origin=${originOf(origin)}
490
- `, {
491
- mode: 384
707
+ function writeCredential(key, origin = DEFAULT_ORIGIN, path = CREDENTIALS_PATH, account = null) {
708
+ return storeLogin(key, origin, account, {
709
+ ...DEFAULT_STORE,
710
+ ...path !== CREDENTIALS_PATH ? { credentialsPath: path } : {}
711
+ }).path;
712
+ }
713
+ function storeLogin(key, origin, account, store = DEFAULT_STORE) {
714
+ return storeCredential(store, {
715
+ key: key.trim(),
716
+ origin: originOf(origin),
717
+ account
492
718
  });
493
- return path;
494
719
  }
495
720
  function clientId(env = process.env, exists) {
496
721
  const explicit = env.VOS_CLIENT?.trim();
@@ -560,13 +785,13 @@ function hostedMediaOf(value) {
560
785
  }
561
786
  function readJsonFile(file) {
562
787
  try {
563
- return JSON.parse(readFileSync(file, "utf8"));
788
+ return JSON.parse(readFileSync2(file, "utf8"));
564
789
  } catch {
565
790
  return null;
566
791
  }
567
792
  }
568
793
  function readSyncState(dir) {
569
- const own = readJsonFile(join(dir, SYNC_STATE_NAME));
794
+ const own = readJsonFile(join2(dir, SYNC_STATE_NAME));
570
795
  if (own && typeof own.vosId === "string") {
571
796
  return {
572
797
  vosId: own.vosId,
@@ -578,7 +803,7 @@ function readSyncState(dir) {
578
803
  ...hostedMediaOf(own.hostedMedia) ? { hostedMedia: hostedMediaOf(own.hostedMedia) } : {}
579
804
  };
580
805
  }
581
- const push = readJsonFile(join(dir, "push.json"));
806
+ const push = readJsonFile(join2(dir, "push.json"));
582
807
  if (push && typeof push.vosId === "string") {
583
808
  return {
584
809
  vosId: push.vosId,
@@ -586,7 +811,7 @@ function readSyncState(dir) {
586
811
  ...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
587
812
  };
588
813
  }
589
- const meta = readJsonFile(join(dir, "meta.json"));
814
+ const meta = readJsonFile(join2(dir, "meta.json"));
590
815
  if (meta && typeof meta.id === "string") {
591
816
  return {
592
817
  vosId: meta.id,
@@ -604,7 +829,7 @@ function writeSyncState(dir, patch) {
604
829
  ...patch
605
830
  };
606
831
  writeFileSafeSync(
607
- join(dir, SYNC_STATE_NAME),
832
+ join2(dir, SYNC_STATE_NAME),
608
833
  `${JSON.stringify(merged, null, 2)}
609
834
  `
610
835
  );
@@ -631,19 +856,19 @@ function serverWarnings(body, log) {
631
856
  import { createHash } from "crypto";
632
857
  import {
633
858
  closeSync,
634
- existsSync as existsSync2,
859
+ existsSync as existsSync3,
635
860
  mkdirSync as mkdirSync3,
636
861
  openSync,
637
862
  readSync,
638
- readdirSync,
863
+ readdirSync as readdirSync2,
639
864
  realpathSync,
640
865
  renameSync,
641
- rmSync,
866
+ rmSync as rmSync2,
642
867
  statSync,
643
868
  writeFileSync as writeFileSync3
644
869
  } from "fs";
645
870
  import { homedir as homedir2 } from "os";
646
- import { extname, join as join2, resolve, sep } from "path";
871
+ import { extname, join as join3, resolve, sep } from "path";
647
872
  var ASSET_ROUTE = "/__assets/";
648
873
  var UUID = "[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}";
649
874
  var HOSTED_URI = new RegExp(`^asset:(${UUID})$`);
@@ -729,7 +954,7 @@ function localFile(ref, baseDir) {
729
954
  };
730
955
  }
731
956
  const file = resolve(baseDir, ref);
732
- if (!existsSync2(file) || !statSync(file).isFile()) {
957
+ if (!existsSync3(file) || !statSync(file).isFile()) {
733
958
  return { refused: "which is not a file beside the config" };
734
959
  }
735
960
  const root = realpathSync(baseDir);
@@ -765,11 +990,11 @@ function manifestRefs(config) {
765
990
  return out;
766
991
  }
767
992
  async function fetchHosted(id, opts) {
768
- const dir = opts.cacheDir ?? join2(homedir2(), ".cache", "vos", "assets");
769
- const cached = existsSync2(dir) ? readdirSync(dir).find(
993
+ const dir = opts.cacheDir ?? join3(homedir2(), ".cache", "vos", "assets");
994
+ const cached = existsSync3(dir) ? readdirSync2(dir).find(
770
995
  (f) => (f === id || f.startsWith(`${id}.`)) && !f.endsWith(".part")
771
996
  ) : void 0;
772
- if (cached) return join2(dir, cached);
997
+ if (cached) return join3(dir, cached);
773
998
  if (!opts.origin || !opts.key) {
774
999
  opts.log(
775
1000
  `note: asset ${id} is hosted and no credential is set, so the render cannot load it (vos login, or keep the file beside the config and name its path)`
@@ -787,7 +1012,7 @@ async function fetchHosted(id, opts) {
787
1012
  }
788
1013
  mkdirSync3(dir, { recursive: true, mode: 448 });
789
1014
  const type = (res.headers.get("content-type") ?? "").split(";")[0].trim();
790
- const file = join2(dir, `${id}${extensionFor(type)}`);
1015
+ const file = join3(dir, `${id}${extensionFor(type)}`);
791
1016
  const partial = `${file}.part`;
792
1017
  try {
793
1018
  writeFileSync3(partial, new Uint8Array(await res.arrayBuffer()), {
@@ -795,7 +1020,7 @@ async function fetchHosted(id, opts) {
795
1020
  });
796
1021
  renameSync(partial, file);
797
1022
  } finally {
798
- rmSync(partial, { force: true });
1023
+ rmSync2(partial, { force: true });
799
1024
  }
800
1025
  return file;
801
1026
  }
@@ -920,10 +1145,25 @@ export {
920
1145
  numFlag2,
921
1146
  strFlag,
922
1147
  hasFlag,
1148
+ CREDENTIALS_PATH,
1149
+ ACCOUNT_ENV,
1150
+ DEFAULT_STORE,
1151
+ originOf,
1152
+ accountFrom,
1153
+ readActive,
1154
+ listStored,
1155
+ isActive,
1156
+ accountLabel,
1157
+ findStored,
1158
+ activate,
1159
+ removeStored,
1160
+ removeAll,
1161
+ switchTarget,
923
1162
  platformOrigin,
924
1163
  parseVosId,
925
1164
  deriveSlug,
926
- CREDENTIALS_PATH,
1165
+ lastCredential,
1166
+ credentialFlags,
927
1167
  resolveCredential,
928
1168
  bearerFor,
929
1169
  mintAt,
@@ -947,4 +1187,4 @@ export {
947
1187
  programAssets,
948
1188
  missingManifestFiles
949
1189
  };
950
- //# sourceMappingURL=chunk-EPSYIF2S.js.map
1190
+ //# sourceMappingURL=chunk-PZP76YJI.js.map