@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.
- package/package.json +4 -2
- package/src/agents/base/base.ts +72 -62
- package/src/agents/index.ts +30 -14
- package/src/agents/researcher/researcher.ts +1 -2
- package/src/agents/tools/aiClient.ts +48 -0
- package/src/agents/tools/list.ts +57 -0
- package/src/agents/tools/startAgentTask.ts +3 -1
- package/src/chat/CliChatService.ts +20 -4
- package/src/chat/modules/AgentModule.ts +399 -357
- package/src/chat/modules/CustomCommandsModule.ts +0 -1
- package/src/chat/modules/InternalChatModule.ts +18 -2
- package/src/chat/modules/RendererModule.ts +109 -0
- package/src/chat/modules/SessionsModule.ts +854 -0
- package/src/chat/modules/SetupModule.ts +6 -8
- package/src/chat/modules/index.ts +1 -0
- package/src/chat/renderer/CompactRenderer.ts +209 -0
- package/src/chat/renderer/ConsoleRenderer.ts +141 -0
- package/src/chat/renderer/FancyRenderer.ts +421 -0
- package/src/chat/renderer/index.ts +5 -0
- package/src/chat/renderer/loadRenderer.ts +314 -0
- package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
- package/src/chat/renderer/types.ts +88 -0
- package/src/chat/types.ts +5 -0
- package/src/chat.ts +69 -5
- package/src/cli.ts +24 -5
- package/src/clients/index.ts +91 -0
- package/src/clients/pricing/google.ts +81 -2
- package/src/clients/pricing/openai.ts +68 -0
- package/src/config.ts +15 -0
- package/src/plugins/AgentsMdPlugin.ts +1 -1
- package/src/plugins/GitPlugin.ts +20 -20
- package/src/plugins/PluginBase.ts +11 -0
- package/src/plugins/SkillsPlugin.ts +150 -0
- package/src/plugins/asana.ts +4 -4
- package/src/plugins/embedding.ts +3 -5
- package/src/plugins/exec.ts +3 -3
- package/src/plugins/figma.ts +3 -7
- package/src/plugins/github.ts +18 -29
- package/src/plugins/jira.ts +2 -2
- package/src/plugins/language.ts +4 -4
- package/src/plugins/linear.ts +4 -4
- package/src/plugins/notion.ts +6 -8
- package/src/plugins/plugins.ts +29 -3
- package/src/plugins/url.ts +2 -2
- package/src/plugins/vim.ts +4 -3
- package/src/services/AgentService.ts +17 -0
- package/src/services/AgentSyncFs.ts +3 -0
- package/src/services/EventService.ts +168 -27
- package/src/services/KnowhowClient.ts +1 -0
- package/src/services/SessionManager.ts +51 -1
- package/src/services/SyncedAgentWatcher.ts +397 -0
- package/src/services/SyncerService.ts +147 -0
- package/src/services/index.ts +2 -0
- package/src/services/modules/index.ts +14 -3
- package/src/types.ts +103 -5
- package/src/worker.ts +80 -2
- package/src/workers/auth/PasskeySetup.ts +185 -0
- package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
- package/src/workers/auth/types.ts +58 -0
- package/src/workers/tools/getChallenge.ts +33 -0
- package/src/workers/tools/index.ts +8 -0
- package/src/workers/tools/lock.ts +31 -0
- package/src/workers/tools/unlock.ts +116 -0
- package/tests/clients/pricing.test.ts +144 -0
- package/tests/unit/modules/moduleLoading.test.ts +226 -0
- package/tests/unit/plugins/pluginLoading.test.ts +151 -0
- package/ts_build/package.json +4 -2
- package/ts_build/src/agents/base/base.d.ts +4 -3
- package/ts_build/src/agents/base/base.js +54 -30
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/index.d.ts +3 -0
- package/ts_build/src/agents/index.js +21 -11
- package/ts_build/src/agents/index.js.map +1 -1
- package/ts_build/src/agents/researcher/researcher.js +1 -1
- package/ts_build/src/agents/researcher/researcher.js.map +1 -1
- package/ts_build/src/agents/tools/aiClient.d.ts +3 -0
- package/ts_build/src/agents/tools/aiClient.js +31 -1
- package/ts_build/src/agents/tools/aiClient.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +48 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.js +2 -1
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +16 -5
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
- package/ts_build/src/chat/modules/AgentModule.js +248 -258
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
- package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
- package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
- package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
- package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
- package/ts_build/src/chat/modules/RendererModule.js +76 -0
- package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
- package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
- package/ts_build/src/chat/modules/SessionsModule.js +582 -0
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
- package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
- package/ts_build/src/chat/modules/SetupModule.js +4 -6
- package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
- package/ts_build/src/chat/modules/index.d.ts +1 -0
- package/ts_build/src/chat/modules/index.js +3 -1
- package/ts_build/src/chat/modules/index.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/index.d.ts +5 -0
- package/ts_build/src/chat/renderer/index.js +29 -0
- package/ts_build/src/chat/renderer/index.js.map +1 -0
- package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
- package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
- package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
- package/ts_build/src/chat/renderer/types.d.ts +75 -0
- package/ts_build/src/chat/renderer/types.js +3 -0
- package/ts_build/src/chat/renderer/types.js.map +1 -0
- package/ts_build/src/chat/types.d.ts +5 -0
- package/ts_build/src/chat.js +46 -4
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +18 -5
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/gemini.d.ts +10 -10
- package/ts_build/src/clients/index.d.ts +10 -0
- package/ts_build/src/clients/index.js +58 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +10 -10
- package/ts_build/src/clients/pricing/google.js +74 -2
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +65 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +17 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
- package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
- package/ts_build/src/plugins/GitPlugin.js +20 -20
- package/ts_build/src/plugins/GitPlugin.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +1 -0
- package/ts_build/src/plugins/PluginBase.js +13 -0
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
- package/ts_build/src/plugins/SkillsPlugin.js +149 -0
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
- package/ts_build/src/plugins/asana.js +4 -4
- package/ts_build/src/plugins/asana.js.map +1 -1
- package/ts_build/src/plugins/embedding.js +3 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/exec.js +3 -3
- package/ts_build/src/plugins/exec.js.map +1 -1
- package/ts_build/src/plugins/figma.js +3 -3
- package/ts_build/src/plugins/figma.js.map +1 -1
- package/ts_build/src/plugins/github.js +18 -18
- package/ts_build/src/plugins/github.js.map +1 -1
- package/ts_build/src/plugins/jira.js +2 -2
- package/ts_build/src/plugins/jira.js.map +1 -1
- package/ts_build/src/plugins/language.js +4 -4
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/linear.js +4 -4
- package/ts_build/src/plugins/linear.js.map +1 -1
- package/ts_build/src/plugins/notion.js +6 -6
- package/ts_build/src/plugins/notion.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +3 -0
- package/ts_build/src/plugins/plugins.js +18 -3
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/url.js +2 -2
- package/ts_build/src/plugins/url.js.map +1 -1
- package/ts_build/src/plugins/vim.js +2 -2
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/services/AgentService.d.ts +3 -0
- package/ts_build/src/services/AgentService.js +7 -0
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
- package/ts_build/src/services/AgentSyncFs.js +2 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +25 -2
- package/ts_build/src/services/EventService.js +92 -14
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/SessionManager.d.ts +6 -0
- package/ts_build/src/services/SessionManager.js +39 -1
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
- package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
- package/ts_build/src/services/SyncerService.d.ts +30 -0
- package/ts_build/src/services/SyncerService.js +72 -0
- package/ts_build/src/services/SyncerService.js.map +1 -0
- package/ts_build/src/services/index.d.ts +2 -0
- package/ts_build/src/services/index.js +2 -0
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.js +10 -2
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +51 -2
- package/ts_build/src/types.js +73 -5
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +2 -0
- package/ts_build/src/worker.js +59 -4
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
- package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
- package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
- package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +36 -0
- package/ts_build/src/workers/auth/types.js +3 -0
- package/ts_build/src/workers/auth/types.js.map +1 -0
- package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
- package/ts_build/src/workers/tools/getChallenge.js +27 -0
- package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
- package/ts_build/src/workers/tools/index.d.ts +6 -0
- package/ts_build/src/workers/tools/index.js +10 -0
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/lock.d.ts +9 -0
- package/ts_build/src/workers/tools/lock.js +27 -0
- package/ts_build/src/workers/tools/lock.js.map +1 -0
- package/ts_build/src/workers/tools/unlock.d.ts +18 -0
- package/ts_build/src/workers/tools/unlock.js +78 -0
- package/ts_build/src/workers/tools/unlock.js.map +1 -0
- package/ts_build/tests/clients/pricing.test.d.ts +1 -0
- package/ts_build/tests/clients/pricing.test.js +90 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
- 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
|
-
|
|
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 = [
|
|
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 = [
|
|
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
|
-
|
|
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
|
+
}
|