@tyvm/knowhow 0.0.83 → 0.0.85

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 (237) 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/researcher/researcher.ts +1 -2
  5. package/src/agents/tools/aiClient.ts +48 -0
  6. package/src/agents/tools/list.ts +57 -0
  7. package/src/agents/tools/startAgentTask.ts +3 -1
  8. package/src/chat/CliChatService.ts +20 -4
  9. package/src/chat/modules/AgentModule.ts +399 -357
  10. package/src/chat/modules/CustomCommandsModule.ts +0 -1
  11. package/src/chat/modules/InternalChatModule.ts +18 -2
  12. package/src/chat/modules/RendererModule.ts +109 -0
  13. package/src/chat/modules/SessionsModule.ts +854 -0
  14. package/src/chat/modules/SetupModule.ts +6 -8
  15. package/src/chat/modules/index.ts +1 -0
  16. package/src/chat/renderer/CompactRenderer.ts +209 -0
  17. package/src/chat/renderer/ConsoleRenderer.ts +141 -0
  18. package/src/chat/renderer/FancyRenderer.ts +421 -0
  19. package/src/chat/renderer/index.ts +5 -0
  20. package/src/chat/renderer/loadRenderer.ts +314 -0
  21. package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
  22. package/src/chat/renderer/types.ts +88 -0
  23. package/src/chat/types.ts +5 -0
  24. package/src/chat.ts +69 -5
  25. package/src/cli.ts +24 -5
  26. package/src/clients/index.ts +91 -0
  27. package/src/clients/pricing/google.ts +81 -2
  28. package/src/clients/pricing/openai.ts +68 -0
  29. package/src/config.ts +15 -0
  30. package/src/plugins/AgentsMdPlugin.ts +1 -1
  31. package/src/plugins/GitPlugin.ts +20 -20
  32. package/src/plugins/PluginBase.ts +11 -0
  33. package/src/plugins/SkillsPlugin.ts +150 -0
  34. package/src/plugins/asana.ts +4 -4
  35. package/src/plugins/embedding.ts +3 -5
  36. package/src/plugins/exec.ts +3 -3
  37. package/src/plugins/figma.ts +3 -7
  38. package/src/plugins/github.ts +18 -29
  39. package/src/plugins/jira.ts +2 -2
  40. package/src/plugins/language.ts +4 -4
  41. package/src/plugins/linear.ts +4 -4
  42. package/src/plugins/notion.ts +6 -8
  43. package/src/plugins/plugins.ts +29 -3
  44. package/src/plugins/url.ts +2 -2
  45. package/src/plugins/vim.ts +4 -3
  46. package/src/services/AgentService.ts +17 -0
  47. package/src/services/AgentSyncFs.ts +3 -0
  48. package/src/services/EventService.ts +168 -27
  49. package/src/services/KnowhowClient.ts +1 -0
  50. package/src/services/SessionManager.ts +51 -1
  51. package/src/services/SyncedAgentWatcher.ts +397 -0
  52. package/src/services/SyncerService.ts +147 -0
  53. package/src/services/index.ts +2 -0
  54. package/src/services/modules/index.ts +14 -3
  55. package/src/types.ts +103 -5
  56. package/src/worker.ts +80 -2
  57. package/src/workers/auth/PasskeySetup.ts +185 -0
  58. package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
  59. package/src/workers/auth/types.ts +58 -0
  60. package/src/workers/tools/getChallenge.ts +33 -0
  61. package/src/workers/tools/index.ts +8 -0
  62. package/src/workers/tools/lock.ts +31 -0
  63. package/src/workers/tools/unlock.ts +116 -0
  64. package/tests/clients/pricing.test.ts +144 -0
  65. package/tests/unit/modules/moduleLoading.test.ts +226 -0
  66. package/tests/unit/plugins/pluginLoading.test.ts +151 -0
  67. package/ts_build/package.json +4 -2
  68. package/ts_build/src/agents/base/base.d.ts +4 -3
  69. package/ts_build/src/agents/base/base.js +54 -30
  70. package/ts_build/src/agents/base/base.js.map +1 -1
  71. package/ts_build/src/agents/index.d.ts +3 -0
  72. package/ts_build/src/agents/index.js +21 -11
  73. package/ts_build/src/agents/index.js.map +1 -1
  74. package/ts_build/src/agents/researcher/researcher.js +1 -1
  75. package/ts_build/src/agents/researcher/researcher.js.map +1 -1
  76. package/ts_build/src/agents/tools/aiClient.d.ts +3 -0
  77. package/ts_build/src/agents/tools/aiClient.js +31 -1
  78. package/ts_build/src/agents/tools/aiClient.js.map +1 -1
  79. package/ts_build/src/agents/tools/list.js +48 -0
  80. package/ts_build/src/agents/tools/list.js.map +1 -1
  81. package/ts_build/src/agents/tools/startAgentTask.js +2 -1
  82. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  83. package/ts_build/src/chat/CliChatService.js +16 -5
  84. package/ts_build/src/chat/CliChatService.js.map +1 -1
  85. package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
  86. package/ts_build/src/chat/modules/AgentModule.js +248 -258
  87. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  88. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
  90. package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
  91. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  92. package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
  93. package/ts_build/src/chat/modules/RendererModule.js +76 -0
  94. package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
  95. package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
  96. package/ts_build/src/chat/modules/SessionsModule.js +582 -0
  97. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
  98. package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
  99. package/ts_build/src/chat/modules/SetupModule.js +4 -6
  100. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  101. package/ts_build/src/chat/modules/index.d.ts +1 -0
  102. package/ts_build/src/chat/modules/index.js +3 -1
  103. package/ts_build/src/chat/modules/index.js.map +1 -1
  104. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
  105. package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
  106. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
  107. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
  108. package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
  109. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
  110. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
  111. package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
  112. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
  113. package/ts_build/src/chat/renderer/index.d.ts +5 -0
  114. package/ts_build/src/chat/renderer/index.js +29 -0
  115. package/ts_build/src/chat/renderer/index.js.map +1 -0
  116. package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
  117. package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
  118. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
  119. package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
  120. package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
  121. package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
  122. package/ts_build/src/chat/renderer/types.d.ts +75 -0
  123. package/ts_build/src/chat/renderer/types.js +3 -0
  124. package/ts_build/src/chat/renderer/types.js.map +1 -0
  125. package/ts_build/src/chat/types.d.ts +5 -0
  126. package/ts_build/src/chat.js +46 -4
  127. package/ts_build/src/chat.js.map +1 -1
  128. package/ts_build/src/cli.js +18 -5
  129. package/ts_build/src/cli.js.map +1 -1
  130. package/ts_build/src/clients/gemini.d.ts +10 -10
  131. package/ts_build/src/clients/index.d.ts +10 -0
  132. package/ts_build/src/clients/index.js +58 -0
  133. package/ts_build/src/clients/index.js.map +1 -1
  134. package/ts_build/src/clients/pricing/google.d.ts +10 -10
  135. package/ts_build/src/clients/pricing/google.js +74 -2
  136. package/ts_build/src/clients/pricing/google.js.map +1 -1
  137. package/ts_build/src/clients/pricing/openai.js +65 -0
  138. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  139. package/ts_build/src/config.d.ts +1 -0
  140. package/ts_build/src/config.js +17 -1
  141. package/ts_build/src/config.js.map +1 -1
  142. package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
  143. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
  144. package/ts_build/src/plugins/GitPlugin.js +20 -20
  145. package/ts_build/src/plugins/GitPlugin.js.map +1 -1
  146. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  147. package/ts_build/src/plugins/PluginBase.js +13 -0
  148. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  149. package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
  150. package/ts_build/src/plugins/SkillsPlugin.js +149 -0
  151. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
  152. package/ts_build/src/plugins/asana.js +4 -4
  153. package/ts_build/src/plugins/asana.js.map +1 -1
  154. package/ts_build/src/plugins/embedding.js +3 -3
  155. package/ts_build/src/plugins/embedding.js.map +1 -1
  156. package/ts_build/src/plugins/exec.js +3 -3
  157. package/ts_build/src/plugins/exec.js.map +1 -1
  158. package/ts_build/src/plugins/figma.js +3 -3
  159. package/ts_build/src/plugins/figma.js.map +1 -1
  160. package/ts_build/src/plugins/github.js +18 -18
  161. package/ts_build/src/plugins/github.js.map +1 -1
  162. package/ts_build/src/plugins/jira.js +2 -2
  163. package/ts_build/src/plugins/jira.js.map +1 -1
  164. package/ts_build/src/plugins/language.js +4 -4
  165. package/ts_build/src/plugins/language.js.map +1 -1
  166. package/ts_build/src/plugins/linear.js +4 -4
  167. package/ts_build/src/plugins/linear.js.map +1 -1
  168. package/ts_build/src/plugins/notion.js +6 -6
  169. package/ts_build/src/plugins/notion.js.map +1 -1
  170. package/ts_build/src/plugins/plugins.d.ts +3 -0
  171. package/ts_build/src/plugins/plugins.js +18 -3
  172. package/ts_build/src/plugins/plugins.js.map +1 -1
  173. package/ts_build/src/plugins/url.js +2 -2
  174. package/ts_build/src/plugins/url.js.map +1 -1
  175. package/ts_build/src/plugins/vim.js +2 -2
  176. package/ts_build/src/plugins/vim.js.map +1 -1
  177. package/ts_build/src/services/AgentService.d.ts +3 -0
  178. package/ts_build/src/services/AgentService.js +7 -0
  179. package/ts_build/src/services/AgentService.js.map +1 -1
  180. package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
  181. package/ts_build/src/services/AgentSyncFs.js +2 -0
  182. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  183. package/ts_build/src/services/EventService.d.ts +25 -2
  184. package/ts_build/src/services/EventService.js +92 -14
  185. package/ts_build/src/services/EventService.js.map +1 -1
  186. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  187. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  188. package/ts_build/src/services/SessionManager.d.ts +6 -0
  189. package/ts_build/src/services/SessionManager.js +39 -1
  190. package/ts_build/src/services/SessionManager.js.map +1 -1
  191. package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
  192. package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
  193. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
  194. package/ts_build/src/services/SyncerService.d.ts +30 -0
  195. package/ts_build/src/services/SyncerService.js +72 -0
  196. package/ts_build/src/services/SyncerService.js.map +1 -0
  197. package/ts_build/src/services/index.d.ts +2 -0
  198. package/ts_build/src/services/index.js +2 -0
  199. package/ts_build/src/services/index.js.map +1 -1
  200. package/ts_build/src/services/modules/index.js +10 -2
  201. package/ts_build/src/services/modules/index.js.map +1 -1
  202. package/ts_build/src/types.d.ts +51 -2
  203. package/ts_build/src/types.js +73 -5
  204. package/ts_build/src/types.js.map +1 -1
  205. package/ts_build/src/worker.d.ts +2 -0
  206. package/ts_build/src/worker.js +59 -4
  207. package/ts_build/src/worker.js.map +1 -1
  208. package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
  209. package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
  210. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
  211. package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
  212. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
  213. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
  214. package/ts_build/src/workers/auth/types.d.ts +36 -0
  215. package/ts_build/src/workers/auth/types.js +3 -0
  216. package/ts_build/src/workers/auth/types.js.map +1 -0
  217. package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
  218. package/ts_build/src/workers/tools/getChallenge.js +27 -0
  219. package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
  220. package/ts_build/src/workers/tools/index.d.ts +6 -0
  221. package/ts_build/src/workers/tools/index.js +10 -0
  222. package/ts_build/src/workers/tools/index.js.map +1 -1
  223. package/ts_build/src/workers/tools/lock.d.ts +9 -0
  224. package/ts_build/src/workers/tools/lock.js +27 -0
  225. package/ts_build/src/workers/tools/lock.js.map +1 -0
  226. package/ts_build/src/workers/tools/unlock.d.ts +18 -0
  227. package/ts_build/src/workers/tools/unlock.js +78 -0
  228. package/ts_build/src/workers/tools/unlock.js.map +1 -0
  229. package/ts_build/tests/clients/pricing.test.d.ts +1 -0
  230. package/ts_build/tests/clients/pricing.test.js +90 -0
  231. package/ts_build/tests/clients/pricing.test.js.map +1 -0
  232. package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
  233. package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
  234. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
  235. package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
  236. package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
  237. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Renderer Loader - Dynamically loads a custom renderer from an npm package or local file.
3
+ *
4
+ * Supports:
5
+ * - built-in names: "basic" | "fancy" | "compact"
6
+ * - npm packages: "@scope/package" or "my-renderer-package"
7
+ * - local JS files: "./my-renderer.js" or "/absolute/path/renderer.js"
8
+ * - local TS files: "./my-renderer.ts" (compiled on-the-fly via tsx/ts-node if available)
9
+ */
10
+
11
+ import path from "path";
12
+ import fs from "fs";
13
+ import { AgentRenderer } from "./types";
14
+ import { ConsoleRenderer } from "./ConsoleRenderer";
15
+ import { FancyRenderer } from "./FancyRenderer";
16
+ import { CompactRenderer } from "./CompactRenderer";
17
+
18
+ /** Built-in renderer names that ship with knowhow */
19
+ const BUILTIN_RENDERERS: Record<string, () => AgentRenderer> = {
20
+ basic: () => new ConsoleRenderer(),
21
+ fancy: () => new FancyRenderer(),
22
+ compact: () => new CompactRenderer(),
23
+ };
24
+
25
+ /**
26
+ * Attempt to transpile + load a TypeScript file as a module.
27
+ * Falls back gracefully if tsx / ts-node are unavailable.
28
+ */
29
+ async function loadTsFile(filePath: string): Promise<any> {
30
+ // Try using jiti (another popular TS loader)
31
+ try {
32
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
33
+ const jiti = require("jiti");
34
+ const loader = typeof jiti === "function" ? jiti : jiti.default;
35
+ return loader(filePath, { interopDefault: true })(filePath);
36
+ } catch (_) {}
37
+
38
+ // Try ts-node
39
+ try {
40
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
41
+ require("ts-node/register");
42
+ return require(filePath); // eslint-disable-line @typescript-eslint/no-var-requires
43
+ } catch (_) {}
44
+
45
+ // Last resort: try esbuild-register
46
+ try {
47
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
48
+ const { register } = require("esbuild-register/dist/node");
49
+ const { unregister } = register();
50
+ const mod = require(filePath);
51
+ unregister();
52
+ return mod;
53
+ } catch (_) {}
54
+
55
+ throw new Error(
56
+ `Cannot load TypeScript file "${filePath}". Install tsx, jiti, ts-node, or esbuild-register:\n` +
57
+ ` npm install -g tsx\n or npm install -g ts-node`
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Load a renderer from a specifier string.
63
+ *
64
+ * The loaded module must export either:
65
+ * - A default export that is an AgentRenderer instance, OR
66
+ * - A named export `createRenderer` function that returns an AgentRenderer, OR
67
+ * - A named export `renderer` that is an AgentRenderer instance
68
+ *
69
+ * @param specifier - npm package name or file path (relative paths resolved from cwd)
70
+ * @returns An AgentRenderer instance
71
+ */
72
+ export async function loadRenderer(specifier: string): Promise<AgentRenderer> {
73
+ // Check for built-in renderer names first
74
+ const builtin = BUILTIN_RENDERERS[specifier.toLowerCase()];
75
+ if (builtin) {
76
+ return builtin();
77
+ }
78
+
79
+ let mod: any;
80
+
81
+ const isLocalPath =
82
+ specifier.startsWith("./") ||
83
+ specifier.startsWith("../") ||
84
+ specifier.startsWith("/");
85
+
86
+ if (isLocalPath) {
87
+ const resolved = path.resolve(process.cwd(), specifier);
88
+
89
+ if (!fs.existsSync(resolved)) {
90
+ throw new Error(`Renderer file not found: ${resolved}`);
91
+ }
92
+
93
+ if (resolved.endsWith(".ts")) {
94
+ mod = await loadTsFile(resolved);
95
+ } else {
96
+ // JS or JSON
97
+ mod = await import(resolved);
98
+ }
99
+ } else {
100
+ // npm package
101
+ try {
102
+ mod = await import(specifier);
103
+ } catch (err: any) {
104
+ throw new Error(
105
+ `Failed to load renderer package "${specifier}": ${err.message}\n` +
106
+ `Make sure it is installed: npm install -g ${specifier}`
107
+ );
108
+ }
109
+ }
110
+
111
+ // Resolve the renderer from the module exports
112
+ if (mod?.default && typeof mod.default.render === "function") {
113
+ return mod.default as AgentRenderer;
114
+ }
115
+
116
+ if (typeof mod?.createRenderer === "function") {
117
+ return mod.createRenderer() as AgentRenderer;
118
+ }
119
+
120
+ if (mod?.renderer && typeof mod.renderer.render === "function") {
121
+ return mod.renderer as AgentRenderer;
122
+ }
123
+
124
+ // Check the default export might be a class
125
+ if (mod?.default && typeof mod.default === "function") {
126
+ try {
127
+ const instance = new mod.default();
128
+ if (typeof instance.render === "function") {
129
+ return instance as AgentRenderer;
130
+ }
131
+ } catch (_) {}
132
+ }
133
+
134
+ // Check named exports for a class with "Renderer" in name
135
+ for (const key of Object.keys(mod || {})) {
136
+ if (key.toLowerCase().includes("renderer") && typeof mod[key] === "function") {
137
+ try {
138
+ const instance = new mod[key]();
139
+ if (typeof instance.render === "function") {
140
+ return instance as AgentRenderer;
141
+ }
142
+ } catch (_) {}
143
+ }
144
+ }
145
+
146
+ throw new Error(
147
+ `Renderer module "${specifier}" does not export a valid AgentRenderer.\n` +
148
+ `Expected one of:\n` +
149
+ ` - default export: AgentRenderer instance\n` +
150
+ ` - default export: AgentRenderer class\n` +
151
+ ` - named export "createRenderer": () => AgentRenderer\n` +
152
+ ` - named export "renderer": AgentRenderer instance\n` +
153
+ ` - named export "SomethingRenderer": AgentRenderer class`
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Load a root chat module from a specifier string.
159
+ *
160
+ * The module must export either:
161
+ * - A default export that is a ChatModule class or instance
162
+ * - A named export "createModule" function
163
+ * - A named export with "Module" in the name
164
+ */
165
+ export async function loadRootModule(specifier: string): Promise<any> {
166
+ let mod: any;
167
+
168
+ const isLocalPath =
169
+ specifier.startsWith("./") ||
170
+ specifier.startsWith("../") ||
171
+ specifier.startsWith("/");
172
+
173
+ if (isLocalPath) {
174
+ const resolved = path.resolve(process.cwd(), specifier);
175
+
176
+ if (!fs.existsSync(resolved)) {
177
+ throw new Error(`Root module file not found: ${resolved}`);
178
+ }
179
+
180
+ if (resolved.endsWith(".ts")) {
181
+ mod = await loadTsFile(resolved);
182
+ } else {
183
+ mod = await import(resolved);
184
+ }
185
+ } else {
186
+ try {
187
+ mod = await import(specifier);
188
+ } catch (err: any) {
189
+ throw new Error(
190
+ `Failed to load root module package "${specifier}": ${err.message}`
191
+ );
192
+ }
193
+ }
194
+
195
+ // Try default export as instance
196
+ if (mod?.default && typeof mod.default.initialize === "function") {
197
+ return mod.default;
198
+ }
199
+
200
+ // Try default export as class
201
+ if (mod?.default && typeof mod.default === "function") {
202
+ try {
203
+ const instance = new mod.default();
204
+ if (typeof instance.initialize === "function") {
205
+ return instance;
206
+ }
207
+ } catch (_) {}
208
+ }
209
+
210
+ // Try createModule factory
211
+ if (typeof mod?.createModule === "function") {
212
+ return mod.createModule();
213
+ }
214
+
215
+ // Try named exports with "Module" in name
216
+ for (const key of Object.keys(mod || {})) {
217
+ if (key.toLowerCase().includes("module") && typeof mod[key] === "function") {
218
+ try {
219
+ const instance = new mod[key]();
220
+ if (typeof instance.initialize === "function") {
221
+ return instance;
222
+ }
223
+ } catch (_) {}
224
+ }
225
+ }
226
+
227
+ throw new Error(
228
+ `Root module "${specifier}" does not export a valid ChatModule.\n` +
229
+ `Expected a class or instance with an "initialize(chatService)" method.`
230
+ );
231
+ }
232
+
233
+ /**
234
+ * Load an additional chat module (not the root module) from a specifier string.
235
+ *
236
+ * The module must export either:
237
+ * - A default export that is a ChatModule class or instance
238
+ * - A named export "createModule" function that returns a ChatModule
239
+ * - A named export with "Module" in the name (class or instance)
240
+ *
241
+ * @param specifier - npm package name or file path (relative paths resolved from cwd)
242
+ * @returns An instantiated ChatModule (not yet initialized)
243
+ */
244
+ export async function loadChatModule(specifier: string): Promise<any> {
245
+ let mod: any;
246
+
247
+ const isLocalPath =
248
+ specifier.startsWith("./") ||
249
+ specifier.startsWith("../") ||
250
+ specifier.startsWith("/");
251
+
252
+ if (isLocalPath) {
253
+ const resolved = path.resolve(process.cwd(), specifier);
254
+
255
+ if (!fs.existsSync(resolved)) {
256
+ throw new Error(`Chat module file not found: ${resolved}`);
257
+ }
258
+
259
+ if (resolved.endsWith(".ts")) {
260
+ mod = await loadTsFile(resolved);
261
+ } else {
262
+ mod = await import(resolved);
263
+ }
264
+ } else {
265
+ try {
266
+ mod = await import(specifier);
267
+ } catch (err: any) {
268
+ throw new Error(
269
+ `Failed to load chat module package "${specifier}": ${err.message}`
270
+ );
271
+ }
272
+ }
273
+
274
+ // Try default export as instance (already has initialize)
275
+ if (mod?.default && typeof mod.default.initialize === "function") {
276
+ return mod.default;
277
+ }
278
+
279
+ // Try default export as class
280
+ if (mod?.default && typeof mod.default === "function") {
281
+ try {
282
+ const instance = new mod.default();
283
+ if (typeof instance.initialize === "function") {
284
+ return instance;
285
+ }
286
+ } catch (_) {}
287
+ }
288
+
289
+ // Try createModule factory
290
+ if (typeof mod?.createModule === "function") {
291
+ return mod.createModule();
292
+ }
293
+
294
+ // Try named exports with "Module" in name
295
+ for (const key of Object.keys(mod || {})) {
296
+ if (key.toLowerCase().includes("module") && typeof mod[key] === "function") {
297
+ try {
298
+ const instance = new mod[key]();
299
+ if (typeof instance.initialize === "function") {
300
+ return instance;
301
+ }
302
+ } catch (_) {}
303
+ }
304
+ }
305
+
306
+ throw new Error(
307
+ `Chat module "${specifier}" does not export a valid ChatModule.\n` +
308
+ `Expected a class or instance with an "initialize(chatService)" method.\n` +
309
+ `Supported export patterns:\n` +
310
+ ` - default export: ChatModule instance or class\n` +
311
+ ` - named export "createModule": () => ChatModule\n` +
312
+ ` - named export "SomethingModule": ChatModule class`
313
+ );
314
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Utility: Convert OpenAI-format Message[] threads into RenderEvent[] for the renderer.
3
+ * Shared by SyncedAgentWatcher implementations and the /logs command.
4
+ */
5
+
6
+ import { RenderEvent } from "../../chat/renderer/types";
7
+
8
+ export interface Message {
9
+ role: "assistant" | "user" | "tool" | "system";
10
+ content?: any;
11
+ tool_calls?: Array<{
12
+ id: string;
13
+ function: {
14
+ name: string;
15
+ arguments: string;
16
+ };
17
+ }>;
18
+ name?: string;
19
+ tool_call_id?: string;
20
+ }
21
+
22
+ export function messagesToRenderEvents(
23
+ messages: Message[],
24
+ taskId: string,
25
+ agentName: string
26
+ ): RenderEvent[] {
27
+ const events: RenderEvent[] = [];
28
+
29
+ for (const msg of messages) {
30
+ if (msg.role === "assistant") {
31
+ if (typeof msg.content === "string" && msg.content) {
32
+ events.push({
33
+ type: "agentMessage",
34
+ taskId,
35
+ agentName,
36
+ message: msg.content,
37
+ role: "assistant",
38
+ });
39
+ }
40
+ if (msg.tool_calls) {
41
+ for (const tc of msg.tool_calls) {
42
+ events.push({
43
+ type: "toolCall",
44
+ taskId,
45
+ agentName,
46
+ toolCall: {
47
+ id: tc.id,
48
+ function: {
49
+ name: tc.function.name,
50
+ arguments: tc.function.arguments,
51
+ },
52
+ },
53
+ });
54
+ }
55
+ }
56
+ } else if (msg.role === "tool") {
57
+ const content =
58
+ typeof msg.content === "string"
59
+ ? msg.content
60
+ : JSON.stringify(msg.content);
61
+ events.push({
62
+ type: "toolResult",
63
+ taskId,
64
+ agentName,
65
+ toolCall: { function: { name: msg.name || "unknown", arguments: "" } },
66
+ result: content,
67
+ });
68
+ } else if (msg.role === "user") {
69
+ const content =
70
+ typeof msg.content === "string"
71
+ ? msg.content
72
+ : Array.isArray(msg.content)
73
+ ? msg.content
74
+ .filter((c: any) => c.type === "text")
75
+ .map((c: any) => c.text)
76
+ .join("\n")
77
+ : String(msg.content ?? "");
78
+ // Skip workflow messages — these are internal agent control messages
79
+ // injected as user-role messages and should not be rendered to the user
80
+ if (content.trim().startsWith("<Workflow>") || /<Workflow>/i.test(content)) {
81
+ continue;
82
+ }
83
+ if (content) {
84
+ events.push({
85
+ type: "agentMessage",
86
+ taskId,
87
+ agentName,
88
+ message: content,
89
+ role: "user",
90
+ });
91
+ }
92
+ }
93
+ }
94
+
95
+ return events;
96
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Renderer interfaces for CLI agent output
3
+ */
4
+
5
+ export interface LogEvent {
6
+ taskId: string;
7
+ agentName: string;
8
+ message: string;
9
+ level: "info" | "warn" | "error";
10
+ timestamp: number;
11
+ }
12
+
13
+ export interface ToolCall {
14
+ id?: string;
15
+ function: {
16
+ name: string;
17
+ arguments: string;
18
+ };
19
+ }
20
+
21
+ export interface ToolCallEvent {
22
+ taskId: string;
23
+ agentName: string;
24
+ toolCall: ToolCall;
25
+ }
26
+
27
+ export interface ToolResultEvent {
28
+ taskId: string;
29
+ agentName: string;
30
+ toolCall: ToolCall;
31
+ result: any;
32
+ }
33
+
34
+ export interface AgentMessageEvent {
35
+ taskId: string;
36
+ agentName: string;
37
+ message: string;
38
+ role: "assistant" | "user";
39
+ }
40
+
41
+ export interface AgentDoneEvent {
42
+ taskId: string;
43
+ agentName: string;
44
+ output: string;
45
+ totalCost: number;
46
+ }
47
+
48
+ export interface AgentStatusEvent {
49
+ taskId: string;
50
+ agentName: string;
51
+ statusMessage: string;
52
+ details: {
53
+ totalCostUsd: number;
54
+ elapsedMs: number;
55
+ remainingTimeMs?: number;
56
+ remainingTurns?: number;
57
+ remainingBudget?: number;
58
+ };
59
+ timestamp: number;
60
+ }
61
+
62
+ export type RenderEvent =
63
+ | ({ type: "log" } & LogEvent)
64
+ | ({ type: "agentStatus" } & AgentStatusEvent)
65
+ | ({ type: "toolCall" } & ToolCallEvent)
66
+ | ({ type: "toolResult" } & ToolResultEvent)
67
+ | ({ type: "agentMessage" } & AgentMessageEvent)
68
+ | ({ type: "agentDone" } & AgentDoneEvent);
69
+
70
+ export interface AgentRenderer {
71
+ onLog(handler: (event: LogEvent) => void): void;
72
+ onAgentStatus(handler: (event: AgentStatusEvent) => void): void;
73
+ onToolCall(handler: (event: ToolCallEvent) => void): void;
74
+ onToolResult(handler: (event: ToolResultEvent) => void): void;
75
+ onAgentMessage(handler: (event: AgentMessageEvent) => void): void;
76
+ onAgentDone(handler: (event: AgentDoneEvent) => void): void;
77
+ render(event: RenderEvent): void;
78
+
79
+ /** Set the currently active agent task ID - only events for this task are shown */
80
+ setActiveTaskId(taskId: string | undefined): void;
81
+ getActiveTaskId(): string | undefined;
82
+
83
+ /**
84
+ * Replay the last N render events (used by /logs command).
85
+ * If count is not provided, shows last 10.
86
+ */
87
+ logMessages(events: RenderEvent[], count?: number): void;
88
+ }
package/src/chat/types.ts CHANGED
@@ -4,11 +4,13 @@
4
4
  import { ChatInteraction, Config } from "../types";
5
5
  import { BaseAgent } from "../agents/base/base";
6
6
  import { ToolsService } from "src/services";
7
+ import { AgentRenderer } from "./renderer/types";
7
8
 
8
9
  export interface ChatContext {
9
10
  debugMode?: boolean;
10
11
  agentMode?: boolean;
11
12
  currentAgent?: string;
13
+ promptText?: string;
12
14
  searchMode?: boolean;
13
15
  voiceMode?: boolean;
14
16
  multilineMode?: boolean;
@@ -17,6 +19,8 @@ export interface ChatContext {
17
19
  inputMethod?: InputMethod;
18
20
  selectedAgent?: BaseAgent;
19
21
  plugins: string[];
22
+ activeAgentTaskId?: string;
23
+ renderer?: AgentRenderer;
20
24
 
21
25
  [key: string]: any;
22
26
  }
@@ -25,6 +29,7 @@ export interface ChatMode {
25
29
  name: string;
26
30
  description: string;
27
31
  active: boolean;
32
+ promptText?: string | (() => string);
28
33
  }
29
34
 
30
35
  export interface CommandResult {
package/src/chat.ts CHANGED
@@ -2,11 +2,24 @@
2
2
 
3
3
  /**
4
4
  * New Modular Chat Interface - Simplified and cleaner than original chat.ts
5
+ *
6
+ * Supports custom renderers and root modules via .knowhow/knowhow.json:
7
+ *
8
+ * ```json
9
+ * {
10
+ * "chat": {
11
+ * "renderer": "./my-renderer.ts", // local TS/JS file or npm package
12
+ * "rootModule": "./my-module.ts" // local TS/JS file or npm package
13
+ * }
14
+ * }
15
+ * ```
5
16
  */
6
17
 
7
18
  import { CliChatService } from "./chat/CliChatService.js";
8
19
  import { InternalChatModule } from "./chat/modules/InternalChatModule.js";
9
20
  import { getConfig } from "./config.js";
21
+ import { loadRenderer, loadRootModule, loadChatModule } from "./chat/renderer/loadRenderer.js";
22
+ import { AgentModule } from "./chat/modules/AgentModule.js";
10
23
 
11
24
  async function main() {
12
25
  try {
@@ -39,11 +52,60 @@ async function main() {
39
52
  }
40
53
 
41
54
  // Create chat service with plugins
42
- const chatService = new CliChatService(config.plugins.enabled);
55
+ const chatService = new CliChatService(config.plugins?.enabled ?? []);
43
56
 
44
- // Load internal chat module (includes all core functionality)
45
- const internalModule = new InternalChatModule();
46
- await internalModule.initialize(chatService);
57
+ // ── Renderer ──────────────────────────────────────────────────────────────
58
+ // Load a custom renderer if configured, otherwise use the default
59
+ // ConsoleRenderer (already the default inside AgentModule).
60
+ const rendererSpecifier = config.chat?.renderer;
61
+ if (rendererSpecifier) {
62
+ try {
63
+ const customRenderer = await loadRenderer(rendererSpecifier);
64
+ // Inject the renderer into AgentModule via the chat service context
65
+ // AgentModule picks it up from context.renderer
66
+ chatService.setContext({ renderer: customRenderer });
67
+ console.log(`✓ Loaded custom renderer: ${rendererSpecifier}`);
68
+ } catch (err: any) {
69
+ console.warn(`⚠ Could not load renderer "${rendererSpecifier}": ${err.message}`);
70
+ console.warn(" Falling back to default ConsoleRenderer.");
71
+ }
72
+ }
73
+
74
+ // ── Root Module ───────────────────────────────────────────────────────────
75
+ // Allow swapping the root chat module entirely via config.
76
+ // The root module drives the whole CLI chat experience.
77
+ const rootModuleSpecifier = config.chat?.rootModule;
78
+ if (rootModuleSpecifier) {
79
+ try {
80
+ const customModule = await loadRootModule(rootModuleSpecifier);
81
+ await customModule.initialize(chatService);
82
+ console.log(`✓ Loaded custom root module: ${rootModuleSpecifier}`);
83
+ } catch (err: any) {
84
+ console.warn(`⚠ Could not load root module "${rootModuleSpecifier}": ${err.message}`);
85
+ console.warn(" Falling back to InternalChatModule.");
86
+ const internalModule = new InternalChatModule();
87
+ await internalModule.initialize(chatService);
88
+ }
89
+ } else {
90
+ // Default: use the internal module
91
+ const internalModule = new InternalChatModule();
92
+ await internalModule.initialize(chatService);
93
+ }
94
+
95
+ // ── Additional Chat Modules ────────────────────────────────────────────────
96
+ // Load any additional chat modules specified in config.chat.modules.
97
+ // These are loaded AFTER the root module so they can add commands/modes
98
+ // on top of whatever the root module provides.
99
+ const moduleSpecifiers = config.chat?.modules ?? [];
100
+ for (const specifier of moduleSpecifiers) {
101
+ try {
102
+ const chatModule = await loadChatModule(specifier);
103
+ await chatModule.initialize(chatService);
104
+ console.log(`✓ Loaded chat module: ${specifier}`);
105
+ } catch (err: any) {
106
+ console.warn(`⚠ Could not load chat module "${specifier}": ${err.message}`);
107
+ }
108
+ }
47
109
 
48
110
  // Start the chat loop
49
111
  await chatService.startChatLoop();
@@ -56,7 +118,9 @@ async function main() {
56
118
  // Check if this file is being run directly
57
119
  const isMainModule =
58
120
  (process.argv[1] && process.argv[1].endsWith("chat2.ts")) ||
59
- (process.argv[1] && process.argv[1].endsWith("chat2.js"));
121
+ (process.argv[1] && process.argv[1].endsWith("chat2.js")) ||
122
+ (process.argv[1] && process.argv[1].endsWith("chat.ts")) ||
123
+ (process.argv[1] && process.argv[1].endsWith("chat.js"));
60
124
 
61
125
  if (isMainModule) {
62
126
  main().catch(console.error);