spirewise 1.0.0 → 1.0.3
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 +218 -74
- package/install.sh +253 -0
- package/package.json +4 -3
- package/skills/README.md +25 -13
- package/skills/f6s-copywriting/SKILL.md +51 -4
- package/skills/linkedin-copywriting/SKILL.md +52 -5
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,42 +2,129 @@
|
|
|
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
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
const USE_COLOR = !process.env.NO_COLOR;
|
|
31
|
+
const RAW = {
|
|
32
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
33
|
+
blue: '\x1b[1;34m', green: '\x1b[1;32m', yellow: '\x1b[1;33m',
|
|
34
|
+
red: '\x1b[1;31m', cyan: '\x1b[1;36m', magenta: '\x1b[1;35m',
|
|
27
35
|
};
|
|
36
|
+
const paint = (code, s) => (USE_COLOR ? `${code}${s}${RAW.reset}` : s);
|
|
37
|
+
const c = USE_COLOR
|
|
38
|
+
? RAW
|
|
39
|
+
: { reset: '', bold: '', dim: '', blue: '', green: '', yellow: '', red: '', cyan: '', magenta: '' };
|
|
40
|
+
|
|
28
41
|
const info = (m) => console.log(`${c.blue}==>${c.reset} ${m}`);
|
|
29
42
|
const ok = (m) => console.log(`${c.green} ok${c.reset} ${m}`);
|
|
30
43
|
const warn = (m) => console.error(`${c.yellow} !${c.reset} ${m}`);
|
|
31
44
|
const die = (m) => { console.error(`${c.red}err${c.reset} ${m}`); process.exit(1); };
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
let PKG_VERSION = '';
|
|
47
|
+
try { PKG_VERSION = require(path.join(PKG_ROOT, 'package.json')).version || ''; } catch (_) {}
|
|
48
|
+
|
|
49
|
+
const BANNER = [
|
|
50
|
+
' ███████╗██████╗ ██╗██████╗ ███████╗██╗ ██╗██╗███████╗███████╗',
|
|
51
|
+
' ██╔════╝██╔══██╗██║██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝',
|
|
52
|
+
' ███████╗██████╔╝██║██████╔╝█████╗ ██║ █╗ ██║██║███████╗█████╗ ',
|
|
53
|
+
' ╚════██║██╔═══╝ ██║██╔══██╗██╔══╝ ██║███╗██║██║╚════██║██╔══╝ ',
|
|
54
|
+
' ███████║██║ ██║██║ ██║███████╗╚███╔███╔╝██║███████║███████╗',
|
|
55
|
+
' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
function banner() {
|
|
59
|
+
console.log('');
|
|
60
|
+
for (const line of BANNER) console.log(paint(RAW.cyan, line));
|
|
61
|
+
const tag = ` Agent Skills · F6S & LinkedIn copywriting${PKG_VERSION ? ' · v' + PKG_VERSION : ''}`;
|
|
62
|
+
console.log(paint(RAW.magenta, tag));
|
|
63
|
+
console.log('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let STEP_TOTAL = 4;
|
|
67
|
+
function step(n, msg) {
|
|
68
|
+
console.log(`${paint(RAW.cyan, ' ▸')} ${paint(RAW.bold, `Step ${n}/${STEP_TOTAL}`)} ${msg}`);
|
|
69
|
+
}
|
|
70
|
+
function substep(msg) {
|
|
71
|
+
console.log(` ${paint(RAW.green, '✓')} ${msg}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function box(lines) {
|
|
75
|
+
const vis = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
76
|
+
const width = Math.max(...lines.map((l) => vis(l).length));
|
|
77
|
+
const top = ' ┌' + '─'.repeat(width + 2) + '┐';
|
|
78
|
+
const bot = ' └' + '─'.repeat(width + 2) + '┘';
|
|
79
|
+
console.log(paint(RAW.green, top));
|
|
80
|
+
for (const l of lines) console.log(paint(RAW.green, ' │ ') + l + ' '.repeat(width - vis(l).length) + paint(RAW.green, ' │'));
|
|
81
|
+
console.log(paint(RAW.green, bot));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Agent registry. `format`:
|
|
86
|
+
* - 'skill': copy the skill folder verbatim (SKILL.md + any files)
|
|
87
|
+
* - 'rule' : write one rule file derived from SKILL.md (ext required)
|
|
88
|
+
* `project` is resolved from cwd; `global` uses ~ for the home directory.
|
|
89
|
+
*/
|
|
90
|
+
const AGENTS = {
|
|
91
|
+
claude: {
|
|
92
|
+
label: 'Claude Code', format: 'skill',
|
|
93
|
+
project: '.claude/skills', global: '~/.claude/skills',
|
|
94
|
+
},
|
|
95
|
+
copilot: {
|
|
96
|
+
label: 'GitHub Copilot', format: 'skill',
|
|
97
|
+
project: '.github/skills', global: '~/.copilot/skills',
|
|
98
|
+
},
|
|
99
|
+
cursor: {
|
|
100
|
+
label: 'Cursor', format: 'rule', ext: '.mdc',
|
|
101
|
+
project: '.cursor/rules', global: '~/.cursor/rules',
|
|
102
|
+
},
|
|
103
|
+
windsurf: {
|
|
104
|
+
label: 'Windsurf', format: 'rule', ext: '.md',
|
|
105
|
+
project: '.windsurf/rules', global: '~/.codeium/windsurf/global_rules',
|
|
106
|
+
},
|
|
107
|
+
codex: {
|
|
108
|
+
label: 'Codex CLI', format: 'skill',
|
|
109
|
+
project: '.codex/skills', global: '~/.codex/skills',
|
|
110
|
+
},
|
|
111
|
+
gemini: {
|
|
112
|
+
label: 'Gemini CLI', format: 'skill',
|
|
113
|
+
project: '.gemini/skills', global: '~/.gemini/skills',
|
|
114
|
+
},
|
|
37
115
|
};
|
|
38
116
|
|
|
39
|
-
function
|
|
40
|
-
|
|
117
|
+
function expandHome(p) {
|
|
118
|
+
if (p === '~') return HOME;
|
|
119
|
+
if (p.startsWith('~/')) return path.join(HOME, p.slice(2));
|
|
120
|
+
return p;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveTarget(agent, scope) {
|
|
124
|
+
const rel = scope === 'project' ? agent.project : agent.global;
|
|
125
|
+
return scope === 'project'
|
|
126
|
+
? path.resolve(process.cwd(), rel)
|
|
127
|
+
: path.resolve(expandHome(rel));
|
|
41
128
|
}
|
|
42
129
|
|
|
43
130
|
function availableSkills() {
|
|
@@ -48,6 +135,19 @@ function availableSkills() {
|
|
|
48
135
|
.sort();
|
|
49
136
|
}
|
|
50
137
|
|
|
138
|
+
function readSkill(name) {
|
|
139
|
+
const raw = fs.readFileSync(path.join(SKILLS_DIR, name, 'SKILL.md'), 'utf8');
|
|
140
|
+
let description = name;
|
|
141
|
+
let body = raw;
|
|
142
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
143
|
+
if (m) {
|
|
144
|
+
body = m[2];
|
|
145
|
+
const dm = m[1].match(/description:\s*(?:>-)?\s*([\s\S]*?)(?:\n\w|$)/);
|
|
146
|
+
if (dm) description = dm[1].replace(/\s+/g, ' ').trim();
|
|
147
|
+
}
|
|
148
|
+
return { description, body: body.replace(/^\s+/, '') };
|
|
149
|
+
}
|
|
150
|
+
|
|
51
151
|
function copyDir(src, dest) {
|
|
52
152
|
fs.mkdirSync(dest, { recursive: true });
|
|
53
153
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -58,81 +158,100 @@ function copyDir(src, dest) {
|
|
|
58
158
|
}
|
|
59
159
|
}
|
|
60
160
|
|
|
61
|
-
function
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
161
|
+
function installToAgent(agentKey, agent, scope, skills) {
|
|
162
|
+
const targetDir = resolveTarget(agent, scope);
|
|
163
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
164
|
+
for (const skill of skills) {
|
|
165
|
+
if (agent.format === 'skill') {
|
|
166
|
+
const dest = path.join(targetDir, skill);
|
|
167
|
+
copyDir(path.join(SKILLS_DIR, skill), dest);
|
|
168
|
+
console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}→ ${dest}${c.reset}`);
|
|
169
|
+
} else {
|
|
170
|
+
const { description, body } = readSkill(skill);
|
|
171
|
+
const front = `---\ndescription: ${description}\nalwaysApply: false\n---\n\n`;
|
|
172
|
+
const file = path.join(targetDir, skill + (agent.ext || '.md'));
|
|
173
|
+
fs.writeFileSync(file, front + body);
|
|
174
|
+
console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}→ ${file}${c.reset}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
67
177
|
}
|
|
68
178
|
|
|
69
179
|
function usage() {
|
|
70
|
-
console.log(`spirewise — install copywriting Agent Skills
|
|
180
|
+
console.log(`spirewise — install copywriting Agent Skills into all your AI agents
|
|
71
181
|
|
|
72
182
|
Usage:
|
|
73
|
-
spirewise list
|
|
74
183
|
spirewise install [skill ...] [options]
|
|
184
|
+
spirewise list
|
|
185
|
+
spirewise agents
|
|
75
186
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
187
|
+
Scope (where to install):
|
|
188
|
+
--workspace Install only into this folder / current project (alias: --project)
|
|
189
|
+
--global Install only into global/home folders
|
|
190
|
+
--both Install into both workspace and global
|
|
191
|
+
(no scope flag -> you are asked interactively)
|
|
79
192
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
--agent <
|
|
83
|
-
|
|
193
|
+
Selection:
|
|
194
|
+
[skill ...] Limit to named skills (default: all)
|
|
195
|
+
--agent <a,b> Limit to named agents (default: all)
|
|
196
|
+
|
|
197
|
+
Other:
|
|
84
198
|
-h, --help Show this help
|
|
85
199
|
|
|
86
|
-
|
|
200
|
+
Supported agents:
|
|
201
|
+
${Object.entries(AGENTS).map(([k, a]) => ` - ${k} (${a.label})`).join('\n')}
|
|
202
|
+
|
|
203
|
+
Skills:
|
|
87
204
|
${availableSkills().map((s) => ' - ' + s).join('\n') || ' (none found)'}
|
|
88
205
|
|
|
89
206
|
Examples:
|
|
90
|
-
npx spirewise install --
|
|
91
|
-
npx spirewise install
|
|
92
|
-
npx spirewise install
|
|
207
|
+
npx spirewise install --both
|
|
208
|
+
npx spirewise install --project --agent claude,cursor
|
|
209
|
+
npx spirewise install f6s-copywriting --global
|
|
93
210
|
`);
|
|
94
211
|
}
|
|
95
212
|
|
|
96
213
|
function parseArgs(argv) {
|
|
97
|
-
const
|
|
214
|
+
const o = { scope: null, agents: null, skills: [] };
|
|
98
215
|
for (let i = 0; i < argv.length; i++) {
|
|
99
216
|
const a = argv[i];
|
|
100
|
-
if (a === '--
|
|
101
|
-
else if (a === '--
|
|
102
|
-
else if (a === '--
|
|
217
|
+
if (a === '--project' || a === '--workspace' || a === '--local') o.scope = 'project';
|
|
218
|
+
else if (a === '--global') o.scope = 'global';
|
|
219
|
+
else if (a === '--both') o.scope = 'both';
|
|
220
|
+
else if (a === '--scope') o.scope = (argv[++i] || die('--scope needs a value'));
|
|
221
|
+
else if (a === '--agent' || a === '--agents') o.agents = (argv[++i] || die('--agent needs a value')).split(',').map((s) => s.trim()).filter(Boolean);
|
|
103
222
|
else if (a === '-h' || a === '--help') { usage(); process.exit(0); }
|
|
104
223
|
else if (a.startsWith('-')) die(`Unknown option: ${a}`);
|
|
105
|
-
else
|
|
224
|
+
else o.skills.push(a);
|
|
106
225
|
}
|
|
107
|
-
return
|
|
226
|
+
return o;
|
|
108
227
|
}
|
|
109
228
|
|
|
110
|
-
function
|
|
229
|
+
function ask(question) {
|
|
111
230
|
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
231
|
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
|
-
});
|
|
232
|
+
rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); });
|
|
129
233
|
});
|
|
130
234
|
}
|
|
131
235
|
|
|
236
|
+
async function promptScope() {
|
|
237
|
+
if (!process.stdin.isTTY) {
|
|
238
|
+
warn('No terminal detected; defaulting scope to "project". Use --global or --both to change.');
|
|
239
|
+
return 'project';
|
|
240
|
+
}
|
|
241
|
+
info('Where should the skills be installed?');
|
|
242
|
+
console.log(' 1) Workspace (this folder only — the current project directory)');
|
|
243
|
+
console.log(' 2) Global (your home folders — applies to all projects)');
|
|
244
|
+
console.log(' 3) Both');
|
|
245
|
+
const ans = (await ask(' Choose 1/2/3 [1]: ')).toLowerCase();
|
|
246
|
+
if (ans === '2' || ans === 'global') return 'global';
|
|
247
|
+
if (ans === '3' || ans === 'both') return 'both';
|
|
248
|
+
return 'project';
|
|
249
|
+
}
|
|
250
|
+
|
|
132
251
|
async function main() {
|
|
133
252
|
let argv = process.argv.slice(2);
|
|
134
253
|
let command = 'install';
|
|
135
|
-
if (
|
|
254
|
+
if (['list', 'install', 'agents'].includes(argv[0])) { command = argv[0]; argv = argv.slice(1); }
|
|
136
255
|
else if (argv[0] === '-h' || argv[0] === '--help') { usage(); return; }
|
|
137
256
|
|
|
138
257
|
const available = availableSkills();
|
|
@@ -143,27 +262,52 @@ async function main() {
|
|
|
143
262
|
available.forEach((s) => console.log(' - ' + s));
|
|
144
263
|
return;
|
|
145
264
|
}
|
|
265
|
+
if (command === 'agents') {
|
|
266
|
+
info('Supported agents and their folders:');
|
|
267
|
+
for (const [k, a] of Object.entries(AGENTS)) {
|
|
268
|
+
console.log(` ${k} (${a.label}) [${a.format}]`);
|
|
269
|
+
console.log(` project: ${a.project}`);
|
|
270
|
+
console.log(` global: ${a.global}`);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const o = parseArgs(argv);
|
|
146
276
|
|
|
147
|
-
|
|
277
|
+
banner();
|
|
148
278
|
|
|
149
|
-
|
|
150
|
-
|
|
279
|
+
step(1, 'Scanning available skills');
|
|
280
|
+
let skills = o.skills.length ? o.skills : available.slice();
|
|
281
|
+
for (const s of skills) if (!available.includes(s)) die(`Unknown skill '${s}'. Run "spirewise list".`);
|
|
282
|
+
substep(`${skills.length} skill(s): ${skills.join(', ')}`);
|
|
283
|
+
|
|
284
|
+
step(2, 'Selecting target agents');
|
|
285
|
+
let agentKeys = Object.keys(AGENTS);
|
|
286
|
+
if (o.agents) {
|
|
287
|
+
for (const k of o.agents) if (!AGENTS[k]) die(`Unknown agent '${k}'. Run "spirewise agents".`);
|
|
288
|
+
agentKeys = o.agents;
|
|
151
289
|
}
|
|
152
|
-
|
|
290
|
+
substep(`${agentKeys.length} agent(s): ${agentKeys.join(', ')}`);
|
|
153
291
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
292
|
+
step(3, 'Choosing install scope');
|
|
293
|
+
let scope = o.scope || await promptScope();
|
|
294
|
+
if (!['project', 'global', 'both'].includes(scope)) die(`Invalid scope '${scope}'.`);
|
|
295
|
+
const scopes = scope === 'both' ? ['project', 'global'] : [scope];
|
|
296
|
+
substep(`scope: ${paint(RAW.bold, scope === 'project' ? 'workspace' : scope)}`);
|
|
157
297
|
|
|
158
|
-
|
|
159
|
-
|
|
298
|
+
step(4, 'Installing');
|
|
299
|
+
let count = 0;
|
|
300
|
+
for (const sc of scopes) {
|
|
301
|
+
for (const k of agentKeys) { installToAgent(k, AGENTS[k], sc, skills); count += skills.length; }
|
|
160
302
|
}
|
|
161
303
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
304
|
+
console.log('');
|
|
305
|
+
box([
|
|
306
|
+
`Installed ${paint(RAW.bold, String(skills.length))} skill(s) into ${paint(RAW.bold, String(agentKeys.length))} agent(s)`,
|
|
307
|
+
`scope: ${scopes.map((s) => (s === 'project' ? 'workspace' : s)).join(' + ')} · ${count} item(s) written`,
|
|
308
|
+
`next: open your agent and say "write our F6S profile copy"`,
|
|
309
|
+
]);
|
|
310
|
+
console.log('');
|
|
167
311
|
}
|
|
168
312
|
|
|
169
313
|
main().catch((e) => die(e && e.message ? e.message : String(e)));
|
package/install.sh
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
step() { printf '%s %s %s\n' "$(color '1;36' ' >')" "$(color '1' "Step $1/4")" "$2"; }
|
|
41
|
+
substep() { printf ' %s %s\n' "$(color '1;32' 'v')" "$1"; }
|
|
42
|
+
|
|
43
|
+
banner() {
|
|
44
|
+
printf '\n'
|
|
45
|
+
color '1;36' ' ███████╗██████╗ ██╗██████╗ ███████╗██╗ ██╗██╗███████╗███████╗'; printf '\n'
|
|
46
|
+
color '1;36' ' ██╔════╝██╔══██╗██║██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝'; printf '\n'
|
|
47
|
+
color '1;36' ' ███████╗██████╔╝██║██████╔╝█████╗ ██║ █╗ ██║██║███████╗█████╗ '; printf '\n'
|
|
48
|
+
color '1;36' ' ╚════██║██╔═══╝ ██║██╔══██╗██╔══╝ ██║███╗██║██║╚════██║██╔══╝ '; printf '\n'
|
|
49
|
+
color '1;36' ' ███████║██║ ██║██║ ██║███████╗╚███╔███╔╝██║███████║███████╗'; printf '\n'
|
|
50
|
+
color '1;36' ' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝'; printf '\n'
|
|
51
|
+
color '1;35' ' Agent Skills · F6S & LinkedIn copywriting'; printf '\n\n'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
usage() {
|
|
55
|
+
cat <<EOF
|
|
56
|
+
spirewise — install copywriting Agent Skills into all your AI agents.
|
|
57
|
+
|
|
58
|
+
Usage:
|
|
59
|
+
install.sh [scope] [--agent a,b] [skill ...]
|
|
60
|
+
|
|
61
|
+
Scope:
|
|
62
|
+
--workspace install only into this folder / current project (alias: --project)
|
|
63
|
+
--global install only into global/home folders
|
|
64
|
+
--both install into both
|
|
65
|
+
(no scope -> you are asked interactively)
|
|
66
|
+
|
|
67
|
+
Selection:
|
|
68
|
+
[skill ...] limit to named skills (default: all)
|
|
69
|
+
--agent <a,b> limit to named agents (default: all)
|
|
70
|
+
--list list available skills
|
|
71
|
+
-h, --help show this help
|
|
72
|
+
|
|
73
|
+
Agents: claude copilot cursor windsurf codex gemini
|
|
74
|
+
EOF
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
|
|
78
|
+
LOCAL_SKILLS_DIR=""
|
|
79
|
+
[[ -n "$SCRIPT_DIR" && -d "$SCRIPT_DIR/$SKILLS_SUBDIR" ]] && LOCAL_SKILLS_DIR="$SCRIPT_DIR/$SKILLS_SUBDIR"
|
|
80
|
+
|
|
81
|
+
WORK=""
|
|
82
|
+
cleanup() { [[ -n "$WORK" && -d "$WORK" ]] && rm -rf "$WORK"; return 0; }
|
|
83
|
+
trap cleanup EXIT
|
|
84
|
+
|
|
85
|
+
# Ensure a local copy of every requested skill folder; echo its base dir.
|
|
86
|
+
ensure_skills_dir() {
|
|
87
|
+
if [[ -n "$LOCAL_SKILLS_DIR" ]]; then
|
|
88
|
+
printf '%s' "$LOCAL_SKILLS_DIR"; return
|
|
89
|
+
fi
|
|
90
|
+
command -v curl >/dev/null || die "curl is required for remote install."
|
|
91
|
+
WORK="$(mktemp -d)"
|
|
92
|
+
printf '%s' "$WORK"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
list_remote_skills() {
|
|
96
|
+
curl -fsSL "$API_BASE" \
|
|
97
|
+
| grep '"name"' | sed -E 's/.*"name": *"([^"]+)".*/\1/' \
|
|
98
|
+
| while read -r n; do
|
|
99
|
+
curl -fsI "${RAW_BASE}/${SKILLS_SUBDIR}/${n}/SKILL.md" >/dev/null 2>&1 && echo "$n"
|
|
100
|
+
done
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
available_skills() {
|
|
104
|
+
if [[ -n "$LOCAL_SKILLS_DIR" ]]; then
|
|
105
|
+
find "$LOCAL_SKILLS_DIR" -maxdepth 2 -name SKILL.md -print0 \
|
|
106
|
+
| xargs -0 -n1 dirname | xargs -n1 basename | sort
|
|
107
|
+
else
|
|
108
|
+
list_remote_skills | sort
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Fetch a single SKILL.md into $BASE/<skill>/SKILL.md when remote.
|
|
113
|
+
fetch_skill() {
|
|
114
|
+
local skill="$1" base="$2"
|
|
115
|
+
[[ -n "$LOCAL_SKILLS_DIR" ]] && return 0
|
|
116
|
+
mkdir -p "$base/$skill"
|
|
117
|
+
curl -fsSL "${RAW_BASE}/${SKILLS_SUBDIR}/${skill}/SKILL.md" -o "$base/$skill/SKILL.md" \
|
|
118
|
+
|| die "Failed to download $skill/SKILL.md"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Extract the description from a SKILL.md frontmatter (handles folded ">-"/"|").
|
|
122
|
+
skill_description() {
|
|
123
|
+
awk '
|
|
124
|
+
/^---[[:space:]]*$/ { c++; next }
|
|
125
|
+
c==1 {
|
|
126
|
+
if ($0 ~ /^description:/) {
|
|
127
|
+
line=$0; sub(/^description:[[:space:]]*/, "", line)
|
|
128
|
+
if (line ~ /^[>|]-?[+]?[[:space:]]*$/) { folded=1; next }
|
|
129
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line; done=1; exit
|
|
130
|
+
}
|
|
131
|
+
if (folded) {
|
|
132
|
+
if ($0 ~ /^[[:space:]]/) {
|
|
133
|
+
s=$0; gsub(/^[[:space:]]+|[[:space:]]+$/, "", s)
|
|
134
|
+
out=(out==""?s:out" "s); next
|
|
135
|
+
}
|
|
136
|
+
print out; done=1; exit
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
END { if (!done && out!="") print out }
|
|
140
|
+
' "$1"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Print SKILL.md body (everything after the closing frontmatter ---).
|
|
144
|
+
skill_body() {
|
|
145
|
+
awk 'BEGIN{c=0} /^---[[:space:]]*$/{c++; next} c>=2{print}' "$1"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Resolve a registry dir token (HOME/... -> $HOME/...).
|
|
149
|
+
resolve_dir() {
|
|
150
|
+
local d="$1"
|
|
151
|
+
if [[ "$d" == HOME/* ]]; then printf '%s/%s' "$HOME" "${d#HOME/}"; else printf '%s' "$d"; fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
install_for_agent() {
|
|
155
|
+
local key="$1" label="$2" fmt="$3" ext="$4" dir="$5" scope="$6" base="$7"; shift 7
|
|
156
|
+
local skills=("$@")
|
|
157
|
+
mkdir -p "$dir"
|
|
158
|
+
local s
|
|
159
|
+
for s in "${skills[@]}"; do
|
|
160
|
+
fetch_skill "$s" "$base"
|
|
161
|
+
if [[ "$fmt" == "skill" ]]; then
|
|
162
|
+
mkdir -p "$dir/$s"
|
|
163
|
+
cp -R "$base/$s/." "$dir/$s/"
|
|
164
|
+
substep "$label ($scope) $s -> $dir/$s"
|
|
165
|
+
else
|
|
166
|
+
local desc body file
|
|
167
|
+
desc="$(skill_description "$base/$s/SKILL.md")"
|
|
168
|
+
file="$dir/$s$ext"
|
|
169
|
+
{ printf -- '---\ndescription: %s\nalwaysApply: false\n---\n\n' "$desc"; skill_body "$base/$s/SKILL.md"; } >"$file"
|
|
170
|
+
substep "$label ($scope) $s -> $file"
|
|
171
|
+
fi
|
|
172
|
+
done
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# ---- Parse args ------------------------------------------------------------
|
|
176
|
+
SCOPE=""
|
|
177
|
+
AGENT_FILTER=""
|
|
178
|
+
DO_LIST=false
|
|
179
|
+
SELECTED=()
|
|
180
|
+
while [[ $# -gt 0 ]]; do
|
|
181
|
+
case "$1" in
|
|
182
|
+
--project|--workspace|--local) SCOPE="project"; shift ;;
|
|
183
|
+
--global) SCOPE="global"; shift ;;
|
|
184
|
+
--both) SCOPE="both"; shift ;;
|
|
185
|
+
--agent|--agents) AGENT_FILTER="${2:?--agent needs a value}"; shift 2 ;;
|
|
186
|
+
--list) DO_LIST=true; shift ;;
|
|
187
|
+
-h|--help) usage; exit 0 ;;
|
|
188
|
+
-*) die "Unknown option: $1" ;;
|
|
189
|
+
*) SELECTED+=("$1"); shift ;;
|
|
190
|
+
esac
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
BASE="$(ensure_skills_dir)"
|
|
194
|
+
|
|
195
|
+
AVAILABLE=()
|
|
196
|
+
while IFS= read -r line; do [[ -n "$line" ]] && AVAILABLE+=("$line"); done < <(available_skills)
|
|
197
|
+
[[ ${#AVAILABLE[@]} -gt 0 ]] || die "No skills found."
|
|
198
|
+
|
|
199
|
+
if $DO_LIST; then
|
|
200
|
+
info "Available skills:"; printf ' - %s\n' "${AVAILABLE[@]}"; exit 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
banner
|
|
204
|
+
|
|
205
|
+
# Step 1: skills (default all).
|
|
206
|
+
step 1 "Scanning available skills"
|
|
207
|
+
[[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${AVAILABLE[@]}")
|
|
208
|
+
for s in "${SELECTED[@]}"; do
|
|
209
|
+
found=false; for a in "${AVAILABLE[@]}"; do [[ "$s" == "$a" ]] && found=true && break; done
|
|
210
|
+
$found || die "Unknown skill '$s'. Use --list."
|
|
211
|
+
done
|
|
212
|
+
substep "${#SELECTED[@]} skill(s): ${SELECTED[*]}"
|
|
213
|
+
|
|
214
|
+
# Step 2: agents
|
|
215
|
+
step 2 "Selecting target agents"
|
|
216
|
+
if [[ -n "$AGENT_FILTER" ]]; then substep "agents: $AGENT_FILTER"; else substep "agents: all supported"; fi
|
|
217
|
+
|
|
218
|
+
# Step 3: scope — prompt if not given.
|
|
219
|
+
step 3 "Choosing install scope"
|
|
220
|
+
if [[ -z "$SCOPE" ]]; then
|
|
221
|
+
if [[ -t 0 ]]; then
|
|
222
|
+
info "Where should the skills be installed?"
|
|
223
|
+
echo " 1) Workspace (this folder only — current project)"
|
|
224
|
+
echo " 2) Global (your home folders — all projects)"
|
|
225
|
+
echo " 3) Both"
|
|
226
|
+
printf ' Choose 1/2/3 [1]: '
|
|
227
|
+
read -r ans
|
|
228
|
+
case "$ans" in 2|global) SCOPE="global";; 3|both) SCOPE="both";; *) SCOPE="project";; esac
|
|
229
|
+
else
|
|
230
|
+
warn 'No terminal; defaulting scope to "project". Use --global or --both.'
|
|
231
|
+
SCOPE="project"
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
[[ "$SCOPE" == "project" ]] && substep "scope: workspace" || substep "scope: $SCOPE"
|
|
235
|
+
|
|
236
|
+
SCOPES=("$SCOPE"); [[ "$SCOPE" == "both" ]] && SCOPES=("project" "global")
|
|
237
|
+
|
|
238
|
+
# Step 4: install
|
|
239
|
+
step 4 "Installing"
|
|
240
|
+
for sc in "${SCOPES[@]}"; do
|
|
241
|
+
for entry in "${AGENTS[@]}"; do
|
|
242
|
+
IFS='|' read -r key label fmt ext pdir gdir <<<"$entry"
|
|
243
|
+
if [[ -n "$AGENT_FILTER" ]]; then
|
|
244
|
+
case ",$AGENT_FILTER," in *",$key,"*) ;; *) continue ;; esac
|
|
245
|
+
fi
|
|
246
|
+
if [[ "$sc" == "project" ]]; then dir="$(resolve_dir "$pdir")"; else dir="$(resolve_dir "$gdir")"; fi
|
|
247
|
+
install_for_agent "$key" "$label" "$fmt" "$ext" "$dir" "$sc" "$BASE" "${SELECTED[@]}"
|
|
248
|
+
done
|
|
249
|
+
done
|
|
250
|
+
|
|
251
|
+
printf '\n'
|
|
252
|
+
ok "Installed ${#SELECTED[@]} skill(s). Next: open your agent and say \"write our F6S profile copy\"."
|
|
253
|
+
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spirewise",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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.
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
name: f6s-copywriting
|
|
3
3
|
description: >-
|
|
4
4
|
Generate complete, ready-to-paste copywriting for an F6S (f6s.com) startup/company
|
|
5
|
-
profile page
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
profile page, written like a senior unicorn-startup marketer — humanized,
|
|
6
|
+
attention-grabbing, and conversion-focused. Use when the user asks to "write our
|
|
7
|
+
F6S profile", "create F6S copy", "fill out F6S page", or prepare content for an
|
|
8
|
+
accelerator / investor application on F6S. Produces a single .txt file under an
|
|
9
|
+
`f6s/` folder in the project root, with every field kept STRICTLY under its
|
|
10
|
+
character limit.
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# F6S Profile Copywriting
|
|
@@ -69,6 +71,49 @@ keep the same "stay under" headroom rule.
|
|
|
69
71
|
- Write in third person or first-person plural ("we") consistently.
|
|
70
72
|
- No keyword stuffing; F6S profiles are read by humans (accelerators/investors).
|
|
71
73
|
|
|
74
|
+
## Voice & quality standard — write like a unicorn-startup marketer
|
|
75
|
+
|
|
76
|
+
Write every word as if you are a **senior brand + growth marketer who has led
|
|
77
|
+
messaging for category-defining startups** (think the caliber of Stripe, Notion,
|
|
78
|
+
Linear, Ramp, Airbnb). The copy an accelerator partner or investor reads should
|
|
79
|
+
feel sharp, confident, and unmistakably human. Non-negotiable bar:
|
|
80
|
+
|
|
81
|
+
**1. Humanized — sounds like a real person, never "AI-generated".**
|
|
82
|
+
- Vary sentence length. Mix short, punchy lines with one longer, rhythmic one.
|
|
83
|
+
- Use plain, concrete language a smart 12-year-old could follow. Cut jargon.
|
|
84
|
+
- Write with a point of view and a pulse — confident, not corporate-bland.
|
|
85
|
+
- Read it aloud in your head; if a sentence sounds robotic, rewrite it.
|
|
86
|
+
|
|
87
|
+
**2. Banned "AI tells" — never use these.**
|
|
88
|
+
- Filler verbs/phrases: "leverage", "utilize", "seamlessly", "robust",
|
|
89
|
+
"cutting-edge", "state-of-the-art", "empower", "unlock", "elevate",
|
|
90
|
+
"revolutionize", "game-changer", "in today's fast-paced world",
|
|
91
|
+
"we are committed to", "at the intersection of".
|
|
92
|
+
- Hype with no proof: "best", "#1", "world-class", "industry-leading".
|
|
93
|
+
- Em-dash overuse, three-item lists on autopilot, and symmetrical "not only…
|
|
94
|
+
but also" scaffolding. Break the pattern; write like a human, not a template.
|
|
95
|
+
|
|
96
|
+
**3. Attracting & focused — earn the next 5 seconds of attention.**
|
|
97
|
+
- Hook first. The opening line must make the reader want the next one.
|
|
98
|
+
- One core idea per field. If it does two jobs, split or cut.
|
|
99
|
+
- Specific > vague every time: real numbers, named customers, concrete outcomes.
|
|
100
|
+
"Cut onboarding from 3 weeks to 2 days for 40+ teams" beats "improves
|
|
101
|
+
efficiency".
|
|
102
|
+
- Lead with the customer's problem and the outcome, not your feature list.
|
|
103
|
+
- Active voice, present tense, strong verbs. Subject does the thing.
|
|
104
|
+
|
|
105
|
+
**4. Investor/accelerator lens (this is F6S).**
|
|
106
|
+
- Make the *why now*, the *traction*, and the *wedge* obvious fast.
|
|
107
|
+
- Show momentum with evidence (growth %, revenue, retention, logos, waitlist).
|
|
108
|
+
- Sound fundable and grounded — ambitious vision backed by real proof.
|
|
109
|
+
|
|
110
|
+
**5. Final humanization pass (always run before saving).**
|
|
111
|
+
- Remove every banned word above; replace with plain, specific language.
|
|
112
|
+
- Delete adjectives that aren't earned by a fact.
|
|
113
|
+
- Tighten: if a word can be cut without losing meaning, cut it.
|
|
114
|
+
- Ensure no two consecutive sentences share the same shape or opener.
|
|
115
|
+
- Confirm it reads like one confident human voice end to end.
|
|
116
|
+
|
|
72
117
|
## Required .txt output template
|
|
73
118
|
|
|
74
119
|
Write the file using exactly this structure. Put the live character count in
|
|
@@ -130,3 +175,5 @@ Other: <url>
|
|
|
130
175
|
3. No field is empty unless intentionally a `[PLACEHOLDER]`.
|
|
131
176
|
4. No banned hype words; tone is factual and specific.
|
|
132
177
|
5. Confirm counts programmatically (e.g. `awk`/`wc -m` per field) — do not trust eyeballing.
|
|
178
|
+
6. Humanization pass done: zero banned "AI tells", varied sentence shapes, reads
|
|
179
|
+
like one confident human marketer wrote it, and every claim is specific.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: linkedin-copywriting
|
|
3
3
|
description: >-
|
|
4
|
-
Generate complete, ready-to-paste copywriting for a LinkedIn Company Page
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Generate complete, ready-to-paste copywriting for a LinkedIn Company Page,
|
|
5
|
+
written like a senior unicorn-startup marketer — humanized, attention-grabbing,
|
|
6
|
+
and conversion-focused. Use when the user asks to "write our LinkedIn page",
|
|
7
|
+
"create LinkedIn company copy", "fill out the About section", or prepare LinkedIn
|
|
8
|
+
page content. Produces a single .txt file under a `linkedin copywriting/` folder
|
|
9
|
+
in the project root, with every field kept STRICTLY under its character limit.
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
# LinkedIn Company Page Copywriting
|
|
@@ -68,6 +69,50 @@ the same "stay under" headroom rule.
|
|
|
68
69
|
- Tone: professional, confident, specific. Avoid empty hype.
|
|
69
70
|
- End the About section with a clear next step (visit site, follow, contact).
|
|
70
71
|
|
|
72
|
+
## Voice & quality standard — write like a unicorn-startup marketer
|
|
73
|
+
|
|
74
|
+
Write every word as if you are a **senior brand + growth marketer who has led
|
|
75
|
+
messaging for category-defining startups** (think the caliber of Stripe, Notion,
|
|
76
|
+
Linear, Ramp, Airbnb). The page should feel sharp, confident, and unmistakably
|
|
77
|
+
human — like a person customers and recruiters *want* to follow. Non-negotiable:
|
|
78
|
+
|
|
79
|
+
**1. Humanized — sounds like a real person, never "AI-generated".**
|
|
80
|
+
- Vary sentence length. Mix short, punchy lines with one longer, rhythmic one.
|
|
81
|
+
- Plain, concrete language a smart 12-year-old could follow. Cut jargon.
|
|
82
|
+
- Write with a point of view and a pulse — confident, not corporate-bland.
|
|
83
|
+
- Read it aloud in your head; if a sentence sounds robotic, rewrite it.
|
|
84
|
+
|
|
85
|
+
**2. Banned "AI tells" — never use these.**
|
|
86
|
+
- Filler: "leverage", "utilize", "seamlessly", "robust", "cutting-edge",
|
|
87
|
+
"state-of-the-art", "empower", "unlock", "elevate", "revolutionize",
|
|
88
|
+
"game-changer", "in today's fast-paced world", "we are committed to",
|
|
89
|
+
"passionate about", "at the intersection of", "solutions provider".
|
|
90
|
+
- Hype with no proof: "best", "#1", "world-class", "industry-leading".
|
|
91
|
+
- Em-dash overuse, robotic three-item lists, and "not only… but also"
|
|
92
|
+
scaffolding. Break the pattern; write like a human, not a template.
|
|
93
|
+
|
|
94
|
+
**3. Attracting & focused — earn the next 5 seconds of attention.**
|
|
95
|
+
- The first ~150 chars of About are the hook shown before "see more" — make them
|
|
96
|
+
a magnetic, standalone value statement, not a throat-clearing intro.
|
|
97
|
+
- One core idea per section. Specific > vague every time: real numbers, named
|
|
98
|
+
customers, concrete outcomes. "Helps 12,000 founders raise faster" beats
|
|
99
|
+
"provides solutions for startups".
|
|
100
|
+
- Lead with the reader's problem and the outcome, then how you deliver it.
|
|
101
|
+
- Active voice, present tense, strong verbs. Subject does the thing.
|
|
102
|
+
|
|
103
|
+
**4. LinkedIn lens (humans + search).**
|
|
104
|
+
- Weave the terms customers/recruiters/partners actually search — naturally,
|
|
105
|
+
never stuffed. Keywords serve the sentence, not the other way around.
|
|
106
|
+
- Tagline = benefit- or category-led with a real hook, not a hollow slogan.
|
|
107
|
+
- Close About with one clear next step (follow, visit site, get in touch).
|
|
108
|
+
|
|
109
|
+
**5. Final humanization pass (always run before saving).**
|
|
110
|
+
- Remove every banned word above; replace with plain, specific language.
|
|
111
|
+
- Delete adjectives that aren't earned by a fact.
|
|
112
|
+
- Tighten: if a word can be cut without losing meaning, cut it.
|
|
113
|
+
- Ensure no two consecutive sentences share the same shape or opener.
|
|
114
|
+
- Confirm it reads like one confident human voice end to end.
|
|
115
|
+
|
|
71
116
|
## Required .txt output template
|
|
72
117
|
|
|
73
118
|
Write the file using exactly this structure. Put the live character count in
|
|
@@ -118,3 +163,5 @@ Generated: <YYYY-MM-DD>
|
|
|
118
163
|
3. Specialties: ≤ 20 items, each ≤ 28 chars.
|
|
119
164
|
4. About section's first ~150 chars work as a standalone preview hook.
|
|
120
165
|
5. Confirm counts programmatically (e.g. `awk`/`wc -m` per field) — do not trust eyeballing.
|
|
166
|
+
6. Humanization pass done: zero banned "AI tells", varied sentence shapes, reads
|
|
167
|
+
like one confident human marketer wrote it, and every claim is specific.
|