spirewise 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -26
- package/bin/cli.js +256 -139
- package/install.sh +54 -18
- package/package.json +1 -1
- package/skills/README.md +25 -15
- package/skills/f6s-copywriting/SKILL.md +51 -4
- package/skills/linkedin-copywriting/SKILL.md +52 -5
package/README.md
CHANGED
|
@@ -10,66 +10,77 @@ project** or **globally** for every project.
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
-
No clone needed:
|
|
13
|
+
No clone needed — just run:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx spirewise
|
|
16
|
+
npx spirewise
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
You
|
|
19
|
+
You get a **full-screen interactive picker** with three steps:
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
══════════════════ Step 1/3 · Select skills ══════════════════
|
|
23
|
+
❯ ◉ f6s-copywriting Generate F6S startup/company profile copy…
|
|
24
|
+
◉ linkedin-copywriting Generate LinkedIn Company Page copy…
|
|
25
|
+
|
|
26
|
+
↑/↓ move · space toggle · a all/none · enter confirm · esc cancel
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
Step 1 picks **skills**, step 2 picks **agents** (checkbox lists — move with
|
|
30
|
+
↑/↓, toggle with **space**, **a** for all/none), step 3 picks **scope**
|
|
31
|
+
(Workspace / Global / Both). Press **enter** to confirm each step.
|
|
32
|
+
|
|
33
|
+
### Or drive it with flags (skips the matching step)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
-s, --skills <a,b> skills to install (default: all / pick)
|
|
37
|
+
-a, --agents <a,b> agents to target (default: all / pick) alias: --agent
|
|
38
|
+
-sc, --scope <s> workspace | global | both
|
|
39
|
+
--workspace | --global | --both scope shortcuts
|
|
40
|
+
```
|
|
29
41
|
|
|
30
42
|
```bash
|
|
31
|
-
npx spirewise
|
|
32
|
-
npx spirewise
|
|
33
|
-
npx spirewise
|
|
43
|
+
npx spirewise -sc both # pick skills+agents, scope both
|
|
44
|
+
npx spirewise -s f6s-copywriting -a claude,cursor -sc workspace
|
|
45
|
+
npx spirewise --global # pick skills+agents, scope global
|
|
34
46
|
```
|
|
35
47
|
|
|
36
48
|
Or install the CLI globally:
|
|
37
49
|
|
|
38
50
|
```bash
|
|
39
51
|
npm install -g spirewise
|
|
40
|
-
spirewise
|
|
52
|
+
spirewise -sc both
|
|
41
53
|
```
|
|
42
54
|
|
|
43
55
|
## What gets installed where
|
|
44
56
|
|
|
45
|
-
| Agent |
|
|
46
|
-
|
|
57
|
+
| Agent | Workspace folder | Global folder | Format |
|
|
58
|
+
|-------|------------------|---------------|--------|
|
|
47
59
|
| Claude Code | `.claude/skills/` | `~/.claude/skills/` | SKILL.md |
|
|
48
60
|
| GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` | SKILL.md |
|
|
49
61
|
| Cursor | `.cursor/rules/` | `~/.cursor/rules/` | `.mdc` rule |
|
|
50
62
|
| Windsurf | `.windsurf/rules/` | `~/.codeium/windsurf/global_rules/` | `.md` rule |
|
|
51
63
|
| Codex CLI | `.codex/skills/` | `~/.codex/skills/` | SKILL.md |
|
|
52
64
|
| Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` | SKILL.md |
|
|
65
|
+
| OpenCode | `.opencode/skills/` | `~/.config/opencode/skills/` | SKILL.md |
|
|
66
|
+
| Cline | `.clinerules/` | `~/.clinerules/` | `.md` rule |
|
|
67
|
+
| Roo Code | `.roo/rules/` | `~/.roo/rules/` | `.md` rule |
|
|
68
|
+
| Kilo Code | `.kilocode/rules/` | `~/.kilocode/rules/` | `.md` rule |
|
|
69
|
+
| Continue | `.continue/rules/` | `~/.continue/rules/` | `.md` rule |
|
|
70
|
+
| Amp | `.amp/rules/` | `~/.config/amp/rules/` | `.md` rule |
|
|
53
71
|
|
|
54
|
-
Rule-based agents
|
|
55
|
-
|
|
72
|
+
Rule-based agents get a single rule file generated from the skill; the rest get
|
|
73
|
+
the full `SKILL.md` folder. (Newer tools' folders are best-effort conventions —
|
|
74
|
+
edit the `AGENTS` registry at the top of `bin/cli.js` if any differ.)
|
|
56
75
|
|
|
57
|
-
##
|
|
76
|
+
## Inspect
|
|
58
77
|
|
|
59
78
|
```bash
|
|
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
79
|
npx spirewise list # available skills
|
|
68
80
|
npx spirewise agents # supported agents + their folders
|
|
81
|
+
npx spirewise --help # all options
|
|
69
82
|
```
|
|
70
83
|
|
|
71
|
-
Run `npx spirewise --help` for all options.
|
|
72
|
-
|
|
73
84
|
## Skills included
|
|
74
85
|
|
|
75
86
|
| Skill | Output file | Purpose |
|
package/bin/cli.js
CHANGED
|
@@ -2,71 +2,84 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* spirewise — install copywriting Agent Skills into
|
|
5
|
+
* spirewise — install copywriting Agent Skills into your AI agents.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Run with no flags for a full-screen interactive picker:
|
|
8
|
+
* npx spirewise (or: npx spirewise install)
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* npx spirewise list
|
|
18
|
-
* npx spirewise agents
|
|
10
|
+
* Or drive everything from flags (skips the matching picker step):
|
|
11
|
+
* -s, --skills <a,b> skills to install (default: all)
|
|
12
|
+
* -a, --agents <a,b> agents to target (default: all) alias: --agent
|
|
13
|
+
* -sc, --scope <s> workspace | global | both
|
|
14
|
+
* --workspace|--global|--both scope shortcuts
|
|
15
|
+
*
|
|
16
|
+
* Other commands:
|
|
17
|
+
* npx spirewise list list skills
|
|
18
|
+
* npx spirewise agents list supported agents + folders
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const fs = require('fs');
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const os = require('os');
|
|
24
|
-
const readline = require('readline');
|
|
25
24
|
|
|
26
25
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
27
26
|
const SKILLS_DIR = path.join(PKG_ROOT, 'skills');
|
|
28
27
|
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
29
28
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const USE_COLOR = !process.env.NO_COLOR;
|
|
30
|
+
const RAW = {
|
|
31
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
32
|
+
blue: '\x1b[1;34m', green: '\x1b[1;32m', yellow: '\x1b[1;33m',
|
|
33
|
+
red: '\x1b[1;31m', cyan: '\x1b[1;36m', magenta: '\x1b[1;35m',
|
|
33
34
|
};
|
|
35
|
+
const paint = (code, s) => (USE_COLOR ? `${code}${s}${RAW.reset}` : s);
|
|
36
|
+
const c = USE_COLOR ? RAW
|
|
37
|
+
: { reset: '', bold: '', dim: '', blue: '', green: '', yellow: '', red: '', cyan: '', magenta: '' };
|
|
38
|
+
|
|
34
39
|
const info = (m) => console.log(`${c.blue}==>${c.reset} ${m}`);
|
|
35
40
|
const ok = (m) => console.log(`${c.green} ok${c.reset} ${m}`);
|
|
36
41
|
const warn = (m) => console.error(`${c.yellow} !${c.reset} ${m}`);
|
|
37
42
|
const die = (m) => { console.error(`${c.red}err${c.reset} ${m}`); process.exit(1); };
|
|
38
43
|
|
|
44
|
+
let PKG_VERSION = '';
|
|
45
|
+
try { PKG_VERSION = require(path.join(PKG_ROOT, 'package.json')).version || ''; } catch (_) {}
|
|
46
|
+
|
|
47
|
+
const BANNER = [
|
|
48
|
+
' ███████╗██████╗ ██╗██████╗ ███████╗██╗ ██╗██╗███████╗███████╗',
|
|
49
|
+
' ██╔════╝██╔══██╗██║██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝',
|
|
50
|
+
' ███████╗██████╔╝██║██████╔╝█████╗ ██║ █╗ ██║██║███████╗█████╗ ',
|
|
51
|
+
' ╚════██║██╔═══╝ ██║██╔══██╗██╔══╝ ██║███╗██║██║╚════██║██╔══╝ ',
|
|
52
|
+
' ███████║██║ ██║██║ ██║███████╗╚███╔███╔╝██║███████║███████╗',
|
|
53
|
+
' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function banner() {
|
|
57
|
+
console.log('');
|
|
58
|
+
for (const line of BANNER) console.log(paint(RAW.cyan, line));
|
|
59
|
+
console.log(paint(RAW.magenta, ` Agent Skills · F6S & LinkedIn copywriting${PKG_VERSION ? ' · v' + PKG_VERSION : ''}`));
|
|
60
|
+
console.log('');
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
/**
|
|
40
64
|
* Agent registry. `format`:
|
|
41
65
|
* - 'skill': copy the skill folder verbatim (SKILL.md + any files)
|
|
42
66
|
* - 'rule' : write one rule file derived from SKILL.md (ext required)
|
|
43
|
-
* `project`
|
|
67
|
+
* `project` resolves from cwd; `global` uses ~ for the home directory.
|
|
68
|
+
* Newer tools' folders are best-effort conventions — edit freely.
|
|
44
69
|
*/
|
|
45
70
|
const AGENTS = {
|
|
46
|
-
claude:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
},
|
|
71
|
+
claude: { label: 'Claude Code', format: 'skill', project: '.claude/skills', global: '~/.claude/skills' },
|
|
72
|
+
copilot: { label: 'GitHub Copilot', format: 'skill', project: '.github/skills', global: '~/.copilot/skills' },
|
|
73
|
+
cursor: { label: 'Cursor', format: 'rule', ext: '.mdc', project: '.cursor/rules', global: '~/.cursor/rules' },
|
|
74
|
+
windsurf: { label: 'Windsurf', format: 'rule', ext: '.md', project: '.windsurf/rules', global: '~/.codeium/windsurf/global_rules' },
|
|
75
|
+
codex: { label: 'Codex CLI', format: 'skill', project: '.codex/skills', global: '~/.codex/skills' },
|
|
76
|
+
gemini: { label: 'Gemini CLI', format: 'skill', project: '.gemini/skills', global: '~/.gemini/skills' },
|
|
77
|
+
opencode: { label: 'OpenCode', format: 'skill', project: '.opencode/skills', global: '~/.config/opencode/skills' },
|
|
78
|
+
cline: { label: 'Cline', format: 'rule', ext: '.md', project: '.clinerules', global: '~/.clinerules' },
|
|
79
|
+
roo: { label: 'Roo Code', format: 'rule', ext: '.md', project: '.roo/rules', global: '~/.roo/rules' },
|
|
80
|
+
kilocode: { label: 'Kilo Code', format: 'rule', ext: '.md', project: '.kilocode/rules', global: '~/.kilocode/rules' },
|
|
81
|
+
continue: { label: 'Continue', format: 'rule', ext: '.md', project: '.continue/rules', global: '~/.continue/rules' },
|
|
82
|
+
amp: { label: 'Amp', format: 'rule', ext: '.md', project: '.amp/rules', global: '~/.config/amp/rules' },
|
|
70
83
|
};
|
|
71
84
|
|
|
72
85
|
function expandHome(p) {
|
|
@@ -74,26 +87,21 @@ function expandHome(p) {
|
|
|
74
87
|
if (p.startsWith('~/')) return path.join(HOME, p.slice(2));
|
|
75
88
|
return p;
|
|
76
89
|
}
|
|
77
|
-
|
|
78
90
|
function resolveTarget(agent, scope) {
|
|
79
91
|
const rel = scope === 'project' ? agent.project : agent.global;
|
|
80
|
-
return scope === 'project'
|
|
81
|
-
? path.resolve(process.cwd(), rel)
|
|
82
|
-
: path.resolve(expandHome(rel));
|
|
92
|
+
return scope === 'project' ? path.resolve(process.cwd(), rel) : path.resolve(expandHome(rel));
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
function availableSkills() {
|
|
86
96
|
if (!fs.existsSync(SKILLS_DIR)) return [];
|
|
87
97
|
return fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
88
98
|
.filter((d) => d.isDirectory() && fs.existsSync(path.join(SKILLS_DIR, d.name, 'SKILL.md')))
|
|
89
|
-
.map((d) => d.name)
|
|
90
|
-
.sort();
|
|
99
|
+
.map((d) => d.name).sort();
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
function readSkill(name) {
|
|
94
103
|
const raw = fs.readFileSync(path.join(SKILLS_DIR, name, 'SKILL.md'), 'utf8');
|
|
95
|
-
let description = name;
|
|
96
|
-
let body = raw;
|
|
104
|
+
let description = name, body = raw;
|
|
97
105
|
const m = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
98
106
|
if (m) {
|
|
99
107
|
body = m[2];
|
|
@@ -102,14 +110,19 @@ function readSkill(name) {
|
|
|
102
110
|
}
|
|
103
111
|
return { description, body: body.replace(/^\s+/, '') };
|
|
104
112
|
}
|
|
113
|
+
function skillHint(name) {
|
|
114
|
+
try {
|
|
115
|
+
const d = readSkill(name).description;
|
|
116
|
+
const first = d.split('. ')[0];
|
|
117
|
+
return first.length > 52 ? first.slice(0, 49) + '…' : first;
|
|
118
|
+
} catch (_) { return ''; }
|
|
119
|
+
}
|
|
105
120
|
|
|
106
121
|
function copyDir(src, dest) {
|
|
107
122
|
fs.mkdirSync(dest, { recursive: true });
|
|
108
123
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
109
|
-
const s = path.join(src, entry.name);
|
|
110
|
-
|
|
111
|
-
if (entry.isDirectory()) copyDir(s, d);
|
|
112
|
-
else fs.copyFileSync(s, d);
|
|
124
|
+
const s = path.join(src, entry.name), d = path.join(dest, entry.name);
|
|
125
|
+
if (entry.isDirectory()) copyDir(s, d); else fs.copyFileSync(s, d);
|
|
113
126
|
}
|
|
114
127
|
}
|
|
115
128
|
|
|
@@ -120,87 +133,147 @@ function installToAgent(agentKey, agent, scope, skills) {
|
|
|
120
133
|
if (agent.format === 'skill') {
|
|
121
134
|
const dest = path.join(targetDir, skill);
|
|
122
135
|
copyDir(path.join(SKILLS_DIR, skill), dest);
|
|
123
|
-
|
|
136
|
+
console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope === 'project' ? 'workspace' : scope})${c.reset} ${skill} ${c.dim}→ ${dest}${c.reset}`);
|
|
124
137
|
} else {
|
|
125
138
|
const { description, body } = readSkill(skill);
|
|
126
139
|
const front = `---\ndescription: ${description}\nalwaysApply: false\n---\n\n`;
|
|
127
140
|
const file = path.join(targetDir, skill + (agent.ext || '.md'));
|
|
128
141
|
fs.writeFileSync(file, front + body);
|
|
129
|
-
|
|
142
|
+
console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope === 'project' ? 'workspace' : scope})${c.reset} ${skill} ${c.dim}→ ${file}${c.reset}`);
|
|
130
143
|
}
|
|
131
144
|
}
|
|
132
145
|
}
|
|
133
146
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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)
|
|
147
|
-
|
|
148
|
-
Selection:
|
|
149
|
-
[skill ...] Limit to named skills (default: all)
|
|
150
|
-
--agent <a,b> Limit to named agents (default: all)
|
|
151
|
-
|
|
152
|
-
Other:
|
|
153
|
-
-h, --help Show this help
|
|
147
|
+
function box(lines) {
|
|
148
|
+
const vis = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
149
|
+
const width = Math.max(...lines.map((l) => vis(l).length));
|
|
150
|
+
console.log(paint(RAW.green, ' ┌' + '─'.repeat(width + 2) + '┐'));
|
|
151
|
+
for (const l of lines) console.log(paint(RAW.green, ' │ ') + l + ' '.repeat(width - vis(l).length) + paint(RAW.green, ' │'));
|
|
152
|
+
console.log(paint(RAW.green, ' └' + '─'.repeat(width + 2) + '┘'));
|
|
153
|
+
}
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// --- Interactive full-width selector ---------------------------------------
|
|
156
|
+
function interactiveSelect({ title, subtitle, items, multi = true, preselected = [] }) {
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
const stdin = process.stdin, stdout = process.stdout;
|
|
159
|
+
if (!stdin.isTTY) { resolve(null); return; }
|
|
160
|
+
|
|
161
|
+
let index = 0;
|
|
162
|
+
const selected = new Set();
|
|
163
|
+
if (multi) items.forEach((it, i) => { if (preselected.includes(it.value)) selected.add(i); });
|
|
164
|
+
else { const pi = items.findIndex((it) => preselected.includes(it.value)); if (pi >= 0) index = pi; }
|
|
165
|
+
|
|
166
|
+
const cols = () => stdout.columns || 80;
|
|
167
|
+
let lastLines = 0;
|
|
168
|
+
|
|
169
|
+
const bar = (text) => {
|
|
170
|
+
const w = cols(), label = ` ${text} `;
|
|
171
|
+
const side = Math.max(0, w - label.length), left = Math.floor(side / 2);
|
|
172
|
+
return paint(RAW.cyan, '═'.repeat(left) + label + '═'.repeat(side - left));
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
function render() {
|
|
176
|
+
const lines = ['', bar(title)];
|
|
177
|
+
if (subtitle) lines.push(paint(RAW.dim, ' ' + subtitle));
|
|
178
|
+
lines.push('');
|
|
179
|
+
items.forEach((it, i) => {
|
|
180
|
+
const cur = i === index ? paint(RAW.cyan, '❯') : ' ';
|
|
181
|
+
const mark = multi ? (selected.has(i) ? paint(RAW.green, '◉') : paint(RAW.dim, '◯'))
|
|
182
|
+
: (i === index ? paint(RAW.green, '◉') : paint(RAW.dim, '◯'));
|
|
183
|
+
const label = i === index ? paint(RAW.bold, it.label) : it.label;
|
|
184
|
+
const hint = it.hint ? paint(RAW.dim, ' ' + it.hint) : '';
|
|
185
|
+
lines.push(` ${cur} ${mark} ${label}${hint}`);
|
|
186
|
+
});
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push(paint(RAW.dim, ' ' + (multi
|
|
189
|
+
? '↑/↓ move · space toggle · a all/none · enter confirm · esc cancel'
|
|
190
|
+
: '↑/↓ move · enter confirm · esc cancel')));
|
|
191
|
+
if (lastLines > 0) stdout.write(`\x1b[${lastLines}A`);
|
|
192
|
+
stdout.write('\x1b[0J' + lines.join('\n') + '\n');
|
|
193
|
+
lastLines = lines.length;
|
|
194
|
+
}
|
|
157
195
|
|
|
158
|
-
|
|
159
|
-
|
|
196
|
+
function cleanup() {
|
|
197
|
+
try { stdin.setRawMode(false); } catch (_) {}
|
|
198
|
+
stdin.pause();
|
|
199
|
+
stdin.removeListener('data', onData);
|
|
200
|
+
}
|
|
201
|
+
const finish = (r) => { cleanup(); resolve(r); };
|
|
202
|
+
|
|
203
|
+
function onData(s) {
|
|
204
|
+
if (s === '\x03' || s === '\x1b' || s === 'q') return finish(null); // ctrl-c / esc / q
|
|
205
|
+
if (s === '\r' || s === '\n') {
|
|
206
|
+
return finish(multi ? items.filter((_, i) => selected.has(i)).map((it) => it.value) : items[index].value);
|
|
207
|
+
}
|
|
208
|
+
if (s === '\x1b[A' || s === 'k') { index = (index - 1 + items.length) % items.length; return render(); }
|
|
209
|
+
if (s === '\x1b[B' || s === 'j') { index = (index + 1) % items.length; return render(); }
|
|
210
|
+
if (multi && s === ' ') { selected.has(index) ? selected.delete(index) : selected.add(index); return render(); }
|
|
211
|
+
if (multi && (s === 'a' || s === 'A')) {
|
|
212
|
+
if (selected.size === items.length) selected.clear(); else items.forEach((_, i) => selected.add(i));
|
|
213
|
+
return render();
|
|
214
|
+
}
|
|
215
|
+
if (!multi && s === ' ') return finish(items[index].value);
|
|
216
|
+
}
|
|
160
217
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
`);
|
|
218
|
+
stdin.setRawMode(true); stdin.resume(); stdin.setEncoding('utf8');
|
|
219
|
+
stdin.on('data', onData);
|
|
220
|
+
render();
|
|
221
|
+
});
|
|
166
222
|
}
|
|
167
223
|
|
|
224
|
+
// --- args -------------------------------------------------------------------
|
|
225
|
+
function splitList(v) { return String(v).split(',').map((s) => s.trim()).filter(Boolean); }
|
|
226
|
+
|
|
168
227
|
function parseArgs(argv) {
|
|
169
|
-
const o = { scope: null, agents: null, skills: [] };
|
|
228
|
+
const o = { scope: null, agents: null, skills: null, positional: [] };
|
|
229
|
+
const norm = (s) => (s === 'project' || s === 'workspace' || s === 'local') ? 'project' : s;
|
|
170
230
|
for (let i = 0; i < argv.length; i++) {
|
|
171
|
-
|
|
172
|
-
if (a
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
231
|
+
let a = argv[i], val = null;
|
|
232
|
+
if (a.startsWith('--') && a.includes('=')) { const idx = a.indexOf('='); val = a.slice(idx + 1); a = a.slice(0, idx); }
|
|
233
|
+
const next = () => (val !== null ? val : argv[++i]);
|
|
234
|
+
switch (a) {
|
|
235
|
+
case '-s': case '--skills': o.skills = splitList(next() || die('--skills needs a value')); break;
|
|
236
|
+
case '-a': case '--agents': case '--agent': o.agents = splitList(next() || die('--agents needs a value')); break;
|
|
237
|
+
case '-sc': case '--scope': o.scope = norm((next() || die('--scope needs a value')).toLowerCase()); break;
|
|
238
|
+
case '--workspace': case '--project': case '--local': o.scope = 'project'; break;
|
|
239
|
+
case '--global': o.scope = 'global'; break;
|
|
240
|
+
case '--both': o.scope = 'both'; break;
|
|
241
|
+
case '-h': case '--help': usage(); process.exit(0); break;
|
|
242
|
+
default:
|
|
243
|
+
if (a.startsWith('-')) die(`Unknown option: ${a}`);
|
|
244
|
+
o.positional.push(a);
|
|
245
|
+
}
|
|
180
246
|
}
|
|
247
|
+
if (!o.skills && o.positional.length) o.skills = o.positional;
|
|
181
248
|
return o;
|
|
182
249
|
}
|
|
183
250
|
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
187
|
-
rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); });
|
|
188
|
-
});
|
|
189
|
-
}
|
|
251
|
+
function usage() {
|
|
252
|
+
console.log(`spirewise — install copywriting Agent Skills into your AI agents
|
|
190
253
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
Usage:
|
|
255
|
+
spirewise [install] [options] run interactive picker for anything not set
|
|
256
|
+
spirewise list list available skills
|
|
257
|
+
spirewise agents list supported agents + folders
|
|
258
|
+
|
|
259
|
+
Options:
|
|
260
|
+
-s, --skills <a,b> skills to install (default: all / pick)
|
|
261
|
+
-a, --agents <a,b> agents to target (default: all / pick) alias: --agent
|
|
262
|
+
-sc, --scope <s> workspace | global | both (default: pick)
|
|
263
|
+
--workspace | --global | --both scope shortcuts
|
|
264
|
+
-h, --help
|
|
265
|
+
|
|
266
|
+
Agents:
|
|
267
|
+
${Object.entries(AGENTS).map(([k, a]) => ` - ${k} (${a.label})`).join('\n')}
|
|
268
|
+
|
|
269
|
+
Skills:
|
|
270
|
+
${availableSkills().map((s) => ' - ' + s).join('\n') || ' (none found)'}
|
|
271
|
+
|
|
272
|
+
Examples:
|
|
273
|
+
npx spirewise # full interactive picker
|
|
274
|
+
npx spirewise -sc both # pick skills+agents, scope=both
|
|
275
|
+
npx spirewise -s f6s-copywriting -a claude,cursor -sc workspace
|
|
276
|
+
`);
|
|
204
277
|
}
|
|
205
278
|
|
|
206
279
|
async function main() {
|
|
@@ -213,44 +286,88 @@ async function main() {
|
|
|
213
286
|
if (available.length === 0) die('No skills found in package.');
|
|
214
287
|
|
|
215
288
|
if (command === 'list') {
|
|
216
|
-
info('Available skills:');
|
|
217
|
-
available.forEach((s) => console.log(' - ' + s));
|
|
218
|
-
return;
|
|
289
|
+
info('Available skills:'); available.forEach((s) => console.log(' - ' + s)); return;
|
|
219
290
|
}
|
|
220
291
|
if (command === 'agents') {
|
|
221
292
|
info('Supported agents and their folders:');
|
|
222
293
|
for (const [k, a] of Object.entries(AGENTS)) {
|
|
223
|
-
console.log(` ${k} (${a.label}) [${a.format}]`);
|
|
224
|
-
console.log(`
|
|
225
|
-
console.log(` global:
|
|
294
|
+
console.log(` ${paint(RAW.bold, k)} (${a.label}) [${a.format}]`);
|
|
295
|
+
console.log(` workspace: ${a.project}`);
|
|
296
|
+
console.log(` global: ${a.global}`);
|
|
226
297
|
}
|
|
227
298
|
return;
|
|
228
299
|
}
|
|
229
300
|
|
|
230
301
|
const o = parseArgs(argv);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
302
|
+
const tty = process.stdin.isTTY;
|
|
303
|
+
banner();
|
|
304
|
+
|
|
305
|
+
// 1) SKILLS
|
|
306
|
+
let skills = o.skills;
|
|
307
|
+
if (skills) {
|
|
308
|
+
for (const s of skills) if (!available.includes(s)) die(`Unknown skill '${s}'. Run "spirewise list".`);
|
|
309
|
+
} else if (tty) {
|
|
310
|
+
skills = await interactiveSelect({
|
|
311
|
+
title: 'Step 1/3 · Select skills',
|
|
312
|
+
subtitle: 'Copy templates to install into your agents',
|
|
313
|
+
items: available.map((s) => ({ value: s, label: s, hint: skillHint(s) })),
|
|
314
|
+
multi: true, preselected: available,
|
|
315
|
+
});
|
|
316
|
+
if (skills === null) die('Cancelled.');
|
|
317
|
+
if (!skills.length) die('No skills selected.');
|
|
318
|
+
} else { warn('No terminal; defaulting to all skills.'); skills = available.slice(); }
|
|
319
|
+
|
|
320
|
+
// 2) AGENTS
|
|
321
|
+
let agentKeys = o.agents;
|
|
322
|
+
if (agentKeys) {
|
|
323
|
+
for (const k of agentKeys) if (!AGENTS[k]) die(`Unknown agent '${k}'. Run "spirewise agents".`);
|
|
324
|
+
} else if (tty) {
|
|
325
|
+
agentKeys = await interactiveSelect({
|
|
326
|
+
title: 'Step 2/3 · Select agents',
|
|
327
|
+
subtitle: 'Each agent gets the skills in its own folder + format',
|
|
328
|
+
items: Object.entries(AGENTS).map(([k, a]) => ({ value: k, label: a.label, hint: `(${k}) · ${a.format}` })),
|
|
329
|
+
multi: true, preselected: Object.keys(AGENTS),
|
|
330
|
+
});
|
|
331
|
+
if (agentKeys === null) die('Cancelled.');
|
|
332
|
+
if (!agentKeys.length) die('No agents selected.');
|
|
333
|
+
} else { warn('No terminal; defaulting to all agents.'); agentKeys = Object.keys(AGENTS); }
|
|
334
|
+
|
|
335
|
+
// 3) SCOPE
|
|
336
|
+
let scope = o.scope;
|
|
337
|
+
if (!scope && tty) {
|
|
338
|
+
scope = await interactiveSelect({
|
|
339
|
+
title: 'Step 3/3 · Select scope',
|
|
340
|
+
subtitle: 'Where should the skills live?',
|
|
341
|
+
items: [
|
|
342
|
+
{ value: 'project', label: 'Workspace', hint: 'this folder only — the current project' },
|
|
343
|
+
{ value: 'global', label: 'Global', hint: 'your home folders — applies to all projects' },
|
|
344
|
+
{ value: 'both', label: 'Both', hint: 'workspace + global' },
|
|
345
|
+
],
|
|
346
|
+
multi: false, preselected: ['project'],
|
|
347
|
+
});
|
|
348
|
+
if (scope === null) die('Cancelled.');
|
|
349
|
+
} else if (!scope) { warn('No terminal; defaulting scope to "workspace".'); scope = 'project'; }
|
|
242
350
|
if (!['project', 'global', 'both'].includes(scope)) die(`Invalid scope '${scope}'.`);
|
|
243
351
|
const scopes = scope === 'both' ? ['project', 'global'] : [scope];
|
|
244
352
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
353
|
+
// INSTALL
|
|
354
|
+
console.log('');
|
|
355
|
+
info(`Installing ${paint(RAW.bold, String(skills.length))} skill(s) into ${paint(RAW.bold, String(agentKeys.length))} agent(s) · scope ${paint(RAW.bold, scope === 'project' ? 'workspace' : scope)}`);
|
|
356
|
+
let count = 0;
|
|
357
|
+
for (const sc of scopes) for (const k of agentKeys) { installToAgent(k, AGENTS[k], sc, skills); count += skills.length; }
|
|
358
|
+
|
|
359
|
+
console.log('');
|
|
360
|
+
box([
|
|
361
|
+
`Installed ${paint(RAW.bold, String(skills.length))} skill(s) into ${paint(RAW.bold, String(agentKeys.length))} agent(s)`,
|
|
362
|
+
`scope: ${scopes.map((s) => (s === 'project' ? 'workspace' : s)).join(' + ')} · ${count} item(s) written`,
|
|
363
|
+
`next: open your agent and say "write our F6S profile copy"`,
|
|
364
|
+
]);
|
|
365
|
+
console.log('');
|
|
366
|
+
}
|
|
251
367
|
|
|
252
|
-
|
|
253
|
-
|
|
368
|
+
if (require.main === module) {
|
|
369
|
+
main().catch((e) => die(e && e.message ? e.message : String(e)));
|
|
370
|
+
} else {
|
|
371
|
+
module.exports = { interactiveSelect, AGENTS, availableSkills, parseArgs };
|
|
254
372
|
}
|
|
255
373
|
|
|
256
|
-
main().catch((e) => die(e && e.message ? e.message : String(e)));
|
package/install.sh
CHANGED
|
@@ -30,6 +30,12 @@ AGENTS=(
|
|
|
30
30
|
"windsurf|Windsurf|rule|.md|.windsurf/rules|HOME/.codeium/windsurf/global_rules"
|
|
31
31
|
"codex|Codex CLI|skill||.codex/skills|HOME/.codex/skills"
|
|
32
32
|
"gemini|Gemini CLI|skill||.gemini/skills|HOME/.gemini/skills"
|
|
33
|
+
"opencode|OpenCode|skill||.opencode/skills|HOME/.config/opencode/skills"
|
|
34
|
+
"cline|Cline|rule|.md|.clinerules|HOME/.clinerules"
|
|
35
|
+
"roo|Roo Code|rule|.md|.roo/rules|HOME/.roo/rules"
|
|
36
|
+
"kilocode|Kilo Code|rule|.md|.kilocode/rules|HOME/.kilocode/rules"
|
|
37
|
+
"continue|Continue|rule|.md|.continue/rules|HOME/.continue/rules"
|
|
38
|
+
"amp|Amp|rule|.md|.amp/rules|HOME/.config/amp/rules"
|
|
33
39
|
)
|
|
34
40
|
|
|
35
41
|
color() { printf '\033[%sm%s\033[0m' "$1" "$2"; }
|
|
@@ -37,27 +43,41 @@ info() { printf '%s %s\n' "$(color '1;34' '==>')" "$1"; }
|
|
|
37
43
|
ok() { printf '%s %s\n' "$(color '1;32' ' ok')" "$1"; }
|
|
38
44
|
warn() { printf '%s %s\n' "$(color '1;33' ' !')" "$1" >&2; }
|
|
39
45
|
die() { printf '%s %s\n' "$(color '1;31' 'err')" "$1" >&2; exit 1; }
|
|
46
|
+
step() { printf '%s %s %s\n' "$(color '1;36' ' >')" "$(color '1' "Step $1/4")" "$2"; }
|
|
47
|
+
substep() { printf ' %s %s\n' "$(color '1;32' 'v')" "$1"; }
|
|
48
|
+
|
|
49
|
+
banner() {
|
|
50
|
+
printf '\n'
|
|
51
|
+
color '1;36' ' ███████╗██████╗ ██╗██████╗ ███████╗██╗ ██╗██╗███████╗███████╗'; printf '\n'
|
|
52
|
+
color '1;36' ' ██╔════╝██╔══██╗██║██╔══██╗██╔════╝██║ ██║██║██╔════╝██╔════╝'; printf '\n'
|
|
53
|
+
color '1;36' ' ███████╗██████╔╝██║██████╔╝█████╗ ██║ █╗ ██║██║███████╗█████╗ '; printf '\n'
|
|
54
|
+
color '1;36' ' ╚════██║██╔═══╝ ██║██╔══██╗██╔══╝ ██║███╗██║██║╚════██║██╔══╝ '; printf '\n'
|
|
55
|
+
color '1;36' ' ███████║██║ ██║██║ ██║███████╗╚███╔███╔╝██║███████║███████╗'; printf '\n'
|
|
56
|
+
color '1;36' ' ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝╚══════╝╚══════╝'; printf '\n'
|
|
57
|
+
color '1;35' ' Agent Skills · F6S & LinkedIn copywriting'; printf '\n\n'
|
|
58
|
+
}
|
|
40
59
|
|
|
41
60
|
usage() {
|
|
42
61
|
cat <<EOF
|
|
43
62
|
spirewise — install copywriting Agent Skills into all your AI agents.
|
|
44
63
|
|
|
45
64
|
Usage:
|
|
46
|
-
install.sh [scope] [
|
|
65
|
+
install.sh [scope] [-a agents] [-s skills]
|
|
47
66
|
|
|
48
67
|
Scope:
|
|
49
|
-
--workspace
|
|
50
|
-
--
|
|
51
|
-
--
|
|
68
|
+
-sc, --scope <s> workspace | global | both
|
|
69
|
+
--workspace this folder / current project (alias: --project)
|
|
70
|
+
--global global/home folders
|
|
71
|
+
--both both
|
|
52
72
|
(no scope -> you are asked interactively)
|
|
53
73
|
|
|
54
74
|
Selection:
|
|
55
|
-
|
|
56
|
-
--
|
|
57
|
-
--list
|
|
58
|
-
-h, --help
|
|
75
|
+
-s, --skills <a,b> limit to named skills (default: all)
|
|
76
|
+
-a, --agents <a,b> limit to named agents (default: all) alias: --agent
|
|
77
|
+
--list list available skills
|
|
78
|
+
-h, --help show this help
|
|
59
79
|
|
|
60
|
-
Agents: claude copilot cursor windsurf codex gemini
|
|
80
|
+
Agents: claude copilot cursor windsurf codex gemini opencode cline roo kilocode continue amp
|
|
61
81
|
EOF
|
|
62
82
|
}
|
|
63
83
|
|
|
@@ -148,13 +168,13 @@ install_for_agent() {
|
|
|
148
168
|
if [[ "$fmt" == "skill" ]]; then
|
|
149
169
|
mkdir -p "$dir/$s"
|
|
150
170
|
cp -R "$base/$s/." "$dir/$s/"
|
|
151
|
-
|
|
171
|
+
substep "$label ($scope) $s -> $dir/$s"
|
|
152
172
|
else
|
|
153
173
|
local desc body file
|
|
154
174
|
desc="$(skill_description "$base/$s/SKILL.md")"
|
|
155
175
|
file="$dir/$s$ext"
|
|
156
176
|
{ printf -- '---\ndescription: %s\nalwaysApply: false\n---\n\n' "$desc"; skill_body "$base/$s/SKILL.md"; } >"$file"
|
|
157
|
-
|
|
177
|
+
substep "$label ($scope) $s -> $file"
|
|
158
178
|
fi
|
|
159
179
|
done
|
|
160
180
|
}
|
|
@@ -169,7 +189,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
169
189
|
--project|--workspace|--local) SCOPE="project"; shift ;;
|
|
170
190
|
--global) SCOPE="global"; shift ;;
|
|
171
191
|
--both) SCOPE="both"; shift ;;
|
|
172
|
-
|
|
192
|
+
-sc|--scope)
|
|
193
|
+
v="${2:?--scope needs a value}"; shift 2
|
|
194
|
+
case "$v" in workspace|project|local) SCOPE="project";; global) SCOPE="global";; both) SCOPE="both";; *) die "Invalid scope '$v'";; esac ;;
|
|
195
|
+
-a|--agent|--agents) AGENT_FILTER="${2:?--agents needs a value}"; shift 2 ;;
|
|
196
|
+
-s|--skills)
|
|
197
|
+
IFS=',' read -r -a _sk <<<"${2:?--skills needs a value}"; SELECTED+=("${_sk[@]}"); shift 2 ;;
|
|
173
198
|
--list) DO_LIST=true; shift ;;
|
|
174
199
|
-h|--help) usage; exit 0 ;;
|
|
175
200
|
-*) die "Unknown option: $1" ;;
|
|
@@ -187,14 +212,23 @@ if $DO_LIST; then
|
|
|
187
212
|
info "Available skills:"; printf ' - %s\n' "${AVAILABLE[@]}"; exit 0
|
|
188
213
|
fi
|
|
189
214
|
|
|
190
|
-
|
|
215
|
+
banner
|
|
216
|
+
|
|
217
|
+
# Step 1: skills (default all).
|
|
218
|
+
step 1 "Scanning available skills"
|
|
191
219
|
[[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${AVAILABLE[@]}")
|
|
192
220
|
for s in "${SELECTED[@]}"; do
|
|
193
221
|
found=false; for a in "${AVAILABLE[@]}"; do [[ "$s" == "$a" ]] && found=true && break; done
|
|
194
222
|
$found || die "Unknown skill '$s'. Use --list."
|
|
195
223
|
done
|
|
224
|
+
substep "${#SELECTED[@]} skill(s): ${SELECTED[*]}"
|
|
196
225
|
|
|
197
|
-
#
|
|
226
|
+
# Step 2: agents
|
|
227
|
+
step 2 "Selecting target agents"
|
|
228
|
+
if [[ -n "$AGENT_FILTER" ]]; then substep "agents: $AGENT_FILTER"; else substep "agents: all supported"; fi
|
|
229
|
+
|
|
230
|
+
# Step 3: scope — prompt if not given.
|
|
231
|
+
step 3 "Choosing install scope"
|
|
198
232
|
if [[ -z "$SCOPE" ]]; then
|
|
199
233
|
if [[ -t 0 ]]; then
|
|
200
234
|
info "Where should the skills be installed?"
|
|
@@ -209,11 +243,12 @@ if [[ -z "$SCOPE" ]]; then
|
|
|
209
243
|
SCOPE="project"
|
|
210
244
|
fi
|
|
211
245
|
fi
|
|
246
|
+
[[ "$SCOPE" == "project" ]] && substep "scope: workspace" || substep "scope: $SCOPE"
|
|
212
247
|
|
|
213
248
|
SCOPES=("$SCOPE"); [[ "$SCOPE" == "both" ]] && SCOPES=("project" "global")
|
|
214
249
|
|
|
215
|
-
|
|
216
|
-
|
|
250
|
+
# Step 4: install
|
|
251
|
+
step 4 "Installing"
|
|
217
252
|
for sc in "${SCOPES[@]}"; do
|
|
218
253
|
for entry in "${AGENTS[@]}"; do
|
|
219
254
|
IFS='|' read -r key label fmt ext pdir gdir <<<"$entry"
|
|
@@ -225,5 +260,6 @@ for sc in "${SCOPES[@]}"; do
|
|
|
225
260
|
done
|
|
226
261
|
done
|
|
227
262
|
|
|
228
|
-
|
|
229
|
-
|
|
263
|
+
printf '\n'
|
|
264
|
+
ok "Installed ${#SELECTED[@]} skill(s). Next: open your agent and say \"write our F6S profile copy\"."
|
|
265
|
+
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -31,35 +31,45 @@ 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
|
|
35
|
-
|
|
34
|
+
The easiest way is the npm CLI. Run it with no flags for a full-screen
|
|
35
|
+
interactive picker (skills → agents → scope), or pass flags to skip steps:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npx spirewise
|
|
39
|
-
npx spirewise
|
|
40
|
-
npx spirewise
|
|
41
|
-
npx spirewise
|
|
38
|
+
npx spirewise # interactive picker for everything
|
|
39
|
+
npx spirewise -sc both # pick skills+agents, scope = both
|
|
40
|
+
npx spirewise -s f6s-copywriting -a claude,cursor -sc workspace
|
|
41
|
+
npx spirewise list # list skills
|
|
42
|
+
npx spirewise agents # agents + folders
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
Flags:
|
|
45
46
|
|
|
46
|
-
```bash
|
|
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
|
|
51
47
|
```
|
|
48
|
+
-s, --skills <a,b> skills to install (default: all / pick)
|
|
49
|
+
-a, --agents <a,b> agents to target (default: all / pick) alias: --agent
|
|
50
|
+
-sc, --scope <s> workspace | global | both
|
|
51
|
+
--workspace | --global | --both scope shortcuts
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
In the picker: **↑/↓** move, **space** toggle, **a** all/none, **enter**
|
|
55
|
+
confirm, **esc** cancel.
|
|
52
56
|
|
|
53
|
-
Supported agents and their folders (
|
|
57
|
+
Supported agents and their folders (workspace / global):
|
|
54
58
|
|
|
55
|
-
| Agent |
|
|
56
|
-
|
|
59
|
+
| Agent | Workspace | Global | Format |
|
|
60
|
+
|-------|-----------|--------|--------|
|
|
57
61
|
| Claude Code | `.claude/skills/` | `~/.claude/skills/` | SKILL.md |
|
|
58
62
|
| GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` | SKILL.md |
|
|
59
63
|
| Cursor | `.cursor/rules/` | `~/.cursor/rules/` | `.mdc` |
|
|
60
64
|
| Windsurf | `.windsurf/rules/` | `~/.codeium/windsurf/global_rules/` | `.md` |
|
|
61
65
|
| Codex CLI | `.codex/skills/` | `~/.codex/skills/` | SKILL.md |
|
|
62
66
|
| Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` | SKILL.md |
|
|
67
|
+
| OpenCode | `.opencode/skills/` | `~/.config/opencode/skills/` | SKILL.md |
|
|
68
|
+
| Cline | `.clinerules/` | `~/.clinerules/` | `.md` |
|
|
69
|
+
| Roo Code | `.roo/rules/` | `~/.roo/rules/` | `.md` |
|
|
70
|
+
| Kilo Code | `.kilocode/rules/` | `~/.kilocode/rules/` | `.md` |
|
|
71
|
+
| Continue | `.continue/rules/` | `~/.continue/rules/` | `.md` |
|
|
72
|
+
| Amp | `.amp/rules/` | `~/.config/amp/rules/` | `.md` |
|
|
63
73
|
|
|
64
74
|
### No-Node fallback: `install.sh`
|
|
65
75
|
|
|
@@ -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.
|