@intelligo-dev/cli 1.0.0-beta.1 → 1.0.0-beta.14

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 (223) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +102 -0
  3. package/dist/bin.js +122 -37
  4. package/dist/bin.js.map +1 -1
  5. package/dist/commands/add.d.ts +26 -1
  6. package/dist/commands/add.d.ts.map +1 -1
  7. package/dist/commands/add.js +70 -4
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/create-flow.d.ts +37 -0
  10. package/dist/commands/create-flow.d.ts.map +1 -0
  11. package/dist/commands/create-flow.js +212 -0
  12. package/dist/commands/create-flow.js.map +1 -0
  13. package/dist/commands/create.d.ts +27 -6
  14. package/dist/commands/create.d.ts.map +1 -1
  15. package/dist/commands/create.js +53 -13
  16. package/dist/commands/create.js.map +1 -1
  17. package/dist/commands/doctor.d.ts +14 -3
  18. package/dist/commands/doctor.d.ts.map +1 -1
  19. package/dist/commands/doctor.js +295 -37
  20. package/dist/commands/doctor.js.map +1 -1
  21. package/dist/commands/migrate-check.d.ts +73 -1
  22. package/dist/commands/migrate-check.d.ts.map +1 -1
  23. package/dist/commands/migrate-check.js +130 -5
  24. package/dist/commands/migrate-check.js.map +1 -1
  25. package/dist/commands/migrate.d.ts +97 -0
  26. package/dist/commands/migrate.d.ts.map +1 -0
  27. package/dist/commands/migrate.js +141 -0
  28. package/dist/commands/migrate.js.map +1 -0
  29. package/dist/commands/sync-messages.d.ts +37 -0
  30. package/dist/commands/sync-messages.d.ts.map +1 -0
  31. package/dist/commands/sync-messages.js +88 -0
  32. package/dist/commands/sync-messages.js.map +1 -0
  33. package/dist/commands/sync-scaffold.d.ts +36 -0
  34. package/dist/commands/sync-scaffold.d.ts.map +1 -0
  35. package/dist/commands/sync-scaffold.js +53 -0
  36. package/dist/commands/sync-scaffold.js.map +1 -0
  37. package/dist/commands/sync.d.ts +103 -0
  38. package/dist/commands/sync.d.ts.map +1 -0
  39. package/dist/commands/sync.js +361 -0
  40. package/dist/commands/sync.js.map +1 -0
  41. package/dist/commands/upgrade-check.d.ts +5 -5
  42. package/dist/commands/upgrade-check.d.ts.map +1 -1
  43. package/dist/commands/upgrade-check.js +22 -6
  44. package/dist/commands/upgrade-check.js.map +1 -1
  45. package/dist/env-files.d.ts +12 -0
  46. package/dist/env-files.d.ts.map +1 -0
  47. package/dist/env-files.js +29 -0
  48. package/dist/env-files.js.map +1 -0
  49. package/dist/index.d.ts +9 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +9 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/manifest.d.ts +36 -15
  54. package/dist/manifest.d.ts.map +1 -1
  55. package/dist/manifest.js +32 -16
  56. package/dist/manifest.js.map +1 -1
  57. package/dist/migrations.d.ts +3 -6
  58. package/dist/migrations.d.ts.map +1 -1
  59. package/dist/migrations.js +3 -6
  60. package/dist/migrations.js.map +1 -1
  61. package/dist/model-catalogue.d.ts +14 -0
  62. package/dist/model-catalogue.d.ts.map +1 -0
  63. package/dist/model-catalogue.js +34 -0
  64. package/dist/model-catalogue.js.map +1 -0
  65. package/dist/module-exports.d.ts +63 -0
  66. package/dist/module-exports.d.ts.map +1 -0
  67. package/dist/module-exports.js +231 -0
  68. package/dist/module-exports.js.map +1 -0
  69. package/dist/registry-bundle.d.ts +43 -0
  70. package/dist/registry-bundle.d.ts.map +1 -0
  71. package/dist/registry-bundle.js +74 -0
  72. package/dist/registry-bundle.js.map +1 -0
  73. package/dist/registry-items.d.ts +84 -0
  74. package/dist/registry-items.d.ts.map +1 -0
  75. package/dist/registry-items.js +187 -0
  76. package/dist/registry-items.js.map +1 -0
  77. package/package.json +36 -10
  78. package/src/bin.ts +316 -0
  79. package/src/commands/add.ts +255 -0
  80. package/src/commands/create-flow.ts +284 -0
  81. package/src/commands/create.ts +149 -0
  82. package/src/commands/doctor.ts +487 -0
  83. package/src/commands/migrate-check.ts +298 -0
  84. package/src/commands/migrate.ts +228 -0
  85. package/src/commands/sync-messages.ts +114 -0
  86. package/src/commands/sync-scaffold.ts +83 -0
  87. package/src/commands/sync.ts +534 -0
  88. package/src/commands/upgrade-check.ts +160 -0
  89. package/src/env-files.ts +31 -0
  90. package/src/index.ts +39 -0
  91. package/src/manifest.ts +147 -0
  92. package/src/migrations-dir.ts +25 -0
  93. package/src/migrations.ts +133 -0
  94. package/src/model-catalogue.ts +35 -0
  95. package/src/module-exports.ts +266 -0
  96. package/src/registry-bundle.ts +103 -0
  97. package/src/registry-items.ts +237 -0
  98. package/templates/admin-page/admin-page.tsx.tpl +2 -3
  99. package/templates/app-scaffold/assistant-route.ts.tpl +2 -2
  100. package/templates/app-scaffold/auth-route.ts.tpl +10 -0
  101. package/templates/app-scaffold/components.json.tpl +6 -2
  102. package/templates/app-scaffold/db-schema.ts.tpl +29 -0
  103. package/templates/app-scaffold/drizzle-journal.json.tpl +5 -0
  104. package/templates/app-scaffold/drizzle.config.ts.tpl +45 -0
  105. package/templates/app-scaffold/env.example.tpl +65 -1
  106. package/templates/app-scaffold/globals.css.tpl +172 -281
  107. package/templates/app-scaffold/i18n-request.ts.tpl +25 -0
  108. package/templates/app-scaffold/instrumentation.ts.tpl +21 -0
  109. package/templates/app-scaffold/intelligo.ts.tpl +95 -41
  110. package/templates/app-scaffold/layout.tsx.tpl +18 -1
  111. package/templates/app-scaffold/lib-utils.ts.tpl +1 -6
  112. package/templates/app-scaffold/next.config.mjs.tpl +2 -0
  113. package/templates/app-scaffold/package.json.tpl +31 -17
  114. package/templates/app-scaffold/page.tsx.tpl +16 -0
  115. package/templates/app-scaffold/plans.ts.tpl +32 -13
  116. package/templates/app-scaffold/{middleware.ts.tpl → proxy.ts.tpl} +6 -0
  117. package/templates/app-scaffold/sonner.tsx.tpl +42 -20
  118. package/templates/app-scaffold/stripe-webhook-route.ts.tpl +24 -0
  119. package/templates/app-scaffold/theme-provider.tsx.tpl +6 -4
  120. package/templates/app-scaffold/tsconfig.json.tpl +20 -4
  121. package/templates/app-scaffold/use-mobile.ts.tpl +19 -0
  122. package/templates/app-scaffold/workspace-bootstrap.ts.tpl +30 -0
  123. package/templates/maintenance/maintenance-route.ts.tpl +119 -0
  124. package/templates/manifest.json +75 -17
  125. package/templates/registry/ai-agent-activity.json +23 -0
  126. package/templates/registry/ai-agent-progress.json +21 -0
  127. package/templates/registry/ai-approval-card.json +27 -0
  128. package/templates/registry/ai-artifact.json +22 -0
  129. package/templates/registry/ai-branch.json +21 -0
  130. package/templates/registry/ai-citations.json +24 -0
  131. package/templates/registry/ai-code-block.json +23 -0
  132. package/templates/registry/ai-composer-menu.json +19 -0
  133. package/templates/registry/ai-file-diff.json +25 -0
  134. package/templates/registry/ai-image-generation.json +23 -0
  135. package/templates/registry/ai-markdown.json +23 -0
  136. package/templates/registry/ai-message-bubble.json +23 -0
  137. package/templates/registry/ai-message-scroller.json +22 -0
  138. package/templates/registry/ai-message.json +21 -0
  139. package/templates/registry/ai-motion.json +19 -0
  140. package/templates/registry/ai-prompt-input.json +28 -0
  141. package/templates/registry/ai-reasoning-text.json +22 -0
  142. package/templates/registry/ai-reasoning.json +22 -0
  143. package/templates/registry/ai-shimmer-text.json +19 -0
  144. package/templates/registry/ai-sidebar.json +25 -0
  145. package/templates/registry/ai-speech-input.json +21 -0
  146. package/templates/registry/ai-streaming-response.json +24 -0
  147. package/templates/registry/ai-suggestion.json +19 -0
  148. package/templates/registry/ai-todo-list.json +22 -0
  149. package/templates/registry/ai-tool-approval.json +26 -0
  150. package/templates/registry/ai-tool-result.json +25 -0
  151. package/templates/registry/alert-dialog.json +23 -0
  152. package/templates/registry/animated-list.json +21 -0
  153. package/templates/registry/app-shell.json +93 -0
  154. package/templates/registry/artifacts.json +82 -0
  155. package/templates/registry/attachment.json +22 -0
  156. package/templates/registry/auth-email-verification.json +39 -0
  157. package/templates/registry/auth-login.json +72 -0
  158. package/templates/registry/auth-password-reset.json +53 -0
  159. package/templates/registry/auth-signup.json +41 -0
  160. package/templates/registry/billing-settings.json +60 -0
  161. package/templates/registry/button.json +22 -0
  162. package/templates/registry/chat-eve.json +19 -0
  163. package/templates/registry/chat-panel.json +30 -0
  164. package/templates/registry/chat-share.json +42 -0
  165. package/templates/registry/chat-widget.json +34 -0
  166. package/templates/registry/chat.json +326 -0
  167. package/templates/registry/checkbox.json +22 -0
  168. package/templates/registry/checkout.json +40 -0
  169. package/templates/registry/collapsible.json +19 -0
  170. package/templates/registry/command.json +23 -0
  171. package/templates/registry/copy-button.json +21 -0
  172. package/templates/registry/dashboard.json +69 -0
  173. package/templates/registry/dialog.json +24 -0
  174. package/templates/registry/document-viewer.json +22 -0
  175. package/templates/registry/dropdown-menu.json +23 -0
  176. package/templates/registry/expandable-tabs.json +22 -0
  177. package/templates/registry/feature-gating.json +73 -0
  178. package/templates/registry/file-upload.json +24 -0
  179. package/templates/registry/hold-action-button.json +22 -0
  180. package/templates/registry/input-group.json +23 -0
  181. package/templates/registry/input.json +19 -0
  182. package/templates/registry/intelligo.json +187 -0
  183. package/templates/registry/invitation-accept.json +64 -0
  184. package/templates/registry/language-switcher.json +29 -0
  185. package/templates/registry/morphing-modal.json +24 -0
  186. package/templates/registry/notification-stack.json +24 -0
  187. package/templates/registry/notifications.json +87 -0
  188. package/templates/registry/onboarding.json +83 -0
  189. package/templates/registry/otp-input.json +22 -0
  190. package/templates/registry/page-header.json +15 -0
  191. package/templates/registry/payment-poll.json +52 -0
  192. package/templates/registry/popover-morph.json +22 -0
  193. package/templates/registry/popover.json +22 -0
  194. package/templates/registry/pricing.json +111 -0
  195. package/templates/registry/privacy-settings.json +65 -0
  196. package/templates/registry/profile-settings.json +68 -0
  197. package/templates/registry/progress.json +19 -0
  198. package/templates/registry/radio-group.json +22 -0
  199. package/templates/registry/registry.json +2945 -0
  200. package/templates/registry/route-error.json +65 -0
  201. package/templates/registry/select-morph.json +23 -0
  202. package/templates/registry/select.json +23 -0
  203. package/templates/registry/settings-shell.json +47 -0
  204. package/templates/registry/sheet.json +24 -0
  205. package/templates/registry/sidebar.json +28 -0
  206. package/templates/registry/smoke.json +34 -0
  207. package/templates/registry/spinner.json +16 -0
  208. package/templates/registry/stat-card.json +18 -0
  209. package/templates/registry/status-badge.json +18 -0
  210. package/templates/registry/switch.json +20 -0
  211. package/templates/registry/tabs.json +23 -0
  212. package/templates/registry/team-settings.json +97 -0
  213. package/templates/registry/textarea.json +16 -0
  214. package/templates/registry/tooltip.json +22 -0
  215. package/templates/registry/trial-banner.json +43 -0
  216. package/templates/registry/usage.json +84 -0
  217. package/templates/registry/workspace-settings.json +71 -0
  218. package/templates/registry-items.json +116 -0
  219. package/templates/registry-requires.json +180 -0
  220. package/templates/usage-page/usage-page.tsx.tpl +23 -8
  221. package/templates/vitest/server-only.ts.tpl +7 -0
  222. package/templates/vitest/vitest.config.ts.tpl +37 -0
  223. package/templates/billing-page/billing-page.tsx.tpl +0 -72
@@ -3,8 +3,32 @@ import path from "node:path";
3
3
 
4
4
  import { getRequestConfig } from "next-intl/server";
5
5
 
6
+ import {
7
+ getRequestHeaders,
8
+ resolveTimeZone,
9
+ } from "@intelligo-dev/core/request-context";
10
+
6
11
  import { routing } from "./routing";
7
12
 
13
+ /**
14
+ * The reader's own time zone, from the cookie the app shell writes.
15
+ *
16
+ * Every date and time on the page formats against this, so a reader in
17
+ * +08:00 sees the day they actually had rather than UTC's. Anything the
18
+ * runtime does not recognise — and a first request, which has no cookie
19
+ * yet — falls back to UTC.
20
+ */
21
+ async function requestTimeZone(): Promise<string> {
22
+ try {
23
+ const cookie = (await getRequestHeaders()).get("cookie") ?? "";
24
+ const match = cookie.match(/(?:^|;\s*)tz=([^;]*)/);
25
+ return resolveTimeZone(match?.[1] ? decodeURIComponent(match[1]) : null);
26
+ } catch {
27
+ // No request context bound (a build-time render, a script).
28
+ return "UTC";
29
+ }
30
+ }
31
+
8
32
  const MESSAGES_ROOT = path.join(process.cwd(), "messages");
9
33
 
10
34
  /**
@@ -47,5 +71,6 @@ export default getRequestConfig(async ({ requestLocale }) => {
47
71
  return {
48
72
  locale,
49
73
  messages: loadMessages(locale),
74
+ timeZone: await requestTimeZone(),
50
75
  };
51
76
  });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Next.js runs `register()` once per server process, before the first
3
+ * request. Composing here is what lets a page, a Server Action or a
4
+ * Route Handler read a registry — plans, model prices, the request
5
+ * context source — without each of them remembering to compose first.
6
+ * The registries live on `globalThis`, so a bundle that duplicates
7
+ * `lib/intelligo` still sees what this bound.
8
+ *
9
+ * Route Handlers keep their own `composeIntelligo()` call as well: it is
10
+ * idempotent, and a handler invoked from a test or a worker has no
11
+ * instrumentation hook to rely on.
12
+ */
13
+ export async function register() {
14
+ if (process.env.NEXT_RUNTIME === "nodejs") {
15
+ const { composeIntelligo, seedIntelligo } = await import("./lib/intelligo");
16
+ composeIntelligo();
17
+ // The first request finds the plan and billing-settings rows in
18
+ // place. A failure is already logged, and the next request retries.
19
+ await seedIntelligo().catch(() => undefined);
20
+ }
21
+ }
@@ -1,7 +1,7 @@
1
1
  import "server-only";
2
2
 
3
3
  /**
4
- * Your composition root (ADR-0005).
4
+ * Your composition root.
5
5
  *
6
6
  * This file is yours — Intelligo generated it once and will not
7
7
  * overwrite it. It is the single place the application wires itself
@@ -14,18 +14,29 @@ import "server-only";
14
14
  */
15
15
 
16
16
  import {
17
- checkQuota,
18
- recordTokenUsage,
19
- releaseReservation,
17
+ createBillingExecutions,
18
+ ensureBillingSettingsRow,
19
+ ensurePlanRows,
20
20
  } from "@intelligo-dev/billing";
21
+ import { DEFAULT_MARGIN_BP } from "@intelligo-dev/executions/pricing";
21
22
  import {
22
23
  registerProductFeatures,
23
24
  registerProductPlans,
25
+ registerTeamMemberLimits,
24
26
  setDefaultProductSlug,
25
27
  } from "@intelligo-dev/billing/plans";
26
- import { createExecutions } from "@intelligo-dev/executions";
28
+ import { setWorkspaceCreatedHandler } from "@intelligo-dev/auth";
29
+ import { assertEnv } from "@intelligo-dev/core/env";
30
+ import { setRequestContextSource } from "@intelligo-dev/core/request-context";
31
+ import {
32
+ DEFAULT_MODELS,
33
+ registerModels,
34
+ } from "@intelligo-dev/executions";
35
+ import { nextRequestContext } from "@intelligo-dev/next";
36
+ import { createLogger } from "@intelligo-dev/core/logger";
27
37
 
28
- import { PLANS, FEATURES } from "./plans";
38
+ import { FEATURES, PLANS, TEAM_MEMBER_LIMITS } from "./plans";
39
+ import { onWorkspaceCreated } from "./workspace-bootstrap";
29
40
 
30
41
  /** Identifies your product to the billing engine. */
31
42
  export const PRODUCT_SLUG = "__APP_SLUG__";
@@ -38,47 +49,90 @@ export const CAPABILITIES = {
38
49
  assistantMessage: "assistant.message",
39
50
  } as const;
40
51
 
52
+ const log = createLogger("Intelligo");
53
+
41
54
  let composed = false;
42
55
 
43
56
  export function composeIntelligo(): void {
44
- if (composed) return;
45
- composed = true;
57
+ if (!composed) {
58
+ composed = true;
59
+ bind();
60
+ }
61
+ // Every call checks the seed, so a database that was unreachable at
62
+ // boot is seeded by the first request after it comes back.
63
+ seedIntelligo().catch((error: unknown) => {
64
+ log.error("Seeding the plan and billing-settings rows failed", {
65
+ error,
66
+ });
67
+ });
68
+ }
69
+
70
+ let seeding: Promise<void> | undefined;
71
+
72
+ /**
73
+ * Writes the rows the registrations imply, and resolves once they
74
+ * exist. Both writes are idempotent upserts, so processes that boot
75
+ * together are safe; callers share one promise, and a failure clears
76
+ * it so the next call tries again.
77
+ */
78
+ export function seedIntelligo(): Promise<void> {
79
+ seeding ??= Promise.all([
80
+ // The plans table is a copy of the catalogue: a subscription row
81
+ // references its plan there, so the rows exist before the first
82
+ // checkout rather than being seeded by a migration.
83
+ ensurePlanRows(),
84
+
85
+ // What you bill in. Provider prices are USD, so a USD deployment
86
+ // converts at exactly 1.0; selling in another currency means stating
87
+ // its rate per USD here, in micros. There is no default rate — a
88
+ // framework that guesses one is inventing money. The row is seeded once;
89
+ // an existing row is left alone, because changing the currency under
90
+ // a ledger that holds balances is your decision, not a deploy's.
91
+ ensureBillingSettingsRow({
92
+ currency: "USD",
93
+ usdRateMicros: 1_000_000,
94
+ marginBp: DEFAULT_MARGIN_BP,
95
+ }),
96
+ ]).then(
97
+ () => undefined,
98
+ (error: unknown) => {
99
+ seeding = undefined;
100
+ throw error;
101
+ }
102
+ );
103
+ return seeding;
104
+ }
105
+
106
+ function bind(): void {
107
+ // Fail on the first request rather than on the first query: a
108
+ // missing DATABASE_URL or auth secret is a configuration error, not
109
+ // something to discover deep inside a handler.
110
+ assertEnv();
111
+
112
+ // Where the framework reads the incoming request's headers from.
113
+ // Only this line knows the app is a Next.js one; `@intelligo-dev/auth`
114
+ // asks `@intelligo-dev/core/request-context` and stays usable from a
115
+ // worker or a test.
116
+ setRequestContextSource(nextRequestContext);
117
+
118
+ // What a new user's personal workspace starts with.
119
+ setWorkspaceCreatedHandler(onWorkspaceCreated);
46
120
 
47
121
  setDefaultProductSlug(PRODUCT_SLUG);
48
122
  registerProductPlans(PRODUCT_SLUG, PLANS);
49
123
  registerProductFeatures(PRODUCT_SLUG, FEATURES);
50
- }
124
+ registerTeamMemberLimits(PRODUCT_SLUG, TEAM_MEMBER_LIMITS);
51
125
 
52
- export const executions = createExecutions({
53
- async checkEntitlement({ workspaceId, requestId, model }) {
54
- // Passing requestId makes admission atomic: the worst-case cost is
55
- // reserved in the same transaction that reads the balance, so
56
- // concurrent requests cannot all pass.
57
- const quota = await checkQuota(workspaceId, { modelId: model, requestId });
58
- return {
59
- allowed: quota.allowed,
60
- reason: quota.reason,
61
- estimatedMnt: quota.estimatedMnt,
62
- usingTrialCredits: quota.usingTrialCredits,
63
- };
64
- },
65
-
66
- async settleUsage(s) {
67
- await recordTokenUsage({
68
- workspaceId: s.workspaceId,
69
- userId: s.userId ?? "",
70
- model: s.model ?? "unknown",
71
- agent: s.capability,
72
- inputTokens: s.inputTokens,
73
- outputTokens: s.outputTokens,
74
- totalTokens: s.totalTokens,
75
- usingTrialCredits: s.usingTrialCredits,
76
- requestId: s.requestId,
77
- metadata: s.metadata,
78
- });
79
- },
126
+ // What each model costs. The framework ships a catalogue as data and
127
+ // registers none of it: an id with no registered price throws where
128
+ // the price is needed, rather than being guessed. Swap in your own
129
+ // contracted rates, or add a model the framework has never heard of,
130
+ // by passing your own array here.
131
+ registerModels(DEFAULT_MODELS);
132
+ }
80
133
 
81
- async releaseHold({ requestId }) {
82
- await releaseReservation(requestId);
83
- },
84
- });
134
+ // The execution boundary, bound to the billing engine: admission
135
+ // reserves the worst-case cost atomically, settlement records usage and
136
+ // deducts credits, reconcile() can tell a committed charge from none.
137
+ // Pass a port to replace one.
138
+ export const executions = createBillingExecutions();
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
+ import { Geist, Geist_Mono } from "next/font/google";
3
4
  import { notFound } from "next/navigation";
4
5
  import { NextIntlClientProvider } from "next-intl";
5
6
  import { getMessages, getTranslations } from "next-intl/server";
@@ -10,6 +11,18 @@ import { ThemeProvider } from "@/components/shell/theme-provider";
10
11
  import { Toaster } from "@/components/ui/sonner";
11
12
  import { routing } from "@/i18n/routing";
12
13
 
14
+ // The theme names "Geist" in `--font-sans`; loading it here registers the
15
+ // faces under that family (and exposes the variables for a product's own
16
+ // use). Without it every surface falls back to the system face.
17
+ const geistSans = Geist({
18
+ subsets: ["latin", "latin-ext", "cyrillic"],
19
+ variable: "--font-geist-sans",
20
+ });
21
+ const geistMono = Geist_Mono({
22
+ subsets: ["latin", "latin-ext", "cyrillic"],
23
+ variable: "--font-geist-mono",
24
+ });
25
+
13
26
  type Props = {
14
27
  children: ReactNode;
15
28
  params: Promise<{ locale: string }>;
@@ -39,7 +52,11 @@ export default async function RootLayout({ children, params }: Props) {
39
52
  return (
40
53
  // suppressHydrationWarning: next-themes sets the class attribute on
41
54
  // <html> before hydration (see components/shell/theme-provider.tsx).
42
- <html lang={locale} suppressHydrationWarning>
55
+ <html
56
+ lang={locale}
57
+ className={`${geistSans.variable} ${geistMono.variable}`}
58
+ suppressHydrationWarning
59
+ >
43
60
  <body>
44
61
  <ThemeProvider>
45
62
  <NextIntlClientProvider messages={messages}>
@@ -1,6 +1 @@
1
- import { type ClassValue, clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
1
+ export { cn } from "cn";
@@ -11,8 +11,10 @@ const nextConfig = {
11
11
  "@intelligo-dev/audit",
12
12
  "@intelligo-dev/auth",
13
13
  "@intelligo-dev/billing",
14
+ "@intelligo-dev/chat",
14
15
  "@intelligo-dev/core",
15
16
  "@intelligo-dev/executions",
17
+ "@intelligo-dev/next",
16
18
  ],
17
19
  };
18
20
 
@@ -7,36 +7,50 @@
7
7
  "dev": "next dev --turbopack --port 3000",
8
8
  "build": "next build",
9
9
  "start": "next start --port 3000",
10
- "type-check": "tsc --noEmit"
10
+ "type-check": "tsc --noEmit",
11
+ "db:generate": "drizzle-kit generate",
12
+ "db:migrate": "intelligo migrate && drizzle-kit migrate",
13
+ "db:check": "intelligo migrate --check"
11
14
  },
12
15
  "dependencies": {
16
+ "@base-ui/react": "^1.8.0",
13
17
  "@intelligo-dev/admin": "__INTELLIGO_DEP__",
14
18
  "@intelligo-dev/audit": "__INTELLIGO_DEP__",
15
19
  "@intelligo-dev/auth": "__INTELLIGO_DEP__",
16
20
  "@intelligo-dev/billing": "__INTELLIGO_DEP__",
21
+ "@intelligo-dev/chat": "__INTELLIGO_DEP__",
17
22
  "@intelligo-dev/core": "__INTELLIGO_DEP__",
18
23
  "@intelligo-dev/executions": "__INTELLIGO_DEP__",
19
- "@intelligo-dev/ui": "__INTELLIGO_DEP__",
20
- "@radix-ui/react-slot": "^1.2.4",
24
+ "@intelligo-dev/jobs": "__INTELLIGO_DEP__",
25
+ "@intelligo-dev/next": "__INTELLIGO_DEP__",
26
+ "ai": "^7.0.103",
27
+ "better-auth": "~1.6.33",
21
28
  "class-variance-authority": "^0.7.1",
22
- "clsx": "^2.1.1",
23
- "lucide-react": "^0.563.0",
24
- "next": "^16.2.3",
25
- "next-intl": "^4.9.2",
29
+ "cn": "^0.3.0",
30
+ "drizzle-orm": "^0.45.2",
31
+ "lucide-react": "^1.46.0",
32
+ "next": "~16.3.5",
33
+ "next-intl": "^4.14.5",
26
34
  "next-themes": "^0.4.6",
27
- "react": "^19.0.0",
28
- "react-dom": "^19.0.0",
35
+ "react": "^19.3.0",
36
+ "react-dom": "^19.3.0",
29
37
  "server-only": "^0.0.1",
30
- "sonner": "^2.0.7",
31
- "tailwind-merge": "^3.4.0"
38
+ "sonner": "^2.0.8",
39
+ "stripe": "^22.6.2",
40
+ "zod": "^4.6.5"
32
41
  },
33
42
  "devDependencies": {
34
- "@tailwindcss/postcss": "^4",
35
- "@types/node": "^22.0.0",
36
- "@types/react": "^19.0.0",
37
- "@types/react-dom": "^19.0.0",
38
- "tailwindcss": "^4",
43
+ "@intelligo-dev/cli": "__INTELLIGO_DEP__",
44
+ "@tailwindcss/postcss": "^4.3.3",
45
+ "@types/node": "^22.20.3",
46
+ "@types/react": "^19.3.0",
47
+ "@types/react-dom": "^19.3.0",
48
+ "dotenv": "^17.4.2",
49
+ "drizzle-kit": "^0.31.10",
50
+ "pg": "8.18.0",
51
+ "shadcn": "^4.21.0",
52
+ "tailwindcss": "^4.3.3",
39
53
  "tw-animate-css": "^1.4.0",
40
- "typescript": "^5.7.2"
54
+ "typescript": "^5.9.3"
41
55
  }
42
56
  }
@@ -0,0 +1,16 @@
1
+ import { getAuthSession } from "@intelligo-dev/auth";
2
+ import { getLocale } from "next-intl/server";
3
+
4
+ import { redirect } from "@/i18n/navigation";
5
+
6
+ /**
7
+ * The root URL — a session decides where you land: the dashboard when
8
+ * signed in (the `dashboard` registry item), the login page otherwise
9
+ * (`auth-login`). Replace it with a landing page of your own whenever
10
+ * you like; it is yours.
11
+ */
12
+ export default async function Home() {
13
+ const locale = await getLocale();
14
+ const session = await getAuthSession();
15
+ redirect({ href: session ? "/dashboard" : "/login", locale });
16
+ }
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Your plans and feature gates.
3
3
  *
4
- * This file is yours. Intelligo's billing engine knows only the shape;
5
- * the names, prices, and limits are entirely your product's, and it
6
- * reads them through the registry your composition root populates
7
- * (ADR-0006). Two limit keys are special because the credit engine
8
- * reads them directly: `monthlyCreditMnt` and `rolloverEnabled`.
4
+ * The billing engine knows only the shape; the names, prices, and
5
+ * limits are yours, read through the registry your composition root
6
+ * populates. `monthlyAllowance` is what the credit engine enforces: an
7
+ * amount with its currency, which must be the one your composition root
8
+ * declares to `ensureBillingSettingsRow`.
9
9
  */
10
10
 
11
+ import { fromMajor } from "@intelligo-dev/core/money";
11
12
  import type { PlanConfig } from "@intelligo-dev/billing/plans";
12
13
 
13
14
  export const PLANS: Record<string, PlanConfig> = {
@@ -15,29 +16,47 @@ export const PLANS: Record<string, PlanConfig> = {
15
16
  name: "Free",
16
17
  slug: "free",
17
18
  description: "Get started",
18
- descriptionMn: "",
19
19
  priceOneTime: 0,
20
20
  targetAudience: "Everyone",
21
21
  aiModelLabel: "Base",
22
- limits: { monthlyCreditMnt: 2_000, rolloverEnabled: false },
22
+ /** Half a dollar of model time a month — roughly a hundred cheap turns. */
23
+ monthlyAllowance: fromMajor(0.5, "USD"),
24
+ limits: {},
23
25
  features: [],
24
- featuresMn: [],
25
26
  },
26
27
  pro: {
27
28
  name: "Pro",
28
29
  slug: "pro",
29
30
  description: "For daily use",
30
- descriptionMn: "",
31
- priceOneTime: 20_000,
31
+ priceOneTime: 20,
32
32
  targetAudience: "Teams",
33
33
  aiModelLabel: "Advanced",
34
- limits: { monthlyCreditMnt: 60_000, rolloverEnabled: true },
34
+ /** $15 of model time inside a $20 plan. */
35
+ monthlyAllowance: fromMajor(15, "USD"),
36
+ limits: {},
35
37
  features: [],
36
- featuresMn: [],
37
38
  },
38
39
  };
39
40
 
40
- /** Which plans grant which feature. An unregistered feature is denied. */
41
+ /**
42
+ * Which plans grant which feature. An unregistered feature is denied —
43
+ * a registry item whose `featureKey` is missing here returns 403 on
44
+ * every request, so add the key when you install the item.
45
+ */
41
46
  export const FEATURES: Record<string, readonly string[]> = {
42
47
  assistant: ["free", "pro"],
48
+ // POST /api/chat (the `chat` registry item). Every plan, so a clean
49
+ // install can chat with no configuration; tighten to ["pro"] to put
50
+ // chat behind a paywall.
51
+ chat: ["free", "pro"],
52
+ };
53
+
54
+ /**
55
+ * Seats per plan: how many members a workspace on each plan may hold,
56
+ * the owner included. -1 is unlimited. A plan left out gets one seat,
57
+ * which refuses every invitation.
58
+ */
59
+ export const TEAM_MEMBER_LIMITS: Record<string, number> = {
60
+ free: 3,
61
+ pro: 25,
43
62
  };
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Next 16 names this file `proxy.ts` (`middleware.ts` still resolves).
3
+ *
4
+ * Optimistic only: it redirects, and never touches the database. Every
5
+ * real check is server-side.
6
+ */
1
7
  import createIntlMiddleware from "next-intl/middleware";
2
8
 
3
9
  import { routing } from "./i18n/routing";
@@ -1,25 +1,24 @@
1
1
  "use client";
2
2
 
3
- /**
4
- * Toast host (shadcn's sonner wrapper) — mounted once by
5
- * `app/[locale]/layout.tsx`. Registry items call `toast(...)` from the
6
- * `sonner` package and rely on this being mounted; without it their
7
- * toasts silently never render.
8
- *
9
- * This is the stock shadcn `sonner` component; `pnpm dlx shadcn add
10
- * sonner` would produce the same file at the same path.
11
- */
12
-
3
+ import { useTheme } from "next-themes";
4
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
13
5
  import {
14
6
  CircleCheckIcon,
15
7
  InfoIcon,
16
- Loader2Icon,
17
- OctagonXIcon,
18
8
  TriangleAlertIcon,
9
+ OctagonXIcon,
10
+ Loader2Icon,
11
+ XIcon,
19
12
  } from "lucide-react";
20
- import { useTheme } from "next-themes";
21
- import { Toaster as Sonner, type ToasterProps } from "sonner";
22
13
 
14
+ /*
15
+ * Sonner, drawn as a stack of rounded cards: each toast behind the front
16
+ * one peeks out a little lower and a little smaller, the status sits in a
17
+ * tinted round chip, and every move settles on a long ease-out that reads
18
+ * as a spring. Toasts are unstyled so the classes below own the surface;
19
+ * Sonner still owns stacking, swipe and timing, and its own
20
+ * prefers-reduced-motion rule still turns the motion off.
21
+ */
23
22
  const Toaster = ({ ...props }: ToasterProps) => {
24
23
  const { theme = "system" } = useTheme();
25
24
 
@@ -27,21 +26,44 @@ const Toaster = ({ ...props }: ToasterProps) => {
27
26
  <Sonner
28
27
  theme={theme as ToasterProps["theme"]}
29
28
  className="toaster group"
29
+ gap={10}
30
30
  icons={{
31
- success: <CircleCheckIcon className="size-4" />,
32
- info: <InfoIcon className="size-4" />,
33
- warning: <TriangleAlertIcon className="size-4" />,
34
- error: <OctagonXIcon className="size-4" />,
35
- loading: <Loader2Icon className="size-4 animate-spin" />,
31
+ success: <CircleCheckIcon className="size-4 text-success" />,
32
+ info: <InfoIcon className="size-4 text-info" />,
33
+ warning: <TriangleAlertIcon className="size-4 text-warning" />,
34
+ error: <OctagonXIcon className="size-4 text-destructive" />,
35
+ loading: <Loader2Icon className="size-4 animate-spin text-muted-foreground" />,
36
+ close: <XIcon className="size-3.5" />,
36
37
  }}
37
38
  style={
38
39
  {
39
40
  "--normal-bg": "var(--popover)",
40
41
  "--normal-text": "var(--popover-foreground)",
41
42
  "--normal-border": "var(--border)",
42
- "--border-radius": "var(--radius)",
43
+ "--border-radius": "var(--radius-2xl)",
43
44
  } as React.CSSProperties
44
45
  }
46
+ toastOptions={{
47
+ unstyled: true,
48
+ style: {
49
+ transitionDuration: "450ms",
50
+ transitionTimingFunction: "cubic-bezier(0.16, 1, 0.3, 1)",
51
+ },
52
+ classNames: {
53
+ toast:
54
+ "cn-toast group/toast flex w-(--width) items-center gap-3 rounded-2xl border border-border bg-popover/95 p-3 font-sans text-popover-foreground shadow-2xl backdrop-blur-xl outline-none focus-visible:ring-2 focus-visible:ring-ring has-[[data-description]]:items-start data-[expanded=false]:data-[front=false]:*:opacity-0",
55
+ icon: "relative grid size-7 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground group-data-[type=success]/toast:bg-success/10 group-data-[type=info]/toast:bg-info/10 group-data-[type=warning]/toast:bg-warning/10 group-data-[type=error]/toast:bg-destructive/10",
56
+ content: "flex min-w-0 flex-1 flex-col gap-0.5",
57
+ title: "text-sm leading-5 font-medium text-popover-foreground",
58
+ description: "line-clamp-2 text-xs leading-4 text-muted-foreground",
59
+ actionButton:
60
+ "inline-flex h-7 shrink-0 items-center rounded-full bg-primary px-3 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/88 focus-visible:ring-2 focus-visible:ring-ring outline-none",
61
+ cancelButton:
62
+ "inline-flex h-7 shrink-0 items-center rounded-full bg-muted px-3 text-xs font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring outline-none",
63
+ closeButton:
64
+ "order-last grid size-7 shrink-0 place-items-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring outline-none",
65
+ },
66
+ }}
45
67
  {...props}
46
68
  />
47
69
  );
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stripe webhook endpoint.
3
+ *
4
+ * Point Stripe at POST /api/webhooks/stripe and set
5
+ * STRIPE_WEBHOOK_SECRET. The handler verifies the signature, records
6
+ * every event in `finance_events` before processing it, runs the
7
+ * built-in handlers (checkout completed, invoice paid / failed,
8
+ * subscription updated / deleted), and answers 500 on a handler error
9
+ * so Stripe retries — see `createStripeWebhookHandler` in
10
+ * `@intelligo-dev/billing` for the exact contract.
11
+ *
12
+ * Pass `onEvent` to react to event types the framework does not.
13
+ */
14
+
15
+ import { createStripeWebhookHandler } from "@intelligo-dev/billing";
16
+
17
+ import { composeIntelligo } from "@/lib/intelligo";
18
+
19
+ const handler = createStripeWebhookHandler();
20
+
21
+ export async function POST(request: Request) {
22
+ composeIntelligo();
23
+ return handler(request);
24
+ }
@@ -4,10 +4,12 @@
4
4
  * Theme provider — mounted by `app/[locale]/layout.tsx` so any theme
5
5
  * switcher (e.g. the app-shell item's user menu) actually works.
6
6
  *
7
- * The same file ships with the `app-shell` registry item at the same
8
- * path; installing app-shell simply overwrites this copy with an
9
- * identical one. Defaults: class-attribute strategy (Tailwind's `.dark`
10
- * tokens), system preference honored, no transition flash on switch.
7
+ * The `app-shell` registry item ships its own provider at this same
8
+ * path; installing app-shell (with --overwrite) replaces this starter
9
+ * copy, and the manifest will report the file as customized from then
10
+ * on — that is expected. Defaults: class-attribute strategy (Tailwind's
11
+ * `.dark` tokens), system preference honored, no transition flash on
12
+ * switch.
11
13
  */
12
14
 
13
15
  import { ThemeProvider as NextThemesProvider } from "next-themes";
@@ -1,12 +1,28 @@
1
1
  {
2
- "extends": "../../tsconfig.base.json",
3
2
  "compilerOptions": {
3
+ "strict": true,
4
+ "esModuleInterop": true,
5
+ "skipLibCheck": true,
6
+ "module": "ESNext",
7
+ "target": "ES2020",
8
+ "moduleResolution": "bundler",
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "incremental": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "forceConsistentCasingInFileNames": true,
4
14
  "lib": ["dom", "dom.iterable", "esnext"],
5
- "jsx": "preserve",
15
+ "jsx": "react-jsx",
6
16
  "noEmit": true,
7
17
  "plugins": [{ "name": "next" }],
8
- "paths": { "@/*": ["./*"] }
18
+ "paths": { "@/*": ["./*"] },
19
+ "allowJs": true
9
20
  },
10
- "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
21
+ "include": [
22
+ "**/*.ts",
23
+ "**/*.tsx",
24
+ ".next/types/**/*.ts",
25
+ ".next/dev/types/**/*.ts"
26
+ ],
11
27
  "exclude": ["node_modules", ".next"]
12
28
  }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener("change", onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener("change", onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }