@lunora/config 1.0.0-alpha.8 → 1.0.0-alpha.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +885 -461
  4. package/dist/index.d.ts +885 -461
  5. package/dist/index.mjs +17 -9
  6. package/dist/packem_shared/AGENT_MODE_ENV-B5R7jpY7.mjs +17 -0
  7. package/dist/packem_shared/DEV_DAEMON_ENV-FIEtW7Fz.mjs +163 -0
  8. package/dist/packem_shared/{LUNORA_EVENT_SOURCE-D2fDeGB6.mjs → LUNORA_EVENT_SOURCE-M1KM9_pR.mjs} +28 -2
  9. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-NCTSphDk.mjs +138 -0
  10. package/dist/packem_shared/{POLICY_SCAFFOLD_ENDPOINT-CiC2IGKx.mjs → POLICY_SCAFFOLD_ENDPOINT-CvTZpaZF.mjs} +7 -10
  11. package/dist/packem_shared/{REMOTE_ELIGIBLE_KEYS-BC7_e9Bz.mjs → REMOTE_ELIGIBLE_KEYS-C-F_Flgy.mjs} +1 -6
  12. package/dist/packem_shared/{REQUIRED_COMPATIBILITY_DATE-DRSNSOOp.mjs → REQUIRED_COMPATIBILITY_DATE-DP0SFVy9.mjs} +134 -3
  13. package/dist/packem_shared/{SCHEMA_EDIT_ENDPOINT-Df-Wrix-.mjs → SCHEMA_EDIT_ENDPOINT-Bt2SNkpV.mjs} +4 -8
  14. package/dist/packem_shared/{SEED_ENDPOINT-DVCjaGO-.mjs → SEED_ENDPOINT-7izz_Jao.mjs} +1 -1
  15. package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-CaXbS7CX.mjs +11 -0
  16. package/dist/packem_shared/assetContentType-ByZjeV3D.mjs +72 -0
  17. package/dist/packem_shared/{buildPackageSecretsBlock-S74dgmwy.mjs → buildPackageSecretsBlock-BxOcyaAY.mjs} +115 -21
  18. package/dist/packem_shared/collectWranglerSecretVariables-BRWIvT-e.mjs +56 -0
  19. package/dist/packem_shared/{createConfirm-fvpdgJ9s.mjs → createConfirm-BMikeJC9.mjs} +22 -5
  20. package/dist/packem_shared/{detectFramework-Br-BcPBq.mjs → detectFramework-U08038Yp.mjs} +3 -3
  21. package/dist/packem_shared/discoverAgentInfo-eXA3hF4v.mjs +19 -0
  22. package/dist/packem_shared/{inferLunoraBindings-DIku9mTN.mjs → inferLunoraBindings-BcvvtpSG.mjs} +145 -50
  23. package/dist/packem_shared/jsonc-edit-SbcD7MAe.mjs +9 -0
  24. package/dist/packem_shared/parseDevVariable-B0FEvfHC.mjs +21 -0
  25. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.ts → policy-scaffold.d-CFd2FlqG.d.mts} +22 -22
  26. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.mts → policy-scaffold.d-CFd2FlqG.d.ts} +22 -22
  27. package/dist/packem_shared/{reconcileWranglerBindings-DTHmqTbL.mjs → reconcileWranglerBindings-2Cww5VLa.mjs} +114 -29
  28. package/dist/packem_shared/reconcileWranglerCompatibilityDate-C8c497ag.mjs +31 -0
  29. package/dist/packem_shared/reconcileWranglerCrons-DvsceVFz.mjs +29 -0
  30. package/dist/packem_shared/{serveJsonHandler-B4OLTGLS.mjs → serveJsonHandler-Cl2Wqkv8.mjs} +2 -2
  31. package/dist/packem_shared/streamContainerLogs-BZ4cOZwH.mjs +157 -0
  32. package/dist/packem_shared/write-atomic-B2REqapi.mjs +14 -0
  33. package/dist/studio-host/index.d.mts +134 -92
  34. package/dist/studio-host/index.d.ts +134 -92
  35. package/dist/studio-host/index.mjs +6 -6
  36. package/package.json +11 -8
  37. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs +0 -62
  38. package/dist/packem_shared/loadStudioAssets-Csk5RS4E.mjs +0 -28
  39. package/dist/packem_shared/parseDevVariable-CJiq2IwE.mjs +0 -30
@@ -0,0 +1,19 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { AGENTS_FILENAME, discoverAgents } from '@lunora/codegen';
3
+ import { Project } from 'ts-morph';
4
+ import { join } from 'node:path';
5
+
6
+ const discoverAgentInfo = (projectRoot, schemaDirectory) => {
7
+ const agentsPath = join(projectRoot, schemaDirectory, AGENTS_FILENAME);
8
+ if (!existsSync(agentsPath)) {
9
+ return { agents: [] };
10
+ }
11
+ try {
12
+ const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
13
+ return { agents: discoverAgents(project, join(projectRoot, schemaDirectory)) };
14
+ } catch (error) {
15
+ return { agents: [], error: error instanceof Error ? error.message : String(error) };
16
+ }
17
+ };
18
+
19
+ export { discoverAgentInfo };
@@ -1,10 +1,39 @@
1
1
  import { existsSync, statSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { init, parse } from 'es-module-lexer';
3
+ import { discoverAgentInfo } from './discoverAgentInfo-eXA3hF4v.mjs';
3
4
  import { discoverContainerInfo } from './discoverContainerInfo-BXFs6Wav.mjs';
5
+ import { FLAGS_FILENAME, discoverFlags, QUEUES_FILENAME, discoverQueues } from '@lunora/codegen';
6
+ import { Project } from 'ts-morph';
7
+ import { join } from 'node:path';
4
8
  import { discoverSchemaInfo } from './discoverSchemaInfo-BB-CKlTK.mjs';
5
9
  import { discoverWorkflowInfo } from './discoverWorkflowInfo-CedvR0mn.mjs';
6
10
  import { WRANGLER_FILES, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
7
- import { join } from 'node:path';
11
+
12
+ const discoverFlagsInfo = (projectRoot, schemaDirectory) => {
13
+ const flagsPath = join(projectRoot, schemaDirectory, FLAGS_FILENAME);
14
+ if (!existsSync(flagsPath)) {
15
+ return {};
16
+ }
17
+ try {
18
+ const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
19
+ return { flags: discoverFlags(project, join(projectRoot, schemaDirectory)) };
20
+ } catch (error) {
21
+ return { error: error instanceof Error ? error.message : String(error) };
22
+ }
23
+ };
24
+
25
+ const discoverQueueInfo = (projectRoot, schemaDirectory) => {
26
+ const queuesPath = join(projectRoot, schemaDirectory, QUEUES_FILENAME);
27
+ if (!existsSync(queuesPath)) {
28
+ return { queues: [] };
29
+ }
30
+ try {
31
+ const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
32
+ return { queues: discoverQueues(project, join(projectRoot, schemaDirectory)) };
33
+ } catch (error) {
34
+ return { error: error instanceof Error ? error.message : String(error), queues: [] };
35
+ }
36
+ };
8
37
 
9
38
  const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".jsx", ".mjs", ".mts", ".ts", ".tsx"]);
10
39
  const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".lunora-cache", ".wrangler", "_generated", "dist", "node_modules"]);
@@ -23,20 +52,60 @@ const TYPE_ONLY_EXPORT_PATTERNS = {
23
52
  };
24
53
  const ENV_DB_PATTERN = /\benv\s*\.\s*DB\b/;
25
54
  const ENV_AI_PATTERN = /\benv\s*\.\s*AI\b/;
55
+ const CTX_PIPELINES_PATTERN = /\bctx\s*\.\s*pipelines\b/;
26
56
  const TYPE_ONLY_IMPORT_PATTERN = /^\s*import\s+type\b/;
57
+ const SANDBOX_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/agent", "@lunora/agent/sandbox"]);
58
+ const extractImportSpecifierList = (statementText) => {
59
+ const openBraceIndex = statementText.indexOf("{");
60
+ if (openBraceIndex === -1) {
61
+ return "";
62
+ }
63
+ const closeBraceIndex = statementText.indexOf("}", openBraceIndex + 1);
64
+ return closeBraceIndex === -1 ? statementText.slice(openBraceIndex + 1) : statementText.slice(openBraceIndex + 1, closeBraceIndex);
65
+ };
66
+ const TYPE_BROWSER_TOOL_SPECIFIER_PATTERN = /\btype\s+browserTool\b/;
67
+ const BROWSER_TOOL_NAME_PATTERN = /\bbrowserTool\b/;
68
+ const SANDBOX_BROWSER_TOOL_FALLBACK_PATTERN = /import\s+\{[^}]*\bbrowserTool\b[^}]*\}\s+from\s+["']@lunora\/agent(?:\/sandbox)?["']/;
69
+ const isValueBrowserToolImport = (statementText) => {
70
+ if (TYPE_ONLY_IMPORT_PATTERN.test(statementText)) {
71
+ return false;
72
+ }
73
+ const specifierList = extractImportSpecifierList(statementText);
74
+ return BROWSER_TOOL_NAME_PATTERN.test(specifierList) && !TYPE_BROWSER_TOOL_SPECIFIER_PATTERN.test(specifierList);
75
+ };
76
+ const hasSandboxBrowserToolImport = (code) => {
77
+ try {
78
+ const [imports] = parse(code);
79
+ return imports.some(
80
+ (entry) => entry.n !== void 0 && SANDBOX_MODULE_SPECIFIERS.has(entry.n) && isValueBrowserToolImport(code.slice(entry.ss, entry.se))
81
+ );
82
+ } catch {
83
+ return SANDBOX_BROWSER_TOOL_FALLBACK_PATTERN.test(code);
84
+ }
85
+ };
27
86
  const CAPABILITY_SOURCES = {
28
87
  usesAi: { pattern: /\bfrom\s+["']@lunora\/ai["']/, source: "@lunora/ai" },
29
- usesAnalytics: { pattern: /\bfrom\s+["']@lunora\/analytics["']/, source: "@lunora/analytics" },
88
+ usesAnalytics: { pattern: /\bfrom\s+["']@lunora\/bindings\/analytics["']/, source: "@lunora/bindings/analytics" },
30
89
  usesAuth: { pattern: /\bfrom\s+["']@lunora\/auth["']/, source: "@lunora/auth" },
31
90
  usesBrowser: { pattern: /\bfrom\s+["']@lunora\/browser["']/, source: "@lunora/browser" },
32
91
  usesHyperdrive: { pattern: /\bfrom\s+["']@lunora\/hyperdrive["']/, source: "@lunora/hyperdrive" },
33
- usesImages: { pattern: /\bfrom\s+["']@lunora\/images["']/, source: "@lunora/images" },
34
- usesKv: { pattern: /\bfrom\s+["']@lunora\/kv["']/, source: "@lunora/kv" },
92
+ usesImages: { pattern: /\bfrom\s+["']@lunora\/bindings\/images["']/, source: "@lunora/bindings/images" },
93
+ usesKv: { pattern: /\bfrom\s+["']@lunora\/bindings\/kv["']/, source: "@lunora/bindings/kv" },
35
94
  usesMail: { pattern: /\bfrom\s+["']@lunora\/mail["']/, source: "@lunora/mail" },
36
95
  usesPayment: { pattern: /\bfrom\s+["']@lunora\/payment["']/, source: "@lunora/payment" },
37
- usesPipelines: { pattern: /\bfrom\s+["']@lunora\/pipelines["']/, source: "@lunora/pipelines" },
96
+ // Keyed off the `ctx.pipelines` access (not an import) — see CTX_PIPELINES_PATTERN.
97
+ // Pipelines is codegen-wired onto ActionCtx, so apps reach it via `ctx.pipelines`
98
+ // rather than importing `@lunora/bindings/pipelines`; `source` names that subpath
99
+ // for the hint message.
100
+ usesPipelines: { pattern: CTX_PIPELINES_PATTERN, source: "@lunora/bindings/pipelines" },
38
101
  usesScheduler: { pattern: /\bfrom\s+["']@lunora\/scheduler["']/, source: "@lunora/scheduler" },
39
- usesStorage: { pattern: /\bfrom\s+["']@lunora\/storage["']/, source: "@lunora/storage" }
102
+ usesStorage: { pattern: /\bfrom\s+["']@lunora\/storage["']/, source: "@lunora/storage" },
103
+ // x402 rails are opt-in add-on subpaths (not part of the `lunorash` umbrella),
104
+ // so they key off the exact `@lunora/x402/{charge,pay}` specifiers. Neither
105
+ // implies a `.dev.vars` secret: the charge recipient is a user-named `[vars]`
106
+ // entry and the pay wallet key is a Secrets Store binding — both hint-only.
107
+ usesX402Charge: { pattern: /\bfrom\s+["']@lunora\/x402\/charge["']/, source: "@lunora/x402/charge" },
108
+ usesX402Pay: { pattern: /\bfrom\s+["']@lunora\/x402\/pay["']/, source: "@lunora/x402/pay" }
40
109
  };
41
110
  const CAPABILITY_FLAGS = Object.keys(CAPABILITY_SOURCES);
42
111
  const PAYMENT_PROVIDER_SECRETS = "STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET (Stripe) or POLAR_ACCESS_TOKEN + POLAR_WEBHOOK_SECRET (Polar)";
@@ -90,7 +159,12 @@ const capabilitiesFromSource = (code) => {
90
159
  } catch {
91
160
  capabilities = regexCapabilities(code);
92
161
  }
93
- return mergeCapabilities(capabilities, { ...NO_CAPABILITIES, needsD1: ENV_DB_PATTERN.test(code), usesAi: ENV_AI_PATTERN.test(code) });
162
+ return mergeCapabilities(capabilities, {
163
+ ...NO_CAPABILITIES,
164
+ needsD1: ENV_DB_PATTERN.test(code),
165
+ usesAi: ENV_AI_PATTERN.test(code),
166
+ usesPipelines: CTX_PIPELINES_PATTERN.test(code)
167
+ });
94
168
  };
95
169
  const collectSourceFiles = (directory, accumulator) => {
96
170
  let entries;
@@ -149,56 +223,39 @@ const detectExportedDurableObjects = (entryPath) => {
149
223
  };
150
224
  });
151
225
  };
152
- const CONTAINERS_STAR_REEXPORT_PATTERN = /\bexport\s*\*\s*from\s*["'][^"']*_generated\/containers(?:\.js)?["']/;
153
- const detectContainerExports = (entryPath, containers) => {
154
- if (containers.length === 0) {
155
- return [];
156
- }
157
- if (entryPath === void 0) {
158
- return containers.map((container) => {
159
- return { ...container, exported: false };
160
- });
161
- }
162
- const code = readFileSync(entryPath, "utf8");
163
- const starReexport = CONTAINERS_STAR_REEXPORT_PATTERN.test(code);
164
- let exportedNames;
165
- try {
166
- const [, exports] = parse(code);
167
- exportedNames = new Set(exports.map((entry) => entry.n));
168
- } catch {
169
- exportedNames = new Set(
170
- containers.map((container) => container.className).filter((className) => new RegExp(String.raw`\bexport\b[^\n;]*\b${className}\b`).test(code))
171
- );
172
- }
173
- return containers.map((container) => {
174
- return { ...container, exported: starReexport || exportedNames.has(container.className) };
175
- });
176
- };
177
- const WORKFLOWS_STAR_REEXPORT_PATTERN = /\bexport\s*\*\s*from\s*["'][^"']*_generated\/workflows(?:\.js)?["']/;
178
- const detectWorkflowExports = (entryPath, workflows) => {
179
- if (workflows.length === 0) {
226
+ const isTypeOnlyClassExport = (code, className) => (
227
+ // `export type Foo` / `export type { … Foo … }` / `export { type Foo }` — all
228
+ // three type-only forms list the name via the lexer but compile away.
229
+ new RegExp(String.raw`\bexport\s+type\s+${className}\b`).test(code) || new RegExp(String.raw`\bexport\s+type\s*\{[^}]*\b${className}\b`).test(code) || new RegExp(String.raw`\bexport\s+\{[^}]*\btype\s+${className}\b`).test(code)
230
+ );
231
+ const detectClassExports = (entryPath, definitions, generatedModule) => {
232
+ if (definitions.length === 0) {
180
233
  return [];
181
234
  }
182
235
  if (entryPath === void 0) {
183
- return workflows.map((workflow) => {
184
- return { ...workflow, exported: false };
236
+ return definitions.map((definition) => {
237
+ return { ...definition, exported: false };
185
238
  });
186
239
  }
187
240
  const code = readFileSync(entryPath, "utf8");
188
- const starReexport = WORKFLOWS_STAR_REEXPORT_PATTERN.test(code);
241
+ const starReexport = new RegExp(String.raw`\bexport\s*\*\s*from\s*["'][^"']*_generated\/${generatedModule}(?:\.js)?["']`).test(code);
189
242
  let exportedNames;
190
243
  try {
191
244
  const [, exports] = parse(code);
192
245
  exportedNames = new Set(exports.map((entry) => entry.n));
193
246
  } catch {
194
247
  exportedNames = new Set(
195
- workflows.map((workflow) => workflow.className).filter((className) => new RegExp(String.raw`\bexport\b[^\n;]*\b${className}\b`).test(code))
248
+ definitions.map((definition) => definition.className).filter((className) => new RegExp(String.raw`\bexport\b[^\n;]*\b${className}\b`).test(code))
196
249
  );
197
250
  }
198
- return workflows.map((workflow) => {
199
- return { ...workflow, exported: starReexport || exportedNames.has(workflow.className) };
251
+ return definitions.map((definition) => {
252
+ const exported = starReexport || exportedNames.has(definition.className) && !isTypeOnlyClassExport(code, definition.className);
253
+ return { ...definition, exported };
200
254
  });
201
255
  };
256
+ const detectContainerExports = (entryPath, containers) => detectClassExports(entryPath, containers, "containers");
257
+ const detectWorkflowExports = (entryPath, workflows) => detectClassExports(entryPath, workflows, "workflows");
258
+ const detectAgentExports = (entryPath, agents) => detectClassExports(entryPath, agents, "agents");
202
259
  const schemaNeedsD1 = (projectRoot, schemaDirectory) => discoverSchemaInfo(projectRoot, schemaDirectory).info?.hasGlobalTable ?? false;
203
260
  const scanCapabilities = (projectRoot, scanDirectories) => {
204
261
  let merged = NO_CAPABILITIES;
@@ -215,12 +272,24 @@ const scanCapabilities = (projectRoot, scanDirectories) => {
215
272
  }
216
273
  return merged;
217
274
  };
218
- const describeDeclaredExports = (containers, workflows) => [
275
+ const scanSandboxBrowserToolUsage = (projectRoot, lunoraDirectory) => {
276
+ const absolute = join(projectRoot, lunoraDirectory);
277
+ if (!existsSync(absolute) || !statSync(absolute).isDirectory()) {
278
+ return false;
279
+ }
280
+ const files = [];
281
+ collectSourceFiles(absolute, files);
282
+ return files.some((file) => hasSandboxBrowserToolImport(readFileSync(file, "utf8")));
283
+ };
284
+ const describeDeclaredExports = (containers, workflows, agents) => [
219
285
  ...containers.map(
220
286
  (container) => container.exported ? `${container.bindingName}/${container.className} (container "${container.exportName}" declared and exported)` : `hint: container "${container.exportName}" is declared but ${container.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/containers"\``
221
287
  ),
222
288
  ...workflows.map(
223
289
  (workflow) => workflow.exported ? `${workflow.bindingName}/${workflow.className} (workflow "${workflow.exportName}" declared and exported)` : `hint: workflow "${workflow.exportName}" is declared but ${workflow.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/workflows"\``
290
+ ),
291
+ ...agents.map(
292
+ (agent) => agent.exported ? `${agent.bindingName}/${agent.className} (agent "${agent.exportName}" declared and exported)` : `hint: agent "${agent.exportName}" is declared but ${agent.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/agents"\``
224
293
  )
225
294
  ];
226
295
  const describeCapabilitySignals = (capabilities, exported) => {
@@ -237,14 +306,14 @@ const describeCapabilitySignals = (capabilities, exported) => {
237
306
  // Self-describing bindings: the binding name is the whole config (no remote
238
307
  // id to mint), so reconcile auto-writes them like the DO/D1 bindings.
239
308
  [capabilities.usesBrowser, "browser (@lunora/browser imported) — self-describing { binding: BROWSER }"],
240
- [capabilities.usesImages, "images (@lunora/images imported) — self-describing { binding: IMAGES }"],
241
- [capabilities.usesAnalytics, "analytics_engine_datasets (@lunora/analytics imported) — self-describing { binding: ANALYTICS, dataset }"],
309
+ [capabilities.usesImages, "images (@lunora/bindings/images imported) — self-describing { binding: IMAGES }"],
310
+ [capabilities.usesAnalytics, "analytics_engine_datasets (@lunora/bindings/analytics imported) — self-describing { binding: ANALYTICS, dataset }"],
242
311
  // Hint bindings: each needs a remote resource Lunora can't fabricate (a KV
243
312
  // namespace id, a Hyperdrive id, a Pipelines pipeline name), so they surface
244
313
  // as hints — never an auto-write — exactly like R2's user-defined bucket name.
245
314
  [
246
315
  capabilities.usesKv,
247
- "hint: @lunora/kv is imported; add a kv_namespaces binding ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned"
316
+ "hint: @lunora/bindings/kv is imported; add a kv_namespaces binding ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned"
248
317
  ],
249
318
  [
250
319
  capabilities.usesHyperdrive,
@@ -252,39 +321,65 @@ const describeCapabilitySignals = (capabilities, exported) => {
252
321
  ],
253
322
  [
254
323
  capabilities.usesPipelines,
255
- "hint: @lunora/pipelines is imported; run 'wrangler pipelines create <name>' and add a 'pipelines' binding ({ binding, pipeline }) — the pipeline resource can't be auto-provisioned"
324
+ "hint: ctx.pipelines is used; run 'wrangler pipelines create <name>' and add a 'pipelines' binding ({ binding, pipeline }) — the pipeline resource can't be auto-provisioned"
325
+ ],
326
+ [
327
+ capabilities.usesX402Charge,
328
+ "hint: @lunora/x402/charge is imported; set the recipient wallet address as a [vars] entry (the var name is yours to choose) and pass it to the charge config — the x402 facilitator settles USDC to that address"
329
+ ],
330
+ [
331
+ capabilities.usesX402Pay,
332
+ "hint: @lunora/x402/pay is imported (ActionCtx-only, spends real funds); add a secrets_store_secrets[] binding for the agent wallet key (name it to match signer.secretName) and pair the pay rail with a spend policy — ctx.secrets reads a Secrets Store binding, not .dev.vars, so the key can't be auto-provisioned"
256
333
  ]
257
334
  ];
258
335
  return rules.filter(([active]) => active).map(([, signal]) => signal);
259
336
  };
260
- const describeSignals = (durableObjects, needsD1, capabilities, containers = [], workflows = []) => {
337
+ const describeSignals = (durableObjects, needsD1, capabilities, containers = [], workflows = [], agents = []) => {
261
338
  const exported = new Set(durableObjects.map((object) => object.className));
262
339
  const signals = durableObjects.map((object) => `${object.binding}/${object.className} (exported by worker entry)`);
263
340
  if (needsD1) {
264
341
  signals.push("DB (.global() table declared)");
265
342
  }
266
- signals.push(...describeDeclaredExports(containers, workflows), ...describeCapabilitySignals(capabilities, exported));
343
+ signals.push(...describeDeclaredExports(containers, workflows, agents), ...describeCapabilitySignals(capabilities, exported));
267
344
  return signals;
268
345
  };
269
346
  const inferLunoraBindings = async (options) => {
270
347
  await init;
271
348
  const schemaDirectory = options.schemaDir ?? "lunora";
272
349
  const scanDirectories = options.scanDirs ?? DEFAULT_SCAN_DIRECTORIES;
273
- const capabilities = scanCapabilities(options.projectRoot, scanDirectories);
350
+ const scannedCapabilities = scanCapabilities(options.projectRoot, scanDirectories);
351
+ const capabilities = {
352
+ ...scannedCapabilities,
353
+ usesBrowser: scannedCapabilities.usesBrowser || scanSandboxBrowserToolUsage(options.projectRoot, schemaDirectory)
354
+ };
274
355
  const entryPath = resolveWorkerEntry(options.projectRoot);
275
356
  const durableObjects = entryPath ? detectExportedDurableObjects(entryPath) : [];
276
357
  const needsD1 = capabilities.needsD1 || schemaNeedsD1(options.projectRoot, schemaDirectory);
277
358
  const containers = detectContainerExports(entryPath, discoverContainerInfo(options.projectRoot, schemaDirectory).containers);
278
359
  const workflows = detectWorkflowExports(entryPath, discoverWorkflowInfo(options.projectRoot, schemaDirectory).workflows);
360
+ const agents = detectAgentExports(entryPath, discoverAgentInfo(options.projectRoot, schemaDirectory).agents);
361
+ const queues = [...discoverQueueInfo(options.projectRoot, schemaDirectory).queues];
362
+ const { flags } = discoverFlagsInfo(options.projectRoot, schemaDirectory);
363
+ const flagshipBinding = flags?.provider === "flagship" && flags.mode === "binding" ? flags.bindingName : void 0;
279
364
  const capabilityFlags = {};
280
365
  for (const flag of CAPABILITY_FLAGS) {
281
366
  capabilityFlags[flag] = capabilities[flag];
282
367
  }
368
+ const signals = describeSignals(durableObjects, needsD1, capabilities, containers, workflows, agents);
369
+ if (flagshipBinding !== void 0) {
370
+ signals.push(
371
+ `hint: lunora/flags.ts uses Flagship in binding mode; add a flagship binding ({ binding: "${flagshipBinding}", app_id }) — the app_id can't be auto-provisioned`
372
+ );
373
+ }
283
374
  return {
375
+ agents,
284
376
  containers,
285
377
  durableObjects,
378
+ flagshipBinding,
286
379
  needsD1,
287
- signals: describeSignals(durableObjects, needsD1, capabilities, containers, workflows),
380
+ queues,
381
+ signals,
382
+ usesFlags: flags !== void 0,
288
383
  workflows,
289
384
  ...capabilityFlags
290
385
  };
@@ -0,0 +1,9 @@
1
+ import { modify, applyEdits } from 'jsonc-parser';
2
+
3
+ const FORMATTING = { formattingOptions: { insertSpaces: true, tabSize: 4 } };
4
+ const applyModify = (text, path, value) => {
5
+ const edits = modify(text, [...path], value, FORMATTING);
6
+ return edits.length > 0 ? applyEdits(text, edits) : text;
7
+ };
8
+
9
+ export { FORMATTING as F, applyModify as a };
@@ -0,0 +1,21 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseDevVariableEntries } from './DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs';
4
+
5
+ const parseDevVariable = (contents, key) => {
6
+ const entry = parseDevVariableEntries(contents).find((candidate) => candidate.key === key);
7
+ return entry === void 0 || entry.value === "" ? void 0 : entry.value;
8
+ };
9
+ const resolveAdminToken = (root) => {
10
+ const fromEnv = process.env["LUNORA_ADMIN_TOKEN"];
11
+ if (typeof fromEnv === "string" && fromEnv !== "") {
12
+ return fromEnv;
13
+ }
14
+ try {
15
+ return parseDevVariable(readFileSync(join(root, ".dev.vars"), "utf8"), "LUNORA_ADMIN_TOKEN");
16
+ } catch {
17
+ return void 0;
18
+ }
19
+ };
20
+
21
+ export { parseDevVariable, resolveAdminToken };
@@ -2,9 +2,9 @@
2
2
  interface ScaffoldPolicyEdit {
3
3
  readonly kind: "scaffoldPolicy";
4
4
  /**
5
- * Base name for the generated file and its exported policy-set identifier,
6
- * e.g. `invoices` → `lunora/invoices.policies.ts` exporting `invoicesPolicies`.
7
- */
5
+ * Base name for the generated file and its exported policy-set identifier,
6
+ * e.g. `invoices` → `lunora/invoices.policies.ts` exporting `invoicesPolicies`.
7
+ */
8
8
  readonly name: string;
9
9
  /** Logical table the scaffolded policy guards (used in the stub body). */
10
10
  readonly table: string;
@@ -20,10 +20,10 @@ interface WireRlsEdit {
20
20
  /** Additive scaffolder edits — the only requests the scaffolder applies. */
21
21
  type AdditivePolicyEdit = ScaffoldPolicyEdit | WireRlsEdit;
22
22
  /**
23
- * Destructive scaffolder requests — never applied. Rewriting an existing `when`
24
- * body changes evaluation semantics silently, so it is refused (STOP condition
25
- * in plan 025); carried as data so the editor can describe the request.
26
- */
23
+ * Destructive scaffolder requests — never applied. Rewriting an existing `when`
24
+ * body changes evaluation semantics silently, so it is refused (STOP condition
25
+ * in plan 025); carried as data so the editor can describe the request.
26
+ */
27
27
  interface DestructivePolicyEdit {
28
28
  readonly exportName?: string;
29
29
  readonly kind: "rewritePolicyWhen";
@@ -32,10 +32,10 @@ interface DestructivePolicyEdit {
32
32
  /** Any request the scaffolder can receive. */
33
33
  type PolicyEdit = AdditivePolicyEdit | DestructivePolicyEdit;
34
34
  /**
35
- * Classify a scaffolder request. Additive requests ({@link AdditivePolicyEdit})
36
- * apply directly; everything else (rewriting an existing predicate) changes
37
- * evaluation semantics and is destructive.
38
- */
35
+ * Classify a scaffolder request. Additive requests ({@link AdditivePolicyEdit})
36
+ * apply directly; everything else (rewriting an existing predicate) changes
37
+ * evaluation semantics and is destructive.
38
+ */
39
39
  declare const classifyPolicyEdit: (edit: PolicyEdit) => "additive" | "destructive";
40
40
  /** Failure reasons a scaffolder request can report. */
41
41
  type PolicyScaffoldFailureReason = "already-wired" | "destructive" | "invalid-identifier" | "unknown-procedure" | "unsupported-procedure-shape";
@@ -57,18 +57,18 @@ type WireResult = {
57
57
  text: string;
58
58
  };
59
59
  /**
60
- * Generate the source of a new policy/role/permission stub file. The `when`
61
- * predicate is a `() => false` skeleton with a TODO — the scaffolder never
62
- * authors real logic, the developer fills it in. Pure (no I/O); the handler
63
- * writes the returned source and refuses to overwrite an existing file.
64
- */
60
+ * Generate the source of a new policy/role/permission stub file. The `when`
61
+ * predicate is a `() => false` skeleton with a TODO — the scaffolder never
62
+ * authors real logic, the developer fills it in. Pure (no I/O); the handler
63
+ * writes the returned source and refuses to overwrite an existing file.
64
+ */
65
65
  declare const scaffoldPolicyFile: (edit: ScaffoldPolicyEdit) => ScaffoldFileResult;
66
66
  /**
67
- * Append `.use(rls(&lt;policies>))` to a procedure's builder chain, preserving the
68
- * terminal `.query(handler)` (and its handler body) byte-for-byte. Only the
69
- * **builder** form can be wired; the bare-factory form (`query({ handler })`)
70
- * has no chain and is reported `unsupported-procedure-shape` so the editor can
71
- * tell the developer to convert it rather than silently rewriting their code.
72
- */
67
+ * Append `.use(rls(&lt;policies>))` to a procedure's builder chain, preserving the
68
+ * terminal `.query(handler)` (and its handler body) byte-for-byte. Only the
69
+ * **builder** form can be wired; the bare-factory form (`query({ handler })`)
70
+ * has no chain and is reported `unsupported-procedure-shape` so the editor can
71
+ * tell the developer to convert it rather than silently rewriting their code.
72
+ */
73
73
  declare const wireRlsIntoProcedure: (source: string, edit: WireRlsEdit) => WireResult;
74
74
  export { AdditivePolicyEdit as A, DestructivePolicyEdit as D, PolicyEdit as P, ScaffoldPolicyEdit as S, WireRlsEdit as W, PolicyScaffoldFailureReason as a, ScaffoldFileResult as b, WireResult as c, classifyPolicyEdit as d, scaffoldPolicyFile as s, wireRlsIntoProcedure as w };
@@ -2,9 +2,9 @@
2
2
  interface ScaffoldPolicyEdit {
3
3
  readonly kind: "scaffoldPolicy";
4
4
  /**
5
- * Base name for the generated file and its exported policy-set identifier,
6
- * e.g. `invoices` → `lunora/invoices.policies.ts` exporting `invoicesPolicies`.
7
- */
5
+ * Base name for the generated file and its exported policy-set identifier,
6
+ * e.g. `invoices` → `lunora/invoices.policies.ts` exporting `invoicesPolicies`.
7
+ */
8
8
  readonly name: string;
9
9
  /** Logical table the scaffolded policy guards (used in the stub body). */
10
10
  readonly table: string;
@@ -20,10 +20,10 @@ interface WireRlsEdit {
20
20
  /** Additive scaffolder edits — the only requests the scaffolder applies. */
21
21
  type AdditivePolicyEdit = ScaffoldPolicyEdit | WireRlsEdit;
22
22
  /**
23
- * Destructive scaffolder requests — never applied. Rewriting an existing `when`
24
- * body changes evaluation semantics silently, so it is refused (STOP condition
25
- * in plan 025); carried as data so the editor can describe the request.
26
- */
23
+ * Destructive scaffolder requests — never applied. Rewriting an existing `when`
24
+ * body changes evaluation semantics silently, so it is refused (STOP condition
25
+ * in plan 025); carried as data so the editor can describe the request.
26
+ */
27
27
  interface DestructivePolicyEdit {
28
28
  readonly exportName?: string;
29
29
  readonly kind: "rewritePolicyWhen";
@@ -32,10 +32,10 @@ interface DestructivePolicyEdit {
32
32
  /** Any request the scaffolder can receive. */
33
33
  type PolicyEdit = AdditivePolicyEdit | DestructivePolicyEdit;
34
34
  /**
35
- * Classify a scaffolder request. Additive requests ({@link AdditivePolicyEdit})
36
- * apply directly; everything else (rewriting an existing predicate) changes
37
- * evaluation semantics and is destructive.
38
- */
35
+ * Classify a scaffolder request. Additive requests ({@link AdditivePolicyEdit})
36
+ * apply directly; everything else (rewriting an existing predicate) changes
37
+ * evaluation semantics and is destructive.
38
+ */
39
39
  declare const classifyPolicyEdit: (edit: PolicyEdit) => "additive" | "destructive";
40
40
  /** Failure reasons a scaffolder request can report. */
41
41
  type PolicyScaffoldFailureReason = "already-wired" | "destructive" | "invalid-identifier" | "unknown-procedure" | "unsupported-procedure-shape";
@@ -57,18 +57,18 @@ type WireResult = {
57
57
  text: string;
58
58
  };
59
59
  /**
60
- * Generate the source of a new policy/role/permission stub file. The `when`
61
- * predicate is a `() => false` skeleton with a TODO — the scaffolder never
62
- * authors real logic, the developer fills it in. Pure (no I/O); the handler
63
- * writes the returned source and refuses to overwrite an existing file.
64
- */
60
+ * Generate the source of a new policy/role/permission stub file. The `when`
61
+ * predicate is a `() => false` skeleton with a TODO — the scaffolder never
62
+ * authors real logic, the developer fills it in. Pure (no I/O); the handler
63
+ * writes the returned source and refuses to overwrite an existing file.
64
+ */
65
65
  declare const scaffoldPolicyFile: (edit: ScaffoldPolicyEdit) => ScaffoldFileResult;
66
66
  /**
67
- * Append `.use(rls(&lt;policies>))` to a procedure's builder chain, preserving the
68
- * terminal `.query(handler)` (and its handler body) byte-for-byte. Only the
69
- * **builder** form can be wired; the bare-factory form (`query({ handler })`)
70
- * has no chain and is reported `unsupported-procedure-shape` so the editor can
71
- * tell the developer to convert it rather than silently rewriting their code.
72
- */
67
+ * Append `.use(rls(&lt;policies>))` to a procedure's builder chain, preserving the
68
+ * terminal `.query(handler)` (and its handler body) byte-for-byte. Only the
69
+ * **builder** form can be wired; the bare-factory form (`query({ handler })`)
70
+ * has no chain and is reported `unsupported-procedure-shape` so the editor can
71
+ * tell the developer to convert it rather than silently rewriting their code.
72
+ */
73
73
  declare const wireRlsIntoProcedure: (source: string, edit: WireRlsEdit) => WireResult;
74
74
  export { AdditivePolicyEdit as A, DestructivePolicyEdit as D, PolicyEdit as P, ScaffoldPolicyEdit as S, WireRlsEdit as W, PolicyScaffoldFailureReason as a, ScaffoldFileResult as b, WireResult as c, classifyPolicyEdit as d, scaffoldPolicyFile as s, wireRlsIntoProcedure as w };