@lythos/skill-deck 0.11.2 → 0.13.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 +17 -17
- package/package.json +3 -3
- package/src/add.ts +19 -0
- package/src/cli.ts +9 -1
- package/src/refresh.ts +79 -5
- package/src/resolve-deck.ts +1 -1
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.13.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.13.0 add mattpocock/skills@tdd
|
|
15
15
|
|
|
16
16
|
# Or FQ locator:
|
|
17
|
-
bunx @lythos/skill-deck@0.
|
|
17
|
+
bunx @lythos/skill-deck@0.13.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.13.0 link
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## For AI Agents
|
|
@@ -25,7 +25,7 @@ bunx @lythos/skill-deck@0.11.2 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.13.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.13.0 link` |
|
|
77
|
+
| Validate `skill-deck.toml` before committing | `bunx @lythos/skill-deck@0.13.0 validate` |
|
|
78
|
+
| Download a skill to cold pool and add to deck | `bunx @lythos/skill-deck@0.13.0 add owner/repo` |
|
|
79
|
+
| Pull latest versions of declared skills | `bunx @lythos/skill-deck@0.13.0 refresh` |
|
|
80
|
+
| Refresh a single skill by alias | `bunx @lythos/skill-deck@0.13.0 refresh tdd` |
|
|
81
|
+
| Remove a skill from deck and working set | `bunx @lythos/skill-deck@0.13.0 remove tdd` |
|
|
82
|
+
| Switch skill to symlink mode (live) | `bunx @lythos/skill-deck@0.13.0 to-symlink tdd` |
|
|
83
|
+
| Switch skill to snapshot mode (pinned) | `bunx @lythos/skill-deck@0.13.0 to-snapshot tdd` |
|
|
84
|
+
| Use a custom deck file or working dir | `bunx @lythos/skill-deck@0.13.0 link --deck ./my-deck.toml --workdir /path/to/project` |
|
|
85
85
|
|
|
86
86
|
### Commands
|
|
87
87
|
|
|
@@ -90,7 +90,7 @@ prompt = "Search for latest info, then generate professional document with diagr
|
|
|
90
90
|
| `link` | `[--deck <path>] [--workdir <dir>]` | Sync working set. Removes undeclared skills (deny-by-default). |
|
|
91
91
|
| `validate` | `[deck.toml] [--workdir <dir>]` | Validate deck config without modifying files. |
|
|
92
92
|
| `add` | `<locator> [--alias <alias>] [--type <type>] [--deck <path>]` | Add skill to cold pool + deck.toml. Accepts skills.sh syntax (owner/repo, owner/repo@skill, github:owner/repo) and FQ locators. |
|
|
93
|
-
| `refresh` | `[<fq\|alias>] [--deck <path>]` |
|
|
93
|
+
| `refresh` | `[<fq\|alias>] [--deck <path>] [--exec]` | Scan declared skills for upstream updates (plan-only by default). Add `--exec` to actually pull. Agent-driven apply preferred. |
|
|
94
94
|
| `remove` | `<fq\|alias> [--deck <path>]` | Remove skill from deck.toml and working set. Cold pool untouched. |
|
|
95
95
|
| `to-symlink` | `<alias> [--deck <path>] [--workdir <dir>]` | Switch a skill to symlink mode (live link, follows cold pool) |
|
|
96
96
|
| `to-snapshot` | `<alias> [--deck <path>] [--workdir <dir>]` | Switch a skill to snapshot mode (pinned cp of current HEAD) |
|
|
@@ -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.13.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.13.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.13.0",
|
|
4
4
|
"description": "Declarative skill deck governance — cold pool, working set, deny-by-default",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@iarna/toml": "^2.2.5",
|
|
34
|
-
"@lythos/cold-pool": "^0.
|
|
35
|
-
"@lythos/infra": "^0.
|
|
34
|
+
"@lythos/cold-pool": "^0.13.0",
|
|
35
|
+
"@lythos/infra": "^0.13.0",
|
|
36
36
|
"yaml": "^2.8.3",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|
package/src/add.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
hashSkillMd,
|
|
29
29
|
type Locator,
|
|
30
30
|
} from '@lythos/cold-pool'
|
|
31
|
+
import { probeConnectivity } from '@lythos/cold-pool/src/mirror.js'
|
|
31
32
|
import { findDeckToml, expandHome } from './link.js'
|
|
32
33
|
import { validateAlias } from './path-guard.js'
|
|
33
34
|
|
|
@@ -263,6 +264,24 @@ export async function addSkill(
|
|
|
263
264
|
// git clone needs the parent of the target dir (e.g. host/owner/) to exist
|
|
264
265
|
mkdirSync(dirname(fetchPlan.targetDir), { recursive: true })
|
|
265
266
|
|
|
267
|
+
// ── Plan→Apply boundary: probe network before any git operation ────────
|
|
268
|
+
if (fetchPlan.cloneUrl) {
|
|
269
|
+
const probe = await probeConnectivity(fetchPlan.cloneUrl, 5000)
|
|
270
|
+
if (!probe) {
|
|
271
|
+
console.error(`❌ Cannot reach ${fetchPlan.cloneUrl}`)
|
|
272
|
+
console.error(` Network probe failed — the host may be unreachable or blocked.`)
|
|
273
|
+
console.error(``)
|
|
274
|
+
console.error(` To fix:`)
|
|
275
|
+
console.error(` export LYTHOSKILL_GH_MIRROR="https://your-mirror.com"`)
|
|
276
|
+
console.error(` # Or set LYTHOS_SOCKS_PROXY for SOCKS5 routing`)
|
|
277
|
+
console.error(` # See: AGENTS.md → Network Restrictions`)
|
|
278
|
+
process.exit(1)
|
|
279
|
+
}
|
|
280
|
+
if (probe.path === 'mirror') {
|
|
281
|
+
console.log(`🪞 Using mirror: ${probe.url} (${probe.latencyMs}ms)`)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
266
285
|
// Note: if cold pool already has the repo (fetchPlan.alreadyExists),
|
|
267
286
|
// executeFetchPlan returns status: 'already-present' and skips clone.
|
|
268
287
|
// We still want to write deck.toml + link this skill — critical for
|
package/src/cli.ts
CHANGED
|
@@ -49,6 +49,7 @@ const format = flagValue('--format')
|
|
|
49
49
|
const noBackup = args.includes('--no-backup')
|
|
50
50
|
const dryRun = args.includes('--dry-run')
|
|
51
51
|
const remote = args.includes('--remote')
|
|
52
|
+
const execFlag = args.includes('--exec')
|
|
52
53
|
const mode = flagValue('--mode') as 'symlink' | 'snapshot' | undefined
|
|
53
54
|
|
|
54
55
|
const HELP_CONFIG = {
|
|
@@ -76,9 +77,16 @@ const HELP_CONFIG = {
|
|
|
76
77
|
{ flag: '--yes', description: 'Skip interactive confirmation' },
|
|
77
78
|
{ flag: '--remote', description: 'For validate: probe each FQ locator against api.github.com' },
|
|
78
79
|
{ flag: '--format <text|json>', description: 'For validate: output format (default: text)' },
|
|
80
|
+
{ flag: '--exec', description: 'For refresh: execute git pull instead of printing plan-only' },
|
|
79
81
|
],
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
// --help / -h anywhere in the arg list triggers help (not treated as a positional arg or ignored)
|
|
85
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
86
|
+
console.log(formatHelp(HELP_CONFIG))
|
|
87
|
+
process.exit(0)
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
switch (command) {
|
|
83
91
|
case '--help':
|
|
84
92
|
case '-h':
|
|
@@ -98,7 +106,7 @@ switch (command) {
|
|
|
98
106
|
}
|
|
99
107
|
case 'refresh': {
|
|
100
108
|
const refreshTarget = args[1] && !args[1].startsWith('-') ? args[1] : undefined
|
|
101
|
-
refreshDeck(deckPath, workdir, refreshTarget)
|
|
109
|
+
await refreshDeck(deckPath, workdir, refreshTarget, execFlag)
|
|
102
110
|
break
|
|
103
111
|
}
|
|
104
112
|
case 'update': {
|
package/src/refresh.ts
CHANGED
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* deck-refresh.ts — Refresh declared skills from their upstream sources
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Default: discover-only — outputs a structured plan, never executes git pull.
|
|
6
|
+
* With `exec: true` — executes the plan (git pull + linkDeck).
|
|
7
|
+
*
|
|
8
|
+
* Agent-driven apply: the agent reads the plan, decides what to pull,
|
|
9
|
+
* and can probe / retry / fix per target. Not a dead heredoc script.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
13
|
import { resolve } from "node:path";
|
|
12
14
|
import { gitPull } from "@lythos/cold-pool";
|
|
15
|
+
import { probeConnectivity } from "@lythos/cold-pool/src/mirror.js";
|
|
13
16
|
import { findDeckToml, linkDeck } from "./link.js";
|
|
14
17
|
import { parseDeck } from "./parse-deck.js";
|
|
15
18
|
import { buildRefreshPlan, detectGitRoot, executeRefreshPlan } from "./refresh-plan.js";
|
|
@@ -20,7 +23,26 @@ export function findGitRoot(dir: string, coldPool: string): string | null {
|
|
|
20
23
|
return result.gitRoot ?? null
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
/** Quick behind-count probe for a single git root. Pure, no mutation. */
|
|
27
|
+
async function probeBehindCount(gitRoot: string): Promise<number | undefined> {
|
|
28
|
+
try {
|
|
29
|
+
const { execSync } = await import("node:child_process");
|
|
30
|
+
execSync("git fetch --depth=1 origin", { cwd: gitRoot, timeout: 5000, stdio: "pipe" });
|
|
31
|
+
const count = execSync("git rev-list HEAD...@{upstream} --count", {
|
|
32
|
+
cwd: gitRoot, timeout: 3000, encoding: "utf-8", stdio: "pipe",
|
|
33
|
+
}).trim();
|
|
34
|
+
return parseInt(count, 10);
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function refreshDeck(
|
|
41
|
+
cliDeckPath?: string,
|
|
42
|
+
cliWorkdir?: string,
|
|
43
|
+
target?: string,
|
|
44
|
+
exec = false,
|
|
45
|
+
): Promise<void> {
|
|
24
46
|
const deckPath = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
|
|
25
47
|
const workdir = cliWorkdir
|
|
26
48
|
|
|
@@ -59,7 +81,59 @@ export function refreshDeck(cliDeckPath?: string, cliWorkdir?: string, target?:
|
|
|
59
81
|
process.exit(1)
|
|
60
82
|
}
|
|
61
83
|
|
|
62
|
-
// ──
|
|
84
|
+
// ── Discover: enrich plan with behind counts ─────────────────────
|
|
85
|
+
const enriched = await Promise.all(
|
|
86
|
+
plan.targets.map(async (t) => {
|
|
87
|
+
if (t.type !== 'git' || !t.gitRoot) return { ...t, behind: undefined };
|
|
88
|
+
const behind = await probeBehindCount(t.gitRoot);
|
|
89
|
+
return { ...t, behind };
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// ── Default path: print plan, do NOT pull ──────────────────────
|
|
94
|
+
if (!exec) {
|
|
95
|
+
console.log(`📋 Refresh Plan — ${enriched.length} skill(s)`)
|
|
96
|
+
console.log(``)
|
|
97
|
+
for (const t of enriched) {
|
|
98
|
+
const behindStr = t.behind === undefined ? '?' : t.behind > 0 ? `${t.behind} behind` : 'up to date';
|
|
99
|
+
switch (t.type) {
|
|
100
|
+
case 'git':
|
|
101
|
+
console.log(`🔄 ${t.alias} ${t.path} (${behindStr})`)
|
|
102
|
+
break
|
|
103
|
+
case 'localhost':
|
|
104
|
+
console.log(`📁 ${t.alias} ${t.path} (localhost — user-managed)`)
|
|
105
|
+
break
|
|
106
|
+
case 'missing':
|
|
107
|
+
console.log(`❌ ${t.alias} ${t.path} (missing from cold pool)`)
|
|
108
|
+
break
|
|
109
|
+
case 'not-git':
|
|
110
|
+
console.log(`📁 ${t.alias} ${t.path} (not a git repo)`)
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log(``)
|
|
115
|
+
console.log(`💡 To apply this plan: deck refresh --exec`)
|
|
116
|
+
console.log(` Or let an agent read this plan and execute per target with probe + retry.`)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Exec path: agent/human has explicitly opted in ─────────────
|
|
121
|
+
// ── Plan→Apply boundary: probe network before any git pull ─────
|
|
122
|
+
const gitTargets = plan.targets.filter(t => t.type === 'git')
|
|
123
|
+
if (gitTargets.length > 0) {
|
|
124
|
+
const firstTarget = gitTargets[0]!
|
|
125
|
+
const probeUrl = `https://${firstTarget.path}`
|
|
126
|
+
const probe = await probeConnectivity(probeUrl, 5000)
|
|
127
|
+
if (!probe) {
|
|
128
|
+
console.error(`⚠️ Network probe failed for ${probeUrl}`)
|
|
129
|
+
console.error(` Refresh may fail for git targets. To fix:`)
|
|
130
|
+
console.error(` export LYTHOSKILL_GH_MIRROR="https://your-mirror.com"`)
|
|
131
|
+
console.error(` # Or set LYTHOS_SOCKS_PROXY for SOCKS5 routing`)
|
|
132
|
+
console.error(` Continuing anyway — per-target errors will be reported below.`)
|
|
133
|
+
console.error()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
63
137
|
const results = executeRefreshPlan(plan, {
|
|
64
138
|
gitPull,
|
|
65
139
|
log: console.log,
|
package/src/resolve-deck.ts
CHANGED
|
@@ -82,7 +82,7 @@ export async function fetchDeckUrl(url: string, io?: FetchDeckIO): Promise<strin
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
if (!res?.ok) {
|
|
85
|
-
throw new Error(`Failed to fetch deck: ${normalized}\n
|
|
85
|
+
throw new Error(`Failed to fetch deck: ${normalized}\n Set LYTHOSKILL_GH_MIRROR to use a custom mirror, or LYTHOS_SOCKS_PROXY for SOCKS5.`)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
writeFn(dest, await res.text())
|