@tickpick/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/README.md +42 -0
- package/cli.mjs +226 -0
- package/package.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @tickpick/skills
|
|
2
|
+
|
|
3
|
+
Install Claude Code skills shared by the TickPick PM team.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# List available skills
|
|
9
|
+
npx @tickpick/skills list
|
|
10
|
+
|
|
11
|
+
# Install a skill (project-local)
|
|
12
|
+
npx @tickpick/skills add write-prd
|
|
13
|
+
|
|
14
|
+
# Install globally (available in all projects)
|
|
15
|
+
npx @tickpick/skills add write-prd --global
|
|
16
|
+
|
|
17
|
+
# Update all installed skills
|
|
18
|
+
npx @tickpick/skills update
|
|
19
|
+
|
|
20
|
+
# Remove a skill
|
|
21
|
+
npx @tickpick/skills remove write-prd
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Using installed skills
|
|
25
|
+
|
|
26
|
+
Once installed, skills are available in Claude Code via the `/` command:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/write-prd Payment confirmation redesign
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
By default, skills are fetched from the TickPick PM Companion registry. Override with:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Environment variable
|
|
38
|
+
SKILLS_REGISTRY_URL=https://your-app.vercel.app/api/skills/registry npx @tickpick/skills list
|
|
39
|
+
|
|
40
|
+
# CLI flag
|
|
41
|
+
npx @tickpick/skills list --registry=https://your-app.vercel.app/api/skills/registry
|
|
42
|
+
```
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_REGISTRY =
|
|
8
|
+
process.env.SKILLS_REGISTRY_URL || "https://pm-companion.vercel.app/api/skills/registry";
|
|
9
|
+
|
|
10
|
+
const MANIFEST_FILE = ".skills-manifest.json";
|
|
11
|
+
|
|
12
|
+
// ─── Helpers ─────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
async function fetchRegistry(registryUrl) {
|
|
15
|
+
const res = await fetch(registryUrl);
|
|
16
|
+
if (!res.ok) throw new Error(`Failed to fetch registry: ${res.status}`);
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
return data.skills;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function fetchSkillMd(registryUrl, slug) {
|
|
22
|
+
const res = await fetch(`${registryUrl}/${slug}`);
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
if (res.status === 404) return null;
|
|
25
|
+
throw new Error(`Failed to fetch skill "${slug}": ${res.status}`);
|
|
26
|
+
}
|
|
27
|
+
return res.text();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getSkillsDir(global) {
|
|
31
|
+
return global
|
|
32
|
+
? join(homedir(), ".claude", "skills")
|
|
33
|
+
: join(process.cwd(), ".claude", "skills");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getManifestPath(global) {
|
|
37
|
+
return global
|
|
38
|
+
? join(homedir(), ".claude", MANIFEST_FILE)
|
|
39
|
+
: join(process.cwd(), ".claude", MANIFEST_FILE);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readManifest(global) {
|
|
43
|
+
const path = getManifestPath(global);
|
|
44
|
+
if (!existsSync(path)) return { registry: DEFAULT_REGISTRY, installed: {} };
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
47
|
+
} catch {
|
|
48
|
+
return { registry: DEFAULT_REGISTRY, installed: {} };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeManifest(global, manifest) {
|
|
53
|
+
const path = getManifestPath(global);
|
|
54
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
55
|
+
writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function installSkill(slug, content, global) {
|
|
59
|
+
const dir = join(getSkillsDir(global), slug);
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
writeFileSync(join(dir, "SKILL.md"), content);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Commands ────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
async function cmdList(registryUrl) {
|
|
67
|
+
const skills = await fetchRegistry(registryUrl);
|
|
68
|
+
if (skills.length === 0) {
|
|
69
|
+
console.log("No skills available.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log("\nAvailable skills:\n");
|
|
74
|
+
const maxSlug = Math.max(...skills.map((s) => s.slug.length));
|
|
75
|
+
for (const s of skills) {
|
|
76
|
+
console.log(` ${s.slug.padEnd(maxSlug + 2)}${s.description}`);
|
|
77
|
+
}
|
|
78
|
+
console.log(`\n${skills.length} skill${skills.length !== 1 ? "s" : ""} available.`);
|
|
79
|
+
console.log("Install with: npx @tickpick/skills add <name>\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function cmdAdd(slug, registryUrl, global) {
|
|
83
|
+
const content = await fetchSkillMd(registryUrl, slug);
|
|
84
|
+
if (!content) {
|
|
85
|
+
console.error(`Error: skill "${slug}" not found.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
installSkill(slug, content, global);
|
|
90
|
+
|
|
91
|
+
// Update manifest
|
|
92
|
+
const manifest = readManifest(global);
|
|
93
|
+
manifest.registry = registryUrl;
|
|
94
|
+
if (!manifest.installed) manifest.installed = {};
|
|
95
|
+
manifest.installed[slug] = { updatedAt: new Date().toISOString() };
|
|
96
|
+
writeManifest(global, manifest);
|
|
97
|
+
|
|
98
|
+
const target = resolve(getSkillsDir(global), slug, "SKILL.md");
|
|
99
|
+
console.log(`✓ Installed ${slug} → ${target}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function cmdUpdate(registryUrl, global) {
|
|
103
|
+
const manifest = readManifest(global);
|
|
104
|
+
const installed = Object.keys(manifest.installed || {});
|
|
105
|
+
|
|
106
|
+
if (installed.length === 0) {
|
|
107
|
+
console.log("No skills installed. Use `npx @tickpick/skills add <name>` first.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const url = registryUrl || manifest.registry || DEFAULT_REGISTRY;
|
|
112
|
+
const skills = await fetchRegistry(url);
|
|
113
|
+
const skillMap = Object.fromEntries(skills.map((s) => [s.slug, s]));
|
|
114
|
+
|
|
115
|
+
let updated = 0;
|
|
116
|
+
for (const slug of installed) {
|
|
117
|
+
const remote = skillMap[slug];
|
|
118
|
+
if (!remote) {
|
|
119
|
+
console.log(` ${slug} ⚠ no longer in registry`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const localDate = manifest.installed[slug]?.updatedAt;
|
|
124
|
+
if (localDate && new Date(remote.updatedAt) <= new Date(localDate)) {
|
|
125
|
+
console.log(` ${slug} · unchanged`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const content = await fetchSkillMd(url, slug);
|
|
130
|
+
if (content) {
|
|
131
|
+
installSkill(slug, content, global);
|
|
132
|
+
manifest.installed[slug] = { updatedAt: remote.updatedAt };
|
|
133
|
+
console.log(` ${slug} ✓ updated`);
|
|
134
|
+
updated++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
writeManifest(global, manifest);
|
|
139
|
+
console.log(`\n${updated} skill${updated !== 1 ? "s" : ""} updated.`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function cmdRemove(slug, global) {
|
|
143
|
+
const dir = join(getSkillsDir(global), slug);
|
|
144
|
+
if (!existsSync(dir)) {
|
|
145
|
+
console.error(`Error: skill "${slug}" is not installed.`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
rmSync(dir, { recursive: true });
|
|
150
|
+
|
|
151
|
+
const manifest = readManifest(global);
|
|
152
|
+
if (manifest.installed) {
|
|
153
|
+
delete manifest.installed[slug];
|
|
154
|
+
writeManifest(global, manifest);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`✓ Removed ${slug}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── CLI entry point ─────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
const args = process.argv.slice(2);
|
|
163
|
+
const command = args[0];
|
|
164
|
+
const isGlobal = args.includes("--global") || args.includes("-g");
|
|
165
|
+
const registryFlag = args.find((a) => a.startsWith("--registry="));
|
|
166
|
+
const registryUrl = registryFlag
|
|
167
|
+
? registryFlag.split("=").slice(1).join("=")
|
|
168
|
+
: DEFAULT_REGISTRY;
|
|
169
|
+
|
|
170
|
+
// Filter out flags to get positional args
|
|
171
|
+
const positional = args.filter(
|
|
172
|
+
(a) => !a.startsWith("--") && !a.startsWith("-g"),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
switch (command) {
|
|
176
|
+
case "list":
|
|
177
|
+
case "ls":
|
|
178
|
+
await cmdList(registryUrl);
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
case "add":
|
|
182
|
+
case "install":
|
|
183
|
+
if (!positional[1]) {
|
|
184
|
+
console.error("Usage: npx @tickpick/skills add <skill-name>");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
await cmdAdd(positional[1], registryUrl, isGlobal);
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case "update":
|
|
191
|
+
case "upgrade":
|
|
192
|
+
await cmdUpdate(registryUrl, isGlobal);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case "remove":
|
|
196
|
+
case "rm":
|
|
197
|
+
if (!positional[1]) {
|
|
198
|
+
console.error("Usage: npx @tickpick/skills remove <skill-name>");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
await cmdRemove(positional[1], isGlobal);
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
console.log(`
|
|
206
|
+
@tickpick/skills — Claude Code skills for the TickPick PM team
|
|
207
|
+
|
|
208
|
+
Commands:
|
|
209
|
+
list Show available skills
|
|
210
|
+
add <name> Install a skill to .claude/skills/
|
|
211
|
+
add <name> --global Install to ~/.claude/skills/ (all projects)
|
|
212
|
+
update Re-fetch all installed skills
|
|
213
|
+
remove <name> Remove an installed skill
|
|
214
|
+
|
|
215
|
+
Options:
|
|
216
|
+
--global, -g Use global (~/.claude/) instead of project-local
|
|
217
|
+
--registry=<url> Override the registry URL
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
npx @tickpick/skills list
|
|
221
|
+
npx @tickpick/skills add write-prd
|
|
222
|
+
npx @tickpick/skills add sprint-retro --global
|
|
223
|
+
npx @tickpick/skills update
|
|
224
|
+
`);
|
|
225
|
+
break;
|
|
226
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tickpick/skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install Claude Code skills shared by the TickPick PM team",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"skills": "./cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.mjs"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"skills",
|
|
15
|
+
"tickpick"
|
|
16
|
+
],
|
|
17
|
+
"license": "UNLICENSED",
|
|
18
|
+
"private": false
|
|
19
|
+
}
|