recur-skills 0.0.1

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.
@@ -0,0 +1,25 @@
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/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Recur Skills for Claude Code
2
+
3
+ Claude Code skills to help developers integrate [Recur](https://recur.tw) - Taiwan's subscription payment platform.
4
+
5
+ ## Installation
6
+
7
+ ### Claude Code Plugin (Recommended)
8
+
9
+ ```bash
10
+ /plugin add recur-tw/skills
11
+ ```
12
+
13
+ ### npm CLI
14
+
15
+ ```bash
16
+ # Install all skills globally
17
+ npx recur-skills install --all --global
18
+
19
+ # Install specific skills
20
+ npx recur-skills install recur-quickstart recur-webhooks
21
+
22
+ # Install to current project
23
+ npx recur-skills install --all --project
24
+ ```
25
+
26
+ ### Manual Installation
27
+
28
+ Copy skills to your Claude Code skills directory:
29
+
30
+ ```bash
31
+ # Global (all projects)
32
+ cp -r skills/* ~/.claude/skills/
33
+
34
+ # Project-specific
35
+ cp -r skills/* .claude/skills/
36
+ ```
37
+
38
+ ## Available Skills
39
+
40
+ ### recur-quickstart
41
+
42
+ Quick setup guide for Recur payment integration.
43
+
44
+ **Triggers:** "integrate Recur", "setup Recur", "Recur 串接", "金流設定"
45
+
46
+ - SDK installation
47
+ - API key configuration
48
+ - Basic provider setup
49
+ - First checkout implementation
50
+
51
+ ### recur-checkout
52
+
53
+ Implement Recur checkout flows.
54
+
55
+ **Triggers:** "checkout", "結帳", "付款按鈕", "embedded checkout"
56
+
57
+ - Embedded, modal, and redirect modes
58
+ - useRecur and useSubscribe hooks
59
+ - Product types (subscription, one-time, credits, donation)
60
+ - Payment error handling
61
+ - 3D verification
62
+
63
+ ### recur-webhooks
64
+
65
+ Set up and handle Recur webhook events.
66
+
67
+ **Triggers:** "webhook", "付款通知", "訂閱事件", "payment notification"
68
+
69
+ - All webhook event types
70
+ - Signature verification
71
+ - Next.js and Express handlers
72
+ - Testing webhooks locally
73
+ - Idempotency handling
74
+
75
+ ### recur-entitlements
76
+
77
+ Implement access control and permission checking.
78
+
79
+ **Triggers:** "paywall", "權限檢查", "entitlements", "access control"
80
+
81
+ - useCustomer hook
82
+ - Cached vs live checks
83
+ - Paywall components
84
+ - Server-side verification
85
+ - Handling subscription statuses
86
+
87
+ ## Usage
88
+
89
+ Once installed, Claude will automatically use these skills when you're working on Recur integration tasks.
90
+
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
+ ## Links
121
+
122
+ - [Recur Website](https://recur.tw)
123
+ - [Documentation](https://recur.tw/docs)
124
+ - [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
+
131
+ ## License
132
+
133
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,163 @@
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();
@@ -0,0 +1,27 @@
1
+ interface SkillMetadata {
2
+ name: string;
3
+ description: string;
4
+ }
5
+ interface Skill extends SkillMetadata {
6
+ slug: string;
7
+ content: string;
8
+ path: string;
9
+ }
10
+ /**
11
+ * Get all available skills
12
+ */
13
+ declare function getAllSkills(): Skill[];
14
+ /**
15
+ * Get a single skill by slug
16
+ */
17
+ declare function getSkill(slug: string): Skill | null;
18
+ /**
19
+ * Get skill names only (for listing)
20
+ */
21
+ declare function getSkillNames(): string[];
22
+ /**
23
+ * Get the path to skills directory
24
+ */
25
+ declare function getSkillsDir(): string;
26
+
27
+ export { type Skill, type SkillMetadata, getAllSkills, getSkill, getSkillNames, getSkillsDir };
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
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
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "recur-skills",
3
+ "version": "0.0.1",
4
+ "description": "Claude Code skills for Recur - Taiwan's subscription payment platform",
5
+ "keywords": [
6
+ "recur",
7
+ "claude-code",
8
+ "skills",
9
+ "subscription",
10
+ "payments",
11
+ "taiwan",
12
+ "agent-skills"
13
+ ],
14
+ "homepage": "https://recur.tw/skills",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/recur-tw/skills"
18
+ },
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "Recur",
22
+ "email": "hi@recur.tw",
23
+ "url": "https://recur.tw"
24
+ },
25
+ "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "import": "./dist/index.js",
29
+ "types": "./dist/index.d.ts"
30
+ },
31
+ "./skills/*": "./skills/*"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "bin": {
36
+ "recur-skills": "./dist/cli.js"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "skills",
41
+ ".claude-plugin"
42
+ ],
43
+ "dependencies": {
44
+ "commander": "^12.1.0",
45
+ "picocolors": "^1.1.1"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.0.0",
49
+ "tsup": "^8.3.5",
50
+ "typescript": "^5.7.3"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "dev": "tsup --watch",
61
+ "lint": "eslint src/",
62
+ "typecheck": "tsc --noEmit"
63
+ }
64
+ }