@atarashi/registry 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Gautam Suthar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # `@atarashi/registry`
2
+
3
+ The only I/O boundary for blueprints. `@atarashi/core` asks this package for a
4
+ `LoadedBlueprint` and never touches the disk or the network itself.
5
+
6
+ ```ts
7
+ import { createRegistry } from '@atarashi/registry';
8
+ import { generate } from '@atarashi/core';
9
+
10
+ const registry = createRegistry({ cwd: process.cwd(), offline: false });
11
+ const plan = await generate(spec, { source: registry, atarashiVersion });
12
+ ```
13
+
14
+ ## Four layers
15
+
16
+ | Layer | Where | Trust |
17
+ |---|---|---|
18
+ | Local | `./.atarashi/blueprints/<ns>/<name>/blueprint.json` | Untrusted |
19
+ | npm | `atarashi-blueprint-*` packages, by `npm:` spec | Untrusted |
20
+ | Registry | `atarashi.gautamsuthar.in/registry`, verified and cached | Per source |
21
+ | Bundled | `@atarashi/blueprints`, inside the npm package | Trusted |
22
+
23
+ Local wins outright, so an author can override anything while developing. Then
24
+ npm. Then the registry and the bundled collection compete on version: the newer
25
+ one wins, which is how a registry release ships a blueprint fix without a CLI
26
+ release, while a run with no network still resolves everything from the
27
+ bundled copy.
28
+
29
+ `trusted` is set by the *source*, never by the document the source serves. Only
30
+ trusted blueprints may run hooks without explicit consent (doc 09, T2).
31
+
32
+ ## Integrity
33
+
34
+ - The index carries a root hash over its canonicalized entries and version
35
+ manifest; it is re-verified on every read, including from the cache.
36
+ - Every tarball is checked against its `sha256` digest before extraction.
37
+ - A mismatch is a hard failure that names the artifact. There is no fallback
38
+ path to an unverified copy: unreachability degrades, tampering does not.
39
+
40
+ ## Extraction
41
+
42
+ Archives are parsed entirely in memory and only written once every member has
43
+ been validated: no absolute paths, drive letters, UNC paths, `..` segments,
44
+ null bytes, symlinks or hard links; per-file, total-size and entry-count
45
+ ceilings; case-collision detection; and a re-check that each resolved path is
46
+ inside the destination. This is a security boundary; see `tests/extract.test.ts`.
47
+
48
+ ## Cache
49
+
50
+ ```
51
+ $XDG_CACHE_HOME/atarashi/ # ~/Library/Caches on macOS, LOCALAPPDATA on Windows
52
+ index/1.4.2.json # verified against its published root hash
53
+ blueprints/db-postgres-1.2.0-8f2c…/
54
+ meta.json # ETags, fetch timestamps
55
+ ```
56
+
57
+ Content-addressed, so it is safe to share between projects and CI runs
58
+ (`~/.cache/atarashi` makes a good CI cache key). Override with
59
+ `ATARASHI_CACHE_DIR`.
60
+
61
+ Freshness is a 24 h TTL plus an ETag: inside the TTL nothing is requested, and
62
+ after it a `304` costs one conditional GET. A pinned version skips both: a
63
+ published registry version is immutable, so a cache hit is always correct.
64
+
65
+ ## Offline
66
+
67
+ `offline: true` or `ATARASHI_OFFLINE=1` skips the network entirely. Bundled
68
+ blueprints and anything already cached still resolve; a registry-only blueprint
69
+ reports what to run to populate the cache.
70
+
71
+ ## Publishing
72
+
73
+ ```bash
74
+ pnpm --filter @atarashi/registry build:index # pack, hash, emit v1/index.json
75
+ pnpm --filter @atarashi/registry bump:versions # propose version-manifest bumps
76
+ ```
77
+
78
+ `build-index.mts` is deterministic (sorted members, fixed mtime, no uid/gid),
79
+ so rebuilding the same sources produces byte-identical tarballs and the same
80
+ digests. `.github/workflows/registry-publish.yml` publishes it and refuses to
81
+ overwrite a version that already exists.
@@ -0,0 +1,477 @@
1
+ import * as _atarashi_schema from '@atarashi/schema';
2
+ import { RegistryIndex, Diagnostic, JsonValue, PresetManifest, BlueprintManifest, RegistryEntry, RegistrySource, RegistryPin } from '@atarashi/schema';
3
+ import { LoadedBlueprint, BlueprintSummary, BlueprintSource } from '@atarashi/core';
4
+
5
+ /** The default public registry. Overridable per-run and per-source. */
6
+ declare const DEFAULT_REGISTRY_URL = "https://atarashi.gautamsuthar.in/registry";
7
+ /** The index is re-checked at most once a day; pinned versions are immutable. */
8
+ declare const DEFAULT_TTL_MS: number;
9
+ interface CacheLayout {
10
+ root: string;
11
+ /** Verified index documents, one file per registry version. */
12
+ index: string;
13
+ /** Extracted blueprints, each directory named by its content hash. */
14
+ blueprints: string;
15
+ /** ETags and fetch timestamps. */
16
+ meta: string;
17
+ }
18
+ /**
19
+ * XDG on Linux, the platform convention elsewhere. `ATARASHI_CACHE_DIR` wins so
20
+ * CI can point the whole cache at a single restorable directory.
21
+ */
22
+ declare function resolveCacheRoot(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string;
23
+ declare function cacheLayout(root: string): CacheLayout;
24
+ /** `db/postgres` → `db-postgres`, so ids are usable as path and file names. */
25
+ declare const slugifyId: (id: string) => string;
26
+
27
+ /** What we remember between runs: ETags, fetch times and what is extracted. */
28
+ interface CacheMeta {
29
+ metaVersion: 1;
30
+ index: Record<string, {
31
+ etag?: string;
32
+ fetchedAt: string;
33
+ version: string;
34
+ }>;
35
+ blueprints: Record<string, {
36
+ id: string;
37
+ version: string;
38
+ integrity: string;
39
+ dir: string;
40
+ }>;
41
+ }
42
+ /**
43
+ * The on-disk cache. Content-addressed: a blueprint directory is named by its
44
+ * tarball's hash, so entries for different versions never collide and the whole
45
+ * directory is safe to share between projects and CI runs.
46
+ */
47
+ declare class RegistryCache {
48
+ readonly layout: CacheLayout;
49
+ private meta;
50
+ constructor(root: string);
51
+ readMeta(): Promise<CacheMeta>;
52
+ writeMeta(update: (meta: CacheMeta) => void): Promise<void>;
53
+ indexPath(version: string): string;
54
+ readIndex(version: string, source: string): Promise<RegistryIndex | undefined>;
55
+ writeIndex(index: RegistryIndex): Promise<void>;
56
+ /** Every registry version this machine has already verified. */
57
+ cachedIndexVersions(): Promise<string[]>;
58
+ /**
59
+ * Content-addressed, but still legible: `db-postgres-1.2.0-8f2c…`. The hash
60
+ * suffix is what makes it content-addressed; the rest is for humans reading
61
+ * `atarashi registry verify` output.
62
+ */
63
+ blueprintDir(id: string, version: string, integrity: string): string;
64
+ hasBlueprint(id: string, version: string, integrity: string): boolean;
65
+ /**
66
+ * Verifies, extracts into a temp directory, then renames into place — so a
67
+ * cache directory only ever exists in its complete, verified form.
68
+ */
69
+ storeBlueprint(entry: {
70
+ id: string;
71
+ version: string;
72
+ integrity: string;
73
+ }, tarball: Buffer): Promise<string>;
74
+ clear(): Promise<void>;
75
+ /**
76
+ * Re-checks that everything the meta file claims is cached is still there.
77
+ * Extracted trees cannot be re-hashed against the tarball digest, so this
78
+ * reports presence and shape, and prunes what has gone missing.
79
+ */
80
+ verify(): Promise<{
81
+ checked: number;
82
+ removed: string[];
83
+ }>;
84
+ size(): Promise<number>;
85
+ }
86
+
87
+ /**
88
+ * Every failure that crosses the registry boundary. Carries a
89
+ * `DIAGNOSTIC_CODES` value so the CLI can map it to an exit code (6 for every
90
+ * registry failure, per doc 04) and the web builder to a UI affordance.
91
+ */
92
+ declare class RegistryError extends Error {
93
+ readonly code: string;
94
+ readonly suggestions: string[];
95
+ readonly detail: string | undefined;
96
+ constructor(code: string, message: string, options?: {
97
+ suggestions?: string[];
98
+ detail?: string;
99
+ cause?: unknown;
100
+ });
101
+ toDiagnostic(): Diagnostic;
102
+ }
103
+ declare const unavailable: (message: string, suggestions?: string[]) => RegistryError;
104
+ declare const integrityFailure: (message: string, detail?: string) => RegistryError;
105
+
106
+ interface ExtractLimits {
107
+ /** Refuses archives with an absurd number of members. */
108
+ maxEntries: number;
109
+ /** Per-file ceiling. */
110
+ maxFileSize: number;
111
+ /** Total uncompressed bytes — the zip-bomb guard. */
112
+ maxTotalSize: number;
113
+ }
114
+ declare const DEFAULT_LIMITS: ExtractLimits;
115
+ interface ExtractedFile {
116
+ path: string;
117
+ mode: number;
118
+ contents: Buffer;
119
+ }
120
+ /**
121
+ * The same path rules the writer applies to generated files, applied to archive
122
+ * members: no absolute paths, no drive letters, no `..`, no null bytes. This is
123
+ * a security boundary (doc 09, T1) — every rejection here is a hard failure,
124
+ * never a skipped entry.
125
+ */
126
+ declare function safeEntryPath(raw: string, strip?: number): string | undefined;
127
+ /**
128
+ * Parses a tarball entirely in memory. Nothing reaches the filesystem until
129
+ * every member has been validated, so a malicious archive cannot leave a
130
+ * half-extracted mess behind.
131
+ */
132
+ declare function readTarball(tarball: Buffer, options?: {
133
+ strip?: number;
134
+ limits?: Partial<ExtractLimits>;
135
+ }): Promise<ExtractedFile[]>;
136
+ /**
137
+ * Writes a validated archive into `dest`. Every resolved path is re-checked
138
+ * against the destination root, so a bug in the path rules above still cannot
139
+ * write outside it.
140
+ */
141
+ declare function extractTarball(tarball: Buffer, dest: string, options?: {
142
+ strip?: number;
143
+ limits?: Partial<ExtractLimits>;
144
+ }): Promise<string[]>;
145
+
146
+ type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
147
+ interface HttpResponse {
148
+ status: number;
149
+ body: Buffer;
150
+ etag: string | undefined;
151
+ }
152
+ /**
153
+ * TLS only, with no plaintext fallback (doc 09, T4). `file:` is allowed so a
154
+ * local mirror — and the test suite — can stand in for the CDN.
155
+ */
156
+ declare function assertFetchable(url: string): URL;
157
+ interface HttpOptions {
158
+ etag?: string | undefined;
159
+ timeoutMs?: number;
160
+ maxBytes?: number;
161
+ fetchImpl?: FetchLike;
162
+ }
163
+ /**
164
+ * A conditional GET. `304` comes back with an empty body and the caller keeps
165
+ * what it has cached; anything else is a registry failure with the URL named.
166
+ */
167
+ declare function httpGet(url: string, options?: HttpOptions): Promise<HttpResponse>;
168
+ /** Joins a registry base URL with a path, tolerating a trailing slash either way. */
169
+ declare const joinUrl: (base: string, path: string) => string;
170
+
171
+ interface IndexStoreOptions {
172
+ cache: RegistryCache;
173
+ url?: string;
174
+ /** Exact registry version to resolve against. Immutable once published. */
175
+ pin?: string | null;
176
+ offline?: boolean;
177
+ ttlMs?: number;
178
+ fetchImpl?: FetchLike;
179
+ now?: () => Date;
180
+ /** Third-party sources never get to run hooks, whatever their index says. */
181
+ trusted?: boolean;
182
+ }
183
+ /** Where a resolved index came from — reported by `atarashi doctor`. */
184
+ interface IndexOrigin {
185
+ version: string;
186
+ url: string;
187
+ from: 'network' | 'cache' | 'bundled';
188
+ fetchedAt?: string;
189
+ integrity?: string;
190
+ }
191
+ /**
192
+ * Fetches, verifies and caches the registry index.
193
+ *
194
+ * Freshness is a 24 h TTL plus an ETag: inside the TTL nothing is requested at
195
+ * all, and after it a `304` costs one conditional GET. A pinned version skips
196
+ * both — it is immutable, so a cache hit is always correct.
197
+ */
198
+ declare class IndexStore {
199
+ private readonly options;
200
+ private loaded;
201
+ readonly diagnostics: Diagnostic[];
202
+ constructor(options: IndexStoreOptions);
203
+ get url(): string;
204
+ get origin(): IndexOrigin | undefined;
205
+ /** Resolves once per process; subsequent calls reuse the verified document. */
206
+ load(): Promise<{
207
+ index: RegistryIndex;
208
+ origin: IndexOrigin;
209
+ }>;
210
+ /** Discards the TTL and re-checks the network — `atarashi registry update`. */
211
+ refresh(): Promise<{
212
+ index: RegistryIndex;
213
+ origin: IndexOrigin;
214
+ }>;
215
+ private loadPinned;
216
+ private loadLatest;
217
+ private parse;
218
+ private persist;
219
+ private touch;
220
+ private newestCached;
221
+ private isFresh;
222
+ private originOf;
223
+ private now;
224
+ private nowIso;
225
+ }
226
+
227
+ type HashAlgorithm = 'sha256' | 'sha384' | 'sha512';
228
+ /** Subresource-integrity style digest: `sha256-<base64>`. */
229
+ declare function digest(data: Buffer | string, algorithm?: HashAlgorithm): string;
230
+ /** The hex form, used for content-addressed directory names. */
231
+ declare function hexDigest(data: Buffer | string, algorithm?: HashAlgorithm): string;
232
+ declare function parseIntegrity(value: string): {
233
+ algorithm: HashAlgorithm;
234
+ base64: string;
235
+ };
236
+ /**
237
+ * A mismatch is a hard failure that names the artifact — never a silent
238
+ * fallback to an unverified copy (doc 09, T4).
239
+ */
240
+ declare function verifyIntegrity(data: Buffer, expected: string, what: string): void;
241
+ /**
242
+ * Deterministic JSON: object keys sorted, no incidental whitespace. Two
243
+ * machines must produce the same bytes for the same document, or the root hash
244
+ * is meaningless.
245
+ */
246
+ declare function canonicalize(value: JsonValue | unknown): string;
247
+ /** The index's root hash covers its entries and its version manifest. */
248
+ declare function indexRootHash(index: Pick<RegistryIndex, 'entries' | 'versionManifest' | 'version' | 'indexVersion'>): string;
249
+ declare function verifyIndexIntegrity(index: RegistryIndex, source: string): void;
250
+
251
+ declare const MANIFEST_FILE = "blueprint.json";
252
+ declare const PRESET_FILE = "preset.json";
253
+ interface DirectoryBlueprintOptions {
254
+ trusted: boolean;
255
+ /** Human-readable provenance for diagnostics. Defaults to the directory. */
256
+ origin?: string;
257
+ }
258
+ /** Loads one blueprint from a directory containing a `blueprint.json`. */
259
+ declare function loadBlueprintDir(dir: string, options: DirectoryBlueprintOptions): Promise<LoadedBlueprint>;
260
+ declare function loadPresetFile(path: string): Promise<PresetManifest>;
261
+ declare const summarize: (manifest: BlueprintManifest) => BlueprintSummary;
262
+ /**
263
+ * A tree of blueprints laid out as `<root>/<namespace>/<name>/blueprint.json`,
264
+ * with presets in `<root>/../presets/*.json`. Backs both the bundled collection
265
+ * and a project's own `./.atarashi/blueprints`.
266
+ */
267
+ declare class DirectorySource {
268
+ readonly root: string;
269
+ private readonly options;
270
+ private manifests;
271
+ constructor(root: string, options: DirectoryBlueprintOptions & {
272
+ presetsDir?: string;
273
+ });
274
+ /** Scans lazily and once — a cold `atarashi list` should not pay twice. */
275
+ private scan;
276
+ ids(): Promise<string[]>;
277
+ has(id: string): Promise<boolean>;
278
+ load(id: string): Promise<LoadedBlueprint | undefined>;
279
+ summaries(): Promise<BlueprintSummary[]>;
280
+ loadPreset(id: string): Promise<PresetManifest | undefined>;
281
+ presets(): Promise<PresetManifest[]>;
282
+ }
283
+
284
+ /**
285
+ * The first-party collection shipped inside the npm package. Its presence is
286
+ * why `atarashi new` works on a plane: no index fetch, no tarball, no network.
287
+ */
288
+ declare function findBundledRoot(explicit?: string): string | undefined;
289
+ interface BundledSource {
290
+ source: DirectorySource;
291
+ root: string;
292
+ versionManifestPath: string;
293
+ }
294
+ declare function createBundledSource(explicit?: string): BundledSource | undefined;
295
+ /**
296
+ * The bundled fallback for the registry's version manifest, so dependency
297
+ * ranges resolve offline exactly as they do online.
298
+ */
299
+ declare function readBundledVersionManifest(path: string): Promise<Record<string, string> | undefined>;
300
+
301
+ /** Where a project keeps blueprints it is developing or vendoring. */
302
+ declare const LOCAL_BLUEPRINTS_DIR: string;
303
+ /**
304
+ * Project-local blueprints win over every other source, so an author can
305
+ * override a published blueprint by dropping a directory in the project. They
306
+ * are untrusted all the same: being on disk is not review.
307
+ */
308
+ declare function createLocalSource(cwd: string, dir?: string): DirectorySource;
309
+
310
+ declare const NPM_PREFIX = "npm:";
311
+ /** `npm:atarashi-blueprint-redis` → `atarashi-blueprint-redis`. */
312
+ declare const parseNpmSpec: (spec: string) => string | undefined;
313
+ /**
314
+ * A community blueprint published to npm. The package's `atarashi` field points
315
+ * at the directory holding its `blueprint.json`; everything else about it is
316
+ * treated as hostile — untrusted, so hooks require explicit consent.
317
+ */
318
+ declare class NpmSource {
319
+ private readonly cwd;
320
+ private readonly packages;
321
+ private readonly cache;
322
+ private resolved;
323
+ constructor(cwd: string, packages?: string[]);
324
+ /** Adds a package for this run, e.g. from `--add npm:atarashi-blueprint-redis`. */
325
+ add(spec: string): this;
326
+ private loadPackage;
327
+ private all;
328
+ load(id: string): Promise<LoadedBlueprint | undefined>;
329
+ summaries(): Promise<BlueprintSummary[]>;
330
+ }
331
+
332
+ declare const entrySummary: (entry: RegistryEntry) => BlueprintSummary;
333
+ /**
334
+ * Blueprints from the remote index. Resolution is index-only — capabilities,
335
+ * versions and conflicts all come from entry summaries — so nothing is
336
+ * downloaded until a blueprint is actually part of the graph.
337
+ */
338
+ declare class RemoteSource {
339
+ private readonly store;
340
+ private readonly cache;
341
+ private readonly options;
342
+ constructor(store: IndexStore, cache: RegistryCache, options?: {
343
+ offline?: boolean;
344
+ fetchImpl?: FetchLike;
345
+ /** Set from the *source* config, never from the index it serves. */
346
+ trusted?: boolean;
347
+ });
348
+ /** `undefined` when the index cannot be reached at all — bundled still works. */
349
+ private index;
350
+ entry(id: string): Promise<RegistryEntry | undefined>;
351
+ summaries(): Promise<BlueprintSummary[]>;
352
+ versionManifest(): Promise<Record<string, string> | undefined>;
353
+ load(id: string): Promise<LoadedBlueprint | undefined>;
354
+ loadEntry(entry: RegistryEntry): Promise<LoadedBlueprint>;
355
+ preset(id: string): Promise<{
356
+ kind: "blueprint" | "preset";
357
+ name: string;
358
+ version: string;
359
+ description: string;
360
+ id: string;
361
+ category: string;
362
+ tags: string[];
363
+ experimental: boolean;
364
+ deprecated: boolean;
365
+ provides: string[];
366
+ requires: string[];
367
+ conflicts: string[];
368
+ engines: {
369
+ atarashi?: string | undefined;
370
+ };
371
+ tarball: string;
372
+ integrity: string;
373
+ bundled: boolean;
374
+ } | undefined>;
375
+ loadPreset(id: string): Promise<{
376
+ blueprints: string[];
377
+ kind: "preset";
378
+ name: string;
379
+ version: string;
380
+ description: string;
381
+ manifestVersion: number;
382
+ id: string;
383
+ tags: string[];
384
+ experimental: boolean;
385
+ recommended: boolean;
386
+ answers: Record<string, _atarashi_schema.JsonValue>;
387
+ deprecated?: {
388
+ since: string;
389
+ use?: string | undefined;
390
+ reason?: string | undefined;
391
+ } | null | undefined;
392
+ } | undefined>;
393
+ /** Downloads and verifies once; every later run is a cache hit. */
394
+ private materialize;
395
+ }
396
+
397
+ interface RegistryOptions {
398
+ /** The project directory — where local and npm blueprints are looked up. */
399
+ cwd?: string;
400
+ cacheDir?: string;
401
+ url?: string;
402
+ /** Exact registry version, from `atarashi.json` or `atarashi registry pin`. */
403
+ pin?: string | null;
404
+ offline?: boolean;
405
+ ttlMs?: number;
406
+ /** Extra third-party sources. Untrusted unless the user marked them trusted. */
407
+ sources?: RegistrySource[];
408
+ /** Community packages pulled in with `--add npm:atarashi-blueprint-x`. */
409
+ npmPackages?: string[];
410
+ bundledDir?: string;
411
+ localDir?: string;
412
+ fetchImpl?: FetchLike;
413
+ now?: () => Date;
414
+ env?: NodeJS.ProcessEnv;
415
+ }
416
+ /** Where a blueprint id was resolved from, for `atarashi info` and diagnostics. */
417
+ type SourceKind = 'local' | 'npm' | 'registry' | 'bundled';
418
+ /**
419
+ * The one I/O boundary for blueprints. Core asks this for a `LoadedBlueprint`
420
+ * and never touches disk or the network itself.
421
+ *
422
+ * Four layers, in precedence order:
423
+ *
424
+ * 1. `./.atarashi/blueprints` — a project's own, so an author can override
425
+ * anything while developing.
426
+ * 2. `npm:` packages the user explicitly asked for.
427
+ * 3. the remote registry and the bundled collection, whichever has the newer
428
+ * version — this is what lets a registry release ship a blueprint fix
429
+ * without a CLI release, while a plane-mode run still works.
430
+ */
431
+ declare class Registry implements BlueprintSource {
432
+ private readonly local;
433
+ private readonly npm;
434
+ private readonly bundled;
435
+ private readonly remotes;
436
+ private readonly primary;
437
+ readonly cache: RegistryCache;
438
+ readonly offline: boolean;
439
+ constructor(options?: RegistryOptions);
440
+ /** Warnings collected while resolving — degraded-network notices, mostly. */
441
+ get diagnostics(): Diagnostic[];
442
+ load(id: string): Promise<LoadedBlueprint | undefined>;
443
+ providersOf(capability: string): Promise<BlueprintSummary[]>;
444
+ loadPreset(id: string): Promise<PresetManifest | undefined>;
445
+ /** Every preset visible from every layer, deduplicated by precedence — `atarashi list presets`. */
446
+ presets(): Promise<PresetManifest[]>;
447
+ /**
448
+ * One place defines the range every blueprint uses. The registry's manifest
449
+ * wins over the bundled one; anything the registry does not pin keeps the
450
+ * bundled value, so an offline run resolves the same names.
451
+ */
452
+ versionManifest(): Promise<Record<string, string>>;
453
+ /** Every blueprint visible from every layer, deduplicated by precedence. */
454
+ summaries(): Promise<BlueprintSummary[]>;
455
+ list(): Promise<BlueprintSummary[]>;
456
+ /** Which layer an id resolves from, without loading its files. */
457
+ originOf(id: string): Promise<SourceKind | undefined>;
458
+ /** What gets written into `atarashi.json` so this run can be reproduced. */
459
+ pin(): Promise<RegistryPin | undefined>;
460
+ /** `atarashi registry update` — ignore the TTL and re-check every source. */
461
+ update(): Promise<{
462
+ name: string;
463
+ version: string;
464
+ from: string;
465
+ }[]>;
466
+ /** `atarashi registry list` — every version this machine has verified. */
467
+ cachedVersions(): Promise<string[]>;
468
+ verifyCache(): Promise<{
469
+ checked: number;
470
+ removed: string[];
471
+ }>;
472
+ clearCache(): Promise<void>;
473
+ addNpmPackage(spec: string): this;
474
+ }
475
+ declare const createRegistry: (options?: RegistryOptions) => Registry;
476
+
477
+ export { type CacheLayout, type CacheMeta, DEFAULT_LIMITS, DEFAULT_REGISTRY_URL, DEFAULT_TTL_MS, DirectorySource, type ExtractLimits, type ExtractedFile, type FetchLike, type HttpResponse, type IndexOrigin, IndexStore, type IndexStoreOptions, LOCAL_BLUEPRINTS_DIR, MANIFEST_FILE, NPM_PREFIX, NpmSource, PRESET_FILE, Registry, RegistryCache, RegistryError, type RegistryOptions, RemoteSource, type SourceKind, assertFetchable, cacheLayout, canonicalize, createBundledSource, createLocalSource, createRegistry, digest, entrySummary, extractTarball, findBundledRoot, hexDigest, httpGet, indexRootHash, integrityFailure, joinUrl, loadBlueprintDir, loadPresetFile, parseIntegrity, parseNpmSpec, readBundledVersionManifest, readTarball, resolveCacheRoot, safeEntryPath, slugifyId, summarize, unavailable, verifyIndexIntegrity, verifyIntegrity };