@getjack/jack 0.1.19 → 0.1.22

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 (105) hide show
  1. package/package.json +5 -2
  2. package/src/commands/down.ts +11 -1
  3. package/src/commands/init.ts +19 -6
  4. package/src/commands/new.ts +56 -4
  5. package/src/commands/publish.ts +1 -1
  6. package/src/lib/agents.ts +3 -1
  7. package/src/lib/auth/ensure-auth.test.ts +3 -3
  8. package/src/lib/control-plane.ts +15 -1
  9. package/src/lib/deploy-upload.ts +26 -1
  10. package/src/lib/hooks.ts +232 -1
  11. package/src/lib/managed-deploy.ts +13 -6
  12. package/src/lib/managed-down.ts +66 -45
  13. package/src/lib/progress.ts +76 -5
  14. package/src/lib/project-list.ts +6 -1
  15. package/src/lib/project-operations.ts +21 -31
  16. package/src/lib/project-resolver.ts +1 -1
  17. package/src/lib/zip-packager.ts +36 -7
  18. package/src/templates/index.ts +1 -1
  19. package/src/templates/types.ts +16 -0
  20. package/templates/CLAUDE.md +172 -5
  21. package/templates/miniapp/.jack.json +1 -3
  22. package/templates/saas/.jack.json +154 -0
  23. package/templates/saas/AGENTS.md +333 -0
  24. package/templates/saas/bun.lock +925 -0
  25. package/templates/saas/components.json +21 -0
  26. package/templates/saas/index.html +12 -0
  27. package/templates/saas/package.json +75 -0
  28. package/templates/saas/public/icon.png +0 -0
  29. package/templates/saas/public/og.png +0 -0
  30. package/templates/saas/schema.sql +73 -0
  31. package/templates/saas/src/auth.ts +77 -0
  32. package/templates/saas/src/client/App.tsx +63 -0
  33. package/templates/saas/src/client/components/ProtectedRoute.tsx +29 -0
  34. package/templates/saas/src/client/components/ThemeToggle.tsx +32 -0
  35. package/templates/saas/src/client/components/ui/accordion.tsx +62 -0
  36. package/templates/saas/src/client/components/ui/alert-dialog.tsx +133 -0
  37. package/templates/saas/src/client/components/ui/alert.tsx +60 -0
  38. package/templates/saas/src/client/components/ui/aspect-ratio.tsx +9 -0
  39. package/templates/saas/src/client/components/ui/avatar.tsx +39 -0
  40. package/templates/saas/src/client/components/ui/badge.tsx +39 -0
  41. package/templates/saas/src/client/components/ui/breadcrumb.tsx +102 -0
  42. package/templates/saas/src/client/components/ui/button-group.tsx +78 -0
  43. package/templates/saas/src/client/components/ui/button.tsx +60 -0
  44. package/templates/saas/src/client/components/ui/card.tsx +75 -0
  45. package/templates/saas/src/client/components/ui/carousel.tsx +228 -0
  46. package/templates/saas/src/client/components/ui/chart.tsx +326 -0
  47. package/templates/saas/src/client/components/ui/checkbox.tsx +29 -0
  48. package/templates/saas/src/client/components/ui/collapsible.tsx +19 -0
  49. package/templates/saas/src/client/components/ui/command.tsx +159 -0
  50. package/templates/saas/src/client/components/ui/context-menu.tsx +224 -0
  51. package/templates/saas/src/client/components/ui/dialog.tsx +127 -0
  52. package/templates/saas/src/client/components/ui/drawer.tsx +124 -0
  53. package/templates/saas/src/client/components/ui/dropdown-menu.tsx +226 -0
  54. package/templates/saas/src/client/components/ui/empty.tsx +94 -0
  55. package/templates/saas/src/client/components/ui/field.tsx +232 -0
  56. package/templates/saas/src/client/components/ui/form.tsx +152 -0
  57. package/templates/saas/src/client/components/ui/hover-card.tsx +38 -0
  58. package/templates/saas/src/client/components/ui/input-group.tsx +158 -0
  59. package/templates/saas/src/client/components/ui/input-otp.tsx +68 -0
  60. package/templates/saas/src/client/components/ui/input.tsx +21 -0
  61. package/templates/saas/src/client/components/ui/item.tsx +172 -0
  62. package/templates/saas/src/client/components/ui/kbd.tsx +28 -0
  63. package/templates/saas/src/client/components/ui/label.tsx +21 -0
  64. package/templates/saas/src/client/components/ui/menubar.tsx +250 -0
  65. package/templates/saas/src/client/components/ui/navigation-menu.tsx +161 -0
  66. package/templates/saas/src/client/components/ui/pagination.tsx +106 -0
  67. package/templates/saas/src/client/components/ui/popover.tsx +42 -0
  68. package/templates/saas/src/client/components/ui/progress.tsx +26 -0
  69. package/templates/saas/src/client/components/ui/radio-group.tsx +45 -0
  70. package/templates/saas/src/client/components/ui/resizable.tsx +46 -0
  71. package/templates/saas/src/client/components/ui/scroll-area.tsx +56 -0
  72. package/templates/saas/src/client/components/ui/select.tsx +173 -0
  73. package/templates/saas/src/client/components/ui/separator.tsx +28 -0
  74. package/templates/saas/src/client/components/ui/sheet.tsx +128 -0
  75. package/templates/saas/src/client/components/ui/sidebar.tsx +694 -0
  76. package/templates/saas/src/client/components/ui/skeleton.tsx +13 -0
  77. package/templates/saas/src/client/components/ui/slider.tsx +58 -0
  78. package/templates/saas/src/client/components/ui/sonner.tsx +38 -0
  79. package/templates/saas/src/client/components/ui/spinner.tsx +16 -0
  80. package/templates/saas/src/client/components/ui/switch.tsx +28 -0
  81. package/templates/saas/src/client/components/ui/table.tsx +90 -0
  82. package/templates/saas/src/client/components/ui/tabs.tsx +54 -0
  83. package/templates/saas/src/client/components/ui/textarea.tsx +18 -0
  84. package/templates/saas/src/client/components/ui/toggle-group.tsx +80 -0
  85. package/templates/saas/src/client/components/ui/toggle.tsx +44 -0
  86. package/templates/saas/src/client/components/ui/tooltip.tsx +57 -0
  87. package/templates/saas/src/client/hooks/use-mobile.ts +19 -0
  88. package/templates/saas/src/client/hooks/useAuth.ts +14 -0
  89. package/templates/saas/src/client/hooks/useSubscription.ts +86 -0
  90. package/templates/saas/src/client/index.css +165 -0
  91. package/templates/saas/src/client/lib/auth-client.ts +7 -0
  92. package/templates/saas/src/client/lib/plans.ts +82 -0
  93. package/templates/saas/src/client/lib/utils.ts +6 -0
  94. package/templates/saas/src/client/main.tsx +15 -0
  95. package/templates/saas/src/client/pages/DashboardPage.tsx +394 -0
  96. package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +153 -0
  97. package/templates/saas/src/client/pages/HomePage.tsx +285 -0
  98. package/templates/saas/src/client/pages/LoginPage.tsx +169 -0
  99. package/templates/saas/src/client/pages/PricingPage.tsx +467 -0
  100. package/templates/saas/src/client/pages/ResetPasswordPage.tsx +200 -0
  101. package/templates/saas/src/client/pages/SignupPage.tsx +192 -0
  102. package/templates/saas/src/index.ts +208 -0
  103. package/templates/saas/tsconfig.json +18 -0
  104. package/templates/saas/vite.config.ts +14 -0
  105. package/templates/saas/wrangler.jsonc +20 -0
@@ -0,0 +1,333 @@
1
+ # Agent Context
2
+
3
+ ## Infrastructure
4
+
5
+ This project uses **jack** for deployment. When Jack MCP is connected, prefer `mcp__jack__*` tools over CLI commands - they're cloud-aware and faster.
6
+
7
+ **Do NOT use wrangler directly.** Jack manages config and may use cloud-hosted resources where wrangler won't work.
8
+
9
+ Common operations:
10
+ - **Deploy**: `jack ship` or Jack MCP
11
+ - **Database**: `jack services db create` or Jack MCP
12
+ - **Status**: `jack status` or Jack MCP
13
+
14
+ ## Project Structure
15
+
16
+ ```
17
+ src/
18
+ ├── index.ts # Hono API entry point
19
+ ├── auth.ts # Better Auth configuration
20
+ ├── client/ # React frontend
21
+ │ ├── App.tsx # Application entry
22
+ │ ├── main.tsx # React entry point
23
+ │ ├── index.css # Tailwind + theme CSS
24
+ │ ├── lib/
25
+ │ │ ├── auth-client.ts # Better Auth client
26
+ │ │ └── utils.ts # Utility functions (cn)
27
+ │ ├── hooks/
28
+ │ │ ├── useAuth.ts # Auth hook
29
+ │ │ └── useSubscription.ts # Subscription hook
30
+ │ ├── pages/ # Page components
31
+ │ │ ├── HomePage.tsx # Public landing page
32
+ │ │ ├── LoginPage.tsx # Login form
33
+ │ │ ├── SignupPage.tsx # Signup form
34
+ │ │ ├── DashboardPage.tsx # Protected dashboard
35
+ │ │ └── PricingPage.tsx # Pricing page
36
+ │ └── components/ # Reusable components
37
+ │ └── ui/ # shadcn/ui components
38
+ └── db/
39
+ └── schema.sql # D1 database schema
40
+ ```
41
+
42
+ ## Authentication (Better Auth)
43
+
44
+ This template uses Better Auth with email/password authentication.
45
+
46
+ ### Server-side Setup (src/auth.ts)
47
+
48
+ ```typescript
49
+ import { betterAuth } from "better-auth";
50
+ import { stripe } from "@better-auth/stripe";
51
+ import { Kysely } from "kysely";
52
+ import { D1Dialect } from "kysely-d1";
53
+
54
+ export function createAuth(env: Env) {
55
+ // Use Kysely with D1 dialect - Better Auth uses Kysely internally for D1
56
+ const db = new Kysely<any>({
57
+ dialect: new D1Dialect({ database: env.DB }),
58
+ });
59
+
60
+ return betterAuth({
61
+ database: { db, type: "sqlite" },
62
+ emailAndPassword: { enabled: true },
63
+ secret: env.BETTER_AUTH_SECRET,
64
+ plugins: [stripe({ /* config */ })],
65
+ });
66
+ }
67
+ ```
68
+
69
+ ### Client-side Usage
70
+
71
+ ```tsx
72
+ import { authClient } from './lib/auth-client';
73
+
74
+ // Sign up new user
75
+ await authClient.signUp.email({
76
+ email: 'user@example.com',
77
+ password: 'password123',
78
+ name: 'John Doe'
79
+ });
80
+
81
+ // Sign in existing user
82
+ await authClient.signIn.email({
83
+ email: 'user@example.com',
84
+ password: 'password123'
85
+ });
86
+
87
+ // Get current session
88
+ const { data: session } = await authClient.getSession();
89
+ if (session) {
90
+ console.log('Logged in as:', session.user.email);
91
+ }
92
+
93
+ // Sign out
94
+ await authClient.signOut();
95
+ ```
96
+
97
+ ## Payments (Stripe)
98
+
99
+ Uses Better Auth's Stripe plugin for subscription management. Webhooks are handled automatically at `/api/auth/stripe/webhook`.
100
+
101
+ ### Subscription Tiers
102
+
103
+ - **Free**: No payment required, basic features
104
+ - **Pro**: Monthly subscription, full features
105
+ - **Enterprise**: Monthly subscription, premium support
106
+
107
+ ### Upgrading a User
108
+
109
+ ```tsx
110
+ import { authClient } from './lib/auth-client';
111
+
112
+ async function handleUpgrade(plan: 'pro' | 'enterprise') {
113
+ const { data, error } = await authClient.subscription.upgrade({
114
+ plan,
115
+ successUrl: '/dashboard?upgraded=true',
116
+ cancelUrl: '/pricing',
117
+ });
118
+
119
+ if (data?.url) {
120
+ // Redirect to Stripe Checkout
121
+ window.location.href = data.url;
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### Checking Subscription Status
127
+
128
+ ```tsx
129
+ import { authClient } from './lib/auth-client';
130
+
131
+ async function getCurrentPlan() {
132
+ const { data: subscriptions } = await authClient.subscription.list();
133
+
134
+ const activeSubscription = subscriptions?.find(
135
+ s => s.status === 'active' || s.status === 'trialing'
136
+ );
137
+
138
+ return activeSubscription?.plan || 'free';
139
+ }
140
+ ```
141
+
142
+ ## Adding New Pages
143
+
144
+ 1. Create component in `src/client/pages/NewPage.tsx`
145
+ 2. Add route in `src/client/App.tsx`
146
+ 3. For protected pages, check session before rendering
147
+
148
+ ### Protected Page Pattern
149
+
150
+ ```tsx
151
+ import { authClient } from '../lib/auth-client';
152
+ import { useEffect, useState } from 'react';
153
+
154
+ export function ProtectedPage() {
155
+ const [session, setSession] = useState(null);
156
+ const [loading, setLoading] = useState(true);
157
+
158
+ useEffect(() => {
159
+ authClient.getSession().then(({ data }) => {
160
+ setSession(data);
161
+ setLoading(false);
162
+ });
163
+ }, []);
164
+
165
+ if (loading) return <div>Loading...</div>;
166
+ if (!session) {
167
+ window.location.href = '/login';
168
+ return null;
169
+ }
170
+
171
+ return <div>Protected content for {session.user.email}</div>;
172
+ }
173
+ ```
174
+
175
+ ## Adding API Routes
176
+
177
+ API routes are defined in `src/index.ts` using Hono.
178
+
179
+ ```typescript
180
+ import { Hono } from "hono";
181
+
182
+ const app = new Hono<{ Bindings: Env }>();
183
+
184
+ // Public route
185
+ app.get("/api/health", (c) => c.json({ status: "ok" }));
186
+
187
+ // Protected route (check auth)
188
+ app.get("/api/me", async (c) => {
189
+ const auth = createAuth(c.env);
190
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
191
+
192
+ if (!session) {
193
+ return c.json({ error: "Unauthorized" }, 401);
194
+ }
195
+
196
+ return c.json({ user: session.user });
197
+ });
198
+ ```
199
+
200
+ ## Database Schema
201
+
202
+ The D1 database uses Better Auth's default schema (singular table names, camelCase columns):
203
+
204
+ - `user` - User accounts with email, name, stripeCustomerId
205
+ - `session` - Active user sessions
206
+ - `account` - OAuth provider accounts (includes password for email/password auth)
207
+ - `verification` - Email verification tokens
208
+ - `subscription` - Stripe subscription records
209
+
210
+ To modify the schema, edit `schema.sql` and run migrations with jack.
211
+
212
+ ## Stripe Setup (Required Order)
213
+
214
+ ### 1. Initial Deploy & Webhook Setup
215
+
216
+ The webhook secret requires the deployment URL, so follow this order:
217
+
218
+ ```bash
219
+ # 1. Deploy first (without webhook secret - subscriptions won't work yet)
220
+ jack ship
221
+
222
+ # 2. Get your deployment URL
223
+ jack status # e.g., https://your-app.runjack.xyz
224
+
225
+ # 3. Create webhook in Stripe Dashboard:
226
+ # - Go to: Developers → Webhooks → Add endpoint
227
+ # - URL: https://your-app.runjack.xyz/api/auth/stripe/webhook
228
+ # - Events: customer.subscription.created, customer.subscription.updated,
229
+ # customer.subscription.deleted, checkout.session.completed
230
+ # - Copy the signing secret (whsec_...)
231
+
232
+ # 4. Set the webhook secret
233
+ jack secrets set STRIPE_WEBHOOK_SECRET whsec_your_secret_here
234
+
235
+ # 5. Redeploy to enable Stripe plugin
236
+ jack ship
237
+ ```
238
+
239
+ **Important:** The Stripe plugin is DISABLED if `STRIPE_WEBHOOK_SECRET` is missing. This prevents silent sync failures.
240
+
241
+ ### 2. Customer Portal Setup
242
+
243
+ For subscription upgrades/downgrades to work:
244
+
245
+ 1. Go to [Stripe Dashboard → Settings → Billing → Customer Portal](https://dashboard.stripe.com/settings/billing/portal)
246
+ 2. Enable **"Customers can switch plans"**
247
+ 3. Add your products/prices to the allowed list
248
+ 4. Enable **"Customers can cancel subscriptions"** if you want in-portal cancellation
249
+
250
+ Without this, users will see: *"This subscription cannot be updated because the subscription update feature in the portal configuration is disabled."*
251
+
252
+ ## Environment Variables
253
+
254
+ Required secrets (set via `jack secrets set KEY value`):
255
+
256
+ | Secret | Description | Where to get it |
257
+ |--------|-------------|-----------------|
258
+ | `BETTER_AUTH_SECRET` | Random secret for auth tokens | `openssl rand -base64 32` |
259
+ | `STRIPE_SECRET_KEY` | Stripe API secret key | [Stripe Dashboard](https://dashboard.stripe.com/apikeys) |
260
+ | `STRIPE_WEBHOOK_SECRET` | Webhook signing secret (whsec_...) | Created after adding webhook |
261
+ | `STRIPE_PRO_PRICE_ID` | Price ID for Pro plan | [Stripe Products](https://dashboard.stripe.com/products) |
262
+ | `STRIPE_ENTERPRISE_PRICE_ID` | Price ID for Enterprise plan | [Stripe Products](https://dashboard.stripe.com/products) |
263
+
264
+ ## SQL Execution
265
+
266
+ Jack supports secure SQL execution against D1 databases:
267
+
268
+ **Via MCP** (preferred for agents):
269
+ - `execute_sql({ sql: "SELECT * FROM user" })` - read queries work by default
270
+ - `execute_sql({ sql: "INSERT...", allow_write: true })` - writes require allow_write
271
+ - Destructive ops (DROP, TRUNCATE, ALTER) are blocked via MCP - use CLI
272
+
273
+ **Via CLI**:
274
+ - `jack services db execute "SELECT * FROM user"` - read queries
275
+ - `jack services db execute "INSERT..." --write` - write queries
276
+ - `jack services db execute --file schema.sql --write` - run SQL from file
277
+
278
+ ## Customizing Theme
279
+
280
+ The template uses shadcn/ui CSS variables for theming. To customize:
281
+
282
+ 1. Visit [ui.shadcn.com/create](https://ui.shadcn.com/create)
283
+ 2. Design your theme with the visual editor
284
+ 3. Copy the generated CSS variables
285
+ 4. Replace the `:root` and `.dark` blocks in `src/client/index.css`
286
+
287
+ The default theme uses neutral colors. The CSS variables control all component colors:
288
+
289
+ ```css
290
+ :root {
291
+ --background: 0 0% 100%;
292
+ --foreground: 0 0% 3.9%;
293
+ --primary: 0 0% 9%;
294
+ --primary-foreground: 0 0% 98%;
295
+ /* ... etc */
296
+ }
297
+ ```
298
+
299
+ ## External API Integration Principles
300
+
301
+ ### 1. Don't trust cached data for real-time state
302
+
303
+ Libraries that sync external data via webhooks (like Better Auth's Stripe plugin) provide **eventual consistency**, not real-time truth. Webhooks may be delayed, fail silently, or not be configured yet.
304
+
305
+ **Rule**: For user-facing state that must be accurate (subscription status, payment state, cancellation), query the source API directly rather than relying on locally cached data.
306
+
307
+ ### 2. Defensive coding for external data
308
+
309
+ External APIs return unpredictable shapes. Fields you expect may be null, undefined, or have different types in edge cases.
310
+
311
+ **Rule**: Always add null checks before transforming external data, especially timestamps, nested objects, and optional fields.
312
+
313
+ ### 3. Return actionable error details
314
+
315
+ Generic "500 Internal Server Error" messages waste debugging time. During development, include the actual error.
316
+
317
+ **Rule**: In catch blocks, return `details: err.message` so you can see what actually failed. Strip these in production if needed.
318
+
319
+ ### 4. Test the unhappy paths
320
+
321
+ The happy path (user signs up, subscribes, uses app) usually works. The bugs hide in: cancellation flows, expired states, partial failures, re-subscription after cancel.
322
+
323
+ **Rule**: Manually test state transitions, not just initial states.
324
+
325
+ ## Resources
326
+
327
+ - [Better Auth Docs](https://www.betterauth.com/docs)
328
+ - [Better Auth Stripe Plugin](https://www.betterauth.com/docs/plugins/stripe)
329
+ - [Stripe Billing Docs](https://docs.stripe.com/billing)
330
+ - [Stripe Webhooks](https://docs.stripe.com/billing/subscriptions/webhooks)
331
+ - [shadcn/ui Theme Creator](https://ui.shadcn.com/create)
332
+ - [Hono Documentation](https://hono.dev)
333
+ - [Cloudflare D1 Docs](https://developers.cloudflare.com/d1)