@volter/world 3.0.190 → 3.0.192

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.
@@ -13,7 +13,7 @@ You need Node 22.6+, pnpm 12.15.0, Docker Compose and registry access during ins
13
13
  Copy this starter into a new folder:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.190 example customer-onboarding --dir onboarding
16
+ $ npx @volter/world@3.0.192 example customer-onboarding --dir onboarding
17
17
  $ cd onboarding
18
18
  $ pnpm install
19
19
  ```
@@ -11,13 +11,13 @@
11
11
  "pg": "8.16.3"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.190",
15
- "@volter/world-core": "3.0.164",
16
- "@volter/world-runtime": "3.0.188",
17
- "@volter/world-console": "3.0.160",
14
+ "@volter/world": "3.0.192",
15
+ "@volter/world-core": "3.0.165",
16
+ "@volter/world-runtime": "3.0.190",
17
+ "@volter/world-console": "3.0.161",
18
18
  "@volter/twin-stripe": "3.0.2",
19
19
  "@volter/twin-resend": "1.0.1",
20
- "@volter/world-ui": "1.0.163"
20
+ "@volter/world-ui": "1.0.164"
21
21
  },
22
22
  "packageManager": "pnpm@12.15.0",
23
23
  "devEngines": {
@@ -13,7 +13,7 @@ Give two workers the same Stripe baseline, let them change different existing cu
13
13
  Use an empty parent folder. Create the baseline starter there:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.190 example integration-branches --dir baseline
16
+ $ npx @volter/world@3.0.192 example integration-branches --dir baseline
17
17
  $ cd baseline
18
18
  $ pnpm install
19
19
  $ ./node_modules/.bin/volter world init --name baseline --twins stripe
@@ -38,9 +38,9 @@ The stopped baseline retains its history and can supply local path remotes. Defa
38
38
  Create three more copies in the same parent folder:
39
39
 
40
40
  ```console
41
- $ npx @volter/world@3.0.190 example integration-branches --dir integration
42
- $ npx @volter/world@3.0.190 example integration-branches --dir worker-a
43
- $ npx @volter/world@3.0.190 example integration-branches --dir worker-b
41
+ $ npx @volter/world@3.0.192 example integration-branches --dir integration
42
+ $ npx @volter/world@3.0.192 example integration-branches --dir worker-a
43
+ $ npx @volter/world@3.0.192 example integration-branches --dir worker-b
44
44
  ```
45
45
 
46
46
  In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
@@ -9,12 +9,12 @@
9
9
  "stripe": "17.7.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@volter/world": "3.0.190",
13
- "@volter/world-core": "3.0.164",
14
- "@volter/world-runtime": "3.0.188",
15
- "@volter/world-console": "3.0.160",
12
+ "@volter/world": "3.0.192",
13
+ "@volter/world-core": "3.0.165",
14
+ "@volter/world-runtime": "3.0.190",
15
+ "@volter/world-console": "3.0.161",
16
16
  "@volter/twin-stripe": "3.0.2",
17
- "@volter/world-ui": "1.0.163"
17
+ "@volter/world-ui": "1.0.164"
18
18
  },
19
19
  "packageManager": "pnpm@12.15.0",
20
20
  "devEngines": {
@@ -19,7 +19,7 @@ The catalog offers current defaults. Keep this example's manifest and installati
19
19
  together; its recorded workflow does not establish behavior for a different release.
20
20
 
21
21
  ```console
22
- $ npx @volter/world@3.0.190 example workplace-agent --dir workplace
22
+ $ npx @volter/world@3.0.192 example workplace-agent --dir workplace
23
23
  $ cd workplace
24
24
  $ pnpm install
25
25
  $ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
@@ -11,14 +11,14 @@
11
11
  "@slack/web-api": "7.19.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.190",
15
- "@volter/world-core": "3.0.164",
16
- "@volter/world-runtime": "3.0.188",
17
- "@volter/world-console": "3.0.160",
14
+ "@volter/world": "3.0.192",
15
+ "@volter/world-core": "3.0.165",
16
+ "@volter/world-runtime": "3.0.190",
17
+ "@volter/world-console": "3.0.161",
18
18
  "@volter/twin-github": "3.0.9",
19
19
  "@volter/twin-linear": "3.0.5",
20
20
  "@volter/twin-slack": "3.0.8",
21
- "@volter/world-ui": "1.0.163"
21
+ "@volter/world-ui": "1.0.164"
22
22
  },
23
23
  "packageManager": "pnpm@12.15.0",
24
24
  "devEngines": {
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 a shared simulated internet (the world of the current directory)\n volter world init [--install] [--name <world>] [--twins a,b] [--source vendor=package] [--allow-unknown] detect or select vendors, choose installed publishers, 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 env [--template <file>] render deployment .env.world references into dotenv files\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 example list ready application starters included in this CLI release\n volter example <name> [--dir <path>] copy a cookbook starter into an empty directory; does not install or start it\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>] [--remote <remote>] [--token <token>] [--force] send entries to the selected remote (default origin); vendor deployment follows its root policy\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";
2
+ export declare const HELP = "volter \u2014 run your app against a world of twins\n\nworld \u2014 a shared simulated internet (the world of the current directory)\n volter world init [--install] [--name <world>] [--twins a,b] [--source vendor=package] [--allow-unknown] detect or select vendors, choose installed publishers, 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 env [--template <file>] render deployment .env.world references into dotenv files\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 example list ready application starters included in this CLI release\n volter example <name> [--dir <path>] copy a cookbook starter into an empty directory; does not install or start it\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 generate write seed.generated.ts from world.json's people, organizations and accounts\n volter world browser <name> [url] [--cdp <endpoint>] open a person's saved World browser, signed in, through the running World (--cdp loads its cookies into a browser already open)\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>] [--remote <remote>] [--token <token>] [--force] send entries to the selected remote (default origin); vendor deployment follows its root policy\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
@@ -16,6 +16,7 @@ import { forgetPlatform, forgetToken, platformOrigin, storePlatform, storeToken,
16
16
  import { parseOriginUrl, World } from "./world.js";
17
17
  import { copyExample, examples } from "./examples.js";
18
18
  import { waitForServeShutdown } from "./serve-shutdown.js";
19
+ import { openWorldBrowser } from "./world-browser.js";
19
20
  import { currentVersion, startUpdateCheck } from "./update-notice.js";
20
21
  import { withoutVaultSignIn } from '@volter/world-runtime';
21
22
  /** Open a URL in the person's browser; a machine without one only loses the convenience. */
@@ -56,6 +57,8 @@ world — a shared simulated internet (the world of the current directory)
56
57
  volter world status world, branch, origin, unpushed changes, what is running
57
58
  volter world log [--receipts] [--json] every write the app made; --receipts adds each change's receipt
58
59
  volter world diff [--json] what changed since the default data (or the last mark)
60
+ volter world generate write seed.generated.ts from world.json's people, organizations and accounts
61
+ volter world browser <name> [url] [--cdp <endpoint>] open a person's saved World browser, signed in, through the running World (--cdp loads its cookies into a browser already open)
59
62
  volter world seed load the default data
60
63
  volter world reset back to the default data
61
64
  volter world down [--purge] stop the twins (--purge forgets this branch's state)
@@ -127,7 +130,7 @@ function positionals(args, valueFlags) {
127
130
  return out;
128
131
  }
129
132
  function afterDashes(args) { const i = args.indexOf('--'); return i < 0 ? [] : args.slice(i + 1); }
130
- const VALUE_FLAGS = ['--world', '--branch', '--name', '--token', '--admin-token', '-m', '--message', '--base', '--org', '--vendors', '--into', '--deploy', '--as', '--note', '--port', '--twins', '--source', '--host', '--origin', '--remote', '--dir', '--scope', '--refresh', '--at-most', '--at', '--template', '--output-root', '--app', '--app-url', '--template-out'];
133
+ const VALUE_FLAGS = ['--world', '--branch', '--name', '--token', '--admin-token', '-m', '--message', '--base', '--org', '--vendors', '--into', '--deploy', '--as', '--note', '--port', '--twins', '--source', '--host', '--origin', '--remote', '--dir', '--scope', '--refresh', '--at-most', '--at', '--template', '--output-root', '--app', '--app-url', '--template-out', '--cdp'];
131
134
  /** Help is resolved before opening a World; the child after `run --` owns its own flags. */
132
135
  function wantsHelp(noun, verb, args) {
133
136
  const boundary = args.indexOf('--');
@@ -344,6 +347,20 @@ async function worldCommand(verb, args) {
344
347
  out(` ${v.service}: ${v.count} change${v.count === 1 ? '' : 's'}`);
345
348
  return;
346
349
  }
350
+ case 'browser': {
351
+ const [name, url] = pos;
352
+ if (!name)
353
+ throw new Error('volter world browser <name> [url] [--cdp <endpoint>]');
354
+ await openWorldBrowser(world, name, url, value(args, '--cdp'), out);
355
+ return;
356
+ }
357
+ case 'generate': {
358
+ const r = world.generate();
359
+ out(`${r.changed ? 'wrote' : 'unchanged'} ${relative(process.cwd(), r.path) || r.path}`);
360
+ if (!r.imported)
361
+ out('note seed.ts beside it does not import ./seed.generated.ts, and the World runs only seed.ts');
362
+ return;
363
+ }
347
364
  case 'seed': {
348
365
  const r = await world.seed();
349
366
  out(`Loaded the default data into ${r.world}: ${Object.entries(r.observed).map(([s, n]) => `${s} ${n}`).join(', ') || 'nothing recorded'}`);
@@ -718,7 +735,7 @@ async function twinCommand(args) {
718
735
  out(t.ingest ? `${vendor} ingest ${t.ingest} (register it at the vendor as the webhook URL, with the signing secret sealed with the credential)` : `${vendor} ingest: a served World's door (volter world serve, or a remote) is the webhook URL to register at the vendor`);
719
736
  return;
720
737
  }
721
- const dir = resolveCatalog(process.cwd()).packDir(vendor);
738
+ const dir = resolveCatalog(process.cwd(), { vendors: [vendor] }).packDir(vendor);
722
739
  if (dir === undefined)
723
740
  throw new Error(`No twin "${vendor}" here — \`pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/twin-${vendor}\`, or run from a checkout that has it`);
724
741
  if (rest.length === 0 || rest[0].startsWith('-'))
@@ -0,0 +1,4 @@
1
+ export declare function openWorldBrowser(world: {
2
+ name: string;
3
+ root: string;
4
+ }, name: string, url: string | undefined, cdp: string | undefined, say: (line: string) => void): Promise<void>;
@@ -0,0 +1,43 @@
1
+ // `volter world browser <name> [url] [--cdp <endpoint>]`: a person's saved World browser opened in a real browser
2
+ // (ADR 0016, rows 3 and 4). The profile is read through the World's own record reader and the browser is routed through
3
+ // the World's proxy with its CA trusted, as attach publishes them. Nothing is written back to the World.
4
+ import { deriveWorldManifest, statusWorld, worldBrowser, worldBrowsers } from '@volter/world-runtime';
5
+ export async function openWorldBrowser(world, name, url, cdp, say) {
6
+ const instance = statusWorld(world.name, world.root);
7
+ const record = worldBrowser(instance.dirs.data, name);
8
+ if (!record) {
9
+ const names = worldBrowsers(instance.dirs.data).map(row => row.name);
10
+ throw new Error(`World "${world.name}" has no browser "${name}" (has: ${names.join(', ') || 'none'})`);
11
+ }
12
+ const manifest = deriveWorldManifest(world.name, instance.env ?? {});
13
+ if (!instance.running || !manifest.browser)
14
+ throw new Error('the World is not running, so it has no proxy to route a browser through: start it with `volter world up`');
15
+ let chromium;
16
+ try {
17
+ ({ chromium } = await import('playwright'));
18
+ }
19
+ catch {
20
+ throw new Error('volter world browser needs playwright: npm i playwright');
21
+ }
22
+ const profile = record.state;
23
+ if (cdp === undefined) {
24
+ const browser = await chromium.launch({ headless: false, args: manifest.browser.args });
25
+ const closed = new Promise(resolve => browser.on('disconnected', () => resolve()));
26
+ const context = await browser.newContext({ ...profile.context, storageState: profile.storageState });
27
+ const page = await context.newPage();
28
+ await page.goto(url ?? 'about:blank');
29
+ await closed;
30
+ return;
31
+ }
32
+ const browser = await chromium.connectOverCDP(cdp);
33
+ const context = browser.contexts()[0];
34
+ if (!context)
35
+ throw new Error(`${cdp} has no default browser context to load ${name}'s cookies into`);
36
+ // An existing default context takes no context options (ADR 0016): its cookies are what is loaded.
37
+ if (Object.keys(profile.context).length)
38
+ say(`note ${name}'s context options (${Object.keys(profile.context).join(', ')}) were not applied: an existing browser's default context takes none`);
39
+ await context.addCookies(profile.storageState.cookies);
40
+ const page = await context.newPage();
41
+ await page.goto(url ?? 'about:blank');
42
+ await browser.close();
43
+ }
@@ -1,6 +1,6 @@
1
1
  import { renderDeploymentTemplates, type DeploymentTemplate } from '@volter/world-runtime/deployment-env';
2
2
  import { rebaseChangeset, worldBootMarker, type Changeset, type LedgerDelta, type TwinAction, type WorldMarker, type ApplyReceipt, applyTwinWrite, type TwinResource } from '@volter/world-core/runtime';
3
- import { approveWorldChangeset, createWorldChangeset, downWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, seedServedWorld, seedWorld, statusWorld, statusWorldChangeset, verifyWorldChangeset, type ServedWorld, type ViewConsole, type WorldView, rootCredentialInput, type DeployTwinOutcome, type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance } from '@volter/world-runtime';
3
+ import { approveWorldChangeset, createWorldChangeset, downWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, seedServedWorld, seedWorld, statusWorld, writeGeneratedSeed, statusWorldChangeset, verifyWorldChangeset, type ServedWorld, type ViewConsole, type WorldView, rootCredentialInput, type DeployTwinOutcome, type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance } from '@volter/world-runtime';
4
4
  import { rebaseBranch, type Receipt, type RootConfig } from '@volter/world-core/runtime';
5
5
  export type WorldRef = {
6
6
  name?: string;
@@ -146,6 +146,9 @@ export declare class World {
146
146
  entry?: string;
147
147
  cwd?: string;
148
148
  }): ReturnType<typeof seedWorld>;
149
+ /** Write `seed.generated.ts` beside world.json from the people, organizations and accounts it declares (ADR 0033), as a
150
+ * lockfile is written from its manifest: whether it changed, and whether `seed.ts`, which the World runs, imports it. */
151
+ generate(): ReturnType<typeof writeGeneratedSeed>;
149
152
  /** Whether this world has default data (`.volter/seed.ts`). */
150
153
  hasSeed(): boolean;
151
154
  /** Set up a served World this one is linked to from this world's seed: run there through its own twins, then marked. */
package/dist/src/world.js CHANGED
@@ -7,7 +7,7 @@ import { platformPerson } from "./credentials.js";
7
7
  import { rebaseChangeset, worldBootMarker, listActions, inWorldHistory, pushablePendingActions, pendingActions, applyTwinWrite, twinResources } from '@volter/world-core/runtime';
8
8
  import { spawnSync } from 'node:child_process';
9
9
  import clockForm from '@volter/world-core/world-clock';
10
- import { activateScript, approveWorldChangeset, branchWorld, clockFile, checkoutWorld, createWorldChangeset, diffWorld, downWorld, fetchFromOrigin, findWorldChangeset, initWorld, listWorldChangesets, listWorldMarks, listWorlds, markWorld, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, runWithWorldEnv, seedServedWorld, seedWorld, shellWorld, materializeBrowserFiles, statusWorld, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin, bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn, materializeRoots, instanceDir, worldChangesetRules } from '@volter/world-runtime';
10
+ import { activateScript, approveWorldChangeset, branchWorld, clockFile, checkoutWorld, createWorldChangeset, diffWorld, downWorld, fetchFromOrigin, findWorldChangeset, initWorld, listWorldChangesets, listWorldMarks, listWorlds, markWorld, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, runWithWorldEnv, seedServedWorld, seedWorld, shellWorld, materializeBrowserFiles, statusWorld, writeGeneratedSeed, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin, bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn, materializeRoots, instanceDir, worldChangesetRules } from '@volter/world-runtime';
11
11
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
12
  import { dirname, join, resolve } from 'node:path';
13
13
  import { requireToken, storeToken } from "./credentials.js";
@@ -248,6 +248,9 @@ export class World {
248
248
  // ── default data ────────────────────────────────────────────────────────────────────────
249
249
  /** Load the default data: the seed runs with every twin recording what it creates as data that was already there. */
250
250
  seed(opts = {}) { return seedWorld(this.name, { root: this.root, cwd: this.root, ...opts }); }
251
+ /** Write `seed.generated.ts` beside world.json from the people, organizations and accounts it declares (ADR 0033), as a
252
+ * lockfile is written from its manifest: whether it changed, and whether `seed.ts`, which the World runs, imports it. */
253
+ generate() { return writeGeneratedSeed(worldConfigPath(this.root), this.root); }
251
254
  /** Whether this world has default data (`.volter/seed.ts`). */
252
255
  hasSeed() { return existsSync(worldSeedPath(this.root)); }
253
256
  /** Set up a served World this one is linked to from this world's seed: run there through its own twins, then marked. */
@@ -13,7 +13,7 @@ You need Node 22.6+, pnpm 12.15.0, Docker Compose and registry access during ins
13
13
  Copy this starter into a new folder:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.190 example customer-onboarding --dir onboarding
16
+ $ npx @volter/world@3.0.192 example customer-onboarding --dir onboarding
17
17
  $ cd onboarding
18
18
  $ pnpm install
19
19
  ```
@@ -11,13 +11,13 @@
11
11
  "pg": "8.16.3"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.190",
15
- "@volter/world-core": "3.0.164",
16
- "@volter/world-runtime": "3.0.188",
17
- "@volter/world-console": "3.0.160",
14
+ "@volter/world": "3.0.192",
15
+ "@volter/world-core": "3.0.165",
16
+ "@volter/world-runtime": "3.0.190",
17
+ "@volter/world-console": "3.0.161",
18
18
  "@volter/twin-stripe": "3.0.2",
19
19
  "@volter/twin-resend": "1.0.1",
20
- "@volter/world-ui": "1.0.163"
20
+ "@volter/world-ui": "1.0.164"
21
21
  },
22
22
  "packageManager": "pnpm@12.15.0",
23
23
  "devEngines": {
@@ -13,7 +13,7 @@ Give two workers the same Stripe baseline, let them change different existing cu
13
13
  Use an empty parent folder. Create the baseline starter there:
14
14
 
15
15
  ```console
16
- $ npx @volter/world@3.0.190 example integration-branches --dir baseline
16
+ $ npx @volter/world@3.0.192 example integration-branches --dir baseline
17
17
  $ cd baseline
18
18
  $ pnpm install
19
19
  $ ./node_modules/.bin/volter world init --name baseline --twins stripe
@@ -38,9 +38,9 @@ The stopped baseline retains its history and can supply local path remotes. Defa
38
38
  Create three more copies in the same parent folder:
39
39
 
40
40
  ```console
41
- $ npx @volter/world@3.0.190 example integration-branches --dir integration
42
- $ npx @volter/world@3.0.190 example integration-branches --dir worker-a
43
- $ npx @volter/world@3.0.190 example integration-branches --dir worker-b
41
+ $ npx @volter/world@3.0.192 example integration-branches --dir integration
42
+ $ npx @volter/world@3.0.192 example integration-branches --dir worker-a
43
+ $ npx @volter/world@3.0.192 example integration-branches --dir worker-b
44
44
  ```
45
45
 
46
46
  In `integration`, then `worker-a`, then `worker-b`, install dependencies and initialize with the folder's name. Before a first boot or app write, link and pull the baseline:
@@ -9,12 +9,12 @@
9
9
  "stripe": "17.7.0"
10
10
  },
11
11
  "devDependencies": {
12
- "@volter/world": "3.0.190",
13
- "@volter/world-core": "3.0.164",
14
- "@volter/world-runtime": "3.0.188",
15
- "@volter/world-console": "3.0.160",
12
+ "@volter/world": "3.0.192",
13
+ "@volter/world-core": "3.0.165",
14
+ "@volter/world-runtime": "3.0.190",
15
+ "@volter/world-console": "3.0.161",
16
16
  "@volter/twin-stripe": "3.0.2",
17
- "@volter/world-ui": "1.0.163"
17
+ "@volter/world-ui": "1.0.164"
18
18
  },
19
19
  "packageManager": "pnpm@12.15.0",
20
20
  "devEngines": {
@@ -19,7 +19,7 @@ The catalog offers current defaults. Keep this example's manifest and installati
19
19
  together; its recorded workflow does not establish behavior for a different release.
20
20
 
21
21
  ```console
22
- $ npx @volter/world@3.0.190 example workplace-agent --dir workplace
22
+ $ npx @volter/world@3.0.192 example workplace-agent --dir workplace
23
23
  $ cd workplace
24
24
  $ pnpm install
25
25
  $ ./node_modules/.bin/volter world init --name workplace --twins github,linear,slack
@@ -11,14 +11,14 @@
11
11
  "@slack/web-api": "7.19.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@volter/world": "3.0.190",
15
- "@volter/world-core": "3.0.164",
16
- "@volter/world-runtime": "3.0.188",
17
- "@volter/world-console": "3.0.160",
14
+ "@volter/world": "3.0.192",
15
+ "@volter/world-core": "3.0.165",
16
+ "@volter/world-runtime": "3.0.190",
17
+ "@volter/world-console": "3.0.161",
18
18
  "@volter/twin-github": "3.0.9",
19
19
  "@volter/twin-linear": "3.0.5",
20
20
  "@volter/twin-slack": "3.0.8",
21
- "@volter/world-ui": "1.0.163"
21
+ "@volter/world-ui": "1.0.164"
22
22
  },
23
23
  "packageManager": "pnpm@12.15.0",
24
24
  "devEngines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world",
3
- "version": "3.0.190",
3
+ "version": "3.0.192",
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",
@@ -31,20 +31,25 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.30.1",
34
- "@volter/world-core": "3.0.164",
35
- "@volter/world-runtime": "3.0.188",
34
+ "@volter/world-core": "3.0.165",
35
+ "@volter/world-runtime": "3.0.190",
36
36
  "zod": "^4.6.5"
37
37
  },
38
38
  "peerDependencies": {
39
- "@volter/world-console": "3.0.160"
39
+ "@volter/world-console": "3.0.161",
40
+ "playwright": "^1.63.0"
40
41
  },
41
42
  "peerDependenciesMeta": {
42
43
  "@volter/world-console": {
43
44
  "optional": true
45
+ },
46
+ "playwright": {
47
+ "optional": true
44
48
  }
45
49
  },
46
50
  "devDependencies": {
47
- "@volter/world-console": "3.0.160"
51
+ "@volter/twin-anthropic": "1.0.6",
52
+ "@volter/world-console": "3.0.161"
48
53
  },
49
54
  "bin": {
50
55
  "volter": "dist/src/cli.js"
package/src/cli.ts CHANGED
@@ -16,6 +16,7 @@ import { forgetPlatform, forgetToken, platformOrigin, storePlatform, storeToken,
16
16
  import { parseOriginUrl, World } from './world.ts';
17
17
  import { copyExample, examples } from './examples.ts';
18
18
  import { waitForServeShutdown } from './serve-shutdown.ts';
19
+ import { openWorldBrowser } from './world-browser.ts';
19
20
  import { currentVersion, startUpdateCheck } from './update-notice.ts';
20
21
  import type { ViewConsole } from '@volter/world-runtime';
21
22
  import { withoutVaultSignIn } from '@volter/world-runtime';
@@ -47,6 +48,8 @@ world — a shared simulated internet (the world of the current directory)
47
48
  volter world status world, branch, origin, unpushed changes, what is running
48
49
  volter world log [--receipts] [--json] every write the app made; --receipts adds each change's receipt
49
50
  volter world diff [--json] what changed since the default data (or the last mark)
51
+ volter world generate write seed.generated.ts from world.json's people, organizations and accounts
52
+ volter world browser <name> [url] [--cdp <endpoint>] open a person's saved World browser, signed in, through the running World (--cdp loads its cookies into a browser already open)
50
53
  volter world seed load the default data
51
54
  volter world reset back to the default data
52
55
  volter world down [--purge] stop the twins (--purge forgets this branch's state)
@@ -114,7 +117,7 @@ function positionals(args: string[], valueFlags: string[]): string[] {
114
117
  return out;
115
118
  }
116
119
  function afterDashes(args: string[]): string[] { const i = args.indexOf('--'); return i < 0 ? [] : args.slice(i + 1); }
117
- const VALUE_FLAGS = ['--world', '--branch', '--name', '--token', '--admin-token', '-m', '--message', '--base', '--org', '--vendors', '--into', '--deploy', '--as', '--note', '--port', '--twins', '--source', '--host', '--origin', '--remote', '--dir', '--scope', '--refresh', '--at-most', '--at', '--template', '--output-root', '--app', '--app-url', '--template-out'];
120
+ const VALUE_FLAGS = ['--world', '--branch', '--name', '--token', '--admin-token', '-m', '--message', '--base', '--org', '--vendors', '--into', '--deploy', '--as', '--note', '--port', '--twins', '--source', '--host', '--origin', '--remote', '--dir', '--scope', '--refresh', '--at-most', '--at', '--template', '--output-root', '--app', '--app-url', '--template-out', '--cdp'];
118
121
 
119
122
  /** Help is resolved before opening a World; the child after `run --` owns its own flags. */
120
123
  function wantsHelp(noun: string | undefined, verb: string | undefined, args: string[]): boolean {
@@ -281,6 +284,18 @@ async function worldCommand(verb: string, args: string[]): Promise<void> {
281
284
  for (const v of delta.vendors) out(` ${v.service}: ${v.count} change${v.count === 1 ? '' : 's'}`);
282
285
  return;
283
286
  }
287
+ case 'browser': {
288
+ const [name, url] = pos;
289
+ if (!name) throw new Error('volter world browser <name> [url] [--cdp <endpoint>]');
290
+ await openWorldBrowser(world, name, url, value(args, '--cdp'), out);
291
+ return;
292
+ }
293
+ case 'generate': {
294
+ const r = world.generate();
295
+ out(`${r.changed ? 'wrote' : 'unchanged'} ${relative(process.cwd(), r.path) || r.path}`);
296
+ if (!r.imported) out('note seed.ts beside it does not import ./seed.generated.ts, and the World runs only seed.ts');
297
+ return;
298
+ }
284
299
  case 'seed': {
285
300
  const r = await world.seed();
286
301
  out(`Loaded the default data into ${r.world}: ${Object.entries(r.observed).map(([s, n]) => `${s} ${n}`).join(', ') || 'nothing recorded'}`);
@@ -529,7 +544,7 @@ async function twinCommand(args: string[]): Promise<void> {
529
544
  if (t.root) out(t.ingest ? `${vendor} ingest ${t.ingest} (register it at the vendor as the webhook URL, with the signing secret sealed with the credential)` : `${vendor} ingest: a served World's door (volter world serve, or a remote) is the webhook URL to register at the vendor`);
530
545
  return;
531
546
  }
532
- const dir = resolveCatalog(process.cwd()).packDir(vendor);
547
+ const dir = resolveCatalog(process.cwd(), { vendors: [vendor] }).packDir(vendor);
533
548
  if (dir === undefined) throw new Error(`No twin "${vendor}" here — \`pnpm add -D --package-import-method=hardlink --config.nodeLinker=hoisted @volter/twin-${vendor}\`, or run from a checkout that has it`);
534
549
  if (rest.length === 0 || rest[0]!.startsWith('-')) throw new Error(`volter twin ${vendor}: which verb? serve | root | credential | refresh`);
535
550
  const cli = packCli(dir); if (cli === undefined) throw new Error(`No cli for twin "${vendor}" (its manifest names no bin)`);
@@ -8,7 +8,7 @@ import { afterAll, describe, expect, test } from 'bun:test';
8
8
  import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
9
9
  import { tmpdir } from 'node:os';
10
10
  import { join } from 'node:path';
11
- import { PACKS } from '../../../scripts/pack-dir.ts';
11
+ import { installedPackageDir } from '@volter/world-core/runtime-state';
12
12
  import { World } from './index.ts';
13
13
 
14
14
  function app(): string {
@@ -19,9 +19,11 @@ function app(): string {
19
19
  writeFileSync(join(dir, '.env.example'), 'ANTHROPIC_API_KEY=\n');
20
20
  const installed = join(parent, 'node_modules', '@volter', 'twin-anthropic');
21
21
  mkdirSync(installed, { recursive: true });
22
- for (const entry of readdirSync(join(PACKS, 'anthropic'))) {
23
- if (entry === 'package.json') copyFileSync(join(PACKS, 'anthropic', entry), join(installed, entry));
24
- else symlinkSync(join(PACKS, 'anthropic', entry), join(installed, entry));
22
+ // the published pack, this package's dev dependency (ADR 0034, decision 6)
23
+ const pack = installedPackageDir(import.meta.dir, '@volter/twin-anthropic')!;
24
+ for (const entry of readdirSync(pack)) {
25
+ if (entry === 'package.json') copyFileSync(join(pack, entry), join(installed, entry));
26
+ else symlinkSync(join(pack, entry), join(installed, entry));
25
27
  }
26
28
  return dir;
27
29
  }
@@ -11,6 +11,14 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
11
11
 
12
12
  const roots: string[] = [];
13
13
  afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
14
+ /** Each fixture vendor's own facts, as its release carries them (ADR 0034, decisions 2 and 6): Protocol 3, the SDK and
15
+ * env stem the app betrays it by, and the host its injector key serves. */
16
+ const FACTS: Record<string, unknown> = {
17
+ stripe: { protocol: { declared: '3', major: 3, standing: 'current' }, adoption: { sdks: ['stripe'], envStems: ['STRIPE'] }, hosts: [{ host: 'api.stripe.com' }] },
18
+ slack: { protocol: { declared: '3', major: 3, standing: 'current' }, adoption: { sdks: ['@slack/web-api'], envStems: ['SLACK'] }, hosts: [{ host: 'slack.com' }] },
19
+ };
20
+ /** An app calling Stripe and Slack, the named publisher fixtures installed, each with its own facts, and a catalog index
21
+ * whose captured releases (one publisher per vendor) let discovery see a vendor the selection leaves out. */
14
22
  function app(packages = ['@one/twin-stripe']): string {
15
23
  const root = mkdtempSync(join(tmpdir(), 'world-init-selection-')); roots.push(root);
16
24
  writeFileSync(join(root, 'package.json'), JSON.stringify({ name: 'selection-app', private: true, dependencies: { stripe: '17.7.0', '@slack/web-api': '7.9.2' } }));
@@ -18,9 +26,21 @@ function app(packages = ['@one/twin-stripe']): string {
18
26
  for (const name of packages) {
19
27
  const dir = join(root, 'node_modules', name);
20
28
  mkdirSync(join(dir, 'src', 'generated'), { recursive: true });
29
+ mkdirSync(join(dir, 'generated'), { recursive: true });
21
30
  writeFileSync(join(dir, 'package.json'), JSON.stringify({ name, version: name.startsWith('@one/') ? '1.0.0' : '9.0.0' }));
22
31
  writeFileSync(join(dir, 'src', 'cli.ts'), '// Discovery fixture: never executed.\n');
23
32
  writeFileSync(join(dir, 'src', 'generated', 'surface.gen.json'), '{}\n');
33
+ const vendor = /twin-([a-z]+)$/.exec(name)?.[1];
34
+ if (vendor) writeFileSync(join(dir, 'generated', 'pack-facts.json'), JSON.stringify({ packs: { [vendor]: FACTS[vendor] } }));
35
+ }
36
+ const index = join(root, 'node_modules', '@volter', 'twin-catalog');
37
+ mkdirSync(join(index, 'vendors'), { recursive: true }); mkdirSync(join(index, 'content'), { recursive: true });
38
+ writeFileSync(join(index, 'package.json'), JSON.stringify({ name: '@volter/twin-catalog', version: '0.0.0' }));
39
+ writeFileSync(join(index, 'sources.json'), JSON.stringify([{ name: 'one', scope: '@one', repository: 'one/twins', protocol: '3' }]));
40
+ for (const vendor of Object.keys(FACTS)) {
41
+ const release = { vendor, package: `@one/twin-${vendor}`, version: '1.0.0' };
42
+ writeFileSync(join(index, 'vendors', `${vendor}.json`), JSON.stringify({ vendor, packages: [{ name: release.package, source: 'one', versions: [{ version: '1.0.0', status: 'live' }] }] }));
43
+ writeFileSync(join(index, 'content', `${vendor}.json`), JSON.stringify({ schemaVersion: 1, release, facts: FACTS[vendor] }));
24
44
  }
25
45
  return root;
26
46
  }
@@ -70,9 +90,9 @@ test('SDK initialization exposes the same vendor and publisher choice', () => {
70
90
  test('an independent package name gets vendor identity from its published facts', () => {
71
91
  const root = app(['@one/twin-stripe', '@two/stripe-simulator']);
72
92
  const dir = join(root, 'node_modules', '@two', 'stripe-simulator', 'generated');
73
- mkdirSync(dir);
74
- const facts = JSON.parse(readFileSync(join(import.meta.dir, '../../world-core/generated/pack-facts.json'), 'utf8'));
75
- writeFileSync(join(dir, 'pack-facts.json'), JSON.stringify({ packs: { stripe: facts.packs.stripe } }));
93
+ mkdirSync(dir, { recursive: true });
94
+ // the independently named publisher's own facts name its vendor (ADR 0034, decision 6)
95
+ writeFileSync(join(dir, 'pack-facts.json'), JSON.stringify({ packs: { stripe: FACTS.stripe } }));
76
96
  const result = init(root, '--twins', 'stripe', '--source', 'stripe=@two/stripe-simulator');
77
97
  expect(result.status).toBe(0);
78
98
  expect(document(root).services[0].source.package).toBe('@two/stripe-simulator');
@@ -1,4 +1,4 @@
1
- // What the tutorial runner and the docs' recorder share: where the commands and the twins are.
1
+ // What the tutorial runner and the docs' recorder share: where the commands are.
2
2
  // A tutorial page is its own journey (tutorial.ts); nothing here runs a step of its own.
3
3
  import { resolve } from 'node:path';
4
4
 
@@ -6,7 +6,5 @@ import { resolve } from 'node:path';
6
6
  export const CLI = resolve(import.meta.dir, '..', 'cli.ts');
7
7
  /** The operator's `volter-world` command, which ships with @volter/world-runtime. */
8
8
  export const OPERATOR_CLI = resolve(import.meta.dir, '..', '..', '..', 'world-runtime', 'src', 'cli.ts');
9
- /** The twins: the pack repository beside this or the main checkout. */
10
- export { PACKS } from '../../../../scripts/pack-dir.ts';
11
9
  /** @volter/world itself. */
12
10
  export const SDK = resolve(import.meta.dir, '..', '..');
@@ -22,8 +22,8 @@ import { downWorld } from '@volter/world-runtime';
22
22
  import { tmpdir } from 'node:os';
23
23
  import { basename, delimiter, dirname, join, resolve } from 'node:path';
24
24
  import { randomUUID } from 'node:crypto';
25
- import { CLI, OPERATOR_CLI, PACKS, SDK } from './kit.ts';
26
- void CLI; void OPERATOR_CLI; void PACKS; void SDK;
25
+ import { CLI, OPERATOR_CLI, SDK } from './kit.ts';
26
+ void CLI; void OPERATOR_CLI; void SDK;
27
27
 
28
28
  export type Step =
29
29
  | { kind: 'file'; path: string; content: string; line: number }
@@ -0,0 +1,37 @@
1
+ // `volter world browser <name> [url] [--cdp <endpoint>]`: a person's saved World browser opened in a real browser
2
+ // (ADR 0016, rows 3 and 4). The profile is read through the World's own record reader and the browser is routed through
3
+ // the World's proxy with its CA trusted, as attach publishes them. Nothing is written back to the World.
4
+ import { deriveWorldManifest, statusWorld, worldBrowser, worldBrowsers } from '@volter/world-runtime';
5
+
6
+ export async function openWorldBrowser(world: { name: string; root: string }, name: string, url: string | undefined, cdp: string | undefined, say: (line: string) => void): Promise<void> {
7
+ const instance = statusWorld(world.name, world.root);
8
+ const record = worldBrowser(instance.dirs.data, name);
9
+ if (!record) {
10
+ const names = worldBrowsers(instance.dirs.data).map(row => row.name);
11
+ throw new Error(`World "${world.name}" has no browser "${name}" (has: ${names.join(', ') || 'none'})`);
12
+ }
13
+ const manifest = deriveWorldManifest(world.name, instance.env ?? {});
14
+ if (!instance.running || !manifest.browser) throw new Error('the World is not running, so it has no proxy to route a browser through: start it with `volter world up`');
15
+ let chromium: typeof import('playwright').chromium;
16
+ try { ({ chromium } = await import('playwright')); }
17
+ catch { throw new Error('volter world browser needs playwright: npm i playwright'); }
18
+ const profile = record.state;
19
+ if (cdp === undefined) {
20
+ const browser = await chromium.launch({ headless: false, args: manifest.browser.args });
21
+ const closed = new Promise<void>(resolve => browser.on('disconnected', () => resolve()));
22
+ const context = await browser.newContext({ ...profile.context, storageState: profile.storageState } as Parameters<typeof browser.newContext>[0]);
23
+ const page = await context.newPage();
24
+ await page.goto(url ?? 'about:blank');
25
+ await closed;
26
+ return;
27
+ }
28
+ const browser = await chromium.connectOverCDP(cdp);
29
+ const context = browser.contexts()[0];
30
+ if (!context) throw new Error(`${cdp} has no default browser context to load ${name}'s cookies into`);
31
+ // An existing default context takes no context options (ADR 0016): its cookies are what is loaded.
32
+ if (Object.keys(profile.context).length) say(`note ${name}'s context options (${Object.keys(profile.context).join(', ')}) were not applied: an existing browser's default context takes none`);
33
+ await context.addCookies(profile.storageState.cookies as Parameters<typeof context.addCookies>[0]);
34
+ const page = await context.newPage();
35
+ await page.goto(url ?? 'about:blank');
36
+ await browser.close();
37
+ }
package/src/world.ts CHANGED
@@ -9,7 +9,7 @@ import { spawnSync } from 'node:child_process';
9
9
  import clockForm from '@volter/world-core/world-clock';
10
10
  import {
11
11
  activateScript, approveWorldChangeset, branchWorld, clockFile, checkoutWorld, createWorldChangeset, diffWorld, downWorld, fetchFromOrigin, findWorldChangeset, initWorld, listWorldChangesets, listWorldMarks, listWorlds, markWorld, pushWorldChangeset, rebaseWorldChangeset, replayWorldChangeset, resetWorld, runWithWorldEnv, seedServedWorld, seedWorld, shellWorld,
12
- materializeBrowserFiles, statusWorld, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin,
12
+ materializeBrowserFiles, statusWorld, writeGeneratedSeed, statusWorldChangeset, upWorld, verifyWorldChangeset, worldLedgers, worldOrigin,
13
13
  bindLegacyCredentials, deployWorld, loadWorldConfig, writeWorldConfig, readServeRecord, refreshTwin, serveWorld, serveWorldView, findInstalledPackage, type ServedWorld, type ViewConsole, type WorldView, rootForControlRoot, sealTwinCredential, sealedCredentialInfo, setTwinRoot, credentialPayloadFrom, rootCredentialInput, withoutVaultSignIn,
14
14
  type DeployTwinOutcome, type MaterializedRoot,
15
15
  type ChangesetLocation, type InitOptions, type InitResult, type WorldInstance, materializeRoots, instanceDir, worldChangesetRules } from '@volter/world-runtime';
@@ -241,6 +241,9 @@ export class World {
241
241
  // ── default data ────────────────────────────────────────────────────────────────────────
242
242
  /** Load the default data: the seed runs with every twin recording what it creates as data that was already there. */
243
243
  seed(opts: { entry?: string; cwd?: string } = {}): ReturnType<typeof seedWorld> { return seedWorld(this.name, { root: this.root, cwd: this.root, ...opts }); }
244
+ /** Write `seed.generated.ts` beside world.json from the people, organizations and accounts it declares (ADR 0033), as a
245
+ * lockfile is written from its manifest: whether it changed, and whether `seed.ts`, which the World runs, imports it. */
246
+ generate(): ReturnType<typeof writeGeneratedSeed> { return writeGeneratedSeed(worldConfigPath(this.root), this.root); }
244
247
  /** Whether this world has default data (`.volter/seed.ts`). */
245
248
  hasSeed(): boolean { return existsSync(worldSeedPath(this.root)); }
246
249
  /** Set up a served World this one is linked to from this world's seed: run there through its own twins, then marked. */