@tyvm/knowhow 0.0.108-dev.c47492f → 0.0.108-dev.d003f8f

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