@jezweb/jezpress-cli 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli.js +15 -1153
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client.d.ts +29 -9
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +15 -2
  6. package/dist/client.js.map +1 -1
  7. package/dist/commands/auth.d.ts +7 -0
  8. package/dist/commands/auth.d.ts.map +1 -0
  9. package/dist/commands/auth.js +56 -0
  10. package/dist/commands/auth.js.map +1 -0
  11. package/dist/commands/docs.d.ts +7 -0
  12. package/dist/commands/docs.d.ts.map +1 -0
  13. package/dist/commands/docs.js +41 -0
  14. package/dist/commands/docs.js.map +1 -0
  15. package/dist/commands/plugins.d.ts +7 -0
  16. package/dist/commands/plugins.d.ts.map +1 -0
  17. package/dist/commands/plugins.js +468 -0
  18. package/dist/commands/plugins.js.map +1 -0
  19. package/dist/commands/sites/bulk.d.ts +7 -0
  20. package/dist/commands/sites/bulk.d.ts.map +1 -0
  21. package/dist/commands/sites/bulk.js +80 -0
  22. package/dist/commands/sites/bulk.js.map +1 -0
  23. package/dist/commands/sites/export.d.ts +7 -0
  24. package/dist/commands/sites/export.d.ts.map +1 -0
  25. package/dist/commands/sites/export.js +85 -0
  26. package/dist/commands/sites/export.js.map +1 -0
  27. package/dist/commands/sites/index.d.ts +7 -0
  28. package/dist/commands/sites/index.d.ts.map +1 -0
  29. package/dist/commands/sites/index.js +406 -0
  30. package/dist/commands/sites/index.js.map +1 -0
  31. package/dist/flare/detect.d.ts +18 -0
  32. package/dist/flare/detect.d.ts.map +1 -0
  33. package/dist/flare/detect.js +130 -0
  34. package/dist/flare/detect.js.map +1 -0
  35. package/dist/flare/html-to-content.d.ts +21 -0
  36. package/dist/flare/html-to-content.d.ts.map +1 -0
  37. package/dist/flare/html-to-content.js +233 -0
  38. package/dist/flare/html-to-content.js.map +1 -0
  39. package/dist/flare/index.d.ts +62 -0
  40. package/dist/flare/index.d.ts.map +1 -0
  41. package/dist/flare/index.js +566 -0
  42. package/dist/flare/index.js.map +1 -0
  43. package/dist/flare/types.d.ts +171 -0
  44. package/dist/flare/types.d.ts.map +1 -0
  45. package/dist/flare/types.js +6 -0
  46. package/dist/flare/types.js.map +1 -0
  47. package/dist/utils/plugin-headers.d.ts +14 -0
  48. package/dist/utils/plugin-headers.d.ts.map +1 -0
  49. package/dist/utils/plugin-headers.js +52 -0
  50. package/dist/utils/plugin-headers.js.map +1 -0
  51. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -1,1170 +1,32 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * JezPress CLI
4
+ * CLI for managing JezPress plugins on the update server
5
+ */
2
6
  import { Command } from 'commander';
3
- import { login, checkAuth } from './auth.js';
4
- import { deleteToken } from './config.js';
5
- import * as api from './client.js';
6
- import { existsSync, statSync, writeFileSync, readFileSync } from 'node:fs';
7
+ import { readFileSync } from 'node:fs';
7
8
  import { fileURLToPath } from 'node:url';
8
9
  import { dirname, join } from 'node:path';
9
- import AdmZip from 'adm-zip';
10
- import { PLATFORM_GUIDE } from './docs.js';
10
+ // Command modules
11
+ import { registerAuthCommands } from './commands/auth.js';
12
+ import { registerDocsCommand } from './commands/docs.js';
13
+ import { registerPluginCommands } from './commands/plugins.js';
14
+ import { registerSitesCommands } from './commands/sites/index.js';
11
15
  // Read version from package.json
12
16
  const __filename = fileURLToPath(import.meta.url);
13
17
  const __dirname = dirname(__filename);
14
18
  const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
15
19
  const VERSION = packageJson.version;
16
20
  const program = new Command();
17
- /**
18
- * Extract WordPress plugin headers from a ZIP file
19
- */
20
- function extractPluginHeaders(zipPath, slug) {
21
- let zip;
22
- try {
23
- zip = new AdmZip(zipPath);
24
- }
25
- catch {
26
- return null;
27
- }
28
- const entries = zip.getEntries();
29
- // Find main plugin file (slug/slug.php or slug.php)
30
- const mainFilePath1 = `${slug}/${slug}.php`;
31
- const mainFilePath2 = `${slug}.php`;
32
- const mainFileEntry = entries.find((e) => e.entryName.toLowerCase() === mainFilePath1.toLowerCase() ||
33
- e.entryName.toLowerCase() === mainFilePath2.toLowerCase());
34
- if (!mainFileEntry) {
35
- return null;
36
- }
37
- const content = mainFileEntry.getData().toString('utf-8');
38
- const headers = {};
39
- // Extract headers using regex
40
- const nameMatch = content.match(/Plugin Name:\s*(.+)/i);
41
- if (nameMatch)
42
- headers.name = nameMatch[1].trim();
43
- const versionMatch = content.match(/Version:\s*(\d+\.\d+(\.\d+)?)/i);
44
- if (versionMatch)
45
- headers.version = versionMatch[1];
46
- const authorMatch = content.match(/Author:\s*(.+)/i);
47
- if (authorMatch)
48
- headers.author = authorMatch[1].trim();
49
- const requiresWPMatch = content.match(/Requires at least:\s*(\d+\.\d+)/i);
50
- if (requiresWPMatch)
51
- headers.requires_wp = requiresWPMatch[1];
52
- const requiresPHPMatch = content.match(/Requires PHP:\s*(\d+\.\d+)/i);
53
- if (requiresPHPMatch)
54
- headers.requires_php = requiresPHPMatch[1];
55
- const testedWPMatch = content.match(/Tested up to:\s*(\d+\.\d+)/i);
56
- if (testedWPMatch)
57
- headers.tested_wp = testedWPMatch[1];
58
- const descMatch = content.match(/Description:\s*(.+)/i);
59
- if (descMatch)
60
- headers.description = descMatch[1].trim();
61
- return headers;
62
- }
63
21
  program
64
22
  .name('jezpress')
65
23
  .description('CLI for managing JezPress plugins on the update server')
66
24
  .version(VERSION);
67
- // ============================================================
68
- // Auth Commands
69
- // ============================================================
70
- program
71
- .command('login')
72
- .description('Log in with your Jezweb Google account')
73
- .action(async () => {
74
- try {
75
- console.log('Starting login...');
76
- const user = await login();
77
- console.log(`\nLogged in as ${user.name} (${user.email})`);
78
- }
79
- catch (error) {
80
- console.error('Login failed:', error.message);
81
- process.exit(1);
82
- }
83
- });
84
- program
85
- .command('logout')
86
- .description('Log out and remove stored token')
87
- .action(() => {
88
- deleteToken();
89
- console.log('Logged out successfully');
90
- });
91
- // ============================================================
92
- // Docs Command
93
- // ============================================================
94
- program
95
- .command('docs')
96
- .description('Display JezPress platform guide for developers and Claude Code')
97
- .option('--save [path]', 'Save guide to file (default: JEZPRESS.md)')
98
- .option('--claude', 'Generate project-level CLAUDE.md template')
99
- .action((options) => {
100
- if (options.claude) {
101
- const path = 'CLAUDE.md';
102
- const jezpressSection = `\n\n---\n\n${PLATFORM_GUIDE}`;
103
- if (existsSync(path)) {
104
- // Append to existing CLAUDE.md
105
- const existing = readFileSync(path, 'utf-8');
106
- if (existing.includes('# JezPress Platform Guide')) {
107
- console.log('CLAUDE.md already contains JezPress guide.');
108
- console.log('Remove the existing JezPress section first if you want to update it.');
109
- process.exit(1);
110
- }
111
- writeFileSync(path, existing + jezpressSection);
112
- console.log(`JezPress guide appended to existing CLAUDE.md`);
113
- }
114
- else {
115
- // Create new CLAUDE.md with full guide
116
- writeFileSync(path, PLATFORM_GUIDE);
117
- console.log(`CLAUDE.md created with full JezPress platform guide.`);
118
- }
119
- console.log('Your Claude Code now has full context about JezPress.');
120
- }
121
- else if (options.save !== undefined) {
122
- const path = typeof options.save === 'string' ? options.save : 'JEZPRESS.md';
123
- writeFileSync(path, PLATFORM_GUIDE);
124
- console.log(`Platform guide saved to: ${path}`);
125
- }
126
- else {
127
- console.log(PLATFORM_GUIDE);
128
- }
129
- });
130
- program
131
- .command('whoami')
132
- .description('Show current user info and owned plugins')
133
- .action(async () => {
134
- // First check local auth
135
- const auth = checkAuth();
136
- if (!auth.loggedIn) {
137
- console.error('Not logged in. Run `jezpress login` first.');
138
- process.exit(1);
139
- }
140
- try {
141
- const data = await api.whoami();
142
- console.log(`\nLogged in as: ${data.user.name}`);
143
- console.log(`Email: ${data.user.email}`);
144
- console.log(`Token expires in: ${auth.expiresIn}`);
145
- console.log(`\nPlugins owned (${data.plugins_owned.length}):`);
146
- if (data.plugins_owned.length === 0) {
147
- console.log(' (none)');
148
- }
149
- else {
150
- data.plugins_owned.forEach((slug) => {
151
- console.log(` - ${slug}`);
152
- });
153
- }
154
- }
155
- catch (error) {
156
- console.error('Error:', error.message);
157
- process.exit(1);
158
- }
159
- });
160
- // ============================================================
161
- // Plugin Commands
162
- // ============================================================
163
- const plugins = program
164
- .command('plugins')
165
- .description('Manage plugins');
166
- plugins
167
- .command('list')
168
- .description('List all plugins')
169
- .option('--mine', 'Only show plugins you own')
170
- .action(async (options) => {
171
- try {
172
- const pluginsList = await api.listPlugins();
173
- let filtered = pluginsList;
174
- if (options.mine) {
175
- filtered = pluginsList.filter((p) => p.is_owner);
176
- }
177
- console.log(`\nPlugins (${filtered.length}):\n`);
178
- if (filtered.length === 0) {
179
- console.log(' (none)');
180
- return;
181
- }
182
- filtered.forEach((p) => {
183
- const ownerMark = p.is_owner ? ' (yours)' : '';
184
- console.log(` ${p.slug} v${p.current_version}${ownerMark}`);
185
- console.log(` Name: ${p.name}`);
186
- console.log(` Updated: ${p.last_updated}`);
187
- console.log('');
188
- });
189
- }
190
- catch (error) {
191
- console.error('Error:', error.message);
192
- process.exit(1);
193
- }
194
- });
195
- plugins
196
- .command('get <slug>')
197
- .description('Get plugin details')
198
- .action(async (slug) => {
199
- try {
200
- const data = await api.getPlugin(slug);
201
- const p = data.plugin;
202
- console.log(`\n${p.name} (${p.slug})\n`);
203
- console.log(`Version: ${p.current_version}`);
204
- console.log(`Type: ${p.type}`);
205
- console.log(`Author: ${p.author}`);
206
- console.log(`Requires PHP: ${p.requires_php}`);
207
- console.log(`Requires WP: ${p.requires_wp}`);
208
- console.log(`Tested WP: ${p.tested_wp}`);
209
- console.log(`License required: ${p.requires_license ? 'Yes' : 'No'}`);
210
- console.log(`Last updated: ${p.last_updated}`);
211
- console.log(`Owner: ${p.owner_email || 'unassigned'}`);
212
- console.log(`You own this: ${p.is_owner ? 'Yes' : 'No'}`);
213
- console.log(`\nAvailable versions: ${data.available_versions.join(', ') || 'none'}`);
214
- console.log(`Total downloads: ${data.download_stats?.total || 0}`);
215
- }
216
- catch (error) {
217
- console.error('Error:', error.message);
218
- process.exit(1);
219
- }
220
- });
221
- plugins
222
- .command('create <slug>')
223
- .description('Create a new plugin (you become the owner)')
224
- .requiredOption('--name <name>', 'Plugin display name')
225
- .option('-v, --ver <version>', 'Initial version', '1.0.0')
226
- .option('--type <type>', 'Type: plugin or theme', 'plugin')
227
- .option('--no-license', 'Plugin does not require license')
228
- .option('--author <author>', 'Author name', 'Jezweb')
229
- .action(async (slug, options) => {
230
- try {
231
- const plugin = await api.createPlugin({
232
- slug,
233
- name: options.name,
234
- version: options.ver,
235
- type: options.type,
236
- requiresLicense: options.license !== false,
237
- author: options.author,
238
- });
239
- console.log(`\nPlugin created: ${plugin.slug}`);
240
- console.log(`Name: ${plugin.name}`);
241
- console.log(`Version: ${plugin.current_version}`);
242
- console.log(`Owner: ${plugin.owner_email}`);
243
- console.log('\nNext: Upload a version with `jezpress plugins upload`');
244
- }
245
- catch (error) {
246
- console.error('Error:', error.message);
247
- process.exit(1);
248
- }
249
- });
250
- plugins
251
- .command('update <slug>')
252
- .description('Update plugin metadata (owner only)')
253
- .option('--name <name>', 'Update display name')
254
- .option('--tested-wp <version>', 'Update tested WP version')
255
- .option('--requires-wp <version>', 'Update minimum WP version')
256
- .option('--requires-php <version>', 'Update minimum PHP version')
257
- .action(async (slug, options) => {
258
- try {
259
- const updates = {};
260
- if (options.name)
261
- updates.name = options.name;
262
- if (options.testedWp)
263
- updates.tested_wp = options.testedWp;
264
- if (options.requiresWp)
265
- updates.requires_wp = options.requiresWp;
266
- if (options.requiresPhp)
267
- updates.requires_php = options.requiresPhp;
268
- if (Object.keys(updates).length === 0) {
269
- console.error('No updates provided. Use --name, --tested-wp, etc.');
270
- process.exit(1);
271
- }
272
- const plugin = await api.updatePlugin(slug, updates);
273
- console.log(`\nPlugin updated: ${plugin.slug}`);
274
- console.log(`Name: ${plugin.name}`);
275
- console.log(`Tested WP: ${plugin.tested_wp}`);
276
- }
277
- catch (error) {
278
- console.error('Error:', error.message);
279
- process.exit(1);
280
- }
281
- });
282
- plugins
283
- .command('upload <slug> <zipFile>')
284
- .description('Upload a new plugin version (owner only)')
285
- .option('-v, --ver <version>', 'Version number (auto-detected from ZIP if omitted)')
286
- .option('--sync-metadata', 'Update plugin metadata (requires_php, requires_wp, tested_wp) from ZIP headers')
287
- .action(async (slug, zipFile, options) => {
288
- // Check file exists
289
- if (!existsSync(zipFile)) {
290
- console.error(`File not found: ${zipFile}`);
291
- process.exit(1);
292
- }
293
- try {
294
- // Extract headers from ZIP
295
- const headers = extractPluginHeaders(zipFile, slug);
296
- if (!headers) {
297
- console.error(`Could not find main plugin file in ZIP. Expected ${slug}/${slug}.php`);
298
- process.exit(1);
299
- }
300
- // Determine version: use --ver if provided, otherwise extract from ZIP
301
- let version = options.ver;
302
- if (!version) {
303
- if (!headers.version) {
304
- console.error('Could not extract version from plugin header. Use -v to specify manually.');
305
- process.exit(1);
306
- }
307
- version = headers.version;
308
- console.log(`Auto-detected version: ${version}`);
309
- }
310
- console.log(`Uploading ${zipFile} as version ${version}...`);
311
- const result = await api.uploadVersion(slug, version, zipFile);
312
- console.log(`\n${result.message}`);
313
- console.log(`Size: ${(result.size_bytes / 1024).toFixed(1)} KB`);
314
- // Sync metadata if requested
315
- if (options.syncMetadata) {
316
- const updates = {};
317
- let hasUpdates = false;
318
- if (headers.requires_php) {
319
- updates.requires_php = headers.requires_php;
320
- hasUpdates = true;
321
- }
322
- if (headers.requires_wp) {
323
- updates.requires_wp = headers.requires_wp;
324
- hasUpdates = true;
325
- }
326
- if (headers.tested_wp) {
327
- updates.tested_wp = headers.tested_wp;
328
- hasUpdates = true;
329
- }
330
- if (hasUpdates) {
331
- console.log('\nSyncing metadata from plugin headers...');
332
- const plugin = await api.updatePlugin(slug, updates);
333
- console.log(` Requires PHP: ${plugin.requires_php}`);
334
- console.log(` Requires WP: ${plugin.requires_wp}`);
335
- console.log(` Tested WP: ${plugin.tested_wp}`);
336
- }
337
- else {
338
- console.log('\nNo metadata headers found in plugin file to sync.');
339
- }
340
- }
341
- }
342
- catch (error) {
343
- console.error('Error:', error.message);
344
- process.exit(1);
345
- }
346
- });
347
- plugins
348
- .command('preflight <slug> <zipFile>')
349
- .description('Validate plugin ZIP before uploading')
350
- .action(async (slug, zipFile) => {
351
- // Check file exists
352
- if (!existsSync(zipFile)) {
353
- console.error(`File not found: ${zipFile}`);
354
- process.exit(1);
355
- }
356
- console.log(`\nValidating ${zipFile} for plugin "${slug}"...\n`);
357
- const issues = [];
358
- const warnings = [];
359
- const info = [];
360
- // Open ZIP file
361
- let zip;
362
- try {
363
- zip = new AdmZip(zipFile);
364
- }
365
- catch {
366
- console.error('❌ FAILED: Not a valid ZIP file');
367
- process.exit(1);
368
- }
369
- const entries = zip.getEntries();
370
- const entryNames = entries.map((e) => e.entryName);
371
- // Get ZIP file size from filesystem (don't use toBuffer - it corrupts entries)
372
- const zipStats = statSync(zipFile);
373
- info.push(`ZIP file size: ${(zipStats.size / 1024).toFixed(1)} KB`);
374
- info.push(`Files in ZIP: ${entries.length}`);
375
- // Find main plugin file (slug/slug.php or slug.php)
376
- const mainFilePath1 = `${slug}/${slug}.php`;
377
- const mainFilePath2 = `${slug}.php`;
378
- let mainFileEntry = entries.find((e) => e.entryName.toLowerCase() === mainFilePath1.toLowerCase() ||
379
- e.entryName.toLowerCase() === mainFilePath2.toLowerCase());
380
- if (!mainFileEntry) {
381
- issues.push(`Missing main plugin file: expected ${mainFilePath1} or ${mainFilePath2}`);
382
- }
383
- else {
384
- info.push(`Main plugin file: ${mainFileEntry.entryName}`);
385
- // Read and parse the main plugin file
386
- const mainFileContent = mainFileEntry.getData().toString('utf-8');
387
- // Check for WordPress plugin headers
388
- const pluginNameMatch = mainFileContent.match(/Plugin Name:\s*(.+)/i);
389
- const versionMatch = mainFileContent.match(/Version:\s*(\d+\.\d+(\.\d+)?)/i);
390
- const authorMatch = mainFileContent.match(/Author:\s*(.+)/i);
391
- const requiresWPMatch = mainFileContent.match(/Requires at least:\s*(\d+\.\d+)/i);
392
- const requiresPHPMatch = mainFileContent.match(/Requires PHP:\s*(\d+\.\d+)/i);
393
- const testedWPMatch = mainFileContent.match(/Tested up to:\s*(\d+\.\d+)/i);
394
- if (!pluginNameMatch) {
395
- issues.push("Missing 'Plugin Name' header in main plugin file");
396
- }
397
- else {
398
- info.push(`Plugin Name: ${pluginNameMatch[1].trim()}`);
399
- }
400
- if (!versionMatch) {
401
- issues.push("Missing 'Version' header in main plugin file");
402
- }
403
- else {
404
- info.push(`Version: ${versionMatch[1]}`);
405
- }
406
- if (!authorMatch) {
407
- warnings.push("Missing 'Author' header (recommended)");
408
- }
409
- if (!requiresWPMatch) {
410
- warnings.push("Missing 'Requires at least' header (recommended)");
411
- }
412
- if (!requiresPHPMatch) {
413
- warnings.push("Missing 'Requires PHP' header (recommended)");
414
- }
415
- if (!testedWPMatch) {
416
- warnings.push("Missing 'Tested up to' header (recommended)");
417
- }
418
- // Security checks on all PHP files
419
- const phpEntries = entries.filter((e) => e.entryName.toLowerCase().endsWith('.php'));
420
- let hasEval = false;
421
- let hasBase64 = false;
422
- let hasShellExec = false;
423
- for (const entry of phpEntries) {
424
- const content = entry.getData().toString('utf-8');
425
- if (/\beval\s*\(/i.test(content))
426
- hasEval = true;
427
- if (/\bbase64_decode\s*\(/i.test(content))
428
- hasBase64 = true;
429
- if (/\b(shell_exec|exec)\s*\(/i.test(content))
430
- hasShellExec = true;
431
- }
432
- if (hasEval) {
433
- warnings.push('Uses eval() - ensure this is intentional and safe');
434
- }
435
- if (hasBase64) {
436
- warnings.push('Uses base64_decode() - ensure this is intentional and safe');
437
- }
438
- if (hasShellExec) {
439
- warnings.push('Uses shell_exec/exec - ensure this is intentional and safe');
440
- }
441
- }
442
- // Check for readme.txt
443
- const readmePath = `${slug}/readme.txt`;
444
- const hasReadme = entryNames.some((name) => name.toLowerCase() === readmePath.toLowerCase());
445
- if (!hasReadme) {
446
- warnings.push('Missing readme.txt (recommended for update server)');
447
- }
448
- else {
449
- info.push('readme.txt found');
450
- }
451
- // Check folder structure - should be slug/
452
- const hasCorrectFolder = entryNames.some((name) => name.toLowerCase().startsWith(`${slug.toLowerCase()}/`));
453
- if (!hasCorrectFolder) {
454
- issues.push(`ZIP should contain a "${slug}/" folder with all plugin files`);
455
- }
456
- // Print results
457
- console.log('INFO:');
458
- info.forEach((i) => console.log(` ✓ ${i}`));
459
- console.log('');
460
- if (warnings.length > 0) {
461
- console.log('WARNINGS:');
462
- warnings.forEach((w) => console.log(` ⚠ ${w}`));
463
- console.log('');
464
- }
465
- if (issues.length > 0) {
466
- console.log('ISSUES:');
467
- issues.forEach((i) => console.log(` ✗ ${i}`));
468
- console.log('');
469
- console.log(`❌ FAILED: ${issues.length} issue(s) found`);
470
- process.exit(1);
471
- }
472
- console.log(`✅ PASSED: Plugin "${slug}" is ready for upload${warnings.length > 0 ? ` (with ${warnings.length} warning(s))` : ''}`);
473
- });
474
- // ============================================================
475
- // New Version Management Commands
476
- // ============================================================
477
- plugins
478
- .command('versions <slug>')
479
- .description('List all versions of a plugin')
480
- .action(async (slug) => {
481
- try {
482
- const data = await api.listVersions(slug);
483
- console.log(`\nVersions for ${data.name} (${data.slug}):`);
484
- console.log(`Current version: ${data.current_version}`);
485
- console.log(`Total downloads: ${data.total_downloads}`);
486
- console.log(`You own this: ${data.is_owner ? 'Yes' : 'No'}\n`);
487
- if (data.versions.length === 0) {
488
- console.log(' (no versions)');
489
- return;
490
- }
491
- data.versions.forEach((v) => {
492
- const isCurrent = v.version === data.current_version ? ' (current)' : '';
493
- console.log(` v${v.version}${isCurrent}`);
494
- console.log(` Released: ${v.release_date}`);
495
- console.log(` Downloads: ${v.downloads}`);
496
- if (v.changelog) {
497
- console.log(` Changelog: ${v.changelog.slice(0, 60)}${v.changelog.length > 60 ? '...' : ''}`);
498
- }
499
- console.log('');
500
- });
501
- }
502
- catch (error) {
503
- console.error('Error:', error.message);
504
- process.exit(1);
505
- }
506
- });
507
- plugins
508
- .command('stats <slug>')
509
- .description('View download statistics for a plugin')
510
- .action(async (slug) => {
511
- try {
512
- const data = await api.getStats(slug);
513
- console.log(`\nStatistics for ${data.name} (${data.slug}):\n`);
514
- console.log(` Total downloads: ${data.total_downloads}`);
515
- console.log(` Version count: ${data.version_count}`);
516
- console.log(` Current version: ${data.current_version}`);
517
- console.log(` Last updated: ${data.last_updated}`);
518
- const byVersion = Object.entries(data.by_version);
519
- if (byVersion.length > 0) {
520
- console.log(`\n Downloads by version:`);
521
- // Sort by version descending
522
- byVersion.sort((a, b) => {
523
- const aParts = a[0].split('.').map(Number);
524
- const bParts = b[0].split('.').map(Number);
525
- for (let i = 0; i < 3; i++) {
526
- if ((bParts[i] || 0) !== (aParts[i] || 0)) {
527
- return (bParts[i] || 0) - (aParts[i] || 0);
528
- }
529
- }
530
- return 0;
531
- });
532
- byVersion.forEach(([version, count]) => {
533
- console.log(` v${version}: ${count}`);
534
- });
535
- }
536
- }
537
- catch (error) {
538
- console.error('Error:', error.message);
539
- process.exit(1);
540
- }
541
- });
542
- plugins
543
- .command('download <slug> <version>')
544
- .description('Download a plugin version ZIP (owner only)')
545
- .option('-o, --output <path>', 'Output file path')
546
- .action(async (slug, version, options) => {
547
- try {
548
- console.log(`Downloading ${slug} v${version}...`);
549
- const result = await api.downloadVersion(slug, version);
550
- const outputPath = options.output || result.filename;
551
- writeFileSync(outputPath, Buffer.from(result.data));
552
- console.log(`\n✅ Downloaded to: ${outputPath}`);
553
- console.log(`Size: ${(result.data.byteLength / 1024).toFixed(1)} KB`);
554
- }
555
- catch (error) {
556
- console.error('Error:', error.message);
557
- process.exit(1);
558
- }
559
- });
560
- plugins
561
- .command('delete-version <slug> <version>')
562
- .description('Delete a plugin version (owner only)')
563
- .option('-y, --yes', 'Skip confirmation')
564
- .action(async (slug, version, options) => {
565
- try {
566
- if (!options.yes) {
567
- console.log(`\n⚠️ You are about to delete version ${version} of ${slug}`);
568
- console.log('This action cannot be undone.');
569
- console.log('\nRun with --yes to confirm.');
570
- process.exit(1);
571
- }
572
- console.log(`Deleting ${slug} v${version}...`);
573
- const result = await api.deleteVersion(slug, version);
574
- console.log(`\n✅ ${result.message}`);
575
- console.log(`Current version is now: ${result.current_version}`);
576
- }
577
- catch (error) {
578
- console.error('Error:', error.message);
579
- process.exit(1);
580
- }
581
- });
582
- plugins
583
- .command('changelog <slug> [version] [text]')
584
- .description('View or update changelog for a plugin version')
585
- .option('-s, --set <text>', 'Set changelog text (alternative to positional argument)')
586
- .action(async (slug, version, text, options) => {
587
- try {
588
- // Text can come from positional arg or --set option
589
- const changelogText = text || options.set;
590
- if (changelogText) {
591
- // Update changelog
592
- if (!version) {
593
- console.error('Version is required when setting changelog');
594
- process.exit(1);
595
- }
596
- console.log(`Updating changelog for ${slug} v${version}...`);
597
- const result = await api.updateVersion(slug, version, { changelog: changelogText });
598
- console.log(`\n✅ ${result.message}`);
599
- console.log(`Changelog: ${result.version.changelog || '(empty)'}`);
600
- }
601
- else {
602
- // View changelog
603
- const data = await api.listVersions(slug);
604
- if (version) {
605
- // Show specific version changelog
606
- const v = data.versions.find((ver) => ver.version === version);
607
- if (!v) {
608
- console.error(`Version ${version} not found`);
609
- process.exit(1);
610
- }
611
- console.log(`\nChangelog for ${data.name} v${version}:\n`);
612
- console.log(v.changelog || '(no changelog)');
613
- }
614
- else {
615
- // Show all changelogs
616
- console.log(`\nChangelogs for ${data.name}:\n`);
617
- data.versions.forEach((v) => {
618
- console.log(`v${v.version} (${v.release_date}):`);
619
- console.log(` ${v.changelog || '(no changelog)'}\n`);
620
- });
621
- }
622
- }
623
- }
624
- catch (error) {
625
- console.error('Error:', error.message);
626
- process.exit(1);
627
- }
628
- });
629
- plugins
630
- .command('transfer <slug> <email>')
631
- .description('Transfer plugin ownership to another @jezweb user (owner only)')
632
- .option('-y, --yes', 'Skip confirmation')
633
- .action(async (slug, email, options) => {
634
- try {
635
- if (!options.yes) {
636
- console.log(`\n⚠️ You are about to transfer ownership of ${slug} to ${email}`);
637
- console.log('You will lose all access to this plugin.');
638
- console.log('\nRun with --yes to confirm.');
639
- process.exit(1);
640
- }
641
- console.log(`Transferring ${slug} to ${email}...`);
642
- const result = await api.transferOwnership(slug, email);
643
- console.log(`\n✅ ${result.message}`);
644
- console.log(`Previous owner: ${result.previous_owner}`);
645
- console.log(`New owner: ${result.new_owner}`);
646
- }
647
- catch (error) {
648
- console.error('Error:', error.message);
649
- process.exit(1);
650
- }
651
- });
652
- // ============================================================
653
- // Site Commands
654
- // ============================================================
655
- const sites = program
656
- .command('sites')
657
- .description('Manage WordPress sites');
658
- sites
659
- .command('list')
660
- .description('List all registered sites')
661
- .option('--status <status>', 'Filter by status (approved/pending/rejected)')
662
- .option('--woo', 'Only show sites with WooCommerce')
663
- .option('--sort <column>', 'Sort by: domain, version, users, admins (default: domain)')
664
- .option('--desc', 'Sort descending')
665
- .option('--table', 'Show as compact table')
666
- .option('--admins-only', 'Only show sites where total users = admin users')
667
- .action(async (options) => {
668
- try {
669
- let sitesList = await api.listSites({
670
- status: options.status,
671
- woo: options.woo,
672
- });
673
- // Filter: admins only (no extra users)
674
- if (options.adminsOnly) {
675
- sitesList = sitesList.filter((s) => s.user_count_total !== undefined &&
676
- s.user_count_total > 0 &&
677
- s.user_count_total === s.user_count_admin);
678
- }
679
- // Sort
680
- const sortCol = options.sort || 'domain';
681
- sitesList.sort((a, b) => {
682
- let cmp = 0;
683
- switch (sortCol) {
684
- case 'version':
685
- cmp = (a.jezpress_version || '0').localeCompare(b.jezpress_version || '0');
686
- break;
687
- case 'users':
688
- cmp = (a.user_count_total || 0) - (b.user_count_total || 0);
689
- break;
690
- case 'admins':
691
- cmp = (a.user_count_admin || 0) - (b.user_count_admin || 0);
692
- break;
693
- default:
694
- cmp = a.domain.localeCompare(b.domain);
695
- }
696
- return options.desc ? -cmp : cmp;
697
- });
698
- console.log(`\nSites (${sitesList.length}):\n`);
699
- if (sitesList.length === 0) {
700
- console.log(' (none)');
701
- return;
702
- }
703
- if (options.table) {
704
- // Compact table format
705
- console.log(' Domain | JezPress | Users | Admins | WP ');
706
- console.log(' ------------------------------------------|----------|-------|--------|------');
707
- sitesList.forEach((s) => {
708
- const domain = s.domain.padEnd(42).slice(0, 42);
709
- const version = (s.jezpress_version || '-').padEnd(8);
710
- const users = String(s.user_count_total ?? '-').padStart(5);
711
- const admins = String(s.user_count_admin ?? '-').padStart(6);
712
- const wp = (s.wp_version || '-').padEnd(5);
713
- console.log(` ${domain} | ${version} | ${users} | ${admins} | ${wp}`);
714
- });
715
- }
716
- else {
717
- // Detailed format
718
- sitesList.forEach((s) => {
719
- const woo = s.woocommerce_active ? ' [WooCommerce]' : '';
720
- console.log(` ${s.domain} (${s.status})${woo}`);
721
- if (s.jezpress_version) {
722
- console.log(` JezPress: v${s.jezpress_version}, WP: ${s.wp_version || 'unknown'}, PHP: ${s.php_version || 'unknown'}`);
723
- }
724
- if (s.user_count_total !== undefined) {
725
- console.log(` Users: ${s.user_count_total} total (${s.user_count_admin || 0} admins)`);
726
- }
727
- console.log('');
728
- });
729
- }
730
- // Summary
731
- const wooCount = sitesList.filter((s) => s.woocommerce_active === 1).length;
732
- console.log(`\nSummary: ${sitesList.length} sites, ${wooCount} with WooCommerce`);
733
- }
734
- catch (error) {
735
- console.error('Error:', error.message);
736
- process.exit(1);
737
- }
738
- });
739
- sites
740
- .command('check <domain>')
741
- .description('Check if a domain is registered in JezPress')
742
- .action(async (domain) => {
743
- try {
744
- const sitesList = await api.listSites({});
745
- const normalizedDomain = domain.toLowerCase()
746
- .replace(/^https?:\/\//, '')
747
- .replace(/^www\./, '')
748
- .replace(/\/$/, '');
749
- const found = sitesList.find((s) => s.domain === normalizedDomain);
750
- if (found) {
751
- console.log(`\n✓ ${normalizedDomain} is registered in JezPress`);
752
- console.log(` Status: ${found.status}`);
753
- if (found.jezpress_version) {
754
- console.log(` JezPress: v${found.jezpress_version}`);
755
- }
756
- if (found.user_count_total !== undefined) {
757
- console.log(` Users: ${found.user_count_total} (${found.user_count_admin} admins)`);
758
- }
759
- }
760
- else {
761
- console.log(`\n✗ ${normalizedDomain} is NOT registered in JezPress`);
762
- }
763
- }
764
- catch (error) {
765
- console.error('Error:', error.message);
766
- process.exit(1);
767
- }
768
- });
769
- sites
770
- .command('scan-all')
771
- .description('Scan all sites and update cached data')
772
- .action(async () => {
773
- try {
774
- console.log('Queuing scan for all sites...\n');
775
- const { jobId, total } = await api.bulkScan();
776
- console.log(`Queued ${total} sites (job: ${jobId})\n`);
777
- // Poll for progress
778
- let lastProgress = -1;
779
- // eslint-disable-next-line no-constant-condition
780
- while (true) {
781
- const status = await api.getScanStatus(jobId);
782
- const progress = status.progress;
783
- if (progress !== lastProgress) {
784
- process.stdout.write(`\rProgress: ${status.completed}/${status.total} complete, ${status.failed} failed (${progress}%)`);
785
- lastProgress = progress;
786
- }
787
- if (status.status === 'completed') {
788
- console.log('\n\n✓ Scan complete!');
789
- console.log(` Succeeded: ${status.completed}`);
790
- console.log(` Failed: ${status.failed}`);
791
- break;
792
- }
793
- if (status.status === 'failed') {
794
- console.log('\n\n✗ Scan failed');
795
- break;
796
- }
797
- // Wait 2 seconds before polling again
798
- await new Promise((r) => setTimeout(r, 2000));
799
- }
800
- }
801
- catch (error) {
802
- console.error('Error:', error.message);
803
- process.exit(1);
804
- }
805
- });
806
- sites
807
- .command('update-all')
808
- .description('Update JezPress on all sites')
809
- .option('--force', 'Clear WordPress update cache before checking (use when updates are stuck)')
810
- .action(async (options) => {
811
- try {
812
- const force = options.force === true;
813
- console.log(`Queuing JezPress updates for all sites${force ? ' (force mode)' : ''}...\n`);
814
- const { jobId, total } = await api.bulkUpdate({ force });
815
- console.log(`Queued ${total} sites (job: ${jobId})\n`);
816
- // Poll for progress
817
- let lastProgress = -1;
818
- // eslint-disable-next-line no-constant-condition
819
- while (true) {
820
- const status = await api.getScanStatus(jobId);
821
- const progress = status.progress;
822
- if (progress !== lastProgress) {
823
- process.stdout.write(`\rProgress: ${status.completed}/${status.total} complete, ${status.failed} failed (${progress}%)`);
824
- lastProgress = progress;
825
- }
826
- if (status.status === 'completed') {
827
- console.log('\n\n✓ Update complete!');
828
- console.log(` Succeeded: ${status.completed}`);
829
- console.log(` Failed: ${status.failed}`);
830
- break;
831
- }
832
- if (status.status === 'failed') {
833
- console.log('\n\n✗ Update failed');
834
- break;
835
- }
836
- // Wait 2 seconds before polling again
837
- await new Promise((r) => setTimeout(r, 2000));
838
- }
839
- }
840
- catch (error) {
841
- console.error('Error:', error.message);
842
- process.exit(1);
843
- }
844
- });
845
- sites
846
- .command('update <domain>')
847
- .description('Update JezPress on a single site')
848
- .action(async (domain) => {
849
- try {
850
- // First get current status
851
- console.log(`Checking ${domain}...`);
852
- const status = await api.getSiteStatus(domain);
853
- console.log(`Current version: ${status.version}`);
854
- console.log(`WP: ${status.wp_version}, PHP: ${status.php_version}\n`);
855
- console.log('Triggering update...');
856
- const result = await api.updateSitePlugin(domain);
857
- if (result.old_version === result.new_version) {
858
- console.log(`\n✓ Already up to date (v${result.new_version})`);
859
- }
860
- else {
861
- console.log(`\n✓ Updated: v${result.old_version} → v${result.new_version}`);
862
- if (result.reactivated) {
863
- console.log(' Plugin was reactivated after update');
864
- }
865
- }
866
- }
867
- catch (error) {
868
- console.error('Error:', error.message);
869
- process.exit(1);
870
- }
871
- });
872
- sites
873
- .command('status <domain>')
874
- .description('Get live status of a site via REST API')
875
- .action(async (domain) => {
876
- try {
877
- console.log(`Checking ${domain}...\n`);
878
- const status = await api.getSiteStatus(domain);
879
- console.log(` Domain: ${status.domain}`);
880
- console.log(` Status: ${status.status}`);
881
- console.log(` JezPress: v${status.version}`);
882
- console.log(` WordPress: ${status.wp_version}`);
883
- console.log(` PHP: ${status.php_version}`);
884
- }
885
- catch (error) {
886
- console.error('Error:', error.message);
887
- process.exit(1);
888
- }
889
- });
890
- sites
891
- .command('info <domain>')
892
- .description('Get comprehensive site information')
893
- .action(async (domain) => {
894
- try {
895
- console.log(`Fetching site info for ${domain}...\n`);
896
- const info = await api.getSiteInfo(domain);
897
- console.log('Versions:');
898
- console.log(` WordPress: ${info.versions.wp}`);
899
- console.log(` PHP: ${info.versions.php}`);
900
- console.log(` JezPress: ${info.versions.jezpress}`);
901
- console.log('\nUsers:');
902
- for (const [role, count] of Object.entries(info.users)) {
903
- if (count > 0) {
904
- console.log(` ${role}: ${count}`);
905
- }
906
- }
907
- console.log('\nWooCommerce:');
908
- if (info.woocommerce.active) {
909
- console.log(` Active: Yes (v${info.woocommerce.version})`);
910
- }
911
- else {
912
- console.log(' Active: No');
913
- }
914
- console.log('\nSettings:');
915
- console.log(` OAuth Only: ${info.settings.disable_password_login ? 'Yes' : 'No'}`);
916
- }
917
- catch (error) {
918
- console.error('Error:', error.message);
919
- process.exit(1);
920
- }
921
- });
922
- sites
923
- .command('plugins <domain>')
924
- .description('List plugins with update status')
925
- .option('--updates', 'Only show plugins with updates available')
926
- .action(async (domain, options) => {
927
- try {
928
- console.log(`Fetching plugins for ${domain}...\n`);
929
- const data = await api.getSitePlugins(domain);
930
- let plugins = data.plugins;
931
- if (options.updates) {
932
- plugins = plugins.filter(p => p.update_available);
933
- }
934
- if (plugins.length === 0) {
935
- if (options.updates) {
936
- console.log('No plugins with updates available.');
937
- }
938
- else {
939
- console.log('No plugins found.');
940
- }
941
- return;
942
- }
943
- for (const plugin of plugins) {
944
- const status = plugin.active ? '●' : '○';
945
- const update = plugin.update_available
946
- ? ` → ${plugin.new_version}`
947
- : '';
948
- console.log(`${status} ${plugin.name} (v${plugin.version}${update})`);
949
- }
950
- console.log(`\n${data.total} plugins, ${data.updates_available} with updates`);
951
- }
952
- catch (error) {
953
- console.error('Error:', error.message);
954
- process.exit(1);
955
- }
956
- });
957
- sites
958
- .command('clear-cache <domain>')
959
- .description('Clear site caches (WP Rocket, LiteSpeed, etc)')
960
- .action(async (domain) => {
961
- try {
962
- console.log(`Clearing cache on ${domain}...`);
963
- const result = await api.clearSiteCache(domain);
964
- if (result.cleared.length > 0) {
965
- console.log(`\n✓ Cleared: ${result.cleared.join(', ')}`);
966
- }
967
- else {
968
- console.log('\n✓ Cache cleared (no specific plugins detected)');
969
- }
970
- }
971
- catch (error) {
972
- console.error('Error:', error.message);
973
- process.exit(1);
974
- }
975
- });
976
- sites
977
- .command('clear-update-cache <domain>')
978
- .description('Force site to check for fresh plugin updates')
979
- .action(async (domain) => {
980
- try {
981
- console.log(`Clearing update cache on ${domain}...`);
982
- await api.clearSiteUpdateCache(domain);
983
- console.log('\n✓ Update cache cleared - next update check will fetch fresh data');
984
- }
985
- catch (error) {
986
- console.error('Error:', error.message);
987
- process.exit(1);
988
- }
989
- });
990
- sites
991
- .command('approve <domain>')
992
- .description('Approve a pending site for OAuth access')
993
- .action(async (domain) => {
994
- try {
995
- console.log(`Approving ${domain}...`);
996
- const result = await api.approveSite(domain);
997
- console.log(`\n✓ ${result.message}`);
998
- }
999
- catch (error) {
1000
- console.error('Error:', error.message);
1001
- process.exit(1);
1002
- }
1003
- });
1004
- sites
1005
- .command('reject <domain>')
1006
- .description('Reject a site from OAuth access')
1007
- .action(async (domain) => {
1008
- try {
1009
- console.log(`Rejecting ${domain}...`);
1010
- const result = await api.rejectSite(domain);
1011
- console.log(`\n✓ ${result.message}`);
1012
- }
1013
- catch (error) {
1014
- console.error('Error:', error.message);
1015
- process.exit(1);
1016
- }
1017
- });
1018
- // ========== NEW API COMMANDS (v1.4.0) ==========
1019
- sites
1020
- .command('export <domain>')
1021
- .description('Export site content (pages, posts, menus, media)')
1022
- .option('--include <sections>', 'Sections to include (comma-separated: site,pages,posts,menus,media)')
1023
- .option('--json', 'Output raw JSON')
1024
- .action(async (domain, options) => {
1025
- try {
1026
- console.log(`Exporting ${domain}...`);
1027
- const data = await api.getSiteExport(domain, {
1028
- include: options.include,
1029
- });
1030
- if (options.json) {
1031
- console.log(JSON.stringify(data, null, 2));
1032
- return;
1033
- }
1034
- console.log(`\nExport Summary:`);
1035
- console.log(` Exported at: ${data.exported_at}`);
1036
- if (data.site)
1037
- console.log(` Site config: included`);
1038
- if (data.pages)
1039
- console.log(` Pages: ${data.pages.length}`);
1040
- if (data.posts)
1041
- console.log(` Posts: ${data.posts.length}`);
1042
- if (data.menus)
1043
- console.log(` Menus: ${data.menus.length}`);
1044
- if (data.media)
1045
- console.log(` Media: ${data.media.length}`);
1046
- }
1047
- catch (error) {
1048
- console.error('Error:', error.message);
1049
- process.exit(1);
1050
- }
1051
- });
1052
- sites
1053
- .command('config <domain>')
1054
- .description('Get site configuration (theme, Elementor, options)')
1055
- .option('--json', 'Output raw JSON')
1056
- .action(async (domain, options) => {
1057
- try {
1058
- console.log(`Fetching config for ${domain}...\n`);
1059
- const data = await api.getSiteConfig(domain);
1060
- if (options.json) {
1061
- console.log(JSON.stringify(data, null, 2));
1062
- return;
1063
- }
1064
- console.log('Theme:');
1065
- console.log(` Name: ${data.theme.name}`);
1066
- console.log(` Version: ${data.theme.version}`);
1067
- if (data.theme.parent)
1068
- console.log(` Parent: ${data.theme.parent}`);
1069
- console.log('\nElementor:');
1070
- console.log(` Active: ${data.elementor.active ? 'Yes' : 'No'}`);
1071
- if (data.elementor.globals?.colors) {
1072
- console.log(` Global colors: ${data.elementor.globals.colors.length}`);
1073
- }
1074
- if (data.elementor.globals?.typography) {
1075
- console.log(` Global fonts: ${data.elementor.globals.typography.length}`);
1076
- }
1077
- console.log('\nSite Options:');
1078
- const opts = data.options;
1079
- const keys = ['blogname', 'siteurl', 'admin_email', 'timezone_string'];
1080
- for (const key of keys) {
1081
- if (opts[key])
1082
- console.log(` ${key}: ${opts[key]}`);
1083
- }
1084
- console.log(` ... and ${Object.keys(opts).length - keys.length} more options`);
1085
- }
1086
- catch (error) {
1087
- console.error('Error:', error.message);
1088
- process.exit(1);
1089
- }
1090
- });
1091
- sites
1092
- .command('diagnostics <domain>')
1093
- .description('Get site diagnostics (health, errors, cron, database)')
1094
- .option('--json', 'Output raw JSON')
1095
- .action(async (domain, options) => {
1096
- try {
1097
- console.log(`Fetching diagnostics for ${domain}...\n`);
1098
- const data = await api.getSiteDiagnostics(domain);
1099
- if (options.json) {
1100
- console.log(JSON.stringify(data, null, 2));
1101
- return;
1102
- }
1103
- console.log('Health:');
1104
- const healthTests = data.health.tests || {};
1105
- const passed = Object.values(healthTests).filter((t) => t.status === 'good').length;
1106
- console.log(` Tests: ${passed}/${Object.keys(healthTests).length} passed`);
1107
- console.log('\nErrors:');
1108
- const errors = data.errors.log || [];
1109
- console.log(` Log entries: ${errors.length}`);
1110
- if (errors.length > 0) {
1111
- console.log(` Latest: ${errors[0].substring(0, 70)}...`);
1112
- }
1113
- console.log('\nDatabase:');
1114
- console.log(` Version: ${data.database.version || 'N/A'}`);
1115
- if (data.database.size)
1116
- console.log(` Size: ${data.database.size}`);
1117
- console.log('\nCron:');
1118
- if (Array.isArray(data.cron)) {
1119
- console.log(` Scheduled jobs: ${data.cron.length}`);
1120
- }
1121
- else {
1122
- console.log(` Cron entries: ${Object.keys(data.cron).length}`);
1123
- }
1124
- console.log('\nTransients:');
1125
- console.log(` Count: ${data.transients.count}`);
1126
- }
1127
- catch (error) {
1128
- console.error('Error:', error.message);
1129
- process.exit(1);
1130
- }
1131
- });
1132
- sites
1133
- .command('ai <domain>')
1134
- .description('Get AI-optimized site knowledge bundle')
1135
- .option('--json', 'Output raw JSON')
1136
- .action(async (domain, options) => {
1137
- try {
1138
- console.log(`Fetching AI knowledge for ${domain}...\n`);
1139
- const data = await api.getSiteAI(domain);
1140
- if (options.json) {
1141
- console.log(JSON.stringify(data, null, 2));
1142
- return;
1143
- }
1144
- console.log('Site Knowledge Bundle:');
1145
- console.log(` Pages: ${data.page_count}`);
1146
- console.log(` Plugins: ${data.plugin_count}`);
1147
- console.log(` Menus: ${data.menus?.length || 0}`);
1148
- console.log('\nPages (first 5):');
1149
- const pages = data.pages || [];
1150
- pages.slice(0, 5).forEach((p) => {
1151
- console.log(` - ${p.title} (/${p.slug})`);
1152
- });
1153
- if (pages.length > 5)
1154
- console.log(` ... and ${pages.length - 5} more`);
1155
- console.log('\nActive Plugins:');
1156
- const plugins = (data.plugins || []).filter((p) => p.active);
1157
- plugins.slice(0, 5).forEach((p) => {
1158
- console.log(` - ${p.name}`);
1159
- });
1160
- if (plugins.length > 5)
1161
- console.log(` ... and ${plugins.length - 5} more`);
1162
- }
1163
- catch (error) {
1164
- console.error('Error:', error.message);
1165
- process.exit(1);
1166
- }
1167
- });
25
+ // Register all command groups
26
+ registerAuthCommands(program);
27
+ registerDocsCommand(program);
28
+ registerPluginCommands(program);
29
+ registerSitesCommands(program);
1168
30
  // Parse and run
1169
31
  program.parse();
1170
32
  //# sourceMappingURL=cli.js.map