@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.
- package/package.json +1 -2
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +0 -2
- package/src/chat/CliChatService.ts +3 -0
- package/src/cli.ts +89 -686
- package/src/clients/index.ts +6 -5
- package/src/commands/agent.ts +246 -0
- package/src/commands/misc.ts +174 -0
- package/src/commands/modules.ts +182 -0
- package/src/commands/services.ts +77 -0
- package/src/commands/workers.ts +160 -0
- package/src/config.ts +37 -0
- package/src/index.ts +1 -0
- package/src/logger.ts +200 -0
- package/src/plugins/plugins.ts +0 -21
- package/src/processors/JsonCompressor.ts +3 -3
- package/src/services/EventService.ts +57 -1
- package/src/services/modules/index.ts +54 -40
- package/src/services/modules/types.ts +4 -0
- package/src/types.ts +0 -1
- package/tests/unit/commands/github-credentials.test.ts +211 -0
- package/tests/unit/modules/moduleLoading.test.ts +39 -37
- package/tests/unit/plugins/pluginLoading.test.ts +0 -85
- package/ts_build/package.json +1 -2
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -2
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +3 -0
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/cli.js +47 -533
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/index.js +2 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/commands/agent.d.ts +6 -0
- package/ts_build/src/commands/agent.js +229 -0
- package/ts_build/src/commands/agent.js.map +1 -0
- package/ts_build/src/commands/misc.d.ts +10 -0
- package/ts_build/src/commands/misc.js +197 -0
- package/ts_build/src/commands/misc.js.map +1 -0
- package/ts_build/src/commands/modules.d.ts +3 -0
- package/ts_build/src/commands/modules.js +160 -0
- package/ts_build/src/commands/modules.js.map +1 -0
- package/ts_build/src/commands/services.d.ts +5 -0
- package/ts_build/src/commands/services.js +87 -0
- package/ts_build/src/commands/services.js.map +1 -0
- package/ts_build/src/commands/workers.d.ts +6 -0
- package/ts_build/src/commands/workers.js +163 -0
- package/ts_build/src/commands/workers.js.map +1 -0
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +32 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +3 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/logger.d.ts +21 -0
- package/ts_build/src/logger.js +109 -0
- package/ts_build/src/logger.js.map +1 -0
- package/ts_build/src/plugins/plugins.d.ts +0 -2
- package/ts_build/src/plugins/plugins.js +0 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/processors/JsonCompressor.js +1 -1
- package/ts_build/src/services/EventService.d.ts +6 -1
- package/ts_build/src/services/EventService.js +28 -0
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +7 -4
- package/ts_build/src/services/modules/index.js +36 -31
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +4 -0
- package/ts_build/src/types.d.ts +0 -1
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js +146 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/src/agents/tools/executeScript/README.md +0 -94
- package/src/agents/tools/executeScript/definition.ts +0 -79
- package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
- package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
- package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
- package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
- package/src/agents/tools/executeScript/index.ts +0 -98
- package/src/services/script-execution/SandboxContext.ts +0 -282
- package/src/services/script-execution/ScriptExecutor.ts +0 -441
- package/src/services/script-execution/ScriptPolicy.ts +0 -194
- package/src/services/script-execution/ScriptTracer.ts +0 -249
- package/src/services/script-execution/types.ts +0 -134
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
- package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
- package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
- package/ts_build/src/agents/tools/executeScript/index.js +0 -72
- package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
- package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
- package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
- package/ts_build/src/services/script-execution/types.d.ts +0 -108
- package/ts_build/src/services/script-execution/types.js +0 -3
- 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 {
|
|
10
|
-
import {
|
|
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
|
|
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
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
program
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
program
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
}
|