@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.
- package/bin/setup.js +44 -262
- 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
98
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
112
|
-
//
|
|
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
|
|
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,52 +179,24 @@ 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
|
+
// 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
|
-
|
|
189
|
+
warn('WordPress Studio not found');
|
|
357
190
|
log('');
|
|
358
|
-
log(' WordPress Studio is
|
|
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('
|
|
363
|
-
log('
|
|
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?
|
|
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
|
|
208
|
+
// Step 2: Install AI Skills
|
|
378
209
|
// ─────────────────────────────────────────────────────────────────────────
|
|
379
|
-
step(
|
|
210
|
+
step(2, totalSteps, 'Installing AI skills...');
|
|
380
211
|
log('');
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
|
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}
|
|
231
|
+
log(`${colors.dim}✓ already installed${colors.reset}`);
|
|
457
232
|
} else {
|
|
458
|
-
log(`${colors.
|
|
233
|
+
log(`${colors.yellow}! not available yet${colors.reset}`);
|
|
459
234
|
}
|
|
460
235
|
}
|
|
461
236
|
|
|
462
237
|
// ─────────────────────────────────────────────────────────────────────────
|
|
463
|
-
// Step
|
|
238
|
+
// Step 3: Done!
|
|
464
239
|
// ─────────────────────────────────────────────────────────────────────────
|
|
465
|
-
step(
|
|
240
|
+
step(3, totalSteps, 'Ready!');
|
|
466
241
|
log('');
|
|
467
|
-
log(` ${colors.green}
|
|
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(
|
|
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
|
-
|
|
260
|
+
|
|
479
261
|
log('');
|
|
480
|
-
log(` ${colors.dim}
|
|
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.
|
|
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"
|