@whop/cli 0.5.0 → 0.7.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 +24 -0
- package/dist/{api-E6Y2WAEG.js → api-YDHSDKDV.js} +4 -2
- package/dist/{chunk-X34EUMEM.js → chunk-BSCOWFVU.js} +17668 -15981
- package/dist/chunk-BSCOWFVU.js.map +1 -0
- package/dist/index.js +697 -200
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/skills/whop-accounts/SKILL.md +24 -0
- package/skills/whop-ad-campaigns/SKILL.md +24 -0
- package/skills/whop-ad-groups/SKILL.md +24 -0
- package/skills/whop-ads/SKILL.md +100 -0
- package/skills/whop-apps/SKILL.md +18 -0
- package/skills/whop-audiences/SKILL.md +20 -0
- package/skills/whop-cards/SKILL.md +21 -0
- package/skills/whop-checkout-configurations/SKILL.md +21 -0
- package/skills/whop-deposits/SKILL.md +19 -0
- package/skills/whop-events/SKILL.md +19 -0
- package/skills/whop-ledgers/SKILL.md +19 -0
- package/skills/whop-media/SKILL.md +23 -0
- package/skills/whop-payouts/SKILL.md +20 -0
- package/skills/whop-people/SKILL.md +19 -0
- package/skills/whop-plans/SKILL.md +23 -0
- package/skills/whop-products/SKILL.md +22 -0
- package/skills/whop-referrals/SKILL.md +22 -0
- package/skills/whop-social-accounts/SKILL.md +26 -0
- package/skills/whop-swaps/SKILL.md +21 -0
- package/skills/whop-transfers/SKILL.md +20 -0
- package/skills/whop-users/SKILL.md +22 -0
- package/skills/whop-verifications/SKILL.md +25 -0
- package/dist/chunk-X34EUMEM.js.map +0 -1
- /package/dist/{api-E6Y2WAEG.js.map → api-YDHSDKDV.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getActiveProfile,
|
|
22
22
|
getSecret,
|
|
23
23
|
getValidAccessToken,
|
|
24
|
+
listOAuthAccounts,
|
|
24
25
|
listProfiles,
|
|
25
26
|
native_spec_default,
|
|
26
27
|
performOAuthLogin,
|
|
@@ -29,107 +30,158 @@ import {
|
|
|
29
30
|
removeAllProfiles,
|
|
30
31
|
removeProfile,
|
|
31
32
|
resolveActiveAccountId,
|
|
33
|
+
selectAccount,
|
|
32
34
|
selectProfile,
|
|
33
35
|
setActiveProfileAccount,
|
|
34
36
|
startSpinner,
|
|
35
37
|
switchProfile,
|
|
36
38
|
upsertProfile,
|
|
37
39
|
validateApiKey
|
|
38
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-BSCOWFVU.js";
|
|
39
41
|
import {
|
|
40
42
|
external_exports
|
|
41
43
|
} from "./chunk-KFCNNWPI.js";
|
|
42
44
|
|
|
43
45
|
// src/api/groups.json
|
|
44
46
|
var groups_default = [
|
|
45
|
-
|
|
46
|
-
"accounts",
|
|
47
|
-
"Accounts"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
47
|
+
{
|
|
48
|
+
name: "accounts",
|
|
49
|
+
tag: "Accounts",
|
|
50
|
+
description: "A business on Whop: profile, wallet, capabilities, settings.",
|
|
51
|
+
group: "Core Resources"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "users",
|
|
55
|
+
tag: "Users",
|
|
56
|
+
description: "A person on Whop: profile and connected identities.",
|
|
57
|
+
group: "Core Resources"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "stats",
|
|
61
|
+
tag: "Stats",
|
|
62
|
+
description: "Aggregated financial time series for reporting.",
|
|
63
|
+
group: "Core Resources"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "ledgers",
|
|
67
|
+
tag: "Ledgers",
|
|
68
|
+
description: "The activity feed behind an account or user's balance.",
|
|
69
|
+
group: "Money"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "payouts",
|
|
73
|
+
tag: "Payouts",
|
|
74
|
+
description: "Send money from a balance to a bank or wallet.",
|
|
75
|
+
group: "Money"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "cards",
|
|
79
|
+
tag: "Cards",
|
|
80
|
+
description: "Issue cards that spend from a balance.",
|
|
81
|
+
group: "Money"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "transfers",
|
|
85
|
+
tag: "Transfers",
|
|
86
|
+
description: "Move funds between Whop accounts and users.",
|
|
87
|
+
group: "Money"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "deposits",
|
|
91
|
+
tag: "Deposits",
|
|
92
|
+
description: "Add funds to a balance.",
|
|
93
|
+
group: "Money"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "swaps",
|
|
97
|
+
tag: "Swaps",
|
|
98
|
+
description: "Convert a balance between currencies.",
|
|
99
|
+
group: "Money"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "verifications",
|
|
103
|
+
tag: "Verifications",
|
|
104
|
+
description: "Identity review required before payouts and card issuing.",
|
|
105
|
+
group: "Money"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "products",
|
|
109
|
+
tag: "Products",
|
|
110
|
+
description: "The things you sell. Each owns plans and a store page.",
|
|
111
|
+
group: "Commerce"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "plans",
|
|
115
|
+
tag: "Plans",
|
|
116
|
+
description: "Pricing for a product: one-time, recurring, trials, stock.",
|
|
117
|
+
group: "Commerce"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "checkout-configurations",
|
|
121
|
+
tag: "Checkout Configurations",
|
|
122
|
+
description: "Turn a plan into a shareable, prefilled checkout link.",
|
|
123
|
+
group: "Commerce"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "referrals",
|
|
127
|
+
tag: "Referrals",
|
|
128
|
+
description: "Track who referred a purchase and what they earn.",
|
|
129
|
+
group: "Commerce"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "people",
|
|
133
|
+
tag: "People",
|
|
134
|
+
description: "Visitors and customers of an account, aggregated from pixel events.",
|
|
135
|
+
group: "Tracking"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "events",
|
|
139
|
+
tag: "Events",
|
|
140
|
+
description: "Conversion and engagement events tracked for attribution.",
|
|
141
|
+
group: "Tracking"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "ads",
|
|
145
|
+
tag: "Ads",
|
|
146
|
+
description: "The creative: copy, assets, and destination URL.",
|
|
147
|
+
group: "Ads"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "ad-campaigns",
|
|
151
|
+
tag: "Ad Campaigns",
|
|
152
|
+
description: "Platform, objective, and budget for a set of ads.",
|
|
153
|
+
group: "Ads"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "ad-groups",
|
|
157
|
+
tag: "Ad Groups",
|
|
158
|
+
description: "Audience, placements, and schedule within a campaign.",
|
|
159
|
+
group: "Ads"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "audiences",
|
|
163
|
+
tag: "Audiences",
|
|
164
|
+
description: "Reusable targeting lists for ad groups.",
|
|
165
|
+
group: "Ads"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "media",
|
|
169
|
+
tag: "Media",
|
|
170
|
+
description: "AI-generated assets, billed from a balance, attachable wherever files are accepted.",
|
|
171
|
+
group: "Media"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "social-accounts",
|
|
175
|
+
tag: "Social Accounts",
|
|
176
|
+
description: "Connected Facebook and Instagram accounts that run ads.",
|
|
177
|
+
group: "Identity"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "apps",
|
|
181
|
+
tag: "Apps",
|
|
182
|
+
description: "Apps you build on Whop: metadata, hosted builds, runtime logs.",
|
|
183
|
+
group: "Developer"
|
|
184
|
+
}
|
|
133
185
|
];
|
|
134
186
|
|
|
135
187
|
// src/auth/commands.ts
|
|
@@ -181,6 +233,29 @@ var switchArgs = external_exports.object({
|
|
|
181
233
|
var switchOutput = external_exports.object({
|
|
182
234
|
active: external_exports.string().describe("The newly active profile")
|
|
183
235
|
});
|
|
236
|
+
var accountArgs = external_exports.object({
|
|
237
|
+
id: external_exports.string().optional().describe(
|
|
238
|
+
"Business account to make the default \u2014 id (biz_xxx) or route (interactive picker if omitted)"
|
|
239
|
+
)
|
|
240
|
+
});
|
|
241
|
+
var accountOptions = external_exports.object({
|
|
242
|
+
list: external_exports.boolean().optional().describe(
|
|
243
|
+
"List the business accounts you can manage without changing the default"
|
|
244
|
+
)
|
|
245
|
+
});
|
|
246
|
+
var accountListItem = external_exports.object({
|
|
247
|
+
id: external_exports.string(),
|
|
248
|
+
title: external_exports.string(),
|
|
249
|
+
route: external_exports.string(),
|
|
250
|
+
active: external_exports.boolean().describe("Whether this is the profile's default business account")
|
|
251
|
+
});
|
|
252
|
+
var accountOutput = external_exports.object({
|
|
253
|
+
profile: external_exports.string().describe("The active profile"),
|
|
254
|
+
account: Identity.nullable().describe(
|
|
255
|
+
"The default business account passed to scoped commands"
|
|
256
|
+
),
|
|
257
|
+
accounts: external_exports.array(accountListItem).describe("Business accounts this user can manage")
|
|
258
|
+
});
|
|
184
259
|
var loginExamples = [
|
|
185
260
|
{ description: "Interactive login" },
|
|
186
261
|
{
|
|
@@ -318,7 +393,7 @@ async function loginAdapter(c2) {
|
|
|
318
393
|
const accountId = getActiveProfile()?.accountId ?? "";
|
|
319
394
|
if (accountId) {
|
|
320
395
|
try {
|
|
321
|
-
const { createWhopFetch: createWhopFetch2 } = await import("./api-
|
|
396
|
+
const { createWhopFetch: createWhopFetch2 } = await import("./api-YDHSDKDV.js");
|
|
322
397
|
const fetch3 = createWhopFetch2();
|
|
323
398
|
const res = await fetch3(
|
|
324
399
|
new Request(
|
|
@@ -524,6 +599,196 @@ function buildAuthGroup() {
|
|
|
524
599
|
);
|
|
525
600
|
}
|
|
526
601
|
});
|
|
602
|
+
auth.command("account", {
|
|
603
|
+
description: "List your business accounts or set the default one for the active profile",
|
|
604
|
+
aliases: ["accounts"],
|
|
605
|
+
args: accountArgs,
|
|
606
|
+
options: accountOptions,
|
|
607
|
+
output: accountOutput,
|
|
608
|
+
examples: [
|
|
609
|
+
{ description: "Pick the default business account interactively" },
|
|
610
|
+
{
|
|
611
|
+
options: { list: true },
|
|
612
|
+
description: "List the business accounts you can manage"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
args: { id: "biz_xxx" },
|
|
616
|
+
description: "Set the default business account"
|
|
617
|
+
}
|
|
618
|
+
],
|
|
619
|
+
hint: "OAuth profiles only \u2014 API key profiles are tied to a single business account.",
|
|
620
|
+
run: async (c2) => {
|
|
621
|
+
const profile = getActiveProfile();
|
|
622
|
+
if (!profile) {
|
|
623
|
+
return c2.error({
|
|
624
|
+
code: "NO_ACTIVE_PROFILE",
|
|
625
|
+
message: "No active profile \u2014 log in first.",
|
|
626
|
+
retryable: true,
|
|
627
|
+
cta: {
|
|
628
|
+
description: "To authenticate:",
|
|
629
|
+
commands: [{ command: "auth login", description: "Log in to Whop" }]
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (profile.method !== "oauth") {
|
|
634
|
+
return c2.error({
|
|
635
|
+
code: "API_KEY_PROFILE",
|
|
636
|
+
message: "API key profiles are tied to a single business account. Log in with Whop (OAuth) to switch between the businesses you manage.",
|
|
637
|
+
retryable: false,
|
|
638
|
+
cta: {
|
|
639
|
+
description: "To use a different business account:",
|
|
640
|
+
commands: [
|
|
641
|
+
{
|
|
642
|
+
command: "auth login",
|
|
643
|
+
description: 'Choose "Log in with Whop"'
|
|
644
|
+
}
|
|
645
|
+
]
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
let token;
|
|
650
|
+
try {
|
|
651
|
+
token = await getValidAccessToken(profile);
|
|
652
|
+
} catch {
|
|
653
|
+
token = null;
|
|
654
|
+
}
|
|
655
|
+
if (!token) {
|
|
656
|
+
return c2.error({
|
|
657
|
+
code: "SESSION_EXPIRED",
|
|
658
|
+
message: "Your session is no longer valid \u2014 log in again.",
|
|
659
|
+
retryable: false,
|
|
660
|
+
cta: {
|
|
661
|
+
description: "To authenticate:",
|
|
662
|
+
commands: [{ command: "auth login", description: "Log in to Whop" }]
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
const s = c2.agent ? null : startSpinner("Loading your business accounts\u2026");
|
|
667
|
+
let accounts;
|
|
668
|
+
try {
|
|
669
|
+
accounts = await listOAuthAccounts(token);
|
|
670
|
+
s?.stop(
|
|
671
|
+
`Found ${accounts.length} business account${accounts.length === 1 ? "" : "s"}.`
|
|
672
|
+
);
|
|
673
|
+
} catch {
|
|
674
|
+
s?.stop("Couldn't load your business accounts.");
|
|
675
|
+
return c2.error({
|
|
676
|
+
code: "ACCOUNT_LOOKUP_FAILED",
|
|
677
|
+
message: "Couldn't load your business accounts. Please try again.",
|
|
678
|
+
retryable: true
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
if (accounts.length === 0) {
|
|
682
|
+
return c2.error({
|
|
683
|
+
code: "NO_ACCOUNTS",
|
|
684
|
+
message: "You don't have a business account yet.",
|
|
685
|
+
retryable: false,
|
|
686
|
+
cta: {
|
|
687
|
+
description: "Create one and start selling in one command:",
|
|
688
|
+
commands: [
|
|
689
|
+
{
|
|
690
|
+
command: "quickstart",
|
|
691
|
+
description: "Create your business account, product, and checkout link"
|
|
692
|
+
}
|
|
693
|
+
]
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
const currentAccount = profile.accountId || profile.accountTitle || profile.accountRoute ? {
|
|
698
|
+
id: profile.accountId,
|
|
699
|
+
title: profile.accountTitle,
|
|
700
|
+
route: profile.accountRoute
|
|
701
|
+
} : null;
|
|
702
|
+
if (c2.options.list) {
|
|
703
|
+
return c2.ok(
|
|
704
|
+
{
|
|
705
|
+
profile: profile.name,
|
|
706
|
+
account: currentAccount,
|
|
707
|
+
accounts: accounts.map((account) => ({
|
|
708
|
+
...account,
|
|
709
|
+
active: account.id === profile.accountId
|
|
710
|
+
}))
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
cta: {
|
|
714
|
+
description: "Change the default business account:",
|
|
715
|
+
commands: [
|
|
716
|
+
{
|
|
717
|
+
command: "auth account",
|
|
718
|
+
args: { id: true },
|
|
719
|
+
description: "Pass a business account id or route"
|
|
720
|
+
}
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
let requested = c2.args.id;
|
|
727
|
+
if (!requested) {
|
|
728
|
+
if (c2.agent) {
|
|
729
|
+
return c2.error({
|
|
730
|
+
code: "ACCOUNT_REQUIRED",
|
|
731
|
+
message: `Specify a business account. Available: ${accounts.map((account) => `${account.id} (${account.title})`).join(", ")}.`,
|
|
732
|
+
retryable: true
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
requested = (await selectAccount(accounts, profile.accountId)).id;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (error instanceof PromptCancelledError) {
|
|
739
|
+
return c2.error({
|
|
740
|
+
code: "CANCELLED",
|
|
741
|
+
message: "Switch cancelled.",
|
|
742
|
+
exitCode: 130
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const match = accounts.find((account) => account.id === requested) ?? accounts.find((account) => account.route === requested);
|
|
749
|
+
if (!match) {
|
|
750
|
+
return c2.error({
|
|
751
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
752
|
+
message: `No business account "${requested}" for this user. Available: ${accounts.map((account) => `${account.id} (${account.title})`).join(", ")}.`,
|
|
753
|
+
retryable: true
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
if (!setActiveProfileAccount(match, profile.name)) {
|
|
757
|
+
return c2.error({
|
|
758
|
+
code: "PROFILE_REMOVED",
|
|
759
|
+
message: `Profile "${profile.name}" was removed while switching \u2014 log in again.`,
|
|
760
|
+
retryable: false,
|
|
761
|
+
cta: {
|
|
762
|
+
description: "To authenticate:",
|
|
763
|
+
commands: [
|
|
764
|
+
{ command: "auth login", description: "Log in to Whop" }
|
|
765
|
+
]
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
return c2.ok(
|
|
770
|
+
{
|
|
771
|
+
profile: profile.name,
|
|
772
|
+
account: match,
|
|
773
|
+
accounts: accounts.map((account) => ({
|
|
774
|
+
...account,
|
|
775
|
+
active: account.id === match.id
|
|
776
|
+
}))
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
cta: {
|
|
780
|
+
description: `Now acting on behalf of ${match.title}.`,
|
|
781
|
+
commands: [
|
|
782
|
+
{
|
|
783
|
+
command: "auth status",
|
|
784
|
+
description: "Show the active identity"
|
|
785
|
+
}
|
|
786
|
+
]
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
527
792
|
auth.command("status", {
|
|
528
793
|
description: "Show the active identity (whoami)",
|
|
529
794
|
aliases: ["whoami"],
|
|
@@ -651,6 +916,14 @@ async function updateAppRoute(appId, route) {
|
|
|
651
916
|
async function updateAppSecrets(appId, secrets) {
|
|
652
917
|
return makeWhopRequest("PATCH", `/apps/${appId}`, { secrets });
|
|
653
918
|
}
|
|
919
|
+
async function listAppLogs(appId, params) {
|
|
920
|
+
const query = new URLSearchParams();
|
|
921
|
+
for (const [key, value] of Object.entries(params)) {
|
|
922
|
+
if (value !== void 0 && value !== "") query.set(key, String(value));
|
|
923
|
+
}
|
|
924
|
+
const qs = query.size > 0 ? `?${query}` : "";
|
|
925
|
+
return makeWhopRequest("GET", `/apps/${appId}/logs${qs}`);
|
|
926
|
+
}
|
|
654
927
|
async function createAppBuild(input) {
|
|
655
928
|
return makeWhopRequest("POST", "/app_builds", {
|
|
656
929
|
app_id: input.app_id,
|
|
@@ -1377,7 +1650,7 @@ async function withRoute(c2, app) {
|
|
|
1377
1650
|
});
|
|
1378
1651
|
}
|
|
1379
1652
|
}
|
|
1380
|
-
function
|
|
1653
|
+
function resolveLinkedAppId(appOption) {
|
|
1381
1654
|
if (appOption) return appOption;
|
|
1382
1655
|
const projectDir = findProjectDir();
|
|
1383
1656
|
const config = projectDir ? readAppConfig(projectDir) : null;
|
|
@@ -1386,6 +1659,33 @@ function resolveSecretsAppId(appOption) {
|
|
|
1386
1659
|
`No app specified. Pass --app app_xxx or run inside a project with ${APP_CONFIG_FILENAME}.`
|
|
1387
1660
|
);
|
|
1388
1661
|
}
|
|
1662
|
+
var DURATION_MS = {
|
|
1663
|
+
s: 1e3,
|
|
1664
|
+
m: 6e4,
|
|
1665
|
+
h: 36e5,
|
|
1666
|
+
d: 864e5
|
|
1667
|
+
};
|
|
1668
|
+
function parseTimeOption(value) {
|
|
1669
|
+
if (!value) return void 0;
|
|
1670
|
+
const match = /^(\d+)([smhd])$/.exec(value);
|
|
1671
|
+
if (!match) return value;
|
|
1672
|
+
return new Date(
|
|
1673
|
+
Date.now() - Number(match[1]) * DURATION_MS[match[2]]
|
|
1674
|
+
).toISOString();
|
|
1675
|
+
}
|
|
1676
|
+
var LOG_LEVEL_COLORS = {
|
|
1677
|
+
error: chalk.red,
|
|
1678
|
+
warn: chalk.yellow
|
|
1679
|
+
};
|
|
1680
|
+
function printLogLine(row) {
|
|
1681
|
+
const time = row.created_at.replace("T", " ").replace("Z", "");
|
|
1682
|
+
const color = LOG_LEVEL_COLORS[row.level] ?? chalk.dim;
|
|
1683
|
+
const request = row.request_method ? chalk.dim(` ${row.request_method} ${row.request_path ?? ""}`) : "";
|
|
1684
|
+
console.log(
|
|
1685
|
+
`${chalk.dim(time)} ${color(row.level.padEnd(5))}${request} ${row.message}`
|
|
1686
|
+
);
|
|
1687
|
+
if (row.stack) console.log(chalk.dim(row.stack));
|
|
1688
|
+
}
|
|
1389
1689
|
function buildSecretsGroup() {
|
|
1390
1690
|
const secrets = Cli_exports.create("secrets", {
|
|
1391
1691
|
description: "Manage app secrets \u2014 encrypted at rest, injected as env bindings into the hosted runtime and `whop apps dev`"
|
|
@@ -1396,7 +1696,7 @@ function buildSecretsGroup() {
|
|
|
1396
1696
|
app: external_exports.string().optional().describe("App id (defaults to the project's linked app)")
|
|
1397
1697
|
}),
|
|
1398
1698
|
run: async (c2) => {
|
|
1399
|
-
const remote = await getApp(
|
|
1699
|
+
const remote = await getApp(resolveLinkedAppId(c2.options.app));
|
|
1400
1700
|
return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
|
|
1401
1701
|
}
|
|
1402
1702
|
});
|
|
@@ -1423,7 +1723,7 @@ function buildSecretsGroup() {
|
|
|
1423
1723
|
updates[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
1424
1724
|
}
|
|
1425
1725
|
const remote = await updateAppSecrets(
|
|
1426
|
-
|
|
1726
|
+
resolveLinkedAppId(c2.options.app),
|
|
1427
1727
|
updates
|
|
1428
1728
|
);
|
|
1429
1729
|
return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
|
|
@@ -1440,7 +1740,7 @@ function buildSecretsGroup() {
|
|
|
1440
1740
|
const updates = {};
|
|
1441
1741
|
for (const key of c2.options.key) updates[key] = null;
|
|
1442
1742
|
const remote = await updateAppSecrets(
|
|
1443
|
-
|
|
1743
|
+
resolveLinkedAppId(c2.options.app),
|
|
1444
1744
|
updates
|
|
1445
1745
|
);
|
|
1446
1746
|
return c2.ok({ app_id: remote.id, secrets: remote.secrets ?? {} });
|
|
@@ -1459,6 +1759,54 @@ async function buildAppGroup() {
|
|
|
1459
1759
|
await registerSpecCommands(builds, "App builds");
|
|
1460
1760
|
app.command(builds);
|
|
1461
1761
|
app.command(buildSecretsGroup());
|
|
1762
|
+
app.command("logs", {
|
|
1763
|
+
description: "Read the app's server runtime logs \u2014 console output, uncaught exceptions, failed requests (kept 7 days)",
|
|
1764
|
+
hint: "Newest first in the structured output; the terminal view prints oldest to newest. Deploy, hit your app, then read its logs to debug server code.",
|
|
1765
|
+
options: external_exports.object({
|
|
1766
|
+
app: external_exports.string().optional().describe("App id (defaults to the project's linked app)"),
|
|
1767
|
+
level: external_exports.enum(["log", "debug", "info", "warn", "error"]).optional().describe("Only console lines of this level"),
|
|
1768
|
+
query: external_exports.string().optional().describe("Only logs whose message contains this text"),
|
|
1769
|
+
build: external_exports.string().optional().describe("Only logs from this build (abld_xxx)"),
|
|
1770
|
+
since: external_exports.string().optional().describe(
|
|
1771
|
+
'Start of the window \u2014 a duration like "10m"/"2h"/"1d" or an ISO 8601 timestamp'
|
|
1772
|
+
),
|
|
1773
|
+
until: external_exports.string().optional().describe("End of the window \u2014 a duration or ISO 8601 timestamp"),
|
|
1774
|
+
first: external_exports.number().optional().describe("Number of log lines to return (max 500)"),
|
|
1775
|
+
after: external_exports.string().optional().describe("Cursor from a previous page's page_info")
|
|
1776
|
+
}),
|
|
1777
|
+
examples: [
|
|
1778
|
+
{ description: "Recent logs for the linked app" },
|
|
1779
|
+
{
|
|
1780
|
+
options: { level: "error", since: "1h" },
|
|
1781
|
+
description: "Errors from the last hour"
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
options: { query: "checkout", app: "app_xxxxxxxx" },
|
|
1785
|
+
description: "Search another app's logs by message text"
|
|
1786
|
+
}
|
|
1787
|
+
],
|
|
1788
|
+
outputPolicy: "agent-only",
|
|
1789
|
+
run: async (c2) => {
|
|
1790
|
+
const appId = resolveLinkedAppId(c2.options.app);
|
|
1791
|
+
const page = await listAppLogs(appId, {
|
|
1792
|
+
level: c2.options.level,
|
|
1793
|
+
query: c2.options.query,
|
|
1794
|
+
app_build_id: c2.options.build,
|
|
1795
|
+
created_after: parseTimeOption(c2.options.since),
|
|
1796
|
+
created_before: parseTimeOption(c2.options.until),
|
|
1797
|
+
first: c2.options.first,
|
|
1798
|
+
after: c2.options.after
|
|
1799
|
+
});
|
|
1800
|
+
const logs = page.data ?? [];
|
|
1801
|
+
if (!c2.agent && !c2.formatExplicit) {
|
|
1802
|
+
if (logs.length === 0) {
|
|
1803
|
+
console.log(chalk.dim("No logs in this window."));
|
|
1804
|
+
}
|
|
1805
|
+
for (const row of [...logs].reverse()) printLogLine(row);
|
|
1806
|
+
}
|
|
1807
|
+
return c2.ok({ app_id: appId, logs, page_info: page.page_info });
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1462
1810
|
app.command("dev", {
|
|
1463
1811
|
description: "Run the local dev server for this app",
|
|
1464
1812
|
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.",
|
|
@@ -2340,7 +2688,7 @@ ${c.success("\u2713 Your store is fully set up.")}`);
|
|
|
2340
2688
|
// package.json
|
|
2341
2689
|
var package_default = {
|
|
2342
2690
|
name: "@whop/cli",
|
|
2343
|
-
version: "0.
|
|
2691
|
+
version: "0.7.0",
|
|
2344
2692
|
description: "The Whop CLI \u2014 build and manage Whop apps from your terminal. Human and agent friendly.",
|
|
2345
2693
|
keywords: [
|
|
2346
2694
|
"agent",
|
|
@@ -2361,7 +2709,8 @@ var package_default = {
|
|
|
2361
2709
|
"whop-cli": "dist/index.js"
|
|
2362
2710
|
},
|
|
2363
2711
|
files: [
|
|
2364
|
-
"dist"
|
|
2712
|
+
"dist",
|
|
2713
|
+
"skills"
|
|
2365
2714
|
],
|
|
2366
2715
|
type: "module",
|
|
2367
2716
|
exports: {
|
|
@@ -2398,7 +2747,7 @@ var package_default = {
|
|
|
2398
2747
|
devDependencies: {
|
|
2399
2748
|
"@types/node": "25.3.5",
|
|
2400
2749
|
fflate: "0.8.2",
|
|
2401
|
-
incur: "github:whopio/incur#
|
|
2750
|
+
incur: "github:whopio/incur#fbe3f4dbf48f1bc780cb1bca476f7dade1fe12d3",
|
|
2402
2751
|
tsup: "8.5.0",
|
|
2403
2752
|
tsx: "4.19.4",
|
|
2404
2753
|
typescript: "5.9.3",
|
|
@@ -2913,7 +3262,9 @@ var HANDWRITTEN_GROUPS = [
|
|
|
2913
3262
|
var HANDWRITTEN_GROUP_NAMES = new Set(
|
|
2914
3263
|
HANDWRITTEN_GROUPS.map(({ name }) => name)
|
|
2915
3264
|
);
|
|
2916
|
-
var API_GROUPS = groups_default.
|
|
3265
|
+
var API_GROUPS = groups_default.filter(
|
|
3266
|
+
({ name }) => !HANDWRITTEN_GROUP_NAMES.has(name)
|
|
3267
|
+
);
|
|
2917
3268
|
var COMMAND_GROUPS = [...API_GROUPS, ...HANDWRITTEN_GROUPS];
|
|
2918
3269
|
async function registerHandwrittenGroups(cli2) {
|
|
2919
3270
|
for (const group of HANDWRITTEN_GROUPS) await group.register(cli2);
|
|
@@ -2934,6 +3285,155 @@ async function setupAgents(cli2) {
|
|
|
2934
3285
|
}
|
|
2935
3286
|
}
|
|
2936
3287
|
|
|
3288
|
+
// src/media/commands.ts
|
|
3289
|
+
import { spinner as spinner2 } from "@clack/prompts";
|
|
3290
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
3291
|
+
var whopFetch2 = createWhopFetch();
|
|
3292
|
+
async function buildMediaGroup(description) {
|
|
3293
|
+
const media = Cli_exports.create("media", { description });
|
|
3294
|
+
const spec2 = filterSpecByTag(native_spec_default, "Media");
|
|
3295
|
+
const generated = await Openapi_exports.generateCommands(
|
|
3296
|
+
spec2,
|
|
3297
|
+
whopFetch2
|
|
3298
|
+
);
|
|
3299
|
+
for (const [name, entry] of generated) {
|
|
3300
|
+
if (!("run" in entry)) continue;
|
|
3301
|
+
if (name === "generate") {
|
|
3302
|
+
media.command(name, buildGenerateCommand(entry));
|
|
3303
|
+
continue;
|
|
3304
|
+
}
|
|
3305
|
+
media.command(name, entry);
|
|
3306
|
+
}
|
|
3307
|
+
return media;
|
|
3308
|
+
}
|
|
3309
|
+
function buildGenerateCommand(entry) {
|
|
3310
|
+
const options = (entry.options ?? external_exports.object({})).extend({
|
|
3311
|
+
wait: external_exports.boolean().optional().describe(
|
|
3312
|
+
"Block until generation finishes and return the terminal asset (with file.id when ready)"
|
|
3313
|
+
),
|
|
3314
|
+
timeout: external_exports.coerce.number().default(600).describe("Max seconds to wait with --wait before giving up")
|
|
3315
|
+
});
|
|
3316
|
+
return {
|
|
3317
|
+
description: entry.description,
|
|
3318
|
+
options,
|
|
3319
|
+
examples: [
|
|
3320
|
+
{
|
|
3321
|
+
options: {
|
|
3322
|
+
type: "image",
|
|
3323
|
+
prompt: "A running club jogging across a bridge at sunrise",
|
|
3324
|
+
wait: true
|
|
3325
|
+
},
|
|
3326
|
+
description: "Generate an image and wait for the finished file"
|
|
3327
|
+
},
|
|
3328
|
+
{
|
|
3329
|
+
options: {
|
|
3330
|
+
type: "video",
|
|
3331
|
+
prompt: "Sneaker product spin on a white background",
|
|
3332
|
+
duration_seconds: 5,
|
|
3333
|
+
wait: true
|
|
3334
|
+
},
|
|
3335
|
+
description: "Generate a 5s video and wait"
|
|
3336
|
+
}
|
|
3337
|
+
],
|
|
3338
|
+
run: async (c2) => {
|
|
3339
|
+
const { wait, timeout, ...body } = c2.options;
|
|
3340
|
+
const request = Object.fromEntries(
|
|
3341
|
+
Object.entries(body).filter(([, value]) => value !== void 0)
|
|
3342
|
+
);
|
|
3343
|
+
const asset = await createGeneration(c2, request);
|
|
3344
|
+
if (!wait) return asset;
|
|
3345
|
+
return awaitGeneration(c2, asset, Number(timeout));
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
async function createGeneration(c2, body) {
|
|
3350
|
+
const res = await whopFetch2(
|
|
3351
|
+
new Request("https://api.whop.com/media/generate", {
|
|
3352
|
+
method: "POST",
|
|
3353
|
+
body: JSON.stringify(body)
|
|
3354
|
+
})
|
|
3355
|
+
);
|
|
3356
|
+
const payload = await res.json().catch(() => null);
|
|
3357
|
+
if (res.ok && payload) return payload;
|
|
3358
|
+
if (res.status === 402) {
|
|
3359
|
+
const depositUrl = payload?.error?.deposit_url;
|
|
3360
|
+
return c2.error({
|
|
3361
|
+
code: "INSUFFICIENT_BALANCE",
|
|
3362
|
+
message: `${payload?.message ?? "Insufficient balance to fund this generation."}${depositUrl ? ` Deposit at ${depositUrl}` : ""}`,
|
|
3363
|
+
retryable: true,
|
|
3364
|
+
cta: {
|
|
3365
|
+
description: "Fund your balance, then retry:",
|
|
3366
|
+
commands: [
|
|
3367
|
+
{ command: "deposits create", description: "Add funds to your balance" }
|
|
3368
|
+
]
|
|
3369
|
+
}
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
return c2.error({
|
|
3373
|
+
code: "API_ERROR",
|
|
3374
|
+
message: payload?.message ?? `Generation request failed (HTTP ${res.status})`,
|
|
3375
|
+
retryable: true
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
async function awaitGeneration(c2, created, timeoutSeconds) {
|
|
3379
|
+
const interactive = !c2.agent && process.stdout.isTTY;
|
|
3380
|
+
const progress = interactive ? spinner2() : null;
|
|
3381
|
+
progress?.start(`Generating ${created.media_type ?? "asset"}\u2026`);
|
|
3382
|
+
const startedAt = Date.now();
|
|
3383
|
+
let asset = created;
|
|
3384
|
+
while (asset.status === "processing") {
|
|
3385
|
+
if (Date.now() - startedAt > timeoutSeconds * 1e3) {
|
|
3386
|
+
progress?.stop(`Still processing after ${timeoutSeconds}s.`);
|
|
3387
|
+
return c2.error({
|
|
3388
|
+
code: "WAIT_TIMEOUT",
|
|
3389
|
+
message: `Generation ${asset.id} is still processing after ${timeoutSeconds}s. It keeps running server-side \u2014 check on it later.`,
|
|
3390
|
+
retryable: true,
|
|
3391
|
+
cta: {
|
|
3392
|
+
description: "Check the asset:",
|
|
3393
|
+
commands: [
|
|
3394
|
+
{ command: `media get ${asset.id}`, description: "Poll the asset status" }
|
|
3395
|
+
]
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
3400
|
+
progress?.message(
|
|
3401
|
+
`Generating ${created.media_type ?? "asset"}\u2026 ${Math.round((Date.now() - startedAt) / 1e3)}s`
|
|
3402
|
+
);
|
|
3403
|
+
asset = await fetchAsset(c2, asset.id, progress);
|
|
3404
|
+
}
|
|
3405
|
+
if (asset.status === "failed") {
|
|
3406
|
+
progress?.stop("Generation failed.");
|
|
3407
|
+
return c2.error({
|
|
3408
|
+
code: "GENERATION_FAILED",
|
|
3409
|
+
message: `${asset.error_message ?? "Generation failed."} The charge was refunded \u2014 adjust the prompt and retry.`,
|
|
3410
|
+
retryable: true
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
progress?.stop(
|
|
3414
|
+
`Generated ${asset.file?.id ?? asset.id} in ${Math.round((Date.now() - startedAt) / 1e3)}s.`
|
|
3415
|
+
);
|
|
3416
|
+
return asset;
|
|
3417
|
+
}
|
|
3418
|
+
async function fetchAsset(c2, id, progress) {
|
|
3419
|
+
const res = await whopFetch2(
|
|
3420
|
+
new Request(`https://api.whop.com/media/${id}`, { method: "GET" })
|
|
3421
|
+
);
|
|
3422
|
+
const payload = await res.json().catch(() => null);
|
|
3423
|
+
if (!res.ok || !payload) {
|
|
3424
|
+
progress?.stop("Polling failed.");
|
|
3425
|
+
return c2.error({
|
|
3426
|
+
code: "API_ERROR",
|
|
3427
|
+
message: payload?.message ?? `Couldn't poll media asset ${id} (HTTP ${res.status})`,
|
|
3428
|
+
retryable: true
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
return payload;
|
|
3432
|
+
}
|
|
3433
|
+
function sleep2(ms) {
|
|
3434
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
3435
|
+
}
|
|
3436
|
+
|
|
2937
3437
|
// src/lib/renderer.ts
|
|
2938
3438
|
var MAX_CELL_WIDTH = 40;
|
|
2939
3439
|
function isPlainObject(value) {
|
|
@@ -2990,13 +3490,91 @@ function renderRows(rows) {
|
|
|
2990
3490
|
${c.muted(`& ${hiddenCount} more columns \u2014 use --format json to see all`)}` : "";
|
|
2991
3491
|
return [header, separator, ...body].join("\n") + overflowHint;
|
|
2992
3492
|
}
|
|
3493
|
+
function pruneEmpty(value) {
|
|
3494
|
+
if (value == null) return { kept: void 0, hidden: 1 };
|
|
3495
|
+
if (Array.isArray(value)) {
|
|
3496
|
+
if (value.length === 0) return { kept: void 0, hidden: 1 };
|
|
3497
|
+
if (!value.every(isPlainObject)) return { kept: value, hidden: 0 };
|
|
3498
|
+
let hidden = 0;
|
|
3499
|
+
const items = [];
|
|
3500
|
+
for (const item of value) {
|
|
3501
|
+
const pruned = pruneEmpty(item);
|
|
3502
|
+
hidden += pruned.hidden;
|
|
3503
|
+
if (pruned.kept !== void 0) items.push(pruned.kept);
|
|
3504
|
+
}
|
|
3505
|
+
if (items.length === 0) return { kept: void 0, hidden };
|
|
3506
|
+
return { kept: items, hidden };
|
|
3507
|
+
}
|
|
3508
|
+
if (isPlainObject(value)) {
|
|
3509
|
+
let hidden = 0;
|
|
3510
|
+
const out = {};
|
|
3511
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
3512
|
+
const pruned = pruneEmpty(entry);
|
|
3513
|
+
hidden += pruned.hidden;
|
|
3514
|
+
if (pruned.kept !== void 0) out[key] = pruned.kept;
|
|
3515
|
+
}
|
|
3516
|
+
if (Object.keys(out).length === 0) return { kept: void 0, hidden: Math.max(hidden, 1) };
|
|
3517
|
+
return { kept: out, hidden };
|
|
3518
|
+
}
|
|
3519
|
+
return { kept: value, hidden: 0 };
|
|
3520
|
+
}
|
|
3521
|
+
function formatScalar(value) {
|
|
3522
|
+
const text3 = String(value);
|
|
3523
|
+
if (typeof value === "boolean") return value ? c.success(text3) : c.error(text3);
|
|
3524
|
+
if (typeof value === "number") return c.number(text3);
|
|
3525
|
+
if (typeof value === "string") {
|
|
3526
|
+
if (WHOP_TAG.test(value)) return c.id(text3);
|
|
3527
|
+
if (/^https?:\/\//.test(value)) return c.link(text3);
|
|
3528
|
+
}
|
|
3529
|
+
return text3;
|
|
3530
|
+
}
|
|
3531
|
+
function renderEntries(obj, depth) {
|
|
3532
|
+
const pad = " ".repeat(depth);
|
|
3533
|
+
const lines = [];
|
|
3534
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3535
|
+
const label = c.muted(`${key}:`);
|
|
3536
|
+
if (isPlainObject(value)) {
|
|
3537
|
+
lines.push(`${pad}${label}`);
|
|
3538
|
+
lines.push(...renderEntries(value, depth + 1));
|
|
3539
|
+
} else if (isObjectArray(value)) {
|
|
3540
|
+
lines.push(`${pad}${label}`);
|
|
3541
|
+
for (const item of value) {
|
|
3542
|
+
const itemLines = renderEntries(item, depth + 1);
|
|
3543
|
+
itemLines[0] = `${pad}- ${itemLines[0].slice(pad.length + 2)}`;
|
|
3544
|
+
lines.push(...itemLines);
|
|
3545
|
+
}
|
|
3546
|
+
} else if (Array.isArray(value)) {
|
|
3547
|
+
lines.push(`${pad}${label} ${value.map(formatScalar).join(", ")}`);
|
|
3548
|
+
} else {
|
|
3549
|
+
lines.push(`${pad}${label} ${formatScalar(value)}`);
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
return lines;
|
|
3553
|
+
}
|
|
3554
|
+
function renderObject(data) {
|
|
3555
|
+
const { kept, hidden } = pruneEmpty(data);
|
|
3556
|
+
const lines = kept === void 0 ? [c.muted("(all fields empty)")] : renderEntries(kept, 0);
|
|
3557
|
+
const hint = hidden > 0 ? `
|
|
3558
|
+
${c.muted(`${hidden} empty ${hidden === 1 ? "field" : "fields"} hidden \u2014 use --format json to see all`)}` : "";
|
|
3559
|
+
return lines.join("\n") + hint;
|
|
3560
|
+
}
|
|
2993
3561
|
function render(data) {
|
|
2994
|
-
if (
|
|
3562
|
+
if (Array.isArray(data)) {
|
|
3563
|
+
if (data.length === 0) return c.muted("(no results)");
|
|
3564
|
+
return isObjectArray(data) ? renderRows(data) : null;
|
|
3565
|
+
}
|
|
2995
3566
|
if (isPlainObject(data)) {
|
|
2996
|
-
const
|
|
2997
|
-
|
|
2998
|
-
|
|
3567
|
+
const values = Object.values(data);
|
|
3568
|
+
const hasScalarField = values.some((v) => v !== null && typeof v !== "object");
|
|
3569
|
+
if (!hasScalarField) {
|
|
3570
|
+
const objectArrays = values.filter(isObjectArray);
|
|
3571
|
+
if (objectArrays.length === 1) return renderRows(objectArrays[0]);
|
|
3572
|
+
const arrays = values.filter(Array.isArray);
|
|
3573
|
+
if (objectArrays.length === 0 && arrays.length === 1 && arrays[0].length === 0) {
|
|
3574
|
+
return c.muted("(no results)");
|
|
3575
|
+
}
|
|
2999
3576
|
}
|
|
3577
|
+
return renderObject(data);
|
|
3000
3578
|
}
|
|
3001
3579
|
return null;
|
|
3002
3580
|
}
|
|
@@ -3007,6 +3585,11 @@ var cli = Cli_exports.create("whop", {
|
|
|
3007
3585
|
description: "Build and manage Whop apps from your terminal.",
|
|
3008
3586
|
renderer: render,
|
|
3009
3587
|
sync: {
|
|
3588
|
+
// Shipped skill files generated by scripts/sync-spec.ts from the
|
|
3589
|
+
// spec's own tag copy (x-whop-summary + description, authored in
|
|
3590
|
+
// backend/openapi/v1/tags.yml). A matching directory name overrides
|
|
3591
|
+
// the auto-generated skill for that command group.
|
|
3592
|
+
include: ["skills/*"],
|
|
3010
3593
|
suggestions: [
|
|
3011
3594
|
"start selling on Whop",
|
|
3012
3595
|
"show my Whop business accounts",
|
|
@@ -3058,100 +3641,16 @@ cli.use(async (c2, next) => {
|
|
|
3058
3641
|
return next();
|
|
3059
3642
|
});
|
|
3060
3643
|
await registerHandwrittenGroups(cli);
|
|
3061
|
-
for (const { name, tag } of API_GROUPS) {
|
|
3062
|
-
|
|
3644
|
+
for (const { name, tag, description } of API_GROUPS) {
|
|
3645
|
+
if (name === "media") {
|
|
3646
|
+
cli.command(await buildMediaGroup(description));
|
|
3647
|
+
continue;
|
|
3648
|
+
}
|
|
3649
|
+
cli.command(name, { description, fetch: fetch2, openapi: spec(tag) });
|
|
3063
3650
|
}
|
|
3064
3651
|
var cli_default = cli;
|
|
3065
3652
|
|
|
3066
|
-
// src/api/api-structure.json
|
|
3067
|
-
var api_structure_default = [
|
|
3068
|
-
{
|
|
3069
|
-
group: "Core Resources",
|
|
3070
|
-
tags: [
|
|
3071
|
-
"Accounts",
|
|
3072
|
-
"Users",
|
|
3073
|
-
"Webhooks",
|
|
3074
|
-
"Stats"
|
|
3075
|
-
]
|
|
3076
|
-
},
|
|
3077
|
-
{
|
|
3078
|
-
group: "Money",
|
|
3079
|
-
tags: [
|
|
3080
|
-
"Ledgers",
|
|
3081
|
-
"Payouts",
|
|
3082
|
-
"Cards",
|
|
3083
|
-
"Transfers",
|
|
3084
|
-
"Deposits",
|
|
3085
|
-
"Swaps",
|
|
3086
|
-
"Payout Methods",
|
|
3087
|
-
"Verifications"
|
|
3088
|
-
]
|
|
3089
|
-
},
|
|
3090
|
-
{
|
|
3091
|
-
group: "Commerce",
|
|
3092
|
-
tags: [
|
|
3093
|
-
"Products",
|
|
3094
|
-
"Plans",
|
|
3095
|
-
"Checkout Configurations",
|
|
3096
|
-
"Referrals"
|
|
3097
|
-
]
|
|
3098
|
-
},
|
|
3099
|
-
{
|
|
3100
|
-
group: "Tracking",
|
|
3101
|
-
tags: [
|
|
3102
|
-
"People",
|
|
3103
|
-
"Events"
|
|
3104
|
-
]
|
|
3105
|
-
},
|
|
3106
|
-
{
|
|
3107
|
-
group: "Ads",
|
|
3108
|
-
tags: [
|
|
3109
|
-
"Ads",
|
|
3110
|
-
"Ad Campaigns",
|
|
3111
|
-
"Ad Groups",
|
|
3112
|
-
"Audiences"
|
|
3113
|
-
]
|
|
3114
|
-
},
|
|
3115
|
-
{
|
|
3116
|
-
group: "Media",
|
|
3117
|
-
tags: [
|
|
3118
|
-
"Media"
|
|
3119
|
-
]
|
|
3120
|
-
},
|
|
3121
|
-
{
|
|
3122
|
-
group: "Identity",
|
|
3123
|
-
tags: [
|
|
3124
|
-
"Social Accounts"
|
|
3125
|
-
]
|
|
3126
|
-
}
|
|
3127
|
-
];
|
|
3128
|
-
|
|
3129
|
-
// src/api/tag-descriptions.json
|
|
3130
|
-
var tag_descriptions_default = {
|
|
3131
|
-
Accounts: "Your Whop account",
|
|
3132
|
-
Users: "Users on your platform",
|
|
3133
|
-
Stats: "Platform-wide statistics",
|
|
3134
|
-
Ledgers: "Transaction history",
|
|
3135
|
-
Payouts: "Withdraw earnings",
|
|
3136
|
-
Cards: "Issued virtual cards",
|
|
3137
|
-
Transfers: "Move funds between accounts",
|
|
3138
|
-
Deposits: "Add funds to your balance",
|
|
3139
|
-
Swaps: "Convert between currencies",
|
|
3140
|
-
Verifications: "Identity verification status",
|
|
3141
|
-
Products: "What you're selling",
|
|
3142
|
-
Plans: "Pricing for your products",
|
|
3143
|
-
"Checkout Configurations": "Optional / advanced: customize the checkout page",
|
|
3144
|
-
Referrals: "Referral programs",
|
|
3145
|
-
Ads: "Ad creatives",
|
|
3146
|
-
"Ad Campaigns": "Top-level campaign organization",
|
|
3147
|
-
"Ad Groups": "Sets of related ads",
|
|
3148
|
-
Audiences: "Who sees your ads",
|
|
3149
|
-
"Social Accounts": "Linked social profiles"
|
|
3150
|
-
};
|
|
3151
|
-
|
|
3152
3653
|
// src/lib/grouped-help.ts
|
|
3153
|
-
var descriptions = tag_descriptions_default;
|
|
3154
|
-
var toCliName = (tag) => tag.toLowerCase().replace(/\s+/g, "-");
|
|
3155
3654
|
var SECTION_LABELS = {
|
|
3156
3655
|
"get-started": "Get Started",
|
|
3157
3656
|
auth: "Auth"
|
|
@@ -3188,14 +3687,12 @@ var handwrittenSection = (section) => ({
|
|
|
3188
3687
|
});
|
|
3189
3688
|
function buildGroupedHelp(version, description) {
|
|
3190
3689
|
const lines = [];
|
|
3191
|
-
const
|
|
3192
|
-
const
|
|
3193
|
-
label
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
}))
|
|
3198
|
-
})).filter((section) => section.commands.length > 0);
|
|
3690
|
+
const apiSections = [];
|
|
3691
|
+
for (const { name, description: description2, group } of API_GROUPS) {
|
|
3692
|
+
let section = apiSections.find((s) => s.label === group);
|
|
3693
|
+
if (!section) apiSections.push(section = { label: group, commands: [] });
|
|
3694
|
+
section.commands.push({ name, description: description2 });
|
|
3695
|
+
}
|
|
3199
3696
|
const commerceSections = apiSections.filter(
|
|
3200
3697
|
(section) => section.label.toLowerCase() === "commerce"
|
|
3201
3698
|
);
|