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 +58 -19
- package/bin/cli.js +158 -71
- package/install.sh +229 -0
- package/package.json +4 -3
- package/skills/README.md +25 -13
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
|
-
|
|
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
|
|
13
|
+
No clone needed:
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
npx spirewise install
|
|
17
|
+
```
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
npx spirewise install f6s-copywriting
|
|
19
|
+
You'll be asked where to install:
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
npx spirewise install
|
|
28
|
+
Skip the prompt with a scope flag:
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
npx spirewise
|
|
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 --
|
|
40
|
+
spirewise install --both
|
|
33
41
|
```
|
|
34
42
|
|
|
35
|
-
##
|
|
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
|
-
|
|
39
|
-
spirewise install --
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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
|
|
9
|
-
* npx spirewise install --
|
|
10
|
-
* npx spirewise install
|
|
11
|
-
* npx spirewise install
|
|
12
|
-
* npx spirewise install
|
|
13
|
-
* npx spirewise install
|
|
14
|
-
* npx spirewise
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
40
|
-
|
|
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
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
--agent <
|
|
83
|
-
|
|
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
|
-
|
|
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 --
|
|
91
|
-
npx spirewise install
|
|
92
|
-
npx spirewise install
|
|
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
|
|
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 === '--
|
|
101
|
-
else if (a === '--
|
|
102
|
-
else if (a === '--
|
|
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
|
|
179
|
+
else o.skills.push(a);
|
|
106
180
|
}
|
|
107
|
-
return
|
|
181
|
+
return o;
|
|
108
182
|
}
|
|
109
183
|
|
|
110
|
-
function
|
|
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(
|
|
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 (
|
|
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
|
|
230
|
+
const o = parseArgs(argv);
|
|
148
231
|
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
155
|
-
if (
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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.
|
|
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
|
|
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
|
|
38
|
-
npx spirewise install
|
|
39
|
-
npx spirewise install
|
|
40
|
-
npx spirewise
|
|
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
|
-
|
|
44
|
+
Narrow it down:
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
|
-
npx spirewise install --
|
|
47
|
-
npx spirewise install
|
|
48
|
-
npx spirewise
|
|
49
|
-
npx spirewise
|
|
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`
|
|
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 --
|
|
70
|
+
./install.sh --both
|
|
59
71
|
|
|
60
72
|
# remote
|
|
61
|
-
curl -fsSL https://raw.githubusercontent.com/spirerise/spirewise/main/install.sh | bash -s -- --
|
|
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.
|