@skunkceo/cli 1.0.1 → 1.0.5

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 +66 -268
  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
- // ═══════════════════════════════════════════════════════════════════════════
112
- // License Validation
113
- // ═══════════════════════════════════════════════════════════════════════════
114
-
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
98
  // ═══════════════════════════════════════════════════════════════════════════
164
- // Installation Functions
99
+ // Skill Installation
165
100
  // ═══════════════════════════════════════════════════════════════════════════
166
101
 
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,164 +179,103 @@ 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
+ // OpenClaw
183
+ let openclawExists = commandExists('openclaw');
351
184
 
352
- if (studioExists) {
353
- const studioVersion = getVersion('studio');
354
- success(`WordPress Studio installed ${studioVersion ? `(${studioVersion})` : ''}`);
185
+ if (openclawExists) {
186
+ const openclawVersion = getVersion('openclaw');
187
+ success(`OpenClaw installed ${openclawVersion ? `(${openclawVersion})` : ''}`);
355
188
  } else {
356
- error('WordPress Studio not found');
357
- log('');
358
- log(' WordPress Studio is required for local WordPress development.');
359
- log('');
360
- log(' Install it from: https://developer.wordpress.org/studio/');
189
+ error('OpenClaw not found');
361
190
  log('');
362
- log(' On macOS:');
363
- log(' brew install --cask wordpress-studio');
191
+ log(' OpenClaw is your AI assistant that uses these skills.');
364
192
  log('');
365
- log(' Or download from the website and install manually.');
193
+ log(' Follow the setup guide:');
194
+ log(` ${colors.cyan}https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
366
195
  log('');
367
196
 
368
- const proceed = await confirm('Continue setup anyway? (skills only)', false);
197
+ const proceed = await confirm('Continue installing skills anyway?', true);
369
198
  if (!proceed) {
370
- log('\n Run `skunk setup` again after installing WordPress Studio.');
199
+ log('\n Run `skunk setup` again after installing OpenClaw.');
371
200
  rl.close();
372
201
  process.exit(0);
373
202
  }
374
203
  }
375
204
 
376
- // ─────────────────────────────────────────────────────────────────────────
377
- // Step 3: Install Plugins
378
- // ─────────────────────────────────────────────────────────────────────────
379
- step(3, totalSteps, 'Plugins...');
380
- 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
- ]);
205
+ // WordPress Studio
206
+ let studioExists = commandExists('studio');
388
207
 
389
- if (wooChoice === 'yes') {
390
- log(' WooCommerce will be installed when you create a site');
391
- success('WooCommerce selected');
208
+ if (studioExists) {
209
+ const studioVersion = getVersion('studio');
210
+ success(`WordPress Studio installed ${studioVersion ? `(${studioVersion})` : ''}`);
392
211
  } else {
393
- log(` ${colors.dim}Skipping WooCommerce${colors.reset}`);
394
- }
395
- 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
-
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
-
212
+ warn('WordPress Studio not found');
213
+ log('');
214
+ log(' WordPress Studio is amazing for local WordPress development.');
215
+ log(' Your AI can create and manage sites through it.');
216
+ log('');
217
+ log(' Install it from: https://developer.wordpress.org/studio/');
218
+ log('');
219
+ log(' macOS: brew install --cask wordpress-studio');
220
+ log(' Other: Download from the website');
440
221
  log('');
441
222
  }
442
223
 
443
224
  // ─────────────────────────────────────────────────────────────────────────
444
- // Step 4: Install Skills
225
+ // Step 2: Install AI Skills
445
226
  // ─────────────────────────────────────────────────────────────────────────
446
- step(4, totalSteps, 'AI Skills...');
227
+ step(2, totalSteps, 'Installing AI skills...');
228
+ log('');
229
+ log(` ${colors.dim}Skills teach your AI assistant how to work with WordPress${colors.reset}`);
230
+ log(` ${colors.dim}and Skunk products. Installing the essentials...${colors.reset}`);
231
+ log('');
447
232
 
448
- const skills = ['wordpress-studio', 'woocommerce-manager', 'skunkcrm', 'skunkforms', 'skunkpages'];
233
+ // Core skills that enable WordPress + Skunk workflow
234
+ const coreSkills = [
235
+ { name: 'wordpress-studio', desc: 'WordPress site management' },
236
+ { name: 'woocommerce', desc: 'WooCommerce store operations' },
237
+ { name: 'skunkcrm', desc: 'SkunkCRM contact & pipeline management' },
238
+ { name: 'skunkforms', desc: 'SkunkForms form building' },
239
+ { name: 'skunkpages', desc: 'SkunkPages landing page optimization' },
240
+ ];
449
241
 
450
- for (const skill of skills) {
451
- process.stdout.write(` ${skill} `);
452
- const result = await installSkill(skill);
242
+ for (const skill of coreSkills) {
243
+ process.stdout.write(` ${skill.name} `);
244
+ const result = await installSkill(skill.name);
453
245
  if (result.status === 'installed') {
454
- log(`${colors.green}✓${colors.reset}`);
246
+ log(`${colors.green}✓${colors.reset} ${colors.dim}${skill.desc}${colors.reset}`);
455
247
  } else if (result.status === 'exists') {
456
- log(`${colors.dim}(already installed)${colors.reset}`);
248
+ log(`${colors.dim}already installed${colors.reset}`);
457
249
  } else {
458
- log(`${colors.red}✗${colors.reset}`);
250
+ log(`${colors.yellow}! not available yet${colors.reset}`);
459
251
  }
460
252
  }
461
253
 
462
254
  // ─────────────────────────────────────────────────────────────────────────
463
- // Step 5: Done!
255
+ // Step 3: Done!
464
256
  // ─────────────────────────────────────────────────────────────────────────
465
- step(5, totalSteps, 'Setup complete!');
257
+ step(3, totalSteps, 'Ready!');
466
258
  log('');
467
- log(` ${colors.green}You're all set!${colors.reset}`);
259
+ log(` ${colors.green}Skills installed!${colors.reset}`);
468
260
  log('');
469
- log(' Next steps:');
470
- log('');
471
- if (studioExists) {
472
- log(' 1. Create a WordPress site:');
473
- log(' studio create my-site');
261
+
262
+ if (openclawExists) {
263
+ log(` ${colors.yellow}Restart OpenClaw to load the new skills:${colors.reset}`);
264
+ log(` ${colors.cyan}openclaw gateway restart${colors.reset}`);
474
265
  log('');
475
- log(' 2. Activate Skunk plugins in WordPress admin');
266
+ log(' Then start chatting:');
476
267
  log('');
268
+ log(` ${colors.cyan}"Create a new WordPress site called my-store"${colors.reset}`);
269
+ log(` ${colors.cyan}"Install WooCommerce and set up a UK store"${colors.reset}`);
270
+ log(` ${colors.cyan}"Install SkunkCRM"${colors.reset}`);
271
+ log(` ${colors.cyan}"Create a contact form"${colors.reset}`);
272
+ } else {
273
+ log(' After installing OpenClaw, run `skunk setup` again');
274
+ log(' to verify everything is ready.');
477
275
  }
478
- log(' 3. Start chatting with your AI assistant');
276
+
479
277
  log('');
480
- log(` ${colors.dim}Docs: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
278
+ log(` ${colors.dim}Guide: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
481
279
  log('');
482
280
 
483
281
  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.5",
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"