@lythos/skill-deck 0.7.0 → 0.7.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lythos/skill-deck",
3
- "version": "0.7.0",
4
- "description": "Declarative skill deck governance \u2014 cold pool, working set, deny-by-default",
3
+ "version": "0.7.1",
4
+ "description": "Declarative skill deck governance cold pool, working set, deny-by-default",
5
5
  "keywords": [
6
6
  "ai-agent",
7
7
  "skill",
package/src/cli.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import { linkDeck } from './link.js'
3
3
  import { validateDeck } from './validate.js'
4
4
  import { addSkill } from './add.js'
5
+ import { updateDeck } from './update.js'
5
6
  import { formatHelp } from './help.js'
6
7
 
7
8
  const HELP_CONFIG = {
@@ -10,6 +11,7 @@ const HELP_CONFIG = {
10
11
  commands: [
11
12
  { name: 'link', description: 'Sync working set with skill-deck.toml' },
12
13
  { name: 'add', description: 'Download skill to cold pool and add to deck', args: '<locator>' },
14
+ { name: 'update', description: 'Pull latest versions of declared skills from upstream' },
13
15
  { name: 'validate', description: 'Validate deck configuration', args: '[deck.toml]' },
14
16
  ],
15
17
  options: [
@@ -49,6 +51,9 @@ switch (command) {
49
51
  await addSkill(locator, { via, deck: deckPath, workdir })
50
52
  break
51
53
  }
54
+ case 'update':
55
+ updateDeck(deckPath, workdir)
56
+ break
52
57
  case 'validate':
53
58
  validateDeck(deckPath, workdir)
54
59
  break
package/src/update.ts ADDED
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * deck-update.ts — Update declared skills from their upstream sources
4
+ *
5
+ * 读取 skill-deck.toml → 遍历声明的 skill → 对 git 来源执行 pull。
6
+ * 职责:让冷池跟上上游版本。
7
+ * 不做:下载新 skill(那是 add 的职责)、修改 deck.toml、同步 working set。
8
+ */
9
+
10
+ import { parse as parseToml } from "@iarna/toml";
11
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
12
+ import { execSync } from "node:child_process";
13
+ import { resolve, dirname, join, relative } from "node:path";
14
+ import { findDeckToml, expandHome, findSource } from "./link.js";
15
+
16
+ interface UpdateResult {
17
+ name: string;
18
+ path: string;
19
+ status: "updated" | "up-to-date" | "skipped" | "failed" | "not-git";
20
+ message?: string;
21
+ }
22
+
23
+ function isGitRepo(dir: string): boolean {
24
+ return existsSync(join(dir, ".git"));
25
+ }
26
+
27
+ function gitPull(dir: string): { status: "updated" | "up-to-date" | "failed"; message: string } {
28
+ try {
29
+ const output = execSync("git pull", {
30
+ cwd: dir,
31
+ encoding: "utf-8",
32
+ stdio: ["pipe", "pipe", "pipe"],
33
+ timeout: 30000,
34
+ }).trim();
35
+
36
+ if (output.includes("Already up to date") || output.includes("Already up-to-date")) {
37
+ return { status: "up-to-date", message: output };
38
+ }
39
+ return { status: "updated", message: output };
40
+ } catch (err: any) {
41
+ const stderr = err.stderr?.toString() || err.message || "";
42
+ return { status: "failed", message: stderr.trim() };
43
+ }
44
+ }
45
+
46
+ export function updateDeck(cliDeckPath?: string, cliWorkdir?: string): void {
47
+ const cliDeck = cliDeckPath || process.argv.find((_, i, a) => a[i - 1] === "--deck");
48
+ const DECK_PATH = cliDeck
49
+ ? resolve(cliDeck)
50
+ : findDeckToml(process.cwd()) || resolve("skill-deck.toml");
51
+
52
+ if (!existsSync(DECK_PATH)) {
53
+ console.error(`❌ skill-deck.toml not found in ${process.cwd()}`);
54
+ console.error(`\nCreate one or specify a path: bunx @lythos/skill-deck link --deck /path/to/deck.toml`);
55
+ process.exit(1);
56
+ }
57
+
58
+ const PROJECT_DIR = cliWorkdir ? resolve(cliWorkdir) : dirname(DECK_PATH);
59
+ const deckRaw = readFileSync(DECK_PATH, "utf-8");
60
+ const deck = parseToml(deckRaw) as any;
61
+
62
+ const COLD_POOL = expandHome(deck.deck?.cold_pool || "~/.agents/skill-repos", PROJECT_DIR);
63
+
64
+ // ── 收集声明 ────────────────────────────────────────────────
65
+
66
+ const declared: { name: string; type: string }[] = [];
67
+
68
+ for (const section of ["innate", "tool", "combo"] as const) {
69
+ for (const name of (deck[section]?.skills || [])) {
70
+ if (!name || typeof name !== "string") continue;
71
+ declared.push({ name, type: section });
72
+ }
73
+ }
74
+
75
+ if (declared.length === 0) {
76
+ console.log("📭 No skills declared in deck. Nothing to update.");
77
+ process.exit(0);
78
+ }
79
+
80
+ // ── 执行更新 ────────────────────────────────────────────────
81
+
82
+ const results: UpdateResult[] = [];
83
+ let updated = 0;
84
+ let upToDate = 0;
85
+ let skipped = 0;
86
+ let failed = 0;
87
+
88
+ for (const { name, type } of declared) {
89
+ const result = findSource(name, COLD_POOL, PROJECT_DIR);
90
+
91
+ if (result.error || !result.path) {
92
+ results.push({ name, path: "", status: "failed", message: result.error || "Skill not found" });
93
+ failed++;
94
+ continue;
95
+ }
96
+
97
+ const path = result.path;
98
+
99
+ // localhost skills are user-managed; skip
100
+ const relativePath = relative(COLD_POOL, path);
101
+ if (relativePath.startsWith("localhost")) {
102
+ results.push({ name, path: relativePath, status: "skipped", message: "localhost skill — user-managed" });
103
+ skipped++;
104
+ continue;
105
+ }
106
+
107
+ if (!isGitRepo(path)) {
108
+ results.push({ name, path: relativePath, status: "not-git", message: "Not a git repository" });
109
+ skipped++;
110
+ continue;
111
+ }
112
+
113
+ const pullResult = gitPull(path);
114
+ results.push({ name, path: relativePath, status: pullResult.status, message: pullResult.message });
115
+
116
+ if (pullResult.status === "updated") updated++;
117
+ else if (pullResult.status === "up-to-date") upToDate++;
118
+ else failed++;
119
+ }
120
+
121
+ // ── 报告 ────────────────────────────────────────────────────
122
+
123
+ console.log(`\n📦 Skill Update Report — ${declared.length} skill(s) checked`);
124
+ console.log(` Updated: ${updated} | Up-to-date: ${upToDate} | Skipped: ${skipped} | Failed: ${failed}`);
125
+ console.log();
126
+
127
+ for (const r of results) {
128
+ const icon =
129
+ r.status === "updated" ? "🔄" :
130
+ r.status === "up-to-date" ? "✅" :
131
+ r.status === "skipped" ? "⏭️" :
132
+ r.status === "not-git" ? "📁" :
133
+ "❌";
134
+ console.log(`${icon} ${r.name}`);
135
+ if (r.message && r.status !== "up-to-date") {
136
+ const lines = r.message.split("\n").filter(l => l.trim());
137
+ for (const line of lines.slice(0, 3)) {
138
+ console.log(` ${line.trim()}`);
139
+ }
140
+ if (lines.length > 3) {
141
+ console.log(` ... (${lines.length - 3} more lines)`);
142
+ }
143
+ }
144
+ }
145
+
146
+ if (updated > 0) {
147
+ console.log(`\n💡 Run 'bunx @lythos/skill-deck link' to sync updated skills to working set.`);
148
+ }
149
+
150
+ if (failed > 0) {
151
+ process.exit(1);
152
+ }
153
+ }
154
+