@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
package/src/types.ts CHANGED
@@ -46,10 +46,23 @@ export type Config = {
46
46
  embedSources: EmbedSource[];
47
47
  embeddingModel: string;
48
48
 
49
+ skills?: string[];
50
+
49
51
  plugins: { enabled: string[]; disabled: string[] };
50
52
 
53
+ chat?: {
54
+ /** Path to a custom root chat module (npm package or local file) */
55
+ rootModule?: string;
56
+ /** Path to a custom renderer (npm package or local file, can be .ts) */
57
+ renderer?: string;
58
+ /** Additional chat modules to load (npm packages or local files, can be .ts) */
59
+ modules?: string[];
60
+ };
61
+
51
62
  modules: string[];
52
63
 
64
+ pluginPackages?: Record<string, string>;
65
+
53
66
  agents: Assistant[];
54
67
  mcps: McpConfig[];
55
68
  modelProviders: ModelProvider[];
@@ -73,6 +86,18 @@ export type Config = {
73
86
  sandbox?: boolean;
74
87
  volumes?: string[];
75
88
  envFile?: string;
89
+ auth?: {
90
+ required?: boolean;
91
+ passkey?: {
92
+ publicKey?: string; // base64-encoded public key
93
+ credentialId?: string; // base64-encoded credential ID
94
+ algorithm?: string; // e.g. "ES256"
95
+ };
96
+ sessionDurationHours?: number;
97
+ };
98
+ commandAuth?: {
99
+ [toolName: string]: "always" | "session" | "never";
100
+ };
76
101
  tunnel?: {
77
102
  enabled?: boolean;
78
103
  allowedPorts?: number[];
@@ -191,6 +216,12 @@ export const Models = {
191
216
  openai: {
192
217
  GPT_5_2: "gpt-5.2",
193
218
  GPT_5_1: "gpt-5.1",
219
+ GPT_54: "gpt-5.4",
220
+ GPT_54_Mini: "gpt-5.4-mini",
221
+ GPT_54_Nano: "gpt-5.4-nano",
222
+ GPT_54_Pro: "gpt-5.4-pro",
223
+ GPT_53_Chat: "gpt-5.3-chat-latest",
224
+ GPT_53_Codex: "gpt-5.3-codex",
194
225
  GPT_5: "gpt-5",
195
226
  GPT_5_Mini: "gpt-5-mini",
196
227
  GPT_5_Nano: "gpt-5-nano",
@@ -214,6 +245,12 @@ export const Models = {
214
245
  GPT_4o_Mini_Search: "gpt-4o-mini-search-preview-2025-03-11",
215
246
  GPT_4o_Search: "gpt-4o-search-preview-2025-03-11",
216
247
 
248
+ GPT_4o_Transcribe: "gpt-4o-transcribe",
249
+ GPT_4o_Mini_Transcribe: "gpt-4o-mini-transcribe",
250
+ GPT_Realtime_15: "gpt-realtime-1.5",
251
+ GPT_Realtime_Mini: "gpt-realtime-mini",
252
+ GPT_Image_15: "gpt-image-1.5",
253
+ GPT_Image_1_Mini: "gpt-image-1-mini",
217
254
  TTS_1: "tts-1",
218
255
  Whisper_1: "whisper-1",
219
256
  DALL_E_3: "dall-e-3",
@@ -225,19 +262,41 @@ export const Models = {
225
262
  // Codex_Mini: "codex-mini-latest",
226
263
  },
227
264
  google: {
228
- Gemini_3_Preview: "gemini-3-pro-preview",
265
+ // Gemini 3.x
266
+ Gemini_31_Pro_Preview: "gemini-3.1-pro-preview",
267
+ Gemini_31_Flash_Image_Preview: "gemini-3.1-flash-image-preview",
268
+ Gemini_31_Flash_Lite_Preview: "gemini-3.1-flash-lite-preview",
269
+ Gemini_3_Flash_Preview: "gemini-3-flash-preview",
270
+ Gemini_3_Pro_Image_Preview: "gemini-3-pro-image-preview",
271
+ // Gemini 2.5
272
+ Gemini_25_Pro: "gemini-2.5-pro",
273
+ Gemini_25_Flash: "gemini-2.5-flash",
274
+ Gemini_25_Flash_Lite: "gemini-2.5-flash-lite",
229
275
  Gemini_25_Flash_Preview: "gemini-2.5-flash-preview-05-20",
230
276
  Gemini_25_Pro_Preview: "gemini-2.5-pro-preview-05-06",
277
+ Gemini_25_Flash_Image: "gemini-2.5-flash-image",
278
+ Gemini_25_Flash_Live: "gemini-2.5-flash-live-preview",
279
+ Gemini_25_Flash_Native_Audio: "gemini-2.5-flash-native-audio-preview-12-2025",
280
+ Gemini_25_Pro_TTS: "gemini-2.5-pro-preview-tts",
281
+ // Gemini 2.0 (deprecated)
231
282
  Gemini_20_Flash: "gemini-2.0-flash",
232
283
  Gemini_20_Flash_Preview_Image_Generation:
233
284
  "gemini-2.0-flash-exp-image-generation",
234
285
  Gemini_20_Flash_Lite: "gemini-2.0-flash-lite",
286
+ // Gemini 1.5 (legacy)
235
287
  Gemini_15_Flash: "gemini-1.5-flash",
236
288
  Gemini_15_Flash_8B: "gemini-1.5-flash-8b",
237
289
  Gemini_15_Pro: "gemini-1.5-pro",
290
+ // Media generation
238
291
  Imagen_3: "imagen-4.0-generate-001",
292
+ Imagen_4_Fast: "imagen-4.0-fast-generate-001",
293
+ Imagen_4_Ultra: "imagen-4.0-ultra-generate-001",
239
294
  Veo_2: "veo-2.0-generate-001",
295
+ Veo_3: "veo-3.0-generate-001",
296
+ Veo_3_Fast: "veo-3.0-fast-generate-001",
240
297
  Veo_3_1: "veo-3.1-generate-preview",
298
+ Veo_3_1_Fast: "veo-3.1-fast-generate-preview",
299
+ // Audio / Live
241
300
  Gemini_20_Flash_Live: "gemini-2.0-flash-live-001",
242
301
  Gemini_25_Flash_TTS: "gemini-2.5-flash-preview-tts",
243
302
  Gemini_20_Flash_TTS: "gemini-2.0-flash-preview-tts",
@@ -252,6 +311,7 @@ export const EmbeddingModels = {
252
311
  },
253
312
  google: {
254
313
  Gemini_Embedding: "gemini-embedding-exp",
314
+ Gemini_Embedding_001: "gemini-embedding-001",
255
315
  },
256
316
  };
257
317
 
@@ -281,6 +341,12 @@ export const OpenAiReasoningModels = [
281
341
  Models.openai.o3,
282
342
  Models.openai.o3_Pro,
283
343
  Models.openai.o4_Mini,
344
+ Models.openai.GPT_54,
345
+ Models.openai.GPT_54_Mini,
346
+ Models.openai.GPT_54_Nano,
347
+ Models.openai.GPT_54_Pro,
348
+ Models.openai.GPT_53_Chat,
349
+ Models.openai.GPT_53_Codex,
284
350
  Models.openai.GPT_5,
285
351
  Models.openai.GPT_5_Mini,
286
352
  Models.openai.GPT_5_Nano,
@@ -296,6 +362,12 @@ export const OpenAiEmbeddingModels = [
296
362
  // export const OpenAiResponseOnlyModels = [Models.openai.Codex_Mini];
297
363
 
298
364
  export const GoogleReasoningModels = [
365
+ Models.google.Gemini_31_Pro_Preview,
366
+ Models.google.Gemini_31_Flash_Lite_Preview,
367
+ Models.google.Gemini_3_Flash_Preview,
368
+ Models.google.Gemini_25_Pro,
369
+ Models.google.Gemini_25_Flash,
370
+ Models.google.Gemini_25_Flash_Lite,
299
371
  Models.google.Gemini_25_Flash_Preview,
300
372
  Models.google.Gemini_25_Pro_Preview,
301
373
  Models.google.Gemini_20_Flash,
@@ -306,13 +378,20 @@ export const GoogleReasoningModels = [
306
378
  ];
307
379
 
308
380
  export const GoogleImageModels = [
381
+ Models.google.Gemini_31_Flash_Image_Preview,
382
+ Models.google.Gemini_3_Pro_Image_Preview,
383
+ Models.google.Gemini_25_Flash_Image,
309
384
  Models.google.Gemini_20_Flash_Preview_Image_Generation,
310
385
  Models.google.Imagen_3,
386
+ Models.google.Imagen_4_Fast,
387
+ Models.google.Imagen_4_Ultra,
311
388
  ];
312
389
 
313
390
  export const OpenAiImageModels = [
314
391
  Models.openai.DALL_E_3,
315
392
  Models.openai.DALL_E_2,
393
+ Models.openai.GPT_Image_15,
394
+ Models.openai.GPT_Image_1_Mini,
316
395
  ];
317
396
 
318
397
  export const OpenAiVideoModels = [
@@ -323,17 +402,36 @@ export const OpenAiVideoModels = [
323
402
 
324
403
  export const OpenAiTTSModels = [Models.openai.TTS_1];
325
404
 
326
- export const OpenAiTranscriptionModels = [Models.openai.Whisper_1];
327
-
328
405
  export const XaiImageModels = [Models.xai.GrokImagineImage];
406
+ export const OpenAiTranscriptionModels = [
407
+ Models.openai.Whisper_1,
408
+ Models.openai.GPT_4o_Transcribe,
409
+ Models.openai.GPT_4o_Mini_Transcribe,
410
+ ];
329
411
 
412
+ export const OpenAiRealtimeModels = [
413
+ Models.openai.GPT_4o_Realtime,
414
+ Models.openai.GPT_4o_Mini_Realtime,
415
+ Models.openai.GPT_Realtime_15,
416
+ Models.openai.GPT_Realtime_Mini,
417
+ ];
330
418
  export const XaiVideoModels = [Models.xai.GrokImagineVideo];
331
419
 
332
420
  export const GoogleTTSModels = [
333
421
  Models.google.Gemini_25_Flash_TTS,
422
+ Models.google.Gemini_25_Pro_TTS,
334
423
  Models.google.Gemini_20_Flash_TTS,
335
424
  ];
336
425
 
337
- export const GoogleVideoModels = [Models.google.Veo_2, Models.google.Veo_3_1];
426
+ export const GoogleVideoModels = [
427
+ Models.google.Veo_2,
428
+ Models.google.Veo_3,
429
+ Models.google.Veo_3_Fast,
430
+ Models.google.Veo_3_1,
431
+ Models.google.Veo_3_1_Fast,
432
+ ];
338
433
 
339
- export const GoogleEmbeddingModels = [EmbeddingModels.google.Gemini_Embedding];
434
+ export const GoogleEmbeddingModels = [
435
+ EmbeddingModels.google.Gemini_Embedding,
436
+ EmbeddingModels.google.Gemini_Embedding_001,
437
+ ];
package/src/worker.ts CHANGED
@@ -4,6 +4,9 @@ import { createTunnelHandler, TunnelHandler } from "@tyvm/knowhow-tunnel";
4
4
  import { includedTools } from "./agents/tools/list";
5
5
  import { loadJwt } from "./login";
6
6
  import { services } from "./services";
7
+ import { PasskeySetupService } from "./workers/auth/PasskeySetup";
8
+ import { WorkerPasskeyAuthService } from "./workers/auth/WorkerPasskeyAuth";
9
+ import { makeUnlockTool, makeLockTool } from "./workers/tools";
7
10
  import { McpServerService } from "./services/Mcp";
8
11
  import * as allTools from "./agents/tools";
9
12
  import workerTools from "./workers/tools";
@@ -85,9 +88,33 @@ export async function worker(options?: {
85
88
  unshare?: boolean;
86
89
  sandbox?: boolean;
87
90
  noSandbox?: boolean;
91
+ passkey?: boolean;
92
+ passkeyReset?: boolean;
88
93
  }) {
89
94
  const config = await getConfig();
90
95
 
96
+ // Handle --passkey-reset: remove passkey from config
97
+ if (options?.passkeyReset) {
98
+ const passkeySetup = new PasskeySetupService();
99
+ await passkeySetup.reset();
100
+ return;
101
+ }
102
+
103
+ // Handle --passkey: run browser-based passkey registration flow
104
+ if (options?.passkey) {
105
+ let jwt: string;
106
+ try {
107
+ jwt = await loadJwt();
108
+ } catch {
109
+ console.error("❌ You must be logged in to set up a passkey.");
110
+ console.error(" Run 'knowhow login' first.");
111
+ process.exit(1);
112
+ }
113
+ const passkeySetup = new PasskeySetupService();
114
+ await passkeySetup.setup(jwt);
115
+ return;
116
+ }
117
+
91
118
  // Check if we're already running inside a Docker container
92
119
  const isInsideDocker = process.env.KNOWHOW_DOCKER === "true";
93
120
 
@@ -158,6 +185,24 @@ export async function worker(options?: {
158
185
  const clientName = "knowhow-worker";
159
186
  const clientVersion = "1.1.1";
160
187
 
188
+ // ---------------------------------------------------------------------------
189
+ // Passkey auth gating
190
+ // ---------------------------------------------------------------------------
191
+ let authService: WorkerPasskeyAuthService | null = null;
192
+ const passkeyConfig = config.worker?.auth?.passkey;
193
+
194
+ if (passkeyConfig?.publicKey && passkeyConfig?.credentialId) {
195
+ authService = new WorkerPasskeyAuthService(
196
+ {
197
+ publicKey: passkeyConfig.publicKey,
198
+ credentialId: passkeyConfig.credentialId,
199
+ algorithm: -7, // ES256
200
+ },
201
+ config.worker?.auth?.sessionDurationHours ?? 3
202
+ );
203
+ console.log("🔒 Passkey auth enabled — worker starts locked");
204
+ }
205
+
161
206
  if (!shouldUseSandbox) {
162
207
  console.log(`🖥️ Using host mode (${sandboxSource})`);
163
208
  }
@@ -172,7 +217,6 @@ export async function worker(options?: {
172
217
  ...config.worker,
173
218
  allowedTools: Tools.getToolNames(),
174
219
  };
175
-
176
220
  await updateConfig(config);
177
221
  return;
178
222
  }
@@ -183,7 +227,41 @@ export async function worker(options?: {
183
227
  return;
184
228
  }
185
229
 
186
- const toolsToUse = Tools.getToolsByNames(config.worker.allowedTools);
230
+ let toolsToUse = Tools.getToolsByNames(config.worker.allowedTools);
231
+
232
+ // If passkey auth is enabled, wrap all tool functions to check locked state
233
+ // and register the unlock/lock auth tools
234
+ if (authService) {
235
+ const _authService = authService;
236
+
237
+ // Wrap every configured tool to gate on locked state
238
+ for (const tool of toolsToUse) {
239
+ const toolName = tool.function.name;
240
+ const originalFn = Tools.getFunction(toolName);
241
+ Tools.addFunctions({
242
+ [toolName]: async (...args: any[]) => {
243
+ if (_authService.isLocked()) {
244
+ return {
245
+ error: "WORKER_LOCKED",
246
+ message:
247
+ "Worker is locked. Call the `unlock` tool with your passkey assertion to unlock it first.",
248
+ };
249
+ }
250
+ return originalFn(...args);
251
+ },
252
+ });
253
+ }
254
+
255
+ // Build and register the auth tools
256
+ const { unlock, unlockDefinition } = makeUnlockTool(_authService);
257
+ const { lock, lockDefinition } = makeLockTool(_authService);
258
+
259
+ Tools.addFunctions({ unlock, lock });
260
+ toolsToUse = [unlockDefinition, lockDefinition, ...toolsToUse];
261
+
262
+ console.log("🔑 Auth tools registered: unlock, lock");
263
+ }
264
+
187
265
  mcpServer.createServer(clientName, clientVersion).withTools(toolsToUse);
188
266
 
189
267
  let connected = false;
@@ -0,0 +1,185 @@
1
+ import axios from "axios";
2
+ import { KNOWHOW_API_URL } from "../../services/KnowhowClient";
3
+ import { openBrowser } from "../../auth/browserLogin";
4
+ import { Spinner } from "../../auth/spinner";
5
+ import { getConfig, updateConfig } from "../../config";
6
+ import { PasskeySetupSession, PasskeySetupStatus, PasskeyCredential } from "./types";
7
+
8
+ /**
9
+ * Service that handles the CLI-side passkey setup flow.
10
+ *
11
+ * Flow:
12
+ * 1. POST /api/worker/passkey/setup/session → get sessionId + browserUrl
13
+ * 2. Open browser to browserUrl
14
+ * 3. Poll /api/worker/passkey/setup/status/:sessionId until status === 'complete'
15
+ * 4. Save the returned credential to local config
16
+ */
17
+ export class PasskeySetupService {
18
+ private baseUrl: string;
19
+
20
+ constructor(baseUrl: string = KNOWHOW_API_URL) {
21
+ if (!baseUrl) {
22
+ throw new Error("KNOWHOW_API_URL environment variable not set");
23
+ }
24
+ this.baseUrl = baseUrl;
25
+ }
26
+
27
+ /**
28
+ * Run the full passkey setup flow.
29
+ */
30
+ async setup(jwt: string): Promise<void> {
31
+ const spinner = new Spinner();
32
+
33
+ try {
34
+ spinner.start("Creating passkey setup session");
35
+ const session = await this.createSetupSession(jwt);
36
+ spinner.stop();
37
+
38
+ await openBrowser(session.browserUrl);
39
+ console.log(
40
+ `\nIf the browser didn't open automatically, please visit:\n ${session.browserUrl}\n`
41
+ );
42
+
43
+ spinner.start("Waiting for passkey registration in browser…");
44
+
45
+ const credential = await this.pollForCompletion(session.sessionId, spinner);
46
+
47
+ spinner.stop();
48
+ spinner.start("Saving passkey credential to config");
49
+
50
+ await this.saveCredential(credential);
51
+
52
+ spinner.stop();
53
+ console.log("✅ Passkey registered successfully!");
54
+ console.log(
55
+ " Worker will now require passkey authentication for new connections."
56
+ );
57
+ } catch (error) {
58
+ spinner.stop();
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Remove passkey requirement from config.
65
+ */
66
+ async reset(): Promise<void> {
67
+ const config = await getConfig();
68
+
69
+ if (!config.worker?.auth?.passkey) {
70
+ console.log("ℹ️ No passkey configured for this worker.");
71
+ return;
72
+ }
73
+
74
+ const updatedConfig = {
75
+ ...config,
76
+ worker: {
77
+ ...config.worker,
78
+ auth: {
79
+ ...config.worker.auth,
80
+ required: false,
81
+ passkey: undefined,
82
+ },
83
+ },
84
+ };
85
+
86
+ await updateConfig(updatedConfig);
87
+ console.log("✅ Passkey requirement removed from config.");
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Private helpers
92
+ // ---------------------------------------------------------------------------
93
+
94
+ private async createSetupSession(jwt: string): Promise<PasskeySetupSession> {
95
+ try {
96
+ const response = await axios.post<PasskeySetupSession>(
97
+ `${this.baseUrl}/api/worker/passkey/setup/session`,
98
+ {},
99
+ {
100
+ headers: { Authorization: `Bearer ${jwt}` },
101
+ timeout: 10000,
102
+ }
103
+ );
104
+ return response.data;
105
+ } catch (error) {
106
+ if (axios.isAxiosError(error)) {
107
+ throw new Error(
108
+ `Failed to create passkey setup session: ${
109
+ error.response?.data?.message || error.message
110
+ }`
111
+ );
112
+ }
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ private async pollForCompletion(
118
+ sessionId: string,
119
+ spinner: Spinner
120
+ ): Promise<PasskeyCredential> {
121
+ const maxAttempts = 60; // 5 minutes at 5-second intervals
122
+ let attempt = 0;
123
+
124
+ while (attempt < maxAttempts) {
125
+ attempt++;
126
+
127
+ try {
128
+ const response = await axios.get<PasskeySetupStatus>(
129
+ `${this.baseUrl}/api/worker/passkey/setup/status/${sessionId}`,
130
+ { timeout: 10000 }
131
+ );
132
+
133
+ const { status, credential } = response.data;
134
+
135
+ if (status === "complete" && credential) {
136
+ return credential;
137
+ } else if (status === "expired") {
138
+ throw new Error(
139
+ "Passkey setup session expired. Please run 'knowhow worker --passkey' again."
140
+ );
141
+ }
142
+ } catch (error) {
143
+ if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
144
+ // Timeout — keep polling
145
+ } else if (!(error instanceof Error && error.message.includes("expired"))) {
146
+ // Re-throw non-timeout, non-expected errors
147
+ throw error;
148
+ } else {
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ await this.sleep(5000);
154
+ }
155
+
156
+ throw new Error("Passkey setup timed out. Please try again.");
157
+ }
158
+
159
+ private async saveCredential(credential: PasskeyCredential): Promise<void> {
160
+ const config = await getConfig();
161
+
162
+ const updatedConfig = {
163
+ ...config,
164
+ worker: {
165
+ ...config.worker,
166
+ auth: {
167
+ ...config.worker?.auth,
168
+ required: true,
169
+ passkey: {
170
+ publicKey: credential.publicKey,
171
+ credentialId: credential.credentialId,
172
+ algorithm: credential.algorithm,
173
+ },
174
+ sessionDurationHours: config.worker?.auth?.sessionDurationHours ?? 3,
175
+ },
176
+ },
177
+ };
178
+
179
+ await updateConfig(updatedConfig);
180
+ }
181
+
182
+ private async sleep(ms: number): Promise<void> {
183
+ return new Promise((resolve) => setTimeout(resolve, ms));
184
+ }
185
+ }
@@ -0,0 +1,190 @@
1
+ import crypto from "crypto";
2
+ import { verifyAuthenticationResponse } from "@simplewebauthn/server";
3
+
4
+ /**
5
+ * Manages the locked/unlocked state of the worker's passkey auth.
6
+ * When the worker has passkey configured, it starts locked.
7
+ * The user must call `unlock` with a valid WebAuthn assertion to unlock.
8
+ * The worker verifies the signature locally using the stored public key.
9
+ */
10
+
11
+ export interface PasskeyConfig {
12
+ publicKey: string; // base64url-encoded COSE public key
13
+ credentialId: string; // base64url-encoded credential ID
14
+ algorithm: number; // e.g. -7 for ES256
15
+ }
16
+
17
+ export interface UnlockParams {
18
+ /** base64url-encoded signature from WebAuthn assertion */
19
+ signature: string;
20
+ /** base64url-encoded credential ID */
21
+ credentialId: string;
22
+ /** base64url-encoded authenticatorData */
23
+ authenticatorData: string;
24
+ /** base64url-encoded clientDataJSON */
25
+ clientDataJSON: string;
26
+ /** The challenge that was signed (base64url) */
27
+ challenge: string;
28
+ }
29
+
30
+ export class WorkerPasskeyAuthService {
31
+ private locked = true;
32
+ private sessionExpiry: number | null = null;
33
+ private sessionDurationMs: number;
34
+ // Pending challenge: base64url
35
+ private pendingChallenge: string | null = null;
36
+ private pendingChallengeExpiry: number | null = null;
37
+ private readonly CHALLENGE_TTL_MS = 5 * 60 * 1000; // 5 minutes
38
+
39
+ constructor(
40
+ private passkeyConfig: PasskeyConfig,
41
+ sessionDurationHours: number = 3
42
+ ) {
43
+ this.sessionDurationMs = sessionDurationHours * 60 * 60 * 1000;
44
+ }
45
+
46
+ isLocked(): boolean {
47
+ if (!this.locked) {
48
+ // Check if session has expired
49
+ if (this.sessionExpiry && Date.now() > this.sessionExpiry) {
50
+ console.log("🔒 Passkey session expired, locking worker");
51
+ this.locked = true;
52
+ this.sessionExpiry = null;
53
+ }
54
+ }
55
+ return this.locked;
56
+ }
57
+
58
+ /**
59
+ * Generate a new challenge for the client to sign.
60
+ * Returns base64url-encoded challenge bytes.
61
+ */
62
+ generateChallenge(): string {
63
+ const challengeBytes = crypto.randomBytes(32);
64
+ const challenge = challengeBytes.toString("base64url");
65
+ this.pendingChallenge = challenge;
66
+ this.pendingChallengeExpiry = Date.now() + this.CHALLENGE_TTL_MS;
67
+ return challenge;
68
+ }
69
+
70
+ /**
71
+ * Attempt to unlock the worker by verifying a WebAuthn assertion.
72
+ * Returns true if the signature is valid and the worker is now unlocked.
73
+ */
74
+ async unlock(params: UnlockParams): Promise<{ success: boolean; reason?: string }> {
75
+ // Verify the challenge matches what we issued
76
+ if (!this.pendingChallenge) {
77
+ return { success: false, reason: "No pending challenge. Call getChallenge first." };
78
+ }
79
+ if (this.pendingChallengeExpiry && Date.now() > this.pendingChallengeExpiry) {
80
+ this.pendingChallenge = null;
81
+ this.pendingChallengeExpiry = null;
82
+ return { success: false, reason: "Challenge expired. Please request a new challenge." };
83
+ }
84
+
85
+ // Verify the challenge in clientDataJSON matches our pending challenge
86
+ let clientData: { type: string; challenge: string; origin: string };
87
+ try {
88
+ const clientDataBytes = Buffer.from(params.clientDataJSON, "base64url");
89
+ clientData = JSON.parse(clientDataBytes.toString("utf8"));
90
+ } catch {
91
+ return { success: false, reason: "Invalid clientDataJSON" };
92
+ }
93
+
94
+ // The challenge in clientDataJSON is base64url-encoded
95
+ if (clientData.challenge !== this.pendingChallenge) {
96
+ return { success: false, reason: "Challenge mismatch" };
97
+ }
98
+
99
+ if (clientData.type !== "webauthn.get") {
100
+ return { success: false, reason: "Invalid clientData type" };
101
+ }
102
+
103
+ // Verify credential ID matches our stored credential
104
+ if (params.credentialId !== this.passkeyConfig.credentialId) {
105
+ return { success: false, reason: "Unknown credential" };
106
+ }
107
+
108
+ // Verify the signature using @simplewebauthn/server
109
+ const valid = await this.verifySignature(params, clientData.origin);
110
+ if (!valid) {
111
+ return { success: false, reason: "Invalid signature" };
112
+ }
113
+
114
+ // Unlock!
115
+ this.locked = false;
116
+ this.sessionExpiry = Date.now() + this.sessionDurationMs;
117
+ this.pendingChallenge = null;
118
+ this.pendingChallengeExpiry = null;
119
+
120
+ const expiresAt = new Date(this.sessionExpiry).toISOString();
121
+ console.log(`🔓 Worker unlocked! Session expires at ${expiresAt}`);
122
+ return { success: true };
123
+ }
124
+
125
+ lock(): void {
126
+ this.locked = true;
127
+ this.sessionExpiry = null;
128
+ this.pendingChallenge = null;
129
+ this.pendingChallengeExpiry = null;
130
+ console.log("🔒 Worker locked");
131
+ }
132
+
133
+ getSessionInfo() {
134
+ if (this.locked || !this.sessionExpiry) {
135
+ return { locked: true, expiresAt: null };
136
+ }
137
+ return {
138
+ locked: false,
139
+ expiresAt: new Date(this.sessionExpiry).toISOString(),
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Returns the credential ID stored in the passkey config.
145
+ */
146
+ getCredentialId(): string {
147
+ return this.passkeyConfig.credentialId;
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Signature verification via @simplewebauthn/server
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /**
155
+ * Verify a WebAuthn assertion using @simplewebauthn/server's verifyAuthenticationResponse.
156
+ * This handles all COSE key format complexity automatically.
157
+ */
158
+ private async verifySignature(params: UnlockParams, origin: string): Promise<boolean> {
159
+ try {
160
+ const { verified } = await verifyAuthenticationResponse({
161
+ response: {
162
+ id: params.credentialId,
163
+ rawId: params.credentialId,
164
+ response: {
165
+ authenticatorData: params.authenticatorData,
166
+ clientDataJSON: params.clientDataJSON,
167
+ signature: params.signature,
168
+ },
169
+ type: "public-key",
170
+ clientExtensionResults: {},
171
+ },
172
+ expectedChallenge: this.pendingChallenge!,
173
+ expectedOrigin: origin,
174
+ expectedRPID: new URL(origin).hostname,
175
+ credential: {
176
+ id: this.passkeyConfig.credentialId,
177
+ publicKey: Buffer.from(this.passkeyConfig.publicKey, "base64url"),
178
+ counter: 0,
179
+ transports: undefined,
180
+ },
181
+ requireUserVerification: false,
182
+ });
183
+
184
+ return verified;
185
+ } catch (err) {
186
+ console.error("Signature verification error:", err);
187
+ return false;
188
+ }
189
+ }
190
+ }