@volter/world 3.0.40 → 3.0.42

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/dist/src/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export declare const HELP = "volter \u2014 run your app against a world of twins\n\nworld \u2014 the twins your app needs, running together (the world of the current directory)\n volter world init [--install] [--name <world>] [--allow-unknown] detect the app's vendors (installing their twins when none are), write .volter/world.json\n volter world up [--sandbox] [--no-seed] start the twins on this branch (loads the default data the first time)\n volter world run [--verbose] -- <command...> run the app or its tests inside the world\n volter world activate eval \"$(volter world activate)\": vendor CLIs and curl in this shell reach the twins\n volter world shell a subshell with the world active\n volter world init --bare <org>/<world> --twins a,b a world with no app: a shared world for a team, or one standing in for a vendor\n volter world serve [--port <p>] [--console-port <c>] [--origin <url>] serve this world on a URL under /<org>/<world>/ for apps; prints the token and the console (on loopback: view's front, no token in the browser)\n volter world view [--port <p>] [--host <h>] [--no-open] [--no-origins] [--origin <url>] step into this world in a browser: its twins' own UIs, timeline, clock, branches (a non-loopback --host serves them on one origin)\n volter world console [<https://host/org/world>] [--port <p>] the console on this machine for a World served elsewhere (default: this world's origin); needs @volter/world-console\n volter world status world, branch, origin, unpushed changes, what is running\n volter world log [--receipts] [--json] every write the app made; --receipts adds each change's receipt\n volter world diff [--json] what changed since the default data (or the last mark)\n volter world seed load the default data\n volter world reset back to the default data\n volter world down [--purge] stop the twins (--purge forgets this branch's state)\n volter world branch [<name>] list branches, or make one from here and check it out\n volter world checkout <name> switch branches\n volter world replay <changeset> --into <branch> feed a changeset's changes into another branch's twins\n volter world clock show | set <iso> | advance <N s|m|h|d> | shift <N s|m|h|d> | clear the world's clock: set, not observed\n volter world clone <url> --token <token> [--admin-token <t>] connect this world to a remote and fetch its history (the admin token copies the whole tree at once)\n volter world fetch what the remote observed since the last fetch\n volter world origin where this world clones from and pushes to\n volter world changeset [<name>] -m \"<message>\" cut the unpushed changes into a reviewable changeset\n volter world pull [--token <token>] fetch, then move this branch onto what came in (git's pull)\n volter world push [<name>] [--token <token>] [--force] push to the remote, which deploys and answers with receipts\n volter world rebase [<changeset>] rebase this branch onto its moved base, or a changeset onto the moved remote\n volter world verify <changeset> run the world's checks over a changeset and record the result\n volter world approve <changeset> --as <principal> sign a changeset's current hash\n volter world deploy [<changeset>] perform landed changes against each twin's root, by its policy\n\ntwin \u2014 one twin: in this world, or on its own\n volter twin <vendor> the twin's URL, root, deploy policy, whether a credential is sealed\n volter twin <vendor> root <url> [--deploy auto|gated|hold] [--scope <path>] the vendor's real account behind this twin (--none clears it)\n volter twin <vendor> credential seal a credential read from stdin beside the world; never readable back\n volter twin <vendor> refresh observe the root now\n volter twin <vendor> serve [--port <p>] [--read-only] serve the twin; your SDK talks to it at http://127.0.0.1:<p>\n\nagents \u2014 your coding agent, driving the same verbs\n volter agents install [--claude] [--cursor] [--vscode] [--codex] [--yes] put the volter-world skill and the MCP server into the agents found here\n volter mcp the MCP server (stdio) an agent starts: the verbs above as tools\n\nremote \u2014 the worlds this one pushes to and fetches from, by name\n volter login <platform url> [--token <personal token>] sign the CLI into the hosted platform: approve it in the browser (or a token made under Account, for CI)\n volter whoami the platform the CLI is signed into, as whom, and its orgs\n volter logout revoke the CLI's token at the platform and forget it here\n volter remote add <name> <url|path|org/world> [--token <token>] [--create] name a remote; org/world (or world, in one org) resolves through the platform you logged into; --create makes it there from this world's twins\n volter open [<org>/<world>] [--no-open] a World's dashboard: a hosted one through the platform, else this world's (as view)\n volter completion bash|zsh|fish|powershell print a completion script for your shell\n volter remote remove <name> forget it\n volter remote list every remote, with its URL and whether a token is stored\n (the hosting product's serve answers here for one release: volter remote serve)\n\n --world <dir> the world root, when the cwd is not inside it\n --branch <name> act on that branch instead of the checked-out one\n --json machine-readable output where offered\n --version the command's version\n";
2
+ export declare const HELP = "volter \u2014 run your app against a world of twins\n\nworld \u2014 the twins your app needs, running together (the world of the current directory)\n volter world init [--install] [--name <world>] [--allow-unknown] detect the app's vendors (installing their twins when none are), write .volter/world.json\n volter world up [--sandbox] [--no-seed] start the twins on this branch (loads the default data the first time)\n volter world run [--verbose] -- <command...> run the app or its tests inside the world\n volter world activate eval \"$(volter world activate)\": vendor CLIs and curl in this shell reach the twins\n volter world shell a subshell with the world active\n volter world init --bare <org>/<world> --twins a,b a world with no app: a shared world for a team, or one standing in for a vendor\n volter world serve [--port <p>] [--console-port <c>] [--origin <url>] serve this world on a URL under /<org>/<world>/ for apps; prints the token and the console (on loopback: view's front, no token in the browser)\n volter world view [--port <p>] [--host <h>] [--no-open] [--no-origins] [--origin <url>] step into this world in a browser: its twins' own UIs, timeline, clock, branches (a non-loopback --host serves them on one origin)\n volter world console [<https://host/org/world>] [--port <p>] the console on this machine for a World served elsewhere (default: this world's origin); needs @volter/world-console\n volter world status world, branch, origin, unpushed changes, what is running\n volter world log [--receipts] [--json] every write the app made; --receipts adds each change's receipt\n volter world diff [--json] what changed since the default data (or the last mark)\n volter world seed load the default data\n volter world reset back to the default data\n volter world down [--purge] stop the twins (--purge forgets this branch's state)\n volter world branch [<name>] list branches, or make one from here and check it out\n volter world checkout <name> switch branches\n volter world replay <changeset> --into <branch> feed a changeset's changes into another branch's twins\n volter world clock show | set <iso> | advance <N s|m|h|d> | shift <N s|m|h|d> | clear the world's clock: set, not observed\n volter world clone <url> --token <token> [--admin-token <t>] connect this world to a remote and fetch its history (the admin token copies the whole tree at once)\n volter world fetch what the remote observed since the last fetch\n volter world origin where this world clones from and pushes to\n volter world changeset [<name>] -m \"<message>\" cut the unpushed changes into a reviewable changeset\n volter world pull [--token <token>] fetch, then move this branch onto what came in (git's pull)\n volter world push [<name>] [--token <token>] [--force] push to the remote, which deploys and answers with receipts\n volter world rebase [<changeset>] rebase this branch onto its moved base, or a changeset onto the moved remote\n volter world verify <changeset> run the world's checks over a changeset and record the result\n volter world approve <changeset> --as <principal> sign a changeset's current hash\n volter world deploy [<changeset>] perform landed changes against each twin's root, by its policy\n\ntwin \u2014 one twin: in this world, or on its own\n volter twin submit --source <id> --vendor <vendor> --package <name> --version <exact> prepare a catalog submission\n volter twin <vendor> the twin's URL, root, deploy policy, whether a credential is sealed\n volter twin <vendor> root <url> [--deploy auto|gated|hold] [--scope <path>] the vendor's real account behind this twin (--none clears it)\n volter twin <vendor> credential seal a credential read from stdin beside the world; never readable back\n volter twin <vendor> refresh observe the root now\n volter twin <vendor> serve [--port <p>] [--read-only] serve the twin; your SDK talks to it at http://127.0.0.1:<p>\n\nagents \u2014 your coding agent, driving the same verbs\n volter agents install [--claude] [--cursor] [--vscode] [--codex] [--yes] put the volter-world skill and the MCP server into the agents found here\n volter mcp the MCP server (stdio) an agent starts: the verbs above as tools\n\nremote \u2014 the worlds this one pushes to and fetches from, by name\n volter login <platform url> [--token <personal token>] sign the CLI into the hosted platform: approve it in the browser (or a token made under Account, for CI)\n volter whoami the platform the CLI is signed into, as whom, and its orgs\n volter logout revoke the CLI's token at the platform and forget it here\n volter remote add <name> <url|path|org/world> [--token <token>] [--create] name a remote; org/world (or world, in one org) resolves through the platform you logged into; --create makes it there from this world's twins\n volter open [<org>/<world>] [--no-open] a World's dashboard: a hosted one through the platform, else this world's (as view)\n volter completion bash|zsh|fish|powershell print a completion script for your shell\n volter remote remove <name> forget it\n volter remote list every remote, with its URL and whether a token is stored\n (the hosting product's serve answers here for one release: volter remote serve)\n\n --world <dir> the world root, when the cwd is not inside it\n --branch <name> act on that branch instead of the checked-out one\n --json machine-readable output where offered\n --version the command's version\n";
package/dist/src/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // verb. `volter world …` acts on the world of the cwd the way git acts on the repository of the
4
4
  // cwd; `volter twin <vendor> …` runs one twin on its own; `volter remote …` is the hosted remote.
5
5
  // One client of `World`; it adds nothing the SDK does not have.
6
- import { detectRepoVendors, errorMessage, formatInitReport, resolveCatalog, findInstalledPackage, packCli, serveConsoleFor } from '@volter/world-runtime';
6
+ import { detectRepoVendors, errorMessage, formatInitReport, resolveCatalog, findInstalledPackage, packCli, serveConsoleFor, catalogDefaults, CATALOG_INDEX_PACKAGE } from '@volter/world-runtime';
7
7
  import { readFileSync, realpathSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { spawn, spawnSync } from 'node:child_process';
@@ -70,6 +70,7 @@ world — the twins your app needs, running together (the world of the current
70
70
  volter world deploy [<changeset>] perform landed changes against each twin's root, by its policy
71
71
 
72
72
  twin — one twin: in this world, or on its own
73
+ volter twin submit --source <id> --vendor <vendor> --package <name> --version <exact> prepare a catalog submission
73
74
  volter twin <vendor> the twin's URL, root, deploy policy, whether a credential is sealed
74
75
  volter twin <vendor> root <url> [--deploy auto|gated|hold] [--scope <path>] the vendor's real account behind this twin (--none clears it)
75
76
  volter twin <vendor> credential seal a credential read from stdin beside the world; never readable back
@@ -595,6 +596,14 @@ async function worldCommand(verb, args) {
595
596
  /** `volter twin <vendor> <verb…>`: the twin's own CLI (serve), found the way a world finds it. */
596
597
  async function twinCommand(args) {
597
598
  const [vendor, ...rest] = args;
599
+ if (vendor === 'submit') {
600
+ const catalog = findInstalledPackage(process.cwd(), CATALOG_INDEX_PACKAGE);
601
+ const cli = catalog && join(catalog, 'bin', 'twin-catalog.mjs');
602
+ if (!cli || !existsSync(cli))
603
+ throw new Error('Install @volter/twin-catalog to submit a twin; its catalog-owned CLI prepares the submission file.');
604
+ process.exitCode = await passthrough([process.execPath, cli, 'submit', ...rest]);
605
+ return;
606
+ }
598
607
  if (!vendor || vendor.startsWith('-'))
599
608
  throw new Error('volter twin: which twin? (volter twin stripe serve --port 12111)');
600
609
  const verb = rest[0];
@@ -965,19 +974,30 @@ ${Object.entries(tree).map(([n, v]) => ` '${n}' = @(${v.map((x) => `'${x}'`).
965
974
  * depends on and names in its env (the signatures ship with the command), and the twins for them are installed with
966
975
  * the app's own package manager: asked at a terminal, or at once with `--install` (a script's or an agent's way). */
967
976
  async function installDetectedTwins(app, install) {
977
+ let installed = new Set();
968
978
  try {
969
- if (resolveCatalog(app).vendors().length > 0)
970
- return;
979
+ installed = new Set(resolveCatalog(app).vendors());
980
+ }
981
+ catch (error) {
982
+ if (!(error instanceof Error) || !error.message.startsWith('No twins found for '))
983
+ throw error;
971
984
  }
972
- catch { /* no twins here yet */ }
973
985
  const found = detectRepoVendors(app).detected;
974
- const vendors = [...found.keys()].filter((v) => /^[a-z0-9][a-z0-9-]*$/.test(v)).sort();
986
+ const vendors = [...found.keys()].filter((v) => !installed.has(v) && /^[a-z0-9][a-z0-9-]*$/.test(v)).sort();
975
987
  if (vendors.length === 0)
976
988
  return; // init says what it found and how to add a twin by hand
977
- const pkgs = vendors.map((v) => `@volter/twin-${v}`);
989
+ const index = findInstalledPackage(app, CATALOG_INDEX_PACKAGE) ?? findInstalledPackage(fileURLToPath(new URL('.', import.meta.url)), CATALOG_INDEX_PACKAGE);
990
+ const recommended = index ? catalogDefaults(index, vendors) : undefined;
991
+ const pkgs = vendors.map((vendor) => {
992
+ const choice = recommended?.[vendor];
993
+ if (recommended && !choice)
994
+ throw new Error(`${vendor}: no approved stable default in the installed catalog; choose an implementation explicitly`);
995
+ return choice ? `${choice.name}@${choice.version}` : `@volter/twin-${vendor}`;
996
+ });
978
997
  const has = (file) => existsSync(join(app, file));
979
998
  // the app's own package manager, by its lockfile; npm where there is none
980
- const command = has('bun.lock') || has('bun.lockb') ? `bun add -d ${pkgs.join(' ')}` : has('pnpm-lock.yaml') ? `pnpm add -D ${pkgs.join(' ')}` : has('yarn.lock') ? `yarn add -D ${pkgs.join(' ')}` : `npm install -D ${pkgs.join(' ')}`;
999
+ const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
1000
+ const command = [...invocation, ...pkgs].join(' ');
981
1001
  out(`detected ${vendors.map((v) => `${v} (${(found.get(v) ?? []).slice(0, 2).join(', ')})`).join('; ')}`);
982
1002
  out(`twins ${pkgs.join(' ')}`);
983
1003
  let yes = install;
@@ -992,7 +1012,7 @@ async function installDetectedTwins(app, install) {
992
1012
  then run volter world init again (or volter world init --install does both)`);
993
1013
  // one command line built from the checked names above: nothing of anyone's input reaches the shell
994
1014
  // a dependency's install script is not Volter's: it never sees the person's vault sign-in
995
- const ran = spawnSync(command, { cwd: app, stdio: 'inherit', shell: true, windowsHide: true, env: withoutVaultSignIn(process.env) });
1015
+ const ran = spawnSync(invocation[0], [...invocation.slice(1), ...pkgs], { cwd: app, stdio: 'inherit', windowsHide: true, env: withoutVaultSignIn(process.env) });
996
1016
  if (ran.status !== 0)
997
1017
  throw new Error(`${command} failed (exit ${ran.status ?? 'none'})`);
998
1018
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world",
3
- "version": "3.0.40",
3
+ "version": "3.0.42",
4
4
  "description": "Run your app against a world of twins. `World` and `Repo` — one method per verb: init, up, run, log, diff, reset, branch, checkout, clone, fetch, changeset, push — and the `volter` command, one client of them.",
5
5
  "keywords": [
6
6
  "volter",
@@ -30,12 +30,12 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@modelcontextprotocol/sdk": "^1.30.1",
33
- "@volter/world-core": "3.0.40",
34
- "@volter/world-runtime": "3.0.40",
33
+ "@volter/world-core": "3.0.42",
34
+ "@volter/world-runtime": "3.0.42",
35
35
  "zod": "^4.6.5"
36
36
  },
37
37
  "peerDependencies": {
38
- "@volter/world-console": "3.0.35"
38
+ "@volter/world-console": "3.0.37"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@volter/world-console": {
@@ -43,7 +43,7 @@
43
43
  }
44
44
  },
45
45
  "devDependencies": {
46
- "@volter/world-console": "3.0.35"
46
+ "@volter/world-console": "3.0.37"
47
47
  },
48
48
  "bin": {
49
49
  "volter": "dist/src/cli.js"
package/src/cli.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // verb. `volter world …` acts on the world of the cwd the way git acts on the repository of the
4
4
  // cwd; `volter twin <vendor> …` runs one twin on its own; `volter remote …` is the hosted remote.
5
5
  // One client of `World`; it adds nothing the SDK does not have.
6
- import { detectRepoVendors, errorMessage, formatInitReport, resolveCatalog, findInstalledPackage, packCli, serveConsoleFor } from '@volter/world-runtime';
6
+ import { detectRepoVendors, errorMessage, formatInitReport, resolveCatalog, findInstalledPackage, packCli, serveConsoleFor, catalogDefaults, CATALOG_INDEX_PACKAGE } from '@volter/world-runtime';
7
7
  import { readFileSync, realpathSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { spawn, spawnSync } from 'node:child_process';
@@ -62,6 +62,7 @@ world — the twins your app needs, running together (the world of the current
62
62
  volter world deploy [<changeset>] perform landed changes against each twin's root, by its policy
63
63
 
64
64
  twin — one twin: in this world, or on its own
65
+ volter twin submit --source <id> --vendor <vendor> --package <name> --version <exact> prepare a catalog submission
65
66
  volter twin <vendor> the twin's URL, root, deploy policy, whether a credential is sealed
66
67
  volter twin <vendor> root <url> [--deploy auto|gated|hold] [--scope <path>] the vendor's real account behind this twin (--none clears it)
67
68
  volter twin <vendor> credential seal a credential read from stdin beside the world; never readable back
@@ -429,6 +430,13 @@ async function worldCommand(verb: string, args: string[]): Promise<void> {
429
430
  /** `volter twin <vendor> <verb…>`: the twin's own CLI (serve), found the way a world finds it. */
430
431
  async function twinCommand(args: string[]): Promise<void> {
431
432
  const [vendor, ...rest] = args;
433
+ if (vendor === 'submit') {
434
+ const catalog = findInstalledPackage(process.cwd(), CATALOG_INDEX_PACKAGE);
435
+ const cli = catalog && join(catalog, 'bin', 'twin-catalog.mjs');
436
+ if (!cli || !existsSync(cli)) throw new Error('Install @volter/twin-catalog to submit a twin; its catalog-owned CLI prepares the submission file.');
437
+ process.exitCode = await passthrough([process.execPath, cli, 'submit', ...rest]);
438
+ return;
439
+ }
432
440
  if (!vendor || vendor.startsWith('-')) throw new Error('volter twin: which twin? (volter twin stripe serve --port 12111)');
433
441
  const verb = rest[0];
434
442
  // the twin IN THIS WORLD: its root, its credential, a refresh, its status
@@ -703,14 +711,24 @@ ${Object.entries(tree).map(([n, v]) => ` '${n}' = @(${v.map((x) => `'${x}'`).
703
711
  * depends on and names in its env (the signatures ship with the command), and the twins for them are installed with
704
712
  * the app's own package manager: asked at a terminal, or at once with `--install` (a script's or an agent's way). */
705
713
  async function installDetectedTwins(app: string, install: boolean): Promise<void> {
706
- try { if (resolveCatalog(app).vendors().length > 0) return; } catch { /* no twins here yet */ }
714
+ let installed = new Set<string>();
715
+ try { installed = new Set(resolveCatalog(app).vendors()); } catch (error) {
716
+ if (!(error instanceof Error) || !error.message.startsWith('No twins found for ')) throw error;
717
+ }
707
718
  const found = detectRepoVendors(app).detected;
708
- const vendors = [...found.keys()].filter((v) => /^[a-z0-9][a-z0-9-]*$/.test(v)).sort();
719
+ const vendors = [...found.keys()].filter((v) => !installed.has(v) && /^[a-z0-9][a-z0-9-]*$/.test(v)).sort();
709
720
  if (vendors.length === 0) return; // init says what it found and how to add a twin by hand
710
- const pkgs = vendors.map((v) => `@volter/twin-${v}`);
721
+ const index = findInstalledPackage(app, CATALOG_INDEX_PACKAGE) ?? findInstalledPackage(fileURLToPath(new URL('.', import.meta.url)), CATALOG_INDEX_PACKAGE);
722
+ const recommended = index ? catalogDefaults(index, vendors) : undefined;
723
+ const pkgs = vendors.map((vendor) => {
724
+ const choice = recommended?.[vendor];
725
+ if (recommended && !choice) throw new Error(`${vendor}: no approved stable default in the installed catalog; choose an implementation explicitly`);
726
+ return choice ? `${choice.name}@${choice.version}` : `@volter/twin-${vendor}`;
727
+ });
711
728
  const has = (file: string): boolean => existsSync(join(app, file));
712
729
  // the app's own package manager, by its lockfile; npm where there is none
713
- const command = has('bun.lock') || has('bun.lockb') ? `bun add -d ${pkgs.join(' ')}` : has('pnpm-lock.yaml') ? `pnpm add -D ${pkgs.join(' ')}` : has('yarn.lock') ? `yarn add -D ${pkgs.join(' ')}` : `npm install -D ${pkgs.join(' ')}`;
730
+ const invocation = has('bun.lock') || has('bun.lockb') ? ['bun', 'add', '-d'] : has('pnpm-lock.yaml') ? ['pnpm', 'add', '-D'] : has('yarn.lock') ? ['yarn', 'add', '-D'] : ['npm', 'install', '-D'];
731
+ const command = [...invocation, ...pkgs].join(' ');
714
732
  out(`detected ${vendors.map((v) => `${v} (${(found.get(v) ?? []).slice(0, 2).join(', ')})`).join('; ')}`);
715
733
  out(`twins ${pkgs.join(' ')}`);
716
734
  let yes = install;
@@ -724,7 +742,7 @@ async function installDetectedTwins(app: string, install: boolean): Promise<void
724
742
  then run volter world init again (or volter world init --install does both)`);
725
743
  // one command line built from the checked names above: nothing of anyone's input reaches the shell
726
744
  // a dependency's install script is not Volter's: it never sees the person's vault sign-in
727
- const ran = spawnSync(command, { cwd: app, stdio: 'inherit', shell: true, windowsHide: true, env: withoutVaultSignIn(process.env) });
745
+ const ran = spawnSync(invocation[0]!, [...invocation.slice(1), ...pkgs], { cwd: app, stdio: 'inherit', windowsHide: true, env: withoutVaultSignIn(process.env) });
728
746
  if (ran.status !== 0) throw new Error(`${command} failed (exit ${ran.status ?? 'none'})`);
729
747
  }
730
748