@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.
- package/bin/setup.js +66 -268
- 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
|
|
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
|
|
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
|
|
162
|
+
log(`${colors.dim}Welcome! Let's get your AI-powered WordPress toolkit ready.${colors.reset}`);
|
|
304
163
|
log('');
|
|
305
164
|
|
|
306
|
-
const totalSteps =
|
|
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
|
-
//
|
|
324
|
-
|
|
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 (
|
|
353
|
-
const
|
|
354
|
-
success(`
|
|
185
|
+
if (openclawExists) {
|
|
186
|
+
const openclawVersion = getVersion('openclaw');
|
|
187
|
+
success(`OpenClaw installed ${openclawVersion ? `(${openclawVersion})` : ''}`);
|
|
355
188
|
} else {
|
|
356
|
-
error('
|
|
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('
|
|
363
|
-
log(' brew install --cask wordpress-studio');
|
|
191
|
+
log(' OpenClaw is your AI assistant that uses these skills.');
|
|
364
192
|
log('');
|
|
365
|
-
log('
|
|
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
|
|
197
|
+
const proceed = await confirm('Continue installing skills anyway?', true);
|
|
369
198
|
if (!proceed) {
|
|
370
|
-
log('\n Run `skunk setup` again after installing
|
|
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
|
-
|
|
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 (
|
|
390
|
-
|
|
391
|
-
success(
|
|
208
|
+
if (studioExists) {
|
|
209
|
+
const studioVersion = getVersion('studio');
|
|
210
|
+
success(`WordPress Studio installed ${studioVersion ? `(${studioVersion})` : ''}`);
|
|
392
211
|
} else {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
log(
|
|
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
|
|
225
|
+
// Step 2: Install AI Skills
|
|
445
226
|
// ─────────────────────────────────────────────────────────────────────────
|
|
446
|
-
step(
|
|
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
|
-
|
|
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
|
|
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}
|
|
248
|
+
log(`${colors.dim}✓ already installed${colors.reset}`);
|
|
457
249
|
} else {
|
|
458
|
-
log(`${colors.
|
|
250
|
+
log(`${colors.yellow}! not available yet${colors.reset}`);
|
|
459
251
|
}
|
|
460
252
|
}
|
|
461
253
|
|
|
462
254
|
// ─────────────────────────────────────────────────────────────────────────
|
|
463
|
-
// Step
|
|
255
|
+
// Step 3: Done!
|
|
464
256
|
// ─────────────────────────────────────────────────────────────────────────
|
|
465
|
-
step(
|
|
257
|
+
step(3, totalSteps, 'Ready!');
|
|
466
258
|
log('');
|
|
467
|
-
log(` ${colors.green}
|
|
259
|
+
log(` ${colors.green}Skills installed!${colors.reset}`);
|
|
468
260
|
log('');
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
log(
|
|
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('
|
|
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
|
-
|
|
276
|
+
|
|
479
277
|
log('');
|
|
480
|
-
log(` ${colors.dim}
|
|
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.
|
|
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"
|