mirror of
https://github.com/codex-storage/cli.git
synced 2025-02-28 16:40:43 +00:00
Merge pull request #5 from codex-storage/feature/tool-config
Feature/tool config
This commit is contained in:
commit
dfec8f7c08
@ -1,11 +1,15 @@
|
|||||||
import { createSpinner } from 'nanospinner';
|
import path from 'path';
|
||||||
import { runCommand } from '../utils/command.js';
|
|
||||||
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
|
||||||
import { checkDependencies, isCodexInstalled } from '../services/nodeService.js';
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { createSpinner } from 'nanospinner';
|
||||||
|
import { runCommand } from '../utils/command.js';
|
||||||
|
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
||||||
|
import { checkDependencies } from '../services/nodeService.js';
|
||||||
|
import { saveConfig } from '../services/config.js';
|
||||||
|
import { getCodexRootPath, getCodexBinPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js';
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
@ -52,19 +56,54 @@ These information will be used for calculating various metrics that can eventual
|
|||||||
return agreement.toLowerCase() === 'y';
|
return agreement.toLowerCase() === 'y';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkCodexInstallation(showNavigationMenu) {
|
export async function getCodexVersion(config) {
|
||||||
|
if (config.codexExe.length < 1) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const version = await runCommand('codex --version');
|
const version = await runCommand(`"${config.codexExe}" --version`);
|
||||||
console.log(chalk.green('Codex is already installed. Version:'));
|
if (version.length < 1) throw new Error("Version info not found.");
|
||||||
console.log(chalk.green(version));
|
return version;
|
||||||
await showNavigationMenu();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...'));
|
return "";
|
||||||
await installCodex(showNavigationMenu);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installCodex(showNavigationMenu) {
|
export async function checkCodexInstallation(config, showNavigationMenu) {
|
||||||
|
const version = await getCodexVersion(config);
|
||||||
|
|
||||||
|
if (version.length > 0) {
|
||||||
|
console.log(chalk.green('Codex is already installed. Version:'));
|
||||||
|
console.log(chalk.green(version));
|
||||||
|
await showNavigationMenu();
|
||||||
|
} else {
|
||||||
|
console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...'));
|
||||||
|
await installCodex(config, showNavigationMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCodexExePathToConfig(config, codexExePath) {
|
||||||
|
config.codexExe = codexExePath;
|
||||||
|
config.dataDir = getCodexDataDirDefaultPath();
|
||||||
|
config.logsDir = getCodexLogsPath();
|
||||||
|
if (!fs.existsSync(config.codexExe)) {
|
||||||
|
console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`));
|
||||||
|
throw new Error("Exe not found");
|
||||||
|
}
|
||||||
|
if (await getCodexVersion(config).length < 1) {
|
||||||
|
console.log(showInfoMessage("no"));
|
||||||
|
throw new Error(`Codex not found at path after install. Path: '${config.codexExe}'`);
|
||||||
|
}
|
||||||
|
saveConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearCodexExePathFromConfig(config) {
|
||||||
|
config.codexExe = "";
|
||||||
|
config.dataDir = "";
|
||||||
|
config.logsDir = "";
|
||||||
|
saveConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function installCodex(config, showNavigationMenu) {
|
||||||
const agreed = await showPrivacyDisclaimer();
|
const agreed = await showPrivacyDisclaimer();
|
||||||
if (!agreed) {
|
if (!agreed) {
|
||||||
console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage'));
|
console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage'));
|
||||||
@ -72,40 +111,25 @@ export async function installCodex(showNavigationMenu) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const installPath = getCodexBinPath();
|
||||||
|
console.log(showInfoMessage("Install location: " + installPath));
|
||||||
|
|
||||||
|
const spinner = createSpinner('Installing Codex...').start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const spinner = createSpinner('Downloading Codex binaries...').start();
|
|
||||||
|
|
||||||
if (platform === 'win32') {
|
if (platform === 'win32') {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
await runCommand('curl --version');
|
await runCommand('curl --version');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.error();
|
|
||||||
throw new Error('curl is not available. Please install curl or update your Windows version.');
|
throw new Error('curl is not available. Please install curl or update your Windows version.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd');
|
await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd');
|
||||||
|
await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`);
|
||||||
|
|
||||||
const currentDir = process.cwd();
|
await saveCodexExePathToConfig(config, path.join(installPath, "codex.exe"));
|
||||||
await runCommand(`"${currentDir}\\install.cmd"`);
|
|
||||||
|
|
||||||
await runCommand('set "PATH=%PATH%;%LOCALAPPDATA%\\Codex"');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runCommand('setx PATH "%PATH%;%LOCALAPPDATA%\\Codex"');
|
|
||||||
spinner.success();
|
|
||||||
console.log(showSuccessMessage('Codex has been installed and PATH has been updated automatically!\n' +
|
|
||||||
`You may need to restart your terminal.`
|
|
||||||
));
|
|
||||||
} catch (error) {
|
|
||||||
spinner.success();
|
|
||||||
console.log(showInfoMessage(
|
|
||||||
'To complete installation:\n\n' +
|
|
||||||
'1. Open Control Panel → System → Advanced System settings → Environment Variables\n' +
|
|
||||||
'2. Or type "environment variables" in Windows Search\n' +
|
|
||||||
'3. Add "%LOCALAPPDATA%\\Codex" to your Path variable'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await runCommand('del /f install.cmd');
|
await runCommand('del /f install.cmd');
|
||||||
@ -113,23 +137,20 @@ export async function installCodex(showNavigationMenu) {
|
|||||||
// Ignore cleanup errors
|
// Ignore cleanup errors
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.error();
|
|
||||||
if (error.message.includes('Access is denied')) {
|
if (error.message.includes('Access is denied')) {
|
||||||
throw new Error('Installation failed. Please run the command prompt as Administrator and try again.');
|
throw new Error('Installation failed. Please run the command prompt as Administrator and try again.');
|
||||||
} else if (error.message.includes('curl')) {
|
} else if (error.message.includes('curl')) {
|
||||||
throw new Error(error.message);
|
throw new Error(error.message);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Installation failed. Please check your internet connection and try again.');
|
throw new Error(`Installation failed: "${error.message}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const dependenciesInstalled = await checkDependencies();
|
const dependenciesInstalled = await checkDependencies();
|
||||||
if (!dependenciesInstalled) {
|
if (!dependenciesInstalled) {
|
||||||
spinner.error();
|
|
||||||
console.log(showInfoMessage('Please install the required dependencies and try again.'));
|
console.log(showInfoMessage('Please install the required dependencies and try again.'));
|
||||||
await showNavigationMenu();
|
throw new Error("Missing dependencies.");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadCommand = 'curl -# --connect-timeout 10 --max-time 60 -L https://get.codex.storage/install.sh -o install.sh && chmod +x install.sh';
|
const downloadCommand = 'curl -# --connect-timeout 10 --max-time 60 -L https://get.codex.storage/install.sh -o install.sh && chmod +x install.sh';
|
||||||
@ -140,19 +161,19 @@ export async function installCodex(showNavigationMenu) {
|
|||||||
eval {
|
eval {
|
||||||
local $SIG{ALRM} = sub { die "timeout\\n" };
|
local $SIG{ALRM} = sub { die "timeout\\n" };
|
||||||
alarm(120);
|
alarm(120);
|
||||||
system("bash install.sh");
|
system("INSTALL_DIR=\\"${installPath}\\" bash install.sh");
|
||||||
alarm(0);
|
alarm(0);
|
||||||
};
|
};
|
||||||
die if $@;
|
die if $@;
|
||||||
'`;
|
'`;
|
||||||
await runCommand(timeoutCommand);
|
await runCommand(timeoutCommand);
|
||||||
} else {
|
} else {
|
||||||
await runCommand('timeout 120 bash install.sh');
|
await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveCodexExePathToConfig(config, path.join(installPath, "codex"));
|
||||||
|
|
||||||
spinner.success();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
spinner.error();
|
|
||||||
if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
|
if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) {
|
||||||
throw new Error('Installation failed. Please check your internet connection and try again.');
|
throw new Error('Installation failed. Please check your internet connection and try again.');
|
||||||
} else if (error.message.includes('Permission denied')) {
|
} else if (error.message.includes('Permission denied')) {
|
||||||
@ -168,28 +189,38 @@ export async function installCodex(showNavigationMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const version = await runCommand('codex --version');
|
const version = await getCodexVersion(config);
|
||||||
console.log(showSuccessMessage(
|
console.log(showSuccessMessage(
|
||||||
'Codex is successfully installed!\n\n' +
|
'Codex is successfully installed!\n' +
|
||||||
|
`Install path: "${config.codexExe}"\n\n` +
|
||||||
`Version: ${version}`
|
`Version: ${version}`
|
||||||
));
|
));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.');
|
throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spinner.success();
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
spinner.error();
|
||||||
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
|
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uninstallCodex(showNavigationMenu) {
|
function removeDir(dir) {
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uninstallCodex(config, showNavigationMenu) {
|
||||||
const { confirm } = await inquirer.prompt([
|
const { confirm } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
name: 'confirm',
|
name: 'confirm',
|
||||||
message: chalk.yellow('⚠️ Are you sure you want to uninstall Codex? This action cannot be undone.'),
|
message: chalk.yellow(
|
||||||
|
'⚠️ Are you sure you want to uninstall Codex? This action cannot be undone. \n' +
|
||||||
|
'All data stored in the local Codex node will be deleted as well.'
|
||||||
|
),
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -201,26 +232,9 @@ export async function uninstallCodex(showNavigationMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (platform === 'win32') {
|
removeDir(getCodexRootPath());
|
||||||
console.log(showInfoMessage('Removing Codex from Windows...'));
|
clearCodexExePathFromConfig(config);
|
||||||
|
|
||||||
await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (TCP-In)"');
|
|
||||||
await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (UDP-In)"');
|
|
||||||
|
|
||||||
await runCommand('rd /s /q "%LOCALAPPDATA%\\Codex"');
|
|
||||||
|
|
||||||
console.log(showInfoMessage(
|
|
||||||
'To complete uninstallation:\n\n' +
|
|
||||||
'1. Open Control Panel → System → Advanced System settings → Environment Variables\n' +
|
|
||||||
'2. Or type "environment variables" in Windows Search\n' +
|
|
||||||
'3. Remove "%LOCALAPPDATA%\\Codex" from your Path variable'
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
const binaryPath = '/usr/local/bin/codex';
|
|
||||||
console.log(showInfoMessage(`Attempting to remove Codex binary from ${binaryPath}...`));
|
|
||||||
await runCommand(`sudo rm ${binaryPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(showSuccessMessage('Codex has been successfully uninstalled.'));
|
console.log(showSuccessMessage('Codex has been successfully uninstalled.'));
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -231,4 +245,4 @@ export async function uninstallCodex(showNavigationMenu) {
|
|||||||
}
|
}
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import path from 'path';
|
||||||
import { createSpinner } from 'nanospinner';
|
import { createSpinner } from 'nanospinner';
|
||||||
import { runCommand } from '../utils/command.js';
|
import { runCommand } from '../utils/command.js';
|
||||||
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
||||||
import { isNodeRunning, isCodexInstalled, logToSupabase, startPeriodicLogging, getWalletAddress, setWalletAddress } from '../services/nodeService.js';
|
import { isNodeRunning, isCodexInstalled, startPeriodicLogging, getWalletAddress, setWalletAddress } from '../services/nodeService.js';
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
@ -27,8 +28,15 @@ async function promptForWalletAddress() {
|
|||||||
return wallet || null;
|
return wallet || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runCodex(showNavigationMenu) {
|
function getCurrentLogFile(config) {
|
||||||
const isInstalled = await isCodexInstalled();
|
const timestamp = new Date().toISOString()
|
||||||
|
.replaceAll(":", "-")
|
||||||
|
.replaceAll(".", "-");
|
||||||
|
return path.join(config.logsDir, `codex_${timestamp}.log`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runCodex(config, showNavigationMenu) {
|
||||||
|
const isInstalled = await isCodexInstalled(config);
|
||||||
if (!isInstalled) {
|
if (!isInstalled) {
|
||||||
console.log(showErrorMessage('Codex is not installed. Please install Codex first using option 1 from the main menu.'));
|
console.log(showErrorMessage('Codex is not installed. Please install Codex first using option 1 from the main menu.'));
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
@ -65,9 +73,19 @@ export async function runCodex(showNavigationMenu) {
|
|||||||
nat = await runCommand('curl -s https://ip.codex.storage');
|
nat = await runCommand('curl -s https://ip.codex.storage');
|
||||||
}
|
}
|
||||||
|
|
||||||
const executable = `codex`;
|
if (config.dataDir.length < 1) throw new Error("Missing config: dataDir");
|
||||||
|
if (config.logsDir.length < 1) throw new Error("Missing config: logsDir");
|
||||||
|
const logFilePath = getCurrentLogFile(config);
|
||||||
|
|
||||||
|
console.log(showInfoMessage(
|
||||||
|
`Data location: ${config.dataDir}\n` +
|
||||||
|
`Logs: ${logFilePath}`
|
||||||
|
));
|
||||||
|
|
||||||
|
const executable = config.codexExe;
|
||||||
const args = [
|
const args = [
|
||||||
`--data-dir=datadir`,
|
`--data-dir="${config.dataDir}"`,
|
||||||
|
`--log-file="${logFilePath}"`,
|
||||||
`--disc-port=${discPort}`,
|
`--disc-port=${discPort}`,
|
||||||
`--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`,
|
`--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`,
|
||||||
`--nat=${nat}`,
|
`--nat=${nat}`,
|
||||||
@ -76,10 +94,11 @@ export async function runCodex(showNavigationMenu) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const command =
|
const command =
|
||||||
`${executable} ${args.join(" ")}`
|
`"${executable}" ${args.join(" ")}`
|
||||||
|
|
||||||
console.log(showInfoMessage(
|
console.log(showInfoMessage(
|
||||||
'🚀 Codex node is running...\n\n' +
|
'🚀 Codex node is running...\n\n' +
|
||||||
|
'If your firewall ask, be sure to allow Codex to receive connections. \n' +
|
||||||
'Please keep this terminal open. Start a new terminal to interact with the node.\n\n' +
|
'Please keep this terminal open. Start a new terminal to interact with the node.\n\n' +
|
||||||
'Press CTRL+C to stop the node'
|
'Press CTRL+C to stop the node'
|
||||||
));
|
));
|
||||||
|
@ -9,6 +9,7 @@ import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandler
|
|||||||
import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js';
|
import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js';
|
||||||
import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js';
|
import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js';
|
||||||
import { showInfoMessage } from './utils/messages.js';
|
import { showInfoMessage } from './utils/messages.js';
|
||||||
|
import { loadConfig } from './services/config.js';
|
||||||
|
|
||||||
async function showNavigationMenu() {
|
async function showNavigationMenu() {
|
||||||
console.log('\n')
|
console.log('\n')
|
||||||
@ -67,9 +68,9 @@ export async function main() {
|
|||||||
process.on('SIGQUIT', handleExit);
|
process.on('SIGQUIT', handleExit);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const config = loadConfig();
|
||||||
while (true) {
|
while (true) {
|
||||||
console.log('\n' + chalk.cyanBright(ASCII_ART));
|
console.log('\n' + chalk.cyanBright(ASCII_ART));
|
||||||
|
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: 'list',
|
||||||
@ -101,10 +102,10 @@ export async function main() {
|
|||||||
|
|
||||||
switch (choice.split('.')[0]) {
|
switch (choice.split('.')[0]) {
|
||||||
case '1':
|
case '1':
|
||||||
await checkCodexInstallation(showNavigationMenu);
|
await checkCodexInstallation(config, showNavigationMenu);
|
||||||
break;
|
break;
|
||||||
case '2':
|
case '2':
|
||||||
await runCodex(showNavigationMenu);
|
await runCodex(config, showNavigationMenu);
|
||||||
return;
|
return;
|
||||||
case '3':
|
case '3':
|
||||||
await checkNodeStatus(showNavigationMenu);
|
await checkNodeStatus(showNavigationMenu);
|
||||||
@ -119,7 +120,7 @@ export async function main() {
|
|||||||
await showLocalFiles(showNavigationMenu);
|
await showLocalFiles(showNavigationMenu);
|
||||||
break;
|
break;
|
||||||
case '7':
|
case '7':
|
||||||
await uninstallCodex(showNavigationMenu);
|
await uninstallCodex(config, showNavigationMenu);
|
||||||
break;
|
break;
|
||||||
case '8':
|
case '8':
|
||||||
const { exec } = await import('child_process');
|
const { exec } = await import('child_process');
|
||||||
|
46
src/services/config.js
Normal file
46
src/services/config.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { getAppDataDir } from '../utils/appdata.js';
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
codexExe: "",
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Save user-selected config options. Use these when starting Codex.
|
||||||
|
dataDir: "",
|
||||||
|
logsDir: ""
|
||||||
|
// storageQuota: 0,
|
||||||
|
// ports: {
|
||||||
|
// discPort: 8090,
|
||||||
|
// listenPort: 8070,
|
||||||
|
// apiPort: 8080
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
function getConfigFilename() {
|
||||||
|
return path.join(getAppDataDir(), "config.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveConfig(config) {
|
||||||
|
const filePath = getConfigFilename();
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(config));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to save config file to '${filePath}' error: '${error}'.`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadConfig() {
|
||||||
|
const filePath = getConfigFilename();
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
saveConfig(defaultConfig);
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
return JSON.parse(fs.readFileSync(filePath));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load config file from '${filePath}' error: '${error}'.`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||||||
import { runCommand } from '../utils/command.js';
|
import { runCommand } from '../utils/command.js';
|
||||||
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
import { getCodexVersion } from '../handlers/installationHandlers.js';
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
@ -29,10 +30,10 @@ export async function isNodeRunning() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isCodexInstalled() {
|
export async function isCodexInstalled(config) {
|
||||||
try {
|
try {
|
||||||
await runCommand('codex --version');
|
const version = await getCodexVersion(config);
|
||||||
return true;
|
return version.length > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
54
src/utils/appdata.js
Normal file
54
src/utils/appdata.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export function getAppDataDir() {
|
||||||
|
return ensureExists(appData("codex-cli"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodexRootPath() {
|
||||||
|
return ensureExists(appData("codex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodexBinPath() {
|
||||||
|
return ensureExists(path.join(appData("codex"), "bin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodexDataDirDefaultPath() {
|
||||||
|
// This path does not exist on first startup. That's good: Codex will
|
||||||
|
// create it with the required access permissions.
|
||||||
|
return path.join(appData("codex"), "datadir");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCodexLogsPath() {
|
||||||
|
return ensureExists(path.join(appData("codex"), "logs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExists(dir) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appData(...app) {
|
||||||
|
let appData;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
appData = path.join(process.env.APPDATA, ...app);
|
||||||
|
} else if (process.platform === 'darwin') {
|
||||||
|
appData = path.join(process.env.HOME, 'Library', 'Application Support', ...app);
|
||||||
|
} else {
|
||||||
|
appData = path.join(process.env.HOME, ...prependDot(...app));
|
||||||
|
}
|
||||||
|
return appData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prependDot(...app) {
|
||||||
|
return app.map((item, i) => {
|
||||||
|
if (i === 0) {
|
||||||
|
return `.${item}`;
|
||||||
|
} else {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
169
src/utils/pathSelector.js
Normal file
169
src/utils/pathSelector.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import inquirer from 'inquirer';
|
||||||
|
import boxen from 'boxen';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
function showMsg(msg) {
|
||||||
|
console.log(boxen(chalk.white(msg), {
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: 'round',
|
||||||
|
borderColor: 'white',
|
||||||
|
titleAlignment: 'center'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitPath(str) {
|
||||||
|
return str.replaceAll("\\", "/").split("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCurrent(currentPath) {
|
||||||
|
const len = currentPath.length;
|
||||||
|
showMsg(`Current path: [${len}]\n` + path.join(...currentPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showMain(currentPath) {
|
||||||
|
showCurrent(currentPath);
|
||||||
|
const { choice } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'choice',
|
||||||
|
message: 'Select an option:',
|
||||||
|
choices: [
|
||||||
|
'1. Enter path',
|
||||||
|
'2. Go up one',
|
||||||
|
'3. Go down one',
|
||||||
|
'4. Check path exists',
|
||||||
|
'5. Create new folder here',
|
||||||
|
'6. Select this path',
|
||||||
|
'7. Cancel'
|
||||||
|
],
|
||||||
|
pageSize: 6,
|
||||||
|
loop: true
|
||||||
|
}
|
||||||
|
]).catch(() => {
|
||||||
|
handleExit();
|
||||||
|
return { choice: '6' };
|
||||||
|
});
|
||||||
|
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function selectPath() {
|
||||||
|
var currentPath = splitPath(process.cwd());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const choice = await showMain(currentPath);
|
||||||
|
|
||||||
|
switch (choice.split('.')[0]) {
|
||||||
|
case '1':
|
||||||
|
currentPath = await enterPath();
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
currentPath = upOne(currentPath);
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
currentPath = await downOne(currentPath);
|
||||||
|
break;
|
||||||
|
case '4':
|
||||||
|
await checkExists(currentPath);
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
currentPath = await createSubDir(currentPath);
|
||||||
|
break;
|
||||||
|
case '6':
|
||||||
|
if (!isDir(currentPath)) {
|
||||||
|
console.log("Current path does not exist.");
|
||||||
|
} else {
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
case '7':
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enterPath() {
|
||||||
|
const response = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'path',
|
||||||
|
message: 'Enter Path:'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return splitPath(response.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function upOne(currentPath) {
|
||||||
|
return currentPath.slice(0, currentPath.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDir(dir) {
|
||||||
|
return fs.lstatSync(dir).isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSubDir(currentPath, entry) {
|
||||||
|
const newPath = path.join(...currentPath, entry);
|
||||||
|
return isDir(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubDirOptions(currentPath) {
|
||||||
|
const entries = fs.readdirSync(path.join(...currentPath));
|
||||||
|
var result = [];
|
||||||
|
var counter = 1;
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (isSubDir(currentPath, entry)) {
|
||||||
|
result.push(counter + ". " + entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downOne(currentPath) {
|
||||||
|
const options = getSubDirOptions(currentPath);
|
||||||
|
if (options.length == 0) {
|
||||||
|
console.log("There are no subdirectories here.");
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { choice } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'choice',
|
||||||
|
message: 'Select an subdir:',
|
||||||
|
choices: options,
|
||||||
|
pageSize: options.length,
|
||||||
|
loop: true
|
||||||
|
}
|
||||||
|
]).catch(() => {
|
||||||
|
return currentPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
const subDir = choice.slice(3);
|
||||||
|
return [...currentPath, subDir];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkExists(currentPath) {
|
||||||
|
if (!isDir(path.join(...currentPath))) {
|
||||||
|
console.log("Current path does not exist.");
|
||||||
|
} else{
|
||||||
|
console.log("Current path exists.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSubDir(currentPath) {
|
||||||
|
const response = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'name',
|
||||||
|
message: 'Enter name:'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const name = response.name;
|
||||||
|
if (name.length < 1) return;
|
||||||
|
|
||||||
|
const fullDir = path.join(...currentPath, name);
|
||||||
|
fs.mkdirSync(fullDir);
|
||||||
|
return [...currentPath, name];
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user