create-whop-kit 0.4.0 → 0.5.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/README.md +83 -20
- package/dist/chunk-BR467LBM.js +370 -0
- package/dist/cli-create.js +91 -94
- package/dist/cli-kit.js +173 -23
- package/package.json +1 -1
- package/dist/chunk-M4AXERQP.js +0 -73
package/README.md
CHANGED
|
@@ -1,41 +1,104 @@
|
|
|
1
1
|
# create-whop-kit
|
|
2
2
|
|
|
3
|
-
Scaffold
|
|
3
|
+
Scaffold and manage [Whop](https://whop.com)-powered apps with [whop-kit](https://www.npmjs.com/package/whop-kit).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Create a new project
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx create-whop-kit my-app
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Interactive prompts guide you through:
|
|
12
12
|
|
|
13
|
-
1. **What you
|
|
14
|
-
2. **
|
|
15
|
-
3. **
|
|
13
|
+
1. **What are you building?** — SaaS (full dashboard + billing) or Blank (just auth + webhooks)
|
|
14
|
+
2. **Which framework?** — Next.js or Astro
|
|
15
|
+
3. **Which database?** — Neon (auto-provisioned), Prisma Postgres (instant), Supabase, manual URL, or skip
|
|
16
|
+
4. **Whop credentials** — App ID, API key, webhook secret (optional, can use setup wizard later)
|
|
16
17
|
|
|
17
|
-
The CLI clones
|
|
18
|
+
The CLI clones a template, provisions your database, writes `.env.local`, installs dependencies, and initializes git.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
### Non-interactive mode
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
#
|
|
23
|
-
npx create-whop-kit my-app
|
|
23
|
+
# Skip all prompts
|
|
24
|
+
npx create-whop-kit my-app --framework nextjs --type saas --db neon --yes
|
|
25
|
+
|
|
26
|
+
# With credentials
|
|
27
|
+
npx create-whop-kit my-app --framework nextjs --db later --app-id "app_xxx" --api-key "apik_xxx"
|
|
24
28
|
|
|
25
|
-
#
|
|
26
|
-
npx create-whop-kit
|
|
29
|
+
# Preview without creating files
|
|
30
|
+
npx create-whop-kit my-app --framework nextjs --db later --dry-run
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
### All flags
|
|
34
|
+
|
|
35
|
+
| Flag | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `--framework` | `nextjs` or `astro` |
|
|
38
|
+
| `--type` | `saas` or `blank` (default: `saas`) |
|
|
39
|
+
| `--db` | `neon`, `prisma-postgres`, `supabase`, `manual`, `later` |
|
|
40
|
+
| `--db-url` | PostgreSQL connection URL (skips DB provisioning) |
|
|
41
|
+
| `--app-id` | Whop App ID |
|
|
42
|
+
| `--api-key` | Whop API Key |
|
|
43
|
+
| `--webhook-secret` | Whop webhook secret |
|
|
44
|
+
| `-y, --yes` | Skip optional prompts |
|
|
45
|
+
| `--dry-run` | Show what would be created |
|
|
46
|
+
| `--verbose` | Detailed output |
|
|
47
|
+
|
|
48
|
+
## Manage your project
|
|
49
|
+
|
|
50
|
+
After creating a project, use `whop-kit` to add features and check status:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Check project health
|
|
54
|
+
npx whop-kit status
|
|
55
|
+
|
|
56
|
+
# Add email (Resend or SendGrid)
|
|
57
|
+
npx whop-kit add email
|
|
58
|
+
|
|
59
|
+
# Add analytics (PostHog, Google Analytics, or Plausible)
|
|
60
|
+
npx whop-kit add analytics
|
|
61
|
+
|
|
62
|
+
# Add a webhook event handler
|
|
63
|
+
npx whop-kit add webhook-event
|
|
64
|
+
|
|
65
|
+
# Open provider dashboards
|
|
66
|
+
npx whop-kit open whop
|
|
67
|
+
npx whop-kit open neon
|
|
68
|
+
npx whop-kit open vercel
|
|
69
|
+
|
|
70
|
+
# Update whop-kit to latest
|
|
71
|
+
npx whop-kit upgrade
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Database provisioning
|
|
75
|
+
|
|
76
|
+
The CLI can provision databases automatically — no need to leave the terminal:
|
|
77
|
+
|
|
78
|
+
| Provider | How it works |
|
|
79
|
+
|----------|-------------|
|
|
80
|
+
| **Neon** | Installs `neonctl` → authenticates (browser) → creates project → gets connection string |
|
|
81
|
+
| **Prisma Postgres** | Runs `npx create-db` → instant database, no account needed |
|
|
82
|
+
| **Supabase** | Installs CLI → authenticates → creates project → guides you to get connection string |
|
|
83
|
+
|
|
29
84
|
## Templates
|
|
30
85
|
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
| SaaS | Next.js | Available |
|
|
34
|
-
| SaaS | Astro |
|
|
35
|
-
|
|
|
36
|
-
| Course | — | Coming soon |
|
|
37
|
-
| Community | — | Coming soon |
|
|
38
|
-
|
|
86
|
+
| App Type | Framework | Template | Status |
|
|
87
|
+
|----------|-----------|----------|--------|
|
|
88
|
+
| SaaS | Next.js | Full dashboard, pricing, billing, docs | Available |
|
|
89
|
+
| SaaS | Astro | Auth, payments, webhooks | Available |
|
|
90
|
+
| Blank | Next.js | Just auth + webhooks — build anything | Available |
|
|
91
|
+
| Course | — | — | Coming soon |
|
|
92
|
+
| Community | — | — | Coming soon |
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
1. **Template** — clones a starter repo from GitHub
|
|
97
|
+
2. **Database** — optionally provisions via provider CLI
|
|
98
|
+
3. **Environment** — writes `.env.local` from the template's `.env.example`
|
|
99
|
+
4. **Manifest** — creates `.whop/config.json` tracking your project state
|
|
100
|
+
5. **Dependencies** — installs with your preferred package manager
|
|
101
|
+
6. **Git** — initializes a fresh repo
|
|
39
102
|
|
|
40
103
|
## License
|
|
41
104
|
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/templates.ts
|
|
4
|
+
var FRAMEWORKS = {
|
|
5
|
+
nextjs: {
|
|
6
|
+
name: "Next.js",
|
|
7
|
+
description: "Full-stack React with App Router, SSR, and API routes",
|
|
8
|
+
available: true
|
|
9
|
+
},
|
|
10
|
+
astro: {
|
|
11
|
+
name: "Astro",
|
|
12
|
+
description: "Content-focused with islands architecture",
|
|
13
|
+
available: true
|
|
14
|
+
},
|
|
15
|
+
tanstack: {
|
|
16
|
+
name: "TanStack Start",
|
|
17
|
+
description: "Full-stack React with TanStack Router",
|
|
18
|
+
available: false
|
|
19
|
+
},
|
|
20
|
+
vite: {
|
|
21
|
+
name: "Vite + React",
|
|
22
|
+
description: "Lightweight SPA with Vite bundler",
|
|
23
|
+
available: false
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var TEMPLATES = {
|
|
27
|
+
"saas:nextjs": {
|
|
28
|
+
name: "Next.js SaaS",
|
|
29
|
+
description: "Full SaaS with dashboard, pricing, billing, and docs",
|
|
30
|
+
repo: "colinmcdermott/whop-saas-starter-v2",
|
|
31
|
+
available: true
|
|
32
|
+
},
|
|
33
|
+
"saas:astro": {
|
|
34
|
+
name: "Astro SaaS",
|
|
35
|
+
description: "SaaS with auth, payments, and webhooks",
|
|
36
|
+
repo: "colinmcdermott/whop-astro-starter",
|
|
37
|
+
available: true
|
|
38
|
+
},
|
|
39
|
+
"blank:nextjs": {
|
|
40
|
+
name: "Next.js Blank",
|
|
41
|
+
description: "Just auth + webhooks \u2014 build anything",
|
|
42
|
+
repo: "colinmcdermott/whop-blank-starter",
|
|
43
|
+
available: true
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
function getTemplate(appType, framework) {
|
|
47
|
+
return TEMPLATES[`${appType}:${framework}`] ?? null;
|
|
48
|
+
}
|
|
49
|
+
var APP_TYPES = {
|
|
50
|
+
saas: {
|
|
51
|
+
name: "SaaS",
|
|
52
|
+
description: "Subscription tiers, dashboard, billing portal",
|
|
53
|
+
available: true
|
|
54
|
+
},
|
|
55
|
+
blank: {
|
|
56
|
+
name: "Blank",
|
|
57
|
+
description: "Just auth + payments, you build the rest",
|
|
58
|
+
available: true
|
|
59
|
+
},
|
|
60
|
+
course: {
|
|
61
|
+
name: "Course",
|
|
62
|
+
description: "Lessons, progress tracking, drip content",
|
|
63
|
+
available: false
|
|
64
|
+
},
|
|
65
|
+
community: {
|
|
66
|
+
name: "Community",
|
|
67
|
+
description: "Member feeds, gated content, roles",
|
|
68
|
+
available: false
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/utils/exec.ts
|
|
73
|
+
import { execSync } from "child_process";
|
|
74
|
+
function exec(cmd, cwd) {
|
|
75
|
+
try {
|
|
76
|
+
const stdout = execSync(cmd, {
|
|
77
|
+
cwd,
|
|
78
|
+
stdio: "pipe",
|
|
79
|
+
encoding: "utf-8",
|
|
80
|
+
timeout: 12e4
|
|
81
|
+
}).trim();
|
|
82
|
+
return { stdout, success: true };
|
|
83
|
+
} catch {
|
|
84
|
+
return { stdout: "", success: false };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function execInteractive(cmd, cwd) {
|
|
88
|
+
try {
|
|
89
|
+
execSync(cmd, { cwd, stdio: "inherit", timeout: 3e5 });
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function hasCommand(cmd) {
|
|
96
|
+
return exec(`which ${cmd}`).success;
|
|
97
|
+
}
|
|
98
|
+
function detectPackageManager() {
|
|
99
|
+
if (hasCommand("pnpm")) return "pnpm";
|
|
100
|
+
if (hasCommand("yarn")) return "yarn";
|
|
101
|
+
if (hasCommand("bun")) return "bun";
|
|
102
|
+
return "npm";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/scaffolding/manifest.ts
|
|
106
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
107
|
+
import { join } from "path";
|
|
108
|
+
var MANIFEST_DIR = ".whop";
|
|
109
|
+
var MANIFEST_FILE = "config.json";
|
|
110
|
+
function getManifestPath(projectDir) {
|
|
111
|
+
return join(projectDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
112
|
+
}
|
|
113
|
+
function createManifest(projectDir, data) {
|
|
114
|
+
const dir = join(projectDir, MANIFEST_DIR);
|
|
115
|
+
if (!existsSync(dir)) {
|
|
116
|
+
mkdirSync(dir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
const manifest = {
|
|
119
|
+
version: 1,
|
|
120
|
+
...data,
|
|
121
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
122
|
+
};
|
|
123
|
+
writeFileSync(getManifestPath(projectDir), JSON.stringify(manifest, null, 2) + "\n");
|
|
124
|
+
}
|
|
125
|
+
function readManifest(projectDir) {
|
|
126
|
+
const path = getManifestPath(projectDir);
|
|
127
|
+
if (!existsSync(path)) return null;
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function addFeatureToManifest(projectDir, feature) {
|
|
135
|
+
const manifest = readManifest(projectDir);
|
|
136
|
+
if (!manifest) return;
|
|
137
|
+
if (!manifest.features.includes(feature)) {
|
|
138
|
+
manifest.features.push(feature);
|
|
139
|
+
}
|
|
140
|
+
writeFileSync(getManifestPath(projectDir), JSON.stringify(manifest, null, 2) + "\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/scaffolding/skills.ts
|
|
144
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
145
|
+
import { join as join2 } from "path";
|
|
146
|
+
var PROVIDER_SKILLS = {
|
|
147
|
+
neon: [
|
|
148
|
+
{ repo: "https://github.com/neondatabase/agent-skills", skill: "neon-postgres" },
|
|
149
|
+
{ repo: "https://github.com/neondatabase/ai-rules", skill: "neon-serverless" }
|
|
150
|
+
],
|
|
151
|
+
supabase: [
|
|
152
|
+
{ repo: "https://github.com/supabase/agent-skills", skill: "supabase-postgres-best-practices" }
|
|
153
|
+
],
|
|
154
|
+
"prisma-postgres": [
|
|
155
|
+
// Prisma skill is typically bundled in the template already
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
function installProviderSkills(projectDir, provider) {
|
|
159
|
+
const skills = PROVIDER_SKILLS[provider];
|
|
160
|
+
if (!skills || skills.length === 0) return;
|
|
161
|
+
for (const { repo, skill } of skills) {
|
|
162
|
+
const result = exec(
|
|
163
|
+
`npx -y skills add ${repo} --skill ${skill}`,
|
|
164
|
+
projectDir
|
|
165
|
+
);
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
console.log(` (Could not install ${skill} skill \u2014 you can add it later)`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function writeProjectContext(projectDir, manifest, envVars) {
|
|
172
|
+
const dir = join2(projectDir, ".whop");
|
|
173
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
174
|
+
const lines = [
|
|
175
|
+
"# Project Context",
|
|
176
|
+
"",
|
|
177
|
+
"Auto-generated by create-whop-kit. Helps AI coding assistants understand",
|
|
178
|
+
"your project. Regenerated by `whop-kit status`. Do not edit manually.",
|
|
179
|
+
"",
|
|
180
|
+
"## Project",
|
|
181
|
+
"",
|
|
182
|
+
`- **Framework:** ${manifest.framework}`,
|
|
183
|
+
`- **App Type:** ${manifest.appType}`,
|
|
184
|
+
`- **Database:** ${manifest.database}`,
|
|
185
|
+
`- **Template Version:** ${manifest.templateVersion}`,
|
|
186
|
+
`- **Created:** ${manifest.createdAt}`,
|
|
187
|
+
"",
|
|
188
|
+
"## Configuration Status",
|
|
189
|
+
""
|
|
190
|
+
];
|
|
191
|
+
const checks = [
|
|
192
|
+
["DATABASE_URL", "Database", true],
|
|
193
|
+
["NEXT_PUBLIC_WHOP_APP_ID", "Whop App ID", true],
|
|
194
|
+
["WHOP_API_KEY", "Whop API Key", true],
|
|
195
|
+
["WHOP_WEBHOOK_SECRET", "Webhook Secret", true],
|
|
196
|
+
["EMAIL_PROVIDER", "Email", false],
|
|
197
|
+
["ANALYTICS_PROVIDER", "Analytics", false]
|
|
198
|
+
];
|
|
199
|
+
for (const [key, label, required] of checks) {
|
|
200
|
+
const set = envVars[key] ?? false;
|
|
201
|
+
const icon = set ? "\u2713" : required ? "\u2717" : "\u25CB";
|
|
202
|
+
lines.push(`- ${icon} ${label} (\`${key}\`)`);
|
|
203
|
+
}
|
|
204
|
+
if (manifest.features.length > 0) {
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("## Installed Features");
|
|
207
|
+
lines.push("");
|
|
208
|
+
for (const feature of manifest.features) {
|
|
209
|
+
lines.push(`- ${feature}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
lines.push("");
|
|
213
|
+
lines.push("## Key Files");
|
|
214
|
+
lines.push("");
|
|
215
|
+
if (manifest.framework === "nextjs") {
|
|
216
|
+
lines.push("- `lib/auth.ts` \u2014 session management (whop-kit/auth)");
|
|
217
|
+
lines.push("- `lib/adapters/next.ts` \u2014 Next.js cookie adapter");
|
|
218
|
+
lines.push("- `lib/adapters/prisma.ts` \u2014 Prisma DB + config adapters");
|
|
219
|
+
lines.push("- `app/api/webhooks/whop/route.ts` \u2014 webhook handler");
|
|
220
|
+
lines.push("- `app/api/auth/` \u2014 OAuth login/callback/logout");
|
|
221
|
+
lines.push("- `db/schema.prisma` \u2014 database schema");
|
|
222
|
+
} else if (manifest.framework === "astro") {
|
|
223
|
+
lines.push("- `src/lib/auth.ts` \u2014 session management (whop-kit/auth)");
|
|
224
|
+
lines.push("- `src/lib/adapters/astro.ts` \u2014 Astro cookie adapter");
|
|
225
|
+
lines.push("- `src/lib/adapters/prisma.ts` \u2014 Prisma DB adapter");
|
|
226
|
+
lines.push("- `src/pages/api/webhooks/whop.ts` \u2014 webhook handler");
|
|
227
|
+
lines.push("- `src/pages/api/auth/` \u2014 OAuth login/callback/logout");
|
|
228
|
+
}
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("## CLI Commands");
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push("```bash");
|
|
233
|
+
lines.push("npx whop-kit status # check project health");
|
|
234
|
+
lines.push("npx whop-kit add email # add email provider");
|
|
235
|
+
lines.push("npx whop-kit add analytics # add analytics");
|
|
236
|
+
lines.push("npx whop-kit catalog # list available services");
|
|
237
|
+
lines.push("npx whop-kit open whop # open Whop dashboard");
|
|
238
|
+
lines.push("npx whop-kit upgrade # update whop-kit");
|
|
239
|
+
lines.push("```");
|
|
240
|
+
lines.push("");
|
|
241
|
+
writeFileSync2(join2(dir, "project-context.md"), lines.join("\n"));
|
|
242
|
+
}
|
|
243
|
+
function writeFeatureSkill(projectDir, featureKey, provider) {
|
|
244
|
+
const dir = join2(projectDir, ".agents", "skills", "whop-kit-features");
|
|
245
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
246
|
+
const content = generateFeatureSkill(featureKey, provider);
|
|
247
|
+
if (content) {
|
|
248
|
+
writeFileSync2(join2(dir, `${featureKey}-${provider}.md`), content);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function generateFeatureSkill(featureKey, provider) {
|
|
252
|
+
const key = `${featureKey}:${provider}`;
|
|
253
|
+
const skills = {
|
|
254
|
+
"email:resend": `---
|
|
255
|
+
name: resend-email
|
|
256
|
+
description: Resend transactional email integration. Use when working with email sending, templates, or email configuration.
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
# Resend Email
|
|
260
|
+
|
|
261
|
+
This project uses Resend for transactional email via whop-kit/email.
|
|
262
|
+
|
|
263
|
+
## Usage
|
|
264
|
+
\`\`\`typescript
|
|
265
|
+
import { sendEmail } from "@/lib/email";
|
|
266
|
+
await sendEmail({ to: "user@example.com", subject: "Hello", html: "<p>Hi!</p>" });
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
## Config
|
|
270
|
+
- \`EMAIL_PROVIDER=resend\`
|
|
271
|
+
- \`EMAIL_API_KEY\` \u2014 Resend API key
|
|
272
|
+
- \`EMAIL_FROM_ADDRESS\` \u2014 verified sender
|
|
273
|
+
|
|
274
|
+
## Templates
|
|
275
|
+
Edit \`lib/email-templates.ts\`. Each function returns \`{ subject, html }\`.
|
|
276
|
+
|
|
277
|
+
## Dashboard
|
|
278
|
+
https://resend.com/overview
|
|
279
|
+
`,
|
|
280
|
+
"email:sendgrid": `---
|
|
281
|
+
name: sendgrid-email
|
|
282
|
+
description: SendGrid transactional email integration. Use when working with email sending, templates, or email configuration.
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
# SendGrid Email
|
|
286
|
+
|
|
287
|
+
This project uses SendGrid for transactional email via whop-kit/email.
|
|
288
|
+
|
|
289
|
+
## Usage
|
|
290
|
+
\`\`\`typescript
|
|
291
|
+
import { sendEmail } from "@/lib/email";
|
|
292
|
+
await sendEmail({ to: "user@example.com", subject: "Hello", html: "<p>Hi!</p>" });
|
|
293
|
+
\`\`\`
|
|
294
|
+
|
|
295
|
+
## Config
|
|
296
|
+
- \`EMAIL_PROVIDER=sendgrid\`
|
|
297
|
+
- \`EMAIL_API_KEY\` \u2014 SendGrid API key
|
|
298
|
+
- \`EMAIL_FROM_ADDRESS\` \u2014 verified sender
|
|
299
|
+
|
|
300
|
+
## Dashboard
|
|
301
|
+
https://app.sendgrid.com
|
|
302
|
+
`,
|
|
303
|
+
"analytics:posthog": `---
|
|
304
|
+
name: posthog-analytics
|
|
305
|
+
description: PostHog product analytics integration. Use when working with analytics, events, or feature flags.
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
# PostHog Analytics
|
|
309
|
+
|
|
310
|
+
Injected via whop-kit/analytics \`getAnalyticsScript()\`. No client SDK needed.
|
|
311
|
+
|
|
312
|
+
## Config
|
|
313
|
+
- \`ANALYTICS_PROVIDER=posthog\`
|
|
314
|
+
- \`ANALYTICS_ID\` \u2014 project API key (phc_xxx)
|
|
315
|
+
|
|
316
|
+
## Dashboard
|
|
317
|
+
https://us.posthog.com
|
|
318
|
+
`,
|
|
319
|
+
"analytics:google": `---
|
|
320
|
+
name: google-analytics
|
|
321
|
+
description: Google Analytics 4 integration.
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
# Google Analytics 4
|
|
325
|
+
|
|
326
|
+
Injected via whop-kit/analytics \`getAnalyticsScript()\`.
|
|
327
|
+
|
|
328
|
+
## Config
|
|
329
|
+
- \`ANALYTICS_PROVIDER=google\`
|
|
330
|
+
- \`ANALYTICS_ID\` \u2014 measurement ID (G-XXXXXXXXXX)
|
|
331
|
+
|
|
332
|
+
## Dashboard
|
|
333
|
+
https://analytics.google.com
|
|
334
|
+
`,
|
|
335
|
+
"analytics:plausible": `---
|
|
336
|
+
name: plausible-analytics
|
|
337
|
+
description: Plausible privacy-friendly analytics integration.
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
# Plausible Analytics
|
|
341
|
+
|
|
342
|
+
Injected via whop-kit/analytics \`getAnalyticsScript()\`. No cookies, GDPR compliant.
|
|
343
|
+
|
|
344
|
+
## Config
|
|
345
|
+
- \`ANALYTICS_PROVIDER=plausible\`
|
|
346
|
+
- \`ANALYTICS_ID\` \u2014 your domain
|
|
347
|
+
|
|
348
|
+
## Dashboard
|
|
349
|
+
https://plausible.io/sites
|
|
350
|
+
`
|
|
351
|
+
};
|
|
352
|
+
return skills[key] ?? null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export {
|
|
356
|
+
FRAMEWORKS,
|
|
357
|
+
TEMPLATES,
|
|
358
|
+
getTemplate,
|
|
359
|
+
APP_TYPES,
|
|
360
|
+
exec,
|
|
361
|
+
execInteractive,
|
|
362
|
+
hasCommand,
|
|
363
|
+
detectPackageManager,
|
|
364
|
+
createManifest,
|
|
365
|
+
readManifest,
|
|
366
|
+
addFeatureToManifest,
|
|
367
|
+
installProviderSkills,
|
|
368
|
+
writeProjectContext,
|
|
369
|
+
writeFeatureSkill
|
|
370
|
+
};
|
package/dist/cli-create.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
APP_TYPES,
|
|
4
|
+
FRAMEWORKS,
|
|
3
5
|
createManifest,
|
|
4
6
|
detectPackageManager,
|
|
5
7
|
exec,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
execInteractive,
|
|
9
|
+
getTemplate,
|
|
10
|
+
hasCommand,
|
|
11
|
+
installProviderSkills,
|
|
12
|
+
writeProjectContext
|
|
13
|
+
} from "./chunk-BR467LBM.js";
|
|
8
14
|
|
|
9
15
|
// src/cli-create.ts
|
|
10
16
|
import { runMain } from "citty";
|
|
@@ -16,75 +22,6 @@ import * as p5 from "@clack/prompts";
|
|
|
16
22
|
import pc5 from "picocolors";
|
|
17
23
|
import { defineCommand } from "citty";
|
|
18
24
|
|
|
19
|
-
// src/templates.ts
|
|
20
|
-
var FRAMEWORKS = {
|
|
21
|
-
nextjs: {
|
|
22
|
-
name: "Next.js",
|
|
23
|
-
description: "Full-stack React with App Router, SSR, and API routes",
|
|
24
|
-
available: true
|
|
25
|
-
},
|
|
26
|
-
astro: {
|
|
27
|
-
name: "Astro",
|
|
28
|
-
description: "Content-focused with islands architecture",
|
|
29
|
-
available: true
|
|
30
|
-
},
|
|
31
|
-
tanstack: {
|
|
32
|
-
name: "TanStack Start",
|
|
33
|
-
description: "Full-stack React with TanStack Router",
|
|
34
|
-
available: false
|
|
35
|
-
},
|
|
36
|
-
vite: {
|
|
37
|
-
name: "Vite + React",
|
|
38
|
-
description: "Lightweight SPA with Vite bundler",
|
|
39
|
-
available: false
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
var TEMPLATES = {
|
|
43
|
-
"saas:nextjs": {
|
|
44
|
-
name: "Next.js SaaS",
|
|
45
|
-
description: "Full SaaS with dashboard, pricing, billing, and docs",
|
|
46
|
-
repo: "colinmcdermott/whop-saas-starter-v2",
|
|
47
|
-
available: true
|
|
48
|
-
},
|
|
49
|
-
"saas:astro": {
|
|
50
|
-
name: "Astro SaaS",
|
|
51
|
-
description: "SaaS with auth, payments, and webhooks",
|
|
52
|
-
repo: "colinmcdermott/whop-astro-starter",
|
|
53
|
-
available: true
|
|
54
|
-
},
|
|
55
|
-
"blank:nextjs": {
|
|
56
|
-
name: "Next.js Blank",
|
|
57
|
-
description: "Just auth + webhooks \u2014 build anything",
|
|
58
|
-
repo: "colinmcdermott/whop-blank-starter",
|
|
59
|
-
available: true
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
function getTemplate(appType, framework) {
|
|
63
|
-
return TEMPLATES[`${appType}:${framework}`] ?? null;
|
|
64
|
-
}
|
|
65
|
-
var APP_TYPES = {
|
|
66
|
-
saas: {
|
|
67
|
-
name: "SaaS",
|
|
68
|
-
description: "Subscription tiers, dashboard, billing portal",
|
|
69
|
-
available: true
|
|
70
|
-
},
|
|
71
|
-
blank: {
|
|
72
|
-
name: "Blank",
|
|
73
|
-
description: "Just auth + payments, you build the rest",
|
|
74
|
-
available: true
|
|
75
|
-
},
|
|
76
|
-
course: {
|
|
77
|
-
name: "Course",
|
|
78
|
-
description: "Lessons, progress tracking, drip content",
|
|
79
|
-
available: false
|
|
80
|
-
},
|
|
81
|
-
community: {
|
|
82
|
-
name: "Community",
|
|
83
|
-
description: "Member feeds, gated content, roles",
|
|
84
|
-
available: false
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
25
|
// src/providers/neon.ts
|
|
89
26
|
import * as p from "@clack/prompts";
|
|
90
27
|
import pc from "picocolors";
|
|
@@ -108,38 +45,72 @@ var neonProvider = {
|
|
|
108
45
|
},
|
|
109
46
|
async provision(projectName) {
|
|
110
47
|
const cli = hasCommand("neonctl") ? "neonctl" : "neon";
|
|
111
|
-
const whoami = exec(`${cli} me`);
|
|
48
|
+
const whoami = exec(`${cli} me --output json`);
|
|
112
49
|
if (!whoami.success) {
|
|
113
|
-
p.log.info("You need to authenticate with Neon.");
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
50
|
+
p.log.info("You need to authenticate with Neon. This will open your browser.");
|
|
51
|
+
console.log("");
|
|
52
|
+
const authOk = execInteractive(`${cli} auth`);
|
|
53
|
+
if (!authOk) {
|
|
117
54
|
p.log.error("Neon authentication failed. Try running manually:");
|
|
118
55
|
p.log.info(pc.bold(` ${cli} auth`));
|
|
119
56
|
return null;
|
|
120
57
|
}
|
|
58
|
+
console.log("");
|
|
121
59
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
`${cli} projects create --name "${projectName}" --set-context
|
|
60
|
+
p.log.info(`Creating Neon project "${projectName}"...`);
|
|
61
|
+
console.log("");
|
|
62
|
+
const createOk = execInteractive(
|
|
63
|
+
`${cli} projects create --name "${projectName}" --set-context`
|
|
126
64
|
);
|
|
127
|
-
if (!
|
|
128
|
-
|
|
129
|
-
p.log.error("Try creating manually at https://console.neon.tech");
|
|
65
|
+
if (!createOk) {
|
|
66
|
+
p.log.error("Failed to create Neon project. Try manually at https://console.neon.tech");
|
|
130
67
|
return null;
|
|
131
68
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
69
|
+
console.log("");
|
|
70
|
+
p.log.success("Neon project created");
|
|
71
|
+
let connString = "";
|
|
72
|
+
const connResult = exec(`${cli} connection-string --prisma --output json`);
|
|
73
|
+
if (connResult.success) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(connResult.stdout);
|
|
76
|
+
connString = parsed.connection_string || parsed.connectionString || connResult.stdout;
|
|
77
|
+
} catch {
|
|
78
|
+
connString = connResult.stdout.trim();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!connString) {
|
|
82
|
+
const fallback = exec(`${cli} connection-string --prisma`);
|
|
83
|
+
if (fallback.success && fallback.stdout.startsWith("postgres")) {
|
|
84
|
+
connString = fallback.stdout.trim();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!connString) {
|
|
88
|
+
const raw = exec(`${cli} connection-string`);
|
|
89
|
+
if (raw.success && raw.stdout.startsWith("postgres")) {
|
|
90
|
+
connString = raw.stdout.trim();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!connString) {
|
|
94
|
+
p.log.warning("Could not extract connection string automatically.");
|
|
95
|
+
console.log("");
|
|
96
|
+
execInteractive(`${cli} connection-string`);
|
|
97
|
+
console.log("");
|
|
98
|
+
const manual = await p.text({
|
|
99
|
+
message: "Paste the connection string shown above",
|
|
100
|
+
placeholder: "postgresql://...",
|
|
101
|
+
validate: (v) => {
|
|
102
|
+
if (!v?.startsWith("postgres")) return "Must be a PostgreSQL connection string";
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
if (p.isCancel(manual)) return null;
|
|
106
|
+
connString = manual;
|
|
107
|
+
}
|
|
108
|
+
if (!connString) {
|
|
109
|
+
p.log.error("Could not get connection string. Get it from: https://console.neon.tech");
|
|
138
110
|
return null;
|
|
139
111
|
}
|
|
140
|
-
s.stop("Connection string retrieved");
|
|
141
112
|
return {
|
|
142
|
-
connectionString:
|
|
113
|
+
connectionString: connString,
|
|
143
114
|
provider: "neon"
|
|
144
115
|
};
|
|
145
116
|
}
|
|
@@ -705,6 +676,28 @@ var init_default = defineCommand({
|
|
|
705
676
|
features: [],
|
|
706
677
|
templateVersion: "0.2.0"
|
|
707
678
|
});
|
|
679
|
+
if (database !== "later" && database !== "manual") {
|
|
680
|
+
s.start("Installing provider skills for AI assistants...");
|
|
681
|
+
installProviderSkills(projectDir, database);
|
|
682
|
+
s.stop("Provider skills installed");
|
|
683
|
+
}
|
|
684
|
+
const envStatus = {};
|
|
685
|
+
if (dbUrl) envStatus["DATABASE_URL"] = true;
|
|
686
|
+
if (appId) {
|
|
687
|
+
envStatus["NEXT_PUBLIC_WHOP_APP_ID"] = true;
|
|
688
|
+
envStatus["WHOP_APP_ID"] = true;
|
|
689
|
+
}
|
|
690
|
+
if (apiKey) envStatus["WHOP_API_KEY"] = true;
|
|
691
|
+
if (webhookSecret) envStatus["WHOP_WEBHOOK_SECRET"] = true;
|
|
692
|
+
const manifest = {
|
|
693
|
+
framework,
|
|
694
|
+
appType,
|
|
695
|
+
database,
|
|
696
|
+
features: [],
|
|
697
|
+
templateVersion: "0.4.0",
|
|
698
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
699
|
+
};
|
|
700
|
+
writeProjectContext(projectDir, { version: 1, ...manifest }, envStatus);
|
|
708
701
|
const pm = detectPackageManager();
|
|
709
702
|
s.start(`Installing dependencies with ${pm}...`);
|
|
710
703
|
const installResult = exec(`${pm} install`, projectDir);
|
|
@@ -733,7 +726,7 @@ var init_default = defineCommand({
|
|
|
733
726
|
if (missing.length > 0) {
|
|
734
727
|
summary += `${pc5.yellow("\u25CB")} Missing: ${missing.join(", ")}
|
|
735
728
|
`;
|
|
736
|
-
summary += ` ${pc5.dim("
|
|
729
|
+
summary += ` ${pc5.dim("The setup wizard at http://localhost:3000 will guide you through it")}
|
|
737
730
|
`;
|
|
738
731
|
}
|
|
739
732
|
if (dbNote) {
|
|
@@ -745,10 +738,14 @@ var init_default = defineCommand({
|
|
|
745
738
|
summary += ` ${pc5.bold("cd")} ${basename2(projectName)}
|
|
746
739
|
`;
|
|
747
740
|
if (dbUrl) {
|
|
748
|
-
summary += ` ${pc5.bold(`${pm} run db:push`)}
|
|
741
|
+
summary += ` ${pc5.bold(`${pm} run db:push`)} ${pc5.dim("# push schema to database")}
|
|
749
742
|
`;
|
|
750
743
|
}
|
|
751
|
-
summary += ` ${pc5.bold(`${pm} run dev`)}
|
|
744
|
+
summary += ` ${pc5.bold(`${pm} run dev`)} ${pc5.dim("# start dev server")}
|
|
745
|
+
`;
|
|
746
|
+
summary += `
|
|
747
|
+
`;
|
|
748
|
+
summary += ` ${pc5.dim("Then open http://localhost:3000")}`;
|
|
752
749
|
p5.note(summary, "Your app is ready");
|
|
753
750
|
p5.outro(`${pc5.green("Happy building!")} ${pc5.dim("\u2014 whop-kit")}`);
|
|
754
751
|
}
|
package/dist/cli-kit.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
APP_TYPES,
|
|
4
|
+
FRAMEWORKS,
|
|
5
|
+
TEMPLATES,
|
|
3
6
|
addFeatureToManifest,
|
|
4
7
|
detectPackageManager,
|
|
5
8
|
exec,
|
|
6
|
-
readManifest
|
|
7
|
-
|
|
9
|
+
readManifest,
|
|
10
|
+
writeFeatureSkill
|
|
11
|
+
} from "./chunk-BR467LBM.js";
|
|
8
12
|
|
|
9
13
|
// src/cli-kit.ts
|
|
10
|
-
import { defineCommand as
|
|
14
|
+
import { defineCommand as defineCommand7, runMain } from "citty";
|
|
11
15
|
|
|
12
16
|
// src/commands/add.ts
|
|
13
17
|
import * as p4 from "@clack/prompts";
|
|
@@ -78,6 +82,7 @@ var emailFeature = {
|
|
|
78
82
|
if (fromAddress) {
|
|
79
83
|
appendEnvVar(projectDir, "EMAIL_FROM_ADDRESS", fromAddress);
|
|
80
84
|
}
|
|
85
|
+
writeFeatureSkill(projectDir, "email", provider);
|
|
81
86
|
}
|
|
82
87
|
};
|
|
83
88
|
|
|
@@ -116,6 +121,7 @@ var analyticsFeature = {
|
|
|
116
121
|
}
|
|
117
122
|
appendEnvVar(projectDir, "ANALYTICS_PROVIDER", provider);
|
|
118
123
|
appendEnvVar(projectDir, "ANALYTICS_ID", id);
|
|
124
|
+
writeFeatureSkill(projectDir, "analytics", provider);
|
|
119
125
|
}
|
|
120
126
|
};
|
|
121
127
|
|
|
@@ -304,10 +310,152 @@ var status_default = defineCommand2({
|
|
|
304
310
|
}
|
|
305
311
|
});
|
|
306
312
|
|
|
307
|
-
// src/commands/
|
|
313
|
+
// src/commands/env.ts
|
|
314
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
315
|
+
import { join as join3 } from "path";
|
|
308
316
|
import * as p6 from "@clack/prompts";
|
|
309
317
|
import pc4 from "picocolors";
|
|
310
318
|
import { defineCommand as defineCommand3 } from "citty";
|
|
319
|
+
function parseEnvFile(projectDir) {
|
|
320
|
+
for (const name of [".env.local", ".env"]) {
|
|
321
|
+
const path = join3(projectDir, name);
|
|
322
|
+
if (existsSync3(path)) {
|
|
323
|
+
const content = readFileSync3(path, "utf-8");
|
|
324
|
+
const vars = {};
|
|
325
|
+
for (const line of content.split("\n")) {
|
|
326
|
+
const trimmed = line.trim();
|
|
327
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
328
|
+
const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=["']?(.*)["']?$/);
|
|
329
|
+
if (match) vars[match[1]] = match[2].replace(/["']$/, "");
|
|
330
|
+
}
|
|
331
|
+
return vars;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {};
|
|
335
|
+
}
|
|
336
|
+
function maskValue(value) {
|
|
337
|
+
if (value.length <= 8) return "****";
|
|
338
|
+
return value.substring(0, 8) + "..." + "*".repeat(4);
|
|
339
|
+
}
|
|
340
|
+
var env_default = defineCommand3({
|
|
341
|
+
meta: {
|
|
342
|
+
name: "env",
|
|
343
|
+
description: "View environment variables (masked by default)"
|
|
344
|
+
},
|
|
345
|
+
args: {
|
|
346
|
+
reveal: {
|
|
347
|
+
type: "boolean",
|
|
348
|
+
description: "Show actual values instead of masked",
|
|
349
|
+
default: false
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
async run({ args }) {
|
|
353
|
+
console.log("");
|
|
354
|
+
p6.intro(`${pc4.bgCyan(pc4.black(" whop-kit env "))}`);
|
|
355
|
+
const manifest = readManifest(".");
|
|
356
|
+
if (!manifest) {
|
|
357
|
+
p6.log.error("No .whop/config.json found. Are you in a whop-kit project?");
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
const vars = parseEnvFile(".");
|
|
361
|
+
if (Object.keys(vars).length === 0) {
|
|
362
|
+
p6.log.warning("No environment variables found. Create .env.local with your configuration.");
|
|
363
|
+
p6.outro("");
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (args.reveal) {
|
|
367
|
+
p6.log.warning("Revealing secret values:");
|
|
368
|
+
console.log("");
|
|
369
|
+
}
|
|
370
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
371
|
+
const displayValue = args.reveal ? pc4.cyan(value) : pc4.dim(maskValue(value));
|
|
372
|
+
console.log(` ${pc4.bold(key.padEnd(40))} ${displayValue}`);
|
|
373
|
+
}
|
|
374
|
+
console.log("");
|
|
375
|
+
if (!args.reveal) {
|
|
376
|
+
p6.log.info(`Use ${pc4.bold("whop-kit env --reveal")} to show actual values`);
|
|
377
|
+
}
|
|
378
|
+
p6.outro("");
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// src/commands/catalog.ts
|
|
383
|
+
import * as p7 from "@clack/prompts";
|
|
384
|
+
import pc5 from "picocolors";
|
|
385
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
386
|
+
var catalog_default = defineCommand4({
|
|
387
|
+
meta: {
|
|
388
|
+
name: "catalog",
|
|
389
|
+
description: "List available templates, frameworks, databases, and features"
|
|
390
|
+
},
|
|
391
|
+
async run() {
|
|
392
|
+
console.log("");
|
|
393
|
+
p7.intro(`${pc5.bgCyan(pc5.black(" whop-kit catalog "))} Available services`);
|
|
394
|
+
console.log(`
|
|
395
|
+
${pc5.bold(pc5.underline("App Types"))}
|
|
396
|
+
`);
|
|
397
|
+
for (const [key, type] of Object.entries(APP_TYPES)) {
|
|
398
|
+
const status = type.available ? pc5.green("available") : pc5.dim("coming soon");
|
|
399
|
+
console.log(` ${pc5.bold(key.padEnd(15))} ${type.description.padEnd(45)} ${status}`);
|
|
400
|
+
}
|
|
401
|
+
console.log(`
|
|
402
|
+
${pc5.bold(pc5.underline("Frameworks"))}
|
|
403
|
+
`);
|
|
404
|
+
for (const [key, fw] of Object.entries(FRAMEWORKS)) {
|
|
405
|
+
const status = fw.available ? pc5.green("available") : pc5.dim("coming soon");
|
|
406
|
+
console.log(` ${pc5.bold(key.padEnd(15))} ${fw.description.padEnd(45)} ${status}`);
|
|
407
|
+
}
|
|
408
|
+
console.log(`
|
|
409
|
+
${pc5.bold(pc5.underline("Templates"))}
|
|
410
|
+
`);
|
|
411
|
+
for (const [key, tmpl] of Object.entries(TEMPLATES)) {
|
|
412
|
+
const status = tmpl.available ? pc5.green("available") : pc5.dim("coming soon");
|
|
413
|
+
console.log(` ${pc5.bold(key.padEnd(20))} ${tmpl.description.padEnd(40)} ${status}`);
|
|
414
|
+
}
|
|
415
|
+
console.log(`
|
|
416
|
+
${pc5.bold(pc5.underline("Database Providers"))}
|
|
417
|
+
`);
|
|
418
|
+
const dbProviders = [
|
|
419
|
+
{ key: "neon", name: "Neon", desc: "Serverless Postgres, auto-provisioned", status: "auto" },
|
|
420
|
+
{ key: "prisma-postgres", name: "Prisma Postgres", desc: "Instant database, no auth needed", status: "auto" },
|
|
421
|
+
{ key: "supabase", name: "Supabase", desc: "Open-source Firebase alternative", status: "guided" },
|
|
422
|
+
{ key: "manual", name: "Manual", desc: "Paste any PostgreSQL connection string", status: "manual" }
|
|
423
|
+
];
|
|
424
|
+
for (const db of dbProviders) {
|
|
425
|
+
const badge = db.status === "auto" ? pc5.green("auto-provision") : db.status === "guided" ? pc5.yellow("guided setup") : pc5.dim("manual");
|
|
426
|
+
console.log(` ${pc5.bold(db.key.padEnd(20))} ${db.desc.padEnd(40)} ${badge}`);
|
|
427
|
+
}
|
|
428
|
+
console.log(`
|
|
429
|
+
${pc5.bold(pc5.underline("Features (whop-kit add)"))}
|
|
430
|
+
`);
|
|
431
|
+
const features = [
|
|
432
|
+
{ key: "email", desc: "Transactional email", providers: "Resend, SendGrid" },
|
|
433
|
+
{ key: "analytics", desc: "Product analytics", providers: "PostHog, Google Analytics, Plausible" },
|
|
434
|
+
{ key: "webhook-event", desc: "Add webhook event handler", providers: "\u2014" }
|
|
435
|
+
];
|
|
436
|
+
for (const feat of features) {
|
|
437
|
+
console.log(` ${pc5.bold(feat.key.padEnd(20))} ${feat.desc.padEnd(30)} ${pc5.dim(feat.providers)}`);
|
|
438
|
+
}
|
|
439
|
+
console.log(`
|
|
440
|
+
${pc5.bold(pc5.underline("Agent Skills (auto-installed)"))}
|
|
441
|
+
`);
|
|
442
|
+
const skills = [
|
|
443
|
+
{ provider: "Neon", skills: "neon-postgres, neon-serverless" },
|
|
444
|
+
{ provider: "Supabase", skills: "supabase-postgres-best-practices" },
|
|
445
|
+
{ provider: "Whop", skills: "whop-saas-starter, whop-dev" }
|
|
446
|
+
];
|
|
447
|
+
for (const s of skills) {
|
|
448
|
+
console.log(` ${pc5.bold(s.provider.padEnd(20))} ${pc5.dim(s.skills)}`);
|
|
449
|
+
}
|
|
450
|
+
console.log("");
|
|
451
|
+
p7.outro(`Run ${pc5.bold("npx create-whop-kit")} to get started`);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// src/commands/open.ts
|
|
456
|
+
import * as p8 from "@clack/prompts";
|
|
457
|
+
import pc6 from "picocolors";
|
|
458
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
311
459
|
var DASHBOARDS = {
|
|
312
460
|
whop: { name: "Whop Developer Dashboard", url: "https://whop.com/dashboard/developer" },
|
|
313
461
|
neon: { name: "Neon Console", url: "https://console.neon.tech" },
|
|
@@ -320,7 +468,7 @@ function openUrl(url) {
|
|
|
320
468
|
else if (platform === "win32") exec(`start "${url}"`);
|
|
321
469
|
else exec(`xdg-open "${url}"`);
|
|
322
470
|
}
|
|
323
|
-
var open_default =
|
|
471
|
+
var open_default = defineCommand5({
|
|
324
472
|
meta: {
|
|
325
473
|
name: "open",
|
|
326
474
|
description: "Open a provider dashboard in your browser"
|
|
@@ -335,7 +483,7 @@ var open_default = defineCommand3({
|
|
|
335
483
|
async run({ args }) {
|
|
336
484
|
let target = args.target;
|
|
337
485
|
if (!target) {
|
|
338
|
-
const result = await
|
|
486
|
+
const result = await p8.select({
|
|
339
487
|
message: "Which dashboard?",
|
|
340
488
|
options: Object.entries(DASHBOARDS).map(([value, d]) => ({
|
|
341
489
|
value,
|
|
@@ -343,43 +491,43 @@ var open_default = defineCommand3({
|
|
|
343
491
|
hint: d.url
|
|
344
492
|
}))
|
|
345
493
|
});
|
|
346
|
-
if (
|
|
347
|
-
|
|
494
|
+
if (p8.isCancel(result)) {
|
|
495
|
+
p8.cancel("Cancelled.");
|
|
348
496
|
process.exit(0);
|
|
349
497
|
}
|
|
350
498
|
target = result;
|
|
351
499
|
}
|
|
352
500
|
const dashboard = DASHBOARDS[target];
|
|
353
501
|
if (!dashboard) {
|
|
354
|
-
|
|
502
|
+
p8.log.error(`Unknown dashboard "${target}". Options: ${Object.keys(DASHBOARDS).join(", ")}`);
|
|
355
503
|
process.exit(1);
|
|
356
504
|
}
|
|
357
505
|
openUrl(dashboard.url);
|
|
358
506
|
console.log(`
|
|
359
|
-
Opening ${
|
|
507
|
+
Opening ${pc6.bold(dashboard.name)} \u2192 ${pc6.cyan(dashboard.url)}
|
|
360
508
|
`);
|
|
361
509
|
}
|
|
362
510
|
});
|
|
363
511
|
|
|
364
512
|
// src/commands/upgrade.ts
|
|
365
|
-
import * as
|
|
366
|
-
import
|
|
367
|
-
import { defineCommand as
|
|
368
|
-
var upgrade_default =
|
|
513
|
+
import * as p9 from "@clack/prompts";
|
|
514
|
+
import pc7 from "picocolors";
|
|
515
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
516
|
+
var upgrade_default = defineCommand6({
|
|
369
517
|
meta: {
|
|
370
518
|
name: "upgrade",
|
|
371
519
|
description: "Update whop-kit to the latest version in your project"
|
|
372
520
|
},
|
|
373
521
|
async run() {
|
|
374
522
|
console.log("");
|
|
375
|
-
|
|
523
|
+
p9.intro(`${pc7.bgCyan(pc7.black(" whop-kit upgrade "))}`);
|
|
376
524
|
const manifest = readManifest(".");
|
|
377
525
|
if (!manifest) {
|
|
378
|
-
|
|
526
|
+
p9.log.error("No .whop/config.json found. Are you in a whop-kit project?");
|
|
379
527
|
process.exit(1);
|
|
380
528
|
}
|
|
381
529
|
const pm = detectPackageManager();
|
|
382
|
-
const s =
|
|
530
|
+
const s = p9.spinner();
|
|
383
531
|
s.start("Checking for updates...");
|
|
384
532
|
const latest = exec("npm view whop-kit version");
|
|
385
533
|
s.stop(latest.success ? `Latest: whop-kit@${latest.stdout}` : "Could not check latest version");
|
|
@@ -387,25 +535,27 @@ var upgrade_default = defineCommand4({
|
|
|
387
535
|
const cmd = pm === "npm" ? "npm install whop-kit@latest" : pm === "yarn" ? "yarn add whop-kit@latest" : pm === "bun" ? "bun add whop-kit@latest" : "pnpm add whop-kit@latest";
|
|
388
536
|
const result = exec(cmd);
|
|
389
537
|
if (result.success) {
|
|
390
|
-
s.stop(
|
|
538
|
+
s.stop(pc7.green("whop-kit upgraded"));
|
|
391
539
|
} else {
|
|
392
|
-
s.stop(
|
|
393
|
-
|
|
540
|
+
s.stop(pc7.red("Upgrade failed"));
|
|
541
|
+
p9.log.error("Try running manually: " + pc7.bold(cmd));
|
|
394
542
|
}
|
|
395
|
-
|
|
543
|
+
p9.outro("Done");
|
|
396
544
|
}
|
|
397
545
|
});
|
|
398
546
|
|
|
399
547
|
// src/cli-kit.ts
|
|
400
|
-
var main =
|
|
548
|
+
var main = defineCommand7({
|
|
401
549
|
meta: {
|
|
402
550
|
name: "whop-kit",
|
|
403
|
-
version: "0.
|
|
551
|
+
version: "0.5.0",
|
|
404
552
|
description: "Manage your Whop project"
|
|
405
553
|
},
|
|
406
554
|
subCommands: {
|
|
407
555
|
add: add_default,
|
|
408
556
|
status: status_default,
|
|
557
|
+
env: env_default,
|
|
558
|
+
catalog: catalog_default,
|
|
409
559
|
open: open_default,
|
|
410
560
|
upgrade: upgrade_default
|
|
411
561
|
}
|
package/package.json
CHANGED
package/dist/chunk-M4AXERQP.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/utils/exec.ts
|
|
4
|
-
import { execSync } from "child_process";
|
|
5
|
-
function exec(cmd, cwd) {
|
|
6
|
-
try {
|
|
7
|
-
const stdout = execSync(cmd, {
|
|
8
|
-
cwd,
|
|
9
|
-
stdio: "pipe",
|
|
10
|
-
encoding: "utf-8",
|
|
11
|
-
timeout: 12e4
|
|
12
|
-
}).trim();
|
|
13
|
-
return { stdout, success: true };
|
|
14
|
-
} catch {
|
|
15
|
-
return { stdout: "", success: false };
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function hasCommand(cmd) {
|
|
19
|
-
return exec(`which ${cmd}`).success;
|
|
20
|
-
}
|
|
21
|
-
function detectPackageManager() {
|
|
22
|
-
if (hasCommand("pnpm")) return "pnpm";
|
|
23
|
-
if (hasCommand("yarn")) return "yarn";
|
|
24
|
-
if (hasCommand("bun")) return "bun";
|
|
25
|
-
return "npm";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/scaffolding/manifest.ts
|
|
29
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
30
|
-
import { join } from "path";
|
|
31
|
-
var MANIFEST_DIR = ".whop";
|
|
32
|
-
var MANIFEST_FILE = "config.json";
|
|
33
|
-
function getManifestPath(projectDir) {
|
|
34
|
-
return join(projectDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
35
|
-
}
|
|
36
|
-
function createManifest(projectDir, data) {
|
|
37
|
-
const dir = join(projectDir, MANIFEST_DIR);
|
|
38
|
-
if (!existsSync(dir)) {
|
|
39
|
-
mkdirSync(dir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
const manifest = {
|
|
42
|
-
version: 1,
|
|
43
|
-
...data,
|
|
44
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
45
|
-
};
|
|
46
|
-
writeFileSync(getManifestPath(projectDir), JSON.stringify(manifest, null, 2) + "\n");
|
|
47
|
-
}
|
|
48
|
-
function readManifest(projectDir) {
|
|
49
|
-
const path = getManifestPath(projectDir);
|
|
50
|
-
if (!existsSync(path)) return null;
|
|
51
|
-
try {
|
|
52
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function addFeatureToManifest(projectDir, feature) {
|
|
58
|
-
const manifest = readManifest(projectDir);
|
|
59
|
-
if (!manifest) return;
|
|
60
|
-
if (!manifest.features.includes(feature)) {
|
|
61
|
-
manifest.features.push(feature);
|
|
62
|
-
}
|
|
63
|
-
writeFileSync(getManifestPath(projectDir), JSON.stringify(manifest, null, 2) + "\n");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export {
|
|
67
|
-
exec,
|
|
68
|
-
hasCommand,
|
|
69
|
-
detectPackageManager,
|
|
70
|
-
createManifest,
|
|
71
|
-
readManifest,
|
|
72
|
-
addFeatureToManifest
|
|
73
|
-
};
|