@tyvm/knowhow 0.0.108 → 0.0.109-dev.2b94ba2

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 (236) hide show
  1. package/README.md +45 -0
  2. package/package.json +9 -4
  3. package/scripts/build-for-node.sh +10 -24
  4. package/scripts/publish.sh +86 -0
  5. package/src/agents/base/base.ts +10 -0
  6. package/src/agents/tools/execCommand.ts +49 -6
  7. package/src/agents/tools/index.ts +0 -1
  8. package/src/agents/tools/list.ts +2 -4
  9. package/src/chat/CliChatService.ts +11 -2
  10. package/src/chat/modules/AgentModule.ts +61 -31
  11. package/src/chat/modules/SessionsModule.ts +47 -3
  12. package/src/chat/modules/SystemModule.ts +2 -2
  13. package/src/chat/renderer/CompactRenderer.ts +20 -0
  14. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  15. package/src/chat/renderer/FancyRenderer.ts +19 -0
  16. package/src/chat/renderer/types.ts +11 -0
  17. package/src/cli.ts +91 -659
  18. package/src/clients/anthropic.ts +18 -17
  19. package/src/clients/index.ts +31 -11
  20. package/src/clients/openai.ts +8 -5
  21. package/src/clients/types.ts +48 -10
  22. package/src/clients/withRetry.ts +89 -0
  23. package/src/cloudWorker.ts +175 -113
  24. package/src/commands/agent.ts +246 -0
  25. package/src/commands/misc.ts +174 -0
  26. package/src/commands/modules.ts +552 -0
  27. package/src/commands/services.ts +77 -0
  28. package/src/commands/workers.ts +168 -0
  29. package/src/config.ts +38 -1
  30. package/src/fileSync.ts +70 -29
  31. package/src/hashes.ts +35 -13
  32. package/src/index.ts +18 -0
  33. package/src/logger.ts +197 -0
  34. package/src/plugins/embedding.ts +11 -6
  35. package/src/plugins/plugins.ts +0 -21
  36. package/src/plugins/vim.ts +5 -16
  37. package/src/processors/JsonCompressor.ts +6 -6
  38. package/src/services/EventService.ts +61 -1
  39. package/src/services/KnowhowClient.ts +34 -4
  40. package/src/services/MediaProcessorService.ts +79 -10
  41. package/src/services/modules/index.ts +102 -53
  42. package/src/services/modules/types.ts +6 -0
  43. package/src/tunnel.ts +216 -0
  44. package/src/types.ts +0 -1
  45. package/src/worker.ts +105 -312
  46. package/src/workers/auth/WsMiddleware.ts +99 -0
  47. package/src/workers/auth/authMiddleware.ts +104 -0
  48. package/src/workers/auth/types.ts +14 -2
  49. package/src/workers/tools/index.ts +2 -0
  50. package/src/workers/tools/reloadConfig.ts +84 -0
  51. package/tests/services/WorkerReloadConfig.test.ts +141 -0
  52. package/tests/unit/clients/AIClient.test.ts +446 -0
  53. package/tests/unit/clients/withRetry.test.ts +319 -0
  54. package/tests/unit/commands/github-credentials.test.ts +210 -0
  55. package/tests/unit/modules/moduleLoading.test.ts +39 -37
  56. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  57. package/ts_build/package.json +9 -4
  58. package/ts_build/src/agents/base/base.js +11 -0
  59. package/ts_build/src/agents/base/base.js.map +1 -1
  60. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  61. package/ts_build/src/agents/tools/execCommand.js +39 -5
  62. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  63. package/ts_build/src/agents/tools/index.d.ts +0 -1
  64. package/ts_build/src/agents/tools/index.js +0 -1
  65. package/ts_build/src/agents/tools/index.js.map +1 -1
  66. package/ts_build/src/agents/tools/list.js +2 -4
  67. package/ts_build/src/agents/tools/list.js.map +1 -1
  68. package/ts_build/src/chat/CliChatService.js +14 -2
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
  71. package/ts_build/src/chat/modules/AgentModule.js +43 -20
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/SessionsModule.js +37 -3
  74. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  75. package/ts_build/src/chat/modules/SystemModule.js +2 -2
  76. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  77. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  78. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  79. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  80. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  81. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  82. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  83. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  84. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  85. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  86. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  87. package/ts_build/src/cli.js +47 -519
  88. package/ts_build/src/cli.js.map +1 -1
  89. package/ts_build/src/clients/anthropic.d.ts +5 -5
  90. package/ts_build/src/clients/anthropic.js +18 -17
  91. package/ts_build/src/clients/anthropic.js.map +1 -1
  92. package/ts_build/src/clients/index.js +9 -10
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/openai.js +4 -4
  95. package/ts_build/src/clients/openai.js.map +1 -1
  96. package/ts_build/src/clients/types.d.ts +15 -8
  97. package/ts_build/src/clients/withRetry.d.ts +2 -0
  98. package/ts_build/src/clients/withRetry.js +60 -0
  99. package/ts_build/src/clients/withRetry.js.map +1 -0
  100. package/ts_build/src/cloudWorker.d.ts +14 -0
  101. package/ts_build/src/cloudWorker.js +105 -66
  102. package/ts_build/src/cloudWorker.js.map +1 -1
  103. package/ts_build/src/commands/agent.d.ts +6 -0
  104. package/ts_build/src/commands/agent.js +229 -0
  105. package/ts_build/src/commands/agent.js.map +1 -0
  106. package/ts_build/src/commands/misc.d.ts +10 -0
  107. package/ts_build/src/commands/misc.js +197 -0
  108. package/ts_build/src/commands/misc.js.map +1 -0
  109. package/ts_build/src/commands/modules.d.ts +3 -0
  110. package/ts_build/src/commands/modules.js +487 -0
  111. package/ts_build/src/commands/modules.js.map +1 -0
  112. package/ts_build/src/commands/services.d.ts +5 -0
  113. package/ts_build/src/commands/services.js +87 -0
  114. package/ts_build/src/commands/services.js.map +1 -0
  115. package/ts_build/src/commands/workers.d.ts +6 -0
  116. package/ts_build/src/commands/workers.js +168 -0
  117. package/ts_build/src/commands/workers.js.map +1 -0
  118. package/ts_build/src/config.d.ts +1 -0
  119. package/ts_build/src/config.js +33 -1
  120. package/ts_build/src/config.js.map +1 -1
  121. package/ts_build/src/fileSync.d.ts +6 -0
  122. package/ts_build/src/fileSync.js +50 -23
  123. package/ts_build/src/fileSync.js.map +1 -1
  124. package/ts_build/src/hashes.d.ts +2 -2
  125. package/ts_build/src/hashes.js +35 -9
  126. package/ts_build/src/hashes.js.map +1 -1
  127. package/ts_build/src/index.d.ts +1 -0
  128. package/ts_build/src/index.js +17 -1
  129. package/ts_build/src/index.js.map +1 -1
  130. package/ts_build/src/logger.d.ts +21 -0
  131. package/ts_build/src/logger.js +106 -0
  132. package/ts_build/src/logger.js.map +1 -0
  133. package/ts_build/src/plugins/embedding.js +4 -3
  134. package/ts_build/src/plugins/embedding.js.map +1 -1
  135. package/ts_build/src/plugins/plugins.d.ts +0 -2
  136. package/ts_build/src/plugins/plugins.js +0 -11
  137. package/ts_build/src/plugins/plugins.js.map +1 -1
  138. package/ts_build/src/plugins/vim.js +3 -9
  139. package/ts_build/src/plugins/vim.js.map +1 -1
  140. package/ts_build/src/processors/JsonCompressor.js +4 -4
  141. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  142. package/ts_build/src/services/EventService.d.ts +6 -1
  143. package/ts_build/src/services/EventService.js +29 -0
  144. package/ts_build/src/services/EventService.js.map +1 -1
  145. package/ts_build/src/services/KnowhowClient.d.ts +13 -1
  146. package/ts_build/src/services/KnowhowClient.js +19 -2
  147. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  148. package/ts_build/src/services/MediaProcessorService.d.ts +5 -4
  149. package/ts_build/src/services/MediaProcessorService.js +53 -8
  150. package/ts_build/src/services/MediaProcessorService.js.map +1 -1
  151. package/ts_build/src/services/modules/index.d.ts +33 -0
  152. package/ts_build/src/services/modules/index.js +73 -49
  153. package/ts_build/src/services/modules/index.js.map +1 -1
  154. package/ts_build/src/services/modules/types.d.ts +6 -0
  155. package/ts_build/src/tunnel.d.ts +27 -0
  156. package/ts_build/src/tunnel.js +112 -0
  157. package/ts_build/src/tunnel.js.map +1 -0
  158. package/ts_build/src/types.d.ts +0 -1
  159. package/ts_build/src/types.js.map +1 -1
  160. package/ts_build/src/worker.d.ts +1 -4
  161. package/ts_build/src/worker.js +59 -227
  162. package/ts_build/src/worker.js.map +1 -1
  163. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  164. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  165. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  166. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  167. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  168. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  169. package/ts_build/src/workers/auth/types.d.ts +8 -1
  170. package/ts_build/src/workers/tools/index.d.ts +2 -0
  171. package/ts_build/src/workers/tools/index.js +4 -1
  172. package/ts_build/src/workers/tools/index.js.map +1 -1
  173. package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
  174. package/ts_build/src/workers/tools/reloadConfig.js +48 -0
  175. package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
  176. package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
  177. package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
  178. package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
  179. package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
  180. package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
  181. package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
  182. package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
  183. package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
  184. package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
  185. package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
  186. package/ts_build/tests/unit/commands/github-credentials.test.js +145 -0
  187. package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
  188. package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
  189. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  190. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  191. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  192. package/src/agents/tools/executeScript/README.md +0 -94
  193. package/src/agents/tools/executeScript/definition.ts +0 -79
  194. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  195. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  196. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  197. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  198. package/src/agents/tools/executeScript/index.ts +0 -98
  199. package/src/services/script-execution/SandboxContext.ts +0 -282
  200. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  201. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  202. package/src/services/script-execution/ScriptTracer.ts +0 -249
  203. package/src/services/script-execution/types.ts +0 -134
  204. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  205. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  206. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  207. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  208. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  209. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  210. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  211. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  212. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  213. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  214. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  215. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  216. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  217. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  218. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  219. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  220. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  221. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  222. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  223. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  224. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  225. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  226. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  227. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  228. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  229. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  230. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  231. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  232. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  233. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  234. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  235. package/ts_build/src/services/script-execution/types.js +0 -3
  236. package/ts_build/src/services/script-execution/types.js.map +0 -1
package/src/cli.ts CHANGED
@@ -1,691 +1,123 @@
1
1
  #!/usr/bin/env node --no-node-snapshot
2
- import * as fs from "fs";
3
- import * as fsPromises from "fs/promises";
4
- import * as path from "path";
5
- import * as os from "os";
6
2
  import { Command } from "commander";
7
- import { execSync } from "child_process";
8
3
  import { version } from "../package.json";
9
- import { generate, embed, upload } from "./index";
10
- import { init, migrateConfig } from "./config";
11
-
12
- import { download, purge } from ".";
13
- import { includedTools } from "./agents/tools/list";
14
- import * as allTools from "./agents/tools";
15
- import { LazyToolsService, services } from "./services";
16
- import { login } from "./login";
17
- import { worker, tunnel } from "./worker";
18
- import { fileSync } from "./fileSync";
19
- import { KnowhowSimpleClient } from "./services/KnowhowClient";
20
- import { ModulesService } from "./services/modules";
21
- import {
22
- startAllWorkers,
23
- listWorkerPaths,
24
- unregisterWorkerPath,
25
- clearWorkerRegistry,
26
- } from "./workerRegistry";
27
- import { agents } from "./agents";
28
- import { startChat } from "./chat";
29
- import { getConfiguredEmbeddingMap, queryEmbedding } from "./embeddings";
30
- import { getConfig } from "./config";
4
+ import { logger } from "./logger";
5
+ import { migrateConfig } from "./config";
6
+ import { getConfig, getGlobalConfig } from "./config";
31
7
  import { getEnabledPlugins } from "./types";
32
- import { marked } from "marked";
33
- import { BaseAgent } from "./agents/base/base";
34
- import { AskModule } from "./chat/modules/AskModule";
35
- import { SearchModule } from "./chat/modules/SearchModule";
36
- import { AgentModule } from "./chat/modules/AgentModule";
37
- import { SessionsModule } from "./chat/modules/SessionsModule";
38
- import { readPromptFile } from "./ai";
39
- import { SetupModule } from "./chat/modules/SetupModule";
40
8
  import { CliChatService } from "./chat/CliChatService";
9
+ import { ModulesService } from "./services/modules";
10
+
11
+ // Command registrars
12
+ import { addModulesCommand } from "./commands/modules";
13
+ import {
14
+ addWorkerCommand,
15
+ addWorkersCommand,
16
+ addTunnelCommand,
17
+ addFilesCommand,
18
+ addCloudWorkerCommand,
19
+ } from "./commands/workers";
20
+ import {
21
+ addAgentCommand,
22
+ addAskCommand,
23
+ addSetupCommand,
24
+ addSearchCommand,
25
+ addSessionsCommand,
26
+ } from "./commands/agent";
27
+ import {
28
+ addInitCommand,
29
+ addLoginCommand,
30
+ addUpdateCommand,
31
+ addGenerateCommand,
32
+ addEmbedCommands,
33
+ addUploadCommand,
34
+ addDownloadCommand,
35
+ addChatCommand,
36
+ addGithubCredentialsCommand,
37
+ } from "./commands/misc";
41
38
 
42
39
  // Handle unhandled promise rejections gracefully — particularly from MCP SDK
43
40
  // which fires errors via event emitters that can bypass Promise.allSettled.
44
41
  // Without this, a single failing MCP server (e.g. expired Notion token) will
45
42
  // crash the entire CLI with an unhandled rejection.
46
43
  process.on("unhandledRejection", (reason: unknown) => {
47
- const message =
48
- reason instanceof Error ? reason.message : String(reason);
44
+ const message = reason instanceof Error ? reason.message : String(reason);
49
45
  // Only warn — don't exit. The MCP connect errors are recoverable;
50
46
  // the server will simply be unavailable but others continue working.
51
- console.warn(
52
- `⚠ Unhandled MCP/async error (non-fatal): ${message}`
53
- );
47
+ console.warn(`⚠ Unhandled MCP/async error (non-fatal): ${message}`);
54
48
  });
55
49
 
56
- async function setupServices() {
57
- const { Agents, Mcp, Clients, Tools: OldTools } = services();
58
- const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
59
-
60
- // Load modules from config first so module-provided tools/agents/plugins are available
61
- // We need to wireup the LazyTools to be connected to the same singletons that are in services()
62
- Tools.setContext({
63
- ...OldTools.getContext(),
64
- });
65
-
66
- // Build the AgentContext with the fully-populated LazyToolsService so every
67
- // agent created (including those in setupAgent) gets all tools registered.
68
- const agentContext: import("./agents/base/base").AgentContext = {
69
- ...services(),
70
- Tools,
71
- };
72
-
73
- const { Researcher, Developer, Patcher, Setup } = agents({
74
- ...agentContext,
75
- });
76
-
77
- Agents.registerAgent(Researcher);
78
- Agents.registerAgent(Patcher);
79
- Agents.registerAgent(Developer);
80
- Agents.registerAgent(Setup);
81
- Agents.loadAgentsFromConfig(agentContext);
82
-
83
- Tools.defineTools(includedTools, allTools);
84
-
85
- // Add Mcp service to tool context directly so MCP management tools can access it
86
- Tools.addContext("Mcp", Mcp);
50
+ async function main() {
51
+ const program = new Command();
87
52
 
88
- // Store the fully-wired AgentContext on AgentService so AgentModule.setupAgent
89
- // can retrieve it when creating fresh agent instances via createAgent().
90
- Agents.setAgentContext(agentContext);
53
+ // Install console overload early so ALL output (including third-party modules)
54
+ // goes through our logger closure respects silence() for clean-stdout commands.
55
+ logger.installConsoleOverload();
91
56
 
92
- console.log("🔌 Connecting to MCP...");
93
- try {
94
- await Mcp.connectToConfigured(Tools);
95
- } catch (mcpError) {
96
- const msg = mcpError instanceof Error ? mcpError.message : String(mcpError);
97
- console.warn(
98
- `⚠ Some MCP servers failed to connect (continuing without them): ${msg}`
99
- );
57
+ // Silence immediately if this is a clean-stdout command (e.g. git credential helpers).
58
+ // Module loading happens before parseAsync, so we must silence before that point.
59
+ const rawArgs = process.argv.slice(2);
60
+ const SILENT_COMMANDS = ["github-credentials"];
61
+ if (rawArgs.some((a) => SILENT_COMMANDS.includes(a))) {
62
+ logger.silence();
100
63
  }
101
- console.log("Connecting to clients...");
102
- await Clients.registerConfiguredModels();
103
- console.log("✓ Services are set up and ready to go!");
104
64
 
105
- // Load modules (tools, plugins, agents) from knowhow.json config
106
- console.log("📦 Loading modules from config...");
107
- const modulesService = new ModulesService();
108
- await modulesService.loadModulesFromConfig({
109
- Agents,
110
- Embeddings: services().Embeddings,
111
- Plugins: services().Plugins,
112
- Clients,
113
- // Use LazyToolsService so module-provided tools are visible to agents and scripts
114
- Tools: Tools as any,
115
- MediaProcessor: services().MediaProcessor,
116
- });
117
-
118
- // Return both LazyToolsService (for agents) and OldTools (plain ToolsService with all tools for scripts)
119
- return { Tools, Clients, PlainTools: OldTools };
120
- }
121
-
122
- // Utility function to read from stdin
123
- async function readStdin(): Promise<string> {
124
- return new Promise((resolve) => {
125
- let data = "";
126
- process.stdin.setEncoding("utf8");
127
-
128
- if (process.stdin.isTTY) {
129
- resolve("");
130
- return;
131
- }
132
-
133
- process.stdin.on("readable", () => {
134
- const chunk = process.stdin.read();
135
- if (chunk !== null) data += chunk;
136
- });
137
-
138
- process.stdin.on("end", () => resolve(data.trim()));
139
- });
140
- }
141
-
142
- async function main() {
143
- const program = new Command();
144
65
  await migrateConfig();
145
66
  const config = await getConfig();
146
67
  const chatService = new CliChatService(getEnabledPlugins(config.plugins));
147
68
 
69
+ // Lazily expose chatService and config to commands that need them
70
+ const getChatService = () => chatService;
71
+ const getConfigFn = () => config;
72
+
148
73
  program
149
74
  .name("knowhow")
150
75
  .description("AI CLI with plugins and agents")
151
76
  .version(version);
152
77
 
153
- program
154
- .command("init")
155
- .description("Initialize knowhow configuration")
156
- .action(async () => {
157
- await init();
158
- });
159
-
160
- program
161
- .command("login")
162
- .description("Login to knowhow")
163
- .option("--jwt", "Use manual JWT input instead of browser login")
164
- .action(async (opts) => {
165
- await login(opts.jwt);
166
- });
167
-
168
- program
169
- .command("update")
170
- .description("Update knowhow to the latest version from npm")
171
- .action(async () => {
172
- try {
173
- console.log("🔄 Checking for knowhow updates...");
174
- console.log(`Current version: ${version}`);
175
-
176
- console.log("📦 Installing latest version from npm...");
177
- execSync("npm install -g @tyvm/knowhow@latest", {
178
- stdio: "inherit",
179
- encoding: "utf-8",
180
- });
181
-
182
- console.log("✓ knowhow has been updated successfully!");
183
- console.log("Run 'knowhow --version' to see the new version.");
184
- } catch (error) {
185
- console.error("Error updating knowhow:", error.message);
186
- process.exit(1);
187
- }
188
- });
189
-
190
- program
191
- .command("generate")
192
- .description("Generate documentation")
193
- .action(async () => {
194
- await setupServices();
195
- await generate();
196
- });
197
-
198
- program
199
- .command("embed")
200
- .description("Create embeddings")
201
- .action(async () => {
202
- await setupServices();
203
- await embed();
204
- });
205
-
206
- program
207
- .command("embed:purge")
208
- .description("Purge embeddings matching a glob pattern")
209
- .argument("<pattern>", "Glob pattern to match files for purging")
210
- .action(async (pattern) => {
211
- await purge(pattern);
212
- });
213
-
214
- program
215
- .command("upload")
216
- .description("Upload data")
217
- .action(async () => {
218
- await upload();
219
- });
220
-
221
- program
222
- .command("download")
223
- .description("Download data")
224
- .action(async () => {
225
- await download();
226
- });
227
-
228
- program
229
- .command("chat")
230
- .description("Start new chat interface")
231
- .action(async () => {
232
- await setupServices();
233
- await startChat();
234
- });
235
-
236
- program
237
- .command("agent")
238
- .description("Spin up agents directly from CLI")
239
- .option(
240
- "--provider <provider>",
241
- "AI provider (openai, anthropic, google, xai)"
242
- )
243
- .option("--model <model>", "Specific model for the provider")
244
- .option("--agent-name <name>", "Which agent to use", "Patcher")
245
- .option(
246
- "--max-time-limit <minutes>",
247
- "Time limit for agent execution (minutes)",
248
- "30"
249
- )
250
- .option(
251
- "--max-spend-limit <dollars>",
252
- "Cost limit for agent execution (dollars)",
253
- "10"
254
- )
255
- .option("--message-id <messageId>", "Knowhow message ID for task tracking")
256
- .option("--sync-fs", "Enable filesystem-based synchronization")
257
- .option(
258
- "--task-id <taskId>",
259
- "Pre-generated task ID (used with --sync-fs for predictable agent directory path)"
260
- )
261
- .option("--prompt-file <path>", "Custom prompt template file with {text}")
262
- .option("--input <text>", "Task input (fallback to stdin if not provided)")
263
- .option(
264
- "--resume",
265
- "Resume a previously started task using the --task-id (local FS or remote)"
266
- )
267
- .action(async (options) => {
268
- try {
269
- await setupServices();
270
- const agentModule = new AgentModule();
271
-
272
- // Handle --resume flag: load threads from local FS or remote using --task-id
273
- if (options.resume) {
274
- const threads = await agentModule.loadThreadsForTask(
275
- options.taskId,
276
- options.messageId
277
- );
278
- const resumeInput =
279
- options.input || "Please continue from where you left off.";
280
-
281
- await agentModule.initialize(chatService);
282
- const { taskCompleted: resumed } =
283
- await agentModule.resumeFromMessages({
284
- agentName: options.agentName || "Patcher",
285
- input: resumeInput,
286
- threads,
287
- messageId: options.messageId,
288
- taskId: options.taskId,
289
- });
290
- await resumed;
291
- return;
292
- }
293
-
294
- let input = options.input;
295
-
296
- // Only read from stdin if we don't have input and don't have a standalone prompt file
297
- if (!input && !options.promptFile) {
298
- input = await readStdin();
299
- }
300
-
301
- // Read prompt file - it will handle cases where input is empty
302
- input = readPromptFile(options.promptFile, input);
303
-
304
- // Only error if we have no prompt file and no input
305
- if (!input) {
306
- console.error(
307
- "Error: No input provided. Use --input flag, pipe input via stdin, or provide --prompt-file."
308
- );
309
- process.exit(1);
310
- }
311
-
312
- await agentModule.initialize(chatService);
313
- const { taskCompleted } = await agentModule.setupAgent({
314
- ...options,
315
- input,
316
- maxTimeLimit: parseInt(options.maxTimeLimit, 10),
317
- maxSpendLimit: parseFloat(options.maxSpendLimit),
318
- run: true,
319
- });
320
- await taskCompleted;
321
- } catch (error) {
322
- console.error("Error running agent:", error);
323
- process.exit(1);
324
- }
325
- });
326
-
327
- program
328
- .command("ask")
329
- .description("Direct AI questioning without agent overhead")
330
- .option("--provider <provider>", "AI provider to use")
331
- .option("--model <model>", "Specific model")
332
- .option("--input <text>", "Question (fallback to stdin if not provided)")
333
- .option("--prompt-file <path>", "Custom prompt template file")
334
- .action(async (options) => {
335
- try {
336
- await setupServices();
337
- let input = options.input;
338
-
339
- // Only read from stdin if we don't have input and don't have a standalone prompt file
340
- if (!input && !options.promptFile) {
341
- input = await readStdin();
342
- }
343
-
344
- // Read prompt file - it will handle cases where input is empty
345
- input = readPromptFile(options.promptFile, input);
346
-
347
- // Only error if we have no prompt file and no input
348
- if (!input) {
349
- console.error(
350
- "Error: No question provided. Use --input flag, pipe input via stdin, or provide --prompt-file."
351
- );
352
- process.exit(1);
353
- }
354
-
355
- const askModule = new AskModule();
356
- await askModule.initialize(chatService);
357
- await askModule.processAIQuery(input, {
358
- plugins: config.plugins.enabled,
359
- currentModel: options.model,
360
- currentProvider: options.provider,
361
- chatHistory: [],
362
- });
363
- } catch (error) {
364
- console.error("Error asking AI:", error);
365
- process.exit(1);
366
- }
367
- });
368
-
369
- program
370
- .command("setup")
371
- .description("Ask the agent to configure knowhow")
372
- .action(async (options) => {
373
- try {
374
- await setupServices();
375
- const agentModule = new AgentModule();
376
- await agentModule.initialize(chatService);
377
- const setupModule = new SetupModule(agentModule);
378
- await setupModule.initialize(chatService);
379
- await setupModule.handleSetupCommand([]);
380
- } catch (error) {
381
- console.error("Error running agent:", error);
382
- process.exit(1);
383
- }
384
- });
385
-
386
- program
387
- .command("search")
388
- .description("Search embeddings directly from CLI")
389
- .option(
390
- "--input <text>",
391
- "Search query (fallback to stdin if not provided)"
392
- )
393
- .option(
394
- "-e, --embedding <path>",
395
- "Specific embedding path (default: all)",
396
- "all"
397
- )
398
- .action(async (options) => {
399
- try {
400
- await setupServices();
401
- let input = options.input;
402
- if (!input) {
403
- input = await readStdin();
404
- if (!input) {
405
- console.error(
406
- "Error: No search query provided. Use --input flag or pipe input via stdin."
407
- );
408
- process.exit(1);
409
- }
410
- }
411
-
412
- await new SearchModule().searchEmbeddingsCLI(input, options.embedding);
413
- } catch (error) {
414
- console.error("Error searching embeddings:", error);
415
- process.exit(1);
416
- }
417
- });
418
-
419
- program
420
- .command("sessions")
421
- .description("Manage agent sessions from CLI")
422
- .option(
423
- "--all",
424
- "Show all historical sessions (default: current process only)"
425
- )
426
- .option("--csv", "Output sessions as CSV")
427
- .action(async (options) => {
428
- try {
429
- const agentModule = new AgentModule();
430
- await agentModule.initialize(chatService);
431
- const sessionsModule = new SessionsModule(agentModule);
432
- await sessionsModule.initialize(chatService);
433
- await sessionsModule.logSessionTable(
434
- options.all || false,
435
- options.csv || false,
436
- true
437
- );
438
- } catch (error) {
439
- console.error("Error listing sessions:", error);
440
- process.exit(1);
441
- }
442
- });
443
-
444
- program
445
- .command("worker")
446
- .description(
447
- "Start worker process and optionally register current directory"
448
- )
449
- .option("--register", "Register current directory as a worker path")
450
- .option(
451
- "--share",
452
- "Share this worker with your organization (allows other users to use it)"
453
- )
454
- .option("--unshare", "Make this worker private (only you can use it)")
455
- .option("--sandbox", "Run worker in a Docker container for isolation")
456
- .option(
457
- "--no-sandbox",
458
- "Run worker directly on host (disable sandbox mode)"
459
- )
460
- .option("--passkey", "Set up passkey authentication for this worker")
461
- .option("--passkey-reset", "Remove passkey authentication requirement")
462
- .action(async (options) => {
463
- await setupServices();
464
- await worker(options);
465
- });
466
-
467
- program
468
- .command("files")
469
- .description(
470
- "Sync files between local filesystem and Knowhow FS (uses fileMounts config)"
471
- )
472
- .option("--upload", "Force upload direction for all mounts")
473
- .option("--download", "Force download direction for all mounts")
474
- .option("--config <path>", "Path to knowhow.json", "./knowhow.json")
475
- .option("--dry-run", "Print what would be synced without doing it")
476
- .action(async (options) => {
477
- try {
478
- await fileSync(options);
479
- } catch (error) {
480
- console.error("Error syncing files:", error);
481
- process.exit(1);
482
- }
483
- });
484
-
485
- program
486
- .command("workers")
487
- .description("Manage and start all registered workers")
488
- .option("--list", "List all registered worker paths")
489
- .option("--unregister <path>", "Unregister a worker path")
490
- .option("--clear", "Clear all registered worker paths")
491
- .action(async (options) => {
492
- try {
493
- if (options.list) {
494
- const workers = await listWorkerPaths();
495
- if (workers.length === 0) {
496
- console.log("No workers registered.");
497
- console.log(
498
- "\nTo register a worker, run 'knowhow worker --register' from the worker directory."
499
- );
500
- } else {
501
- console.log(`Registered workers (${workers.length}):`);
502
- workers.forEach((workerPath, index) => {
503
- console.log(` ${index + 1}. ${workerPath}`);
504
- });
505
- }
506
- return;
507
- }
508
-
509
- if (options.unregister) {
510
- await unregisterWorkerPath(options.unregister);
511
- return;
512
- }
513
-
514
- if (options.clear) {
515
- await clearWorkerRegistry();
516
- return;
517
- }
518
-
519
- // Default action: start all workers
520
- await setupServices();
521
- await startAllWorkers();
522
- } catch (error) {
523
- console.error("Error managing workers:", error);
524
- process.exit(1);
525
- }
526
- });
527
-
528
- program
529
- .command("cloudworker")
530
- .description("Create or sync a cloud worker with your local knowhow config")
531
- .option("--create", "Create a new cloud worker with synced config and files")
532
- .option("--push <uid>", "Push/sync local config and files to an existing cloud worker")
533
- .option("--name <name>", "Name for the cloud worker (used with --create)")
534
- .option("--dry-run", "Print what would be synced without doing it")
535
- .action(async (options) => {
536
- try {
537
- const { cloudWorker } = await import("./cloudWorker");
538
- await cloudWorker(options);
539
- } catch (error) {
540
- console.error("Error running cloudworker:", error);
541
- process.exit(1);
542
- }
543
- });
544
-
545
- program
546
- .command("tunnel")
547
- .description(
548
- "Start tunnel-only mode: expose local ports to the cloud without registering any tools"
549
- )
550
- .option(
551
- "--share",
552
- "Share this tunnel with your organization (allows other users to use it)"
553
- )
554
- .option("--unshare", "Make this tunnel private (only you can use it)")
555
- .action(async (options) => {
556
- await tunnel(options);
557
- });
558
-
559
-
560
- program
561
- .command("script")
562
- .description("Run a local tool script file using the executeScript sandbox")
563
- .option("--input-file <path>", "Path to a local .js/.ts script file to run")
564
- .option(
565
- "--allow-network",
566
- "Allow fetch() calls in the script (disabled by default for security)"
567
- )
568
- .action(async (options) => {
569
- try {
570
- if (!options.inputFile) {
571
- console.error(
572
- "Error: Provide --input-file <path> to the script file to run"
573
- );
574
- process.exit(1);
575
- }
576
-
577
- // Run a local script file
578
- const scriptPath = path.resolve(options.inputFile);
579
- if (!fs.existsSync(scriptPath)) {
580
- console.error(`Error: Script file not found: ${scriptPath}`);
581
- process.exit(1);
582
- }
583
- const scriptContent = fs.readFileSync(scriptPath, "utf-8");
584
-
585
- const { Tools, Clients } = await setupServices();
586
-
587
- // Enable all tools on the LazyToolsService so scripts can access MCP tools
588
- // (LazyToolsService starts with only meta-tools enabled; we need all for scripts)
589
- Tools.enableTools(["*"]);
590
-
591
- const { ScriptExecutor } = await import(
592
- "./services/script-execution/ScriptExecutor"
593
- );
594
- const executor = new ScriptExecutor(Tools, Clients);
595
- const result = await executor.execute({
596
- script: scriptContent,
597
- policy: {
598
- allowNetworkAccess: !!options.allowNetwork,
599
- },
600
- quotas: {
601
- maxExecutionTimeMs: 5 * 60 * 1000, // 5 minutes for CLI scripts
602
- },
603
- });
604
-
605
- if (result.consoleOutput?.length) {
606
- console.log(result.consoleOutput.join("\n"));
607
- }
608
- console.log(JSON.stringify(result.result, null, 2));
609
- if (!result.success) {
610
- console.error("Script error:", result.error);
611
- process.exit(1);
612
- }
613
- } catch (error) {
614
- console.error("Error running script:", error);
615
- process.exit(1);
616
- }
617
- });
618
-
619
- program
620
- .command("github-credentials [action]")
621
- .description(
622
- "Git credential helper for GitHub. Use as: git config credential.helper 'knowhow github-credentials'"
623
- )
624
- .option(
625
- "--repo <repo>",
626
- "Repository in owner/repo format (e.g. myorg/myrepo)"
627
- )
628
- .action(async (action: string | undefined, options: { repo?: string }) => {
629
- const client = new KnowhowSimpleClient();
630
-
631
- // Determine what repo to fetch credentials for
632
- let repo = options.repo;
633
-
634
- // If action is "get", we're being called as a git credential helper
635
- // git sends lines like: protocol=https\nhost=github.com\n on stdin
636
- if (action === "get") {
637
- // Read from stdin (git sends protocol/host/username)
638
- const lines: string[] = [];
639
- const readline = await import("readline");
640
- const rl = readline.createInterface({
641
- input: process.stdin,
642
- terminal: false,
643
- });
644
- await new Promise<void>((resolve) => {
645
- rl.on("line", (line) => {
646
- if (line.trim()) lines.push(line.trim());
647
- });
648
- rl.on("close", resolve);
649
- });
650
- // We always return GitHub credentials regardless of the parsed host
651
- // repo will be inferred from git remote if not provided
652
- } else if (action === "store" || action === "erase") {
653
- // git credential helper store/erase — nothing to do, just exit cleanly
654
- process.exit(0);
655
- }
656
-
657
- // If no repo provided, try to infer from git remote
658
- if (!repo) {
659
- try {
660
- const remoteUrl = execSync("git remote get-url origin", {
661
- encoding: "utf-8",
662
- stdio: ["pipe", "pipe", "pipe"],
663
- }).trim();
664
- // Parse owner/repo from URL formats:
665
- // https://github.com/owner/repo.git
666
- // git@github.com:owner/repo.git
667
- const match =
668
- remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/) ||
669
- remoteUrl.match(/github\.com\/([^/]+\/[^/]+)/);
670
- if (match) {
671
- repo = match[1];
672
- }
673
- } catch {
674
- // Not in a git repo or no remote — proceed without repo
78
+ // Register all commands
79
+ addInitCommand(program);
80
+ addLoginCommand(program);
81
+ addUpdateCommand(program);
82
+ addGenerateCommand(program);
83
+ addEmbedCommands(program);
84
+ addUploadCommand(program);
85
+ addDownloadCommand(program);
86
+ addChatCommand(program);
87
+ addAgentCommand(program, getChatService);
88
+ addAskCommand(program, getChatService, getConfigFn);
89
+ addSetupCommand(program, getChatService);
90
+ addSearchCommand(program);
91
+ addSessionsCommand(program, getChatService);
92
+ addWorkerCommand(program);
93
+ addWorkersCommand(program);
94
+ addTunnelCommand(program);
95
+ addFilesCommand(program);
96
+ addCloudWorkerCommand(program);
97
+ addGithubCredentialsCommand(program);
98
+ addModulesCommand(program);
99
+
100
+ // Load global modules early (before parse) so they can register CLI subcommands.
101
+ // We pass only the Program in context — no services are spun up at this stage.
102
+ // Each module's command action is responsible for calling setupServices() as needed.
103
+ try {
104
+ const globalConfig = await getGlobalConfig();
105
+ const allModulePaths = [
106
+ ...(globalConfig.modules || []),
107
+ ...(config.modules || []),
108
+ ];
109
+ if (allModulePaths.length) {
110
+ const earlyModulesService = new ModulesService();
111
+ await earlyModulesService.loadModulesFrom(
112
+ { ...config, modules: allModulePaths },
113
+ {
114
+ Program: program,
675
115
  }
676
- }
677
-
678
- try {
679
- const credential = await client.getGitCredential(repo || "");
680
- // Output in git credential helper format
681
- process.stdout.write(
682
- `protocol=${credential.protocol}\nhost=${credential.host}\nusername=${credential.username}\npassword=${credential.password}\n`
683
- );
684
- } catch (error) {
685
- console.error("Failed to get git credentials:", error.message);
686
- process.exit(1);
687
- }
688
- });
116
+ );
117
+ }
118
+ } catch (e) {
119
+ // Non-fatal: if global modules fail to load for CLI registration, continue
120
+ }
689
121
 
690
122
  await program.parseAsync(process.argv);
691
123
  }