@tyvm/knowhow 0.0.68 → 0.0.70

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 (215) hide show
  1. package/docs/shell-commands.md +174 -0
  2. package/package.json +2 -2
  3. package/src/agents/base/base.ts +1 -3
  4. package/src/agents/developer/developer.ts +21 -16
  5. package/src/agents/tools/agentCall.ts +4 -2
  6. package/src/agents/tools/fileSearch.ts +5 -1
  7. package/src/agents/tools/list.ts +41 -37
  8. package/src/agents/tools/startAgentTask.ts +131 -22
  9. package/src/chat/CliChatService.ts +57 -11
  10. package/src/chat/modules/AgentModule.ts +72 -12
  11. package/src/chat/modules/CustomCommandsModule.ts +79 -0
  12. package/src/chat/modules/InternalChatModule.ts +11 -1
  13. package/src/chat/modules/ShellCommandModule.ts +96 -0
  14. package/src/chat/modules/index.ts +1 -0
  15. package/src/chat/types.ts +14 -2
  16. package/src/chat.ts +16 -13
  17. package/src/cli.ts +16 -6
  18. package/src/clients/anthropic.ts +88 -91
  19. package/src/clients/gemini.ts +495 -94
  20. package/src/clients/index.ts +125 -0
  21. package/src/clients/knowhow.ts +81 -0
  22. package/src/clients/openai.ts +256 -145
  23. package/src/clients/pricing/anthropic.ts +90 -0
  24. package/src/clients/pricing/google.ts +65 -0
  25. package/src/clients/pricing/index.ts +4 -0
  26. package/src/clients/pricing/openai.ts +134 -0
  27. package/src/clients/pricing/xai.ts +62 -0
  28. package/src/clients/types.ts +170 -1
  29. package/src/clients/xai.ts +275 -46
  30. package/src/config.ts +61 -15
  31. package/src/embeddings.ts +9 -1
  32. package/src/microphone.ts +15 -16
  33. package/src/migrations.ts +151 -0
  34. package/src/plugins/AgentsMdPlugin.ts +118 -0
  35. package/src/plugins/PluginBase.ts +8 -0
  36. package/src/plugins/downloader/downloader.ts +5 -6
  37. package/src/plugins/embedding.ts +10 -8
  38. package/src/plugins/exec.ts +70 -0
  39. package/src/plugins/github.ts +120 -74
  40. package/src/plugins/language.ts +11 -13
  41. package/src/plugins/plugins.ts +25 -4
  42. package/src/plugins/tmux.ts +132 -0
  43. package/src/plugins/types.ts +1 -0
  44. package/src/plugins/vim.ts +14 -1
  45. package/src/server/index.ts +2 -0
  46. package/src/services/AgentSyncFs.ts +417 -0
  47. package/src/services/{AgentSynchronization.ts → AgentSyncKnowhowWeb.ts} +2 -2
  48. package/src/services/EventService.ts +0 -1
  49. package/src/services/KnowhowClient.ts +106 -0
  50. package/src/services/index.ts +4 -2
  51. package/src/types.ts +57 -4
  52. package/src/worker.ts +25 -2
  53. package/tests/manual/modalities/README.md +157 -0
  54. package/tests/manual/modalities/google.modalities.test.ts +335 -0
  55. package/tests/manual/modalities/openai.modalities.test.ts +329 -0
  56. package/tests/manual/modalities/streaming.test.ts +260 -0
  57. package/tests/manual/modalities/xai.modalities.test.ts +307 -0
  58. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +5 -5
  59. package/tests/plugins/language/languagePlugin-integration.test.ts +1 -1
  60. package/tests/plugins/language/languagePlugin.test.ts +17 -8
  61. package/ts_build/package.json +2 -2
  62. package/ts_build/src/agents/base/base.js +1 -1
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/developer/developer.js +21 -15
  65. package/ts_build/src/agents/developer/developer.js.map +1 -1
  66. package/ts_build/src/agents/tools/agentCall.js +4 -2
  67. package/ts_build/src/agents/tools/agentCall.js.map +1 -1
  68. package/ts_build/src/agents/tools/executeScript/index.d.ts +1 -1
  69. package/ts_build/src/agents/tools/fileSearch.js +2 -1
  70. package/ts_build/src/agents/tools/fileSearch.js.map +1 -1
  71. package/ts_build/src/agents/tools/github/index.d.ts +1 -1
  72. package/ts_build/src/agents/tools/list.js +41 -37
  73. package/ts_build/src/agents/tools/list.js.map +1 -1
  74. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -1
  75. package/ts_build/src/agents/tools/startAgentTask.js +118 -17
  76. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  77. package/ts_build/src/chat/CliChatService.d.ts +4 -0
  78. package/ts_build/src/chat/CliChatService.js +39 -5
  79. package/ts_build/src/chat/CliChatService.js.map +1 -1
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +4 -1
  81. package/ts_build/src/chat/modules/AgentModule.js +49 -11
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +9 -0
  84. package/ts_build/src/chat/modules/CustomCommandsModule.js +58 -0
  85. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -0
  86. package/ts_build/src/chat/modules/InternalChatModule.d.ts +2 -0
  87. package/ts_build/src/chat/modules/InternalChatModule.js +10 -0
  88. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/ShellCommandModule.d.ts +8 -0
  90. package/ts_build/src/chat/modules/ShellCommandModule.js +83 -0
  91. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -0
  92. package/ts_build/src/chat/modules/index.d.ts +1 -0
  93. package/ts_build/src/chat/modules/index.js +3 -1
  94. package/ts_build/src/chat/modules/index.js.map +1 -1
  95. package/ts_build/src/chat/types.d.ts +11 -1
  96. package/ts_build/src/chat.js +16 -13
  97. package/ts_build/src/chat.js.map +1 -1
  98. package/ts_build/src/cli.js +10 -3
  99. package/ts_build/src/cli.js.map +1 -1
  100. package/ts_build/src/clients/anthropic.d.ts +6 -1
  101. package/ts_build/src/clients/anthropic.js +47 -92
  102. package/ts_build/src/clients/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/gemini.d.ts +81 -2
  104. package/ts_build/src/clients/gemini.js +362 -79
  105. package/ts_build/src/clients/gemini.js.map +1 -1
  106. package/ts_build/src/clients/index.d.ts +9 -1
  107. package/ts_build/src/clients/index.js +65 -0
  108. package/ts_build/src/clients/index.js.map +1 -1
  109. package/ts_build/src/clients/knowhow.d.ts +9 -1
  110. package/ts_build/src/clients/knowhow.js +43 -0
  111. package/ts_build/src/clients/knowhow.js.map +1 -1
  112. package/ts_build/src/clients/openai.d.ts +9 -1
  113. package/ts_build/src/clients/openai.js +201 -133
  114. package/ts_build/src/clients/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/anthropic.d.ts +17 -0
  116. package/ts_build/src/clients/pricing/anthropic.js +93 -0
  117. package/ts_build/src/clients/pricing/anthropic.js.map +1 -0
  118. package/ts_build/src/clients/pricing/google.d.ts +73 -0
  119. package/ts_build/src/clients/pricing/google.js +68 -0
  120. package/ts_build/src/clients/pricing/google.js.map +1 -0
  121. package/ts_build/src/clients/pricing/index.d.ts +4 -0
  122. package/ts_build/src/clients/pricing/index.js +14 -0
  123. package/ts_build/src/clients/pricing/index.js.map +1 -0
  124. package/ts_build/src/clients/pricing/openai.d.ts +7 -0
  125. package/ts_build/src/clients/pricing/openai.js +137 -0
  126. package/ts_build/src/clients/pricing/openai.js.map +1 -0
  127. package/ts_build/src/clients/pricing/xai.d.ts +26 -0
  128. package/ts_build/src/clients/pricing/xai.js +59 -0
  129. package/ts_build/src/clients/pricing/xai.js.map +1 -0
  130. package/ts_build/src/clients/types.d.ts +135 -0
  131. package/ts_build/src/clients/xai.d.ts +9 -1
  132. package/ts_build/src/clients/xai.js +178 -46
  133. package/ts_build/src/clients/xai.js.map +1 -1
  134. package/ts_build/src/config.d.ts +1 -0
  135. package/ts_build/src/config.js +45 -16
  136. package/ts_build/src/config.js.map +1 -1
  137. package/ts_build/src/embeddings.js +8 -1
  138. package/ts_build/src/embeddings.js.map +1 -1
  139. package/ts_build/src/microphone.js +7 -9
  140. package/ts_build/src/microphone.js.map +1 -1
  141. package/ts_build/src/migrations.d.ts +17 -0
  142. package/ts_build/src/migrations.js +86 -0
  143. package/ts_build/src/migrations.js.map +1 -0
  144. package/ts_build/src/plugins/AgentsMdPlugin.d.ts +13 -0
  145. package/ts_build/src/plugins/AgentsMdPlugin.js +118 -0
  146. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -0
  147. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  148. package/ts_build/src/plugins/PluginBase.js +3 -0
  149. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  150. package/ts_build/src/plugins/downloader/downloader.js +5 -5
  151. package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
  152. package/ts_build/src/plugins/embedding.js +9 -8
  153. package/ts_build/src/plugins/embedding.js.map +1 -1
  154. package/ts_build/src/plugins/exec.d.ts +10 -0
  155. package/ts_build/src/plugins/exec.js +56 -0
  156. package/ts_build/src/plugins/exec.js.map +1 -0
  157. package/ts_build/src/plugins/github.js +93 -51
  158. package/ts_build/src/plugins/github.js.map +1 -1
  159. package/ts_build/src/plugins/language.js +14 -11
  160. package/ts_build/src/plugins/language.js.map +1 -1
  161. package/ts_build/src/plugins/plugins.d.ts +1 -0
  162. package/ts_build/src/plugins/plugins.js +19 -1
  163. package/ts_build/src/plugins/plugins.js.map +1 -1
  164. package/ts_build/src/plugins/tmux.d.ts +14 -0
  165. package/ts_build/src/plugins/tmux.js +108 -0
  166. package/ts_build/src/plugins/tmux.js.map +1 -0
  167. package/ts_build/src/plugins/types.d.ts +1 -0
  168. package/ts_build/src/plugins/vim.js +11 -1
  169. package/ts_build/src/plugins/vim.js.map +1 -1
  170. package/ts_build/src/server/index.js.map +1 -1
  171. package/ts_build/src/services/AgentSyncFs.d.ts +34 -0
  172. package/ts_build/src/services/AgentSyncFs.js +325 -0
  173. package/ts_build/src/services/AgentSyncFs.js.map +1 -0
  174. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +29 -0
  175. package/ts_build/src/services/AgentSyncKnowhowWeb.js +178 -0
  176. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -0
  177. package/ts_build/src/services/AgentSynchronization.d.ts +1 -1
  178. package/ts_build/src/services/AgentSynchronization.js +3 -3
  179. package/ts_build/src/services/AgentSynchronization.js.map +1 -1
  180. package/ts_build/src/services/EventService.js.map +1 -1
  181. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  182. package/ts_build/src/services/KnowhowClient.js +58 -0
  183. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  184. package/ts_build/src/services/index.d.ts +2 -1
  185. package/ts_build/src/services/index.js +2 -1
  186. package/ts_build/src/services/index.js.map +1 -1
  187. package/ts_build/src/types.d.ts +26 -1
  188. package/ts_build/src/types.js +45 -4
  189. package/ts_build/src/types.js.map +1 -1
  190. package/ts_build/src/utils/PersistentInputManager.d.ts +28 -0
  191. package/ts_build/src/utils/PersistentInputManager.js +293 -0
  192. package/ts_build/src/utils/PersistentInputManager.js.map +1 -0
  193. package/ts_build/src/worker.js +11 -2
  194. package/ts_build/src/worker.js.map +1 -1
  195. package/ts_build/tests/manual/modalities/google.modalities.test.d.ts +1 -0
  196. package/ts_build/tests/manual/modalities/google.modalities.test.js +252 -0
  197. package/ts_build/tests/manual/modalities/google.modalities.test.js.map +1 -0
  198. package/ts_build/tests/manual/modalities/openai.modalities.test.d.ts +1 -0
  199. package/ts_build/tests/manual/modalities/openai.modalities.test.js +252 -0
  200. package/ts_build/tests/manual/modalities/openai.modalities.test.js.map +1 -0
  201. package/ts_build/tests/manual/modalities/streaming.test.d.ts +1 -0
  202. package/ts_build/tests/manual/modalities/streaming.test.js +206 -0
  203. package/ts_build/tests/manual/modalities/streaming.test.js.map +1 -0
  204. package/ts_build/tests/manual/modalities/xai.modalities.test.d.ts +1 -0
  205. package/ts_build/tests/manual/modalities/xai.modalities.test.js +226 -0
  206. package/ts_build/tests/manual/modalities/xai.modalities.test.js.map +1 -0
  207. package/ts_build/tests/manual/persistent-input-test.d.ts +1 -0
  208. package/ts_build/tests/manual/persistent-input-test.js +35 -0
  209. package/ts_build/tests/manual/persistent-input-test.js.map +1 -0
  210. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +5 -5
  211. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  212. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +1 -1
  213. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
  214. package/ts_build/tests/plugins/language/languagePlugin.test.js +17 -7
  215. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
@@ -1,10 +1,25 @@
1
1
  import OpenAI from "openai";
2
+ import { XaiTextPricing, XaiImagePricing, XaiVideoPricing } from "./pricing";
2
3
  import {
3
4
  GenericClient,
4
5
  CompletionOptions,
5
6
  CompletionResponse,
6
7
  EmbeddingOptions,
7
8
  EmbeddingResponse,
9
+ AudioTranscriptionOptions,
10
+ AudioTranscriptionResponse,
11
+ AudioGenerationOptions,
12
+ AudioGenerationResponse,
13
+ ImageGenerationOptions,
14
+ ImageGenerationResponse,
15
+ VideoGenerationOptions,
16
+ VideoGenerationResponse,
17
+ VideoStatusOptions,
18
+ VideoStatusResponse,
19
+ FileUploadOptions,
20
+ FileUploadResponse,
21
+ FileDownloadOptions,
22
+ FileDownloadResponse,
8
23
  } from "./types";
9
24
  import {
10
25
  ChatCompletionMessageParam,
@@ -78,52 +93,7 @@ export class GenericXAIClient implements GenericClient {
78
93
  }
79
94
 
80
95
  pricesPerMillion() {
81
- return {
82
- [Models.xai.Grok4_1_Fast_NonReasoning]: {
83
- input: 0.2,
84
- cache_hit: 0.05,
85
- output: 0.5,
86
- },
87
- [Models.xai.Grok4_1_Fast_Reasoning]: {
88
- input: 0.2,
89
- cache_hit: 0.05,
90
- output: 0.5,
91
- },
92
- [Models.xai.GrokCodeFast]: {
93
- input: 0.2,
94
- cache_hit: 0.02,
95
- output: 1.5,
96
- },
97
- [Models.xai.Grok4]: {
98
- input: 3.0,
99
- output: 15.0,
100
- },
101
- [Models.xai.Grok3Beta]: {
102
- input: 3.0,
103
- output: 15.0,
104
- },
105
- [Models.xai.Grok3MiniBeta]: {
106
- input: 0.3,
107
- output: 0.5,
108
- },
109
- [Models.xai.Grok3FastBeta]: {
110
- input: 5.0,
111
- output: 25.0,
112
- },
113
- [Models.xai.Grok3MiniFastBeta]: {
114
- input: 0.6,
115
- output: 4.0,
116
- },
117
- [Models.xai.Grok21212]: {
118
- input: 2.0,
119
- output: 10.0,
120
- },
121
- [Models.xai.Grok2Vision1212]: {
122
- input: 2.0,
123
- output: 10.0,
124
- image_input: 2.0,
125
- },
126
- };
96
+ return XaiTextPricing;
127
97
  }
128
98
 
129
99
  calculateCost(
@@ -163,4 +133,263 @@ export class GenericXAIClient implements GenericClient {
163
133
  async createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse> {
164
134
  throw new Error("XAI provider does not support embeddings");
165
135
  }
136
+
137
+ async createAudioTranscription(
138
+ options: AudioTranscriptionOptions
139
+ ): Promise<AudioTranscriptionResponse> {
140
+ throw new Error(
141
+ "Audio transcription is not supported by the XAI provider. Use OpenAI client with Whisper model instead."
142
+ );
143
+ }
144
+
145
+ async createAudioGeneration(
146
+ options: AudioGenerationOptions
147
+ ): Promise<AudioGenerationResponse> {
148
+ throw new Error(
149
+ "Audio generation is not supported by the XAI provider. Use OpenAI client with TTS model instead."
150
+ );
151
+ }
152
+
153
+ async createImageGeneration(
154
+ options: ImageGenerationOptions
155
+ ): Promise<ImageGenerationResponse> {
156
+ const response = await this.client.images.generate({
157
+ model: options.model || "grok-imagine-image",
158
+ prompt: options.prompt,
159
+ n: options.n,
160
+ size: options.size,
161
+ quality: options.quality,
162
+ style: options.style,
163
+ response_format: options.response_format,
164
+ user: options.user,
165
+ });
166
+
167
+ // Calculate cost based on model name
168
+ const imageModel = options.model || "grok-imagine-image";
169
+ const costPerImage =
170
+ XaiImagePricing[imageModel as keyof typeof XaiImagePricing] || 0.02;
171
+ const usdCost = (options.n || 1) * costPerImage;
172
+
173
+ return {
174
+ ...response,
175
+ created: response.created || Math.floor(Date.now() / 1000),
176
+ usd_cost: usdCost,
177
+ };
178
+ }
179
+
180
+ async createVideoGeneration(
181
+ options: VideoGenerationOptions
182
+ ): Promise<VideoGenerationResponse> {
183
+ const model = options.model || "grok-imagine-video";
184
+
185
+ // Step 1: Start the video generation request
186
+ const startPayload = {
187
+ model,
188
+ prompt: options.prompt,
189
+ } as {
190
+ model: string;
191
+ prompt: string;
192
+ duration?: number;
193
+ aspect_ratio?: string;
194
+ resolution?: string;
195
+ image_url?: string;
196
+ video_url?: string;
197
+ };
198
+
199
+ // Add optional parameters if provided
200
+ if (options.duration !== undefined) {
201
+ startPayload.duration = options.duration;
202
+ }
203
+ if (options.aspect_ratio) {
204
+ startPayload.aspect_ratio = options.aspect_ratio;
205
+ }
206
+ if (options.resolution) {
207
+ startPayload.resolution = options.resolution;
208
+ }
209
+ if (options.image_url) {
210
+ startPayload.image_url = options.image_url;
211
+ }
212
+ if (options.video_url) {
213
+ startPayload.video_url = options.video_url;
214
+ }
215
+
216
+ const startResponse = await fetch(
217
+ "https://api.x.ai/v1/videos/generations",
218
+ {
219
+ method: "POST",
220
+ headers: {
221
+ "Content-Type": "application/json",
222
+ Authorization: `Bearer ${this.apiKey}`,
223
+ },
224
+ body: JSON.stringify(startPayload),
225
+ }
226
+ );
227
+
228
+ if (!startResponse.ok) {
229
+ const errorText = await startResponse.text();
230
+ throw new Error(
231
+ `XAI video generation start failed: ${startResponse.status} ${errorText}`
232
+ );
233
+ }
234
+
235
+ const startData = await startResponse.json();
236
+ const requestId = startData.request_id;
237
+
238
+ if (!requestId) {
239
+ throw new Error("No request_id returned from XAI video generation start");
240
+ }
241
+
242
+ // Return immediately with the jobId – do NOT poll here.
243
+ // Use getVideoStatus() to poll and downloadVideo() to fetch the result.
244
+ const duration = options.duration || 5;
245
+ const pricePerSecond = XaiVideoPricing[model] || 0.07;
246
+ const usdCost = duration * pricePerSecond;
247
+
248
+ return {
249
+ created: Math.floor(Date.now() / 1000),
250
+ data: [],
251
+ jobId: requestId,
252
+ usd_cost: usdCost,
253
+ };
254
+ }
255
+
256
+ async getVideoStatus(
257
+ options: VideoStatusOptions
258
+ ): Promise<VideoStatusResponse> {
259
+ const statusResponse = await fetch(
260
+ `https://api.x.ai/v1/videos/${options.jobId}`,
261
+ {
262
+ method: "GET",
263
+ headers: {
264
+ Authorization: `Bearer ${this.apiKey}`,
265
+ },
266
+ }
267
+ );
268
+
269
+ if (!statusResponse.ok) {
270
+ const errorText = await statusResponse.text();
271
+ throw new Error(
272
+ `XAI video status check failed: ${statusResponse.status} ${errorText}`
273
+ );
274
+ }
275
+
276
+ const statusData = await statusResponse.json();
277
+
278
+ // Map XAI status to standard status
279
+ let mappedStatus: "queued" | "in_progress" | "completed" | "failed" | "expired";
280
+ switch (statusData.status) {
281
+ case "pending":
282
+ mappedStatus = "queued";
283
+ break;
284
+ case "processing":
285
+ mappedStatus = "in_progress";
286
+ break;
287
+ case "succeeded":
288
+ mappedStatus = "completed";
289
+ break;
290
+ case "failed":
291
+ mappedStatus = "failed";
292
+ break;
293
+ case "expired":
294
+ mappedStatus = "expired";
295
+ break;
296
+ default:
297
+ mappedStatus = "queued";
298
+ }
299
+
300
+ const response: VideoStatusResponse = {
301
+ jobId: options.jobId,
302
+ status: mappedStatus,
303
+ };
304
+
305
+ // If completed, include the video URL
306
+ if (mappedStatus === "completed" && statusData.video?.url) {
307
+ response.data = [
308
+ {
309
+ url: statusData.video.url,
310
+ },
311
+ ];
312
+ }
313
+
314
+ return response;
315
+ }
316
+
317
+ async downloadVideo(
318
+ options: FileDownloadOptions
319
+ ): Promise<FileDownloadResponse> {
320
+ // XAI returns a URL for the video, not raw bytes from their API
321
+ const url = options.uri || options.fileId;
322
+
323
+ const response = await fetch(url);
324
+ if (!response.ok) {
325
+ throw new Error(
326
+ `Failed to download video from XAI URL: ${response.status} ${response.statusText}`
327
+ );
328
+ }
329
+
330
+ const arrayBuffer = await response.arrayBuffer();
331
+ return {
332
+ data: Buffer.from(arrayBuffer),
333
+ mimeType: "video/mp4",
334
+ };
335
+ }
336
+
337
+ async uploadFile(
338
+ options: FileUploadOptions
339
+ ): Promise<FileUploadResponse> {
340
+ const apiKey = this.apiKey || process.env.XAI_API_KEY;
341
+ if (!apiKey) {
342
+ throw new Error("XAI API key not set");
343
+ }
344
+
345
+ const formData = new FormData();
346
+ formData.append("purpose", "assistants");
347
+ const blob = new Blob([options.data], { type: options.mimeType || "application/octet-stream" });
348
+ formData.append("file", blob, options.fileName || "upload");
349
+
350
+ const response = await fetch("https://api.x.ai/v1/files", {
351
+ method: "POST",
352
+ headers: { Authorization: `Bearer ${apiKey}` },
353
+ body: formData,
354
+ });
355
+
356
+ if (!response.ok) {
357
+ const errorText = await response.text();
358
+ throw new Error(`XAI uploadFile failed: ${response.status} ${errorText}`);
359
+ }
360
+
361
+ const data = await response.json();
362
+ return { fileId: data.id, uri: data.uri };
363
+ }
364
+
365
+ async downloadFile(
366
+ options: FileDownloadOptions
367
+ ): Promise<FileDownloadResponse> {
368
+ const apiKey = this.apiKey || process.env.XAI_API_KEY;
369
+ if (!apiKey) {
370
+ throw new Error("XAI API key not set");
371
+ }
372
+
373
+ const fileId = options.fileId;
374
+ if (!fileId) {
375
+ throw new Error("downloadFile requires fileId");
376
+ }
377
+
378
+ const response = await fetch(`https://api.x.ai/v1/files/${fileId}/content`, {
379
+ method: "GET",
380
+ headers: { Authorization: `Bearer ${apiKey}` },
381
+ });
382
+
383
+ if (!response.ok) {
384
+ const errorText = await response.text();
385
+ throw new Error(`XAI downloadFile failed: ${response.status} ${errorText}`);
386
+ }
387
+
388
+ const mimeType = response.headers.get("content-type") || undefined;
389
+ const data = Buffer.from(await response.arrayBuffer());
390
+ return {
391
+ data,
392
+ mimeType,
393
+ };
394
+ }
166
395
  }
package/src/config.ts CHANGED
@@ -12,23 +12,30 @@ import {
12
12
  EmbeddingModels,
13
13
  } from "./types";
14
14
  import { mkdir, writeFile, readFile, fileExists } from "./utils";
15
+ import { applyMigrations } from "./migrations";
15
16
 
16
17
  const defaultConfig = {
17
18
  promptsDir: ".knowhow/prompts",
18
19
  modules: [],
19
- plugins: [
20
- "embeddings",
21
- "language",
22
- "git",
23
- "vim",
24
- "github",
25
- "asana",
26
- "jira",
27
- "linear",
28
- "download",
29
- "figma",
30
- "url",
31
- ],
20
+ plugins: {
21
+ enabled: [
22
+ "embeddings",
23
+ "language",
24
+ "git",
25
+ "vim",
26
+ "github",
27
+ "asana",
28
+ "jira",
29
+ "linear",
30
+ "download",
31
+ "figma",
32
+ "url",
33
+ "tmux",
34
+ "agents-md",
35
+ "exec",
36
+ ],
37
+ disabled: [],
38
+ },
32
39
  lintCommands: {
33
40
  js: "eslint",
34
41
  ts: "tslint",
@@ -169,6 +176,7 @@ export async function getLanguageConfig() {
169
176
  );
170
177
  return language as Language;
171
178
  } catch (e) {
179
+ console.error("Error reading .knowhow/language.json:", e);
172
180
  return {} as Language;
173
181
  }
174
182
  }
@@ -217,7 +225,14 @@ export function getConfigSync() {
217
225
  const config = JSON.parse(
218
226
  fs.readFileSync(".knowhow/knowhow.json", "utf8").toString()
219
227
  );
220
- return config as Config;
228
+
229
+ // Apply migrations synchronously
230
+ const { config: migratedConfig } = applyMigrations(config);
231
+
232
+ // Note: We don't save here in sync mode to avoid blocking operations
233
+ // The async getConfig() will handle saving on next call
234
+
235
+ return migratedConfig as Config;
221
236
  } catch (e) {
222
237
  return {} as Config;
223
238
  }
@@ -238,13 +253,44 @@ export async function getConfig() {
238
253
  }
239
254
  try {
240
255
  const config = await readFile(".knowhow/knowhow.json", "utf8");
241
- return JSON.parse(config) as Config;
256
+ const parsedConfig = JSON.parse(config);
257
+
258
+ return parsedConfig as Config;
242
259
  } catch (error) {
243
260
  console.error("Error reading .knowhow/knowhow.json:", error);
244
261
  throw new Error("Failed to load KnowHow configuration.");
245
262
  }
246
263
  }
247
264
 
265
+ export async function migrateConfig() {
266
+ // Apply migrations, used to keep config structure up to date.
267
+ const parsedConfig = await getConfig();
268
+ const { modified, config: migratedConfig } = applyMigrations(parsedConfig);
269
+
270
+ // After migrations, check if any plugins from defaultConfig are missing
271
+ let configModified = modified;
272
+ if (migratedConfig.plugins) {
273
+ const enabled = migratedConfig.plugins.enabled || [];
274
+ const disabled = migratedConfig.plugins.disabled || [];
275
+ const missingPlugins = defaultConfig.plugins.enabled.filter(
276
+ (plugin) => !enabled.includes(plugin) && !disabled.includes(plugin)
277
+ );
278
+
279
+ if (missingPlugins.length > 0) {
280
+ console.log(
281
+ `Adding missing plugins to enabled list: ${missingPlugins.join(", ")}`
282
+ );
283
+ migratedConfig.plugins.enabled = [...enabled, ...missingPlugins];
284
+ configModified = true;
285
+ }
286
+ }
287
+
288
+ // If migrations were applied, save the updated config
289
+ if (configModified) {
290
+ await updateConfig(migratedConfig);
291
+ }
292
+ }
293
+
248
294
  export async function loadPrompt(promptName: string) {
249
295
  const config = await getConfig();
250
296
  if (!promptName) {
package/src/embeddings.ts CHANGED
@@ -27,7 +27,15 @@ export { cosineSimilarity };
27
27
 
28
28
  export async function loadEmbedding(filePath: string) {
29
29
  if (await fileExists(filePath)) {
30
- return JSON.parse(await readFile(filePath, "utf8")) as Embeddable[];
30
+ try {
31
+ const parsed = JSON.parse(
32
+ await readFile(filePath, "utf8")
33
+ ) as Embeddable[];
34
+ return parsed;
35
+ } catch (e) {
36
+ console.error("Error loading embedding file", filePath, e);
37
+ return [];
38
+ }
31
39
  }
32
40
  return [];
33
41
  }
package/src/microphone.ts CHANGED
@@ -77,7 +77,7 @@ export async function recordAudio() {
77
77
  const filePath = "/tmp/knowhow.wav";
78
78
  const audioFile = fs.createWriteStream(filePath, { encoding: "binary" });
79
79
  const defaultMic = getDefaultMic();
80
- const hasSox = await execAsync("which sox");
80
+ const hasSox = await execAsync("which sox").catch(() => false);
81
81
 
82
82
  if (!hasSox && !defaultMic) {
83
83
  console.error("Sox is required to record audio");
@@ -117,26 +117,25 @@ sudo apt-get install sox libsox-fmt-all
117
117
  }
118
118
 
119
119
  export async function voiceToText() {
120
- let input = await ask(
121
- "Press Enter to Start Recording, or exit to quit...: ",
122
- ["exit"]
123
- );
124
-
125
- if (input === "exit") {
126
- return "/voice";
120
+ // Allow user to type commands to exit voice mode or press Enter to record
121
+ const startInput = await ask("Press Enter to Start Recording (or type a command to exit): ", []);
122
+
123
+ // If user typed anything other than empty string, return it (e.g., /voice, /exit, etc.)
124
+ if (startInput.trim() !== "") {
125
+ return startInput;
127
126
  }
128
-
127
+
129
128
  const recording = await recordAudio();
130
129
  console.log("Recording audio...");
131
- input = await ask("Press Enter to Stop Recording, or exit to quit...: ", [
132
- "exit",
133
- ]);
130
+
131
+ const stopInput = await ask("Press Enter to Stop Recording (or type a command to cancel): ", []);
134
132
  recording.stop();
135
133
  console.log("Stopped recording");
136
-
137
- if (input === "exit") {
138
- return "/voice";
134
+
135
+ // If user typed anything during recording, return it instead of transcribing
136
+ if (stopInput.trim() !== "") {
137
+ return stopInput;
139
138
  }
140
-
139
+
141
140
  return convertAudioToText("/tmp/knowhow.wav", false);
142
141
  }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Configuration Migration System
3
+ *
4
+ * This module provides a framework for migrating configuration files
5
+ * as the schema evolves over time. Each migration is a function that
6
+ * transforms the config and returns whether it made changes.
7
+ */
8
+
9
+ import { Config } from "./types";
10
+
11
+ export type Migration = {
12
+ version: number;
13
+ description: string;
14
+ migrate: (config: any, context?: MigrationContext) => { modified: boolean; config: any };
15
+ };
16
+
17
+ export type MigrationContext = {
18
+ allPluginKeys?: string[];
19
+ };
20
+
21
+ /**
22
+ * Default list of all known plugins at time of migration
23
+ * This list is used if the migration context doesn't provide plugin keys
24
+ */
25
+ const DEFAULT_PLUGINS = [
26
+ "embeddings",
27
+ "language",
28
+ "git",
29
+ "vim",
30
+ "linter",
31
+ "github",
32
+ "asana",
33
+ "jira",
34
+ "linear",
35
+ "notion",
36
+ "download",
37
+ "figma",
38
+ "url",
39
+ "tmux",
40
+ "agents-md",
41
+ ];
42
+
43
+ /**
44
+ * Migration 1: Convert plugins from string[] to { enabled, disabled } format
45
+ * Also ensures all registered plugins are in the enabled list
46
+ */
47
+ const migration1: Migration = {
48
+ version: 1,
49
+ description: "Convert plugins from string[] to { enabled, disabled } format and add missing plugins",
50
+ migrate: (config: any, context?: MigrationContext) => {
51
+ const allPluginKeys = context?.allPluginKeys || DEFAULT_PLUGINS;
52
+ let modified = false;
53
+
54
+ // If plugins doesn't exist, no migration needed
55
+ if (!config.plugins) {
56
+ // Create default plugins config with all plugins enabled
57
+ config.plugins = {
58
+ enabled: [...allPluginKeys],
59
+ disabled: [],
60
+ };
61
+ return { modified: true, config };
62
+ }
63
+
64
+ // If plugins is a string array, convert it
65
+ if (Array.isArray(config.plugins)) {
66
+ config.plugins = {
67
+ enabled: config.plugins,
68
+ disabled: [],
69
+ };
70
+ modified = true;
71
+ }
72
+
73
+ // Ensure config.plugins is an object with enabled/disabled arrays
74
+ if (typeof config.plugins === "object" && !Array.isArray(config.plugins)) {
75
+ // Ensure enabled array exists
76
+ if (!config.plugins.enabled) {
77
+ config.plugins.enabled = [...allPluginKeys];
78
+ modified = true;
79
+ }
80
+
81
+ // Ensure disabled array exists
82
+ if (!config.plugins.disabled) {
83
+ config.plugins.disabled = [];
84
+ modified = true;
85
+ }
86
+
87
+ // Add any missing plugins to enabled list
88
+ const missingPlugins = allPluginKeys.filter(
89
+ (key) =>
90
+ !config.plugins.enabled.includes(key) &&
91
+ !config.plugins.disabled.includes(key)
92
+ );
93
+
94
+ if (missingPlugins.length > 0) {
95
+ config.plugins.enabled.push(...missingPlugins);
96
+ modified = true;
97
+ }
98
+ }
99
+
100
+ return { modified, config };
101
+ },
102
+ };
103
+
104
+ /**
105
+ * All migrations in order
106
+ */
107
+ export const migrations: Migration[] = [migration1];
108
+
109
+ /**
110
+ * Apply all migrations to a config object
111
+ * @param config The config object to migrate
112
+ * @param context Optional context for migrations (e.g., list of all plugin keys)
113
+ * @returns Object containing the migrated config and whether any changes were made
114
+ */
115
+ export function applyMigrations(config: any, context?: MigrationContext): {
116
+ modified: boolean;
117
+ config: any;
118
+ } {
119
+ let modified = false;
120
+ let currentConfig = { ...config };
121
+
122
+ for (const migration of migrations) {
123
+ try {
124
+ const result = migration.migrate(currentConfig, context);
125
+ if (result.modified) {
126
+ console.log(
127
+ `Applied migration ${migration.version}: ${migration.description}`
128
+ );
129
+ modified = true;
130
+ currentConfig = result.config;
131
+ }
132
+ } catch (error) {
133
+ console.error(
134
+ `Failed to apply migration ${migration.version}: ${migration.description}`,
135
+ error
136
+ );
137
+ // Continue with other migrations even if one fails
138
+ }
139
+ }
140
+
141
+ return { modified, config: currentConfig };
142
+ }
143
+
144
+ /**
145
+ * Get the current migration version (highest migration number)
146
+ */
147
+ export function getCurrentMigrationVersion(): number {
148
+ return migrations.length > 0
149
+ ? Math.max(...migrations.map((m) => m.version))
150
+ : 0;
151
+ }