@whop/cli 0.4.0 → 0.6.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/index.js CHANGED
@@ -35,97 +35,145 @@ import {
35
35
  switchProfile,
36
36
  upsertProfile,
37
37
  validateApiKey
38
- } from "./chunk-2K4WBZA5.js";
38
+ } from "./chunk-GKKJXDPK.js";
39
39
  import {
40
40
  external_exports
41
41
  } from "./chunk-KFCNNWPI.js";
42
42
 
43
43
  // src/api/groups.json
44
44
  var groups_default = [
45
- [
46
- "accounts",
47
- "Accounts"
48
- ],
49
- [
50
- "users",
51
- "Users"
52
- ],
53
- [
54
- "stats",
55
- "Stats"
56
- ],
57
- [
58
- "ledgers",
59
- "Ledgers"
60
- ],
61
- [
62
- "payouts",
63
- "Payouts"
64
- ],
65
- [
66
- "cards",
67
- "Cards"
68
- ],
69
- [
70
- "transfers",
71
- "Transfers"
72
- ],
73
- [
74
- "deposits",
75
- "Deposits"
76
- ],
77
- [
78
- "swaps",
79
- "Swaps"
80
- ],
81
- [
82
- "verifications",
83
- "Verifications"
84
- ],
85
- [
86
- "products",
87
- "Products"
88
- ],
89
- [
90
- "plans",
91
- "Plans"
92
- ],
93
- [
94
- "checkout-configurations",
95
- "Checkout Configurations"
96
- ],
97
- [
98
- "referrals",
99
- "Referrals"
100
- ],
101
- [
102
- "people",
103
- "People"
104
- ],
105
- [
106
- "events",
107
- "Events"
108
- ],
109
- [
110
- "ads",
111
- "Ads"
112
- ],
113
- [
114
- "ad-campaigns",
115
- "Ad Campaigns"
116
- ],
117
- [
118
- "ad-groups",
119
- "Ad Groups"
120
- ],
121
- [
122
- "audiences",
123
- "Audiences"
124
- ],
125
- [
126
- "social-accounts",
127
- "Social Accounts"
128
- ]
45
+ {
46
+ name: "accounts",
47
+ tag: "Accounts",
48
+ description: "A business on Whop: profile, wallet, capabilities, settings.",
49
+ group: "Core Resources"
50
+ },
51
+ {
52
+ name: "users",
53
+ tag: "Users",
54
+ description: "A person on Whop: profile and connected identities.",
55
+ group: "Core Resources"
56
+ },
57
+ {
58
+ name: "stats",
59
+ tag: "Stats",
60
+ description: "Aggregated financial time series for reporting.",
61
+ group: "Core Resources"
62
+ },
63
+ {
64
+ name: "ledgers",
65
+ tag: "Ledgers",
66
+ description: "The activity feed behind an account or user's balance.",
67
+ group: "Money"
68
+ },
69
+ {
70
+ name: "payouts",
71
+ tag: "Payouts",
72
+ description: "Send money from a balance to a bank or wallet.",
73
+ group: "Money"
74
+ },
75
+ {
76
+ name: "cards",
77
+ tag: "Cards",
78
+ description: "Issue cards that spend from a balance.",
79
+ group: "Money"
80
+ },
81
+ {
82
+ name: "transfers",
83
+ tag: "Transfers",
84
+ description: "Move funds between Whop accounts and users.",
85
+ group: "Money"
86
+ },
87
+ {
88
+ name: "deposits",
89
+ tag: "Deposits",
90
+ description: "Add funds to a balance.",
91
+ group: "Money"
92
+ },
93
+ {
94
+ name: "swaps",
95
+ tag: "Swaps",
96
+ description: "Convert a balance between currencies.",
97
+ group: "Money"
98
+ },
99
+ {
100
+ name: "verifications",
101
+ tag: "Verifications",
102
+ description: "Identity review required before payouts and card issuing.",
103
+ group: "Money"
104
+ },
105
+ {
106
+ name: "products",
107
+ tag: "Products",
108
+ description: "The things you sell. Each owns plans and a store page.",
109
+ group: "Commerce"
110
+ },
111
+ {
112
+ name: "plans",
113
+ tag: "Plans",
114
+ description: "Pricing for a product: one-time, recurring, trials, stock.",
115
+ group: "Commerce"
116
+ },
117
+ {
118
+ name: "checkout-configurations",
119
+ tag: "Checkout Configurations",
120
+ description: "Turn a plan into a shareable, prefilled checkout link.",
121
+ group: "Commerce"
122
+ },
123
+ {
124
+ name: "referrals",
125
+ tag: "Referrals",
126
+ description: "Track who referred a purchase and what they earn.",
127
+ group: "Commerce"
128
+ },
129
+ {
130
+ name: "people",
131
+ tag: "People",
132
+ description: "Visitors and customers of an account, aggregated from pixel events.",
133
+ group: "Tracking"
134
+ },
135
+ {
136
+ name: "events",
137
+ tag: "Events",
138
+ description: "Conversion and engagement events tracked for attribution.",
139
+ group: "Tracking"
140
+ },
141
+ {
142
+ name: "ads",
143
+ tag: "Ads",
144
+ description: "The creative: copy, assets, and destination URL.",
145
+ group: "Ads"
146
+ },
147
+ {
148
+ name: "ad-campaigns",
149
+ tag: "Ad Campaigns",
150
+ description: "Platform, objective, and budget for a set of ads.",
151
+ group: "Ads"
152
+ },
153
+ {
154
+ name: "ad-groups",
155
+ tag: "Ad Groups",
156
+ description: "Audience, placements, and schedule within a campaign.",
157
+ group: "Ads"
158
+ },
159
+ {
160
+ name: "audiences",
161
+ tag: "Audiences",
162
+ description: "Reusable targeting lists for ad groups.",
163
+ group: "Ads"
164
+ },
165
+ {
166
+ name: "media",
167
+ tag: "Media",
168
+ description: "AI-generated assets, billed from a balance, attachable wherever files are accepted.",
169
+ group: "Media"
170
+ },
171
+ {
172
+ name: "social-accounts",
173
+ tag: "Social Accounts",
174
+ description: "Connected Facebook and Instagram accounts that run ads.",
175
+ group: "Identity"
176
+ }
129
177
  ];
130
178
 
131
179
  // src/auth/commands.ts
@@ -314,7 +362,7 @@ async function loginAdapter(c2) {
314
362
  const accountId = getActiveProfile()?.accountId ?? "";
315
363
  if (accountId) {
316
364
  try {
317
- const { createWhopFetch: createWhopFetch2 } = await import("./api-7L74UEG4.js");
365
+ const { createWhopFetch: createWhopFetch2 } = await import("./api-P7EWPZKG.js");
318
366
  const fetch3 = createWhopFetch2();
319
367
  const res = await fetch3(
320
368
  new Request(
@@ -644,6 +692,9 @@ async function getApp(appId) {
644
692
  async function updateAppRoute(appId, route) {
645
693
  return makeWhopRequest("PATCH", `/apps/${appId}`, { route });
646
694
  }
695
+ async function updateAppSecrets(appId, secrets) {
696
+ return makeWhopRequest("PATCH", `/apps/${appId}`, { secrets });
697
+ }
647
698
  async function createAppBuild(input) {
648
699
  return makeWhopRequest("POST", "/app_builds", {
649
700
  app_id: input.app_id,
@@ -984,6 +1035,17 @@ function replaceTopLevelName(content, route) {
984
1035
 
985
1036
  // src/apps/commands.ts
986
1037
  var BUILD_ARCHIVE = "dist/whop-build.zip";
1038
+ var UNSAFE_DEV_ENV_KEYS = /* @__PURE__ */ new Set([
1039
+ "PATH",
1040
+ "NODE_OPTIONS",
1041
+ "NODE_EXTRA_CA_CERTS",
1042
+ "NODE_REPL_EXTERNAL_MODULE"
1043
+ ]);
1044
+ var UNSAFE_DEV_ENV_PREFIXES = ["NPM_", "PNPM_", "YARN_", "BUN_", "LD_", "DYLD_"];
1045
+ function isUnsafeDevEnvKey(key) {
1046
+ const upper = key.toUpperCase();
1047
+ return UNSAFE_DEV_ENV_KEYS.has(upper) || UNSAFE_DEV_ENV_PREFIXES.some((prefix) => upper.startsWith(prefix));
1048
+ }
987
1049
  var BuildSchema = external_exports.object({
988
1050
  id: external_exports.string(),
989
1051
  status: external_exports.string(),
@@ -1359,6 +1421,77 @@ async function withRoute(c2, app) {
1359
1421
  });
1360
1422
  }
1361
1423
  }
1424
+ function resolveSecretsAppId(appOption) {
1425
+ if (appOption) return appOption;
1426
+ const projectDir = findProjectDir();
1427
+ const config = projectDir ? readAppConfig(projectDir) : null;
1428
+ if (config) return config.app_id;
1429
+ throw new Error(
1430
+ `No app specified. Pass --app app_xxx or run inside a project with ${APP_CONFIG_FILENAME}.`
1431
+ );
1432
+ }
1433
+ function buildSecretsGroup() {
1434
+ const secrets = Cli_exports.create("secrets", {
1435
+ description: "Manage app secrets \u2014 encrypted at rest, injected as env bindings into the hosted runtime and `whop apps dev`"
1436
+ });
1437
+ secrets.command("list", {
1438
+ description: "Show the app's secrets",
1439
+ options: external_exports.object({
1440
+ app: external_exports.string().optional().describe("App id (defaults to the project's linked app)")
1441
+ }),
1442
+ run: async (c2) => {
1443
+ const remote = await getApp(resolveSecretsAppId(c2.options.app));
1444
+ return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
1445
+ }
1446
+ });
1447
+ secrets.command("set", {
1448
+ description: "Add or overwrite secrets",
1449
+ options: external_exports.object({
1450
+ secret: external_exports.array(external_exports.string()).min(1).describe("KEY=VALUE pair (repeatable)"),
1451
+ app: external_exports.string().optional().describe("App id (defaults to the project's linked app)")
1452
+ }),
1453
+ examples: [
1454
+ { options: { secret: ["MAIL_API_KEY=mail-key-123"] } },
1455
+ { options: { secret: ["A=1", "B=2"], app: "app_xxxxxxxx" } }
1456
+ ],
1457
+ run: async (c2) => {
1458
+ const updates = {};
1459
+ for (const pair of c2.options.secret) {
1460
+ const eq = pair.indexOf("=");
1461
+ if (eq <= 0) {
1462
+ return c2.error({
1463
+ code: "INVALID_SECRET",
1464
+ message: `"${pair}" is not KEY=VALUE. Use --secret NAME=value (an empty value deletes the secret).`
1465
+ });
1466
+ }
1467
+ updates[pair.slice(0, eq)] = pair.slice(eq + 1);
1468
+ }
1469
+ const remote = await updateAppSecrets(
1470
+ resolveSecretsAppId(c2.options.app),
1471
+ updates
1472
+ );
1473
+ return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
1474
+ }
1475
+ });
1476
+ secrets.command("unset", {
1477
+ description: "Delete secrets by name",
1478
+ options: external_exports.object({
1479
+ key: external_exports.array(external_exports.string()).min(1).describe("Secret name (repeatable)"),
1480
+ app: external_exports.string().optional().describe("App id (defaults to the project's linked app)")
1481
+ }),
1482
+ examples: [{ options: { key: ["MAIL_API_KEY"] } }],
1483
+ run: async (c2) => {
1484
+ const updates = {};
1485
+ for (const key of c2.options.key) updates[key] = null;
1486
+ const remote = await updateAppSecrets(
1487
+ resolveSecretsAppId(c2.options.app),
1488
+ updates
1489
+ );
1490
+ return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
1491
+ }
1492
+ });
1493
+ return secrets;
1494
+ }
1362
1495
  async function buildAppGroup() {
1363
1496
  const app = Cli_exports.create("apps", {
1364
1497
  description: "Build and deploy fully-hosted web apps on Whop (*.whop.app)"
@@ -1369,6 +1502,7 @@ async function buildAppGroup() {
1369
1502
  });
1370
1503
  await registerSpecCommands(builds, "App builds");
1371
1504
  app.command(builds);
1505
+ app.command(buildSecretsGroup());
1372
1506
  app.command("dev", {
1373
1507
  description: "Run the local dev server for this app",
1374
1508
  hint: "Starts the project's dev script with WHOP_APP_ID set and a short-lived access token injected as WHOP_API_KEY (minted from your CLI credential), so server-side SDK calls work locally without env setup. An explicitly exported WHOP_API_KEY is used as-is.",
@@ -1423,6 +1557,40 @@ async function buildAppGroup() {
1423
1557
  );
1424
1558
  }
1425
1559
  }
1560
+ try {
1561
+ const remote = await getApp(config.app_id);
1562
+ const secretEntries = Object.entries(remote.secrets ?? {}).filter(
1563
+ ([key]) => !(key in process.env) && !(key in injected)
1564
+ );
1565
+ const unsafe = secretEntries.filter(([key]) => isUnsafeDevEnvKey(key));
1566
+ for (const [key, value] of secretEntries) {
1567
+ if (!isUnsafeDevEnvKey(key)) injected[key] = value;
1568
+ }
1569
+ if (unsafe.length > 0) {
1570
+ log(
1571
+ c2,
1572
+ chalk.yellow(
1573
+ `Skipped app secret${unsafe.length === 1 ? "" : "s"} ${unsafe.map(([key]) => key).join(", ")} \u2014 these names control the local runtime and are never injected by \`whop apps dev\`.`
1574
+ )
1575
+ );
1576
+ }
1577
+ const injectedCount = secretEntries.length - unsafe.length;
1578
+ if (injectedCount > 0) {
1579
+ log(
1580
+ c2,
1581
+ chalk.dim(
1582
+ `Injected ${injectedCount} app secret${injectedCount === 1 ? "" : "s"} into the environment (${secretEntries.filter(([key]) => !isUnsafeDevEnvKey(key)).map(([key]) => key).join(", ")}).`
1583
+ )
1584
+ );
1585
+ }
1586
+ } catch (err) {
1587
+ log(
1588
+ c2,
1589
+ chalk.yellow(
1590
+ `Couldn't fetch app secrets (${err instanceof Error ? err.message : "unknown error"}) \u2014 continuing without them.`
1591
+ )
1592
+ );
1593
+ }
1426
1594
  log(c2, chalk.dim(`Starting dev server for ${config.name}...`));
1427
1595
  try {
1428
1596
  runScript(projectDir, "dev", injected);
@@ -1443,9 +1611,7 @@ async function buildAppGroup() {
1443
1611
  app: external_exports.string().optional().describe(
1444
1612
  "Link this project to an app (app_xxx) before deploying \u2014 writes whop.app.json, replacing any existing link"
1445
1613
  ),
1446
- skip_promote: external_exports.boolean().optional().describe(
1447
- "Upload the build without promoting it to production"
1448
- ),
1614
+ skip_promote: external_exports.boolean().optional().describe("Upload the build without promoting it to production"),
1449
1615
  skip_typecheck: external_exports.boolean().optional().describe("Skip the typecheck step"),
1450
1616
  skip_build: external_exports.boolean().optional().describe("Skip the build step and upload the existing dist/ output")
1451
1617
  }),
@@ -2218,7 +2384,7 @@ ${c.success("\u2713 Your store is fully set up.")}`);
2218
2384
  // package.json
2219
2385
  var package_default = {
2220
2386
  name: "@whop/cli",
2221
- version: "0.4.0",
2387
+ version: "0.6.0",
2222
2388
  description: "The Whop CLI \u2014 build and manage Whop apps from your terminal. Human and agent friendly.",
2223
2389
  keywords: [
2224
2390
  "agent",
@@ -2239,7 +2405,8 @@ var package_default = {
2239
2405
  "whop-cli": "dist/index.js"
2240
2406
  },
2241
2407
  files: [
2242
- "dist"
2408
+ "dist",
2409
+ "skills"
2243
2410
  ],
2244
2411
  type: "module",
2245
2412
  exports: {
@@ -2791,7 +2958,9 @@ var HANDWRITTEN_GROUPS = [
2791
2958
  var HANDWRITTEN_GROUP_NAMES = new Set(
2792
2959
  HANDWRITTEN_GROUPS.map(({ name }) => name)
2793
2960
  );
2794
- var API_GROUPS = groups_default.map(([name, tag]) => ({ name, tag })).filter(({ name }) => !HANDWRITTEN_GROUP_NAMES.has(name));
2961
+ var API_GROUPS = groups_default.filter(
2962
+ ({ name }) => !HANDWRITTEN_GROUP_NAMES.has(name)
2963
+ );
2795
2964
  var COMMAND_GROUPS = [...API_GROUPS, ...HANDWRITTEN_GROUPS];
2796
2965
  async function registerHandwrittenGroups(cli2) {
2797
2966
  for (const group of HANDWRITTEN_GROUPS) await group.register(cli2);
@@ -2885,6 +3054,11 @@ var cli = Cli_exports.create("whop", {
2885
3054
  description: "Build and manage Whop apps from your terminal.",
2886
3055
  renderer: render,
2887
3056
  sync: {
3057
+ // Shipped skill files generated by scripts/sync-spec.ts from the
3058
+ // spec's own tag copy (x-whop-summary + description, authored in
3059
+ // backend/openapi/v1/tags.yml). A matching directory name overrides
3060
+ // the auto-generated skill for that command group.
3061
+ include: ["skills/*"],
2888
3062
  suggestions: [
2889
3063
  "start selling on Whop",
2890
3064
  "show my Whop business accounts",
@@ -2936,94 +3110,12 @@ cli.use(async (c2, next) => {
2936
3110
  return next();
2937
3111
  });
2938
3112
  await registerHandwrittenGroups(cli);
2939
- for (const { name, tag } of API_GROUPS) {
2940
- cli.command(name, { fetch: fetch2, openapi: spec(tag) });
3113
+ for (const { name, tag, description } of API_GROUPS) {
3114
+ cli.command(name, { description, fetch: fetch2, openapi: spec(tag) });
2941
3115
  }
2942
3116
  var cli_default = cli;
2943
3117
 
2944
- // src/api/api-structure.json
2945
- var api_structure_default = [
2946
- {
2947
- group: "Core Resources",
2948
- tags: [
2949
- "Accounts",
2950
- "Users",
2951
- "Webhooks",
2952
- "Stats"
2953
- ]
2954
- },
2955
- {
2956
- group: "Money",
2957
- tags: [
2958
- "Ledgers",
2959
- "Payouts",
2960
- "Cards",
2961
- "Transfers",
2962
- "Deposits",
2963
- "Swaps",
2964
- "Payout Methods",
2965
- "Verifications"
2966
- ]
2967
- },
2968
- {
2969
- group: "Commerce",
2970
- tags: [
2971
- "Products",
2972
- "Plans",
2973
- "Checkout Configurations",
2974
- "Referrals"
2975
- ]
2976
- },
2977
- {
2978
- group: "Tracking",
2979
- tags: [
2980
- "People",
2981
- "Events"
2982
- ]
2983
- },
2984
- {
2985
- group: "Ads",
2986
- tags: [
2987
- "Ads",
2988
- "Ad Campaigns",
2989
- "Ad Groups",
2990
- "Audiences"
2991
- ]
2992
- },
2993
- {
2994
- group: "Identity",
2995
- tags: [
2996
- "Social Accounts"
2997
- ]
2998
- }
2999
- ];
3000
-
3001
- // src/api/tag-descriptions.json
3002
- var tag_descriptions_default = {
3003
- Accounts: "Your Whop account",
3004
- Users: "Users on your platform",
3005
- Stats: "Platform-wide statistics",
3006
- Ledgers: "Transaction history",
3007
- Payouts: "Withdraw earnings",
3008
- Cards: "Issued virtual cards",
3009
- Transfers: "Move funds between accounts",
3010
- Deposits: "Add funds to your balance",
3011
- Swaps: "Convert between currencies",
3012
- Verifications: "Identity verification status",
3013
- Products: "What you're selling",
3014
- Plans: "Pricing for your products",
3015
- "Checkout Configurations": "Optional / advanced: customize the checkout page",
3016
- Referrals: "Referral programs",
3017
- Ads: "Ad creatives",
3018
- "Ad Campaigns": "Top-level campaign organization",
3019
- "Ad Groups": "Sets of related ads",
3020
- Audiences: "Who sees your ads",
3021
- "Social Accounts": "Linked social profiles"
3022
- };
3023
-
3024
3118
  // src/lib/grouped-help.ts
3025
- var descriptions = tag_descriptions_default;
3026
- var toCliName = (tag) => tag.toLowerCase().replace(/\s+/g, "-");
3027
3119
  var SECTION_LABELS = {
3028
3120
  "get-started": "Get Started",
3029
3121
  auth: "Auth"
@@ -3060,14 +3152,12 @@ var handwrittenSection = (section) => ({
3060
3152
  });
3061
3153
  function buildGroupedHelp(version, description) {
3062
3154
  const lines = [];
3063
- const presentCommands = new Set(API_GROUPS.map(({ name }) => name));
3064
- const apiSections = api_structure_default.map(({ group, tags }) => ({
3065
- label: group,
3066
- commands: tags.filter((tag) => presentCommands.has(toCliName(tag))).map((tag) => ({
3067
- name: toCliName(tag),
3068
- description: descriptions[tag] ?? ""
3069
- }))
3070
- })).filter((section) => section.commands.length > 0);
3155
+ const apiSections = [];
3156
+ for (const { name, description: description2, group } of API_GROUPS) {
3157
+ let section = apiSections.find((s) => s.label === group);
3158
+ if (!section) apiSections.push(section = { label: group, commands: [] });
3159
+ section.commands.push({ name, description: description2 });
3160
+ }
3071
3161
  const commerceSections = apiSections.filter(
3072
3162
  (section) => section.label.toLowerCase() === "commerce"
3073
3163
  );