@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 +1 -1
- package/src/cli.mjs +223 -93
- package/templates/AGENTS.md +3 -1
- package/templates/CLAUDE.md +6 -1
package/package.json
CHANGED
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
196
|
-
name: "
|
|
197
|
-
check: () => commandExists("
|
|
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: "
|
|
222
|
-
debian: "
|
|
223
|
-
fedora: "
|
|
224
|
-
arch: "
|
|
225
|
-
windows: "
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
|
338
|
-
const
|
|
339
|
-
const
|
|
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
|
|
536
|
-
let
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
"
|
|
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
|
-
|
|
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
|
|
581
|
-
|
|
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
|
-
|
|
587
|
-
-
|
|
588
|
-
-
|
|
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
|
-
|
|
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
|
|
598
|
-
|
|
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
|
-
|
|
604
|
-
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
|
|
609
|
-
|
|
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();
|
package/templates/AGENTS.md
CHANGED
|
@@ -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-
|
|
120
|
+
- `test-driven-development`, `frontend-design`, `commit-commands`, `pr-review-toolkit`, `code-simplifier`, `agent-browser`, `find-skills`, `supabase-postgres-best-practices`
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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
|
|
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
|