@lunora/config 1.0.0-alpha.1 → 1.0.0-alpha.100

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 (77) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +956 -446
  4. package/dist/index.d.ts +956 -446
  5. package/dist/index.mjs +1 -20
  6. package/dist/packem_shared/ACCENT-CLeV5v0K.mjs +7 -0
  7. package/dist/packem_shared/AGENT_MODE_ENV-B54hVQ_w.mjs +1 -0
  8. package/dist/packem_shared/AGENT_RULES_DIR-hP9TiDNx.mjs +1 -0
  9. package/dist/packem_shared/DEV_DAEMON_ENV-D9Z83rlU.mjs +4 -0
  10. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-DX6xGbpr.mjs +1 -0
  11. package/dist/packem_shared/LINKED_PROJECT_DIR-CMzUvj-1.mjs +2 -0
  12. package/dist/packem_shared/LUNORA_CONFIG_FILE-3bpx6TZN.mjs +1 -0
  13. package/dist/packem_shared/LUNORA_EVENT_SOURCE-ZclWASXS.mjs +1 -0
  14. package/dist/packem_shared/LunoraReporter-BnXUqh8t.mjs +6 -0
  15. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-BgmvEPA-.mjs +1 -0
  16. package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CUYWD0Mh.mjs +1 -0
  17. package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-ws6iE0y-.mjs +1 -0
  18. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-XijSB4Zt.mjs +1 -0
  19. package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-BNB1ogaj.mjs +1 -0
  20. package/dist/packem_shared/SEED_ENDPOINT-BaYShU8q.mjs +1 -0
  21. package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-B1h_wNDN.mjs +1 -0
  22. package/dist/packem_shared/WRANGLER_FILES-Bi_18Pj6.mjs +1 -0
  23. package/dist/packem_shared/applyAdditiveEdit-Cff30cSa.mjs +6 -0
  24. package/dist/packem_shared/assetContentType-DNZyUOuF.mjs +1 -0
  25. package/dist/packem_shared/buildPackageSecretsBlock-BpvaZzQ8.mjs +18 -0
  26. package/dist/packem_shared/classifyPolicyEdit-BzC_MfYK.mjs +24 -0
  27. package/dist/packem_shared/collectWranglerSecretVariables-OacEwPmL.mjs +1 -0
  28. package/dist/packem_shared/createConfirm-7IL0kZyE.mjs +7 -0
  29. package/dist/packem_shared/detectFramework-VTQfCNXy.mjs +1 -0
  30. package/dist/packem_shared/discoverAgentInfo-BJm0QtoI.mjs +1 -0
  31. package/dist/packem_shared/discoverContainerInfo-CYG9j2LY.mjs +1 -0
  32. package/dist/packem_shared/discoverSchemaInfo-C8X9mo-i.mjs +1 -0
  33. package/dist/packem_shared/discoverWorkflowInfo-Bbc0u2gE.mjs +1 -0
  34. package/dist/packem_shared/inferLunoraBindings-B2y-19KL.mjs +1 -0
  35. package/dist/packem_shared/jsonc-edit-BZVpxVA0.mjs +1 -0
  36. package/dist/packem_shared/parseDevVariable-NAVJ8tgA.mjs +1 -0
  37. package/dist/packem_shared/parseSchema-BQjgz6bk.mjs +1 -0
  38. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.mts → policy-scaffold.d-CFd2FlqG.d.mts} +22 -22
  39. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.ts → policy-scaffold.d-CFd2FlqG.d.ts} +22 -22
  40. package/dist/packem_shared/reconcileWranglerBindings-CHFT6zqp.mjs +1 -0
  41. package/dist/packem_shared/reconcileWranglerCompatibilityDate-BXNiEQ7p.mjs +1 -0
  42. package/dist/packem_shared/reconcileWranglerCrons-BmQa_kGL.mjs +1 -0
  43. package/dist/packem_shared/renderStudioHtml-B2AkxcW1.mjs +15 -0
  44. package/dist/packem_shared/serveJsonHandler-C5GWlWJF.mjs +1 -0
  45. package/dist/packem_shared/streamContainerLogs-BPYBNrWS.mjs +2 -0
  46. package/dist/packem_shared/write-atomic-Cdr2oQb-.mjs +1 -0
  47. package/dist/studio-host/index.d.mts +134 -92
  48. package/dist/studio-host/index.d.ts +134 -92
  49. package/dist/studio-host/index.mjs +1 -7
  50. package/package.json +11 -7
  51. package/dist/packem_shared/AGENT_RULES_DIR-lcgC08aE.mjs +0 -40
  52. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs +0 -37
  53. package/dist/packem_shared/LINKED_PROJECT_DIR-CXwXzV_C.mjs +0 -52
  54. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs +0 -62
  55. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-Dd1suoit.mjs +0 -476
  56. package/dist/packem_shared/applyAdditiveEdit-C-snTFEV.mjs +0 -228
  57. package/dist/packem_shared/buildPackageSecretsBlock-S74dgmwy.mjs +0 -187
  58. package/dist/packem_shared/classifyPolicyEdit-BHeAqF8P.mjs +0 -99
  59. package/dist/packem_shared/createConfirm-fvpdgJ9s.mjs +0 -100
  60. package/dist/packem_shared/detectFramework-Br-BcPBq.mjs +0 -41
  61. package/dist/packem_shared/discoverContainerInfo-BXFs6Wav.mjs +0 -19
  62. package/dist/packem_shared/discoverSchemaInfo-DWtypqpP.mjs +0 -25
  63. package/dist/packem_shared/discoverWorkflowInfo-CedvR0mn.mjs +0 -19
  64. package/dist/packem_shared/findWranglerFile-DwSuC-Kn.mjs +0 -25
  65. package/dist/packem_shared/formatLunoraEvent-D2fDeGB6.mjs +0 -86
  66. package/dist/packem_shared/handlePolicyScaffoldRequest-CiC2IGKx.mjs +0 -103
  67. package/dist/packem_shared/handleSchemaEditRequest-Df-Wrix-.mjs +0 -99
  68. package/dist/packem_shared/handleSeedRequest-DVCjaGO-.mjs +0 -61
  69. package/dist/packem_shared/inferLunoraBindings-0W3eRdIP.mjs +0 -302
  70. package/dist/packem_shared/injectRemoteFlags-C-WZAKLY.mjs +0 -105
  71. package/dist/packem_shared/interpretRemote-CtcIcB5-.mjs +0 -34
  72. package/dist/packem_shared/parseDevVariable-CJiq2IwE.mjs +0 -30
  73. package/dist/packem_shared/parseSchema-DSeyktvG.mjs +0 -107
  74. package/dist/packem_shared/reconcileWranglerBindings-ByJk3yLU.mjs +0 -277
  75. package/dist/packem_shared/renderStudioHtml-449Ysn75.mjs +0 -37
  76. package/dist/packem_shared/serveJsonHandler-B4OLTGLS.mjs +0 -86
  77. package/dist/packem_shared/studioAssetsStamp-Csk5RS4E.mjs +0 -28
package/dist/index.d.mts CHANGED
@@ -1,42 +1,73 @@
1
- import { ContainerIR, WorkflowIR } from '@lunora/codegen';
2
- export type { ContainerIR, WorkflowIR } from '@lunora/codegen';
1
+ import { AgentIR, ContainerIR, WorkflowIR, QueueIR, WranglerVariableIR } from '@lunora/codegen';
2
+ export type { AgentIR, ContainerIR, WorkflowIR } from '@lunora/codegen';
3
+ import { Writable } from 'node:stream';
3
4
  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.mjs";
5
+ 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-CFd2FlqG.mjs";
6
+ /** Minimal env shape — `process.env` structurally, injectable for tests. */
7
+ type EnvLike = Readonly<Record<string, string | undefined>>;
8
+ /** One detected agent: which tool, and which env var gave it away. */
9
+ interface AgentDetection {
10
+ /** Human-readable agent name, for the "agent detected" log line. */
11
+ name: string;
12
+ /** The environment variable that matched. */
13
+ variable: string;
14
+ }
15
+ /** Env var that forces agent mode on (`1`/`true`) or off (`0`/`false`), overriding detection. */
16
+ declare const AGENT_MODE_ENV = "LUNORA_AGENT_MODE";
17
+ /**
18
+ * Detect the AI agent driving this process, or `undefined` when none is.
19
+ * `LUNORA_AGENT_MODE` wins over `@visulima/find-ai-runner`'s marker table in
20
+ * both directions. Pure — pass a custom `env` in tests.
21
+ */
22
+ declare const detectAiAgent: (env?: EnvLike) => AgentDetection | undefined;
23
+ interface DiscoverAgentInfoResult {
24
+ /** Discovered agent definitions; `[]` when none are declared or parsing failed. */
25
+ agents: ReadonlyArray<AgentIR>;
26
+ /** Parse error message, when `lunora/agents.ts` exists but could not be analyzed. */
27
+ error?: string;
28
+ }
5
29
  /**
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
- */
30
+ * Discover the project's `defineAgent` declarations. Returns `{ agents: [] }`
31
+ * when the project has no `lunora/agents.ts` (not an error), or
32
+ * `{ agents: [], error }` when the file exists but could not be parsed — callers
33
+ * decide whether that is a warning (validator) or ignorable (inference).
34
+ */
35
+ declare const discoverAgentInfo: (projectRoot: string, schemaDirectory: string) => DiscoverAgentInfoResult;
36
+ /**
37
+ * Project-relative directory the Lunora agent skills ("rules") install into.
38
+ * This is the portable [Agent Skills](https://tanstack.com/intent/latest/docs/registry)
39
+ * location — Cursor, Claude Code, and GitHub Copilot all discover skills here.
40
+ */
10
41
  declare const AGENT_RULES_DIR = ".agents/skills";
11
42
  /** Env var the once-per-process-tree hint guard ({@link claimAgentRulesHint}) sets. */
12
43
  declare const AGENT_RULES_HINT_ENV = "LUNORA_RULES_HINT_SHOWN";
13
44
  /**
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
- */
45
+ * The Lunora agent skills shipped by `@lunora/cli`. The first entry (`lunora`)
46
+ * is the router skill — its presence is what {@link detectAgentRules} treats as
47
+ * "rules installed", since every other skill is reachable through it.
48
+ */
18
49
  declare const LUNORA_SKILL_NAMES: ReadonlyArray<string>;
19
50
  /** The router skill whose presence marks the rule set as installed. */
20
51
  declare const ROOT_SKILL_NAME = "lunora";
21
52
  /**
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
- */
53
+ * The single "rules not installed" message shared by every surface (the CLI
54
+ * `lunora dev` summary and the Vite dev plugin), so the wording and the
55
+ * pointer at `lunora rules install` stay identical wherever it appears.
56
+ */
26
57
  declare const AGENT_RULES_HINT = "Lunora AI rules not installed — run `lunora rules install` so your coding agent knows how to use Lunora.";
27
58
  /**
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
- */
59
+ * Process-tree guard so the hint is emitted at most once. The first surface to
60
+ * print it sets {@link AGENT_RULES_HINT_ENV} on `process.env`; later surfaces (a
61
+ * Vite dev-server restart, or a child process that inherited the env) read it
62
+ * and stay quiet. Returns `true` the first time, `false` afterwards.
63
+ */
33
64
  declare const claimAgentRulesHint: () => boolean;
34
65
  interface AgentRulesStatus {
35
66
  /**
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
- */
67
+ * True when the `lunora` router skill is installed. We key on the router
68
+ * (not "all nine present") so a project that intentionally trims the set
69
+ * still counts as installed and isn't nagged.
70
+ */
40
71
  readonly installed: boolean;
41
72
  /** Skill names with no `SKILL.md` under `&lt;root>/.agents/skills/&lt;name>/`. */
42
73
  readonly missing: ReadonlyArray<string>;
@@ -44,12 +75,12 @@ interface AgentRulesStatus {
44
75
  readonly present: ReadonlyArray<string>;
45
76
  }
46
77
  /**
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
- */
78
+ * Detect whether the Lunora agent skills are installed in `projectRoot` by
79
+ * checking the skills folder for each `SKILL.md`. Pure filesystem reads, safe to
80
+ * call on every dev-server / CLI startup — the CLI, the Vite plugin, and the
81
+ * studio host all use it to decide whether to surface the "rules not installed"
82
+ * hint.
83
+ */
53
84
  declare const detectAgentRules: (projectRoot: string) => AgentRulesStatus;
54
85
  interface DiscoverContainerInfoResult {
55
86
  /** Discovered container definitions; `[]` when none are declared or parsing failed. */
@@ -58,25 +89,96 @@ interface DiscoverContainerInfoResult {
58
89
  error?: string;
59
90
  }
60
91
  /**
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
- */
92
+ * Discover the project's `defineContainer` declarations. Returns
93
+ * `{ containers: [] }` when the project has no `lunora/containers.ts` (not an
94
+ * error), or `{ containers: [], error }` when the file exists but could not be
95
+ * parsed — callers decide whether that is a warning (validator) or ignorable
96
+ * (inference).
97
+ */
67
98
  declare const discoverContainerInfo: (projectRoot: string, schemaDirectory: string) => DiscoverContainerInfoResult;
99
+ /** Severity a container output line is surfaced at: `stderr` → `error`, `stdout` → `info`. */
100
+ type ContainerLogLevel = "error" | "info";
101
+ /** One declared container to follow, identified by the names codegen lifts from `defineContainer`. */
102
+ interface ContainerLogSource {
103
+ /** Generated Durable Object class name, e.g. `TranscoderContainer`. Wrangler's dev image is `cloudflare-dev/&lt;lowercased>`. */
104
+ className: string;
105
+ /** The `lunora/containers.ts` export name, e.g. `transcoder` — used as the display tag. */
106
+ exportName: string;
107
+ }
108
+ /** A single line of container output handed back to the caller. */
109
+ interface ContainerLogLine {
110
+ /** `"error"` for the container's stderr, `"info"` for its stdout. */
111
+ level: ContainerLogLevel;
112
+ /** The container's export name (`transcoder`), for tagging the line. */
113
+ name: string;
114
+ /** One output line, with the trailing newline (and any `\r`) stripped. */
115
+ text: string;
116
+ }
117
+ interface ContainerLogStreamOptions {
118
+ /** The declared containers to follow. An empty list yields an inert handle. */
119
+ containers: ReadonlyArray<ContainerLogSource>;
120
+ /** Injected Docker client — defaults to a real lazily-imported `dockerode` instance. Tests pass a stub. */
121
+ docker?: DockerLike;
122
+ /** Called once per container output line. */
123
+ onLine: (line: ContainerLogLine) => void;
124
+ /** Called once when the Docker engine can't be reached (re-armed after it recovers). Defaults to silent. */
125
+ onUnavailable?: (message: string) => void;
126
+ /** Poll interval override, in ms. */
127
+ pollIntervalMs?: number;
128
+ }
129
+ /** Handle controlling a running log stream. */
130
+ interface ContainerLogStreamHandle {
131
+ /** Stop polling and tear down every attached log stream. Idempotent. */
132
+ close: () => void;
133
+ }
134
+ /** The minimal structural slice of a `dockerode` log stream this module consumes. */
135
+ interface DockerLogStream {
136
+ destroy: () => void;
137
+ on: (event: "data" | "end" | "error", listener: (chunk?: Buffer) => void) => void;
138
+ }
139
+ /** The minimal structural slice of a `dockerode` instance this module consumes. */
140
+ interface DockerLike {
141
+ getContainer: (id: string) => {
142
+ logs: (options: {
143
+ follow: true;
144
+ stderr: true;
145
+ stdout: true;
146
+ tail: "all";
147
+ timestamps: false;
148
+ }) => Promise<DockerLogStream>;
149
+ };
150
+ listContainers: (options: {
151
+ filters: {
152
+ status: ["running"];
153
+ };
154
+ }) => Promise<{
155
+ Id: string;
156
+ Image: string;
157
+ }[]>;
158
+ modem: {
159
+ demuxStream: (stream: DockerLogStream, stdout: Writable, stderr: Writable) => void;
160
+ };
161
+ }
162
+ /**
163
+ * Follow the local Docker logs of every declared container, emitting each output
164
+ * line through `onLine` tagged with its export name. Polls for containers (they
165
+ * start lazily on first request and may be replaced on restart), attaches once
166
+ * per container id, and drops streams whose container has gone. Returns
167
+ * immediately with a `close()` handle; all work happens asynchronously.
168
+ */
169
+ declare const streamContainerLogs: (options: ContainerLogStreamOptions) => ContainerLogStreamHandle;
68
170
  /**
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
- */
171
+ * The meta-frameworks Lunora can compose with, plus `"none"` for a standalone
172
+ * SPA / SSR-less project (the current default). Mirrors PLAN4 §2.4.
173
+ */
72
174
  type DetectedFramework = "astro" | "none" | "nuxt" | "react-router" | "solid-start" | "sveltekit" | "tanstack-start" | "tanstack-start-solid";
73
175
  /**
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
- */
176
+ * void's class model (PLAN4 §3). Class A is Vite-native and Lunora owns the
177
+ * worker entry (`createWorker({ httpRouter })`). Class B frameworks own their own
178
+ * Cloudflare adapter, so Lunora injects its worker composition into the
179
+ * framework's server entry via hooks (PLAN4 M4). Class C is non-CF / SSR-less —
180
+ * ship the client adapter + a standalone Lunora worker (today's default).
181
+ */
80
182
  type FrameworkClass = "A" | "B" | "C";
81
183
  interface FrameworkDetection {
82
184
  /** void's composition class for the detected framework. */
@@ -85,36 +187,147 @@ interface FrameworkDetection {
85
187
  framework: DetectedFramework;
86
188
  }
87
189
  /**
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
- */
190
+ * Read and parse the project `package.json`, returning its merged
191
+ * `dependencies` + `devDependencies` name set (empty on any failure). Public
192
+ * so sibling consumers (e.g. the CLI's Vite-project detection) share one
193
+ * best-effort reader instead of re-parsing `package.json` themselves.
194
+ */
195
+ declare const readProjectDependencyNames: (root: string) => ReadonlySet<string>;
196
+ /**
197
+ * Detect which meta-framework a project uses by inspecting its `package.json`
198
+ * dependencies, and classify it under void's class-A/B/C model (PLAN4 §3).
199
+ *
200
+ * Pure and best-effort: never throws. An unknown / missing / malformed
201
+ * `package.json` yields `{ framework: "none", class: "C" }` so the standalone
202
+ * SPA flow is preserved.
203
+ */
95
204
  declare const detectFramework: (root: string) => FrameworkDetection;
205
+ /** Directory holding per-checkout Lunora state (gitignored by convention). */
206
+ declare const DEV_STATE_DIR = ".lunora";
207
+ /** The dev-server state filename, relative to the project root. */
208
+ declare const DEV_STATE_FILE: string;
209
+ /** Log file a backgrounded dev server's output is captured to, relative to the project root. */
210
+ declare const DEV_LOG_FILE: string;
211
+ /**
212
+ * Marker env `lunora dev --background` sets on the detached server process
213
+ * (the daemon `lunora dev` or `vite dev`), so it records itself as
214
+ * `background: true` — and, for the CLI daemon, never re-detects an agent and
215
+ * recurses into background mode again.
216
+ */
217
+ declare const DEV_DAEMON_ENV = "LUNORA_DEV_DAEMON";
218
+ /** Env carrying the capture-log path into the detached server, recorded in the state file. */
219
+ declare const DEV_LOG_FILE_ENV = "LUNORA_DEV_LOG_FILE";
220
+ /**
221
+ * Env carrying the PID of a parent that holds a *provisional* state record it
222
+ * expects the child dev server to supersede. The CLI claims `.lunora/dev.json`
223
+ * with its own PID before spawning the real server (closing the duplicate-start
224
+ * race for the vite flavor and the wrangler daemon), then hands its PID down
225
+ * via this variable; the child's claim (see {@link claimDevServerState}'s
226
+ * `supersedePid`) may replace exactly that record with the authoritative
227
+ * URL + PID.
228
+ */
229
+ declare const DEV_HANDOFF_ENV = "LUNORA_DEV_HANDOFF_PID";
230
+ /** How the recorded dev server runs. */
231
+ type DevServerMode = "cli" | "vite";
232
+ /** The state record persisted to `.lunora/dev.json`. */
233
+ interface DevServerState {
234
+ /** Whether the server was detached into the background (`lunora dev --background`). */
235
+ background?: boolean;
236
+ /** Absolute path of the log file capturing the server's output, when captured. */
237
+ logFile?: string;
238
+ /** `"cli"` for the `lunora dev` wrangler orchestration, `"vite"` for a Vite dev server. */
239
+ mode: DevServerMode;
240
+ /** PID of the process to signal for shutdown (the orchestrating CLI or the Vite process). */
241
+ pid: number;
242
+ /** ISO-8601 stamp written at startup, purely informational (drives `status` uptime). */
243
+ startedAt?: string;
244
+ /** The embedded studio server's URL, when it runs. */
245
+ studioUrl?: string;
246
+ /** The primary URL serving the worker/app. */
247
+ url: string;
248
+ }
249
+ /**
250
+ * True when a process with `pid` is currently alive AND signalable by this
251
+ * user. Signal `0` performs the existence check without delivering anything.
252
+ *
253
+ * `EPERM` ("alive but another user's process") deliberately counts as NOT
254
+ * alive here: every dev server this module tracks was spawned by the current
255
+ * user, so a PID we cannot signal is by definition a recycled PID — treating
256
+ * it as running would wedge the lockfile permanently and aim `dev stop`'s
257
+ * kill escalation at an innocent process.
258
+ */
259
+ declare const isProcessAlive: (pid: number) => boolean;
260
+ /**
261
+ * True when the record's PID verifiably still refers to the dev server that
262
+ * wrote the record — not merely "some process exists with that number".
263
+ * Guards against PID reuse: a server process necessarily starts BEFORE its
264
+ * record is written, so a process that started after `startedAt` (+ skew) is
265
+ * a recycled PID wearing the corpse's number. The start-time check runs only
266
+ * where the platform exposes it (Linux); elsewhere liveness alone decides.
267
+ */
268
+ declare const isRecordedProcessCurrent: (state: DevServerState) => boolean;
96
269
  /**
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
- */
270
+ * Read the state record from `.lunora/dev.json`, or `undefined` when there is
271
+ * no usable record. Best-effort; performs NO liveness check — use
272
+ * {@link readLiveDevServerState} for "is a dev server actually running".
273
+ */
274
+ declare const readDevServerState: (projectRoot: string) => DevServerState | undefined;
275
+ /**
276
+ * Write the state record to `.lunora/dev.json`, creating the `.lunora/`
277
+ * directory when absent. Returns the absolute path written, or `undefined`
278
+ * when the write failed (state is convenience metadata — a read-only checkout
279
+ * must never crash dev startup).
280
+ */
281
+ declare const writeDevServerState: (projectRoot: string, state: DevServerState) => string | undefined;
282
+ /**
283
+ * Merge `patch` into the existing state record, when one exists. Used by the
284
+ * CLI to stamp `background`/`logFile` onto the record the Vite plugin wrote.
285
+ * Returns the merged record, or `undefined` when there was nothing to update.
286
+ */
287
+ declare const updateDevServerState: (projectRoot: string, patch: Partial<DevServerState>) => DevServerState | undefined;
288
+ /**
289
+ * Remove `.lunora/dev.json`. Idempotent and never throws. When `expectedPid`
290
+ * is given, the file is only removed while it still records that PID — so a
291
+ * shutting-down server can't clobber the record a newer server just wrote.
292
+ */
293
+ declare const clearDevServerState: (projectRoot: string, expectedPid?: number) => void;
294
+ /**
295
+ * The state record of a dev server that is verifiably running right now, or
296
+ * `undefined`. A record whose PID is dead — or recycled onto a different
297
+ * process (see {@link isRecordedProcessCurrent}) — is stale: it is cleared on
298
+ * the spot so subsequent starts don't keep re-reading a corpse.
299
+ */
300
+ declare const readLiveDevServerState: (projectRoot: string) => DevServerState | undefined;
301
+ /** Result of {@link claimDevServerState}: claimed, or lost to the live server already recorded. */
302
+ interface ClaimDevServerStateResult {
303
+ /** The live record that won the race, when `ok` is `false`. */
304
+ existing?: DevServerState;
305
+ /** Whether this process now owns the record. */
306
+ ok: boolean;
307
+ }
308
+ declare const claimDevServerState: (projectRoot: string, state: DevServerState, options?: {
309
+ supersedePid?: number;
310
+ }) => ClaimDevServerStateResult;
311
+ /**
312
+ * The `.dev.vars` line grammar — one owner, shared by every reader/writer of the
313
+ * file so the format can't drift between packages. `@lunora/cli`'s `env`
314
+ * command (parse/serialize) and `@lunora/config`'s scaffolder (comment-
315
+ * preserving rewrite) do different *transforms*, but they agree on these
316
+ * primitives: the filename, what a `KEY` looks like, how lines split, and how
317
+ * quotes strip.
318
+ */
104
319
  /** The conventional filename for local Cloudflare dev secrets (gitignored). */
105
320
  declare const DEV_VARS_FILE: string;
106
321
  /** Its committed, secret-free counterpart that scaffolding reads from. */
107
322
  declare const DEV_VARS_EXAMPLE_FILE: string;
108
323
  /** A bare `KEY` identifier — the part left of `=` in a `.dev.vars` line. */
109
324
  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
- */
325
+ /**
326
+ * Parse `.dev.vars` content into its `{ key, value }` entries, in file order,
327
+ * with values unquoted and comments/blank/invalid lines dropped. The canonical
328
+ * read of the whole file — callers that just want the variables (rather than a
329
+ * comment-preserving rewrite) use this instead of hand-rolling the split loop.
330
+ */
118
331
  declare const parseDevVariableEntries: (content: string) => {
119
332
  key: string;
120
333
  value: string;
@@ -126,69 +339,104 @@ interface DiscoverWorkflowInfoResult {
126
339
  workflows: ReadonlyArray<WorkflowIR>;
127
340
  }
128
341
  /**
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
- */
342
+ * Discover the project's `defineWorkflow` declarations. Returns
343
+ * `{ workflows: [] }` when the project has no `lunora/workflows.ts` (not an
344
+ * error), or `{ workflows: [], error }` when the file exists but could not be
345
+ * parsed — callers decide whether that is a warning (validator) or ignorable
346
+ * (inference).
347
+ */
135
348
  declare const discoverWorkflowInfo: (projectRoot: string, schemaDirectory: string) => DiscoverWorkflowInfoResult;
136
349
  interface DurableObjectSpec {
137
350
  binding: string;
138
351
  className: string;
139
352
  }
140
353
  /**
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
- */
354
+ * A `defineContainer` declaration plus whether its generated DO class is
355
+ * exported by the worker entry. Only exported containers are safe to
356
+ * provision — wrangler rejects a `containers[].class_name` (and its Durable
357
+ * Object binding) that the worker doesn't export.
358
+ */
146
359
  interface InferredContainer extends ContainerIR {
147
360
  exported: boolean;
148
361
  }
149
362
  /**
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
- */
363
+ * A `defineWorkflow` declaration plus whether its generated
364
+ * `WorkflowEntrypoint` class is exported by the worker entry. Only exported
365
+ * workflows are safe to provision — wrangler rejects a `workflows[].class_name`
366
+ * the worker doesn't export. Workflows are NOT Durable Objects, so this never
367
+ * implies a `durable_objects` binding or migration.
368
+ */
156
369
  interface InferredWorkflow extends WorkflowIR {
157
370
  exported: boolean;
158
371
  }
372
+ /**
373
+ * A `defineAgent` declaration plus whether its generated agent
374
+ * `WorkflowEntrypoint` class (e.g. `SupportAgentWorkflow`) is exported by the
375
+ * worker entry. An agent compiles onto a Cloudflare Workflow, so — exactly like
376
+ * {@link InferredWorkflow} — only exported agents are safe to provision
377
+ * (wrangler rejects a `workflows[].class_name` the worker doesn't export), and
378
+ * an agent is NOT a Durable Object (no `durable_objects` binding or migration).
379
+ */
380
+ interface InferredAgent extends AgentIR {
381
+ exported: boolean;
382
+ }
383
+ /**
384
+ * A queue declared in `lunora/queues.ts`. Unlike workflows, a queue needs no
385
+ * worker-entry class export (its `queue()` handler rides `createWorker`), so
386
+ * there is no `exported` flag — every declared queue is reconcilable into the
387
+ * wrangler `queues.producers[]` / `queues.consumers[]`.
388
+ */
389
+ type InferredQueue = QueueIR;
159
390
  interface InferredBindings {
391
+ /** Agents declared in `lunora/agents.ts` (exported or not — see {@link InferredAgent.exported}); reconciled into `workflows[]`. */
392
+ agents: InferredAgent[];
160
393
  /** Containers declared in `lunora/containers.ts` (exported or not — see {@link InferredContainer.exported}). */
161
394
  containers: InferredContainer[];
162
395
  /** Durable Objects the worker entry exports → safe to bind. */
163
396
  durableObjects: DurableObjectSpec[];
397
+ /**
398
+ * The wrangler `flagship[].binding` name implied by `lunora/flags.ts` when it
399
+ * uses the Flagship provider in binding mode — `undefined` for HTTP-mode
400
+ * Flagship, a custom OpenFeature provider, or no flags. The binding needs an
401
+ * un-mintable `app_id`, so it is reconciled as a hint, not auto-written.
402
+ */
403
+ flagshipBinding?: string;
164
404
  /** Schema declares a `.global()` table → needs the `DB` D1 binding. */
165
405
  needsD1: boolean;
406
+ /** Queues declared in `lunora/queues.ts` → reconciled into `queues.producers[]` / `queues.consumers[]`. */
407
+ queues: InferredQueue[];
166
408
  /** Human-readable provenance for each inferred binding / hint, for logging. */
167
409
  signals: string[];
168
410
  /** `@lunora/ai` is imported or `env.AI` is used → needs the `ai` Workers AI binding. */
169
411
  usesAi: boolean;
170
- /** `@lunora/analytics` is imported → self-describing `analytics_engine_datasets` binding (auto-writeable). */
412
+ /** `@lunora/bindings/analytics` is imported → self-describing `analytics_engine_datasets` binding (auto-writeable). */
171
413
  usesAnalytics: boolean;
172
414
  /** `@lunora/auth` is imported (sessions may be D1- or `SessionDO`-backed). */
173
415
  usesAuth: boolean;
174
416
  /** `@lunora/browser` is imported → self-describing `browser` binding (auto-writeable). */
175
417
  usesBrowser: boolean;
418
+ /** `lunora/flags.ts` declares a feature-flag provider (any OpenFeature provider — Flagship or custom). */
419
+ usesFlags: boolean;
176
420
  /** `@lunora/hyperdrive` is imported (binding needs an un-mintable remote `id`; hint-only). */
177
421
  usesHyperdrive: boolean;
178
- /** `@lunora/images` is imported → self-describing `images` binding (auto-writeable). */
422
+ /** `@lunora/bindings/images` is imported → self-describing `images` binding (auto-writeable). */
179
423
  usesImages: boolean;
180
- /** `@lunora/kv` is imported (namespace binding name + id are user-defined; hint-only). */
424
+ /** `@lunora/bindings/kv` is imported (namespace binding name + id are user-defined; hint-only). */
181
425
  usesKv: boolean;
182
426
  /** `@lunora/mail` is imported (Resend API key must be set in `.dev.vars`; no binding). */
183
427
  usesMail: boolean;
184
428
  /** `@lunora/payment` is imported (provider secrets must be set in `.dev.vars`; no binding). */
185
429
  usesPayment: boolean;
186
- /** `@lunora/pipelines` is imported (binding needs an un-mintable remote pipeline name; hint-only). */
430
+ /** `ctx.pipelines` is used (binding needs an un-mintable remote pipeline name; hint-only). */
187
431
  usesPipelines: boolean;
188
432
  /** `@lunora/scheduler` is imported. */
189
433
  usesScheduler: boolean;
190
434
  /** `@lunora/storage` is imported (R2 bucket binding name is user-defined). */
191
435
  usesStorage: boolean;
436
+ /** `@lunora/x402/charge` is imported — the charge rail settles USDC to a recipient address (a public `[vars]` entry, user-named; hint-only). */
437
+ usesX402Charge: boolean;
438
+ /** `@lunora/x402/pay` is imported — the agent-wallet pay rail signs from a Secrets Store binding paired with a spend policy (ActionCtx-only; hint-only). */
439
+ usesX402Pay: boolean;
192
440
  /** Workflows declared in `lunora/workflows.ts` (exported or not — see {@link InferredWorkflow.exported}). */
193
441
  workflows: InferredWorkflow[];
194
442
  }
@@ -200,33 +448,33 @@ interface InferOptions {
200
448
  schemaDir?: string;
201
449
  }
202
450
  /**
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
- */
451
+ * Scan a Lunora project and report which Cloudflare bindings its code implies.
452
+ * Read-only: performs no writes. Binding provisioning is driven by the worker
453
+ * entry's Durable Object exports plus the schema's D1 need; capability imports
454
+ * surface as hints.
455
+ */
208
456
  declare const inferLunoraBindings: (options: InferOptions) => Promise<InferredBindings>;
209
457
  /**
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
- */
458
+ * Derive the list of `@lunora/*` package names that are actively used by a
459
+ * project, based on its already-resolved {@link InferredBindings}.
460
+ *
461
+ * This is the canonical bridge between binding inference and the package-aware
462
+ * `.dev.vars.example` scaffolding in `scaffold-dev-variables.ts`. The result is
463
+ * a stable, predictable slice of {@link CAPABILITY_SOURCES} source values,
464
+ * filtered to the flags that are `true` in `bindings` — in CAPABILITY_SOURCES
465
+ * declaration order.
466
+ */
219
467
  declare const packageNamesFromBindings: (bindings: InferredBindings) => string[];
220
468
  /** Directory holding per-checkout Lunora state (gitignored by convention). */
221
469
  declare const LINKED_PROJECT_DIR = ".lunora";
222
470
  /** The canonical link filename, relative to the project root. */
223
471
  declare const LINKED_PROJECT_FILE: string;
224
472
  /**
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
- */
473
+ * The link record persisted to `.lunora/project.json`. Every field is optional
474
+ * so a partially-populated link (e.g. a worker name with no URL yet) still
475
+ * round-trips. `linkedAt` is an ISO-8601 stamp written at link time, purely
476
+ * informational.
477
+ */
230
478
  interface LinkedProject {
231
479
  /** Cloudflare account id the worker lives under, when known. */
232
480
  account?: string;
@@ -240,34 +488,17 @@ interface LinkedProject {
240
488
  workerUrl?: string;
241
489
  }
242
490
  /**
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
- */
491
+ * Read the link record from `.lunora/project.json`, or `undefined` when there
492
+ * is no usable link. Best-effort: a missing file, parse error, or unexpected
493
+ * shape all collapse to `undefined`.
494
+ */
247
495
  declare const readLinkedProject: (projectRoot: string) => LinkedProject | undefined;
248
496
  /**
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
- */
497
+ * Write the link record to `.lunora/project.json`, creating the `.lunora/`
498
+ * directory when absent. Only defined fields are persisted (so an empty value
499
+ * never clobbers a known one). Returns the absolute path written.
500
+ */
253
501
  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
502
  /** Severity a formatted line should be surfaced at, mapped onto the three logger channels. */
272
503
  type LunoraLineLevel = "error" | "info" | "warn";
273
504
  /** A formatted lunora event: the channel to surface it on, the display text, and which event produced it. */
@@ -282,39 +513,45 @@ interface LunoraFormattedLine {
282
513
  /** Stable `source` tag every lunora console event carries. Mirrors `REQUEST_LOG_EVENT_SOURCE` in `@lunora/do`. */
283
514
  declare const LUNORA_EVENT_SOURCE = "lunora";
284
515
  /**
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
- */
516
+ * Parse a single worker-output line and, when it is a lunora structured event,
517
+ * return its severity plus display text. Returns `undefined` for anything else —
518
+ * non-JSON lines, JSON that isn't a lunora event, or an unrecognised event type
519
+ * — so the caller passes the original line through untouched. Pure and total.
520
+ */
290
521
  declare const formatLunoraEvent: (line: string) => LunoraFormattedLine | undefined;
522
+ declare class LunoraReporter {
523
+ #private;
524
+ setStdout(stdout: NodeJS.WriteStream): void;
525
+ setStderr(stderr: NodeJS.WriteStream): void;
526
+ log(meta: unknown): void;
527
+ }
291
528
  /**
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
- */
529
+ * Per-package secret-requirements registry for `.dev.vars` scaffolding.
530
+ *
531
+ * Each entry maps a `@lunora/*` package name → the secrets it requires at
532
+ * runtime, expressed as `{ key, description, docsUrl }` records. The scaffolder
533
+ * in {@link ./scaffold-dev-variables} reads this registry to emit package-aware
534
+ * `.dev.vars.example` entries (placeholders + inline doc-pointer comments).
535
+ *
536
+ * ## Adding a new add-on
537
+ *
538
+ * When a new `@lunora/*` package requires runtime secrets, add one entry to
539
+ * {@link PACKAGE_SECRETS_REGISTRY} keyed by its exact npm package name. Each
540
+ * `SecretEntry` in the array needs:
541
+ *
542
+ * - `key` — the env-var name the package reads from `env` (e.g. `RESEND_API_KEY`).
543
+ * - `description` — one line describing the secret and how to obtain it.
544
+ * - `docsUrl` — a stable URL to the package/provider docs.
545
+ *
546
+ * The registry lives in `@lunora/config` so that add-ons themselves never need
547
+ * to depend on it (no circular coupling). Add-ons document their secrets in
548
+ * their own READMEs; the registry duplicates that knowledge in a machine-readable
549
+ * form that the scaffolder can consume.
550
+ *
551
+ * **Never write a real secret value** in this file — `placeholderValue` entries
552
+ * are the only allowed values (they must pass `isPlaceholderValue` from
553
+ * scaffold-dev-variables).
554
+ */
318
555
  /** A single secret variable required by a package. */
319
556
  interface SecretEntry {
320
557
  /** One sentence describing what this secret is and how to obtain it. */
@@ -324,87 +561,87 @@ interface SecretEntry {
324
561
  /** The env-var key as it appears in `.dev.vars`, e.g. `AUTH_SECRET`. */
325
562
  key: string;
326
563
  /**
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
- */
564
+ * The placeholder value written into `.dev.vars.example`.
565
+ * Must pass `isPlaceholderValue` from scaffold-dev-variables so the
566
+ * scaffolder regenerates it when generating `.dev.vars`. Use angle-bracket
567
+ * conventions or a recognised marker like `replace-with-openssl-rand-hex-32`.
568
+ * Non-secret env-vars (e.g. `AUTH_URL`) may carry a real default value.
569
+ */
333
570
  placeholderValue: string;
334
571
  }
335
572
  /**
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
- */
573
+ * The canonical registry of per-package secret requirements.
574
+ *
575
+ * Keys are exact npm package names (e.g. `"@lunora/auth"`). Values are
576
+ * non-empty arrays of {@link SecretEntry} — one entry per required secret key.
577
+ *
578
+ * The scaffolder in `scaffold-dev-variables.ts` calls
579
+ * {@link secretsForPackages} to resolve the applicable entries from this map
580
+ * given the set of detected capability package names.
581
+ */
345
582
  declare const PACKAGE_SECRETS_REGISTRY: Readonly<Record<string, ReadonlyArray<SecretEntry>>>;
346
583
  /**
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
- */
584
+ * Collect all secret entries required by the given set of package names. The
585
+ * order follows the order of `packageNames` (stable, predictable output), and
586
+ * within each package the entries are returned in registry declaration order.
587
+ *
588
+ * Only packages present in {@link PACKAGE_SECRETS_REGISTRY} contribute entries;
589
+ * unknown package names are silently ignored — this makes the call site resilient
590
+ * to future capability flags whose packages have no secrets.
591
+ */
355
592
  declare const secretsForPackages: (packageNames: ReadonlyArray<string>) => SecretEntry[];
356
593
  /** The canonical project-config filename probed at the project root. */
357
594
  declare const LUNORA_CONFIG_FILE = "lunora.json";
358
595
  /**
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
- */
596
+ * The parsed `remote` preference from `lunora.json`:
597
+ *
598
+ * - `true` / `false` — the boolean form: enable or explicitly disable remote dev.
599
+ * - `undefined` — no usable preference (file absent, key absent, or malformed).
600
+ *
601
+ * The object form (scoping which binding kinds go remote) is reserved for a
602
+ * future increment; for now an object value is treated as "enabled" (truthy
603
+ * presence) so forward-written configs still turn remote on.
604
+ */
368
605
  type RemotePreference = boolean | undefined;
369
606
  /** The structural slice of `lunora.json` Lunora reads. */
370
607
  interface LunoraProjectConfig {
371
608
  remote?: unknown;
372
609
  }
373
610
  /**
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
- */
611
+ * Interpret a raw `remote` value into a tri-state preference. A boolean passes
612
+ * through; an object is treated as enabled (the documented-but-not-yet-honored
613
+ * scoping form is still an opt-in); anything else (string, number, null) is no
614
+ * preference.
615
+ */
379
616
  declare const interpretRemote: (value: unknown) => RemotePreference;
380
617
  /**
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
- */
618
+ * Read the project's `remote` preference from `lunora.json`, or `undefined` when
619
+ * there's no usable preference. Best-effort: never throws — a missing file,
620
+ * parse error, or unexpected shape all collapse to `undefined` so the caller
621
+ * falls through to the env/flag layers.
622
+ */
386
623
  declare const readProjectRemotePreference: (projectRoot: string) => RemotePreference;
387
624
  /**
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
- */
625
+ * Whether we can interactively prompt — stdin must be a TTY. In CI / piped
626
+ * contexts this is false, and callers should fall back to a non-interactive
627
+ * default (skip, or require an explicit `--yes`) rather than hang on a read.
628
+ */
392
629
  declare const isInteractive: () => boolean;
393
630
  /**
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
- */
631
+ * Ask a yes/no question on stdin. With `defaultYes`, an empty answer (just
632
+ * Enter) counts as yes and the prompt should read `[Y/n]`; otherwise empty is
633
+ * no (`[y/N]`). Shared by the CLI (`reset`, `dev`) and the Vite dev server.
634
+ */
398
635
  declare const promptYesNo: (prompt: string, options?: {
399
636
  defaultYes?: boolean;
400
637
  }) => Promise<boolean>;
401
638
  /**
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
- */
639
+ * Build a default-yes `confirm(message)` for the scaffolders' `ensureDevVariables`:
640
+ * an interactive `[Y/n]` prompt (optionally prefixed, e.g. `"[lunora] "`) when
641
+ * stdin is a TTY, or an immediate `false` otherwise — so CI declines silently
642
+ * instead of blocking. Keeps the "non-interactive ⇒ decline" policy in one place
643
+ * rather than re-stated at every call site.
644
+ */
408
645
  declare const createConfirm: (prefix?: string) => ((message: string) => Promise<boolean>);
409
646
  /** One choice in a {@link promptSelect} list. `value` is returned; `label` (and optional `description`) are shown. */
410
647
  interface SelectOption<T extends string> {
@@ -413,49 +650,59 @@ interface SelectOption<T extends string> {
413
650
  value: T;
414
651
  }
415
652
  /**
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
- */
653
+ * Ask the user to pick one option from a numbered list on stdin. Accepts the
654
+ * 1-based number or the option's `value`/`label` typed verbatim; an empty answer
655
+ * (just Enter) takes `settings.default`. In a non-interactive context (CI /
656
+ * piped — no TTY) it never reads and returns `settings.default` (or `undefined`),
657
+ * mirroring {@link createConfirm}'s "non-interactive ⇒ fall back" policy so
658
+ * automation never blocks.
659
+ */
423
660
  declare const promptSelect: <T extends string>(message: string, options: ReadonlyArray<SelectOption<T>>, settings?: {
424
661
  default?: T;
425
662
  }) => Promise<T | undefined>;
663
+ /**
664
+ * Ask a free-text question on stdin, returning the trimmed answer. An empty
665
+ * answer (just Enter) takes `settings.default`. In a non-interactive context
666
+ * (CI / piped — no TTY) it never reads and returns `settings.default` (or
667
+ * `undefined`), mirroring {@link promptSelect}'s "non-interactive ⇒ fall back"
668
+ * policy so automation never blocks.
669
+ */
670
+ declare const promptText: (message: string, settings?: {
671
+ default?: string;
672
+ }) => Promise<string | undefined>;
426
673
  /** One choice in a {@link promptMultiSelect} list. Identical shape to {@link SelectOption}; `value` is returned when picked. */
427
674
  type MultiSelectOption<T extends string> = SelectOption<T>;
428
675
  /**
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
- */
676
+ * Ask the user to pick zero or more options from a numbered list on stdin.
677
+ * Accepts a comma- or space-separated list of 1-based numbers and/or option
678
+ * `value`/`label`s typed verbatim; an empty answer (just Enter) takes
679
+ * `settings.defaults`. In a non-interactive context (CI / piped — no TTY) it
680
+ * never reads and returns `settings.defaults ?? []`, mirroring {@link promptSelect}'s
681
+ * "non-interactive ⇒ fall back" policy so automation never blocks. Unknown
682
+ * tokens are ignored; the returned list is de-duplicated and preserves option
683
+ * order.
684
+ */
438
685
  declare const promptMultiSelect: <T extends string>(message: string, options: ReadonlyArray<MultiSelectOption<T>>, settings?: {
439
686
  defaults?: ReadonlyArray<T>;
440
687
  }) => Promise<T[]>;
441
688
  /**
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
- */
689
+ * A container/workflow that is declared (so codegen emits its class) but the
690
+ * worker entry never re-exports — the one wiring step the generators can't always
691
+ * do for the developer. wrangler rejects a `class_name` the deployed worker
692
+ * doesn't export, so a deploy fails late on this; surfacing it as structured data
693
+ * lets the Vite plugin raise it in the dev error overlay (not just the console)
694
+ * the moment the gap appears. The human-readable form is also folded into
695
+ * {@link ReconcileBindingsResult.warnings}.
696
+ */
450
697
  interface ExportGap {
451
698
  /** Generated class wrangler needs exported, e.g. `OrderPipelineWorkflow`. */
452
699
  className: string;
453
- /** The `lunora/{containers,workflows}.ts` export name, e.g. `orderPipeline`. */
700
+ /** The `lunora/{agents,containers,workflows}.ts` export name, e.g. `orderPipeline`. */
454
701
  exportName: string;
455
702
  /** Which declaration is unexported. */
456
- kind: "container" | "workflow";
703
+ kind: "agent" | "container" | "workflow";
457
704
  /** The `_generated/{module}` to re-export from, e.g. `workflows`. */
458
- module: "containers" | "workflows";
705
+ module: "agents" | "containers" | "workflows";
459
706
  }
460
707
  interface ReconcileBindingsResult {
461
708
  /** Short labels for each binding written (e.g. `"SCHEDULER/SchedulerDO"`). */
@@ -463,10 +710,10 @@ interface ReconcileBindingsResult {
463
710
  /** `true` when `wrangler.jsonc` was rewritten. */
464
711
  changed: boolean;
465
712
  /**
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
- */
713
+ * Declared containers/workflows the worker entry doesn't re-export — the
714
+ * structured form of the corresponding `warnings` entries, for the dev error
715
+ * overlay. Empty when every declaration is wired.
716
+ */
470
717
  exportGaps: ExportGap[];
471
718
  /** Reason reconciliation was skipped, for logging. */
472
719
  reason?: string;
@@ -476,30 +723,68 @@ interface ReconcileBindingsResult {
476
723
  wranglerPath?: string;
477
724
  }
478
725
  /**
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
- */
726
+ * Reconcile inferred Durable Object / D1 bindings into `wrangler.jsonc`.
727
+ *
728
+ * Writes only when something is missing; returns `changed: false` when the
729
+ * config already satisfies the inferred needs.
730
+ */
484
731
  declare const reconcileWranglerBindings: (projectRoot: string, inferred: InferredBindings) => ReconcileBindingsResult;
732
+ interface ReconcileCompatibilityDateResult {
733
+ /** `true` when `wrangler.jsonc` was rewritten. */
734
+ changed: boolean;
735
+ /** The new date value, or the existing one when unchanged. */
736
+ date: string | undefined;
737
+ /** Human-readable reason when reconciliation was skipped. */
738
+ reason?: string;
739
+ /** Resolved wrangler path, or `undefined` when none was found. */
740
+ wranglerPath?: string;
741
+ }
485
742
  /**
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
- */
743
+ * Reconcile the `compatibility_date` in `wrangler.jsonc` when Workers Cache
744
+ * is enabled but the date is below the minimum required.
745
+ */
746
+ declare const reconcileWranglerCompatibilityDate: (projectRoot: string) => ReconcileCompatibilityDateResult;
747
+ interface ReconcileResult {
748
+ /** `true` when `wrangler.jsonc` was rewritten. */
749
+ changed: boolean;
750
+ /** Human-readable reason when reconciliation was skipped (for logging). */
751
+ reason?: string;
752
+ /** Resolved wrangler path, or `undefined` when none was found. */
753
+ wranglerPath?: string;
754
+ }
755
+ /**
756
+ * Reconcile the codegen-derived cron schedules into the project's
757
+ * `wrangler.jsonc` `triggers.crons` array, preserving comments and formatting
758
+ * via `jsonc-parser`'s structural edits.
759
+ *
760
+ * When `triggers.crons` already matches `cronTriggers`, nothing is written (so
761
+ * we don't churn the file or trip the dev server's file watcher). When the
762
+ * project declares no crons, a stale non-empty array is cleared so removed
763
+ * crons stop firing.
764
+ *
765
+ * This intentionally writes the SAME `triggers.crons` shape the
766
+ * `@lunora/config` validator accepts, so the wrangler validator never fights
767
+ * the generated value.
768
+ */
769
+ declare const reconcileWranglerCrons: (projectRoot: string, cronTriggers: ReadonlyArray<string>) => ReconcileResult;
770
+ /**
771
+ * The wrangler config sections Lunora can safely flip to remote mode in dev,
772
+ * each with the human label used in logs and the structural `shape` the entry
773
+ * lives in.
774
+ *
775
+ * `"array"` is a top-level array of binding objects (`d1_databases`,
776
+ * `kv_namespaces`, `r2_buckets`, `vectorize`, `services`). `"producers"` is
777
+ * `queues.producers[]` — consumers are NOT remoted (their schema has no `remote`
778
+ * field) and the edit path is two levels deep. `"object"` is a single binding
779
+ * object, not an array (`ai`), whose edit path targets the section key directly.
780
+ *
781
+ * Every kind here was confirmed against `wrangler/config-schema.json`: the
782
+ * entry's schema declares a `remote` property. Deliberately omits
783
+ * `durable_objects` (no CF remote-DO mode; shards stay local) and sections whose
784
+ * schema has no `remote` field (`hyperdrive`, `analytics_engine_datasets`,
785
+ * `secrets_store_secrets`, queue consumers, …). Widening further is a one-line
786
+ * table edit.
787
+ */
503
788
  declare const REMOTE_ELIGIBLE_KEYS: {
504
789
  readonly ai: {
505
790
  readonly label: "AI";
@@ -543,11 +828,11 @@ interface RemoteBindingPlan {
543
828
  /** Short kind label for logging (`"D1"`, `"KV"`, `"R2"`, `"Vectorize"`, …). */
544
829
  kind: string;
545
830
  /**
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
- */
831
+ * The jsonc edit path within {@link RemoteBindingPlan.section}, relative to
832
+ * the section key: `[index]` for an `"array"` section, `["producers", index]`
833
+ * for a queue producer, or `[]` for the single-object `ai` section. The
834
+ * materializer prepends the section key and appends `"remote"`.
835
+ */
551
836
  path: ReadonlyArray<number | string>;
552
837
  /** The wrangler config key the entry lives under. */
553
838
  section: RemoteEligibleKey;
@@ -565,19 +850,19 @@ interface RemoteWranglerShape {
565
850
  vectorize?: ReadonlyArray<BindingEntry | null | undefined>;
566
851
  }
567
852
  /**
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
- */
853
+ * Inspect a parsed wrangler config and list every eligible binding that should
854
+ * be flipped to remote mode. Pure — no file-system access, no mutation. An
855
+ * entry already carrying `"remote": true` is still reported (so logging is
856
+ * complete) but the materializer's edit is a harmless no-op for it.
857
+ */
573
858
  declare const planRemoteBindings: (parsed: RemoteWranglerShape) => RemoteBindingPlan[];
574
859
  /**
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
- */
860
+ * Inject `"remote": true` onto each planned binding in the config `text`,
861
+ * comment-preservingly via jsonc edits. Pure string→string; the edits target
862
+ * disjoint entries so applying them sequentially is safe. The edit path is
863
+ * `[section, ...plan.path, "remote"]`, which resolves to the array element, the
864
+ * `queues.producers[i]` entry, or the single `ai` object as the plan demands.
865
+ */
581
866
  declare const injectRemoteFlags: (text: string, plans: ReadonlyArray<RemoteBindingPlan>) => string;
582
867
  interface MaterializeOptions {
583
868
  /** When `false`, the call is a no-op (returns `enabled: false`). */
@@ -586,18 +871,18 @@ interface MaterializeOptions {
586
871
  }
587
872
  interface MaterializeResult {
588
873
  /**
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
- */
874
+ * Removes the generated temp config file. Always present and always safe to
875
+ * call: it is idempotent, a no-op when nothing was written (disabled /
876
+ * fall-through cases), and never throws if the path is already gone. The dev
877
+ * command calls this on every exit path (normal, signal, error).
878
+ */
594
879
  cleanup: () => void;
595
880
  /**
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
- */
881
+ * Absolute path to the generated temp config to pass to
882
+ * `wrangler dev --config`. `undefined` when remote mode is disabled, no
883
+ * wrangler config was found, it failed to parse, or it declared no eligible
884
+ * binding (nothing to remote — run plain local dev).
885
+ */
601
886
  configPath?: string;
602
887
  /** Whether remote mode was requested at all. */
603
888
  enabled: boolean;
@@ -607,33 +892,33 @@ interface MaterializeResult {
607
892
  remoteBindings: RemoteBindingPlan[];
608
893
  }
609
894
  /**
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
- */
895
+ * Produce a temporary wrangler config with `"remote": true` on every eligible
896
+ * binding, so `lunora dev` can run `wrangler dev --config &lt;temp>` against the
897
+ * deployed D1/KV/R2 without touching the user's file.
898
+ *
899
+ * The temp file is written as a sibling of the source `wrangler.jsonc` (in the
900
+ * project root), NOT an OS temp dir: wrangler resolves a config's relative paths
901
+ * (`main`, `assets`, `migrations_dir`, …) against the **config file's own
902
+ * directory**, so a temp config in `/tmp` would make wrangler look for
903
+ * `/tmp/src/server.ts` and fail to start the worker. Keeping it beside the real
904
+ * config preserves those relative paths. Returns `configPath: undefined` (with a
905
+ * `reason`) for every fall-through case so the caller degrades to plain local
906
+ * dev instead of failing.
907
+ */
623
908
  declare const materializeRemoteWranglerConfig: (options: MaterializeOptions) => MaterializeResult;
624
909
  /**
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
- */
910
+ * Parse a `LUNORA_REMOTE` env value into the on/off decision. Truthy when set to
911
+ * `"1"` or `"true"` (case-insensitive); anything else — unset, `"0"`, `"false"`,
912
+ * empty — is off. Mirrors the `"1" | "true"` convention used across the runtime.
913
+ */
629
914
  declare const isRemoteEnvEnabled: (value: string | undefined) => boolean;
630
915
  /** The three inputs that can switch remote-binding dev on, in precedence order. */
631
916
  interface RemoteEnableInputs {
632
917
  /**
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
- */
918
+ * The `remote` preference from `lunora.json` (the lowest-priority signal).
919
+ * `undefined` means "no project preference"; an explicit `false` here loses
920
+ * to neither the flag nor the env when those are absent — it just stays off.
921
+ */
637
922
  configPreference?: boolean;
638
923
  /** The raw `LUNORA_REMOTE` env value (parsed with {@link isRemoteEnvEnabled}). */
639
924
  envValue?: string;
@@ -641,33 +926,44 @@ interface RemoteEnableInputs {
641
926
  flag?: boolean;
642
927
  }
643
928
  /**
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
- */
929
+ * Resolve whether remote-binding dev is on, with a clear precedence:
930
+ *
931
+ * 1. an explicit `--remote` flag (highest — a deliberate per-invocation choice),
932
+ * 2. then `LUNORA_REMOTE` in the environment,
933
+ * 3. then the `remote` key in `lunora.json` (lowest — a project default).
934
+ *
935
+ * The flag and env are one-directional (they can only turn remote *on*); only
936
+ * the config preference carries a meaningful `false`, and it applies solely when
937
+ * neither stronger signal is present. So a project that sets `"remote": false`
938
+ * is still overridable per-run by `--remote` or `LUNORA_REMOTE=1`.
939
+ */
655
940
  declare const resolveRemoteEnabled: (inputs: RemoteEnableInputs) => boolean;
941
+ /** Core (always-scaffolded) secrets followed by the package-specific ones for the detected capabilities. */
942
+ declare const requiredSecrets: (packageNames: ReadonlyArray<string>) => SecretEntry[];
656
943
  /**
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
- */
944
+ * Whether an (already-unquoted) value looks like a fill-me-in placeholder —
945
+ * empty, angle-bracketed, or containing a known marker — rather than a real
946
+ * value. Used both when scaffolding (which values to regenerate) and by
947
+ * `lunora env doctor` (which set values are still unfilled).
948
+ */
662
949
  declare const isPlaceholderValue: (value: string) => boolean;
663
950
  /**
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
- */
951
+ * True for a secret-looking key whose value Lunora can mint locally (a random
952
+ * 32-byte hex, like `openssl rand -hex 32`) — e.g. `AUTH_SECRET`,
953
+ * `LUNORA_ADMIN_TOKEN`, `STORAGE_SIGNING_SECRET`. False for provider-issued keys
954
+ * ({@link PROVIDER_SECRET_KEYS}) and any non-secret key.
955
+ */
956
+ declare const isMintableSecretKey: (key: string) => boolean;
957
+ /** Mint a fresh strong secret value — 64 hex chars (32 bytes), like `openssl rand -hex 32`. */
958
+ declare const generateSecretValue: (randomHex?: (bytes: number) => string) => string;
959
+ /**
960
+ * The outcome of planning a scaffold — a discriminated union so the orchestrator
961
+ * never has to re-derive whether `content` is present.
962
+ *
963
+ * `exists`: `.dev.vars` is already there; nothing to do.
964
+ * `no-example`: nothing to scaffold from (stay silent — the project may not use secrets).
965
+ * `generate`: write `content`, a copy of the example with secret-looking placeholders replaced by fresh random hex (`generatedKeys` lists which).
966
+ */
671
967
  type ScaffoldPlan = {
672
968
  content: string;
673
969
  generatedKeys: string[];
@@ -680,7 +976,8 @@ type ScaffoldPlan = {
680
976
  /** Decide whether (and what) to scaffold. Pure — given the current state of the two files. */
681
977
  declare const planDevVariablesScaffold: (input: {
682
978
  devVarsExists: boolean;
683
- exampleContent: string | undefined; /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
979
+ exampleContent: string | undefined;
980
+ /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
684
981
  randomHex?: (bytes: number) => string;
685
982
  }) => ScaffoldPlan;
686
983
  interface AugmentPlan {
@@ -692,22 +989,23 @@ interface AugmentPlan {
692
989
  missingKeys: string[];
693
990
  }
694
991
  /**
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
- */
992
+ * Plan how to top up an existing `.dev.vars` from the example: every example key
993
+ * not already present becomes an appended line (secret placeholders filled with
994
+ * fresh random hex, other values copied). Pure — no I/O. Empty `missingKeys`
995
+ * means the file is already complete.
996
+ */
700
997
  declare const planDevVariablesAugment: (input: {
701
998
  exampleContent: string;
702
- existingContent: string; /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
999
+ existingContent: string;
1000
+ /** Injectable for deterministic tests; defaults to `crypto.randomBytes`. */
703
1001
  randomHex?: (bytes: number) => string;
704
1002
  }) => AugmentPlan;
705
1003
  interface EnsureDevVariablesDeps {
706
1004
  /**
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
- */
1005
+ * Ask the user to confirm generating the file. Return `true` to generate.
1006
+ * Consumers pass a TTY-aware prompt; in non-interactive contexts they should
1007
+ * resolve `false` (we then report `"declined"` and the caller can hint).
1008
+ */
711
1009
  confirm: (message: string) => Promise<boolean>;
712
1010
  cwd: string;
713
1011
  /** Emit a human-facing line (success / hint). */
@@ -726,44 +1024,106 @@ interface EnsureDevVariablesResult {
726
1024
  status: EnsureDevVariablesStatus;
727
1025
  }
728
1026
  /**
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
- */
1027
+ * Reconcile the project's `.dev.vars` with its `.dev.vars.example`:
1028
+ *
1029
+ * - file missing → offer to generate it (secret placeholders auto-filled);
1030
+ * - file present but missing keys the example lists → offer to append them;
1031
+ * - file present and complete → nothing to do.
1032
+ *
1033
+ * Prompts via `confirm` (skipped when `yes`); never overwrites existing values.
1034
+ * Returns what happened so the caller can tailor any follow-up. Shared by
1035
+ * `lunora dev` and the `@lunora/vite` dev server. All side effects funnel
1036
+ * through `confirm`/`info`/`randomHex`.
1037
+ */
740
1038
  declare const ensureDevVariables: (deps: EnsureDevVariablesDeps) => Promise<EnsureDevVariablesResult>;
741
1039
  /**
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
- */
1040
+ * Build the text that should be merged into `.dev.vars.example` for the given
1041
+ * set of package names. Only entries whose key is not already present in
1042
+ * `existingKeys` are included (additive / idempotent). Returns an empty string
1043
+ * when there is nothing to add.
1044
+ *
1045
+ * The output is grouped by package with a blank-line separator so the file
1046
+ * reads cleanly when multiple packages each contribute several keys.
1047
+ *
1048
+ * **Safety invariant:** this function never writes a real secret — every value
1049
+ * in the output is the entry's `placeholderValue`.
1050
+ */
753
1051
  declare const buildPackageSecretsBlock: (packageNames: ReadonlyArray<string>, existingKeys: ReadonlySet<string>) => string;
754
1052
  /**
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
- */
1053
+ * Write (or update) `.dev.vars.example` so that it contains the secrets
1054
+ * required by `packageNames`. Existing lines are never removed or rewritten;
1055
+ * new entries are appended (with a blank-line separator after existing content).
1056
+ *
1057
+ * Idempotent: re-running with the same `packageNames` does not duplicate keys
1058
+ * already in the file. Returns the list of keys that were actually appended.
1059
+ *
1060
+ * **Safety invariant:** only placeholder values are written — no real secrets.
1061
+ */
764
1062
  declare const ensureDevVariablesExample: (cwd: string, packageNames: ReadonlyArray<string>) => string[];
1063
+ interface DevSecretsFillPlan {
1064
+ /** {@link CORE_SECRETS} keys appended because they were absent (each generated). */
1065
+ addedKeys: string[];
1066
+ /** The full new file content to write. */
1067
+ content: string;
1068
+ /** Existing empty/placeholder secret-keyed entries filled with fresh values. */
1069
+ filledKeys: string[];
1070
+ }
1071
+ /**
1072
+ * Plan the in-place generation of dev secrets for a `.dev.vars`. First, every
1073
+ * line whose KEY looks like a secret (`*_SECRET`, `*_TOKEN`, `*_KEY`,
1074
+ * `*_PASSWORD`) and whose value is empty or a placeholder gets a freshly
1075
+ * generated value — so a `lunora add`-scaffolded `.dev.vars` (which writes each
1076
+ * secret blank) becomes usable on `lunora dev` / `vite dev` without the user
1077
+ * running `openssl` by hand. Second, any {@link CORE_SECRETS} key absent from
1078
+ * the file is appended (generated) — notably `LUNORA_ADMIN_TOKEN`, which the
1079
+ * local Studio needs to call the worker's admin gate in dev (without it the
1080
+ * Studio shows its login gate).
1081
+ *
1082
+ * Pure (given `randomHex`): real (non-placeholder) values are never touched, and
1083
+ * comments + non-secret entries are preserved verbatim.
1084
+ */
1085
+ declare const planDevSecretsFill: (input: {
1086
+ existingContent: string;
1087
+ randomHex?: (bytes: number) => string;
1088
+ }) => DevSecretsFillPlan;
1089
+ interface FillDevSecretsResult {
1090
+ /** Core secret keys appended (generated) because they were missing. */
1091
+ addedKeys: string[];
1092
+ /** Existing empty/placeholder secrets filled with generated values. */
1093
+ filledKeys: string[];
1094
+ /** `created` = no `.dev.vars` existed; `filled` = topped up an existing one; `unchanged` = nothing to do. */
1095
+ status: "created" | "filled" | "unchanged";
1096
+ }
1097
+ /**
1098
+ * Generate any missing/empty dev secrets in the project's `.dev.vars`, in place.
1099
+ *
1100
+ * Complements {@link ensureDevVariables} (which scaffolds `.dev.vars` from
1101
+ * `.dev.vars.example`). A `lunora add`-scaffolded project writes secrets blank
1102
+ * straight into `.dev.vars` (no example) and never includes `LUNORA_ADMIN_TOKEN`
1103
+ * — so the worker boots with empty secrets and the Studio shows its login gate.
1104
+ * This fills those gaps at dev startup, so both `lunora dev` and the
1105
+ * `@lunora/vite` dev server give a working project with zero manual `openssl`.
1106
+ *
1107
+ * Never overwrites a real (non-placeholder) value. The write is atomic + owner-
1108
+ * only (temp + rename, `mode: 0o600`), matching the other `.dev.vars` writers.
1109
+ */
1110
+ declare const fillDevSecrets: (deps: {
1111
+ cwd: string;
1112
+ info?: (message: string) => void;
1113
+ randomHex?: (bytes: number) => string;
1114
+ }) => FillDevSecretsResult;
1115
+ /** Storage backends `.global()` accepts. A closed union, so it is never free text. */
1116
+ type GlobalBackend = "d1" | "hyperdrive";
765
1117
  /** Add a new table to `defineSchema({ ... })`. */
766
1118
  interface AddTableEdit {
1119
+ /**
1120
+ * Mark the table `.global()`. Pass `{}` for the D1 default, or a backend for
1121
+ * an external store — `lunora introspect` uses `{ backend: "hyperdrive" }`
1122
+ * because the rows live in the database it read.
1123
+ */
1124
+ readonly global?: {
1125
+ readonly backend?: GlobalBackend;
1126
+ };
767
1127
  readonly kind: "addTable";
768
1128
  readonly table: string;
769
1129
  }
@@ -773,11 +1133,11 @@ interface AddOptionalColumnEdit {
773
1133
  readonly kind: "addOptionalColumn";
774
1134
  readonly table: string;
775
1135
  /**
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
- */
1136
+ * Inner validator expression text WITHOUT the `v.optional(...)` wrapper, e.g.
1137
+ * `v.string()`. Always wrapped in `v.optional(...)` on apply, because only
1138
+ * optional columns are additive-safe (a required column needs a backfill
1139
+ * migration).
1140
+ */
781
1141
  readonly validator: string;
782
1142
  }
783
1143
  /** Add a secondary index to an existing table. */
@@ -791,9 +1151,9 @@ interface AddIndexEdit {
791
1151
  /** Additive edits — the only requests {@link applyAdditiveEdit} applies. */
792
1152
  type AdditiveEdit = AddIndexEdit | AddOptionalColumnEdit | AddTableEdit;
793
1153
  /**
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
- */
1154
+ * Destructive edits — never applied directly; routed to the migration handoff
1155
+ * (plan 024 Item 5). Carried as data so the editor can describe the request.
1156
+ */
797
1157
  interface DestructiveEdit {
798
1158
  readonly column?: string;
799
1159
  readonly kind: "changeColumnType" | "dropColumn" | "dropTable" | "makeRequired" | "renameColumn";
@@ -804,9 +1164,9 @@ interface DestructiveEdit {
804
1164
  /** Any edit the editor can request. */
805
1165
  type SchemaEdit = AdditiveEdit | DestructiveEdit;
806
1166
  /**
807
- * Classify an edit request. Additive edits ({@link AdditiveEdit}) apply
808
- * directly; everything else changes stored data and is destructive.
809
- */
1167
+ * Classify an edit request. Additive edits ({@link AdditiveEdit}) apply
1168
+ * directly; everything else changes stored data and is destructive.
1169
+ */
810
1170
  declare const classifyEdit: (edit: SchemaEdit) => "additive" | "destructive";
811
1171
  /** Failure reasons an additive edit can report. */
812
1172
  type ApplyFailureReason = "aliased-define-schema" | "destructive" | "duplicate-column" | "duplicate-index" | "duplicate-table" | "invalid-identifier" | "invalid-validator" | "no-define-schema" | "non-object-argument" | "unknown-table";
@@ -819,10 +1179,10 @@ type ApplyEditResult = {
819
1179
  text: string;
820
1180
  };
821
1181
  /**
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
- */
1182
+ * Apply an **additive** edit to a schema source string, preserving formatting.
1183
+ * Destructive edits are refused with `{ ok: false, reason: "destructive" }`;
1184
+ * route them through the migration handoff (plan 024 Item 5).
1185
+ */
826
1186
  declare const applyAdditiveEdit: (source: string, edit: SchemaEdit) => ApplyEditResult;
827
1187
  /** A single declared column: its name and the raw validator expression text. */
828
1188
  interface SchemaColumn {
@@ -861,22 +1221,35 @@ type ParseSchemaResult = {
861
1221
  tables: ReadonlyArray<SchemaTable>;
862
1222
  };
863
1223
  /**
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
- */
1224
+ * Parse the tables (with typed columns + indexes) out of a `lunora/schema.ts`
1225
+ * source string. Returns a tagged result so callers can render a helpful
1226
+ * message per failure mode without throwing.
1227
+ */
874
1228
  declare const parseSchema: (source: string) => ParseSchemaResult;
1229
+ /** One filterable Vectorize metadata property a schema declares. */
1230
+ interface VectorMetadataDeclaration {
1231
+ /** The vector index the property belongs to. */
1232
+ index: string;
1233
+ /**
1234
+ * The validator kind behind the column, or `undefined` when the column
1235
+ * isn't in the owning table's shape. Callers map it to the Vectorize
1236
+ * metadata type; a kind that can't be filtered on is reported, not indexed.
1237
+ */
1238
+ kind: string | undefined;
1239
+ /** Column mirrored into vector metadata. */
1240
+ property: string;
1241
+ }
875
1242
  interface SchemaInfo {
876
1243
  /** Whether the lunora schema declares any `.global()` table. */
877
1244
  hasGlobalTable: boolean;
878
1245
  /** Names of vector indexes declared via `.vectorize()` / `defineVectorIndex()`. */
879
1246
  vectorIndexNames?: ReadonlyArray<string>;
1247
+ /**
1248
+ * Metadata properties declared filterable on a vector index. Cloudflare
1249
+ * needs an explicit metadata index per property before a `filter` can match
1250
+ * anything, so deploy provisions these and doctor reports them.
1251
+ */
1252
+ vectorMetadata?: ReadonlyArray<VectorMetadataDeclaration>;
880
1253
  }
881
1254
  interface DiscoverSchemaInfoResult {
882
1255
  /** Parse error message, when the schema exists but could not be analyzed. */
@@ -885,12 +1258,93 @@ interface DiscoverSchemaInfoResult {
885
1258
  info: SchemaInfo | undefined;
886
1259
  }
887
1260
  /**
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
- */
1261
+ * Discover {@link SchemaInfo} for a project. Returns `{ info: undefined }` when
1262
+ * the project declares no `schema.ts` (not an error), or `{ info: undefined,
1263
+ * error }` when a present schema could not be parsed — callers decide whether a
1264
+ * parse failure is a warning (validator) or simply ignorable (inference).
1265
+ */
893
1266
  declare const discoverSchemaInfo: (projectRoot: string, schemaDirectory: string) => DiscoverSchemaInfoResult;
1267
+ /**
1268
+ * A badge: the short colored label that prefixes a line. `bg`/`fg` are hex so the
1269
+ * same value drives both colorize's `bgHex().hex()` and the tui `&lt;Text>` props.
1270
+ */
1271
+ interface BadgeSpec {
1272
+ bg: `#${string}`;
1273
+ fg: `#${string}`;
1274
+ text: string;
1275
+ }
1276
+ /** Lunora purple — the accent shared with the CLI prompt frames. */
1277
+ declare const ACCENT: `#${string}`;
1278
+ /** Standard log-level badge names (the restyled base output). */
1279
+ type LevelBadgeName = "debug" | "error" | "info" | "success" | "warn";
1280
+ /** Step-phase badge names (the create-astro-style flow transcript). */
1281
+ type StepBadgeName = "add" | "deps" | "dir" | "git" | "lunora" | "next" | "tmpl";
1282
+ type BadgeName = LevelBadgeName | StepBadgeName;
1283
+ /** The ordered step-phase names, used to register custom pail log types. */
1284
+ declare const STEP_BADGE_NAMES: ReadonlyArray<StepBadgeName>;
1285
+ /**
1286
+ * Every badge, keyed by name. Levels get their conventional colors (red/amber/
1287
+ * green/blue/grey); step phases follow create-astro's green→purple→cyan rhythm.
1288
+ */
1289
+ declare const BADGES: Record<BadgeName, BadgeSpec>;
1290
+ /**
1291
+ * Luna, the mascot: the folklore rabbit-in-the-moon — a bunny tucked inside the
1292
+ * moon disc. Pure ASCII so it renders the same everywhere (including piped logs).
1293
+ * The CLI signs off the `init` flow with it, the way create-astro closes with
1294
+ * Houston.
1295
+ */
1296
+ declare const LUNA_NAME = "Luna";
1297
+ declare const LUNA_SIGNOFF = "Safe travels, voyager.";
1298
+ declare const LUNA_BUNNY: string;
1299
+ /**
1300
+ * {@link LUNA_BUNNY} with its leading newline stripped, ready to render inline
1301
+ * (beside the name + sign-off). Both render paths — the tui mascot frame and the
1302
+ * pail off-TTY fallback — use this so neither re-implements the strip.
1303
+ */
1304
+ declare const LUNA_ART: string;
1305
+ /** The colored part of a badge — the word with one space of padding each side. */
1306
+ declare const padBadge: (text: string) => string;
1307
+ /** Leading spaces that right-align a badge's box within the gutter. */
1308
+ declare const badgeLead: (text: string) => string;
1309
+ /** Total columns a rendered badge column occupies (lead + box), constant across badges. */
1310
+ declare const BADGE_COLUMN_WIDTH: number;
1311
+ /** Columns a rendered badge occupies — the gutter-aligned column width. */
1312
+ declare const badgeWidth: (_spec: BadgeSpec) => number;
1313
+ /** Paint a badge as an ANSI string (the non-tui path): right-aligning spaces + the colored box. */
1314
+ declare const paintBadge: (spec: BadgeSpec) => string;
1315
+ /** Dim continuation text (a step's chosen answer, shown under the question). */
1316
+ declare const paintAnswer: (text: string) => string;
1317
+ /**
1318
+ * Single source of truth for "does this wrangler config enable Workers
1319
+ * Cache?" and "what compatibility_date does that require?".
1320
+ *
1321
+ * Before this module, `reconcile-compatibility-date.ts` (auto-bump) and
1322
+ * `wrangler-validator.ts` (validation) each carried their own
1323
+ * `WORKERS_CACHE_MIN_DATE` literal and their own top-level/`exports[]`
1324
+ * cache-enabled walk — two copies of the same fact that could silently drift
1325
+ * apart (a date bumped in one file without the other would either validate a
1326
+ * config the reconciler wouldn't produce, or vice versa).
1327
+ */
1328
+ /** The `compatibility_date` Workers Cache (`cache.enabled: true`) requires. */
1329
+ declare const WORKERS_CACHE_MIN_DATE = "2026-05-01";
1330
+ /** The subset of a parsed `wrangler.jsonc` the cache-enabled check reads. */
1331
+ interface WranglerCacheShape {
1332
+ cache?: {
1333
+ enabled?: boolean;
1334
+ } | null;
1335
+ exports?: Record<string, {
1336
+ cache?: {
1337
+ enabled?: boolean;
1338
+ } | null;
1339
+ } | null> | null;
1340
+ }
1341
+ /**
1342
+ * Whether Workers Cache is enabled anywhere in a parsed wrangler config — the
1343
+ * top-level `cache.enabled` toggle, or a per-export override in
1344
+ * `exports[name].cache.enabled` (Workers can scope cache per named export).
1345
+ * `undefined`/`null` (an unparsed or absent config) is treated as disabled.
1346
+ */
1347
+ declare const isCacheEnabled: (parsed: WranglerCacheShape | null | undefined) => boolean;
894
1348
  /** Candidate wrangler config filenames, in the order every consumer probes them. */
895
1349
  declare const WRANGLER_FILES: readonly ["wrangler.jsonc", "wrangler.json"];
896
1350
  /** Locate the project's wrangler config, or `undefined` when none exists. */
@@ -902,12 +1356,29 @@ interface ReadWranglerResult<T> {
902
1356
  text: string;
903
1357
  }
904
1358
  /**
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
- */
1359
+ * Read and JSONC-parse a wrangler config file. Returns the raw `text` (for
1360
+ * structural edits) alongside `parsed`, which is `undefined` when the file is
1361
+ * not valid JSONC or does not parse to an object. Allows trailing commas, as
1362
+ * wrangler does.
1363
+ */
910
1364
  declare const readWranglerJsonc: <T = unknown>(wranglerPath: string) => ReadWranglerResult<T>;
1365
+ /**
1366
+ * Pure scan of a `vars` map for plaintext secrets — the FS-free core, exported for
1367
+ * unit tests. A variable is flagged when, for a **string** value that is neither a
1368
+ * placeholder nor a public/publishable key, EITHER the value matches a known
1369
+ * secret shape (`secretKindOf`) OR the key name strongly implies a secret and the
1370
+ * value is long enough to plausibly be one. `kind` is the matched shape, or
1371
+ * `secret_named_var` for the key-name path.
1372
+ */
1373
+ declare const scanWranglerVariablesForSecrets: (variables: Record<string, unknown> | undefined, file: string) => WranglerVariableIR[];
1374
+ /**
1375
+ * Read the project's `wrangler.jsonc` and return its plaintext-secret `vars` as IR
1376
+ * for the `plaintext_secret_in_wrangler_vars` lint. Returns `[]` when there is no
1377
+ * wrangler config, it doesn't parse, or nothing looks like a secret. Scans the
1378
+ * top-level `vars` block (mirroring the existing `validateCorsVariables` scope);
1379
+ * per-environment `env.&lt;name>.vars` overrides are out of scope for now.
1380
+ */
1381
+ declare const collectWranglerSecretVariables: (projectRoot: string) => WranglerVariableIR[];
911
1382
  declare const REQUIRED_COMPATIBILITY_DATE: string;
912
1383
  declare const REQUIRED_FLAG: string;
913
1384
  interface WranglerDurableObjectBinding {
@@ -915,10 +1386,10 @@ interface WranglerDurableObjectBinding {
915
1386
  name?: string;
916
1387
  }
917
1388
  /**
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
- */
1389
+ * A `tail_consumers` entry: a Worker that receives this Worker's tail events
1390
+ * (logs, exceptions, fetch metadata) for forwarding to an external sink. See
1391
+ * `withTailConsumer` for the wiring helper.
1392
+ */
922
1393
  interface TailConsumer {
923
1394
  /** Optional Cloudflare environment of the consumer Worker. */
924
1395
  environment?: string;
@@ -937,15 +1408,31 @@ interface WranglerContainerEntry {
937
1408
  max_instances?: number;
938
1409
  }
939
1410
  /**
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
- */
1411
+ * A wrangler `workflows[]` entry (parsed from untrusted JSONC). Unlike
1412
+ * containers, workflows are NOT Durable Objects — the entry stands alone (no
1413
+ * `durable_objects` binding, no migration class).
1414
+ */
944
1415
  interface WranglerWorkflowEntry {
945
1416
  binding?: string;
946
1417
  class_name?: string;
947
1418
  name?: string;
948
1419
  }
1420
+ /** A wrangler `queues.producers[]` entry — a `Queue` binding sending to `queue`. */
1421
+ interface WranglerQueueProducer {
1422
+ binding?: string;
1423
+ delivery_delay?: number;
1424
+ queue?: string;
1425
+ }
1426
+ /** A wrangler `queues.consumers[]` entry — push (worker) or `type: "http_pull"`. */
1427
+ interface WranglerQueueConsumer {
1428
+ dead_letter_queue?: string;
1429
+ max_batch_size?: number;
1430
+ max_batch_timeout?: number;
1431
+ max_retries?: number;
1432
+ queue?: string;
1433
+ retry_delay?: number;
1434
+ type?: string;
1435
+ }
949
1436
  interface WranglerConfig {
950
1437
  analytics_engine_datasets?: ReadonlyArray<{
951
1438
  binding?: string;
@@ -960,6 +1447,9 @@ interface WranglerConfig {
960
1447
  browser?: {
961
1448
  binding?: string;
962
1449
  };
1450
+ cache?: {
1451
+ enabled?: boolean;
1452
+ } | null;
963
1453
  compatibility_date?: string;
964
1454
  compatibility_flags?: ReadonlyArray<string>;
965
1455
  containers?: ReadonlyArray<WranglerContainerEntry | null | undefined>;
@@ -974,6 +1464,16 @@ interface WranglerConfig {
974
1464
  durable_objects?: {
975
1465
  bindings?: ReadonlyArray<WranglerDurableObjectBinding>;
976
1466
  };
1467
+ exports?: Record<string, {
1468
+ cache?: {
1469
+ enabled?: boolean;
1470
+ } | null;
1471
+ type?: string;
1472
+ } | null> | null;
1473
+ flagship?: ReadonlyArray<{
1474
+ app_id?: string;
1475
+ binding?: string;
1476
+ } | null | undefined>;
977
1477
  hyperdrive?: ReadonlyArray<{
978
1478
  binding?: string;
979
1479
  id?: string;
@@ -1006,13 +1506,23 @@ interface WranglerConfig {
1006
1506
  pipelines?: ReadonlyArray<{
1007
1507
  binding?: string;
1008
1508
  pipeline?: string;
1509
+ stream?: string;
1009
1510
  } | null | undefined>;
1010
1511
  placement?: {
1011
1512
  mode?: string;
1012
1513
  };
1514
+ queues?: {
1515
+ consumers?: ReadonlyArray<WranglerQueueConsumer | null | undefined>;
1516
+ producers?: ReadonlyArray<WranglerQueueProducer | null | undefined>;
1517
+ };
1013
1518
  r2_buckets?: ReadonlyArray<{
1014
1519
  binding?: string;
1015
1520
  }>;
1521
+ secrets_store_secrets?: ReadonlyArray<{
1522
+ binding?: string;
1523
+ secret_name?: string;
1524
+ store_id?: string;
1525
+ } | null | undefined>;
1016
1526
  send_email?: ReadonlyArray<{
1017
1527
  allowed_destination_addresses?: ReadonlyArray<string>;
1018
1528
  destination_address?: string;
@@ -1038,23 +1548,23 @@ interface WranglerValidationReport {
1038
1548
  warnings: string[];
1039
1549
  }
1040
1550
  /**
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
- */
1551
+ * Return a new `WranglerConfig` with `consumer` present in `tail_consumers`,
1552
+ * wiring this Worker to forward its tail events (logs/exceptions) to another
1553
+ * Worker that fans them out to an external sink. Pure and idempotent: an
1554
+ * existing entry with the same `service` + `environment` is left untouched
1555
+ * rather than duplicated, so it is safe to call on every codegen/deploy.
1556
+ */
1047
1557
  declare const withTailConsumer: (wrangler: WranglerConfig, consumer: TailConsumer) => WranglerConfig;
1048
1558
  /**
1049
- * Pure validator: given a parsed `WranglerConfig` object and an optional
1050
- * `SchemaInfo`, produce a structured report. Performs no I/O.
1051
- */
1559
+ * Pure validator: given a parsed `WranglerConfig` object and an optional
1560
+ * `SchemaInfo`, produce a structured report. Performs no I/O.
1561
+ */
1052
1562
  declare const validateWranglerConfig: (wrangler: WranglerConfig | undefined, schema?: SchemaInfo) => WranglerValidationReport;
1053
1563
  /**
1054
- * Convenience alias matching the original task-spec signature
1055
- * `validateWrangler(wranglerJson, schema)` returning
1056
- * `{ valid, errors, warnings }`.
1057
- */
1564
+ * Convenience alias matching the original task-spec signature
1565
+ * `validateWrangler(wranglerJson, schema)` returning
1566
+ * `{ valid, errors, warnings }`.
1567
+ */
1058
1568
  declare const validateWrangler: typeof validateWranglerConfig;
1059
1569
  interface WranglerProjectValidationOptions {
1060
1570
  projectRoot: string;
@@ -1066,10 +1576,10 @@ interface WranglerProjectValidationResult {
1066
1576
  wranglerPath: string | undefined;
1067
1577
  }
1068
1578
  /**
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
- */
1579
+ * File-system aware variant: reads `wrangler.jsonc`/`wrangler.json` from
1580
+ * the given project root, discovers the schema (if any), and delegates to
1581
+ * `validateWranglerConfig`. Returns the legacy
1582
+ * `{ problems, wranglerPath }` shape plus the structured `report`.
1583
+ */
1074
1584
  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 };
1585
+ export { ACCENT, AGENT_MODE_ENV, AGENT_RULES_DIR, AGENT_RULES_HINT, AGENT_RULES_HINT_ENV, type AddIndexEdit, type AddOptionalColumnEdit, type AddTableEdit, type AdditiveEdit, type AgentDetection, type AgentRulesStatus, type ApplyEditResult, type ApplyFailureReason, type AugmentPlan, BADGES, BADGE_COLUMN_WIDTH, type BadgeName, type BadgeSpec, type ClaimDevServerStateResult, type ContainerLogLevel, type ContainerLogLine, type ContainerLogSource, type ContainerLogStreamHandle, type ContainerLogStreamOptions, DEV_DAEMON_ENV, DEV_HANDOFF_ENV, DEV_LOG_FILE, DEV_LOG_FILE_ENV, DEV_STATE_DIR, DEV_STATE_FILE, DEV_VARS_EXAMPLE_FILE, DEV_VARS_FILE, DEV_VARS_KEY_PATTERN, type DestructiveEdit, type DetectedFramework, type DevSecretsFillPlan, type DevServerMode, type DevServerState, type DiscoverAgentInfoResult, type DiscoverContainerInfoResult, type DiscoverSchemaInfoResult, type DiscoverWorkflowInfoResult, type DockerLike, type EnsureDevVariablesDeps, type EnsureDevVariablesResult, type EnsureDevVariablesStatus, type ExportGap, type FillDevSecretsResult, type FrameworkClass, type FrameworkDetection, type InferOptions, type InferredAgent, type InferredBindings, type InferredContainer, type InferredWorkflow, LINKED_PROJECT_DIR, LINKED_PROJECT_FILE, LUNA_ART, LUNA_BUNNY, LUNA_NAME, LUNA_SIGNOFF, LUNORA_CONFIG_FILE, LUNORA_EVENT_SOURCE, LUNORA_SKILL_NAMES, type LevelBadgeName, type LinkedProject, type LunoraFormattedLine, type LunoraLineLevel, type LunoraProjectConfig, LunoraReporter, 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 ReconcileCompatibilityDateResult, type ReconcileResult as ReconcileCronsResult, type RemoteBindingPlan, type RemoteEnableInputs, type RemotePreference, type RemoteWranglerShape, STEP_BADGE_NAMES, type ScaffoldPlan, type SchemaColumn, type SchemaEdit, type SchemaIndex, type SchemaInfo, type SchemaTable, type SecretEntry, type SelectOption, type StepBadgeName, type TailConsumer, WORKERS_CACHE_MIN_DATE, WRANGLER_FILES, type WranglerCacheShape, type WranglerConfig, type WranglerContainerEntry, type WranglerProjectValidationOptions, type WranglerProjectValidationResult, type WranglerValidationReport, type WranglerWorkflowEntry, applyAdditiveEdit, badgeLead, badgeWidth, buildPackageSecretsBlock, claimAgentRulesHint, claimDevServerState, classifyEdit, clearDevServerState, collectWranglerSecretVariables, createConfirm, detectAgentRules, detectAiAgent, detectFramework, discoverAgentInfo, discoverContainerInfo, discoverSchemaInfo, discoverWorkflowInfo, ensureDevVariables, ensureDevVariablesExample as ensureDevVarsExample, fillDevSecrets, findWranglerFile, formatLunoraEvent, generateSecretValue, inferLunoraBindings, injectRemoteFlags, interpretRemote, isCacheEnabled, isInteractive, isMintableSecretKey, isPlaceholderValue, isProcessAlive, isRecordedProcessCurrent, isRemoteEnvEnabled, materializeRemoteWranglerConfig, packageNamesFromBindings, padBadge, paintAnswer, paintBadge, parseDevVariableEntries, parseSchema, planDevSecretsFill, planDevVariablesAugment, planDevVariablesScaffold, planRemoteBindings, promptMultiSelect, promptSelect, promptText, promptYesNo, readDevServerState, readLinkedProject, readLiveDevServerState, readProjectDependencyNames, readProjectRemotePreference, readWranglerJsonc, reconcileWranglerBindings, reconcileWranglerCompatibilityDate, reconcileWranglerCrons, requiredSecrets, resolveRemoteEnabled, scanWranglerVariablesForSecrets, secretsForPackages, streamContainerLogs, updateDevServerState, validateWrangler, validateWranglerConfig, validateWranglerProject, withTailConsumer, writeDevServerState, writeLinkedProject };