@cmo-so/skills 0.3.0 → 0.4.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.
- package/README.md +42 -17
- package/bin/cmo-skills.mjs +192 -109
- package/catalog.json +1 -1
- package/package.json +12 -2
- package/skills/cmo-ab-testing/SKILL.md +1 -1
- package/skills/cmo-ad-creative/SKILL.md +1 -1
- package/skills/cmo-ads-audit/SKILL.md +1 -1
- package/skills/cmo-ads-budget/SKILL.md +1 -1
- package/skills/cmo-ads-competitor-research/SKILL.md +1 -1
- package/skills/cmo-ads-launch/SKILL.md +1 -1
- package/skills/cmo-ads-monitoring/SKILL.md +1 -1
- package/skills/cmo-ads-strategy/SKILL.md +1 -1
- package/skills/cmo-aeo-content/SKILL.md +1 -1
- package/skills/cmo-agent-readiness/SKILL.md +1 -1
- package/skills/cmo-ai-brand-monitoring/SKILL.md +1 -1
- package/skills/cmo-ai-citation-gaps/SKILL.md +1 -1
- package/skills/cmo-ai-prompt-research/SKILL.md +1 -1
- package/skills/cmo-ai-visibility/SKILL.md +1 -1
- package/skills/cmo-ai-visibility-audit/SKILL.md +1 -1
- package/skills/cmo-app-store-optimization/SKILL.md +1 -1
- package/skills/cmo-attribution/SKILL.md +1 -1
- package/skills/cmo-autopilot/SKILL.md +1 -1
- package/skills/cmo-blog-planner/SKILL.md +1 -1
- package/skills/cmo-blog-writing/SKILL.md +1 -1
- package/skills/cmo-brand-naming/SKILL.md +1 -1
- package/skills/cmo-brand-profile/SKILL.md +1 -1
- package/skills/cmo-brand-protection/SKILL.md +1 -1
- package/skills/cmo-campaign-analytics/SKILL.md +1 -1
- package/skills/cmo-checkout-cro/SKILL.md +1 -1
- package/skills/cmo-china-social/SKILL.md +1 -1
- package/skills/cmo-churn-prevention/SKILL.md +1 -1
- package/skills/cmo-client-report/SKILL.md +1 -1
- package/skills/cmo-closed-loop-measurement/SKILL.md +1 -1
- package/skills/cmo-co-marketing/SKILL.md +1 -1
- package/skills/cmo-cold-email/SKILL.md +1 -1
- package/skills/cmo-community/SKILL.md +1 -1
- package/skills/cmo-competitor-pages/SKILL.md +1 -1
- package/skills/cmo-competitor-research/SKILL.md +1 -1
- package/skills/cmo-connect/SKILL.md +1 -1
- package/skills/cmo-content-brief/SKILL.md +1 -1
- package/skills/cmo-content-refresh/SKILL.md +1 -1
- package/skills/cmo-content-repurposing/SKILL.md +1 -1
- package/skills/cmo-content-strategy/SKILL.md +1 -1
- package/skills/cmo-copy-editing/SKILL.md +1 -1
- package/skills/cmo-copywriting/SKILL.md +1 -1
- package/skills/cmo-customer-research/SKILL.md +1 -1
- package/skills/cmo-daily/SKILL.md +1 -1
- package/skills/cmo-directory-submissions/SKILL.md +1 -1
- package/skills/cmo-draft-review/SKILL.md +1 -1
- package/skills/cmo-ecommerce-seo/SKILL.md +1 -1
- package/skills/cmo-email-deliverability/SKILL.md +1 -1
- package/skills/cmo-email-sequences/SKILL.md +1 -1
- package/skills/cmo-employee-advocacy/SKILL.md +1 -1
- package/skills/cmo-events-webinars/SKILL.md +1 -1
- package/skills/cmo-expert-panel/SKILL.md +1 -1
- package/skills/cmo-form-cro/SKILL.md +1 -1
- package/skills/cmo-free-tools/SKILL.md +1 -1
- package/skills/cmo-google-ads/SKILL.md +1 -1
- package/skills/cmo-humanize/SKILL.md +1 -1
- package/skills/cmo-image-generation/SKILL.md +1 -1
- package/skills/cmo-influencer/SKILL.md +1 -1
- package/skills/cmo-international-seo/SKILL.md +1 -1
- package/skills/cmo-keyword-clustering/SKILL.md +1 -1
- package/skills/cmo-keyword-research/SKILL.md +1 -1
- package/skills/cmo-landing-page-audit/SKILL.md +1 -1
- package/skills/cmo-landing-page-builder/SKILL.md +1 -1
- package/skills/cmo-lead-magnets/SKILL.md +1 -1
- package/skills/cmo-link-building/SKILL.md +1 -1
- package/skills/cmo-linkedin/SKILL.md +1 -1
- package/skills/cmo-linkedin-ads/SKILL.md +1 -1
- package/skills/cmo-linkedin-engagement/SKILL.md +1 -1
- package/skills/cmo-linkedin-posts/SKILL.md +1 -1
- package/skills/cmo-linkedin-profile/SKILL.md +1 -1
- package/skills/cmo-linkedin-strategy/SKILL.md +1 -1
- package/skills/cmo-local-seo/SKILL.md +1 -1
- package/skills/cmo-market-sizing/SKILL.md +1 -1
- package/skills/cmo-marketing-audit/SKILL.md +1 -1
- package/skills/cmo-marketing-automation/SKILL.md +1 -1
- package/skills/cmo-marketing-ideas/SKILL.md +1 -1
- package/skills/cmo-marketing-plan/SKILL.md +1 -1
- package/skills/cmo-marketing-psychology/SKILL.md +1 -1
- package/skills/cmo-meta-ads/SKILL.md +1 -1
- package/skills/cmo-newsletter/SKILL.md +1 -1
- package/skills/cmo-offer-design/SKILL.md +1 -1
- package/skills/cmo-onboarding-activation/SKILL.md +1 -1
- package/skills/cmo-open-social/SKILL.md +1 -1
- package/skills/cmo-organic-social/SKILL.md +1 -1
- package/skills/cmo-page-cro/SKILL.md +1 -1
- package/skills/cmo-page-seo/SKILL.md +1 -1
- package/skills/cmo-paywalls/SKILL.md +1 -1
- package/skills/cmo-pinterest/SKILL.md +1 -1
- package/skills/cmo-popups/SKILL.md +1 -1
- package/skills/cmo-positioning/SKILL.md +1 -1
- package/skills/cmo-pricing-strategy/SKILL.md +1 -1
- package/skills/cmo-product-launch/SKILL.md +1 -1
- package/skills/cmo-programmatic-seo/SKILL.md +1 -1
- package/skills/cmo-proposals/SKILL.md +1 -1
- package/skills/cmo-prospecting/SKILL.md +1 -1
- package/skills/cmo-public-relations/SKILL.md +1 -1
- package/skills/cmo-reddit/SKILL.md +1 -1
- package/skills/cmo-referral-program/SKILL.md +1 -1
- package/skills/cmo-revops/SKILL.md +1 -1
- package/skills/cmo-sales-enablement/SKILL.md +1 -1
- package/skills/cmo-schema-markup/SKILL.md +1 -1
- package/skills/cmo-search-intent/SKILL.md +1 -1
- package/skills/cmo-seo-audit/SKILL.md +1 -1
- package/skills/cmo-seo-monitoring/SKILL.md +1 -1
- package/skills/cmo-seo-opportunities/SKILL.md +1 -1
- package/skills/cmo-seo-plan/SKILL.md +1 -1
- package/skills/cmo-short-form-video/SKILL.md +1 -1
- package/skills/cmo-signup-flow/SKILL.md +1 -1
- package/skills/cmo-site-architecture/SKILL.md +1 -1
- package/skills/cmo-sms-whatsapp/SKILL.md +1 -1
- package/skills/cmo-social-analytics/SKILL.md +1 -1
- package/skills/cmo-social-crisis/SKILL.md +1 -1
- package/skills/cmo-social-listening/SKILL.md +1 -1
- package/skills/cmo-social-strategy/SKILL.md +1 -1
- package/skills/cmo-team-access/SKILL.md +1 -1
- package/skills/cmo-technical-seo/SKILL.md +1 -1
- package/skills/cmo-thought-leadership/SKILL.md +1 -1
- package/skills/cmo-tracking-setup/SKILL.md +1 -1
- package/skills/cmo-video-production/SKILL.md +1 -1
- package/skills/cmo-video-repurposing/SKILL.md +1 -1
- package/skills/cmo-wasted-spend/SKILL.md +1 -1
- package/skills/cmo-website-setup/SKILL.md +1 -1
- package/skills/cmo-weekly-report/SKILL.md +1 -1
- package/skills/cmo-x-growth/SKILL.md +1 -1
- package/skills/cmo-youtube/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# @cmo-so/skills
|
|
2
2
|
|
|
3
|
-
The [CMO.so](https://cmo.so) marketing skills for **Claude Code** and
|
|
3
|
+
The [CMO.so](https://cmo.so) marketing skills for **Claude Code, Codex, Pi, Cursor, Gemini CLI, OpenCode, GitHub Copilot, Windsurf** and other coding agents: 124 skills for SEO, AI search, content, social, email, paid ads, CRO, analytics and growth, powered by Maggie and the CMO.so MCP server.
|
|
4
4
|
|
|
5
5
|
Install them into one project, or for your user so every project gets them.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npx @cmo-so/skills install
|
|
9
|
-
npx @cmo-so/skills install
|
|
8
|
+
npx @cmo-so/skills install # this project, for the agents set up here
|
|
9
|
+
npx @cmo-so/skills install -g # your user: every project
|
|
10
|
+
npx @cmo-so/skills install -g -a codex,pi # pick the agents
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Pi users can also install it as a Pi package: `pi install npm:@cmo-so/skills`.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| `user` (`--global`) | `~/.claude/skills` (`CLAUDE_CONFIG_DIR`) | `~/.codex/skills` (`CODEX_HOME`) | `~/.config/cmo-so/skills-install.json` (`CMO_SO_HOME`) |
|
|
15
|
+
## Agents and folders
|
|
16
|
+
|
|
17
|
+
`--agent` (`-a`) takes one or more agent ids, comma-separated. `auto` (the default) installs for the agents already set up in that scope (their folders exist), else for Claude Code; `all` installs for every agent below. Agents that share a folder get one copy.
|
|
18
18
|
|
|
19
|
-
`--agent
|
|
19
|
+
| `--agent` | Agent | Project scope | User scope (`-g`) |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `claude` | Claude Code | `.claude/skills` | `~/.claude/skills` (`CLAUDE_CONFIG_DIR`) |
|
|
22
|
+
| `codex` | Codex | `.agents/skills` | `~/.agents/skills` |
|
|
23
|
+
| `pi` | Pi | `.agents/skills` | `~/.agents/skills` |
|
|
24
|
+
| `cursor` | Cursor | `.agents/skills` | `~/.cursor/skills` |
|
|
25
|
+
| `gemini` | Gemini CLI | `.agents/skills` | `~/.gemini/skills` |
|
|
26
|
+
| `opencode` | OpenCode | `.agents/skills` | `~/.config/opencode/skills` |
|
|
27
|
+
| `copilot` | GitHub Copilot | `.agents/skills` | `~/.copilot/skills` |
|
|
28
|
+
| `amp` | Amp | `.agents/skills` | `~/.config/agents/skills` |
|
|
29
|
+
| `cline` | Cline | `.agents/skills` | `~/.agents/skills` |
|
|
30
|
+
| `windsurf` | Windsurf | `.windsurf/skills` | `~/.codeium/windsurf/skills` |
|
|
31
|
+
| `goose` | Goose | `.goose/skills` | `~/.config/goose/skills` |
|
|
32
|
+
| `kiro` | Kiro | `.kiro/skills` | `~/.kiro/skills` |
|
|
33
|
+
| `qwen` | Qwen Code | `.qwen/skills` | `~/.qwen/skills` |
|
|
34
|
+
| `trae` | Trae | `.trae/skills` | `~/.trae/skills` |
|
|
35
|
+
| `roo` | Roo Code | `.roo/skills` | `~/.roo/skills` |
|
|
36
|
+
| `kilo` | Kilo Code | `.kilocode/skills` | `~/.kilocode/skills` |
|
|
37
|
+
| `augment` | Augment | `.augment/skills` | `~/.augment/skills` |
|
|
38
|
+
| `droid` | Factory Droid | `.factory/skills` | `~/.factory/skills` |
|
|
39
|
+
| `openhands` | OpenHands | `.openhands/skills` | `~/.openhands/skills` |
|
|
40
|
+
| `antigravity` | Antigravity | `.agent/skills` | `~/.gemini/antigravity/skills` |
|
|
41
|
+
|
|
42
|
+
`npx @cmo-so/skills agents` prints this table and marks the agents found on your machine. The install record lives in `<project>/.cmo-so/skills-install.json`, or `~/.config/cmo-so/skills-install.json` for the user scope (`CMO_SO_HOME`). Commit the project's skill folders and `.cmo-so/` to share the skills with your team.
|
|
20
43
|
|
|
21
44
|
## Pick what you need
|
|
22
45
|
|
|
@@ -33,27 +56,29 @@ The 12 core skills (connect, daily check-in, weekly report…) always come along
|
|
|
33
56
|
The core skills run on the CMO.so MCP server, `https://mcp.cmo.so/mcp` (OAuth: sign in to CMO.so, no keys to paste).
|
|
34
57
|
|
|
35
58
|
```sh
|
|
36
|
-
npx @cmo-so/skills install --mcp # also adds it to the project's .mcp.json
|
|
37
|
-
npx @cmo-so/skills mcp
|
|
59
|
+
npx @cmo-so/skills install --mcp # also adds it to the project's .mcp.json (Claude Code)
|
|
60
|
+
npx @cmo-so/skills mcp -g # prints the commands for Claude Code, Codex, Gemini CLI, Cursor and Pi
|
|
38
61
|
```
|
|
39
62
|
|
|
40
|
-
Then in Claude Code: `/mcp` → **cmo-so** → **Authenticate**.
|
|
41
|
-
|
|
42
63
|
## Update and remove
|
|
43
64
|
|
|
44
65
|
```sh
|
|
45
66
|
npx @cmo-so/skills@latest update # this project
|
|
46
|
-
npx @cmo-so/skills@latest update
|
|
47
|
-
npx @cmo-so/skills remove cmo-x-growth # or --all
|
|
67
|
+
npx @cmo-so/skills@latest update -g # your user
|
|
68
|
+
npx @cmo-so/skills remove cmo-x-growth # or --all; -a codex removes it for one agent only
|
|
48
69
|
npx @cmo-so/skills doctor # check an install
|
|
49
70
|
```
|
|
50
71
|
|
|
51
|
-
`update` brings installed skills to the new version and adds new skills in the categories you chose. Files you edited are kept (you'll see `kept …`); pass `--force` to replace them. `remove` also keeps folders you edited unless you pass `--force`. Every command accepts `--dry-run`.
|
|
72
|
+
`update` brings installed skills to the new version, for the agents you installed them for, and adds new skills in the categories you chose. Files you edited are kept (you'll see `kept …`); pass `--force` to replace them. `remove` also keeps folders you edited unless you pass `--force`. Every command accepts `--dry-run`.
|
|
73
|
+
|
|
74
|
+
Installed with 0.3.0 for Codex in user scope? Those skills are in `~/.codex/skills`, and updates keep going there. Codex now reads `~/.agents/skills`: run `npx @cmo-so/skills install -g -a codex` to move over.
|
|
52
75
|
|
|
53
76
|
## Other ways to install
|
|
54
77
|
|
|
55
78
|
- Claude Code plugins: `/plugin marketplace add AI-Marketing-OS-LTD/cmo-so-skills`, then `/plugin install cmo-so-all@cmo-so`.
|
|
56
|
-
-
|
|
79
|
+
- Codex plugins: `codex plugin marketplace add AI-Marketing-OS-LTD/cmo-so-skills`, then `codex plugin add cmo-so@cmo-so` (it brings the MCP server; `codex mcp login cmo-so` signs in).
|
|
80
|
+
- Pi: `pi install npm:@cmo-so/skills` or `pi install git:github.com/AI-Marketing-OS-LTD/cmo-so-skills`.
|
|
81
|
+
- The open `skills` CLI: `npx skills add AI-Marketing-OS-LTD/cmo-so-skills -a <agent>`.
|
|
57
82
|
|
|
58
83
|
## License
|
|
59
84
|
|
package/bin/cmo-skills.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Install, update and remove the CMO.so marketing skills for Claude Code
|
|
3
|
-
// in one project (default) or for the current user.
|
|
2
|
+
// Install, update and remove the CMO.so marketing skills for Claude Code, Codex, Pi, Cursor,
|
|
3
|
+
// Gemini CLI and other coding agents, in one project (default) or for the current user.
|
|
4
|
+
// No dependencies.
|
|
4
5
|
import { createHash } from "node:crypto";
|
|
5
6
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { homedir } from "node:os";
|
|
@@ -12,35 +13,109 @@ const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
|
12
13
|
const SKILLS_ROOT = join(PKG_ROOT, "skills");
|
|
13
14
|
const MCP_URL = "https://mcp.cmo.so/mcp";
|
|
14
15
|
const ID = /^cmo-[a-z0-9-]+$/;
|
|
15
|
-
const AGENTS = ["claude", "codex"];
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
class UsageError extends Error {}
|
|
18
|
+
|
|
19
|
+
// ---------- agents ----------
|
|
20
|
+
// Where each agent reads skills. project: relative to the project; user: under the home folder.
|
|
21
|
+
// `detect` lists folders whose presence means the agent is set up (user scope: under home).
|
|
22
|
+
// Several agents share .agents/skills (the cross-agent folder); a shared folder is written once.
|
|
23
|
+
const HOME = () => homedir();
|
|
24
|
+
const AGENTS = {
|
|
25
|
+
claude: { name: "Claude Code", project: ".claude/skills", user: () => join(process.env.CLAUDE_CONFIG_DIR || join(HOME(), ".claude"), "skills"), detect: [".claude"] },
|
|
26
|
+
codex: { name: "Codex", project: ".agents/skills", user: () => join(HOME(), ".agents", "skills"), detect: [".codex"] },
|
|
27
|
+
pi: { name: "Pi", project: ".agents/skills", user: () => join(HOME(), ".agents", "skills"), detect: [".pi"] },
|
|
28
|
+
cursor: { name: "Cursor", project: ".agents/skills", user: () => join(HOME(), ".cursor", "skills"), detect: [".cursor"] },
|
|
29
|
+
gemini: { name: "Gemini CLI", project: ".agents/skills", user: () => join(HOME(), ".gemini", "skills"), detect: [".gemini"] },
|
|
30
|
+
opencode: { name: "OpenCode", project: ".agents/skills", user: () => join(HOME(), ".config", "opencode", "skills"), detect: [".config/opencode", ".opencode"] },
|
|
31
|
+
copilot: { name: "GitHub Copilot", project: ".agents/skills", user: () => join(HOME(), ".copilot", "skills"), detect: [".copilot"] },
|
|
32
|
+
amp: { name: "Amp", project: ".agents/skills", user: () => join(HOME(), ".config", "agents", "skills"), detect: [".config/amp"] },
|
|
33
|
+
cline: { name: "Cline", project: ".agents/skills", user: () => join(HOME(), ".agents", "skills"), detect: [".cline"] },
|
|
34
|
+
windsurf: { name: "Windsurf", project: ".windsurf/skills", user: () => join(HOME(), ".codeium", "windsurf", "skills"), detect: [".codeium/windsurf", ".windsurf"] },
|
|
35
|
+
goose: { name: "Goose", project: ".goose/skills", user: () => join(HOME(), ".config", "goose", "skills"), detect: [".config/goose", ".goose"] },
|
|
36
|
+
kiro: { name: "Kiro", project: ".kiro/skills", user: () => join(HOME(), ".kiro", "skills"), detect: [".kiro"] },
|
|
37
|
+
qwen: { name: "Qwen Code", project: ".qwen/skills", user: () => join(HOME(), ".qwen", "skills"), detect: [".qwen"] },
|
|
38
|
+
trae: { name: "Trae", project: ".trae/skills", user: () => join(HOME(), ".trae", "skills"), detect: [".trae"] },
|
|
39
|
+
roo: { name: "Roo Code", project: ".roo/skills", user: () => join(HOME(), ".roo", "skills"), detect: [".roo"] },
|
|
40
|
+
kilo: { name: "Kilo Code", project: ".kilocode/skills", user: () => join(HOME(), ".kilocode", "skills"), detect: [".kilocode"] },
|
|
41
|
+
augment: { name: "Augment", project: ".augment/skills", user: () => join(HOME(), ".augment", "skills"), detect: [".augment"] },
|
|
42
|
+
droid: { name: "Factory Droid", project: ".factory/skills", user: () => join(HOME(), ".factory", "skills"), detect: [".factory"] },
|
|
43
|
+
openhands: { name: "OpenHands", project: ".openhands/skills", user: () => join(HOME(), ".openhands", "skills"), detect: [".openhands"] },
|
|
44
|
+
antigravity: { name: "Antigravity", project: ".agent/skills", user: () => join(HOME(), ".gemini", "antigravity", "skills"), detect: [".gemini/antigravity"] },
|
|
45
|
+
};
|
|
46
|
+
const ALIASES = { "claude-code": "claude", "gemini-cli": "gemini", "github-copilot": "copilot", "kilocode": "kilo", "factory": "droid", "roo-code": "roo", "qwen-code": "qwen" };
|
|
47
|
+
|
|
48
|
+
function agentDir(agent, scope, root) {
|
|
49
|
+
return scope === "user" ? resolve(AGENTS[agent].user()) : join(root, AGENTS[agent].project);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detected(scope, root) {
|
|
53
|
+
const base = scope === "user" ? HOME() : root;
|
|
54
|
+
return Object.keys(AGENTS).filter((a) => AGENTS[a].detect.some((d) => existsSync(join(base, d))));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseAgents(value) {
|
|
58
|
+
return list(value).map((a) => {
|
|
59
|
+
const id = ALIASES[a.toLowerCase()] || a.toLowerCase();
|
|
60
|
+
if (!["auto", "all"].includes(id) && !AGENTS[id]) throw new UsageError(`unknown agent ${a} (see \`cmo-skills agents\`)`);
|
|
61
|
+
return id;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Returns [{ dir, agents: [...] }], one entry per distinct folder.
|
|
66
|
+
function targetsFor(opts, scope, root, previous) {
|
|
67
|
+
let agents = parseAgents(opts["--agent"] || "auto");
|
|
68
|
+
if (agents.includes("all")) agents = Object.keys(AGENTS);
|
|
69
|
+
else if (agents.includes("auto")) {
|
|
70
|
+
if (previous?.targets?.length) return previous.targets;
|
|
71
|
+
agents = detected(scope, root);
|
|
72
|
+
// A project with no agent folders yet: use the agents set up for this user, else Claude Code.
|
|
73
|
+
if (!agents.length && scope === "project") agents = detected("user", root);
|
|
74
|
+
if (!agents.length) agents = ["claude"];
|
|
21
75
|
}
|
|
22
|
-
const
|
|
23
|
-
for (const
|
|
24
|
-
|
|
76
|
+
const byDir = new Map();
|
|
77
|
+
for (const a of agents) {
|
|
78
|
+
const dir = agentDir(a, scope, root);
|
|
79
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
80
|
+
byDir.get(dir).push(a);
|
|
81
|
+
}
|
|
82
|
+
return [...byDir].map(([dir, as]) => ({ dir, agents: as }));
|
|
25
83
|
}
|
|
26
84
|
|
|
27
|
-
|
|
85
|
+
// 0.3.0 records listed agent names only; Codex then lived in ~/.codex/skills (CODEX_HOME).
|
|
86
|
+
function legacyTargets(state, scope, root) {
|
|
87
|
+
if (state.targets) return state.targets;
|
|
88
|
+
return (state.agents || []).map((a) => ({
|
|
89
|
+
dir: a === "codex" && scope === "user" ? join(process.env.CODEX_HOME || join(HOME(), ".codex"), "skills") : agentDir(a, scope, root),
|
|
90
|
+
agents: [a],
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function mergeTargets(a = [], b = []) {
|
|
95
|
+
const byDir = new Map();
|
|
96
|
+
for (const t of [...a, ...b]) byDir.set(t.dir, [...new Set([...(byDir.get(t.dir) || []), ...t.agents])]);
|
|
97
|
+
return [...byDir].map(([dir, agents]) => ({ dir, agents }));
|
|
98
|
+
}
|
|
28
99
|
|
|
29
100
|
// ---------- arguments ----------
|
|
30
101
|
|
|
31
102
|
function parse(argv) {
|
|
32
|
-
const flags = new Set(["--global", "--force", "--dry-run", "--mcp", "--installed", "--json", "--all", "--help", "-h", "--version", "-v"]);
|
|
33
|
-
const valued = new Set(["--scope", "--project", "--agent", "--category", "--skills"]);
|
|
103
|
+
const flags = new Set(["--global", "-g", "--force", "--dry-run", "--mcp", "--installed", "--json", "--all", "--help", "-h", "--version", "-v"]);
|
|
104
|
+
const valued = new Set(["--scope", "--project", "--agent", "-a", "--category", "--skills"]);
|
|
34
105
|
const opts = { _: [] };
|
|
106
|
+
const add = (k, v) => {
|
|
107
|
+
if (k === "-a") k = "--agent";
|
|
108
|
+
opts[k] = k === "--agent" && opts[k] ? `${opts[k]},${v}` : v;
|
|
109
|
+
};
|
|
35
110
|
for (let i = 0; i < argv.length; i++) {
|
|
36
111
|
const a = argv[i];
|
|
37
|
-
if (a.
|
|
112
|
+
if (a.startsWith("--") && a.includes("=") && valued.has(a.split("=")[0])) {
|
|
38
113
|
const [k, ...v] = a.split("=");
|
|
39
|
-
|
|
114
|
+
add(k, v.join("="));
|
|
40
115
|
} else if (valued.has(a)) {
|
|
41
116
|
if (argv[i + 1] === undefined) throw new UsageError(`${a} needs a value`);
|
|
42
|
-
|
|
43
|
-
} else if (flags.has(a)) opts[a] = true;
|
|
117
|
+
add(a, argv[++i]);
|
|
118
|
+
} else if (flags.has(a)) opts[a === "-g" ? "--global" : a] = true;
|
|
44
119
|
else if (a.startsWith("-")) throw new UsageError(`unknown option ${a}`);
|
|
45
120
|
else opts._.push(a);
|
|
46
121
|
}
|
|
@@ -63,37 +138,24 @@ function projectRoot(opts) {
|
|
|
63
138
|
return resolve(opts["--project"] || process.cwd());
|
|
64
139
|
}
|
|
65
140
|
|
|
66
|
-
function userAgentHome(agent) {
|
|
67
|
-
if (agent === "claude") return resolve(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"));
|
|
68
|
-
return resolve(process.env.CODEX_HOME || join(homedir(), ".codex"));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
141
|
function stateFile(scope, root) {
|
|
72
142
|
return scope === "user"
|
|
73
|
-
? join(resolve(process.env.CMO_SO_HOME || join(
|
|
143
|
+
? join(resolve(process.env.CMO_SO_HOME || join(HOME(), ".config", "cmo-so")), "skills-install.json")
|
|
74
144
|
: join(root, ".cmo-so", "skills-install.json");
|
|
75
145
|
}
|
|
76
146
|
|
|
77
|
-
//
|
|
78
|
-
// project: Claude Code <project>/.claude/skills, Codex <project>/.agents/skills
|
|
79
|
-
// user: Claude Code ~/.claude/skills (CLAUDE_CONFIG_DIR), Codex ~/.codex/skills (CODEX_HOME)
|
|
80
|
-
function skillDir(agent, scope, root) {
|
|
81
|
-
if (scope === "user") return join(userAgentHome(agent), "skills");
|
|
82
|
-
return join(root, agent === "claude" ? ".claude" : ".agents", "skills");
|
|
83
|
-
}
|
|
147
|
+
// ---------- catalog and selection ----------
|
|
84
148
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
return
|
|
149
|
+
function loadCatalog() {
|
|
150
|
+
const path = join(PKG_ROOT, "catalog.json");
|
|
151
|
+
if (!existsSync(path) || !existsSync(SKILLS_ROOT)) {
|
|
152
|
+
throw new UsageError("the bundled skills are missing; build the package with `npm pack` (it runs scripts/bundle.mjs)");
|
|
153
|
+
}
|
|
154
|
+
const catalog = JSON.parse(readFileSync(path, "utf8"));
|
|
155
|
+
for (const s of catalog.skills) if (!ID.test(s.id)) throw new Error(`bad skill id in catalog: ${s.id}`);
|
|
156
|
+
return catalog;
|
|
93
157
|
}
|
|
94
158
|
|
|
95
|
-
// ---------- selection ----------
|
|
96
|
-
|
|
97
159
|
function resolveSelection(catalog, selection) {
|
|
98
160
|
const byCategory = new Map();
|
|
99
161
|
for (const s of catalog.skills) {
|
|
@@ -147,7 +209,7 @@ function filesUnder(dir, base = dir) {
|
|
|
147
209
|
return out.sort();
|
|
148
210
|
}
|
|
149
211
|
|
|
150
|
-
function
|
|
212
|
+
function readJson(path) {
|
|
151
213
|
try {
|
|
152
214
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
153
215
|
} catch {
|
|
@@ -156,7 +218,7 @@ function readState(path) {
|
|
|
156
218
|
}
|
|
157
219
|
|
|
158
220
|
// Sync one skill folder. A file is replaced only if it is new, identical, or unchanged since we
|
|
159
|
-
// installed it (its hash matches the
|
|
221
|
+
// installed it (its hash matches the record); local edits are kept unless --force.
|
|
160
222
|
function syncSkill(id, target, managed, opts, log, stats) {
|
|
161
223
|
const source = join(SKILLS_ROOT, id);
|
|
162
224
|
const dry = opts["--dry-run"];
|
|
@@ -176,27 +238,26 @@ function syncSkill(id, target, managed, opts, log, stats) {
|
|
|
176
238
|
for (const rel of filesUnder(source)) {
|
|
177
239
|
const src = join(source, rel);
|
|
178
240
|
const dst = join(target, rel);
|
|
179
|
-
const key = join(target, rel);
|
|
180
241
|
const want = sha(src);
|
|
181
242
|
if (fresh) {
|
|
182
|
-
newManaged[
|
|
243
|
+
newManaged[dst] = want;
|
|
183
244
|
continue;
|
|
184
245
|
}
|
|
185
246
|
const have = existsSync(dst) ? sha(dst) : null;
|
|
186
247
|
if (have === want) {
|
|
187
|
-
newManaged[
|
|
248
|
+
newManaged[dst] = want;
|
|
188
249
|
stats.unchanged++;
|
|
189
|
-
} else if (have === null || managed[
|
|
250
|
+
} else if (have === null || managed[dst] === have || opts["--force"]) {
|
|
190
251
|
if (!dry) {
|
|
191
252
|
mkdirSync(dirname(dst), { recursive: true });
|
|
192
253
|
writeFileSync(dst, readFileSync(src));
|
|
193
254
|
}
|
|
194
255
|
log(`${have === null ? "added" : "updated"} ${dst}`);
|
|
195
|
-
newManaged[
|
|
256
|
+
newManaged[dst] = want;
|
|
196
257
|
stats.updated++;
|
|
197
258
|
} else {
|
|
198
259
|
log(`kept ${dst} (edited locally; --force replaces it)`);
|
|
199
|
-
newManaged[
|
|
260
|
+
newManaged[dst] = managed[dst];
|
|
200
261
|
stats.kept++;
|
|
201
262
|
}
|
|
202
263
|
}
|
|
@@ -235,9 +296,8 @@ function mcpProject(root, opts, log) {
|
|
|
235
296
|
const path = join(root, ".mcp.json");
|
|
236
297
|
let config = {};
|
|
237
298
|
if (existsSync(path)) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} catch {
|
|
299
|
+
config = readJson(path);
|
|
300
|
+
if (!config) {
|
|
241
301
|
log(`skipped ${path} (not valid JSON); add the server by hand: ${MCP_URL}`);
|
|
242
302
|
return;
|
|
243
303
|
}
|
|
@@ -253,15 +313,14 @@ function mcpProject(root, opts, log) {
|
|
|
253
313
|
}
|
|
254
314
|
|
|
255
315
|
function mcpHelp(scope) {
|
|
256
|
-
const
|
|
316
|
+
const user = scope === "user";
|
|
257
317
|
return [
|
|
258
|
-
|
|
259
|
-
` Claude Code
|
|
260
|
-
|
|
261
|
-
`
|
|
262
|
-
|
|
263
|
-
`
|
|
264
|
-
" Others: npx -y @cmo-so/mcp (stdio), after `npx @cmo-so/cli login`",
|
|
318
|
+
`Connect the CMO.so MCP server (${MCP_URL}; the core skills need it, sign in with your CMO.so account):`,
|
|
319
|
+
` Claude Code claude mcp add --scope ${user ? "user" : "project"} --transport http cmo-so ${MCP_URL}`,
|
|
320
|
+
` Codex codex mcp add cmo-so --url ${MCP_URL} (or [mcp_servers.cmo-so] url = "${MCP_URL}" in ~/.codex/config.toml)`,
|
|
321
|
+
` Gemini CLI gemini mcp add --transport http ${user ? "--scope user " : ""}cmo-so ${MCP_URL}`,
|
|
322
|
+
` Cursor Settings → MCP → Add → URL ${MCP_URL}`,
|
|
323
|
+
" Pi and agents that start a command: npx -y @cmo-so/mcp (stdio), after `npx @cmo-so/cli login`",
|
|
265
324
|
].join("\n");
|
|
266
325
|
}
|
|
267
326
|
|
|
@@ -272,19 +331,19 @@ function install(opts, { update = false } = {}) {
|
|
|
272
331
|
const scope = scopeOf(opts);
|
|
273
332
|
const root = projectRoot(opts);
|
|
274
333
|
const state = stateFile(scope, root);
|
|
275
|
-
const previous =
|
|
334
|
+
const previous = readJson(state);
|
|
276
335
|
if (update && !previous) throw new UsageError(`nothing installed in ${scope === "user" ? "user scope" : root}; run \`cmo-skills install\` first`);
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const selection = update ? previous.selection : mergeSelection(previous?.selection,
|
|
280
|
-
const
|
|
336
|
+
const previousTargets = previous ? legacyTargets(previous, scope, root) : [];
|
|
337
|
+
const targets = targetsFor(opts, scope, root, previous ? { targets: previousTargets } : null);
|
|
338
|
+
const selection = update ? previous.selection : mergeSelection(previous?.selection, selectionFrom(opts));
|
|
339
|
+
const named = [...opts._, ...list(opts["--skills"])];
|
|
340
|
+
const ids = resolveSelection(catalog, update && named.length ? { skills: named, withCore: false } : selection);
|
|
281
341
|
const managed = previous?.files || {};
|
|
282
342
|
const stats = { installed: 0, updated: 0, unchanged: 0, kept: 0, removed: 0 };
|
|
283
343
|
const log = (line) => console.log(opts["--dry-run"] ? `[dry run] ${line}` : line);
|
|
284
344
|
const files = { ...managed };
|
|
285
345
|
|
|
286
|
-
for (const
|
|
287
|
-
const dir = skillDir(agent, scope, root);
|
|
346
|
+
for (const { dir } of targets) {
|
|
288
347
|
for (const id of ids) Object.assign(files, syncSkill(id, join(dir, id), managed, opts, log, stats));
|
|
289
348
|
// Skills that were installed before but are no longer in the package.
|
|
290
349
|
for (const id of previous?.skills || []) {
|
|
@@ -297,16 +356,21 @@ function install(opts, { update = false } = {}) {
|
|
|
297
356
|
}
|
|
298
357
|
}
|
|
299
358
|
|
|
300
|
-
const
|
|
301
|
-
const
|
|
359
|
+
const known = (id) => catalog.skills.some((s) => s.id === id);
|
|
360
|
+
const installed = [...new Set([...(previous?.skills || []), ...ids])].filter(known).sort();
|
|
361
|
+
const allTargets = mergeTargets(previousTargets, targets);
|
|
302
362
|
if (opts["--mcp"] && scope === "project") mcpProject(root, opts, log);
|
|
303
363
|
if (!opts["--dry-run"]) {
|
|
304
364
|
mkdirSync(dirname(state), { recursive: true });
|
|
305
|
-
|
|
365
|
+
const record = { package: PKG.name, version: PKG.version, scope, ...(scope === "project" ? { project: root } : {}), targets: allTargets, selection, skills: installed, files, [update ? "updated_at" : "installed_at"]: new Date().toISOString() };
|
|
366
|
+
writeFileSync(state, `${JSON.stringify(record, null, 2)}\n`);
|
|
306
367
|
}
|
|
307
|
-
|
|
308
|
-
|
|
368
|
+
console.log(`\n${update ? "Updated" : "Installed"} ${ids.length} CMO.so skills v${PKG.version} (${scope} scope):`);
|
|
369
|
+
for (const t of targets) console.log(` ${t.dir} (${t.agents.map((a) => AGENTS[a]?.name || a).join(", ")})`);
|
|
370
|
+
console.log(`${stats.installed} new, ${stats.updated} files updated, ${stats.unchanged} unchanged, ${stats.kept} kept${stats.removed ? `, ${stats.removed} retired skills removed` : ""}.`);
|
|
309
371
|
if (stats.kept) console.log("Files you edited were kept. Rerun with --force to replace them.");
|
|
372
|
+
const legacyCodex = previousTargets.find((t) => t.dir.endsWith(join(".codex", "skills")));
|
|
373
|
+
if (legacyCodex) console.log(`Note: Codex now reads ~/.agents/skills. Move to it with \`cmo-skills install --scope user --agent codex\`, then delete ${legacyCodex.dir}/cmo-*.`);
|
|
310
374
|
if (!update && !(opts["--mcp"] && scope === "project")) console.log(`\n${mcpHelp(scope)}`);
|
|
311
375
|
if (!update) console.log(`\nRestart your agent (or start a new session) to load the skills. Try: "Connect to CMO.so" or "What needs me today on my website?"`);
|
|
312
376
|
}
|
|
@@ -315,24 +379,26 @@ function remove(opts) {
|
|
|
315
379
|
const scope = scopeOf(opts);
|
|
316
380
|
const root = projectRoot(opts);
|
|
317
381
|
const state = stateFile(scope, root);
|
|
318
|
-
const previous =
|
|
382
|
+
const previous = readJson(state);
|
|
319
383
|
if (!previous) throw new UsageError(`nothing installed in ${scope === "user" ? "user scope" : root}`);
|
|
320
384
|
const ids = opts["--all"] ? previous.skills : [...opts._, ...list(opts["--skills"])];
|
|
321
385
|
if (!ids.length) throw new UsageError("name the skills to remove, or pass --all");
|
|
322
|
-
const
|
|
386
|
+
const recorded = legacyTargets(previous, scope, root);
|
|
387
|
+
const targets = opts["--agent"] ? targetsFor(opts, scope, root, null) : recorded;
|
|
323
388
|
const log = (line) => console.log(opts["--dry-run"] ? `[dry run] ${line}` : line);
|
|
324
389
|
const files = { ...previous.files };
|
|
325
390
|
const left = new Set(previous.skills);
|
|
326
391
|
for (const id of ids) {
|
|
327
392
|
if (!ID.test(id)) throw new UsageError(`bad skill id ${id}`);
|
|
328
393
|
let gone = true;
|
|
329
|
-
for (const
|
|
330
|
-
const target = join(
|
|
394
|
+
for (const { dir } of targets) {
|
|
395
|
+
const target = join(dir, id);
|
|
331
396
|
if (removeSkill(target, previous.files, opts, log)) {
|
|
332
397
|
for (const k of Object.keys(files)) if (k.startsWith(target + sep)) delete files[k];
|
|
333
398
|
} else gone = false;
|
|
334
399
|
}
|
|
335
|
-
|
|
400
|
+
// Removing from some agents only: the skill stays installed for the others.
|
|
401
|
+
if (gone && targets.length === recorded.length) left.delete(id);
|
|
336
402
|
}
|
|
337
403
|
if (opts["--dry-run"]) return;
|
|
338
404
|
if (!left.size) {
|
|
@@ -340,18 +406,14 @@ function remove(opts) {
|
|
|
340
406
|
console.log("All CMO.so skills removed.");
|
|
341
407
|
return;
|
|
342
408
|
}
|
|
343
|
-
|
|
344
|
-
writeFileSync(state, `${JSON.stringify({ ...previous, selection, skills: [...left].sort(), files, updated_at: new Date().toISOString() }, null, 2)}\n`);
|
|
409
|
+
writeFileSync(state, `${JSON.stringify({ ...previous, targets: recorded, selection: { categories: [], skills: [...left].sort() }, skills: [...left].sort(), files, updated_at: new Date().toISOString() }, null, 2)}\n`);
|
|
345
410
|
console.log(`${left.size} CMO.so skills still installed.`);
|
|
346
411
|
}
|
|
347
412
|
|
|
348
413
|
function listCmd(opts) {
|
|
349
414
|
const catalog = loadCatalog();
|
|
350
415
|
let installed = null;
|
|
351
|
-
if (opts["--installed"])
|
|
352
|
-
const scope = scopeOf(opts);
|
|
353
|
-
installed = new Set(readState(stateFile(scope, projectRoot(opts)))?.skills || []);
|
|
354
|
-
}
|
|
416
|
+
if (opts["--installed"]) installed = new Set(readJson(stateFile(scopeOf(opts), projectRoot(opts)))?.skills || []);
|
|
355
417
|
const category = opts["--category"];
|
|
356
418
|
const rows = catalog.skills.filter((s) => (!category || s.category === category || s.plugin === category) && (!installed || installed.has(s.id)));
|
|
357
419
|
if (opts["--json"]) {
|
|
@@ -364,14 +426,30 @@ function listCmd(opts) {
|
|
|
364
426
|
console.log(`\n${c.name} (--category ${c.id})`);
|
|
365
427
|
for (const s of inCat) console.log(` ${s.id.padEnd(30)} ${s.summary}`);
|
|
366
428
|
}
|
|
367
|
-
console.log(`\n${rows.length} skills. Install: npx @cmo-so/skills install [--category seo,social] [--scope user]`);
|
|
429
|
+
console.log(`\n${rows.length} skills. Install: npx @cmo-so/skills install [--category seo,social] [--agent codex,pi] [--scope user]`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function agentsCmd(opts) {
|
|
433
|
+
const root = projectRoot(opts);
|
|
434
|
+
const found = { project: new Set(detected("project", root)), user: new Set(detected("user", root)) };
|
|
435
|
+
if (opts["--json"]) {
|
|
436
|
+
console.log(JSON.stringify(Object.entries(AGENTS).map(([id, a]) => ({ id, name: a.name, project: a.project, user: resolve(a.user()), detected: found.user.has(id) || found.project.has(id) })), null, 2));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
console.log(`${"--agent".padEnd(12)} ${"Agent".padEnd(15)} ${"Project folder".padEnd(18)} User folder`);
|
|
440
|
+
for (const [id, a] of Object.entries(AGENTS)) {
|
|
441
|
+
const mark = found.project.has(id) || found.user.has(id) ? " *" : "";
|
|
442
|
+
console.log(`${id.padEnd(12)} ${a.name.padEnd(15)} ${a.project.padEnd(18)} ${a.user().replace(HOME(), "~")}${mark}`);
|
|
443
|
+
}
|
|
444
|
+
console.log("\n* set up on this machine or in this project. --agent auto (the default) installs for those, else Claude Code.");
|
|
445
|
+
console.log("Combine agents with commas: --agent codex,pi,claude. Agents that share a folder get one copy.");
|
|
368
446
|
}
|
|
369
447
|
|
|
370
448
|
function doctor(opts) {
|
|
371
449
|
const catalog = loadCatalog();
|
|
372
450
|
const scope = scopeOf(opts);
|
|
373
451
|
const root = projectRoot(opts);
|
|
374
|
-
const state =
|
|
452
|
+
const state = readJson(stateFile(scope, root));
|
|
375
453
|
let problems = 0;
|
|
376
454
|
const report = (ok, line) => {
|
|
377
455
|
console.log(`${ok ? "PASS" : "FAIL"} ${line}`);
|
|
@@ -382,17 +460,16 @@ function doctor(opts) {
|
|
|
382
460
|
console.log(`INFO nothing installed in ${scope} scope${scope === "project" ? ` (${root})` : ""}`);
|
|
383
461
|
return;
|
|
384
462
|
}
|
|
385
|
-
report(true, `
|
|
386
|
-
if (state.version !== PKG.version) console.log(`INFO installed v${state.version}, package v${PKG.version}: run \`cmo-skills update${scope === "user" ? " --scope user" : ""}\``);
|
|
387
|
-
for (const
|
|
388
|
-
const dir = skillDir(agent, scope, root);
|
|
463
|
+
report(true, `record ${stateFile(scope, root)} (installed v${state.version})`);
|
|
464
|
+
if (state.version !== PKG.version) console.log(`INFO installed v${state.version}, package v${PKG.version}: run \`npx @cmo-so/skills@latest update${scope === "user" ? " --scope user" : ""}\``);
|
|
465
|
+
for (const { dir, agents } of legacyTargets(state, scope, root)) {
|
|
389
466
|
const missing = state.skills.filter((id) => !existsSync(join(dir, id, "SKILL.md")));
|
|
390
|
-
report(!missing.length, `${
|
|
467
|
+
report(!missing.length, `${agents.map((a) => AGENTS[a]?.name || a).join(", ")}: ${state.skills.length - missing.length}/${state.skills.length} skills in ${dir}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`);
|
|
391
468
|
const edited = Object.entries(state.files).filter(([p, h]) => p.startsWith(dir + sep) && existsSync(p) && sha(p) !== h).length;
|
|
392
|
-
if (edited) console.log(`INFO ${
|
|
469
|
+
if (edited) console.log(`INFO ${edited} file(s) in ${dir} edited locally (kept on update)`);
|
|
393
470
|
}
|
|
394
471
|
if (scope === "project") {
|
|
395
|
-
const mcp =
|
|
472
|
+
const mcp = readJson(join(root, ".mcp.json"));
|
|
396
473
|
console.log(`INFO MCP server in .mcp.json: ${mcp?.mcpServers?.["cmo-so"] ? "yes" : "no (cmo-skills mcp, or --mcp on install)"}`);
|
|
397
474
|
}
|
|
398
475
|
if (problems) process.exitCode = 1;
|
|
@@ -405,7 +482,8 @@ function mcp(opts) {
|
|
|
405
482
|
}
|
|
406
483
|
|
|
407
484
|
function help() {
|
|
408
|
-
console.log(`@cmo-so/skills ${PKG.version}: the CMO.so marketing skills for Claude Code
|
|
485
|
+
console.log(`@cmo-so/skills ${PKG.version}: the CMO.so marketing skills for Claude Code, Codex, Pi, Cursor,
|
|
486
|
+
Gemini CLI and other coding agents
|
|
409
487
|
|
|
410
488
|
Usage: npx @cmo-so/skills <command> [options]
|
|
411
489
|
|
|
@@ -414,37 +492,40 @@ Commands
|
|
|
414
492
|
update Update installed skills to this version; adds new skills in the categories you chose
|
|
415
493
|
remove SKILL ... | --all Remove skills (keeps folders you edited unless --force)
|
|
416
494
|
list Show the skills by category
|
|
495
|
+
agents Show the supported agents and their skill folders
|
|
417
496
|
doctor Check an install
|
|
418
497
|
mcp Connect the CMO.so MCP server (writes .mcp.json in project scope)
|
|
419
498
|
|
|
420
499
|
Options
|
|
421
|
-
--scope project|user project (default):
|
|
422
|
-
user: ~/.claude/skills
|
|
423
|
-
--global
|
|
500
|
+
--scope project|user project (default): skill folders inside the project, e.g. .claude/skills, .agents/skills
|
|
501
|
+
user: folders in your home, e.g. ~/.claude/skills, ~/.agents/skills, for every project
|
|
502
|
+
-g, --global Same as --scope user
|
|
424
503
|
--project PATH Project folder (default: current folder)
|
|
425
|
-
--agent auto
|
|
426
|
-
|
|
427
|
-
--
|
|
428
|
-
--
|
|
504
|
+
-a, --agent LIST auto (default), all, or agents: claude, codex, pi, cursor, gemini, opencode, copilot,
|
|
505
|
+
windsurf, amp, cline, goose, kiro, qwen, trae, roo, kilo, augment, droid, openhands, antigravity
|
|
506
|
+
--category LIST Categories or plugins, e.g. seo,social or cmo-so-seo
|
|
507
|
+
--skills LIST Skill ids, e.g. cmo-seo-audit,cmo-x-growth
|
|
508
|
+
--mcp Also add the CMO.so MCP server to the project's .mcp.json (Claude Code)
|
|
429
509
|
--force Replace or remove files you edited
|
|
430
510
|
--dry-run Show what would change, change nothing
|
|
431
|
-
--installed, --json For list
|
|
511
|
+
--installed, --json For list and agents
|
|
432
512
|
|
|
433
513
|
Examples
|
|
434
|
-
npx @cmo-so/skills install
|
|
435
|
-
npx @cmo-so/skills install
|
|
436
|
-
npx @cmo-so/skills install --category seo,ai-search --mcp
|
|
437
|
-
npx @cmo-so/skills
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
514
|
+
npx @cmo-so/skills install # this project, for the agents set up here
|
|
515
|
+
npx @cmo-so/skills install -g -a codex,pi # every project, Codex and Pi (~/.agents/skills)
|
|
516
|
+
npx @cmo-so/skills install -a claude,codex --category seo,ai-search --mcp
|
|
517
|
+
npx @cmo-so/skills@latest update -g
|
|
518
|
+
|
|
519
|
+
Also: Claude Code plugins (/plugin marketplace add AI-Marketing-OS-LTD/cmo-so-skills),
|
|
520
|
+
Codex plugins (codex plugin marketplace add AI-Marketing-OS-LTD/cmo-so-skills),
|
|
521
|
+
Pi packages (pi install npm:@cmo-so/skills).`);
|
|
441
522
|
}
|
|
442
523
|
|
|
443
524
|
function main(argv) {
|
|
444
525
|
const [command, ...rest] = argv;
|
|
445
526
|
const opts = parse(rest);
|
|
446
|
-
if (!command ||
|
|
447
|
-
if (
|
|
527
|
+
if (!command || ["help", "--help", "-h"].includes(command) || opts["--help"] || opts["-h"]) return help();
|
|
528
|
+
if (["--version", "-v", "version"].includes(command)) return console.log(PKG.version);
|
|
448
529
|
switch (command) {
|
|
449
530
|
case "install":
|
|
450
531
|
case "add":
|
|
@@ -458,6 +539,8 @@ function main(argv) {
|
|
|
458
539
|
case "list":
|
|
459
540
|
case "ls":
|
|
460
541
|
return listCmd(opts);
|
|
542
|
+
case "agents":
|
|
543
|
+
return agentsCmd(opts);
|
|
461
544
|
case "doctor":
|
|
462
545
|
return doctor(opts);
|
|
463
546
|
case "mcp":
|
package/catalog.json
CHANGED