Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix console.log and file deletion bugs #86

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "terminalgpt",
"version": "1.7.3",
"version": "1.7.4",
"main": "lib/index.js",
"description": "Get GPT like chatGPT on your terminal",
"scripts": {
Expand Down
5 changes: 5 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

let context: any[] = [];

/**
* Adds a new context to the existing context array.
*
* @param {any} text - The text to be added to the context array.
*/
export function addContext(text: any) {
context = [...context, text];
}
Expand Down
115 changes: 78 additions & 37 deletions src/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,101 @@ import * as crypto from "crypto";
const algorithm = "aes-256-cbc";
const secretKey = "terminalGPT";

/**
* Encrypts the given text using the specified algorithm, secret key, and initialization vector.
*
* @param {string} text - The text to be encrypted.
* @return {string} The encrypted text in the format: IV:encryptedText.
*/
export function encrypt(text: string) {
const iv = crypto.randomBytes(16);
const key = crypto.scryptSync(secretKey, "salt", 32);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(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");
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString("hex") + ":" + encrypted.toString("hex");
}

/**
* Decrypts the given text using a specific algorithm and secret key.
*
* @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();
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();
}

/**
* Saves a custom URL to a file.
*
* @param {string | undefined} url - The custom URL to be saved. If undefined, the file is deleted.
* @return {void}
*/
export function saveCustomUrl(url: string | undefined) {
if (url === undefined) {
fs.unlinkSync(`${__dirname}/customUrl.txt`);
} else {
fs.writeFileSync(`${__dirname}/customUrl.txt`, url);
}
if (url === undefined) {
fs.unlinkSync(`${__dirname}/customUrl.txt`);
} else {
fs.writeFileSync(`${__dirname}/customUrl.txt`, url);
}
}

/**
* Retrieves a custom URL from a text file if it exists.
*
* @return {string | undefined} The custom URL if it exists, otherwise undefined.
*/
export function getCustomUrl(): string | undefined {
if (fs.existsSync(`${__dirname}/customUrl.txt`)) {
return fs.readFileSync(
`${__dirname}/customUrl.txt`,
"utf8"
);
}
return undefined
if (fs.existsSync(`${__dirname}/customUrl.txt`)) {
return fs.readFileSync(`${__dirname}/customUrl.txt`, "utf8");
}
return undefined;
}

/**
* Saves the API key to a file.
*
* @param {string} apiKey - The API key to save.
* @return {void} This function does not return anything.
*/
export function saveApiKey(apiKey: string) {
fs.writeFileSync(`${__dirname}/apiKey.txt`, apiKey);
fs.writeFileSync(`${__dirname}/apiKey.txt`, apiKey);
}

/**
* Deletes the API key file if it exists.
*
* @return {boolean} Returns true if the API key file was deleted, false otherwise.
*/
export function deleteApiKey() {
fs.unlinkSync(`${__dirname}/apiKey.txt`);
const apiKeyFilePath = `${__dirname}/apiKey.txt`;
if (fs.existsSync(apiKeyFilePath)) {
fs.unlinkSync(apiKeyFilePath);
return true;
}
return false;
}

/**
* Retrieves the API key from the "apiKey.txt" file, decrypts it, and returns it.
*
* @return {string | null} The decrypted API key, or null if the file does not exist.
*/
export function getApiKey(): string | null {
if (fs.existsSync(`${__dirname}/apiKey.txt`)) {
const getEncryptedScript = fs.readFileSync(
`${__dirname}/apiKey.txt`,
"utf8"
);
return decrypt(getEncryptedScript);
}
return null;
}
if (fs.existsSync(`${__dirname}/apiKey.txt`)) {
const getEncryptedScript = fs.readFileSync(
`${__dirname}/apiKey.txt`,
"utf8"
);
return decrypt(getEncryptedScript);
}
return null;
}
2 changes: 2 additions & 0 deletions src/gpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
});
addContext({ role: "user", content: prompt });

const request = await openai.chat.completions
.create({
model: opts.engine || "gpt-4-1106-preview",
messages: getContext(),
temperature: opts.temperature ? Number(opts.temperature) : 1,
})
.then((res) => {
console.log("here");

Check warning on line 35 in src/gpt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
if (typeof res.choices[0].message !== "undefined") {
addContext(res.choices[0].message);
spinner.stop();
Expand All @@ -41,6 +42,7 @@
}
})
.catch((err) => {
console.log(err);

Check warning on line 45 in src/gpt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
spinner.stop();
switch (err["response"]["status"]) {
case 404:
Expand Down
5 changes: 5 additions & 0 deletions src/gradient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const referenceGradient: string[] = [
...gradientColors,
];

/**
* Generates the frames for a gradient animation.
*
* @return {string[]} An array of strings representing the frames of the animation.
*/
export function getGradientAnimFrames() {
const frames: string[] = [];
for (let start = 0; start < gradientColors.length * 2; start++) {
Expand Down
165 changes: 85 additions & 80 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,104 @@ import prompts from "prompts";

import intro from "./intro";

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

import {deleteApiKey, saveCustomUrl} from "./encrypt";
import { deleteApiKey, saveCustomUrl } from "./encrypt";

import * as c from "commander";

const commander = new c.Command()
const commander = new c.Command();
commander
.command("chat")
.option("-e, --engine <engine>", "GPT model to use")
.option("-t, --temperature <temperature>", "Response temperature")
.option("-m, --markdown", "Show markdown in the terminal")
.usage(`"<project-directory>" [options]`)
.action(async (opts) => {
intro();
apiKeyPrompt()
.then((apiKey: string | null) => {
const prompt = async () => {
const response = await prompts({
type: "text",
name: "value",
message: `${chalk.blueBright("You: ")}`,
validate: () => {
return true
},
onState: (state) => {
if (state.aborted) {
process.exit(0);
}
}
})
.command("chat")
.option("-e, --engine <engine>", "GPT model to use")
.option("-t, --temperature <temperature>", "Response temperature")
.option("-m, --markdown", "Show markdown in the terminal")
.usage(`"<project-directory>" [options]`)
.action(async (opts) => {
intro();
apiKeyPrompt().then((apiKey: string | null) => {
const prompt = async () => {
const response = await prompts({
type: "text",
name: "value",
message: `${chalk.blueBright("You: ")}`,
validate: () => {
return true;
},
onState: (state) => {
if (state.aborted) {
process.exit(0);
}
},
});

switch (response.value) {
case "exit":
return process.exit(0);
case "clear":
return process.stdout.write("\x1Bc");
default:
if (apiKey != null) {
generateResponse(apiKey, prompt, response, opts);
}
return;
}
};
prompt();
});
switch (response.value) {
case "exit":
return process.exit(0);
case "clear":
return process.stdout.write("\x1Bc");
default:
if (apiKey != null) {
generateResponse(apiKey, prompt, response, opts);
}
return;
}
};
prompt();
});
});

// create commander to delete api key
commander
.command("delete")
.description("Delete your API key")
.action(async () => {
const response = await prompts({
type: "select",
name: "value",
message: "Are you sure?",
choices: [
{title: "Yes", value: "yes"},
{title: "No - exit", value: "no"},
],
initial: 0,
});

if (response.value) {
return process.exit(0)
}
.command("delete")
.description("Delete your API key")
.action(async () => {
const response = await prompts({
type: "select",
name: "value",
message: "Are you sure?",
choices: [
{ title: "Yes", value: "yes" },
{ title: "No - exit", value: "no" },
],
initial: 0,
});

deleteApiKey();
if (response.value === "yes") {
const apiKeyDeleted = deleteApiKey();
if (apiKeyDeleted) {
console.log("API key deleted");
});
} else {
console.log("API key file not found, no action taken.");
}
return process.exit(0);
} else {
console.log("Deletion cancelled");
return process.exit(0);
}
});

commander
.command("endpoint")
.option("--set <url>", "Set your custom endpoint")
.option("-r, --reset", "Reset the API endpoint to default ")
.description("Configure your API endpoint")
.action(async () => {
console.log("Send empty to set default openai endpoint")
prompts({
type: "text",
name: "value",
validate: (t) =>
t.search(/(https?:\/(\/.)+).*/g) === 0 || t === "" ? true : "Urls only allowed",
message: "Insert endpoint: "
})
.then(response =>
(response.value as string)
.replace('/chat/completions', '')
)
.then(value => /(https?:\/(\/.)+).*/g.test(value) ? value : undefined)
.then(saveCustomUrl)
}
)
.command("endpoint")
.option("--set <url>", "Set your custom endpoint")
.option("-r, --reset", "Reset the API endpoint to default ")
.description("Configure your API endpoint")
.action(async () => {
console.log("Send empty to set default openai endpoint");
prompts({
type: "text",
name: "value",
validate: (t) =>
t.search(/(https?:\/(\/.)+).*/g) === 0 || t === ""
? true
: "Urls only allowed",
message: "Insert endpoint: ",
})
.then((response) =>
(response.value as string).replace("/chat/completions", "")
)
.then((value) => (/(https?:\/(\/.)+).*/g.test(value) ? value : undefined))
.then(saveCustomUrl);
});

commander.parse(process.argv);
Loading
Loading