This commit is contained in:
Matyi Kari 2025-03-04 16:05:05 -06:00
parent 13ffdff9a6
commit 19e3e38571
8 changed files with 172 additions and 39 deletions

4
.gitignore vendored
View File

@ -1,3 +1,7 @@
node_modules
.env
dist
*.app
*.exe
*.pkg
*.blob

View File

@ -1,7 +1,7 @@
{
"name": "vvk",
"version": "1.0.0",
"description": "",
"version": "1.1.0",
"description": "A command-line interface tool that converts natural language instructions into shell commands using OpenAI's GPT-4.",
"main": "index.js",
"scripts": {
"build": "rimraf dist && webpack"
@ -22,6 +22,7 @@
"webpack-cli": "^6.0.1"
},
"dependencies": {
"axios": "^1.8.1",
"child_process": "^1.0.2",
"openai": "^4.85.4",
"readline": "^1.3.0"

30
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
axios:
specifier: ^1.8.1
version: 1.8.1
child_process:
specifier: ^1.0.2
version: 1.0.2
@ -242,6 +245,9 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.8.1:
resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -412,6 +418,15 @@ packages:
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
hasBin: true
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@ -639,6 +654,9 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -1098,6 +1116,14 @@ snapshots:
asynckit@0.4.0: {}
axios@1.8.1:
dependencies:
follow-redirects: 1.15.9
form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
balanced-match@1.0.2: {}
brace-expansion@2.0.1:
@ -1241,6 +1267,8 @@ snapshots:
flat@5.0.2: {}
follow-redirects@1.15.9: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@ -1436,6 +1464,8 @@ snapshots:
dependencies:
find-up: 4.1.0
proxy-from-env@1.1.0: {}
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1

View File

@ -17,14 +17,14 @@ export function configCommand(args: string[]) {
}
const updated = updateConfig({ [key]: parsedValue });
console.log('Configuration updated:', updated);
} else if (command === 'get') {
const config = loadConfig();
console.log('Current configuration:', config);
} else if (command === 'list') {
const config = loadConfig();
} else if (command === 'get' || command === 'list') {
let config = loadConfig();
if (config.key && config.key.length > 0) {
config.key = '********';
}
console.log('Current configuration:', config);
} else {
console.error('Unknown config command. Use "set" or "get".');
console.error('Unknown config command. Use "set" or "list".');
}
process.exit(0);
}

View File

@ -4,12 +4,16 @@ import os from 'os';
export interface Config {
openaiApiKey: string;
userId: string;
key: string;
confirmCommand: boolean;
defaultConfirmation: 'y' | 'n';
}
const DEFAULT_CONFIG: Config = {
openaiApiKey: '',
userId: '',
key: '',
confirmCommand: true,
defaultConfirmation: 'y',
};

View File

@ -1,14 +1,13 @@
import OpenAI from 'openai';
import { loadConfig } from './config';
import axios from 'axios';
export async function generateCommand(input: string) {
const { openaiApiKey } = loadConfig();
const { openaiApiKey, key, userId } = loadConfig();
if (!openaiApiKey) {
console.log("Couldn't generage command: Set your OpenAI API Key with vvk config set openaiApiKey <your_api_key>");
process.exit(1);
}
let command;
if (openaiApiKey && openaiApiKey.length > 1) {
const openai = new OpenAI({ apiKey: openaiApiKey });
const response = await openai.chat.completions.create({
@ -23,8 +22,21 @@ export async function generateCommand(input: string) {
],
});
const command = response.choices[0]?.message?.content?.trim();
command = response.choices[0]?.message?.content?.trim();
} else if (key?.length > 1 && userId?.length > 1) {
const response = await axios.post('https://vvk.ai/api/command', {
input,
key,
userId,
});
command = response.data.command;
} else {
console.log(
"Couldn't generage command: Set your OpenAI API Key with vvk config set openaiApiKey <your_api_key> or log in with vvk login"
);
process.exit(1);
}
if (!command) {
console.log("Couldn't generate a command.");
process.exit(1);

View File

@ -1,10 +1,14 @@
#!/usr/bin/env node
// remove warnings in console
process.removeAllListeners('warning');
import { exec } from 'child_process';
import readline from 'readline';
import { generateCommand } from './generate-command';
import { loadConfig } from './config';
import { configCommand } from './config-command';
import { login, logout } from './login';
const config = loadConfig();
@ -14,15 +18,29 @@ const userInput = args.join(' ');
if (args[0] === 'config') {
configCommand(args);
}
if ((args[0] === '--version' || args[0] === '-v') && args.length === 1) {
console.log('1.0.0');
} else if (args[0] === 'help' && args.length === 1) {
console.log(`
Available commands:
vvk <command> : Generate and execute a command
vvk config set ... : Set configuration options
vvk login : Log in to your account
vvk logout : Log out of your account
vvk --version : Show version
`);
process.exit(0);
}
if (!userInput) {
} else if ((args[0] === '--version' || args[0] === '-v') && args.length === 1) {
console.log('1.0.0');
} else if (args[0] === 'login' && args.length === 1) {
login();
} else if (args[0] === 'logout' && args.length === 1) {
logout();
} else {
if (!userInput) {
console.log('Usage: vvk <your natural language command>');
process.exit(1);
}
processCommand(userInput);
}
// Function to prompt for confirmation
@ -87,5 +105,3 @@ async function processCommand(input: string) {
confirmExecution(command);
}
processCommand(userInput);

66
src/login.ts Normal file
View File

@ -0,0 +1,66 @@
import { randomUUID } from 'crypto';
import axios from 'axios';
import { updateConfig } from './config';
import { spawn } from 'child_process';
import os from 'os';
async function openUrl(url: string) {
const platform = os.platform();
if (platform === 'win32') {
spawn('cmd', ['/c', 'start', url], { detached: true, stdio: 'ignore' });
} else if (platform === 'darwin') {
spawn('open', [url], { detached: true, stdio: 'ignore' });
} else {
spawn('xdg-open', [url], { detached: true, stdio: 'ignore' });
}
}
export async function login() {
// const { default: open } = await import('open');
const loginCode = randomUUID();
const loginUrl = `http://vvk.ai/api/cli-login?c=${loginCode}`;
await openUrl(loginUrl);
console.log('🌐 Please log in via the browser. Waiting for authentication...');
let key, userId;
const startTime = Date.now();
const timeoutMs = 60 * 1000; // 60 seconds timeout
while (!key) {
// Check if timeout has been reached
if (Date.now() - startTime > timeoutMs) {
console.log('❌ Login timed out after 60 seconds. Please try again.');
process.exit(1);
}
await new Promise((res) => setTimeout(res, 3000)); // Wait 3 seconds between polls
try {
const { data } = await axios.get(`https://vvk.ai/api/cli-auth?c=${loginCode}`);
if (data.key && data.userId) {
key = data.key;
userId = data.userId;
}
} catch (err) {
// Ignore errors while waiting
}
}
updateConfig({ key, userId });
console.log('✅ Logged in successfully!');
process.exit(0);
}
export async function logout() {
try {
// Clear user credentials from config
updateConfig({ key: '', userId: '' });
console.log('✅ Logged out successfully!');
process.exit(0);
} catch (err: any) {
console.error('❌ Error during logout:', err.message);
process.exit(1);
}
}