ckweb-cli 0.6.0 → 0.8.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 CHANGED
@@ -72,15 +72,19 @@ ckweb seo traffic "example.com"
72
72
  | `referrals dashboard` | View referral stats |
73
73
  | `referrals request-payout` | Request payout |
74
74
  | `referrals payout-history` | View payout history |
75
+ | `referrals leaderboard\|tiers\|resolve\|track\|referees` | Referral discovery, tracking, and referee lookup |
75
76
  | `payout profile` | View payout profiles |
76
77
  | `payout create-profile` | Create payout profile |
77
78
  | `payout delete-profile` | Delete payout profile |
78
79
 
80
+ Payout profile note: current `claudekit-web` payout profile endpoints are browser-session routes. Full API-key automation for payout profile and VNeID conversion flows requires a backend API-key contract first.
81
+
79
82
  ### Content
80
83
  | Command | Description |
81
84
  |---------|-------------|
82
85
  | `blog list` | List blog articles |
83
86
  | `blog get <id>` | Get article details |
87
+ | `blog regenerate-html --article <id>\|--all` | Regenerate article HTML (admin key) |
84
88
  | `releases list [--tab]` | List product releases |
85
89
 
86
90
  ### Proxy Services (YouTube, Web, SEO)
@@ -97,12 +101,13 @@ ckweb seo traffic "example.com"
97
101
  | `pricing exchange-rate` | Get USD/VND rate |
98
102
  | `pricing early-access` | Early access status |
99
103
  | `loyalty eligibility` | Check loyalty eligibility |
104
+ | `premium validate` | Validate premium access for current API key |
100
105
  | `stats users` | Platform user count |
101
106
 
102
107
  ### Other
103
108
  | Command | Description |
104
109
  |---------|-------------|
105
- | `github invite\|revoke` | GitHub repo access |
110
+ | `github validate\|invite\|revoke` | GitHub username validation and repo access |
106
111
  | `newsletter subscribe\|unsubscribe` | Newsletter management |
107
112
  | `waitlist join` | Join product waitlist |
108
113
  | `health` | Check API health |
@@ -122,9 +127,13 @@ ckweb seo traffic "example.com"
122
127
  | `admin invites` | list, create, get, delete, resend, accept, validate |
123
128
  | `admin admins` | list, get, promote, demote, permissions |
124
129
  | `admin discounts` | list, create, update, delete, sync-status, export |
125
- | `admin discount-groups` | list, get, create, update, delete |
130
+ | `admin discount-groups` | list, get, create (--generate), generate, update, delete |
126
131
  | `admin tiers` | list |
127
132
 
133
+ Admin payout note: backend payout PII/actions such as `/api/admin/payouts`, `/api/admin/payout-requests`, and `/api/admin/payout-vneid-requests` intentionally require an interactive browser admin session. The CLI documents those as unsupported by API-key auth instead of pretending they are safe automation commands.
134
+
135
+ Browser-session note: several dashboard endpoints in `claudekit-web` still use cookie session auth (`getCurrentUser()`), including user API-key management, teams, loyalty checkout, GDPR account actions, and referral payout requests. The coverage test tracks these as explicit backend-contract gaps, not working API-key CLI coverage.
136
+
128
137
  ## Authentication
129
138
 
130
139
  Two types of API keys:
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ function buildUrl(path2, params) {
211
211
  }
212
212
  function buildHeaders(apiKey, hasBody) {
213
213
  const headers = {
214
- "User-Agent": `ckweb-cli/${"0.6.0"}`
214
+ "User-Agent": `ckweb-cli/${"0.8.0"}`
215
215
  };
216
216
  if (hasBody) {
217
217
  headers["Content-Type"] = "application/json";
@@ -823,6 +823,22 @@ var PAYOUT_COLUMNS = [
823
823
  { key: "status", header: "Status", width: 12 },
824
824
  { key: "createdAt", header: "Created", width: 22 }
825
825
  ];
826
+ var REFEREE_STATUSES = ["all", "pending", "approved", "paid", "cancelled"];
827
+ var REFEREE_SORTS = ["createdAt", "amount", "status"];
828
+ var REFEREE_ORDERS = ["asc", "desc"];
829
+ function parseBoundedInteger(value, label, min, max) {
830
+ const parsed = Number(value);
831
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < min || parsed > max) {
832
+ throw new CliError(`${label} must be an integer between ${min} and ${max}`);
833
+ }
834
+ return parsed;
835
+ }
836
+ function assertOneOf(value, allowed, label) {
837
+ if (!allowed.includes(value)) {
838
+ throw new CliError(`${label} must be one of: ${allowed.join(", ")}`);
839
+ }
840
+ return value;
841
+ }
826
842
  function registerReferralsCommand(program2) {
827
843
  const referrals = program2.command("referrals").description("Referral program management");
828
844
  referrals.command("dashboard").description("View your referral dashboard").action(async function() {
@@ -853,6 +869,69 @@ function registerReferralsCommand(program2) {
853
869
  handleError(err);
854
870
  }
855
871
  });
872
+ referrals.command("leaderboard").description("View public referral leaderboard").action(async function() {
873
+ try {
874
+ const data = await fetchApi("GET", "/referrals/leaderboard", { noAuth: true });
875
+ formatOutput(data, getOutputOpts(this));
876
+ } catch (err) {
877
+ handleError(err);
878
+ }
879
+ });
880
+ referrals.command("tiers").description("List public referral tiers").action(async function() {
881
+ try {
882
+ const data = await fetchApi("GET", "/referrals/tiers", { noAuth: true });
883
+ formatOutput(data, getOutputOpts(this));
884
+ } catch (err) {
885
+ handleError(err);
886
+ }
887
+ });
888
+ referrals.command("resolve <code>").description("Resolve a referral code").action(async function(code) {
889
+ try {
890
+ const data = await fetchApi("GET", "/referrals/resolve", {
891
+ noAuth: true,
892
+ params: { code }
893
+ });
894
+ formatOutput(data, getOutputOpts(this));
895
+ } catch (err) {
896
+ handleError(err);
897
+ }
898
+ });
899
+ referrals.command("track <code>").description("Track a referral click").action(async function(code) {
900
+ try {
901
+ const data = await fetchApi("GET", "/referrals/track", {
902
+ noAuth: true,
903
+ params: { code }
904
+ });
905
+ formatOutput(data, getOutputOpts(this));
906
+ } catch (err) {
907
+ handleError(err);
908
+ }
909
+ });
910
+ referrals.command("referees").description("List referees attributed to your referral account").option("--status <status>", "Filter by commission status", "all").option("--sort <field>", "Sort by createdAt, amount, or status", "createdAt").option("--order <direction>", "Sort direction: asc or desc", "desc").option("--order-ref <id>", "Filter by 8-character order display ID").option("--limit <n>", "Page size", "50").option("--offset <n>", "Offset for pagination", "0").action(async function() {
911
+ try {
912
+ ensureAuth();
913
+ const opts = this.opts();
914
+ const limit = parseBoundedInteger(opts.limit, "--limit", 1, 100);
915
+ const offset = parseBoundedInteger(opts.offset, "--offset", 0, 5e3);
916
+ const params = {
917
+ status: assertOneOf(opts.status, REFEREE_STATUSES, "--status"),
918
+ sort: assertOneOf(opts.sort, REFEREE_SORTS, "--sort"),
919
+ order: assertOneOf(opts.order, REFEREE_ORDERS, "--order"),
920
+ limit: String(limit),
921
+ offset: String(offset)
922
+ };
923
+ if (opts.orderRef) {
924
+ if (!/^[0-9a-fA-F]{8}$/.test(opts.orderRef)) {
925
+ throw new CliError("--order-ref must be an 8-character order display ID");
926
+ }
927
+ params["orderRef"] = opts.orderRef.toLowerCase();
928
+ }
929
+ const data = await fetchApi("GET", "/referrals/referees", { params });
930
+ formatOutput(data, getOutputOpts(this));
931
+ } catch (err) {
932
+ handleError(err);
933
+ }
934
+ });
856
935
  }
857
936
 
858
937
  // src/commands/blog.ts
@@ -881,6 +960,26 @@ function registerBlogCommand(program2) {
881
960
  handleError(err);
882
961
  }
883
962
  });
963
+ blog.command("regenerate-html").description("Regenerate stored HTML for missing article content").option("--article <id>", "Regenerate one article by ID").option("--all", "Regenerate all articles missing HTML").action(async function() {
964
+ try {
965
+ ensureAdminAuth();
966
+ const opts = this.opts();
967
+ if (!opts.article && !opts.all) {
968
+ throw new CliError("Provide --article <id> or explicit --all.");
969
+ }
970
+ if (opts.article && opts.all) {
971
+ throw new CliError("Use either --article <id> or --all, not both.");
972
+ }
973
+ const body = opts.article ? { articleId: opts.article } : {};
974
+ const data = await fetchApi("POST", "/blog/regenerate-html", {
975
+ adminAuth: true,
976
+ body
977
+ });
978
+ formatOutput(data, getOutputOpts(this));
979
+ } catch (err) {
980
+ handleError(err);
981
+ }
982
+ });
884
983
  }
885
984
 
886
985
  // src/commands/health.ts
@@ -987,6 +1086,17 @@ function registerCheckoutCommand(program2) {
987
1086
  import * as readline3 from "readline/promises";
988
1087
  function registerGithubCommand(program2) {
989
1088
  const github = program2.command("github").description("GitHub repository access");
1089
+ github.command("validate <username>").description("Validate a GitHub username").action(async function(username) {
1090
+ try {
1091
+ const data = await fetchApi("GET", "/github/validate", {
1092
+ noAuth: true,
1093
+ params: { username }
1094
+ });
1095
+ formatOutput(data, getOutputOpts(this));
1096
+ } catch (err) {
1097
+ handleError(err);
1098
+ }
1099
+ });
990
1100
  github.command("invite").description("Grant GitHub repository access via license").requiredOption("--license <id>", "License ID").action(async function() {
991
1101
  try {
992
1102
  ensureAuth();
@@ -1154,6 +1264,20 @@ function registerPricingCommand(program2) {
1154
1264
  });
1155
1265
  }
1156
1266
 
1267
+ // src/commands/premium.ts
1268
+ function registerPremiumCommand(program2) {
1269
+ const premium = program2.command("premium").description("Premium license utilities");
1270
+ premium.command("validate").description("Validate whether the current API key has premium access").action(async function() {
1271
+ try {
1272
+ ensureAuth();
1273
+ const data = await fetchApi("GET", "/premium/validate");
1274
+ formatOutput(data, getOutputOpts(this));
1275
+ } catch (err) {
1276
+ handleError(err);
1277
+ }
1278
+ });
1279
+ }
1280
+
1157
1281
  // src/commands/refunds.ts
1158
1282
  import * as readline5 from "readline/promises";
1159
1283
  function registerRefundsCommand(program2) {
@@ -2971,6 +3095,14 @@ var GROUP_COLUMNS = [
2971
3095
  { key: "codesUsed", header: "Used", width: 8 },
2972
3096
  { key: "createdAt", header: "Created", width: 22 }
2973
3097
  ];
3098
+ function parseGenerateCount(value, optionName = "--count") {
3099
+ if (value === void 0) return void 0;
3100
+ const count = Number(value);
3101
+ if (!Number.isInteger(count) || count < 1 || count > 100) {
3102
+ throw new CliError(`${optionName} must be an integer from 1 to 100`);
3103
+ }
3104
+ return count;
3105
+ }
2974
3106
  function buildGroupBody(opts) {
2975
3107
  const body = {};
2976
3108
  if (opts.name !== void 0) body["name"] = opts.name;
@@ -3011,6 +3143,12 @@ function buildGroupBody(opts) {
3011
3143
  if (opts.ends !== void 0) body["endsAt"] = opts.ends;
3012
3144
  return body;
3013
3145
  }
3146
+ async function generateDiscountGroupCodes(id, count) {
3147
+ return fetchApi("POST", `/admin/discount-groups/${id}/codes`, {
3148
+ adminAuth: true,
3149
+ body: { count }
3150
+ });
3151
+ }
3014
3152
  function registerAdminDiscountGroupsCommand(admin) {
3015
3153
  const groups = admin.command("discount-groups").description("Discount group management");
3016
3154
  groups.command("list").description("List discount groups").option("--query <text>", "Search by name or prefix").option("--page <n>", "Page number", "1").option("--limit <n>", "Page size", "50").action(async function() {
@@ -3038,15 +3176,37 @@ function registerAdminDiscountGroupsCommand(admin) {
3038
3176
  handleError(err);
3039
3177
  }
3040
3178
  });
3041
- groups.command("create").description("Create a discount group template").requiredOption("--name <name>", "Group name").requiredOption("--prefix <prefix>", "Code prefix (3-20 chars, uppercased)").requiredOption("--type <percentage|fixed>", "Discount type").option("--amount <n>", "Amount in cents (fixed type)").option("--basis-points <n>", "Basis points (percentage type, 0..10000)").option("--duration <once|repeating|forever>", "Discount duration").option("--duration-in-months <n>", "Months for repeating duration (1..24)").option("--max-redemptions-per-code <n>", "Per-code redemption cap").option("--starts <date>", "Start date (ISO 8601)").option("--ends <date>", "End date (ISO 8601)").action(async function() {
3179
+ groups.command("create").description("Create a discount group template").requiredOption("--name <name>", "Group name").requiredOption("--prefix <prefix>", "Code prefix (3-20 chars, uppercased)").requiredOption("--type <percentage|fixed>", "Discount type").option("--amount <n>", "Amount in cents (fixed type)").option("--basis-points <n>", "Basis points (percentage type, 0..10000)").option("--duration <once|repeating|forever>", "Discount duration").option("--duration-in-months <n>", "Months for repeating duration (1..24)").option("--max-redemptions-per-code <n>", "Per-code redemption cap").option("--starts <date>", "Start date (ISO 8601)").option("--ends <date>", "End date (ISO 8601)").option("--generate <n>", "Generate N discount codes after creating the group (1..100)").action(async function() {
3042
3180
  try {
3043
3181
  ensureAdminAuth();
3044
- const body = buildGroupBody(this.opts());
3182
+ const opts = this.opts();
3183
+ const generateCount = parseGenerateCount(opts.generate, "--generate");
3184
+ const body = buildGroupBody(opts);
3045
3185
  const data = await fetchApi("POST", "/admin/discount-groups", {
3046
3186
  adminAuth: true,
3047
3187
  body
3048
3188
  });
3049
3189
  printSuccess(`Discount group created: ${data?.group?.id ?? ""}`);
3190
+ if (generateCount !== void 0) {
3191
+ const id = data?.group?.id;
3192
+ if (!id) throw new CliError("Backend did not return a discount group ID for code generation.");
3193
+ const generated = await generateDiscountGroupCodes(id, generateCount);
3194
+ printSuccess(`Generated ${generated.generated} discount code(s).`);
3195
+ formatOutput({ ...data, ...generated }, getOutputOpts(this));
3196
+ return;
3197
+ }
3198
+ formatOutput(data, getOutputOpts(this));
3199
+ } catch (err) {
3200
+ handleError(err);
3201
+ }
3202
+ });
3203
+ groups.command("generate <id>").description("Generate discount codes for a group").requiredOption("--count <n>", "Number of codes to generate (1..100)").action(async function(id) {
3204
+ try {
3205
+ ensureAdminAuth();
3206
+ validateId(id, "discount group ID");
3207
+ const count = parseGenerateCount(this.opts().count);
3208
+ const data = await generateDiscountGroupCodes(id, count);
3209
+ printSuccess(`Generated ${data.generated} discount code(s).`);
3050
3210
  formatOutput(data, getOutputOpts(this));
3051
3211
  } catch (err) {
3052
3212
  handleError(err);
@@ -3133,7 +3293,7 @@ function registerAdminCommand(program2) {
3133
3293
  // src/index.ts
3134
3294
  loadDotenvFiles();
3135
3295
  var program = new Command();
3136
- program.name("ckweb").description("CLI for interacting with ClaudeKit.cc API").version("0.6.0");
3296
+ program.name("ckweb").description("CLI for interacting with ClaudeKit.cc API").version("0.8.0");
3137
3297
  program.option("--json", "Output as JSON").option("--table", "Output as table").option("--quiet", "Minimal output");
3138
3298
  registerAuthCommand(program);
3139
3299
  registerHealthCommand(program);
@@ -3147,6 +3307,7 @@ registerLoyaltyCommand(program);
3147
3307
  registerNewsletterCommand(program);
3148
3308
  registerPayoutCommand(program);
3149
3309
  registerPricingCommand(program);
3310
+ registerPremiumCommand(program);
3150
3311
  registerRefundsCommand(program);
3151
3312
  registerReleasesCommand(program);
3152
3313
  registerStatsCommand(program);