@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.
Files changed (128) hide show
  1. package/README.md +42 -17
  2. package/bin/cmo-skills.mjs +192 -109
  3. package/catalog.json +1 -1
  4. package/package.json +12 -2
  5. package/skills/cmo-ab-testing/SKILL.md +1 -1
  6. package/skills/cmo-ad-creative/SKILL.md +1 -1
  7. package/skills/cmo-ads-audit/SKILL.md +1 -1
  8. package/skills/cmo-ads-budget/SKILL.md +1 -1
  9. package/skills/cmo-ads-competitor-research/SKILL.md +1 -1
  10. package/skills/cmo-ads-launch/SKILL.md +1 -1
  11. package/skills/cmo-ads-monitoring/SKILL.md +1 -1
  12. package/skills/cmo-ads-strategy/SKILL.md +1 -1
  13. package/skills/cmo-aeo-content/SKILL.md +1 -1
  14. package/skills/cmo-agent-readiness/SKILL.md +1 -1
  15. package/skills/cmo-ai-brand-monitoring/SKILL.md +1 -1
  16. package/skills/cmo-ai-citation-gaps/SKILL.md +1 -1
  17. package/skills/cmo-ai-prompt-research/SKILL.md +1 -1
  18. package/skills/cmo-ai-visibility/SKILL.md +1 -1
  19. package/skills/cmo-ai-visibility-audit/SKILL.md +1 -1
  20. package/skills/cmo-app-store-optimization/SKILL.md +1 -1
  21. package/skills/cmo-attribution/SKILL.md +1 -1
  22. package/skills/cmo-autopilot/SKILL.md +1 -1
  23. package/skills/cmo-blog-planner/SKILL.md +1 -1
  24. package/skills/cmo-blog-writing/SKILL.md +1 -1
  25. package/skills/cmo-brand-naming/SKILL.md +1 -1
  26. package/skills/cmo-brand-profile/SKILL.md +1 -1
  27. package/skills/cmo-brand-protection/SKILL.md +1 -1
  28. package/skills/cmo-campaign-analytics/SKILL.md +1 -1
  29. package/skills/cmo-checkout-cro/SKILL.md +1 -1
  30. package/skills/cmo-china-social/SKILL.md +1 -1
  31. package/skills/cmo-churn-prevention/SKILL.md +1 -1
  32. package/skills/cmo-client-report/SKILL.md +1 -1
  33. package/skills/cmo-closed-loop-measurement/SKILL.md +1 -1
  34. package/skills/cmo-co-marketing/SKILL.md +1 -1
  35. package/skills/cmo-cold-email/SKILL.md +1 -1
  36. package/skills/cmo-community/SKILL.md +1 -1
  37. package/skills/cmo-competitor-pages/SKILL.md +1 -1
  38. package/skills/cmo-competitor-research/SKILL.md +1 -1
  39. package/skills/cmo-connect/SKILL.md +1 -1
  40. package/skills/cmo-content-brief/SKILL.md +1 -1
  41. package/skills/cmo-content-refresh/SKILL.md +1 -1
  42. package/skills/cmo-content-repurposing/SKILL.md +1 -1
  43. package/skills/cmo-content-strategy/SKILL.md +1 -1
  44. package/skills/cmo-copy-editing/SKILL.md +1 -1
  45. package/skills/cmo-copywriting/SKILL.md +1 -1
  46. package/skills/cmo-customer-research/SKILL.md +1 -1
  47. package/skills/cmo-daily/SKILL.md +1 -1
  48. package/skills/cmo-directory-submissions/SKILL.md +1 -1
  49. package/skills/cmo-draft-review/SKILL.md +1 -1
  50. package/skills/cmo-ecommerce-seo/SKILL.md +1 -1
  51. package/skills/cmo-email-deliverability/SKILL.md +1 -1
  52. package/skills/cmo-email-sequences/SKILL.md +1 -1
  53. package/skills/cmo-employee-advocacy/SKILL.md +1 -1
  54. package/skills/cmo-events-webinars/SKILL.md +1 -1
  55. package/skills/cmo-expert-panel/SKILL.md +1 -1
  56. package/skills/cmo-form-cro/SKILL.md +1 -1
  57. package/skills/cmo-free-tools/SKILL.md +1 -1
  58. package/skills/cmo-google-ads/SKILL.md +1 -1
  59. package/skills/cmo-humanize/SKILL.md +1 -1
  60. package/skills/cmo-image-generation/SKILL.md +1 -1
  61. package/skills/cmo-influencer/SKILL.md +1 -1
  62. package/skills/cmo-international-seo/SKILL.md +1 -1
  63. package/skills/cmo-keyword-clustering/SKILL.md +1 -1
  64. package/skills/cmo-keyword-research/SKILL.md +1 -1
  65. package/skills/cmo-landing-page-audit/SKILL.md +1 -1
  66. package/skills/cmo-landing-page-builder/SKILL.md +1 -1
  67. package/skills/cmo-lead-magnets/SKILL.md +1 -1
  68. package/skills/cmo-link-building/SKILL.md +1 -1
  69. package/skills/cmo-linkedin/SKILL.md +1 -1
  70. package/skills/cmo-linkedin-ads/SKILL.md +1 -1
  71. package/skills/cmo-linkedin-engagement/SKILL.md +1 -1
  72. package/skills/cmo-linkedin-posts/SKILL.md +1 -1
  73. package/skills/cmo-linkedin-profile/SKILL.md +1 -1
  74. package/skills/cmo-linkedin-strategy/SKILL.md +1 -1
  75. package/skills/cmo-local-seo/SKILL.md +1 -1
  76. package/skills/cmo-market-sizing/SKILL.md +1 -1
  77. package/skills/cmo-marketing-audit/SKILL.md +1 -1
  78. package/skills/cmo-marketing-automation/SKILL.md +1 -1
  79. package/skills/cmo-marketing-ideas/SKILL.md +1 -1
  80. package/skills/cmo-marketing-plan/SKILL.md +1 -1
  81. package/skills/cmo-marketing-psychology/SKILL.md +1 -1
  82. package/skills/cmo-meta-ads/SKILL.md +1 -1
  83. package/skills/cmo-newsletter/SKILL.md +1 -1
  84. package/skills/cmo-offer-design/SKILL.md +1 -1
  85. package/skills/cmo-onboarding-activation/SKILL.md +1 -1
  86. package/skills/cmo-open-social/SKILL.md +1 -1
  87. package/skills/cmo-organic-social/SKILL.md +1 -1
  88. package/skills/cmo-page-cro/SKILL.md +1 -1
  89. package/skills/cmo-page-seo/SKILL.md +1 -1
  90. package/skills/cmo-paywalls/SKILL.md +1 -1
  91. package/skills/cmo-pinterest/SKILL.md +1 -1
  92. package/skills/cmo-popups/SKILL.md +1 -1
  93. package/skills/cmo-positioning/SKILL.md +1 -1
  94. package/skills/cmo-pricing-strategy/SKILL.md +1 -1
  95. package/skills/cmo-product-launch/SKILL.md +1 -1
  96. package/skills/cmo-programmatic-seo/SKILL.md +1 -1
  97. package/skills/cmo-proposals/SKILL.md +1 -1
  98. package/skills/cmo-prospecting/SKILL.md +1 -1
  99. package/skills/cmo-public-relations/SKILL.md +1 -1
  100. package/skills/cmo-reddit/SKILL.md +1 -1
  101. package/skills/cmo-referral-program/SKILL.md +1 -1
  102. package/skills/cmo-revops/SKILL.md +1 -1
  103. package/skills/cmo-sales-enablement/SKILL.md +1 -1
  104. package/skills/cmo-schema-markup/SKILL.md +1 -1
  105. package/skills/cmo-search-intent/SKILL.md +1 -1
  106. package/skills/cmo-seo-audit/SKILL.md +1 -1
  107. package/skills/cmo-seo-monitoring/SKILL.md +1 -1
  108. package/skills/cmo-seo-opportunities/SKILL.md +1 -1
  109. package/skills/cmo-seo-plan/SKILL.md +1 -1
  110. package/skills/cmo-short-form-video/SKILL.md +1 -1
  111. package/skills/cmo-signup-flow/SKILL.md +1 -1
  112. package/skills/cmo-site-architecture/SKILL.md +1 -1
  113. package/skills/cmo-sms-whatsapp/SKILL.md +1 -1
  114. package/skills/cmo-social-analytics/SKILL.md +1 -1
  115. package/skills/cmo-social-crisis/SKILL.md +1 -1
  116. package/skills/cmo-social-listening/SKILL.md +1 -1
  117. package/skills/cmo-social-strategy/SKILL.md +1 -1
  118. package/skills/cmo-team-access/SKILL.md +1 -1
  119. package/skills/cmo-technical-seo/SKILL.md +1 -1
  120. package/skills/cmo-thought-leadership/SKILL.md +1 -1
  121. package/skills/cmo-tracking-setup/SKILL.md +1 -1
  122. package/skills/cmo-video-production/SKILL.md +1 -1
  123. package/skills/cmo-video-repurposing/SKILL.md +1 -1
  124. package/skills/cmo-wasted-spend/SKILL.md +1 -1
  125. package/skills/cmo-website-setup/SKILL.md +1 -1
  126. package/skills/cmo-weekly-report/SKILL.md +1 -1
  127. package/skills/cmo-x-growth/SKILL.md +1 -1
  128. 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 **Codex**: 124 skills for SEO, AI search, content, social, email, paid ads, CRO, analytics and growth, powered by Maggie and the CMO.so MCP server.
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 # all skills, this project (.claude/skills)
9
- npx @cmo-so/skills install --scope user # all skills, every project (~/.claude/skills)
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
- ## Scopes
13
+ Pi users can also install it as a Pi package: `pi install npm:@cmo-so/skills`.
13
14
 
14
- | Scope | Claude Code | Codex | Install record |
15
- |---|---|---|---|
16
- | `project` (default) | `<project>/.claude/skills` | `<project>/.agents/skills` | `<project>/.cmo-so/skills-install.json` |
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 auto` (the default) installs for the agents already set up in that scope, or for Claude Code if neither is. Use `--agent claude`, `codex` or `all` to choose. Commit the project's `.claude/skills` (and `.cmo-so/`) to share the skills with your team.
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 --scope user # prints the commands for Claude Code and Codex
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 --scope user # your user
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
- - Any agent that reads `SKILL.md` folders: copy them from [the repository](https://github.com/AI-Marketing-OS-LTD/cmo-so-skills/tree/main/plugins).
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
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // Install, update and remove the CMO.so marketing skills for Claude Code and Codex,
3
- // in one project (default) or for the current user. No dependencies.
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
- function loadCatalog() {
18
- const path = join(PKG_ROOT, "catalog.json");
19
- if (!existsSync(path) || !existsSync(SKILLS_ROOT)) {
20
- throw new UsageError("the bundled skills are missing; build the package with `npm pack` (it runs scripts/bundle.mjs)");
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 catalog = JSON.parse(readFileSync(path, "utf8"));
23
- for (const s of catalog.skills) if (!ID.test(s.id)) throw new Error(`bad skill id in catalog: ${s.id}`);
24
- return catalog;
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
- class UsageError extends Error {}
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.includes("=") && a.startsWith("--") && valued.has(a.split("=")[0])) {
112
+ if (a.startsWith("--") && a.includes("=") && valued.has(a.split("=")[0])) {
38
113
  const [k, ...v] = a.split("=");
39
- opts[k] = v.join("=");
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
- opts[a] = argv[++i];
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(homedir(), ".config", "cmo-so")), "skills-install.json")
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
- // Where each agent reads skills from.
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 agentsFor(opts, scope, root, previous) {
86
- const requested = opts["--agent"] || "auto";
87
- if (!["auto", "all", ...AGENTS].includes(requested)) throw new UsageError(`unknown agent ${requested} (use auto, claude, codex or all)`);
88
- if (requested === "all") return [...AGENTS];
89
- if (requested !== "auto") return [requested];
90
- if (previous?.agents?.length) return previous.agents;
91
- const found = AGENTS.filter((a) => existsSync(scope === "user" ? userAgentHome(a) : join(root, a === "claude" ? ".claude" : ".agents")));
92
- return found.length ? found : ["claude"];
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 readState(path) {
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 manifest); local edits are kept unless --force.
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[key] = want;
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[key] = want;
248
+ newManaged[dst] = want;
188
249
  stats.unchanged++;
189
- } else if (have === null || managed[key] === have || opts["--force"]) {
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[key] = want;
256
+ newManaged[dst] = want;
196
257
  stats.updated++;
197
258
  } else {
198
259
  log(`kept ${dst} (edited locally; --force replaces it)`);
199
- newManaged[key] = managed[key];
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
- try {
239
- config = JSON.parse(readFileSync(path, "utf8"));
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 claudeScope = scope === "user" ? "user" : "project";
316
+ const user = scope === "user";
257
317
  return [
258
- "Connect the CMO.so MCP server (the core skills need it):",
259
- ` Claude Code: claude mcp add --scope ${claudeScope} --transport http cmo-so ${MCP_URL}`,
260
- " then /mcp → cmo-so → Authenticate (sign in to CMO.so in the browser)",
261
- ` Codex: add to ${scope === "user" ? "~/.codex/config.toml" : ".codex/config.toml"}:`,
262
- " [mcp_servers.cmo-so]",
263
- ` url = "${MCP_URL}"`,
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 = readState(state);
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 agents = agentsFor(opts, scope, root, previous);
278
- const asked = update ? {} : selectionFrom(opts);
279
- const selection = update ? previous.selection : mergeSelection(previous?.selection, asked);
280
- const ids = resolveSelection(catalog, update && (opts._.length || opts["--skills"]) ? { skills: [...opts._, ...list(opts["--skills"])], withCore: false } : selection);
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 agent of agents) {
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 installed = [...new Set([...(update ? previous.skills : previous?.skills || []), ...ids])].filter((id) => catalog.skills.some((s) => s.id === id)).sort();
301
- const allAgents = [...new Set([...(previous?.agents || []), ...agents])];
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
- writeFileSync(state, `${JSON.stringify({ package: PKG.name, version: PKG.version, scope, ...(scope === "project" ? { project: root } : {}), agents: allAgents, selection, skills: installed, files, [update ? "updated_at" : "installed_at"]: new Date().toISOString() }, null, 2)}\n`);
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
- const where = agents.map((a) => skillDir(a, scope, root)).join(", ");
308
- console.log(`\n${update ? "Updated" : "Installed"} ${ids.length} CMO.so skills v${PKG.version} (${scope} scope) in ${where}: ${stats.installed} new, ${stats.updated} files updated, ${stats.unchanged} unchanged, ${stats.kept} kept${stats.removed ? `, ${stats.removed} retired skills removed` : ""}.`);
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 = readState(state);
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 agents = opts["--agent"] && opts["--agent"] !== "auto" ? agentsFor(opts, scope, root, null) : previous.agents;
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 agent of agents) {
330
- const target = join(skillDir(agent, scope, root), id);
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
- if (gone) left.delete(id);
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
- const selection = { categories: [], skills: [...left].sort() };
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 = readState(stateFile(scope, root));
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, `manifest ${stateFile(scope, root)} (installed v${state.version})`);
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 agent of state.agents) {
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, `${agent}: ${state.skills.length - missing.length}/${state.skills.length} skills in ${dir}${missing.length ? ` (missing: ${missing.join(", ")})` : ""}`);
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 ${agent}: ${edited} file(s) edited locally (kept on update)`);
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 = readState(join(root, ".mcp.json"));
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 and Codex
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): <project>/.claude/skills and <project>/.agents/skills
422
- user: ~/.claude/skills and ~/.codex/skills, for every project
423
- --global Same as --scope user
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|claude|codex|all Which agent to install for (auto: the ones set up, else Claude Code)
426
- --category LIST Comma-separated categories or plugins, e.g. seo,social or cmo-so-seo
427
- --skills LIST Comma-separated skill ids, e.g. cmo-seo-audit,cmo-x-growth
428
- --mcp Also add the CMO.so MCP server to the project's .mcp.json
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 # all skills, this project
435
- npx @cmo-so/skills install --scope user # all skills, every project
436
- npx @cmo-so/skills install --category seo,ai-search --mcp
437
- npx @cmo-so/skills install cmo-seo-audit cmo-x-growth --agent codex
438
- npx @cmo-so/skills@latest update --scope user
439
-
440
- Prefer Claude Code plugins? /plugin marketplace add AI-Marketing-OS-LTD/cmo-so-skills`);
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 || command === "help" || command === "--help" || command === "-h" || opts["--help"] || opts["-h"]) return help();
447
- if (command === "--version" || command === "-v" || command === "version") return console.log(PKG.version);
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.0",
2
+ "version": "0.4.0",
3
3
  "updated": "2026-10-13",
4
4
  "mcp_server": "https://mcp.cmo.so/mcp",
5
5
  "categories": [