@lunora/cli 1.0.0-alpha.74 → 1.0.0-alpha.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './packem_shared/COMMANDS-0BD3K1pb.mjs';
2
+ import { runCli } from './packem_shared/COMMANDS-DW8lQmuW.mjs';
3
3
 
4
4
  try {
5
5
  const code = await runCli();
package/dist/index.d.mts CHANGED
@@ -736,7 +736,7 @@ interface AddCommandResult {
736
736
  * sub-prompt or alias; every other value IS the registry item name applied
737
737
  * directly (`storage` → the `storage` registry item, etc.).
738
738
  */
739
- type StackFeature = "auth" | "backup" | "crons" | "email" | "presence" | "ratelimit" | "storage";
739
+ type StackFeature = "ai" | "auth" | "backup" | "browser" | "cloudflare-access" | "crons" | "email" | "flags" | "hyperdrive" | "payment" | "presence" | "queue" | "storage" | "workflow";
740
740
  /** Customize a resolved manifest before it is written (e.g. inject the chosen R2 bucket name). */
741
741
  type OfferTransformManifest = (manifest: RegistryManifest) => RegistryManifest;
742
742
  /**
@@ -792,7 +792,9 @@ interface OfferDeps {
792
792
  }) => Promise<string>;
793
793
  }
794
794
  /**
795
- * Offer the stack features (auth, email, storage, rate limiting, crons,
795
+ * Offer the stack features (ai, auth, backup, browser, cloudflare-access,
796
+ * crons, email, flags, hyperdrive, payment, presence, queue, storage,
797
+ * workflow) in ONE multi-select after a successful scaffold. Auth,
796
798
  * presence, backups) in ONE multi-select after a successful scaffold. Auth,
797
799
  * email, and storage run a follow-up prompt (provider / destination / bucket
798
800
  * name); every other feature value is applied as its registry item directly.
@@ -806,8 +808,8 @@ type Template = "analog" | "astro" | "next" | "nuxt" | "react-router" | "standal
806
808
  interface InitCommandOptions {
807
809
  /**
808
810
  * Add features non-interactively after scaffolding (the `--add` flag): a
809
- * comma-separated list of `auth | email | storage | ratelimit | crons |
810
- * presence | backup`. Bypasses the interactive multi-select and sub-prompts —
811
+ * comma-separated list of `ai | auth | backup | browser | cloudflare-access | crons | email | flags | hyperdrive | payment | presence | queue | storage | workflow`.
812
+ * Bypasses the interactive multi-select and sub-prompts —
811
813
  * each named feature is applied with its shipped defaults.
812
814
  */
813
815
  add?: string;
package/dist/index.d.ts CHANGED
@@ -736,7 +736,7 @@ interface AddCommandResult {
736
736
  * sub-prompt or alias; every other value IS the registry item name applied
737
737
  * directly (`storage` → the `storage` registry item, etc.).
738
738
  */
739
- type StackFeature = "auth" | "backup" | "crons" | "email" | "presence" | "ratelimit" | "storage";
739
+ type StackFeature = "ai" | "auth" | "backup" | "browser" | "cloudflare-access" | "crons" | "email" | "flags" | "hyperdrive" | "payment" | "presence" | "queue" | "storage" | "workflow";
740
740
  /** Customize a resolved manifest before it is written (e.g. inject the chosen R2 bucket name). */
741
741
  type OfferTransformManifest = (manifest: RegistryManifest) => RegistryManifest;
742
742
  /**
@@ -792,7 +792,9 @@ interface OfferDeps {
792
792
  }) => Promise<string>;
793
793
  }
794
794
  /**
795
- * Offer the stack features (auth, email, storage, rate limiting, crons,
795
+ * Offer the stack features (ai, auth, backup, browser, cloudflare-access,
796
+ * crons, email, flags, hyperdrive, payment, presence, queue, storage,
797
+ * workflow) in ONE multi-select after a successful scaffold. Auth,
796
798
  * presence, backups) in ONE multi-select after a successful scaffold. Auth,
797
799
  * email, and storage run a follow-up prompt (provider / destination / bucket
798
800
  * name); every other feature value is applied as its registry item directly.
@@ -806,8 +808,8 @@ type Template = "analog" | "astro" | "next" | "nuxt" | "react-router" | "standal
806
808
  interface InitCommandOptions {
807
809
  /**
808
810
  * Add features non-interactively after scaffolding (the `--add` flag): a
809
- * comma-separated list of `auth | email | storage | ratelimit | crons |
810
- * presence | backup`. Bypasses the interactive multi-select and sub-prompts —
811
+ * comma-separated list of `ai | auth | backup | browser | cloudflare-access | crons | email | flags | hyperdrive | payment | presence | queue | storage | workflow`.
812
+ * Bypasses the interactive multi-select and sub-prompts —
811
813
  * each named feature is applied with its shipped defaults.
812
814
  */
813
815
  add?: string;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-0BD3K1pb.mjs';
1
+ export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-DW8lQmuW.mjs';
2
2
  export { runCodegenCommand } from './packem_chunks/runCodegenCommand.mjs';
3
3
  export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-CRQmmBZM.mjs';
4
4
  export { runDeployCommand } from './packem_chunks/runDeployCommand.mjs';
@@ -16,4 +16,4 @@ export { createRecordingSpawner, defaultSpawner } from './packem_shared/createRe
16
16
  export { default as parseManifest } from './packem_shared/parseManifest-Dbp-Q2q3.mjs';
17
17
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
18
18
  export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BS5ig822.mjs';
19
- export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-CF2YHeFa.mjs';
19
+ export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-ClEvcz3V.mjs';
@@ -4,8 +4,8 @@ import { join, basename } from '@visulima/path';
4
4
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
5
  import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
6
6
  import { t as tuiText, a as tuiSelect } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
7
- import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-HRI1N4Ao.mjs';
8
- import { r as runAddCommand } from '../packem_shared/commands-CF2YHeFa.mjs';
7
+ import { n as normalizeFeature, E as EMAIL_ITEM, s as sanitizeBucketName, d as deriveBucketName, p as promptBucketName, r as resolveTypedDestination, M as MAIL_DESTINATION_PROMPT, a as sanitizeDatabaseName, b as deriveDatabaseName, c as promptDatabaseName, D as DEFAULT_AUTH_ITEM, e as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS, w as withStorageBucketName, f as withMailDestination, g as withAuthDatabaseName } from '../packem_shared/storage-DEXpJkXh.mjs';
8
+ import { r as runAddCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
9
9
 
10
10
  const providerToItem = (provider) => {
11
11
  const value = provider.trim().toLowerCase();
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
- import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-CF2YHeFa.mjs';
2
+ import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
3
3
 
4
4
  const execute = defineHandler(({ argument, cwd, logger, options }) => {
5
5
  const subcommand = argument[0];
@@ -11,11 +11,11 @@ import { d as detectPackageManager, a as detectInstalledManagers, i as installAr
11
11
  import MagicString from 'magic-string';
12
12
  import { Project, SyntaxKind } from 'ts-morph';
13
13
  import { P as PromptCancelledError } from '../packem_shared/prompt-cancelled-APzX1Im-.mjs';
14
- import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-CF2YHeFa.mjs';
14
+ import { c as resolveTagVersions, d as resolveSourceRef, e as resolvePinnedSourceRef, f as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-ClEvcz3V.mjs';
15
15
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
16
16
  import { d as tuiMascot, e as tuiStep, f as tuiMoonrise, t as tuiText, g as tuiHeadline, h as tuiInfo, a as tuiSelect, b as tuiConfirm, w as withTuiSpinner, i as tuiNextSteps, j as tuiTasks, k as tuiMultiSelect, l as withTuiBadgeProgress } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
17
17
  import { logStep } from '../packem_shared/createLogger-CIWSHrTL.mjs';
18
- import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-HRI1N4Ao.mjs';
18
+ import { E as EMAIL_ITEM, p as promptBucketName, w as withStorageBucketName, M as MAIL_DESTINATION_PROMPT, r as resolveTypedDestination, f as withMailDestination, e as promptAuthProvider, c as promptDatabaseName, g as withAuthDatabaseName } from '../packem_shared/storage-DEXpJkXh.mjs';
19
19
  import dns from 'node:dns/promises';
20
20
 
21
21
  const GITHUB_CONTENT = `name: Deploy
@@ -343,13 +343,20 @@ ${LUNA_NAME}: ${LUNA_SIGNOFF}`);
343
343
  };
344
344
 
345
345
  const STACK_FEATURE_OPTIONS = [
346
+ { description: "LLMs via Workers AI (summarize, generate, stream)", label: "AI", value: "ai" },
346
347
  { description: "Sign-up / sign-in (asks which provider)", label: "Authentication", value: "auth" },
347
- { description: "Cloudflare Email Workers + a dev mail catcher", label: "Transactional email", value: "email" },
348
- { description: "Typed R2 buckets + signed URLs (@lunora/storage)", label: "File storage", value: "storage" },
349
- { description: "Token-bucket / sliding-window limits (@lunora/ratelimit)", label: "Rate limiting", value: "ratelimit" },
348
+ { description: "Snapshot + restore your Durable Object data", label: "Backups", value: "backup" },
349
+ { description: "Headless browser screenshots + PDFs", label: "Browser rendering", value: "browser" },
350
+ { description: "Zero Trust identity via Cloudflare Access", label: "Cloudflare Access", value: "cloudflare-access" },
350
351
  { description: "Scheduled jobs via Cron Triggers (@lunora/scheduler)", label: "Cron jobs", value: "crons" },
352
+ { description: "Cloudflare Email Workers + a dev mail catcher", label: "Transactional email", value: "email" },
353
+ { description: "OpenFeature feature flags (ctx.flags)", label: "Feature flags", value: "flags" },
354
+ { description: "External Postgres/MySQL via Hyperdrive", label: "Hyperdrive", value: "hyperdrive" },
355
+ { description: "Stripe-first payments (checkout, subscription, webhooks)", label: "Payments", value: "payment" },
351
356
  { description: "Live presence / who's-online over hibernated WebSockets", label: "Presence", value: "presence" },
352
- { description: "Snapshot + restore your Durable Object data", label: "Backups", value: "backup" }
357
+ { description: "Async message queues (push/pull consumers)", label: "Queues", value: "queue" },
358
+ { description: "Typed R2 buckets + signed URLs (@lunora/storage)", label: "File storage", value: "storage" },
359
+ { description: "Durable long-running workflows (step.do, sleep, branch)", label: "Workflows", value: "workflow" }
353
360
  ];
354
361
  const STACK_FEATURE_VALUES = STACK_FEATURE_OPTIONS.map((option) => option.value);
355
362
  const featureItem = (feature) => feature === "email" ? EMAIL_ITEM : feature;
@@ -401,7 +408,10 @@ const offerRegistryExtras = async (deps) => {
401
408
  return;
402
409
  }
403
410
  if (!deps.interactive) {
404
- deps.logger.info("tip: add features later with `lunora add <auth|email|storage|ratelimit|crons|presence|backup>`.");
411
+ deps.logger.info(
412
+ // eslint-disable-next-line no-secrets/no-secrets -- the pipe-separated feature list in this tip is a UI prompt, not a credential
413
+ "tip: add features later with `lunora add <ai|auth|backup|browser|cloudflare-access|crons|email|flags|hyperdrive|payment|presence|queue|storage|workflow>`."
414
+ );
405
415
  return;
406
416
  }
407
417
  const picked = await deps.multiSelect("Which features do you want to add?", STACK_FEATURE_OPTIONS, { defaults: [] });
@@ -1470,7 +1480,49 @@ const ADAPTERS = {
1470
1480
  };
1471
1481
  const isOverlayFramework = (value) => Object.hasOwn(ADAPTERS, value);
1472
1482
 
1473
- const LUNORA_SCHEMA = `import { defineSchema, defineTable, v } from "lunorash/server";
1483
+ const RATELIMIT_SCHEMA = `import type { Middleware } from "lunorash/server";
1484
+ import { defineSchemaExtension, defineTable, definePlugin, v } from "lunorash/server";
1485
+ import { createDbStore, RateLimiter } from "lunorash/ratelimit";
1486
+ import type { RateLimitConfigMap } from "lunorash/ratelimit";
1487
+
1488
+ export const limits = {
1489
+ default: { kind: "token bucket", period: 60_000, rate: 10 },
1490
+ } as const satisfies RateLimitConfigMap;
1491
+
1492
+ export type LimitName = keyof typeof limits;
1493
+
1494
+ export const makeRateLimiter = (ctx: { db: unknown }): RateLimiter<LimitName> =>
1495
+ new RateLimiter<LimitName>({
1496
+ config: limits,
1497
+ store: createDbStore({ db: ctx.db as never, table: "ratelimit_buckets" }),
1498
+ });
1499
+
1500
+ const middleware: Middleware<{ api?: Record<string, unknown>; db: unknown }, { api: Record<string, unknown>; db: unknown }> = ({ ctx, next }) =>
1501
+ next({
1502
+ ctx: {
1503
+ ...ctx,
1504
+ api: { ...ctx.api, ratelimit: makeRateLimiter(ctx) },
1505
+ },
1506
+ });
1507
+
1508
+ export const ratelimit = definePlugin("ratelimit", {
1509
+ extension: defineSchemaExtension("ratelimit", {
1510
+ tables: {
1511
+ buckets: defineTable({
1512
+ key: v.string(),
1513
+ value: v.number(),
1514
+ ts: v.number(),
1515
+ prev: v.optional(v.number()),
1516
+ })
1517
+ .index("by_key", ["key"])
1518
+ .externallyManaged(),
1519
+ },
1520
+ }),
1521
+ middleware,
1522
+ });
1523
+ `;
1524
+ const LUNORA_SCHEMA = `import { ratelimit } from "./ratelimit/schema.js";
1525
+ import { defineSchema, defineTable, v } from "lunorash/server";
1474
1526
 
1475
1527
  export default defineSchema({
1476
1528
  messages: defineTable({
@@ -1479,22 +1531,17 @@ export default defineSchema({
1479
1531
  })
1480
1532
  .shardBy("channelId")
1481
1533
  .index("by_channel", ["channelId"]),
1482
- });
1534
+ }).extend(ratelimit.extension);
1483
1535
  `;
1484
- const LUNORA_MESSAGES = `import { RateLimiter, rateLimit } from "@lunora/ratelimit";
1536
+ const LUNORA_MESSAGES = `import { RateLimiter, rateLimit, createDbStore } from "lunorash/ratelimit";
1485
1537
 
1486
1538
  import { mutation, query, v } from "#lunora/_generated/server.js";
1487
1539
 
1488
- /**
1489
- * One in-memory limiter so the public \`send\` mutation isn't an open flood target
1490
- * out of the box. The default store is in-memory (per-isolate, resets on
1491
- * eviction) — fine for a starter; run \`lunora add ratelimit\` for the durable,
1492
- * \`ctx.db\`-backed store when you ship to production.
1493
- */
1494
- const limiter = new RateLimiter({
1540
+ const limiter = (ctx: { db: unknown }) => new RateLimiter({
1495
1541
  config: {
1496
1542
  send: { kind: "token bucket", period: 60_000, rate: 30 },
1497
1543
  },
1544
+ store: createDbStore({ db: ctx.db as never, table: "ratelimit_buckets" }),
1498
1545
  });
1499
1546
 
1500
1547
  export const list = query.input({ channelId: v.string().meta({ schema: { maxLength: 256 } }), limit: v.optional(v.number()) }).query(async ({ args, ctx }) => {
@@ -1620,6 +1667,7 @@ const patchBaseViteConfig = (target, logger) => {
1620
1667
  const applyLunoraOverlay = async (options) => {
1621
1668
  const { adapter, distTag, logger, name, target } = options;
1622
1669
  const written = [];
1670
+ writeFile(target, join$1("lunora", "ratelimit", "schema.ts"), RATELIMIT_SCHEMA, written);
1623
1671
  writeFile(target, join$1("lunora", "schema.ts"), LUNORA_SCHEMA, written);
1624
1672
  writeFile(target, join$1("lunora", "messages.ts"), LUNORA_MESSAGES, written);
1625
1673
  writeFile(target, join$1("src", "server.ts"), SERVER_ENTRY, written);
@@ -10,8 +10,12 @@ import { tmpdir } from 'node:os';
10
10
  import { join } from 'node:path';
11
11
 
12
12
  const addCommand = {
13
- argument: { description: "Feature or registry item: auth | email | storage | crons | presence | ratelimit | backup | …", name: "feature", type: String },
14
- description: "Add a feature or registry item (auth, email, storage, crons, …) to the current Lunora project",
13
+ argument: {
14
+ description: "Feature or registry item: ai | auth | email | storage | crons | presence | queue | workflow | flags | backup | …",
15
+ name: "feature",
16
+ type: String
17
+ },
18
+ description: "Add a feature or registry item (ai, auth, email, storage, crons, …) to the current Lunora project",
15
19
  examples: [
16
20
  ["lunora add auth", "Add authentication (asks which provider)"],
17
21
  ["lunora add auth --provider clerk", "Add Clerk auth without prompting"],
@@ -446,7 +450,7 @@ const initCommand = {
446
450
  type: String
447
451
  },
448
452
  {
449
- description: "Add features non-interactively after scaffolding (comma-separated): auth | email | storage | ratelimit | crons | presence | backup",
453
+ description: "Add features non-interactively after scaffolding (comma-separated): ai | auth | backup | browser | cloudflare-access | crons | email | flags | hyperdrive | payment | presence | queue | storage | workflow",
450
454
  name: "add",
451
455
  type: String
452
456
  },
@@ -148,8 +148,8 @@ const resolveDepRange = (range) => {
148
148
  }
149
149
  return rest;
150
150
  };
151
- const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
152
- const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|runtime|server|values)(\/[^'"]*)?\1/gu;
151
+ const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/ratelimit", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
152
+ const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|ratelimit|runtime|server|values)(\/[^'"]*)?\1/gu;
153
153
  const projectUsesUmbrella = (projectRoot) => {
154
154
  const packageJsonPath = join(projectRoot, "package.json");
155
155
  if (!existsSync(packageJsonPath)) {
@@ -1,4 +1,4 @@
1
1
  import 'node:fs';
2
2
  import '@visulima/path';
3
- export { r as runAddCommand, a as runBuildIndexCommand, g as runListCommand, b as runRegistryViewCommand } from './commands-CF2YHeFa.mjs';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, g as runListCommand, b as runRegistryViewCommand } from './commands-ClEvcz3V.mjs';
4
4
  import './buildRegistryIndex-BS5ig822.mjs';
@@ -1,4 +1,4 @@
1
- import { s as setBindingField } from './commands-CF2YHeFa.mjs';
1
+ import { s as setBindingField } from './commands-ClEvcz3V.mjs';
2
2
 
3
3
  const INVALID_SLUG_CHARS = /[^a-z0-9]+/u;
4
4
  const toKebabSlug = (input, min, max) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.74",
3
+ "version": "1.0.0-alpha.76",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",