@zenera/gym 1.1.40 → 1.1.41

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.
Files changed (51) hide show
  1. package/README.md +169 -104
  2. package/dist/agents.d.ts +25 -0
  3. package/dist/agents.js +52 -0
  4. package/dist/bindings.d.ts +25 -0
  5. package/dist/bindings.js +81 -0
  6. package/dist/box.d.ts +39 -36
  7. package/dist/box.js +183 -217
  8. package/dist/cache.d.ts +24 -14
  9. package/dist/cache.js +102 -66
  10. package/dist/catalog.d.ts +83 -0
  11. package/dist/catalog.js +293 -0
  12. package/dist/command.d.ts +1 -0
  13. package/dist/command.js +235 -68
  14. package/dist/fake.d.ts +4 -0
  15. package/dist/fake.js +142 -0
  16. package/dist/generate.d.ts +28 -24
  17. package/dist/generate.js +417 -120
  18. package/dist/inputs.d.ts +12 -0
  19. package/dist/inputs.js +132 -0
  20. package/dist/knowledge.d.ts +50 -0
  21. package/dist/knowledge.js +199 -0
  22. package/dist/logger.d.ts +10 -8
  23. package/dist/logger.js +4 -3
  24. package/dist/paging.d.ts +2 -2
  25. package/dist/paging.js +2 -2
  26. package/dist/probe.d.ts +5 -5
  27. package/dist/probe.js +15 -15
  28. package/dist/prompt.d.ts +31 -18
  29. package/dist/prompt.js +160 -146
  30. package/dist/quiet.d.ts +2 -0
  31. package/dist/quiet.js +7 -0
  32. package/dist/router.js +5 -1
  33. package/dist/runtime.d.ts +160 -0
  34. package/dist/runtime.js +229 -0
  35. package/dist/schema.js +1 -2
  36. package/dist/server.js +98 -57
  37. package/dist/setup.d.ts +47 -6
  38. package/dist/setup.js +157 -21
  39. package/dist/spec.d.ts +4 -3
  40. package/dist/spec.js +73 -22
  41. package/dist/store.d.ts +78 -0
  42. package/dist/store.js +253 -0
  43. package/dist/synth.d.ts +42 -0
  44. package/dist/synth.js +589 -0
  45. package/dist/worker.d.ts +41 -0
  46. package/dist/worker.js +99 -0
  47. package/package.json +5 -3
  48. package/dist/envelope.d.ts +0 -13
  49. package/dist/envelope.js +0 -9
  50. package/dist/image.d.ts +0 -25
  51. package/dist/image.js +0 -81
package/dist/setup.js CHANGED
@@ -1,30 +1,38 @@
1
- import { credentialError, ensureHome, ensurePodmanReady, envNames, home, invalidError, KeyStore, paths, PROVIDERS, readJson, writeJson, } from '@zenera/cli/lib';
2
- import { createModel } from '@zenera/neo';
3
- import { mkdirSync } from 'node:fs';
1
+ import { credentialError, CURATED, ensureHome, envNames, home, invalidError, KeyStore, paths, PROVIDERS, readJson, writeJson, } from '@zenera/cli/lib';
2
+ import { createEmbedder, createModel } from '@zenera/neo';
3
+ import { existsSync, mkdirSync } from 'node:fs';
4
4
  import { join, resolve } from 'node:path';
5
+ import { Bindings } from "./bindings.js";
5
6
  import { Box } from "./box.js";
6
7
  import { Cache } from "./cache.js";
7
- import { ensureImage } from "./image.js";
8
+ import { Catalog } from "./catalog.js";
9
+ import { checkTexts, expandSchemas } from "./inputs.js";
10
+ import { openKnowledge } from "./knowledge.js";
8
11
  import { Router } from "./router.js";
9
12
  import { loadSpecs, SpecError } from "./spec.js";
13
+ import { listWorlds, worldPath, WorldStore } from "./store.js";
14
+ import { World } from "./synth.js";
10
15
  import { Checks } from "./validate.js";
11
16
  // ---------------------------------------------------------------------------
12
17
  // Assembly
13
18
  //
14
19
  // The order here is the whole point and it is the same order `zen run` uses:
15
- // credentials before anything that needs one, the container engine before the
16
- // image, the image before the box, and the documents last — so the failure a
17
- // user sees is the first thing that was actually wrong rather than whatever
18
- // happened to be checked first.
20
+ // credentials before anything that needs one, then the documents, then the
21
+ // story, and the world last because its file is named after the story — so the
22
+ // failure a user sees is the first thing that was actually wrong rather than
23
+ // whatever happened to be checked first.
19
24
  // ---------------------------------------------------------------------------
20
25
  /**
21
26
  * The model each provider gets when none is named — the same picks `zen meta`
22
27
  * recommends (`RECOMMENDED` in `packages/cli/src/meta/model.ts`). Every ref names its
23
28
  * provider: the shorthand reads the first segment as a *provider name*, so a
24
29
  * bare `gemini-3.8-flash` would be asked of OpenAI.
30
+ *
31
+ * Gym's agents call tools, and OpenAI's reasoning models take tools only over
32
+ * the Responses API, so that is the one named.
25
33
  */
26
34
  const DEFAULT_MODEL = {
27
- openai: 'openai:gpt-5.6-sol',
35
+ openai: 'openai/responses:gpt-5.6-sol',
28
36
  anthropic: 'anthropic:claude-opus-5',
29
37
  google: 'google:gemini-3.8-flash',
30
38
  vertex: 'vertex:gemini-3.8-flash',
@@ -35,7 +43,7 @@ const DEFAULT_MODEL = {
35
43
  //
36
44
  // The same chain `zen meta` uses: a flag beats the environment, the environment
37
45
  // beats what was stored, and with none of them set it is whatever the keys on
38
- // hand can buy. Which model writes generators is a choice about a tool on this
46
+ // hand can buy. Which model writes handlers is a choice about a tool on this
39
47
  // machine and not about a document, so it is stored beside the keyring rather
40
48
  // than anywhere near a specification.
41
49
  // ---------------------------------------------------------------------------
@@ -70,9 +78,54 @@ export async function chooseModel(flag, keys) {
70
78
  }
71
79
  return undefined;
72
80
  }
81
+ /** The variable that names the embedding model, or `none`. */
82
+ export const EMBEDDING_ENV = 'ZENERA_GYM_EMBEDDING';
83
+ /** `--embedding` values that ask for whichever model is configured. */
84
+ const EMBEDDING_ON = new Set(['on', 'default']);
85
+ /** `--embedding` values that keep it off, which is also what no flag means. */
86
+ const EMBEDDING_OFF = new Set(['', 'off', 'none']);
87
+ /**
88
+ * Which model embeds the docs, if any. Off unless the flag asks: a ref names
89
+ * the model outright; `on` (or `default`) takes the configured one — the
90
+ * variable, then the stored setting, then the first keyed provider that offers
91
+ * one. The variable and the setting only answer that question; on their own
92
+ * they turn nothing on.
93
+ */
94
+ export async function chooseEmbedding(flag, keys) {
95
+ const asked = flag?.trim().toLowerCase() ?? '';
96
+ if (EMBEDDING_OFF.has(asked)) {
97
+ return { from: 'none' };
98
+ }
99
+ if (!EMBEDDING_ON.has(asked)) {
100
+ return { ref: flag.trim(), from: 'flag' };
101
+ }
102
+ const configured = await configuredEmbedding(keys);
103
+ if (configured.from === 'none') {
104
+ throw credentialError('--embedding on, but there is no embedding model to use', `name one with --embedding <ref>, set ${EMBEDDING_ENV}, ` +
105
+ 'or add a key for a provider that offers one: zen key add openai');
106
+ }
107
+ return configured;
108
+ }
109
+ /** The model `--embedding on` would use; `none` at a link means off. */
110
+ export async function configuredEmbedding(keys) {
111
+ const chain = [
112
+ ['env', process.env[EMBEDDING_ENV]],
113
+ ['store', (await readSettings()).embedding],
114
+ ['default', defaultEmbedding(keys)],
115
+ ];
116
+ for (const [from, ref] of chain) {
117
+ const named = ref?.trim();
118
+ if (named) {
119
+ return EMBEDDING_OFF.has(named.toLowerCase()) ? { from } : { ref: named, from };
120
+ }
121
+ }
122
+ return { from: 'none' };
123
+ }
73
124
  export async function open(opts) {
74
- if (opts.specs.length === 0) {
75
- throw invalidError('no specification given', 'name one or more openapi/swagger files');
125
+ const story = opts.story ?? [];
126
+ const docsArgs = opts.docs ?? [];
127
+ if (opts.schemas.length === 0) {
128
+ throw invalidError('no API document given', 'name OpenAPI/Swagger files, directories or patterns, e.g. zen gym serve api/');
76
129
  }
77
130
  // Real environment variables win, exactly as they do for `zen`.
78
131
  ensureHome();
@@ -85,29 +138,101 @@ export async function open(opts) {
85
138
  const model = createModel(choice.ref);
86
139
  const root = resolve(opts.cwd, opts.cache ?? paths.gym());
87
140
  mkdirSync(root, { recursive: true, mode: 0o700 });
88
- // Podman is asked about before the image is built, so a machine without a
89
- // container engine says so instead of failing halfway through a build.
90
- await ensurePodmanReady({ image: opts.image, yes: true });
91
- const image = opts.image ?? (await ensureImage({ root, onBuild: opts.onImageBuild }));
92
- const operations = await loadSpecs(opts.specs.map((s) => resolve(opts.cwd, s)));
141
+ const files = await expandSchemas(opts.schemas, opts.cwd);
142
+ await checkTexts(story, opts.cwd, '--story');
143
+ await checkTexts(docsArgs, opts.cwd, '--docs');
144
+ const operations = await loadSpecs(files);
93
145
  if (operations.length === 0) {
94
- throw invalidError('the specification declares no operations');
146
+ throw invalidError('the API documents declare no operations');
95
147
  }
96
148
  const router = new Router(operations);
97
149
  const checks = new Checks();
98
- const box = new Box({ root, image, timeout: opts.timeout });
99
- await box.fresh();
150
+ const catalog = await Catalog.open(files);
151
+ // Everything is indexed before anything is served: no request ever waits
152
+ // on, or works around, an index that is still being built.
153
+ const embedding = docsArgs.length > 0
154
+ ? await chooseEmbedding(opts.embedding, keys)
155
+ : { from: 'none' };
156
+ const knowledge = await openKnowledge({
157
+ story,
158
+ docs: docsArgs,
159
+ cwd: opts.cwd,
160
+ root,
161
+ embedding: embedding.ref
162
+ ? { ref: embedding.ref, embedder: createEmbedder(embedding.ref) }
163
+ : undefined,
164
+ onWarn: opts.onWarn,
165
+ onProgress: opts.onIndexing,
166
+ });
167
+ const worldFile = worldPath({
168
+ root,
169
+ cwd: opts.cwd,
170
+ world: opts.world,
171
+ storyDigest: knowledge.storyDigest || undefined,
172
+ });
173
+ if (!opts.world && knowledge.storyDigest && !existsSync(worldFile)) {
174
+ const earlier = listWorlds(root).find((w) => w.name.startsWith('story-'));
175
+ if (earlier) {
176
+ opts.onWarn?.(`a new story, so a new world: ${worldFile}. The last one used is ${earlier.path}; ` +
177
+ 'name a world with --world to keep one across story edits');
178
+ }
179
+ }
180
+ const store = new WorldStore(worldFile);
181
+ if (opts.reset) {
182
+ store.reset();
183
+ }
184
+ const bindings = new Bindings(Object.keys(catalog.types), { ephemeral: opts.ephemeral });
185
+ const world = new World({
186
+ model,
187
+ knowledge,
188
+ catalog,
189
+ bindings,
190
+ store,
191
+ reportDir: join(root, 'reports'),
192
+ onMade: opts.onWorld,
193
+ });
194
+ const box = new Box({
195
+ root,
196
+ db: store.path,
197
+ timeout: opts.timeout,
198
+ synthesize: world.synthesize,
199
+ bindings: () => bindings.all(),
200
+ });
100
201
  const cache = new Cache({
101
202
  box,
102
203
  checks,
103
204
  model,
205
+ catalog,
206
+ world,
207
+ bindings,
208
+ operations,
104
209
  attempts: opts.attempts,
105
210
  concurrency: opts.concurrency,
106
211
  rebuild: opts.rebuild,
107
212
  ephemeral: opts.ephemeral,
108
213
  ...opts.events,
109
214
  });
110
- return { router, checks, cache, box, model, choice, image, root, close: () => box.dispose() };
215
+ return {
216
+ router,
217
+ checks,
218
+ cache,
219
+ box,
220
+ model,
221
+ choice,
222
+ catalog,
223
+ knowledge,
224
+ embedding,
225
+ files,
226
+ world,
227
+ worldFile,
228
+ root,
229
+ close: async () => {
230
+ await box.dispose();
231
+ world.close();
232
+ store.close();
233
+ knowledge.close();
234
+ },
235
+ };
111
236
  }
112
237
  /**
113
238
  * Presence, not liveness. `zen init` probes because it is writing a project
@@ -119,5 +244,16 @@ function defaultRef(keys) {
119
244
  PROVIDERS.find((p) => keys.active(p) !== undefined);
120
245
  return provider ? DEFAULT_MODEL[provider] : undefined;
121
246
  }
247
+ /** The first keyed provider, in preference order, with a curated embedding model. */
248
+ function defaultEmbedding(keys) {
249
+ const keyed = PROVIDERS.filter((p) => envNames(p).some((name) => process.env[name]) || keys.active(p) !== undefined);
250
+ for (const provider of keyed) {
251
+ const model = CURATED[provider].find((m) => m.roles.includes('embedding'));
252
+ if (model) {
253
+ return `${provider}:${model.id}`;
254
+ }
255
+ }
256
+ return undefined;
257
+ }
122
258
  export { SpecError };
123
259
  //# sourceMappingURL=setup.js.map
package/dist/spec.d.ts CHANGED
@@ -2,10 +2,11 @@ import { CliError } from '@zenera/cli/lib';
2
2
  import { type Paging } from './paging.ts';
3
3
  import { type Schema } from './schema.ts';
4
4
  /**
5
- * Version of generator prompt rules and environment contracts.
6
- * Bumping this invalidates cached Python generators and triggers regeneration.
5
+ * Version of the handler contract: the prompt rules and the `ctx` runtime.
6
+ * Bumping it gives every operation a new cache key, so nothing written to an
7
+ * older contract is ever run against a newer one.
7
8
  */
8
- export declare const GENERATOR_RULES_VERSION = 2;
9
+ export declare const CONTRACT_VERSION = 4;
9
10
  export declare const METHODS: readonly ["get", "put", "post", "delete", "patch", "head", "options"];
10
11
  export type Method = (typeof METHODS)[number];
11
12
  export type ParamIn = 'path' | 'query' | 'header' | 'cookie';
package/dist/spec.js CHANGED
@@ -4,10 +4,11 @@ import { createHash } from 'node:crypto';
4
4
  import { pagingOf } from "./paging.js";
5
5
  import { normalize } from "./schema.js";
6
6
  /**
7
- * Version of generator prompt rules and environment contracts.
8
- * Bumping this invalidates cached Python generators and triggers regeneration.
7
+ * Version of the handler contract: the prompt rules and the `ctx` runtime.
8
+ * Bumping it gives every operation a new cache key, so nothing written to an
9
+ * older contract is ever run against a newer one.
9
10
  */
10
- export const GENERATOR_RULES_VERSION = 2;
11
+ export const CONTRACT_VERSION = 4;
11
12
  // ---------------------------------------------------------------------------
12
13
  // Documents, flattened
13
14
  //
@@ -16,9 +17,10 @@ export const GENERATOR_RULES_VERSION = 2;
16
17
  // document it came from, and which of the three dialects that document was
17
18
  // written in, stops mattering here.
18
19
  //
19
- // `dereference` is used rather than `bundle` on purpose: the generator is asked
20
- // to produce a body for one operation, and it should see that operation's
21
- // shapes rather than a pointer into a components section it was never shown.
20
+ // `dereference` is used rather than `bundle` on purpose: requests and responses
21
+ // are checked one operation at a time, and a check wants the whole shape in
22
+ // hand. The agents see the other view — named, bundled types — through
23
+ // catalog.ts.
22
24
  // ---------------------------------------------------------------------------
23
25
  export const METHODS = ['get', 'put', 'post', 'delete', 'patch', 'head', 'options'];
24
26
  const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -116,7 +118,7 @@ function splitQuery(template) {
116
118
  const path = template.slice(0, cut) || '/';
117
119
  return Object.keys(fixed).length > 0 ? { path, fixed } : { path };
118
120
  }
119
- /** A one-value enum, so the check, the page and the generator all see the rule. */
121
+ /** A one-value enum, so the check, the page and the handler all see the rule. */
120
122
  function pinned(fixed) {
121
123
  return Object.entries(fixed ?? {}).map(([name, value]) => ({
122
124
  name,
@@ -147,32 +149,81 @@ function build(b) {
147
149
  return { ...operation, key: keyOf(operation) };
148
150
  }
149
151
  /**
150
- * Cache identity. Everything that changes what a generator must produce is in
152
+ * Cache identity. Everything that changes what a handler must produce is in
151
153
  * here and nothing else is — not the file it came from, not its summary, not
152
- * the order of its keys. Editing a spec therefore yields a new key and the old
153
- * artefact is simply never looked up again, so there is no invalidation step
154
- * to get wrong.
154
+ * the wording of a field's description, not the order of its keys. Editing a
155
+ * spec's shape therefore yields a new key and the old artefact is simply never
156
+ * looked up again, so there is no invalidation step to get wrong.
157
+ *
158
+ * The world's type bindings are part of a handler's identity too, but they are
159
+ * known only once it is written; `cache.ts` adds them on top of this key.
155
160
  */
156
161
  function keyOf(op) {
157
162
  const shape = {
158
- version: GENERATOR_RULES_VERSION,
163
+ version: CONTRACT_VERSION,
159
164
  method: op.method,
160
165
  path: op.path,
161
166
  operationId: op.operationId,
162
167
  params: [...op.params]
163
168
  .sort((a, b) => `${a.in}:${a.name}`.localeCompare(`${b.in}:${b.name}`))
164
- .map((p) => [p.in, p.name, p.required, canonical(p.schema)]),
165
- body: op.requestBody ? [op.requestBody.required, canonical(op.requestBody.schema)] : null,
166
- success: [op.success.status, op.success.schema ? canonical(op.success.schema) : null],
167
- // Derived from the two above, so it adds nothing to the identity — it is
168
- // here to *change* it, once, for the operations whose generator now has
169
- // a pagination rule to obey. Absent rather than null when there is no
170
- // paging, so that everything else hashes to exactly what it did before
171
- // and no one else is asked to rebuild.
172
- ...(op.paging ? { paging: canonical(op.paging) } : {}),
169
+ .map((p) => [p.in, p.name, p.required, canonical(bare(p.schema))]),
170
+ body: op.requestBody
171
+ ? [op.requestBody.required, canonical(bare(op.requestBody.schema))]
172
+ : null,
173
+ success: [op.success.status, op.success.schema ? canonical(bare(op.success.schema)) : null],
174
+ paging: op.paging ? canonical(op.paging) : null,
173
175
  };
174
176
  return createHash('sha256').update(JSON.stringify(shape)).digest('hex').slice(0, 16);
175
177
  }
178
+ /** Keywords that annotate a schema and change nothing a handler must produce. */
179
+ const ANNOTATIONS = new Set([
180
+ '$schema',
181
+ '$comment',
182
+ 'title',
183
+ 'description',
184
+ 'example',
185
+ 'examples',
186
+ 'externalDocs',
187
+ 'deprecated',
188
+ ]);
189
+ /** Keywords whose value is a map of names to schemas, not a schema itself. */
190
+ const SCHEMA_MAPS = new Set([
191
+ 'properties',
192
+ 'patternProperties',
193
+ '$defs',
194
+ 'definitions',
195
+ 'dependentSchemas',
196
+ ]);
197
+ /** Keywords whose value is data, kept verbatim whatever keys it happens to have. */
198
+ const DATA = new Set(['enum', 'const', 'default']);
199
+ /**
200
+ * A schema without its annotations. Schema-aware rather than a blanket key
201
+ * filter, because a *property* called `description` is shape, not prose.
202
+ */
203
+ function bare(schema) {
204
+ if (Array.isArray(schema)) {
205
+ return schema.map(bare);
206
+ }
207
+ if (!isObject(schema)) {
208
+ return schema;
209
+ }
210
+ const out = {};
211
+ for (const [key, value] of Object.entries(schema)) {
212
+ if (ANNOTATIONS.has(key) || key.startsWith('x-')) {
213
+ continue;
214
+ }
215
+ if (DATA.has(key)) {
216
+ out[key] = value;
217
+ }
218
+ else if (SCHEMA_MAPS.has(key) && isObject(value)) {
219
+ out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, bare(v)]));
220
+ }
221
+ else {
222
+ out[key] = bare(value);
223
+ }
224
+ }
225
+ return out;
226
+ }
176
227
  /** Key order is a formatting accident; it must not change a cache key. */
177
228
  function canonical(value) {
178
229
  if (Array.isArray(value)) {
@@ -262,7 +313,7 @@ function requestBody(op, dialect) {
262
313
  * The response a call gets answered with: the lowest 2xx that carries a JSON
263
314
  * body, or the lowest 2xx at all when none of them do. An operation whose
264
315
  * success response has no JSON schema is answered `204` and never gets a
265
- * generator — there is nothing for one to produce.
316
+ * handler — there is nothing for one to produce.
266
317
  */
267
318
  function successOf(op, dialect) {
268
319
  const responses = isObject(op.responses) ? op.responses : {};
@@ -0,0 +1,78 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export interface CollectionSummary {
3
+ name: string;
4
+ count: number;
5
+ sampleIds: string[];
6
+ }
7
+ export interface Collection<T = Record<string, unknown>> {
8
+ readonly name: string;
9
+ count(): number;
10
+ all(): T[];
11
+ get(id: string | number): T | undefined;
12
+ has(id: string | number): boolean;
13
+ find(predicate: (item: T) => boolean): T[];
14
+ put(id: string | number, value: T): T;
15
+ putMany(items: readonly {
16
+ id: string | number;
17
+ value: T;
18
+ }[]): number;
19
+ delete(id: string | number): boolean;
20
+ clear(): void;
21
+ }
22
+ export declare class WorldStore {
23
+ #private;
24
+ readonly path: string;
25
+ constructor(path: string);
26
+ collection<T = Record<string, unknown>>(name: string): Collection<T>;
27
+ collections(): CollectionSummary[];
28
+ getMeta<T = unknown>(key: string): T | undefined;
29
+ setMeta(key: string, value: unknown): void;
30
+ /**
31
+ * A consistent copy of the whole world, for judging a handler without
32
+ * letting its test traffic leak into what real requests will see.
33
+ */
34
+ snapshot(to: string): string;
35
+ /**
36
+ * Takes a lease, or says someone else holds it. A lease is a row in `meta`
37
+ * claimed inside one immediate transaction, so two processes on the same
38
+ * file cannot both win; it lapses when its time is up or its process is
39
+ * gone, so a crash costs nobody a wait.
40
+ */
41
+ claim(key: string, owner: string, ttlMs: number): boolean;
42
+ /** Gives a lease back, if it is still this owner's. */
43
+ release(key: string, owner: string): void;
44
+ /** Throws away every entity, keeping the file and its schema. */
45
+ reset(): void;
46
+ close(): void;
47
+ /** @internal */
48
+ get raw(): DatabaseSync;
49
+ }
50
+ /** A lease owner for this process. */
51
+ export declare const leaseOwner: () => string;
52
+ /**
53
+ * Where the world lives. `--world` wins: a name is a file under
54
+ * `<root>/worlds`, anything that looks like a path is that path. Otherwise the
55
+ * story names its own world, keyed by what the story says, so two processes
56
+ * handed the same story share one world without being told to; with neither,
57
+ * there is one default world.
58
+ */
59
+ export declare function worldPath(opts: {
60
+ root: string;
61
+ cwd: string;
62
+ world?: string;
63
+ storyDigest?: string;
64
+ }): string;
65
+ export declare const worldsDir: (root: string) => string;
66
+ export interface WorldFile {
67
+ name: string;
68
+ path: string;
69
+ bytes: number;
70
+ usedAt: Date;
71
+ collections: CollectionSummary[];
72
+ }
73
+ /** Every world under `<root>/worlds`, most recently used first. */
74
+ export declare function listWorlds(root: string): WorldFile[];
75
+ /** Deletes a world and the journal files SQLite keeps beside it. */
76
+ export declare function removeWorld(path: string): void;
77
+ export declare const digest: (text: string) => string;
78
+ //# sourceMappingURL=store.d.ts.map