@beignet/cli 0.0.44 → 0.0.46

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 (81) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +78 -16
  3. package/dist/choices.d.ts +5 -1
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +12 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/db.d.ts +4 -0
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +12 -6
  10. package/dist/db.js.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +13 -13
  13. package/dist/index.js.map +1 -1
  14. package/dist/inspect.d.ts.map +1 -1
  15. package/dist/inspect.js +598 -62
  16. package/dist/inspect.js.map +1 -1
  17. package/dist/lib.d.ts +4 -1
  18. package/dist/lib.d.ts.map +1 -1
  19. package/dist/lib.js +1 -1
  20. package/dist/lib.js.map +1 -1
  21. package/dist/mcp.d.ts +2 -1
  22. package/dist/mcp.d.ts.map +1 -1
  23. package/dist/mcp.js +356 -5
  24. package/dist/mcp.js.map +1 -1
  25. package/dist/operational-lifecycle.d.ts +12 -0
  26. package/dist/operational-lifecycle.d.ts.map +1 -0
  27. package/dist/operational-lifecycle.js +39 -0
  28. package/dist/operational-lifecycle.js.map +1 -0
  29. package/dist/operational-path.d.ts +3 -0
  30. package/dist/operational-path.d.ts.map +1 -0
  31. package/dist/operational-path.js +26 -0
  32. package/dist/operational-path.js.map +1 -0
  33. package/dist/operational-process.d.ts +78 -0
  34. package/dist/operational-process.d.ts.map +1 -0
  35. package/dist/operational-process.js +228 -0
  36. package/dist/operational-process.js.map +1 -0
  37. package/dist/operational-runner.d.ts +2 -0
  38. package/dist/operational-runner.d.ts.map +1 -0
  39. package/dist/operational-runner.js +168 -0
  40. package/dist/operational-runner.js.map +1 -0
  41. package/dist/outbox.d.ts +14 -6
  42. package/dist/outbox.d.ts.map +1 -1
  43. package/dist/outbox.js +140 -124
  44. package/dist/outbox.js.map +1 -1
  45. package/dist/schedule.d.ts +2 -1
  46. package/dist/schedule.d.ts.map +1 -1
  47. package/dist/schedule.js +79 -81
  48. package/dist/schedule.js.map +1 -1
  49. package/dist/task.d.ts +2 -1
  50. package/dist/task.d.ts.map +1 -1
  51. package/dist/task.js +57 -61
  52. package/dist/task.js.map +1 -1
  53. package/dist/templates/agents.d.ts.map +1 -1
  54. package/dist/templates/agents.js +32 -8
  55. package/dist/templates/agents.js.map +1 -1
  56. package/dist/templates/server.d.ts.map +1 -1
  57. package/dist/templates/server.js +9 -2
  58. package/dist/templates/server.js.map +1 -1
  59. package/dist/templates/shadcn.d.ts.map +1 -1
  60. package/dist/templates/shadcn.js +3 -1
  61. package/dist/templates/shadcn.js.map +1 -1
  62. package/dist/templates/shared.js +1 -1
  63. package/package.json +2 -2
  64. package/skills/app-structure/SKILL.md +26 -7
  65. package/src/choices.ts +22 -0
  66. package/src/db.ts +12 -6
  67. package/src/index.ts +19 -16
  68. package/src/inspect.ts +844 -67
  69. package/src/lib.ts +27 -1
  70. package/src/mcp.ts +459 -4
  71. package/src/operational-lifecycle.ts +56 -0
  72. package/src/operational-path.ts +35 -0
  73. package/src/operational-process.ts +454 -0
  74. package/src/operational-runner.ts +221 -0
  75. package/src/outbox.ts +154 -128
  76. package/src/schedule.ts +84 -89
  77. package/src/task.ts +58 -62
  78. package/src/templates/agents.ts +32 -8
  79. package/src/templates/server.ts +9 -2
  80. package/src/templates/shadcn.ts +3 -1
  81. package/src/templates/shared.ts +1 -1
package/src/task.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readFile, stat } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import type { TaskDef, TaskRunContextArgs } from "@beignet/core/tasks";
4
4
  import { createJiti } from "jiti";
@@ -8,6 +8,8 @@ import {
8
8
  type OperationalErrorReportingContext,
9
9
  reportOperationalFailure,
10
10
  } from "./operational-error-reporting.js";
11
+ import { runWithOperationalCleanup } from "./operational-lifecycle.js";
12
+ import { resolveAppOperationalModulePath } from "./operational-path.js";
11
13
 
12
14
  /**
13
15
  * Options for running an app-owned operational task.
@@ -15,7 +17,8 @@ import {
15
17
  export type RunAppTaskOptions = {
16
18
  name: string;
17
19
  cwd?: string;
18
- input?: string | Record<string, unknown>;
20
+ /** Pre-parsed input validated by the registered task schema. */
21
+ input?: unknown;
19
22
  tenant?: string;
20
23
  modulePath?: string;
21
24
  };
@@ -49,7 +52,7 @@ export async function runAppTask(
49
52
  const cwd = path.resolve(options.cwd ?? process.cwd());
50
53
  const config = await loadBeignetConfig(cwd);
51
54
  const modulePath = normalizePath(options.modulePath ?? config.paths.tasks);
52
- const rawInput = parseTaskInputFlag(options.input);
55
+ const rawInput = options.input === undefined ? {} : options.input;
53
56
  const startedAt = performance.now();
54
57
  const taskModule = await loadTaskModule(cwd, modulePath);
55
58
  const task = findTask(taskModule.tasks, options.name, modulePath);
@@ -67,51 +70,56 @@ export async function runAppTask(
67
70
  ? await taskModule.createTaskContext(contextArgs)
68
71
  : {};
69
72
 
70
- try {
71
- let output: unknown;
72
- try {
73
- output = await runTask(task, {
74
- input: parsedInput,
75
- ctx,
76
- });
77
- } catch (error) {
78
- await reportOperationalFailure({
79
- ctx: toOperationalContext(ctx),
80
- error,
81
- reportOptions: {
82
- level: "error",
83
- mechanism: "beignet.task.cli",
84
- handled: false,
85
- tags: {
86
- "beignet.kind": "task",
87
- "beignet.task": task.name,
88
- },
89
- contexts: {
90
- task: {
91
- name: task.name,
92
- tenant: options.tenant ?? null,
93
- source: "beignet-cli",
73
+ return runWithOperationalCleanup({
74
+ operation: `Task "${task.name}"`,
75
+ cleanupLabel: "stopTaskContext",
76
+ run: async () => {
77
+ let output: unknown;
78
+ try {
79
+ output = await runTask(task, {
80
+ input: parsedInput,
81
+ ctx,
82
+ });
83
+ } catch (error) {
84
+ await reportOperationalFailure({
85
+ ctx: toOperationalContext(ctx),
86
+ error,
87
+ reportOptions: {
88
+ level: "error",
89
+ mechanism: "beignet.task.cli",
90
+ handled: false,
91
+ tags: {
92
+ "beignet.kind": "task",
93
+ "beignet.task": task.name,
94
+ },
95
+ contexts: {
96
+ task: {
97
+ name: task.name,
98
+ tenant: options.tenant ?? null,
99
+ source: "beignet-cli",
100
+ },
94
101
  },
95
102
  },
96
- },
97
- });
98
- throw error;
99
- }
100
-
101
- return {
102
- schemaVersion: 1,
103
- name: task.name,
104
- cwd,
105
- modulePath,
106
- input: parsedInput,
107
- ...(options.tenant !== undefined ? { tenant: options.tenant } : {}),
108
- output,
109
- durationMs: Math.round(performance.now() - startedAt),
110
- };
111
- } finally {
112
- await flushOperationalErrorReporter(toOperationalContext(ctx));
113
- await taskModule.stopTaskContext?.(ctx, contextArgs);
114
- }
103
+ });
104
+ throw error;
105
+ }
106
+
107
+ return {
108
+ schemaVersion: 1,
109
+ name: task.name,
110
+ cwd,
111
+ modulePath,
112
+ input: parsedInput,
113
+ ...(options.tenant !== undefined ? { tenant: options.tenant } : {}),
114
+ output,
115
+ durationMs: Math.round(performance.now() - startedAt),
116
+ };
117
+ },
118
+ cleanup: async () => {
119
+ await flushOperationalErrorReporter(toOperationalContext(ctx));
120
+ await taskModule.stopTaskContext?.(ctx, contextArgs);
121
+ },
122
+ });
115
123
  }
116
124
 
117
125
  function toOperationalContext(ctx: unknown): OperationalErrorReportingContext {
@@ -120,27 +128,15 @@ function toOperationalContext(ctx: unknown): OperationalErrorReportingContext {
120
128
  : {};
121
129
  }
122
130
 
123
- function parseTaskInputFlag(input: RunAppTaskOptions["input"]): unknown {
124
- if (input === undefined) return {};
125
- if (typeof input !== "string") return input;
126
-
127
- try {
128
- return JSON.parse(input) as unknown;
129
- } catch (error) {
130
- throw new Error(
131
- `Invalid --input JSON: ${error instanceof Error ? error.message : String(error)}`,
132
- );
133
- }
134
- }
135
-
136
131
  async function loadTaskModule(
137
132
  cwd: string,
138
133
  modulePath: string,
139
134
  ): Promise<TaskModule> {
140
- const absolutePath = path.join(cwd, modulePath);
135
+ let absolutePath: string;
141
136
  try {
142
- await stat(absolutePath);
143
- } catch {
137
+ absolutePath = await resolveAppOperationalModulePath(cwd, modulePath);
138
+ } catch (error) {
139
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
144
140
  throw new Error(
145
141
  `Could not find app task registry at ${modulePath}. Create it with beignet make task <feature.name> or configure paths.tasks in beignet.config.ts.`,
146
142
  );
@@ -56,6 +56,10 @@ is a silent failure — the file exists but never runs:
56
56
  in \`server/outbox.ts\`.
57
57
  - Listeners must be added to the \`listeners\` array in \`server/listeners.ts\`
58
58
  and wired through a \`registerListeners(...)\` call in server provider wiring.
59
+ - Apps using Inngest must also spread feature job registries into
60
+ \`inngestJobs\` in \`server/inngest.ts\`.
61
+ - Apps using runtime integrity must list workflow registries in
62
+ \`defineRuntimeManifest({...})\`.
59
63
 
60
64
  The starter ships no workflow registries — generators create them on first
61
65
  use, so their absence is fine. \`${cli} make event\`, \`${cli} make job\`,
@@ -69,7 +73,14 @@ manual registry instruction in the error.
69
73
  with \`${cli} doctor --fix --dry-run\`, then apply the returned plan with
70
74
  \`${cli} doctor --fix --plan <plan-id>\` and optional
71
75
  \`--only <operation-ids>\`. Plain \`${cli} doctor --fix\` remains the
72
- apply-all shortcut.
76
+ apply-all shortcut. Doctor can append fully unregistered artifacts to
77
+ existing central, Inngest, and runtime-manifest arrays when their imports and
78
+ array anchors are unambiguous. It also syncs missing default Beignet
79
+ provider-table exports when \`infra/db/schema/beignet.ts\` is missing or still
80
+ matches \`${cli} db schema sync\` output and the schema index has no custom
81
+ named re-export for that file. Run \`${cli} db generate\` and
82
+ \`${cli} db migrate\` after accepting that source repair. Custom or ambiguous
83
+ code stays diagnostic-only.
73
84
 
74
85
  ## Prefer generators
75
86
 
@@ -81,7 +92,9 @@ events, listener registration, jobs, and outbox wiring. After changing the Drizz
81
92
  \`infra/db/schema/\`, run \`${cli} db generate\` then \`${cli} db migrate\`.
82
93
  When MCP is available, use \`db_schema_sync\` for Beignet provider table
83
94
  re-exports and \`db\` for \`generate\`, \`migrate\`, \`seed\`, or \`reset\`
84
- instead of falling back to a shell.
95
+ instead of falling back to a shell. Use \`task_run\`, \`schedule_run\`,
96
+ \`outbox_inspect\`, and \`outbox_run\` for registered operational workflows
97
+ and outbox recovery.
85
98
 
86
99
  ## The framework already solves these
87
100
 
@@ -94,7 +107,7 @@ apps have reimplemented by accident:
94
107
  | Ports outside a request — auth callbacks, module-level helpers | \`const { ports } = await getServer()\` (dynamic \`import("@/server")\` breaks module cycles). Do not construct parallel provider clients or fall back to \`console.*\` when \`ports.logger\` exists. |
95
108
  | Routes that cannot be contracts — webhooks, third-party callbacks, streaming | \`createWebhookRoute\`, \`createPaymentWebhookRoute\`, \`createScheduleRoute\`, \`createOutboxDrainRoute\` from \`@beignet/next\`; \`server.rawRoute(...)\` for anything else. All run the hooks pipeline — never hand-enforce rate limits in a route body. |
96
109
  | Rate limiting or idempotency on a route | Declare \`metadata.rateLimit\` / \`metadata.idempotency\` on the contract (or the \`pipeline\` option on raw routes); hooks enforce it. |
97
- | Tenant-owned repository access | Pass \`TenantScope\` through app-facing repository methods and unwrap it with \`tenantScopeId(scope)\` in adapters. Raw provider-correlation lookups such as webhook customer IDs are not tenant authorization. |
110
+ | Tenant-owned repository access | Resolve the request tenant from membership-backed app state or provider claims the app explicitly treats as authoritative and current; never trust caller-supplied tenant IDs or unverified session fields. Pass \`TenantScope\` through app-facing repository methods and unwrap it with \`tenantScopeId(scope)\` in adapters. Raw provider-correlation lookups such as webhook customer IDs are not tenant authorization. |
98
111
  | Route-level tests that exercise real hooks | \`createTestApp\` / \`createTestRequester\` from \`@beignet/web/testing\`. Bind the rate-limit and idempotency ports in the test app when asserting 429s or replay. |
99
112
  | Environment configuration | \`lib/env.ts\` (\`createEnv\`), never ad-hoc \`process.env\` reads in app code. |
100
113
  | The same lookup runs several times in one request — context, policy, use case | Wrap the repository read in \`createMemo(...)\` from \`@beignet/core/memo\` where the adapter is wired in infra. The cache lives for exactly one request; pair mutations with \`.invalidate(...)\`. Never hand-roll per-request caches. |
@@ -111,7 +124,7 @@ ${cli} check
111
124
  It runs \`${cli} lint\` (Beignet's dependency-direction lint),
112
125
  \`${cli} doctor --strict\`, and the app's \`lint\` (Biome), \`typecheck\`, and
113
126
  \`test\` scripts in one pass, reporting every failure. Add \`--fix\` to apply
114
- doctor's low-risk registration fixes first. Use \`${format}\` to apply
127
+ doctor's eligible low-risk fixes first. Use \`${format}\` to apply
115
128
  formatting. The individual commands (\`${lint}\`, \`${cli} lint\`,
116
129
  \`${cli} doctor --strict\`, \`${test}\`, \`${typecheck}\`) still work when you
117
130
  need one check alone.
@@ -159,10 +172,13 @@ skill-loading block.
159
172
  ## MCP server
160
173
 
161
174
  \`.mcp.json\` registers the app-local \`@beignet/cli\` bin at
162
- \`./node_modules/.bin/beignet mcp\`, which exposes the app map, validation, and
163
- generators as structured tools named exactly: \`app_map\`, \`explain\`,
164
- \`check\`, \`db\`, \`db_schema_sync\`, \`routes\`, \`doctor\`,
175
+ \`./node_modules/.bin/beignet mcp\`, which exposes app context, validation,
176
+ generation, and operations as structured tools named exactly: \`app_map\`,
177
+ \`explain\`, \`check\`, \`db\`, \`db_schema_sync\`, \`task_run\`,
178
+ \`schedule_run\`, \`outbox_inspect\`, \`outbox_run\`, \`routes\`, \`doctor\`,
165
179
  \`doctor_fix_plan\`, \`doctor_fix\`, \`lint\`, \`make\`, \`provider_add\`.
180
+ It also publishes \`beignet://app/guidance\` and focused
181
+ \`beignet://app/features/{feature}\` resources.
166
182
  Use \`doctor_fix_plan\` to inspect
167
183
  stable operation IDs, hashes, exact patches, and current diagnostics before
168
184
  passing its \`planId\` and optional \`fixIds\` to \`doctor_fix\`. Guarded
@@ -180,7 +196,15 @@ with \`generate\` and \`migrate\`; database output is bounded, commands time
180
196
  out, and cancellation stops the active process tree. Treat \`seed\` and
181
197
  especially \`reset\` as app-owned mutations. Lifecycle \`dryRun\` validates
182
198
  and reports the script without executing it; it does not simulate SQL or data
183
- changes. Clients that do not read \`.mcp.json\` can use the same command from
199
+ changes. Use \`task_run\` and \`schedule_run\` for registered operational
200
+ workflows, \`outbox_inspect\` for read-only \`list\` and \`show\`, and
201
+ \`outbox_run\` for \`drain\`, \`requeue\`, \`purge\`, or \`prune\`; purge and
202
+ prune support \`dryRun\`. Operational commands run in isolated process trees
203
+ with bounded results, cancellation, and timeouts. Optional \`module\` overrides
204
+ must remain inside the app root where the MCP server started. Cancellation and
205
+ timeouts cannot roll back side effects that already completed. Inspect app
206
+ state before retrying an interrupted operation or one that reports a cleanup
207
+ failure. Clients that do not read \`.mcp.json\` can use the same command from
184
208
  the app root; use \`${cli} mcp\` only for terminal debugging.
185
209
  `;
186
210
  }
@@ -601,7 +601,9 @@ export type TenantResolutionInput = {
601
601
  export function resolveRequestTenant({
602
602
  auth,
603
603
  }: TenantResolutionInput): ActivityTenant | undefined {
604
- const tenantId = auth ? tenantIdFromAuth(auth) : undefined;
604
+ const tenantId = auth
605
+ ? tenantIdFromAuthoritativeAuthClaims(auth)
606
+ : undefined;
605
607
 
606
608
  return tenantId ? createTenant(tenantId) : undefined;
607
609
  }
@@ -614,7 +616,12 @@ export function resolveServiceTenant(
614
616
  return normalizedTenantId ? createTenant(normalizedTenantId) : undefined;
615
617
  }
616
618
 
617
- function tenantIdFromAuth(auth: AuthSession) {
619
+ /**
620
+ * Resolve only provider-issued claims that the application treats as
621
+ * authoritative. Multi-tenant apps must replace this starter seam with a
622
+ * membership-backed lookup before authorizing tenant-owned resources.
623
+ */
624
+ function tenantIdFromAuthoritativeAuthClaims(auth: AuthSession) {
618
625
  return (
619
626
  stringProperty(auth.session, "tenantId") ??
620
627
  stringProperty(auth.session, "organizationId") ??
@@ -186,7 +186,9 @@ const appGlobalsCss = `@import "tailwindcss";
186
186
  }
187
187
  `;
188
188
 
189
- const uiButton = `import { cva, type VariantProps } from "class-variance-authority";
189
+ const uiButton = `"use client";
190
+
191
+ import { cva, type VariantProps } from "class-variance-authority";
190
192
  import { Slot } from "radix-ui";
191
193
  import type * as React from "react";
192
194
 
@@ -44,7 +44,7 @@ export const externalVersions = {
44
44
  tanstackReactQuery: "^5.100.7",
45
45
  hookformResolvers: "^5.0.0",
46
46
  reactHookForm: "^7.74.0",
47
- betterAuth: "1.6.20",
47
+ betterAuth: "1.6.25",
48
48
  bullmq: "^5.0.0",
49
49
  drizzleKit: "^0.31.10",
50
50
  drizzleOrm: "^0.45.2",