@whop/cli 0.1.0 → 0.2.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
@@ -25,13 +25,14 @@ import {
25
25
  readConfig,
26
26
  removeAllProfiles,
27
27
  removeProfile,
28
- resolveActiveCompanyId,
28
+ resolveActiveAccountId,
29
29
  selectProfile,
30
+ setActiveProfileAccount,
30
31
  startSpinner,
31
32
  switchProfile,
32
33
  upsertProfile,
33
34
  validateApiKey
34
- } from "./chunk-ET6QSHSA.js";
35
+ } from "./chunk-2Y4EKBOD.js";
35
36
  import {
36
37
  external_exports
37
38
  } from "./chunk-KFCNNWPI.js";
@@ -128,7 +129,7 @@ var groups_default = [
128
129
  var loginOptions = external_exports.object({
129
130
  apiKey: external_exports.string().optional().describe("API key for non-interactive login (skips the prompt)"),
130
131
  profile: external_exports.string().optional().describe("Name for this profile (defaults to the account route)"),
131
- companyId: external_exports.string().optional().describe(
132
+ accountId: external_exports.string().optional().describe(
132
133
  "Business account to act on behalf of (biz_xxx) \u2014 for OAuth users who manage more than one"
133
134
  )
134
135
  });
@@ -193,7 +194,7 @@ async function performLogin(input) {
193
194
  const { profileName, identity: identity2, existed: existed2 } = await performOAuthLogin({
194
195
  profile: input.profile,
195
196
  agent: input.agent,
196
- companyId: input.companyId
197
+ accountId: input.accountId
197
198
  });
198
199
  return {
199
200
  ok: true,
@@ -262,7 +263,7 @@ async function loginAdapter(c2) {
262
263
  agent: c2.agent,
263
264
  apiKey: c2.options.apiKey ?? c2.env.WHOP_API_KEY,
264
265
  profile: c2.options.profile,
265
- companyId: c2.options.companyId
266
+ accountId: c2.options.accountId
266
267
  });
267
268
  } catch (error) {
268
269
  if (error instanceof PromptCancelledError) {
@@ -287,14 +288,14 @@ async function loginAdapter(c2) {
287
288
  }
288
289
  ]
289
290
  };
290
- } else if (result.code === "MULTIPLE_COMPANIES" || result.code === "COMPANY_NOT_FOUND") {
291
+ } else if (result.code === "MULTIPLE_ACCOUNTS" || result.code === "ACCOUNT_NOT_FOUND") {
291
292
  cta = {
292
293
  description: "Choose which business account to use:",
293
294
  commands: [
294
295
  {
295
296
  command: "auth login",
296
- options: { companyId: true },
297
- description: "Pass --company-id biz_xxx"
297
+ options: { accountId: true },
298
+ description: "Pass --account-id biz_xxx"
298
299
  }
299
300
  ]
300
301
  };
@@ -307,14 +308,14 @@ async function loginAdapter(c2) {
307
308
  });
308
309
  }
309
310
  let firstRun = !result.existed;
310
- const companyId = getActiveProfile()?.companyId ?? "";
311
- if (companyId) {
311
+ const accountId = getActiveProfile()?.accountId ?? "";
312
+ if (accountId) {
312
313
  try {
313
- const { createWhopFetch: createWhopFetch2 } = await import("./api-3MQ6MQGV.js");
314
+ const { createWhopFetch: createWhopFetch2 } = await import("./api-U7WU3JXH.js");
314
315
  const fetch3 = createWhopFetch2();
315
316
  const res = await fetch3(
316
317
  new Request(
317
- `https://api.whop.com/products?company_id=${companyId}&first=1`
318
+ `https://api.whop.com/products?company_id=${accountId}&first=1`
318
319
  )
319
320
  );
320
321
  if (res.ok) {
@@ -324,14 +325,14 @@ async function loginAdapter(c2) {
324
325
  } catch {
325
326
  }
326
327
  }
327
- if (!companyId) {
328
+ if (!accountId) {
328
329
  return c2.ok(result.output, {
329
330
  cta: {
330
- description: "You're logged in, but no business account is linked to this account yet. Create a business account on Whop, then log in again:",
331
+ description: "You're logged in, but you don't have a business account yet. Create one and start selling in one command:",
331
332
  commands: [
332
333
  {
333
- command: "auth login",
334
- description: "Re-authenticate once you have a business account"
334
+ command: "quickstart",
335
+ description: "Create your business account, product, and checkout link"
335
336
  }
336
337
  ]
337
338
  }
@@ -850,7 +851,7 @@ async function resolveProductId(providedId, isAgent) {
850
851
  if (providedId) return { ok: true, productId: providedId, autoResolved: false };
851
852
  const profile = getActiveProfile();
852
853
  const params = new URLSearchParams({ first: "100" });
853
- if (profile?.companyId) params.set("company_id", profile.companyId);
854
+ if (profile?.accountId) params.set("company_id", profile.accountId);
854
855
  let data;
855
856
  try {
856
857
  const res = await makeWhopRequest("GET", `/products?${params}`);
@@ -1121,7 +1122,10 @@ var quickstartOptions = external_exports.object({
1121
1122
  "every_two_years",
1122
1123
  "one_time"
1123
1124
  ]).optional().describe("Billing interval \u2014 use 'one_time' for a single payment"),
1124
- company_id: external_exports.string().optional().describe(
1125
+ business_name: external_exports.string().optional().describe(
1126
+ "Name for a new business account \u2014 quickstart creates it and sets up your product under it (user logins only; ignored with API-key auth, which already belongs to a business)"
1127
+ ),
1128
+ account_id: external_exports.string().optional().describe(
1125
1129
  "Business account to act on behalf of (biz_xxx); defaults to your active account"
1126
1130
  )
1127
1131
  });
@@ -1131,7 +1135,11 @@ var quickstartOutput = external_exports.object({
1131
1135
  checkoutUrl: external_exports.string().nullable().describe(
1132
1136
  "The live checkout link to share \u2014 the journey's payoff, present once a plan exists"
1133
1137
  ),
1134
- created: external_exports.object({ productId: external_exports.string().optional(), planId: external_exports.string().optional() }).nullable().describe(
1138
+ created: external_exports.object({
1139
+ accountId: external_exports.string().optional(),
1140
+ productId: external_exports.string().optional(),
1141
+ planId: external_exports.string().optional()
1142
+ }).nullable().describe(
1135
1143
  "What this run created, if anything \u2014 null for a read-only status check"
1136
1144
  ),
1137
1145
  steps: external_exports.array(StepSchema).describe("The path to your first sale"),
@@ -1170,36 +1178,87 @@ ${chalk3.yellow("!")} Agent setup skipped: ${message}`);
1170
1178
  );
1171
1179
  }
1172
1180
  }
1173
- const companyId = c2.options.company_id ?? await resolveActiveCompanyId() ?? "";
1181
+ const accountId = c2.options.account_id ?? await resolveActiveAccountId() ?? "";
1174
1182
  const accountName = profile?.accountTitle ?? profile?.accountRoute ?? profile?.name ?? "your account";
1175
- if (!companyId) {
1176
- return c2.error({
1177
- code: "NO_COMPANY",
1178
- message: "No business account is linked to this login. Create a business account on Whop, then log in again.",
1179
- retryable: false,
1180
- cta: {
1181
- description: "Create a business account, then re-authenticate:",
1182
- commands: [
1183
- {
1184
- command: "auth login",
1185
- description: "Log in and pick a business account"
1186
- }
1187
- ]
1188
- }
1183
+ const interactive = !c2.agent && !c2.formatExplicit;
1184
+ const canCreateBusiness = profile?.method === "oauth";
1185
+ let newBusinessName = canCreateBusiness ? c2.options.business_name : void 0;
1186
+ if (!canCreateBusiness && c2.options.business_name !== void 0 && interactive) {
1187
+ console.log(
1188
+ `
1189
+ ${chalk3.dim("Ignoring --business_name \u2014 this API key already belongs to a business.")}`
1190
+ );
1191
+ }
1192
+ if (!accountId && newBusinessName === void 0) {
1193
+ if (!canCreateBusiness) {
1194
+ return c2.error({
1195
+ code: "NO_ACCOUNT",
1196
+ message: "Couldn't resolve the business account for this API key. Check the key, or log in again.",
1197
+ retryable: true,
1198
+ cta: {
1199
+ commands: [{ command: "auth login", description: "Log in to Whop" }]
1200
+ }
1201
+ });
1202
+ }
1203
+ if (!interactive) {
1204
+ return c2.error({
1205
+ code: "NO_ACCOUNT",
1206
+ message: "No business account is linked to this login. Pass --business_name to create one and continue.",
1207
+ retryable: true,
1208
+ cta: {
1209
+ description: "Create your business account and product in one run:",
1210
+ commands: [
1211
+ {
1212
+ command: "quickstart",
1213
+ options: { business_name: "My Business" },
1214
+ description: "Create a business account, then your product"
1215
+ }
1216
+ ]
1217
+ }
1218
+ });
1219
+ }
1220
+ console.log(
1221
+ `
1222
+ ${chalk3.bold("You don't have a business account yet \u2014 let's create one.")}`
1223
+ );
1224
+ const name = await text2({
1225
+ message: "What's your business called?",
1226
+ placeholder: "My Business"
1189
1227
  });
1228
+ if (isCancel3(name)) {
1229
+ return c2.error({
1230
+ code: "CANCELLED",
1231
+ message: "Setup cancelled.",
1232
+ exitCode: 130
1233
+ });
1234
+ }
1235
+ newBusinessName = name;
1236
+ }
1237
+ if (newBusinessName !== void 0) {
1238
+ try {
1239
+ return await runCreate(c2, {
1240
+ accountId: "",
1241
+ accountName,
1242
+ products: [],
1243
+ newBusinessName
1244
+ });
1245
+ } catch (err) {
1246
+ if (err instanceof QuickstartError) return c2.error(err.envelope);
1247
+ throw err;
1248
+ }
1190
1249
  }
1191
1250
  const [productsRes, plansRes, checkoutRes] = await Promise.allSettled([
1192
1251
  makeWhopRequest(
1193
1252
  "GET",
1194
- `/products?company_id=${companyId}&first=100`
1253
+ `/products?company_id=${accountId}&first=100`
1195
1254
  ),
1196
1255
  makeWhopRequest(
1197
1256
  "GET",
1198
- `/plans?account_id=${companyId}&first=100`
1257
+ `/plans?account_id=${accountId}&first=100`
1199
1258
  ),
1200
1259
  makeWhopRequest(
1201
1260
  "GET",
1202
- `/checkout_configurations?company_id=${companyId}&first=100`
1261
+ `/checkout_configurations?company_id=${accountId}&first=100`
1203
1262
  )
1204
1263
  ]);
1205
1264
  const critical = [productsRes, plansRes].find((r) => r.status === "rejected");
@@ -1216,11 +1275,17 @@ ${chalk3.yellow("!")} Agent setup skipped: ${message}`);
1216
1275
  const checkouts = checkoutRes.status === "fulfilled" ? checkoutRes.value.data ?? [] : [];
1217
1276
  const ready = products.length > 0 && plans.length > 0;
1218
1277
  const hasCreationFlags = c2.options.title !== void 0 || c2.options.price !== void 0 || c2.options.billing_period !== void 0;
1219
- const canMutateInteractively = !c2.agent && !c2.formatExplicit;
1220
- const shouldCreate = hasCreationFlags || !ready && canMutateInteractively;
1278
+ const shouldCreate = hasCreationFlags || !ready && interactive;
1221
1279
  if (shouldCreate) {
1222
1280
  try {
1223
- return await runCreate(c2, { companyId, accountName, products });
1281
+ return await runCreate(c2, {
1282
+ accountId,
1283
+ accountName,
1284
+ products,
1285
+ // Only offer the new-business choice in the fully-guided flow —
1286
+ // flags mean the user already said what they want.
1287
+ offerNewBusiness: interactive && !hasCreationFlags && c2.options.account_id === void 0 && canCreateBusiness
1288
+ });
1224
1289
  } catch (err) {
1225
1290
  if (err instanceof QuickstartError) return c2.error(err.envelope);
1226
1291
  throw err;
@@ -1266,7 +1331,39 @@ async function runCreate(c2, state) {
1266
1331
  ${chalk3.bold("Let's get you set up to sell.")}
1267
1332
  `);
1268
1333
  }
1269
- const needsNewProduct = state.products.length === 0 || c2.options.title !== void 0;
1334
+ let { accountId, accountName, products, newBusinessName } = state;
1335
+ if (state.offerNewBusiness) {
1336
+ const choice = await select2({
1337
+ message: "Which business account is this for?",
1338
+ options: [
1339
+ { value: "current", label: accountName, hint: accountId },
1340
+ { value: "new", label: "Create a new business account" }
1341
+ ]
1342
+ });
1343
+ if (isCancel3(choice)) {
1344
+ throw new QuickstartError({
1345
+ code: "CANCELLED",
1346
+ message: "Setup cancelled.",
1347
+ exitCode: 130
1348
+ });
1349
+ }
1350
+ if (choice === "new") {
1351
+ const name = await text2({
1352
+ message: "What's your new business called?",
1353
+ placeholder: "My Business"
1354
+ });
1355
+ if (isCancel3(name)) {
1356
+ throw new QuickstartError({
1357
+ code: "CANCELLED",
1358
+ message: "Setup cancelled.",
1359
+ exitCode: 130
1360
+ });
1361
+ }
1362
+ newBusinessName = name;
1363
+ products = [];
1364
+ }
1365
+ }
1366
+ const needsNewProduct = products.length === 0 || c2.options.title !== void 0;
1270
1367
  const newTitle = needsNewProduct ? await resolveOrPrompt(
1271
1368
  c2,
1272
1369
  c2.options.title,
@@ -1280,7 +1377,7 @@ ${chalk3.bold("Let's get you set up to sell.")}
1280
1377
  placeholder: "My Online Course"
1281
1378
  })
1282
1379
  ) : void 0;
1283
- const existing = needsNewProduct ? void 0 : await resolveExistingProduct(c2, state.products);
1380
+ const existing = needsNewProduct ? void 0 : await resolveExistingProduct(c2, products);
1284
1381
  const price = await resolveOrPrompt(
1285
1382
  c2,
1286
1383
  c2.options.price,
@@ -1316,6 +1413,33 @@ ${chalk3.bold("Let's get you set up to sell.")}
1316
1413
  ]
1317
1414
  })
1318
1415
  );
1416
+ let accountCreated = false;
1417
+ if (newBusinessName !== void 0) {
1418
+ let account;
1419
+ try {
1420
+ account = await makeWhopRequest("POST", "/accounts", { title: newBusinessName });
1421
+ } catch (err) {
1422
+ return c2.error({
1423
+ code: "ACCOUNT_FAILED",
1424
+ message: `Failed to create the business account: ${err instanceof Error ? err.message : "unknown error"}`,
1425
+ retryable: true
1426
+ });
1427
+ }
1428
+ accountId = account.id;
1429
+ accountName = account.title ?? newBusinessName;
1430
+ accountCreated = true;
1431
+ setActiveProfileAccount({
1432
+ id: account.id,
1433
+ title: account.title ?? void 0,
1434
+ route: account.route ?? void 0
1435
+ });
1436
+ if (!c2.agent) {
1437
+ console.log(
1438
+ `
1439
+ ${chalk3.green("\u2713")} Created business account "${accountName}" ${chalk3.dim(`(${accountId})`)} \u2014 the CLI now acts on behalf of it.`
1440
+ );
1441
+ }
1442
+ }
1319
1443
  let productId;
1320
1444
  let productTitle;
1321
1445
  let productCreated = false;
@@ -1330,7 +1454,7 @@ ${chalk3.bold("Let's get you set up to sell.")}
1330
1454
  "/products",
1331
1455
  {
1332
1456
  title: newTitle,
1333
- company_id: state.companyId
1457
+ company_id: accountId
1334
1458
  }
1335
1459
  );
1336
1460
  } catch (err) {
@@ -1347,7 +1471,7 @@ ${chalk3.bold("Let's get you set up to sell.")}
1347
1471
  const isRenewal = billingPeriod !== "one_time";
1348
1472
  const planBody = {
1349
1473
  product_id: productId,
1350
- account_id: state.companyId,
1474
+ account_id: accountId,
1351
1475
  initial_price: price,
1352
1476
  plan_type: isRenewal ? "renewal" : "one_time"
1353
1477
  };
@@ -1383,6 +1507,15 @@ ${chalk3.bold("Let's get you set up to sell.")}
1383
1507
  const checkoutUrl = plan.purchase_url ?? null;
1384
1508
  const steps = [
1385
1509
  { id: "auth", label: "Log in", complete: true },
1510
+ ...accountCreated ? [
1511
+ {
1512
+ id: "account",
1513
+ label: "Create a business account",
1514
+ complete: true,
1515
+ firstId: accountId,
1516
+ firstTitle: accountName
1517
+ }
1518
+ ] : [],
1386
1519
  {
1387
1520
  id: "products",
1388
1521
  label: "Create a product",
@@ -1405,20 +1538,17 @@ ${chalk3.bold("Let's get you set up to sell.")}
1405
1538
  false
1406
1539
  );
1407
1540
  if (!c2.agent && !c2.formatExplicit) {
1408
- printSummary(
1409
- state.accountName,
1410
- steps,
1411
- true,
1412
- checkoutUrl,
1413
- nextStep,
1414
- productTitle
1415
- );
1541
+ printSummary(accountName, steps, true, checkoutUrl, nextStep, productTitle);
1416
1542
  }
1417
1543
  return c2.ok({
1418
- account: state.accountName,
1544
+ account: accountName,
1419
1545
  ready: true,
1420
1546
  checkoutUrl,
1421
- created: { productId, planId: plan.id },
1547
+ created: {
1548
+ ...accountCreated ? { accountId } : {},
1549
+ productId,
1550
+ planId: plan.id
1551
+ },
1422
1552
  steps,
1423
1553
  nextStep
1424
1554
  });
@@ -1548,7 +1678,7 @@ ${c.success("\u2713 Your store is fully set up.")}`);
1548
1678
  // package.json
1549
1679
  var package_default = {
1550
1680
  name: "@whop/cli",
1551
- version: "0.1.0",
1681
+ version: "0.2.0",
1552
1682
  description: "The Whop CLI \u2014 build and manage Whop apps from your terminal. Human and agent friendly.",
1553
1683
  keywords: [
1554
1684
  "agent",
@@ -2026,7 +2156,7 @@ var HANDWRITTEN_GROUPS = [
2026
2156
  section: "get-started",
2027
2157
  register: (cli2) => cli2.command("quickstart", {
2028
2158
  description: "Start selling \u2014 check your status and create what's missing",
2029
- hint: "In a terminal it guides you to your first sale. Agents: a read-only status check unless you pass --title/--price/--billing_period.",
2159
+ hint: "In a terminal it guides you to your first sale \u2014 including creating a business account if you don't have one. Agents: a read-only status check unless you pass --business_name/--title/--price/--billing_period.",
2030
2160
  options: quickstartOptions,
2031
2161
  output: quickstartOutput,
2032
2162
  examples: [
@@ -2038,6 +2168,15 @@ var HANDWRITTEN_GROUPS = [
2038
2168
  billing_period: "monthly"
2039
2169
  },
2040
2170
  description: "Create a $9.99/mo product non-interactively"
2171
+ },
2172
+ {
2173
+ options: {
2174
+ business_name: "My Business",
2175
+ title: "My Course",
2176
+ price: 9.99,
2177
+ billing_period: "one_time"
2178
+ },
2179
+ description: "No business account yet? Create it and the product in one run"
2041
2180
  }
2042
2181
  ],
2043
2182
  outputPolicy: "agent-only",