@skunkceo/cli 1.0.0 → 1.0.1
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 +490 -0
- package/bin/skunk.js +12 -5
- package/package.json +8 -2
package/bin/setup.js
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync, spawn } = require('child_process');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
// ASCII Art
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
const SKUNK_LOGO = `
|
|
14
|
+
███████╗██╗ ██╗██╗ ██╗███╗ ██╗██╗ ██╗
|
|
15
|
+
██╔════╝██║ ██╔╝██║ ██║████╗ ██║██║ ██╔╝
|
|
16
|
+
███████╗█████╔╝ ██║ ██║██╔██╗ ██║█████╔╝
|
|
17
|
+
╚════██║██╔═██╗ ██║ ██║██║╚██╗██║██╔═██╗
|
|
18
|
+
███████║██║ ██╗╚██████╔╝██║ ╚████║██║ ██╗
|
|
19
|
+
╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝
|
|
20
|
+
GLOBAL
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
// Utilities
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
const colors = {
|
|
28
|
+
reset: '\x1b[0m',
|
|
29
|
+
bright: '\x1b[1m',
|
|
30
|
+
dim: '\x1b[2m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
red: '\x1b[31m',
|
|
34
|
+
cyan: '\x1b[36m',
|
|
35
|
+
magenta: '\x1b[35m',
|
|
36
|
+
white: '\x1b[37m',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function log(msg = '') {
|
|
40
|
+
console.log(msg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function success(msg) {
|
|
44
|
+
console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function warn(msg) {
|
|
48
|
+
console.log(`${colors.yellow}!${colors.reset} ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function error(msg) {
|
|
52
|
+
console.log(`${colors.red}✗${colors.reset} ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function step(num, total, msg) {
|
|
56
|
+
console.log(`\n${colors.cyan}[${num}/${total}]${colors.reset} ${colors.bright}${msg}${colors.reset}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function commandExists(cmd) {
|
|
60
|
+
try {
|
|
61
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getVersion(cmd) {
|
|
69
|
+
try {
|
|
70
|
+
return execSync(`${cmd} --version`, { encoding: 'utf8' }).trim().split('\n')[0];
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
77
|
+
// Prompts
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
79
|
+
|
|
80
|
+
const rl = readline.createInterface({
|
|
81
|
+
input: process.stdin,
|
|
82
|
+
output: process.stdout,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function ask(question) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
rl.question(question, resolve);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function confirm(question, defaultYes = true) {
|
|
92
|
+
const hint = defaultYes ? '(Y/n)' : '(y/N)';
|
|
93
|
+
const answer = await ask(`${question} ${hint} `);
|
|
94
|
+
if (!answer) return defaultYes;
|
|
95
|
+
return answer.toLowerCase().startsWith('y');
|
|
96
|
+
}
|
|
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
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
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
|
+
async function installSkill(skillName) {
|
|
244
|
+
const skillsDir = path.join(process.env.HOME, '.openclaw', 'skills');
|
|
245
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
246
|
+
|
|
247
|
+
if (fs.existsSync(skillDir)) {
|
|
248
|
+
return { status: 'exists' };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Fetch from GitHub
|
|
252
|
+
const files = ['SKILL.md', 'config.json'];
|
|
253
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
254
|
+
|
|
255
|
+
for (const file of files) {
|
|
256
|
+
const url = `https://raw.githubusercontent.com/skunkceo/openclaw-skills/main/skills/${skillName}/${file}`;
|
|
257
|
+
try {
|
|
258
|
+
const content = await fetchFile(url);
|
|
259
|
+
if (content) {
|
|
260
|
+
fs.writeFileSync(path.join(skillDir, file), content);
|
|
261
|
+
}
|
|
262
|
+
} catch (e) {
|
|
263
|
+
// Optional files may not exist
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (fs.existsSync(path.join(skillDir, 'SKILL.md'))) {
|
|
268
|
+
return { status: 'installed' };
|
|
269
|
+
} else {
|
|
270
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
271
|
+
return { status: 'failed' };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function fetchFile(url) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
https.get(url, { headers: { 'User-Agent': 'skunk-cli' } }, (res) => {
|
|
278
|
+
if (res.statusCode === 404) {
|
|
279
|
+
resolve(null);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (res.statusCode !== 200) {
|
|
283
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
let data = '';
|
|
287
|
+
res.on('data', (chunk) => (data += chunk));
|
|
288
|
+
res.on('end', () => resolve(data));
|
|
289
|
+
}).on('error', reject);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
294
|
+
// Main Setup Flow
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
296
|
+
|
|
297
|
+
async function main() {
|
|
298
|
+
console.clear();
|
|
299
|
+
|
|
300
|
+
// Show splash
|
|
301
|
+
log(colors.bright + SKUNK_LOGO + colors.reset);
|
|
302
|
+
log('');
|
|
303
|
+
log(`${colors.dim}Welcome! Let's get your Skunk suite set up.${colors.reset}`);
|
|
304
|
+
log('');
|
|
305
|
+
|
|
306
|
+
const totalSteps = 5;
|
|
307
|
+
|
|
308
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
309
|
+
// Step 1: Environment checks
|
|
310
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
311
|
+
step(1, totalSteps, 'Checking environment...');
|
|
312
|
+
|
|
313
|
+
// Node.js
|
|
314
|
+
const nodeVersion = getVersion('node');
|
|
315
|
+
if (nodeVersion) {
|
|
316
|
+
success(`Node.js ${nodeVersion}`);
|
|
317
|
+
} else {
|
|
318
|
+
error('Node.js not found');
|
|
319
|
+
log('\n Install Node.js: https://nodejs.org/');
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
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');
|
|
351
|
+
|
|
352
|
+
if (studioExists) {
|
|
353
|
+
const studioVersion = getVersion('studio');
|
|
354
|
+
success(`WordPress Studio installed ${studioVersion ? `(${studioVersion})` : ''}`);
|
|
355
|
+
} 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/');
|
|
361
|
+
log('');
|
|
362
|
+
log(' On macOS:');
|
|
363
|
+
log(' brew install --cask wordpress-studio');
|
|
364
|
+
log('');
|
|
365
|
+
log(' Or download from the website and install manually.');
|
|
366
|
+
log('');
|
|
367
|
+
|
|
368
|
+
const proceed = await confirm('Continue setup anyway? (skills only)', false);
|
|
369
|
+
if (!proceed) {
|
|
370
|
+
log('\n Run `skunk setup` again after installing WordPress Studio.');
|
|
371
|
+
rl.close();
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
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
|
+
]);
|
|
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
|
+
}
|
|
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
|
+
|
|
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'];
|
|
449
|
+
|
|
450
|
+
for (const skill of skills) {
|
|
451
|
+
process.stdout.write(` ${skill} `);
|
|
452
|
+
const result = await installSkill(skill);
|
|
453
|
+
if (result.status === 'installed') {
|
|
454
|
+
log(`${colors.green}✓${colors.reset}`);
|
|
455
|
+
} else if (result.status === 'exists') {
|
|
456
|
+
log(`${colors.dim}(already installed)${colors.reset}`);
|
|
457
|
+
} else {
|
|
458
|
+
log(`${colors.red}✗${colors.reset}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
463
|
+
// Step 5: Done!
|
|
464
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
465
|
+
step(5, totalSteps, 'Setup complete!');
|
|
466
|
+
log('');
|
|
467
|
+
log(` ${colors.green}You're all set!${colors.reset}`);
|
|
468
|
+
log('');
|
|
469
|
+
log(' Next steps:');
|
|
470
|
+
log('');
|
|
471
|
+
if (studioExists) {
|
|
472
|
+
log(' 1. Create a WordPress site:');
|
|
473
|
+
log(' studio create my-site');
|
|
474
|
+
log('');
|
|
475
|
+
log(' 2. Activate Skunk plugins in WordPress admin');
|
|
476
|
+
log('');
|
|
477
|
+
}
|
|
478
|
+
log(' 3. Start chatting with your AI assistant');
|
|
479
|
+
log('');
|
|
480
|
+
log(` ${colors.dim}Docs: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}`);
|
|
481
|
+
log('');
|
|
482
|
+
|
|
483
|
+
rl.close();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Run
|
|
487
|
+
main().catch((err) => {
|
|
488
|
+
console.error('Setup failed:', err.message);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
});
|
package/bin/skunk.js
CHANGED
|
@@ -14,6 +14,7 @@ const commands = {
|
|
|
14
14
|
list: listSkills,
|
|
15
15
|
available: listAvailable,
|
|
16
16
|
remove: removeSkill,
|
|
17
|
+
setup: runSetup,
|
|
17
18
|
help: showHelp,
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -28,11 +29,17 @@ if (commands[command]) {
|
|
|
28
29
|
showHelp();
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function runSetup() {
|
|
33
|
+
const setupPath = path.join(__dirname, 'setup.js');
|
|
34
|
+
require(setupPath);
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
function showHelp() {
|
|
32
38
|
console.log(`
|
|
33
|
-
🦨 Skunk CLI -
|
|
39
|
+
🦨 Skunk CLI - Skunk Global Suite for OpenClaw
|
|
34
40
|
|
|
35
41
|
Usage:
|
|
42
|
+
skunk setup Interactive setup wizard (start here!)
|
|
36
43
|
skunk install <skill> Install a skill
|
|
37
44
|
skunk remove <skill> Remove an installed skill
|
|
38
45
|
skunk list List installed skills
|
|
@@ -40,11 +47,11 @@ Usage:
|
|
|
40
47
|
skunk help Show this help
|
|
41
48
|
|
|
42
49
|
Examples:
|
|
43
|
-
skunk
|
|
44
|
-
skunk install
|
|
45
|
-
skunk list
|
|
50
|
+
skunk setup # Full suite setup wizard
|
|
51
|
+
skunk install wordpress-studio # Install individual skill
|
|
52
|
+
skunk list # See what's installed
|
|
46
53
|
|
|
47
|
-
|
|
54
|
+
Docs: https://skunkglobal.com/guides/openclaw-wordpress
|
|
48
55
|
`);
|
|
49
56
|
}
|
|
50
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skunkceo/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Install and manage Skunk Global skills for OpenClaw",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skunk": "./bin/skunk.js"
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "https://github.com/skunkceo/skunk-cli"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"openclaw",
|
|
14
|
+
"skills",
|
|
15
|
+
"wordpress",
|
|
16
|
+
"ai",
|
|
17
|
+
"skunk"
|
|
18
|
+
],
|
|
13
19
|
"author": "Skunk Global",
|
|
14
20
|
"license": "MIT",
|
|
15
21
|
"homepage": "https://skunkglobal.com/skills"
|