@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
package/src/index.ts CHANGED
@@ -1,14 +1,8 @@
1
1
  import { summarizeFiles, summarizeFile } from "./ai";
2
- import {
3
- saveAllFileHashes,
4
- saveHashes,
5
- getHashes,
6
- checkNoFilesChanged,
7
- } from "./hashes";
2
+ import type { AgentOptions } from "./ai";
8
3
  import * as fs from "fs";
9
4
  import * as path from "path";
10
5
  import * as crypto from "crypto";
11
- import { promisify } from "util";
12
6
  import { globSync } from "glob";
13
7
 
14
8
  import { Prompts } from "./prompts";
@@ -37,12 +31,25 @@ import {
37
31
  getConfiguredEmbeddingMap,
38
32
  } from "./embeddings";
39
33
 
40
- import { abort } from "process";
41
34
  import { convertToText } from "./conversion";
42
35
  import { knowhowMcpClient } from "./services/Mcp";
43
36
  import { services } from "./services/";
44
37
  import { Models } from "./types";
45
38
 
39
+ // ---------------------------------------------------------------------------
40
+ // Re-export generate pipeline (moved to src/generate.ts)
41
+ // ---------------------------------------------------------------------------
42
+ export {
43
+ generate,
44
+ GenerateOptions,
45
+ buildWaves,
46
+ normalizeInputPattern,
47
+ withOutputTarget,
48
+ writeAgentOrSummaryOutput,
49
+ handleMultiOutputGeneration,
50
+ handleSingleOutputGeneration,
51
+ } from "./generate";
52
+
46
53
  export * as clients from "./clients";
47
54
  export * as agents from "./agents";
48
55
  export * as services from "./services";
@@ -60,11 +67,16 @@ export { ConversionService } from "./services/conversion/ConversionService";
60
67
  // Export plugin types for external plugins
61
68
  export { PluginBase } from "./plugins/PluginBase";
62
69
  export { PluginMeta, Plugin, PluginContext } from "./plugins/types";
70
+ export { PluginService } from "./plugins/plugins";
71
+ export { SkillsPlugin } from "./plugins/SkillsPlugin";
63
72
  // Export embedding types
64
73
  export { MinimalEmbedding, Embeddable } from "./types";
65
74
 
75
+ // ---------------------------------------------------------------------------
76
+ // Embed
77
+ // ---------------------------------------------------------------------------
78
+
66
79
  export async function embed() {
67
- // load config
68
80
  const config = await getConfig();
69
81
  const ignorePattern = await getIgnorePattern();
70
82
 
@@ -72,7 +84,6 @@ export async function embed() {
72
84
  config.embeddingModel || EmbeddingModels.openai.EmbeddingAda2;
73
85
 
74
86
  if (!config.embedSources) {
75
- // No embeddings configured
76
87
  return;
77
88
  }
78
89
 
@@ -81,6 +92,10 @@ export async function embed() {
81
92
  }
82
93
  }
83
94
 
95
+ // ---------------------------------------------------------------------------
96
+ // Purge
97
+ // ---------------------------------------------------------------------------
98
+
84
99
  export async function purge(globPath: string) {
85
100
  const files = globSync(globPath);
86
101
  const embeddings = await getConfiguredEmbeddingMap();
@@ -107,6 +122,10 @@ export async function purge(globPath: string) {
107
122
  }
108
123
  }
109
124
 
125
+ // ---------------------------------------------------------------------------
126
+ // Upload
127
+ // ---------------------------------------------------------------------------
128
+
110
129
  export async function upload() {
111
130
  const config = await getConfig();
112
131
  const { AwsS3, Embeddings, knowhowApiClient } = services();
@@ -124,10 +143,6 @@ export async function upload() {
124
143
  }
125
144
  const items = JSON.parse(await readFile(source.output, "utf8"));
126
145
  const { name: embeddingName } = path.parse(source.output);
127
- const data = {
128
- embeddingName,
129
- items,
130
- };
131
146
 
132
147
  if (Embeddings.hasResolver(source.remoteType) && source.remoteType !== "knowhow") {
133
148
  console.log(
@@ -142,7 +157,6 @@ export async function upload() {
142
157
  if (!source.remoteId) {
143
158
  throw new Error("remoteId is required for knowhow uploads");
144
159
  }
145
- // Warn if the local embeddingModel differs from the one stored on the backend
146
160
  try {
147
161
  const remoteEmbedding = await knowhowApiClient.getOrgEmbedding(source.remoteId);
148
162
  const localModel = config.embeddingModel || EmbeddingModels.openai.EmbeddingAda2;
@@ -157,12 +171,11 @@ export async function upload() {
157
171
  );
158
172
  }
159
173
  } catch (e) {
160
- // Non-fatal — don't block upload if metadata fetch fails
174
+ // Non-fatal
161
175
  }
162
176
  const url = await knowhowApiClient.getPresignedUploadUrl(source);
163
177
  console.log("Uploading to", url);
164
178
  await AwsS3.uploadToPresignedUrl(url, source.output);
165
- // Sync config metadata back to the backend DB
166
179
  await knowhowApiClient.updateEmbeddingMetadata(source.remoteId, {
167
180
  inputGlob: source.input,
168
181
  outputPath: source.output,
@@ -181,201 +194,9 @@ export async function upload() {
181
194
  }
182
195
  }
183
196
 
184
- /**
185
- * Normalizes an input pattern to a valid glob pattern.
186
- * Supports:
187
- * - Standard glob patterns (e.g. "src/**\/*.ts")
188
- * - Brace expansion (e.g. "{src/a.ts,src/b.ts}")
189
- * - Comma-separated file paths (e.g. "src/a.ts,src/b.ts") — auto-converted to brace expansion
190
- * - Mixed comma-separated list with globs (e.g. "src/a.ts,src/commands/**\/*.ts")
191
- */
192
- function normalizeInputPattern(input: string): string {
193
- // If it already has braces, use as-is (already brace-expanded)
194
- if (input.includes("{")) {
195
- return input;
196
- }
197
- // If it contains commas, treat as comma-separated list and wrap in braces
198
- // This also handles the mixed case: "src/a.ts,src/commands/**/*.ts"
199
- if (input.includes(",")) {
200
- const parts = input.split(",").map((p) => p.trim());
201
- return `{${parts.join(",")}}`;
202
- }
203
- return input;
204
- }
205
-
206
- export async function generate(): Promise<void> {
207
- const config = await getConfig();
208
- for (const source of config.sources) {
209
- console.log("Generating", source.input, "to", source.output);
210
- if (source.kind === "file" || !source.kind) {
211
- const files = globSync(normalizeInputPattern(source.input));
212
- const prompt = await loadPrompt(source.prompt);
213
-
214
- if (source.output.endsWith("/")) {
215
- await handleMultiOutputGeneration(
216
- source.model,
217
- source.input,
218
- files,
219
- prompt,
220
- source.output,
221
- source.outputExt,
222
- source.outputName,
223
- source.kind,
224
- source.agent
225
- );
226
- } else {
227
- await handleSingleOutputGeneration(
228
- source.model,
229
- files,
230
- prompt,
231
- source.output,
232
- source.kind,
233
- source.agent
234
- );
235
- }
236
- } else {
237
- await handleAllKindsGeneration(source);
238
- }
239
- }
240
- }
241
-
242
- async function handleAllKindsGeneration(source: GenerationSource) {
243
- const { Plugins } = services();
244
- const { kind, input } = source;
245
- if (Plugins.isPlugin(kind)) {
246
- const data = await Plugins.call(kind, input);
247
- if (source.output.endsWith("/")) {
248
- throw new Error(`Plugin ${kind} can only output to a single file`);
249
- }
250
- await writeFile(source.output, data);
251
- }
252
- return handleFileKindGeneration(source);
253
- }
254
-
255
- async function handleFileKindGeneration(source: GenerationSource) {
256
- const prompt = await loadPrompt(source.prompt);
257
- const files = globSync(normalizeInputPattern(source.input));
258
- console.log("Analyzing files: ", files);
259
-
260
- if (source.output.endsWith("/")) {
261
- await handleMultiOutputGeneration(
262
- source.model,
263
- source.input,
264
- files,
265
- prompt,
266
- source.output,
267
- source.outputExt,
268
- source.outputName,
269
- source.kind,
270
- source.agent
271
- );
272
- } else {
273
- await handleSingleOutputGeneration(
274
- source.model,
275
- files,
276
- prompt,
277
- source.output,
278
- source.kind,
279
- source.agent
280
- );
281
- }
282
- }
283
- export async function handleMultiOutputGeneration(
284
- model: string,
285
- inputPattern: string,
286
- files: string[],
287
- prompt: string,
288
- output: string,
289
- outputExt = "mdx",
290
- outputName?: string,
291
- kind?: string,
292
- agent?: string
293
- ) {
294
- // get the hash of the prompt
295
- const promptHash = crypto.createHash("md5").update(prompt).digest("hex");
296
-
297
- // get the files matching the input pattern
298
- const hashes = await getHashes();
299
-
300
- const inputPath = inputPattern.includes("**")
301
- ? inputPattern.split("**")[0]
302
- : "";
303
-
304
- for (const file of files) {
305
- // get the hash of the file
306
- const fileContent = await convertToText(file);
307
- const fileHash = crypto.createHash("md5").update(fileContent).digest("hex");
308
-
309
- if (!hashes[file]) {
310
- hashes[file] = { promptHash: "", fileHash: "" };
311
- }
312
-
313
- // write the summary to the output file
314
- const { name, ext, dir } = path.parse(file);
315
- const nestedFolder = inputPath ? (dir + "/").replace(inputPath, "") : "";
316
- const outputFolder = path.join(output, nestedFolder);
317
-
318
- if (!fs.existsSync(outputFolder)) {
319
- fs.mkdirSync(outputFolder, { recursive: true });
320
- }
321
-
322
- const outputFileName = outputName || name;
323
- const outputFile = path.join(
324
- outputFolder,
325
- outputFileName + "." + outputExt
326
- );
327
- console.log({ dir, inputPath, nestedFolder, outputFile });
328
-
329
- const toCheck = [file, outputFile];
330
- const noChanges = await checkNoFilesChanged(toCheck, promptHash, hashes);
331
- if (noChanges) {
332
- console.log("Skipping file", file, "because it hasn't changed");
333
- continue;
334
- }
335
-
336
- // summarize the file
337
- console.log("Summarizing", file);
338
- const summary = prompt
339
- ? await summarizeFile(file, prompt, model, agent)
340
- : fileContent;
341
-
342
- console.log("Writing summary to", outputFile);
343
- await writeFile(outputFile, summary);
344
-
345
- await saveAllFileHashes(toCheck, promptHash);
346
- }
347
- }
348
-
349
- export async function handleSingleOutputGeneration(
350
- model: string,
351
- files: string[],
352
- prompt: string,
353
- outputFile: string,
354
- kind?: string,
355
- agent?: string
356
- ) {
357
- const hashes = await getHashes();
358
- const promptHash = crypto.createHash("md5").update(prompt).digest("hex");
359
-
360
- const filesToCheck = [outputFile, ...files];
361
- const noChanges = await checkNoFilesChanged(filesToCheck, promptHash, hashes);
362
- if (noChanges) {
363
- console.log(`Skipping ${files.length} files because they haven't changed`);
364
- return;
365
- }
366
-
367
- console.log("Summarizing", files.length, "files");
368
- const summary = prompt
369
- ? await summarizeFiles(files, prompt, model, agent)
370
- : (await Promise.all(files.map(convertToText))).join("\n\n");
371
-
372
- const fileHash = crypto.createHash("md5").update(summary).digest("hex");
373
-
374
- console.log("Writing summary to", outputFile);
375
- await writeFile(outputFile, summary);
376
-
377
- await saveAllFileHashes(filesToCheck, promptHash);
378
- }
197
+ // ---------------------------------------------------------------------------
198
+ // Download
199
+ // ---------------------------------------------------------------------------
379
200
 
380
201
  export async function download() {
381
202
  const config = await getConfig();
@@ -419,10 +240,7 @@ export async function download() {
419
240
  "to",
420
241
  destinationPath
421
242
  );
422
- const preSignedUrl = await knowhowApiClient.getPresignedDownloadUrl(
423
- source
424
- );
425
- // Ensure output directory exists
243
+ const preSignedUrl = await knowhowApiClient.getPresignedDownloadUrl(source);
426
244
  const outputDir = path.dirname(destinationPath);
427
245
  if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
428
246
  await AwsS3.downloadFromPresignedUrl(preSignedUrl, destinationPath);
@@ -1,4 +1,3 @@
1
- import { MinimalEmbedding } from "../types";
2
1
  import { Plugin, PluginContext, PluginMeta } from "./types";
3
2
 
4
3
  export abstract class PluginBase implements Plugin {
@@ -66,7 +65,6 @@ export abstract class PluginBase implements Plugin {
66
65
  /** Mandatory plugin actions ---------------------------------------- */
67
66
  /* ------------------------------------------------------------------ */
68
67
  abstract call(input?: string): Promise<string>;
69
- abstract embed(input: string): Promise<MinimalEmbedding[]>;
70
68
  }
71
69
 
72
70
  export { PluginMeta, Plugin } from "./types";
@@ -1,33 +1,11 @@
1
- import { promises as fs, Dirent } from "fs";
2
- import * as path from "path";
3
1
  import { PluginBase, PluginMeta } from "./PluginBase";
4
2
  import { Plugin, PluginContext } from "./types";
5
- import { MinimalEmbedding } from "../types";
6
- import { getConfig } from "../config";
7
-
8
- interface SkillMeta {
9
- name: string;
10
- description: string;
11
- filePath: string;
12
- }
13
-
14
- function parseFrontmatter(content: string): Record<string, string> {
15
- const match = content.match(/^---\n([\s\S]*?)\n---/);
16
- if (!match) return {};
17
- const result: Record<string, string> = {};
18
- for (const line of match[1].split("\n")) {
19
- const [key, ...rest] = line.split(":");
20
- if (key && rest.length) result[key.trim()] = rest.join(":").trim();
21
- }
22
- return result;
23
- }
24
3
 
25
4
  export class SkillsPlugin extends PluginBase implements Plugin {
26
5
  static readonly meta: PluginMeta = {
27
6
  key: "skills",
28
7
  name: "Skills Plugin",
29
- description:
30
- "Scans configured skills directories for SKILL.md files and provides skill content to agents",
8
+ description: "Lists available skills",
31
9
  requires: [],
32
10
  };
33
11
 
@@ -37,114 +15,23 @@ export class SkillsPlugin extends PluginBase implements Plugin {
37
15
  super(context);
38
16
  }
39
17
 
40
- async call(input?: string): Promise<string> {
41
- const result = await this.embed(input || "");
42
- return result.map((e) => e.text).join("\n\n");
43
- }
44
-
45
- async embed(userPrompt: string): Promise<MinimalEmbedding[]> {
46
- const config = await getConfig();
47
- const skillDirs: string[] = (config as any).skills || [];
48
-
49
- if (skillDirs.length === 0) {
50
- return [];
51
- }
52
-
53
- const skills = await this.scanSkills(skillDirs);
18
+ /**
19
+ * Returns a summary list of in-memory skills (name + description).
20
+ * This is called by the plugin system to inject context into agent prompts.
21
+ * Skills must be loaded into memory first via Behaviors.initFromDisk() at startup.
22
+ */
23
+ async call(_input?: string): Promise<string> {
24
+ const { Behaviors } = this.context;
25
+ const skills = Behaviors.listSkills();
54
26
 
55
27
  if (skills.length === 0) {
56
- return [];
57
- }
58
-
59
- // Check if any skill name appears in the prompt
60
- const matchedSkills = skills.filter((skill) =>
61
- userPrompt.toLowerCase().includes(skill.name.toLowerCase())
62
- );
63
-
64
- if (matchedSkills.length > 0) {
65
- // Return full skill content for matched skills
66
- const results: MinimalEmbedding[] = [];
67
- for (const skill of matchedSkills) {
68
- try {
69
- const content = await fs.readFile(skill.filePath, "utf-8");
70
- results.push({
71
- id: skill.filePath,
72
- text: `## Skill: ${skill.name}\nFile: ${skill.filePath}\n\n${content}`,
73
- metadata: { filePath: skill.filePath },
74
- });
75
- } catch {
76
- // Skip unreadable files
77
- }
78
- }
79
- return results;
80
- }
81
-
82
- // Return skill discovery summary
83
- const summary = this.buildSkillSummary(skills);
84
- return [
85
- {
86
- id: "skills:summary",
87
- text: summary,
88
- metadata: {},
89
- },
90
- ];
91
- }
92
-
93
- private async scanSkills(dirs: string[]): Promise<SkillMeta[]> {
94
- const skills: SkillMeta[] = [];
95
- for (const dir of dirs) {
96
- const resolvedDir = dir.replace(/^~/, process.env.HOME || "");
97
- try {
98
- const found = await this.findSkillFiles(resolvedDir);
99
- skills.push(...found);
100
- } catch {
101
- // Skip directories that don't exist or can't be read
102
- }
103
- }
104
- return skills;
105
- }
106
-
107
- private async findSkillFiles(dir: string): Promise<SkillMeta[]> {
108
- const skills: SkillMeta[] = [];
109
- let entries: Dirent[];
110
- try {
111
- entries = await fs.readdir(dir, { withFileTypes: true });
112
- } catch {
113
- return skills;
114
- }
115
-
116
- for (const entry of entries) {
117
- const fullPath = path.join(dir, entry.name);
118
- if (entry.isDirectory()) {
119
- const nested = await this.findSkillFiles(fullPath);
120
- skills.push(...nested);
121
- } else if (entry.isFile() && entry.name === "SKILL.md") {
122
- try {
123
- const content = await fs.readFile(fullPath, "utf-8");
124
- const frontmatter = parseFrontmatter(content);
125
- const name = frontmatter.name;
126
- const description = frontmatter.description || "";
127
- if (name) {
128
- skills.push({ name, description, filePath: fullPath });
129
- }
130
- } catch {
131
- // Skip unreadable files
132
- }
133
- }
28
+ return "No skills available";
134
29
  }
135
30
 
136
- return skills;
137
- }
138
-
139
- private buildSkillSummary(skills: SkillMeta[]): string {
140
31
  const lines = ["Available skills:"];
141
32
  for (const skill of skills) {
142
- lines.push(`- ${skill.name} (${skill.filePath}): ${skill.description}`);
33
+ lines.push(`- ${skill.name}: ${skill.description}`);
143
34
  }
144
- lines.push("");
145
- lines.push(
146
- "To use a skill, reference its name in your request and I will load the full instructions."
147
- );
148
35
  return lines.join("\n");
149
36
  }
150
37
  }
@@ -6,7 +6,6 @@ import { getConfig, getLanguageConfig } from "../config";
6
6
  import { getEnabledPlugins } from "../types";
7
7
  import { PluginBase, PluginMeta } from "./PluginBase";
8
8
  import { Plugin, PluginContext } from "./types";
9
- import { PluginService } from "./plugins";
10
9
 
11
10
  /**
12
11
  * Language Plugin with event-driven context loading
@@ -14,24 +14,12 @@ import { SkillsPlugin } from "./SkillsPlugin";
14
14
  import { EventService } from "../services/EventService";
15
15
 
16
16
  export class PluginService {
17
- private pluginMap = new Map<string, Plugin>();
17
+ protected pluginMap = new Map<string, Plugin>();
18
18
  private events?: EventService;
19
19
 
20
20
  constructor(context: PluginContext) {
21
21
  this.events = context.Events;
22
22
  context.Plugins = this;
23
-
24
- // Register migrated PluginBase plugins
25
- this.pluginMap.set("embeddings", new EmbeddingPlugin(context));
26
- this.pluginMap.set("vim", new VimPlugin(context));
27
- this.pluginMap.set("linter", new LinterPlugin(context));
28
- this.pluginMap.set("language", new LanguagePlugin(context));
29
- this.pluginMap.set("url", new UrlPlugin(context));
30
- this.pluginMap.set("git", new GitPlugin(context));
31
- this.pluginMap.set("tmux", new TmuxPlugin(context));
32
- this.pluginMap.set("agents-md", new AgentsMdPlugin(context));
33
- this.pluginMap.set("exec", new ExecPlugin(context));
34
- this.pluginMap.set("skills", new SkillsPlugin(context));
35
23
  }
36
24
 
37
25
  /* -------- lifecycle helpers ------------------------------------ */
@@ -146,3 +134,26 @@ export class PluginService {
146
134
  return newPlugin.embed ? newPlugin.embed(userInput) : [];
147
135
  }
148
136
  }
137
+
138
+ /**
139
+ * CliPluginService extends PluginService with the full set of default plugins
140
+ * used by the Knowhow CLI. Use this class when setting up agents for CLI usage.
141
+ * Use the base PluginService when you want a clean slate to register your own plugins.
142
+ */
143
+ export class CliPluginService extends PluginService {
144
+ constructor(context: PluginContext) {
145
+ super(context);
146
+
147
+ // Register CLI default plugins
148
+ this.pluginMap.set("embeddings", new EmbeddingPlugin(context));
149
+ this.pluginMap.set("vim", new VimPlugin(context));
150
+ this.pluginMap.set("linter", new LinterPlugin(context));
151
+ this.pluginMap.set("language", new LanguagePlugin(context));
152
+ this.pluginMap.set("url", new UrlPlugin(context));
153
+ this.pluginMap.set("git", new GitPlugin(context));
154
+ this.pluginMap.set("tmux", new TmuxPlugin(context));
155
+ this.pluginMap.set("agents-md", new AgentsMdPlugin(context));
156
+ this.pluginMap.set("exec", new ExecPlugin(context));
157
+ this.pluginMap.set("skills", new SkillsPlugin(context));
158
+ }
159
+ }
@@ -1,5 +1,10 @@
1
1
  import { Embeddable, MinimalEmbedding } from "../types";
2
- import { AgentService, EventService, ToolsService } from "../services";
2
+ import {
3
+ AgentService,
4
+ BehaviorsService,
5
+ EventService,
6
+ ToolsService,
7
+ } from "../services";
3
8
  import { AIClient } from "../clients";
4
9
  import { PluginService } from "./plugins";
5
10
 
@@ -13,7 +18,7 @@ export interface PluginMeta {
13
18
  export interface Plugin {
14
19
  callMany(userInput?: string): Promise<string>;
15
20
  call(userInput?: string): Promise<string>;
16
- embed(userInput?: string): Promise<MinimalEmbedding[]>;
21
+ embed?(userInput?: string): Promise<MinimalEmbedding[]>;
17
22
  enable(): void;
18
23
  disable(): void;
19
24
  isEnabled(): boolean;
@@ -27,4 +32,5 @@ export interface PluginContext {
27
32
  Clients?: AIClient;
28
33
  Tools?: ToolsService;
29
34
  Plugins?: PluginService;
35
+ Behaviors?: BehaviorsService;
30
36
  }