@dealdeploy/skl 0.4.0 → 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/add.ts +55 -64
- package/index.ts +110 -972
- package/lib.test.ts +110 -41
- package/lib.ts +125 -38
- package/package.json +1 -1
- package/tui.test.ts +565 -0
- package/tui.ts +612 -0
- package/update.ts +102 -87
package/update.ts
CHANGED
|
@@ -1,105 +1,120 @@
|
|
|
1
|
-
|
|
1
|
+
import { rmSync, mkdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { readLock, writeLock, catalogDir, downloadSkillFiles } from "./lib.ts";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { homedir } from "os";
|
|
6
|
-
import { findOrphanSkills, adoptSkills } from "./lib.ts";
|
|
5
|
+
const lock = readLock();
|
|
6
|
+
const remoteSkills = Object.entries(lock.skills).filter(([, e]) => e.source && e.treeSHA);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!existsSync(LIBRARY)) {
|
|
11
|
-
console.error(`skl: library not found at ${LIBRARY}`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const localDirs: [string, string][] = [];
|
|
16
|
-
const agentsDir = join(process.cwd(), ".agents/skills");
|
|
17
|
-
if (existsSync(agentsDir)) localDirs.push([agentsDir, ".agents/skills"]);
|
|
18
|
-
|
|
19
|
-
const claudeDir = join(process.cwd(), ".claude/skills");
|
|
20
|
-
if (existsSync(claudeDir)) localDirs.push([claudeDir, ".claude/skills"]);
|
|
21
|
-
|
|
22
|
-
if (localDirs.length === 0) {
|
|
23
|
-
console.log("No local skill directories found.");
|
|
8
|
+
if (remoteSkills.length === 0) {
|
|
9
|
+
console.log("No remote skills to update.");
|
|
24
10
|
process.exit(0);
|
|
25
11
|
}
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
13
|
+
// Get GitHub token
|
|
14
|
+
let token = "";
|
|
15
|
+
try {
|
|
16
|
+
const proc = Bun.spawn(["gh", "auth", "token"], { stdout: "pipe", stderr: "pipe" });
|
|
17
|
+
token = (await new Response(proc.stdout).text()).trim();
|
|
18
|
+
await proc.exited;
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
const headers: Record<string, string> = {
|
|
22
|
+
Accept: "application/vnd.github+json",
|
|
23
|
+
};
|
|
24
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
25
|
+
|
|
26
|
+
// Group skills by source repo
|
|
27
|
+
const byRepo = new Map<string, { name: string; skillPath: string; treeSHA: string }[]>();
|
|
28
|
+
for (const [name, entry] of remoteSkills) {
|
|
29
|
+
const list = byRepo.get(entry.source) ?? [];
|
|
30
|
+
list.push({ name, skillPath: entry.skillPath, treeSHA: entry.treeSHA });
|
|
31
|
+
byRepo.set(entry.source, list);
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const localPath = join(s.dir, s.name);
|
|
60
|
-
unlinkSync(localPath);
|
|
61
|
-
cpSync(join(LIBRARY, s.name), localPath, { recursive: true });
|
|
62
|
-
console.log(` converted ${s.label}/${s.name}`);
|
|
34
|
+
let updated = 0;
|
|
35
|
+
let upToDate = 0;
|
|
36
|
+
let errors = 0;
|
|
37
|
+
const CATALOG = catalogDir();
|
|
38
|
+
|
|
39
|
+
for (const [repo, skills] of byRepo) {
|
|
40
|
+
process.stdout.write(`Checking ${repo}...`);
|
|
41
|
+
|
|
42
|
+
// Get default branch
|
|
43
|
+
let branch: string;
|
|
44
|
+
try {
|
|
45
|
+
const repoResp = await fetch(`https://api.github.com/repos/${repo}`, { headers });
|
|
46
|
+
if (!repoResp.ok) {
|
|
47
|
+
console.log(` failed (${repoResp.status})`);
|
|
48
|
+
errors += skills.length;
|
|
49
|
+
continue;
|
|
63
50
|
}
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
const repoData = (await repoResp.json()) as { default_branch: string };
|
|
52
|
+
branch = repoData.default_branch;
|
|
53
|
+
} catch (e: any) {
|
|
54
|
+
console.log(` failed (${e.message})`);
|
|
55
|
+
errors += skills.length;
|
|
56
|
+
continue;
|
|
66
57
|
}
|
|
67
|
-
}
|
|
68
58
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
// Fetch full tree
|
|
60
|
+
let tree: { path: string; type: string; sha: string }[];
|
|
61
|
+
try {
|
|
62
|
+
const treeResp = await fetch(
|
|
63
|
+
`https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1`,
|
|
64
|
+
{ headers },
|
|
65
|
+
);
|
|
66
|
+
if (!treeResp.ok) {
|
|
67
|
+
console.log(` failed (${treeResp.status})`);
|
|
68
|
+
errors += skills.length;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const treeData = (await treeResp.json()) as { tree: { path: string; type: string; sha: string }[] };
|
|
72
|
+
tree = treeData.tree;
|
|
73
|
+
} catch (e: any) {
|
|
74
|
+
console.log(` failed (${e.message})`);
|
|
75
|
+
errors += skills.length;
|
|
76
|
+
continue;
|
|
77
77
|
}
|
|
78
|
-
}
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
console.log("Nothing to update.");
|
|
82
|
-
}
|
|
79
|
+
console.log("");
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.log(` ${o.dir.replace(homedir(), "~")}/${o.name}`);
|
|
89
|
-
}
|
|
90
|
-
process.stdout.write(`\nCopy to ${LIBRARY.replace(homedir(), "~")}? [y/N] `);
|
|
81
|
+
for (const skill of skills) {
|
|
82
|
+
const remoteEntry = tree.find(
|
|
83
|
+
(e) => e.path === skill.skillPath && e.type === "tree"
|
|
84
|
+
);
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
if (!remoteEntry) {
|
|
87
|
+
console.log(` ${skill.name}: not found in remote tree (skipped)`);
|
|
88
|
+
errors++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.log(` copied ${r.name}`);
|
|
92
|
+
if (remoteEntry.sha === skill.treeSHA) {
|
|
93
|
+
upToDate++;
|
|
94
|
+
continue;
|
|
100
95
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
|
|
97
|
+
// SHA differs — re-download
|
|
98
|
+
const skillDir = join(CATALOG, skill.name);
|
|
99
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
100
|
+
mkdirSync(skillDir, { recursive: true });
|
|
101
|
+
|
|
102
|
+
process.stdout.write(` ${skill.name}...`);
|
|
103
|
+
const fileCount = await downloadSkillFiles(repo, branch, skill.skillPath, skillDir, tree);
|
|
104
|
+
|
|
105
|
+
// Update lock entry
|
|
106
|
+
lock.skills[skill.name]!.treeSHA = remoteEntry.sha;
|
|
107
|
+
console.log(` updated (${fileCount} files)`);
|
|
108
|
+
updated++;
|
|
104
109
|
}
|
|
105
110
|
}
|
|
111
|
+
|
|
112
|
+
if (updated > 0) {
|
|
113
|
+
writeLock(lock);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parts: string[] = [];
|
|
117
|
+
if (updated > 0) parts.push(`Updated ${updated} skill(s)`);
|
|
118
|
+
if (upToDate > 0) parts.push(`${upToDate} already up to date`);
|
|
119
|
+
if (errors > 0) parts.push(`${errors} error(s)`);
|
|
120
|
+
console.log(`\n${parts.join(", ")}`);
|