@tyvm/knowhow 0.0.83 → 0.0.84

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 (210) hide show
  1. package/package.json +4 -2
  2. package/src/agents/base/base.ts +72 -62
  3. package/src/agents/index.ts +30 -14
  4. package/src/agents/tools/startAgentTask.ts +3 -1
  5. package/src/chat/CliChatService.ts +20 -4
  6. package/src/chat/modules/AgentModule.ts +399 -357
  7. package/src/chat/modules/CustomCommandsModule.ts +0 -1
  8. package/src/chat/modules/InternalChatModule.ts +18 -2
  9. package/src/chat/modules/RendererModule.ts +109 -0
  10. package/src/chat/modules/SessionsModule.ts +854 -0
  11. package/src/chat/modules/SetupModule.ts +6 -8
  12. package/src/chat/modules/index.ts +1 -0
  13. package/src/chat/renderer/CompactRenderer.ts +209 -0
  14. package/src/chat/renderer/ConsoleRenderer.ts +141 -0
  15. package/src/chat/renderer/FancyRenderer.ts +421 -0
  16. package/src/chat/renderer/index.ts +5 -0
  17. package/src/chat/renderer/loadRenderer.ts +314 -0
  18. package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
  19. package/src/chat/renderer/types.ts +88 -0
  20. package/src/chat/types.ts +5 -0
  21. package/src/chat.ts +69 -5
  22. package/src/cli.ts +24 -5
  23. package/src/config.ts +15 -0
  24. package/src/plugins/AgentsMdPlugin.ts +1 -1
  25. package/src/plugins/GitPlugin.ts +20 -20
  26. package/src/plugins/PluginBase.ts +11 -0
  27. package/src/plugins/SkillsPlugin.ts +150 -0
  28. package/src/plugins/asana.ts +4 -4
  29. package/src/plugins/embedding.ts +3 -5
  30. package/src/plugins/exec.ts +3 -3
  31. package/src/plugins/figma.ts +3 -7
  32. package/src/plugins/github.ts +18 -29
  33. package/src/plugins/jira.ts +2 -2
  34. package/src/plugins/language.ts +4 -4
  35. package/src/plugins/linear.ts +4 -4
  36. package/src/plugins/notion.ts +6 -8
  37. package/src/plugins/plugins.ts +29 -3
  38. package/src/plugins/url.ts +2 -2
  39. package/src/plugins/vim.ts +4 -3
  40. package/src/services/AgentService.ts +17 -0
  41. package/src/services/AgentSyncFs.ts +3 -0
  42. package/src/services/EventService.ts +168 -27
  43. package/src/services/KnowhowClient.ts +1 -0
  44. package/src/services/SessionManager.ts +51 -1
  45. package/src/services/SyncedAgentWatcher.ts +397 -0
  46. package/src/services/SyncerService.ts +147 -0
  47. package/src/services/index.ts +2 -0
  48. package/src/services/modules/index.ts +14 -3
  49. package/src/types.ts +25 -0
  50. package/src/worker.ts +80 -2
  51. package/src/workers/auth/PasskeySetup.ts +185 -0
  52. package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
  53. package/src/workers/auth/types.ts +58 -0
  54. package/src/workers/tools/getChallenge.ts +33 -0
  55. package/src/workers/tools/index.ts +8 -0
  56. package/src/workers/tools/lock.ts +31 -0
  57. package/src/workers/tools/unlock.ts +116 -0
  58. package/tests/unit/modules/moduleLoading.test.ts +226 -0
  59. package/tests/unit/plugins/pluginLoading.test.ts +151 -0
  60. package/ts_build/package.json +4 -2
  61. package/ts_build/src/agents/base/base.d.ts +4 -3
  62. package/ts_build/src/agents/base/base.js +54 -30
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/index.d.ts +3 -0
  65. package/ts_build/src/agents/index.js +21 -11
  66. package/ts_build/src/agents/index.js.map +1 -1
  67. package/ts_build/src/agents/tools/startAgentTask.js +2 -1
  68. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  69. package/ts_build/src/chat/CliChatService.js +16 -5
  70. package/ts_build/src/chat/CliChatService.js.map +1 -1
  71. package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
  72. package/ts_build/src/chat/modules/AgentModule.js +248 -258
  73. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  74. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  75. package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
  76. package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
  77. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  78. package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
  79. package/ts_build/src/chat/modules/RendererModule.js +76 -0
  80. package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
  81. package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
  82. package/ts_build/src/chat/modules/SessionsModule.js +582 -0
  83. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
  84. package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
  85. package/ts_build/src/chat/modules/SetupModule.js +4 -6
  86. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  87. package/ts_build/src/chat/modules/index.d.ts +1 -0
  88. package/ts_build/src/chat/modules/index.js +3 -1
  89. package/ts_build/src/chat/modules/index.js.map +1 -1
  90. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
  91. package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
  92. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
  93. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
  94. package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
  95. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
  96. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
  97. package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
  98. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
  99. package/ts_build/src/chat/renderer/index.d.ts +5 -0
  100. package/ts_build/src/chat/renderer/index.js +29 -0
  101. package/ts_build/src/chat/renderer/index.js.map +1 -0
  102. package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
  103. package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
  104. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
  105. package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
  106. package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
  107. package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
  108. package/ts_build/src/chat/renderer/types.d.ts +75 -0
  109. package/ts_build/src/chat/renderer/types.js +3 -0
  110. package/ts_build/src/chat/renderer/types.js.map +1 -0
  111. package/ts_build/src/chat/types.d.ts +5 -0
  112. package/ts_build/src/chat.js +46 -4
  113. package/ts_build/src/chat.js.map +1 -1
  114. package/ts_build/src/cli.js +18 -5
  115. package/ts_build/src/cli.js.map +1 -1
  116. package/ts_build/src/config.d.ts +1 -0
  117. package/ts_build/src/config.js +17 -1
  118. package/ts_build/src/config.js.map +1 -1
  119. package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
  120. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
  121. package/ts_build/src/plugins/GitPlugin.js +20 -20
  122. package/ts_build/src/plugins/GitPlugin.js.map +1 -1
  123. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  124. package/ts_build/src/plugins/PluginBase.js +13 -0
  125. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  126. package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
  127. package/ts_build/src/plugins/SkillsPlugin.js +149 -0
  128. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
  129. package/ts_build/src/plugins/asana.js +4 -4
  130. package/ts_build/src/plugins/asana.js.map +1 -1
  131. package/ts_build/src/plugins/embedding.js +3 -3
  132. package/ts_build/src/plugins/embedding.js.map +1 -1
  133. package/ts_build/src/plugins/exec.js +3 -3
  134. package/ts_build/src/plugins/exec.js.map +1 -1
  135. package/ts_build/src/plugins/figma.js +3 -3
  136. package/ts_build/src/plugins/figma.js.map +1 -1
  137. package/ts_build/src/plugins/github.js +18 -18
  138. package/ts_build/src/plugins/github.js.map +1 -1
  139. package/ts_build/src/plugins/jira.js +2 -2
  140. package/ts_build/src/plugins/jira.js.map +1 -1
  141. package/ts_build/src/plugins/language.js +4 -4
  142. package/ts_build/src/plugins/language.js.map +1 -1
  143. package/ts_build/src/plugins/linear.js +4 -4
  144. package/ts_build/src/plugins/linear.js.map +1 -1
  145. package/ts_build/src/plugins/notion.js +6 -6
  146. package/ts_build/src/plugins/notion.js.map +1 -1
  147. package/ts_build/src/plugins/plugins.d.ts +3 -0
  148. package/ts_build/src/plugins/plugins.js +18 -3
  149. package/ts_build/src/plugins/plugins.js.map +1 -1
  150. package/ts_build/src/plugins/url.js +2 -2
  151. package/ts_build/src/plugins/url.js.map +1 -1
  152. package/ts_build/src/plugins/vim.js +2 -2
  153. package/ts_build/src/plugins/vim.js.map +1 -1
  154. package/ts_build/src/services/AgentService.d.ts +3 -0
  155. package/ts_build/src/services/AgentService.js +7 -0
  156. package/ts_build/src/services/AgentService.js.map +1 -1
  157. package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
  158. package/ts_build/src/services/AgentSyncFs.js +2 -0
  159. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  160. package/ts_build/src/services/EventService.d.ts +25 -2
  161. package/ts_build/src/services/EventService.js +92 -14
  162. package/ts_build/src/services/EventService.js.map +1 -1
  163. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  164. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  165. package/ts_build/src/services/SessionManager.d.ts +6 -0
  166. package/ts_build/src/services/SessionManager.js +39 -1
  167. package/ts_build/src/services/SessionManager.js.map +1 -1
  168. package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
  169. package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
  170. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
  171. package/ts_build/src/services/SyncerService.d.ts +30 -0
  172. package/ts_build/src/services/SyncerService.js +72 -0
  173. package/ts_build/src/services/SyncerService.js.map +1 -0
  174. package/ts_build/src/services/index.d.ts +2 -0
  175. package/ts_build/src/services/index.js +2 -0
  176. package/ts_build/src/services/index.js.map +1 -1
  177. package/ts_build/src/services/modules/index.js +10 -2
  178. package/ts_build/src/services/modules/index.js.map +1 -1
  179. package/ts_build/src/types.d.ts +19 -0
  180. package/ts_build/src/types.js.map +1 -1
  181. package/ts_build/src/worker.d.ts +2 -0
  182. package/ts_build/src/worker.js +59 -4
  183. package/ts_build/src/worker.js.map +1 -1
  184. package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
  185. package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
  186. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
  187. package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
  188. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
  189. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
  190. package/ts_build/src/workers/auth/types.d.ts +36 -0
  191. package/ts_build/src/workers/auth/types.js +3 -0
  192. package/ts_build/src/workers/auth/types.js.map +1 -0
  193. package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
  194. package/ts_build/src/workers/tools/getChallenge.js +27 -0
  195. package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
  196. package/ts_build/src/workers/tools/index.d.ts +6 -0
  197. package/ts_build/src/workers/tools/index.js +10 -0
  198. package/ts_build/src/workers/tools/index.js.map +1 -1
  199. package/ts_build/src/workers/tools/lock.d.ts +9 -0
  200. package/ts_build/src/workers/tools/lock.js +27 -0
  201. package/ts_build/src/workers/tools/lock.js.map +1 -0
  202. package/ts_build/src/workers/tools/unlock.d.ts +18 -0
  203. package/ts_build/src/workers/tools/unlock.js +78 -0
  204. package/ts_build/src/workers/tools/unlock.js.map +1 -0
  205. package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
  206. package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
  207. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
  208. package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
  209. package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
  210. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
package/src/cli.ts CHANGED
@@ -35,6 +35,7 @@ import { BaseAgent } from "./agents/base/base";
35
35
  import { AskModule } from "./chat/modules/AskModule";
36
36
  import { SearchModule } from "./chat/modules/SearchModule";
37
37
  import { AgentModule } from "./chat/modules/AgentModule";
38
+ import { SessionsModule } from "./chat/modules/SessionsModule";
38
39
  import { readPromptFile } from "./ai";
39
40
  import { SetupModule } from "./chat/modules/SetupModule";
40
41
  import { CliChatService } from "./chat/CliChatService";
@@ -48,22 +49,32 @@ async function setupServices() {
48
49
  ...OldTools.getContext(),
49
50
  });
50
51
 
51
- const { Researcher, Developer, Patcher, Setup } = agents({
52
+ // Build the AgentContext with the fully-populated LazyToolsService so every
53
+ // agent created (including those in setupAgent) gets all tools registered.
54
+ const agentContext: import("./agents/base/base").AgentContext = {
52
55
  ...services(),
53
56
  Tools,
57
+ };
58
+
59
+ const { Researcher, Developer, Patcher, Setup } = agents({
60
+ ...agentContext,
54
61
  });
55
62
 
56
63
  Agents.registerAgent(Researcher);
57
64
  Agents.registerAgent(Patcher);
58
65
  Agents.registerAgent(Developer);
59
66
  Agents.registerAgent(Setup);
60
- Agents.loadAgentsFromConfig(services());
67
+ Agents.loadAgentsFromConfig(agentContext);
61
68
 
62
69
  Tools.defineTools(includedTools, allTools);
63
70
 
64
71
  // Add Mcp service to tool context directly so MCP management tools can access it
65
72
  Tools.addContext("Mcp", Mcp);
66
73
 
74
+ // Store the fully-wired AgentContext on AgentService so AgentModule.setupAgent
75
+ // can retrieve it when creating fresh agent instances via createAgent().
76
+ Agents.setAgentContext(agentContext);
77
+
67
78
  console.log("🔌 Connecting to MCP...");
68
79
  await Mcp.connectToConfigured(Tools);
69
80
  console.log("Connecting to clients...");
@@ -322,7 +333,9 @@ async function main() {
322
333
  .action(async (options) => {
323
334
  try {
324
335
  await setupServices();
325
- const setupModule = new SetupModule();
336
+ const agentModule = new AgentModule();
337
+ await agentModule.initialize(chatService);
338
+ const setupModule = new SetupModule(agentModule);
326
339
  await setupModule.initialize(chatService);
327
340
  await setupModule.handleSetupCommand([]);
328
341
  } catch (error) {
@@ -367,11 +380,15 @@ async function main() {
367
380
  program
368
381
  .command("sessions")
369
382
  .description("Manage agent sessions from CLI")
370
- .action(async () => {
383
+ .option("--all", "Show all historical sessions (default: current process only)")
384
+ .option("--csv", "Output sessions as CSV")
385
+ .action(async (options) => {
371
386
  try {
372
387
  const agentModule = new AgentModule();
373
388
  await agentModule.initialize(chatService);
374
- await agentModule.logSessionTable();
389
+ const sessionsModule = new SessionsModule(agentModule);
390
+ await sessionsModule.initialize(chatService);
391
+ await sessionsModule.logSessionTable(options.all || false, options.csv || false, true);
375
392
  } catch (error) {
376
393
  console.error("Error listing sessions:", error);
377
394
  process.exit(1);
@@ -394,6 +411,8 @@ async function main() {
394
411
  "--no-sandbox",
395
412
  "Run worker directly on host (disable sandbox mode)"
396
413
  )
414
+ .option("--passkey", "Set up passkey authentication for this worker")
415
+ .option("--passkey-reset", "Remove passkey authentication requirement")
397
416
  .action(async (options) => {
398
417
  await setupServices();
399
418
  await worker(options);
package/src/config.ts CHANGED
@@ -262,6 +262,21 @@ export async function getConfig() {
262
262
  }
263
263
  }
264
264
 
265
+ export async function getGlobalConfig(): Promise<Config> {
266
+ const globalConfigDir = getGlobalConfigDir();
267
+ const globalConfigPath = path.join(globalConfigDir, "knowhow.json");
268
+ if (!fs.existsSync(globalConfigPath)) {
269
+ return {} as Config;
270
+ }
271
+ try {
272
+ const config = await readFile(globalConfigPath, "utf8");
273
+ return JSON.parse(config) as Config;
274
+ } catch (error) {
275
+ console.warn("Failed to load global knowhow config:", error);
276
+ return {} as Config;
277
+ }
278
+ }
279
+
265
280
  export async function migrateConfig() {
266
281
  // Apply migrations, used to keep config structure up to date.
267
282
  if (!fs.existsSync(".knowhow/knowhow.json")) {
@@ -98,7 +98,7 @@ export class AgentsMdPlugin extends PluginBase implements Plugin {
98
98
 
99
99
  events.emit("agent:msg", alertMessage);
100
100
  } catch (error) {
101
- console.error("AGENTS-MD PLUGIN: Error handling file event:", error);
101
+ this.log("AGENTS-MD PLUGIN: Error handling file event: " + error, "error");
102
102
  }
103
103
  }
104
104
 
@@ -110,7 +110,7 @@ Your modifications are automatically tracked separately and won't affect the use
110
110
 
111
111
  await this.setBranch("main");
112
112
  } catch (error) {
113
- console.error("Failed to initialize .knowhow git repository:", error);
113
+ this.log(`Failed to initialize .knowhow git repository: ${error}`, "error");
114
114
  }
115
115
  }
116
116
 
@@ -144,7 +144,7 @@ Your modifications are automatically tracked separately and won't affect the use
144
144
  try {
145
145
  return this.gitCommand(command, options);
146
146
  } catch (error) {
147
- console.warn(`Safe git command failed: ${command}`, error);
147
+ this.log(`Safe git command failed: ${command} - ${error}`, "warn");
148
148
  return null;
149
149
  }
150
150
  }
@@ -235,7 +235,7 @@ Your modifications are automatically tracked separately and won't affect the use
235
235
 
236
236
  // Get the current HEAD commit hash from the actual repo (if it exists)
237
237
  const actualRepoHash = this.getRepoHash();
238
- console.log(`GitPlugin: Current branch is ${this.getCurrentBranch()}`);
238
+ this.log(`Current branch is ${this.getCurrentBranch()}`);
239
239
 
240
240
  // First, handle any uncommitted changes on the current branch
241
241
  const hasChanges = await this.hasChanges();
@@ -246,7 +246,7 @@ Your modifications are automatically tracked separately and won't affect the use
246
246
  : `sync ${new Date().toISOString()}`;
247
247
  await this.commitAll(message);
248
248
  } catch (error) {
249
- console.error("Failed to commit uncommitted changes:", error);
249
+ this.log(`Failed to commit uncommitted changes: ${error}`, "error");
250
250
  }
251
251
  }
252
252
 
@@ -259,7 +259,7 @@ Your modifications are automatically tracked separately and won't affect the use
259
259
 
260
260
  await this.setBranch("main");
261
261
  } catch (error) {
262
- console.error("Failed to ensure clean state:", error);
262
+ this.log(`Failed to ensure clean state: ${error}`, "error");
263
263
  }
264
264
  }
265
265
 
@@ -288,7 +288,7 @@ Your modifications are automatically tracked separately and won't affect the use
288
288
  this.gitCommand(`checkout -b ${branchName}`);
289
289
  }
290
290
  } catch (error) {
291
- console.error(`GitPlugin: Failed to set branch ${branchName}:`);
291
+ this.log(`Failed to set branch ${branchName}`, "error");
292
292
  }
293
293
  }
294
294
 
@@ -298,7 +298,7 @@ Your modifications are automatically tracked separately and won't affect the use
298
298
  try {
299
299
  this.gitCommand(`checkout -b ${branchName}`);
300
300
  } catch (error) {
301
- console.error(`GitPlugin: Failed to create branch ${branchName}:`);
301
+ this.log(`Failed to create branch ${branchName}`, "error");
302
302
  }
303
303
  }
304
304
 
@@ -317,7 +317,7 @@ Your modifications are automatically tracked separately and won't affect the use
317
317
  try {
318
318
  this.gitCommand(`add "${file}"`);
319
319
  } catch (error) {
320
- console.warn(`Failed to add file ${file}:`, error);
320
+ this.log(`Failed to add file ${file}: ${error}`, "warn");
321
321
  }
322
322
  }
323
323
  } else {
@@ -339,7 +339,7 @@ Your modifications are automatically tracked separately and won't affect the use
339
339
  this.gitCommand("add -A");
340
340
  await this.commitWithEvents(message);
341
341
  } catch (error) {
342
- console.error("Failed to commit all changes:", error);
342
+ this.log(`Failed to commit all changes: ${error}`, "error");
343
343
  }
344
344
  }
345
345
 
@@ -389,7 +389,7 @@ Your modifications are automatically tracked separately and won't affect the use
389
389
  `
390
390
  );
391
391
  } catch (error) {
392
- console.error("Failed to commit with events:", error);
392
+ this.log(`Failed to commit with events: ${error}`, "error");
393
393
  }
394
394
  }
395
395
 
@@ -402,7 +402,7 @@ Your modifications are automatically tracked separately and won't affect the use
402
402
  try {
403
403
  this.gitCommand('commit --allow-empty -m "Initial empty commit"');
404
404
  } catch (error) {
405
- console.warn("Could not create initial commit:", error);
405
+ this.log(`Could not create initial commit: ${error}`, "warn");
406
406
  }
407
407
  }
408
408
  }
@@ -424,7 +424,7 @@ Your modifications are automatically tracked separately and won't affect the use
424
424
 
425
425
  await this.commitAll(message);
426
426
  } catch (error) {
427
- console.error("Auto-commit failed:", error);
427
+ this.log(`Auto-commit failed: ${error}`, "error");
428
428
  }
429
429
  }
430
430
 
@@ -455,9 +455,9 @@ Your modifications are automatically tracked separately and won't affect the use
455
455
  );
456
456
  }
457
457
 
458
- console.log(`Created new task branch: ${branchName}`);
458
+ this.log(`Created new task branch: ${branchName}`);
459
459
  } catch (error) {
460
- console.error("Failed to handle new task:", error);
460
+ this.log(`Failed to handle new task: ${error}`, "error");
461
461
  }
462
462
  }
463
463
 
@@ -466,7 +466,7 @@ Your modifications are automatically tracked separately and won't affect the use
466
466
 
467
467
  try {
468
468
  if (!this.currentTask) {
469
- console.warn("No tasks in progress to complete");
469
+ this.log("No tasks in progress to complete", "warn");
470
470
  return;
471
471
  }
472
472
 
@@ -488,7 +488,7 @@ Your modifications are automatically tracked separately and won't affect the use
488
488
  // Clear current task
489
489
  this.currentTask = null;
490
490
  } catch (error) {
491
- console.error("Failed to handle task completion:", error);
491
+ this.log(`Failed to handle task completion: ${error}`, "error");
492
492
  }
493
493
  }
494
494
 
@@ -523,9 +523,9 @@ Your modifications are automatically tracked separately and won't affect the use
523
523
  try {
524
524
  const noteMessage = `[Build Stable] No linting issues found on branch: ${this.getCurrentBranch()}`;
525
525
  this.gitCommand(`notes add -f -m "${noteMessage}" ${commitHash}`);
526
- console.log(`GitPlugin: Marked commit ${commitHash} as build stable`);
526
+ this.log(`Marked commit ${commitHash} as build stable`);
527
527
  } catch (error) {
528
- console.warn(`GitPlugin: Failed to add git note for commit ${commitHash}:`, error);
528
+ this.log(`Failed to add git note for commit ${commitHash}: ${error}`, "warn");
529
529
  }
530
530
  }
531
531
 
@@ -559,7 +559,7 @@ Your modifications are automatically tracked separately and won't affect the use
559
559
  .map((line) => line.replace(/^\*?\s*/, "").trim())
560
560
  .filter((line) => line.length > 0);
561
561
  } catch (error) {
562
- console.error("Error getting branches:", error);
562
+ this.log(`Error getting branches: ${error}`, "error");
563
563
  return [];
564
564
  }
565
565
  }
@@ -592,7 +592,7 @@ Your modifications are automatically tracked separately and won't affect the use
592
592
  this.commitAll(message);
593
593
  }
594
594
  } catch (error) {
595
- console.error(`Failed to merge ${branchName}:`, error);
595
+ this.log(`Failed to merge ${branchName}: ${error}`, "error");
596
596
  }
597
597
  }
598
598
  }
@@ -43,6 +43,17 @@ export abstract class PluginBase implements Plugin {
43
43
  return true; // subclasses override if needed
44
44
  }
45
45
 
46
+ protected log(message: string, level: "info" | "warn" | "error" = "info"): void {
47
+ if (this.context.Events) {
48
+ this.context.Events.log(this.meta.name, message, level);
49
+ } else {
50
+ // Fallback to console if no Events service
51
+ if (level === "error") console.error(`[${this.meta.name}] ${message}`);
52
+ else if (level === "warn") console.warn(`[${this.meta.name}] ${message}`);
53
+ else console.log(`[${this.meta.name}] ${message}`);
54
+ }
55
+ }
56
+
46
57
  /* ------------------------------------------------------------------ */
47
58
  /** Default callMany implementation - delegates to call ------------ */
48
59
  /* ------------------------------------------------------------------ */
@@ -0,0 +1,150 @@
1
+ import { promises as fs, Dirent } from "fs";
2
+ import * as path from "path";
3
+ import { PluginBase, PluginMeta } from "./PluginBase";
4
+ 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
+
25
+ export class SkillsPlugin extends PluginBase implements Plugin {
26
+ static readonly meta: PluginMeta = {
27
+ key: "skills",
28
+ name: "Skills Plugin",
29
+ description:
30
+ "Scans configured skills directories for SKILL.md files and provides skill content to agents",
31
+ requires: [],
32
+ };
33
+
34
+ meta = SkillsPlugin.meta;
35
+
36
+ constructor(context: PluginContext) {
37
+ super(context);
38
+ }
39
+
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);
54
+
55
+ 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
+ }
134
+ }
135
+
136
+ return skills;
137
+ }
138
+
139
+ private buildSkillSummary(skills: SkillMeta[]): string {
140
+ const lines = ["Available skills:"];
141
+ for (const skill of skills) {
142
+ lines.push(`- ${skill.name} (${skill.filePath}): ${skill.description}`);
143
+ }
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
+ return lines.join("\n");
149
+ }
150
+ }
@@ -57,7 +57,7 @@ export class AsanaPlugin extends PluginBase {
57
57
  });
58
58
 
59
59
  const allTasks = tasksEmbeddings.concat(projectTaskEmbeddings);
60
- console.log("Found ", allTasks.length, "tasks");
60
+ this.log(`Found ${allTasks.length} tasks`);
61
61
  return allTasks;
62
62
  }
63
63
 
@@ -92,7 +92,7 @@ export class AsanaPlugin extends PluginBase {
92
92
  async getTasksFromProjectUrl(url: string) {
93
93
  const urlParts = url.split("/");
94
94
  const projectId = urlParts[4];
95
- console.log({ projectId });
95
+ this.log(`Project ID: ${projectId}`);
96
96
  if (!projectId) {
97
97
  return null;
98
98
  }
@@ -113,7 +113,7 @@ export class AsanaPlugin extends PluginBase {
113
113
  async getTaskFromUrl(url: string) {
114
114
  const taskId = url.split("/").pop();
115
115
  if (taskId) {
116
- console.log(`Fetching Asana task ${taskId}`);
116
+ this.log(`Fetching Asana task ${taskId}`);
117
117
  return await this.getTaskData(taskId);
118
118
  }
119
119
  return null;
@@ -124,7 +124,7 @@ export class AsanaPlugin extends PluginBase {
124
124
  const task = await this.asanaClient.tasks.findById(taskId);
125
125
  return task;
126
126
  } catch (error) {
127
- console.error("Error fetching Asana task:", error);
127
+ this.log(`Error fetching Asana task: ${error}`, "error");
128
128
  return null;
129
129
  }
130
130
  }
@@ -41,7 +41,7 @@ export class EmbeddingPlugin extends PluginBase {
41
41
  cwd: process.cwd(),
42
42
  });
43
43
  child.unref();
44
- console.log(`EMBEDDING PLUGIN: Started 'knowhow embed' in background (pid: ${child.pid})`);
44
+ this.log(`Started 'knowhow embed' in background (pid: ${child.pid})`);
45
45
  return "Embedding started in background process";
46
46
  }
47
47
 
@@ -65,13 +65,11 @@ export class EmbeddingPlugin extends PluginBase {
65
65
  pruneMetadata(context);
66
66
 
67
67
  for (const entry of context) {
68
- console.log(`EMBEDDING PLUGIN: Reading entry ${entry.id}`);
68
+ this.log(`Reading entry ${entry.id}`);
69
69
  }
70
70
 
71
71
  const contextLength = JSON.stringify(context).split(" ").length;
72
- console.log(
73
- `EMBEDDING PLUGIN: Found ${context.length} entries. Loading ${contextLength} words`
74
- );
72
+ this.log(`Found ${context.length} entries. Loading ${contextLength} words`);
75
73
 
76
74
  return `EMBEDDING PLUGIN: Our knowledgebase contains this information which can be used to answer the question:
77
75
  ${JSON.stringify(context)}`;
@@ -40,7 +40,7 @@ export class ExecPlugin extends PluginBase {
40
40
  }
41
41
 
42
42
  try {
43
- console.log(`EXEC PLUGIN: Executing: ${command}`);
43
+ this.log(`Executing: ${command}`);
44
44
 
45
45
  // Execute the command
46
46
  const result = execSync(command, {
@@ -55,9 +55,9 @@ export class ExecPlugin extends PluginBase {
55
55
  const stderr = error.stderr || "";
56
56
  const stdout = error.stdout || "";
57
57
 
58
- console.error(`EXEC PLUGIN: Command failed: ${errorMessage}`);
58
+ this.log(`Command failed: ${errorMessage}`, "error");
59
59
  if (stderr) {
60
- console.error(stderr);
60
+ this.log(stderr, "error");
61
61
  }
62
62
 
63
63
  return `EXEC PLUGIN: Command \`${command}\` failed:\n\`\`\`\n${stdout}\n${stderr}\n${errorMessage}\n\`\`\``;
@@ -64,11 +64,11 @@ export class FigmaPlugin extends PluginBase implements Plugin {
64
64
  return null;
65
65
  }
66
66
  try {
67
- console.log("Fetching figma data", { fileId, nodeIds });
67
+ this.log(`Fetching figma data: ${JSON.stringify({ fileId, nodeIds })}`);
68
68
  const response = await this.client.fileImages(fileId, { ids: nodeIds });
69
69
  return { id: fileId, ...response.data };
70
70
  } catch (error) {
71
- console.error("Error fetching Figma file data:", error);
71
+ this.log(`Error fetching Figma file data: ${error}`, "error");
72
72
  return null;
73
73
  }
74
74
  }
@@ -146,11 +146,7 @@ export class FigmaPlugin extends PluginBase implements Plugin {
146
146
  imageUrl,
147
147
  `Describe the image with relavant information for this user question: ${userPrompt}`
148
148
  );
149
-
150
- console.log(
151
- "FIGMA PLUGIN: Image description",
152
- imageDescription.choices[0].message.content
153
- );
149
+ this.log(`Image description: ${imageDescription.choices[0].message.content}`);
154
150
  responses.push({ nodeId, imageDescription });
155
151
  }
156
152
  }