spirewise 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,46 +2,73 @@
2
2
 
3
3
  Installable **Agent Skills** for copywriting — generate ready-to-paste copy for
4
4
  your **F6S** and **LinkedIn** company pages, with strict character-limit safety.
5
- Works with **GitHub Copilot**, **Claude Code**, and **Cursor**.
5
+
6
+ One command installs the skills into **all your AI agents at once** —
7
+ **GitHub Copilot, Claude Code, Cursor, Windsurf, Codex CLI, Gemini CLI** — each in
8
+ its correct folder and format. It asks whether to install for the **current
9
+ project** or **globally** for every project.
6
10
 
7
11
  ## Install
8
12
 
9
- No clone needed — run it with `npx`:
13
+ No clone needed:
10
14
 
11
15
  ```bash
12
- # install every skill
13
- npx spirewise install --all
16
+ npx spirewise install
17
+ ```
14
18
 
15
- # install a single skill
16
- npx spirewise install f6s-copywriting
19
+ You'll be asked where to install:
17
20
 
18
- # install several
19
- npx spirewise install f6s-copywriting linkedin-copywriting
21
+ ```
22
+ ==> Where should the skills be installed?
23
+ 1) Workspace (this folder only — the current project directory)
24
+ 2) Global (your home folders, all projects)
25
+ 3) Both
26
+ ```
20
27
 
21
- # interactive picker
22
- npx spirewise install
28
+ Skip the prompt with a scope flag:
23
29
 
24
- # list what's available
25
- npx spirewise list
30
+ ```bash
31
+ npx spirewise install --workspace # this folder only (current project)
32
+ npx spirewise install --global # all projects (home folders)
33
+ npx spirewise install --both # both
26
34
  ```
27
35
 
28
36
  Or install the CLI globally:
29
37
 
30
38
  ```bash
31
39
  npm install -g spirewise
32
- spirewise install --all
40
+ spirewise install --both
33
41
  ```
34
42
 
35
- ## Choose the target agent / folder
43
+ ## What gets installed where
44
+
45
+ | Agent | Project folder | Global folder | Format |
46
+ |-------|----------------|---------------|--------|
47
+ | Claude Code | `.claude/skills/` | `~/.claude/skills/` | SKILL.md |
48
+ | GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` | SKILL.md |
49
+ | Cursor | `.cursor/rules/` | `~/.cursor/rules/` | `.mdc` rule |
50
+ | Windsurf | `.windsurf/rules/` | `~/.codeium/windsurf/global_rules/` | `.md` rule |
51
+ | Codex CLI | `.codex/skills/` | `~/.codex/skills/` | SKILL.md |
52
+ | Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` | SKILL.md |
53
+
54
+ Rule-based agents (Cursor, Windsurf) get a single rule file generated from the
55
+ skill; the rest get the full `SKILL.md` folder.
56
+
57
+ ## Narrow it down
36
58
 
37
59
  ```bash
38
- spirewise install --all --agent claude # ~/.claude/skills (default)
39
- spirewise install --all --agent copilot # ~/.copilot/skills
40
- spirewise install --all --agent cursor # ~/.cursor/skills
41
- spirewise install --all --target ./skills # any custom folder
60
+ # only some agents
61
+ npx spirewise install --both --agent claude,cursor
62
+
63
+ # only some skills
64
+ npx spirewise install f6s-copywriting --global
65
+
66
+ # inspect
67
+ npx spirewise list # available skills
68
+ npx spirewise agents # supported agents + their folders
42
69
  ```
43
70
 
44
- Run `spirewise --help` for all options.
71
+ Run `npx spirewise --help` for all options.
45
72
 
46
73
  ## Skills included
47
74
 
@@ -63,6 +90,18 @@ Once installed, just ask your agent:
63
90
  The agent follows the skill: gathers your company details, writes the `.txt`
64
91
  file in the right folder, and confirms all character counts stay under limit.
65
92
 
93
+ ## No Node? Use the shell installer
94
+
95
+ ```bash
96
+ # from a clone
97
+ ./install.sh --both
98
+
99
+ # remote
100
+ curl -fsSL https://raw.githubusercontent.com/spirerise/spirewise/main/install.sh | bash -s -- --both
101
+ ```
102
+
103
+ It mirrors the CLI: all agents, project/global scope prompt, `--agent` filter.
104
+
66
105
  ## License
67
106
 
68
107
  MIT
package/bin/cli.js CHANGED
@@ -2,24 +2,30 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * spirewise — install copywriting Agent Skills into your AI agent.
5
+ * spirewise — install copywriting Agent Skills into ALL your AI agents at once.
6
+ *
7
+ * Installs into each agent's correct folder, in the format that agent expects,
8
+ * and asks whether to install for THIS project (cwd) or GLOBALLY (home dirs).
6
9
  *
7
10
  * Usage:
8
- * npx spirewise list
9
- * npx spirewise install --all
10
- * npx spirewise install f6s-copywriting
11
- * npx spirewise install f6s-copywriting linkedin-copywriting
12
- * npx spirewise install (interactive picker)
13
- * npx spirewise install --agent cursor --all
14
- * npx spirewise install --target ./my/skills --all
11
+ * npx spirewise install # interactive: pick scope, install all
12
+ * npx spirewise install --project # this project only
13
+ * npx spirewise install --global # global (home) only
14
+ * npx spirewise install --both # project + global
15
+ * npx spirewise install --agent claude,cursor # restrict agents
16
+ * npx spirewise install f6s-copywriting # restrict skills
17
+ * npx spirewise list # list skills
18
+ * npx spirewise agents # list supported agents + folders
15
19
  */
16
20
 
17
21
  const fs = require('fs');
18
22
  const path = require('path');
23
+ const os = require('os');
19
24
  const readline = require('readline');
20
25
 
21
26
  const PKG_ROOT = path.resolve(__dirname, '..');
22
27
  const SKILLS_DIR = path.join(PKG_ROOT, 'skills');
28
+ const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
23
29
 
24
30
  const c = {
25
31
  reset: '\x1b[0m', blue: '\x1b[1;34m', green: '\x1b[1;32m',
@@ -30,14 +36,50 @@ const ok = (m) => console.log(`${c.green} ok${c.reset} ${m}`);
30
36
  const warn = (m) => console.error(`${c.yellow} !${c.reset} ${m}`);
31
37
  const die = (m) => { console.error(`${c.red}err${c.reset} ${m}`); process.exit(1); };
32
38
 
33
- const AGENT_DIRS = {
34
- claude: path.join(os_home(), '.claude', 'skills'),
35
- copilot: path.join(os_home(), '.copilot', 'skills'),
36
- cursor: path.join(os_home(), '.cursor', 'skills'),
39
+ /**
40
+ * Agent registry. `format`:
41
+ * - 'skill': copy the skill folder verbatim (SKILL.md + any files)
42
+ * - 'rule' : write one rule file derived from SKILL.md (ext required)
43
+ * `project` is resolved from cwd; `global` uses ~ for the home directory.
44
+ */
45
+ const AGENTS = {
46
+ claude: {
47
+ label: 'Claude Code', format: 'skill',
48
+ project: '.claude/skills', global: '~/.claude/skills',
49
+ },
50
+ copilot: {
51
+ label: 'GitHub Copilot', format: 'skill',
52
+ project: '.github/skills', global: '~/.copilot/skills',
53
+ },
54
+ cursor: {
55
+ label: 'Cursor', format: 'rule', ext: '.mdc',
56
+ project: '.cursor/rules', global: '~/.cursor/rules',
57
+ },
58
+ windsurf: {
59
+ label: 'Windsurf', format: 'rule', ext: '.md',
60
+ project: '.windsurf/rules', global: '~/.codeium/windsurf/global_rules',
61
+ },
62
+ codex: {
63
+ label: 'Codex CLI', format: 'skill',
64
+ project: '.codex/skills', global: '~/.codex/skills',
65
+ },
66
+ gemini: {
67
+ label: 'Gemini CLI', format: 'skill',
68
+ project: '.gemini/skills', global: '~/.gemini/skills',
69
+ },
37
70
  };
38
71
 
39
- function os_home() {
40
- return process.env.HOME || process.env.USERPROFILE || require('os').homedir();
72
+ function expandHome(p) {
73
+ if (p === '~') return HOME;
74
+ if (p.startsWith('~/')) return path.join(HOME, p.slice(2));
75
+ return p;
76
+ }
77
+
78
+ function resolveTarget(agent, scope) {
79
+ const rel = scope === 'project' ? agent.project : agent.global;
80
+ return scope === 'project'
81
+ ? path.resolve(process.cwd(), rel)
82
+ : path.resolve(expandHome(rel));
41
83
  }
42
84
 
43
85
  function availableSkills() {
@@ -48,6 +90,19 @@ function availableSkills() {
48
90
  .sort();
49
91
  }
50
92
 
93
+ function readSkill(name) {
94
+ const raw = fs.readFileSync(path.join(SKILLS_DIR, name, 'SKILL.md'), 'utf8');
95
+ let description = name;
96
+ let body = raw;
97
+ const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
98
+ if (m) {
99
+ body = m[2];
100
+ const dm = m[1].match(/description:\s*(?:>-)?\s*([\s\S]*?)(?:\n\w|$)/);
101
+ if (dm) description = dm[1].replace(/\s+/g, ' ').trim();
102
+ }
103
+ return { description, body: body.replace(/^\s+/, '') };
104
+ }
105
+
51
106
  function copyDir(src, dest) {
52
107
  fs.mkdirSync(dest, { recursive: true });
53
108
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -58,81 +113,100 @@ function copyDir(src, dest) {
58
113
  }
59
114
  }
60
115
 
61
- function installSkill(skill, targetDir) {
62
- const src = path.join(SKILLS_DIR, skill);
63
- if (!fs.existsSync(src)) die(`Skill '${skill}' not found in package.`);
64
- const dest = path.join(targetDir, skill);
65
- copyDir(src, dest);
66
- ok(`installed ${skill} ${c.dim}-> ${dest}${c.reset}`);
116
+ function installToAgent(agentKey, agent, scope, skills) {
117
+ const targetDir = resolveTarget(agent, scope);
118
+ fs.mkdirSync(targetDir, { recursive: true });
119
+ for (const skill of skills) {
120
+ if (agent.format === 'skill') {
121
+ const dest = path.join(targetDir, skill);
122
+ copyDir(path.join(SKILLS_DIR, skill), dest);
123
+ ok(`${agent.label} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}-> ${dest}${c.reset}`);
124
+ } else {
125
+ const { description, body } = readSkill(skill);
126
+ const front = `---\ndescription: ${description}\nalwaysApply: false\n---\n\n`;
127
+ const file = path.join(targetDir, skill + (agent.ext || '.md'));
128
+ fs.writeFileSync(file, front + body);
129
+ ok(`${agent.label} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}-> ${file}${c.reset}`);
130
+ }
131
+ }
67
132
  }
68
133
 
69
134
  function usage() {
70
- console.log(`spirewise — install copywriting Agent Skills
135
+ console.log(`spirewise — install copywriting Agent Skills into all your AI agents
71
136
 
72
137
  Usage:
73
- spirewise list
74
138
  spirewise install [skill ...] [options]
139
+ spirewise list
140
+ spirewise agents
75
141
 
76
- Commands:
77
- list List available skills
78
- install Install skills (default command)
142
+ Scope (where to install):
143
+ --workspace Install only into this folder / current project (alias: --project)
144
+ --global Install only into global/home folders
145
+ --both Install into both workspace and global
146
+ (no scope flag -> you are asked interactively)
79
147
 
80
- Options:
81
- --all Install every available skill
82
- --agent <name> Target agent: claude | copilot | cursor (default: claude)
83
- --target <dir> Install into a specific folder (overrides --agent)
148
+ Selection:
149
+ [skill ...] Limit to named skills (default: all)
150
+ --agent <a,b> Limit to named agents (default: all)
151
+
152
+ Other:
84
153
  -h, --help Show this help
85
154
 
86
- Available skills:
155
+ Supported agents:
156
+ ${Object.entries(AGENTS).map(([k, a]) => ` - ${k} (${a.label})`).join('\n')}
157
+
158
+ Skills:
87
159
  ${availableSkills().map((s) => ' - ' + s).join('\n') || ' (none found)'}
88
160
 
89
161
  Examples:
90
- npx spirewise install --all
91
- npx spirewise install f6s-copywriting
92
- npx spirewise install --agent cursor --all
162
+ npx spirewise install --both
163
+ npx spirewise install --project --agent claude,cursor
164
+ npx spirewise install f6s-copywriting --global
93
165
  `);
94
166
  }
95
167
 
96
168
  function parseArgs(argv) {
97
- const opts = { all: false, agent: 'claude', target: null, skills: [] };
169
+ const o = { scope: null, agents: null, skills: [] };
98
170
  for (let i = 0; i < argv.length; i++) {
99
171
  const a = argv[i];
100
- if (a === '--all') opts.all = true;
101
- else if (a === '--agent') opts.agent = argv[++i] || die('--agent needs a value');
102
- else if (a === '--target') opts.target = argv[++i] || die('--target needs a value');
172
+ if (a === '--project' || a === '--workspace' || a === '--local') o.scope = 'project';
173
+ else if (a === '--global') o.scope = 'global';
174
+ else if (a === '--both') o.scope = 'both';
175
+ else if (a === '--scope') o.scope = (argv[++i] || die('--scope needs a value'));
176
+ else if (a === '--agent' || a === '--agents') o.agents = (argv[++i] || die('--agent needs a value')).split(',').map((s) => s.trim()).filter(Boolean);
103
177
  else if (a === '-h' || a === '--help') { usage(); process.exit(0); }
104
178
  else if (a.startsWith('-')) die(`Unknown option: ${a}`);
105
- else opts.skills.push(a);
179
+ else o.skills.push(a);
106
180
  }
107
- return opts;
181
+ return o;
108
182
  }
109
183
 
110
- function pick(available) {
184
+ function ask(question) {
111
185
  return new Promise((resolve) => {
112
- if (!process.stdin.isTTY) {
113
- die("No skills specified. Use --all or name skills (run in a terminal for the picker).");
114
- }
115
- info('Select skills to install (space-separated numbers, or "a" for all):');
116
- available.forEach((s, i) => console.log(` ${i + 1}) ${s}`));
117
186
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
118
- rl.question(' > ', (answer) => {
119
- rl.close();
120
- const t = answer.trim().toLowerCase();
121
- if (t === 'a') return resolve(available.slice());
122
- const chosen = [];
123
- for (const tok of t.split(/\s+/)) {
124
- const idx = parseInt(tok, 10) - 1;
125
- if (idx >= 0 && idx < available.length) chosen.push(available[idx]);
126
- }
127
- resolve(chosen);
128
- });
187
+ rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); });
129
188
  });
130
189
  }
131
190
 
191
+ async function promptScope() {
192
+ if (!process.stdin.isTTY) {
193
+ warn('No terminal detected; defaulting scope to "project". Use --global or --both to change.');
194
+ return 'project';
195
+ }
196
+ info('Where should the skills be installed?');
197
+ console.log(' 1) Workspace (this folder only — the current project directory)');
198
+ console.log(' 2) Global (your home folders — applies to all projects)');
199
+ console.log(' 3) Both');
200
+ const ans = (await ask(' Choose 1/2/3 [1]: ')).toLowerCase();
201
+ if (ans === '2' || ans === 'global') return 'global';
202
+ if (ans === '3' || ans === 'both') return 'both';
203
+ return 'project';
204
+ }
205
+
132
206
  async function main() {
133
207
  let argv = process.argv.slice(2);
134
208
  let command = 'install';
135
- if (argv[0] === 'list' || argv[0] === 'install') { command = argv[0]; argv = argv.slice(1); }
209
+ if (['list', 'install', 'agents'].includes(argv[0])) { command = argv[0]; argv = argv.slice(1); }
136
210
  else if (argv[0] === '-h' || argv[0] === '--help') { usage(); return; }
137
211
 
138
212
  const available = availableSkills();
@@ -143,27 +217,40 @@ async function main() {
143
217
  available.forEach((s) => console.log(' - ' + s));
144
218
  return;
145
219
  }
220
+ if (command === 'agents') {
221
+ info('Supported agents and their folders:');
222
+ for (const [k, a] of Object.entries(AGENTS)) {
223
+ console.log(` ${k} (${a.label}) [${a.format}]`);
224
+ console.log(` project: ${a.project}`);
225
+ console.log(` global: ${a.global}`);
226
+ }
227
+ return;
228
+ }
146
229
 
147
- const opts = parseArgs(argv);
230
+ const o = parseArgs(argv);
148
231
 
149
- if (opts.agent && !AGENT_DIRS[opts.agent] && !opts.target) {
150
- die(`Unknown agent '${opts.agent}' (use claude | copilot | cursor)`);
232
+ let agentKeys = Object.keys(AGENTS);
233
+ if (o.agents) {
234
+ for (const k of o.agents) if (!AGENTS[k]) die(`Unknown agent '${k}'. Run "spirewise agents".`);
235
+ agentKeys = o.agents;
151
236
  }
152
- const targetDir = opts.target ? path.resolve(opts.target) : AGENT_DIRS[opts.agent];
153
237
 
154
- let selected = opts.all ? available.slice() : opts.skills.slice();
155
- if (selected.length === 0) selected = await pick(available);
156
- if (selected.length === 0) die('Nothing selected.');
238
+ let skills = o.skills.length ? o.skills : available.slice();
239
+ for (const s of skills) if (!available.includes(s)) die(`Unknown skill '${s}'. Run "spirewise list".`);
157
240
 
158
- for (const s of selected) {
159
- if (!available.includes(s)) die(`Unknown skill '${s}'. Run "spirewise list" to see options.`);
241
+ let scope = o.scope || await promptScope();
242
+ if (!['project', 'global', 'both'].includes(scope)) die(`Invalid scope '${scope}'.`);
243
+ const scopes = scope === 'both' ? ['project', 'global'] : [scope];
244
+
245
+ info(`Installing ${skills.length} skill(s) into ${agentKeys.length} agent(s): ${agentKeys.join(', ')}`);
246
+ info(`Scope: ${scope}`);
247
+
248
+ for (const sc of scopes) {
249
+ for (const k of agentKeys) installToAgent(k, AGENTS[k], sc, skills);
160
250
  }
161
251
 
162
- info(`Target: ${targetDir}`);
163
- fs.mkdirSync(targetDir, { recursive: true });
164
- for (const s of selected) installSkill(s, targetDir);
165
- ok(`Done. Installed ${selected.length} skill(s).`);
166
- info('Restart your agent or reload skills if needed.');
252
+ ok(`Done. ${skills.length} skill(s) -> ${agentKeys.length} agent(s) (${scopes.join(' + ')}).`);
253
+ info('Restart your agent or reload skills/rules if needed.');
167
254
  }
168
255
 
169
256
  main().catch((e) => die(e && e.message ? e.message : String(e)));
package/install.sh ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # spirewise — no-Node installer for copywriting Agent Skills.
4
+ # Installs into ALL supported agents, in their correct folders, and asks
5
+ # whether to install for THIS project (cwd) or GLOBALLY (home dirs).
6
+ #
7
+ # Local: ./install.sh [--project|--global|--both] [--agent a,b] [skill ...]
8
+ # Remote: curl -fsSL <raw>/install.sh | bash -s -- --both
9
+ #
10
+ # Prefer the npm CLI when Node is available: npx spirewise install
11
+ #
12
+ set -euo pipefail
13
+
14
+ # ---- Repo config (used only for remote install) ---------------------------
15
+ REPO_OWNER="${SKILLS_REPO_OWNER:-spirerise}"
16
+ REPO_NAME="${SKILLS_REPO_NAME:-spirewise}"
17
+ REPO_BRANCH="${SKILLS_REPO_BRANCH:-main}"
18
+ SKILLS_SUBDIR="skills"
19
+ # ---------------------------------------------------------------------------
20
+
21
+ RAW_BASE="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${REPO_BRANCH}"
22
+ API_BASE="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/${SKILLS_SUBDIR}?ref=${REPO_BRANCH}"
23
+
24
+ # Agent registry: "key|Label|format|ext|projectDir|globalDir"
25
+ # format = skill (copy SKILL.md folder) | rule (single file from SKILL.md)
26
+ AGENTS=(
27
+ "claude|Claude Code|skill||.claude/skills|HOME/.claude/skills"
28
+ "copilot|GitHub Copilot|skill||.github/skills|HOME/.copilot/skills"
29
+ "cursor|Cursor|rule|.mdc|.cursor/rules|HOME/.cursor/rules"
30
+ "windsurf|Windsurf|rule|.md|.windsurf/rules|HOME/.codeium/windsurf/global_rules"
31
+ "codex|Codex CLI|skill||.codex/skills|HOME/.codex/skills"
32
+ "gemini|Gemini CLI|skill||.gemini/skills|HOME/.gemini/skills"
33
+ )
34
+
35
+ color() { printf '\033[%sm%s\033[0m' "$1" "$2"; }
36
+ info() { printf '%s %s\n' "$(color '1;34' '==>')" "$1"; }
37
+ ok() { printf '%s %s\n' "$(color '1;32' ' ok')" "$1"; }
38
+ warn() { printf '%s %s\n' "$(color '1;33' ' !')" "$1" >&2; }
39
+ die() { printf '%s %s\n' "$(color '1;31' 'err')" "$1" >&2; exit 1; }
40
+
41
+ usage() {
42
+ cat <<EOF
43
+ spirewise — install copywriting Agent Skills into all your AI agents.
44
+
45
+ Usage:
46
+ install.sh [scope] [--agent a,b] [skill ...]
47
+
48
+ Scope:
49
+ --workspace install only into this folder / current project (alias: --project)
50
+ --global install only into global/home folders
51
+ --both install into both
52
+ (no scope -> you are asked interactively)
53
+
54
+ Selection:
55
+ [skill ...] limit to named skills (default: all)
56
+ --agent <a,b> limit to named agents (default: all)
57
+ --list list available skills
58
+ -h, --help show this help
59
+
60
+ Agents: claude copilot cursor windsurf codex gemini
61
+ EOF
62
+ }
63
+
64
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
65
+ LOCAL_SKILLS_DIR=""
66
+ [[ -n "$SCRIPT_DIR" && -d "$SCRIPT_DIR/$SKILLS_SUBDIR" ]] && LOCAL_SKILLS_DIR="$SCRIPT_DIR/$SKILLS_SUBDIR"
67
+
68
+ WORK=""
69
+ cleanup() { [[ -n "$WORK" && -d "$WORK" ]] && rm -rf "$WORK"; return 0; }
70
+ trap cleanup EXIT
71
+
72
+ # Ensure a local copy of every requested skill folder; echo its base dir.
73
+ ensure_skills_dir() {
74
+ if [[ -n "$LOCAL_SKILLS_DIR" ]]; then
75
+ printf '%s' "$LOCAL_SKILLS_DIR"; return
76
+ fi
77
+ command -v curl >/dev/null || die "curl is required for remote install."
78
+ WORK="$(mktemp -d)"
79
+ printf '%s' "$WORK"
80
+ }
81
+
82
+ list_remote_skills() {
83
+ curl -fsSL "$API_BASE" \
84
+ | grep '"name"' | sed -E 's/.*"name": *"([^"]+)".*/\1/' \
85
+ | while read -r n; do
86
+ curl -fsI "${RAW_BASE}/${SKILLS_SUBDIR}/${n}/SKILL.md" >/dev/null 2>&1 && echo "$n"
87
+ done
88
+ }
89
+
90
+ available_skills() {
91
+ if [[ -n "$LOCAL_SKILLS_DIR" ]]; then
92
+ find "$LOCAL_SKILLS_DIR" -maxdepth 2 -name SKILL.md -print0 \
93
+ | xargs -0 -n1 dirname | xargs -n1 basename | sort
94
+ else
95
+ list_remote_skills | sort
96
+ fi
97
+ }
98
+
99
+ # Fetch a single SKILL.md into $BASE/<skill>/SKILL.md when remote.
100
+ fetch_skill() {
101
+ local skill="$1" base="$2"
102
+ [[ -n "$LOCAL_SKILLS_DIR" ]] && return 0
103
+ mkdir -p "$base/$skill"
104
+ curl -fsSL "${RAW_BASE}/${SKILLS_SUBDIR}/${skill}/SKILL.md" -o "$base/$skill/SKILL.md" \
105
+ || die "Failed to download $skill/SKILL.md"
106
+ }
107
+
108
+ # Extract the description from a SKILL.md frontmatter (handles folded ">-"/"|").
109
+ skill_description() {
110
+ awk '
111
+ /^---[[:space:]]*$/ { c++; next }
112
+ c==1 {
113
+ if ($0 ~ /^description:/) {
114
+ line=$0; sub(/^description:[[:space:]]*/, "", line)
115
+ if (line ~ /^[>|]-?[+]?[[:space:]]*$/) { folded=1; next }
116
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line; done=1; exit
117
+ }
118
+ if (folded) {
119
+ if ($0 ~ /^[[:space:]]/) {
120
+ s=$0; gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
121
+ out=(out==""?s:out" "s); next
122
+ }
123
+ print out; done=1; exit
124
+ }
125
+ }
126
+ END { if (!done && out!="") print out }
127
+ ' "$1"
128
+ }
129
+
130
+ # Print SKILL.md body (everything after the closing frontmatter ---).
131
+ skill_body() {
132
+ awk 'BEGIN{c=0} /^---[[:space:]]*$/{c++; next} c>=2{print}' "$1"
133
+ }
134
+
135
+ # Resolve a registry dir token (HOME/... -> $HOME/...).
136
+ resolve_dir() {
137
+ local d="$1"
138
+ if [[ "$d" == HOME/* ]]; then printf '%s/%s' "$HOME" "${d#HOME/}"; else printf '%s' "$d"; fi
139
+ }
140
+
141
+ install_for_agent() {
142
+ local key="$1" label="$2" fmt="$3" ext="$4" dir="$5" scope="$6" base="$7"; shift 7
143
+ local skills=("$@")
144
+ mkdir -p "$dir"
145
+ local s
146
+ for s in "${skills[@]}"; do
147
+ fetch_skill "$s" "$base"
148
+ if [[ "$fmt" == "skill" ]]; then
149
+ mkdir -p "$dir/$s"
150
+ cp -R "$base/$s/." "$dir/$s/"
151
+ ok "$label ($scope) $s -> $dir/$s"
152
+ else
153
+ local desc body file
154
+ desc="$(skill_description "$base/$s/SKILL.md")"
155
+ file="$dir/$s$ext"
156
+ { printf -- '---\ndescription: %s\nalwaysApply: false\n---\n\n' "$desc"; skill_body "$base/$s/SKILL.md"; } >"$file"
157
+ ok "$label ($scope) $s -> $file"
158
+ fi
159
+ done
160
+ }
161
+
162
+ # ---- Parse args ------------------------------------------------------------
163
+ SCOPE=""
164
+ AGENT_FILTER=""
165
+ DO_LIST=false
166
+ SELECTED=()
167
+ while [[ $# -gt 0 ]]; do
168
+ case "$1" in
169
+ --project|--workspace|--local) SCOPE="project"; shift ;;
170
+ --global) SCOPE="global"; shift ;;
171
+ --both) SCOPE="both"; shift ;;
172
+ --agent|--agents) AGENT_FILTER="${2:?--agent needs a value}"; shift 2 ;;
173
+ --list) DO_LIST=true; shift ;;
174
+ -h|--help) usage; exit 0 ;;
175
+ -*) die "Unknown option: $1" ;;
176
+ *) SELECTED+=("$1"); shift ;;
177
+ esac
178
+ done
179
+
180
+ BASE="$(ensure_skills_dir)"
181
+
182
+ AVAILABLE=()
183
+ while IFS= read -r line; do [[ -n "$line" ]] && AVAILABLE+=("$line"); done < <(available_skills)
184
+ [[ ${#AVAILABLE[@]} -gt 0 ]] || die "No skills found."
185
+
186
+ if $DO_LIST; then
187
+ info "Available skills:"; printf ' - %s\n' "${AVAILABLE[@]}"; exit 0
188
+ fi
189
+
190
+ # Skills: default all.
191
+ [[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${AVAILABLE[@]}")
192
+ for s in "${SELECTED[@]}"; do
193
+ found=false; for a in "${AVAILABLE[@]}"; do [[ "$s" == "$a" ]] && found=true && break; done
194
+ $found || die "Unknown skill '$s'. Use --list."
195
+ done
196
+
197
+ # Scope: prompt if not given.
198
+ if [[ -z "$SCOPE" ]]; then
199
+ if [[ -t 0 ]]; then
200
+ info "Where should the skills be installed?"
201
+ echo " 1) Workspace (this folder only — current project)"
202
+ echo " 2) Global (your home folders — all projects)"
203
+ echo " 3) Both"
204
+ printf ' Choose 1/2/3 [1]: '
205
+ read -r ans
206
+ case "$ans" in 2|global) SCOPE="global";; 3|both) SCOPE="both";; *) SCOPE="project";; esac
207
+ else
208
+ warn 'No terminal; defaulting scope to "project". Use --global or --both.'
209
+ SCOPE="project"
210
+ fi
211
+ fi
212
+
213
+ SCOPES=("$SCOPE"); [[ "$SCOPE" == "both" ]] && SCOPES=("project" "global")
214
+
215
+ info "Installing ${#SELECTED[@]} skill(s) into all matching agents. Scope: $SCOPE"
216
+
217
+ for sc in "${SCOPES[@]}"; do
218
+ for entry in "${AGENTS[@]}"; do
219
+ IFS='|' read -r key label fmt ext pdir gdir <<<"$entry"
220
+ if [[ -n "$AGENT_FILTER" ]]; then
221
+ case ",$AGENT_FILTER," in *",$key,"*) ;; *) continue ;; esac
222
+ fi
223
+ if [[ "$sc" == "project" ]]; then dir="$(resolve_dir "$pdir")"; else dir="$(resolve_dir "$gdir")"; fi
224
+ install_for_agent "$key" "$label" "$fmt" "$ext" "$dir" "$sc" "$BASE" "${SELECTED[@]}"
225
+ done
226
+ done
227
+
228
+ ok "Done."
229
+ info "Restart your agent or reload skills/rules if needed."
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "spirewise",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Installable Agent Skills for copywriting (F6S & LinkedIn company pages) for GitHub Copilot, Claude Code, and Cursor.",
5
5
  "bin": {
6
6
  "spirewise": "bin/cli.js"
7
7
  },
8
8
  "files": [
9
9
  "bin/",
10
- "skills/"
10
+ "skills/",
11
+ "install.sh"
11
12
  ],
12
13
  "scripts": {
13
14
  "test": "node bin/cli.js list"
@@ -29,6 +30,6 @@
29
30
  },
30
31
  "repository": {
31
32
  "type": "git",
32
- "url": "https://github.com/spirerise/spirewise.git"
33
+ "url": "git+https://github.com/spirerise/spirewise.git"
33
34
  }
34
35
  }
package/skills/README.md CHANGED
@@ -31,34 +31,46 @@ LinkedIn company page copy" and it will follow the skill: gather inputs, write t
31
31
 
32
32
  ## Install via command
33
33
 
34
- The easiest way is the npm CLI (skills are bundled in the package):
34
+ The easiest way is the npm CLI it installs into **all** supported agents at
35
+ once and asks whether to install for the current **project** or **globally**:
35
36
 
36
37
  ```bash
37
- npx spirewise install --all # install every skill
38
- npx spirewise install f6s-copywriting # install just one
39
- npx spirewise install # interactive picker
40
- npx spirewise list # list available skills
38
+ npx spirewise install # asks: project / global / both
39
+ npx spirewise install --both # project + global, all agents
40
+ npx spirewise install --workspace # this folder only (current project)
41
+ npx spirewise install --global # all projects (home folders)
41
42
  ```
42
43
 
43
- Target a specific agent or folder:
44
+ Narrow it down:
44
45
 
45
46
  ```bash
46
- npx spirewise install --all --agent claude # ~/.claude/skills (default)
47
- npx spirewise install --all --agent copilot # ~/.copilot/skills
48
- npx spirewise install --all --agent cursor # ~/.cursor/skills
49
- npx spirewise install --all --target ./skills
47
+ npx spirewise install --both --agent claude,cursor # only some agents
48
+ npx spirewise install f6s-copywriting --global # only some skills
49
+ npx spirewise list # list skills
50
+ npx spirewise agents # agents + folders
50
51
  ```
51
52
 
53
+ Supported agents and their folders (project / global):
54
+
55
+ | Agent | Project | Global | Format |
56
+ |-------|---------|--------|--------|
57
+ | Claude Code | `.claude/skills/` | `~/.claude/skills/` | SKILL.md |
58
+ | GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` | SKILL.md |
59
+ | Cursor | `.cursor/rules/` | `~/.cursor/rules/` | `.mdc` |
60
+ | Windsurf | `.windsurf/rules/` | `~/.codeium/windsurf/global_rules/` | `.md` |
61
+ | Codex CLI | `.codex/skills/` | `~/.codex/skills/` | SKILL.md |
62
+ | Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` | SKILL.md |
63
+
52
64
  ### No-Node fallback: `install.sh`
53
65
 
54
- If you can't use npm, the bundled `install.sh` does the same over `curl`:
66
+ If you can't use npm, the bundled `install.sh` mirrors the CLI over `curl`:
55
67
 
56
68
  ```bash
57
69
  # from a clone
58
- ./install.sh --all
70
+ ./install.sh --both
59
71
 
60
72
  # remote
61
- curl -fsSL https://raw.githubusercontent.com/spirerise/spirewise/main/install.sh | bash -s -- --all
73
+ curl -fsSL https://raw.githubusercontent.com/spirerise/spirewise/main/install.sh | bash -s -- --both
62
74
  ```
63
75
 
64
76
  Run `./install.sh --help` or `npx spirewise --help` for all options.