@gallopsystems/agent-skills 1.0.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 +137 -0
- package/package.json +26 -0
- package/plugins/doctl/.claude-plugin/plugin.json +8 -0
- package/plugins/doctl/skills/doctl/SKILL.md +93 -0
- package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
- package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
- package/plugins/linear/.claude-plugin/plugin.json +8 -0
- package/plugins/linear/skills/linear/SKILL.md +1040 -0
- package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
- package/plugins/linear/skills/linear/tech-stack.md +273 -0
- package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
- package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
- package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
- package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
- package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
- package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
- package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
- package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
- package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
- package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
- package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
- package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
- package/scripts/link-skills.mjs +252 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Postinstall: symlink this package's skills and commands into the consuming
|
|
3
|
+
// project's .claude directory, so a `yarn add` / `yarn install` keeps the
|
|
4
|
+
// project in sync with this package's version:
|
|
5
|
+
//
|
|
6
|
+
// skills -> .claude/skills/<name> (a dir containing SKILL.md)
|
|
7
|
+
// commands -> .claude/commands/<rel>.md (a .md file under any commands/ dir)
|
|
8
|
+
//
|
|
9
|
+
// This runs during `yarn install`, so it MUST NOT throw — a thrown error aborts
|
|
10
|
+
// the whole install. Every failure path here degrades to a warning + exit 0.
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
// scripts/link-skills.mjs -> package root
|
|
17
|
+
const PKG_DIR = path.resolve(fileURLToPath(import.meta.url), '..', '..')
|
|
18
|
+
const PKG_DIR_REAL = realOrSelf(PKG_DIR)
|
|
19
|
+
const UNLINK = process.argv.includes('--unlink')
|
|
20
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '.claude'])
|
|
21
|
+
|
|
22
|
+
const log = (msg) => console.log(`[claude-skills] ${msg}`)
|
|
23
|
+
const warn = (msg) => console.warn(`[claude-skills] ${msg}`)
|
|
24
|
+
|
|
25
|
+
function realOrSelf(p) {
|
|
26
|
+
try {
|
|
27
|
+
return fs.realpathSync(p)
|
|
28
|
+
} catch {
|
|
29
|
+
return p
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Resolve the project that pulled us in. npm and Yarn (Classic + Berry) set
|
|
34
|
+
// INIT_CWD to the directory where the install command ran — the project root,
|
|
35
|
+
// or the workspace root in a monorepo, which is exactly where the shared
|
|
36
|
+
// .claude directory should live. Fall back to climbing out of node_modules.
|
|
37
|
+
function resolveProjectRoot() {
|
|
38
|
+
// INIT_CWD may equal PKG_DIR when installing in this repo itself; main()
|
|
39
|
+
// detects that and skips, so return it unconditionally here.
|
|
40
|
+
if (process.env.INIT_CWD) return path.resolve(process.env.INIT_CWD)
|
|
41
|
+
|
|
42
|
+
const marker = `${path.sep}node_modules${path.sep}`
|
|
43
|
+
const idx = PKG_DIR.indexOf(marker)
|
|
44
|
+
if (idx !== -1) return PKG_DIR.slice(0, idx)
|
|
45
|
+
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Skills: every directory that directly contains a SKILL.md. Skills don't nest,
|
|
50
|
+
// so stop descending once one is found. Returns Map<name, sourceDir>.
|
|
51
|
+
function collectSkills(root) {
|
|
52
|
+
const found = new Map()
|
|
53
|
+
const walk = (dir) => {
|
|
54
|
+
let entries
|
|
55
|
+
try {
|
|
56
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
57
|
+
} catch {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (entries.some((e) => e.isFile() && e.name === 'SKILL.md')) {
|
|
61
|
+
add(found, path.basename(dir), dir, 'skill')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
for (const e of entries) {
|
|
65
|
+
if (e.isDirectory() && !SKIP_DIRS.has(e.name)) walk(path.join(dir, e.name))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
walk(root)
|
|
69
|
+
return found
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Commands: every .md file under any directory named `commands/`, keyed by its
|
|
73
|
+
// path relative to that commands dir (so commands/git/sync.md -> git/sync.md).
|
|
74
|
+
// Returns Map<relPath, sourceFile>.
|
|
75
|
+
function collectCommands(root) {
|
|
76
|
+
const found = new Map()
|
|
77
|
+
const harvest = (cmdDir) => {
|
|
78
|
+
const walk = (dir) => {
|
|
79
|
+
let entries
|
|
80
|
+
try {
|
|
81
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
82
|
+
} catch {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
for (const e of entries) {
|
|
86
|
+
const p = path.join(dir, e.name)
|
|
87
|
+
if (e.isDirectory()) walk(p)
|
|
88
|
+
else if (e.isFile() && e.name.endsWith('.md')) add(found, path.relative(cmdDir, p), p, 'command')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
walk(cmdDir)
|
|
92
|
+
}
|
|
93
|
+
const walk = (dir) => {
|
|
94
|
+
let entries
|
|
95
|
+
try {
|
|
96
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
97
|
+
} catch {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
for (const e of entries) {
|
|
101
|
+
if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
|
|
102
|
+
const p = path.join(dir, e.name)
|
|
103
|
+
if (e.name === 'commands') harvest(p)
|
|
104
|
+
else walk(p)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
walk(root)
|
|
108
|
+
return found
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function add(map, key, src, kind) {
|
|
112
|
+
if (map.has(key)) {
|
|
113
|
+
warn(`duplicate ${kind} "${key}" (${src}); keeping ${map.get(key)}.`)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
map.set(key, src)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// True only for symlinks whose real target lives inside this package — i.e.
|
|
120
|
+
// links we created. Used so cleanup never touches the user's own files.
|
|
121
|
+
function isOwnedLink(linkPath) {
|
|
122
|
+
let raw
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.lstatSync(linkPath).isSymbolicLink()) return false
|
|
125
|
+
raw = fs.readlinkSync(linkPath)
|
|
126
|
+
} catch {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
const target = realOrSelf(path.resolve(path.dirname(linkPath), raw))
|
|
130
|
+
return target === PKG_DIR_REAL || target.startsWith(PKG_DIR_REAL + path.sep)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Recursively remove owned links under root whose rel path isn't wanted, then
|
|
134
|
+
// prune directories that we emptied out. Never recurses into a symlink, so it
|
|
135
|
+
// won't follow a linked skill dir back into the package.
|
|
136
|
+
function removeStaleLinks(root, keepRels) {
|
|
137
|
+
const walk = (dir) => {
|
|
138
|
+
let entries
|
|
139
|
+
try {
|
|
140
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
141
|
+
} catch {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
for (const e of entries) {
|
|
145
|
+
const p = path.join(dir, e.name)
|
|
146
|
+
if (e.isSymbolicLink()) {
|
|
147
|
+
if (!keepRels.has(path.relative(root, p)) && isOwnedLink(p)) {
|
|
148
|
+
try {
|
|
149
|
+
fs.rmSync(p, { force: true })
|
|
150
|
+
} catch (err) {
|
|
151
|
+
warn(`could not remove stale link "${path.relative(root, p)}": ${err.message}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else if (e.isDirectory()) {
|
|
155
|
+
walk(p)
|
|
156
|
+
try {
|
|
157
|
+
if (fs.readdirSync(p).length === 0) fs.rmdirSync(p)
|
|
158
|
+
} catch {
|
|
159
|
+
// not empty or not ours — leave it
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
walk(root)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Link every wanted item into destRoot, refreshing existing owned links and
|
|
168
|
+
// never clobbering a real path the user authored. Returns the count linked.
|
|
169
|
+
function linkInto(destRoot, wanted, kind) {
|
|
170
|
+
const exists = fs.existsSync(destRoot)
|
|
171
|
+
|
|
172
|
+
// Nothing to link: only tidy up previously-created links (if the dir exists),
|
|
173
|
+
// so we never create an empty .claude/<kind> directory in every consumer.
|
|
174
|
+
if (wanted.size === 0) {
|
|
175
|
+
if (exists) removeStaleLinks(realOrSelf(destRoot), new Set())
|
|
176
|
+
return 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fs.mkdirSync(destRoot, { recursive: true })
|
|
180
|
+
// Realpath the link root so relative targets stay clean even when the project
|
|
181
|
+
// lives under a symlinked path (PKG_DIR is realpath-resolved via
|
|
182
|
+
// import.meta.url; a mismatch blows targets up to a ../../../ chain to root).
|
|
183
|
+
const root = realOrSelf(destRoot)
|
|
184
|
+
removeStaleLinks(root, new Set(wanted.keys()))
|
|
185
|
+
|
|
186
|
+
let linked = 0
|
|
187
|
+
for (const [rel, src] of wanted) {
|
|
188
|
+
const dest = path.join(root, rel)
|
|
189
|
+
let existing = null
|
|
190
|
+
try {
|
|
191
|
+
existing = fs.lstatSync(dest)
|
|
192
|
+
} catch {
|
|
193
|
+
// doesn't exist yet
|
|
194
|
+
}
|
|
195
|
+
if (existing) {
|
|
196
|
+
if (existing.isSymbolicLink()) {
|
|
197
|
+
fs.rmSync(dest, { force: true }) // refresh the target
|
|
198
|
+
} else {
|
|
199
|
+
warn(`"${rel}" already exists in .claude/${kind} as a real path; leaving it untouched.`)
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const isDir = (() => {
|
|
204
|
+
try {
|
|
205
|
+
return fs.statSync(src).isDirectory()
|
|
206
|
+
} catch {
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
})()
|
|
210
|
+
try {
|
|
211
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true }) // for nested command paths
|
|
212
|
+
fs.symlinkSync(path.relative(path.dirname(dest), src), dest, isDir ? 'dir' : 'file')
|
|
213
|
+
linked++
|
|
214
|
+
} catch (err) {
|
|
215
|
+
warn(`failed to link "${rel}": ${err.message}`)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return linked
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function main() {
|
|
222
|
+
const projectRoot = resolveProjectRoot()
|
|
223
|
+
if (!projectRoot) {
|
|
224
|
+
warn('could not determine the project root (INIT_CWD unset); skipping.')
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
if (realOrSelf(projectRoot) === PKG_DIR_REAL && !process.env.CLAUDE_SKILLS_LINK_SELF) {
|
|
228
|
+
log('installed in its own repo; skipping self-link (set CLAUDE_SKILLS_LINK_SELF=1 to override).')
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const skillsRoot = path.join(projectRoot, '.claude', 'skills')
|
|
233
|
+
const commandsRoot = path.join(projectRoot, '.claude', 'commands')
|
|
234
|
+
|
|
235
|
+
if (UNLINK) {
|
|
236
|
+
for (const root of [skillsRoot, commandsRoot]) {
|
|
237
|
+
if (fs.existsSync(root)) removeStaleLinks(realOrSelf(root), new Set())
|
|
238
|
+
}
|
|
239
|
+
log('removed managed symlinks from .claude/skills and .claude/commands.')
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const skills = linkInto(skillsRoot, collectSkills(PKG_DIR), 'skills')
|
|
244
|
+
const commands = linkInto(commandsRoot, collectCommands(PKG_DIR), 'commands')
|
|
245
|
+
log(`linked ${skills} skill${skills === 1 ? '' : 's'} and ${commands} command${commands === 1 ? '' : 's'} into .claude/`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
main()
|
|
250
|
+
} catch (err) {
|
|
251
|
+
warn(`unexpected error, skipping: ${err && err.message}`)
|
|
252
|
+
}
|