Skip to content

Commit 2b35d5a

Browse files
committed
chore: add action skill
1 parent b6b4c17 commit 2b35d5a

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

.claude/skills

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/home/pooya/Code/codeup/skills

skills/codeup-action/SKILL.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
---
2+
name: codeup-action
3+
description: Create a new codeup action with filter and apply steps
4+
argument-hint: "[action-name]"
5+
---
6+
7+
# Codeup Action Skill
8+
9+
Create a new codeup action based on user-provided filter and steps.
10+
11+
## Instructions
12+
13+
When user provides:
14+
15+
- **filter**: Condition for when the action should run
16+
- **steps**: What the action should do
17+
18+
Generate a new action file in `actions/` directory.
19+
20+
## Action Structure
21+
22+
```ts
23+
import { defineAction } from "codeup";
24+
25+
export default defineAction({
26+
meta: {
27+
name: "action-name", // kebab-case name
28+
description: "Description", // Short description of what it does
29+
date: "YYYY-MM-DD", // Today's date
30+
},
31+
async filter({ utils }) {
32+
// Return true if action should run, false otherwise
33+
return await utils.exists("some-file");
34+
},
35+
async apply({ utils }) {
36+
// Perform the action steps
37+
},
38+
});
39+
```
40+
41+
## Available Utilities (`utils`)
42+
43+
### File System
44+
45+
| Method | Description |
46+
| ------------------------------ | ------------------------------------------------------------------------------------ |
47+
| `resolve(path)` | Resolve path relative to cwd |
48+
| `exists(path)` | Check if file/directory exists |
49+
| `existsWithAnyExt(path)` | Check if file exists with any extension (e.g., `.eslintrc` matches `.eslintrc.json`) |
50+
| `read(path)` | Read file contents as string |
51+
| `readLines(path)` | Read file as array of lines |
52+
| `write(path, contents, opts?)` | Write file. Options: `{ skipIfExists?: boolean, log?: boolean }` |
53+
| `remove(path)` | Remove file or directory |
54+
| `findUp(name)` | Find file in cwd or parent directories |
55+
| `update(path, fn)` | Read file, transform with fn, write back |
56+
| `append(path, contents)` | Append to file |
57+
58+
### JSON
59+
60+
| Method | Description |
61+
| ----------------------------- | ---------------------------------------- |
62+
| `readJSON(path)` | Read and parse JSON file |
63+
| `writeJSON(path, obj, opts?)` | Write object as JSON |
64+
| `updateJSON<T>(path, fn)` | Read JSON, transform with fn, write back |
65+
66+
### Package.json
67+
68+
| Method | Description |
69+
| ------------------------------- | --------------------------------------------- |
70+
| `readPackageJSON()` | Read closest package.json |
71+
| `updatePackageJSON(fn)` | Update package.json with transformer function |
72+
| `addDependency(name)` | Add dependency (string or array) |
73+
| `addDevDependency(name)` | Add dev dependency (string or array) |
74+
| `removeDependency(name)` | Remove dependency |
75+
| `detectPackageManager()` | Detect npm/yarn/pnpm/bun |
76+
| `runPackageManagerCommand(cmd)` | Run command with detected package manager |
77+
| `runScript(script)` | Run package.json script |
78+
79+
## Common Filter Patterns
80+
81+
```typescript
82+
// File exists
83+
await utils.exists("tsconfig.json");
84+
85+
// File with any extension exists
86+
await utils.existsWithAnyExt(".eslintrc");
87+
88+
// File exists AND another doesn't
89+
(await utils.exists("old.config")) && !(await utils.exists("new.config"));
90+
91+
// Check package.json for dependency
92+
const pkg = await utils.readPackageJSON();
93+
return !!pkg?.devDependencies?.["some-package"];
94+
95+
// Check package.json scripts
96+
const pkg = await utils.readPackageJSON();
97+
const scripts = Object.values(pkg?.scripts || {}) as string[];
98+
return scripts.some((s) => s.includes("some-command"));
99+
```
100+
101+
## Common Apply Patterns
102+
103+
```typescript
104+
// Remove files
105+
await utils.remove(".oldconfig");
106+
await utils.remove(".oldignore");
107+
108+
// Write new config (skip if exists)
109+
await utils.write(
110+
"new.config.json",
111+
JSON.stringify({ key: "value" }, null, 2),
112+
{ skipIfExists: true },
113+
);
114+
115+
// Update package.json scripts
116+
await utils.updatePackageJSON((pkg) => {
117+
for (const name in pkg.scripts) {
118+
if (pkg.scripts[name]?.includes("old-cmd")) {
119+
pkg.scripts[name] = pkg.scripts[name].replace("old-cmd", "new-cmd");
120+
}
121+
}
122+
});
123+
124+
// Add/remove dependencies
125+
await utils.addDevDependency("new-package@^1.0.0");
126+
await utils.removeDependency("old-package");
127+
128+
// Update JSON config
129+
await utils.updateJSON<SomeType>("config.json", (config) => {
130+
delete config.oldOption;
131+
config.newOption = true;
132+
});
133+
134+
// Run script after changes
135+
await utils.runScript("lint:fix");
136+
```
137+
138+
## Example Actions
139+
140+
### Migration Action (eslint-flat style)
141+
142+
```typescript
143+
import { defineAction } from "codeup";
144+
145+
export default defineAction({
146+
meta: {
147+
name: "migrate-config",
148+
description: "Migrate from old config to new config",
149+
date: "2026-01-29",
150+
},
151+
async filter({ utils }) {
152+
return (
153+
(await utils.existsWithAnyExt(".oldrc")) &&
154+
!(await utils.existsWithAnyExt("new.config"))
155+
);
156+
},
157+
async apply({ utils }) {
158+
// Read old config
159+
const oldConfig = await utils.readJSON(".oldrc");
160+
161+
// Write new config
162+
await utils.write("new.config.mjs", getTemplate(oldConfig));
163+
164+
// Remove old files
165+
await utils.remove(".oldrc");
166+
await utils.remove(".oldignore");
167+
168+
// Update dependencies
169+
await utils.addDevDependency("new-tool@^2.0.0");
170+
await utils.removeDependency("old-tool");
171+
},
172+
});
173+
```
174+
175+
### Tool Switch Action (tsgo style)
176+
177+
```typescript
178+
import { defineAction } from "codeup";
179+
180+
export default defineAction({
181+
meta: {
182+
name: "switch-tool",
183+
description: "Switch from tool-a to tool-b",
184+
date: "2026-01-29",
185+
},
186+
async filter({ utils }) {
187+
const pkg = await utils.readPackageJSON();
188+
return !!pkg?.devDependencies?.["tool-a"];
189+
},
190+
async apply({ utils }) {
191+
// Update scripts
192+
await utils.updatePackageJSON((pkg) => {
193+
for (const name in pkg.scripts) {
194+
if (pkg.scripts[name]?.includes("tool-a")) {
195+
pkg.scripts[name] = pkg.scripts[name].replace("tool-a", "tool-b");
196+
}
197+
}
198+
});
199+
200+
// Swap dependencies
201+
await utils.removeDependency("tool-a");
202+
await utils.addDevDependency("tool-b@latest");
203+
},
204+
});
205+
```
206+
207+
## Task
208+
209+
1. Ask for action **name** if not provided
210+
2. Ask for **filter** condition (when should this action run?)
211+
3. Ask for **steps** (what should the action do?)
212+
4. Determine appropriate directory under `actions/` (use `actions/unjs/` for unjs-related actions)
213+
5. Generate the action file with proper structure
214+
6. Use today's date for the `meta.date` field

0 commit comments

Comments
 (0)