recur-skills 0.0.2 → 0.0.4
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/.claude-plugin/marketplace.json +20 -2
- package/README.md +11 -58
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +154 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +9 -7
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +82 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +11 -10
- package/skills/recur-help/SKILL.md +55 -0
- package/.claude-plugin/plugin.json +0 -25
- package/dist/cli.js +0 -163
- package/dist/index.js +0 -72
|
@@ -4,11 +4,29 @@
|
|
|
4
4
|
"name": "Recur",
|
|
5
5
|
"email": "hi@recur.tw"
|
|
6
6
|
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "Claude Code skills for integrating Recur - Taiwan's subscription payment platform",
|
|
9
|
+
"version": "0.0.4"
|
|
10
|
+
},
|
|
7
11
|
"plugins": [
|
|
8
12
|
{
|
|
9
13
|
"name": "recur-skills",
|
|
10
|
-
"description": "
|
|
11
|
-
"source": "
|
|
14
|
+
"description": "Skills for Recur SDK integration including quickstart, checkout, webhooks, and entitlements",
|
|
15
|
+
"source": "./",
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/recur-help",
|
|
18
|
+
"./skills/recur-quickstart",
|
|
19
|
+
"./skills/recur-checkout",
|
|
20
|
+
"./skills/recur-webhooks",
|
|
21
|
+
"./skills/recur-entitlements"
|
|
22
|
+
],
|
|
23
|
+
"commands": [
|
|
24
|
+
"./skills/recur-help/SKILL.md",
|
|
25
|
+
"./skills/recur-quickstart/SKILL.md",
|
|
26
|
+
"./skills/recur-checkout/SKILL.md",
|
|
27
|
+
"./skills/recur-webhooks/SKILL.md",
|
|
28
|
+
"./skills/recur-entitlements/SKILL.md"
|
|
29
|
+
]
|
|
12
30
|
}
|
|
13
31
|
]
|
|
14
32
|
}
|
package/README.md
CHANGED
|
@@ -4,38 +4,28 @@ Claude Code skills to help developers integrate [Recur](https://recur.tw) - Taiw
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
### Claude Code Plugin (Recommended)
|
|
8
|
-
|
|
9
7
|
```bash
|
|
10
8
|
/plugin marketplace add recur-tw/skills
|
|
9
|
+
/plugin install recur-skills@recur-skills
|
|
11
10
|
```
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Install all skills globally
|
|
17
|
-
npx recur-skills install --all --global
|
|
12
|
+
## Getting Started
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
npx recur-skills install recur-quickstart recur-webhooks
|
|
14
|
+
Not sure where to begin? Ask Claude:
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
```
|
|
17
|
+
Recur 有什麼功能?
|
|
24
18
|
```
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
Or type `/recur-help` to see all available skills.
|
|
27
21
|
|
|
28
|
-
|
|
22
|
+
## Available Skills
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
# Global (all projects)
|
|
32
|
-
cp -r skills/* ~/.claude/skills/
|
|
24
|
+
### recur-help
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
cp -r skills/* .claude/skills/
|
|
36
|
-
```
|
|
26
|
+
List all available Recur skills and how to use them.
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
**Triggers:** "Recur 有什麼功能", "help with Recur", "what can Recur do"
|
|
39
29
|
|
|
40
30
|
### recur-quickstart
|
|
41
31
|
|
|
@@ -45,7 +35,7 @@ Quick setup guide for Recur payment integration.
|
|
|
45
35
|
|
|
46
36
|
- SDK installation
|
|
47
37
|
- API key configuration
|
|
48
|
-
-
|
|
38
|
+
- Provider setup
|
|
49
39
|
- First checkout implementation
|
|
50
40
|
|
|
51
41
|
### recur-checkout
|
|
@@ -58,7 +48,6 @@ Implement Recur checkout flows.
|
|
|
58
48
|
- useRecur and useSubscribe hooks
|
|
59
49
|
- Product types (subscription, one-time, credits, donation)
|
|
60
50
|
- Payment error handling
|
|
61
|
-
- 3D verification
|
|
62
51
|
|
|
63
52
|
### recur-webhooks
|
|
64
53
|
|
|
@@ -69,7 +58,6 @@ Set up and handle Recur webhook events.
|
|
|
69
58
|
- All webhook event types
|
|
70
59
|
- Signature verification
|
|
71
60
|
- Next.js and Express handlers
|
|
72
|
-
- Testing webhooks locally
|
|
73
61
|
- Idempotency handling
|
|
74
62
|
|
|
75
63
|
### recur-entitlements
|
|
@@ -82,51 +70,16 @@ Implement access control and permission checking.
|
|
|
82
70
|
- Cached vs live checks
|
|
83
71
|
- Paywall components
|
|
84
72
|
- Server-side verification
|
|
85
|
-
- Handling subscription statuses
|
|
86
73
|
|
|
87
74
|
## Usage
|
|
88
75
|
|
|
89
76
|
Once installed, Claude will automatically use these skills when you're working on Recur integration tasks.
|
|
90
77
|
|
|
91
|
-
You can also invoke them directly:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
/recur-quickstart
|
|
95
|
-
/recur-checkout
|
|
96
|
-
/recur-webhooks
|
|
97
|
-
/recur-entitlements
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Utility Scripts
|
|
101
|
-
|
|
102
|
-
### Check Environment
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
./skills/recur-quickstart/scripts/check-env.sh
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Test Webhook Locally
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
./skills/recur-webhooks/scripts/test-webhook.sh http://localhost:3000/api/webhooks/recur checkout.completed
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Verify Webhook Signature
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
npx tsx ./skills/recur-webhooks/scripts/verify-signature.ts '<payload>' '<signature>' '<secret>'
|
|
118
|
-
```
|
|
119
|
-
|
|
120
78
|
## Links
|
|
121
79
|
|
|
122
80
|
- [Recur Website](https://recur.tw)
|
|
123
81
|
- [Documentation](https://recur.tw/docs)
|
|
124
82
|
- [SDK on npm](https://www.npmjs.com/package/recur-tw)
|
|
125
|
-
- [API Reference](https://recur.tw/docs/api)
|
|
126
|
-
|
|
127
|
-
## Contributing
|
|
128
|
-
|
|
129
|
-
Found an issue or want to improve a skill? Please open an issue or PR at [github.com/recur-tw/skills](https://github.com/recur-tw/skills).
|
|
130
83
|
|
|
131
84
|
## License
|
|
132
85
|
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
|
11
|
+
/**
|
|
12
|
+
* Parse SKILL.md frontmatter
|
|
13
|
+
*/
|
|
14
|
+
function parseFrontmatter(content) {
|
|
15
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
16
|
+
if (!match) return {
|
|
17
|
+
data: {},
|
|
18
|
+
content
|
|
19
|
+
};
|
|
20
|
+
const data = {};
|
|
21
|
+
const lines = match[1].split("\n");
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const colonIndex = line.indexOf(":");
|
|
24
|
+
if (colonIndex > 0) {
|
|
25
|
+
const key = line.slice(0, colonIndex).trim();
|
|
26
|
+
data[key] = line.slice(colonIndex + 1).trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
data,
|
|
31
|
+
content: match[2]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all available skills
|
|
36
|
+
*/
|
|
37
|
+
function getAllSkills() {
|
|
38
|
+
if (!existsSync(SKILLS_DIR)) return [];
|
|
39
|
+
const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
40
|
+
const skills = [];
|
|
41
|
+
for (const dir of skillDirs) {
|
|
42
|
+
const skillPath = join(SKILLS_DIR, dir, "SKILL.md");
|
|
43
|
+
if (!existsSync(skillPath)) continue;
|
|
44
|
+
const { data, content } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
|
|
45
|
+
skills.push({
|
|
46
|
+
slug: dir,
|
|
47
|
+
name: data.name || dir,
|
|
48
|
+
description: data.description || "",
|
|
49
|
+
content,
|
|
50
|
+
path: skillPath
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return skills;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get a single skill by slug
|
|
57
|
+
*/
|
|
58
|
+
function getSkill(slug) {
|
|
59
|
+
const skillPath = join(SKILLS_DIR, slug, "SKILL.md");
|
|
60
|
+
if (!existsSync(skillPath)) return null;
|
|
61
|
+
const { data, content } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
|
|
62
|
+
return {
|
|
63
|
+
slug,
|
|
64
|
+
name: data.name || slug,
|
|
65
|
+
description: data.description || "",
|
|
66
|
+
content,
|
|
67
|
+
path: skillPath
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the path to skills directory
|
|
72
|
+
*/
|
|
73
|
+
function getSkillsDir() {
|
|
74
|
+
return SKILLS_DIR;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/cli.ts
|
|
79
|
+
const SKILLS_SOURCE = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
|
80
|
+
program.name("recur-skills").description("Claude Code skills for Recur payment integration").version("0.0.1");
|
|
81
|
+
program.command("list").description("List all available skills").action(() => {
|
|
82
|
+
const skills = getAllSkills();
|
|
83
|
+
if (skills.length === 0) {
|
|
84
|
+
console.log(pc.yellow("No skills found."));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
console.log(pc.bold("\n📦 Available Recur Skills\n"));
|
|
88
|
+
for (const skill of skills) {
|
|
89
|
+
console.log(pc.cyan(` ${skill.name}`));
|
|
90
|
+
console.log(pc.dim(` ${skill.description}\n`));
|
|
91
|
+
}
|
|
92
|
+
console.log(pc.dim(`Total: ${skills.length} skills\n`));
|
|
93
|
+
});
|
|
94
|
+
program.command("info <skill>").description("Show detailed information about a skill").action((skillName) => {
|
|
95
|
+
const skill = getSkill(skillName);
|
|
96
|
+
if (!skill) {
|
|
97
|
+
console.log(pc.red(`Skill "${skillName}" not found.`));
|
|
98
|
+
console.log(pc.dim("\nRun `recur-skills list` to see available skills."));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
console.log(pc.bold(`\n📦 ${skill.name}\n`));
|
|
102
|
+
console.log(pc.dim("Description:"));
|
|
103
|
+
console.log(` ${skill.description}\n`);
|
|
104
|
+
console.log(pc.dim("Path:"));
|
|
105
|
+
console.log(` ${skill.path}\n`);
|
|
106
|
+
});
|
|
107
|
+
program.command("install [skills...]").description("Install skills to your Claude Code skills directory").option("-g, --global", "Install to global ~/.claude/skills/", false).option("-p, --project", "Install to project .claude/skills/", false).option("-a, --all", "Install all skills", false).action((skillNames, options) => {
|
|
108
|
+
let targetDir;
|
|
109
|
+
if (options.global) targetDir = join(process.env.HOME || "~", ".claude", "skills");
|
|
110
|
+
else if (options.project) targetDir = join(process.cwd(), ".claude", "skills");
|
|
111
|
+
else targetDir = join(process.env.HOME || "~", ".claude", "skills");
|
|
112
|
+
let toInstall;
|
|
113
|
+
if (options.all) toInstall = readdirSync(SKILLS_SOURCE, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
114
|
+
else if (skillNames.length === 0) {
|
|
115
|
+
console.log(pc.yellow("Please specify skills to install or use --all"));
|
|
116
|
+
console.log(pc.dim("\nExamples:"));
|
|
117
|
+
console.log(pc.dim(" recur-skills install recur-quickstart"));
|
|
118
|
+
console.log(pc.dim(" recur-skills install recur-checkout recur-webhooks"));
|
|
119
|
+
console.log(pc.dim(" recur-skills install --all"));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
} else toInstall = skillNames;
|
|
122
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
123
|
+
console.log(pc.bold(`\n📦 Installing skills to ${targetDir}\n`));
|
|
124
|
+
let installed = 0;
|
|
125
|
+
for (const skillName of toInstall) {
|
|
126
|
+
const sourcePath = join(SKILLS_SOURCE, skillName);
|
|
127
|
+
const destPath = join(targetDir, skillName);
|
|
128
|
+
if (!existsSync(sourcePath)) {
|
|
129
|
+
console.log(pc.red(` ✗ ${skillName} - not found`));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
cpSync(sourcePath, destPath, { recursive: true });
|
|
134
|
+
console.log(pc.green(` ✓ ${skillName}`));
|
|
135
|
+
installed++;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.log(pc.red(` ✗ ${skillName} - ${error}`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
console.log(pc.dim(`\nInstalled ${installed}/${toInstall.length} skills\n`));
|
|
141
|
+
if (installed > 0) {
|
|
142
|
+
console.log(pc.cyan("Skills are now available in Claude Code!"));
|
|
143
|
+
console.log(pc.dim("Claude will automatically use them when relevant,"));
|
|
144
|
+
console.log(pc.dim("or you can invoke them directly with /skill-name\n"));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
program.command("path").description("Show the path to the skills directory").action(() => {
|
|
148
|
+
console.log(getSkillsDir());
|
|
149
|
+
});
|
|
150
|
+
program.parse();
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
export { };
|
|
154
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["data: Record<string, string>","skills: Skill[]","targetDir: string","toInstall: string[]"],"sources":["../src/index.ts","../src/cli.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst SKILLS_DIR = join(__dirname, '..', 'skills')\n\nexport interface SkillMetadata {\n name: string\n description: string\n}\n\nexport interface Skill extends SkillMetadata {\n slug: string\n content: string\n path: string\n}\n\n/**\n * Parse SKILL.md frontmatter\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string>; content: string } {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/)\n if (!match) {\n return { data: {}, content }\n }\n\n const data: Record<string, string> = {}\n const lines = match[1].split('\\n')\n for (const line of lines) {\n const colonIndex = line.indexOf(':')\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim()\n const value = line.slice(colonIndex + 1).trim()\n data[key] = value\n }\n }\n\n return { data, content: match[2] }\n}\n\n/**\n * Get all available skills\n */\nexport function getAllSkills(): Skill[] {\n if (!existsSync(SKILLS_DIR)) {\n return []\n }\n\n const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter(dirent => dirent.isDirectory())\n .map(dirent => dirent.name)\n\n const skills: Skill[] = []\n\n for (const dir of skillDirs) {\n const skillPath = join(SKILLS_DIR, dir, 'SKILL.md')\n if (!existsSync(skillPath)) continue\n\n const rawContent = readFileSync(skillPath, 'utf-8')\n const { data, content } = parseFrontmatter(rawContent)\n\n skills.push({\n slug: dir,\n name: data.name || dir,\n description: data.description || '',\n content,\n path: skillPath,\n })\n }\n\n return skills\n}\n\n/**\n * Get a single skill by slug\n */\nexport function getSkill(slug: string): Skill | null {\n const skillPath = join(SKILLS_DIR, slug, 'SKILL.md')\n if (!existsSync(skillPath)) return null\n\n const rawContent = readFileSync(skillPath, 'utf-8')\n const { data, content } = parseFrontmatter(rawContent)\n\n return {\n slug,\n name: data.name || slug,\n description: data.description || '',\n content,\n path: skillPath,\n }\n}\n\n/**\n * Get skill names only (for listing)\n */\nexport function getSkillNames(): string[] {\n if (!existsSync(SKILLS_DIR)) {\n return []\n }\n\n return readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter(dirent => dirent.isDirectory())\n .filter(dirent => existsSync(join(SKILLS_DIR, dirent.name, 'SKILL.md')))\n .map(dirent => dirent.name)\n}\n\n/**\n * Get the path to skills directory\n */\nexport function getSkillsDir(): string {\n return SKILLS_DIR\n}\n","import { program } from 'commander'\nimport pc from 'picocolors'\nimport { existsSync, mkdirSync, cpSync, readdirSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { getAllSkills, getSkill, getSkillsDir } from './index.js'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst SKILLS_SOURCE = join(__dirname, '..', 'skills')\n\nprogram\n .name('recur-skills')\n .description('Claude Code skills for Recur payment integration')\n .version('0.0.1')\n\nprogram\n .command('list')\n .description('List all available skills')\n .action(() => {\n const skills = getAllSkills()\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'))\n return\n }\n\n console.log(pc.bold('\\n📦 Available Recur Skills\\n'))\n\n for (const skill of skills) {\n console.log(pc.cyan(` ${skill.name}`))\n console.log(pc.dim(` ${skill.description}\\n`))\n }\n\n console.log(pc.dim(`Total: ${skills.length} skills\\n`))\n })\n\nprogram\n .command('info <skill>')\n .description('Show detailed information about a skill')\n .action((skillName: string) => {\n const skill = getSkill(skillName)\n\n if (!skill) {\n console.log(pc.red(`Skill \"${skillName}\" not found.`))\n console.log(pc.dim('\\nRun `recur-skills list` to see available skills.'))\n process.exit(1)\n }\n\n console.log(pc.bold(`\\n📦 ${skill.name}\\n`))\n console.log(pc.dim('Description:'))\n console.log(` ${skill.description}\\n`)\n console.log(pc.dim('Path:'))\n console.log(` ${skill.path}\\n`)\n })\n\nprogram\n .command('install [skills...]')\n .description('Install skills to your Claude Code skills directory')\n .option('-g, --global', 'Install to global ~/.claude/skills/', false)\n .option('-p, --project', 'Install to project .claude/skills/', false)\n .option('-a, --all', 'Install all skills', false)\n .action((skillNames: string[], options: { global: boolean; project: boolean; all: boolean }) => {\n // Determine target directory\n let targetDir: string\n if (options.global) {\n targetDir = join(process.env.HOME || '~', '.claude', 'skills')\n } else if (options.project) {\n targetDir = join(process.cwd(), '.claude', 'skills')\n } else {\n // Default to global\n targetDir = join(process.env.HOME || '~', '.claude', 'skills')\n }\n\n // Determine which skills to install\n let toInstall: string[]\n if (options.all) {\n toInstall = readdirSync(SKILLS_SOURCE, { withFileTypes: true })\n .filter(d => d.isDirectory())\n .map(d => d.name)\n } else if (skillNames.length === 0) {\n console.log(pc.yellow('Please specify skills to install or use --all'))\n console.log(pc.dim('\\nExamples:'))\n console.log(pc.dim(' recur-skills install recur-quickstart'))\n console.log(pc.dim(' recur-skills install recur-checkout recur-webhooks'))\n console.log(pc.dim(' recur-skills install --all'))\n process.exit(1)\n } else {\n toInstall = skillNames\n }\n\n // Create target directory\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true })\n }\n\n console.log(pc.bold(`\\n📦 Installing skills to ${targetDir}\\n`))\n\n let installed = 0\n for (const skillName of toInstall) {\n const sourcePath = join(SKILLS_SOURCE, skillName)\n const destPath = join(targetDir, skillName)\n\n if (!existsSync(sourcePath)) {\n console.log(pc.red(` ✗ ${skillName} - not found`))\n continue\n }\n\n try {\n cpSync(sourcePath, destPath, { recursive: true })\n console.log(pc.green(` ✓ ${skillName}`))\n installed++\n } catch (error) {\n console.log(pc.red(` ✗ ${skillName} - ${error}`))\n }\n }\n\n console.log(pc.dim(`\\nInstalled ${installed}/${toInstall.length} skills\\n`))\n\n if (installed > 0) {\n console.log(pc.cyan('Skills are now available in Claude Code!'))\n console.log(pc.dim('Claude will automatically use them when relevant,'))\n console.log(pc.dim('or you can invoke them directly with /skill-name\\n'))\n }\n })\n\nprogram\n .command('path')\n .description('Show the path to the skills directory')\n .action(() => {\n console.log(getSkillsDir())\n })\n\nprogram.parse()\n"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
1
2
|
interface SkillMetadata {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
4
5
|
}
|
|
5
6
|
interface Skill extends SkillMetadata {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
slug: string;
|
|
8
|
+
content: string;
|
|
9
|
+
path: string;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Get all available skills
|
|
@@ -23,5 +24,6 @@ declare function getSkillNames(): string[];
|
|
|
23
24
|
* Get the path to skills directory
|
|
24
25
|
*/
|
|
25
26
|
declare function getSkillsDir(): string;
|
|
26
|
-
|
|
27
|
-
export {
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Skill, SkillMetadata, getAllSkills, getSkill, getSkillNames, getSkillsDir };
|
|
29
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";UAOiB,aAAA;EAAA,IAAA,EAAA,MAAA;EAKA,WAAM,EAAA,MAAQ;AAgC/B;AAiCgB,UAjEC,KAAA,SAAc,aAiEc,CAAA;EAmB7B,IAAA,EAAA,MAAA;EAcA,OAAA,EAAA,MAAY;;;;;;iBAlEZ,YAAA,CAAA,GAAgB;;;;iBAiChB,QAAA,gBAAwB;;;;iBAmBxB,aAAA,CAAA;;;;iBAcA,YAAA,CAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
|
7
|
+
/**
|
|
8
|
+
* Parse SKILL.md frontmatter
|
|
9
|
+
*/
|
|
10
|
+
function parseFrontmatter(content) {
|
|
11
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
12
|
+
if (!match) return {
|
|
13
|
+
data: {},
|
|
14
|
+
content
|
|
15
|
+
};
|
|
16
|
+
const data = {};
|
|
17
|
+
const lines = match[1].split("\n");
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const colonIndex = line.indexOf(":");
|
|
20
|
+
if (colonIndex > 0) {
|
|
21
|
+
const key = line.slice(0, colonIndex).trim();
|
|
22
|
+
data[key] = line.slice(colonIndex + 1).trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
data,
|
|
27
|
+
content: match[2]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get all available skills
|
|
32
|
+
*/
|
|
33
|
+
function getAllSkills() {
|
|
34
|
+
if (!existsSync(SKILLS_DIR)) return [];
|
|
35
|
+
const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
36
|
+
const skills = [];
|
|
37
|
+
for (const dir of skillDirs) {
|
|
38
|
+
const skillPath = join(SKILLS_DIR, dir, "SKILL.md");
|
|
39
|
+
if (!existsSync(skillPath)) continue;
|
|
40
|
+
const { data, content } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
|
|
41
|
+
skills.push({
|
|
42
|
+
slug: dir,
|
|
43
|
+
name: data.name || dir,
|
|
44
|
+
description: data.description || "",
|
|
45
|
+
content,
|
|
46
|
+
path: skillPath
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return skills;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get a single skill by slug
|
|
53
|
+
*/
|
|
54
|
+
function getSkill(slug) {
|
|
55
|
+
const skillPath = join(SKILLS_DIR, slug, "SKILL.md");
|
|
56
|
+
if (!existsSync(skillPath)) return null;
|
|
57
|
+
const { data, content } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
|
|
58
|
+
return {
|
|
59
|
+
slug,
|
|
60
|
+
name: data.name || slug,
|
|
61
|
+
description: data.description || "",
|
|
62
|
+
content,
|
|
63
|
+
path: skillPath
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get skill names only (for listing)
|
|
68
|
+
*/
|
|
69
|
+
function getSkillNames() {
|
|
70
|
+
if (!existsSync(SKILLS_DIR)) return [];
|
|
71
|
+
return readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => existsSync(join(SKILLS_DIR, dirent.name, "SKILL.md"))).map((dirent) => dirent.name);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the path to skills directory
|
|
75
|
+
*/
|
|
76
|
+
function getSkillsDir() {
|
|
77
|
+
return SKILLS_DIR;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { getAllSkills, getSkill, getSkillNames, getSkillsDir };
|
|
82
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["data: Record<string, string>","skills: Skill[]"],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst SKILLS_DIR = join(__dirname, '..', 'skills')\n\nexport interface SkillMetadata {\n name: string\n description: string\n}\n\nexport interface Skill extends SkillMetadata {\n slug: string\n content: string\n path: string\n}\n\n/**\n * Parse SKILL.md frontmatter\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string>; content: string } {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/)\n if (!match) {\n return { data: {}, content }\n }\n\n const data: Record<string, string> = {}\n const lines = match[1].split('\\n')\n for (const line of lines) {\n const colonIndex = line.indexOf(':')\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim()\n const value = line.slice(colonIndex + 1).trim()\n data[key] = value\n }\n }\n\n return { data, content: match[2] }\n}\n\n/**\n * Get all available skills\n */\nexport function getAllSkills(): Skill[] {\n if (!existsSync(SKILLS_DIR)) {\n return []\n }\n\n const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter(dirent => dirent.isDirectory())\n .map(dirent => dirent.name)\n\n const skills: Skill[] = []\n\n for (const dir of skillDirs) {\n const skillPath = join(SKILLS_DIR, dir, 'SKILL.md')\n if (!existsSync(skillPath)) continue\n\n const rawContent = readFileSync(skillPath, 'utf-8')\n const { data, content } = parseFrontmatter(rawContent)\n\n skills.push({\n slug: dir,\n name: data.name || dir,\n description: data.description || '',\n content,\n path: skillPath,\n })\n }\n\n return skills\n}\n\n/**\n * Get a single skill by slug\n */\nexport function getSkill(slug: string): Skill | null {\n const skillPath = join(SKILLS_DIR, slug, 'SKILL.md')\n if (!existsSync(skillPath)) return null\n\n const rawContent = readFileSync(skillPath, 'utf-8')\n const { data, content } = parseFrontmatter(rawContent)\n\n return {\n slug,\n name: data.name || slug,\n description: data.description || '',\n content,\n path: skillPath,\n }\n}\n\n/**\n * Get skill names only (for listing)\n */\nexport function getSkillNames(): string[] {\n if (!existsSync(SKILLS_DIR)) {\n return []\n }\n\n return readdirSync(SKILLS_DIR, { withFileTypes: true })\n .filter(dirent => dirent.isDirectory())\n .filter(dirent => existsSync(join(SKILLS_DIR, dirent.name, 'SKILL.md')))\n .map(dirent => dirent.name)\n}\n\n/**\n * Get the path to skills directory\n */\nexport function getSkillsDir(): string {\n return SKILLS_DIR\n}\n"],"mappings":";;;;;AAKA,MAAM,aAAa,KADD,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACtB,MAAM,SAAS;;;;AAgBlD,SAAS,iBAAiB,SAAoE;CAC5F,MAAM,QAAQ,QAAQ,MAAM,oCAAoC;AAChE,KAAI,CAAC,MACH,QAAO;EAAE,MAAM,EAAE;EAAE;EAAS;CAG9B,MAAMA,OAA+B,EAAE;CACvC,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;AAClC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,KAAK,QAAQ,IAAI;AACpC,MAAI,aAAa,GAAG;GAClB,MAAM,MAAM,KAAK,MAAM,GAAG,WAAW,CAAC,MAAM;AAE5C,QAAK,OADS,KAAK,MAAM,aAAa,EAAE,CAAC,MAAM;;;AAKnD,QAAO;EAAE;EAAM,SAAS,MAAM;EAAI;;;;;AAMpC,SAAgB,eAAwB;AACtC,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,EAAE;CAGX,MAAM,YAAY,YAAY,YAAY,EAAE,eAAe,MAAM,CAAC,CAC/D,QAAO,WAAU,OAAO,aAAa,CAAC,CACtC,KAAI,WAAU,OAAO,KAAK;CAE7B,MAAMC,SAAkB,EAAE;AAE1B,MAAK,MAAM,OAAO,WAAW;EAC3B,MAAM,YAAY,KAAK,YAAY,KAAK,WAAW;AACnD,MAAI,CAAC,WAAW,UAAU,CAAE;EAG5B,MAAM,EAAE,MAAM,YAAY,iBADP,aAAa,WAAW,QAAQ,CACG;AAEtD,SAAO,KAAK;GACV,MAAM;GACN,MAAM,KAAK,QAAQ;GACnB,aAAa,KAAK,eAAe;GACjC;GACA,MAAM;GACP,CAAC;;AAGJ,QAAO;;;;;AAMT,SAAgB,SAAS,MAA4B;CACnD,MAAM,YAAY,KAAK,YAAY,MAAM,WAAW;AACpD,KAAI,CAAC,WAAW,UAAU,CAAE,QAAO;CAGnC,MAAM,EAAE,MAAM,YAAY,iBADP,aAAa,WAAW,QAAQ,CACG;AAEtD,QAAO;EACL;EACA,MAAM,KAAK,QAAQ;EACnB,aAAa,KAAK,eAAe;EACjC;EACA,MAAM;EACP;;;;;AAMH,SAAgB,gBAA0B;AACxC,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,EAAE;AAGX,QAAO,YAAY,YAAY,EAAE,eAAe,MAAM,CAAC,CACpD,QAAO,WAAU,OAAO,aAAa,CAAC,CACtC,QAAO,WAAU,WAAW,KAAK,YAAY,OAAO,MAAM,WAAW,CAAC,CAAC,CACvE,KAAI,WAAU,OAAO,KAAK;;;;;AAM/B,SAAgB,eAAuB;AACrC,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recur-skills",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Claude Code skills for Recur - Taiwan's subscription payment platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"recur",
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
|
-
"import": "./dist/index.
|
|
29
|
-
"types": "./dist/index.d.
|
|
28
|
+
"import": "./dist/index.mjs",
|
|
29
|
+
"types": "./dist/index.d.mts"
|
|
30
30
|
},
|
|
31
31
|
"./skills/*": "./skills/*"
|
|
32
32
|
},
|
|
33
|
-
"main": "./dist/index.
|
|
34
|
-
"types": "./dist/index.d.
|
|
33
|
+
"main": "./dist/index.mjs",
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
35
|
"bin": {
|
|
36
|
-
"recur-skills": "./dist/cli.
|
|
36
|
+
"recur-skills": "./dist/cli.mjs"
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
@@ -41,11 +41,12 @@
|
|
|
41
41
|
".claude-plugin"
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
|
-
"build": "
|
|
45
|
-
"dev": "
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
46
|
"lint": "eslint src/",
|
|
47
47
|
"typecheck": "tsc --noEmit",
|
|
48
|
-
"prepublishOnly": "pnpm build"
|
|
48
|
+
"prepublishOnly": "pnpm build",
|
|
49
|
+
"version": "node scripts/sync-version.js && git add .claude-plugin/marketplace.json"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"commander": "^12.1.0",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/node": "^22.0.0",
|
|
56
|
-
"
|
|
57
|
+
"tsdown": "^0.18.4",
|
|
57
58
|
"typescript": "^5.7.3"
|
|
58
59
|
},
|
|
59
60
|
"engines": {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recur-help
|
|
3
|
+
description: List all available Recur skills and how to use them. Use when user asks "what can Recur do", "Recur skills", "Recur 有什麼功能", "help with Recur", "如何使用 Recur skills".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recur Skills 使用指南
|
|
7
|
+
|
|
8
|
+
當用戶詢問 Recur skills 的功能時,向他們介紹以下可用的 skills:
|
|
9
|
+
|
|
10
|
+
## 可用的 Skills
|
|
11
|
+
|
|
12
|
+
### 1. recur-quickstart
|
|
13
|
+
**用途**:快速開始 Recur 整合
|
|
14
|
+
**觸發方式**:
|
|
15
|
+
- 說:「幫我整合 Recur」「setup Recur」「Recur 串接」「金流設定」
|
|
16
|
+
- 或輸入:`/recur-quickstart`
|
|
17
|
+
|
|
18
|
+
### 2. recur-checkout
|
|
19
|
+
**用途**:實作結帳流程(embedded、modal、redirect)
|
|
20
|
+
**觸發方式**:
|
|
21
|
+
- 說:「加上結帳按鈕」「checkout」「付款按鈕」「embedded checkout」
|
|
22
|
+
- 或輸入:`/recur-checkout`
|
|
23
|
+
|
|
24
|
+
### 3. recur-webhooks
|
|
25
|
+
**用途**:設定 webhook 接收付款通知
|
|
26
|
+
**觸發方式**:
|
|
27
|
+
- 說:「設定 webhook」「付款通知」「訂閱事件」
|
|
28
|
+
- 或輸入:`/recur-webhooks`
|
|
29
|
+
|
|
30
|
+
### 4. recur-entitlements
|
|
31
|
+
**用途**:實作付費功能權限檢查和 Paywall
|
|
32
|
+
**觸發方式**:
|
|
33
|
+
- 說:「檢查付費權限」「paywall」「權限檢查」「entitlements」
|
|
34
|
+
- 或輸入:`/recur-entitlements`
|
|
35
|
+
|
|
36
|
+
## 回覆格式
|
|
37
|
+
|
|
38
|
+
用繁體中文回覆,格式如下:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
## Recur Skills 使用指南
|
|
42
|
+
|
|
43
|
+
我可以幫你完成以下 Recur 整合任務:
|
|
44
|
+
|
|
45
|
+
| Skill | 用途 | 怎麼觸發 |
|
|
46
|
+
|-------|------|---------|
|
|
47
|
+
| **quickstart** | 快速開始整合 | 說「幫我整合 Recur」 |
|
|
48
|
+
| **checkout** | 結帳流程 | 說「加上結帳按鈕」 |
|
|
49
|
+
| **webhooks** | 付款通知 | 說「設定 webhook」 |
|
|
50
|
+
| **entitlements** | 權限檢查 | 說「檢查付費權限」 |
|
|
51
|
+
|
|
52
|
+
💡 **建議從 quickstart 開始**,它會引導你完成 SDK 安裝和基本設定。
|
|
53
|
+
|
|
54
|
+
有什麼想做的嗎?
|
|
55
|
+
```
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "recur-skills",
|
|
3
|
-
"description": "Claude Code skills for integrating Recur - Taiwan's subscription payment platform. Includes checkout, webhooks, and entitlements.",
|
|
4
|
-
"version": "0.0.1",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "Recur",
|
|
7
|
-
"email": "hi@recur.tw",
|
|
8
|
-
"url": "https://recur.tw"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "https://github.com/recur-tw/skills"
|
|
13
|
-
},
|
|
14
|
-
"homepage": "https://recur.tw/skills",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"keywords": [
|
|
17
|
-
"recur",
|
|
18
|
-
"payments",
|
|
19
|
-
"subscription",
|
|
20
|
-
"taiwan",
|
|
21
|
-
"checkout",
|
|
22
|
-
"webhooks",
|
|
23
|
-
"entitlements"
|
|
24
|
-
]
|
|
25
|
-
}
|
package/dist/cli.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import { program } from "commander";
|
|
5
|
-
import pc from "picocolors";
|
|
6
|
-
import { existsSync as existsSync2, mkdirSync, cpSync, readdirSync as readdirSync2 } from "fs";
|
|
7
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
8
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
|
-
|
|
10
|
-
// src/index.ts
|
|
11
|
-
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
12
|
-
import { join, dirname } from "path";
|
|
13
|
-
import { fileURLToPath } from "url";
|
|
14
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
var SKILLS_DIR = join(__dirname, "..", "skills");
|
|
16
|
-
function parseFrontmatter(content) {
|
|
17
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
18
|
-
if (!match) {
|
|
19
|
-
return { data: {}, content };
|
|
20
|
-
}
|
|
21
|
-
const data = {};
|
|
22
|
-
const lines = match[1].split("\n");
|
|
23
|
-
for (const line of lines) {
|
|
24
|
-
const colonIndex = line.indexOf(":");
|
|
25
|
-
if (colonIndex > 0) {
|
|
26
|
-
const key = line.slice(0, colonIndex).trim();
|
|
27
|
-
const value = line.slice(colonIndex + 1).trim();
|
|
28
|
-
data[key] = value;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return { data, content: match[2] };
|
|
32
|
-
}
|
|
33
|
-
function getAllSkills() {
|
|
34
|
-
if (!existsSync(SKILLS_DIR)) {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
38
|
-
const skills = [];
|
|
39
|
-
for (const dir of skillDirs) {
|
|
40
|
-
const skillPath = join(SKILLS_DIR, dir, "SKILL.md");
|
|
41
|
-
if (!existsSync(skillPath)) continue;
|
|
42
|
-
const rawContent = readFileSync(skillPath, "utf-8");
|
|
43
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
44
|
-
skills.push({
|
|
45
|
-
slug: dir,
|
|
46
|
-
name: data.name || dir,
|
|
47
|
-
description: data.description || "",
|
|
48
|
-
content,
|
|
49
|
-
path: skillPath
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return skills;
|
|
53
|
-
}
|
|
54
|
-
function getSkill(slug) {
|
|
55
|
-
const skillPath = join(SKILLS_DIR, slug, "SKILL.md");
|
|
56
|
-
if (!existsSync(skillPath)) return null;
|
|
57
|
-
const rawContent = readFileSync(skillPath, "utf-8");
|
|
58
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
59
|
-
return {
|
|
60
|
-
slug,
|
|
61
|
-
name: data.name || slug,
|
|
62
|
-
description: data.description || "",
|
|
63
|
-
content,
|
|
64
|
-
path: skillPath
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function getSkillsDir() {
|
|
68
|
-
return SKILLS_DIR;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/cli.ts
|
|
72
|
-
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
73
|
-
var SKILLS_SOURCE = join2(__dirname2, "..", "skills");
|
|
74
|
-
program.name("recur-skills").description("Claude Code skills for Recur payment integration").version("0.0.1");
|
|
75
|
-
program.command("list").description("List all available skills").action(() => {
|
|
76
|
-
const skills = getAllSkills();
|
|
77
|
-
if (skills.length === 0) {
|
|
78
|
-
console.log(pc.yellow("No skills found."));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
console.log(pc.bold("\n\u{1F4E6} Available Recur Skills\n"));
|
|
82
|
-
for (const skill of skills) {
|
|
83
|
-
console.log(pc.cyan(` ${skill.name}`));
|
|
84
|
-
console.log(pc.dim(` ${skill.description}
|
|
85
|
-
`));
|
|
86
|
-
}
|
|
87
|
-
console.log(pc.dim(`Total: ${skills.length} skills
|
|
88
|
-
`));
|
|
89
|
-
});
|
|
90
|
-
program.command("info <skill>").description("Show detailed information about a skill").action((skillName) => {
|
|
91
|
-
const skill = getSkill(skillName);
|
|
92
|
-
if (!skill) {
|
|
93
|
-
console.log(pc.red(`Skill "${skillName}" not found.`));
|
|
94
|
-
console.log(pc.dim("\nRun `recur-skills list` to see available skills."));
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
console.log(pc.bold(`
|
|
98
|
-
\u{1F4E6} ${skill.name}
|
|
99
|
-
`));
|
|
100
|
-
console.log(pc.dim("Description:"));
|
|
101
|
-
console.log(` ${skill.description}
|
|
102
|
-
`);
|
|
103
|
-
console.log(pc.dim("Path:"));
|
|
104
|
-
console.log(` ${skill.path}
|
|
105
|
-
`);
|
|
106
|
-
});
|
|
107
|
-
program.command("install [skills...]").description("Install skills to your Claude Code skills directory").option("-g, --global", "Install to global ~/.claude/skills/", false).option("-p, --project", "Install to project .claude/skills/", false).option("-a, --all", "Install all skills", false).action((skillNames, options) => {
|
|
108
|
-
let targetDir;
|
|
109
|
-
if (options.global) {
|
|
110
|
-
targetDir = join2(process.env.HOME || "~", ".claude", "skills");
|
|
111
|
-
} else if (options.project) {
|
|
112
|
-
targetDir = join2(process.cwd(), ".claude", "skills");
|
|
113
|
-
} else {
|
|
114
|
-
targetDir = join2(process.env.HOME || "~", ".claude", "skills");
|
|
115
|
-
}
|
|
116
|
-
let toInstall;
|
|
117
|
-
if (options.all) {
|
|
118
|
-
toInstall = readdirSync2(SKILLS_SOURCE, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
119
|
-
} else if (skillNames.length === 0) {
|
|
120
|
-
console.log(pc.yellow("Please specify skills to install or use --all"));
|
|
121
|
-
console.log(pc.dim("\nExamples:"));
|
|
122
|
-
console.log(pc.dim(" recur-skills install recur-quickstart"));
|
|
123
|
-
console.log(pc.dim(" recur-skills install recur-checkout recur-webhooks"));
|
|
124
|
-
console.log(pc.dim(" recur-skills install --all"));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
} else {
|
|
127
|
-
toInstall = skillNames;
|
|
128
|
-
}
|
|
129
|
-
if (!existsSync2(targetDir)) {
|
|
130
|
-
mkdirSync(targetDir, { recursive: true });
|
|
131
|
-
}
|
|
132
|
-
console.log(pc.bold(`
|
|
133
|
-
\u{1F4E6} Installing skills to ${targetDir}
|
|
134
|
-
`));
|
|
135
|
-
let installed = 0;
|
|
136
|
-
for (const skillName of toInstall) {
|
|
137
|
-
const sourcePath = join2(SKILLS_SOURCE, skillName);
|
|
138
|
-
const destPath = join2(targetDir, skillName);
|
|
139
|
-
if (!existsSync2(sourcePath)) {
|
|
140
|
-
console.log(pc.red(` \u2717 ${skillName} - not found`));
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
cpSync(sourcePath, destPath, { recursive: true });
|
|
145
|
-
console.log(pc.green(` \u2713 ${skillName}`));
|
|
146
|
-
installed++;
|
|
147
|
-
} catch (error) {
|
|
148
|
-
console.log(pc.red(` \u2717 ${skillName} - ${error}`));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
console.log(pc.dim(`
|
|
152
|
-
Installed ${installed}/${toInstall.length} skills
|
|
153
|
-
`));
|
|
154
|
-
if (installed > 0) {
|
|
155
|
-
console.log(pc.cyan("Skills are now available in Claude Code!"));
|
|
156
|
-
console.log(pc.dim("Claude will automatically use them when relevant,"));
|
|
157
|
-
console.log(pc.dim("or you can invoke them directly with /skill-name\n"));
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
program.command("path").description("Show the path to the skills directory").action(() => {
|
|
161
|
-
console.log(getSkillsDir());
|
|
162
|
-
});
|
|
163
|
-
program.parse();
|
package/dist/index.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
3
|
-
import { join, dirname } from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
var SKILLS_DIR = join(__dirname, "..", "skills");
|
|
7
|
-
function parseFrontmatter(content) {
|
|
8
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
9
|
-
if (!match) {
|
|
10
|
-
return { data: {}, content };
|
|
11
|
-
}
|
|
12
|
-
const data = {};
|
|
13
|
-
const lines = match[1].split("\n");
|
|
14
|
-
for (const line of lines) {
|
|
15
|
-
const colonIndex = line.indexOf(":");
|
|
16
|
-
if (colonIndex > 0) {
|
|
17
|
-
const key = line.slice(0, colonIndex).trim();
|
|
18
|
-
const value = line.slice(colonIndex + 1).trim();
|
|
19
|
-
data[key] = value;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return { data, content: match[2] };
|
|
23
|
-
}
|
|
24
|
-
function getAllSkills() {
|
|
25
|
-
if (!existsSync(SKILLS_DIR)) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
const skillDirs = readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
29
|
-
const skills = [];
|
|
30
|
-
for (const dir of skillDirs) {
|
|
31
|
-
const skillPath = join(SKILLS_DIR, dir, "SKILL.md");
|
|
32
|
-
if (!existsSync(skillPath)) continue;
|
|
33
|
-
const rawContent = readFileSync(skillPath, "utf-8");
|
|
34
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
35
|
-
skills.push({
|
|
36
|
-
slug: dir,
|
|
37
|
-
name: data.name || dir,
|
|
38
|
-
description: data.description || "",
|
|
39
|
-
content,
|
|
40
|
-
path: skillPath
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
return skills;
|
|
44
|
-
}
|
|
45
|
-
function getSkill(slug) {
|
|
46
|
-
const skillPath = join(SKILLS_DIR, slug, "SKILL.md");
|
|
47
|
-
if (!existsSync(skillPath)) return null;
|
|
48
|
-
const rawContent = readFileSync(skillPath, "utf-8");
|
|
49
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
50
|
-
return {
|
|
51
|
-
slug,
|
|
52
|
-
name: data.name || slug,
|
|
53
|
-
description: data.description || "",
|
|
54
|
-
content,
|
|
55
|
-
path: skillPath
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function getSkillNames() {
|
|
59
|
-
if (!existsSync(SKILLS_DIR)) {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
return readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).filter((dirent) => existsSync(join(SKILLS_DIR, dirent.name, "SKILL.md"))).map((dirent) => dirent.name);
|
|
63
|
-
}
|
|
64
|
-
function getSkillsDir() {
|
|
65
|
-
return SKILLS_DIR;
|
|
66
|
-
}
|
|
67
|
-
export {
|
|
68
|
-
getAllSkills,
|
|
69
|
-
getSkill,
|
|
70
|
-
getSkillNames,
|
|
71
|
-
getSkillsDir
|
|
72
|
-
};
|