Skip to content

Commit

Permalink
Merge pull request #90 from warpy-ai/main
Browse files Browse the repository at this point in the history
New changes on terminalGPT
  • Loading branch information
jucasoliveira authored Sep 9, 2024
2 parents b288435 + e01c1a2 commit 7760bd0
Show file tree
Hide file tree
Showing 24 changed files with 1,047 additions and 482 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,6 @@ dist
*.txt
*.jsonl
.yarn
lib
lib

credentials.json
113 changes: 57 additions & 56 deletions __tests__/encrypt.spec.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,71 @@
import * as crypto from "../src/encrypt";

import fs from "fs";

import { expect } from "chai";
import * as fs from "fs";
import * as path from "path";
import {
encrypt,
decrypt,
getCredentials,
saveCredentials,
deleteCredentials,
} from "../src/creds";

import {expect} from "chai";
describe("Encryption and Decryption", () => {
it("should return a string in the format 'IV:encrypted_text'", () => {
const result = encrypt("test");
expect(result).to.be.a("string");
expect(result).to.include(":");
});

describe("encrypt()", () => {
it("should return a string in the format 'IV:encrypted_text'", () => {
const result = crypto.encrypt("test");
expect(result).to.match(/^[a-f0-9]{32}:([a-f0-9]{2})*$/);
});
it("should return a different result each time it is called", () => {
const result1 = encrypt("test");
const result2 = encrypt("test");
expect(result1).to.not.equal(result2);
});

it("should return a different result each time it is called", () => {
const result1 = crypto.encrypt("test");
const result2 = crypto.encrypt("test");
expect(result1).not.equal(result2);
});
});
it("should return the original input text when given a valid encrypted string", () => {
const encrypted = encrypt("test");
const decrypted = decrypt(encrypted);
expect(decrypted).to.equal("test");
});

describe("decrypt()", () => {
it("should return the original input text when given a valid encrypted string", () => {
const encrypted = crypto.encrypt("test");
const result = crypto.decrypt(encrypted);
expect(result).to.equal("test");
});

it("should throw an error when given an invalid encrypted string", () => {
expect(() => crypto.decrypt("invalid")).to.throw();
});
it("should throw an error when given an invalid encrypted string", () => {
expect(() => decrypt("invalid:string")).to.throw();
});
});

describe("Api Key ", () => {
// the path to the apiKey.txt file should point to ../src/apiKey.txt
const apiKeyPath = path.resolve(__dirname, "../src/apiKey.txt");
describe("Credentials Management", () => {
const credentialsPath = path.resolve(__dirname, "../src/credentials.json");

const removeLink = () => {
try {
fs.unlinkSync(apiKeyPath)
} catch { /* empty */
}
}
beforeEach(() => {
deleteCredentials();
});

beforeEach(removeLink)
afterEach(removeLink)
afterEach(() => {
deleteCredentials();
});

it("should return null if the API key has not been saved", () => {
const result = crypto.getApiKey();
expect(result).to.equal(null);
it("should return null values if credentials have not been saved", () => {
const result = getCredentials();
expect(result).to.deep.equal({
apiKey: null,
engine: null,
tavilyApiKey: null,
});
});

it("should return the API key if it has been saved", () => {
const encrypted = crypto.encrypt("test");
crypto.saveApiKey(encrypted)
const result = crypto.getApiKey();
expect(result).to.equal("test");
});
it("should save and retrieve credentials correctly", () => {
saveCredentials("testApiKey", "testEngine", "testTavilyApiKey");
const result = getCredentials();
expect(result.apiKey).to.equal("testApiKey");
expect(result.engine).to.equal("testEngine");
expect(result.tavilyApiKey).to.equal("testTavilyApiKey");
});

it("should save the given API key to a file", () => {
if (!fs.existsSync(apiKeyPath)) {
fs.writeFileSync(apiKeyPath, "");
}
const encryptedText = crypto.encrypt("test");
crypto.saveApiKey(encryptedText);
const result = crypto.getApiKey()// fs.readFileSync(apiKeyPath, "utf8");

expect(result).to.equal("test");
});
it("should encrypt the API key when saving", () => {
saveCredentials("testApiKey", "testEngine", "testTavilyApiKey");
const rawData = fs.readFileSync(credentialsPath, "utf-8");
const savedCredentials = JSON.parse(rawData);
expect(savedCredentials.apiKey).to.not.equal("testApiKey");
expect(decrypt(savedCredentials.apiKey)).to.equal("testApiKey");
});
});
81 changes: 44 additions & 37 deletions __tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@ import * as path from "path";

import fs from "fs";

import {expect} from "chai";
import { expect } from "chai";

import { apiKeyPrompt /*, generateResponse*/ } from "../src/utils";

import {apiKeyPrompt/*, generateResponse*/} from "../src/utils";

import {encrypt} from "../src/encrypt";

import { encrypt } from "../src/creds";

describe("apiKeyPrompt()", () => {
const apiKeyPath = path.resolve(__dirname, "../src/apiKey.txt");
const credentialsPath = path.resolve(__dirname, "../src/credentials.json");

beforeEach(() => {
if (!fs.existsSync(apiKeyPath)) {
fs.writeFileSync(apiKeyPath, encrypt("test"));
}
});
beforeEach(() => {
if (!fs.existsSync(credentialsPath)) {
fs.writeFileSync(
credentialsPath,
JSON.stringify({
apiKey: encrypt("test"),
engine: "test",
tavilyApiKey: encrypt("test"),
})
);
}
});

it("the api key prompt to user should return a string", async () => {
const result = await apiKeyPrompt()
expect(result).to.be.a("string")
})
})
it("the api key prompt to user should return an object with apiKey and engine", async () => {
const result = await apiKeyPrompt();
expect(result).to.be.an("object");
expect(result).to.have.property("apiKey");
expect(result).to.have.property("engine");
});
});

// FIXME cannot be executed without a real api key
// describe("generateResponse()", () => {
Expand Down Expand Up @@ -66,25 +73,25 @@ describe("apiKeyPrompt()", () => {
// });
// });

// it("Should create a instance of Configuration", async () => {
// const
// apiKey = "abx",
// prompt = jest.fn(),
// options = {},
// response = {
// data: {
// choices: ["abc", "def"],
// },
// }
//
// const getResponse = await generateResponse(
// apiKey,
// prompt,
// options,
// response
// )
//
// // expect(getResponse).throw(Error)
// expect(getResponse).to.be.a("string");
// });
// it("Should create a instance of Configuration", async () => {
// const
// apiKey = "abx",
// prompt = jest.fn(),
// options = {},
// response = {
// data: {
// choices: ["abc", "def"],
// },
// }
//
// const getResponse = await generateResponse(
// apiKey,
// prompt,
// options,
// response
// )
//
// // expect(getResponse).throw(Error)
// expect(getResponse).to.be.a("string");
// });
// });
Binary file added bun.lockb
Binary file not shown.
14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
{
"name": "terminalgpt",
"version": "1.7.4",
"version": "1.7.5",
"main": "lib/index.js",
"description": "Get GPT like chatGPT on your terminal",
"scripts": {
"tgpt": "node lib/index.js",
"test": "jest --ci --coverage --verbose",
"dev": "ts-node src/index.ts chat --engine gpt-4 --temperature 0.7",
"dev:markdown": "ts-node src/index.ts chat --engine gpt-4 --temperature 0.7 --markdown",
"tunne": "ts-node src/index.ts chat --engine gpt-4 --temperature 0.7 --finetunning true --limit 1",
"dev": "ts-node src/index.ts chat --temperature 0.7",
"dev:delete": "ts-node src/index.ts delete",
"postinstall": "tsc"
},
Expand All @@ -32,16 +30,21 @@
}
},
"dependencies": {
"@anthropic-ai/sdk": "^0.27.2",
"@dqbd/tiktoken": "^1.0.16",
"@google/generative-ai": "^0.17.1",
"@types/gradient-string": "^1.1.5",
"@types/marked": "^6.0.0",
"@types/marked-terminal": "^6.0.1",
"@types/node": "^16.0.0",
"@types/prompts": "^2.4.8",
"axios": "^1.7.7",
"chalk": "^4.1.2",
"clipboardy": "2.3.0",
"commander": "^9.5.0",
"compromise": "^14.8.1",
"gradient-string": "^2.0.2",
"hnswlib-node": "^3.0.0",
"lowdb": "^5.1.0",
"markdown": "^0.5.0",
"marked": "^9.1.6",
Expand All @@ -66,5 +69,6 @@
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
Empty file added src/commands/autossugestion.ts
Empty file.
5 changes: 5 additions & 0 deletions src/commands/clear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const clearFunc = () => {
process.stdout.write("\x1Bc");
};

export default clearFunc;
8 changes: 8 additions & 0 deletions src/commands/exit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import chalk from "chalk";

const exitFunc = () => {
console.log(chalk.yellow("Goodbye!"));
process.exit(0);
};

export default exitFunc;
22 changes: 22 additions & 0 deletions src/commands/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import chalk from "chalk";
import { handleFileReference } from "../handlers/fileHandler"; // Assuming this function exists
import { apiKeyPrompt, promptResponse } from "../utils"; // Assuming this function exists

const fileFunc = async (userInput: string) => {
const creds = await apiKeyPrompt();
// we need to call file handler here
const [, filePath, ...promptParts] = userInput.split(" ");
const promptText = promptParts.join(" ");
if (filePath) {
await handleFileReference(filePath, promptText);
if (creds.apiKey != null) {
await promptResponse(creds.engine, creds.apiKey, userInput, {});
}
} else {
console.log(
chalk.yellow("Please provide a file path. Usage: @file <path> [prompt]")
);
}
};

export default fileFunc;
43 changes: 43 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import exitFunc from "./exit";
import clearFunc from "./clear";
import fileFunc from "./file";
import webFunc from "./web";

export interface Plugin {
keyword: string;
execute: (userInput: string) => Promise<void> | void;
}

const plugins: Plugin[] = [];

const registerPlugin = (
keyword: string,
execute: (userInput: string) => Promise<void> | void
) => {
plugins.push({ keyword, execute });
};

export const mapPlugins = (userInput: string): Plugin | undefined => {
return plugins.find((plugin) => userInput.startsWith(plugin.keyword));
};

export const initializePlugins = () => {
// Register your plugins here
registerPlugin("exit", exitFunc);
registerPlugin("clear", clearFunc);
registerPlugin("@file", fileFunc);
registerPlugin("@web", webFunc);
};

export const executeCommand = (userInput: string): boolean => {
const command = plugins.find((plugin) =>
userInput.startsWith(plugin.keyword)
);
if (command) {
command.execute(userInput);
return true;
}
return false;
};

export default executeCommand;
20 changes: 20 additions & 0 deletions src/commands/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import chalk from "chalk";
import { handleWebResearch } from "../handlers/webHandler";
import { promptResponse, apiKeyPrompt } from "../utils";

const webFunc = async (userInput: string) => {
const creds = await apiKeyPrompt();
const query = userInput.slice(5).trim();
if (query) {
await handleWebResearch(query, userInput);
if (creds.apiKey != null) {
await promptResponse(creds.engine, creds.apiKey, userInput, {});
}
} else {
console.log(
chalk.yellow("Please provide a search query. Usage: @web <query>")
);
}
};

export default webFunc;
Loading

0 comments on commit 7760bd0

Please sign in to comment.