@tyvm/knowhow 0.0.107 → 0.0.108-dev.4a8ba55

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 (193) hide show
  1. package/README.md +45 -0
  2. package/package.json +9 -4
  3. package/scripts/publish.sh +86 -0
  4. package/src/agents/base/base.ts +10 -0
  5. package/src/agents/tools/execCommand.ts +49 -6
  6. package/src/agents/tools/index.ts +0 -1
  7. package/src/agents/tools/list.ts +0 -2
  8. package/src/chat/CliChatService.ts +7 -1
  9. package/src/chat/modules/AgentModule.ts +55 -30
  10. package/src/chat/modules/SessionsModule.ts +7 -2
  11. package/src/chat/renderer/CompactRenderer.ts +20 -0
  12. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  13. package/src/chat/renderer/FancyRenderer.ts +19 -0
  14. package/src/chat/renderer/types.ts +11 -0
  15. package/src/cli.ts +79 -661
  16. package/src/clients/anthropic.ts +19 -16
  17. package/src/clients/types.ts +23 -4
  18. package/src/cloudWorker.ts +75 -1
  19. package/src/commands/agent.ts +246 -0
  20. package/src/commands/misc.ts +169 -0
  21. package/src/commands/modules.ts +182 -0
  22. package/src/commands/services.ts +72 -0
  23. package/src/commands/workers.ts +160 -0
  24. package/src/config.ts +37 -0
  25. package/src/index.ts +18 -0
  26. package/src/plugins/embedding.ts +11 -6
  27. package/src/plugins/plugins.ts +0 -21
  28. package/src/plugins/vim.ts +5 -16
  29. package/src/processors/JsonCompressor.ts +6 -6
  30. package/src/services/KnowhowClient.ts +22 -2
  31. package/src/services/S3.ts +10 -0
  32. package/src/services/modules/index.ts +58 -49
  33. package/src/services/modules/types.ts +4 -0
  34. package/src/tunnel.ts +216 -0
  35. package/src/types.ts +0 -1
  36. package/src/worker.ts +105 -312
  37. package/src/workers/auth/WsMiddleware.ts +99 -0
  38. package/src/workers/auth/authMiddleware.ts +104 -0
  39. package/src/workers/auth/types.ts +14 -2
  40. package/src/workers/tools/index.ts +2 -0
  41. package/src/workers/tools/reloadConfig.ts +84 -0
  42. package/tests/services/WorkerReloadConfig.test.ts +141 -0
  43. package/tests/unit/modules/moduleLoading.test.ts +0 -25
  44. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  45. package/ts_build/package.json +9 -4
  46. package/ts_build/src/agents/base/base.js +11 -0
  47. package/ts_build/src/agents/base/base.js.map +1 -1
  48. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  49. package/ts_build/src/agents/tools/execCommand.js +39 -5
  50. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  51. package/ts_build/src/agents/tools/index.d.ts +0 -1
  52. package/ts_build/src/agents/tools/index.js +0 -1
  53. package/ts_build/src/agents/tools/index.js.map +1 -1
  54. package/ts_build/src/agents/tools/list.js +0 -2
  55. package/ts_build/src/agents/tools/list.js.map +1 -1
  56. package/ts_build/src/chat/CliChatService.js +10 -1
  57. package/ts_build/src/chat/CliChatService.js.map +1 -1
  58. package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
  59. package/ts_build/src/chat/modules/AgentModule.js +39 -19
  60. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  61. package/ts_build/src/chat/modules/SessionsModule.js +7 -2
  62. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  63. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  64. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  65. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  66. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  67. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  68. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  69. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  70. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  71. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  72. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  73. package/ts_build/src/cli.js +40 -519
  74. package/ts_build/src/cli.js.map +1 -1
  75. package/ts_build/src/clients/anthropic.d.ts +5 -5
  76. package/ts_build/src/clients/anthropic.js +19 -16
  77. package/ts_build/src/clients/anthropic.js.map +1 -1
  78. package/ts_build/src/clients/types.d.ts +5 -2
  79. package/ts_build/src/cloudWorker.d.ts +9 -0
  80. package/ts_build/src/cloudWorker.js +36 -0
  81. package/ts_build/src/cloudWorker.js.map +1 -1
  82. package/ts_build/src/commands/agent.d.ts +6 -0
  83. package/ts_build/src/commands/agent.js +229 -0
  84. package/ts_build/src/commands/agent.js.map +1 -0
  85. package/ts_build/src/commands/misc.d.ts +10 -0
  86. package/ts_build/src/commands/misc.js +195 -0
  87. package/ts_build/src/commands/misc.js.map +1 -0
  88. package/ts_build/src/commands/modules.d.ts +3 -0
  89. package/ts_build/src/commands/modules.js +160 -0
  90. package/ts_build/src/commands/modules.js.map +1 -0
  91. package/ts_build/src/commands/services.d.ts +5 -0
  92. package/ts_build/src/commands/services.js +86 -0
  93. package/ts_build/src/commands/services.js.map +1 -0
  94. package/ts_build/src/commands/workers.d.ts +6 -0
  95. package/ts_build/src/commands/workers.js +163 -0
  96. package/ts_build/src/commands/workers.js.map +1 -0
  97. package/ts_build/src/config.d.ts +1 -0
  98. package/ts_build/src/config.js +32 -0
  99. package/ts_build/src/config.js.map +1 -1
  100. package/ts_build/src/index.d.ts +1 -0
  101. package/ts_build/src/index.js +17 -1
  102. package/ts_build/src/index.js.map +1 -1
  103. package/ts_build/src/plugins/embedding.js +4 -3
  104. package/ts_build/src/plugins/embedding.js.map +1 -1
  105. package/ts_build/src/plugins/plugins.d.ts +0 -2
  106. package/ts_build/src/plugins/plugins.js +0 -11
  107. package/ts_build/src/plugins/plugins.js.map +1 -1
  108. package/ts_build/src/plugins/vim.js +3 -9
  109. package/ts_build/src/plugins/vim.js.map +1 -1
  110. package/ts_build/src/processors/JsonCompressor.js +4 -4
  111. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  112. package/ts_build/src/services/KnowhowClient.d.ts +12 -0
  113. package/ts_build/src/services/KnowhowClient.js +11 -0
  114. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  115. package/ts_build/src/services/S3.js +7 -0
  116. package/ts_build/src/services/S3.js.map +1 -1
  117. package/ts_build/src/services/modules/index.d.ts +33 -0
  118. package/ts_build/src/services/modules/index.js +38 -42
  119. package/ts_build/src/services/modules/index.js.map +1 -1
  120. package/ts_build/src/services/modules/types.d.ts +4 -0
  121. package/ts_build/src/tunnel.d.ts +27 -0
  122. package/ts_build/src/tunnel.js +112 -0
  123. package/ts_build/src/tunnel.js.map +1 -0
  124. package/ts_build/src/types.d.ts +0 -1
  125. package/ts_build/src/types.js.map +1 -1
  126. package/ts_build/src/worker.d.ts +1 -4
  127. package/ts_build/src/worker.js +59 -227
  128. package/ts_build/src/worker.js.map +1 -1
  129. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  130. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  131. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  132. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  133. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  134. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  135. package/ts_build/src/workers/auth/types.d.ts +8 -1
  136. package/ts_build/src/workers/tools/index.d.ts +2 -0
  137. package/ts_build/src/workers/tools/index.js +4 -1
  138. package/ts_build/src/workers/tools/index.js.map +1 -1
  139. package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
  140. package/ts_build/src/workers/tools/reloadConfig.js +48 -0
  141. package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
  142. package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
  143. package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
  144. package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
  145. package/ts_build/tests/unit/modules/moduleLoading.test.js +0 -19
  146. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  147. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  148. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  149. package/src/agents/tools/executeScript/README.md +0 -94
  150. package/src/agents/tools/executeScript/definition.ts +0 -79
  151. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  152. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  153. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  154. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  155. package/src/agents/tools/executeScript/index.ts +0 -98
  156. package/src/services/script-execution/SandboxContext.ts +0 -282
  157. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  158. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  159. package/src/services/script-execution/ScriptTracer.ts +0 -249
  160. package/src/services/script-execution/types.ts +0 -134
  161. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  162. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  163. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  164. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  165. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  166. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  167. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  168. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  169. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  170. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  171. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  172. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  173. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  174. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  175. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  176. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  177. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  178. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  179. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  180. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  181. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  182. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  183. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  184. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  185. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  186. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  187. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  188. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  189. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  190. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  191. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  192. package/ts_build/src/services/script-execution/types.js +0 -3
  193. package/ts_build/src/services/script-execution/types.js.map +0 -1
@@ -40,11 +40,11 @@ export class GenericAnthropicClient implements GenericClient {
40
40
  });
41
41
  }
42
42
 
43
- handleToolCaching(tools: Anthropic.Tool[]) {
43
+ handleToolCaching(tools: Anthropic.Tool[], longTtl = false) {
44
44
  const lastTool = tools[tools.length - 1];
45
45
 
46
46
  if (lastTool) {
47
- lastTool.cache_control = { type: "ephemeral" };
47
+ lastTool.cache_control = longTtl ? { type: "ephemeral", ttl: "1h" } as any : { type: "ephemeral" };
48
48
  }
49
49
  }
50
50
 
@@ -94,7 +94,7 @@ export class GenericAnthropicClient implements GenericClient {
94
94
  return cleaned;
95
95
  }
96
96
 
97
- transformTools(tools?: Tool[]): Anthropic.Tool[] {
97
+ transformTools(tools?: Tool[], longTtl = false): Anthropic.Tool[] {
98
98
  if (!tools) {
99
99
  return [];
100
100
  }
@@ -104,7 +104,7 @@ export class GenericAnthropicClient implements GenericClient {
104
104
  input_schema: this.cleanSchemaForAnthropic(tool.function.parameters) as any,
105
105
  }));
106
106
 
107
- this.handleToolCaching(transformed);
107
+ this.handleToolCaching(transformed, longTtl);
108
108
 
109
109
  return transformed;
110
110
  }
@@ -153,16 +153,16 @@ export class GenericAnthropicClient implements GenericClient {
153
153
  return messages;
154
154
  }
155
155
 
156
- cacheLastContent(message: MessageParam) {
156
+ cacheLastContent(message: MessageParam, longTtl = false) {
157
157
  if (Array.isArray(message.content)) {
158
158
  const lastMessage = message.content[message.content.length - 1];
159
159
  if (
160
160
  lastMessage.type !== "thinking" &&
161
161
  lastMessage.type !== "redacted_thinking"
162
162
  ) {
163
- lastMessage.cache_control = {
164
- type: "ephemeral",
165
- };
163
+ lastMessage.cache_control = longTtl
164
+ ? ({ type: "ephemeral", ttl: "1h" } as any)
165
+ : { type: "ephemeral" };
166
166
  }
167
167
  }
168
168
  }
@@ -179,7 +179,7 @@ export class GenericAnthropicClient implements GenericClient {
179
179
  }
180
180
  }
181
181
 
182
- handleMessageCaching(groupedMessages: MessageParam[]) {
182
+ handleMessageCaching(groupedMessages: MessageParam[], longTtl = false) {
183
183
  this.handleClearingCache(groupedMessages);
184
184
 
185
185
  // find the last two messages and mark them as ephemeral
@@ -189,7 +189,7 @@ export class GenericAnthropicClient implements GenericClient {
189
189
 
190
190
  for (const m of lastTwoUserMessages) {
191
191
  if (Array.isArray(m.content)) {
192
- this.cacheLastContent(m);
192
+ this.cacheLastContent(m, longTtl);
193
193
  }
194
194
  }
195
195
  }
@@ -203,7 +203,7 @@ export class GenericAnthropicClient implements GenericClient {
203
203
  }
204
204
  }
205
205
 
206
- transformMessages(messages: Message[]): MessageParam[] {
206
+ transformMessages(messages: Message[], longTtl = false): MessageParam[] {
207
207
  const toolCalls = messages.flatMap((msg) => msg.tool_calls || []);
208
208
  const claudeMessages: MessageParam[] = messages
209
209
  .filter((msg) => msg.role !== "system")
@@ -302,7 +302,7 @@ export class GenericAnthropicClient implements GenericClient {
302
302
 
303
303
  const groupedMessages = this.combineMessages(claudeMessages);
304
304
 
305
- this.handleMessageCaching(groupedMessages);
305
+ this.handleMessageCaching(groupedMessages, longTtl);
306
306
 
307
307
  return groupedMessages;
308
308
  }
@@ -349,14 +349,15 @@ export class GenericAnthropicClient implements GenericClient {
349
349
  async createChatCompletion(
350
350
  options: CompletionOptions
351
351
  ): Promise<CompletionResponse> {
352
+ const longTtl = !!options.long_ttl_cache;
352
353
  const systemMessage = options.messages
353
354
  .filter((msg) => msg.role === "system")
354
355
  .map((msg) => msg.content || "")
355
356
  .join("\n");
356
357
 
357
- const claudeMessages = this.transformMessages(options.messages);
358
+ const claudeMessages = this.transformMessages(options.messages, longTtl);
358
359
 
359
- const tools = this.transformTools(options.tools);
360
+ const tools = this.transformTools(options.tools, longTtl);
360
361
  try {
361
362
  const response = await this.client.messages.create({
362
363
  model: options.model,
@@ -365,7 +366,7 @@ export class GenericAnthropicClient implements GenericClient {
365
366
  ? [
366
367
  {
367
368
  text: systemMessage,
368
- cache_control: { type: "ephemeral" },
369
+ cache_control: longTtl ? ({ type: "ephemeral", ttl: "1h" } as any) : { type: "ephemeral" },
369
370
  type: "text",
370
371
  },
371
372
  ]
@@ -413,6 +414,8 @@ export class GenericAnthropicClient implements GenericClient {
413
414
 
414
415
  model: options.model,
415
416
  usage: response.usage ? {
417
+ input_tokens: response.usage.input_tokens ?? 0,
418
+ output_tokens: response.usage.output_tokens ?? 0,
416
419
  prompt_tokens: response.usage.input_tokens ?? 0,
417
420
  completion_tokens: response.usage.output_tokens ?? 0,
418
421
  total_tokens: (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0),
@@ -422,7 +425,7 @@ export class GenericAnthropicClient implements GenericClient {
422
425
  usd_cost: this.calculateCost(options.model, response.usage),
423
426
  };
424
427
  } catch (err) {
425
- if ("headers" in err && err.headers["x-should-retry"] === "true") {
428
+ if ("headers" in err && err.headers?.["x-should-retry"] === "true") {
426
429
  console.warn("Retrying failed request", err);
427
430
  await wait(2500);
428
431
  return this.createChatCompletion(options);
@@ -1,4 +1,10 @@
1
- export type ModelModality = "completion" | "embedding" | "image" | "audio" | "video" | "transcription";
1
+ export type ModelModality =
2
+ | "completion"
3
+ | "embedding"
4
+ | "image"
5
+ | "audio"
6
+ | "video"
7
+ | "transcription";
2
8
 
3
9
  export type MessageContent =
4
10
  | { type: "text"; text: string }
@@ -8,7 +14,7 @@ export type MessageContent =
8
14
 
9
15
  export interface Message {
10
16
  role: "system" | "user" | "assistant" | "tool";
11
- content?: string | MessageContent[];
17
+ content?: string | MessageContent[] | null;
12
18
 
13
19
  name?: string;
14
20
  tool_call_id?: string;
@@ -16,7 +22,7 @@ export interface Message {
16
22
  }
17
23
 
18
24
  export interface OutputMessage extends Message {
19
- content: string;
25
+ content?: string | null;
20
26
  }
21
27
 
22
28
  export interface ToolProp {
@@ -61,6 +67,13 @@ export interface CompletionOptions {
61
67
  * Maps to: OpenAI reasoning_effort, xAI reasoning.effort, Gemini thinkingLevel/thinkingBudget, Anthropic thinking budget.
62
68
  * "low" = minimal thinking, "medium" = balanced, "high" = maximum reasoning */
63
69
  reasoning_effort?: "low" | "medium" | "high";
70
+ /**
71
+ * When true, hints to the client that this task is long-running and it should
72
+ * use a long-TTL cache where available.
73
+ * - Anthropic: enables the `extended-cache-ttl-2025-02-19` beta and sets
74
+ * `cache_control.ttl` to 3600 (1 hour) instead of the default 5-minute ephemeral cache.
75
+ */
76
+ long_ttl_cache?: boolean;
64
77
  }
65
78
 
66
79
  /**
@@ -73,6 +86,10 @@ export interface TokenUsage {
73
86
  prompt_tokens: number;
74
87
  /** Total output/completion tokens generated */
75
88
  completion_tokens: number;
89
+ /** Alternative field name for input tokens (some providers use this) */
90
+ input_tokens?: number;
91
+ /** Alternative field name for output tokens (some providers use this) */
92
+ output_tokens?: number;
76
93
  /** Convenience total (prompt + completion) */
77
94
  total_tokens?: number;
78
95
  /** Cache details */
@@ -290,7 +307,9 @@ export interface GenericClient {
290
307
  * When modality is provided, return only models for that modality (static list).
291
308
  * When omitted, return ALL models (backward compat — may do a live API call).
292
309
  */
293
- getModels(modality?: ModelModality): Promise<{ id: string; modality?: ModelModality[] }[]>;
310
+ getModels(
311
+ modality?: ModelModality
312
+ ): Promise<{ id: string; modality?: ModelModality[] }[]>;
294
313
  /**
295
314
  * Returns the context window limit and compression threshold for a given model,
296
315
  * or undefined if the model is not known to this client.
@@ -4,9 +4,14 @@ import { KnowhowSimpleClient, KNOWHOW_API_URL } from "./services/KnowhowClient";
4
4
  import { loadJwt } from "./login";
5
5
  import { getConfig, updateConfig, getLanguageConfig } from "./config";
6
6
  import { services } from "./services";
7
- import { Language, Config } from "./types";
7
+ import { Language, Config, McpConfig } from "./types";
8
8
  import { S3Service } from "./services/S3";
9
9
 
10
+ export interface CloudWorkerPullOptions {
11
+ id: string;
12
+ apiUrl?: string;
13
+ }
14
+
10
15
  export interface CloudWorkerOptions {
11
16
  create?: boolean;
12
17
  push?: string; // uid of existing cloud worker
@@ -330,3 +335,72 @@ export async function cloudWorker(options: CloudWorkerOptions) {
330
335
  console.log(`\n✅ Cloud worker sync complete!`);
331
336
  }
332
337
  }
338
+
339
+ /**
340
+ * Pull the latest workerConfigJson from the cloud worker API and update the
341
+ * local knowhow.json config to match.
342
+ *
343
+ * This is the "pull" half of the config sync cycle. After running this,
344
+ * you can reload the worker's MCPs (in-process) via the reloadConfig
345
+ * WebSocket message or by calling `knowhow worker` again.
346
+ *
347
+ * Merged fields from workerConfigJson:
348
+ * - mcps → overwrites config.mcps
349
+ * - modules → overwrites config.modules (optional, only if present)
350
+ * - plugins → overwrites config.plugins (optional, only if present)
351
+ * - agents → overwrites config.agents (optional, only if present)
352
+ */
353
+ export async function pullCloudWorkerConfig(options: CloudWorkerPullOptions) {
354
+ const { id, apiUrl = KNOWHOW_API_URL } = options;
355
+
356
+ // Load JWT
357
+ const jwt = await loadJwt();
358
+ if (!jwt) {
359
+ console.error("❌ No JWT token found. Please run 'knowhow login' first.");
360
+ process.exit(1);
361
+ }
362
+
363
+ const client = new KnowhowSimpleClient(apiUrl, jwt);
364
+
365
+ console.log(`🔄 Pulling config for cloud worker ${id}...`);
366
+
367
+ const resp = await client.getCloudWorker(id);
368
+ const remoteWorker = resp.data;
369
+
370
+ if (!remoteWorker) {
371
+ console.error(`❌ Cloud worker ${id} not found.`);
372
+ process.exit(1);
373
+ }
374
+
375
+ const remoteConfig = (remoteWorker.workerConfigJson ?? {}) as {
376
+ mcps?: McpConfig[];
377
+ modules?: string[];
378
+ plugins?: Config["plugins"];
379
+ agents?: Config["agents"];
380
+ };
381
+
382
+ // Load current local config
383
+ const localConfig = await getConfig();
384
+
385
+ // Merge remote fields into local config
386
+ if (remoteConfig.mcps !== undefined) {
387
+ localConfig.mcps = remoteConfig.mcps;
388
+ }
389
+ if (remoteConfig.modules !== undefined) {
390
+ localConfig.modules = remoteConfig.modules;
391
+ }
392
+ if (remoteConfig.plugins !== undefined) {
393
+ localConfig.plugins = remoteConfig.plugins;
394
+ }
395
+ if (remoteConfig.agents !== undefined) {
396
+ localConfig.agents = remoteConfig.agents;
397
+ }
398
+
399
+ await updateConfig(localConfig);
400
+
401
+ const mcpCount = remoteConfig.mcps?.length ?? 0;
402
+ console.log(`✅ Config pulled! ${mcpCount} MCP(s) now configured locally.`);
403
+ console.log(` Run 'knowhow worker' or trigger reloadConfig to apply changes.`);
404
+
405
+ return { mcps: remoteConfig.mcps, modules: remoteConfig.modules };
406
+ }
@@ -0,0 +1,246 @@
1
+ import { Command } from "commander";
2
+ import { readPromptFile } from "../ai";
3
+ import { AgentModule } from "../chat/modules/AgentModule";
4
+ import { AskModule } from "../chat/modules/AskModule";
5
+ import { SearchModule } from "../chat/modules/SearchModule";
6
+ import { SessionsModule } from "../chat/modules/SessionsModule";
7
+ import { SetupModule } from "../chat/modules/SetupModule";
8
+
9
+ async function readStdin(): Promise<string> {
10
+ return new Promise((resolve) => {
11
+ let data = "";
12
+ process.stdin.setEncoding("utf8");
13
+
14
+ if (process.stdin.isTTY) {
15
+ resolve("");
16
+ return;
17
+ }
18
+
19
+ process.stdin.on("readable", () => {
20
+ const chunk = process.stdin.read();
21
+ if (chunk !== null) data += chunk;
22
+ });
23
+
24
+ process.stdin.on("end", () => resolve(data.trim()));
25
+ });
26
+ }
27
+
28
+ export function addAgentCommand(program: Command, getChatService: () => any): void {
29
+ program
30
+ .command("agent")
31
+ .description("Spin up agents directly from CLI")
32
+ .option(
33
+ "--provider <provider>",
34
+ "AI provider (openai, anthropic, google, xai)"
35
+ )
36
+ .option("--model <model>", "Specific model for the provider")
37
+ .option("--agent-name <name>", "Which agent to use", "Patcher")
38
+ .option(
39
+ "--max-time-limit <minutes>",
40
+ "Time limit for agent execution (minutes)",
41
+ "30"
42
+ )
43
+ .option(
44
+ "--max-spend-limit <dollars>",
45
+ "Cost limit for agent execution (dollars)",
46
+ "10"
47
+ )
48
+ .option("--message-id <messageId>", "Knowhow message ID for task tracking")
49
+ .option("--sync-fs", "Enable filesystem-based synchronization")
50
+ .option(
51
+ "--task-id <taskId>",
52
+ "Pre-generated task ID (used with --sync-fs for predictable agent directory path)"
53
+ )
54
+ .option("--prompt-file <path>", "Custom prompt template file with {text}")
55
+ .option("--input <text>", "Task input (fallback to stdin if not provided)")
56
+ .option(
57
+ "--resume",
58
+ "Resume a previously started task using the --task-id (local FS or remote)"
59
+ )
60
+ .action(async (options) => {
61
+ try {
62
+ const { setupServices } = await import("./services");
63
+ await setupServices();
64
+ const chatService = getChatService();
65
+ const agentModule = new AgentModule();
66
+
67
+ if (options.resume) {
68
+ const threads = await agentModule.loadThreadsForTask(
69
+ options.taskId,
70
+ options.messageId
71
+ );
72
+ const resumeInput =
73
+ options.input || "Please continue from where you left off.";
74
+
75
+ await agentModule.initialize(chatService);
76
+ const { taskCompleted: resumed } =
77
+ await agentModule.resumeFromMessages({
78
+ agentName: options.agentName || "Patcher",
79
+ input: resumeInput,
80
+ threads,
81
+ messageId: options.messageId,
82
+ taskId: options.taskId,
83
+ });
84
+ await resumed;
85
+ return;
86
+ }
87
+
88
+ let input = options.input;
89
+
90
+ if (!input && !options.promptFile) {
91
+ input = await readStdin();
92
+ }
93
+
94
+ input = readPromptFile(options.promptFile, input);
95
+
96
+ if (!input) {
97
+ console.error(
98
+ "Error: No input provided. Use --input flag, pipe input via stdin, or provide --prompt-file."
99
+ );
100
+ process.exit(1);
101
+ }
102
+
103
+ await agentModule.initialize(chatService);
104
+ const { taskCompleted } = await agentModule.setupAgent({
105
+ ...options,
106
+ input,
107
+ maxTimeLimit: parseInt(options.maxTimeLimit, 10),
108
+ maxSpendLimit: parseFloat(options.maxSpendLimit),
109
+ run: true,
110
+ });
111
+ await taskCompleted;
112
+ } catch (error) {
113
+ console.error("Error running agent:", error);
114
+ process.exit(1);
115
+ }
116
+ });
117
+ }
118
+
119
+ export function addAskCommand(program: Command, getChatService: () => any, getConfig: () => any): void {
120
+ program
121
+ .command("ask")
122
+ .description("Direct AI questioning without agent overhead")
123
+ .option("--provider <provider>", "AI provider to use")
124
+ .option("--model <model>", "Specific model")
125
+ .option("--input <text>", "Question (fallback to stdin if not provided)")
126
+ .option("--prompt-file <path>", "Custom prompt template file")
127
+ .action(async (options) => {
128
+ try {
129
+ const { setupServices } = await import("./services");
130
+ await setupServices();
131
+ const chatService = getChatService();
132
+ const config = getConfig();
133
+ let input = options.input;
134
+
135
+ if (!input && !options.promptFile) {
136
+ input = await readStdin();
137
+ }
138
+
139
+ input = readPromptFile(options.promptFile, input);
140
+
141
+ if (!input) {
142
+ console.error(
143
+ "Error: No question provided. Use --input flag, pipe input via stdin, or provide --prompt-file."
144
+ );
145
+ process.exit(1);
146
+ }
147
+
148
+ const askModule = new AskModule();
149
+ await askModule.initialize(chatService);
150
+ await askModule.processAIQuery(input, {
151
+ plugins: config.plugins.enabled,
152
+ currentModel: options.model,
153
+ currentProvider: options.provider,
154
+ chatHistory: [],
155
+ });
156
+ } catch (error) {
157
+ console.error("Error asking AI:", error);
158
+ process.exit(1);
159
+ }
160
+ });
161
+ }
162
+
163
+ export function addSetupCommand(program: Command, getChatService: () => any): void {
164
+ program
165
+ .command("setup")
166
+ .description("Ask the agent to configure knowhow")
167
+ .action(async () => {
168
+ try {
169
+ const { setupServices } = await import("./services");
170
+ await setupServices();
171
+ const chatService = getChatService();
172
+ const agentModule = new AgentModule();
173
+ await agentModule.initialize(chatService);
174
+ const setupModule = new SetupModule(agentModule);
175
+ await setupModule.initialize(chatService);
176
+ await setupModule.handleSetupCommand([]);
177
+ } catch (error) {
178
+ console.error("Error running agent:", error);
179
+ process.exit(1);
180
+ }
181
+ });
182
+ }
183
+
184
+ export function addSearchCommand(program: Command): void {
185
+ program
186
+ .command("search")
187
+ .description("Search embeddings directly from CLI")
188
+ .option(
189
+ "--input <text>",
190
+ "Search query (fallback to stdin if not provided)"
191
+ )
192
+ .option(
193
+ "-e, --embedding <path>",
194
+ "Specific embedding path (default: all)",
195
+ "all"
196
+ )
197
+ .action(async (options) => {
198
+ try {
199
+ const { setupServices } = await import("./services");
200
+ await setupServices();
201
+ let input = options.input;
202
+ if (!input) {
203
+ input = await readStdin();
204
+ if (!input) {
205
+ console.error(
206
+ "Error: No search query provided. Use --input flag or pipe input via stdin."
207
+ );
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ await new SearchModule().searchEmbeddingsCLI(input, options.embedding);
213
+ } catch (error) {
214
+ console.error("Error searching embeddings:", error);
215
+ process.exit(1);
216
+ }
217
+ });
218
+ }
219
+
220
+ export function addSessionsCommand(program: Command, getChatService: () => any): void {
221
+ program
222
+ .command("sessions")
223
+ .description("Manage agent sessions from CLI")
224
+ .option(
225
+ "--all",
226
+ "Show all historical sessions (default: current process only)"
227
+ )
228
+ .option("--csv", "Output sessions as CSV")
229
+ .action(async (options) => {
230
+ try {
231
+ const chatService = getChatService();
232
+ const agentModule = new AgentModule();
233
+ await agentModule.initialize(chatService);
234
+ const sessionsModule = new SessionsModule(agentModule);
235
+ await sessionsModule.initialize(chatService);
236
+ await sessionsModule.logSessionTable(
237
+ options.all || false,
238
+ options.csv || false,
239
+ true
240
+ );
241
+ } catch (error) {
242
+ console.error("Error listing sessions:", error);
243
+ process.exit(1);
244
+ }
245
+ });
246
+ }
@@ -0,0 +1,169 @@
1
+ import { Command } from "commander";
2
+ import { execSync } from "child_process";
3
+ import { version } from "../../package.json";
4
+ import { generate, embed, upload, download, purge } from "../index";
5
+ import { init } from "../config";
6
+ import { login } from "../login";
7
+ import { KnowhowSimpleClient } from "../services/KnowhowClient";
8
+ import { startChat } from "../chat";
9
+
10
+ export function addInitCommand(program: Command): void {
11
+ program
12
+ .command("init")
13
+ .description("Initialize knowhow configuration")
14
+ .action(async () => {
15
+ await init();
16
+ });
17
+ }
18
+
19
+ export function addLoginCommand(program: Command): void {
20
+ program
21
+ .command("login")
22
+ .description("Login to knowhow")
23
+ .option("--jwt", "Use manual JWT input instead of browser login")
24
+ .action(async (opts) => {
25
+ await login(opts.jwt);
26
+ });
27
+ }
28
+
29
+ export function addUpdateCommand(program: Command): void {
30
+ program
31
+ .command("update")
32
+ .description("Update knowhow to the latest version from npm")
33
+ .action(async () => {
34
+ try {
35
+ console.log("🔄 Checking for knowhow updates...");
36
+ console.log(`Current version: ${version}`);
37
+ console.log("📦 Installing latest version from npm...");
38
+ execSync("npm install -g @tyvm/knowhow@latest", {
39
+ stdio: "inherit",
40
+ encoding: "utf-8",
41
+ });
42
+ console.log("✓ knowhow has been updated successfully!");
43
+ console.log("Run 'knowhow --version' to see the new version.");
44
+ } catch (error) {
45
+ console.error("Error updating knowhow:", error.message);
46
+ process.exit(1);
47
+ }
48
+ });
49
+ }
50
+
51
+ export function addGenerateCommand(program: Command): void {
52
+ program
53
+ .command("generate")
54
+ .description("Generate documentation")
55
+ .action(async () => {
56
+ const { setupServices } = await import("./services");
57
+ await setupServices();
58
+ await generate();
59
+ });
60
+ }
61
+
62
+ export function addEmbedCommands(program: Command): void {
63
+ program
64
+ .command("embed")
65
+ .description("Create embeddings")
66
+ .action(async () => {
67
+ const { setupServices } = await import("./services");
68
+ await setupServices();
69
+ await embed();
70
+ });
71
+
72
+ program
73
+ .command("embed:purge")
74
+ .description("Purge embeddings matching a glob pattern")
75
+ .argument("<pattern>", "Glob pattern to match files for purging")
76
+ .action(async (pattern) => {
77
+ await purge(pattern);
78
+ });
79
+ }
80
+
81
+ export function addUploadCommand(program: Command): void {
82
+ program
83
+ .command("upload")
84
+ .description("Upload data")
85
+ .action(async () => {
86
+ await upload();
87
+ });
88
+ }
89
+
90
+ export function addDownloadCommand(program: Command): void {
91
+ program
92
+ .command("download")
93
+ .description("Download data")
94
+ .action(async () => {
95
+ await download();
96
+ });
97
+ }
98
+
99
+ export function addChatCommand(program: Command): void {
100
+ program
101
+ .command("chat")
102
+ .description("Start new chat interface")
103
+ .action(async () => {
104
+ const { setupServices } = await import("./services");
105
+ await setupServices();
106
+ await startChat();
107
+ });
108
+ }
109
+
110
+ export function addGithubCredentialsCommand(program: Command): void {
111
+ program
112
+ .command("github-credentials [action]")
113
+ .description(
114
+ "Git credential helper for GitHub. Use as: git config credential.helper 'knowhow github-credentials'"
115
+ )
116
+ .option(
117
+ "--repo <repo>",
118
+ "Repository in owner/repo format (e.g. myorg/myrepo)"
119
+ )
120
+ .action(async (action: string | undefined, options: { repo?: string }) => {
121
+ const client = new KnowhowSimpleClient();
122
+
123
+ let repo = options.repo;
124
+
125
+ if (action === "get") {
126
+ const lines: string[] = [];
127
+ const readline = await import("readline");
128
+ const rl = readline.createInterface({
129
+ input: process.stdin,
130
+ terminal: false,
131
+ });
132
+ await new Promise<void>((resolve) => {
133
+ rl.on("line", (line) => {
134
+ if (line.trim()) lines.push(line.trim());
135
+ });
136
+ rl.on("close", resolve);
137
+ });
138
+ } else if (action === "store" || action === "erase") {
139
+ process.exit(0);
140
+ }
141
+
142
+ if (!repo) {
143
+ try {
144
+ const remoteUrl = execSync("git remote get-url origin", {
145
+ encoding: "utf-8",
146
+ stdio: ["pipe", "pipe", "pipe"],
147
+ }).trim();
148
+ const match =
149
+ remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/) ||
150
+ remoteUrl.match(/github\.com\/([^/]+\/[^/]+)/);
151
+ if (match) {
152
+ repo = match[1];
153
+ }
154
+ } catch {
155
+ // Not in a git repo or no remote — proceed without repo
156
+ }
157
+ }
158
+
159
+ try {
160
+ const credential = await client.getGitCredential(repo || "");
161
+ process.stdout.write(
162
+ `protocol=${credential.protocol}\nhost=${credential.host}\nusername=${credential.username}\npassword=${credential.password}\n`
163
+ );
164
+ } catch (error) {
165
+ console.error("Failed to get git credentials:", error.message);
166
+ process.exit(1);
167
+ }
168
+ });
169
+ }