ckweb-cli 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/README.md +193 -0
- package/dist/index.js +2345 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/env-resolver.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
var ENV_USER_KEY = "CKWEB_API_KEY";
|
|
7
|
+
var ENV_ADMIN_KEY = "CKWEB_ADMIN_API_KEY";
|
|
8
|
+
var ENV_BASE_URL = "CKWEB_API_URL";
|
|
9
|
+
var DOTENV_VALUES = {};
|
|
10
|
+
var DOTENV_FILES = [".env.local", ".env"];
|
|
11
|
+
function parseDotenv(src) {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const rawLine of src.split(/\r?\n/)) {
|
|
14
|
+
const line = rawLine.trim();
|
|
15
|
+
if (!line || line.startsWith("#")) continue;
|
|
16
|
+
const eq = line.indexOf("=");
|
|
17
|
+
if (eq <= 0) continue;
|
|
18
|
+
const key = line.slice(0, eq).trim();
|
|
19
|
+
let value = line.slice(eq + 1).trim();
|
|
20
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
21
|
+
value = value.slice(1, -1);
|
|
22
|
+
} else {
|
|
23
|
+
const hash = value.indexOf(" #");
|
|
24
|
+
if (hash >= 0) value = value.slice(0, hash).trim();
|
|
25
|
+
}
|
|
26
|
+
if (key) out[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function loadDotenvFiles(cwd = process.cwd()) {
|
|
31
|
+
for (const file of DOTENV_FILES) {
|
|
32
|
+
const full = path.join(cwd, file);
|
|
33
|
+
if (!fs.existsSync(full)) continue;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = parseDotenv(fs.readFileSync(full, "utf8"));
|
|
36
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
37
|
+
if (!(k in DOTENV_VALUES)) DOTENV_VALUES[k] = v;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveOsEnv(name) {
|
|
44
|
+
const v = process.env[name];
|
|
45
|
+
return v !== void 0 && v !== "" ? v : void 0;
|
|
46
|
+
}
|
|
47
|
+
function resolveDotenv(name) {
|
|
48
|
+
const v = DOTENV_VALUES[name];
|
|
49
|
+
return v !== void 0 && v !== "" ? v : void 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/index.ts
|
|
53
|
+
import { Command } from "commander";
|
|
54
|
+
|
|
55
|
+
// src/lib/errors.ts
|
|
56
|
+
import chalk from "chalk";
|
|
57
|
+
var CliError = class extends Error {
|
|
58
|
+
constructor(message, exitCode = 1) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.exitCode = exitCode;
|
|
61
|
+
this.name = "CliError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var AuthError = class extends CliError {
|
|
65
|
+
constructor(message = "Authentication required. Run: ckweb auth login") {
|
|
66
|
+
super(message, 1);
|
|
67
|
+
this.name = "AuthError";
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var ApiError = class extends CliError {
|
|
71
|
+
constructor(message, statusCode, code) {
|
|
72
|
+
super(message, 1);
|
|
73
|
+
this.statusCode = statusCode;
|
|
74
|
+
this.code = code;
|
|
75
|
+
this.name = "ApiError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var NetworkError = class extends CliError {
|
|
79
|
+
constructor(message = "Network error. Check your connection.") {
|
|
80
|
+
super(message, 1);
|
|
81
|
+
this.name = "NetworkError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function validateId(id, label = "ID") {
|
|
85
|
+
if (!/^[\w-]+$/.test(id)) {
|
|
86
|
+
throw new CliError(`Invalid ${label}: "${id}"`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function handleError(err) {
|
|
90
|
+
if (err instanceof AuthError) {
|
|
91
|
+
console.error(chalk.yellow(`Auth: ${err.message}`));
|
|
92
|
+
process.exit(err.exitCode);
|
|
93
|
+
}
|
|
94
|
+
if (err instanceof ApiError) {
|
|
95
|
+
console.error(chalk.red(`API Error (${err.statusCode}): ${err.message}`));
|
|
96
|
+
if (err.code) console.error(chalk.dim(`Code: ${err.code}`));
|
|
97
|
+
process.exit(err.exitCode);
|
|
98
|
+
}
|
|
99
|
+
if (err instanceof NetworkError) {
|
|
100
|
+
console.error(chalk.red(`Network: ${err.message}`));
|
|
101
|
+
process.exit(err.exitCode);
|
|
102
|
+
}
|
|
103
|
+
if (err instanceof CliError) {
|
|
104
|
+
console.error(chalk.red(err.message));
|
|
105
|
+
process.exit(err.exitCode);
|
|
106
|
+
}
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/commands/auth.ts
|
|
113
|
+
import chalk3 from "chalk";
|
|
114
|
+
import * as readline from "readline/promises";
|
|
115
|
+
|
|
116
|
+
// src/lib/config.ts
|
|
117
|
+
import Conf from "conf";
|
|
118
|
+
var DEFAULT_BASE_URL = "https://claudekit.cc/api";
|
|
119
|
+
var USER_KEY_PATTERN = /^ck_live_[a-zA-Z0-9]{32,}$/;
|
|
120
|
+
var ADMIN_KEY_PATTERN = /^ck_admin_[a-zA-Z0-9]{32,}$/;
|
|
121
|
+
function resolveKey(envName, pattern, stored) {
|
|
122
|
+
const os = resolveOsEnv(envName);
|
|
123
|
+
if (os && pattern.test(os)) return os;
|
|
124
|
+
const cfg = stored();
|
|
125
|
+
if (cfg) return cfg;
|
|
126
|
+
const dot = resolveDotenv(envName);
|
|
127
|
+
if (dot && pattern.test(dot)) return dot;
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
var config = new Conf({
|
|
131
|
+
projectName: "ckweb-cli",
|
|
132
|
+
schema: {
|
|
133
|
+
apiKey: { type: "string" },
|
|
134
|
+
adminApiKey: { type: "string" },
|
|
135
|
+
baseUrl: { type: "string" }
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
function getApiKey() {
|
|
139
|
+
return resolveKey(ENV_USER_KEY, USER_KEY_PATTERN, () => config.get("apiKey"));
|
|
140
|
+
}
|
|
141
|
+
function setApiKey(key) {
|
|
142
|
+
if (!USER_KEY_PATTERN.test(key)) {
|
|
143
|
+
throw new Error("Invalid API key format. Expected: ck_live_<32+ chars>");
|
|
144
|
+
}
|
|
145
|
+
config.set("apiKey", key);
|
|
146
|
+
}
|
|
147
|
+
function clearApiKey() {
|
|
148
|
+
config.delete("apiKey");
|
|
149
|
+
}
|
|
150
|
+
function getAdminApiKey() {
|
|
151
|
+
return resolveKey(ENV_ADMIN_KEY, ADMIN_KEY_PATTERN, () => config.get("adminApiKey"));
|
|
152
|
+
}
|
|
153
|
+
function setAdminApiKey(key) {
|
|
154
|
+
if (!ADMIN_KEY_PATTERN.test(key)) {
|
|
155
|
+
throw new Error("Invalid admin API key format. Expected: ck_admin_<32+ chars>");
|
|
156
|
+
}
|
|
157
|
+
config.set("adminApiKey", key);
|
|
158
|
+
}
|
|
159
|
+
function clearAdminApiKey() {
|
|
160
|
+
config.delete("adminApiKey");
|
|
161
|
+
}
|
|
162
|
+
function getBaseUrl() {
|
|
163
|
+
return resolveOsEnv(ENV_BASE_URL) || config.get("baseUrl") || resolveDotenv(ENV_BASE_URL) || DEFAULT_BASE_URL;
|
|
164
|
+
}
|
|
165
|
+
function maskApiKey(key) {
|
|
166
|
+
if (key.length < 12) return "****";
|
|
167
|
+
return `${key.slice(0, 8)}****${key.slice(-4)}`;
|
|
168
|
+
}
|
|
169
|
+
function getConfigPath() {
|
|
170
|
+
return config.path;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/lib/api-client.ts
|
|
174
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
175
|
+
async function fetchApi(method, path2, options = {}) {
|
|
176
|
+
const { body, params, timeout = DEFAULT_TIMEOUT, noAuth = false, adminAuth = false } = options;
|
|
177
|
+
let apiKey;
|
|
178
|
+
if (!noAuth) {
|
|
179
|
+
if (adminAuth) {
|
|
180
|
+
apiKey = getAdminApiKey();
|
|
181
|
+
if (!apiKey) {
|
|
182
|
+
throw new AuthError("Admin authentication required. Run: ckweb auth login --admin");
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
apiKey = getApiKey();
|
|
186
|
+
if (!apiKey) {
|
|
187
|
+
throw new AuthError();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const baseUrl = getBaseUrl().replace(/\/?$/, "/");
|
|
192
|
+
const url = new URL(path2.replace(/^\//, ""), baseUrl);
|
|
193
|
+
if (params) {
|
|
194
|
+
for (const [key, value] of Object.entries(params)) {
|
|
195
|
+
url.searchParams.set(key, value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const headers = {
|
|
199
|
+
"Content-Type": "application/json",
|
|
200
|
+
"User-Agent": "ckweb-cli/0.1.0"
|
|
201
|
+
};
|
|
202
|
+
if (apiKey) {
|
|
203
|
+
headers["x-api-key"] = apiKey;
|
|
204
|
+
}
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch(url.toString(), {
|
|
209
|
+
method,
|
|
210
|
+
headers,
|
|
211
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
212
|
+
signal: controller.signal
|
|
213
|
+
});
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
const contentType = response.headers.get("content-type") || "";
|
|
216
|
+
let data;
|
|
217
|
+
if (contentType.includes("application/json")) {
|
|
218
|
+
data = await response.json();
|
|
219
|
+
} else {
|
|
220
|
+
data = await response.text();
|
|
221
|
+
}
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
const errorBody = data;
|
|
224
|
+
const message = errorBody?.error || errorBody?.message || `HTTP ${response.status}`;
|
|
225
|
+
const code = errorBody?.code;
|
|
226
|
+
if (response.status === 401 || response.status === 403) {
|
|
227
|
+
throw new AuthError(message);
|
|
228
|
+
}
|
|
229
|
+
throw new ApiError(message, response.status, code);
|
|
230
|
+
}
|
|
231
|
+
return data;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
if (err instanceof AuthError || err instanceof ApiError) {
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
238
|
+
throw new NetworkError(`Request timed out after ${timeout / 1e3}s`);
|
|
239
|
+
}
|
|
240
|
+
if (err instanceof TypeError) {
|
|
241
|
+
throw new NetworkError();
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/lib/output.ts
|
|
248
|
+
import chalk2 from "chalk";
|
|
249
|
+
function getOutputOpts(cmd) {
|
|
250
|
+
const opts = cmd.optsWithGlobals();
|
|
251
|
+
return { json: opts.json, table: opts.table, quiet: opts.quiet };
|
|
252
|
+
}
|
|
253
|
+
function formatOutput(data, options, tableColumns) {
|
|
254
|
+
if (options.quiet) {
|
|
255
|
+
printQuiet(data);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (options.json) {
|
|
259
|
+
printJson(data);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(data) && tableColumns) {
|
|
263
|
+
printTable(data, tableColumns);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (options.table && data && typeof data === "object" && !Array.isArray(data)) {
|
|
267
|
+
const obj = data;
|
|
268
|
+
const rows = Object.entries(obj).map(([key, value]) => ({
|
|
269
|
+
key,
|
|
270
|
+
value: typeof value === "object" ? JSON.stringify(value) : String(value ?? "\u2014")
|
|
271
|
+
}));
|
|
272
|
+
printTable(rows, [
|
|
273
|
+
{ key: "key", header: "Field", width: 20 },
|
|
274
|
+
{ key: "value", header: "Value", width: 50 }
|
|
275
|
+
]);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
printJson(data);
|
|
279
|
+
}
|
|
280
|
+
function printJson(data) {
|
|
281
|
+
console.log(JSON.stringify(data, null, 2));
|
|
282
|
+
}
|
|
283
|
+
function printTable(rows, columns) {
|
|
284
|
+
if (rows.length === 0) {
|
|
285
|
+
console.log(chalk2.dim("No results."));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const widths = columns.map((col) => {
|
|
289
|
+
const headerLen = col.header.length;
|
|
290
|
+
const maxDataLen = rows.reduce((max, row) => {
|
|
291
|
+
const val = String(row[col.key] ?? "");
|
|
292
|
+
return Math.max(max, val.length);
|
|
293
|
+
}, 0);
|
|
294
|
+
return col.width || Math.min(Math.max(headerLen, maxDataLen) + 2, 50);
|
|
295
|
+
});
|
|
296
|
+
const headerLine = columns.map((col, i) => col.header.padEnd(widths[i])).join(" ");
|
|
297
|
+
console.log(chalk2.bold(headerLine));
|
|
298
|
+
console.log(chalk2.dim("\u2500".repeat(headerLine.length)));
|
|
299
|
+
for (const row of rows) {
|
|
300
|
+
const line = columns.map((col, i) => {
|
|
301
|
+
const val = String(row[col.key] ?? "\u2014");
|
|
302
|
+
return val.length > widths[i] ? val.slice(0, widths[i] - 1) + "\u2026" : val.padEnd(widths[i]);
|
|
303
|
+
}).join(" ");
|
|
304
|
+
console.log(line);
|
|
305
|
+
}
|
|
306
|
+
console.log(chalk2.dim(`
|
|
307
|
+
${rows.length} result(s)`));
|
|
308
|
+
}
|
|
309
|
+
function printQuiet(data) {
|
|
310
|
+
if (data === null || data === void 0) return;
|
|
311
|
+
if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") {
|
|
312
|
+
console.log(String(data));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(data)) {
|
|
316
|
+
console.log(String(data.length));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (typeof data === "object") {
|
|
320
|
+
const obj = data;
|
|
321
|
+
const firstKey = Object.keys(obj)[0];
|
|
322
|
+
if (firstKey) {
|
|
323
|
+
console.log(String(obj[firstKey]));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function printSuccess(message) {
|
|
328
|
+
console.log(chalk2.green(`\u2713 ${message}`));
|
|
329
|
+
}
|
|
330
|
+
function printInfo(message) {
|
|
331
|
+
console.log(chalk2.blue(`\u2139 ${message}`));
|
|
332
|
+
}
|
|
333
|
+
function printWarn(message) {
|
|
334
|
+
console.log(chalk2.yellow(`\u26A0 ${message}`));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/commands/auth.ts
|
|
338
|
+
function ensureAuth() {
|
|
339
|
+
const key = getApiKey();
|
|
340
|
+
if (!key) throw new AuthError();
|
|
341
|
+
return key;
|
|
342
|
+
}
|
|
343
|
+
function ensureAdminAuth() {
|
|
344
|
+
const key = getAdminApiKey();
|
|
345
|
+
if (!key) throw new AuthError("Admin authentication required. Run: ckweb auth login --admin");
|
|
346
|
+
return key;
|
|
347
|
+
}
|
|
348
|
+
async function promptApiKey(isAdmin) {
|
|
349
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
350
|
+
try {
|
|
351
|
+
const prefix = isAdmin ? "ck_admin_" : "ck_live_";
|
|
352
|
+
return (await rl.question(chalk3.cyan(`Enter your API key (${prefix}...): `))).trim();
|
|
353
|
+
} finally {
|
|
354
|
+
rl.close();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function registerAuthCommand(program2) {
|
|
358
|
+
const auth = program2.command("auth").description("Manage authentication");
|
|
359
|
+
auth.command("login").description("Authenticate with your ClaudeKit API key").option("--admin", "Login with admin API key").action(async function() {
|
|
360
|
+
try {
|
|
361
|
+
const isAdmin = this.opts().admin === true;
|
|
362
|
+
const key = await promptApiKey(isAdmin);
|
|
363
|
+
if (!key) {
|
|
364
|
+
printWarn("No API key provided.");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
printInfo("Validating API key...");
|
|
368
|
+
try {
|
|
369
|
+
if (isAdmin) {
|
|
370
|
+
setAdminApiKey(key);
|
|
371
|
+
} else {
|
|
372
|
+
setApiKey(key);
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
const expected = isAdmin ? "ck_admin_<32+ chars>" : "ck_live_<32+ chars>";
|
|
376
|
+
printWarn(`Invalid key format. Expected: ${expected}`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const result = await fetchApi("POST", "/keys/validate", {
|
|
381
|
+
adminAuth: isAdmin
|
|
382
|
+
});
|
|
383
|
+
if (result.valid) {
|
|
384
|
+
printSuccess(`Authenticated! Key: ${maskApiKey(key)}`);
|
|
385
|
+
printInfo(`Config saved to: ${getConfigPath()}`);
|
|
386
|
+
} else {
|
|
387
|
+
if (isAdmin) clearAdminApiKey();
|
|
388
|
+
else clearApiKey();
|
|
389
|
+
printWarn(result.error || "Invalid API key.");
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
if (isAdmin) clearAdminApiKey();
|
|
393
|
+
else clearApiKey();
|
|
394
|
+
throw new AuthError("API key validation failed. Check your key and try again.");
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
handleError(err);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
auth.command("validate").description("Validate stored API key").option("--admin", "Validate admin API key").action(async function() {
|
|
401
|
+
try {
|
|
402
|
+
const isAdmin = this.opts().admin === true;
|
|
403
|
+
const key = isAdmin ? ensureAdminAuth() : ensureAuth();
|
|
404
|
+
printInfo(`Validating ${isAdmin ? "admin " : ""}key: ${maskApiKey(key)}`);
|
|
405
|
+
const result = await fetchApi("POST", "/keys/validate", {
|
|
406
|
+
adminAuth: isAdmin
|
|
407
|
+
});
|
|
408
|
+
if (result.valid) {
|
|
409
|
+
printSuccess("API key is valid.");
|
|
410
|
+
if (result.userId) console.log(chalk3.dim(`User ID: ${result.userId}`));
|
|
411
|
+
if (result.rateLimit) console.log(chalk3.dim(`Rate limit: ${result.rateLimit} req/hour`));
|
|
412
|
+
} else {
|
|
413
|
+
printWarn(result.error || "API key is invalid or expired.");
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
handleError(err);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
auth.command("logout").description("Remove stored API key").option("--admin", "Remove admin API key").option("--all", "Remove all stored keys").action(function() {
|
|
420
|
+
try {
|
|
421
|
+
const opts = this.opts();
|
|
422
|
+
if (opts.all) {
|
|
423
|
+
clearApiKey();
|
|
424
|
+
clearAdminApiKey();
|
|
425
|
+
printSuccess("All API keys removed.");
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (opts.admin) {
|
|
429
|
+
if (!getAdminApiKey()) {
|
|
430
|
+
printInfo("No admin API key stored.");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
clearAdminApiKey();
|
|
434
|
+
printSuccess("Admin API key removed.");
|
|
435
|
+
} else {
|
|
436
|
+
if (!getApiKey()) {
|
|
437
|
+
printInfo("No API key stored.");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
clearApiKey();
|
|
441
|
+
printSuccess("API key removed.");
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
handleError(err);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
auth.command("status").description("Show authentication status").action(() => {
|
|
448
|
+
try {
|
|
449
|
+
const userKey = getApiKey();
|
|
450
|
+
const adminKey = getAdminApiKey();
|
|
451
|
+
const userSource = resolveOsEnv(ENV_USER_KEY) ? `env:${ENV_USER_KEY}` : resolveDotenv(ENV_USER_KEY) ? `.env` : userKey ? "config" : null;
|
|
452
|
+
const adminSource = resolveOsEnv(ENV_ADMIN_KEY) ? `env:${ENV_ADMIN_KEY}` : resolveDotenv(ENV_ADMIN_KEY) ? `.env` : adminKey ? "config" : null;
|
|
453
|
+
console.log(chalk3.bold("Authentication Status"));
|
|
454
|
+
console.log(
|
|
455
|
+
` User key: ${userKey ? chalk3.green(maskApiKey(userKey)) : chalk3.dim("not set")}` + (userSource ? chalk3.dim(` (from ${userSource})`) : "")
|
|
456
|
+
);
|
|
457
|
+
console.log(
|
|
458
|
+
` Admin key: ${adminKey ? chalk3.green(maskApiKey(adminKey)) : chalk3.dim("not set")}` + (adminSource ? chalk3.dim(` (from ${adminSource})`) : "")
|
|
459
|
+
);
|
|
460
|
+
console.log(chalk3.dim(` Config: ${getConfigPath()}`));
|
|
461
|
+
console.log(chalk3.dim(` Resolution: OS env > stored config > .env.*`));
|
|
462
|
+
} catch (err) {
|
|
463
|
+
handleError(err);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/commands/youtube.ts
|
|
469
|
+
var PROXY_BASE = "/proxy/vidcap/v1/youtube";
|
|
470
|
+
var SEARCH_COLUMNS = [
|
|
471
|
+
{ key: "title", header: "Title", width: 40 },
|
|
472
|
+
{ key: "channel", header: "Channel", width: 20 },
|
|
473
|
+
{ key: "videoId", header: "Video ID", width: 14 }
|
|
474
|
+
];
|
|
475
|
+
var COMMENT_COLUMNS = [
|
|
476
|
+
{ key: "author", header: "Author", width: 20 },
|
|
477
|
+
{ key: "text", header: "Comment", width: 50 },
|
|
478
|
+
{ key: "likes", header: "Likes", width: 8 }
|
|
479
|
+
];
|
|
480
|
+
async function youtubeGet(endpoint, params) {
|
|
481
|
+
return fetchApi("GET", `${PROXY_BASE}/${endpoint}`, { params });
|
|
482
|
+
}
|
|
483
|
+
function registerYoutubeCommand(program2) {
|
|
484
|
+
const yt = program2.command("youtube").alias("yt").description("YouTube video tools (via VidCap)");
|
|
485
|
+
yt.command("info").argument("<url>", "YouTube video URL").description("Get video metadata").action(async function(url) {
|
|
486
|
+
try {
|
|
487
|
+
ensureAuth();
|
|
488
|
+
const data = await youtubeGet("info", { url });
|
|
489
|
+
formatOutput(data, getOutputOpts(this));
|
|
490
|
+
} catch (err) {
|
|
491
|
+
handleError(err);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
yt.command("caption").argument("<url>", "YouTube video URL").description("Get video transcript/captions").option("-l, --locale <locale>", "Caption language", "en").option("-f, --format <format>", "Output format (json3/vtt/srt)", "vtt").action(async function(url) {
|
|
495
|
+
try {
|
|
496
|
+
ensureAuth();
|
|
497
|
+
const opts = this.opts();
|
|
498
|
+
const data = await youtubeGet("caption", {
|
|
499
|
+
url,
|
|
500
|
+
locale: opts.locale,
|
|
501
|
+
ext: opts.format
|
|
502
|
+
});
|
|
503
|
+
formatOutput(data, getOutputOpts(this));
|
|
504
|
+
} catch (err) {
|
|
505
|
+
handleError(err);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
yt.command("summary").argument("<url>", "YouTube video URL").description("Get AI-generated video summary").option("-l, --locale <locale>", "Summary language", "en").action(async function(url) {
|
|
509
|
+
try {
|
|
510
|
+
ensureAuth();
|
|
511
|
+
const opts = this.opts();
|
|
512
|
+
const data = await youtubeGet("summary", { url, locale: opts.locale });
|
|
513
|
+
formatOutput(data, getOutputOpts(this));
|
|
514
|
+
} catch (err) {
|
|
515
|
+
handleError(err);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
yt.command("screenshot").argument("<url>", "YouTube video URL").description("Get video screenshot at timestamp").option("-t, --time <seconds>", "Timestamp in seconds", "0").action(async function(url) {
|
|
519
|
+
try {
|
|
520
|
+
ensureAuth();
|
|
521
|
+
const opts = this.opts();
|
|
522
|
+
const data = await youtubeGet("screenshot", { url, second: opts.time });
|
|
523
|
+
formatOutput(data, getOutputOpts(this));
|
|
524
|
+
} catch (err) {
|
|
525
|
+
handleError(err);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
yt.command("search").argument("<query>", "Search query").description("Search YouTube videos").option("-n, --max-results <count>", "Max results", "10").option("--order <order>", "Sort order (relevance/date/rating/viewCount)", "relevance").action(async function(query) {
|
|
529
|
+
try {
|
|
530
|
+
ensureAuth();
|
|
531
|
+
const opts = this.opts();
|
|
532
|
+
const data = await youtubeGet("search", {
|
|
533
|
+
query,
|
|
534
|
+
maxResults: opts.maxResults,
|
|
535
|
+
order: opts.order
|
|
536
|
+
});
|
|
537
|
+
formatOutput(data, getOutputOpts(this), SEARCH_COLUMNS);
|
|
538
|
+
} catch (err) {
|
|
539
|
+
handleError(err);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
yt.command("comments").argument("<url>", "YouTube video URL").description("Get video comments").option("--order <order>", "Sort order (time/relevance)", "relevance").option("--replies", "Include replies", false).action(async function(url) {
|
|
543
|
+
try {
|
|
544
|
+
ensureAuth();
|
|
545
|
+
const opts = this.opts();
|
|
546
|
+
const data = await youtubeGet("comments", {
|
|
547
|
+
url,
|
|
548
|
+
order: opts.order,
|
|
549
|
+
includeReplies: String(opts.replies)
|
|
550
|
+
});
|
|
551
|
+
formatOutput(data, getOutputOpts(this), COMMENT_COLUMNS);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
handleError(err);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/commands/web.ts
|
|
559
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
560
|
+
var PROXY_BASE2 = "/proxy/reviewweb/v1";
|
|
561
|
+
async function webPost(endpoint, body) {
|
|
562
|
+
return fetchApi("POST", `${PROXY_BASE2}/${endpoint}`, { body });
|
|
563
|
+
}
|
|
564
|
+
function registerWebCommand(program2) {
|
|
565
|
+
const web = program2.command("web").description("Website analysis tools (via ReviewWeb)");
|
|
566
|
+
web.command("review").argument("<url>", "Website URL to review").description("Comprehensive website analysis").action(async function(url) {
|
|
567
|
+
try {
|
|
568
|
+
ensureAuth();
|
|
569
|
+
const data = await webPost("review", { url });
|
|
570
|
+
formatOutput(data, getOutputOpts(this));
|
|
571
|
+
} catch (err) {
|
|
572
|
+
handleError(err);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
web.command("scrape").argument("<url>", "URL to scrape").description("Extract HTML content from URL").action(async function(url) {
|
|
576
|
+
try {
|
|
577
|
+
ensureAuth();
|
|
578
|
+
const data = await webPost("scrape", { url });
|
|
579
|
+
formatOutput(data, getOutputOpts(this));
|
|
580
|
+
} catch (err) {
|
|
581
|
+
handleError(err);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
web.command("extract").argument("<url>", "URL to extract data from").description("Extract structured data using AI").requiredOption("-i, --instructions <text>", "Extraction instructions").requiredOption("-t, --template <json>", "JSON template (string or @filepath)").action(async function(url) {
|
|
585
|
+
try {
|
|
586
|
+
ensureAuth();
|
|
587
|
+
const opts = this.opts();
|
|
588
|
+
let jsonTemplate = opts.template;
|
|
589
|
+
if (jsonTemplate.startsWith("@")) {
|
|
590
|
+
const filePath = jsonTemplate.slice(1);
|
|
591
|
+
jsonTemplate = readFileSync2(filePath, "utf-8");
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
JSON.parse(jsonTemplate);
|
|
595
|
+
} catch {
|
|
596
|
+
console.error("Invalid JSON template. Provide valid JSON or @filepath.");
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
const data = await webPost("extract", {
|
|
600
|
+
url,
|
|
601
|
+
instructions: opts.instructions,
|
|
602
|
+
jsonTemplate
|
|
603
|
+
});
|
|
604
|
+
formatOutput(data, getOutputOpts(this));
|
|
605
|
+
} catch (err) {
|
|
606
|
+
handleError(err);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
web.command("convert").argument("<url>", "URL to convert").description("Convert webpage to Markdown").action(async function(url) {
|
|
610
|
+
try {
|
|
611
|
+
ensureAuth();
|
|
612
|
+
const data = await webPost("convert/markdown", { url });
|
|
613
|
+
formatOutput(data, getOutputOpts(this));
|
|
614
|
+
} catch (err) {
|
|
615
|
+
handleError(err);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
web.command("summarize").argument("<url>", "URL to summarize").description("AI-generated page summary").action(async function(url) {
|
|
619
|
+
try {
|
|
620
|
+
ensureAuth();
|
|
621
|
+
const data = await webPost("summarize/url", { url });
|
|
622
|
+
formatOutput(data, getOutputOpts(this));
|
|
623
|
+
} catch (err) {
|
|
624
|
+
handleError(err);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
web.command("screenshot").argument("<url>", "URL to screenshot").description("Capture webpage screenshot").action(async function(url) {
|
|
628
|
+
try {
|
|
629
|
+
ensureAuth();
|
|
630
|
+
const data = await webPost("screenshot", { url });
|
|
631
|
+
formatOutput(data, getOutputOpts(this));
|
|
632
|
+
} catch (err) {
|
|
633
|
+
handleError(err);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/commands/seo.ts
|
|
639
|
+
var PROXY_BASE3 = "/proxy/reviewweb/v1/seo-insights";
|
|
640
|
+
var KEYWORD_COLUMNS = [
|
|
641
|
+
{ key: "keyword", header: "Keyword", width: 30 },
|
|
642
|
+
{ key: "volume", header: "Volume", width: 10 },
|
|
643
|
+
{ key: "competition", header: "Competition", width: 14 }
|
|
644
|
+
];
|
|
645
|
+
async function seoPost(endpoint, body) {
|
|
646
|
+
return fetchApi("POST", `${PROXY_BASE3}/${endpoint}`, { body });
|
|
647
|
+
}
|
|
648
|
+
function registerSeoCommand(program2) {
|
|
649
|
+
const seo = program2.command("seo").description("SEO analysis tools");
|
|
650
|
+
seo.command("keywords").argument("<keyword>", "Seed keyword").description("Get keyword ideas and search volume").option("--country <code>", "Country code", "us").action(async function(keyword) {
|
|
651
|
+
try {
|
|
652
|
+
ensureAuth();
|
|
653
|
+
const opts = this.opts();
|
|
654
|
+
const data = await seoPost("keyword-ideas", {
|
|
655
|
+
keyword,
|
|
656
|
+
country: opts.country
|
|
657
|
+
});
|
|
658
|
+
formatOutput(data, getOutputOpts(this), KEYWORD_COLUMNS);
|
|
659
|
+
} catch (err) {
|
|
660
|
+
handleError(err);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
seo.command("difficulty").argument("<keyword>", "Keyword to check").description("Check keyword ranking difficulty").option("--country <code>", "Country code", "us").action(async function(keyword) {
|
|
664
|
+
try {
|
|
665
|
+
ensureAuth();
|
|
666
|
+
const opts = this.opts();
|
|
667
|
+
const data = await seoPost("keyword-difficulty", {
|
|
668
|
+
keyword,
|
|
669
|
+
country: opts.country
|
|
670
|
+
});
|
|
671
|
+
formatOutput(data, getOutputOpts(this));
|
|
672
|
+
} catch (err) {
|
|
673
|
+
handleError(err);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
seo.command("traffic").argument("<domain>", "Domain or URL to check").description("Check traffic for a domain").option("--country <code>", "Country code").action(async function(domain) {
|
|
677
|
+
try {
|
|
678
|
+
ensureAuth();
|
|
679
|
+
const opts = this.opts();
|
|
680
|
+
const body = { domainOrUrl: domain };
|
|
681
|
+
if (opts.country) body.country = opts.country;
|
|
682
|
+
const data = await seoPost("traffic", body);
|
|
683
|
+
formatOutput(data, getOutputOpts(this));
|
|
684
|
+
} catch (err) {
|
|
685
|
+
handleError(err);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
seo.command("backlinks").argument("<domain>", "Domain to check").description("Get backlink analysis").action(async function(domain) {
|
|
689
|
+
try {
|
|
690
|
+
ensureAuth();
|
|
691
|
+
const data = await seoPost("backlinks", { domain });
|
|
692
|
+
formatOutput(data, getOutputOpts(this));
|
|
693
|
+
} catch (err) {
|
|
694
|
+
handleError(err);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/commands/orders.ts
|
|
700
|
+
var ORDER_COLUMNS = [
|
|
701
|
+
{ key: "id", header: "ID", width: 12 },
|
|
702
|
+
{ key: "productType", header: "Product", width: 16 },
|
|
703
|
+
{ key: "status", header: "Status", width: 12 },
|
|
704
|
+
{ key: "amount", header: "Amount", width: 10 },
|
|
705
|
+
{ key: "currency", header: "Currency", width: 8 },
|
|
706
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
707
|
+
];
|
|
708
|
+
function registerOrdersCommand(program2) {
|
|
709
|
+
const orders = program2.command("orders").description("Look up orders and purchases");
|
|
710
|
+
orders.command("get").argument("<id>", "Order ID").description("Get order details").action(async function(id) {
|
|
711
|
+
try {
|
|
712
|
+
validateId(id, "order ID");
|
|
713
|
+
const data = await fetchApi("GET", `/orders/${id}`, { noAuth: true });
|
|
714
|
+
formatOutput(data, getOutputOpts(this), ORDER_COLUMNS);
|
|
715
|
+
} catch (err) {
|
|
716
|
+
handleError(err);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
orders.command("status").argument("<id>", "Order ID").description("Check order status").action(async function(id) {
|
|
720
|
+
try {
|
|
721
|
+
validateId(id, "order ID");
|
|
722
|
+
const data = await fetchApi("GET", `/orders/${id}/status`, { noAuth: true });
|
|
723
|
+
formatOutput(data, getOutputOpts(this));
|
|
724
|
+
} catch (err) {
|
|
725
|
+
handleError(err);
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/commands/referrals.ts
|
|
731
|
+
var PAYOUT_COLUMNS = [
|
|
732
|
+
{ key: "id", header: "ID", width: 12 },
|
|
733
|
+
{ key: "amount", header: "Amount", width: 10 },
|
|
734
|
+
{ key: "currency", header: "Currency", width: 8 },
|
|
735
|
+
{ key: "status", header: "Status", width: 12 },
|
|
736
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
737
|
+
];
|
|
738
|
+
function registerReferralsCommand(program2) {
|
|
739
|
+
const referrals = program2.command("referrals").description("Referral program management");
|
|
740
|
+
referrals.command("dashboard").description("View your referral dashboard").action(async function() {
|
|
741
|
+
try {
|
|
742
|
+
ensureAuth();
|
|
743
|
+
const data = await fetchApi("GET", "/referrals/dashboard");
|
|
744
|
+
formatOutput(data, getOutputOpts(this));
|
|
745
|
+
} catch (err) {
|
|
746
|
+
handleError(err);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
referrals.command("payout-history").description("View your payout request history").action(async function() {
|
|
750
|
+
try {
|
|
751
|
+
ensureAuth();
|
|
752
|
+
const data = await fetchApi("GET", "/referrals/request-payout");
|
|
753
|
+
formatOutput(data, getOutputOpts(this), PAYOUT_COLUMNS);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
handleError(err);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
referrals.command("request-payout").description("Request a referral payout").action(async function() {
|
|
759
|
+
try {
|
|
760
|
+
ensureAuth();
|
|
761
|
+
const data = await fetchApi("POST", "/referrals/request-payout");
|
|
762
|
+
printSuccess("Payout requested.");
|
|
763
|
+
formatOutput(data, getOutputOpts(this));
|
|
764
|
+
} catch (err) {
|
|
765
|
+
handleError(err);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/commands/blog.ts
|
|
771
|
+
var ARTICLE_COLUMNS = [
|
|
772
|
+
{ key: "id", header: "ID", width: 12 },
|
|
773
|
+
{ key: "title", header: "Title", width: 36 },
|
|
774
|
+
{ key: "status", header: "Status", width: 12 },
|
|
775
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
776
|
+
];
|
|
777
|
+
function registerBlogCommand(program2) {
|
|
778
|
+
const blog = program2.command("blog").description("Browse blog articles");
|
|
779
|
+
blog.command("list").description("List blog articles").action(async function() {
|
|
780
|
+
try {
|
|
781
|
+
const data = await fetchApi("GET", "/blog/articles", { noAuth: true });
|
|
782
|
+
formatOutput(data, getOutputOpts(this), ARTICLE_COLUMNS);
|
|
783
|
+
} catch (err) {
|
|
784
|
+
handleError(err);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
blog.command("get").argument("<id>", "Article ID").description("Get article details").action(async function(id) {
|
|
788
|
+
try {
|
|
789
|
+
validateId(id, "article ID");
|
|
790
|
+
const data = await fetchApi("GET", `/blog/articles/${id}`, { noAuth: true });
|
|
791
|
+
formatOutput(data, getOutputOpts(this));
|
|
792
|
+
} catch (err) {
|
|
793
|
+
handleError(err);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/commands/health.ts
|
|
799
|
+
import chalk4 from "chalk";
|
|
800
|
+
function registerHealthCommand(program2) {
|
|
801
|
+
program2.command("health").description("Check ClaudeKit.cc API health").action(async function() {
|
|
802
|
+
try {
|
|
803
|
+
const data = await fetchApi("GET", "/health", { noAuth: true });
|
|
804
|
+
const outputOpts = getOutputOpts(this);
|
|
805
|
+
if (outputOpts.json || outputOpts.quiet) {
|
|
806
|
+
formatOutput(data, outputOpts);
|
|
807
|
+
} else {
|
|
808
|
+
printSuccess("ClaudeKit.cc API is healthy.");
|
|
809
|
+
if (data.version) console.log(chalk4.dim(`Version: ${data.version}`));
|
|
810
|
+
if (data.uptime) console.log(chalk4.dim(`Uptime: ${data.uptime}`));
|
|
811
|
+
}
|
|
812
|
+
} catch (err) {
|
|
813
|
+
handleError(err);
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/commands/account.ts
|
|
819
|
+
import * as readline2 from "readline/promises";
|
|
820
|
+
function registerAccountCommand(program2) {
|
|
821
|
+
const account = program2.command("account").description("Account management");
|
|
822
|
+
account.command("export-data").description("Export your personal data (GDPR)").action(async function() {
|
|
823
|
+
try {
|
|
824
|
+
ensureAuth();
|
|
825
|
+
const data = await fetchApi("GET", "/gdpr/export-data");
|
|
826
|
+
formatOutput(data, getOutputOpts(this));
|
|
827
|
+
} catch (err) {
|
|
828
|
+
handleError(err);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
account.command("delete").description("Delete your account permanently (GDPR)").option("--reason <text>", "Reason for account deletion").action(async function() {
|
|
832
|
+
try {
|
|
833
|
+
ensureAuth();
|
|
834
|
+
const { reason } = this.opts();
|
|
835
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
836
|
+
const answer = await rl.question(
|
|
837
|
+
'This action is irreversible. Type "DELETE_MY_ACCOUNT" to confirm: '
|
|
838
|
+
);
|
|
839
|
+
rl.close();
|
|
840
|
+
if (answer.trim() !== "DELETE_MY_ACCOUNT") {
|
|
841
|
+
printWarn("Cancelled. Account was not deleted.");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
await fetchApi("POST", "/gdpr/delete-account", {
|
|
845
|
+
body: { confirmation: "DELETE_MY_ACCOUNT", reason }
|
|
846
|
+
});
|
|
847
|
+
printSuccess("Account deletion request submitted.");
|
|
848
|
+
} catch (err) {
|
|
849
|
+
handleError(err);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/commands/checkout.ts
|
|
855
|
+
function addCheckoutOptions(cmd) {
|
|
856
|
+
return cmd.requiredOption("--email <email>", "Customer email").requiredOption("--product <type>", "Product type").option("--name <name>", "Customer name").option("--github <username>", "GitHub username").option("--referral <code>", "Referral code").option("--coupon <code>", "Coupon code");
|
|
857
|
+
}
|
|
858
|
+
function buildCheckoutBody(opts) {
|
|
859
|
+
return {
|
|
860
|
+
email: opts.email,
|
|
861
|
+
name: opts.name,
|
|
862
|
+
productType: opts.product,
|
|
863
|
+
githubUsername: opts.github,
|
|
864
|
+
referralCode: opts.referral,
|
|
865
|
+
couponCode: opts.coupon
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function registerCheckoutCommand(program2) {
|
|
869
|
+
const checkout = program2.command("checkout").description("Create payment checkout");
|
|
870
|
+
addCheckoutOptions(
|
|
871
|
+
checkout.command("polar").description("Create a Polar checkout session")
|
|
872
|
+
).action(async function() {
|
|
873
|
+
try {
|
|
874
|
+
const data = await fetchApi("POST", "/checkout/polar", {
|
|
875
|
+
noAuth: true,
|
|
876
|
+
body: buildCheckoutBody(this.opts())
|
|
877
|
+
});
|
|
878
|
+
formatOutput(data, getOutputOpts(this));
|
|
879
|
+
} catch (err) {
|
|
880
|
+
handleError(err);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
addCheckoutOptions(
|
|
884
|
+
checkout.command("sepay").description("Create a SePay checkout session")
|
|
885
|
+
).action(async function() {
|
|
886
|
+
try {
|
|
887
|
+
const data = await fetchApi("POST", "/checkout/sepay", {
|
|
888
|
+
noAuth: true,
|
|
889
|
+
body: buildCheckoutBody(this.opts())
|
|
890
|
+
});
|
|
891
|
+
formatOutput(data, getOutputOpts(this));
|
|
892
|
+
} catch (err) {
|
|
893
|
+
handleError(err);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/commands/github.ts
|
|
899
|
+
import * as readline3 from "readline/promises";
|
|
900
|
+
function registerGithubCommand(program2) {
|
|
901
|
+
const github = program2.command("github").description("GitHub repository access");
|
|
902
|
+
github.command("invite").description("Grant GitHub repository access via license").requiredOption("--license <id>", "License ID").action(async function() {
|
|
903
|
+
try {
|
|
904
|
+
ensureAuth();
|
|
905
|
+
const { license } = this.opts();
|
|
906
|
+
const data = await fetchApi("POST", "/github/invite", {
|
|
907
|
+
body: { licenseId: license }
|
|
908
|
+
});
|
|
909
|
+
formatOutput(data, getOutputOpts(this));
|
|
910
|
+
} catch (err) {
|
|
911
|
+
handleError(err);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
github.command("revoke").description("Revoke GitHub repository access via license").requiredOption("--license <id>", "License ID").action(async function() {
|
|
915
|
+
try {
|
|
916
|
+
ensureAuth();
|
|
917
|
+
const { license } = this.opts();
|
|
918
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
919
|
+
const answer = await rl.question(`Revoke GitHub access for license "${license}"? (y/N): `);
|
|
920
|
+
rl.close();
|
|
921
|
+
if (answer.toLowerCase() !== "y") {
|
|
922
|
+
printWarn("Cancelled.");
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const data = await fetchApi("POST", "/github/revoke", {
|
|
926
|
+
body: { licenseId: license }
|
|
927
|
+
});
|
|
928
|
+
formatOutput(data, getOutputOpts(this));
|
|
929
|
+
printSuccess("GitHub access revoked.");
|
|
930
|
+
} catch (err) {
|
|
931
|
+
handleError(err);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/commands/loyalty.ts
|
|
937
|
+
function registerLoyaltyCommand(program2) {
|
|
938
|
+
const loyalty = program2.command("loyalty").description("Loyalty program");
|
|
939
|
+
loyalty.command("eligibility").description("Check your loyalty program eligibility").action(async function() {
|
|
940
|
+
try {
|
|
941
|
+
ensureAuth();
|
|
942
|
+
const data = await fetchApi("GET", "/loyalty/eligibility");
|
|
943
|
+
formatOutput(data, getOutputOpts(this));
|
|
944
|
+
} catch (err) {
|
|
945
|
+
handleError(err);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/commands/newsletter.ts
|
|
951
|
+
function registerNewsletterCommand(program2) {
|
|
952
|
+
const newsletter = program2.command("newsletter").description("Newsletter subscription");
|
|
953
|
+
newsletter.command("subscribe").description("Subscribe an email to the newsletter").requiredOption("--email <email>", "Email address to subscribe").action(async function() {
|
|
954
|
+
try {
|
|
955
|
+
const { email } = this.opts();
|
|
956
|
+
const data = await fetchApi("POST", "/newsletter/subscribe", {
|
|
957
|
+
noAuth: true,
|
|
958
|
+
body: { email }
|
|
959
|
+
});
|
|
960
|
+
formatOutput(data, getOutputOpts(this));
|
|
961
|
+
} catch (err) {
|
|
962
|
+
handleError(err);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
newsletter.command("unsubscribe").description("Unsubscribe from the newsletter using a token").requiredOption("--token <token>", "Unsubscribe token from email").action(async function() {
|
|
966
|
+
try {
|
|
967
|
+
const { token } = this.opts();
|
|
968
|
+
const data = await fetchApi("POST", "/newsletter/unsubscribe", {
|
|
969
|
+
noAuth: true,
|
|
970
|
+
body: { token }
|
|
971
|
+
});
|
|
972
|
+
formatOutput(data, getOutputOpts(this));
|
|
973
|
+
} catch (err) {
|
|
974
|
+
handleError(err);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/commands/payout.ts
|
|
980
|
+
import * as readline4 from "readline/promises";
|
|
981
|
+
var PAYOUT_PROFILE_COLUMNS = [
|
|
982
|
+
{ key: "id", header: "ID", width: 36 },
|
|
983
|
+
{ key: "paymentMethod", header: "Method", width: 20 },
|
|
984
|
+
{ key: "createdAt", header: "Created At", width: 24 }
|
|
985
|
+
];
|
|
986
|
+
function registerPayoutCommand(program2) {
|
|
987
|
+
const payout = program2.command("payout").description("Payout profile management");
|
|
988
|
+
payout.command("profile").description("List your payout profiles").action(async function() {
|
|
989
|
+
try {
|
|
990
|
+
ensureAuth();
|
|
991
|
+
const data = await fetchApi("GET", "/payout/profile");
|
|
992
|
+
formatOutput(data, getOutputOpts(this), PAYOUT_PROFILE_COLUMNS);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
handleError(err);
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
payout.command("create-profile").description("Create a new payout profile").requiredOption("--method <method>", "Payment method (e.g. bank_transfer, paypal)").requiredOption("--details <json>", "Payment details as JSON string").action(async function() {
|
|
998
|
+
try {
|
|
999
|
+
ensureAuth();
|
|
1000
|
+
const { method, details } = this.opts();
|
|
1001
|
+
let paymentDetails;
|
|
1002
|
+
try {
|
|
1003
|
+
paymentDetails = JSON.parse(details);
|
|
1004
|
+
} catch {
|
|
1005
|
+
throw new CliError("--details must be valid JSON.");
|
|
1006
|
+
}
|
|
1007
|
+
const data = await fetchApi("POST", "/payout/profile", {
|
|
1008
|
+
body: { paymentMethod: method, paymentDetails }
|
|
1009
|
+
});
|
|
1010
|
+
formatOutput(data, getOutputOpts(this));
|
|
1011
|
+
printSuccess("Payout profile created.");
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
handleError(err);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
payout.command("delete-profile").description("Delete a payout profile").requiredOption("--id <id>", "Payout profile ID to delete").action(async function() {
|
|
1017
|
+
try {
|
|
1018
|
+
ensureAuth();
|
|
1019
|
+
const { id } = this.opts();
|
|
1020
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
1021
|
+
const answer = await rl.question(`Delete payout profile "${id}"? (y/N): `);
|
|
1022
|
+
rl.close();
|
|
1023
|
+
if (answer.toLowerCase() !== "y") {
|
|
1024
|
+
printWarn("Cancelled.");
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
await fetchApi("DELETE", "/payout/profile", { params: { id } });
|
|
1028
|
+
printSuccess("Payout profile deleted.");
|
|
1029
|
+
} catch (err) {
|
|
1030
|
+
handleError(err);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/commands/pricing.ts
|
|
1036
|
+
function registerPricingCommand(program2) {
|
|
1037
|
+
const pricing = program2.command("pricing").description("Pricing and promotions");
|
|
1038
|
+
pricing.command("validate-discount <code>").description("Validate a discount code").action(async function(code) {
|
|
1039
|
+
try {
|
|
1040
|
+
const data = await fetchApi("POST", "/discounts/validate", {
|
|
1041
|
+
noAuth: true,
|
|
1042
|
+
body: { code }
|
|
1043
|
+
});
|
|
1044
|
+
formatOutput(data, getOutputOpts(this));
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
handleError(err);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
pricing.command("exchange-rate").description("Get current exchange rate").action(async function() {
|
|
1050
|
+
try {
|
|
1051
|
+
const data = await fetchApi("GET", "/exchange-rate", { noAuth: true });
|
|
1052
|
+
formatOutput(data, getOutputOpts(this));
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
handleError(err);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
pricing.command("early-access").description("Get early access promotion status").action(async function() {
|
|
1058
|
+
try {
|
|
1059
|
+
const data = await fetchApi("GET", "/promotions/early-access", {
|
|
1060
|
+
noAuth: true
|
|
1061
|
+
});
|
|
1062
|
+
formatOutput(data, getOutputOpts(this));
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
handleError(err);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/commands/refunds.ts
|
|
1070
|
+
import * as readline5 from "readline/promises";
|
|
1071
|
+
function registerRefundsCommand(program2) {
|
|
1072
|
+
const refunds = program2.command("refunds").description("Refund management");
|
|
1073
|
+
refunds.command("request <orderId>").description("Request a refund for an order").requiredOption("--reason <text>", "Reason for refund request").option("--bank-details <json>", "Bank transfer details as JSON string").action(async function(orderId) {
|
|
1074
|
+
try {
|
|
1075
|
+
ensureAuth();
|
|
1076
|
+
validateId(orderId, "orderId");
|
|
1077
|
+
const { reason, bankDetails } = this.opts();
|
|
1078
|
+
let bankTransferDetails;
|
|
1079
|
+
if (bankDetails) {
|
|
1080
|
+
try {
|
|
1081
|
+
bankTransferDetails = JSON.parse(bankDetails);
|
|
1082
|
+
} catch {
|
|
1083
|
+
throw new CliError("--bank-details must be valid JSON.");
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
1087
|
+
const answer = await rl.question(`Request refund for order "${orderId}"? (y/N): `);
|
|
1088
|
+
rl.close();
|
|
1089
|
+
if (answer.toLowerCase() !== "y") {
|
|
1090
|
+
printWarn("Cancelled.");
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const data = await fetchApi("POST", "/refunds/request", {
|
|
1094
|
+
body: { orderId, reason, bankTransferDetails }
|
|
1095
|
+
});
|
|
1096
|
+
formatOutput(data, getOutputOpts(this));
|
|
1097
|
+
printSuccess("Refund request submitted.");
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
handleError(err);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/commands/releases.ts
|
|
1105
|
+
var RELEASE_COLUMNS = [
|
|
1106
|
+
{ key: "tagName", header: "Tag", width: 20 },
|
|
1107
|
+
{ key: "name", header: "Name", width: 40 },
|
|
1108
|
+
{ key: "publishedAt", header: "Published At", width: 24 }
|
|
1109
|
+
];
|
|
1110
|
+
function registerReleasesCommand(program2) {
|
|
1111
|
+
const releases = program2.command("releases").description("Product releases");
|
|
1112
|
+
releases.command("list").description("List product releases").option("--tab <all|engineer|marketing>", "Release tab filter", "all").action(async function() {
|
|
1113
|
+
try {
|
|
1114
|
+
const { tab } = this.opts();
|
|
1115
|
+
const data = await fetchApi("GET", "/releases", {
|
|
1116
|
+
noAuth: true,
|
|
1117
|
+
params: { tab }
|
|
1118
|
+
});
|
|
1119
|
+
formatOutput(data, getOutputOpts(this), RELEASE_COLUMNS);
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
handleError(err);
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/commands/stats.ts
|
|
1127
|
+
function registerStatsCommand(program2) {
|
|
1128
|
+
const stats = program2.command("stats").description("Platform statistics");
|
|
1129
|
+
stats.command("users").description("Get platform user statistics").action(async function() {
|
|
1130
|
+
try {
|
|
1131
|
+
const data = await fetchApi("GET", "/stats/users", { noAuth: true });
|
|
1132
|
+
formatOutput(data, getOutputOpts(this));
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
handleError(err);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/commands/waitlist.ts
|
|
1140
|
+
function registerWaitlistCommand(program2) {
|
|
1141
|
+
const waitlist = program2.command("waitlist").description("Product waitlist");
|
|
1142
|
+
waitlist.command("join").description("Join the marketing waitlist").requiredOption("--email <email>", "Email address to join with").action(async function() {
|
|
1143
|
+
try {
|
|
1144
|
+
const { email } = this.opts();
|
|
1145
|
+
const data = await fetchApi("POST", "/waitlist/marketing", {
|
|
1146
|
+
noAuth: true,
|
|
1147
|
+
body: { email }
|
|
1148
|
+
});
|
|
1149
|
+
formatOutput(data, getOutputOpts(this));
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
handleError(err);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// src/commands/admin/admin-utils.ts
|
|
1157
|
+
import * as readline6 from "readline/promises";
|
|
1158
|
+
async function confirm(message) {
|
|
1159
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
1160
|
+
try {
|
|
1161
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
1162
|
+
return answer.trim().toLowerCase() === "y";
|
|
1163
|
+
} finally {
|
|
1164
|
+
rl.close();
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/commands/admin/admin-orders.ts
|
|
1169
|
+
var ORDER_COLUMNS2 = [
|
|
1170
|
+
{ key: "id", header: "ID", width: 12 },
|
|
1171
|
+
{ key: "userEmail", header: "User Email", width: 28 },
|
|
1172
|
+
{ key: "productType", header: "Product", width: 16 },
|
|
1173
|
+
{ key: "amount", header: "Amount", width: 10 },
|
|
1174
|
+
{ key: "currency", header: "Currency", width: 8 },
|
|
1175
|
+
{ key: "status", header: "Status", width: 12 },
|
|
1176
|
+
{ key: "paymentProvider", header: "Provider", width: 12 },
|
|
1177
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
1178
|
+
];
|
|
1179
|
+
function registerAdminOrdersCommand(admin) {
|
|
1180
|
+
const orders = admin.command("orders").description("Admin order management");
|
|
1181
|
+
orders.command("list").description("List all orders").option("--provider <name>", "Filter by payment provider").action(async function() {
|
|
1182
|
+
try {
|
|
1183
|
+
ensureAdminAuth();
|
|
1184
|
+
const opts = this.opts();
|
|
1185
|
+
const params = {};
|
|
1186
|
+
if (opts.provider) params["provider"] = opts.provider;
|
|
1187
|
+
const data = await fetchApi("GET", "/admin/orders", { adminAuth: true, params });
|
|
1188
|
+
formatOutput(data, getOutputOpts(this), ORDER_COLUMNS2);
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
handleError(err);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
orders.command("get <id>").description("Get order details").action(async function(id) {
|
|
1194
|
+
try {
|
|
1195
|
+
ensureAdminAuth();
|
|
1196
|
+
validateId(id, "order ID");
|
|
1197
|
+
const data = await fetchApi("GET", `/admin/orders/${id}`, { adminAuth: true });
|
|
1198
|
+
formatOutput(data, getOutputOpts(this));
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
handleError(err);
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
orders.command("complete <id>").description("Mark an order as complete").action(async function(id) {
|
|
1204
|
+
try {
|
|
1205
|
+
ensureAdminAuth();
|
|
1206
|
+
validateId(id, "order ID");
|
|
1207
|
+
const ok = await confirm(`Complete order ${id}?`);
|
|
1208
|
+
if (!ok) {
|
|
1209
|
+
console.log("Aborted.");
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const data = await fetchApi("POST", `/admin/orders/${id}/complete`, { adminAuth: true });
|
|
1213
|
+
printSuccess(`Order ${id} marked complete.`);
|
|
1214
|
+
formatOutput(data, getOutputOpts(this));
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
handleError(err);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
orders.command("refund <id>").description("Refund an order (revokes GitHub access)").action(async function(id) {
|
|
1220
|
+
try {
|
|
1221
|
+
ensureAdminAuth();
|
|
1222
|
+
validateId(id, "order ID");
|
|
1223
|
+
const ok = await confirm(`Refund order ${id}? WARNING: this will revoke GitHub access. Continue?`);
|
|
1224
|
+
if (!ok) {
|
|
1225
|
+
console.log("Aborted.");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const data = await fetchApi("POST", `/admin/orders/${id}/refund`, { adminAuth: true });
|
|
1229
|
+
printSuccess(`Order ${id} refunded.`);
|
|
1230
|
+
formatOutput(data, getOutputOpts(this));
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
handleError(err);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
orders.command("refund-keep-access <id>").description("Refund an order but keep GitHub access").action(async function(id) {
|
|
1236
|
+
try {
|
|
1237
|
+
ensureAdminAuth();
|
|
1238
|
+
validateId(id, "order ID");
|
|
1239
|
+
const ok = await confirm(`Refund order ${id} (keep access)?`);
|
|
1240
|
+
if (!ok) {
|
|
1241
|
+
console.log("Aborted.");
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const data = await fetchApi("POST", `/admin/orders/${id}/refund-keep-access`, { adminAuth: true });
|
|
1245
|
+
printSuccess(`Order ${id} refunded (access retained).`);
|
|
1246
|
+
formatOutput(data, getOutputOpts(this));
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
handleError(err);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
orders.command("switch-product <id>").description("Switch the product type of an order").requiredOption("--product <engineer_kit|marketing_kit>", "Target product type").option("--reason <text>", "Reason for the switch").action(async function(id) {
|
|
1252
|
+
try {
|
|
1253
|
+
ensureAdminAuth();
|
|
1254
|
+
validateId(id, "order ID");
|
|
1255
|
+
const { product, reason } = this.opts();
|
|
1256
|
+
if (!["engineer_kit", "marketing_kit"].includes(product)) {
|
|
1257
|
+
throw new CliError("--product must be one of: engineer_kit, marketing_kit");
|
|
1258
|
+
}
|
|
1259
|
+
const ok = await confirm(`Switch order ${id} to product "${product}"?`);
|
|
1260
|
+
if (!ok) {
|
|
1261
|
+
console.log("Aborted.");
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const body = { targetProductType: product };
|
|
1265
|
+
if (reason) body["reason"] = reason;
|
|
1266
|
+
const data = await fetchApi("POST", `/admin/orders/${id}/switch-product`, { adminAuth: true, body });
|
|
1267
|
+
printSuccess(`Order ${id} product switched to ${product}.`);
|
|
1268
|
+
formatOutput(data, getOutputOpts(this));
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
handleError(err);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
orders.command("resend-emails <id>").description("Resend emails for an order").requiredOption("--type <order|welcome|invoice|both>", "Email type to resend").action(async function(id) {
|
|
1274
|
+
try {
|
|
1275
|
+
ensureAdminAuth();
|
|
1276
|
+
validateId(id, "order ID");
|
|
1277
|
+
const { type } = this.opts();
|
|
1278
|
+
const data = await fetchApi("POST", `/admin/orders/${id}/resend-emails`, {
|
|
1279
|
+
adminAuth: true,
|
|
1280
|
+
body: { emailType: type }
|
|
1281
|
+
});
|
|
1282
|
+
printSuccess(`Emails (${type}) resent for order ${id}.`);
|
|
1283
|
+
formatOutput(data, getOutputOpts(this));
|
|
1284
|
+
} catch (err) {
|
|
1285
|
+
handleError(err);
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
orders.command("export").description("Export orders as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").option("--search <text>", "Search text").option("--provider <name>", "Filter by payment provider").option("--status <status>", "Filter by status").action(async function() {
|
|
1289
|
+
try {
|
|
1290
|
+
ensureAdminAuth();
|
|
1291
|
+
const opts = this.opts();
|
|
1292
|
+
const params = {};
|
|
1293
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1294
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1295
|
+
if (opts.search) params["search"] = opts.search;
|
|
1296
|
+
if (opts.provider) params["provider"] = opts.provider;
|
|
1297
|
+
if (opts.status) params["status"] = opts.status;
|
|
1298
|
+
const csv = await fetchApi("GET", "/admin/orders/export", { adminAuth: true, params });
|
|
1299
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1300
|
+
} catch (err) {
|
|
1301
|
+
handleError(err);
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/commands/admin/admin-users.ts
|
|
1307
|
+
function registerAdminUsersCommand(admin) {
|
|
1308
|
+
const users = admin.command("users").description("Admin user management");
|
|
1309
|
+
users.command("get <id>").description("Get user details").action(async function(id) {
|
|
1310
|
+
try {
|
|
1311
|
+
ensureAdminAuth();
|
|
1312
|
+
validateId(id, "user ID");
|
|
1313
|
+
const data = await fetchApi("GET", `/admin/users/${id}`, { adminAuth: true });
|
|
1314
|
+
formatOutput(data, getOutputOpts(this));
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
handleError(err);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
users.command("update-github <id>").description("Update GitHub username for a user").requiredOption("--username <name>", "New GitHub username").action(async function(id) {
|
|
1320
|
+
try {
|
|
1321
|
+
ensureAdminAuth();
|
|
1322
|
+
validateId(id, "user ID");
|
|
1323
|
+
const { username } = this.opts();
|
|
1324
|
+
const data = await fetchApi("PATCH", `/admin/users/${id}/github-username`, {
|
|
1325
|
+
adminAuth: true,
|
|
1326
|
+
body: { githubUsername: username }
|
|
1327
|
+
});
|
|
1328
|
+
printSuccess(`GitHub username updated for user ${id}.`);
|
|
1329
|
+
formatOutput(data, getOutputOpts(this));
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
handleError(err);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
users.command("update-rates <id>").description("Update commission/discount rates for a user").option("--commission <rate>", "Commission rate (number)").option("--discount <rate>", "Discount rate (number)").action(async function(id) {
|
|
1335
|
+
try {
|
|
1336
|
+
ensureAdminAuth();
|
|
1337
|
+
validateId(id, "user ID");
|
|
1338
|
+
const opts = this.opts();
|
|
1339
|
+
if (opts.commission === void 0 && opts.discount === void 0) {
|
|
1340
|
+
throw new CliError("At least one of --commission or --discount is required.");
|
|
1341
|
+
}
|
|
1342
|
+
const body = {};
|
|
1343
|
+
if (opts.commission !== void 0) {
|
|
1344
|
+
const val = Number(opts.commission);
|
|
1345
|
+
if (Number.isNaN(val) || val < 0 || val > 100) throw new CliError("--commission must be a number between 0 and 100");
|
|
1346
|
+
body["commissionRate"] = val;
|
|
1347
|
+
}
|
|
1348
|
+
if (opts.discount !== void 0) {
|
|
1349
|
+
const val = Number(opts.discount);
|
|
1350
|
+
if (Number.isNaN(val) || val < 0 || val > 100) throw new CliError("--discount must be a number between 0 and 100");
|
|
1351
|
+
body["discountRate"] = val;
|
|
1352
|
+
}
|
|
1353
|
+
const data = await fetchApi("PATCH", `/admin/users/${id}/rates`, { adminAuth: true, body });
|
|
1354
|
+
printSuccess(`Rates updated for user ${id}.`);
|
|
1355
|
+
formatOutput(data, getOutputOpts(this));
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
handleError(err);
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
users.command("set-tier <id>").description("Set manual tier for a user (use 'none' to remove)").requiredOption("--tier <tierId>", "Tier ID or 'none' to remove").action(async function(id) {
|
|
1361
|
+
try {
|
|
1362
|
+
ensureAdminAuth();
|
|
1363
|
+
validateId(id, "user ID");
|
|
1364
|
+
const { tier } = this.opts();
|
|
1365
|
+
const data = await fetchApi("PATCH", `/admin/users/${id}/tier`, {
|
|
1366
|
+
adminAuth: true,
|
|
1367
|
+
body: { manualTierId: tier === "none" ? null : tier }
|
|
1368
|
+
});
|
|
1369
|
+
printSuccess(`Tier updated for user ${id}.`);
|
|
1370
|
+
formatOutput(data, getOutputOpts(this));
|
|
1371
|
+
} catch (err) {
|
|
1372
|
+
handleError(err);
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
users.command("export").description("Export users as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").option("--search <text>", "Search text").action(async function() {
|
|
1376
|
+
try {
|
|
1377
|
+
ensureAdminAuth();
|
|
1378
|
+
const opts = this.opts();
|
|
1379
|
+
const params = {};
|
|
1380
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1381
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1382
|
+
if (opts.search) params["search"] = opts.search;
|
|
1383
|
+
const csv = await fetchApi("GET", "/admin/users/export", { adminAuth: true, params });
|
|
1384
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
handleError(err);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// src/commands/admin/admin-referrals.ts
|
|
1392
|
+
function registerAdminReferralsCommand(admin) {
|
|
1393
|
+
const referrals = admin.command("referrals").description("Admin referral management");
|
|
1394
|
+
referrals.command("stats").description("Get referral statistics").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").action(async function() {
|
|
1395
|
+
try {
|
|
1396
|
+
ensureAdminAuth();
|
|
1397
|
+
const opts = this.opts();
|
|
1398
|
+
const params = {};
|
|
1399
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1400
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1401
|
+
const data = await fetchApi("GET", "/admin/referrals/stats", { adminAuth: true, params });
|
|
1402
|
+
formatOutput(data, getOutputOpts(this));
|
|
1403
|
+
} catch (err) {
|
|
1404
|
+
handleError(err);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
referrals.command("tiers-stats").description("Get referral tier statistics").action(async function() {
|
|
1408
|
+
try {
|
|
1409
|
+
ensureAdminAuth();
|
|
1410
|
+
const data = await fetchApi("GET", "/admin/referrals/tiers/stats", { adminAuth: true });
|
|
1411
|
+
formatOutput(data, getOutputOpts(this));
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
handleError(err);
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
referrals.command("diagnose").description("Run referral diagnostics").action(async function() {
|
|
1417
|
+
try {
|
|
1418
|
+
ensureAdminAuth();
|
|
1419
|
+
const data = await fetchApi("GET", "/admin/referrals/diagnose", { adminAuth: true });
|
|
1420
|
+
formatOutput(data, getOutputOpts(this));
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
handleError(err);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
referrals.command("export").description("Export referral data as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").action(async function() {
|
|
1426
|
+
try {
|
|
1427
|
+
ensureAdminAuth();
|
|
1428
|
+
const opts = this.opts();
|
|
1429
|
+
const params = {};
|
|
1430
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1431
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1432
|
+
const csv = await fetchApi("GET", "/admin/referrals/export", { adminAuth: true, params });
|
|
1433
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1434
|
+
} catch (err) {
|
|
1435
|
+
handleError(err);
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/commands/admin/admin-revenue.ts
|
|
1441
|
+
function registerAdminRevenueCommand(admin) {
|
|
1442
|
+
const revenue = admin.command("revenue").description("Admin revenue reporting");
|
|
1443
|
+
revenue.command("stats").description("Get revenue statistics").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").action(async function() {
|
|
1444
|
+
try {
|
|
1445
|
+
ensureAdminAuth();
|
|
1446
|
+
const opts = this.opts();
|
|
1447
|
+
const params = {};
|
|
1448
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1449
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1450
|
+
const data = await fetchApi("GET", "/admin/revenue", { adminAuth: true, params });
|
|
1451
|
+
formatOutput(data, getOutputOpts(this));
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
handleError(err);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
revenue.command("export").description("Export revenue data as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").action(async function() {
|
|
1457
|
+
try {
|
|
1458
|
+
ensureAdminAuth();
|
|
1459
|
+
const opts = this.opts();
|
|
1460
|
+
const params = {};
|
|
1461
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1462
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1463
|
+
const csv = await fetchApi("GET", "/admin/revenue/export", { adminAuth: true, params });
|
|
1464
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
handleError(err);
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
revenue.command("maintainer").description("Get maintainer revenue breakdown").requiredOption("--id <cke|ckm>", "Maintainer ID").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").action(async function() {
|
|
1470
|
+
try {
|
|
1471
|
+
ensureAdminAuth();
|
|
1472
|
+
const opts = this.opts();
|
|
1473
|
+
const params = { id: opts.id };
|
|
1474
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1475
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1476
|
+
const data = await fetchApi("GET", "/admin/revenue/maintainer", { adminAuth: true, params });
|
|
1477
|
+
formatOutput(data, getOutputOpts(this));
|
|
1478
|
+
} catch (err) {
|
|
1479
|
+
handleError(err);
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
revenue.command("costs").description("Get monthly costs").option("--month <YYYY-MM>", "Month in YYYY-MM format").action(async function() {
|
|
1483
|
+
try {
|
|
1484
|
+
ensureAdminAuth();
|
|
1485
|
+
const opts = this.opts();
|
|
1486
|
+
const params = {};
|
|
1487
|
+
if (opts.month) params["yearMonth"] = opts.month;
|
|
1488
|
+
const data = await fetchApi("GET", "/admin/revenue/monthly-costs", { adminAuth: true, params });
|
|
1489
|
+
formatOutput(data, getOutputOpts(this));
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
handleError(err);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
revenue.command("add-cost").description("Add a monthly cost entry").requiredOption("--month <YYYY-MM>", "Month in YYYY-MM format").requiredOption("--category <name>", "Cost category").requiredOption("--description <text>", "Cost description").requiredOption("--amount <cents>", "Amount in cents").action(async function() {
|
|
1495
|
+
try {
|
|
1496
|
+
ensureAdminAuth();
|
|
1497
|
+
const opts = this.opts();
|
|
1498
|
+
const amount = Number(opts.amount);
|
|
1499
|
+
if (Number.isNaN(amount)) throw new CliError("--amount must be a valid number");
|
|
1500
|
+
const data = await fetchApi("POST", "/admin/revenue/monthly-costs", {
|
|
1501
|
+
adminAuth: true,
|
|
1502
|
+
body: { yearMonth: opts.month, category: opts.category, description: opts.description, amount }
|
|
1503
|
+
});
|
|
1504
|
+
printSuccess("Monthly cost entry added.");
|
|
1505
|
+
formatOutput(data, getOutputOpts(this));
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
handleError(err);
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
revenue.command("update-cost <id>").description("Update a monthly cost entry").option("--month <YYYY-MM>", "Month in YYYY-MM format").option("--category <name>", "Cost category").option("--description <text>", "Cost description").option("--amount <cents>", "Amount in cents").action(async function(id) {
|
|
1511
|
+
try {
|
|
1512
|
+
ensureAdminAuth();
|
|
1513
|
+
validateId(id, "cost ID");
|
|
1514
|
+
const opts = this.opts();
|
|
1515
|
+
const body = {};
|
|
1516
|
+
if (opts.month) body["yearMonth"] = opts.month;
|
|
1517
|
+
if (opts.category) body["category"] = opts.category;
|
|
1518
|
+
if (opts.description) body["description"] = opts.description;
|
|
1519
|
+
if (opts.amount !== void 0) {
|
|
1520
|
+
const amount = Number(opts.amount);
|
|
1521
|
+
if (Number.isNaN(amount)) throw new CliError("--amount must be a valid number");
|
|
1522
|
+
body["amount"] = amount;
|
|
1523
|
+
}
|
|
1524
|
+
const data = await fetchApi("PUT", `/admin/revenue/monthly-costs/${id}`, { adminAuth: true, body });
|
|
1525
|
+
printSuccess(`Cost entry ${id} updated.`);
|
|
1526
|
+
formatOutput(data, getOutputOpts(this));
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
handleError(err);
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
revenue.command("delete-cost <id>").description("Delete a monthly cost entry").action(async function(id) {
|
|
1532
|
+
try {
|
|
1533
|
+
ensureAdminAuth();
|
|
1534
|
+
validateId(id, "cost ID");
|
|
1535
|
+
const ok = await confirm(`Delete cost entry ${id}? This cannot be undone.`);
|
|
1536
|
+
if (!ok) {
|
|
1537
|
+
console.log("Aborted.");
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
await fetchApi("DELETE", `/admin/revenue/monthly-costs/${id}`, { adminAuth: true });
|
|
1541
|
+
printSuccess(`Cost entry ${id} deleted.`);
|
|
1542
|
+
} catch (err) {
|
|
1543
|
+
handleError(err);
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
revenue.command("reports").description("Generate revenue reports").option("--maintainer <cke|ckm|all>", "Maintainer ID").option("--month <YYYY-MM>", "Month in YYYY-MM format").option("--skip-email", "Skip sending email").action(async function() {
|
|
1547
|
+
try {
|
|
1548
|
+
ensureAdminAuth();
|
|
1549
|
+
const opts = this.opts();
|
|
1550
|
+
const body = {};
|
|
1551
|
+
if (opts.maintainer) body["maintainerId"] = opts.maintainer;
|
|
1552
|
+
if (opts.month) body["yearMonth"] = opts.month;
|
|
1553
|
+
if (opts.skipEmail) body["skipEmail"] = true;
|
|
1554
|
+
const data = await fetchApi("POST", "/admin/revenue/reports", { adminAuth: true, body });
|
|
1555
|
+
formatOutput(data, getOutputOpts(this));
|
|
1556
|
+
} catch (err) {
|
|
1557
|
+
handleError(err);
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// src/commands/admin/admin-payouts.ts
|
|
1563
|
+
var PAYOUT_COLUMNS2 = [
|
|
1564
|
+
{ key: "id", header: "ID", width: 12 },
|
|
1565
|
+
{ key: "userEmail", header: "User Email", width: 28 },
|
|
1566
|
+
{ key: "amount", header: "Amount", width: 10 },
|
|
1567
|
+
{ key: "currency", header: "Currency", width: 8 },
|
|
1568
|
+
{ key: "status", header: "Status", width: 12 },
|
|
1569
|
+
{ key: "paymentMethod", header: "Method", width: 14 },
|
|
1570
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
1571
|
+
];
|
|
1572
|
+
function registerAdminPayoutsCommand(admin) {
|
|
1573
|
+
const payouts = admin.command("payouts").description("Admin payout management");
|
|
1574
|
+
payouts.command("list").description("List all payout requests").action(async function() {
|
|
1575
|
+
try {
|
|
1576
|
+
ensureAdminAuth();
|
|
1577
|
+
const data = await fetchApi("GET", "/admin/payout-requests", { adminAuth: true });
|
|
1578
|
+
formatOutput(data, getOutputOpts(this), PAYOUT_COLUMNS2);
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
handleError(err);
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1583
|
+
payouts.command("update <id>").description("Approve, reject, or complete a payout request").requiredOption("--action <approve|reject|complete>", "Action to perform").action(async function(id) {
|
|
1584
|
+
try {
|
|
1585
|
+
ensureAdminAuth();
|
|
1586
|
+
validateId(id, "payout request ID");
|
|
1587
|
+
const { action } = this.opts();
|
|
1588
|
+
const validActions = ["approve", "reject", "complete"];
|
|
1589
|
+
if (!validActions.includes(action)) {
|
|
1590
|
+
throw new CliError(`--action must be one of: ${validActions.join(", ")}`);
|
|
1591
|
+
}
|
|
1592
|
+
const ok = await confirm(`${action} payout request ${id}?`);
|
|
1593
|
+
if (!ok) {
|
|
1594
|
+
console.log("Aborted.");
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const data = await fetchApi("PATCH", `/admin/payout-requests/${id}`, {
|
|
1598
|
+
adminAuth: true,
|
|
1599
|
+
body: { action }
|
|
1600
|
+
});
|
|
1601
|
+
printSuccess(`Payout request ${id} updated: ${action}.`);
|
|
1602
|
+
formatOutput(data, getOutputOpts(this));
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
handleError(err);
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
payouts.command("export").description("Export payouts as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").option("--status <status>", "Filter by status").action(async function() {
|
|
1608
|
+
try {
|
|
1609
|
+
ensureAdminAuth();
|
|
1610
|
+
const opts = this.opts();
|
|
1611
|
+
const params = {};
|
|
1612
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1613
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1614
|
+
if (opts.status) params["status"] = opts.status;
|
|
1615
|
+
const csv = await fetchApi("GET", "/admin/payouts/export", { adminAuth: true, params });
|
|
1616
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
handleError(err);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
payouts.command("export-detailed").description("Export detailed payouts as CSV").option("--status <status>", "Filter by status").action(async function() {
|
|
1622
|
+
try {
|
|
1623
|
+
ensureAdminAuth();
|
|
1624
|
+
const opts = this.opts();
|
|
1625
|
+
const params = {};
|
|
1626
|
+
if (opts.status) params["status"] = opts.status;
|
|
1627
|
+
const csv = await fetchApi("GET", "/admin/payouts/export-detailed", { adminAuth: true, params });
|
|
1628
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
handleError(err);
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
payouts.command("request-csv <id>").description("Export a payout request as CSV").action(async function(id) {
|
|
1634
|
+
try {
|
|
1635
|
+
ensureAdminAuth();
|
|
1636
|
+
validateId(id, "payout request ID");
|
|
1637
|
+
const csv = await fetchApi("GET", `/admin/payout-requests/${id}/export-csv`, { adminAuth: true });
|
|
1638
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1639
|
+
} catch (err) {
|
|
1640
|
+
handleError(err);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// src/commands/admin/admin-loyalty.ts
|
|
1646
|
+
import * as readline7 from "readline/promises";
|
|
1647
|
+
async function confirm2(message) {
|
|
1648
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
1649
|
+
try {
|
|
1650
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
1651
|
+
return answer.trim().toLowerCase() === "y";
|
|
1652
|
+
} finally {
|
|
1653
|
+
rl.close();
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function registerAdminLoyaltyCommand(admin) {
|
|
1657
|
+
const loyalty = admin.command("loyalty").description("Admin loyalty program management");
|
|
1658
|
+
loyalty.command("stats").description("Get loyalty program statistics").action(async function() {
|
|
1659
|
+
try {
|
|
1660
|
+
ensureAdminAuth();
|
|
1661
|
+
const data = await fetchApi("GET", "/admin/loyalty/stats", {
|
|
1662
|
+
adminAuth: true
|
|
1663
|
+
});
|
|
1664
|
+
formatOutput(data, getOutputOpts(this));
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
handleError(err);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
loyalty.command("blast-email").description("Send loyalty blast email to eligible users").option("--test", "Run in test mode (no confirmation required)").option("--test-email <email>", "Send test email to this address").option("--batch-size <n>", "Number of emails per batch").action(async function() {
|
|
1670
|
+
try {
|
|
1671
|
+
ensureAdminAuth();
|
|
1672
|
+
const opts = this.opts();
|
|
1673
|
+
const isTest = opts.test === true;
|
|
1674
|
+
if (!isTest) {
|
|
1675
|
+
const ok = await confirm2("Send loyalty blast email to all eligible users?");
|
|
1676
|
+
if (!ok) {
|
|
1677
|
+
console.log("Aborted.");
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const body = { testMode: isTest };
|
|
1682
|
+
if (opts.testEmail) body["testEmail"] = opts.testEmail;
|
|
1683
|
+
if (opts.batchSize) body["batchSize"] = Number(opts.batchSize);
|
|
1684
|
+
const data = await fetchApi("POST", "/admin/loyalty/blast-email", {
|
|
1685
|
+
adminAuth: true,
|
|
1686
|
+
body
|
|
1687
|
+
});
|
|
1688
|
+
printSuccess("Loyalty blast email queued.");
|
|
1689
|
+
formatOutput(data, getOutputOpts(this));
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
handleError(err);
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/commands/admin/admin-blog.ts
|
|
1697
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1698
|
+
function resolveContent(opts) {
|
|
1699
|
+
if (opts.file) {
|
|
1700
|
+
try {
|
|
1701
|
+
return readFileSync3(opts.file, "utf-8");
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1704
|
+
throw new CliError(`Cannot read file "${opts.file}": ${msg}`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return opts.content;
|
|
1708
|
+
}
|
|
1709
|
+
function registerAdminBlogCommand(admin) {
|
|
1710
|
+
const blog = admin.command("blog").description("Admin blog management");
|
|
1711
|
+
blog.command("create").description("Create a new blog article").requiredOption("--title <title>", "Article title").option("--content <content>", "Article content (inline)").option("--file <filepath>", "Read content from file").action(async function() {
|
|
1712
|
+
try {
|
|
1713
|
+
ensureAdminAuth();
|
|
1714
|
+
const opts = this.opts();
|
|
1715
|
+
const content = resolveContent(opts);
|
|
1716
|
+
const data = await fetchApi("POST", "/blog/articles", {
|
|
1717
|
+
adminAuth: true,
|
|
1718
|
+
body: { title: opts.title, content }
|
|
1719
|
+
});
|
|
1720
|
+
printSuccess("Article created.");
|
|
1721
|
+
formatOutput(data, getOutputOpts(this));
|
|
1722
|
+
} catch (err) {
|
|
1723
|
+
handleError(err);
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
blog.command("update <id>").description("Update an existing blog article").option("--title <title>", "New title").option("--content <content>", "New content (inline)").option("--file <filepath>", "Read new content from file").action(async function(id) {
|
|
1727
|
+
try {
|
|
1728
|
+
ensureAdminAuth();
|
|
1729
|
+
validateId(id, "article ID");
|
|
1730
|
+
const opts = this.opts();
|
|
1731
|
+
const content = resolveContent(opts);
|
|
1732
|
+
const body = {};
|
|
1733
|
+
if (opts.title) body["title"] = opts.title;
|
|
1734
|
+
if (content !== void 0) body["content"] = content;
|
|
1735
|
+
const data = await fetchApi("PATCH", `/blog/articles/${id}`, {
|
|
1736
|
+
adminAuth: true,
|
|
1737
|
+
body
|
|
1738
|
+
});
|
|
1739
|
+
printSuccess(`Article ${id} updated.`);
|
|
1740
|
+
formatOutput(data, getOutputOpts(this));
|
|
1741
|
+
} catch (err) {
|
|
1742
|
+
handleError(err);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
blog.command("delete <id>").description("Delete a blog article").action(async function(id) {
|
|
1746
|
+
try {
|
|
1747
|
+
ensureAdminAuth();
|
|
1748
|
+
validateId(id, "article ID");
|
|
1749
|
+
const ok = await confirm(`Delete article ${id}? This cannot be undone.`);
|
|
1750
|
+
if (!ok) {
|
|
1751
|
+
console.log("Aborted.");
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
await fetchApi("DELETE", `/blog/articles/${id}`, { adminAuth: true });
|
|
1755
|
+
printSuccess(`Article ${id} deleted.`);
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
handleError(err);
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
blog.command("publish <id>").description("Publish a blog article").action(async function(id) {
|
|
1761
|
+
try {
|
|
1762
|
+
ensureAdminAuth();
|
|
1763
|
+
validateId(id, "article ID");
|
|
1764
|
+
const ok = await confirm(`Publish article ${id}?`);
|
|
1765
|
+
if (!ok) {
|
|
1766
|
+
console.log("Aborted.");
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
const data = await fetchApi("POST", `/blog/articles/${id}/publish`, {
|
|
1770
|
+
adminAuth: true
|
|
1771
|
+
});
|
|
1772
|
+
printSuccess(`Article ${id} published.`);
|
|
1773
|
+
formatOutput(data, getOutputOpts(this));
|
|
1774
|
+
} catch (err) {
|
|
1775
|
+
handleError(err);
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
blog.command("ai-generate").description("Generate a blog article using AI").requiredOption("--prompt <text>", "Generation prompt").action(async function() {
|
|
1779
|
+
try {
|
|
1780
|
+
ensureAdminAuth();
|
|
1781
|
+
const { prompt } = this.opts();
|
|
1782
|
+
const data = await fetchApi("POST", "/blog/ai/generate", {
|
|
1783
|
+
adminAuth: true,
|
|
1784
|
+
body: { prompt }
|
|
1785
|
+
});
|
|
1786
|
+
formatOutput(data, getOutputOpts(this));
|
|
1787
|
+
} catch (err) {
|
|
1788
|
+
handleError(err);
|
|
1789
|
+
}
|
|
1790
|
+
});
|
|
1791
|
+
blog.command("ai-transform").description("Transform text using AI").requiredOption("--text <text>", "Source text to transform").requiredOption("--instruction <text>", "Transformation instruction").action(async function() {
|
|
1792
|
+
try {
|
|
1793
|
+
ensureAdminAuth();
|
|
1794
|
+
const { text, instruction } = this.opts();
|
|
1795
|
+
const data = await fetchApi("POST", "/blog/ai/transform", {
|
|
1796
|
+
adminAuth: true,
|
|
1797
|
+
body: { text, instruction }
|
|
1798
|
+
});
|
|
1799
|
+
formatOutput(data, getOutputOpts(this));
|
|
1800
|
+
} catch (err) {
|
|
1801
|
+
handleError(err);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
blog.command("generate-metadata <id>").description("Generate SEO metadata for an article").action(async function(id) {
|
|
1805
|
+
try {
|
|
1806
|
+
ensureAdminAuth();
|
|
1807
|
+
validateId(id, "article ID");
|
|
1808
|
+
const data = await fetchApi("POST", "/blog/generate-metadata", {
|
|
1809
|
+
adminAuth: true,
|
|
1810
|
+
body: { articleId: id }
|
|
1811
|
+
});
|
|
1812
|
+
formatOutput(data, getOutputOpts(this));
|
|
1813
|
+
} catch (err) {
|
|
1814
|
+
handleError(err);
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
blog.command("upload-url").description("Get a presigned URL for blog asset upload").requiredOption("--filename <name>", "File name to upload").requiredOption("--content-type <type>", "MIME type of the file").action(async function() {
|
|
1818
|
+
try {
|
|
1819
|
+
ensureAdminAuth();
|
|
1820
|
+
const opts = this.opts();
|
|
1821
|
+
const data = await fetchApi("POST", "/blog/upload/presigned-url", {
|
|
1822
|
+
adminAuth: true,
|
|
1823
|
+
body: { filename: opts.filename, contentType: opts.contentType }
|
|
1824
|
+
});
|
|
1825
|
+
formatOutput(data, getOutputOpts(this));
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
handleError(err);
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
blog.command("export").description("Export blog articles as CSV").option("--start <date>", "Start date (ISO 8601)").option("--end <date>", "End date (ISO 8601)").option("--status <status>", "Filter by status").action(async function() {
|
|
1831
|
+
try {
|
|
1832
|
+
ensureAdminAuth();
|
|
1833
|
+
const opts = this.opts();
|
|
1834
|
+
const params = {};
|
|
1835
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
1836
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
1837
|
+
if (opts.status) params["status"] = opts.status;
|
|
1838
|
+
const csv = await fetchApi("GET", "/admin/blog/export", { adminAuth: true, params });
|
|
1839
|
+
console.log(typeof csv === "string" ? csv : JSON.stringify(csv));
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
handleError(err);
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/commands/admin/admin-keys.ts
|
|
1847
|
+
import chalk5 from "chalk";
|
|
1848
|
+
var KEY_COLUMNS = [
|
|
1849
|
+
{ key: "id", header: "ID", width: 12 },
|
|
1850
|
+
{ key: "name", header: "Name", width: 20 },
|
|
1851
|
+
{ key: "scopes", header: "Scopes", width: 30 },
|
|
1852
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
1853
|
+
];
|
|
1854
|
+
function registerAdminKeysCommand(admin) {
|
|
1855
|
+
const keys = admin.command("keys").description("Admin API key management");
|
|
1856
|
+
keys.command("list").description("List all API keys").option("--limit <n>", "Number of results", "50").option("--offset <n>", "Offset for pagination", "0").action(async function() {
|
|
1857
|
+
try {
|
|
1858
|
+
ensureAdminAuth();
|
|
1859
|
+
const opts = this.opts();
|
|
1860
|
+
const limit = Number(opts.limit);
|
|
1861
|
+
const offset = Number(opts.offset);
|
|
1862
|
+
if (Number.isNaN(limit) || limit < 1) throw new Error("--limit must be a positive number");
|
|
1863
|
+
if (Number.isNaN(offset) || offset < 0) throw new Error("--offset must be a non-negative number");
|
|
1864
|
+
const data = await fetchApi("GET", "/admin/keys", {
|
|
1865
|
+
adminAuth: true,
|
|
1866
|
+
params: { limit: String(limit), offset: String(offset) }
|
|
1867
|
+
});
|
|
1868
|
+
formatOutput(data, getOutputOpts(this), KEY_COLUMNS);
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
handleError(err);
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
keys.command("create").description("Create a new API key").requiredOption("--name <name>", "Key name").requiredOption("--scopes <scopes>", "Comma-separated list of scopes").action(async function() {
|
|
1874
|
+
try {
|
|
1875
|
+
ensureAdminAuth();
|
|
1876
|
+
const { name, scopes } = this.opts();
|
|
1877
|
+
const data = await fetchApi("POST", "/admin/keys", {
|
|
1878
|
+
adminAuth: true,
|
|
1879
|
+
body: { name, scopes: scopes.split(",").map((s) => s.trim()) }
|
|
1880
|
+
});
|
|
1881
|
+
printSuccess(`API key "${name}" created.`);
|
|
1882
|
+
formatOutput(data, getOutputOpts(this));
|
|
1883
|
+
} catch (err) {
|
|
1884
|
+
handleError(err);
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1887
|
+
keys.command("validate").description("Validate the current admin API key").action(async function() {
|
|
1888
|
+
try {
|
|
1889
|
+
ensureAdminAuth();
|
|
1890
|
+
const data = await fetchApi("POST", "/admin/keys/validate", {
|
|
1891
|
+
adminAuth: true
|
|
1892
|
+
});
|
|
1893
|
+
printSuccess("Admin API key is valid.");
|
|
1894
|
+
formatOutput(data, getOutputOpts(this));
|
|
1895
|
+
} catch (err) {
|
|
1896
|
+
handleError(err);
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
keys.command("revoke <id>").description("Revoke an API key").action(async function(id) {
|
|
1900
|
+
try {
|
|
1901
|
+
ensureAdminAuth();
|
|
1902
|
+
validateId(id, "key ID");
|
|
1903
|
+
const ok = await confirm(`Revoke API key ${id}?`);
|
|
1904
|
+
if (!ok) {
|
|
1905
|
+
console.log("Aborted.");
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
const data = await fetchApi("POST", `/admin/keys/${id}/revoke`, {
|
|
1909
|
+
adminAuth: true
|
|
1910
|
+
});
|
|
1911
|
+
printSuccess(`API key ${id} revoked.`);
|
|
1912
|
+
formatOutput(data, getOutputOpts(this));
|
|
1913
|
+
} catch (err) {
|
|
1914
|
+
handleError(err);
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
keys.command("rotate <id>").description("Rotate an API key (generates a new key value)").action(async function(id) {
|
|
1918
|
+
try {
|
|
1919
|
+
ensureAdminAuth();
|
|
1920
|
+
validateId(id, "key ID");
|
|
1921
|
+
const ok = await confirm(`Rotate API key ${id}? The old key will be invalidated.`);
|
|
1922
|
+
if (!ok) {
|
|
1923
|
+
console.log("Aborted.");
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
const data = await fetchApi("POST", `/admin/keys/${id}/rotate`, {
|
|
1927
|
+
adminAuth: true
|
|
1928
|
+
});
|
|
1929
|
+
printSuccess(`API key ${id} rotated.`);
|
|
1930
|
+
if (data && typeof data === "object" && "key" in data && data.key) {
|
|
1931
|
+
console.log(chalk5.bold.green(`New key: ${data.key}`));
|
|
1932
|
+
console.log(chalk5.yellow("Store this key securely \u2014 it will not be shown again."));
|
|
1933
|
+
} else {
|
|
1934
|
+
formatOutput(data, getOutputOpts(this));
|
|
1935
|
+
}
|
|
1936
|
+
} catch (err) {
|
|
1937
|
+
handleError(err);
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/commands/admin/admin-invites.ts
|
|
1943
|
+
var INVITE_COLUMNS = [
|
|
1944
|
+
{ key: "id", header: "ID", width: 12 },
|
|
1945
|
+
{ key: "email", header: "Email", width: 28 },
|
|
1946
|
+
{ key: "status", header: "Status", width: 10 },
|
|
1947
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
1948
|
+
];
|
|
1949
|
+
function registerAdminInvitesCommand(admin) {
|
|
1950
|
+
const invites = admin.command("invites").description("Admin invite management");
|
|
1951
|
+
invites.command("list").description("List all invites").action(async function() {
|
|
1952
|
+
try {
|
|
1953
|
+
ensureAdminAuth();
|
|
1954
|
+
const data = await fetchApi("GET", "/admin/invites", {
|
|
1955
|
+
adminAuth: true
|
|
1956
|
+
});
|
|
1957
|
+
formatOutput(data, getOutputOpts(this), INVITE_COLUMNS);
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
handleError(err);
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1962
|
+
invites.command("create").description("Create a new invite").requiredOption("--email <email>", "Email address to invite").action(async function() {
|
|
1963
|
+
try {
|
|
1964
|
+
ensureAdminAuth();
|
|
1965
|
+
const { email } = this.opts();
|
|
1966
|
+
const data = await fetchApi("POST", "/admin/invites", {
|
|
1967
|
+
adminAuth: true,
|
|
1968
|
+
body: { email }
|
|
1969
|
+
});
|
|
1970
|
+
printSuccess(`Invite sent to ${email}.`);
|
|
1971
|
+
formatOutput(data, getOutputOpts(this));
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
handleError(err);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
invites.command("get <id>").description("Get invite details").action(async function(id) {
|
|
1977
|
+
try {
|
|
1978
|
+
ensureAdminAuth();
|
|
1979
|
+
validateId(id, "invite ID");
|
|
1980
|
+
const data = await fetchApi("GET", `/admin/invites/${id}`, {
|
|
1981
|
+
adminAuth: true
|
|
1982
|
+
});
|
|
1983
|
+
formatOutput(data, getOutputOpts(this));
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
handleError(err);
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
invites.command("delete <id>").description("Delete an invite").action(async function(id) {
|
|
1989
|
+
try {
|
|
1990
|
+
ensureAdminAuth();
|
|
1991
|
+
validateId(id, "invite ID");
|
|
1992
|
+
const ok = await confirm(`Delete invite ${id}?`);
|
|
1993
|
+
if (!ok) {
|
|
1994
|
+
console.log("Aborted.");
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const data = await fetchApi("DELETE", `/admin/invites/${id}`, {
|
|
1998
|
+
adminAuth: true
|
|
1999
|
+
});
|
|
2000
|
+
printSuccess(`Invite ${id} deleted.`);
|
|
2001
|
+
formatOutput(data, getOutputOpts(this));
|
|
2002
|
+
} catch (err) {
|
|
2003
|
+
handleError(err);
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
invites.command("resend <id>").description("Resend an invite email").action(async function(id) {
|
|
2007
|
+
try {
|
|
2008
|
+
ensureAdminAuth();
|
|
2009
|
+
validateId(id, "invite ID");
|
|
2010
|
+
const ok = await confirm(`Resend invite ${id}?`);
|
|
2011
|
+
if (!ok) {
|
|
2012
|
+
console.log("Aborted.");
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
const data = await fetchApi("POST", `/admin/invites/${id}/resend`, {
|
|
2016
|
+
adminAuth: true
|
|
2017
|
+
});
|
|
2018
|
+
printSuccess(`Invite ${id} resent.`);
|
|
2019
|
+
formatOutput(data, getOutputOpts(this));
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
handleError(err);
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
invites.command("accept").description("Accept an invite (public endpoint)").requiredOption("--token <token>", "Invite token").option("--name <name>", "Display name (optional)").action(async function() {
|
|
2025
|
+
try {
|
|
2026
|
+
const { token, name } = this.opts();
|
|
2027
|
+
const body = { token };
|
|
2028
|
+
if (name) body["name"] = name;
|
|
2029
|
+
const data = await fetchApi("POST", "/admin/invites/accept", {
|
|
2030
|
+
noAuth: true,
|
|
2031
|
+
body
|
|
2032
|
+
});
|
|
2033
|
+
printSuccess("Invite accepted.");
|
|
2034
|
+
formatOutput(data, getOutputOpts(this));
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
handleError(err);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
invites.command("validate").description("Validate an invite token (public endpoint)").requiredOption("--token <token>", "Invite token").action(async function() {
|
|
2040
|
+
try {
|
|
2041
|
+
const { token } = this.opts();
|
|
2042
|
+
const data = await fetchApi("GET", "/admin/invites/validate", {
|
|
2043
|
+
noAuth: true,
|
|
2044
|
+
params: { token }
|
|
2045
|
+
});
|
|
2046
|
+
formatOutput(data, getOutputOpts(this));
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
handleError(err);
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// src/commands/admin/admin-admins.ts
|
|
2054
|
+
function registerAdminAdminsCommand(admin) {
|
|
2055
|
+
const admins = admin.command("admins").description("Admin role management");
|
|
2056
|
+
admins.command("list").description("List all admins").action(async function() {
|
|
2057
|
+
try {
|
|
2058
|
+
ensureAdminAuth();
|
|
2059
|
+
const data = await fetchApi("GET", "/admin/admins", {
|
|
2060
|
+
adminAuth: true
|
|
2061
|
+
});
|
|
2062
|
+
formatOutput(data, getOutputOpts(this));
|
|
2063
|
+
} catch (err) {
|
|
2064
|
+
handleError(err);
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
admins.command("get <id>").description("Get admin details").action(async function(id) {
|
|
2068
|
+
try {
|
|
2069
|
+
ensureAdminAuth();
|
|
2070
|
+
validateId(id, "admin ID");
|
|
2071
|
+
const data = await fetchApi("GET", `/admin/admins/${id}`, {
|
|
2072
|
+
adminAuth: true
|
|
2073
|
+
});
|
|
2074
|
+
formatOutput(data, getOutputOpts(this));
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
handleError(err);
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
admins.command("promote <id>").description("Promote a user to admin").option("--grant-all", "Grant all permissions").action(async function(id) {
|
|
2080
|
+
try {
|
|
2081
|
+
ensureAdminAuth();
|
|
2082
|
+
validateId(id, "user ID");
|
|
2083
|
+
const opts = this.opts();
|
|
2084
|
+
const ok = await confirm(`Promote user ${id} to admin?`);
|
|
2085
|
+
if (!ok) {
|
|
2086
|
+
console.log("Aborted.");
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
const data = await fetchApi("POST", `/admin/admins/${id}/promote`, {
|
|
2090
|
+
adminAuth: true,
|
|
2091
|
+
body: { grantAllPermissions: opts.grantAll || false }
|
|
2092
|
+
});
|
|
2093
|
+
printSuccess(`User ${id} promoted to admin.`);
|
|
2094
|
+
formatOutput(data, getOutputOpts(this));
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
handleError(err);
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
admins.command("demote <id>").description("Remove admin access from a user").action(async function(id) {
|
|
2100
|
+
try {
|
|
2101
|
+
ensureAdminAuth();
|
|
2102
|
+
validateId(id, "admin ID");
|
|
2103
|
+
const ok = await confirm(
|
|
2104
|
+
`Demote admin ${id}? WARNING: this removes all admin access.`
|
|
2105
|
+
);
|
|
2106
|
+
if (!ok) {
|
|
2107
|
+
console.log("Aborted.");
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
const data = await fetchApi("DELETE", `/admin/admins/${id}`, {
|
|
2111
|
+
adminAuth: true
|
|
2112
|
+
});
|
|
2113
|
+
printSuccess(`Admin ${id} demoted.`);
|
|
2114
|
+
formatOutput(data, getOutputOpts(this));
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
handleError(err);
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
admins.command("permissions <id>").description("Update admin permissions").requiredOption("--permissions <json>", "JSON array of permission strings").action(async function(id) {
|
|
2120
|
+
try {
|
|
2121
|
+
ensureAdminAuth();
|
|
2122
|
+
validateId(id, "admin ID");
|
|
2123
|
+
const { permissions: permissionsJson } = this.opts();
|
|
2124
|
+
let permissions;
|
|
2125
|
+
try {
|
|
2126
|
+
permissions = JSON.parse(permissionsJson);
|
|
2127
|
+
} catch {
|
|
2128
|
+
throw new CliError(`--permissions must be valid JSON (e.g. '["read","write"]')`);
|
|
2129
|
+
}
|
|
2130
|
+
if (!Array.isArray(permissions)) {
|
|
2131
|
+
throw new CliError("--permissions must be a JSON array");
|
|
2132
|
+
}
|
|
2133
|
+
const data = await fetchApi("PATCH", `/admin/admins/${id}/permissions`, {
|
|
2134
|
+
adminAuth: true,
|
|
2135
|
+
body: { permissions }
|
|
2136
|
+
});
|
|
2137
|
+
printSuccess(`Permissions updated for admin ${id}.`);
|
|
2138
|
+
formatOutput(data, getOutputOpts(this));
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
handleError(err);
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// src/commands/admin/admin-discounts.ts
|
|
2146
|
+
var DISCOUNT_COLUMNS = [
|
|
2147
|
+
{ key: "id", header: "ID", width: 12 },
|
|
2148
|
+
{ key: "code", header: "Code", width: 20 },
|
|
2149
|
+
{ key: "type", header: "Type", width: 12 },
|
|
2150
|
+
{ key: "amount", header: "Amount", width: 10 },
|
|
2151
|
+
{ key: "status", header: "Status", width: 10 },
|
|
2152
|
+
{ key: "createdAt", header: "Created", width: 22 }
|
|
2153
|
+
];
|
|
2154
|
+
function buildDiscountBody(opts) {
|
|
2155
|
+
const body = {};
|
|
2156
|
+
if (opts.code !== void 0) body["code"] = opts.code;
|
|
2157
|
+
if (opts.type !== void 0) {
|
|
2158
|
+
if (opts.type !== "percentage" && opts.type !== "fixed") {
|
|
2159
|
+
throw new CliError("--type must be 'percentage' or 'fixed'");
|
|
2160
|
+
}
|
|
2161
|
+
body["type"] = opts.type;
|
|
2162
|
+
}
|
|
2163
|
+
if (opts.amount !== void 0) {
|
|
2164
|
+
const val = Number(opts.amount);
|
|
2165
|
+
if (Number.isNaN(val) || val < 0) throw new CliError("--amount must be a non-negative number (cents)");
|
|
2166
|
+
body["amount"] = val;
|
|
2167
|
+
}
|
|
2168
|
+
if (opts.basisPoints !== void 0) {
|
|
2169
|
+
const val = Number(opts.basisPoints);
|
|
2170
|
+
if (Number.isNaN(val) || val < 0) throw new CliError("--basis-points must be a non-negative number");
|
|
2171
|
+
body["basisPoints"] = val;
|
|
2172
|
+
}
|
|
2173
|
+
if (opts.maxRedemptions !== void 0) {
|
|
2174
|
+
const val = Number(opts.maxRedemptions);
|
|
2175
|
+
if (Number.isNaN(val) || val < 1) throw new CliError("--max-redemptions must be a positive integer");
|
|
2176
|
+
body["maxRedemptions"] = val;
|
|
2177
|
+
}
|
|
2178
|
+
if (opts.starts !== void 0) body["startsAt"] = opts.starts;
|
|
2179
|
+
if (opts.ends !== void 0) body["endsAt"] = opts.ends;
|
|
2180
|
+
return body;
|
|
2181
|
+
}
|
|
2182
|
+
function registerAdminDiscountsCommand(admin) {
|
|
2183
|
+
const discounts = admin.command("discounts").description("Discount management");
|
|
2184
|
+
discounts.command("list").description("List discounts").option("--query <search>", "Search query").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "50").action(async function() {
|
|
2185
|
+
try {
|
|
2186
|
+
ensureAdminAuth();
|
|
2187
|
+
const opts = this.opts();
|
|
2188
|
+
const page = Number(opts.page);
|
|
2189
|
+
const limit = Number(opts.limit);
|
|
2190
|
+
if (Number.isNaN(page) || page < 1) throw new CliError("--page must be a positive integer");
|
|
2191
|
+
if (Number.isNaN(limit) || limit < 1) throw new CliError("--limit must be a positive integer");
|
|
2192
|
+
const params = {
|
|
2193
|
+
page: String(page),
|
|
2194
|
+
limit: String(limit)
|
|
2195
|
+
};
|
|
2196
|
+
if (opts.query) params["query"] = opts.query;
|
|
2197
|
+
const data = await fetchApi("GET", "/admin/discounts", {
|
|
2198
|
+
adminAuth: true,
|
|
2199
|
+
params
|
|
2200
|
+
});
|
|
2201
|
+
formatOutput(data, getOutputOpts(this), DISCOUNT_COLUMNS);
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
handleError(err);
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
2206
|
+
discounts.command("create").description("Create a discount code").requiredOption("--code <code>", "Discount code").requiredOption("--type <percentage|fixed>", "Discount type").option("--amount <n>", "Amount in cents (fixed type)").option("--basis-points <n>", "Basis points (percentage type)").option("--max-redemptions <n>", "Maximum number of redemptions").option("--starts <date>", "Start date (ISO 8601)").option("--ends <date>", "End date (ISO 8601)").action(async function() {
|
|
2207
|
+
try {
|
|
2208
|
+
ensureAdminAuth();
|
|
2209
|
+
const body = buildDiscountBody(this.opts());
|
|
2210
|
+
const data = await fetchApi("POST", "/admin/discounts", {
|
|
2211
|
+
adminAuth: true,
|
|
2212
|
+
body
|
|
2213
|
+
});
|
|
2214
|
+
printSuccess(`Discount "${body["code"]}" created.`);
|
|
2215
|
+
formatOutput(data, getOutputOpts(this));
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
handleError(err);
|
|
2218
|
+
}
|
|
2219
|
+
});
|
|
2220
|
+
discounts.command("update <id>").description("Update a discount code").option("--code <code>", "Discount code").option("--type <percentage|fixed>", "Discount type").option("--amount <n>", "Amount in cents (fixed type)").option("--basis-points <n>", "Basis points (percentage type)").option("--max-redemptions <n>", "Maximum number of redemptions").option("--starts <date>", "Start date (ISO 8601)").option("--ends <date>", "End date (ISO 8601)").action(async function(id) {
|
|
2221
|
+
try {
|
|
2222
|
+
ensureAdminAuth();
|
|
2223
|
+
validateId(id, "discount ID");
|
|
2224
|
+
const body = buildDiscountBody(this.opts());
|
|
2225
|
+
if (Object.keys(body).length === 0) {
|
|
2226
|
+
throw new CliError("At least one option must be provided to update.");
|
|
2227
|
+
}
|
|
2228
|
+
const data = await fetchApi("PATCH", `/admin/discounts/${id}`, {
|
|
2229
|
+
adminAuth: true,
|
|
2230
|
+
body
|
|
2231
|
+
});
|
|
2232
|
+
printSuccess(`Discount ${id} updated.`);
|
|
2233
|
+
formatOutput(data, getOutputOpts(this));
|
|
2234
|
+
} catch (err) {
|
|
2235
|
+
handleError(err);
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
discounts.command("delete <id>").description("Delete a discount code").action(async function(id) {
|
|
2239
|
+
try {
|
|
2240
|
+
ensureAdminAuth();
|
|
2241
|
+
validateId(id, "discount ID");
|
|
2242
|
+
const ok = await confirm(`Delete discount ${id}?`);
|
|
2243
|
+
if (!ok) {
|
|
2244
|
+
console.log("Aborted.");
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const data = await fetchApi("DELETE", `/admin/discounts/${id}`, {
|
|
2248
|
+
adminAuth: true
|
|
2249
|
+
});
|
|
2250
|
+
printSuccess(`Discount ${id} deleted.`);
|
|
2251
|
+
formatOutput(data, getOutputOpts(this));
|
|
2252
|
+
} catch (err) {
|
|
2253
|
+
handleError(err);
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
discounts.command("export").description("Export discounts as CSV").option("--start <date>", "Start date filter (ISO 8601)").option("--end <date>", "End date filter (ISO 8601)").option("--status <all|active|expired>", "Filter by status", "all").action(async function() {
|
|
2257
|
+
try {
|
|
2258
|
+
ensureAdminAuth();
|
|
2259
|
+
const opts = this.opts();
|
|
2260
|
+
const params = { status: opts.status };
|
|
2261
|
+
if (opts.start) params["startDate"] = opts.start;
|
|
2262
|
+
if (opts.end) params["endDate"] = opts.end;
|
|
2263
|
+
const data = await fetchApi("GET", "/admin/discounts/export", {
|
|
2264
|
+
adminAuth: true,
|
|
2265
|
+
params
|
|
2266
|
+
});
|
|
2267
|
+
console.log(data);
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
handleError(err);
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// src/commands/admin/admin-tiers.ts
|
|
2275
|
+
var TIER_COLUMNS = [
|
|
2276
|
+
{ key: "id", header: "ID", width: 12 },
|
|
2277
|
+
{ key: "name", header: "Name", width: 16 },
|
|
2278
|
+
{ key: "displayName", header: "Display Name", width: 20 },
|
|
2279
|
+
{ key: "commissionRate", header: "Commission", width: 10 },
|
|
2280
|
+
{ key: "badgeColor", header: "Badge Color", width: 12 }
|
|
2281
|
+
];
|
|
2282
|
+
function registerAdminTiersCommand(admin) {
|
|
2283
|
+
const tiers = admin.command("tiers").description("Tier management");
|
|
2284
|
+
tiers.command("list").description("List manual referral tiers").action(async function() {
|
|
2285
|
+
try {
|
|
2286
|
+
ensureAdminAuth();
|
|
2287
|
+
const data = await fetchApi("GET", "/admin/tiers/manual", {
|
|
2288
|
+
adminAuth: true
|
|
2289
|
+
});
|
|
2290
|
+
formatOutput(data, getOutputOpts(this), TIER_COLUMNS);
|
|
2291
|
+
} catch (err) {
|
|
2292
|
+
handleError(err);
|
|
2293
|
+
}
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// src/commands/admin/index.ts
|
|
2298
|
+
function registerAdminCommand(program2) {
|
|
2299
|
+
const admin = program2.command("admin").description("Admin operations (requires admin API key)");
|
|
2300
|
+
registerAdminOrdersCommand(admin);
|
|
2301
|
+
registerAdminUsersCommand(admin);
|
|
2302
|
+
registerAdminReferralsCommand(admin);
|
|
2303
|
+
registerAdminRevenueCommand(admin);
|
|
2304
|
+
registerAdminPayoutsCommand(admin);
|
|
2305
|
+
registerAdminLoyaltyCommand(admin);
|
|
2306
|
+
registerAdminBlogCommand(admin);
|
|
2307
|
+
registerAdminKeysCommand(admin);
|
|
2308
|
+
registerAdminInvitesCommand(admin);
|
|
2309
|
+
registerAdminAdminsCommand(admin);
|
|
2310
|
+
registerAdminDiscountsCommand(admin);
|
|
2311
|
+
registerAdminTiersCommand(admin);
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// src/index.ts
|
|
2315
|
+
loadDotenvFiles();
|
|
2316
|
+
var program = new Command();
|
|
2317
|
+
program.name("ckweb").description("CLI for interacting with ClaudeKit.cc API").version("0.1.0");
|
|
2318
|
+
program.option("--json", "Output as JSON").option("--table", "Output as table").option("--quiet", "Minimal output");
|
|
2319
|
+
registerAuthCommand(program);
|
|
2320
|
+
registerHealthCommand(program);
|
|
2321
|
+
registerOrdersCommand(program);
|
|
2322
|
+
registerReferralsCommand(program);
|
|
2323
|
+
registerBlogCommand(program);
|
|
2324
|
+
registerAccountCommand(program);
|
|
2325
|
+
registerCheckoutCommand(program);
|
|
2326
|
+
registerGithubCommand(program);
|
|
2327
|
+
registerLoyaltyCommand(program);
|
|
2328
|
+
registerNewsletterCommand(program);
|
|
2329
|
+
registerPayoutCommand(program);
|
|
2330
|
+
registerPricingCommand(program);
|
|
2331
|
+
registerRefundsCommand(program);
|
|
2332
|
+
registerReleasesCommand(program);
|
|
2333
|
+
registerStatsCommand(program);
|
|
2334
|
+
registerWaitlistCommand(program);
|
|
2335
|
+
registerYoutubeCommand(program);
|
|
2336
|
+
registerWebCommand(program);
|
|
2337
|
+
registerSeoCommand(program);
|
|
2338
|
+
registerAdminCommand(program);
|
|
2339
|
+
program.addHelpCommand(true);
|
|
2340
|
+
try {
|
|
2341
|
+
await program.parseAsync(process.argv);
|
|
2342
|
+
} catch (err) {
|
|
2343
|
+
handleError(err);
|
|
2344
|
+
}
|
|
2345
|
+
//# sourceMappingURL=index.js.map
|