@acmeacmeio/setup-sh 0.2.6 → 0.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acmeacmeio/setup-sh",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Bootstrap a multi-agent workspace (Claude Code, Codex, Cursor) with team standards, hooks, and skills",
5
5
  "author": "ACMEACMEIO",
6
6
  "license": "MIT",
package/src/cli.mjs CHANGED
@@ -110,6 +110,23 @@ function exec(cmd, options = {}) {
110
110
  // ============================================================================
111
111
  // Interactive Prompts
112
112
  // ============================================================================
113
+ function renderMultiSelect(question, options, cursor, selected) {
114
+ const lines = [`\n${question} ${colors.dim}(space=toggle, enter=confirm)${colors.reset}`];
115
+ for (let i = 0; i < options.length; i++) {
116
+ const isSelected = selected.has(i);
117
+ const isCursor = i === cursor;
118
+ const pointer = isCursor ? `${colors.cyan}>${colors.reset}` : " ";
119
+ const checkbox = isSelected
120
+ ? `${colors.green}[x]${colors.reset}`
121
+ : `${colors.dim}[ ]${colors.reset}`;
122
+ const label = isCursor
123
+ ? `${colors.bold}${options[i].label}${colors.reset}`
124
+ : options[i].label;
125
+ lines.push(` ${pointer} ${checkbox} ${label}`);
126
+ }
127
+ return lines.join("\n");
128
+ }
129
+
113
130
  function createPrompt(autoYes = false) {
114
131
  const rl = createInterface({
115
132
  input: process.stdin,
@@ -133,23 +150,134 @@ function createPrompt(autoYes = false) {
133
150
  if (answer === "") return defaultYes;
134
151
  return answer.toLowerCase() === "y";
135
152
  },
136
- select: async (question, options) => {
137
- console.log(`\n${question}`);
138
- options.forEach((opt, i) => {
139
- console.log(
140
- ` ${colors.cyan}${i + 1}${colors.reset}) ${opt.label}${opt.default ? ` ${colors.dim}(default)${colors.reset}` : ""}`,
153
+ /**
154
+ * Interactive multi-select with arrow keys.
155
+ * Returns an array of selected values.
156
+ */
157
+ multiSelect: (question, options) =>
158
+ new Promise((resolve) => {
159
+ // Pause readline so we can use raw mode
160
+ rl.pause();
161
+
162
+ let cursor = 0;
163
+ const selected = new Set(
164
+ options
165
+ .map((opt, i) => (opt.default ? i : -1))
166
+ .filter((i) => i >= 0),
141
167
  );
142
- });
143
- const answer = await new Promise((resolve) => {
144
- rl.question(`\nSelect [1-${options.length}]: `, resolve);
145
- });
146
- const idx = parseInt(answer, 10) - 1;
147
- if (isNaN(idx) || idx < 0 || idx >= options.length) {
148
- const defaultOpt = options.find((o) => o.default);
149
- return defaultOpt ? defaultOpt.value : options[0].value;
150
- }
151
- return options[idx].value;
152
- },
168
+
169
+ // Render initial state
170
+ let output = renderMultiSelect(question, options, cursor, selected);
171
+ process.stdout.write(output);
172
+
173
+ const stdin = process.stdin;
174
+ stdin.setRawMode(true);
175
+ stdin.resume();
176
+ stdin.setEncoding("utf8");
177
+
178
+ // Line count minus 1 = how many lines to move up from the last line
179
+ const linesToMoveUp = output.split("\n").length - 1;
180
+
181
+ function redraw() {
182
+ // Move cursor up to the start, then clear to end of screen
183
+ process.stdout.write(`\x1b[${linesToMoveUp}A\x1b[0J`);
184
+ output = renderMultiSelect(question, options, cursor, selected);
185
+ process.stdout.write(output);
186
+ }
187
+
188
+ function cleanup() {
189
+ stdin.setRawMode(false);
190
+ stdin.removeListener("data", onKey);
191
+ rl.resume();
192
+ }
193
+
194
+ // Buffer for multi-byte escape sequences (e.g. arrow keys: \x1b [ A)
195
+ let escBuf = "";
196
+
197
+ function onKey(chunk) {
198
+ for (const ch of chunk) {
199
+ // If we're accumulating an escape sequence
200
+ if (escBuf.length > 0) {
201
+ escBuf += ch;
202
+ // Second byte must be '['
203
+ if (escBuf.length === 2 && ch !== "[") {
204
+ escBuf = "";
205
+ continue;
206
+ }
207
+ // Third byte completes the sequence
208
+ if (escBuf.length === 3) {
209
+ const seq = escBuf;
210
+ escBuf = "";
211
+ if (seq === "\x1b[A") {
212
+ cursor = (cursor - 1 + options.length) % options.length;
213
+ redraw();
214
+ } else if (seq === "\x1b[B") {
215
+ cursor = (cursor + 1) % options.length;
216
+ redraw();
217
+ }
218
+ }
219
+ continue;
220
+ }
221
+
222
+ // Start of escape sequence
223
+ if (ch === "\x1b") {
224
+ escBuf = ch;
225
+ continue;
226
+ }
227
+
228
+ // Ctrl+C
229
+ if (ch === "\x03") {
230
+ cleanup();
231
+ process.exit(0);
232
+ }
233
+
234
+ // Enter
235
+ if (ch === "\r" || ch === "\n") {
236
+ cleanup();
237
+ redraw();
238
+ process.stdout.write("\n");
239
+ const values = [...selected].map((i) => options[i].value);
240
+ resolve(values.length > 0 ? values : [options[0].value]);
241
+ return;
242
+ }
243
+
244
+ // Space - toggle
245
+ if (ch === " ") {
246
+ if (selected.has(cursor)) {
247
+ selected.delete(cursor);
248
+ } else {
249
+ selected.add(cursor);
250
+ }
251
+ redraw();
252
+ continue;
253
+ }
254
+
255
+ // vim-style navigation
256
+ if (ch === "k") {
257
+ cursor = (cursor - 1 + options.length) % options.length;
258
+ redraw();
259
+ continue;
260
+ }
261
+ if (ch === "j") {
262
+ cursor = (cursor + 1) % options.length;
263
+ redraw();
264
+ continue;
265
+ }
266
+
267
+ // 'a' to toggle all
268
+ if (ch === "a") {
269
+ if (selected.size === options.length) {
270
+ selected.clear();
271
+ } else {
272
+ for (let i = 0; i < options.length; i++) selected.add(i);
273
+ }
274
+ redraw();
275
+ }
276
+ }
277
+ }
278
+
279
+ stdin.on("data", onKey);
280
+ }),
153
281
  close: () => rl.close(),
154
282
  };
155
283
  }
@@ -192,37 +320,15 @@ const TOOLS = {
192
320
  windows: "npm install -g pnpm",
193
321
  },
194
322
  },
195
- python: {
196
- name: "Python 3.11+",
197
- check: () => commandExists("python3.11") || commandExists("python3"),
198
- install: {
199
- macos: "brew install python@3.11",
200
- debian: "sudo apt install -y python3.11 python3.11-venv",
201
- fedora: "sudo dnf install -y python3.11",
202
- arch: "sudo pacman -S --noconfirm python",
203
- windows: "winget install Python.Python.3.11",
204
- },
205
- },
206
- uv: {
207
- name: "uv (Python package manager)",
208
- check: () => commandExists("uv"),
209
- install: {
210
- macos: "curl -LsSf https://astral.sh/uv/install.sh | sh",
211
- debian: "curl -LsSf https://astral.sh/uv/install.sh | sh",
212
- fedora: "curl -LsSf https://astral.sh/uv/install.sh | sh",
213
- arch: "curl -LsSf https://astral.sh/uv/install.sh | sh",
214
- windows: 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"',
215
- },
216
- },
217
- "browser-use": {
218
- name: "browser-use (Browser automation)",
219
- check: () => commandExists("browser-use"),
323
+ "agent-browser": {
324
+ name: "agent-browser (Browser automation)",
325
+ check: () => commandExists("agent-browser"),
220
326
  install: {
221
- macos: "uv tool install browser-use && uvx browser-use install",
222
- debian: "uv tool install browser-use && uvx browser-use install",
223
- fedora: "uv tool install browser-use && uvx browser-use install",
224
- arch: "uv tool install browser-use && uvx browser-use install",
225
- windows: "uv tool install browser-use && uvx browser-use install",
327
+ macos: "npm install -g agent-browser && agent-browser install",
328
+ debian: "npm install -g agent-browser && agent-browser install --with-deps",
329
+ fedora: "npm install -g agent-browser && agent-browser install --with-deps",
330
+ arch: "npm install -g agent-browser && agent-browser install --with-deps",
331
+ windows: "npm install -g agent-browser && agent-browser install",
226
332
  },
227
333
  },
228
334
  };
@@ -282,24 +388,26 @@ const SKILL_SOURCES = [
282
388
  // Official Anthropic skills
283
389
  { repo: "anthropics/anthropic-skills", skills: ["frontend-design"] },
284
390
  // Browser automation
285
- { repo: "browser-use/browser-use", skills: ["browser-use"] },
391
+ { repo: "vercel-labs/agent-browser", skills: ["agent-browser"], cmd: "npx skills add https://github.com/vercel-labs/agent-browser --skill agent-browser" },
392
+ // Skill discovery
393
+ { repo: "vercel-labs/skills", skills: ["find-skills"], cmd: "npx skills add https://github.com/vercel-labs/skills --skill find-skills" },
286
394
  // TDD workflow
287
395
  { repo: "obra/superpowers", skills: ["test-driven-development"] },
396
+ // Vercel React best practices
397
+ { repo: "vercel-labs/agent-skills", skills: ["vercel-react-best-practices"], cmd: "npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices" },
398
+ // Next.js best practices
399
+ { repo: "vercel-labs/next-skills", skills: ["next-best-practices"], cmd: "npx skills add https://github.com/vercel-labs/next-skills --skill next-best-practices" },
288
400
  // Supabase Postgres
289
401
  { repo: "supabase/agent-skills", skills: ["supabase-postgres-best-practices"] },
290
402
  ];
291
403
 
292
404
  async function installSkills(prompt, agentChoice) {
293
- // Skills installation only applies to Claude Code
294
- // Codex uses the bundled skills from .codex/skills/
295
- // Cursor uses MDC rules bundled in .cursor/rules/
296
- if (agentChoice === "codex") {
297
- log.info("Codex skills are bundled in .codex/skills/ directory");
298
- return;
299
- }
405
+ const agents = normalizeAgents(agentChoice);
300
406
 
301
- if (agentChoice === "cursor") {
302
- log.info("Cursor rules are bundled in .cursor/rules/ directory");
407
+ // Skills installation only applies to Claude Code
408
+ if (!agents.has("claude")) {
409
+ if (agents.has("codex")) log.info("Codex skills are bundled in .codex/skills/ directory");
410
+ if (agents.has("cursor")) log.info("Cursor rules are bundled in .cursor/rules/ directory");
303
411
  return;
304
412
  }
305
413
 
@@ -316,7 +424,8 @@ async function installSkills(prompt, agentChoice) {
316
424
  for (const skill of source.skills) {
317
425
  log.step(`Installing ${skill} from ${source.repo}...`);
318
426
  try {
319
- exec(`npx add-skill ${source.repo} ${skill}`, {
427
+ const installCmd = source.cmd ?? `npx add-skill ${source.repo} ${skill}`;
428
+ exec(installCmd, {
320
429
  silent: true,
321
430
  ignoreError: true,
322
431
  });
@@ -331,12 +440,19 @@ async function installSkills(prompt, agentChoice) {
331
440
  // ============================================================================
332
441
  // Template Files
333
442
  // ============================================================================
443
+ function normalizeAgents(agentChoice) {
444
+ if (Array.isArray(agentChoice)) return new Set(agentChoice);
445
+ if (agentChoice === "all") return new Set(["claude", "codex", "cursor"]);
446
+ return new Set([agentChoice]);
447
+ }
448
+
334
449
  function copyTemplates(targetDir, agentChoice) {
335
450
  log.title("Setting Up Workspace");
336
451
 
337
- const shouldCopyClaude = agentChoice === "claude" || agentChoice === "all";
338
- const shouldCopyCodex = agentChoice === "codex" || agentChoice === "all";
339
- const shouldCopyCursor = agentChoice === "cursor" || agentChoice === "all";
452
+ const agents = normalizeAgents(agentChoice);
453
+ const shouldCopyClaude = agents.has("claude");
454
+ const shouldCopyCodex = agents.has("codex");
455
+ const shouldCopyCursor = agents.has("cursor");
340
456
 
341
457
  // Copy Claude Code templates
342
458
  if (shouldCopyClaude) {
@@ -479,7 +595,7 @@ ${colors.bold}Usage:${colors.reset}
479
595
  npx @acmeacmeio/setup-sh [directory] [options]
480
596
 
481
597
  ${colors.bold}Options:${colors.reset}
482
- --agent=<agent> Select AI coding agent (claude, codex, cursor, all)
598
+ --agent=<agent> Select AI coding agent(s) (claude, codex, cursor, all)
483
599
  -y, --yes Auto-confirm all prompts
484
600
  -h, --help Show this help message
485
601
 
@@ -532,20 +648,27 @@ ${colors.dim}Multi-Agent Workspace Initializer${colors.reset}
532
648
  const prompt = createPrompt(autoYes);
533
649
 
534
650
  try {
535
- // Step 0: Select AI coding agent
536
- let agentChoice = flags.agent;
537
- if (!agentChoice) {
538
- agentChoice = await prompt.select(
539
- "Which AI coding agent are you using?",
651
+ // Step 0: Select AI coding agents
652
+ let agents;
653
+ if (flags.agent) {
654
+ agents = flags.agent === "all"
655
+ ? ["claude", "codex", "cursor"]
656
+ : [flags.agent];
657
+ } else if (autoYes) {
658
+ agents = ["claude"];
659
+ log.info("Auto-selecting: Claude Code (Anthropic)");
660
+ } else {
661
+ agents = await prompt.multiSelect(
662
+ "Which AI coding agents do you use?",
540
663
  [
541
664
  { value: "claude", label: "Claude Code (Anthropic)", default: true },
542
665
  { value: "codex", label: "Codex (OpenAI)" },
543
666
  { value: "cursor", label: "Cursor IDE" },
544
- { value: "all", label: "All agents (generate all configs)" },
545
667
  ],
546
668
  );
547
669
  }
548
- log.info(`Selected agent: ${agentChoice}`);
670
+ const agentChoice = agents.length === 3 ? "all" : agents.length === 1 ? agents[0] : agents;
671
+ log.info(`Selected: ${agents.join(", ")}`);
549
672
 
550
673
  // Step 1: Install system tools
551
674
  await installTools(platform, prompt);
@@ -577,38 +700,45 @@ ${getTips(agentChoice)}
577
700
  }
578
701
 
579
702
  function getNextSteps(agentChoice) {
580
- const claudeSteps = ` - Review and customize ${colors.bold}CLAUDE.md${colors.reset} for your project
581
- - Run ${colors.bold}claude${colors.reset} to start coding with AI assistance`;
582
-
583
- const codexSteps = ` - Review and customize ${colors.bold}AGENTS.md${colors.reset} for your project
584
- - Run ${colors.bold}codex${colors.reset} to start coding with AI assistance`;
703
+ const agents = normalizeAgents(agentChoice);
704
+ const steps = [];
585
705
 
586
- const cursorSteps = ` - Review and customize ${colors.bold}CLAUDE.md${colors.reset} for your project (Cursor reads this)
587
- - Add custom rules in ${colors.bold}.cursor/rules/${colors.reset}
588
- - Open the project in Cursor IDE`;
706
+ if (agents.has("claude")) {
707
+ steps.push(` - Review and customize ${colors.bold}CLAUDE.md${colors.reset} for your project`);
708
+ steps.push(` - Run ${colors.bold}claude${colors.reset} to start coding with AI assistance`);
709
+ }
710
+ if (agents.has("codex")) {
711
+ steps.push(` - Review and customize ${colors.bold}AGENTS.md${colors.reset} for your project`);
712
+ steps.push(` - Run ${colors.bold}codex${colors.reset} to start coding with AI assistance`);
713
+ }
714
+ if (agents.has("cursor")) {
715
+ steps.push(` - Review and customize ${colors.bold}CLAUDE.md${colors.reset} for your project (Cursor reads this)`);
716
+ steps.push(` - Add custom rules in ${colors.bold}.cursor/rules/${colors.reset}`);
717
+ steps.push(` - Open the project in Cursor IDE`);
718
+ }
589
719
 
590
- if (agentChoice === "claude") return claudeSteps;
591
- if (agentChoice === "codex") return codexSteps;
592
- if (agentChoice === "cursor") return cursorSteps;
593
- return claudeSteps + "\n" + codexSteps + "\n" + cursorSteps;
720
+ return steps.join("\n");
594
721
  }
595
722
 
596
723
  function getTips(agentChoice) {
597
- const claudeTips = ` - Create ${colors.bold}CLAUDE.local.md${colors.reset} for personal preferences (gitignored)
598
- - Edit ${colors.bold}.claude/settings.local.json${colors.reset} for local overrides`;
599
-
600
- const codexTips = ` - Create ${colors.bold}AGENTS.local.md${colors.reset} for personal preferences (gitignored)
601
- - Edit ${colors.bold}.codex/config.local.toml${colors.reset} for local overrides`;
724
+ const agents = normalizeAgents(agentChoice);
725
+ const tips = [];
602
726
 
603
- const cursorTips = ` - Add ${colors.bold}alwaysApply: true${colors.reset} to rules that should always be active
604
- - Use ${colors.bold}globs${colors.reset} in rules for file-specific context`;
605
-
606
- const sharedTips = ` - Run ${colors.bold}gh auth login${colors.reset} if GitHub CLI needs authentication`;
727
+ if (agents.has("claude")) {
728
+ tips.push(` - Create ${colors.bold}CLAUDE.local.md${colors.reset} for personal preferences (gitignored)`);
729
+ tips.push(` - Edit ${colors.bold}.claude/settings.local.json${colors.reset} for local overrides`);
730
+ }
731
+ if (agents.has("codex")) {
732
+ tips.push(` - Create ${colors.bold}AGENTS.local.md${colors.reset} for personal preferences (gitignored)`);
733
+ tips.push(` - Edit ${colors.bold}.codex/config.local.toml${colors.reset} for local overrides`);
734
+ }
735
+ if (agents.has("cursor")) {
736
+ tips.push(` - Add ${colors.bold}alwaysApply: true${colors.reset} to rules that should always be active`);
737
+ tips.push(` - Use ${colors.bold}globs${colors.reset} in rules for file-specific context`);
738
+ }
607
739
 
608
- if (agentChoice === "claude") return claudeTips + "\n" + sharedTips;
609
- if (agentChoice === "codex") return codexTips + "\n" + sharedTips;
610
- if (agentChoice === "cursor") return cursorTips + "\n" + sharedTips;
611
- return claudeTips + "\n" + codexTips + "\n" + cursorTips + "\n" + sharedTips;
740
+ tips.push(` - Run ${colors.bold}gh auth login${colors.reset} if GitHub CLI needs authentication`);
741
+ return tips.join("\n");
612
742
  }
613
743
 
614
744
  main();
@@ -10,6 +10,8 @@ All new code follows Test-Driven Development:
10
10
  2. **Green**: Write minimal code to pass
11
11
  3. **Refactor**: Clean up while tests stay green
12
12
 
13
+ **IMPORTANT: NEVER write implementation code before writing a failing test for it. The test file MUST exist and fail before any production code is written or modified. This is a hard requirement, not a guideline. If you are about to create or modify a source file, stop and write/update the test first.**
14
+
13
15
  TDD is enforced through the `test-driven-development` skill (installed from `obra/superpowers`). When writing new code, always use this skill to ensure proper Red-Green-Refactor workflow.
14
16
 
15
17
  ### TypeScript: Strict Mode
@@ -115,4 +117,4 @@ Use latest Next.js with App Router. Server Components by default, Client Compone
115
117
  - `$security-review` - Security audit checklist
116
118
 
117
119
  Note: Codex uses bundled skills from `.codex/skills/`. The following skills are available to Claude Code users via `npx add-skill`:
118
- - `test-driven-development`, `frontend-design`, `commit-commands`, `pr-review-toolkit`, `code-simplifier`, `browser-use`, `supabase-postgres-best-practices`
120
+ - `test-driven-development`, `frontend-design`, `commit-commands`, `pr-review-toolkit`, `code-simplifier`, `agent-browser`, `find-skills`, `supabase-postgres-best-practices`
@@ -10,6 +10,8 @@ All new code follows Test-Driven Development:
10
10
  2. **Green**: Write minimal code to pass
11
11
  3. **Refactor**: Clean up while tests stay green
12
12
 
13
+ **IMPORTANT: NEVER write implementation code before writing a failing test for it. The test file MUST exist and fail before any production code is written or modified. This is a hard requirement, not a guideline. If you are about to create or modify a source file, stop and write/update the test first.**
14
+
13
15
  TDD is enforced through the `test-driven-development` skill (installed from `obra/superpowers`). When writing new code, always use this skill to ensure proper Red-Green-Refactor workflow.
14
16
 
15
17
  ### TypeScript: Strict Mode
@@ -127,8 +129,11 @@ These are bundled extensions that provide subagents, skills, and commands as coh
127
129
  ### Installed Skills (via npx add-skill)
128
130
  - `test-driven-development` - TDD workflow enforcement (obra/superpowers)
129
131
  - `frontend-design` - Frontend design patterns (anthropics/anthropic-skills)
130
- - `browser-use` - Browser automation (browser-use/browser-use)
132
+ - `agent-browser` - Browser automation (vercel-labs/agent-browser)
131
133
  - `supabase-postgres-best-practices` - Supabase/Postgres patterns (supabase/agent-skills)
134
+ - `vercel-react-best-practices` - React/Next.js performance optimization (vercel-labs/agent-skills)
135
+ - `next-best-practices` - Next.js best practices (vercel-labs/next-skills)
136
+ - `find-skills` - Discover and install skills from the open agent skills ecosystem (vercel-labs/skills)
132
137
 
133
138
  ### Bundled Skills (in .claude/skills/)
134
139
  - `api-design` - REST + Zod API patterns