From 288ac2654f6630b810763fc4a11956f70eb812e7 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 10 Feb 2025 10:16:00 -0500 Subject: [PATCH 1/4] good initial version --- src/index.ts | 6 ++++ src/utils/versionCheck.ts | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/utils/versionCheck.ts diff --git a/src/index.ts b/src/index.ts index b00c7e9..e0173a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { createRequire } from "module"; import { join } from "path"; import { fileURLToPath } from "url"; import { fileCommands } from "yargs-file-commands"; +import { checkForUpdates } from "./utils/versionCheck.js"; import sourceMapSupport from "source-map-support"; @@ -20,6 +21,11 @@ const main = async () => { const logger = new Logger({ name: "Main", color: "white" }); + const updateMessage = await checkForUpdates(); + if (updateMessage) { + logger.info(updateMessage); + } + // Error handling process.on("SIGINT", () => { logger.warn("\nGracefully shutting down..."); diff --git a/src/utils/versionCheck.ts b/src/utils/versionCheck.ts new file mode 100644 index 0000000..50d1c7e --- /dev/null +++ b/src/utils/versionCheck.ts @@ -0,0 +1,59 @@ +import { Logger } from "./logger.js"; +import { createRequire } from "module"; +import type { PackageJson } from "type-fest"; + +const require = createRequire(import.meta.url); +const packageInfo = require("../../package.json") as PackageJson; +const logger = new Logger({ name: "version-check" }); + +/** + * Checks if a newer version of the package is available on npm. + * Only runs check when package is installed globally. + * + * @returns Upgrade message string if update available, null otherwise + */ +export async function checkForUpdates(): Promise { + try { + // Only check for updates if running as global package + if (!process.env.npm_config_global) { + return null; + } + + const packageName = packageInfo.name; + const currentVersion = packageInfo.version; + + if (!packageName || !currentVersion) { + logger.warn("Unable to determine package name or version"); + return null; + } + + // Fetch latest version from npm registry + const registryUrl = `https://registry.npmjs.org/${packageName}/latest`; + const response = await fetch(registryUrl); + + if (!response.ok) { + throw new Error(`Failed to fetch version info: ${response.statusText}`); + } + + const data = (await response.json()) as { version: string | undefined }; + const latestVersion = data.version; + + if (!latestVersion) { + throw new Error("Unable to determine latest version"); + } + + // Compare versions + if (currentVersion !== latestVersion) { + return `Update available: ${currentVersion} → ${latestVersion}\nRun 'npm install -g ${packageName}' to update`; + } + + return null; + } catch (error) { + // Log error but don't throw to handle gracefully + logger.error( + "Error checking for updates:", + error instanceof Error ? error.message : String(error) + ); + return null; + } +} From 700d101fca987cd32519981723e71953c0cba6fa Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 10 Feb 2025 10:18:20 -0500 Subject: [PATCH 2/4] better error messages. --- src/utils/versionCheck.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/versionCheck.ts b/src/utils/versionCheck.ts index 50d1c7e..1c5ed47 100644 --- a/src/utils/versionCheck.ts +++ b/src/utils/versionCheck.ts @@ -23,7 +23,7 @@ export async function checkForUpdates(): Promise { const currentVersion = packageInfo.version; if (!packageName || !currentVersion) { - logger.warn("Unable to determine package name or version"); + logger.warn("Unable to determine current package name or version"); return null; } @@ -39,7 +39,8 @@ export async function checkForUpdates(): Promise { const latestVersion = data.version; if (!latestVersion) { - throw new Error("Unable to determine latest version"); + logger.warn("Unable to determine determine latest published version"); + return null; } // Compare versions @@ -50,7 +51,7 @@ export async function checkForUpdates(): Promise { return null; } catch (error) { // Log error but don't throw to handle gracefully - logger.error( + logger.warn( "Error checking for updates:", error instanceof Error ? error.message : String(error) ); From 81a9b4021b37e9a33717246639502d8a9fa18273 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 10 Feb 2025 10:29:15 -0500 Subject: [PATCH 3/4] improve testability of versionCheck.ts --- src/utils/versionCheck.test.ts | 129 +++++++++++++++++++++++++++++++++ src/utils/versionCheck.ts | 87 ++++++++++++++++------ 2 files changed, 194 insertions(+), 22 deletions(-) create mode 100644 src/utils/versionCheck.test.ts diff --git a/src/utils/versionCheck.test.ts b/src/utils/versionCheck.test.ts new file mode 100644 index 0000000..b3f0d45 --- /dev/null +++ b/src/utils/versionCheck.test.ts @@ -0,0 +1,129 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { + isGlobalPackage, + generateUpgradeMessage, + fetchLatestVersion, + getPackageInfo, + checkForUpdates, +} from "./versionCheck.js"; + +// eslint-disable-next-line max-lines-per-function +describe("versionCheck", () => { + describe("isGlobalPackage", () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("returns true when npm_config_global is set", () => { + process.env.npm_config_global = "true"; + expect(isGlobalPackage()).toBe(true); + }); + + it("returns false when npm_config_global is not set", () => { + delete process.env.npm_config_global; + expect(isGlobalPackage()).toBe(false); + }); + }); + + describe("generateUpgradeMessage", () => { + it("returns null when versions are the same", () => { + expect(generateUpgradeMessage("1.0.0", "1.0.0", "test-package")).toBe( + null, + ); + }); + + it("returns upgrade message when versions differ", () => { + const message = generateUpgradeMessage("1.0.0", "1.1.0", "test-package"); + expect(message).toContain("Update available: 1.0.0 → 1.1.0"); + expect(message).toContain("Run 'npm install -g test-package' to update"); + }); + }); + + describe("fetchLatestVersion", () => { + const mockFetch = vi.fn(); + const originalFetch = global.fetch; + + beforeEach(() => { + global.fetch = mockFetch; + }); + + afterEach(() => { + global.fetch = originalFetch; + vi.clearAllMocks(); + }); + + it("returns version when fetch succeeds", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ version: "1.1.0" }), + }); + + const version = await fetchLatestVersion("test-package"); + expect(version).toBe("1.1.0"); + expect(mockFetch).toHaveBeenCalledWith( + "https://registry.npmjs.org/test-package/latest", + ); + }); + + it("returns null when fetch fails", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + statusText: "Not Found", + }); + + const version = await fetchLatestVersion("test-package"); + expect(version).toBe(null); + }); + }); + + describe("getPackageInfo", () => { + it("returns package info from package.json", () => { + const info = getPackageInfo(); + expect(info).toHaveProperty("name"); + expect(info).toHaveProperty("version"); + expect(typeof info.name).toBe("string"); + expect(typeof info.version).toBe("string"); + }); + }); + + describe("checkForUpdates", () => { + const mockFetch = vi.fn(); + const originalFetch = global.fetch; + const originalEnv = process.env; + + beforeEach(() => { + global.fetch = mockFetch; + process.env = { ...originalEnv }; + }); + + afterEach(() => { + global.fetch = originalFetch; + process.env = originalEnv; + vi.clearAllMocks(); + }); + + it("returns null when not running as global package", async () => { + delete process.env.npm_config_global; + const result = await checkForUpdates(); + expect(result).toBe(null); + expect(mockFetch).not.toHaveBeenCalled(); + }); + + it("returns upgrade message when update available", async () => { + process.env.npm_config_global = "true"; + mockFetch.mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ version: "999.0.0" }), // Much higher version + }); + + const result = await checkForUpdates(); + expect(result).toContain("Update available"); + }); + }); +}); diff --git a/src/utils/versionCheck.ts b/src/utils/versionCheck.ts index 1c5ed47..dabf3d6 100644 --- a/src/utils/versionCheck.ts +++ b/src/utils/versionCheck.ts @@ -3,9 +3,67 @@ import { createRequire } from "module"; import type { PackageJson } from "type-fest"; const require = createRequire(import.meta.url); -const packageInfo = require("../../package.json") as PackageJson; const logger = new Logger({ name: "version-check" }); +/** + * Gets the current package info from package.json + */ +export function getPackageInfo(): { + name: string | undefined; + version: string | undefined; +} { + const packageInfo = require("../../package.json") as PackageJson; + return { + name: packageInfo.name, + version: packageInfo.version, + }; +} + +/** + * Checks if the package is running as a global npm package + */ +export function isGlobalPackage(): boolean { + return !!process.env.npm_config_global; +} + +/** + * Fetches the latest version of a package from npm registry + */ +export async function fetchLatestVersion( + packageName: string, +): Promise { + try { + const registryUrl = `https://registry.npmjs.org/${packageName}/latest`; + const response = await fetch(registryUrl); + + if (!response.ok) { + throw new Error(`Failed to fetch version info: ${response.statusText}`); + } + + const data = (await response.json()) as { version: string | undefined }; + return data.version ?? null; + } catch (error) { + logger.warn( + "Error fetching latest version:", + error instanceof Error ? error.message : String(error), + ); + return null; + } +} + +/** + * Generates an upgrade message if versions differ + */ +export function generateUpgradeMessage( + currentVersion: string, + latestVersion: string, + packageName: string, +): string | null { + return currentVersion !== latestVersion + ? `Update available: ${currentVersion} → ${latestVersion}\nRun 'npm install -g ${packageName}' to update` + : null; +} + /** * Checks if a newer version of the package is available on npm. * Only runs check when package is installed globally. @@ -15,45 +73,30 @@ const logger = new Logger({ name: "version-check" }); export async function checkForUpdates(): Promise { try { // Only check for updates if running as global package - if (!process.env.npm_config_global) { + if (!isGlobalPackage()) { return null; } - const packageName = packageInfo.name; - const currentVersion = packageInfo.version; + const { name: packageName, version: currentVersion } = getPackageInfo(); if (!packageName || !currentVersion) { logger.warn("Unable to determine current package name or version"); return null; } - // Fetch latest version from npm registry - const registryUrl = `https://registry.npmjs.org/${packageName}/latest`; - const response = await fetch(registryUrl); - - if (!response.ok) { - throw new Error(`Failed to fetch version info: ${response.statusText}`); - } - - const data = (await response.json()) as { version: string | undefined }; - const latestVersion = data.version; + const latestVersion = await fetchLatestVersion(packageName); if (!latestVersion) { - logger.warn("Unable to determine determine latest published version"); + logger.warn("Unable to determine latest published version"); return null; } - // Compare versions - if (currentVersion !== latestVersion) { - return `Update available: ${currentVersion} → ${latestVersion}\nRun 'npm install -g ${packageName}' to update`; - } - - return null; + return generateUpgradeMessage(currentVersion, latestVersion, packageName); } catch (error) { // Log error but don't throw to handle gracefully logger.warn( "Error checking for updates:", - error instanceof Error ? error.message : String(error) + error instanceof Error ? error.message : String(error), ); return null; } From c353b6f3b252780b9ab62bba52da1f4cd7d3ff87 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 10 Feb 2025 10:29:19 -0500 Subject: [PATCH 4/4] format + lint --- src/commands/$default.ts | 6 +- src/commands/tools.ts | 12 ++-- src/core/executeToolCall.ts | 2 +- src/core/toolAgent.test.ts | 12 ++-- src/core/toolAgent.ts | 18 +++--- src/index.ts | 4 +- src/tools/getTools.test.ts | 2 +- src/tools/interaction/subAgent.test.ts | 10 ++-- src/tools/interaction/subAgent.ts | 4 +- src/tools/io/fetch.ts | 10 ++-- src/tools/io/readFile.test.ts | 4 +- src/tools/io/readFile.ts | 6 +- src/tools/io/updateFile.test.ts | 28 ++++----- src/tools/io/updateFile.ts | 4 +- src/tools/system/shellExecute.test.ts | 4 +- src/tools/system/shellExecute.ts | 6 +- src/utils/logger.test.ts | 79 +++++++++++++++----------- src/utils/logger.ts | 10 ++-- 18 files changed, 118 insertions(+), 103 deletions(-) diff --git a/src/commands/$default.ts b/src/commands/$default.ts index b859622..fc82330 100644 --- a/src/commands/$default.ts +++ b/src/commands/$default.ts @@ -48,8 +48,10 @@ export const handler = async (argv: ArgumentsCamelCase) => { if (argv.file) { try { prompt = await fs.readFile(argv.file, "utf-8"); - } catch (error) { - logger.error(`Failed to read prompt file: ${argv.file}`); + } catch (error: any) { + logger.error( + `Failed to read prompt file: ${argv.file}, ${error?.message}` + ); process.exit(1); } } diff --git a/src/commands/tools.ts b/src/commands/tools.ts index 66c6bb4..c8c9d3f 100644 --- a/src/commands/tools.ts +++ b/src/commands/tools.ts @@ -1,4 +1,4 @@ -import type { ArgumentsCamelCase, Argv } from "yargs"; +import type { Argv } from "yargs"; import { getTools } from "../tools/getTools.js"; import type { JsonSchema7Type } from "zod-to-json-schema"; @@ -42,7 +42,7 @@ function formatSchema(schema: { return output; } -export const handler = async (_argv: ArgumentsCamelCase) => { +export const handler = async () => { try { const tools = await getTools(); @@ -61,8 +61,8 @@ export const handler = async (_argv: ArgumentsCamelCase) => { tool.parameters as { properties?: Record; required?: string[]; - }, - ), + } + ) ); // Returns section @@ -73,8 +73,8 @@ export const handler = async (_argv: ArgumentsCamelCase) => { tool.returns as { properties?: Record; required?: string[]; - }, - ), + } + ) ); } else { console.log(" Type: any"); diff --git a/src/core/executeToolCall.ts b/src/core/executeToolCall.ts index 82a04cb..fa59adf 100644 --- a/src/core/executeToolCall.ts +++ b/src/core/executeToolCall.ts @@ -9,7 +9,7 @@ const OUTPUT_LIMIT = 12 * 1024; // 10KB limit export const executeToolCall = async ( toolCall: ToolCall, tools: Tool[], - parentLogger: Logger + parentLogger: Logger, ): Promise => { const logger = new Logger({ name: `Tool:${toolCall.name}`, diff --git a/src/core/toolAgent.test.ts b/src/core/toolAgent.test.ts index c8b9ea0..4b7a767 100644 --- a/src/core/toolAgent.test.ts +++ b/src/core/toolAgent.test.ts @@ -96,7 +96,7 @@ describe("toolAgent", () => { input: { input: "test" }, }, [mockTool], - logger + logger, ); expect(result.includes("Processed: test")).toBeTruthy(); @@ -111,8 +111,8 @@ describe("toolAgent", () => { input: {}, }, [mockTool], - logger - ) + logger, + ), ).rejects.toThrow("No tool with the name 'nonexistentTool' exists."); }); @@ -142,8 +142,8 @@ describe("toolAgent", () => { input: {}, }, [errorTool], - logger - ) + logger, + ), ).rejects.toThrow("Deliberate failure"); }); @@ -153,7 +153,7 @@ describe("toolAgent", () => { "Test prompt", [sequenceCompleteTool], logger, - testConfig + testConfig, ); expect(result.result).toBe("Test complete"); diff --git a/src/core/toolAgent.ts b/src/core/toolAgent.ts index 33d315b..596a2d8 100644 --- a/src/core/toolAgent.ts +++ b/src/core/toolAgent.ts @@ -112,7 +112,7 @@ async function executeTools( toolCalls: ToolUseContent[], tools: Tool[], messages: Message[], - logger: Logger + logger: Logger, ): Promise { if (toolCalls.length === 0) { return { sequenceCompleted: false, toolResults: [] }; @@ -134,7 +134,7 @@ async function executeTools( content: toolResult, isComplete: call.name === "sequenceComplete", }; - }) + }), ); const toolResults = results.map(({ type, tool_use_id, content }) => ({ @@ -160,7 +160,7 @@ export const toolAgent = async ( initialPrompt: string, tools: Tool[], logger: Logger, - config = CONFIG + config = CONFIG, ): Promise => { logger.verbose("Starting agent execution"); logger.verbose("Initial prompt:", initialPrompt); @@ -189,7 +189,7 @@ export const toolAgent = async ( logger.verbose( `Requesting completion ${i + 1} with ${messages.length} messages with ${ JSON.stringify(messages).length - } bytes` + } bytes`, ); interactions++; @@ -218,7 +218,7 @@ export const toolAgent = async ( interactions, }; logger.verbose( - `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions` + `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions`, ); return result; } @@ -226,7 +226,7 @@ export const toolAgent = async ( totalInputTokens += response.usage.input_tokens; totalOutputTokens += response.usage.output_tokens; logger.verbose( - ` Token usage: ${response.usage.input_tokens} input, ${response.usage.output_tokens} output` + ` Token usage: ${response.usage.input_tokens} input, ${response.usage.output_tokens} output`, ); const { content, toolCalls } = processResponse(response); @@ -245,7 +245,7 @@ export const toolAgent = async ( toolCalls, tools, messages, - logger + logger, ); if (sequenceCompleted) { @@ -260,7 +260,7 @@ export const toolAgent = async ( interactions, }; logger.verbose( - `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions` + `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions`, ); return result; } @@ -276,7 +276,7 @@ export const toolAgent = async ( interactions, }; logger.verbose( - `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions` + `Agent completed with ${result.tokens.input} input tokens, ${result.tokens.output} output tokens in ${result.interactions} interactions`, ); return result; }; diff --git a/src/index.ts b/src/index.ts index e0173a6..dfb1fe9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ const main = async () => { "Fatal error:", error.constructor.name, error.message, - error.stack + error.stack, ); process.exit(1); }); @@ -69,7 +69,7 @@ const main = async () => { await fileCommands({ commandDirs: [commandsDir], logLevel: "info", - }) + }), ) .strict() .showHelpOnFail(true) diff --git a/src/tools/getTools.test.ts b/src/tools/getTools.test.ts index 323a13a..aab66f1 100644 --- a/src/tools/getTools.test.ts +++ b/src/tools/getTools.test.ts @@ -33,7 +33,7 @@ describe("getTools", () => { name: expect.any(String), description: expect.any(String), parameters: expect.any(Object), - }) + }), ); } }); diff --git a/src/tools/interaction/subAgent.test.ts b/src/tools/interaction/subAgent.test.ts index 1ebd2be..c0bdd8e 100644 --- a/src/tools/interaction/subAgent.test.ts +++ b/src/tools/interaction/subAgent.test.ts @@ -41,7 +41,7 @@ describe("subAgent", () => { prompt: "Test sub-agent task", description: "A test agent for unit testing", }, - { logger } + { logger }, ); expect(result.toString()).toContain("Sub-agent task complete"); @@ -57,8 +57,8 @@ describe("subAgent", () => { prompt: "Test task", description: "An agent that should fail", }, - { logger } - ) + { logger }, + ), ).rejects.toThrow("ANTHROPIC_API_KEY environment variable is not set"); }); @@ -72,8 +72,8 @@ describe("subAgent", () => { prompt: "Test task", description: longDescription, }, - { logger } - ) + { logger }, + ), ).rejects.toThrow(); }); }); diff --git a/src/tools/interaction/subAgent.ts b/src/tools/interaction/subAgent.ts index 05b6584..c033e46 100644 --- a/src/tools/interaction/subAgent.ts +++ b/src/tools/interaction/subAgent.ts @@ -15,7 +15,7 @@ const parameterSchema = z.object({ const returnSchema = z .string() .describe( - "The response from the sub-agent including its reasoning and tool usage" + "The response from the sub-agent including its reasoning and tool usage", ); type Parameters = z.infer; @@ -49,7 +49,7 @@ export const subAgentTool: Tool = { const { prompt } = parameterSchema.parse(params); const tools = (await getTools()).filter( - (tool) => tool.name !== "userPrompt" + (tool) => tool.name !== "userPrompt", ); const result = await toolAgent(prompt, tools, logger, subAgentConfig); diff --git a/src/tools/io/fetch.ts b/src/tools/io/fetch.ts index 68f51df..85f8b3b 100644 --- a/src/tools/io/fetch.ts +++ b/src/tools/io/fetch.ts @@ -6,7 +6,7 @@ const parameterSchema = z.object({ method: z .string() .describe( - "HTTP method to use (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)" + "HTTP method to use (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)", ), url: z.string().describe("URL to make the request to"), params: z @@ -39,7 +39,7 @@ export const fetchTool: Tool = { returns: zodToJsonSchema(returnSchema), execute: async ( { method, url, params, body, headers }: Parameters, - { logger } + { logger }, ): Promise => { logger.verbose(`Starting ${method} request to ${url}`); const urlObj = new URL(url); @@ -48,7 +48,7 @@ export const fetchTool: Tool = { if (params) { logger.verbose("Adding query parameters:", params); Object.entries(params).forEach(([key, value]) => - urlObj.searchParams.append(key, value as string) + urlObj.searchParams.append(key, value as string), ); } @@ -71,7 +71,7 @@ export const fetchTool: Tool = { logger.verbose("Request options:", options); const response = await fetch(urlObj.toString(), options); logger.verbose( - `Request completed with status ${response.status} ${response.statusText}` + `Request completed with status ${response.status} ${response.statusText}`, ); const contentType = response.headers.get("content-type"); @@ -91,7 +91,7 @@ export const fetchTool: Tool = { logParameters(params, { logger }) { const { method, url, params: queryParams } = params; logger.info( - `${method} ${url}${queryParams ? `?${new URLSearchParams(queryParams)}` : ""}` + `${method} ${url}${queryParams ? `?${new URLSearchParams(queryParams)}` : ""}`, ); }, }; diff --git a/src/tools/io/readFile.test.ts b/src/tools/io/readFile.test.ts index 5387d41..53ee251 100644 --- a/src/tools/io/readFile.test.ts +++ b/src/tools/io/readFile.test.ts @@ -8,7 +8,7 @@ describe("readFile", () => { it("should read a file", async () => { const { content } = await readFileTool.execute( { path: "package.json", description: "test" }, - { logger } + { logger }, ); expect(content).toContain("mycoder"); }); @@ -17,7 +17,7 @@ describe("readFile", () => { try { await readFileTool.execute( { path: "nonexistent.txt", description: "test" }, - { logger } + { logger }, ); expect(true).toBe(false); // Should not reach here } catch (error: any) { diff --git a/src/tools/io/readFile.ts b/src/tools/io/readFile.ts index 1cc5dca..6d4e3ee 100644 --- a/src/tools/io/readFile.ts +++ b/src/tools/io/readFile.ts @@ -19,7 +19,7 @@ const parameterSchema = z.object({ .number() .optional() .describe( - "Maximum size to read, prevents reading arbitrarily large files that blow up the context window" + "Maximum size to read, prevents reading arbitrarily large files that blow up the context window", ), description: z .string() @@ -54,7 +54,7 @@ export const readFileTool: Tool = { const readSize = range ? range.end - range.start : stats.size; if (readSize > maxSize) { throw new Error( - `Requested size ${readSize} bytes exceeds maximum ${maxSize} bytes` + `Requested size ${readSize} bytes exceeds maximum ${maxSize} bytes`, ); } @@ -66,7 +66,7 @@ export const readFileTool: Tool = { buffer, 0, readSize, - range.start + range.start, ); return { path: filePath, diff --git a/src/tools/io/updateFile.test.ts b/src/tools/io/updateFile.test.ts index a005797..2473583 100644 --- a/src/tools/io/updateFile.test.ts +++ b/src/tools/io/updateFile.test.ts @@ -21,7 +21,7 @@ describe("updateFile", () => { afterEach(async () => { await shellExecuteTool.execute( { command: `rm -rf "${testDir}"`, description: "test" }, - { logger } + { logger }, ); }); @@ -39,7 +39,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Verify return value @@ -49,7 +49,7 @@ describe("updateFile", () => { // Verify content const readResult = await readFileTool.execute( { path: testPath, description: "test" }, - { logger } + { logger }, ); expect(readResult.content).toBe(testContent); }); @@ -70,7 +70,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Append content @@ -83,7 +83,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Verify return value @@ -93,7 +93,7 @@ describe("updateFile", () => { // Verify content const readResult = await readFileTool.execute( { path: testPath, description: "test" }, - { logger } + { logger }, ); expect(readResult.content).toBe(expectedContent); }); @@ -115,7 +115,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Update specific text @@ -129,7 +129,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Verify return value @@ -139,7 +139,7 @@ describe("updateFile", () => { // Verify content const readResult = await readFileTool.execute( { path: testPath, description: "test" }, - { logger } + { logger }, ); expect(readResult.content).toBe(expectedContent); }); @@ -160,7 +160,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Attempt update that should fail @@ -175,8 +175,8 @@ describe("updateFile", () => { }, description: "test", }, - { logger } - ) + { logger }, + ), ).rejects.toThrow("Found 2 occurrences of oldStr, expected exactly 1"); }); @@ -194,7 +194,7 @@ describe("updateFile", () => { }, description: "test", }, - { logger } + { logger }, ); // Verify return value @@ -204,7 +204,7 @@ describe("updateFile", () => { // Verify content const readResult = await readFileTool.execute( { path: nestedPath, description: "test" }, - { logger } + { logger }, ); expect(readResult.content).toBe(testContent); }); diff --git a/src/tools/io/updateFile.ts b/src/tools/io/updateFile.ts index 843779f..a5b7905 100644 --- a/src/tools/io/updateFile.ts +++ b/src/tools/io/updateFile.ts @@ -54,13 +54,13 @@ export const updateFileTool: Tool = { const occurrences = content.split(operation.oldStr).length - 1; if (occurrences !== 1) { throw new Error( - `Found ${occurrences} occurrences of oldStr, expected exactly 1` + `Found ${occurrences} occurrences of oldStr, expected exactly 1`, ); } await fs.writeFile( absolutePath, content.replace(operation.oldStr, operation.newStr), - "utf8" + "utf8", ); } else if (operation.command === "append") { await fs.appendFile(absolutePath, operation.content, "utf8"); diff --git a/src/tools/system/shellExecute.test.ts b/src/tools/system/shellExecute.test.ts index 4d2f218..da3bbca 100644 --- a/src/tools/system/shellExecute.test.ts +++ b/src/tools/system/shellExecute.test.ts @@ -8,7 +8,7 @@ describe("shellExecute", () => { it("should execute shell commands", async () => { const { stdout } = await shellExecuteTool.execute( { command: "echo 'test'", description: "test" }, - { logger } + { logger }, ); expect(stdout).toContain("test"); }); @@ -16,7 +16,7 @@ describe("shellExecute", () => { it("should handle command errors", async () => { const { error } = await shellExecuteTool.execute( { command: "nonexistentcommand", description: "test" }, - { logger } + { logger }, ); expect(error).toContain("Command failed:"); }); diff --git a/src/tools/system/shellExecute.ts b/src/tools/system/shellExecute.ts index b51d297..98355c6 100644 --- a/src/tools/system/shellExecute.ts +++ b/src/tools/system/shellExecute.ts @@ -29,7 +29,7 @@ const returnSchema = z error: z.string().optional(), }) .describe( - "Command execution results including stdout, stderr, and exit code" + "Command execution results including stdout, stderr, and exit code", ); type Parameters = z.infer; @@ -49,10 +49,10 @@ export const shellExecuteTool: Tool = { execute: async ( { command, timeout = 30000 }, - { logger } + { logger }, ): Promise => { logger.verbose( - `Executing shell command with ${timeout}ms timeout: ${command}` + `Executing shell command with ${timeout}ms timeout: ${command}`, ); try { diff --git a/src/utils/logger.test.ts b/src/utils/logger.test.ts index 7041951..271e907 100644 --- a/src/utils/logger.test.ts +++ b/src/utils/logger.test.ts @@ -1,16 +1,16 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { Logger } from './logger.js'; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { Logger } from "./logger.js"; -describe('Logger', () => { +describe("Logger", () => { let consoleSpy: { [key: string]: any }; beforeEach(() => { // Setup console spies before each test consoleSpy = { - log: vi.spyOn(console, 'log').mockImplementation(() => {}), - info: vi.spyOn(console, 'log').mockImplementation(() => {}), - warn: vi.spyOn(console, 'warn').mockImplementation(() => {}), - error: vi.spyOn(console, 'error').mockImplementation(() => {}) + log: vi.spyOn(console, "log").mockImplementation(() => {}), + info: vi.spyOn(console, "log").mockImplementation(() => {}), + warn: vi.spyOn(console, "warn").mockImplementation(() => {}), + error: vi.spyOn(console, "error").mockImplementation(() => {}), }; }); @@ -19,76 +19,89 @@ describe('Logger', () => { vi.clearAllMocks(); }); - describe('Basic logging functionality', () => { - const logger = new Logger({ name: 'TestLogger', logLevel: 'debug' }); - const testMessage = 'Test message'; + describe("Basic logging functionality", () => { + const logger = new Logger({ name: "TestLogger", logLevel: "debug" }); + const testMessage = "Test message"; - it('should log debug messages', () => { + it("should log debug messages", () => { logger.debug(testMessage); - expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining(testMessage)); + expect(consoleSpy.log).toHaveBeenCalledWith( + expect.stringContaining(testMessage), + ); }); - it('should log verbose messages', () => { + it("should log verbose messages", () => { logger.verbose(testMessage); - expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining(testMessage)); + expect(consoleSpy.log).toHaveBeenCalledWith( + expect.stringContaining(testMessage), + ); }); - it('should log info messages', () => { + it("should log info messages", () => { logger.info(testMessage); - expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining(testMessage)); + expect(consoleSpy.log).toHaveBeenCalledWith( + expect.stringContaining(testMessage), + ); }); - it('should log warning messages', () => { + it("should log warning messages", () => { logger.warn(testMessage); - expect(consoleSpy.warn).toHaveBeenCalledWith(expect.stringContaining(testMessage)); + expect(consoleSpy.warn).toHaveBeenCalledWith( + expect.stringContaining(testMessage), + ); }); - it('should log error messages', () => { + it("should log error messages", () => { logger.error(testMessage); - expect(consoleSpy.error).toHaveBeenCalledWith(expect.stringContaining(testMessage)); + expect(consoleSpy.error).toHaveBeenCalledWith( + expect.stringContaining(testMessage), + ); }); }); - describe('Nested logger functionality', () => { - const parentLogger = new Logger({ name: 'ParentLogger', logLevel: 'debug' }); + describe("Nested logger functionality", () => { + const parentLogger = new Logger({ + name: "ParentLogger", + logLevel: "debug", + }); const childLogger = new Logger({ - name: 'ChildLogger', + name: "ChildLogger", parent: parentLogger, - logLevel: 'debug' + logLevel: "debug", }); - const testMessage = 'Nested test message'; + const testMessage = "Nested test message"; - it('should include proper indentation for nested loggers', () => { + it("should include proper indentation for nested loggers", () => { childLogger.info(testMessage); expect(consoleSpy.log).toHaveBeenCalledWith( - expect.stringContaining(' ') // Two spaces of indentation + expect.stringContaining(" "), // Two spaces of indentation ); }); - it('should properly log messages at all levels with nested logger', () => { + it("should properly log messages at all levels with nested logger", () => { childLogger.debug(testMessage); expect(consoleSpy.log).toHaveBeenCalledWith( - expect.stringContaining(testMessage) + expect.stringContaining(testMessage), ); childLogger.verbose(testMessage); expect(consoleSpy.log).toHaveBeenCalledWith( - expect.stringContaining(testMessage) + expect.stringContaining(testMessage), ); childLogger.info(testMessage); expect(consoleSpy.log).toHaveBeenCalledWith( - expect.stringContaining(testMessage) + expect.stringContaining(testMessage), ); childLogger.warn(testMessage); expect(consoleSpy.warn).toHaveBeenCalledWith( - expect.stringContaining(testMessage) + expect.stringContaining(testMessage), ); childLogger.error(testMessage); expect(consoleSpy.error).toHaveBeenCalledWith( - expect.stringContaining(testMessage) + expect.stringContaining(testMessage), ); }); }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index a4d9d23..e2eb820 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -77,10 +77,10 @@ export class Logger { // Split into lines and add prefix to each line if showPrefix is true return formatted .split("\n") - .map((line) => - showPrefix - ? `${prefixChalk(prefix)} ${messagesChalk(`${line}`)}` - : `${this.offset}${messagesChalk(`${line}`)}` + .map((line) => + showPrefix + ? `${prefixChalk(prefix)} ${messagesChalk(`${line}`)}` + : `${this.offset}${messagesChalk(`${line}`)}`, ) .join("\n"); } @@ -156,4 +156,4 @@ export class Logger { ), ); } -} \ No newline at end of file +}