@intelligo-dev/cli 1.0.0-beta.1 → 1.0.0-beta.13
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/NOTICE +6 -0
- package/README.md +63 -0
- package/dist/bin.js +85 -37
- package/dist/bin.js.map +1 -1
- package/dist/commands/add.d.ts +22 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +61 -4
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/create-flow.d.ts +34 -0
- package/dist/commands/create-flow.d.ts.map +1 -0
- package/dist/commands/create-flow.js +180 -0
- package/dist/commands/create-flow.js.map +1 -0
- package/dist/commands/create.d.ts +23 -6
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +45 -13
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.d.ts +14 -3
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +293 -37
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/migrate-check.d.ts +54 -1
- package/dist/commands/migrate-check.d.ts.map +1 -1
- package/dist/commands/migrate-check.js +101 -5
- package/dist/commands/migrate-check.js.map +1 -1
- package/dist/commands/migrate.d.ts +97 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +147 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/upgrade-check.d.ts +5 -5
- package/dist/commands/upgrade-check.d.ts.map +1 -1
- package/dist/commands/upgrade-check.js +19 -6
- package/dist/commands/upgrade-check.js.map +1 -1
- package/dist/env-files.d.ts +12 -0
- package/dist/env-files.d.ts.map +1 -0
- package/dist/env-files.js +29 -0
- package/dist/env-files.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +9 -15
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +5 -16
- package/dist/manifest.js.map +1 -1
- package/dist/migrations.d.ts +3 -6
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +3 -6
- package/dist/migrations.js.map +1 -1
- package/dist/model-catalogue.d.ts +14 -0
- package/dist/model-catalogue.d.ts.map +1 -0
- package/dist/model-catalogue.js +34 -0
- package/dist/model-catalogue.js.map +1 -0
- package/dist/registry-items.d.ts +56 -0
- package/dist/registry-items.d.ts.map +1 -0
- package/dist/registry-items.js +103 -0
- package/dist/registry-items.js.map +1 -0
- package/package.json +36 -10
- package/src/bin.ts +264 -0
- package/src/commands/add.ts +244 -0
- package/src/commands/create-flow.ts +228 -0
- package/src/commands/create.ts +139 -0
- package/src/commands/doctor.ts +487 -0
- package/src/commands/migrate-check.ts +257 -0
- package/src/commands/migrate.ts +234 -0
- package/src/commands/upgrade-check.ts +157 -0
- package/src/env-files.ts +31 -0
- package/src/index.ts +39 -0
- package/src/manifest.ts +93 -0
- package/src/migrations-dir.ts +25 -0
- package/src/migrations.ts +133 -0
- package/src/model-catalogue.ts +35 -0
- package/src/registry-items.ts +132 -0
- package/templates/admin-page/admin-page.tsx.tpl +2 -3
- package/templates/app-scaffold/assistant-route.ts.tpl +2 -2
- package/templates/app-scaffold/auth-route.ts.tpl +10 -0
- package/templates/app-scaffold/components.json.tpl +6 -2
- package/templates/app-scaffold/db-schema.ts.tpl +29 -0
- package/templates/app-scaffold/drizzle-journal.json.tpl +5 -0
- package/templates/app-scaffold/drizzle.config.ts.tpl +45 -0
- package/templates/app-scaffold/env.example.tpl +65 -1
- package/templates/app-scaffold/globals.css.tpl +172 -281
- package/templates/app-scaffold/i18n-request.ts.tpl +25 -0
- package/templates/app-scaffold/instrumentation.ts.tpl +21 -0
- package/templates/app-scaffold/intelligo.ts.tpl +104 -9
- package/templates/app-scaffold/layout.tsx.tpl +18 -1
- package/templates/app-scaffold/lib-utils.ts.tpl +1 -6
- package/templates/app-scaffold/next.config.mjs.tpl +2 -0
- package/templates/app-scaffold/package.json.tpl +31 -17
- package/templates/app-scaffold/page.tsx.tpl +16 -0
- package/templates/app-scaffold/plans.ts.tpl +32 -13
- package/templates/app-scaffold/{middleware.ts.tpl → proxy.ts.tpl} +6 -0
- package/templates/app-scaffold/sonner.tsx.tpl +42 -20
- package/templates/app-scaffold/stripe-webhook-route.ts.tpl +24 -0
- package/templates/app-scaffold/theme-provider.tsx.tpl +6 -4
- package/templates/app-scaffold/tsconfig.json.tpl +20 -4
- package/templates/app-scaffold/use-mobile.ts.tpl +19 -0
- package/templates/app-scaffold/workspace-bootstrap.ts.tpl +30 -0
- package/templates/maintenance/maintenance-route.ts.tpl +119 -0
- package/templates/manifest.json +57 -18
- package/templates/registry-items.json +116 -0
- package/templates/registry-requires.json +156 -0
- package/templates/usage-page/usage-page.tsx.tpl +23 -8
- package/templates/billing-page/billing-page.tsx.tpl +0 -72
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maintenance route: the periodic work the framework relies on.
|
|
3
|
+
*
|
|
4
|
+
* Mount a scheduler at GET /api/cron/maintenance with
|
|
5
|
+
* `Authorization: Bearer $CRON_SECRET` every five minutes. A stale
|
|
6
|
+
* execution is reconciled once it is ten minutes old, so the cadence
|
|
7
|
+
* decides how long an abandoned run keeps its credit hold.
|
|
8
|
+
*
|
|
9
|
+
* On Vercel, `intelligo add maintenance` writes the `crons` entry to
|
|
10
|
+
* vercel.json (`0-59/5 * * * *`) when the app has none, and Vercel
|
|
11
|
+
* sends the header itself. Vercel Hobby runs a cron at most once a day
|
|
12
|
+
* and refuses a deployment that asks for more: on Hobby, or on any
|
|
13
|
+
* other host, drop the entry and call the route from a scheduler of
|
|
14
|
+
* your own —
|
|
15
|
+
*
|
|
16
|
+
* curl -fsS -H "Authorization: Bearer $CRON_SECRET" \
|
|
17
|
+
* https://your-app.example/api/cron/maintenance
|
|
18
|
+
*
|
|
19
|
+
* What one run does, and why each step exists:
|
|
20
|
+
*
|
|
21
|
+
* - **Reconcile stale executions.** A row still `running` or `settling`
|
|
22
|
+
* past the chat route's `maxDuration` is usage nobody finished
|
|
23
|
+
* accounting for. `executions.reconcile` confirms a charge the
|
|
24
|
+
* ledger already holds, re-runs settlement from the recorded usage,
|
|
25
|
+
* or fails an abandoned run and releases its hold.
|
|
26
|
+
* - **Drop expired reservations.** Admission already ignores them; this
|
|
27
|
+
* is table hygiene.
|
|
28
|
+
* - **Drop old rate-limit buckets.** Same.
|
|
29
|
+
* - **Expire trials and send reminders.** `hasActiveTrial` checks the
|
|
30
|
+
* end date inline, so admission is safe without this — feature
|
|
31
|
+
* access and the reminder emails are what it drives.
|
|
32
|
+
* - **Prune finished jobs** older than a week.
|
|
33
|
+
*
|
|
34
|
+
* Draining the job queue is NOT done here: `drain` needs your handlers,
|
|
35
|
+
* so give it its own route (or call it below once you have some).
|
|
36
|
+
*
|
|
37
|
+
* This file is yours. Add product maintenance to it freely.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { timingSafeEqual } from "node:crypto";
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
cleanupExpiredReservations,
|
|
44
|
+
cleanupRateLimitEntries,
|
|
45
|
+
processTrialExpirations,
|
|
46
|
+
} from "@intelligo-dev/billing";
|
|
47
|
+
import { findStaleExecutions } from "@intelligo-dev/executions";
|
|
48
|
+
import { pruneJobs } from "@intelligo-dev/jobs";
|
|
49
|
+
import { createLogger } from "@intelligo-dev/core/logger";
|
|
50
|
+
|
|
51
|
+
import { composeIntelligo, executions } from "@/lib/intelligo";
|
|
52
|
+
|
|
53
|
+
const log = createLogger("cron.maintenance");
|
|
54
|
+
|
|
55
|
+
export const maxDuration = 60;
|
|
56
|
+
|
|
57
|
+
/** Executions older than this and still non-terminal are reconciled. */
|
|
58
|
+
const STALE_AFTER_MS = 10 * 60_000;
|
|
59
|
+
/** Finished jobs older than this are pruned. */
|
|
60
|
+
const PRUNE_JOBS_AFTER_MS = 7 * 24 * 60 * 60_000;
|
|
61
|
+
|
|
62
|
+
/** Constant-time bearer comparison; length is compared too. */
|
|
63
|
+
function bearerMatches(header: string | null, secret: string): boolean {
|
|
64
|
+
if (!header?.startsWith("Bearer ")) return false;
|
|
65
|
+
const token = Buffer.from(header.slice("Bearer ".length));
|
|
66
|
+
const expected = Buffer.from(secret);
|
|
67
|
+
if (token.length !== expected.length) return false;
|
|
68
|
+
return timingSafeEqual(token, expected);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function GET(request: Request) {
|
|
72
|
+
const secret = process.env.CRON_SECRET;
|
|
73
|
+
if (!secret || secret.length < 32) {
|
|
74
|
+
log.error("CRON_SECRET missing or shorter than 32 chars — refusing");
|
|
75
|
+
return new Response("Forbidden", { status: 403 });
|
|
76
|
+
}
|
|
77
|
+
if (!bearerMatches(request.headers.get("authorization"), secret)) {
|
|
78
|
+
return new Response("Unauthorized", { status: 401 });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
composeIntelligo();
|
|
82
|
+
const errors: string[] = [];
|
|
83
|
+
const step = async <T>(name: string, run: () => Promise<T>): Promise<T | null> => {
|
|
84
|
+
try {
|
|
85
|
+
return await run();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
log.error(`${name} failed`, { error: message });
|
|
89
|
+
errors.push(`${name}: ${message}`);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const stale = await step("findStaleExecutions", () =>
|
|
95
|
+
findStaleExecutions(new Date(Date.now() - STALE_AFTER_MS))
|
|
96
|
+
);
|
|
97
|
+
const reconciled: Record<string, number> = {};
|
|
98
|
+
for (const row of stale ?? []) {
|
|
99
|
+
const result = await step(`reconcile ${row.id}`, () =>
|
|
100
|
+
executions.reconcile(row.id, { abandonRunningAfterMs: STALE_AFTER_MS })
|
|
101
|
+
);
|
|
102
|
+
if (result) reconciled[result.action] = (reconciled[result.action] ?? 0) + 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const summary = {
|
|
106
|
+
staleExecutions: stale?.length ?? 0,
|
|
107
|
+
reconciled,
|
|
108
|
+
expiredReservationsDeleted: await step("cleanupExpiredReservations", cleanupExpiredReservations),
|
|
109
|
+
rateLimitEntriesDeleted: await step("cleanupRateLimitEntries", cleanupRateLimitEntries),
|
|
110
|
+
trials: await step("processTrialExpirations", processTrialExpirations),
|
|
111
|
+
jobsPruned: await step("pruneJobs", () =>
|
|
112
|
+
pruneJobs(new Date(Date.now() - PRUNE_JOBS_AFTER_MS))
|
|
113
|
+
),
|
|
114
|
+
errors,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
log.info("Maintenance run", summary);
|
|
118
|
+
return Response.json(summary, { status: errors.length ? 207 : 200 });
|
|
119
|
+
}
|
package/templates/manifest.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"usage-page": {
|
|
3
|
-
"templateVersion": "1.0.
|
|
3
|
+
"templateVersion": "1.0.1",
|
|
4
4
|
"description": "A usage page listing executions and cost for the current workspace",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"template": "usage-page/usage-page.tsx.tpl",
|
|
8
|
-
"target": "app/usage/page.tsx"
|
|
8
|
+
"target": "app/[locale]/usage/page.tsx"
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"deprecated": "Superseded by the `usage` registry item \u2014 install it with the shadcn CLI from the Intelligo registry instead; it ships a full page, components and thin actions as consumer-owned source."
|
|
12
12
|
},
|
|
13
13
|
"app-scaffold": {
|
|
14
|
-
"templateVersion": "1.
|
|
15
|
-
"description": "A new application: composition root, plans, an assistant route, config, next-intl i18n plumbing, and a registry-consuming shadcn
|
|
14
|
+
"templateVersion": "1.14.0",
|
|
15
|
+
"description": "A new application: composition root (run once per server process from instrumentation.ts), plans, an assistant route, a Stripe webhook endpoint, config, a drizzle config for the tables you own, next-intl i18n plumbing, and a registry-consuming shadcn base-nova setup with the intelligo design tokens",
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
|
18
18
|
"template": "app-scaffold/package.json.tpl",
|
|
@@ -34,10 +34,22 @@
|
|
|
34
34
|
"template": "app-scaffold/intelligo.ts.tpl",
|
|
35
35
|
"target": "lib/intelligo.ts"
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
"template": "app-scaffold/instrumentation.ts.tpl",
|
|
39
|
+
"target": "instrumentation.ts"
|
|
40
|
+
},
|
|
37
41
|
{
|
|
38
42
|
"template": "app-scaffold/plans.ts.tpl",
|
|
39
43
|
"target": "lib/plans.ts"
|
|
40
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"template": "app-scaffold/workspace-bootstrap.ts.tpl",
|
|
47
|
+
"target": "lib/workspace-bootstrap.ts"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"template": "app-scaffold/auth-route.ts.tpl",
|
|
51
|
+
"target": "app/api/auth/[...all]/route.ts"
|
|
52
|
+
},
|
|
41
53
|
{
|
|
42
54
|
"template": "app-scaffold/assistant-route.ts.tpl",
|
|
43
55
|
"target": "app/api/assistant/route.ts"
|
|
@@ -58,10 +70,18 @@
|
|
|
58
70
|
"template": "app-scaffold/lib-utils.ts.tpl",
|
|
59
71
|
"target": "lib/utils.ts"
|
|
60
72
|
},
|
|
73
|
+
{
|
|
74
|
+
"template": "app-scaffold/use-mobile.ts.tpl",
|
|
75
|
+
"target": "hooks/use-mobile.ts"
|
|
76
|
+
},
|
|
61
77
|
{
|
|
62
78
|
"template": "app-scaffold/layout.tsx.tpl",
|
|
63
79
|
"target": "app/[locale]/layout.tsx"
|
|
64
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"template": "app-scaffold/page.tsx.tpl",
|
|
83
|
+
"target": "app/[locale]/page.tsx"
|
|
84
|
+
},
|
|
65
85
|
{
|
|
66
86
|
"template": "app-scaffold/theme-provider.tsx.tpl",
|
|
67
87
|
"target": "components/shell/theme-provider.tsx"
|
|
@@ -83,33 +103,52 @@
|
|
|
83
103
|
"target": "i18n/request.ts"
|
|
84
104
|
},
|
|
85
105
|
{
|
|
86
|
-
"template": "app-scaffold/
|
|
87
|
-
"target": "
|
|
106
|
+
"template": "app-scaffold/proxy.ts.tpl",
|
|
107
|
+
"target": "proxy.ts"
|
|
88
108
|
},
|
|
89
109
|
{
|
|
90
110
|
"template": "app-scaffold/messages-en-app.json.tpl",
|
|
91
111
|
"target": "messages/en/app.json"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"template": "app-scaffold/drizzle.config.ts.tpl",
|
|
115
|
+
"target": "drizzle.config.ts"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"template": "app-scaffold/db-schema.ts.tpl",
|
|
119
|
+
"target": "lib/db/schema.ts"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"template": "app-scaffold/drizzle-journal.json.tpl",
|
|
123
|
+
"target": "drizzle/meta/_journal.json"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"template": "app-scaffold/stripe-webhook-route.ts.tpl",
|
|
127
|
+
"target": "app/api/webhooks/stripe/route.ts"
|
|
92
128
|
}
|
|
93
129
|
]
|
|
94
130
|
},
|
|
95
|
-
"
|
|
96
|
-
"templateVersion": "1.
|
|
97
|
-
"description": "
|
|
131
|
+
"admin-page": {
|
|
132
|
+
"templateVersion": "1.1.0",
|
|
133
|
+
"description": "Mount the Intelligo operational console at /admin",
|
|
98
134
|
"files": [
|
|
99
135
|
{
|
|
100
|
-
"template": "
|
|
101
|
-
"target": "app/
|
|
136
|
+
"template": "admin-page/admin-page.tsx.tpl",
|
|
137
|
+
"target": "app/[locale]/admin/page.tsx"
|
|
102
138
|
}
|
|
103
|
-
]
|
|
104
|
-
"deprecated": "Superseded by the `pricing` and `billing-settings` registry items \u2014 install them with the shadcn CLI from the Intelligo registry instead."
|
|
139
|
+
]
|
|
105
140
|
},
|
|
106
|
-
"
|
|
107
|
-
"templateVersion": "1.
|
|
108
|
-
"description": "
|
|
141
|
+
"maintenance": {
|
|
142
|
+
"templateVersion": "1.1.0",
|
|
143
|
+
"description": "A CRON_SECRET-gated GET /api/cron/maintenance that reconciles stale executions, drops expired reservations and rate-limit buckets, expires trials and prunes old jobs \u2014 scheduled every five minutes, in vercel.json when the app has none",
|
|
144
|
+
"cron": {
|
|
145
|
+
"path": "/api/cron/maintenance",
|
|
146
|
+
"schedule": "0-59/5 * * * *"
|
|
147
|
+
},
|
|
109
148
|
"files": [
|
|
110
149
|
{
|
|
111
|
-
"template": "
|
|
112
|
-
"target": "app/
|
|
150
|
+
"template": "maintenance/maintenance-route.ts.tpl",
|
|
151
|
+
"target": "app/api/cron/maintenance/route.ts"
|
|
113
152
|
}
|
|
114
153
|
]
|
|
115
154
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": {
|
|
3
|
+
"route-error": {
|
|
4
|
+
"title": "Route Error States",
|
|
5
|
+
"description": "The app's error surfaces: a shared RouteError component every error.tsx renders, message-based error classification, an (app) group boundary, a translated 404, and a dependency-free global-error. Error reporting goes through the lib/error-reporting.ts seam (a no-op until you bind a reporter), so no SDK enters the registry. Items that ship an error.tsx depend on this one - install it first."
|
|
6
|
+
},
|
|
7
|
+
"team-settings": {
|
|
8
|
+
"title": "Team Settings",
|
|
9
|
+
"description": "Workspace team management: invite members, change roles, remove members, and manage pending invitations, backed by the framework's team service."
|
|
10
|
+
},
|
|
11
|
+
"usage": {
|
|
12
|
+
"title": "Usage",
|
|
13
|
+
"description": "Workspace usage dashboard: current-period summary tiles, a period selector, and a table of recent AI execution records."
|
|
14
|
+
},
|
|
15
|
+
"invitation-accept": {
|
|
16
|
+
"title": "Invitation Accept",
|
|
17
|
+
"description": "Accept or decline a workspace invitation via the team service. Requires the team-settings item (provides lib/team.ts and the role badge); install it first."
|
|
18
|
+
},
|
|
19
|
+
"app-shell": {
|
|
20
|
+
"title": "App Shell",
|
|
21
|
+
"description": "Authenticated application shell: session and onboarding gate, workspace bootstrap, a sidebar that morphs into an icon rail with a gliding active pill, workspace switcher, configurable nav and user menu, and a page region that lifts each section in."
|
|
22
|
+
},
|
|
23
|
+
"settings-shell": {
|
|
24
|
+
"title": "Settings Shell",
|
|
25
|
+
"description": "The shared frame around every /settings page: layout with heading and width constraint, a configurable tab bar (lib/settings-nav.ts), and a /settings index that redirects to the first tab. Install alongside the settings items - without it they render unframed and plain /settings links 404."
|
|
26
|
+
},
|
|
27
|
+
"workspace-settings": {
|
|
28
|
+
"title": "Workspace Settings",
|
|
29
|
+
"description": "Workspace name/URL editing and owner-only deletion, backed by the framework's workspace service."
|
|
30
|
+
},
|
|
31
|
+
"auth-login": {
|
|
32
|
+
"title": "Auth: Login",
|
|
33
|
+
"description": "Email/password and OAuth login. Ships the shared auth route layout, the de-branded AuthCard shell, and the auth-validation.ts zod schemas reused by auth-signup, auth-password-reset, and auth-email-verification - install this item first."
|
|
34
|
+
},
|
|
35
|
+
"auth-signup": {
|
|
36
|
+
"title": "Auth: Signup",
|
|
37
|
+
"description": "Email/password and OAuth account creation. Requires the auth-login item (provides the shared AuthCard shell, SocialLoginButtons, and lib/auth-validation.ts schemas); install it first. Redirects to auth-email-verification's /verify-email route when the server requires email verification."
|
|
38
|
+
},
|
|
39
|
+
"auth-password-reset": {
|
|
40
|
+
"title": "Auth: Password Reset",
|
|
41
|
+
"description": "Forgot-password and reset-password pages, backed by the Better-Auth client SDK's requestPasswordReset/resetPassword actions. Requires the auth-login item (provides the shared AuthCard shell and lib/auth-validation.ts schemas); install it first."
|
|
42
|
+
},
|
|
43
|
+
"auth-email-verification": {
|
|
44
|
+
"title": "Auth: Email Verification",
|
|
45
|
+
"description": "Post-signup email verification status page with a resend control, backed by the Better-Auth client SDK's sendVerificationEmail action. Reads the ?error=/?email= search params Better-Auth's own /verify-email link handler redirects back with. Requires the auth-login item (provides the shared AuthCard shell); install it first."
|
|
46
|
+
},
|
|
47
|
+
"language-switcher": {
|
|
48
|
+
"title": "Language Switcher",
|
|
49
|
+
"description": "Locale dropdown over the locales i18n/routing.ts declares, labeled with each language's own name. Renders nothing in a single-locale deployment, so it is safe to mount unconditionally (e.g. in shellConfig.headerRight). Adding a locale to routing.ts plus a messages directory is the whole job."
|
|
50
|
+
},
|
|
51
|
+
"trial-banner": {
|
|
52
|
+
"title": "Trial Banner",
|
|
53
|
+
"description": "Dismissable trial-status strip for the app shell's bannerTop seam: days and credits remaining with an upgrade CTA, urgency-ramped visuals, dismiss-for-today, and a consumer config (lib/trial-banner-config.ts) for the CTA target and route suppression (hidden on /chat by default). Bind TrialBannerContainer as shellConfig.bannerTop."
|
|
54
|
+
},
|
|
55
|
+
"notifications": {
|
|
56
|
+
"title": "Notifications",
|
|
57
|
+
"description": "Notification bell with an unread badge and dropdown, plus a full notifications page with mark-as-read and load-more, backed by the framework's notification records."
|
|
58
|
+
},
|
|
59
|
+
"pricing": {
|
|
60
|
+
"title": "Pricing",
|
|
61
|
+
"description": "Plan comparison and upgrade page with a monthly/yearly toggle, backed by the framework's checkout service and the product's registered plan catalogue. Ships the lib/billing.ts product binding and actions/billing.ts checkout actions that the checkout and billing-settings items also depend on — install this item first."
|
|
62
|
+
},
|
|
63
|
+
"checkout": {
|
|
64
|
+
"title": "Checkout",
|
|
65
|
+
"description": "Checkout-success page that verifies the Stripe session directly through the framework's checkout service, covering the webhook-race window between the browser redirect and Stripe's webhook delivery."
|
|
66
|
+
},
|
|
67
|
+
"billing-settings": {
|
|
68
|
+
"title": "Billing Settings",
|
|
69
|
+
"description": "Workspace billing management: current plan, credit balance, credit bundle purchase, and the Stripe billing portal, shaped server-side by role (member/admin/owner). Requires the pricing item (provides lib/billing.ts and actions/billing.ts); install it first."
|
|
70
|
+
},
|
|
71
|
+
"feature-gating": {
|
|
72
|
+
"title": "Feature Gating",
|
|
73
|
+
"description": "The vocabulary for selling a plan: a server-side FeatureGate that renders gated content only to entitled workspaces, three densities of upgrade prompt (full, compact, inline), a quota warning that escalates as a limit approaches, and a preview paywall that blurs content instead of hiding it. Which features exist and which plan unlocks them is consumer config (lib/feature-catalog.ts). Install pricing or billing-settings for somewhere to send the user."
|
|
74
|
+
},
|
|
75
|
+
"payment-poll": {
|
|
76
|
+
"title": "QR Payment (poll)",
|
|
77
|
+
"description": "The non-card checkout the catalogue was missing: issue an invoice, show a QR the user scans in their banking app, poll until the provider confirms. Provider-agnostic — QPay, SocialPay, PIX, UPI, PromptPay all fit — through the lib/local-payment.ts binding. Requires the pricing item for lib/billing-config.ts's CURRENCY."
|
|
78
|
+
},
|
|
79
|
+
"profile-settings": {
|
|
80
|
+
"title": "Profile Settings",
|
|
81
|
+
"description": "User profile name editing, read-only email, and account deletion with typed confirmation, backed by the framework's profile service."
|
|
82
|
+
},
|
|
83
|
+
"onboarding": {
|
|
84
|
+
"title": "Onboarding",
|
|
85
|
+
"description": "A generic multi-step onboarding wizard driven by a consumer-owned steps config: text and single-select-card fields, a completion screen, skip, and back/next - backed by the framework's onboarding service (durable step progress, no vertical-specific UI)."
|
|
86
|
+
},
|
|
87
|
+
"dashboard": {
|
|
88
|
+
"title": "Dashboard",
|
|
89
|
+
"description": "An AI-first workspace home: a hero and a real composer centred as one unit, starter chips that fill it, and a resume affordance for unfinished work — and nothing else, because history belongs to the sidebar and spend to /usage. Configurable through lib/dashboard-config.tsx (copy, starters, chat base path) and lib/dashboard-data.ts (what \"resume\" means). Pairs with the chat item for its /chat routes; install it first."
|
|
90
|
+
},
|
|
91
|
+
"artifacts": {
|
|
92
|
+
"title": "Artifacts",
|
|
93
|
+
"description": "Browse and manage AI-generated document artifacts: filterable list, full-content preview, copy-to-clipboard, and version delete, backed by the framework's document persistence service."
|
|
94
|
+
},
|
|
95
|
+
"privacy-settings": {
|
|
96
|
+
"title": "Privacy Settings",
|
|
97
|
+
"description": "GDPR-style data export, a read-only memory-audit trail, and per-category fact listing with delete, backed by the framework's identity service."
|
|
98
|
+
},
|
|
99
|
+
"chat": {
|
|
100
|
+
"title": "Chat",
|
|
101
|
+
"description": "A ChatGPT-level AI chat surface: a streaming thread with edit, regenerate and response versions, attachments, a model picker, reasoning, sources, tool cards and human-in-the-loop approvals, agent activity for Mastra and eve turns, a canvas beside the chat for documents a tool streams, a sidebar with pinned and dated groups, share links and feedback — on a two-line Route Handler that binds @intelligo-dev/chat's transport (auth, rate limit, feature gate, persistence, the execution boundary) to consumer-owned config (lib/chat-server-config.ts). Every tool renders through one entry in lib/chat-renderers.tsx, inline and in the canvas. Ships with a deterministic stub model so a clean install streams with no API keys. The config imports lib/intelligo.ts (the composition root) and lib/plans.ts, which are provided by the `intelligo create` scaffold, not by any registry item - in a hand-rolled app, create both before installing (see the CLI's app-scaffold templates)."
|
|
102
|
+
},
|
|
103
|
+
"chat-panel": {
|
|
104
|
+
"title": "Chat Panel",
|
|
105
|
+
"description": "The chat as a side panel on any page: a sheet that opens the same thread the chat page runs, with the page's context on every turn. Requires the chat item."
|
|
106
|
+
},
|
|
107
|
+
"chat-widget": {
|
|
108
|
+
"title": "Chat Widget",
|
|
109
|
+
"description": "The floating assistant: a launcher in a corner of every page and a compact chat over it, configured in lib/chat-widget-config.tsx. Requires the chat item."
|
|
110
|
+
},
|
|
111
|
+
"chat-share": {
|
|
112
|
+
"title": "Chat Share",
|
|
113
|
+
"description": "A conversation its owner published, readable by anyone with the link at /share/[id]: the same transcript, read-only, minus reasoning, tool details and attachments. Requires the chat item."
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Machine-readable requirements of every registry item \u2014 what shadcn's schema cannot express. `scaffold`: consumer files `intelligo create` provides that items may import. Per item: `marker` (the file whose presence means the item is installed), `items` (sibling items that ship files this one imports \u2014 install them first), `files` (scaffold files it imports), `exports` (names a scaffold file must export), `features` (feature keys the item gates on; register them in lib/plans.ts). tests/architecture/registry.test.ts asserts this file matches the code; CI derives the install order from it; `intelligo doctor` checks an app against it (packages/cli/templates/registry-requires.json is a copy the CLI build regenerates; the suite fails if it is stale).",
|
|
3
|
+
"scaffold": [
|
|
4
|
+
"lib/intelligo",
|
|
5
|
+
"lib/plans",
|
|
6
|
+
"lib/utils",
|
|
7
|
+
"hooks/use-mobile",
|
|
8
|
+
"i18n/routing",
|
|
9
|
+
"i18n/navigation"
|
|
10
|
+
],
|
|
11
|
+
"items": {
|
|
12
|
+
"smoke": {
|
|
13
|
+
"marker": "app/[locale]/registry-smoke/page.tsx"
|
|
14
|
+
},
|
|
15
|
+
"route-error": {
|
|
16
|
+
"marker": "app/[locale]/(app)/error.tsx",
|
|
17
|
+
"files": ["i18n/navigation"]
|
|
18
|
+
},
|
|
19
|
+
"team-settings": {
|
|
20
|
+
"marker": "app/[locale]/(app)/settings/team/page.tsx",
|
|
21
|
+
"items": ["route-error"],
|
|
22
|
+
"files": ["i18n/navigation"]
|
|
23
|
+
},
|
|
24
|
+
"usage": {
|
|
25
|
+
"marker": "app/[locale]/(app)/usage/page.tsx",
|
|
26
|
+
"items": ["pricing", "route-error"],
|
|
27
|
+
"files": ["i18n/navigation"]
|
|
28
|
+
},
|
|
29
|
+
"invitation-accept": {
|
|
30
|
+
"marker": "app/[locale]/accept-invitation/[id]/page.tsx",
|
|
31
|
+
"items": ["team-settings"],
|
|
32
|
+
"files": ["i18n/navigation"]
|
|
33
|
+
},
|
|
34
|
+
"app-shell": {
|
|
35
|
+
"marker": "app/[locale]/(app)/layout.tsx",
|
|
36
|
+
"files": ["i18n/navigation", "lib/utils"]
|
|
37
|
+
},
|
|
38
|
+
"settings-shell": {
|
|
39
|
+
"marker": "app/[locale]/(app)/settings/layout.tsx",
|
|
40
|
+
"files": ["i18n/navigation"]
|
|
41
|
+
},
|
|
42
|
+
"workspace-settings": {
|
|
43
|
+
"marker": "app/[locale]/(app)/settings/workspace/page.tsx",
|
|
44
|
+
"items": ["route-error"],
|
|
45
|
+
"files": ["i18n/navigation"]
|
|
46
|
+
},
|
|
47
|
+
"auth-login": {
|
|
48
|
+
"marker": "app/[locale]/(auth)/layout.tsx",
|
|
49
|
+
"items": ["route-error"],
|
|
50
|
+
"files": ["i18n/navigation"]
|
|
51
|
+
},
|
|
52
|
+
"auth-signup": {
|
|
53
|
+
"marker": "app/[locale]/(auth)/signup/page.tsx",
|
|
54
|
+
"items": ["auth-login"],
|
|
55
|
+
"files": ["i18n/navigation"]
|
|
56
|
+
},
|
|
57
|
+
"auth-password-reset": {
|
|
58
|
+
"marker": "app/[locale]/(auth)/forgot-password/page.tsx",
|
|
59
|
+
"items": ["auth-login"],
|
|
60
|
+
"files": ["i18n/navigation"]
|
|
61
|
+
},
|
|
62
|
+
"auth-email-verification": {
|
|
63
|
+
"marker": "app/[locale]/(auth)/verify-email/page.tsx",
|
|
64
|
+
"items": ["auth-login"],
|
|
65
|
+
"files": ["i18n/navigation"]
|
|
66
|
+
},
|
|
67
|
+
"language-switcher": {
|
|
68
|
+
"marker": "components/layout/language-switcher.tsx",
|
|
69
|
+
"files": ["i18n/navigation", "i18n/routing"]
|
|
70
|
+
},
|
|
71
|
+
"trial-banner": {
|
|
72
|
+
"marker": "components/trial/trial-banner-container.tsx",
|
|
73
|
+
"files": ["i18n/navigation"]
|
|
74
|
+
},
|
|
75
|
+
"notifications": {
|
|
76
|
+
"marker": "app/[locale]/(app)/notifications/page.tsx",
|
|
77
|
+
"items": ["route-error"],
|
|
78
|
+
"files": ["i18n/navigation"]
|
|
79
|
+
},
|
|
80
|
+
"pricing": {
|
|
81
|
+
"marker": "app/[locale]/(app)/pricing/page.tsx",
|
|
82
|
+
"files": ["i18n/navigation"]
|
|
83
|
+
},
|
|
84
|
+
"checkout": {
|
|
85
|
+
"marker": "app/[locale]/(app)/checkout/success/page.tsx",
|
|
86
|
+
"files": ["i18n/navigation"]
|
|
87
|
+
},
|
|
88
|
+
"billing-settings": {
|
|
89
|
+
"marker": "app/[locale]/(app)/settings/billing/page.tsx",
|
|
90
|
+
"items": ["pricing", "route-error"],
|
|
91
|
+
"files": ["i18n/navigation"]
|
|
92
|
+
},
|
|
93
|
+
"feature-gating": {
|
|
94
|
+
"marker": "components/billing/feature-gate.tsx",
|
|
95
|
+
"items": ["pricing"],
|
|
96
|
+
"files": ["i18n/navigation", "lib/utils"]
|
|
97
|
+
},
|
|
98
|
+
"payment-poll": {
|
|
99
|
+
"marker": "components/billing/local-payment-modal.tsx",
|
|
100
|
+
"items": ["pricing"]
|
|
101
|
+
},
|
|
102
|
+
"profile-settings": {
|
|
103
|
+
"marker": "app/[locale]/(app)/settings/profile/page.tsx",
|
|
104
|
+
"items": ["route-error"],
|
|
105
|
+
"files": ["i18n/navigation"]
|
|
106
|
+
},
|
|
107
|
+
"onboarding": {
|
|
108
|
+
"marker": "app/[locale]/onboarding/layout.tsx",
|
|
109
|
+
"items": ["route-error"],
|
|
110
|
+
"files": ["i18n/navigation"]
|
|
111
|
+
},
|
|
112
|
+
"dashboard": {
|
|
113
|
+
"marker": "app/[locale]/(app)/dashboard/page.tsx",
|
|
114
|
+
"items": ["route-error"],
|
|
115
|
+
"files": ["i18n/navigation"]
|
|
116
|
+
},
|
|
117
|
+
"artifacts": {
|
|
118
|
+
"marker": "app/[locale]/(app)/artifacts/page.tsx",
|
|
119
|
+
"files": ["i18n/navigation"]
|
|
120
|
+
},
|
|
121
|
+
"privacy-settings": {
|
|
122
|
+
"marker": "app/[locale]/(app)/settings/privacy/page.tsx",
|
|
123
|
+
"items": ["route-error"]
|
|
124
|
+
},
|
|
125
|
+
"chat": {
|
|
126
|
+
"marker": "app/[locale]/(app)/chat/page.tsx",
|
|
127
|
+
"items": ["route-error"],
|
|
128
|
+
"files": [
|
|
129
|
+
"hooks/use-mobile",
|
|
130
|
+
"i18n/navigation",
|
|
131
|
+
"i18n/routing",
|
|
132
|
+
"lib/intelligo",
|
|
133
|
+
"lib/utils"
|
|
134
|
+
],
|
|
135
|
+
"features": ["chat"],
|
|
136
|
+
"exports": {
|
|
137
|
+
"lib/intelligo": ["composeIntelligo", "executions"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"chat-panel": {
|
|
141
|
+
"marker": "components/chat/chat-panel.tsx",
|
|
142
|
+
"items": ["chat"],
|
|
143
|
+
"files": ["lib/utils"]
|
|
144
|
+
},
|
|
145
|
+
"chat-widget": {
|
|
146
|
+
"marker": "components/chat/chat-widget.tsx",
|
|
147
|
+
"items": ["chat"],
|
|
148
|
+
"files": ["i18n/navigation", "lib/utils"]
|
|
149
|
+
},
|
|
150
|
+
"chat-share": {
|
|
151
|
+
"marker": "app/[locale]/share/[id]/page.tsx",
|
|
152
|
+
"items": ["chat"],
|
|
153
|
+
"files": ["i18n/navigation"]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* it, `intelligo upgrade --check` reports the upstream change as a
|
|
6
|
-
* diff and leaves your version alone (ADR-0002).
|
|
2
|
+
* Generated by `intelligo add usage-page` and never overwritten: once
|
|
3
|
+
* you edit it, `intelligo upgrade --check` reports a template change as
|
|
4
|
+
* a diff and leaves your version alone.
|
|
7
5
|
*
|
|
8
6
|
* It reads through @intelligo-dev/executions' query API rather than the
|
|
9
7
|
* database directly, so it keeps working across schema changes.
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
import { requireWorkspace } from "@intelligo-dev/auth";
|
|
11
|
+
import { formatMoney, type Money } from "@intelligo-dev/core/money";
|
|
13
12
|
import { listExecutions, summarizeExecutions } from "@intelligo-dev/executions";
|
|
14
13
|
|
|
14
|
+
/** Amounts arrive as micros with their currency; never assume a symbol. */
|
|
15
|
+
const show = (amounts: Money[]) =>
|
|
16
|
+
amounts.length === 0
|
|
17
|
+
? "—"
|
|
18
|
+
: amounts.map((amount) => formatMoney(amount, "en-US")).join(" · ");
|
|
19
|
+
|
|
15
20
|
export default async function UsagePage() {
|
|
16
21
|
const { workspace } = await requireWorkspace();
|
|
17
22
|
|
|
18
23
|
const now = new Date();
|
|
19
|
-
|
|
24
|
+
// Billing periods are UTC calendar months.
|
|
25
|
+
const monthStart = new Date(
|
|
26
|
+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)
|
|
27
|
+
);
|
|
20
28
|
|
|
21
29
|
const [summary, recent] = await Promise.all([
|
|
22
30
|
summarizeExecutions(workspace.id, { from: monthStart, to: now }),
|
|
@@ -30,7 +38,7 @@ export default async function UsagePage() {
|
|
|
30
38
|
<p>
|
|
31
39
|
{summary.totals.count} executions this month ·{" "}
|
|
32
40
|
{summary.totals.totalTokens.toLocaleString()} tokens ·{" "}
|
|
33
|
-
{summary.totals.
|
|
41
|
+
{show(summary.totals.charged)}
|
|
34
42
|
</p>
|
|
35
43
|
|
|
36
44
|
<table cellPadding={8}>
|
|
@@ -49,7 +57,14 @@ export default async function UsagePage() {
|
|
|
49
57
|
<td>{e.capability}</td>
|
|
50
58
|
<td>{e.status}</td>
|
|
51
59
|
<td align="right">{e.totalTokens ?? 0}</td>
|
|
52
|
-
<td align="right">
|
|
60
|
+
<td align="right">
|
|
61
|
+
{e.chargedMicros !== null && e.currency
|
|
62
|
+
? formatMoney(
|
|
63
|
+
{ amount: e.chargedMicros, currency: e.currency } as Money,
|
|
64
|
+
"en-US"
|
|
65
|
+
)
|
|
66
|
+
: "—"}
|
|
67
|
+
</td>
|
|
53
68
|
<td>{e.startedAt.toISOString()}</td>
|
|
54
69
|
</tr>
|
|
55
70
|
))}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Billing page — generated by `intelligo add billing-page`.
|
|
3
|
-
*
|
|
4
|
-
* Yours from here. It reads the plan catalogue your composition root
|
|
5
|
-
* registered rather than a list baked into the framework, so changing
|
|
6
|
-
* your prices is a change to your own `lib/plans.ts` (ADR-0006).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { requireWorkspace } from "@intelligo-dev/auth";
|
|
10
|
-
import { getWorkspaceBilling } from "@intelligo-dev/billing";
|
|
11
|
-
import { getPlanConfigs } from "@intelligo-dev/billing/plans";
|
|
12
|
-
import { CreditBalance } from "@intelligo-dev/ui";
|
|
13
|
-
|
|
14
|
-
import { PRODUCT_SLUG } from "@/lib/intelligo";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Your currency, in one place. @intelligo-dev/ui takes a formatter rather
|
|
18
|
-
* than assuming one, because a framework package deciding that money
|
|
19
|
-
* is written "5,000₮" is wrong everywhere but here.
|
|
20
|
-
*/
|
|
21
|
-
const formatAmount = (amount: number) => `${amount.toLocaleString()}₮`;
|
|
22
|
-
|
|
23
|
-
export const dynamic = "force-dynamic";
|
|
24
|
-
|
|
25
|
-
export default async function BillingPage() {
|
|
26
|
-
const { workspace } = await requireWorkspace();
|
|
27
|
-
|
|
28
|
-
const [billing, plans] = await Promise.all([
|
|
29
|
-
getWorkspaceBilling(workspace.id),
|
|
30
|
-
Promise.resolve(getPlanConfigs(PRODUCT_SLUG)),
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const currentSlug = billing.plan?.slug ?? "free";
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<main style={{ fontFamily: "system-ui", padding: "2rem" }}>
|
|
37
|
-
<h1>Billing</h1>
|
|
38
|
-
|
|
39
|
-
<p>
|
|
40
|
-
Current plan: <strong>{billing.plan?.name ?? "Free"}</strong>
|
|
41
|
-
</p>
|
|
42
|
-
|
|
43
|
-
<CreditBalance
|
|
44
|
-
remainingMnt={billing.creditBalance.balanceMnt ?? 0}
|
|
45
|
-
formatAmount={formatAmount}
|
|
46
|
-
/>
|
|
47
|
-
|
|
48
|
-
<section>
|
|
49
|
-
<h2>Plans</h2>
|
|
50
|
-
{Object.values(plans).map((plan) => (
|
|
51
|
-
<article key={plan.slug} style={{ marginBottom: "1.5rem" }}>
|
|
52
|
-
<h3>
|
|
53
|
-
{plan.name}
|
|
54
|
-
{plan.slug === currentSlug ? " (current)" : ""}
|
|
55
|
-
</h3>
|
|
56
|
-
<p>{plan.description}</p>
|
|
57
|
-
<p>
|
|
58
|
-
{plan.priceOneTime === 0
|
|
59
|
-
? "Free"
|
|
60
|
-
: formatAmount(plan.priceOneTime)}
|
|
61
|
-
</p>
|
|
62
|
-
<ul>
|
|
63
|
-
{plan.features.map((f) => (
|
|
64
|
-
<li key={f}>{f}</li>
|
|
65
|
-
))}
|
|
66
|
-
</ul>
|
|
67
|
-
</article>
|
|
68
|
-
))}
|
|
69
|
-
</section>
|
|
70
|
-
</main>
|
|
71
|
-
);
|
|
72
|
-
}
|