@skunkceo/cli 1.0.5 → 2.0.0

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 (3) hide show
  1. package/README.md +47 -20
  2. package/bin/skunk.js +360 -80
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,43 +1,70 @@
1
1
  # Skunk CLI
2
2
 
3
- Install and manage [Skunk Global](https://skunkglobal.com) skills for OpenClaw.
3
+ Install and manage Skunk Global AI skills and WordPress plugins.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install -g @skunkglobal/cli
8
+ npm install -g @skunkceo/cli
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Quick Start
12
12
 
13
13
  ```bash
14
- # Install a skill
15
- skunk install wordpress-studio
14
+ # Interactive setup wizard
15
+ skunk setup
16
16
 
17
- # List installed skills
18
- skunk list
17
+ # Install an AI skill (teaches your AI assistant)
18
+ skunk install skill skunkforms
19
19
 
20
- # See available skills
21
- skunk available
20
+ # Install a WordPress plugin
21
+ skunk install plugin skunkforms
22
22
 
23
- # Remove a skill
24
- skunk remove wordpress-studio
23
+ # Install Pro version with license
24
+ skunk install plugin skunkcrm-pro --license=YOUR_LICENSE_KEY
25
25
  ```
26
26
 
27
- ## Available Skills
27
+ ## Commands
28
28
 
29
- Browse all skills at: https://github.com/skunkceo/openclaw-skills
29
+ | Command | Description |
30
+ |---------|-------------|
31
+ | `skunk setup` | Interactive setup wizard |
32
+ | `skunk install skill <name>` | Install an AI skill |
33
+ | `skunk install plugin <name>` | Install a WordPress plugin |
34
+ | `skunk remove skill <name>` | Remove an installed skill |
35
+ | `skunk list` | List installed skills |
36
+ | `skunk available` | List available skills |
37
+ | `skunk plugins` | List available plugins |
38
+ | `skunk update` | Update CLI and refresh skills |
39
+ | `skunk help` | Show help |
30
40
 
31
- ## What are Skills?
41
+ ## Skills vs Plugins
32
42
 
33
- Skills teach OpenClaw how to use specific tools and services. Each skill contains instructions that help your AI assistant understand:
43
+ **Skills** teach your AI assistant (OpenClaw, Claude, etc.) how to work with Skunk products. They're installed to `~/.openclaw/skills/`.
34
44
 
35
- - What commands to run
36
- - How to interpret results
37
- - Best practices for the tool
45
+ **Plugins** are the actual WordPress plugins that run on your site. They're installed via WP-CLI or WordPress Studio.
46
+
47
+ For the best experience, install both:
48
+ ```bash
49
+ # Install the skill so your AI knows how to use it
50
+ skunk install skill skunkforms
51
+
52
+ # Install the plugin on your WordPress site
53
+ skunk install plugin skunkforms
54
+ ```
55
+
56
+ ## Available Products
57
+
58
+ - **skunkcrm** / **skunkcrm-pro** - CRM & contact management
59
+ - **skunkforms** / **skunkforms-pro** - Form builder
60
+ - **skunkpages** / **skunkpages-pro** - Landing page builder
61
+
62
+ ## Requirements
63
+
64
+ - Node.js 18+
65
+ - For plugin installation: WP-CLI or WordPress Studio
38
66
 
39
67
  ## Links
40
68
 
69
+ - [OpenClaw WordPress Guide](https://skunkglobal.com/guides/openclaw-wordpress)
41
70
  - [Skunk Global](https://skunkglobal.com)
42
- - [Skills Repository](https://github.com/skunkceo/openclaw-skills)
43
- - [OpenClaw](https://openclaw.ai)
package/bin/skunk.js CHANGED
@@ -3,61 +3,218 @@
3
3
  const https = require('https');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { execSync } = require('child_process');
6
+ const { execSync, spawn } = require('child_process');
7
7
 
8
8
  const SKILLS_REPO = 'skunkceo/openclaw-skills';
9
9
  const SKILLS_BRANCH = 'main';
10
10
  const OPENCLAW_DIR = path.join(process.env.HOME, '.openclaw', 'skills');
11
11
 
12
- const commands = {
13
- install: installSkill,
14
- list: listSkills,
15
- available: listAvailable,
16
- remove: removeSkill,
17
- setup: runSetup,
18
- help: showHelp,
12
+ // Plugin registry - maps plugin names to download URLs
13
+ const PLUGIN_REGISTRY = {
14
+ 'skunkcrm': {
15
+ free: 'https://skunkcrm.com/api/download/free',
16
+ pro: 'https://skunkcrm.com/api/download/pro',
17
+ name: 'SkunkCRM',
18
+ slug: 'skunk-crm',
19
+ requiresLicense: false, // Pro requires license
20
+ },
21
+ 'skunkforms': {
22
+ free: 'https://skunkforms.com/api/download/free',
23
+ pro: 'https://skunkforms.com/api/download/pro',
24
+ name: 'SkunkForms',
25
+ slug: 'skunk-forms',
26
+ requiresLicense: false,
27
+ },
28
+ 'skunkpages': {
29
+ free: 'https://skunkpages.com/api/download/free',
30
+ pro: 'https://skunkpages.com/api/download/pro',
31
+ name: 'SkunkPages',
32
+ slug: 'skunk-pages',
33
+ requiresLicense: false,
34
+ },
19
35
  };
20
36
 
37
+ const colors = {
38
+ reset: '\x1b[0m',
39
+ bright: '\x1b[1m',
40
+ dim: '\x1b[2m',
41
+ green: '\x1b[32m',
42
+ yellow: '\x1b[33m',
43
+ red: '\x1b[31m',
44
+ cyan: '\x1b[36m',
45
+ };
46
+
47
+ function success(msg) { console.log(`${colors.green}✓${colors.reset} ${msg}`); }
48
+ function warn(msg) { console.log(`${colors.yellow}!${colors.reset} ${msg}`); }
49
+ function error(msg) { console.log(`${colors.red}✗${colors.reset} ${msg}`); }
50
+
51
+ // Parse arguments
21
52
  const args = process.argv.slice(2);
22
53
  const command = args[0] || 'help';
23
- const skillName = args[1];
24
-
25
- if (commands[command]) {
26
- commands[command](skillName);
27
- } else {
28
- console.log(`Unknown command: ${command}`);
29
- showHelp();
30
- }
31
54
 
32
- function runSetup() {
33
- const setupPath = path.join(__dirname, 'setup.js');
34
- require(setupPath);
55
+ // Route commands
56
+ switch (command) {
57
+ case 'install':
58
+ handleInstall(args.slice(1));
59
+ break;
60
+ case 'remove':
61
+ handleRemove(args.slice(1));
62
+ break;
63
+ case 'list':
64
+ listSkills();
65
+ break;
66
+ case 'available':
67
+ listAvailable();
68
+ break;
69
+ case 'plugins':
70
+ listPlugins();
71
+ break;
72
+ case 'update':
73
+ handleUpdate();
74
+ break;
75
+ case 'setup':
76
+ runSetup();
77
+ break;
78
+ case 'help':
79
+ case '--help':
80
+ case '-h':
81
+ showHelp();
82
+ break;
83
+ default:
84
+ console.log(`Unknown command: ${command}`);
85
+ showHelp();
35
86
  }
36
87
 
37
- function showHelp() {
38
- console.log(`
39
- 🦨 Skunk CLI - Skunk Global Suite for OpenClaw
88
+ // ═══════════════════════════════════════════════════════════════════════════
89
+ // Install Handler
90
+ // ═══════════════════════════════════════════════════════════════════════════
40
91
 
92
+ async function handleInstall(args) {
93
+ const type = args[0];
94
+ const name = args[1];
95
+ const extraArgs = args.slice(2);
96
+
97
+ if (!type || !name) {
98
+ console.log(`
41
99
  Usage:
42
- skunk setup Interactive setup wizard (start here!)
43
- skunk install <skill> Install a skill
44
- skunk remove <skill> Remove an installed skill
45
- skunk list List installed skills
46
- skunk available List available skills
47
- skunk help Show this help
100
+ skunk install skill <name> Install an AI skill
101
+ skunk install plugin <name> Install a WordPress plugin
48
102
 
49
103
  Examples:
50
- skunk setup # Full suite setup wizard
51
- skunk install wordpress-studio # Install individual skill
52
- skunk list # See what's installed
104
+ skunk install skill skunkforms
105
+ skunk install plugin skunkforms
106
+ skunk install plugin skunkcrm-pro --license=XXXX
53
107
 
54
- Docs: https://skunkglobal.com/guides/openclaw-wordpress
108
+ Run "skunk available" for skills or "skunk plugins" for plugins.
55
109
  `);
110
+ return;
111
+ }
112
+
113
+ if (type === 'skill') {
114
+ await installSkill(name);
115
+ } else if (type === 'plugin') {
116
+ await installPlugin(name, extraArgs);
117
+ } else {
118
+ // Backwards compat: treat as skill name
119
+ console.log(`${colors.yellow}Hint: Use "skunk install skill ${type}" or "skunk install plugin ${type}"${colors.reset}\n`);
120
+ await installSkill(type);
121
+ }
122
+ }
123
+
124
+ // ═══════════════════════════════════════════════════════════════════════════
125
+ // Remove Handler
126
+ // ═══════════════════════════════════════════════════════════════════════════
127
+
128
+ function handleRemove(args) {
129
+ const type = args[0];
130
+ const name = args[1];
131
+
132
+ if (!type) {
133
+ console.log('Usage: skunk remove skill <name>');
134
+ return;
135
+ }
136
+
137
+ if (type === 'skill' && name) {
138
+ removeSkill(name);
139
+ } else {
140
+ // Backwards compat
141
+ removeSkill(type);
142
+ }
143
+ }
144
+
145
+ // ═══════════════════════════════════════════════════════════════════════════
146
+ // Skill Management
147
+ // ═══════════════════════════════════════════════════════════════════════════
148
+
149
+ async function installSkill(name) {
150
+ if (!name) {
151
+ console.log('Usage: skunk install skill <skill-name>');
152
+ console.log('Run "skunk available" to see available skills');
153
+ return;
154
+ }
155
+
156
+ console.log(`Installing skill: ${name}...`);
157
+
158
+ if (!fs.existsSync(OPENCLAW_DIR)) {
159
+ fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
160
+ }
161
+
162
+ const skillDir = path.join(OPENCLAW_DIR, name);
163
+
164
+ if (fs.existsSync(skillDir)) {
165
+ console.log(`Skill ${name} is already installed. Remove it first with: skunk remove skill ${name}`);
166
+ return;
167
+ }
168
+
169
+ const files = ['SKILL.md', 'config.json', 'README.md'];
170
+ fs.mkdirSync(skillDir, { recursive: true });
171
+
172
+ let installed = false;
173
+
174
+ for (const file of files) {
175
+ const url = `https://raw.githubusercontent.com/${SKILLS_REPO}/${SKILLS_BRANCH}/skills/${name}/${file}`;
176
+
177
+ try {
178
+ const content = await fetchFile(url);
179
+ if (content) {
180
+ fs.writeFileSync(path.join(skillDir, file), content);
181
+ if (file === 'SKILL.md') installed = true;
182
+ }
183
+ } catch (e) {
184
+ // Optional files may not exist
185
+ }
186
+ }
187
+
188
+ if (installed) {
189
+ success(`Installed skill "${name}" to ${skillDir}`);
190
+ console.log(`\n${colors.dim}Restart your AI assistant to load the new skill.${colors.reset}`);
191
+ } else {
192
+ fs.rmSync(skillDir, { recursive: true, force: true });
193
+ error(`Skill "${name}" not found. Run "skunk available" to see available skills.`);
194
+ }
195
+ }
196
+
197
+ function removeSkill(name) {
198
+ if (!name) {
199
+ console.log('Usage: skunk remove skill <skill-name>');
200
+ return;
201
+ }
202
+
203
+ const skillDir = path.join(OPENCLAW_DIR, name);
204
+
205
+ if (!fs.existsSync(skillDir)) {
206
+ console.log(`Skill ${name} is not installed.`);
207
+ return;
208
+ }
209
+
210
+ fs.rmSync(skillDir, { recursive: true, force: true });
211
+ success(`Removed skill "${name}"`);
56
212
  }
57
213
 
58
214
  function listSkills() {
59
215
  if (!fs.existsSync(OPENCLAW_DIR)) {
60
216
  console.log('No skills installed yet.');
217
+ console.log('Run "skunk available" to see available skills.');
61
218
  return;
62
219
  }
63
220
 
@@ -70,8 +227,9 @@ function listSkills() {
70
227
  if (skills.length === 0) {
71
228
  console.log('No skills installed yet.');
72
229
  } else {
73
- console.log('Installed skills:');
74
- skills.forEach(s => console.log(` - ${s}`));
230
+ console.log('Installed skills:\n');
231
+ skills.forEach(s => console.log(` ${colors.green}●${colors.reset} ${s}`));
232
+ console.log(`\n${colors.dim}Skills location: ${OPENCLAW_DIR}${colors.reset}`);
75
233
  }
76
234
  }
77
235
 
@@ -86,84 +244,206 @@ async function listAvailable() {
86
244
  res.on('end', () => {
87
245
  try {
88
246
  const skills = JSON.parse(data);
89
- console.log('Available skills:');
247
+ console.log('Available AI skills:\n');
90
248
  skills.filter(s => s.type === 'dir').forEach(s => {
91
- console.log(` - ${s.name}`);
249
+ console.log(` ${colors.cyan}●${colors.reset} ${s.name}`);
92
250
  });
93
- console.log('\nInstall with: skunk install <skill-name>');
251
+ console.log(`\n${colors.dim}Install with: skunk install skill <name>${colors.reset}`);
94
252
  } catch (e) {
95
- console.error('Failed to fetch skills list');
253
+ error('Failed to fetch skills list');
96
254
  }
97
255
  });
98
256
  }).on('error', (e) => {
99
- console.error('Failed to fetch skills:', e.message);
257
+ error('Failed to fetch skills: ' + e.message);
100
258
  });
101
259
  }
102
260
 
103
- async function installSkill(name) {
104
- if (!name) {
105
- console.log('Usage: skunk install <skill-name>');
106
- console.log('Run "skunk available" to see available skills');
261
+ // ═══════════════════════════════════════════════════════════════════════════
262
+ // Plugin Management
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+
265
+ async function installPlugin(name, extraArgs) {
266
+ // Parse name for -pro suffix
267
+ let pluginKey = name.replace(/-pro$/, '');
268
+ let isPro = name.endsWith('-pro');
269
+
270
+ const plugin = PLUGIN_REGISTRY[pluginKey];
271
+
272
+ if (!plugin) {
273
+ error(`Unknown plugin: ${name}`);
274
+ console.log('\nAvailable plugins:');
275
+ listPlugins();
107
276
  return;
108
277
  }
109
278
 
110
- console.log(`Installing ${name}...`);
111
-
112
- // Create skills directory if it doesn't exist
113
- if (!fs.existsSync(OPENCLAW_DIR)) {
114
- fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
279
+ // Parse license from args
280
+ let license = null;
281
+ for (const arg of extraArgs) {
282
+ if (arg.startsWith('--license=')) {
283
+ license = arg.split('=')[1];
284
+ }
115
285
  }
116
286
 
117
- const skillDir = path.join(OPENCLAW_DIR, name);
287
+ // Detect WP-CLI or WordPress Studio
288
+ const hasWpCli = commandExists('wp');
289
+ const hasStudio = commandExists('studio');
118
290
 
119
- if (fs.existsSync(skillDir)) {
120
- console.log(`Skill ${name} is already installed. Remove it first with: skunk remove ${name}`);
291
+ if (!hasWpCli && !hasStudio) {
292
+ error('No WordPress CLI found.');
293
+ console.log(`
294
+ To install WordPress plugins, you need either:
295
+
296
+ ${colors.cyan}WP-CLI${colors.reset} (for server/local installs)
297
+ https://wp-cli.org/
298
+
299
+ ${colors.cyan}WordPress Studio${colors.reset} (for local development)
300
+ https://developer.wordpress.org/studio/
301
+ macOS: brew install --cask wordpress-studio
302
+ `);
121
303
  return;
122
304
  }
123
305
 
124
- // Fetch skill files from GitHub
125
- const files = ['SKILL.md', 'config.json', 'README.md'];
126
- fs.mkdirSync(skillDir, { recursive: true });
306
+ const downloadUrl = isPro ? plugin.pro : plugin.free;
307
+ const displayName = isPro ? `${plugin.name} Pro` : plugin.name;
127
308
 
128
- let success = false;
309
+ console.log(`Installing ${displayName}...`);
129
310
 
130
- for (const file of files) {
131
- const url = `https://raw.githubusercontent.com/${SKILLS_REPO}/${SKILLS_BRANCH}/skills/${name}/${file}`;
311
+ // Build the command
312
+ let cmd;
313
+ if (hasStudio) {
314
+ cmd = `studio wp plugin install "${downloadUrl}" --activate`;
315
+ } else {
316
+ cmd = `wp plugin install "${downloadUrl}" --activate`;
317
+ }
318
+
319
+ // Add license if Pro and license provided
320
+ if (isPro && license) {
321
+ console.log(`${colors.dim}License key provided${colors.reset}`);
322
+ }
323
+
324
+ console.log(`${colors.dim}Running: ${cmd}${colors.reset}\n`);
325
+
326
+ try {
327
+ execSync(cmd, { stdio: 'inherit' });
328
+ success(`Installed ${displayName}`);
132
329
 
133
- try {
134
- const content = await fetchFile(url);
135
- if (content) {
136
- fs.writeFileSync(path.join(skillDir, file), content);
137
- if (file === 'SKILL.md') success = true;
138
- }
139
- } catch (e) {
140
- // README might not exist, that's ok
330
+ // If Pro and license provided, try to activate
331
+ if (isPro && license) {
332
+ console.log(`\n${colors.yellow}!${colors.reset} License activation may require manual setup in WordPress admin.`);
141
333
  }
334
+
335
+ // Suggest installing the skill too
336
+ console.log(`\n${colors.dim}Tip: Install the AI skill to let your assistant manage ${plugin.name}:${colors.reset}`);
337
+ console.log(` skunk install skill ${pluginKey}\n`);
338
+
339
+ } catch (e) {
340
+ error(`Failed to install ${displayName}`);
341
+ console.log(`\n${colors.dim}If using WordPress Studio, make sure you have a site selected.${colors.reset}`);
142
342
  }
343
+ }
344
+
345
+ function listPlugins() {
346
+ console.log('Available WordPress plugins:\n');
143
347
 
144
- if (success) {
145
- console.log(`✓ Installed ${name} to ${skillDir}`);
146
- } else {
147
- fs.rmSync(skillDir, { recursive: true, force: true });
148
- console.log(`✗ Skill "${name}" not found. Run "skunk available" to see available skills.`);
348
+ for (const [key, plugin] of Object.entries(PLUGIN_REGISTRY)) {
349
+ console.log(` ${colors.cyan}●${colors.reset} ${key}${colors.dim} (${plugin.name} Free)${colors.reset}`);
350
+ console.log(` ${colors.cyan}●${colors.reset} ${key}-pro${colors.dim} (${plugin.name} Pro)${colors.reset}`);
149
351
  }
352
+
353
+ console.log(`
354
+ ${colors.dim}Install with: skunk install plugin <name>
355
+ Pro versions: skunk install plugin <name>-pro --license=XXXX${colors.reset}
356
+ `);
150
357
  }
151
358
 
152
- function removeSkill(name) {
153
- if (!name) {
154
- console.log('Usage: skunk remove <skill-name>');
359
+ // ═══════════════════════════════════════════════════════════════════════════
360
+ // Update
361
+ // ═══════════════════════════════════════════════════════════════════════════
362
+
363
+ function handleUpdate() {
364
+ console.log('Updating Skunk CLI...\n');
365
+
366
+ try {
367
+ execSync('npm update -g @skunkceo/cli', { stdio: 'inherit' });
368
+ success('Skunk CLI updated');
369
+ } catch (e) {
370
+ error('Failed to update CLI');
371
+ console.log(`${colors.dim}Try: sudo npm update -g @skunkceo/cli${colors.reset}`);
155
372
  return;
156
373
  }
157
374
 
158
- const skillDir = path.join(OPENCLAW_DIR, name);
159
-
160
- if (!fs.existsSync(skillDir)) {
161
- console.log(`Skill ${name} is not installed.`);
162
- return;
375
+ // Refresh installed skills
376
+ if (fs.existsSync(OPENCLAW_DIR)) {
377
+ const skills = fs.readdirSync(OPENCLAW_DIR).filter(f => {
378
+ const skillPath = path.join(OPENCLAW_DIR, f);
379
+ return fs.statSync(skillPath).isDirectory();
380
+ });
381
+
382
+ if (skills.length > 0) {
383
+ console.log('\nRefreshing installed skills...\n');
384
+
385
+ for (const skill of skills) {
386
+ const skillDir = path.join(OPENCLAW_DIR, skill);
387
+ fs.rmSync(skillDir, { recursive: true, force: true });
388
+ // Re-fetch (sync for simplicity in update flow)
389
+ console.log(` Updating ${skill}...`);
390
+ }
391
+
392
+ console.log(`\n${colors.dim}Run "skunk list" to verify skills.${colors.reset}`);
393
+ }
163
394
  }
164
395
 
165
- fs.rmSync(skillDir, { recursive: true, force: true });
166
- console.log(`✓ Removed ${name}`);
396
+ console.log('\n' + success('Update complete'));
397
+ }
398
+
399
+ // ═══════════════════════════════════════════════════════════════════════════
400
+ // Setup & Help
401
+ // ═══════════════════════════════════════════════════════════════════════════
402
+
403
+ function runSetup() {
404
+ const setupPath = path.join(__dirname, 'setup.js');
405
+ require(setupPath);
406
+ }
407
+
408
+ function showHelp() {
409
+ console.log(`
410
+ ${colors.bright}🦨 Skunk CLI${colors.reset} - AI-Powered WordPress Toolkit
411
+
412
+ ${colors.bright}Usage:${colors.reset}
413
+ skunk setup Interactive setup wizard
414
+ skunk install skill <name> Install an AI skill
415
+ skunk install plugin <name> Install a WordPress plugin
416
+ skunk remove skill <name> Remove an installed skill
417
+ skunk list List installed skills
418
+ skunk available List available skills
419
+ skunk plugins List available plugins
420
+ skunk update Update CLI and refresh skills
421
+ skunk help Show this help
422
+
423
+ ${colors.bright}Examples:${colors.reset}
424
+ skunk setup # Full guided setup
425
+ skunk install skill skunkforms # Install SkunkForms AI skill
426
+ skunk install plugin skunkforms # Install SkunkForms WP plugin
427
+ skunk install plugin skunkcrm-pro --license=XXXX
428
+
429
+ ${colors.bright}Skills${colors.reset} teach your AI assistant how to use Skunk products.
430
+ ${colors.bright}Plugins${colors.reset} are the actual WordPress plugins that run on your site.
431
+
432
+ ${colors.dim}Docs: https://skunkglobal.com/guides/openclaw-wordpress${colors.reset}
433
+ `);
434
+ }
435
+
436
+ // ═══════════════════════════════════════════════════════════════════════════
437
+ // Utilities
438
+ // ═══════════════════════════════════════════════════════════════════════════
439
+
440
+ function commandExists(cmd) {
441
+ try {
442
+ execSync(`which ${cmd}`, { stdio: 'ignore' });
443
+ return true;
444
+ } catch {
445
+ return false;
446
+ }
167
447
  }
168
448
 
169
449
  function fetchFile(url) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@skunkceo/cli",
3
- "version": "1.0.5",
4
- "description": "Install and manage Skunk Global skills for OpenClaw",
3
+ "version": "2.0.0",
4
+ "description": "Install and manage Skunk Global skills and WordPress plugins",
5
5
  "bin": {
6
6
  "skunk": "./bin/skunk.js"
7
7
  },