spirewise 1.0.1 → 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/bin/cli.js CHANGED
@@ -27,15 +27,60 @@ const PKG_ROOT = path.resolve(__dirname, '..');
27
27
  const SKILLS_DIR = path.join(PKG_ROOT, 'skills');
28
28
  const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
29
29
 
30
- const c = {
31
- reset: '\x1b[0m', blue: '\x1b[1;34m', green: '\x1b[1;32m',
32
- yellow: '\x1b[1;33m', red: '\x1b[1;31m', dim: '\x1b[2m',
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',
33
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
+
34
41
  const info = (m) => console.log(`${c.blue}==>${c.reset} ${m}`);
35
42
  const ok = (m) => console.log(`${c.green} ok${c.reset} ${m}`);
36
43
  const warn = (m) => console.error(`${c.yellow} !${c.reset} ${m}`);
37
44
  const die = (m) => { console.error(`${c.red}err${c.reset} ${m}`); process.exit(1); };
38
45
 
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
+
39
84
  /**
40
85
  * Agent registry. `format`:
41
86
  * - 'skill': copy the skill folder verbatim (SKILL.md + any files)
@@ -120,13 +165,13 @@ function installToAgent(agentKey, agent, scope, skills) {
120
165
  if (agent.format === 'skill') {
121
166
  const dest = path.join(targetDir, skill);
122
167
  copyDir(path.join(SKILLS_DIR, skill), dest);
123
- ok(`${agent.label} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}-> ${dest}${c.reset}`);
168
+ console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim} ${dest}${c.reset}`);
124
169
  } else {
125
170
  const { description, body } = readSkill(skill);
126
171
  const front = `---\ndescription: ${description}\nalwaysApply: false\n---\n\n`;
127
172
  const file = path.join(targetDir, skill + (agent.ext || '.md'));
128
173
  fs.writeFileSync(file, front + body);
129
- ok(`${agent.label} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim}-> ${file}${c.reset}`);
174
+ console.log(` ${paint(RAW.green, '✓')} ${paint(RAW.bold, agent.label)} ${c.dim}(${scope})${c.reset} ${skill} ${c.dim} ${file}${c.reset}`);
130
175
  }
131
176
  }
132
177
  }
@@ -229,28 +274,40 @@ async function main() {
229
274
 
230
275
  const o = parseArgs(argv);
231
276
 
277
+ banner();
278
+
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');
232
285
  let agentKeys = Object.keys(AGENTS);
233
286
  if (o.agents) {
234
287
  for (const k of o.agents) if (!AGENTS[k]) die(`Unknown agent '${k}'. Run "spirewise agents".`);
235
288
  agentKeys = o.agents;
236
289
  }
290
+ substep(`${agentKeys.length} agent(s): ${agentKeys.join(', ')}`);
237
291
 
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".`);
240
-
292
+ step(3, 'Choosing install scope');
241
293
  let scope = o.scope || await promptScope();
242
294
  if (!['project', 'global', 'both'].includes(scope)) die(`Invalid scope '${scope}'.`);
243
295
  const scopes = scope === 'both' ? ['project', 'global'] : [scope];
296
+ substep(`scope: ${paint(RAW.bold, scope === 'project' ? 'workspace' : scope)}`);
244
297
 
245
- info(`Installing ${skills.length} skill(s) into ${agentKeys.length} agent(s): ${agentKeys.join(', ')}`);
246
- info(`Scope: ${scope}`);
247
-
298
+ step(4, 'Installing');
299
+ let count = 0;
248
300
  for (const sc of scopes) {
249
- for (const k of agentKeys) installToAgent(k, AGENTS[k], sc, skills);
301
+ for (const k of agentKeys) { installToAgent(k, AGENTS[k], sc, skills); count += skills.length; }
250
302
  }
251
303
 
252
- ok(`Done. ${skills.length} skill(s) -> ${agentKeys.length} agent(s) (${scopes.join(' + ')}).`);
253
- info('Restart your agent or reload skills/rules if needed.');
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('');
254
311
  }
255
312
 
256
313
  main().catch((e) => die(e && e.message ? e.message : String(e)));
package/install.sh CHANGED
@@ -37,6 +37,19 @@ info() { printf '%s %s\n' "$(color '1;34' '==>')" "$1"; }
37
37
  ok() { printf '%s %s\n' "$(color '1;32' ' ok')" "$1"; }
38
38
  warn() { printf '%s %s\n' "$(color '1;33' ' !')" "$1" >&2; }
39
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
+ }
40
53
 
41
54
  usage() {
42
55
  cat <<EOF
@@ -148,13 +161,13 @@ install_for_agent() {
148
161
  if [[ "$fmt" == "skill" ]]; then
149
162
  mkdir -p "$dir/$s"
150
163
  cp -R "$base/$s/." "$dir/$s/"
151
- ok "$label ($scope) $s -> $dir/$s"
164
+ substep "$label ($scope) $s -> $dir/$s"
152
165
  else
153
166
  local desc body file
154
167
  desc="$(skill_description "$base/$s/SKILL.md")"
155
168
  file="$dir/$s$ext"
156
169
  { printf -- '---\ndescription: %s\nalwaysApply: false\n---\n\n' "$desc"; skill_body "$base/$s/SKILL.md"; } >"$file"
157
- ok "$label ($scope) $s -> $file"
170
+ substep "$label ($scope) $s -> $file"
158
171
  fi
159
172
  done
160
173
  }
@@ -187,14 +200,23 @@ if $DO_LIST; then
187
200
  info "Available skills:"; printf ' - %s\n' "${AVAILABLE[@]}"; exit 0
188
201
  fi
189
202
 
190
- # Skills: default all.
203
+ banner
204
+
205
+ # Step 1: skills (default all).
206
+ step 1 "Scanning available skills"
191
207
  [[ ${#SELECTED[@]} -eq 0 ]] && SELECTED=("${AVAILABLE[@]}")
192
208
  for s in "${SELECTED[@]}"; do
193
209
  found=false; for a in "${AVAILABLE[@]}"; do [[ "$s" == "$a" ]] && found=true && break; done
194
210
  $found || die "Unknown skill '$s'. Use --list."
195
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
196
217
 
197
- # Scope: prompt if not given.
218
+ # Step 3: scope — prompt if not given.
219
+ step 3 "Choosing install scope"
198
220
  if [[ -z "$SCOPE" ]]; then
199
221
  if [[ -t 0 ]]; then
200
222
  info "Where should the skills be installed?"
@@ -209,11 +231,12 @@ if [[ -z "$SCOPE" ]]; then
209
231
  SCOPE="project"
210
232
  fi
211
233
  fi
234
+ [[ "$SCOPE" == "project" ]] && substep "scope: workspace" || substep "scope: $SCOPE"
212
235
 
213
236
  SCOPES=("$SCOPE"); [[ "$SCOPE" == "both" ]] && SCOPES=("project" "global")
214
237
 
215
- info "Installing ${#SELECTED[@]} skill(s) into all matching agents. Scope: $SCOPE"
216
-
238
+ # Step 4: install
239
+ step 4 "Installing"
217
240
  for sc in "${SCOPES[@]}"; do
218
241
  for entry in "${AGENTS[@]}"; do
219
242
  IFS='|' read -r key label fmt ext pdir gdir <<<"$entry"
@@ -225,5 +248,6 @@ for sc in "${SCOPES[@]}"; do
225
248
  done
226
249
  done
227
250
 
228
- ok "Done."
229
- info "Restart your agent or reload skills/rules if needed."
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,6 +1,6 @@
1
1
  {
2
2
  "name": "spirewise",
3
- "version": "1.0.1",
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"
@@ -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. Use when the user asks to "write our F6S profile", "create F6S
6
- copy", "fill out F6S page", or prepare content for an accelerator / investor
7
- application on F6S. Produces a single .txt file under an `f6s/` folder in the
8
- project root, with every field kept STRICTLY under its character limit.
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
- Use when the user asks to "write our LinkedIn page", "create LinkedIn company
6
- copy", "fill out the About section", or prepare LinkedIn page content. Produces
7
- a single .txt file under a `linkedin copywriting/` folder in the project root,
8
- with every field kept STRICTLY under its character limit.
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.