From 9a1ce2bf52cfd5eeb90be03f2a23374b18bfb811 Mon Sep 17 00:00:00 2001 From: thatben Date: Fri, 14 Feb 2025 11:56:16 +0100 Subject: [PATCH 01/14] wip --- src/main.js | 6 ++++++ src/utils/appdata.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/utils/appdata.js diff --git a/src/main.js b/src/main.js index c93c116..ee09762 100644 --- a/src/main.js +++ b/src/main.js @@ -9,6 +9,7 @@ import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandler import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js'; import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; +import { getAppDataDir } from "./utils/appdata.js"; async function showNavigationMenu() { console.log('\n') @@ -49,6 +50,10 @@ function handleExit() { process.exit(0); } +function getWorkingDir(commandArgs) { + +} + export async function main() { const commandArgs = parseCommandLineArgs(); if (commandArgs) { @@ -69,6 +74,7 @@ export async function main() { try { while (true) { console.log('\n' + chalk.cyanBright(ASCII_ART)); + console.log(showInfoMessage(`Working directory: ${getAppDataDir()}`)); const { choice } = await inquirer.prompt([ { diff --git a/src/utils/appdata.js b/src/utils/appdata.js new file mode 100644 index 0000000..24b1d99 --- /dev/null +++ b/src/utils/appdata.js @@ -0,0 +1,28 @@ +import path from 'path'; + +export function getAppDataDir() { + return appData("codex-cli"); +} + +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; + } + }); +} + From 2a8888669a33eeb150f67beefbcc96298b67782d Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 09:39:45 +0100 Subject: [PATCH 02/14] Adds config module --- src/main.js | 3 --- src/services/config.js | 43 ++++++++++++++++++++++++++++++++++++++++++ src/utils/appdata.js | 7 ++++++- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/services/config.js diff --git a/src/main.js b/src/main.js index ee09762..80483fa 100644 --- a/src/main.js +++ b/src/main.js @@ -9,7 +9,6 @@ import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandler import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js'; import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; -import { getAppDataDir } from "./utils/appdata.js"; async function showNavigationMenu() { console.log('\n') @@ -74,8 +73,6 @@ export async function main() { try { while (true) { console.log('\n' + chalk.cyanBright(ASCII_ART)); - console.log(showInfoMessage(`Working directory: ${getAppDataDir()}`)); - const { choice } = await inquirer.prompt([ { type: 'list', diff --git a/src/services/config.js b/src/services/config.js new file mode 100644 index 0000000..13a6944 --- /dev/null +++ b/src/services/config.js @@ -0,0 +1,43 @@ +import fs from 'fs'; +import path from 'path'; +import { getAppDataDir } from '../utils/appdata.js'; + +const defaultConfig = { + dataDir: "", + storageQuota: 0, + ports: { + discPort: 8090, + listenPort: 8070, + apiPort: 8080 + } +}; + +function getConfigFilename() { + return path.join(getAppDataDir(), "config.json"); +} + +export function saveConfig(config) { + const filePath = getConfigFilename(); + console.log("writing to: " + filePath ); + 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(); + console.log("loading from: " + filePath ); + 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; + } +} diff --git a/src/utils/appdata.js b/src/utils/appdata.js index 24b1d99..14dbcb5 100644 --- a/src/utils/appdata.js +++ b/src/utils/appdata.js @@ -1,7 +1,12 @@ import path from 'path'; +import fs from 'fs'; export function getAppDataDir() { - return appData("codex-cli"); + const dir = appData("codex-cli"); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + return dir; } function appData(...app) { From 34121d9d1a422f7cf91eae94bba668fd5f86d1fd Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 12:23:48 +0100 Subject: [PATCH 03/14] working windows setup --- src/handlers/installationHandlers.js | 89 +++++++++++++++------------- src/main.js | 4 +- src/services/config.js | 20 ++++--- 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 6616416..6236d70 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -1,11 +1,14 @@ -import { createSpinner } from 'nanospinner'; -import { runCommand } from '../utils/command.js'; -import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js'; -import { checkDependencies, isCodexInstalled } from '../services/nodeService.js'; +import path from 'path'; import inquirer from 'inquirer'; import boxen from 'boxen'; import chalk from 'chalk'; 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, isCodexInstalled } from '../services/nodeService.js'; +import { saveConfig } from '../services/config.js'; const platform = os.platform(); @@ -52,19 +55,43 @@ These information will be used for calculating various metrics that can eventual return agreement.toLowerCase() === 'y'; } -export async function checkCodexInstallation(showNavigationMenu) { +async function getCodexVersion(config) { try { - const version = await runCommand('codex --version'); - console.log(chalk.green('Codex is already installed. Version:')); - console.log(chalk.green(version)); - await showNavigationMenu(); + const version = await runCommand(`${config.codexExe} --version`); + if (version.length < 1) throw new Error("Version info not found."); + return version; } catch (error) { - console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...')); - await installCodex(showNavigationMenu); + return ""; } } -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) { + config.codexExe = path.join(process.env.LOCALAPPDATA, "Codex", "codex.exe"); + 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); +} + +export async function installCodex(config, showNavigationMenu) { const agreed = await showPrivacyDisclaimer(); if (!agreed) { console.log(showInfoMessage('You can find manual setup instructions at docs.codex.storage')); @@ -72,40 +99,22 @@ export async function installCodex(showNavigationMenu) { return; } + const spinner = createSpinner('Installing Codex...').start(); + try { - const spinner = createSpinner('Downloading Codex binaries...').start(); if (platform === 'win32') { try { try { await runCommand('curl --version'); } catch (error) { - spinner.error(); 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(`"${process.cwd()}\\install.cmd"`); - const currentDir = process.cwd(); - 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' - )); - } + await saveCodexExePathToConfig(config); try { await runCommand('del /f install.cmd'); @@ -113,20 +122,18 @@ export async function installCodex(showNavigationMenu) { // Ignore cleanup errors } } catch (error) { - spinner.error(); if (error.message.includes('Access is denied')) { throw new Error('Installation failed. Please run the command prompt as Administrator and try again.'); } else if (error.message.includes('curl')) { throw new Error(error.message); } else { - throw new Error('Installation failed. Please check your internet connection and try again.'); + throw new Error(`Installation failed: "${error.message}"`); } } } else { try { const dependenciesInstalled = await checkDependencies(); if (!dependenciesInstalled) { - spinner.error(); console.log(showInfoMessage('Please install the required dependencies and try again.')); await showNavigationMenu(); return; @@ -150,9 +157,7 @@ export async function installCodex(showNavigationMenu) { await runCommand('timeout 120 bash install.sh'); } - spinner.success(); } catch (error) { - spinner.error(); if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { throw new Error('Installation failed. Please check your internet connection and try again.'); } else if (error.message.includes('Permission denied')) { @@ -168,7 +173,7 @@ export async function installCodex(showNavigationMenu) { } try { - const version = await runCommand('codex --version'); + const version = await getCodexVersion(config); console.log(showSuccessMessage( 'Codex is successfully installed!\n\n' + `Version: ${version}` @@ -177,8 +182,10 @@ export async function installCodex(showNavigationMenu) { throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.'); } + spinner.success(); await showNavigationMenu(); } catch (error) { + spinner.error(); console.log(showErrorMessage(`Failed to install Codex: ${error.message}`)); await showNavigationMenu(); } @@ -231,4 +238,4 @@ export async function uninstallCodex(showNavigationMenu) { } await showNavigationMenu(); } -} \ No newline at end of file +} diff --git a/src/main.js b/src/main.js index 80483fa..0759928 100644 --- a/src/main.js +++ b/src/main.js @@ -9,6 +9,7 @@ import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandler import { checkCodexInstallation, installCodex, uninstallCodex } from './handlers/installationHandlers.js'; import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js'; import { showInfoMessage } from './utils/messages.js'; +import { loadConfig } from './services/config.js'; async function showNavigationMenu() { console.log('\n') @@ -71,6 +72,7 @@ export async function main() { process.on('SIGQUIT', handleExit); try { + const config = loadConfig(); while (true) { console.log('\n' + chalk.cyanBright(ASCII_ART)); const { choice } = await inquirer.prompt([ @@ -104,7 +106,7 @@ export async function main() { switch (choice.split('.')[0]) { case '1': - await checkCodexInstallation(showNavigationMenu); + await checkCodexInstallation(config, showNavigationMenu); break; case '2': await runCodex(showNavigationMenu); diff --git a/src/services/config.js b/src/services/config.js index 13a6944..70ae156 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -3,13 +3,17 @@ import path from 'path'; import { getAppDataDir } from '../utils/appdata.js'; const defaultConfig = { - dataDir: "", - storageQuota: 0, - ports: { - discPort: 8090, - listenPort: 8070, - apiPort: 8080 - } + codexExe: "", + + // TODO: + // Save user-selected config options. Use these when starting Codex. + // dataDir: "", + // storageQuota: 0, + // ports: { + // discPort: 8090, + // listenPort: 8070, + // apiPort: 8080 + // } }; function getConfigFilename() { @@ -18,7 +22,6 @@ function getConfigFilename() { export function saveConfig(config) { const filePath = getConfigFilename(); - console.log("writing to: " + filePath ); try { fs.writeFileSync(filePath, JSON.stringify(config)); } catch (error) { @@ -29,7 +32,6 @@ export function saveConfig(config) { export function loadConfig() { const filePath = getConfigFilename(); - console.log("loading from: " + filePath ); try { if (!fs.existsSync(filePath)) { saveConfig(defaultConfig); From 771888f427c0167b2193f8ab5947ec8d7e879c18 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 12:35:54 +0100 Subject: [PATCH 04/14] Runs codex from correct path --- src/handlers/installationHandlers.js | 7 +++++-- src/handlers/nodeHandlers.js | 7 ++++--- src/main.js | 2 +- src/services/nodeService.js | 7 ++++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 6236d70..7fdceb2 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -55,7 +55,9 @@ These information will be used for calculating various metrics that can eventual return agreement.toLowerCase() === 'y'; } -async function getCodexVersion(config) { +export async function getCodexVersion(config) { + if (config.codexExe.length < 1) return ""; + try { const version = await runCommand(`${config.codexExe} --version`); if (version.length < 1) throw new Error("Version info not found."); @@ -175,7 +177,8 @@ export async function installCodex(config, showNavigationMenu) { try { const version = await getCodexVersion(config); console.log(showSuccessMessage( - 'Codex is successfully installed!\n\n' + + 'Codex is successfully installed!\n' + + `Install path: "${config.codexExe}"\n\n` + `Version: ${version}` )); } catch (error) { diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index 5988935..1a74047 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -27,8 +27,8 @@ async function promptForWalletAddress() { return wallet || null; } -export async function runCodex(showNavigationMenu) { - const isInstalled = await isCodexInstalled(); +export async function runCodex(config, showNavigationMenu) { + const isInstalled = await isCodexInstalled(config); if (!isInstalled) { console.log(showErrorMessage('Codex is not installed. Please install Codex first using option 1 from the main menu.')); await showNavigationMenu(); @@ -65,7 +65,7 @@ export async function runCodex(showNavigationMenu) { nat = await runCommand('curl -s https://ip.codex.storage'); } - const executable = `codex`; + const executable = config.codexExe; const args = [ `--data-dir=datadir`, `--disc-port=${discPort}`, @@ -80,6 +80,7 @@ export async function runCodex(showNavigationMenu) { console.log(showInfoMessage( '🚀 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' + 'Press CTRL+C to stop the node' )); diff --git a/src/main.js b/src/main.js index 0759928..e43505e 100644 --- a/src/main.js +++ b/src/main.js @@ -109,7 +109,7 @@ export async function main() { await checkCodexInstallation(config, showNavigationMenu); break; case '2': - await runCodex(showNavigationMenu); + await runCodex(config, showNavigationMenu); return; case '3': await checkNodeStatus(showNavigationMenu); diff --git a/src/services/nodeService.js b/src/services/nodeService.js index bba61ff..810760b 100644 --- a/src/services/nodeService.js +++ b/src/services/nodeService.js @@ -2,6 +2,7 @@ import axios from 'axios'; import { runCommand } from '../utils/command.js'; import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js'; import os from 'os'; +import { getCodexVersion } from '../handlers/installationHandlers.js'; const platform = os.platform(); @@ -29,10 +30,10 @@ export async function isNodeRunning() { } } -export async function isCodexInstalled() { +export async function isCodexInstalled(config) { try { - await runCommand('codex --version'); - return true; + const version = await getCodexVersion(config); + return version.length > 0; } catch (error) { return false; } From 27bd79d4b11d346cf2260f41e383b5ba6a08827b Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 12:41:19 +0100 Subject: [PATCH 05/14] attempt to fix install path for non-windows --- src/handlers/installationHandlers.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 7fdceb2..b37afc5 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -80,8 +80,8 @@ export async function checkCodexInstallation(config, showNavigationMenu) { } } -async function saveCodexExePathToConfig(config) { - config.codexExe = path.join(process.env.LOCALAPPDATA, "Codex", "codex.exe"); +async function saveCodexExePathToConfig(config, codexExePath) { + config.codexExe = codexExePath; if (!fs.existsSync(config.codexExe)) { console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`)); throw new Error("Exe not found"); @@ -116,7 +116,7 @@ export async function installCodex(config, showNavigationMenu) { await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd'); await runCommand(`"${process.cwd()}\\install.cmd"`); - await saveCodexExePathToConfig(config); + await saveCodexExePathToConfig(config, path.join(process.env.LOCALAPPDATA, "Codex", "codex.exe")); try { await runCommand('del /f install.cmd'); @@ -158,6 +158,8 @@ export async function installCodex(config, showNavigationMenu) { } else { await runCommand('timeout 120 bash install.sh'); } + + await saveCodexExePathToConfig(config, path.join(process.env.LOCALAPPDATA, "Codex", "codex")); } catch (error) { if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { From 90d1c951e1e08bea1195d70a0fa9e7997cd89b4a Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 13:20:22 +0100 Subject: [PATCH 06/14] fixes trailing newline --- src/handlers/installationHandlers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index b37afc5..52781bd 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -159,7 +159,8 @@ export async function installCodex(config, showNavigationMenu) { await runCommand('timeout 120 bash install.sh'); } - await saveCodexExePathToConfig(config, path.join(process.env.LOCALAPPDATA, "Codex", "codex")); + const codexExePath = (await runCommand("which codex")).replace("\n", ""); + await saveCodexExePathToConfig(config, codexExePath); } catch (error) { if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { From e66bd9643e6e5144ce79b76697ea76c64891766a Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 13:34:48 +0100 Subject: [PATCH 07/14] cleanup --- src/main.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main.js b/src/main.js index e43505e..e42c9ba 100644 --- a/src/main.js +++ b/src/main.js @@ -50,10 +50,6 @@ function handleExit() { process.exit(0); } -function getWorkingDir(commandArgs) { - -} - export async function main() { const commandArgs = parseCommandLineArgs(); if (commandArgs) { From 0379c7c57396831614a627aad1c4c9deee213416 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Feb 2025 15:13:47 +0100 Subject: [PATCH 08/14] implements path selector --- src/handlers/installationHandlers.js | 3 +- src/utils/pathSelector.js | 169 +++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/utils/pathSelector.js diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 52781bd..2a85638 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -137,8 +137,7 @@ export async function installCodex(config, showNavigationMenu) { const dependenciesInstalled = await checkDependencies(); if (!dependenciesInstalled) { console.log(showInfoMessage('Please install the required dependencies and try again.')); - await showNavigationMenu(); - return; + throw new Error("Missing dependencies."); } const downloadCommand = 'curl -# --connect-timeout 10 --max-time 60 -L https://get.codex.storage/install.sh -o install.sh && chmod +x install.sh'; diff --git a/src/utils/pathSelector.js b/src/utils/pathSelector.js new file mode 100644 index 0000000..172734f --- /dev/null +++ b/src/utils/pathSelector.js @@ -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]; +} From e97493c183a74faa8b3ea40e228a32690efa2f20 Mon Sep 17 00:00:00 2001 From: thatben Date: Tue, 18 Feb 2025 13:30:12 +0100 Subject: [PATCH 09/14] controls Codex install location --- src/handlers/installationHandlers.js | 17 ++++++++++------- src/utils/appdata.js | 10 +++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 2a85638..9aad90a 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -7,8 +7,9 @@ import fs from 'fs'; import { createSpinner } from 'nanospinner'; import { runCommand } from '../utils/command.js'; import { showErrorMessage, showInfoMessage, showSuccessMessage } from '../utils/messages.js'; -import { checkDependencies, isCodexInstalled } from '../services/nodeService.js'; +import { checkDependencies } from '../services/nodeService.js'; import { saveConfig } from '../services/config.js'; +import { getCodexInstallPath } from '../utils/appdata.js'; const platform = os.platform(); @@ -101,6 +102,9 @@ export async function installCodex(config, showNavigationMenu) { return; } + const installPath = getCodexInstallPath(); + console.log(showInfoMessage("Install location: " + installPath)); + const spinner = createSpinner('Installing Codex...').start(); try { @@ -114,9 +118,9 @@ export async function installCodex(config, showNavigationMenu) { } await runCommand('curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd'); - await runCommand(`"${process.cwd()}\\install.cmd"`); + await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`); - await saveCodexExePathToConfig(config, path.join(process.env.LOCALAPPDATA, "Codex", "codex.exe")); + await saveCodexExePathToConfig(config, path.join(installPath, "codex.exe")); try { await runCommand('del /f install.cmd'); @@ -148,18 +152,17 @@ export async function installCodex(config, showNavigationMenu) { eval { local $SIG{ALRM} = sub { die "timeout\\n" }; alarm(120); - system("bash install.sh"); + system("INSTALL_DIR="${installPath}" bash install.sh"); alarm(0); }; die if $@; '`; await runCommand(timeoutCommand); } else { - await runCommand('timeout 120 bash install.sh'); + await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`); } - const codexExePath = (await runCommand("which codex")).replace("\n", ""); - await saveCodexExePathToConfig(config, codexExePath); + await saveCodexExePathToConfig(config, path.join(installPath, "codex")); } catch (error) { if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT')) { diff --git a/src/utils/appdata.js b/src/utils/appdata.js index 14dbcb5..b277ac6 100644 --- a/src/utils/appdata.js +++ b/src/utils/appdata.js @@ -2,7 +2,15 @@ import path from 'path'; import fs from 'fs'; export function getAppDataDir() { - const dir = appData("codex-cli"); + return getExists("codex-cli"); +} + +export function getCodexInstallPath() { + return getExists("codex"); +} + +function getExists(appName) { + const dir = appData(appName); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } From ba2aae04a949e2911fd1f280247dd212cd01bb32 Mon Sep 17 00:00:00 2001 From: thatben Date: Tue, 18 Feb 2025 14:26:27 +0100 Subject: [PATCH 10/14] Sets dataDir and logDir in config. Defaults to user-accessible folder. --- src/handlers/installationHandlers.js | 4 +++- src/handlers/nodeHandlers.js | 22 ++++++++++++++++++++-- src/services/config.js | 3 ++- src/utils/appdata.js | 19 ++++++++++++++----- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 9aad90a..65f7021 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -9,7 +9,7 @@ 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 { getCodexInstallPath } from '../utils/appdata.js'; +import { getCodexInstallPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js'; const platform = os.platform(); @@ -83,6 +83,8 @@ export async function checkCodexInstallation(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"); diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index 1a74047..ce3073e 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -1,7 +1,8 @@ +import path from 'path'; import { createSpinner } from 'nanospinner'; import { runCommand } from '../utils/command.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 boxen from 'boxen'; import chalk from 'chalk'; @@ -27,6 +28,13 @@ async function promptForWalletAddress() { return wallet || null; } +function getCurrentLogFile(config) { + 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) { @@ -65,9 +73,19 @@ export async function runCodex(config, showNavigationMenu) { nat = await runCommand('curl -s https://ip.codex.storage'); } + 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 = [ - `--data-dir=datadir`, + `--data-dir=${config.dataDir}`, + `--log-file=${logFilePath}`, `--disc-port=${discPort}`, `--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`, `--nat=${nat}`, diff --git a/src/services/config.js b/src/services/config.js index 70ae156..7bc2f4a 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -7,7 +7,8 @@ const defaultConfig = { // TODO: // Save user-selected config options. Use these when starting Codex. - // dataDir: "", + dataDir: "", + logsDir: "" // storageQuota: 0, // ports: { // discPort: 8090, diff --git a/src/utils/appdata.js b/src/utils/appdata.js index b277ac6..2860ec5 100644 --- a/src/utils/appdata.js +++ b/src/utils/appdata.js @@ -2,17 +2,26 @@ import path from 'path'; import fs from 'fs'; export function getAppDataDir() { - return getExists("codex-cli"); + return ensureExists(appData("codex-cli")); } export function getCodexInstallPath() { - return getExists("codex"); + return ensureExists(path.join(appData("codex"), "bin")); } -function getExists(appName) { - const dir = appData(appName); +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); + fs.mkdirSync(dir, { recursive: true }); } return dir; } From e2f4c9c85e25fc0b4da43bece1b37f9359e922d8 Mon Sep 17 00:00:00 2001 From: thatben Date: Tue, 18 Feb 2025 15:01:07 +0100 Subject: [PATCH 11/14] Fixes uninstall on Windows --- src/handlers/installationHandlers.js | 45 +++++++++++++--------------- src/main.js | 2 +- src/utils/appdata.js | 6 +++- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 65f7021..56766c0 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -9,7 +9,7 @@ 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 { getCodexInstallPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js'; +import { getCodexRootPath, getCodexBinPath, getCodexDataDirDefaultPath, getCodexLogsPath } from '../utils/appdata.js'; const platform = os.platform(); @@ -96,6 +96,13 @@ async function saveCodexExePathToConfig(config, codexExePath) { saveConfig(config); } +async function clearCodexExePathFromConfig(config) { + config.codexExe = ""; + config.dataDir = ""; + config.logsDir = ""; + saveConfig(config); +} + export async function installCodex(config, showNavigationMenu) { const agreed = await showPrivacyDisclaimer(); if (!agreed) { @@ -104,7 +111,7 @@ export async function installCodex(config, showNavigationMenu) { return; } - const installPath = getCodexInstallPath(); + const installPath = getCodexBinPath(); console.log(showInfoMessage("Install location: " + installPath)); const spinner = createSpinner('Installing Codex...').start(); @@ -201,12 +208,19 @@ export async function installCodex(config, 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([ { type: '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 } ]); @@ -218,26 +232,9 @@ export async function uninstallCodex(showNavigationMenu) { } try { - if (platform === 'win32') { - console.log(showInfoMessage('Removing Codex from Windows...')); - - 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}`); - } - + removeDir(getCodexRootPath()); + clearCodexExePathFromConfig(config); + console.log(showSuccessMessage('Codex has been successfully uninstalled.')); await showNavigationMenu(); } catch (error) { diff --git a/src/main.js b/src/main.js index e42c9ba..9997f79 100644 --- a/src/main.js +++ b/src/main.js @@ -120,7 +120,7 @@ export async function main() { await showLocalFiles(showNavigationMenu); break; case '7': - await uninstallCodex(showNavigationMenu); + await uninstallCodex(config, showNavigationMenu); break; case '8': const { exec } = await import('child_process'); diff --git a/src/utils/appdata.js b/src/utils/appdata.js index 2860ec5..1ff6059 100644 --- a/src/utils/appdata.js +++ b/src/utils/appdata.js @@ -5,7 +5,11 @@ export function getAppDataDir() { return ensureExists(appData("codex-cli")); } -export function getCodexInstallPath() { +export function getCodexRootPath() { + return ensureExists(appData("codex")); +} + +export function getCodexBinPath() { return ensureExists(path.join(appData("codex"), "bin")); } From 5b8d023979486be758c31e8a880ea6a986f00d9a Mon Sep 17 00:00:00 2001 From: thatben Date: Tue, 18 Feb 2025 16:19:51 +0100 Subject: [PATCH 12/14] escapes install path in mac command --- src/handlers/installationHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 56766c0..011cb1c 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -161,7 +161,7 @@ export async function installCodex(config, showNavigationMenu) { eval { local $SIG{ALRM} = sub { die "timeout\\n" }; alarm(120); - system("INSTALL_DIR="${installPath}" bash install.sh"); + system("INSTALL_DIR=\"${installPath}\" bash install.sh"); alarm(0); }; die if $@; From 28c509ae5f81761a9a690042f4c7f5960ed4e364 Mon Sep 17 00:00:00 2001 From: thatben Date: Wed, 19 Feb 2025 12:45:39 +0100 Subject: [PATCH 13/14] double escapes the install path quotes for macos --- src/handlers/installationHandlers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index 011cb1c..dd0326f 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -161,7 +161,7 @@ export async function installCodex(config, showNavigationMenu) { eval { local $SIG{ALRM} = sub { die "timeout\\n" }; alarm(120); - system("INSTALL_DIR=\"${installPath}\" bash install.sh"); + system("INSTALL_DIR=\\"${installPath}\\" bash install.sh"); alarm(0); }; die if $@; From 91fa0258645a2b45ad2556e8d0e0f9c9c013779d Mon Sep 17 00:00:00 2001 From: thatben Date: Wed, 19 Feb 2025 13:18:22 +0100 Subject: [PATCH 14/14] Adds quotes to fix paths with spaces --- src/handlers/installationHandlers.js | 2 +- src/handlers/nodeHandlers.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handlers/installationHandlers.js b/src/handlers/installationHandlers.js index dd0326f..9141bc0 100644 --- a/src/handlers/installationHandlers.js +++ b/src/handlers/installationHandlers.js @@ -60,7 +60,7 @@ export async function getCodexVersion(config) { if (config.codexExe.length < 1) return ""; try { - const version = await runCommand(`${config.codexExe} --version`); + const version = await runCommand(`"${config.codexExe}" --version`); if (version.length < 1) throw new Error("Version info not found."); return version; } catch (error) { diff --git a/src/handlers/nodeHandlers.js b/src/handlers/nodeHandlers.js index ce3073e..b6dc09e 100644 --- a/src/handlers/nodeHandlers.js +++ b/src/handlers/nodeHandlers.js @@ -84,8 +84,8 @@ export async function runCodex(config, showNavigationMenu) { const executable = config.codexExe; const args = [ - `--data-dir=${config.dataDir}`, - `--log-file=${logFilePath}`, + `--data-dir="${config.dataDir}"`, + `--log-file="${logFilePath}"`, `--disc-port=${discPort}`, `--listen-addrs=/ip4/0.0.0.0/tcp/${listenPort}`, `--nat=${nat}`, @@ -94,7 +94,7 @@ export async function runCodex(config, showNavigationMenu) { ]; const command = - `${executable} ${args.join(" ")}` + `"${executable}" ${args.join(" ")}` console.log(showInfoMessage( '🚀 Codex node is running...\n\n' +