@skunkceo/cli 1.0.1 → 1.0.2

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 (2) hide show
  1. package/bin/setup.js +44 -262
  2. package/package.json +2 -8
package/bin/setup.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const https = require('https');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { execSync, spawn } = require('child_process');
6
+ const { execSync } = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
9
  // ═══════════════════════════════════════════════════════════════════════════
@@ -95,151 +95,10 @@ async function confirm(question, defaultYes = true) {
95
95
  return answer.toLowerCase().startsWith('y');
96
96
  }
97
97
 
98
- async function choice(question, options) {
99
- log(question);
100
- options.forEach((opt, i) => {
101
- log(` ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}`);
102
- });
103
- const answer = await ask(`\nChoice (1-${options.length}): `);
104
- const idx = parseInt(answer, 10) - 1;
105
- if (idx >= 0 && idx < options.length) {
106
- return options[idx].value;
107
- }
108
- return options[0].value; // default to first
109
- }
110
-
111
98
  // ═══════════════════════════════════════════════════════════════════════════
112
- // License Validation
99
+ // Skill Installation
113
100
  // ═══════════════════════════════════════════════════════════════════════════
114
101
 
115
- async function validateLicense(licenseKey, product) {
116
- return new Promise((resolve) => {
117
- const postData = JSON.stringify({
118
- license_key: licenseKey.toUpperCase().trim(),
119
- site_url: 'cli-setup', // Validation only, not activation
120
- });
121
-
122
- const options = {
123
- hostname: 'skunkglobal.com',
124
- port: 443,
125
- path: '/api/license/validate',
126
- method: 'POST',
127
- headers: {
128
- 'Content-Type': 'application/json',
129
- 'User-Agent': 'skunk-cli',
130
- },
131
- };
132
-
133
- const req = https.request(options, (res) => {
134
- let data = '';
135
- res.on('data', (chunk) => (data += chunk));
136
- res.on('end', () => {
137
- try {
138
- const json = JSON.parse(data);
139
- if (json.success && json.data && json.data.valid) {
140
- resolve({
141
- valid: true,
142
- activationsLeft: json.data.max_sites ? json.data.max_sites - (json.data.active_sites || 0) : 'unlimited',
143
- productsCovered: json.data.products_covered || [product],
144
- });
145
- } else {
146
- resolve({ valid: false, error: json.message || 'Invalid license' });
147
- }
148
- } catch (e) {
149
- resolve({ valid: false, error: 'Failed to validate license' });
150
- }
151
- });
152
- });
153
-
154
- req.on('error', () => {
155
- resolve({ valid: false, error: 'Could not connect to license server' });
156
- });
157
-
158
- req.write(postData);
159
- req.end();
160
- });
161
- }
162
-
163
- // ═══════════════════════════════════════════════════════════════════════════
164
- // Installation Functions
165
- // ═══════════════════════════════════════════════════════════════════════════
166
-
167
- const PRODUCTS = [
168
- {
169
- name: 'SkunkCRM',
170
- slug: 'skunkcrm',
171
- skill: 'skunkcrm',
172
- freeRepo: 'skunkceo/skunkcrm',
173
- proSlug: 'skunkcrm-pro',
174
- },
175
- {
176
- name: 'SkunkForms',
177
- slug: 'skunkforms',
178
- skill: 'skunkforms',
179
- freeRepo: 'skunkceo/skunkforms',
180
- proSlug: 'skunkforms-pro',
181
- },
182
- {
183
- name: 'SkunkPages',
184
- slug: 'skunkpages',
185
- skill: 'skunkpages',
186
- freeRepo: 'skunkceo/skunkpages',
187
- proSlug: 'skunkpages-pro',
188
- },
189
- ];
190
-
191
- async function installPlugin(repo, pluginSlug, wpPath = null) {
192
- const url = `https://github.com/${repo}/releases/latest/download/${pluginSlug}.zip`;
193
-
194
- // If WP path provided and wp-cli available, install directly
195
- if (wpPath && commandExists('wp')) {
196
- try {
197
- log(` Installing ${pluginSlug} to WordPress...`);
198
- execSync(`wp plugin install "${url}" --path="${wpPath}"`, { stdio: 'pipe' });
199
- return { status: 'installed', location: 'wordpress' };
200
- } catch (e) {
201
- // Fall through to cache
202
- }
203
- }
204
-
205
- // Otherwise, cache in ~/.skunk/plugins/ for later
206
- const cacheDir = path.join(process.env.HOME, '.skunk', 'plugins');
207
- fs.mkdirSync(cacheDir, { recursive: true });
208
-
209
- const zipPath = path.join(cacheDir, `${pluginSlug}.zip`);
210
-
211
- log(` Downloading ${pluginSlug}...`);
212
-
213
- return new Promise((resolve) => {
214
- const file = fs.createWriteStream(zipPath);
215
-
216
- https.get(url, { headers: { 'User-Agent': 'skunk-cli' } }, (res) => {
217
- // Handle GitHub redirect
218
- if (res.statusCode === 302 || res.statusCode === 301) {
219
- https.get(res.headers.location, { headers: { 'User-Agent': 'skunk-cli' } }, (res2) => {
220
- res2.pipe(file);
221
- file.on('finish', () => {
222
- file.close();
223
- resolve({ status: 'cached', location: zipPath });
224
- });
225
- }).on('error', () => resolve({ status: 'failed' }));
226
- return;
227
- }
228
-
229
- if (res.statusCode !== 200) {
230
- resolve({ status: 'failed', error: `HTTP ${res.statusCode}` });
231
- return;
232
- }
233
-
234
- res.pipe(file);
235
- file.on('finish', () => {
236
- file.close();
237
- resolve({ status: 'cached', location: zipPath });
238
- });
239
- }).on('error', () => resolve({ status: 'failed' }));
240
- });
241
- }
242
-
243
102
  async function installSkill(skillName) {
244
103
  const skillsDir = path.join(process.env.HOME, '.openclaw', 'skills');
245
104
  const skillDir = path.join(skillsDir, skillName);
@@ -300,10 +159,10 @@ async function main() {
300
159
  // Show splash
301
160
  log(colors.bright + SKUNK_LOGO + colors.reset);
302
161
  log('');
303
- log(`${colors.dim}Welcome! Let's get your Skunk suite set up.${colors.reset}`);
162
+ log(`${colors.dim}Welcome! Let's get your AI-powered WordPress toolkit ready.${colors.reset}`);
304
163
  log('');
305
164
 
306
- const totalSteps = 5;
165
+ const totalSteps = 3;
307
166
 
308
167
  // ─────────────────────────────────────────────────────────────────────────
309
168
  // Step 1: Environment checks
@@ -320,52 +179,24 @@ async function main() {
320
179
  process.exit(1);
321
180
  }
322
181
 
323
- // npm
324
- const npmVersion = getVersion('npm');
325
- if (npmVersion) {
326
- success(`npm ${npmVersion}`);
327
- } else {
328
- error('npm not found');
329
- process.exit(1);
330
- }
331
-
332
- // WP-CLI (optional but recommended)
333
- let wpVersion = null;
334
- try {
335
- wpVersion = execSync('wp --version --allow-root 2>/dev/null || wp --version 2>/dev/null', { encoding: 'utf8' }).trim().split('\n')[0];
336
- } catch (e) {
337
- // WP-CLI not installed
338
- }
339
- if (wpVersion) {
340
- success(`WP-CLI ${wpVersion}`);
341
- } else {
342
- warn('WP-CLI not found (optional, needed for direct plugin management)');
343
- }
344
-
345
- // ─────────────────────────────────────────────────────────────────────────
346
- // Step 2: WordPress Studio
347
- // ─────────────────────────────────────────────────────────────────────────
348
- step(2, totalSteps, 'WordPress Studio...');
349
-
350
- const studioExists = commandExists('studio');
182
+ // WordPress Studio
183
+ let studioExists = commandExists('studio');
351
184
 
352
185
  if (studioExists) {
353
186
  const studioVersion = getVersion('studio');
354
187
  success(`WordPress Studio installed ${studioVersion ? `(${studioVersion})` : ''}`);
355
188
  } else {
356
- error('WordPress Studio not found');
189
+ warn('WordPress Studio not found');
357
190
  log('');
358
- log(' WordPress Studio is required for local WordPress development.');
191
+ log(' WordPress Studio is needed to create and manage WordPress sites.');
359
192
  log('');
360
193
  log(' Install it from: https://developer.wordpress.org/studio/');
361
194
  log('');
362
- log(' On macOS:');
363
- log(' brew install --cask wordpress-studio');
364
- log('');
365
- log(' Or download from the website and install manually.');
195
+ log(' macOS: brew install --cask wordpress-studio');
196
+ log(' Other: Download from the website');
366
197
  log('');
367
198
 
368
- const proceed = await confirm('Continue setup anyway? (skills only)', false);
199
+ const proceed = await confirm('Continue setup anyway?', true);
369
200
  if (!proceed) {
370
201
  log('\n Run `skunk setup` again after installing WordPress Studio.');
371
202
  rl.close();
@@ -374,110 +205,61 @@ async function main() {
374
205
  }
375
206
 
376
207
  // ─────────────────────────────────────────────────────────────────────────
377
- // Step 3: Install Plugins
208
+ // Step 2: Install AI Skills
378
209
  // ─────────────────────────────────────────────────────────────────────────
379
- step(3, totalSteps, 'Plugins...');
210
+ step(2, totalSteps, 'Installing AI skills...');
380
211
  log('');
381
-
382
- // WooCommerce (optional)
383
- log(` ${colors.bright}WooCommerce${colors.reset}`);
384
- const wooChoice = await choice(' Install WooCommerce?', [
385
- { label: 'Yes, include WooCommerce', value: 'yes' },
386
- { label: 'No, skip WooCommerce', value: 'no' },
387
- ]);
388
-
389
- if (wooChoice === 'yes') {
390
- log(' WooCommerce will be installed when you create a site');
391
- success('WooCommerce selected');
392
- } else {
393
- log(` ${colors.dim}Skipping WooCommerce${colors.reset}`);
394
- }
212
+ log(` ${colors.dim}Skills teach your AI assistant how to work with WordPress${colors.reset}`);
213
+ log(` ${colors.dim}and Skunk products. Installing the essentials...${colors.reset}`);
395
214
  log('');
396
-
397
- const licenses = {};
398
-
399
- for (const product of PRODUCTS) {
400
- log(` ${colors.bright}${product.name}${colors.reset}`);
401
-
402
- const licenseChoice = await choice(` Do you have a ${product.name} Pro license?`, [
403
- { label: 'No, install free version', value: 'free' },
404
- { label: 'Yes, enter license key', value: 'pro' },
405
- { label: 'Skip for now', value: 'skip' },
406
- ]);
407
-
408
- if (licenseChoice === 'skip') {
409
- warn(` Skipping ${product.name}`);
410
- continue;
411
- }
412
215
 
413
- if (licenseChoice === 'pro') {
414
- const key = await ask(' License key: ');
415
- if (key) {
416
- log(' Validating license...');
417
- const result = await validateLicense(key, product.proSlug);
418
-
419
- if (result.valid) {
420
- success(`License valid (${result.activationsLeft} activations remaining)`);
421
- licenses[product.slug] = key;
422
- // Install both free and pro
423
- await installPlugin(product.freeRepo, product.slug);
424
- success(`${product.name} (free) ready`);
425
- // TODO: Download pro from update server with license
426
- success(`${product.name} Pro ready`);
427
- } else {
428
- warn(`License invalid: ${result.error}`);
429
- warn('Installing free version instead');
430
- await installPlugin(product.freeRepo, product.slug);
431
- success(`${product.name} (free) ready`);
432
- }
433
- }
434
- } else {
435
- // Free version
436
- await installPlugin(product.freeRepo, product.slug);
437
- success(`${product.name} (free) ready`);
438
- }
439
-
440
- log('');
441
- }
442
-
443
- // ─────────────────────────────────────────────────────────────────────────
444
- // Step 4: Install Skills
445
- // ─────────────────────────────────────────────────────────────────────────
446
- step(4, totalSteps, 'AI Skills...');
447
-
448
- const skills = ['wordpress-studio', 'woocommerce-manager', 'skunkcrm', 'skunkforms', 'skunkpages'];
216
+ // Core skills that enable WordPress + Skunk workflow
217
+ const coreSkills = [
218
+ { name: 'wordpress-studio', desc: 'WordPress site management' },
219
+ { name: 'woocommerce', desc: 'WooCommerce store operations' },
220
+ { name: 'skunkcrm', desc: 'SkunkCRM contact & pipeline management' },
221
+ { name: 'skunkforms', desc: 'SkunkForms form building' },
222
+ { name: 'skunkpages', desc: 'SkunkPages landing page optimization' },
223
+ ];
449
224
 
450
- for (const skill of skills) {
451
- process.stdout.write(` ${skill} `);
452
- const result = await installSkill(skill);
225
+ for (const skill of coreSkills) {
226
+ process.stdout.write(` ${skill.name} `);
227
+ const result = await installSkill(skill.name);
453
228
  if (result.status === 'installed') {
454
- log(`${colors.green}✓${colors.reset}`);
229
+ log(`${colors.green}✓${colors.reset} ${colors.dim}${skill.desc}${colors.reset}`);
455
230
  } else if (result.status === 'exists') {
456
- log(`${colors.dim}(already installed)${colors.reset}`);
231
+ log(`${colors.dim}already installed${colors.reset}`);
457
232
  } else {
458
- log(`${colors.red}✗${colors.reset}`);
233
+ log(`${colors.yellow}! not available yet${colors.reset}`);
459
234
  }
460
235
  }
461
236
 
462
237
  // ─────────────────────────────────────────────────────────────────────────
463
- // Step 5: Done!
238
+ // Step 3: Done!
464
239
  // ─────────────────────────────────────────────────────────────────────────
465
- step(5, totalSteps, 'Setup complete!');
240
+ step(3, totalSteps, 'Ready!');
466
241
  log('');
467
- log(` ${colors.green}You're all set!${colors.reset}`);
242
+ log(` ${colors.green}Your AI assistant now knows WordPress and Skunk!${colors.reset}`);
468
243
  log('');
469
244
  log(' Next steps:');
470
245
  log('');
246
+
471
247
  if (studioExists) {
472
248
  log(' 1. Create a WordPress site:');
473
- log(' studio create my-site');
474
- log('');
475
- log(' 2. Activate Skunk plugins in WordPress admin');
249
+ log(` ${colors.cyan}studio create my-site${colors.reset}`);
476
250
  log('');
251
+ log(' 2. Start chatting with your AI:');
252
+ log(` ${colors.cyan}"Install WooCommerce"${colors.reset}`);
253
+ log(` ${colors.cyan}"Install SkunkCRM"${colors.reset}`);
254
+ log(` ${colors.cyan}"Create a contact form"${colors.reset}`);
255
+ } else {
256
+ log(' 1. Install WordPress Studio');
257
+ log(' 2. Create a site: studio create my-site');
258
+ log(' 3. Chat with your AI to set up plugins');
477
259
  }
478
- log(' 3. Start chatting with your AI assistant');
260
+
479
261
  log('');
480
- log(` ${colors.dim}Docs: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
262
+ log(` ${colors.dim}Guide: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
481
263
  log('');
482
264
 
483
265
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skunkceo/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Install and manage Skunk Global skills for OpenClaw",
5
5
  "bin": {
6
6
  "skunk": "./bin/skunk.js"
@@ -9,13 +9,7 @@
9
9
  "type": "git",
10
10
  "url": "https://github.com/skunkceo/skunk-cli"
11
11
  },
12
- "keywords": [
13
- "openclaw",
14
- "skills",
15
- "wordpress",
16
- "ai",
17
- "skunk"
18
- ],
12
+ "keywords": ["openclaw", "skills", "wordpress", "ai", "skunk"],
19
13
  "author": "Skunk Global",
20
14
  "license": "MIT",
21
15
  "homepage": "https://skunkglobal.com/skills"