diff --git a/__tests__/encrypt.spec.ts b/__tests__/encrypt.spec.ts index dbbe56a..8085649 100644 --- a/__tests__/encrypt.spec.ts +++ b/__tests__/encrypt.spec.ts @@ -28,8 +28,10 @@ describe("Encryption and Decryption", () => { expect(decrypted).to.equal("test"); }); - it("should throw an error when given an invalid encrypted string", () => { - expect(() => decrypt("invalid:string")).to.throw(); + it("should handle invalid encrypted strings correctly", () => { + expect(decrypt("invalid:string")).to.equal("invalid:string"); + expect(decrypt("invalidstring")).to.equal("invalidstring"); + expect(decrypt("")).to.be.null; }); }); diff --git a/package.json b/package.json index e661e6f..9c40a26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "terminalgpt", - "version": "1.7.5", + "version": "1.7.6", "main": "lib/index.js", "description": "Get GPT like chatGPT on your terminal", "scripts": { diff --git a/src/creds.ts b/src/creds.ts index ea13e9f..ae11edc 100644 --- a/src/creds.ts +++ b/src/creds.ts @@ -1,9 +1,31 @@ import * as fs from "fs"; - import * as crypto from "crypto"; +import * as path from "path"; const algorithm = "aes-256-cbc"; -const secretKey = "terminalGPT"; +const secretKeyFile = path.join(__dirname, "secret_key.txt"); + +// Function to generate and save a secret key +function generateAndSaveSecretKey(): string { + const secretKey = crypto.randomBytes(32).toString("hex"); + fs.writeFileSync(secretKeyFile, secretKey, "utf8"); + return secretKey; +} + +// Function to get or create the secret key +function getSecretKey(): string { + if (fs.existsSync(secretKeyFile)) { + return fs.readFileSync(secretKeyFile, "utf8"); + } else { + return generateAndSaveSecretKey(); + } +} + +const secretKey = getSecretKey(); + +function isEncrypted(text: string): boolean { + return text.includes(":") && /^[0-9a-f]+:[0-9a-f]+$/.test(text); +} /** * Encrypts the given text using the specified algorithm, secret key, and initialization vector. @@ -11,14 +33,16 @@ const secretKey = "terminalGPT"; * @param {string} text - The text to be encrypted. * @return {string} The encrypted text in the format: IV:encryptedText. */ -export function encrypt(text: string) { +export function encrypt(text: string): string { + if (isEncrypted(text)) { + return text; + } const iv = crypto.randomBytes(16); const key = crypto.scryptSync(secretKey, "salt", 32); const cipher = crypto.createCipheriv(algorithm, key, iv); - let encrypted = cipher.update(text); - - encrypted = Buffer.concat([encrypted, cipher.final()]); - return iv.toString("hex") + ":" + encrypted.toString("hex"); + let encrypted = cipher.update(text, "utf8", "hex"); + encrypted += cipher.final("hex"); + return iv.toString("hex") + ":" + encrypted; } /** @@ -27,15 +51,33 @@ export function encrypt(text: string) { * @param {string} text - The text to be decrypted. * @return {string} - The decrypted text. */ -export function decrypt(text: string) { - const textParts = text.split(":"); - const iv = Buffer.from(textParts.shift()!, "hex"); - const encryptedText = Buffer.from(textParts.join(":"), "hex"); - const key = crypto.scryptSync(secretKey, "salt", 32); - const decipher = crypto.createDecipheriv(algorithm, key, iv); - let decrypted = decipher.update(encryptedText); - decrypted = Buffer.concat([decrypted, decipher.final()]); - return decrypted.toString(); +export function decrypt(text: string | undefined): string | null { + if (!text) { + return null; + } + + if (!isEncrypted(text)) { + return text; + } + + const [ivHex, encryptedHex] = text.split(":"); + if (!ivHex || !encryptedHex) { + return null; + } + + try { + const iv = Buffer.from(ivHex, "hex"); + const encrypted = Buffer.from(encryptedHex, "hex"); + const key = crypto.scryptSync(secretKey, "salt", 32); + const decipher = crypto.createDecipheriv(algorithm, key, iv); + let decrypted = decipher.update(encrypted); + decrypted = Buffer.concat([decrypted, decipher.final()]); + const result = decrypted.toString("utf8"); + return result; + } catch (error) { + console.error("Decryption error:", error); + return null; + } } /** @@ -61,13 +103,18 @@ export function getEngine(): string | null { export function saveCredentials( apiKey: string, engine: string, - tavilyApiKey: string + tavilyApiKey?: string ) { - const credentials = { apiKey: encrypt(apiKey), engine, tavilyApiKey }; + const credentials = { + apiKey: encrypt(apiKey), + engine, + tavilyApiKey: tavilyApiKey ? encrypt(tavilyApiKey) : undefined, + }; fs.writeFileSync( `${__dirname}/credentials.json`, - JSON.stringify(credentials) + JSON.stringify(credentials, null, 2) ); + console.log("Credentials saved successfully"); } /** @@ -81,14 +128,20 @@ export function getCredentials(): { tavilyApiKey: string | null; } { if (fs.existsSync(`${__dirname}/credentials.json`)) { - const data = fs.readFileSync(`${__dirname}/credentials.json`, "utf8"); - const { apiKey, engine, tavilyApiKey } = JSON.parse(data); - return { - apiKey: apiKey ? decrypt(apiKey) : null, - engine, - tavilyApiKey: tavilyApiKey || null, - }; + try { + const data = fs.readFileSync(`${__dirname}/credentials.json`, "utf8"); + const { apiKey, engine, tavilyApiKey } = JSON.parse(data); + return { + apiKey: apiKey ? decrypt(apiKey) : null, + engine: engine || null, + tavilyApiKey: tavilyApiKey ? decrypt(tavilyApiKey) : null, + }; + } catch (error) { + console.error("Error reading or parsing credentials:", error); + return { apiKey: null, engine: null, tavilyApiKey: null }; + } } + console.log("Credentials file not found"); return { apiKey: null, engine: null, tavilyApiKey: null }; } diff --git a/src/engine/anthropic.ts b/src/engine/anthropic.ts index 8867c54..a09ff49 100644 --- a/src/engine/anthropic.ts +++ b/src/engine/anthropic.ts @@ -15,12 +15,12 @@ export const AnthropicEngine = async ( } ) => { const apiKeyValue = await apiKey; + const anthropic = new AnthropicClient({ apiKey: apiKeyValue }); const spinner = loadWithRocketGradient("Thinking...").start(); try { const relevantContext = getContext(prompt); - console.log("Relevant context:", relevantContext); // Debug log let messages: ContextItem[] = []; let systemMessage = ""; @@ -46,8 +46,6 @@ export const AnthropicEngine = async ( } } - console.log("Final messages:", messages); // Debug log - const requestParams: MessageCreateParamsNonStreaming = { model: opts.model || "claude-3-opus-20240229", messages: messages, diff --git a/src/handlers/webHandler.ts b/src/handlers/webHandler.ts index bb42d4a..622d483 100644 --- a/src/handlers/webHandler.ts +++ b/src/handlers/webHandler.ts @@ -2,29 +2,27 @@ import axios from "axios"; import { addContext } from "../context"; import chalk from "chalk"; -import { getCredentials, saveCredentials, encrypt, decrypt } from "../creds"; +import { saveCredentials, getCredentials } from "../creds"; import readline from "readline"; export async function handleWebResearch(query: string, userPrompt: string) { try { - let credentials = getCredentials(); + let credentials = await getCredentials(); if (!credentials.tavilyApiKey) { console.log(chalk.yellow("Tavily API key not found.")); console.log( chalk.cyan("Please visit https://tavily.com to get an API key.") ); - const apiKey = await promptForApiKey(); - const encryptedTavilyApiKey = encrypt(apiKey); + const tavilyApiKey = await promptForApiKey(); saveCredentials( credentials.apiKey || "", credentials.engine || "", - encryptedTavilyApiKey + tavilyApiKey ); credentials = getCredentials(); } - - const tavilyApiKey = decrypt(credentials.tavilyApiKey!); + const tavilyApiKey = credentials.tavilyApiKey!; console.log(chalk.yellow(`Searching the web for: "${query}"...`)); @@ -38,7 +36,7 @@ export async function handleWebResearch(query: string, userPrompt: string) { const searchResults = response.data.results .map( (result: any) => - `Title: ${result.title}\nSnippet: ${result.snippet}\n\n` + `Title: ${result.title}\nSnippet: ${result.content}\n\n` ) .join(""); @@ -57,6 +55,7 @@ export async function handleWebResearch(query: string, userPrompt: string) { } } +// Update the promptForApiKey function async function promptForApiKey(): Promise { const rl = readline.createInterface({ input: process.stdin, @@ -66,7 +65,8 @@ async function promptForApiKey(): Promise { return new Promise((resolve) => { rl.question("Please enter your Tavily API key: ", (apiKey) => { rl.close(); - resolve(encrypt(apiKey.trim())); + // Remove encryption here + resolve(apiKey.trim()); }); }); } diff --git a/src/utils.ts b/src/utils.ts index 491c882..967574e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -55,7 +55,7 @@ export async function apiKeyPrompt() { if (!apiKey || !engine) { const response = await prompts(questions); // Save both API key and engine - saveCredentials(encrypt(response.apiKey), response.engine, "user"); + saveCredentials(encrypt(response.apiKey), response.engine); return { apiKey: response.apiKey, engine: response.engine }; }