@xalia/agent 1.0.19

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 (64) hide show
  1. package/.prettierrc.json +11 -0
  2. package/README.md +57 -0
  3. package/dist/agent.js +278 -0
  4. package/dist/agentUtils.js +88 -0
  5. package/dist/chat.js +278 -0
  6. package/dist/dummyLLM.js +28 -0
  7. package/dist/files.js +115 -0
  8. package/dist/iplatform.js +2 -0
  9. package/dist/llm.js +2 -0
  10. package/dist/main.js +136 -0
  11. package/dist/mcpServerManager.js +269 -0
  12. package/dist/nodePlatform.js +61 -0
  13. package/dist/openAILLM.js +31 -0
  14. package/dist/options.js +79 -0
  15. package/dist/prompt.js +83 -0
  16. package/dist/sudoMcpServerManager.js +174 -0
  17. package/dist/test/imageLoad.test.js +14 -0
  18. package/dist/test/mcpServerManager.test.js +71 -0
  19. package/dist/test/prompt.test.js +26 -0
  20. package/dist/test/sudoMcpServerManager.test.js +49 -0
  21. package/dist/tokenAuth.js +39 -0
  22. package/dist/tools.js +44 -0
  23. package/eslint.config.mjs +25 -0
  24. package/frog.png +0 -0
  25. package/package.json +41 -0
  26. package/scripts/git_message +31 -0
  27. package/scripts/git_wip +21 -0
  28. package/scripts/pr_message +18 -0
  29. package/scripts/pr_review +16 -0
  30. package/scripts/sudomcp_import +23 -0
  31. package/scripts/test_script +60 -0
  32. package/src/agent.ts +357 -0
  33. package/src/agentUtils.ts +188 -0
  34. package/src/chat.ts +325 -0
  35. package/src/dummyLLM.ts +36 -0
  36. package/src/files.ts +95 -0
  37. package/src/iplatform.ts +11 -0
  38. package/src/llm.ts +12 -0
  39. package/src/main.ts +171 -0
  40. package/src/mcpServerManager.ts +365 -0
  41. package/src/nodePlatform.ts +24 -0
  42. package/src/openAILLM.ts +43 -0
  43. package/src/options.ts +103 -0
  44. package/src/prompt.ts +93 -0
  45. package/src/sudoMcpServerManager.ts +268 -0
  46. package/src/test/imageLoad.test.ts +14 -0
  47. package/src/test/mcpServerManager.test.ts +98 -0
  48. package/src/test/prompt.test.src +0 -0
  49. package/src/test/prompt.test.ts +26 -0
  50. package/src/test/sudoMcpServerManager.test.ts +63 -0
  51. package/src/tokenAuth.ts +50 -0
  52. package/src/tools.ts +57 -0
  53. package/test_data/background_test_profile.json +7 -0
  54. package/test_data/background_test_script.json +11 -0
  55. package/test_data/dummyllm_script_simplecalc.json +28 -0
  56. package/test_data/git_message_profile.json +4 -0
  57. package/test_data/git_wip_system.txt +5 -0
  58. package/test_data/pr_message_profile.json +4 -0
  59. package/test_data/pr_review_profile.json +4 -0
  60. package/test_data/prompt_simplecalc.txt +1 -0
  61. package/test_data/simplecalc_profile.json +4 -0
  62. package/test_data/sudomcp_import_profile.json +4 -0
  63. package/test_data/test_script_profile.json +9 -0
  64. package/tsconfig.json +13 -0
package/src/chat.ts ADDED
@@ -0,0 +1,325 @@
1
+ import yocto from "yocto-spinner";
2
+ import { Spinner } from "yocto-spinner";
3
+ import * as fs from "fs";
4
+ import { Agent, AgentProfile } from "./agent";
5
+ import { displayToolCall } from "./tools";
6
+ import { McpServerManager } from "./mcpServerManager";
7
+ import OpenAI from "openai";
8
+ import { loadImageB64OrUndefined } from "./files";
9
+ import { Prompt, parsePrompt } from "./prompt";
10
+ import { SudoMcpServerManager } from "./sudoMcpServerManager";
11
+ import chalk from "chalk";
12
+ import { configuration, authenticate } from "@xalia/xmcp/tool";
13
+ import { getLogger, InvalidConfiguration } from "@xalia/xmcp/sdk";
14
+ import { createAgentAndSudoMcpServerManager } from "./agentUtils";
15
+ import { NODE_PLATFORM } from "./nodePlatform";
16
+
17
+ const logger = getLogger();
18
+
19
+ export async function runChat(
20
+ agentProfile: AgentProfile,
21
+ conversation: OpenAI.ChatCompletionMessageParam[] | undefined,
22
+ prompt: string | undefined,
23
+ image: string | undefined,
24
+ openaiApiKey: string | undefined,
25
+ sudomcpConfig: configuration.Configuration,
26
+ approveToolsUpTo: number
27
+ ) {
28
+ // In chat mode, just print the messages. Ask for tool confirmation
29
+ // unless approveTools is set.
30
+
31
+ const spinner: Spinner = yocto();
32
+ const onMessage = async (
33
+ msg: OpenAI.ChatCompletionMessageParam,
34
+ _msgEnd: boolean
35
+ ) => {
36
+ if (msg.content) {
37
+ if (spinner) {
38
+ spinner.stop().clear();
39
+ }
40
+ console.log(`AGENT: ${msg.content}`);
41
+ }
42
+ };
43
+
44
+ let remainingApprovedToolCalls = approveToolsUpTo;
45
+ const onToolCall = async (toolCall: OpenAI.ChatCompletionMessageToolCall) => {
46
+ if (remainingApprovedToolCalls !== 0) {
47
+ --remainingApprovedToolCalls;
48
+ return true;
49
+ }
50
+
51
+ spinner.stop().clear();
52
+ const result = await promptToolCall(repl, toolCall);
53
+ spinner.start();
54
+ return result;
55
+ };
56
+
57
+ // Create agent
58
+
59
+ const [agent, sudoMcpServerManager] =
60
+ await createAgentAndSudoMcpServerManager(
61
+ agentProfile,
62
+ onMessage,
63
+ onToolCall,
64
+ NODE_PLATFORM,
65
+ openaiApiKey,
66
+ sudomcpConfig,
67
+ authenticate.FRONTEND_PROD_AUTHORIZED_URL,
68
+ undefined
69
+ );
70
+ if (conversation) {
71
+ agent.setConversation(conversation);
72
+ }
73
+
74
+ // Opening banner
75
+ console.log(`${chalk.green("SudoMCP Agent CLI")}`);
76
+ console.log(
77
+ `(Type ${chalk.yellow("/h")} for help, ${chalk.yellow("/q")} to quit.)`
78
+ );
79
+
80
+ // Display first prompt if supplied
81
+ if (prompt) {
82
+ console.log(`USER: ${prompt}`);
83
+ }
84
+
85
+ const repl = new Prompt();
86
+
87
+ // Conversation loop
88
+ while (true) {
89
+ if (!prompt) {
90
+ const [msg, img] = await getNextPrompt(repl, agent, sudoMcpServerManager);
91
+ if (!msg && !img) {
92
+ break;
93
+ }
94
+ prompt = msg;
95
+ image = img;
96
+ }
97
+
98
+ image = loadImageB64OrUndefined(image);
99
+
100
+ // Pass the prompt and image to the Agent
101
+ try {
102
+ spinner.start();
103
+ await agent.userMessage(prompt, image);
104
+ } catch (e) {
105
+ console.log(`ERROR: ${e}`);
106
+ }
107
+ prompt = undefined;
108
+ image = undefined;
109
+ }
110
+
111
+ // Shutdown the agent
112
+
113
+ repl.shutdown();
114
+ await agent.shutdown();
115
+
116
+ logger.debug("shutdown done");
117
+ }
118
+
119
+ /**
120
+ * Read lines from the prompt, parsing any commands, and return once there is
121
+ * a prompt and/or image for the llm. Both undefined means exit.
122
+ */
123
+ async function getNextPrompt(
124
+ repl: Prompt,
125
+ agent: Agent,
126
+ sudoMcpServerManager: SudoMcpServerManager
127
+ ): Promise<[string | undefined, string | undefined]> {
128
+ while (true) {
129
+ // Get a line, detecting the EOF signal.
130
+ const line = await repl.run();
131
+ if (typeof line === "undefined") {
132
+ console.log("closing ...");
133
+ return [undefined, undefined];
134
+ }
135
+ if (line.length === 0) {
136
+ continue;
137
+ }
138
+
139
+ // Extract prompt or commands
140
+ const { msg, cmds } = parsePrompt(line);
141
+
142
+ // If there are no commands, this must be a prompt only
143
+ if (!cmds) {
144
+ return [msg, undefined];
145
+ }
146
+
147
+ // There are commands. If it's image, return [prompt, image]. If it's
148
+ // quit, return [undefined, undefined], otherwise it must be a command.
149
+ // Execute it and prompt again.
150
+ switch (cmds[0]) {
151
+ case "i": // image
152
+ return [msg, cmds[1]];
153
+ case "q":
154
+ case "quit":
155
+ case "exit":
156
+ return [undefined, undefined];
157
+ default:
158
+ break;
159
+ }
160
+
161
+ try {
162
+ await runCommand(
163
+ agent,
164
+ agent.getMcpServerManager(),
165
+ sudoMcpServerManager,
166
+ cmds
167
+ );
168
+ } catch (e) {
169
+ console.log(`ERROR: ${e}`);
170
+ }
171
+ }
172
+ }
173
+
174
+ async function runCommand(
175
+ agent: Agent,
176
+ mcpServerManager: McpServerManager,
177
+ sudoMcpServerManager: SudoMcpServerManager,
178
+ cmds: string[]
179
+ ) {
180
+ switch (cmds[0]) {
181
+ case "lt":
182
+ listTools(mcpServerManager);
183
+ break;
184
+ case "ls":
185
+ listServers(sudoMcpServerManager);
186
+ break;
187
+ case "as":
188
+ await addServer(sudoMcpServerManager, cmds[1]);
189
+ break;
190
+ case "rs":
191
+ await mcpServerManager.removeMcpServer(cmds[1]);
192
+ break;
193
+ case "e":
194
+ mcpServerManager.enableTool(cmds[1], cmds[2]);
195
+ console.log(`Enabled tool ${cmds[2]} for server ${cmds[1]}`);
196
+ break;
197
+ case "d":
198
+ mcpServerManager.disableTool(cmds[1], cmds[2]);
199
+ console.log(`Disabled tool ${cmds[2]} for server ${cmds[1]}`);
200
+ break;
201
+ case "ea":
202
+ mcpServerManager.enableAllTools(cmds[1]);
203
+ console.log(`Enabled all tools for server ${cmds[1]}`);
204
+ break;
205
+ case "da":
206
+ mcpServerManager.disableAllTools(cmds[1]);
207
+ console.log(`Disabled all tools for server ${cmds[1]}`);
208
+ break;
209
+ case "wc":
210
+ writeConversation(agent, cmds[1]);
211
+ break;
212
+ case "wa":
213
+ writeAgentProfile(agent, cmds[1]);
214
+ break;
215
+ case "h":
216
+ case "help":
217
+ case "?":
218
+ helpMenu();
219
+ break;
220
+ default:
221
+ console.log(`error: Unknown command ${cmds[0]}`);
222
+ }
223
+ }
224
+
225
+ function helpMenu() {
226
+ console.log(`Tool management commands:`);
227
+ console.log(` ${chalk.yellow("/lt")} List tools: `);
228
+ console.log(` ${chalk.yellow("/ls")} List servers`);
229
+ console.log(` ${chalk.yellow("/as <server-name>")} Add server`);
230
+ console.log(
231
+ ` ${chalk.yellow("/rs <server-name>")} Remove server`
232
+ );
233
+ console.log(` ${chalk.yellow("/e <server-name> <tool-name>")} Enable tool`);
234
+ console.log(` ${chalk.yellow("/d <server-name> <tool-name>")} Disable tool`);
235
+ console.log(
236
+ ` ${chalk.yellow("/ea <server-name>")} Enable all tools`
237
+ );
238
+ console.log(
239
+ ` ${chalk.yellow("/da <server-name>")} Disable all tools`
240
+ );
241
+ console.log(
242
+ ` ${chalk.yellow("/wc <file-name>")} Write conversation file`
243
+ );
244
+ console.log(
245
+ ` ${chalk.yellow("/wa <file-name>")} Write agent profile file`
246
+ );
247
+
248
+ console.log(` ${chalk.yellow("/q")} Quit`);
249
+ }
250
+
251
+ function listTools(mcpServerManager: McpServerManager) {
252
+ console.log("Mcp servers and tools (* - enabled):");
253
+
254
+ const serverNames = mcpServerManager.getMcpServerNames();
255
+ for (const serverName of serverNames) {
256
+ const server = mcpServerManager.getMcpServer(serverName);
257
+ console.log(` ${chalk.green(serverName)}`);
258
+ const tools = server.getTools();
259
+ const enabled = server.getEnabledTools();
260
+ for (const tool of tools) {
261
+ const isEnabled = enabled[tool.name] ? "*" : " ";
262
+ console.log(` [${isEnabled}] ${tool.name}`);
263
+ }
264
+ }
265
+ }
266
+
267
+ function listServers(sudoMcpServerManager: SudoMcpServerManager) {
268
+ console.log(`Available MCP Servers:`);
269
+
270
+ const serverBriefs = sudoMcpServerManager.getServerBriefs();
271
+ serverBriefs.sort((a, b) => a.name.localeCompare(b.name));
272
+ for (const brief of serverBriefs) {
273
+ console.log(`- ${chalk.green(brief.name)}: ${brief.description}`);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Adds server and enables all tools.
279
+ */
280
+ async function addServer(
281
+ sudoMcpServerManager: SudoMcpServerManager,
282
+ serverName: string
283
+ ) {
284
+ const sudoMcpConfig = configuration.loadEnsureConfig();
285
+ const config = sudoMcpConfig
286
+ ? sudoMcpConfig.getConfigForServer(serverName)
287
+ : {};
288
+ try {
289
+ await sudoMcpServerManager.addMcpServer(serverName, config);
290
+ } catch (e) {
291
+ if (e instanceof InvalidConfiguration) {
292
+ throw `${e}. \n
293
+ Have you installed server ${serverName} using the SudoMCP CLI tool?`;
294
+ } else {
295
+ throw e;
296
+ }
297
+ }
298
+ sudoMcpServerManager.getMcpServerManager().enableAllTools(serverName);
299
+ console.log(`Added server: ${serverName} and enabled all tools.`);
300
+ console.log(`Current tool list:`);
301
+ listTools(sudoMcpServerManager.getMcpServerManager());
302
+ }
303
+
304
+ function writeConversation(agent: Agent, fileName: string) {
305
+ const conversation = agent.getConversation();
306
+ fs.writeFileSync(fileName, JSON.stringify(conversation), {
307
+ encoding: "utf8",
308
+ });
309
+ console.log(`Conversation written to ${fileName}`);
310
+ }
311
+
312
+ function writeAgentProfile(agent: Agent, fileName: string) {
313
+ const profile = agent.getAgentProfile();
314
+ fs.writeFileSync(fileName, JSON.stringify(profile));
315
+ console.log(`AgentProfile written to ${fileName}`);
316
+ }
317
+
318
+ async function promptToolCall(
319
+ repl: Prompt,
320
+ toolCall: OpenAI.ChatCompletionMessageToolCall
321
+ ): Promise<boolean> {
322
+ displayToolCall(toolCall);
323
+ const response = await repl.run("Approve tool call? (Y/n) ");
324
+ return response === "y" || response === "yes" || response == "";
325
+ }
@@ -0,0 +1,36 @@
1
+ import { ILLM } from "./llm";
2
+ import { OpenAI } from "openai";
3
+ import { strict as assert } from "assert";
4
+
5
+ export class DummyLLM implements ILLM {
6
+ private readonly responses: OpenAI.Chat.Completions.ChatCompletion.Choice[];
7
+ private idx: number;
8
+
9
+ constructor(responses: OpenAI.Chat.Completions.ChatCompletion.Choice[]) {
10
+ this.responses = responses;
11
+ this.idx = 0;
12
+ }
13
+
14
+ public getModel(): string {
15
+ return "dummy";
16
+ }
17
+
18
+ public getUrl(): string {
19
+ throw "cannot get url for DummyLLM";
20
+ }
21
+
22
+ public async getConversationResponse(
23
+ _messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
24
+ _tools?: OpenAI.Chat.Completions.ChatCompletionTool[]
25
+ ): Promise<OpenAI.Chat.Completions.ChatCompletion> {
26
+ await new Promise((r) => setTimeout(r, 0));
27
+ assert(this.idx < this.responses.length);
28
+ return {
29
+ id: "" + this.idx,
30
+ choices: [this.responses[this.idx++]],
31
+ created: Date.now(),
32
+ model: "dummyLlmModel",
33
+ object: "chat.completion",
34
+ };
35
+ }
36
+ }
package/src/files.ts ADDED
@@ -0,0 +1,95 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { McpServerUrls } from "./agent";
4
+
5
+ export function loadFileOrUndefined(
6
+ file: string | undefined
7
+ ): string | undefined {
8
+ if (file) {
9
+ return fs.readFileSync(file, { encoding: "utf8" });
10
+ }
11
+ return undefined;
12
+ }
13
+
14
+ /**
15
+ * If filename is `-` read everything from stdin and return it. Otherwise,
16
+ * load the file and return the content.
17
+ */
18
+ export async function loadFileOrStdin(file: string): Promise<string> {
19
+ if (file === "-") {
20
+ return new Promise((resolve, reject) => {
21
+ let data = "";
22
+
23
+ process.stdin.setEncoding("utf8");
24
+
25
+ process.stdin.on("data", (chunk) => {
26
+ data += chunk;
27
+ });
28
+
29
+ process.stdin.on("end", () => {
30
+ resolve(data);
31
+ });
32
+
33
+ process.stdin.on("error", (err) => {
34
+ reject(err);
35
+ });
36
+ });
37
+ }
38
+
39
+ return fs.readFileSync(file, { encoding: "utf8" });
40
+ }
41
+
42
+ export function loadImageB64OrUndefined(
43
+ file: string | undefined
44
+ ): string | undefined {
45
+ if (file) {
46
+ const ext = path.extname(file).slice(1).toLowerCase();
47
+ const mimeType = {
48
+ jpg: "image/jpeg",
49
+ jpeg: "image/jpeg",
50
+ png: "image/png",
51
+ gif: "image/gif",
52
+ svg: "image/svg+xml",
53
+ webp: "image/webp",
54
+ }[ext];
55
+ if (!mimeType) {
56
+ throw Error(`invalid file extension: ${ext}`);
57
+ }
58
+
59
+ const fileBuffer = fs.readFileSync(file);
60
+ const imgB64 = fileBuffer.toString("base64");
61
+ return `data:${mimeType};base64,${imgB64}`;
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ export function loadServerUrls(path: string): McpServerUrls {
67
+ try {
68
+ const file = fs.readFileSync(path, "utf-8");
69
+ const urls = JSON.parse(file);
70
+
71
+ // Validate the structure
72
+ if (typeof urls !== "object" || urls === null) {
73
+ throw new Error("Invalid server URLs format: must be an object");
74
+ }
75
+
76
+ // Validate each URL is a string
77
+ for (const [key, value] of Object.entries(urls)) {
78
+ if (typeof value !== "string") {
79
+ throw new Error(
80
+ `Invalid URL format for server ${key}: must be a string`
81
+ );
82
+ }
83
+ }
84
+
85
+ return urls as McpServerUrls;
86
+ } catch (error) {
87
+ if (error instanceof SyntaxError) {
88
+ throw new Error(`Invalid JSON in server URLs file: ${error.message}`);
89
+ }
90
+ if (error instanceof Error) {
91
+ throw new Error(`Failed to load server URLs: ${error.message}`);
92
+ }
93
+ throw error;
94
+ }
95
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Per-platform services that the agent and related classes can use.
3
+ */
4
+ export interface IPlatform {
5
+ /**
6
+ * Open the browser (or a new tab) at some URL.
7
+ */
8
+ openUrl(url: string): void;
9
+
10
+ load(filename: string): Promise<string>;
11
+ }
package/src/llm.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { OpenAI } from "openai";
2
+
3
+ export interface ILLM {
4
+ getModel(): string;
5
+
6
+ getUrl(): string;
7
+
8
+ getConversationResponse(
9
+ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
10
+ tools?: OpenAI.Chat.Completions.ChatCompletionTool[]
11
+ ): Promise<OpenAI.Chat.Completions.ChatCompletion>;
12
+ }
package/src/main.ts ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ // -*- typescript -*-
3
+
4
+ import * as fs from "fs";
5
+ import * as dotenv from "dotenv";
6
+ import { run, command, option, optional, string } from "cmd-ts";
7
+ import { AgentProfile } from "./agent";
8
+ import {
9
+ openaiApiKey,
10
+ promptFile,
11
+ oneShot,
12
+ approveTools,
13
+ approveToolsUpTo,
14
+ imageFile,
15
+ } from "./options";
16
+ import OpenAI from "openai";
17
+ import {
18
+ loadFileOrUndefined,
19
+ loadFileOrStdin,
20
+ loadImageB64OrUndefined,
21
+ } from "./files";
22
+ import { configuration, utils } from "@xalia/xmcp/tool";
23
+ import { getLogger } from "@xalia/xmcp/sdk";
24
+ import { runOneShot } from "./agentUtils";
25
+ import { runChat } from "./chat";
26
+ import { NODE_PLATFORM } from "./nodePlatform";
27
+ import { strict as assert } from "assert";
28
+
29
+ dotenv.config();
30
+
31
+ const logger = getLogger();
32
+
33
+ const main = command({
34
+ name: "main",
35
+ args: {
36
+ promptFile,
37
+ imageFile,
38
+ openaiApiKey,
39
+ oneShot,
40
+ approveTools,
41
+ approveToolsUpTo,
42
+ sudomcpConfigFile: option({
43
+ type: optional(string),
44
+ long: "sudomcp-config-file",
45
+ description:
46
+ "SudoMCP config file (content or file, default: ~/config/..)",
47
+ env: "SUDOMCP_CONFIG",
48
+ }),
49
+ conversationFile: option({
50
+ type: optional(string),
51
+ long: "conversation",
52
+ description: "Restore conversation (content or file) (see /wc)",
53
+ env: "CONVERSATION",
54
+ }),
55
+ conversationOutputFile: option({
56
+ type: optional(string),
57
+ long: "conversation-output",
58
+ description: "Save final conversation to file (--one-shot mode)",
59
+ env: "CONVERSATION_OUTPUT",
60
+ }),
61
+ agentProfileFile: option({
62
+ type: optional(string),
63
+ long: "agent-profile",
64
+ description: "Agent profile (content or filename)",
65
+ env: "AGENT_PROFILE",
66
+ }),
67
+ },
68
+ handler: async ({
69
+ promptFile,
70
+ imageFile,
71
+ openaiApiKey,
72
+ oneShot,
73
+ approveTools,
74
+ approveToolsUpTo,
75
+ sudomcpConfigFile,
76
+ conversationFile,
77
+ conversationOutputFile,
78
+ agentProfileFile,
79
+ }): Promise<void> => {
80
+ approveToolsUpTo = (() => {
81
+ if (typeof approveToolsUpTo === "undefined") {
82
+ // For the non-interactive case, `--approve-tools` is ignored.
83
+ if (oneShot) {
84
+ return -1;
85
+ }
86
+ return approveTools ? -1 : 0;
87
+ }
88
+ return approveToolsUpTo;
89
+ })();
90
+ assert(typeof approveToolsUpTo === "number");
91
+
92
+ // Load the AgentProfile or use a default
93
+
94
+ const agentProfile: AgentProfile = (() => {
95
+ let agentProfile = utils.loadContentOrFileOrUndefined(
96
+ agentProfileFile,
97
+ AgentProfile
98
+ );
99
+ if (!agentProfile) {
100
+ agentProfile = new AgentProfile(
101
+ undefined,
102
+ undefined,
103
+ "You are a helpful agent",
104
+ {}
105
+ );
106
+ }
107
+
108
+ return agentProfile;
109
+ })();
110
+ logger.debug(`agent config: ${JSON.stringify(agentProfile)}`);
111
+
112
+ // Read sudomcp config file, overriding values from command lines params.
113
+
114
+ const sudomcpConfig = configuration.loadEnsureConfig(sudomcpConfigFile);
115
+
116
+ // Restore conversation from value or file.
117
+
118
+ const startingConversation:
119
+ | OpenAI.ChatCompletionMessageParam[]
120
+ | undefined = utils.loadContentOrFileOrUndefined(conversationFile);
121
+ logger.debug(
122
+ `startingConversation: ${JSON.stringify(startingConversation)}`
123
+ );
124
+
125
+ // Run in one-shot mode or chat-mode
126
+
127
+ if (oneShot) {
128
+ if (!promptFile) {
129
+ throw "one-shot mode requires a prompt";
130
+ }
131
+ const prompt = await loadFileOrStdin(promptFile);
132
+
133
+ const { response, conversation } = await runOneShot(
134
+ agentProfile,
135
+ startingConversation,
136
+ NODE_PLATFORM,
137
+ prompt,
138
+ loadImageB64OrUndefined(imageFile),
139
+ openaiApiKey,
140
+ sudomcpConfig,
141
+ approveToolsUpTo
142
+ );
143
+ console.log(response);
144
+
145
+ if (conversationOutputFile) {
146
+ logger.debug(`writing conversation to ${conversationOutputFile}:`);
147
+ logger.debug(` conversation: ${JSON.stringify(conversation)}`);
148
+
149
+ fs.writeFileSync(conversationOutputFile, JSON.stringify(conversation));
150
+ }
151
+ } else {
152
+ const prompt = loadFileOrUndefined(promptFile);
153
+ return runChat(
154
+ agentProfile,
155
+ startingConversation,
156
+ prompt,
157
+ imageFile,
158
+ openaiApiKey,
159
+ sudomcpConfig,
160
+ approveToolsUpTo
161
+ );
162
+ }
163
+ },
164
+ });
165
+
166
+ function handleError(msg: string) {
167
+ console.error(msg);
168
+ process.exit(1);
169
+ }
170
+
171
+ run(main, process.argv.slice(2)).catch(handleError);