@lythos/skill-deck 0.9.51 → 0.11.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 +16 -16
- package/package.json +3 -2
- package/src/add.test.ts +18 -0
- package/src/refresh-plan.ts +6 -1
- package/src/resolve-deck.test.ts +45 -0
- package/src/resolve-deck.ts +35 -7
package/README.md
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
# Add a skill from skills.sh (owner/repo syntax — no conversion needed)
|
|
11
|
-
bunx @lythos/skill-deck@0.
|
|
11
|
+
bunx @lythos/skill-deck@0.11.0 add vercel-labs/agent-skills
|
|
12
12
|
|
|
13
13
|
# Or with @skill filter (same as npx skills add):
|
|
14
|
-
bunx @lythos/skill-deck@0.
|
|
14
|
+
bunx @lythos/skill-deck@0.11.0 add mattpocock/skills@tdd
|
|
15
15
|
|
|
16
16
|
# Or FQ locator:
|
|
17
|
-
bunx @lythos/skill-deck@0.
|
|
17
|
+
bunx @lythos/skill-deck@0.11.0 add github.com/anthropics/skills/skills/frontend-design
|
|
18
18
|
|
|
19
19
|
# Sync working set (deny-by-default):
|
|
20
|
-
bunx @lythos/skill-deck@0.
|
|
20
|
+
bunx @lythos/skill-deck@0.11.0 link
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## For AI Agents
|
|
@@ -25,7 +25,7 @@ bunx @lythos/skill-deck@0.9.51 link
|
|
|
25
25
|
This package exposes a **CLI**. Invoke via:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
bunx @lythos/skill-deck@0.
|
|
28
|
+
bunx @lythos/skill-deck@0.11.0 <command> [options]
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
No installation required. `bunx` auto-downloads the package.
|
|
@@ -73,15 +73,15 @@ prompt = "Search for latest info, then generate professional document with diagr
|
|
|
73
73
|
|
|
74
74
|
| Situation | Command |
|
|
75
75
|
|-----------|---------|
|
|
76
|
-
| Sync working set with `skill-deck.toml` | `bunx @lythos/skill-deck@0.
|
|
77
|
-
| Validate `skill-deck.toml` before committing | `bunx @lythos/skill-deck@0.
|
|
78
|
-
| Download a skill to cold pool and add to deck | `bunx @lythos/skill-deck@0.
|
|
79
|
-
| Pull latest versions of declared skills | `bunx @lythos/skill-deck@0.
|
|
80
|
-
| Refresh a single skill by alias | `bunx @lythos/skill-deck@0.
|
|
81
|
-
| Remove a skill from deck and working set | `bunx @lythos/skill-deck@0.
|
|
82
|
-
| Switch skill to symlink mode (live) | `bunx @lythos/skill-deck@0.
|
|
83
|
-
| Switch skill to snapshot mode (pinned) | `bunx @lythos/skill-deck@0.
|
|
84
|
-
| Use a custom deck file or working dir | `bunx @lythos/skill-deck@0.
|
|
76
|
+
| Sync working set with `skill-deck.toml` | `bunx @lythos/skill-deck@0.11.0 link` |
|
|
77
|
+
| Validate `skill-deck.toml` before committing | `bunx @lythos/skill-deck@0.11.0 validate` |
|
|
78
|
+
| Download a skill to cold pool and add to deck | `bunx @lythos/skill-deck@0.11.0 add owner/repo` |
|
|
79
|
+
| Pull latest versions of declared skills | `bunx @lythos/skill-deck@0.11.0 refresh` |
|
|
80
|
+
| Refresh a single skill by alias | `bunx @lythos/skill-deck@0.11.0 refresh tdd` |
|
|
81
|
+
| Remove a skill from deck and working set | `bunx @lythos/skill-deck@0.11.0 remove tdd` |
|
|
82
|
+
| Switch skill to symlink mode (live) | `bunx @lythos/skill-deck@0.11.0 to-symlink tdd` |
|
|
83
|
+
| Switch skill to snapshot mode (pinned) | `bunx @lythos/skill-deck@0.11.0 to-snapshot tdd` |
|
|
84
|
+
| Use a custom deck file or working dir | `bunx @lythos/skill-deck@0.11.0 link --deck ./my-deck.toml --workdir /path/to/project` |
|
|
85
85
|
|
|
86
86
|
### Commands
|
|
87
87
|
|
|
@@ -143,7 +143,7 @@ source = "https://github.com/lythos-labs/lythoskill/blob/HEAD/skills/lythoskill-
|
|
|
143
143
|
EOF
|
|
144
144
|
|
|
145
145
|
# 2. Link — creates symlinks in .claude/skills/
|
|
146
|
-
bunx @lythos/skill-deck@0.
|
|
146
|
+
bunx @lythos/skill-deck@0.11.0 link
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
### Key Concepts
|
|
@@ -229,7 +229,7 @@ Caution: deck's deny-by-default will remove any skills not declared in your deck
|
|
|
229
229
|
|
|
230
230
|
| Symptom | Cause | Fix |
|
|
231
231
|
|---------|-------|-----|
|
|
232
|
-
| `❌ Skill not found: <name>` | Skill declared in deck but not in cold pool | `bunx @lythos/skill-deck@0.
|
|
232
|
+
| `❌ Skill not found: <name>` | Skill declared in deck but not in cold pool | `bunx @lythos/skill-deck@0.11.0 add github.com/owner/repo/skill` or clone manually into cold pool |
|
|
233
233
|
| `link` skips entries with warnings | Real files/directories exist in working set (not symlinks) | Delete the real directories in `working_set` and re-run `link`. Never create directories manually there |
|
|
234
234
|
| `refresh` reports "Not a git repository" | Skill was copied (not cloned) into cold pool | Re-clone with `git clone` or use `deck add` which clones by default |
|
|
235
235
|
| `deck update` prints deprecation warning | `update` was renamed to `refresh` in v0.8+ | Use `deck refresh` instead |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lythos/skill-deck",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Declarative skill deck governance — cold pool, working set, deny-by-default",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@iarna/toml": "^2.2.5",
|
|
34
|
-
"@lythos/cold-pool": "
|
|
34
|
+
"@lythos/cold-pool": "workspace:*",
|
|
35
|
+
"@lythos/infra": "workspace:*",
|
|
35
36
|
"yaml": "^2.8.3",
|
|
36
37
|
"zod": "^4.3.6"
|
|
37
38
|
},
|
package/src/add.test.ts
CHANGED
|
@@ -107,6 +107,24 @@ describe('normalizeSkillsSh', () => {
|
|
|
107
107
|
.toBe('github.com/mattpocock/skills')
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
+
// baoyu-skills @skill syntax (largest personal skill pack, 17,900+ stars)
|
|
111
|
+
it('normalizes JimLiu/baoyu-skills@baoyu-image-cards', () => {
|
|
112
|
+
const r = normalizeSkillsSh('JimLiu/baoyu-skills@baoyu-image-cards')
|
|
113
|
+
expect(r.fq).toBe('github.com/JimLiu/baoyu-skills')
|
|
114
|
+
expect(r.skillFilter).toBe('baoyu-image-cards')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('normalizes jimliu/baoyu-skills@baoyu-infographic (case insensitive)', () => {
|
|
118
|
+
const r = normalizeSkillsSh('jimliu/baoyu-skills@baoyu-infographic')
|
|
119
|
+
expect(r.fq).toBe('github.com/jimliu/baoyu-skills')
|
|
120
|
+
expect(r.skillFilter).toBe('baoyu-infographic')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('normalizes baoyu-skills subpath form', () => {
|
|
124
|
+
expect(normalizeSkillsSh('JimLiu/baoyu-skills/skills/baoyu-diagram').fq)
|
|
125
|
+
.toBe('github.com/JimLiu/baoyu-skills/skills/baoyu-diagram')
|
|
126
|
+
})
|
|
127
|
+
|
|
110
128
|
// subpath
|
|
111
129
|
it('normalizes owner/repo/subpath', () => {
|
|
112
130
|
expect(normalizeSkillsSh('anthropics/skills/skills/frontend-design').fq)
|
package/src/refresh-plan.ts
CHANGED
|
@@ -78,7 +78,12 @@ export function detectGitRoot(skillDir: string, coldPool: string): { gitRoot?: s
|
|
|
78
78
|
(resolvedRoot === resolvedPool || resolvedRoot.startsWith(resolvedPool + '/'))) {
|
|
79
79
|
return { gitRoot: out, type: 'git' }
|
|
80
80
|
}
|
|
81
|
-
} catch {
|
|
81
|
+
} catch (e: any) {
|
|
82
|
+
// git rev-parse fails for non-git dirs (expected) — log unexpected failures
|
|
83
|
+
if (!e.message?.includes('not a git repository')) {
|
|
84
|
+
console.warn(`detectGitRoot: git rev-parse failed for ${skillDir}: ${e.message}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
82
87
|
|
|
83
88
|
return { type: 'not-git' }
|
|
84
89
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test'
|
|
2
|
+
import { fetchDeckUrl, type FetchDeckIO } from './resolve-deck.js'
|
|
3
|
+
|
|
4
|
+
describe('fetchDeckUrl', () => {
|
|
5
|
+
const written: Array<{ path: string; content: string }> = []
|
|
6
|
+
|
|
7
|
+
function createMockIO(partial?: Partial<FetchDeckIO>): FetchDeckIO {
|
|
8
|
+
return {
|
|
9
|
+
existsSync: () => false,
|
|
10
|
+
writeFileSync: (path: string, content: string) => {
|
|
11
|
+
written.push({ path, content })
|
|
12
|
+
},
|
|
13
|
+
fetch: async () => new Response('deck content'),
|
|
14
|
+
...partial,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
written.length = 0
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('fetch happy path: writes file on success', async () => {
|
|
23
|
+
const io = createMockIO()
|
|
24
|
+
await fetchDeckUrl('https://example.com/deck.toml', io)
|
|
25
|
+
expect(written).toHaveLength(1)
|
|
26
|
+
expect(written[0].content).toBe('deck content')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('with proxy: intercepts through fetch deps', async () => {
|
|
30
|
+
const io = createMockIO({
|
|
31
|
+
fetch: async () => new Response('proxy deck content'),
|
|
32
|
+
})
|
|
33
|
+
await fetchDeckUrl('https://example.com/deck.toml', io)
|
|
34
|
+
expect(written).toHaveLength(1)
|
|
35
|
+
expect(written[0].content).toBe('proxy deck content')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('file already exists: refuses to overwrite', async () => {
|
|
39
|
+
const io = createMockIO({ existsSync: () => true })
|
|
40
|
+
await expect(fetchDeckUrl('https://example.com/deck.toml', io)).rejects.toThrow(
|
|
41
|
+
/Refusing to overwrite/,
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
})
|
package/src/resolve-deck.ts
CHANGED
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
* T1 of EPIC-20260508082810062 (Everything-from-URL).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { existsSync as _existsSync, writeFileSync as _writeFileSync } from 'node:fs'
|
|
12
12
|
import { resolve } from 'node:path'
|
|
13
|
+
import { fetchWithProxy } from '@lythos/infra'
|
|
14
|
+
import { mirrorUrls } from '../../lythoskill-cold-pool/src/mirror.js'
|
|
13
15
|
import { findDeckToml } from './link.js'
|
|
14
16
|
|
|
17
|
+
export interface FetchDeckIO {
|
|
18
|
+
fetch?: typeof fetchWithProxy
|
|
19
|
+
existsSync?: typeof _existsSync
|
|
20
|
+
writeFileSync?: typeof _writeFileSync
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
export interface ResolvedDeck {
|
|
16
24
|
path: string
|
|
17
25
|
source: 'url' | 'local' | 'default'
|
|
@@ -41,10 +49,14 @@ export function resolveDeckPathSync(cliArg?: string): ResolvedDeck {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
/** Async: fetch URL deck, save to cwd, return local path. */
|
|
44
|
-
export async function fetchDeckUrl(url: string): Promise<string> {
|
|
52
|
+
export async function fetchDeckUrl(url: string, io?: FetchDeckIO): Promise<string> {
|
|
53
|
+
const fetchFn = io?.fetch ?? fetchWithProxy
|
|
54
|
+
const existsFn = io?.existsSync ?? _existsSync
|
|
55
|
+
const writeFn = io?.writeFileSync ?? _writeFileSync
|
|
56
|
+
|
|
45
57
|
const normalized = normalizeUrl(url)
|
|
46
58
|
const dest = resolve(process.cwd(), 'skill-deck.toml')
|
|
47
|
-
if (
|
|
59
|
+
if (existsFn(dest)) {
|
|
48
60
|
throw new Error(
|
|
49
61
|
`Refusing to overwrite existing ${dest}.\n` +
|
|
50
62
|
` A skill-deck.toml already exists in this directory.\n` +
|
|
@@ -53,11 +65,27 @@ export async function fetchDeckUrl(url: string): Promise<string> {
|
|
|
53
65
|
)
|
|
54
66
|
}
|
|
55
67
|
console.log(`📥 Fetching deck: ${normalized}`)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
|
|
69
|
+
let res: Response | undefined
|
|
70
|
+
try {
|
|
71
|
+
res = await fetchFn(normalized, { signal: AbortSignal.timeout(30_000) })
|
|
72
|
+
} catch {}
|
|
73
|
+
|
|
74
|
+
if (!res?.ok) {
|
|
75
|
+
for (const mirrorUrl of mirrorUrls(normalized)) {
|
|
76
|
+
try {
|
|
77
|
+
console.log(` ↳ trying mirror: ${mirrorUrl}`)
|
|
78
|
+
const r = await fetchFn(mirrorUrl, { signal: AbortSignal.timeout(30_000) })
|
|
79
|
+
if (r.ok) { res = r; break }
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
59
82
|
}
|
|
60
|
-
|
|
83
|
+
|
|
84
|
+
if (!res?.ok) {
|
|
85
|
+
throw new Error(`Failed to fetch deck: ${normalized}\n All mirrors exhausted. Set LYTHOSKILL_GH_MIRROR to use a custom mirror, or LYTHOS_SOCKS_PROXY for SOCKS5.`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
writeFn(dest, await res.text())
|
|
61
89
|
console.log(` → saved to ${dest}`)
|
|
62
90
|
return dest
|
|
63
91
|
}
|