yuri-skills 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yuri Yu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # yuri-skills
2
+
3
+ A curated collection of Agent Skills for AI workflows, engineering, research, and productivity.
4
+
5
+ Each skill is a self-contained folder under `skills/`. Pre-packaged `.skill` files for one-click install live in `dist/`.
6
+
7
+ Skills may be authored in English or Chinese. Chinese skills are suffixed (e.g. `-zh`) in the folder name.
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ Pick the method that matches your AI client.
14
+
15
+ ### Option A: Claude Code + Codex via npx
16
+
17
+ Install every Yuri skill globally for both Claude Code and Codex:
18
+
19
+ ```bash
20
+ npx yuri-skills install
21
+ ```
22
+
23
+ The npm package only ships the installer. Skills are downloaded from GitHub at install time, so skill updates only need to be pushed to this repository.
24
+
25
+ Install one or more skills:
26
+
27
+ ```bash
28
+ npx yuri-skills install grill-me-harder
29
+ npx yuri-skills install grill-me-harder ruthless-paper-reviewer
30
+ ```
31
+
32
+ Limit the target agent or install into the current project:
33
+
34
+ ```bash
35
+ npx yuri-skills install --agent codex
36
+ npx yuri-skills install --agent claude
37
+ npx yuri-skills install --scope project
38
+ ```
39
+
40
+ Pin installs to a branch, tag, or commit:
41
+
42
+ ```bash
43
+ npx yuri-skills install --ref v0.1.0
44
+ ```
45
+
46
+ Remove Yuri skills from the same default global locations:
47
+
48
+ ```bash
49
+ npx yuri-skills remove
50
+ ```
51
+
52
+ Defaults: all skills, both Claude and Codex, user-global install, GitHub `main` branch. The installer supports macOS, Linux, and Windows.
53
+
54
+ ## Triggering skills
55
+
56
+ After installation, Claude Code and Codex can auto-trigger skills based on each skill's `description` field in `SKILL.md`.
57
+
58
+ You can also invoke the intended behavior directly in natural language, for example:
59
+
60
+ ```text
61
+ grill me on this design
62
+ pressure test this plan
63
+ 拷打我这个方案
64
+ 帮我锐评这篇论文
65
+ ```
66
+
67
+ Chinese-language skills use the `-zh` suffix and include Chinese trigger phrasing in their own `SKILL.md`.
68
+
69
+ ### Option B: Claude.ai web / desktop app (recommended for non-technical users)
70
+
71
+ 1. Open the [dist/](dist/) directory.
72
+ 2. Download the `<skill-name>.skill` file you want.
73
+ 3. In Claude.ai, go to **Settings → Capabilities → Skills → Upload skill**.
74
+ 4. Select the downloaded `.skill` file. Done.
75
+
76
+ ### Option C: Manual install
77
+
78
+ You can also copy a skill folder directly into your agent's skills directory.
79
+
80
+ **Claude Code global install** (available in every project):
81
+
82
+ ```bash
83
+ # macOS / Linux
84
+ cp -r skills/<skill-name> ~/.claude/skills/
85
+
86
+ # Windows (PowerShell)
87
+ Copy-Item -Recurse skills\<skill-name> $env:USERPROFILE\.claude\skills\
88
+ ```
89
+
90
+ **Claude Code project-scoped install** (current repo only):
91
+
92
+ ```bash
93
+ cp -r skills/<skill-name> <your-project>/.claude/skills/
94
+ ```
95
+
96
+ **Codex project-scoped install** (current repo only):
97
+
98
+ ```bash
99
+ # macOS / Linux
100
+ mkdir -p .agents/skills
101
+ cp -r skills/<skill-name> .agents/skills/
102
+
103
+ # Windows (PowerShell)
104
+ New-Item -ItemType Directory -Force .agents\skills
105
+ Copy-Item -Recurse skills\<skill-name> .agents\skills\
106
+ ```
107
+
108
+ **Codex user-scoped install** (available in every project):
109
+
110
+ ```bash
111
+ # macOS / Linux
112
+ mkdir -p ~/.agents/skills
113
+ cp -r skills/<skill-name> ~/.agents/skills/
114
+
115
+ # Windows (PowerShell)
116
+ New-Item -ItemType Directory -Force $HOME\.agents\skills
117
+ Copy-Item -Recurse skills\<skill-name> $HOME\.agents\skills\
118
+ ```
119
+
120
+ Restart Claude Code or Codex if the new skill does not appear. Some older Codex setups may use `~/.codex/skills`; prefer `.agents/skills` for new installs.
121
+
122
+ ---
123
+
124
+ ## Available skills
125
+
126
+ > This list grows as new skills are added. Each skill's `SKILL.md` has full details and trigger examples.
127
+
128
+ | Skill | What it does | Source |
129
+ | --- | --- | --- |
130
+ | [grill-me-harder](skills/grill-me-harder/) | Adversarially interviews you about a plan or design, one branch at a time, until every decision is concrete. | Adapted from [mattpocock/skills · grill-me](https://github.com/mattpocock/skills) |
131
+ | [grill-me-harder-zh](skills/grill-me-harder-zh/) | 中文版"拷打我"。用调侃但不留情面的语气,逐个分支把你的方案追问到落地,直到你"悟了"。 | Adapted from [mattpocock/skills · grill-me](https://github.com/mattpocock/skills) |
132
+ | [ruthless-paper-reviewer](skills/ruthless-paper-reviewer/) | Ruthlessly roasts an academic paper. Hunts for fatal logic flaws, tech-washing, dataset-timeline mismatches, and unsupported conclusions. Evidence-grounded, no "pros and cons" essays. | Original |
133
+ | [ruthless-paper-reviewer-zh](skills/ruthless-paper-reviewer-zh/) | 中文版"学术论文锐评"。B 站锐评味儿打底,第一性原理收尾——专治缝合怪、A+B 灌水、跑分游戏、Math/Tech-washing。皮调侃,骨头硬。 | Original |
134
+
135
+ ---
136
+
137
+ ## For contributors
138
+
139
+ See [CLAUDE.md](CLAUDE.md) for the full skill-authoring workflow — you can hand it to Claude Code and have it walk through the process.
140
+
141
+ Package a skill:
142
+
143
+ ```bash
144
+ python scripts/package_skill.py skills/<skill-name>
145
+ # Output: dist/<skill-name>.skill
146
+ ```
147
+
148
+ ---
149
+
150
+ ## License
151
+
152
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const https = require("https");
5
+ const os = require("os");
6
+ const path = require("path");
7
+
8
+ const GITHUB_REPO = "MSWinds/yuri-skills";
9
+ const DEFAULT_REF = "main";
10
+ const MARKER_FILE = ".yuri-skills.json";
11
+ const VALID_AGENTS = new Set(["all", "claude", "codex"]);
12
+ const VALID_SCOPES = new Set(["user", "project", "repo", "global", "local"]);
13
+ const SKILL_NAME_RE = /^[a-z0-9-]+$/;
14
+
15
+ function usage() {
16
+ console.log(`yuri-skills
17
+
18
+ Usage:
19
+ yuri-skills list [--ref main]
20
+ yuri-skills install [skill...] [--agent claude|codex|all] [--scope user|project] [--ref main] [--dry-run]
21
+ yuri-skills remove [skill...] [--agent claude|codex|all] [--scope user|project] [--dry-run]
22
+
23
+ Defaults:
24
+ install all Yuri skills from GitHub
25
+ remove all skills installed by yuri-skills
26
+ --agent all
27
+ --scope user
28
+ --ref main
29
+
30
+ Examples:
31
+ yuri-skills install
32
+ yuri-skills install grill-me-harder --agent codex
33
+ yuri-skills install --scope project --ref v0.1.0
34
+ yuri-skills remove
35
+ yuri-skills remove --agent claude --dry-run
36
+ `);
37
+ }
38
+
39
+ function fail(message) {
40
+ console.error(`Error: ${message}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ function parseArgs(argv) {
45
+ const out = {
46
+ command: argv[0],
47
+ skills: [],
48
+ agent: "all",
49
+ scope: "user",
50
+ ref: DEFAULT_REF,
51
+ dryRun: false,
52
+ };
53
+
54
+ for (let i = 1; i < argv.length; i += 1) {
55
+ const arg = argv[i];
56
+
57
+ if (arg === "--dry-run") {
58
+ out.dryRun = true;
59
+ continue;
60
+ }
61
+
62
+ if (arg === "--agent" || arg === "-a") {
63
+ out.agent = argv[i + 1];
64
+ i += 1;
65
+ continue;
66
+ }
67
+
68
+ if (arg.startsWith("--agent=")) {
69
+ out.agent = arg.slice("--agent=".length);
70
+ continue;
71
+ }
72
+
73
+ if (arg === "--scope" || arg === "-s") {
74
+ out.scope = argv[i + 1];
75
+ i += 1;
76
+ continue;
77
+ }
78
+
79
+ if (arg.startsWith("--scope=")) {
80
+ out.scope = arg.slice("--scope=".length);
81
+ continue;
82
+ }
83
+
84
+ if (arg === "--ref" || arg === "-r") {
85
+ out.ref = argv[i + 1];
86
+ i += 1;
87
+ continue;
88
+ }
89
+
90
+ if (arg.startsWith("--ref=")) {
91
+ out.ref = arg.slice("--ref=".length);
92
+ continue;
93
+ }
94
+
95
+ if (arg.startsWith("-")) {
96
+ fail(`unknown option: ${arg}`);
97
+ }
98
+
99
+ out.skills.push(arg);
100
+ }
101
+
102
+ if (!VALID_AGENTS.has(out.agent)) {
103
+ fail(`--agent must be one of: all, claude, codex`);
104
+ }
105
+
106
+ if (!VALID_SCOPES.has(out.scope)) {
107
+ fail(`--scope must be one of: user, project`);
108
+ }
109
+
110
+ if (out.scope === "global") {
111
+ out.scope = "user";
112
+ }
113
+
114
+ if (out.scope === "repo" || out.scope === "local") {
115
+ out.scope = "project";
116
+ }
117
+
118
+ if (!out.ref) {
119
+ fail(`--ref requires a value`);
120
+ }
121
+
122
+ for (const name of out.skills) {
123
+ if (!SKILL_NAME_RE.test(name)) {
124
+ fail(`invalid skill name: ${name}`);
125
+ }
126
+ }
127
+
128
+ return out;
129
+ }
130
+
131
+ function selectedAgents(agent) {
132
+ if (agent === "all") {
133
+ return ["claude", "codex"];
134
+ }
135
+ return [agent];
136
+ }
137
+
138
+ function targetRoot(agent, scope) {
139
+ const base = scope === "user" ? os.homedir() : process.cwd();
140
+
141
+ if (agent === "claude") {
142
+ return path.join(base, ".claude", "skills");
143
+ }
144
+
145
+ return path.join(base, ".agents", "skills");
146
+ }
147
+
148
+ function request(url, parseJson = false) {
149
+ return new Promise((resolve, reject) => {
150
+ const req = https.get(
151
+ url,
152
+ {
153
+ headers: {
154
+ "Accept": "application/vnd.github+json",
155
+ "User-Agent": "yuri-skills-installer",
156
+ "X-GitHub-Api-Version": "2022-11-28",
157
+ },
158
+ },
159
+ (res) => {
160
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
161
+ res.resume();
162
+ request(res.headers.location, parseJson).then(resolve, reject);
163
+ return;
164
+ }
165
+
166
+ const chunks = [];
167
+ res.on("data", (chunk) => chunks.push(chunk));
168
+ res.on("end", () => {
169
+ const body = Buffer.concat(chunks);
170
+ if (res.statusCode < 200 || res.statusCode >= 300) {
171
+ reject(new Error(`GitHub request failed (${res.statusCode}): ${body.toString("utf8")}`));
172
+ return;
173
+ }
174
+
175
+ if (parseJson) {
176
+ try {
177
+ resolve(JSON.parse(body.toString("utf8")));
178
+ } catch (error) {
179
+ reject(error);
180
+ }
181
+ return;
182
+ }
183
+
184
+ resolve(body);
185
+ });
186
+ },
187
+ );
188
+
189
+ req.on("error", reject);
190
+ });
191
+ }
192
+
193
+ function githubContents(apiPath, ref) {
194
+ const url = `https://api.github.com/repos/${GITHUB_REPO}/contents/${apiPath}?ref=${encodeURIComponent(ref)}`;
195
+ return request(url, true);
196
+ }
197
+
198
+ async function remoteSkillNames(ref) {
199
+ const entries = await githubContents("skills", ref);
200
+ if (!Array.isArray(entries)) {
201
+ fail(`GitHub path is not a directory: skills`);
202
+ }
203
+
204
+ return entries
205
+ .filter((entry) => entry.type === "dir" && SKILL_NAME_RE.test(entry.name))
206
+ .map((entry) => entry.name)
207
+ .sort();
208
+ }
209
+
210
+ async function selectedSkills(requested, ref) {
211
+ const available = await remoteSkillNames(ref);
212
+ if (requested.length === 0) {
213
+ return available;
214
+ }
215
+
216
+ for (const name of requested) {
217
+ if (!available.includes(name)) {
218
+ fail(`unknown skill: ${name}. Available at ${GITHUB_REPO}@${ref}: ${available.join(", ")}`);
219
+ }
220
+ }
221
+
222
+ return requested;
223
+ }
224
+
225
+ function frontmatterName(skillMdPath) {
226
+ if (!fs.existsSync(skillMdPath)) {
227
+ return null;
228
+ }
229
+
230
+ const text = fs.readFileSync(skillMdPath, "utf8");
231
+ const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
232
+ if (!match) {
233
+ return null;
234
+ }
235
+
236
+ const nameLine = match[1]
237
+ .split(/\r?\n/)
238
+ .find((line) => line.trim().startsWith("name:"));
239
+
240
+ if (!nameLine) {
241
+ return null;
242
+ }
243
+
244
+ return nameLine.split(":").slice(1).join(":").trim().replace(/^["']|["']$/g, "");
245
+ }
246
+
247
+ function readMarker(targetDir) {
248
+ const markerPath = path.join(targetDir, MARKER_FILE);
249
+ if (!fs.existsSync(markerPath)) {
250
+ return null;
251
+ }
252
+
253
+ try {
254
+ return JSON.parse(fs.readFileSync(markerPath, "utf8"));
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+
260
+ function writeMarker(targetDir, skillName, ref, agent, scope) {
261
+ const marker = {
262
+ installer: "yuri-skills",
263
+ source: `github:${GITHUB_REPO}`,
264
+ skill: skillName,
265
+ ref,
266
+ agent,
267
+ scope,
268
+ installedAt: new Date().toISOString(),
269
+ };
270
+ fs.writeFileSync(path.join(targetDir, MARKER_FILE), `${JSON.stringify(marker, null, 2)}\n`);
271
+ }
272
+
273
+ function isYuriSkillTarget(targetDir, skillName) {
274
+ const marker = readMarker(targetDir);
275
+ if (marker) {
276
+ return marker.installer === "yuri-skills" && marker.skill === skillName;
277
+ }
278
+
279
+ return frontmatterName(path.join(targetDir, "SKILL.md")) === skillName;
280
+ }
281
+
282
+ function ensureSafeExistingTarget(targetDir, skillName) {
283
+ if (!fs.existsSync(targetDir)) {
284
+ return;
285
+ }
286
+
287
+ if (!isYuriSkillTarget(targetDir, skillName)) {
288
+ fail(`refusing to overwrite ${targetDir}; it does not look like ${skillName} installed by yuri-skills`);
289
+ }
290
+ }
291
+
292
+ async function downloadDirectory(apiPath, targetDir, ref) {
293
+ const entries = await githubContents(apiPath, ref);
294
+ if (!Array.isArray(entries)) {
295
+ fail(`GitHub path is not a directory: ${apiPath}`);
296
+ }
297
+
298
+ fs.mkdirSync(targetDir, { recursive: true });
299
+
300
+ for (const entry of entries) {
301
+ const entryTarget = path.join(targetDir, entry.name);
302
+
303
+ if (entry.type === "dir") {
304
+ await downloadDirectory(entry.path, entryTarget, ref);
305
+ continue;
306
+ }
307
+
308
+ if (entry.type === "file") {
309
+ const content = await request(entry.download_url, false);
310
+ fs.writeFileSync(entryTarget, content);
311
+ }
312
+ }
313
+ }
314
+
315
+ async function downloadSkill(skillName, ref) {
316
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `yuri-skills-${skillName}-`));
317
+ try {
318
+ const skillDir = path.join(tempDir, skillName);
319
+ await downloadDirectory(`skills/${skillName}`, skillDir, ref);
320
+ const actualName = frontmatterName(path.join(skillDir, "SKILL.md"));
321
+ if (actualName !== skillName) {
322
+ fail(`downloaded skill ${skillName} has mismatched SKILL.md name: ${actualName || "missing"}`);
323
+ }
324
+ return { tempDir, skillDir };
325
+ } catch (error) {
326
+ fs.rmSync(tempDir, { recursive: true, force: true });
327
+ throw error;
328
+ }
329
+ }
330
+
331
+ async function installSkill(skillName, root, options, agent) {
332
+ const targetDir = path.join(root, skillName);
333
+ ensureSafeExistingTarget(targetDir, skillName);
334
+
335
+ if (options.dryRun) {
336
+ console.log(`[dry-run] install ${skillName} from ${GITHUB_REPO}@${options.ref} -> ${targetDir}`);
337
+ return;
338
+ }
339
+
340
+ const { tempDir, skillDir } = await downloadSkill(skillName, options.ref);
341
+ try {
342
+ fs.mkdirSync(root, { recursive: true });
343
+ fs.rmSync(targetDir, { recursive: true, force: true });
344
+ fs.cpSync(skillDir, targetDir, { recursive: true });
345
+ writeMarker(targetDir, skillName, options.ref, agent, options.scope);
346
+ console.log(`Installed ${skillName} -> ${targetDir}`);
347
+ } finally {
348
+ fs.rmSync(tempDir, { recursive: true, force: true });
349
+ }
350
+ }
351
+
352
+ function installedYuriSkills(root) {
353
+ if (!fs.existsSync(root)) {
354
+ return [];
355
+ }
356
+
357
+ return fs
358
+ .readdirSync(root, { withFileTypes: true })
359
+ .filter((entry) => entry.isDirectory())
360
+ .map((entry) => entry.name)
361
+ .filter((name) => {
362
+ const marker = readMarker(path.join(root, name));
363
+ return marker && marker.installer === "yuri-skills" && marker.skill === name;
364
+ })
365
+ .sort();
366
+ }
367
+
368
+ function removeSkill(skillName, root, dryRun) {
369
+ const targetDir = path.join(root, skillName);
370
+ if (!fs.existsSync(targetDir)) {
371
+ console.log(`Skipped ${skillName}; not installed at ${targetDir}`);
372
+ return;
373
+ }
374
+
375
+ if (!isYuriSkillTarget(targetDir, skillName)) {
376
+ fail(`refusing to remove ${targetDir}; it does not look like ${skillName} installed by yuri-skills`);
377
+ }
378
+
379
+ if (dryRun) {
380
+ console.log(`[dry-run] remove ${targetDir}`);
381
+ return;
382
+ }
383
+
384
+ fs.rmSync(targetDir, { recursive: true, force: true });
385
+ console.log(`Removed ${targetDir}`);
386
+ }
387
+
388
+ async function listSkills(options) {
389
+ for (const skill of await remoteSkillNames(options.ref)) {
390
+ console.log(skill);
391
+ }
392
+ }
393
+
394
+ async function install(options) {
395
+ const skills = await selectedSkills(options.skills, options.ref);
396
+ for (const agent of selectedAgents(options.agent)) {
397
+ const root = targetRoot(agent, options.scope);
398
+ for (const skill of skills) {
399
+ await installSkill(skill, root, options, agent);
400
+ }
401
+ }
402
+ }
403
+
404
+ function remove(options) {
405
+ for (const agent of selectedAgents(options.agent)) {
406
+ const root = targetRoot(agent, options.scope);
407
+ const skills = options.skills.length > 0 ? options.skills : installedYuriSkills(root);
408
+
409
+ if (skills.length === 0) {
410
+ console.log(`No yuri-skills installs found at ${root}`);
411
+ continue;
412
+ }
413
+
414
+ for (const skill of skills) {
415
+ removeSkill(skill, root, options.dryRun);
416
+ }
417
+ }
418
+ }
419
+
420
+ async function main() {
421
+ const options = parseArgs(process.argv.slice(2));
422
+
423
+ if (!options.command || options.command === "--help" || options.command === "-h") {
424
+ usage();
425
+ return;
426
+ }
427
+
428
+ if (options.command === "list") {
429
+ await listSkills(options);
430
+ return;
431
+ }
432
+
433
+ if (options.command === "install") {
434
+ await install(options);
435
+ return;
436
+ }
437
+
438
+ if (options.command === "remove" || options.command === "uninstall") {
439
+ remove(options);
440
+ return;
441
+ }
442
+
443
+ fail(`unknown command: ${options.command}`);
444
+ }
445
+
446
+ main().catch((error) => {
447
+ console.error(`Error: ${error.message}`);
448
+ process.exit(1);
449
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "yuri-skills",
3
+ "version": "0.1.0",
4
+ "description": "A curated collection of Agent Skills for AI workflows, engineering, research, and productivity.",
5
+ "bin": {
6
+ "yuri-skills": "bin/yuri-skills.js"
7
+ },
8
+ "scripts": {
9
+ "check": "node bin/yuri-skills.js list && node bin/yuri-skills.js install --agent codex --scope project --dry-run && node bin/yuri-skills.js remove --agent codex --scope project --dry-run"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/MSWinds/yuri-skills.git"
14
+ },
15
+ "keywords": [],
16
+ "files": [
17
+ "bin/",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "type": "commonjs",
24
+ "bugs": {
25
+ "url": "https://github.com/MSWinds/yuri-skills/issues"
26
+ },
27
+ "homepage": "https://github.com/MSWinds/yuri-skills#readme"
28
+ }