@dashai/cli 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -865,8 +865,14 @@ npm run dev:next # next dev on :3000 (terminal 2)
865
865
  - **\`module.json\`** \u2014 manifest (\`module_kind: 'custom'\`, \`runtime.kind: 'machine'\`).
866
866
  - **\`middleware.ts\`** \u2014 \`withAuth\` from \`@dashai/sdk/auth/middleware\`. Redirects unauthenticated requests through the DashWise SSO flow.
867
867
  - **\`app/layout.tsx\`** \u2014 wraps the tree in \`<AuthProvider>\` from \`@dashai/sdk/auth/client\`.
868
- - **\`app/page.tsx\`** \u2014 public landing (allowlisted in \`middleware.ts\`).
869
- - **\`app/dashboard/page.tsx\`** \u2014 Server Component requiring a valid session via \`getServerSession()\`.
868
+ - **\`app/page.tsx\`** \u2014 the app's home at \`/\` (auth-gated; zero-click for platform users via \`autoSignIn\`). **This is where you build your app.**
869
+ - **\`app/dashboard/page.tsx\`** \u2014 an additional example Server Component route, also session-gated via \`getServerSession()\`.
870
+
871
+ The design is yours: edit the theme tokens in \`app/globals.css\` (they ship
872
+ colored on purpose, as a starting point) and use any Tailwind colors or
873
+ shadcn blocks you like. A manual "Sign in" landing page is only for a
874
+ \`public_access_allowed\` module \u2014 re-add \`'/'\` to \`middleware.ts\` publicPaths
875
+ and drop the home's session guard to build one.
870
876
  - **\`app/api/auth/callback/route.ts\`** \u2014 exchanges the SSO one-time code for a session cookie (uses \`exchangeCode()\` from the SDK).
871
877
  - **\`app/api/auth/sign-in/route.ts\`** \u2014 one entry point that calls the backend's \`POST /api/module-api/sso/start-url\` (authenticated with your \`dwk_\` key) and 302s the browser to the returned \`redirect_url\`. Same path in dev + prod.
872
878
  - **\`app/api/auth/sign-out/route.ts\`** \u2014 clears the session cookie + tells the backend to revoke.
@@ -943,18 +949,28 @@ function middlewareTs() {
943
949
  import { withAuth } from '@dashai/sdk/auth/middleware';
944
950
 
945
951
  export default withAuth({
946
- // \`/sign-in-error\` is public so unauthenticated visitors can actually
947
- // see the polished SSO error page \u2014 otherwise a sign-in failure would
948
- // redirect-loop trying to re-authenticate.
949
- publicPaths: ['/', '/about', '/sign-in-error'],
952
+ // Only the SSO error page is public. The app's home lives at \`/\`
953
+ // (app/page.tsx) and is auth-gated like the rest of the app \u2014 a
954
+ // signed-in platform user still lands there with ZERO clicks because
955
+ // \`autoSignIn\` (below) completes the handshake silently. There is no
956
+ // public "sign in" wall in front of your app by default.
957
+ //
958
+ // \`/sign-in-error\` stays public so a failed sign-in can render its
959
+ // error page instead of redirect-looping back into re-authentication.
960
+ //
961
+ // Building a \`public_access_allowed\` module that wants an ANONYMOUS
962
+ // landing page (a marketing / manual "Sign in with DashWise" screen)?
963
+ // Re-add \`'/'\` here AND make app/page.tsx public (drop its
964
+ // getServerSession guard). See the README's "Design & home" note.
965
+ publicPaths: ['/sign-in-error'],
950
966
  loginPath: '/api/auth/sign-in',
951
967
  // Silent sign-in: when a signed-in DashWise user opens the module through
952
968
  // the platform (in-app embed or the module subdomain), the SSO handshake
953
- // completes with zero clicks \u2014 they land straight in the app, landing page
954
- // included. It never touches:
969
+ // completes with zero clicks \u2014 they land straight on the page they asked
970
+ // for, the home at \`/\` included. It never touches:
955
971
  // - anonymous visitors on public installs (no platform-session hint),
956
- // - local \`dashwise dev\` (no platform proxy \u2192 no hint header) \u2014 the
957
- // landing's "Sign in with DashWise" button stays your dev flow,
972
+ // - local \`dashwise dev\` (no platform proxy \u2192 no hint header) \u2014 a
973
+ // manual sign-in through /api/auth/sign-in stays your dev flow,
958
974
  // - anything within 60s of a failed silent attempt (no redirect loops).
959
975
  autoSignIn: true,
960
976
  });
@@ -996,84 +1012,118 @@ export default function RootLayout({ children }: { children: React.ReactNode })
996
1012
  `;
997
1013
  }
998
1014
  function appPageCustomTsx(opts) {
999
- return `// Public landing page \u2014 listed in middleware.ts publicPaths.
1015
+ return `// app/page.tsx \u2014 the app's REAL home, served at \`/\`. THIS IS YOUR APP.
1016
+ // Build it here.
1000
1017
  //
1001
- // NOTE: signed-in DashWise users usually never see this page \u2014 the
1002
- // middleware's \`autoSignIn\` signs them in silently and they land on the
1003
- // page they asked for. This landing serves anonymous visitors (public
1004
- // installs), local \`dashwise dev\`, and the rare failed silent attempt \u2014
1005
- // its "Sign in with DashWise" button is the manual fallback that kicks
1006
- // off the same SSO flow (via /api/auth/sign-in). After signing in, the
1007
- // user lands on /dashboard (Server Component, session-validated).
1018
+ // A signed-in DashWise user who opens this module through the platform
1019
+ // (in-app embed or the module subdomain) lands on THIS page with zero
1020
+ // clicks: the middleware's \`autoSignIn\` completes the SSO handshake
1021
+ // silently, so there is no sign-in wall in front of your app. The guard
1022
+ // below mirrors app/dashboard/page.tsx \u2014 no session \u2192 bounce through the
1023
+ // SSO flow and come right back to \`/\`.
1008
1024
  //
1009
- // Styling: shadcn/ui primitives (\`<Button>\`, \`<Card>\`) on Tailwind 4
1010
- // theme tokens. Add more blocks via \`npx shadcn add @shadcn-studio/...\`
1011
- // (see README's "Adding UI blocks" section).
1025
+ // A public, anonymous sign-in landing page is only for a
1026
+ // \`public_access_allowed\` module that wants an unauthenticated entry
1027
+ // point. To build one, re-add \`'/'\` to middleware.ts publicPaths and drop
1028
+ // the getServerSession guard here (see the README's "Design & home" note).
1029
+ //
1030
+ // The design is YOURS to own. These shadcn primitives sit on the theme
1031
+ // tokens in app/globals.css \u2014 edit the hues freely \u2014 and the full
1032
+ // Tailwind palette (bg-indigo-600, text-emerald-500, \u2026) is available too.
1033
+ // This colored starter look is a starting point, not a mandate.
1012
1034
  import Link from 'next/link';
1013
- import { LogIn, LayoutDashboard } from 'lucide-react';
1035
+ import { getServerSession } from '@dashai/sdk/auth/server';
1036
+ import { redirect } from 'next/navigation';
1037
+ import { ArrowRight, Table2, Database, PenLine } from 'lucide-react';
1014
1038
  import { Button } from '@/components/ui/button';
1015
1039
  import {
1016
1040
  Card,
1017
- CardContent,
1018
1041
  CardDescription,
1019
1042
  CardHeader,
1020
1043
  CardTitle,
1021
1044
  } from '@/components/ui/card';
1022
1045
 
1023
- export default function Page() {
1046
+ // force-dynamic: this page calls getServerSession() at render, which
1047
+ // reads DASHWISE_API_URL/KEY. \`next build\` statically PRERENDERS pages by
1048
+ // default and runs with NO DashWise env, so the SDK would throw
1049
+ // "missing apiBaseUrl" and the platform's first publish would fail with
1050
+ // an opaque BUILD_FAILED. Opting into dynamic rendering skips prerender.
1051
+ export const dynamic = 'force-dynamic';
1052
+
1053
+ export default async function Home() {
1054
+ const session = await getServerSession();
1055
+ if (!session) {
1056
+ redirect('/api/auth/sign-in?return_to=/');
1057
+ }
1024
1058
  return (
1025
- <main className="flex min-h-dvh flex-col items-center justify-center gap-12 px-6 py-16">
1026
- <section className="flex max-w-2xl flex-col items-center gap-6 text-center">
1027
- <h1 className="text-balance text-4xl font-semibold tracking-tight sm:text-5xl">
1059
+ <main className="mx-auto flex min-h-dvh max-w-4xl flex-col gap-10 px-6 py-12">
1060
+ {/* Primary-colored hero \u2014 the default look is colored on purpose,
1061
+ to signal the design is yours to change. */}
1062
+ <section className="flex flex-col gap-4 rounded-2xl bg-primary px-8 py-12 text-primary-foreground shadow-sm">
1063
+ <p className="text-sm font-medium uppercase tracking-wide opacity-80">
1028
1064
  ${escapeHtml(opts.name)}
1065
+ </p>
1066
+ <h1 className="text-balance text-4xl font-semibold tracking-tight sm:text-5xl">
1067
+ Your app starts here.
1029
1068
  </h1>
1030
- <p className="text-balance text-lg text-muted-foreground">
1031
- A DashWise module. Sign in with your DashWise account to access the
1032
- dashboard and start using the workspace's data through the typed SDK.
1069
+ <p className="max-w-xl text-pretty text-lg opacity-90">
1070
+ Signed in as{' '}
1071
+ <span className="font-medium">{session.user.email}</span>. This is the
1072
+ real home of your module \u2014 edit{' '}
1073
+ <code className="rounded bg-primary-foreground/15 px-1.5 py-0.5 font-mono">
1074
+ app/page.tsx
1075
+ </code>{' '}
1076
+ to build it.
1033
1077
  </p>
1034
- <div className="flex flex-wrap items-center justify-center gap-3">
1035
- <Button asChild size="lg">
1036
- <Link href="/api/auth/sign-in?return_to=/dashboard">
1037
- <LogIn aria-hidden="true" />
1038
- Sign in with DashWise
1039
- </Link>
1040
- </Button>
1041
- <Button asChild variant="ghost" size="lg">
1078
+ <div className="flex flex-wrap gap-3 pt-2">
1079
+ <Button asChild size="lg" variant="secondary">
1042
1080
  <Link href="/dashboard">
1043
- <LayoutDashboard aria-hidden="true" />
1044
- Go to dashboard
1081
+ See the example route
1082
+ <ArrowRight aria-hidden="true" />
1045
1083
  </Link>
1046
1084
  </Button>
1047
1085
  </div>
1048
1086
  </section>
1049
1087
 
1050
- <Card className="w-full max-w-2xl">
1051
- <CardHeader>
1052
- <CardTitle>What you can do here</CardTitle>
1053
- <CardDescription>
1054
- This is a custom DashWise module \u2014 a full Next.js app authenticated
1055
- against the DashWise SSO. Edit \`app/page.tsx\` to replace this
1056
- landing page; everything else is yours to customize.
1057
- </CardDescription>
1058
- </CardHeader>
1059
- <CardContent className="text-sm text-muted-foreground">
1060
- <ul className="space-y-2">
1061
- <li>
1062
- Edit <code className="rounded bg-muted px-1.5 py-0.5">module.json</code>
1063
- {' '}to declare tables, dependencies, and runtime config.
1064
- </li>
1065
- <li>
1066
- Run <code className="rounded bg-muted px-1.5 py-0.5">dashwise dev</code>
1067
- {' '}for a one-command local loop with auth env vars auto-injected.
1068
- </li>
1069
- <li>
1070
- Import typed table clients from{' '}
1071
- <code className="rounded bg-muted px-1.5 py-0.5">@dashai/generated</code>
1072
- {' '}\u2014 the SDK regenerates on every manifest save.
1073
- </li>
1074
- </ul>
1075
- </CardContent>
1076
- </Card>
1088
+ {/* A few colored pointers to get moving. */}
1089
+ <section className="grid gap-4 sm:grid-cols-3">
1090
+ <Card className="border-l-4 border-l-chart-1">
1091
+ <CardHeader>
1092
+ <Table2 className="size-5 text-chart-1" aria-hidden="true" />
1093
+ <CardTitle className="text-base">Declare your data</CardTitle>
1094
+ <CardDescription>
1095
+ Add tables to{' '}
1096
+ <code className="rounded bg-muted px-1 py-0.5 font-mono">module.json</code>{' '}
1097
+ (or run{' '}
1098
+ <code className="rounded bg-muted px-1 py-0.5 font-mono">dashwise table create</code>).
1099
+ </CardDescription>
1100
+ </CardHeader>
1101
+ </Card>
1102
+ <Card className="border-l-4 border-l-chart-2">
1103
+ <CardHeader>
1104
+ <Database className="size-5 text-chart-2" aria-hidden="true" />
1105
+ <CardTitle className="text-base">Read it, typed</CardTitle>
1106
+ <CardDescription>
1107
+ Import{' '}
1108
+ <code className="rounded bg-muted px-1 py-0.5 font-mono">db</code>{' '}
1109
+ from{' '}
1110
+ <code className="rounded bg-muted px-1 py-0.5 font-mono">@dashai/generated</code>{' '}
1111
+ \u2014 regenerated on every manifest save.
1112
+ </CardDescription>
1113
+ </CardHeader>
1114
+ </Card>
1115
+ <Card className="border-l-4 border-l-chart-3">
1116
+ <CardHeader>
1117
+ <PenLine className="size-5 text-chart-3" aria-hidden="true" />
1118
+ <CardTitle className="text-base">Write via server actions</CardTitle>
1119
+ <CardDescription>
1120
+ Mutate data from Server Actions / route handlers so the scoped{' '}
1121
+ <code className="rounded bg-muted px-1 py-0.5 font-mono">dwk_</code>{' '}
1122
+ key stays server-side.
1123
+ </CardDescription>
1124
+ </CardHeader>
1125
+ </Card>
1126
+ </section>
1077
1127
  </main>
1078
1128
  );
1079
1129
  }
@@ -1093,18 +1143,25 @@ import {
1093
1143
  } from '@/components/ui/card';
1094
1144
 
1095
1145
  /**
1096
- * Auth-gated Server Component. The middleware ensures the user has
1097
- * a session cookie before this page renders; \`getServerSession()\`
1146
+ * An ADDITIONAL example route \u2014 your real home is app/page.tsx (\`/\`).
1147
+ * This \`/dashboard\` page is here to show a second auth-gated Server
1148
+ * Component: keep it, rename it, or delete it. The middleware ensures the
1149
+ * user has a session cookie before this page renders; \`getServerSession()\`
1098
1150
  * validates the cookie against the backend's \`/api/auth/sessions/me\`
1099
1151
  * endpoint, returning the decoded \`Session\` (or \`null\` if stale).
1100
1152
  *
1101
1153
  * On a successful render, this is the polished session card \u2014 replace
1102
1154
  * the contents with whatever your module actually needs to show.
1103
1155
  */
1156
+ // force-dynamic: same reason as app/page.tsx \u2014 this route calls
1157
+ // getServerSession() at render, which needs DASHWISE_API_URL/KEY. Static
1158
+ // prerender during \`next build\` runs with no env \u2192 BUILD_FAILED. Opt out.
1159
+ export const dynamic = 'force-dynamic';
1160
+
1104
1161
  export default async function Dashboard() {
1105
1162
  const session = await getServerSession();
1106
1163
  if (!session) {
1107
- redirect('/api/auth/sign-in?return_to=/dashboard');
1164
+ redirect('/api/auth/sign-in?return_to=/');
1108
1165
  }
1109
1166
  return (
1110
1167
  <main className="mx-auto flex min-h-dvh max-w-3xl flex-col gap-6 px-6 py-12">
@@ -1157,7 +1214,9 @@ export default async function Dashboard() {
1157
1214
  <CardHeader>
1158
1215
  <CardTitle>Next steps</CardTitle>
1159
1216
  <CardDescription>
1160
- Replace this dashboard with your module's actual UI.
1217
+ This is an extra example route \u2014 your real home is{' '}
1218
+ <code className="rounded bg-muted px-1.5 py-0.5">app/page.tsx</code>.
1219
+ Keep, rename, or delete this one.
1161
1220
  </CardDescription>
1162
1221
  </CardHeader>
1163
1222
  <CardContent className="space-y-2 text-sm text-muted-foreground">
@@ -1548,7 +1607,12 @@ export default config;
1548
1607
  `;
1549
1608
  }
1550
1609
  function globalsCss() {
1551
- return `@import "tailwindcss";
1610
+ return `/* These theme tokens are a STARTING POINT \u2014 change the hues freely.
1611
+ The default \`--primary\` is a violet/indigo and the \`--chart-*\` ramp is
1612
+ a 5-hue set; swap them for your brand. The full Tailwind color palette
1613
+ (bg-indigo-600, text-emerald-500, \u2026) is also available on top of these
1614
+ tokens. This is YOUR design to own. */
1615
+ @import "tailwindcss";
1552
1616
  @import "tw-animate-css";
1553
1617
  @import "shadcn/tailwind.css";
1554
1618
 
@@ -1561,20 +1625,33 @@ function globalsCss() {
1561
1625
  --card-foreground: oklch(0.14 0 0);
1562
1626
  --popover: oklch(1 0 0);
1563
1627
  --popover-foreground: oklch(0.14 0 0);
1564
- --primary: oklch(0.2 0 0);
1565
- --primary-foreground: oklch(0.99 0 0);
1566
- --secondary: oklch(0.97 0 0);
1567
- --secondary-foreground: oklch(0.2 0 0);
1568
- --muted: oklch(0.97 0 0);
1569
- --muted-foreground: oklch(0.56 0 0);
1570
- --accent: oklch(0.97 0 0);
1571
- --accent-foreground: oklch(0.2 0 0);
1628
+ /* Colored primary (violet/indigo, ~indigo-600). White foreground reads
1629
+ cleanly on it (~5:1). Change the hue (275) to re-brand. */
1630
+ --primary: oklch(0.55 0.22 275);
1631
+ --primary-foreground: oklch(0.985 0 0);
1632
+ /* secondary + muted stay near-neutral (clean UI) but carry a faint
1633
+ violet tint so nothing is pure grayscale. */
1634
+ --secondary: oklch(0.97 0.005 275);
1635
+ --secondary-foreground: oklch(0.25 0.02 275);
1636
+ --muted: oklch(0.97 0.005 275);
1637
+ --muted-foreground: oklch(0.55 0.02 275);
1638
+ /* accent = a soft, low-chroma wash of the primary hue. */
1639
+ --accent: oklch(0.96 0.03 275);
1640
+ --accent-foreground: oklch(0.4 0.1 275);
1572
1641
  --destructive: oklch(0.58 0.24 28.48);
1573
1642
  --border: oklch(0.92 0 0);
1574
1643
  --input: oklch(0.92 0 0);
1575
- --ring: oklch(0.71 0 0);
1644
+ --ring: oklch(0.55 0.2 275);
1576
1645
  --radius: 0.625rem;
1577
1646
 
1647
+ /* 5-hue chart ramp \u2014 distinct, readable hues for data viz + accents.
1648
+ Wired to \`bg-chart-1\` \u2026 \`text-chart-5\` via the @theme block below. */
1649
+ --chart-1: oklch(0.55 0.2 275);
1650
+ --chart-2: oklch(0.6 0.13 195);
1651
+ --chart-3: oklch(0.7 0.16 70);
1652
+ --chart-4: oklch(0.65 0.17 145);
1653
+ --chart-5: oklch(0.6 0.22 15);
1654
+
1578
1655
  --font-sans:
1579
1656
  ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
1580
1657
  "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
@@ -1590,18 +1667,37 @@ function globalsCss() {
1590
1667
  --card-foreground: oklch(0.99 0 0);
1591
1668
  --popover: oklch(0.2 0 0);
1592
1669
  --popover-foreground: oklch(0.99 0 0);
1593
- --primary: oklch(0.92 0 0);
1594
- --primary-foreground: oklch(0.2 0 0);
1595
- --secondary: oklch(0.27 0 0);
1670
+ /* Slightly lighter violet so it pops on the dark background; white
1671
+ foreground stays readable (~4.8:1). */
1672
+ --primary: oklch(0.58 0.21 275);
1673
+ --primary-foreground: oklch(0.985 0 0);
1674
+ --secondary: oklch(0.27 0.01 275);
1596
1675
  --secondary-foreground: oklch(0.99 0 0);
1597
- --muted: oklch(0.27 0 0);
1598
- --muted-foreground: oklch(0.71 0 0);
1599
- --accent: oklch(0.27 0 0);
1600
- --accent-foreground: oklch(0.99 0 0);
1676
+ --muted: oklch(0.27 0.01 275);
1677
+ --muted-foreground: oklch(0.71 0.02 275);
1678
+ --accent: oklch(0.3 0.04 275);
1679
+ --accent-foreground: oklch(0.97 0.01 275);
1601
1680
  --destructive: oklch(0.7 0.19 22.23);
1602
1681
  --border: oklch(0.27 0 0);
1603
1682
  --input: oklch(0.27 0 0);
1604
- --ring: oklch(0.56 0 0);
1683
+ --ring: oklch(0.58 0.19 275);
1684
+
1685
+ --chart-1: oklch(0.62 0.19 275);
1686
+ --chart-2: oklch(0.67 0.13 195);
1687
+ --chart-3: oklch(0.75 0.15 70);
1688
+ --chart-4: oklch(0.7 0.16 145);
1689
+ --chart-5: oklch(0.67 0.2 15);
1690
+ }
1691
+
1692
+ /* Wire the chart tokens into Tailwind's color namespace so \`bg-chart-1\`,
1693
+ \`text-chart-2\`, \`border-l-chart-3\`, \u2026 resolve. (\`@import "shadcn/tailwind.css"\`
1694
+ already maps the core tokens like \`--primary\`; this covers the ramp.) */
1695
+ @theme inline {
1696
+ --color-chart-1: var(--chart-1);
1697
+ --color-chart-2: var(--chart-2);
1698
+ --color-chart-3: var(--chart-3);
1699
+ --color-chart-4: var(--chart-4);
1700
+ --color-chart-5: var(--chart-5);
1605
1701
  }
1606
1702
 
1607
1703
  * {
@@ -1872,13 +1968,13 @@ export default async function SignInErrorPage({ searchParams }: SignInErrorPageP
1872
1968
  )}
1873
1969
  <div className="flex flex-wrap gap-2 pt-2">
1874
1970
  <Button asChild>
1875
- <Link href="/api/auth/sign-in?return_to=/dashboard">
1971
+ <Link href="/api/auth/sign-in?return_to=/">
1876
1972
  <RefreshCw aria-hidden="true" />
1877
1973
  Try again
1878
1974
  </Link>
1879
1975
  </Button>
1880
1976
  <Button asChild variant="ghost">
1881
- <Link href="/">Back to landing</Link>
1977
+ <Link href="/">Back to home</Link>
1882
1978
  </Button>
1883
1979
  </div>
1884
1980
  </CardContent>
@@ -1960,10 +2056,10 @@ dashwise field remove <table> <slug>
1960
2056
  dashwise field set-type <table> <slug> <type>
1961
2057
  \`\`\`
1962
2058
 
1963
- Allowed field types: \`text\`, \`long_text\`, \`number\`, \`boolean\`,
2059
+ Allowed field types (18): \`text\`, \`long_text\`, \`number\`, \`boolean\`,
1964
2060
  \`date\`, \`datetime\`, \`single_select\`, \`multi_select\`,
1965
2061
  \`link_row\`, \`file\`, \`multi_file\`, \`email\`, \`url\`,
1966
- \`phone\`, \`formula\`, \`ai\`.
2062
+ \`phone\`, \`formula\`, \`ai\`, \`uuid\`, \`jsonb\`.
1967
2063
 
1968
2064
  ## Read + write data via the generated SDK
1969
2065
 
@@ -1978,8 +2074,10 @@ import { db, qb } from '@dashai/generated';
1978
2074
  const rows = await db.<table>.list();
1979
2075
  await db.<table>.create({ /* fields */ });
1980
2076
 
1981
- // Query builder (joins, aggregates)
1982
- const stats = await qb.from('<table>').where({ /* ... */ }).execute();
2077
+ // Query builder \u2014 a per-table getter (NOT a \`.from()\` call); \`.where()\`
2078
+ // takes a single clause object (field \u2192 value or field \u2192 operator), and
2079
+ // \`.execute()\` runs it. Chain \`.orderBy()\` / \`.limit()\` / \`.count()\` etc.
2080
+ const open = await qb.<table>.where({ status: { equal: 'open' } }).execute();
1983
2081
  \`\`\`
1984
2082
 
1985
2083
  The data plane authenticates via the per-module scoped \`dwk_\` API key
@@ -2025,7 +2123,37 @@ In Server Components: \`await getServerSession()\` (from
2025
2123
  In Client Components: \`useSession()\` (from
2026
2124
  \`@dashai/sdk/auth/client\`), wrapped in \`<AuthProvider>\` (already
2027
2125
  in \`app/layout.tsx\`).
2028
-
2126
+ ${isCustom ? `
2127
+ ## Design & home
2128
+
2129
+ You OWN the UI \u2014 layout, components, and colors. Edit the theme tokens in
2130
+ \`app/globals.css\` freely (they're a starting point, not a mandate), reach
2131
+ for any Tailwind color (\`bg-indigo-600\`, \`text-emerald-500\`, \u2026), and pull
2132
+ in more shadcn blocks (see the README's "Adding UI blocks").
2133
+
2134
+ The app's **home is \`app/page.tsx\`, served at \`/\`** \u2014 build the real app
2135
+ there. It's auth-gated: a platform user lands on it zero-click (the
2136
+ middleware's \`autoSignIn\` signs them in silently), and \`app/dashboard/page.tsx\`
2137
+ is just an extra example route. A manual "Sign in with DashWise" landing
2138
+ page belongs ONLY to a \`public_access_allowed\` module that wants an
2139
+ anonymous entry point (re-add \`'/'\` to \`middleware.ts\` publicPaths + drop
2140
+ the home's session guard to build one).
2141
+
2142
+ ## Before you publish
2143
+
2144
+ Add \`export const dynamic = 'force-dynamic'\` to the top of ANY page that
2145
+ reads the session or data at render (calls \`getServerSession()\` or a
2146
+ \`dwk_\`-authenticated data call). Without it, \`next build\` statically
2147
+ prerenders the page with NO DashWise env vars, the SDK throws
2148
+ \`missing apiBaseUrl\`, and the platform's first publish fails with an
2149
+ opaque \`BUILD_FAILED\`. The scaffolded \`app/page.tsx\` + \`app/dashboard/page.tsx\`
2150
+ already have it \u2014 any new session-gated page you add needs it too.
2151
+
2152
+ A passing local \`npm run build\` is NOT proof: your \`.env.local\` supplies
2153
+ the env the platform build won't have, so it masks the failure. \`GET\` route
2154
+ handlers under \`app/api/*\` are dynamic by default in Next 15, so they don't
2155
+ hit this trap.
2156
+ ` : ""}
2029
2157
  ## Local dev lifecycle
2030
2158
 
2031
2159
  \`\`\`
@@ -9943,7 +10071,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
9943
10071
  }
9944
10072
  })();
9945
10073
  function getVersion() {
9946
- return "0.13.0";
10074
+ return "0.14.0";
9947
10075
  }
9948
10076
  function parseIntOption(value) {
9949
10077
  const n = Number.parseInt(value, 10);