@lunora/config 0.0.0 → 1.0.0-alpha.1

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 (39) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +115 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.mts +1075 -0
  5. package/dist/index.d.ts +1075 -0
  6. package/dist/index.mjs +20 -0
  7. package/dist/packem_shared/AGENT_RULES_DIR-lcgC08aE.mjs +40 -0
  8. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs +37 -0
  9. package/dist/packem_shared/LINKED_PROJECT_DIR-CXwXzV_C.mjs +52 -0
  10. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs +62 -0
  11. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-Dd1suoit.mjs +476 -0
  12. package/dist/packem_shared/applyAdditiveEdit-C-snTFEV.mjs +228 -0
  13. package/dist/packem_shared/buildPackageSecretsBlock-S74dgmwy.mjs +187 -0
  14. package/dist/packem_shared/classifyPolicyEdit-BHeAqF8P.mjs +99 -0
  15. package/dist/packem_shared/createConfirm-fvpdgJ9s.mjs +100 -0
  16. package/dist/packem_shared/detectFramework-Br-BcPBq.mjs +41 -0
  17. package/dist/packem_shared/discoverContainerInfo-BXFs6Wav.mjs +19 -0
  18. package/dist/packem_shared/discoverSchemaInfo-DWtypqpP.mjs +25 -0
  19. package/dist/packem_shared/discoverWorkflowInfo-CedvR0mn.mjs +19 -0
  20. package/dist/packem_shared/findWranglerFile-DwSuC-Kn.mjs +25 -0
  21. package/dist/packem_shared/formatLunoraEvent-D2fDeGB6.mjs +86 -0
  22. package/dist/packem_shared/handlePolicyScaffoldRequest-CiC2IGKx.mjs +103 -0
  23. package/dist/packem_shared/handleSchemaEditRequest-Df-Wrix-.mjs +99 -0
  24. package/dist/packem_shared/handleSeedRequest-DVCjaGO-.mjs +61 -0
  25. package/dist/packem_shared/inferLunoraBindings-0W3eRdIP.mjs +302 -0
  26. package/dist/packem_shared/injectRemoteFlags-C-WZAKLY.mjs +105 -0
  27. package/dist/packem_shared/interpretRemote-CtcIcB5-.mjs +34 -0
  28. package/dist/packem_shared/parseDevVariable-CJiq2IwE.mjs +30 -0
  29. package/dist/packem_shared/parseSchema-DSeyktvG.mjs +107 -0
  30. package/dist/packem_shared/policy-scaffold.d-DCmwn7zQ.d.mts +74 -0
  31. package/dist/packem_shared/policy-scaffold.d-DCmwn7zQ.d.ts +74 -0
  32. package/dist/packem_shared/reconcileWranglerBindings-ByJk3yLU.mjs +277 -0
  33. package/dist/packem_shared/renderStudioHtml-449Ysn75.mjs +37 -0
  34. package/dist/packem_shared/serveJsonHandler-B4OLTGLS.mjs +86 -0
  35. package/dist/packem_shared/studioAssetsStamp-Csk5RS4E.mjs +28 -0
  36. package/dist/studio-host/index.d.mts +227 -0
  37. package/dist/studio-host/index.d.ts +227 -0
  38. package/dist/studio-host/index.mjs +7 -0
  39. package/package.json +57 -17
@@ -0,0 +1,1075 @@
1
+ import { ContainerIR, WorkflowIR } from '@lunora/codegen';
2
+ export type { ContainerIR, WorkflowIR } from '@lunora/codegen';
3
+ import 'ts-morph';
4
+ export { type A as AdditivePolicyEdit, type D as DestructivePolicyEdit, type P as PolicyEdit, type a as PolicyScaffoldFailureReason, type b as ScaffoldFileResult, type S as ScaffoldPolicyEdit, type c as WireResult, type W as WireRlsEdit, d as classifyPolicyEdit, s as scaffoldPolicyFile, w as wireRlsIntoProcedure } from "./packem_shared/policy-scaffold.d-DCmwn7zQ.js";
5
+ /**
6
+ * Project-relative directory the Lunora agent skills ("rules") install into.
7
+ * This is the portable [Agent Skills](https://tanstack.com/intent/latest/docs/registry)
8
+ * location — Cursor, Claude Code, and GitHub Copilot all discover skills here.
9
+ */
10
+ declare const AGENT_RULES_DIR = ".agents/skills";
11
+ /** Env var the once-per-process-tree hint guard ({@link claimAgentRulesHint}) sets. */
12
+ declare const AGENT_RULES_HINT_ENV = "LUNORA_RULES_HINT_SHOWN";
13
+ /**
14
+ * The Lunora agent skills shipped by `@lunora/cli`. The first entry (`lunora`)
15
+ * is the router skill — its presence is what {@link detectAgentRules} treats as
16
+ * "rules installed", since every other skill is reachable through it.
17
+ */
18
+ declare const LUNORA_SKILL_NAMES: ReadonlyArray<string>;
19
+ /** The router skill whose presence marks the rule set as installed. */
20
+ declare const ROOT_SKILL_NAME = "lunora";
21
+ /**
22
+ * The single "rules not installed" message shared by every surface (the CLI
23
+ * `lunora dev` summary and the Vite dev plugin), so the wording and the
24
+ * pointer at `lunora rules install` stay identical wherever it appears.
25
+ */
26
+ declare const AGENT_RULES_HINT = "Lunora AI rules not installed — run `lunora rules install` so your coding agent knows how to use Lunora.";
27
+ /**
28
+ * Process-tree guard so the hint is emitted at most once. The first surface to
29
+ * print it sets {@link AGENT_RULES_HINT_ENV} on `process.env`; later surfaces (a
30
+ * Vite dev-server restart, or a child process that inherited the env) read it
31
+ * and stay quiet. Returns `true` the first time, `false` afterwards.
32
+ */
33
+ declare const claimAgentRulesHint: () => boolean;
34
+ interface AgentRulesStatus {
35
+ /**
36
+ * True when the `lunora` router skill is installed. We key on the router
37
+ * (not "all nine present") so a project that intentionally trims the set
38
+ * still counts as installed and isn't nagged.
39
+ */
40
+ readonly installed: boolean;
41
+ /** Skill names with no `SKILL.md` under `&lt;root>/.agents/skills/&lt;name>/`. */
42
+ readonly missing: ReadonlyArray<string>;
43
+ /** Skill names found under `&lt;root>/.agents/skills/&lt;name>/SKILL.md`. */
44
+ readonly present: ReadonlyArray<string>;
45
+ }
46
+ /**
47
+ * Detect whether the Lunora agent skills are installed in `projectRoot` by
48
+ * checking the skills folder for each `SKILL.md`. Pure filesystem reads, safe to
49
+ * call on every dev-server / CLI startup — the CLI, the Vite plugin, and the
50
+ * studio host all use it to decide whether to surface the "rules not installed"
51
+ * hint.
52
+ */
53
+ declare const detectAgentRules: (projectRoot: string) => AgentRulesStatus;
54
+ interface DiscoverContainerInfoResult {
55
+ /** Discovered container definitions; `[]` when none are declared or parsing failed. */
56
+ containers: ReadonlyArray<ContainerIR>;
57
+ /** Parse error message, when `lunora/containers.ts` exists but could not be analyzed. */
58
+ error?: string;
59
+ }
60
+ /**
61
+ * Discover the project's `defineContainer` declarations. Returns
62
+ * `{ containers: [] }` when the project has no `lunora/containers.ts` (not an
63
+ * error), or `{ containers: [], error }` when the file exists but could not be
64
+ * parsed — callers decide whether that is a warning (validator) or ignorable
65
+ * (inference).
66
+ */
67
+ declare const discoverContainerInfo: (projectRoot: string, schemaDirectory: string) => DiscoverContainerInfoResult;
68
+ /**
69
+ * The meta-frameworks Lunora can compose with, plus `"none"` for a standalone
70
+ * SPA / SSR-less project (the current default). Mirrors PLAN4 §2.4.
71
+ */
72
+ type DetectedFramework = "astro" | "none" | "nuxt" | "react-router" | "solid-start" | "sveltekit" | "tanstack-start" | "tanstack-start-solid";
73
+ /**
74
+ * void's class model (PLAN4 §3). Class A is Vite-native and Lunora owns the
75
+ * worker entry (`createWorker({ httpRouter })`). Class B frameworks own their own
76
+ * Cloudflare adapter, so Lunora injects its worker composition into the
77
+ * framework's server entry via hooks (PLAN4 M4). Class C is non-CF / SSR-less —
78
+ * ship the client adapter + a standalone Lunora worker (today's default).
79
+ */
80
+ type FrameworkClass = "A" | "B" | "C";
81
+ interface FrameworkDetection {
82
+ /** void's composition class for the detected framework. */
83
+ class: FrameworkClass;
84
+ /** The detected meta-framework, or `"none"` when no known one is present. */
85
+ framework: DetectedFramework;
86
+ }
87
+ /**
88
+ * Detect which meta-framework a project uses by inspecting its `package.json`
89
+ * dependencies, and classify it under void's class-A/B/C model (PLAN4 §3).
90
+ *
91
+ * Pure and best-effort: never throws. An unknown / missing / malformed
92
+ * `package.json` yields `{ framework: "none", class: "C" }` so the standalone
93
+ * SPA flow is preserved.
94
+ */
95
+ declare const detectFramework: (root: string) => FrameworkDetection;
96
+ /**
97
+ * The `.dev.vars` line grammar — one owner, shared by every reader/writer of the
98
+ * file so the format can't drift between packages. `@lunora/cli`'s `env`
99
+ * command (parse/serialize) and `@lunora/config`'s scaffolder (comment-
100
+ * preserving rewrite) do different *transforms*, but they agree on these
101
+ * primitives: the filename, what a `KEY` looks like, how lines split, and how
102
+ * quotes strip.
103
+ */
104
+ /** The conventional filename for local Cloudflare dev secrets (gitignored). */
105
+ declare const DEV_VARS_FILE: string;
106
+ /** Its committed, secret-free counterpart that scaffolding reads from. */
107
+ declare const DEV_VARS_EXAMPLE_FILE: string;
108
+ /** A bare `KEY` identifier — the part left of `=` in a `.dev.vars` line. */
109
+ declare const DEV_VARS_KEY_PATTERN: RegExp;
110
+ /** Splits file content into lines on either newline style. */
111
+
112
+ /**
113
+ * Parse `.dev.vars` content into its `{ key, value }` entries, in file order,
114
+ * with values unquoted and comments/blank/invalid lines dropped. The canonical
115
+ * read of the whole file — callers that just want the variables (rather than a
116
+ * comment-preserving rewrite) use this instead of hand-rolling the split loop.
117
+ */
118
+ declare const parseDevVariableEntries: (content: string) => {
119
+ key: string;
120
+ value: string;
121
+ }[];
122
+ interface DiscoverWorkflowInfoResult {
123
+ /** Parse error message, when `lunora/workflows.ts` exists but could not be analyzed. */
124
+ error?: string;
125
+ /** Discovered workflow definitions; `[]` when none are declared or parsing failed. */
126
+ workflows: ReadonlyArray<WorkflowIR>;
127
+ }
128
+ /**
129
+ * Discover the project's `defineWorkflow` declarations. Returns
130
+ * `{ workflows: [] }` when the project has no `lunora/workflows.ts` (not an
131
+ * error), or `{ workflows: [], error }` when the file exists but could not be
132
+ * parsed — callers decide whether that is a warning (validator) or ignorable
133
+ * (inference).
134
+ */
135
+ declare const discoverWorkflowInfo: (projectRoot: string, schemaDirectory: string) => DiscoverWorkflowInfoResult;
136
+ interface DurableObjectSpec {
137
+ binding: string;
138
+ className: string;
139
+ }
140
+ /**
141
+ * A `defineContainer` declaration plus whether its generated DO class is
142
+ * exported by the worker entry. Only exported containers are safe to
143
+ * provision — wrangler rejects a `containers[].class_name` (and its Durable
144
+ * Object binding) that the worker doesn't export.
145
+ */
146
+ interface InferredContainer extends ContainerIR {
147
+ exported: boolean;
148
+ }
149
+ /**
150
+ * A `defineWorkflow` declaration plus whether its generated
151
+ * `WorkflowEntrypoint` class is exported by the worker entry. Only exported
152
+ * workflows are safe to provision — wrangler rejects a `workflows[].class_name`
153
+ * the worker doesn't export. Workflows are NOT Durable Objects, so this never
154
+ * implies a `durable_objects` binding or migration.
155
+ */
156
+ interface InferredWorkflow extends WorkflowIR {
157
+ exported: boolean;
158
+ }
159
+ interface InferredBindings {
160
+ /** Containers declared in `lunora/containers.ts` (exported or not — see {@link InferredContainer.exported}). */
161
+ containers: InferredContainer[];
162
+ /** Durable Objects the worker entry exports → safe to bind. */
163
+ durableObjects: DurableObjectSpec[];
164
+ /** Schema declares a `.global()` table → needs the `DB` D1 binding. */
165
+ needsD1: boolean;
166
+ /** Human-readable provenance for each inferred binding / hint, for logging. */
167
+ signals: string[];
168
+ /** `@lunora/ai` is imported or `env.AI` is used → needs the `ai` Workers AI binding. */
169
+ usesAi: boolean;
170
+ /** `@lunora/analytics` is imported → self-describing `analytics_engine_datasets` binding (auto-writeable). */
171
+ usesAnalytics: boolean;
172
+ /** `@lunora/auth` is imported (sessions may be D1- or `SessionDO`-backed). */
173
+ usesAuth: boolean;
174
+ /** `@lunora/browser` is imported → self-describing `browser` binding (auto-writeable). */
175
+ usesBrowser: boolean;
176
+ /** `@lunora/hyperdrive` is imported (binding needs an un-mintable remote `id`; hint-only). */
177
+ usesHyperdrive: boolean;
178
+ /** `@lunora/images` is imported → self-describing `images` binding (auto-writeable). */
179
+ usesImages: boolean;
180
+ /** `@lunora/kv` is imported (namespace binding name + id are user-defined; hint-only). */
181
+ usesKv: boolean;
182
+ /** `@lunora/mail` is imported (Resend API key must be set in `.dev.vars`; no binding). */
183
+ usesMail: boolean;
184
+ /** `@lunora/payment` is imported (provider secrets must be set in `.dev.vars`; no binding). */
185
+ usesPayment: boolean;
186
+ /** `@lunora/pipelines` is imported (binding needs an un-mintable remote pipeline name; hint-only). */
187
+ usesPipelines: boolean;
188
+ /** `@lunora/scheduler` is imported. */
189
+ usesScheduler: boolean;
190
+ /** `@lunora/storage` is imported (R2 bucket binding name is user-defined). */
191
+ usesStorage: boolean;
192
+ /** Workflows declared in `lunora/workflows.ts` (exported or not — see {@link InferredWorkflow.exported}). */
193
+ workflows: InferredWorkflow[];
194
+ }
195
+ interface InferOptions {
196
+ projectRoot: string;
197
+ /** Directories (relative to root) to scan. Defaults to `lunora` + `src`. */
198
+ scanDirs?: ReadonlyArray<string>;
199
+ /** Lunora source directory holding `schema.ts`. Defaults to `lunora`. */
200
+ schemaDir?: string;
201
+ }
202
+ /**
203
+ * Scan a Lunora project and report which Cloudflare bindings its code implies.
204
+ * Read-only: performs no writes. Binding provisioning is driven by the worker
205
+ * entry's Durable Object exports plus the schema's D1 need; capability imports
206
+ * surface as hints.
207
+ */
208
+ declare const inferLunoraBindings: (options: InferOptions) => Promise<InferredBindings>;
209
+ /**
210
+ * Derive the list of `@lunora/*` package names that are actively used by a
211
+ * project, based on its already-resolved {@link InferredBindings}.
212
+ *
213
+ * This is the canonical bridge between binding inference and the package-aware
214
+ * `.dev.vars.example` scaffolding in `scaffold-dev-variables.ts`. The result is
215
+ * a stable, predictable slice of {@link CAPABILITY_SOURCES} source values,
216
+ * filtered to the flags that are `true` in `bindings` — in CAPABILITY_SOURCES
217
+ * declaration order.
218
+ */
219
+ declare const packageNamesFromBindings: (bindings: InferredBindings) => string[];
220
+ /** Directory holding per-checkout Lunora state (gitignored by convention). */
221
+ declare const LINKED_PROJECT_DIR = ".lunora";
222
+ /** The canonical link filename, relative to the project root. */
223
+ declare const LINKED_PROJECT_FILE: string;
224
+ /**
225
+ * The link record persisted to `.lunora/project.json`. Every field is optional
226
+ * so a partially-populated link (e.g. a worker name with no URL yet) still
227
+ * round-trips. `linkedAt` is an ISO-8601 stamp written at link time, purely
228
+ * informational.
229
+ */
230
+ interface LinkedProject {
231
+ /** Cloudflare account id the worker lives under, when known. */
232
+ account?: string;
233
+ /** Cloudflare environment name (`wrangler … --env &lt;env>`), when scoped. */
234
+ env?: string;
235
+ /** ISO-8601 timestamp recorded when the link was written. */
236
+ linkedAt?: string;
237
+ /** The deployed Worker's name (matches `name` in wrangler config). */
238
+ workerName?: string;
239
+ /** The deployed Worker's public URL (e.g. `https://app.acme.workers.dev`). */
240
+ workerUrl?: string;
241
+ }
242
+ /**
243
+ * Read the link record from `.lunora/project.json`, or `undefined` when there
244
+ * is no usable link. Best-effort: a missing file, parse error, or unexpected
245
+ * shape all collapse to `undefined`.
246
+ */
247
+ declare const readLinkedProject: (projectRoot: string) => LinkedProject | undefined;
248
+ /**
249
+ * Write the link record to `.lunora/project.json`, creating the `.lunora/`
250
+ * directory when absent. Only defined fields are persisted (so an empty value
251
+ * never clobbers a known one). Returns the absolute path written.
252
+ */
253
+ declare const writeLinkedProject: (projectRoot: string, link: LinkedProject) => string;
254
+ /**
255
+ * Shared formatter for the structured log events the Lunora runtime emits to the
256
+ * worker's `console` during development. Both the CLI `dev` command and the Vite
257
+ * plugin pipe worker output through `formatLunoraEvent` so a developer sees
258
+ * attributed, readable lines instead of raw JSON.
259
+ *
260
+ * The runtime emits two event shapes, each a single `console` line tagged
261
+ * `source: "lunora"` (see `@lunora/do`'s `request-log.ts`): a `type: "log"`
262
+ * event per `ctx.log.*` call, and a `type: "request"` event per RPC dispatch
263
+ * (opt-in for successful calls, always for errors).
264
+ *
265
+ * This module is intentionally dependency-free and colour-free: it returns the
266
+ * severity plus a plain display string, leaving ANSI/level colouring to each
267
+ * caller (the CLI routes through its `pail` logger; the Vite plugin dims inline).
268
+ * Any line that is not a lunora event returns `undefined`, signalling the caller
269
+ * to pass it through unchanged.
270
+ */
271
+ /** Severity a formatted line should be surfaced at, mapped onto the three logger channels. */
272
+ type LunoraLineLevel = "error" | "info" | "warn";
273
+ /** A formatted lunora event: the channel to surface it on, the display text, and which event produced it. */
274
+ interface LunoraFormattedLine {
275
+ /** `"log"` for a `ctx.log.*` line, `"rpc"` for a dispatch summary. */
276
+ kind: "log" | "rpc";
277
+ /** Logger channel — callers colour by this. */
278
+ level: LunoraLineLevel;
279
+ /** Human-readable, colour-free line content (no `[lunora]` tag — callers add their own). */
280
+ text: string;
281
+ }
282
+ /** Stable `source` tag every lunora console event carries. Mirrors `REQUEST_LOG_EVENT_SOURCE` in `@lunora/do`. */
283
+ declare const LUNORA_EVENT_SOURCE = "lunora";
284
+ /**
285
+ * Parse a single worker-output line and, when it is a lunora structured event,
286
+ * return its severity plus display text. Returns `undefined` for anything else —
287
+ * non-JSON lines, JSON that isn't a lunora event, or an unrecognised event type
288
+ * — so the caller passes the original line through untouched. Pure and total.
289
+ */
290
+ declare const formatLunoraEvent: (line: string) => LunoraFormattedLine | undefined;
291
+ /**
292
+ * Per-package secret-requirements registry for `.dev.vars` scaffolding.
293
+ *
294
+ * Each entry maps a `@lunora/*` package name → the secrets it requires at
295
+ * runtime, expressed as `{ key, description, docsUrl }` records. The scaffolder
296
+ * in {@link ./scaffold-dev-variables} reads this registry to emit package-aware
297
+ * `.dev.vars.example` entries (placeholders + inline doc-pointer comments).
298
+ *
299
+ * ## Adding a new add-on
300
+ *
301
+ * When a new `@lunora/*` package requires runtime secrets, add one entry to
302
+ * {@link PACKAGE_SECRETS_REGISTRY} keyed by its exact npm package name. Each
303
+ * `SecretEntry` in the array needs:
304
+ *
305
+ * - `key` — the env-var name the package reads from `env` (e.g. `RESEND_API_KEY`).
306
+ * - `description` — one line describing the secret and how to obtain it.
307
+ * - `docsUrl` — a stable URL to the package/provider docs.
308
+ *
309
+ * The registry lives in `@lunora/config` so that add-ons themselves never need
310
+ * to depend on it (no circular coupling). Add-ons document their secrets in
311
+ * their own READMEs; the registry duplicates that knowledge in a machine-readable
312
+ * form that the scaffolder can consume.
313
+ *
314
+ * **Never write a real secret value** in this file — `placeholderValue` entries
315
+ * are the only allowed values (they must pass `isPlaceholderValue` from
316
+ * scaffold-dev-variables).
317
+ */
318
+ /** A single secret variable required by a package. */
319
+ interface SecretEntry {
320
+ /** One sentence describing what this secret is and how to obtain it. */
321
+ description: string;
322
+ /** URL to the package or provider docs for this secret. */
323
+ docsUrl: string;
324
+ /** The env-var key as it appears in `.dev.vars`, e.g. `AUTH_SECRET`. */
325
+ key: string;
326
+ /**
327
+ * The placeholder value written into `.dev.vars.example`.
328
+ * Must pass `isPlaceholderValue` from scaffold-dev-variables so the
329
+ * scaffolder regenerates it when generating `.dev.vars`. Use angle-bracket
330
+ * conventions or a recognised marker like `replace-with-openssl-rand-hex-32`.
331
+ * Non-secret env-vars (e.g. `AUTH_URL`) may carry a real default value.
332
+ */
333
+ placeholderValue: string;
334
+ }
335
+ /**
336
+ * The canonical registry of per-package secret requirements.
337
+ *
338
+ * Keys are exact npm package names (e.g. `"@lunora/auth"`). Values are
339
+ * non-empty arrays of {@link SecretEntry} — one entry per required secret key.
340
+ *
341
+ * The scaffolder in `scaffold-dev-variables.ts` calls
342
+ * {@link secretsForPackages} to resolve the applicable entries from this map
343
+ * given the set of detected capability package names.
344
+ */
345
+ declare const PACKAGE_SECRETS_REGISTRY: Readonly<Record<string, ReadonlyArray<SecretEntry>>>;
346
+ /**
347
+ * Collect all secret entries required by the given set of package names. The
348
+ * order follows the order of `packageNames` (stable, predictable output), and
349
+ * within each package the entries are returned in registry declaration order.
350
+ *
351
+ * Only packages present in {@link PACKAGE_SECRETS_REGISTRY} contribute entries;
352
+ * unknown package names are silently ignored — this makes the call site resilient
353
+ * to future capability flags whose packages have no secrets.
354
+ */
355
+ declare const secretsForPackages: (packageNames: ReadonlyArray<string>) => SecretEntry[];
356
+ /** The canonical project-config filename probed at the project root. */
357
+ declare const LUNORA_CONFIG_FILE = "lunora.json";
358
+ /**
359
+ * The parsed `remote` preference from `lunora.json`:
360
+ *
361
+ * - `true` / `false` — the boolean form: enable or explicitly disable remote dev.
362
+ * - `undefined` — no usable preference (file absent, key absent, or malformed).
363
+ *
364
+ * The object form (scoping which binding kinds go remote) is reserved for a
365
+ * future increment; for now an object value is treated as "enabled" (truthy
366
+ * presence) so forward-written configs still turn remote on.
367
+ */
368
+ type RemotePreference = boolean | undefined;
369
+ /** The structural slice of `lunora.json` Lunora reads. */
370
+ interface LunoraProjectConfig {
371
+ remote?: unknown;
372
+ }
373
+ /**
374
+ * Interpret a raw `remote` value into a tri-state preference. A boolean passes
375
+ * through; an object is treated as enabled (the documented-but-not-yet-honored
376
+ * scoping form is still an opt-in); anything else (string, number, null) is no
377
+ * preference.
378
+ */
379
+ declare const interpretRemote: (value: unknown) => RemotePreference;
380
+ /**
381
+ * Read the project's `remote` preference from `lunora.json`, or `undefined` when
382
+ * there's no usable preference. Best-effort: never throws — a missing file,
383
+ * parse error, or unexpected shape all collapse to `undefined` so the caller
384
+ * falls through to the env/flag layers.
385
+ */
386
+ declare const readProjectRemotePreference: (projectRoot: string) => RemotePreference;
387
+ /**
388
+ * Whether we can interactively prompt — stdin must be a TTY. In CI / piped
389
+ * contexts this is false, and callers should fall back to a non-interactive
390
+ * default (skip, or require an explicit `--yes`) rather than hang on a read.
391
+ */
392
+ declare const isInteractive: () => boolean;
393
+ /**
394
+ * Ask a yes/no question on stdin. With `defaultYes`, an empty answer (just
395
+ * Enter) counts as yes and the prompt should read `[Y/n]`; otherwise empty is
396
+ * no (`[y/N]`). Shared by the CLI (`reset`, `dev`) and the Vite dev server.
397
+ */
398
+ declare const promptYesNo: (prompt: string, options?: {
399
+ defaultYes?: boolean;
400
+ }) => Promise<boolean>;
401
+ /**
402
+ * Build a default-yes `confirm(message)` for the scaffolders' `ensureDevVariables`:
403
+ * an interactive `[Y/n]` prompt (optionally prefixed, e.g. `"[lunora] "`) when
404
+ * stdin is a TTY, or an immediate `false` otherwise — so CI declines silently
405
+ * instead of blocking. Keeps the "non-interactive ⇒ decline" policy in one place
406
+ * rather than re-stated at every call site.
407
+ */
408
+ declare const createConfirm: (prefix?: string) => ((message: string) => Promise<boolean>);
409
+ /** One choice in a {@link promptSelect} list. `value` is returned; `label` (and optional `description`) are shown. */
410
+ interface SelectOption<T extends string> {
411
+ description?: string;
412
+ label: string;
413
+ value: T;
414
+ }
415
+ /**
416
+ * Ask the user to pick one option from a numbered list on stdin. Accepts the
417
+ * 1-based number or the option's `value`/`label` typed verbatim; an empty answer
418
+ * (just Enter) takes `settings.default`. In a non-interactive context (CI /
419
+ * piped — no TTY) it never reads and returns `settings.default` (or `undefined`),
420
+ * mirroring {@link createConfirm}'s "non-interactive ⇒ fall back" policy so
421
+ * automation never blocks.
422
+ */
423
+ declare const promptSelect: <T extends string>(message: string, options: ReadonlyArray<SelectOption<T>>, settings?: {
424
+ default?: T;
425
+ }) => Promise<T | undefined>;
426
+ /** One choice in a {@link promptMultiSelect} list. Identical shape to {@link SelectOption}; `value` is returned when picked. */
427
+ type MultiSelectOption<T extends string> = SelectOption<T>;
428
+ /**
429
+ * Ask the user to pick zero or more options from a numbered list on stdin.
430
+ * Accepts a comma- or space-separated list of 1-based numbers and/or option
431
+ * `value`/`label`s typed verbatim; an empty answer (just Enter) takes
432
+ * `settings.defaults`. In a non-interactive context (CI / piped — no TTY) it
433
+ * never reads and returns `settings.defaults ?? []`, mirroring {@link promptSelect}'s
434
+ * "non-interactive ⇒ fall back" policy so automation never blocks. Unknown
435
+ * tokens are ignored; the returned list is de-duplicated and preserves option
436
+ * order.
437
+ */
438
+ declare const promptMultiSelect: <T extends string>(message: string, options: ReadonlyArray<MultiSelectOption<T>>, settings?: {
439
+ defaults?: ReadonlyArray<T>;
440
+ }) => Promise<T[]>;
441
+ /**
442
+ * A container/workflow that is declared (so codegen emits its class) but the
443
+ * worker entry never re-exports — the one wiring step the generators can't always
444
+ * do for the developer. wrangler rejects a `class_name` the deployed worker
445
+ * doesn't export, so a deploy fails late on this; surfacing it as structured data
446
+ * lets the Vite plugin raise it in the dev error overlay (not just the console)
447
+ * the moment the gap appears. The human-readable form is also folded into
448
+ * {@link ReconcileBindingsResult.warnings}.
449
+ */
450
+ interface ExportGap {
451
+ /** Generated class wrangler needs exported, e.g. `OrderPipelineWorkflow`. */
452
+ className: string;
453
+ /** The `lunora/{containers,workflows}.ts` export name, e.g. `orderPipeline`. */
454
+ exportName: string;
455
+ /** Which declaration is unexported. */
456
+ kind: "container" | "workflow";
457
+ /** The `_generated/{module}` to re-export from, e.g. `workflows`. */
458
+ module: "containers" | "workflows";
459
+ }
460
+ interface ReconcileBindingsResult {
461
+ /** Short labels for each binding written (e.g. `"SCHEDULER/SchedulerDO"`). */
462
+ added: string[];
463
+ /** `true` when `wrangler.jsonc` was rewritten. */
464
+ changed: boolean;
465
+ /**
466
+ * Declared containers/workflows the worker entry doesn't re-export — the
467
+ * structured form of the corresponding `warnings` entries, for the dev error
468
+ * overlay. Empty when every declaration is wired.
469
+ */
470
+ exportGaps: ExportGap[];
471
+ /** Reason reconciliation was skipped, for logging. */
472
+ reason?: string;
473
+ /** Non-fatal hints for capabilities that cannot be auto-provisioned. */
474
+ warnings: string[];
475
+ /** Resolved wrangler path, or `undefined` when none was found. */
476
+ wranglerPath?: string;
477
+ }
478
+ /**
479
+ * Reconcile inferred Durable Object / D1 bindings into `wrangler.jsonc`.
480
+ *
481
+ * Writes only when something is missing; returns `changed: false` when the
482
+ * config already satisfies the inferred needs.
483
+ */
484
+ declare const reconcileWranglerBindings: (projectRoot: string, inferred: InferredBindings) => ReconcileBindingsResult;
485
+ /**
486
+ * The wrangler config sections Lunora can safely flip to remote mode in dev,
487
+ * each with the human label used in logs and the structural `shape` the entry
488
+ * lives in.
489
+ *
490
+ * `"array"` is a top-level array of binding objects (`d1_databases`,
491
+ * `kv_namespaces`, `r2_buckets`, `vectorize`, `services`). `"producers"` is
492
+ * `queues.producers[]` — consumers are NOT remoted (their schema has no `remote`
493
+ * field) and the edit path is two levels deep. `"object"` is a single binding
494
+ * object, not an array (`ai`), whose edit path targets the section key directly.
495
+ *
496
+ * Every kind here was confirmed against `wrangler/config-schema.json`: the
497
+ * entry's schema declares a `remote` property. Deliberately omits
498
+ * `durable_objects` (no CF remote-DO mode; shards stay local) and sections whose
499
+ * schema has no `remote` field (`hyperdrive`, `analytics_engine_datasets`,
500
+ * `secrets_store_secrets`, queue consumers, …). Widening further is a one-line
501
+ * table edit.
502
+ */
503
+ declare const REMOTE_ELIGIBLE_KEYS: {
504
+ readonly ai: {
505
+ readonly label: "AI";
506
+ readonly shape: "object";
507
+ };
508
+ readonly d1_databases: {
509
+ readonly label: "D1";
510
+ readonly shape: "array";
511
+ };
512
+ readonly kv_namespaces: {
513
+ readonly label: "KV";
514
+ readonly shape: "array";
515
+ };
516
+ readonly queues: {
517
+ readonly label: "Queue";
518
+ readonly shape: "producers";
519
+ };
520
+ readonly r2_buckets: {
521
+ readonly label: "R2";
522
+ readonly shape: "array";
523
+ };
524
+ readonly services: {
525
+ readonly label: "Service";
526
+ readonly shape: "array";
527
+ };
528
+ readonly vectorize: {
529
+ readonly label: "Vectorize";
530
+ readonly shape: "array";
531
+ };
532
+ };
533
+ type RemoteEligibleKey = keyof typeof REMOTE_ELIGIBLE_KEYS;
534
+ /** One binding object as it appears in any eligible section. */
535
+ interface BindingEntry {
536
+ binding?: string;
537
+ remote?: boolean;
538
+ }
539
+ /** One binding entry we mark remote, with enough provenance to log + edit it. */
540
+ interface RemoteBindingPlan {
541
+ /** The binding name as declared in the config (e.g. `"DB"`, `"FILES"`). */
542
+ binding: string;
543
+ /** Short kind label for logging (`"D1"`, `"KV"`, `"R2"`, `"Vectorize"`, …). */
544
+ kind: string;
545
+ /**
546
+ * The jsonc edit path within {@link RemoteBindingPlan.section}, relative to
547
+ * the section key: `[index]` for an `"array"` section, `["producers", index]`
548
+ * for a queue producer, or `[]` for the single-object `ai` section. The
549
+ * materializer prepends the section key and appends `"remote"`.
550
+ */
551
+ path: ReadonlyArray<number | string>;
552
+ /** The wrangler config key the entry lives under. */
553
+ section: RemoteEligibleKey;
554
+ }
555
+ /** The structural slice of a wrangler config the remote planner reads. */
556
+ interface RemoteWranglerShape {
557
+ ai?: BindingEntry | null;
558
+ d1_databases?: ReadonlyArray<BindingEntry | null | undefined>;
559
+ kv_namespaces?: ReadonlyArray<BindingEntry | null | undefined>;
560
+ queues?: {
561
+ producers?: ReadonlyArray<BindingEntry | null | undefined>;
562
+ } | null;
563
+ r2_buckets?: ReadonlyArray<BindingEntry | null | undefined>;
564
+ services?: ReadonlyArray<BindingEntry | null | undefined>;
565
+ vectorize?: ReadonlyArray<BindingEntry | null | undefined>;
566
+ }
567
+ /**
568
+ * Inspect a parsed wrangler config and list every eligible binding that should
569
+ * be flipped to remote mode. Pure — no file-system access, no mutation. An
570
+ * entry already carrying `"remote": true` is still reported (so logging is
571
+ * complete) but the materializer's edit is a harmless no-op for it.
572
+ */
573
+ declare const planRemoteBindings: (parsed: RemoteWranglerShape) => RemoteBindingPlan[];
574
+ /**
575
+ * Inject `"remote": true` onto each planned binding in the config `text`,
576
+ * comment-preservingly via jsonc edits. Pure string→string; the edits target
577
+ * disjoint entries so applying them sequentially is safe. The edit path is
578
+ * `[section, ...plan.path, "remote"]`, which resolves to the array element, the
579
+ * `queues.producers[i]` entry, or the single `ai` object as the plan demands.
580
+ */
581
+ declare const injectRemoteFlags: (text: string, plans: ReadonlyArray<RemoteBindingPlan>) => string;
582
+ interface MaterializeOptions {
583
+ /** When `false`, the call is a no-op (returns `enabled: false`). */
584
+ enabled: boolean;
585
+ projectRoot: string;
586
+ }
587
+ interface MaterializeResult {
588
+ /**
589
+ * Removes the generated temp config file. Always present and always safe to
590
+ * call: it is idempotent, a no-op when nothing was written (disabled /
591
+ * fall-through cases), and never throws if the path is already gone. The dev
592
+ * command calls this on every exit path (normal, signal, error).
593
+ */
594
+ cleanup: () => void;
595
+ /**
596
+ * Absolute path to the generated temp config to pass to
597
+ * `wrangler dev --config`. `undefined` when remote mode is disabled, no
598
+ * wrangler config was found, it failed to parse, or it declared no eligible
599
+ * binding (nothing to remote — run plain local dev).
600
+ */
601
+ configPath?: string;
602
+ /** Whether remote mode was requested at all. */
603
+ enabled: boolean;
604
+ /** Why no temp config was produced, for logging (only set when none was). */
605
+ reason?: string;
606
+ /** The bindings flipped to remote, for the dev banner. */
607
+ remoteBindings: RemoteBindingPlan[];
608
+ }
609
+ /**
610
+ * Produce a temporary wrangler config with `"remote": true` on every eligible
611
+ * binding, so `lunora dev` can run `wrangler dev --config &lt;temp>` against the
612
+ * deployed D1/KV/R2 without touching the user's file.
613
+ *
614
+ * The temp file is written as a sibling of the source `wrangler.jsonc` (in the
615
+ * project root), NOT an OS temp dir: wrangler resolves a config's relative paths
616
+ * (`main`, `assets`, `migrations_dir`, …) against the **config file's own
617
+ * directory**, so a temp config in `/tmp` would make wrangler look for
618
+ * `/tmp/src/server.ts` and fail to start the worker. Keeping it beside the real
619
+ * config preserves those relative paths. Returns `configPath: undefined` (with a
620
+ * `reason`) for every fall-through case so the caller degrades to plain local
621
+ * dev instead of failing.
622
+ */
623
+ declare const materializeRemoteWranglerConfig: (options: MaterializeOptions) => MaterializeResult;
624
+ /**
625
+ * Parse a `LUNORA_REMOTE` env value into the on/off decision. Truthy when set to
626
+ * `"1"` or `"true"` (case-insensitive); anything else — unset, `"0"`, `"false"`,
627
+ * empty — is off. Mirrors the `"1" | "true"` convention used across the runtime.
628
+ */
629
+ declare const isRemoteEnvEnabled: (value: string | undefined) => boolean;
630
+ /** The three inputs that can switch remote-binding dev on, in precedence order. */
631
+ interface RemoteEnableInputs {
632
+ /**
633
+ * The `remote` preference from `lunora.json` (the lowest-priority signal).
634
+ * `undefined` means "no project preference"; an explicit `false` here loses
635
+ * to neither the flag nor the env when those are absent — it just stays off.
636
+ */
637
+ configPreference?: boolean;
638
+ /** The raw `LUNORA_REMOTE` env value (parsed with {@link isRemoteEnvEnabled}). */
639
+ envValue?: string;
640
+ /** The explicit `--remote` CLI flag — `true` when passed, `undefined`/`false` otherwise. */
641
+ flag?: boolean;
642
+ }
643
+ /**
644
+ * Resolve whether remote-binding dev is on, with a clear precedence:
645
+ *
646
+ * 1. an explicit `--remote` flag (highest — a deliberate per-invocation choice),
647
+ * 2. then `LUNORA_REMOTE` in the environment,
648
+ * 3. then the `remote` key in `lunora.json` (lowest — a project default).
649
+ *
650
+ * The flag and env are one-directional (they can only turn remote *on*); only
651
+ * the config preference carries a meaningful `false`, and it applies solely when
652
+ * neither stronger signal is present. So a project that sets `"remote": false`
653
+ * is still overridable per-run by `--remote` or `LUNORA_REMOTE=1`.
654
+ */
655
+ declare const resolveRemoteEnabled: (inputs: RemoteEnableInputs) => boolean;
656
+ /**
657
+ * Whether an (already-unquoted) value looks like a fill-me-in placeholder —
658
+ * empty, angle-bracketed, or containing a known marker — rather than a real
659
+ * value. Used both when scaffolding (which values to regenerate) and by
660
+ * `lunora env doctor` (which set values are still unfilled).
661
+ */
662
+ declare const isPlaceholderValue: (value: string) => boolean;
663
+ /**
664
+ * The outcome of planning a scaffold — a discriminated union so the orchestrator
665
+ * never has to re-derive whether `content` is present.
666
+ *
667
+ * `exists`: `.dev.vars` is already there; nothing to do.
668
+ * `no-example`: nothing to scaffold from (stay silent — the project may not use secrets).
669
+ * `generate`: write `content`, a copy of the example with secret-looking placeholders replaced by fresh random hex (`generatedKeys` lists which).
670
+ */
671
+ type ScaffoldPlan = {
672
+ content: string;
673
+ generatedKeys: string[];
674
+ status: "generate";
675
+ } | {
676
+ status: "exists";
677
+ } | {
678
+ status: "no-example";
679
+ };
680
+ /** Decide whether (and what) to scaffold. Pure — given the current state of the two files. */
681
+ declare const planDevVariablesScaffold: (input: {
682
+ devVarsExists: boolean;
683
+ exampleContent: string | undefined; /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
684
+ randomHex?: (bytes: number) => string;
685
+ }) => ScaffoldPlan;
686
+ interface AugmentPlan {
687
+ /** The `.dev.vars` lines to append, in example order. */
688
+ additions: string[];
689
+ /** The subset of `missingKeys` whose values were freshly generated. */
690
+ generatedKeys: string[];
691
+ /** Keys present in the example but absent from the current `.dev.vars`. */
692
+ missingKeys: string[];
693
+ }
694
+ /**
695
+ * Plan how to top up an existing `.dev.vars` from the example: every example key
696
+ * not already present becomes an appended line (secret placeholders filled with
697
+ * fresh random hex, other values copied). Pure — no I/O. Empty `missingKeys`
698
+ * means the file is already complete.
699
+ */
700
+ declare const planDevVariablesAugment: (input: {
701
+ exampleContent: string;
702
+ existingContent: string; /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
703
+ randomHex?: (bytes: number) => string;
704
+ }) => AugmentPlan;
705
+ interface EnsureDevVariablesDeps {
706
+ /**
707
+ * Ask the user to confirm generating the file. Return `true` to generate.
708
+ * Consumers pass a TTY-aware prompt; in non-interactive contexts they should
709
+ * resolve `false` (we then report `"declined"` and the caller can hint).
710
+ */
711
+ confirm: (message: string) => Promise<boolean>;
712
+ cwd: string;
713
+ /** Emit a human-facing line (success / hint). */
714
+ info: (message: string) => void;
715
+ /** Injectable for tests; defaults to `crypto.randomBytes` hex. */
716
+ randomHex?: (bytes: number) => string;
717
+ /** Generate without prompting (e.g. a `--yes` flag). */
718
+ yes?: boolean;
719
+ }
720
+ type EnsureDevVariablesStatus = "augmented" | "declined" | "exists" | "generated" | "no-example" | "skipped-exists";
721
+ interface EnsureDevVariablesResult {
722
+ /** Keys appended to an existing file, when `status` is `"augmented"`. */
723
+ addedKeys: string[];
724
+ /** Keys whose values were freshly generated, when `status` is `"generated"`/`"augmented"`. */
725
+ generatedKeys: string[];
726
+ status: EnsureDevVariablesStatus;
727
+ }
728
+ /**
729
+ * Reconcile the project's `.dev.vars` with its `.dev.vars.example`:
730
+ *
731
+ * - file missing → offer to generate it (secret placeholders auto-filled);
732
+ * - file present but missing keys the example lists → offer to append them;
733
+ * - file present and complete → nothing to do.
734
+ *
735
+ * Prompts via `confirm` (skipped when `yes`); never overwrites existing values.
736
+ * Returns what happened so the caller can tailor any follow-up. Shared by
737
+ * `lunora dev` and the `@lunora/vite` dev server. All side effects funnel
738
+ * through `confirm`/`info`/`randomHex`.
739
+ */
740
+ declare const ensureDevVariables: (deps: EnsureDevVariablesDeps) => Promise<EnsureDevVariablesResult>;
741
+ /**
742
+ * Build the text that should be merged into `.dev.vars.example` for the given
743
+ * set of package names. Only entries whose key is not already present in
744
+ * `existingKeys` are included (additive / idempotent). Returns an empty string
745
+ * when there is nothing to add.
746
+ *
747
+ * The output is grouped by package with a blank-line separator so the file
748
+ * reads cleanly when multiple packages each contribute several keys.
749
+ *
750
+ * **Safety invariant:** this function never writes a real secret — every value
751
+ * in the output is the entry's `placeholderValue`.
752
+ */
753
+ declare const buildPackageSecretsBlock: (packageNames: ReadonlyArray<string>, existingKeys: ReadonlySet<string>) => string;
754
+ /**
755
+ * Write (or update) `.dev.vars.example` so that it contains the secrets
756
+ * required by `packageNames`. Existing lines are never removed or rewritten;
757
+ * new entries are appended (with a blank-line separator after existing content).
758
+ *
759
+ * Idempotent: re-running with the same `packageNames` does not duplicate keys
760
+ * already in the file. Returns the list of keys that were actually appended.
761
+ *
762
+ * **Safety invariant:** only placeholder values are written — no real secrets.
763
+ */
764
+ declare const ensureDevVariablesExample: (cwd: string, packageNames: ReadonlyArray<string>) => string[];
765
+ /** Add a new table to `defineSchema({ ... })`. */
766
+ interface AddTableEdit {
767
+ readonly kind: "addTable";
768
+ readonly table: string;
769
+ }
770
+ /** Add an `v.optional(...)` column to an existing table. */
771
+ interface AddOptionalColumnEdit {
772
+ readonly column: string;
773
+ readonly kind: "addOptionalColumn";
774
+ readonly table: string;
775
+ /**
776
+ * Inner validator expression text WITHOUT the `v.optional(...)` wrapper, e.g.
777
+ * `v.string()`. Always wrapped in `v.optional(...)` on apply, because only
778
+ * optional columns are additive-safe (a required column needs a backfill
779
+ * migration).
780
+ */
781
+ readonly validator: string;
782
+ }
783
+ /** Add a secondary index to an existing table. */
784
+ interface AddIndexEdit {
785
+ readonly fields: ReadonlyArray<string>;
786
+ readonly kind: "addIndex";
787
+ readonly name: string;
788
+ readonly table: string;
789
+ readonly unique?: boolean;
790
+ }
791
+ /** Additive edits — the only requests {@link applyAdditiveEdit} applies. */
792
+ type AdditiveEdit = AddIndexEdit | AddOptionalColumnEdit | AddTableEdit;
793
+ /**
794
+ * Destructive edits — never applied directly; routed to the migration handoff
795
+ * (plan 024 Item 5). Carried as data so the editor can describe the request.
796
+ */
797
+ interface DestructiveEdit {
798
+ readonly column?: string;
799
+ readonly kind: "changeColumnType" | "dropColumn" | "dropTable" | "makeRequired" | "renameColumn";
800
+ readonly newName?: string;
801
+ readonly table: string;
802
+ readonly validator?: string;
803
+ }
804
+ /** Any edit the editor can request. */
805
+ type SchemaEdit = AdditiveEdit | DestructiveEdit;
806
+ /**
807
+ * Classify an edit request. Additive edits ({@link AdditiveEdit}) apply
808
+ * directly; everything else changes stored data and is destructive.
809
+ */
810
+ declare const classifyEdit: (edit: SchemaEdit) => "additive" | "destructive";
811
+ /** Failure reasons an additive edit can report. */
812
+ type ApplyFailureReason = "aliased-define-schema" | "destructive" | "duplicate-column" | "duplicate-index" | "duplicate-table" | "invalid-identifier" | "invalid-validator" | "no-define-schema" | "non-object-argument" | "unknown-table";
813
+ /** Tagged result of applying an additive edit. */
814
+ type ApplyEditResult = {
815
+ ok: false;
816
+ reason: ApplyFailureReason;
817
+ } | {
818
+ ok: true;
819
+ text: string;
820
+ };
821
+ /**
822
+ * Apply an **additive** edit to a schema source string, preserving formatting.
823
+ * Destructive edits are refused with `{ ok: false, reason: "destructive" }`;
824
+ * route them through the migration handoff (plan 024 Item 5).
825
+ */
826
+ declare const applyAdditiveEdit: (source: string, edit: SchemaEdit) => ApplyEditResult;
827
+ /** A single declared column: its name and the raw validator expression text. */
828
+ interface SchemaColumn {
829
+ /** Column key (quotes stripped). */
830
+ readonly name: string;
831
+ /** Whether the validator is wrapped in `v.optional(...)`. */
832
+ readonly optional: boolean;
833
+ /** Raw validator expression text, e.g. `v.string()` or `v.optional(v.number())`. */
834
+ readonly validator: string;
835
+ }
836
+ /** A declared secondary index on a table. */
837
+ interface SchemaIndex {
838
+ /** Fields the index covers, in declaration order. */
839
+ readonly fields: ReadonlyArray<string>;
840
+ /** Index name. */
841
+ readonly name: string;
842
+ /** Whether the index was declared `{ unique: true }`. */
843
+ readonly unique: boolean;
844
+ }
845
+ /** One table parsed from `defineSchema({ ... })`. */
846
+ interface SchemaTable {
847
+ readonly columns: ReadonlyArray<SchemaColumn>;
848
+ /** Whether the table is `.global()`. */
849
+ readonly global: boolean;
850
+ readonly indexes: ReadonlyArray<SchemaIndex>;
851
+ readonly name: string;
852
+ /** The `.shardBy("field")` key, if any. */
853
+ readonly shardBy?: string;
854
+ }
855
+ /** Result of parsing a schema source string. */
856
+ type ParseSchemaResult = {
857
+ ok: false;
858
+ reason: "aliased-define-schema" | "no-define-schema" | "non-object-argument";
859
+ } | {
860
+ ok: true;
861
+ tables: ReadonlyArray<SchemaTable>;
862
+ };
863
+ /**
864
+ * Every `CallExpression` at or below a node. `getDescendantsOfKind` excludes the
865
+ * node itself, but a table initializer often is the outermost call in the
866
+ * `defineTable(...).global().index(...)` chain, so include it explicitly.
867
+ */
868
+
869
+ /**
870
+ * Parse the tables (with typed columns + indexes) out of a `lunora/schema.ts`
871
+ * source string. Returns a tagged result so callers can render a helpful
872
+ * message per failure mode without throwing.
873
+ */
874
+ declare const parseSchema: (source: string) => ParseSchemaResult;
875
+ interface SchemaInfo {
876
+ /** Whether the lunora schema declares any `.global()` table. */
877
+ hasGlobalTable: boolean;
878
+ /** Names of vector indexes declared via `.vectorize()` / `defineVectorIndex()`. */
879
+ vectorIndexNames?: ReadonlyArray<string>;
880
+ }
881
+ interface DiscoverSchemaInfoResult {
882
+ /** Parse error message, when the schema exists but could not be analyzed. */
883
+ error?: string;
884
+ /** Schema facts, or `undefined` when no `schema.ts` exists or parsing failed. */
885
+ info: SchemaInfo | undefined;
886
+ }
887
+ /**
888
+ * Discover {@link SchemaInfo} for a project. Returns `{ info: undefined }` when
889
+ * the project declares no `schema.ts` (not an error), or `{ info: undefined,
890
+ * error }` when a present schema could not be parsed — callers decide whether a
891
+ * parse failure is a warning (validator) or simply ignorable (inference).
892
+ */
893
+ declare const discoverSchemaInfo: (projectRoot: string, schemaDirectory: string) => DiscoverSchemaInfoResult;
894
+ /** Candidate wrangler config filenames, in the order every consumer probes them. */
895
+ declare const WRANGLER_FILES: readonly ["wrangler.jsonc", "wrangler.json"];
896
+ /** Locate the project's wrangler config, or `undefined` when none exists. */
897
+ declare const findWranglerFile: (projectRoot: string) => string | undefined;
898
+ interface ReadWranglerResult<T> {
899
+ /** Parsed config, or `undefined` when the file was not valid JSONC. */
900
+ parsed: T | undefined;
901
+ /** Raw file text — needed for comment-preserving `modify`/`applyEdits`. */
902
+ text: string;
903
+ }
904
+ /**
905
+ * Read and JSONC-parse a wrangler config file. Returns the raw `text` (for
906
+ * structural edits) alongside `parsed`, which is `undefined` when the file is
907
+ * not valid JSONC or does not parse to an object. Allows trailing commas, as
908
+ * wrangler does.
909
+ */
910
+ declare const readWranglerJsonc: <T = unknown>(wranglerPath: string) => ReadWranglerResult<T>;
911
+ declare const REQUIRED_COMPATIBILITY_DATE: string;
912
+ declare const REQUIRED_FLAG: string;
913
+ interface WranglerDurableObjectBinding {
914
+ class_name?: string;
915
+ name?: string;
916
+ }
917
+ /**
918
+ * A `tail_consumers` entry: a Worker that receives this Worker's tail events
919
+ * (logs, exceptions, fetch metadata) for forwarding to an external sink. See
920
+ * `withTailConsumer` for the wiring helper.
921
+ */
922
+ interface TailConsumer {
923
+ /** Optional Cloudflare environment of the consumer Worker. */
924
+ environment?: string;
925
+ /** Name of the Worker that consumes tail events. */
926
+ service?: string;
927
+ }
928
+ /** A wrangler `containers[]` entry (parsed from untrusted JSONC). */
929
+ interface WranglerContainerEntry {
930
+ class_name?: string;
931
+ image?: string;
932
+ instance_type?: string | {
933
+ disk_mb?: number;
934
+ memory_mib?: number;
935
+ vcpu?: number;
936
+ };
937
+ max_instances?: number;
938
+ }
939
+ /**
940
+ * A wrangler `workflows[]` entry (parsed from untrusted JSONC). Unlike
941
+ * containers, workflows are NOT Durable Objects — the entry stands alone (no
942
+ * `durable_objects` binding, no migration class).
943
+ */
944
+ interface WranglerWorkflowEntry {
945
+ binding?: string;
946
+ class_name?: string;
947
+ name?: string;
948
+ }
949
+ interface WranglerConfig {
950
+ analytics_engine_datasets?: ReadonlyArray<{
951
+ binding?: string;
952
+ dataset?: string;
953
+ } | null | undefined>;
954
+ assets?: {
955
+ binding?: string;
956
+ directory?: string;
957
+ html_handling?: string;
958
+ not_found_handling?: string;
959
+ };
960
+ browser?: {
961
+ binding?: string;
962
+ };
963
+ compatibility_date?: string;
964
+ compatibility_flags?: ReadonlyArray<string>;
965
+ containers?: ReadonlyArray<WranglerContainerEntry | null | undefined>;
966
+ d1_databases?: ReadonlyArray<{
967
+ binding?: string;
968
+ }>;
969
+ dispatch_namespaces?: ReadonlyArray<{
970
+ binding?: string;
971
+ namespace?: string;
972
+ outbound?: unknown;
973
+ } | null | undefined>;
974
+ durable_objects?: {
975
+ bindings?: ReadonlyArray<WranglerDurableObjectBinding>;
976
+ };
977
+ hyperdrive?: ReadonlyArray<{
978
+ binding?: string;
979
+ id?: string;
980
+ localConnectionString?: string;
981
+ } | null | undefined>;
982
+ images?: {
983
+ binding?: string;
984
+ };
985
+ kv_namespaces?: ReadonlyArray<{
986
+ binding?: string;
987
+ id?: string;
988
+ } | null | undefined>;
989
+ logpush?: boolean;
990
+ migrations?: ReadonlyArray<{
991
+ new_classes?: ReadonlyArray<string>;
992
+ new_sqlite_classes?: ReadonlyArray<string>;
993
+ } | null | undefined>;
994
+ mtls_certificates?: ReadonlyArray<{
995
+ binding?: string;
996
+ certificate_id?: string;
997
+ } | null | undefined>;
998
+ observability?: {
999
+ enabled?: boolean;
1000
+ head_sampling_rate?: number;
1001
+ logs?: {
1002
+ enabled?: boolean;
1003
+ head_sampling_rate?: number;
1004
+ };
1005
+ };
1006
+ pipelines?: ReadonlyArray<{
1007
+ binding?: string;
1008
+ pipeline?: string;
1009
+ } | null | undefined>;
1010
+ placement?: {
1011
+ mode?: string;
1012
+ };
1013
+ r2_buckets?: ReadonlyArray<{
1014
+ binding?: string;
1015
+ }>;
1016
+ send_email?: ReadonlyArray<{
1017
+ allowed_destination_addresses?: ReadonlyArray<string>;
1018
+ destination_address?: string;
1019
+ name?: string;
1020
+ } | null | undefined>;
1021
+ services?: ReadonlyArray<{
1022
+ binding?: string;
1023
+ entrypoint?: string;
1024
+ environment?: string;
1025
+ service?: string;
1026
+ } | null | undefined>;
1027
+ tail_consumers?: ReadonlyArray<TailConsumer | null | undefined>;
1028
+ vars?: Record<string, unknown>;
1029
+ vectorize?: ReadonlyArray<{
1030
+ binding?: string;
1031
+ index_name?: string;
1032
+ } | null | undefined>;
1033
+ workflows?: ReadonlyArray<WranglerWorkflowEntry | null | undefined>;
1034
+ }
1035
+ interface WranglerValidationReport {
1036
+ errors: string[];
1037
+ valid: boolean;
1038
+ warnings: string[];
1039
+ }
1040
+ /**
1041
+ * Return a new `WranglerConfig` with `consumer` present in `tail_consumers`,
1042
+ * wiring this Worker to forward its tail events (logs/exceptions) to another
1043
+ * Worker that fans them out to an external sink. Pure and idempotent: an
1044
+ * existing entry with the same `service` + `environment` is left untouched
1045
+ * rather than duplicated, so it is safe to call on every codegen/deploy.
1046
+ */
1047
+ declare const withTailConsumer: (wrangler: WranglerConfig, consumer: TailConsumer) => WranglerConfig;
1048
+ /**
1049
+ * Pure validator: given a parsed `WranglerConfig` object and an optional
1050
+ * `SchemaInfo`, produce a structured report. Performs no I/O.
1051
+ */
1052
+ declare const validateWranglerConfig: (wrangler: WranglerConfig | undefined, schema?: SchemaInfo) => WranglerValidationReport;
1053
+ /**
1054
+ * Convenience alias matching the original task-spec signature
1055
+ * `validateWrangler(wranglerJson, schema)` returning
1056
+ * `{ valid, errors, warnings }`.
1057
+ */
1058
+ declare const validateWrangler: typeof validateWranglerConfig;
1059
+ interface WranglerProjectValidationOptions {
1060
+ projectRoot: string;
1061
+ schemaDir?: string;
1062
+ }
1063
+ interface WranglerProjectValidationResult {
1064
+ problems: ReadonlyArray<string>;
1065
+ report: WranglerValidationReport;
1066
+ wranglerPath: string | undefined;
1067
+ }
1068
+ /**
1069
+ * File-system aware variant: reads `wrangler.jsonc`/`wrangler.json` from
1070
+ * the given project root, discovers the schema (if any), and delegates to
1071
+ * `validateWranglerConfig`. Returns the legacy
1072
+ * `{ problems, wranglerPath }` shape plus the structured `report`.
1073
+ */
1074
+ declare const validateWranglerProject: (options: WranglerProjectValidationOptions) => WranglerProjectValidationResult;
1075
+ export { AGENT_RULES_DIR, AGENT_RULES_HINT, AGENT_RULES_HINT_ENV, type AddIndexEdit, type AddOptionalColumnEdit, type AddTableEdit, type AdditiveEdit, type AgentRulesStatus, type ApplyEditResult, type ApplyFailureReason, type AugmentPlan, DEV_VARS_EXAMPLE_FILE, DEV_VARS_FILE, DEV_VARS_KEY_PATTERN, type DestructiveEdit, type DetectedFramework, type DiscoverContainerInfoResult, type DiscoverSchemaInfoResult, type DiscoverWorkflowInfoResult, type EnsureDevVariablesDeps, type EnsureDevVariablesResult, type EnsureDevVariablesStatus, type ExportGap, type FrameworkClass, type FrameworkDetection, type InferOptions, type InferredBindings, type InferredContainer, type InferredWorkflow, LINKED_PROJECT_DIR, LINKED_PROJECT_FILE, LUNORA_CONFIG_FILE, LUNORA_EVENT_SOURCE, LUNORA_SKILL_NAMES, type LinkedProject, type LunoraFormattedLine, type LunoraLineLevel, type LunoraProjectConfig, type MaterializeOptions, type MaterializeResult, type MultiSelectOption, PACKAGE_SECRETS_REGISTRY, type ParseSchemaResult, REMOTE_ELIGIBLE_KEYS, REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, ROOT_SKILL_NAME, type ReadWranglerResult, type ReconcileBindingsResult, type RemoteBindingPlan, type RemoteEnableInputs, type RemotePreference, type RemoteWranglerShape, type ScaffoldPlan, type SchemaColumn, type SchemaEdit, type SchemaIndex, type SchemaInfo, type SchemaTable, type SecretEntry, type SelectOption, type TailConsumer, WRANGLER_FILES, type WranglerConfig, type WranglerContainerEntry, type WranglerProjectValidationOptions, type WranglerProjectValidationResult, type WranglerValidationReport, type WranglerWorkflowEntry, applyAdditiveEdit, buildPackageSecretsBlock, claimAgentRulesHint, classifyEdit, createConfirm, detectAgentRules, detectFramework, discoverContainerInfo, discoverSchemaInfo, discoverWorkflowInfo, ensureDevVariables, ensureDevVariablesExample as ensureDevVarsExample, findWranglerFile, formatLunoraEvent, inferLunoraBindings, injectRemoteFlags, interpretRemote, isInteractive, isPlaceholderValue, isRemoteEnvEnabled, materializeRemoteWranglerConfig, packageNamesFromBindings, parseDevVariableEntries, parseSchema, planDevVariablesAugment, planDevVariablesScaffold, planRemoteBindings, promptMultiSelect, promptSelect, promptYesNo, readLinkedProject, readProjectRemotePreference, readWranglerJsonc, reconcileWranglerBindings, resolveRemoteEnabled, secretsForPackages, validateWrangler, validateWranglerConfig, validateWranglerProject, withTailConsumer, writeLinkedProject };