@tyvm/knowhow 0.0.129 → 0.0.131

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.
Files changed (164) hide show
  1. package/package.json +4 -4
  2. package/src/agents/base/base.ts +40 -5
  3. package/src/agents/configurable/ConfigAgent.ts +0 -1
  4. package/src/agents/tools/index.ts +1 -0
  5. package/src/agents/tools/list.ts +2 -0
  6. package/src/agents/tools/skills.ts +79 -0
  7. package/src/agents/tools/startAgentTask.ts +17 -13
  8. package/src/ai.ts +55 -24
  9. package/src/chat/CliChatService.ts +7 -1
  10. package/src/chat/modules/AgentModule.ts +156 -15
  11. package/src/chat/renderer/PlainRenderer.ts +218 -0
  12. package/src/chat/renderer/index.ts +1 -0
  13. package/src/chat/renderer/loadRenderer.ts +2 -0
  14. package/src/chat.ts +2 -2
  15. package/src/cli.ts +10 -0
  16. package/src/clients/anthropic.ts +68 -2
  17. package/src/clients/fireworks.ts +10 -1
  18. package/src/clients/index.ts +5 -3
  19. package/src/clients/meta.ts +9 -0
  20. package/src/clients/openai.ts +28 -5
  21. package/src/clients/pricing/anthropic.ts +11 -0
  22. package/src/clients/pricing/fireworks.ts +12 -0
  23. package/src/clients/pricing/openai.ts +3 -0
  24. package/src/clients/types.ts +27 -4
  25. package/src/clients/xai.ts +2 -2
  26. package/src/commands/agent.ts +70 -18
  27. package/src/commands/agents.ts +556 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/hash.ts +124 -0
  30. package/src/commands/misc.ts +157 -3
  31. package/src/commands/services.ts +6 -2
  32. package/src/commands/skills.ts +107 -0
  33. package/src/config.ts +51 -1
  34. package/src/generate.ts +507 -0
  35. package/src/index.ts +34 -216
  36. package/src/plugins/PluginBase.ts +0 -2
  37. package/src/plugins/SkillsPlugin.ts +11 -124
  38. package/src/plugins/language.ts +0 -1
  39. package/src/plugins/plugins.ts +24 -13
  40. package/src/plugins/types.ts +8 -2
  41. package/src/services/BehaviorsService.ts +464 -0
  42. package/src/services/KnowhowClient.ts +61 -0
  43. package/src/services/SkillsService.ts +820 -0
  44. package/src/services/Tools.ts +2 -0
  45. package/src/services/index.ts +8 -2
  46. package/src/services/modules/types.ts +2 -0
  47. package/src/types.ts +36 -4
  48. package/ts_build/package.json +4 -4
  49. package/ts_build/src/agents/base/base.d.ts +9 -1
  50. package/ts_build/src/agents/base/base.js +29 -4
  51. package/ts_build/src/agents/base/base.js.map +1 -1
  52. package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
  53. package/ts_build/src/agents/tools/index.d.ts +1 -0
  54. package/ts_build/src/agents/tools/index.js +1 -0
  55. package/ts_build/src/agents/tools/index.js.map +1 -1
  56. package/ts_build/src/agents/tools/list.js +2 -0
  57. package/ts_build/src/agents/tools/list.js.map +1 -1
  58. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  59. package/ts_build/src/agents/tools/skills.js +62 -0
  60. package/ts_build/src/agents/tools/skills.js.map +1 -0
  61. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
  62. package/ts_build/src/agents/tools/startAgentTask.js +13 -12
  63. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  64. package/ts_build/src/ai.d.ts +10 -4
  65. package/ts_build/src/ai.js +27 -25
  66. package/ts_build/src/ai.js.map +1 -1
  67. package/ts_build/src/chat/CliChatService.d.ts +1 -1
  68. package/ts_build/src/chat/CliChatService.js +4 -1
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  71. package/ts_build/src/chat/modules/AgentModule.js +130 -4
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  74. package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
  75. package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
  76. package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
  77. package/ts_build/src/chat/renderer/index.d.ts +1 -0
  78. package/ts_build/src/chat/renderer/index.js +3 -1
  79. package/ts_build/src/chat/renderer/index.js.map +1 -1
  80. package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
  81. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
  82. package/ts_build/src/chat.d.ts +1 -1
  83. package/ts_build/src/chat.js +2 -2
  84. package/ts_build/src/chat.js.map +1 -1
  85. package/ts_build/src/cli.js +9 -0
  86. package/ts_build/src/cli.js.map +1 -1
  87. package/ts_build/src/clients/anthropic.d.ts +2 -0
  88. package/ts_build/src/clients/anthropic.js +39 -1
  89. package/ts_build/src/clients/anthropic.js.map +1 -1
  90. package/ts_build/src/clients/fireworks.js +7 -0
  91. package/ts_build/src/clients/fireworks.js.map +1 -1
  92. package/ts_build/src/clients/index.js +3 -1
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +2 -0
  95. package/ts_build/src/clients/meta.js +5 -0
  96. package/ts_build/src/clients/meta.js.map +1 -1
  97. package/ts_build/src/clients/openai.d.ts +1 -0
  98. package/ts_build/src/clients/openai.js +25 -5
  99. package/ts_build/src/clients/openai.js.map +1 -1
  100. package/ts_build/src/clients/pricing/anthropic.d.ts +3 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +6 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
  104. package/ts_build/src/clients/pricing/fireworks.js +5 -1
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/openai.js +3 -0
  107. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  108. package/ts_build/src/clients/types.d.ts +6 -1
  109. package/ts_build/src/clients/xai.js +1 -1
  110. package/ts_build/src/clients/xai.js.map +1 -1
  111. package/ts_build/src/commands/agent.js +58 -15
  112. package/ts_build/src/commands/agent.js.map +1 -1
  113. package/ts_build/src/commands/agents.d.ts +2 -0
  114. package/ts_build/src/commands/agents.js +460 -0
  115. package/ts_build/src/commands/agents.js.map +1 -0
  116. package/ts_build/src/commands/behaviors.d.ts +2 -0
  117. package/ts_build/src/commands/behaviors.js +43 -0
  118. package/ts_build/src/commands/behaviors.js.map +1 -0
  119. package/ts_build/src/commands/hash.d.ts +2 -0
  120. package/ts_build/src/commands/hash.js +117 -0
  121. package/ts_build/src/commands/hash.js.map +1 -0
  122. package/ts_build/src/commands/misc.js +108 -2
  123. package/ts_build/src/commands/misc.js.map +1 -1
  124. package/ts_build/src/commands/services.js +4 -2
  125. package/ts_build/src/commands/services.js.map +1 -1
  126. package/ts_build/src/commands/skills.d.ts +2 -0
  127. package/ts_build/src/commands/skills.js +85 -0
  128. package/ts_build/src/commands/skills.js.map +1 -0
  129. package/ts_build/src/config.js +34 -0
  130. package/ts_build/src/config.js.map +1 -1
  131. package/ts_build/src/generate.d.ts +16 -0
  132. package/ts_build/src/generate.js +304 -0
  133. package/ts_build/src/generate.js.map +1 -0
  134. package/ts_build/src/index.d.ts +3 -3
  135. package/ts_build/src/index.js +13 -118
  136. package/ts_build/src/index.js.map +1 -1
  137. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  138. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  139. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  140. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  141. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  142. package/ts_build/src/plugins/language.js.map +1 -1
  143. package/ts_build/src/plugins/plugins.d.ts +4 -1
  144. package/ts_build/src/plugins/plugins.js +17 -11
  145. package/ts_build/src/plugins/plugins.js.map +1 -1
  146. package/ts_build/src/plugins/types.d.ts +3 -2
  147. package/ts_build/src/services/BehaviorsService.d.ts +48 -0
  148. package/ts_build/src/services/BehaviorsService.js +382 -0
  149. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  150. package/ts_build/src/services/KnowhowClient.d.ts +8 -0
  151. package/ts_build/src/services/KnowhowClient.js +20 -0
  152. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  153. package/ts_build/src/services/SkillsService.d.ts +60 -0
  154. package/ts_build/src/services/SkillsService.js +641 -0
  155. package/ts_build/src/services/SkillsService.js.map +1 -0
  156. package/ts_build/src/services/Tools.d.ts +2 -0
  157. package/ts_build/src/services/Tools.js.map +1 -1
  158. package/ts_build/src/services/index.d.ts +3 -0
  159. package/ts_build/src/services/index.js +8 -2
  160. package/ts_build/src/services/index.js.map +1 -1
  161. package/ts_build/src/services/modules/index.d.ts +4 -2
  162. package/ts_build/src/services/modules/types.d.ts +2 -0
  163. package/ts_build/src/types.d.ts +14 -4
  164. package/ts_build/src/types.js.map +1 -1
@@ -0,0 +1,641 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SkillsService = void 0;
37
+ exports.computeSkillApiHash = computeSkillApiHash;
38
+ exports.computeSkillFolderHash = computeSkillFolderHash;
39
+ exports.parseSkillRef = parseSkillRef;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ const crypto_1 = require("crypto");
44
+ const https = __importStar(require("https"));
45
+ const KnowhowClient_1 = require("./KnowhowClient");
46
+ const LOCAL_SKILLS_DIR = ".knowhow/skills";
47
+ const LOCK_FILE_PRIMARY = ".knowhow/skills/skills-lock.json";
48
+ const LOCK_FILE_LEGACY = "skills-lock.json";
49
+ const GLOBAL_SKILLS_DIR = path.join(os.homedir(), ".knowhow", "skills");
50
+ const GLOBAL_LOCK_FILE = path.join(os.homedir(), ".knowhow", "skills", "skills-lock.json");
51
+ const SKILLS_SH_API = "https://skills.sh";
52
+ function createEmptyLock() {
53
+ return { version: 1, skills: {} };
54
+ }
55
+ function computeSkillApiHash(files) {
56
+ const sorted = [...files].sort((a, b) => a.path.localeCompare(b.path));
57
+ const hash = (0, crypto_1.createHash)("sha256");
58
+ for (const file of sorted) {
59
+ hash.update(file.path);
60
+ hash.update(Buffer.from(file.contents, "utf-8"));
61
+ }
62
+ return hash.digest("hex");
63
+ }
64
+ function computeSkillFolderHash(skillDir) {
65
+ const files = [];
66
+ collectFiles(skillDir, skillDir, files);
67
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
68
+ const hash = (0, crypto_1.createHash)("sha256");
69
+ for (const file of files) {
70
+ hash.update(file.relativePath);
71
+ hash.update(file.content);
72
+ }
73
+ return hash.digest("hex");
74
+ }
75
+ function collectFiles(baseDir, currentDir, results) {
76
+ if (!fs.existsSync(currentDir))
77
+ return;
78
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ const fullPath = path.join(currentDir, entry.name);
81
+ if (entry.isDirectory()) {
82
+ if (entry.name === ".git" || entry.name === "node_modules")
83
+ continue;
84
+ collectFiles(baseDir, fullPath, results);
85
+ }
86
+ else if (entry.isFile()) {
87
+ const content = fs.readFileSync(fullPath);
88
+ const relativePath = path
89
+ .relative(baseDir, fullPath)
90
+ .split("\\")
91
+ .join("/");
92
+ results.push({ relativePath, content });
93
+ }
94
+ }
95
+ }
96
+ function fetchJson(url) {
97
+ return new Promise((resolve, reject) => {
98
+ https
99
+ .get(url, { headers: { "User-Agent": "knowhow-cli" } }, (res) => {
100
+ const chunks = [];
101
+ res.on("data", (chunk) => chunks.push(chunk));
102
+ res.on("end", () => {
103
+ try {
104
+ const body = Buffer.concat(chunks).toString("utf-8");
105
+ if (res.statusCode && res.statusCode >= 400) {
106
+ reject(new Error(`HTTP ${res.statusCode}: ${body}`));
107
+ return;
108
+ }
109
+ resolve(JSON.parse(body));
110
+ }
111
+ catch (e) {
112
+ reject(e);
113
+ }
114
+ });
115
+ })
116
+ .on("error", reject);
117
+ });
118
+ }
119
+ function fetchRaw(url) {
120
+ return new Promise((resolve, reject) => {
121
+ https
122
+ .get(url, { headers: { "User-Agent": "knowhow-cli" } }, (res) => {
123
+ const chunks = [];
124
+ res.on("data", (chunk) => chunks.push(chunk));
125
+ res.on("end", () => {
126
+ const body = Buffer.concat(chunks).toString("utf-8");
127
+ if (res.statusCode && res.statusCode >= 400) {
128
+ reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
129
+ return;
130
+ }
131
+ resolve(body);
132
+ });
133
+ })
134
+ .on("error", reject);
135
+ });
136
+ }
137
+ async function fetchLatestCommitHash(owner, repo, filePath) {
138
+ try {
139
+ const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits?path=${encodeURIComponent(filePath)}&per_page=1`;
140
+ const commits = await fetchJson(url);
141
+ if (Array.isArray(commits) && commits.length > 0) {
142
+ return commits[0].sha;
143
+ }
144
+ }
145
+ catch {
146
+ }
147
+ return undefined;
148
+ }
149
+ function parseSkillRef(input, skillNameOverride) {
150
+ let normalized = input.replace(/\.git$/, "");
151
+ let skillFromAt;
152
+ const atIdx = normalized.lastIndexOf("@");
153
+ if (atIdx > 0 && !normalized.slice(0, atIdx).match(/\/\/$/)) {
154
+ const beforeAt = normalized.slice(0, atIdx);
155
+ const afterAt = normalized.slice(atIdx + 1);
156
+ if (beforeAt.includes("/")) {
157
+ skillFromAt = afterAt;
158
+ normalized = beforeAt;
159
+ }
160
+ }
161
+ let owner;
162
+ let repo;
163
+ let sourceUrl;
164
+ const githubMatch = normalized.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/);
165
+ if (githubMatch) {
166
+ owner = githubMatch[1];
167
+ repo = githubMatch[2];
168
+ sourceUrl = `https://github.com/${owner}/${repo}`;
169
+ }
170
+ else {
171
+ const parts = normalized.split("/");
172
+ if (parts.length < 2) {
173
+ throw new Error(`Invalid skill reference "${input}". Expected formats:\n` +
174
+ ` owner/repo@skill-name\n` +
175
+ ` https://github.com/owner/repo --skill <name>`);
176
+ }
177
+ owner = parts[0];
178
+ repo = parts[1];
179
+ }
180
+ const skill = skillNameOverride ?? skillFromAt;
181
+ return { owner, repo, skill, sourceUrl };
182
+ }
183
+ class SkillsService {
184
+ cwd;
185
+ isGlobal;
186
+ customInstallDir;
187
+ client = new KnowhowClient_1.KnowhowSimpleClient();
188
+ constructor(opts = {}) {
189
+ this.cwd = opts.cwd ?? process.cwd();
190
+ this.isGlobal = opts.global ?? false;
191
+ this.customInstallDir = opts.installDir;
192
+ }
193
+ getInstallDir() {
194
+ if (this.customInstallDir) {
195
+ return path.isAbsolute(this.customInstallDir)
196
+ ? this.customInstallDir
197
+ : path.join(this.cwd, this.customInstallDir);
198
+ }
199
+ if (this.isGlobal) {
200
+ return GLOBAL_SKILLS_DIR;
201
+ }
202
+ return path.join(this.cwd, LOCAL_SKILLS_DIR);
203
+ }
204
+ getLockPath() {
205
+ if (this.isGlobal) {
206
+ return GLOBAL_LOCK_FILE;
207
+ }
208
+ if (this.customInstallDir) {
209
+ return path.join(this.getInstallDir(), "skills-lock.json");
210
+ }
211
+ const primary = path.join(this.cwd, LOCK_FILE_PRIMARY);
212
+ const legacy = path.join(this.cwd, LOCK_FILE_LEGACY);
213
+ if (!fs.existsSync(primary) && fs.existsSync(legacy)) {
214
+ return legacy;
215
+ }
216
+ return primary;
217
+ }
218
+ getLockWritePath() {
219
+ if (this.isGlobal)
220
+ return GLOBAL_LOCK_FILE;
221
+ if (this.customInstallDir) {
222
+ return path.join(this.getInstallDir(), "skills-lock.json");
223
+ }
224
+ return path.join(this.cwd, LOCK_FILE_PRIMARY);
225
+ }
226
+ readLock() {
227
+ const lockPath = this.getLockPath();
228
+ try {
229
+ if (fs.existsSync(lockPath)) {
230
+ const content = fs.readFileSync(lockPath, "utf-8");
231
+ const parsed = JSON.parse(content);
232
+ if (typeof parsed.version !== "number" || !parsed.skills) {
233
+ return createEmptyLock();
234
+ }
235
+ return parsed;
236
+ }
237
+ }
238
+ catch {
239
+ }
240
+ return createEmptyLock();
241
+ }
242
+ writeLock(lock) {
243
+ const lockPath = this.getLockWritePath();
244
+ const dir = path.dirname(lockPath);
245
+ fs.mkdirSync(dir, { recursive: true });
246
+ const sortedSkills = {};
247
+ for (const key of Object.keys(lock.skills).sort()) {
248
+ sortedSkills[key] = lock.skills[key];
249
+ }
250
+ const sorted = { version: lock.version, skills: sortedSkills };
251
+ fs.writeFileSync(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
252
+ }
253
+ async fetchSkillFromApi(owner, repo, skill) {
254
+ const url = `${SKILLS_SH_API}/api/download/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(skill)}`;
255
+ return fetchJson(url);
256
+ }
257
+ async add(skillRef, skillNameOverride) {
258
+ const { owner, repo, skill, sourceUrl } = parseSkillRef(skillRef, skillNameOverride);
259
+ const source = `${owner}/${repo}`;
260
+ if (!skill) {
261
+ throw new Error(`Please specify a skill name:\n` +
262
+ ` knowhow skills add ${source}@<skill-name>\n` +
263
+ ` knowhow skills add https://github.com/${source} --skill <skill-name>`);
264
+ }
265
+ const installDir = this.getInstallDir();
266
+ const scope = this.isGlobal ? "globally" : "locally";
267
+ console.log(`📦 Fetching skill "${skill}" from ${source} (installing ${scope})...`);
268
+ let download;
269
+ try {
270
+ download = await this.fetchSkillFromApi(owner, repo, skill);
271
+ }
272
+ catch (e) {
273
+ const msg = e instanceof Error ? e.message : String(e);
274
+ throw new Error(`Failed to fetch skill "${skill}" from skills.sh API: ${msg}\n` +
275
+ `Make sure the skill exists at: https://skills.sh/${source}/${skill}`);
276
+ }
277
+ if (!download.files || download.files.length === 0) {
278
+ throw new Error(`No files found for skill "${skill}"`);
279
+ }
280
+ const skillInstallDir = path.join(installDir, skill);
281
+ fs.mkdirSync(skillInstallDir, { recursive: true });
282
+ for (const file of download.files) {
283
+ const filePath = path.join(skillInstallDir, file.path);
284
+ const fileDir = path.dirname(filePath);
285
+ fs.mkdirSync(fileDir, { recursive: true });
286
+ fs.writeFileSync(filePath, file.contents, "utf-8");
287
+ }
288
+ const computedHash = computeSkillApiHash(download.files);
289
+ if (download.hash && download.hash !== computedHash) {
290
+ console.warn(`⚠ Note: API cached hash (${download.hash}) differs from computed hash (${computedHash}). Storing computed hash.`);
291
+ }
292
+ const skillPath = `skills/${skill}/SKILL.md`;
293
+ const commitHash = await fetchLatestCommitHash(owner, repo, skillPath);
294
+ if (commitHash) {
295
+ console.log(`📎 Pinned to commit ${commitHash.slice(0, 8)}`);
296
+ }
297
+ const lock = this.readLock();
298
+ lock.skills[skill] = {
299
+ source,
300
+ sourceType: "github",
301
+ skillPath,
302
+ computedHash,
303
+ ...(commitHash ? { commitHash } : {}),
304
+ ...(sourceUrl ? { sourceUrl } : {}),
305
+ };
306
+ this.writeLock(lock);
307
+ console.log(`✅ Installed skill "${skill}" to ${skillInstallDir}`);
308
+ console.log(`🔒 Updated lock file at ${this.getLockWritePath()}`);
309
+ }
310
+ remove(skillName) {
311
+ const lock = this.readLock();
312
+ if (!lock.skills[skillName]) {
313
+ console.warn(`⚠ Skill "${skillName}" not found in lock file.`);
314
+ }
315
+ else {
316
+ delete lock.skills[skillName];
317
+ this.writeLock(lock);
318
+ console.log(`🗑 Removed "${skillName}" from lock file.`);
319
+ }
320
+ const installDir = path.join(this.getInstallDir(), skillName);
321
+ if (fs.existsSync(installDir)) {
322
+ fs.rmSync(installDir, { recursive: true, force: true });
323
+ console.log(`🗑 Deleted ${installDir}`);
324
+ }
325
+ else {
326
+ console.warn(`⚠ Skill directory not found: ${installDir}`);
327
+ }
328
+ }
329
+ list() {
330
+ const lock = this.readLock();
331
+ const skillEntries = Object.entries(lock.skills);
332
+ if (skillEntries.length === 0) {
333
+ console.log("No skills installed. Run `knowhow skills add <owner/repo@skill>` to install one.");
334
+ return;
335
+ }
336
+ const installDir = this.getInstallDir();
337
+ console.log(`Installed skills (${skillEntries.length}):\n`);
338
+ for (const [name, entry] of skillEntries) {
339
+ const skillDir = path.join(installDir, name);
340
+ const installed = fs.existsSync(skillDir);
341
+ const statusIcon = installed ? "✅" : "❌";
342
+ console.log(` ${statusIcon} ${name}`);
343
+ console.log(` source: ${entry.source}`);
344
+ if (entry.skillPath)
345
+ console.log(` path: ${entry.skillPath}`);
346
+ console.log(` hash: ${entry.computedHash}`);
347
+ }
348
+ }
349
+ async install() {
350
+ const lock = this.readLock();
351
+ const skillEntries = Object.entries(lock.skills);
352
+ if (skillEntries.length === 0) {
353
+ console.log("No skills in lock file. Nothing to install.");
354
+ return;
355
+ }
356
+ const installDir = this.getInstallDir();
357
+ console.log(`📦 Installing ${skillEntries.length} skill(s) from lock file...\n`);
358
+ let installed = 0;
359
+ let skipped = 0;
360
+ let failed = 0;
361
+ for (const [name, entry] of skillEntries) {
362
+ const skillDir = path.join(installDir, name);
363
+ if (fs.existsSync(skillDir)) {
364
+ const currentHash = computeSkillFolderHash(skillDir);
365
+ if (currentHash === entry.computedHash) {
366
+ console.log(` ⏭ ${name} (already up to date)`);
367
+ skipped++;
368
+ continue;
369
+ }
370
+ }
371
+ const parts = entry.source.split("/");
372
+ if (parts.length < 2 || entry.sourceType !== "github") {
373
+ console.warn(` ⚠ ${name}: unsupported sourceType "${entry.sourceType}", skipping`);
374
+ failed++;
375
+ continue;
376
+ }
377
+ const [owner, repo] = parts;
378
+ let skillSlug = name;
379
+ if (entry.skillPath) {
380
+ const pathParts = entry.skillPath.split("/");
381
+ if (pathParts.length >= 2) {
382
+ skillSlug = pathParts[pathParts.length - 2];
383
+ }
384
+ }
385
+ try {
386
+ console.log(` 📥 Installing ${name}...`);
387
+ const download = await this.fetchSkillFromApi(owner, repo, skillSlug);
388
+ const files = download.files;
389
+ fs.mkdirSync(skillDir, { recursive: true });
390
+ for (const file of files) {
391
+ const filePath = path.join(skillDir, file.path);
392
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
393
+ fs.writeFileSync(filePath, file.contents, "utf-8");
394
+ }
395
+ const actualHash = computeSkillApiHash(files);
396
+ if (actualHash !== entry.computedHash) {
397
+ console.warn(` ⚠ Hash mismatch for ${name}: locked ${entry.computedHash.slice(0, 16)}..., got ${actualHash.slice(0, 16)}...`);
398
+ }
399
+ else {
400
+ console.log(` ✓ Hash verified`);
401
+ }
402
+ console.log(` ✅ ${name}`);
403
+ installed++;
404
+ }
405
+ catch (e) {
406
+ const msg = e instanceof Error ? e.message : String(e);
407
+ console.error(` ❌ ${name}: ${msg}`);
408
+ failed++;
409
+ }
410
+ }
411
+ console.log(`\nDone: ${installed} installed, ${skipped} skipped, ${failed} failed.`);
412
+ }
413
+ async update(skillNames) {
414
+ const lock = this.readLock();
415
+ const toUpdate = skillNames && skillNames.length > 0
416
+ ? skillNames
417
+ : Object.keys(lock.skills);
418
+ if (toUpdate.length === 0) {
419
+ console.log("No skills to update.");
420
+ return;
421
+ }
422
+ const installDir = this.getInstallDir();
423
+ console.log(`🔄 Updating ${toUpdate.length} skill(s)...\n`);
424
+ let updated = 0;
425
+ let unchanged = 0;
426
+ let failed = 0;
427
+ for (const name of toUpdate) {
428
+ const entry = lock.skills[name];
429
+ if (!entry) {
430
+ console.warn(` ⚠ "${name}" not found in lock file, skipping.`);
431
+ failed++;
432
+ continue;
433
+ }
434
+ const parts = entry.source.split("/");
435
+ if (parts.length < 2 || entry.sourceType !== "github") {
436
+ console.warn(` ⚠ ${name}: unsupported sourceType, skipping`);
437
+ failed++;
438
+ continue;
439
+ }
440
+ const [owner, repo] = parts;
441
+ let skillSlug = name;
442
+ if (entry.skillPath) {
443
+ const pathParts = entry.skillPath.split("/");
444
+ if (pathParts.length >= 2) {
445
+ skillSlug = pathParts[pathParts.length - 2];
446
+ }
447
+ }
448
+ try {
449
+ const download = await this.fetchSkillFromApi(owner, repo, skillSlug);
450
+ const newHash = computeSkillApiHash(download.files);
451
+ if (newHash === entry.computedHash) {
452
+ console.log(` ⏭ ${name} (already up to date)`);
453
+ unchanged++;
454
+ continue;
455
+ }
456
+ const skillDir = path.join(installDir, name);
457
+ fs.mkdirSync(skillDir, { recursive: true });
458
+ for (const file of download.files) {
459
+ const filePath = path.join(skillDir, file.path);
460
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
461
+ fs.writeFileSync(filePath, file.contents, "utf-8");
462
+ }
463
+ lock.skills[name] = { ...entry, computedHash: newHash };
464
+ console.log(` ✅ ${name} (updated)`);
465
+ updated++;
466
+ }
467
+ catch (e) {
468
+ const msg = e instanceof Error ? e.message : String(e);
469
+ console.error(` ❌ ${name}: ${msg}`);
470
+ failed++;
471
+ }
472
+ }
473
+ if (updated > 0) {
474
+ this.writeLock(lock);
475
+ }
476
+ console.log(`\nDone: ${updated} updated, ${unchanged} unchanged, ${failed} failed.`);
477
+ }
478
+ async upload() {
479
+ const installDir = this.getInstallDir();
480
+ if (!fs.existsSync(installDir)) {
481
+ console.error(`No skills directory found at ${installDir}. Run 'knowhow skills add <ref>' first.`);
482
+ return;
483
+ }
484
+ const entries = fs.readdirSync(installDir, { withFileTypes: true });
485
+ const skillDirs = entries.filter((e) => e.isDirectory());
486
+ if (skillDirs.length === 0) {
487
+ console.log("No skill directories found. Nothing to upload.");
488
+ return;
489
+ }
490
+ let uploaded = 0;
491
+ let failed = 0;
492
+ for (const dir of skillDirs) {
493
+ const skillFile = path.join(installDir, dir.name, "SKILL.md");
494
+ if (!fs.existsSync(skillFile)) {
495
+ console.warn(` ⚠ ${dir.name}: no SKILL.md found, skipping.`);
496
+ continue;
497
+ }
498
+ try {
499
+ const raw = fs.readFileSync(skillFile, "utf-8");
500
+ const behavior = this.parseSkillMd(raw, dir.name);
501
+ const payload = {
502
+ ...behavior,
503
+ isSkill: true,
504
+ };
505
+ if (behavior.id) {
506
+ await this.client.updateOrgBehavior(behavior.id, payload);
507
+ console.log(` 📝 Updated: ${behavior.name} (${behavior.id})`);
508
+ }
509
+ else {
510
+ const result = await this.client.createOrgBehavior(payload);
511
+ const created = result.data;
512
+ if (created.id) {
513
+ const withId = this.injectIdIntoSkillMd(raw, created.id);
514
+ fs.writeFileSync(skillFile, withId, "utf-8");
515
+ console.log(` ✨ Created: ${behavior.name} (id: ${created.id})`);
516
+ }
517
+ else {
518
+ console.log(` ✨ Created: ${behavior.name}`);
519
+ }
520
+ }
521
+ uploaded++;
522
+ }
523
+ catch (e) {
524
+ const msg = e instanceof Error ? e.message : String(e);
525
+ console.error(` ❌ ${dir.name}: ${msg}`);
526
+ failed++;
527
+ }
528
+ }
529
+ console.log(`\nUploaded ${uploaded} skill(s). Failed: ${failed}.`);
530
+ }
531
+ async download() {
532
+ const installDir = this.getInstallDir();
533
+ const result = await this.client.getOrgSkills(false);
534
+ const skills = result.data;
535
+ if (skills.length === 0) {
536
+ console.log("No skills found on backend.");
537
+ return;
538
+ }
539
+ let created = 0;
540
+ let updated = 0;
541
+ let unchanged = 0;
542
+ for (const skill of skills) {
543
+ const name = (skill.name || "unnamed")
544
+ .toLowerCase()
545
+ .replace(/[^a-z0-9-]+/g, "-");
546
+ const skillDir = path.join(installDir, name);
547
+ fs.mkdirSync(skillDir, { recursive: true });
548
+ const skillFile = path.join(skillDir, "SKILL.md");
549
+ const content = this.serializeSkillMd(skill);
550
+ const hash = (s) => (0, crypto_1.createHash)("sha256").update(s).digest("hex");
551
+ if (!fs.existsSync(skillFile)) {
552
+ fs.writeFileSync(skillFile, content, "utf-8");
553
+ console.log(` ✨ Created: ${skillFile}`);
554
+ created++;
555
+ }
556
+ else {
557
+ const existing = fs.readFileSync(skillFile, "utf-8");
558
+ if (hash(existing) !== hash(content)) {
559
+ fs.writeFileSync(skillFile, content, "utf-8");
560
+ console.log(` 📝 Updated: ${skillFile}`);
561
+ updated++;
562
+ }
563
+ else {
564
+ unchanged++;
565
+ }
566
+ }
567
+ }
568
+ console.log(`\nSync complete: ${created} created, ${updated} updated, ${unchanged} unchanged → ${installDir}/`);
569
+ }
570
+ parseSkillMd(content, fallbackName) {
571
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
572
+ if (!match) {
573
+ return { name: fallbackName, description: "", instructions: content.trim(), isSkill: true };
574
+ }
575
+ const meta = {};
576
+ for (const line of match[1].split("\n")) {
577
+ const colonIdx = line.indexOf(":");
578
+ if (colonIdx > 0) {
579
+ const key = line.slice(0, colonIdx).trim();
580
+ const val = line.slice(colonIdx + 1).trim();
581
+ meta[key] = val;
582
+ }
583
+ }
584
+ return {
585
+ id: meta.id || undefined,
586
+ name: meta.name || fallbackName,
587
+ description: meta.description || "",
588
+ instructions: match[2].trim(),
589
+ textTrigger: meta.textTrigger || undefined,
590
+ semanticTriggerText: meta.semanticTriggerText || undefined,
591
+ model: meta.model || undefined,
592
+ mcpServers: meta.mcpServers || undefined,
593
+ tools: meta.tools || undefined,
594
+ embeddings: meta.embeddings || undefined,
595
+ isSkill: true,
596
+ isPublic: meta.isPublic === "true",
597
+ };
598
+ }
599
+ serializeSkillMd(skill) {
600
+ const lines = ["---"];
601
+ lines.push(`name: ${skill.name || ""}`);
602
+ lines.push(`description: ${skill.description || ""}`);
603
+ if (skill.model)
604
+ lines.push(`model: ${skill.model}`);
605
+ if (skill.textTrigger)
606
+ lines.push(`textTrigger: ${skill.textTrigger}`);
607
+ if (skill.semanticTriggerText)
608
+ lines.push(`semanticTriggerText: ${skill.semanticTriggerText}`);
609
+ if (skill.mcpServers)
610
+ lines.push(`mcpServers: ${skill.mcpServers}`);
611
+ if (skill.tools)
612
+ lines.push(`tools: ${skill.tools}`);
613
+ if (skill.embeddings)
614
+ lines.push(`embeddings: ${skill.embeddings}`);
615
+ if (skill.isPublic !== undefined)
616
+ lines.push(`isPublic: ${skill.isPublic}`);
617
+ if (skill.id)
618
+ lines.push(`id: ${skill.id}`);
619
+ lines.push("---");
620
+ lines.push("");
621
+ lines.push((skill.instructions || "").trim());
622
+ lines.push("");
623
+ return lines.join("\n");
624
+ }
625
+ injectIdIntoSkillMd(content, id) {
626
+ const match = content.match(/^(---\n[\s\S]*?\n---\n?)([\s\S]*)$/);
627
+ if (!match)
628
+ return content;
629
+ let frontmatter = match[1];
630
+ const body = match[2];
631
+ if (/^id:/m.test(frontmatter)) {
632
+ frontmatter = frontmatter.replace(/^id:.*$/m, `id: ${id}`);
633
+ }
634
+ else {
635
+ frontmatter = frontmatter.replace(/\n---\n?$/, `\nid: ${id}\n---\n`);
636
+ }
637
+ return frontmatter + body;
638
+ }
639
+ }
640
+ exports.SkillsService = SkillsService;
641
+ //# sourceMappingURL=SkillsService.js.map