mirror of
https://github.com/logos-storage/logos-storage-installer.git
synced 2026-01-07 16:03:07 +00:00
Sets up formatting and main
This commit is contained in:
parent
a2467e3ff1
commit
ef589cf085
1302
package-lock.json
generated
1302
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -8,13 +8,19 @@
|
|||||||
"codexstorage": "./index.js"
|
"codexstorage": "./index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js"
|
"start": "node index.js",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"format": "prettier --write ./src"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"codex",
|
"codex",
|
||||||
"storage",
|
"storage",
|
||||||
"cli"
|
"cli"
|
||||||
],
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
"author": "Codex Storage",
|
"author": "Codex Storage",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -27,5 +33,9 @@
|
|||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"fs-filesystem": "^2.1.2",
|
"fs-filesystem": "^2.1.2",
|
||||||
"open": "^10.1.0"
|
"open": "^10.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,41 @@
|
|||||||
import { showErrorMessage } from '../utils/messages.js';
|
import { showErrorMessage } from "../utils/messages.js";
|
||||||
|
|
||||||
export function handleCommandLineOperation() {
|
export function handleCommandLineOperation() {
|
||||||
return process.argv.length > 2;
|
return process.argv.length > 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCommandLineArgs() {
|
export function parseCommandLineArgs() {
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
if (args.length === 0) return null;
|
if (args.length === 0) return null;
|
||||||
|
|
||||||
switch (args[0]) {
|
switch (args[0]) {
|
||||||
case '--upload':
|
case "--upload":
|
||||||
if (args.length !== 2) {
|
if (args.length !== 2) {
|
||||||
console.log(showErrorMessage('Usage: npx codexstorage --upload <filename>'));
|
console.log(
|
||||||
process.exit(1);
|
showErrorMessage("Usage: npx codexstorage --upload <filename>"),
|
||||||
}
|
);
|
||||||
return { command: 'upload', value: args[1] };
|
process.exit(1);
|
||||||
|
}
|
||||||
case '--download':
|
return { command: "upload", value: args[1] };
|
||||||
if (args.length !== 2) {
|
|
||||||
console.log(showErrorMessage('Usage: npx codexstorage --download <cid>'));
|
case "--download":
|
||||||
process.exit(1);
|
if (args.length !== 2) {
|
||||||
}
|
console.log(
|
||||||
return { command: 'download', value: args[1] };
|
showErrorMessage("Usage: npx codexstorage --download <cid>"),
|
||||||
|
);
|
||||||
default:
|
process.exit(1);
|
||||||
console.log(showErrorMessage(
|
}
|
||||||
'Invalid command. Available commands:\n\n' +
|
return { command: "download", value: args[1] };
|
||||||
'npx codexstorage\n' +
|
|
||||||
'npx codexstorage --upload <filename>\n' +
|
default:
|
||||||
'npx codexstorage --download <cid>'
|
console.log(
|
||||||
));
|
showErrorMessage(
|
||||||
process.exit(1);
|
"Invalid command. Available commands:\n\n" +
|
||||||
}
|
"npx codexstorage\n" +
|
||||||
}
|
"npx codexstorage --upload <filename>\n" +
|
||||||
|
"npx codexstorage --download <cid>",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import inquirer from 'inquirer';
|
import inquirer from "inquirer";
|
||||||
import chalk from 'chalk';
|
import chalk from "chalk";
|
||||||
import { showErrorMessage, showInfoMessage } from './utils/messages.js';
|
import { showErrorMessage, showInfoMessage } from "./utils/messages.js";
|
||||||
import { isDir, showPathSelector } from './utils/pathSelector.js';
|
import { isDir, showPathSelector } from "./utils/pathSelector.js";
|
||||||
import { saveConfig } from './services/config.js';
|
import { saveConfig } from "./services/config.js";
|
||||||
import { showNumberSelector } from './utils/numberSelector.js';
|
import { showNumberSelector } from "./utils/numberSelector.js";
|
||||||
import fs from 'fs-extra';
|
import fs from "fs-extra";
|
||||||
|
|
||||||
function bytesAmountToString(numBytes) {
|
function bytesAmountToString(numBytes) {
|
||||||
const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
|
||||||
var value = numBytes;
|
var value = numBytes;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
while (value > 1024) {
|
while (value > 1024) {
|
||||||
@ -22,8 +22,12 @@ function bytesAmountToString(numBytes) {
|
|||||||
|
|
||||||
async function showStorageQuotaSelector(config) {
|
async function showStorageQuotaSelector(config) {
|
||||||
console.log(showInfoMessage('You can use: "GB" or "gb", etc.'));
|
console.log(showInfoMessage('You can use: "GB" or "gb", etc.'));
|
||||||
const result = await showNumberSelector(config.storageQuota, "Storage quota", true);
|
const result = await showNumberSelector(
|
||||||
if (result < (100 * 1024 * 1024)) {
|
config.storageQuota,
|
||||||
|
"Storage quota",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (result < 100 * 1024 * 1024) {
|
||||||
console.log(showErrorMessage("Storage quote should be >= 100mb."));
|
console.log(showErrorMessage("Storage quote should be >= 100mb."));
|
||||||
return config.storageQuota;
|
return config.storageQuota;
|
||||||
}
|
}
|
||||||
@ -31,68 +35,86 @@ async function showStorageQuotaSelector(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function showConfigMenu(config) {
|
export async function showConfigMenu(config) {
|
||||||
var newDataDir = config.dataDir;
|
var newDataDir = config.dataDir;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
console.log(showInfoMessage("Codex Configuration"));
|
console.log(showInfoMessage("Codex Configuration"));
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer
|
||||||
{
|
.prompt([
|
||||||
type: 'list',
|
{
|
||||||
name: 'choice',
|
type: "list",
|
||||||
message: 'Select to edit:',
|
name: "choice",
|
||||||
choices: [
|
message: "Select to edit:",
|
||||||
`1. Data path = "${newDataDir}"`,
|
choices: [
|
||||||
`2. Logs path = "${config.logsDir}"`,
|
`1. Data path = "${newDataDir}"`,
|
||||||
`3. Storage quota = ${bytesAmountToString(config.storageQuota)}`,
|
`2. Logs path = "${config.logsDir}"`,
|
||||||
`4. Discovery port = ${config.ports.discPort}`,
|
`3. Storage quota = ${bytesAmountToString(config.storageQuota)}`,
|
||||||
`5. P2P listen port = ${config.ports.listenPort}`,
|
`4. Discovery port = ${config.ports.discPort}`,
|
||||||
`6. API port = ${config.ports.apiPort}`,
|
`5. P2P listen port = ${config.ports.listenPort}`,
|
||||||
'7. Save changes and exit',
|
`6. API port = ${config.ports.apiPort}`,
|
||||||
'8. Discard changes and exit'
|
"7. Save changes and exit",
|
||||||
],
|
"8. Discard changes and exit",
|
||||||
pageSize: 8,
|
],
|
||||||
loop: true
|
pageSize: 8,
|
||||||
}
|
loop: true,
|
||||||
]).catch(() => {
|
},
|
||||||
return;
|
])
|
||||||
});
|
.catch(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
switch (choice.split('.')[0]) {
|
switch (choice.split(".")[0]) {
|
||||||
case '1':
|
case "1":
|
||||||
newDataDir = await showPathSelector(config.dataDir, false);
|
newDataDir = await showPathSelector(config.dataDir, false);
|
||||||
if (isDir(newDataDir)) {
|
if (isDir(newDataDir)) {
|
||||||
console.log(showInfoMessage("Warning: The new data path already exists. Make sure you know what you're doing."));
|
console.log(
|
||||||
}
|
showInfoMessage(
|
||||||
break;
|
"Warning: The new data path already exists. Make sure you know what you're doing.",
|
||||||
case '2':
|
),
|
||||||
config.logsDir = await showPathSelector(config.logsDir, true);
|
);
|
||||||
break;
|
}
|
||||||
case '3':
|
break;
|
||||||
config.storageQuota = await showStorageQuotaSelector(config);
|
case "2":
|
||||||
break;
|
config.logsDir = await showPathSelector(config.logsDir, true);
|
||||||
case '4':
|
break;
|
||||||
config.ports.discPort = await showNumberSelector(config.ports.discPort, "Discovery Port (UDP)", false);
|
case "3":
|
||||||
break;
|
config.storageQuota = await showStorageQuotaSelector(config);
|
||||||
case '5':
|
break;
|
||||||
config.ports.listenPort = await showNumberSelector(config.ports.listenPort, "Listen Port (TCP)", false);
|
case "4":
|
||||||
break;
|
config.ports.discPort = await showNumberSelector(
|
||||||
case '6':
|
config.ports.discPort,
|
||||||
config.ports.apiPort = await showNumberSelector(config.ports.apiPort, "API Port (TCP)", false);
|
"Discovery Port (UDP)",
|
||||||
break;
|
false,
|
||||||
case '7':
|
);
|
||||||
// save changes, back to main menu
|
break;
|
||||||
config = updateDataDir(config, newDataDir);
|
case "5":
|
||||||
saveConfig(config);
|
config.ports.listenPort = await showNumberSelector(
|
||||||
return;
|
config.ports.listenPort,
|
||||||
case '8':
|
"Listen Port (TCP)",
|
||||||
// discard changes, back to main menu
|
false,
|
||||||
return;
|
);
|
||||||
}
|
break;
|
||||||
}
|
case "6":
|
||||||
} catch (error) {
|
config.ports.apiPort = await showNumberSelector(
|
||||||
console.error(chalk.red('An error occurred:', error.message));
|
config.ports.apiPort,
|
||||||
return;
|
"API Port (TCP)",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "7":
|
||||||
|
// save changes, back to main menu
|
||||||
|
config = updateDataDir(config, newDataDir);
|
||||||
|
saveConfig(config);
|
||||||
|
return;
|
||||||
|
case "8":
|
||||||
|
// discard changes, back to main menu
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red("An error occurred:", error.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDataDir(config, newDataDir) {
|
function updateDataDir(config, newDataDir) {
|
||||||
@ -104,28 +126,34 @@ function updateDataDir(config, newDataDir) {
|
|||||||
// If the old one does exist: We move it.
|
// If the old one does exist: We move it.
|
||||||
|
|
||||||
if (isDir(config.dataDir)) {
|
if (isDir(config.dataDir)) {
|
||||||
console.log(showInfoMessage(
|
console.log(
|
||||||
'Moving Codex data folder...\n' +
|
showInfoMessage(
|
||||||
`From: "${config.dataDir}"\n` +
|
"Moving Codex data folder...\n" +
|
||||||
`To: "${newDataDir}"`
|
`From: "${config.dataDir}"\n` +
|
||||||
));
|
`To: "${newDataDir}"`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.moveSync(config.dataDir, newDataDir);
|
fs.moveSync(config.dataDir, newDataDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(showErrorMessage("Error while moving dataDir: " + error.message));
|
console.log(
|
||||||
|
showErrorMessage("Error while moving dataDir: " + error.message),
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Old data dir does not exist.
|
// Old data dir does not exist.
|
||||||
if (isDir(newDataDir)) {
|
if (isDir(newDataDir)) {
|
||||||
console.log(showInfoMessage(
|
console.log(
|
||||||
"Warning: the selected data path already exists.\n" +
|
showInfoMessage(
|
||||||
`New data path = "${newDataDir}"\n` +
|
"Warning: the selected data path already exists.\n" +
|
||||||
"Codex may overwrite data in this folder.\n" +
|
`New data path = "${newDataDir}"\n` +
|
||||||
"Codex will fail to start if this folder does not have the required\n" +
|
"Codex may overwrite data in this folder.\n" +
|
||||||
"security permissions."
|
"Codex will fail to start if this folder does not have the required\n" +
|
||||||
));
|
"security permissions.",
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,4 +16,4 @@ export const ASCII_ART = `
|
|||||||
+--------------------------------------------------------------------+
|
+--------------------------------------------------------------------+
|
||||||
| Docs : docs.codex.storage | Discord : discord.gg/codex-storage |
|
| Docs : docs.codex.storage | Discord : discord.gg/codex-storage |
|
||||||
+--------------------------------------------------------------------+
|
+--------------------------------------------------------------------+
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,197 +1,263 @@
|
|||||||
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 {
|
||||||
import { isNodeRunning } from '../services/nodeService.js';
|
showErrorMessage,
|
||||||
import fs from 'fs/promises';
|
showInfoMessage,
|
||||||
import boxen from 'boxen';
|
showSuccessMessage,
|
||||||
import chalk from 'chalk';
|
} from "../utils/messages.js";
|
||||||
import inquirer from 'inquirer';
|
import { isNodeRunning } from "../services/nodeService.js";
|
||||||
import path from 'path';
|
import fs from "fs/promises";
|
||||||
import mime from 'mime-types';
|
import boxen from "boxen";
|
||||||
import axios from 'axios';
|
import chalk from "chalk";
|
||||||
|
import inquirer from "inquirer";
|
||||||
|
import path from "path";
|
||||||
|
import mime from "mime-types";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export async function uploadFile(config, filePath = null, handleCommandLineOperation, showNavigationMenu) {
|
export async function uploadFile(
|
||||||
const nodeRunning = await isNodeRunning(config);
|
config,
|
||||||
if (!nodeRunning) {
|
filePath = null,
|
||||||
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
|
handleCommandLineOperation,
|
||||||
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
|
showNavigationMenu,
|
||||||
}
|
) {
|
||||||
|
const nodeRunning = await isNodeRunning(config);
|
||||||
|
if (!nodeRunning) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
"Codex node is not running. Try again after starting the node",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return handleCommandLineOperation()
|
||||||
|
? process.exit(1)
|
||||||
|
: showNavigationMenu();
|
||||||
|
}
|
||||||
|
|
||||||
console.log(boxen(
|
console.log(
|
||||||
chalk.yellow('⚠️ Codex does not encrypt files. Anything uploaded will be available publicly on testnet.\nThe testnet does not provide any guarantees - please do not use in production.'),
|
boxen(
|
||||||
{
|
chalk.yellow(
|
||||||
padding: 1,
|
"⚠️ Codex does not encrypt files. Anything uploaded will be available publicly on testnet.\nThe testnet does not provide any guarantees - please do not use in production.",
|
||||||
margin: 1,
|
),
|
||||||
borderStyle: 'round',
|
{
|
||||||
borderColor: 'yellow',
|
padding: 1,
|
||||||
title: '⚠️ Warning',
|
margin: 1,
|
||||||
titleAlignment: 'center'
|
borderStyle: "round",
|
||||||
}
|
borderColor: "yellow",
|
||||||
));
|
title: "⚠️ Warning",
|
||||||
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let fileToUpload = filePath;
|
let fileToUpload = filePath;
|
||||||
if (!fileToUpload) {
|
if (!fileToUpload) {
|
||||||
const { inputPath } = await inquirer.prompt([
|
const { inputPath } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'inputPath',
|
name: "inputPath",
|
||||||
message: 'Enter the file path to upload:',
|
message: "Enter the file path to upload:",
|
||||||
validate: input => input.length > 0
|
validate: (input) => input.length > 0,
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
fileToUpload = inputPath;
|
fileToUpload = inputPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.access(fileToUpload);
|
||||||
|
|
||||||
|
const filename = path.basename(fileToUpload);
|
||||||
|
const contentType = mime.lookup(fileToUpload) || "application/octet-stream";
|
||||||
|
|
||||||
|
const spinner = createSpinner("Uploading file").start();
|
||||||
try {
|
try {
|
||||||
await fs.access(fileToUpload);
|
const result = await runCommand(
|
||||||
|
`curl -X POST http://localhost:${config.ports.apiPort}/api/codex/v1/data ` +
|
||||||
const filename = path.basename(fileToUpload);
|
`-H 'Content-Type: ${contentType}' ` +
|
||||||
const contentType = mime.lookup(fileToUpload) || 'application/octet-stream';
|
`-H 'Content-Disposition: attachment; filename="${filename}"' ` +
|
||||||
|
`-w '\\n' -T "${fileToUpload}"`,
|
||||||
const spinner = createSpinner('Uploading file').start();
|
);
|
||||||
try {
|
spinner.success();
|
||||||
const result = await runCommand(
|
console.log(
|
||||||
`curl -X POST http://localhost:${config.ports.apiPort}/api/codex/v1/data ` +
|
showSuccessMessage("Successfully uploaded!\n\nCID: " + result.trim()),
|
||||||
`-H 'Content-Type: ${contentType}' ` +
|
);
|
||||||
`-H 'Content-Disposition: attachment; filename="${filename}"' ` +
|
|
||||||
`-w '\\n' -T "${fileToUpload}"`
|
|
||||||
);
|
|
||||||
spinner.success();
|
|
||||||
console.log(showSuccessMessage('Successfully uploaded!\n\nCID: ' + result.trim()));
|
|
||||||
} catch (error) {
|
|
||||||
spinner.error();
|
|
||||||
throw new Error(`Failed to upload: ${error.message}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(showErrorMessage(error.code === 'ENOENT'
|
spinner.error();
|
||||||
? `File not found: ${fileToUpload}`
|
throw new Error(`Failed to upload: ${error.message}`);
|
||||||
: `Error uploading file: ${error.message}`));
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
error.code === "ENOENT"
|
||||||
|
? `File not found: ${fileToUpload}`
|
||||||
|
: `Error uploading file: ${error.message}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
|
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadFile(config, cid = null, handleCommandLineOperation, showNavigationMenu) {
|
export async function downloadFile(
|
||||||
const nodeRunning = await isNodeRunning(config);
|
config,
|
||||||
if (!nodeRunning) {
|
cid = null,
|
||||||
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
|
handleCommandLineOperation,
|
||||||
return handleCommandLineOperation() ? process.exit(1) : showNavigationMenu();
|
showNavigationMenu,
|
||||||
}
|
) {
|
||||||
|
const nodeRunning = await isNodeRunning(config);
|
||||||
|
if (!nodeRunning) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
"Codex node is not running. Try again after starting the node",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return handleCommandLineOperation()
|
||||||
|
? process.exit(1)
|
||||||
|
: showNavigationMenu();
|
||||||
|
}
|
||||||
|
|
||||||
let cidToDownload = cid;
|
let cidToDownload = cid;
|
||||||
if (!cidToDownload) {
|
if (!cidToDownload) {
|
||||||
const { inputCid } = await inquirer.prompt([
|
const { inputCid } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'inputCid',
|
name: "inputCid",
|
||||||
message: 'Enter the CID:',
|
message: "Enter the CID:",
|
||||||
validate: input => input.length > 0
|
validate: (input) => input.length > 0,
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
cidToDownload = inputCid;
|
cidToDownload = inputCid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const spinner = createSpinner("Fetching file metadata...").start();
|
||||||
try {
|
try {
|
||||||
const spinner = createSpinner('Fetching file metadata...').start();
|
// First, get the file metadata
|
||||||
try {
|
const metadataResponse = await axios.post(
|
||||||
// First, get the file metadata
|
`http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network`,
|
||||||
const metadataResponse = await axios.post(`http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network`);
|
);
|
||||||
const { manifest } = metadataResponse.data;
|
const { manifest } = metadataResponse.data;
|
||||||
const { filename, mimetype } = manifest;
|
const { filename, mimetype } = manifest;
|
||||||
|
|
||||||
spinner.success();
|
spinner.success();
|
||||||
spinner.start('Downloading file...');
|
spinner.start("Downloading file...");
|
||||||
|
|
||||||
// Then download the file with the correct filename
|
// Then download the file with the correct filename
|
||||||
await runCommand(`curl "http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`);
|
await runCommand(
|
||||||
|
`curl "http://localhost:${config.ports.apiPort}/api/codex/v1/data/${cidToDownload}/network/stream" -o "${filename}"`,
|
||||||
spinner.success();
|
);
|
||||||
console.log(showSuccessMessage(
|
|
||||||
'Successfully downloaded!\n\n' +
|
|
||||||
`Filename: ${filename}\n` +
|
|
||||||
`Type: ${mimetype}`
|
|
||||||
));
|
|
||||||
|
|
||||||
// Show file details
|
spinner.success();
|
||||||
console.log(boxen(
|
console.log(
|
||||||
`${chalk.cyan('File Details')}\n\n` +
|
showSuccessMessage(
|
||||||
`${chalk.cyan('Filename:')} ${filename}\n` +
|
"Successfully downloaded!\n\n" +
|
||||||
`${chalk.cyan('MIME Type:')} ${mimetype}\n` +
|
`Filename: ${filename}\n` +
|
||||||
`${chalk.cyan('CID:')} ${cidToDownload}\n` +
|
`Type: ${mimetype}`,
|
||||||
`${chalk.cyan('Protected:')} ${manifest.protected ? chalk.green('Yes') : chalk.red('No')}\n` +
|
),
|
||||||
`${chalk.cyan('Uploaded:')} ${new Date(manifest.uploadedAt * 1000).toLocaleString()}`,
|
);
|
||||||
{
|
|
||||||
padding: 1,
|
// Show file details
|
||||||
margin: 1,
|
console.log(
|
||||||
borderStyle: 'round',
|
boxen(
|
||||||
borderColor: 'blue',
|
`${chalk.cyan("File Details")}\n\n` +
|
||||||
title: '📁 Download Complete',
|
`${chalk.cyan("Filename:")} ${filename}\n` +
|
||||||
titleAlignment: 'center'
|
`${chalk.cyan("MIME Type:")} ${mimetype}\n` +
|
||||||
}
|
`${chalk.cyan("CID:")} ${cidToDownload}\n` +
|
||||||
));
|
`${chalk.cyan("Protected:")} ${manifest.protected ? chalk.green("Yes") : chalk.red("No")}\n` +
|
||||||
} catch (error) {
|
`${chalk.cyan("Uploaded:")} ${new Date(manifest.uploadedAt * 1000).toLocaleString()}`,
|
||||||
spinner.error();
|
{
|
||||||
if (error.response) {
|
padding: 1,
|
||||||
throw new Error(`Failed to download: ${error.response.data.message || 'File not found'}`);
|
margin: 1,
|
||||||
} else {
|
borderStyle: "round",
|
||||||
throw new Error(`Failed to download: ${error.message}`);
|
borderColor: "blue",
|
||||||
}
|
title: "📁 Download Complete",
|
||||||
}
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(showErrorMessage(`Error downloading file: ${error.message}`));
|
spinner.error();
|
||||||
|
if (error.response) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to download: ${error.response.data.message || "File not found"}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to download: ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(showErrorMessage(`Error downloading file: ${error.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
|
return handleCommandLineOperation() ? process.exit(0) : showNavigationMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showLocalFiles(config, showNavigationMenu) {
|
export async function showLocalFiles(config, showNavigationMenu) {
|
||||||
const nodeRunning = await isNodeRunning(config);
|
const nodeRunning = await isNodeRunning(config);
|
||||||
if (!nodeRunning) {
|
if (!nodeRunning) {
|
||||||
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
|
console.log(
|
||||||
await showNavigationMenu();
|
showErrorMessage(
|
||||||
return;
|
"Codex node is not running. Try again after starting the node",
|
||||||
}
|
),
|
||||||
|
);
|
||||||
try {
|
|
||||||
const spinner = createSpinner('Fetching local files...').start();
|
|
||||||
const filesResponse = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/data`);
|
|
||||||
const filesData = filesResponse.data;
|
|
||||||
spinner.success();
|
|
||||||
|
|
||||||
if (filesData.content && filesData.content.length > 0) {
|
|
||||||
console.log(showInfoMessage(`Found ${filesData.content.length} local file(s)`));
|
|
||||||
|
|
||||||
filesData.content.forEach((file, index) => {
|
|
||||||
const { cid, manifest } = file;
|
|
||||||
const { rootHash, originalBytes, blockSize, protected: isProtected, filename, mimetype, uploadedAt } = manifest;
|
|
||||||
|
|
||||||
const uploadedDate = new Date(uploadedAt * 1000).toLocaleString();
|
|
||||||
const fileSize = (originalBytes / 1024).toFixed(2);
|
|
||||||
|
|
||||||
console.log(boxen(
|
|
||||||
`${chalk.cyan('File')} ${index + 1} of ${filesData.content.length}\n\n` +
|
|
||||||
`${chalk.cyan('Filename:')} ${filename}\n` +
|
|
||||||
`${chalk.cyan('CID:')} ${cid}\n` +
|
|
||||||
`${chalk.cyan('Size:')} ${fileSize} KB\n` +
|
|
||||||
`${chalk.cyan('MIME Type:')} ${mimetype}\n` +
|
|
||||||
`${chalk.cyan('Uploaded:')} ${uploadedDate}\n` +
|
|
||||||
`${chalk.cyan('Protected:')} ${isProtected ? chalk.green('Yes') : chalk.red('No')}`,
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'blue',
|
|
||||||
title: `📁 File Details`,
|
|
||||||
titleAlignment: 'center'
|
|
||||||
}
|
|
||||||
));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(showInfoMessage("Node contains no datasets."));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(showErrorMessage(`Failed to fetch local files: ${error.message}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const spinner = createSpinner("Fetching local files...").start();
|
||||||
|
const filesResponse = await axios.get(
|
||||||
|
`http://localhost:${config.ports.apiPort}/api/codex/v1/data`,
|
||||||
|
);
|
||||||
|
const filesData = filesResponse.data;
|
||||||
|
spinner.success();
|
||||||
|
|
||||||
|
if (filesData.content && filesData.content.length > 0) {
|
||||||
|
console.log(
|
||||||
|
showInfoMessage(`Found ${filesData.content.length} local file(s)`),
|
||||||
|
);
|
||||||
|
|
||||||
|
filesData.content.forEach((file, index) => {
|
||||||
|
const { cid, manifest } = file;
|
||||||
|
const {
|
||||||
|
rootHash,
|
||||||
|
originalBytes,
|
||||||
|
blockSize,
|
||||||
|
protected: isProtected,
|
||||||
|
filename,
|
||||||
|
mimetype,
|
||||||
|
uploadedAt,
|
||||||
|
} = manifest;
|
||||||
|
|
||||||
|
const uploadedDate = new Date(uploadedAt * 1000).toLocaleString();
|
||||||
|
const fileSize = (originalBytes / 1024).toFixed(2);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
`${chalk.cyan("File")} ${index + 1} of ${filesData.content.length}\n\n` +
|
||||||
|
`${chalk.cyan("Filename:")} ${filename}\n` +
|
||||||
|
`${chalk.cyan("CID:")} ${cid}\n` +
|
||||||
|
`${chalk.cyan("Size:")} ${fileSize} KB\n` +
|
||||||
|
`${chalk.cyan("MIME Type:")} ${mimetype}\n` +
|
||||||
|
`${chalk.cyan("Uploaded:")} ${uploadedDate}\n` +
|
||||||
|
`${chalk.cyan("Protected:")} ${isProtected ? chalk.green("Yes") : chalk.red("No")}`,
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "blue",
|
||||||
|
title: `📁 File Details`,
|
||||||
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(showInfoMessage("Node contains no datasets."));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(`Failed to fetch local files: ${error.message}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await showNavigationMenu();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,158 +1,192 @@
|
|||||||
import path from 'path';
|
import path from "path";
|
||||||
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 fs from "fs";
|
||||||
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 {
|
||||||
import { checkDependencies } from '../services/nodeService.js';
|
showErrorMessage,
|
||||||
import { saveConfig } from '../services/config.js';
|
showInfoMessage,
|
||||||
import { getCodexRootPath, getCodexBinPath } from '../utils/appdata.js';
|
showSuccessMessage,
|
||||||
|
} from "../utils/messages.js";
|
||||||
|
import { checkDependencies } from "../services/nodeService.js";
|
||||||
|
import { saveConfig } from "../services/config.js";
|
||||||
|
import { getCodexRootPath, getCodexBinPath } from "../utils/appdata.js";
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
async function showPrivacyDisclaimer() {
|
async function showPrivacyDisclaimer() {
|
||||||
const disclaimer = boxen(`
|
const disclaimer = boxen(
|
||||||
${chalk.yellow.bold('Privacy Disclaimer')}
|
`
|
||||||
|
${chalk.yellow.bold("Privacy Disclaimer")}
|
||||||
|
|
||||||
Codex is currently in testnet and to make your testnet experience better, we are currently tracking some of your node and network information such as:
|
Codex is currently in testnet and to make your testnet experience better, we are currently tracking some of your node and network information such as:
|
||||||
|
|
||||||
${chalk.cyan('- Node ID')}
|
${chalk.cyan("- Node ID")}
|
||||||
${chalk.cyan('- Peer ID')}
|
${chalk.cyan("- Peer ID")}
|
||||||
${chalk.cyan('- Public IP address')}
|
${chalk.cyan("- Public IP address")}
|
||||||
${chalk.cyan('- Codex node version')}
|
${chalk.cyan("- Codex node version")}
|
||||||
${chalk.cyan('- Number of connected peers')}
|
${chalk.cyan("- Number of connected peers")}
|
||||||
${chalk.cyan('- Discovery and listening ports')}
|
${chalk.cyan("- Discovery and listening ports")}
|
||||||
|
|
||||||
These information will be used for calculating various metrics that can eventually make the Codex experience better. Please agree to the following disclaimer to continue using the Codex Storage CLI or alternatively, use the manual setup instructions at docs.codex.storage.
|
These information will be used for calculating various metrics that can eventually make the Codex experience better. Please agree to the following disclaimer to continue using the Codex Storage CLI or alternatively, use the manual setup instructions at docs.codex.storage.
|
||||||
`, {
|
`,
|
||||||
padding: 1,
|
{
|
||||||
margin: 1,
|
padding: 1,
|
||||||
borderStyle: 'double',
|
margin: 1,
|
||||||
borderColor: 'yellow',
|
borderStyle: "double",
|
||||||
title: '📋 IMPORTANT',
|
borderColor: "yellow",
|
||||||
titleAlignment: 'center'
|
title: "📋 IMPORTANT",
|
||||||
});
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
console.log(disclaimer);
|
console.log(disclaimer);
|
||||||
|
|
||||||
const { agreement } = await inquirer.prompt([
|
const { agreement } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'agreement',
|
name: "agreement",
|
||||||
message: 'Do you agree to the privacy disclaimer? (y/n):',
|
message: "Do you agree to the privacy disclaimer? (y/n):",
|
||||||
validate: (input) => {
|
validate: (input) => {
|
||||||
const lowercased = input.toLowerCase();
|
const lowercased = input.toLowerCase();
|
||||||
if (lowercased === 'y' || lowercased === 'n') {
|
if (lowercased === "y" || lowercased === "n") {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
return 'Please enter either y or n';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]);
|
return "Please enter either y or n";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
return agreement.toLowerCase() === 'y';
|
return agreement.toLowerCase() === "y";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCodexVersion(config) {
|
export async function getCodexVersion(config) {
|
||||||
if (config.codexExe.length < 1) return "";
|
if (config.codexExe.length < 1) return "";
|
||||||
|
|
||||||
try {
|
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.");
|
if (version.length < 1) throw new Error("Version info not found.");
|
||||||
return version;
|
return version;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installCodex(config, showNavigationMenu) {
|
export async function installCodex(config, showNavigationMenu) {
|
||||||
const version = await getCodexVersion(config);
|
const version = await getCodexVersion(config);
|
||||||
|
|
||||||
if (version.length > 0) {
|
if (version.length > 0) {
|
||||||
console.log(chalk.green('Codex is already installed. Version:'));
|
console.log(chalk.green("Codex is already installed. Version:"));
|
||||||
console.log(chalk.green(version));
|
console.log(chalk.green(version));
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.cyanBright('Codex is not installed, proceeding with installation...'));
|
console.log(
|
||||||
return await performInstall(config);
|
chalk.cyanBright(
|
||||||
}
|
"Codex is not installed, proceeding with installation...",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return await performInstall(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveCodexExePath(config, codexExePath) {
|
async function saveCodexExePath(config, codexExePath) {
|
||||||
config.codexExe = codexExePath;
|
config.codexExe = codexExePath;
|
||||||
if (!fs.existsSync(config.codexExe)) {
|
if (!fs.existsSync(config.codexExe)) {
|
||||||
console.log(showErrorMessage(`Codex executable not found in expected path: ${config.codexExe}`));
|
console.log(
|
||||||
throw new Error("Exe not found");
|
showErrorMessage(
|
||||||
}
|
`Codex executable not found in expected path: ${config.codexExe}`,
|
||||||
if (await getCodexVersion(config).length < 1) {
|
),
|
||||||
console.log(showInfoMessage("no"));
|
);
|
||||||
throw new Error(`Codex not found at path after install. Path: '${config.codexExe}'`);
|
throw new Error("Exe not found");
|
||||||
}
|
}
|
||||||
saveConfig(config);
|
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) {
|
async function clearCodexExePathFromConfig(config) {
|
||||||
config.codexExe = "";
|
config.codexExe = "";
|
||||||
saveConfig(config);
|
saveConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performInstall(config) {
|
async function performInstall(config) {
|
||||||
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(
|
||||||
process.exit(0);
|
showInfoMessage(
|
||||||
}
|
"You can find manual setup instructions at docs.codex.storage",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const installPath = getCodexBinPath();
|
const installPath = getCodexBinPath();
|
||||||
console.log(showInfoMessage("Install location: " + installPath));
|
console.log(showInfoMessage("Install location: " + installPath));
|
||||||
|
|
||||||
const spinner = createSpinner('Installing Codex...').start();
|
const spinner = createSpinner("Installing Codex...").start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (platform === 'win32') {
|
if (platform === "win32") {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
await runCommand('curl --version');
|
await runCommand("curl --version");
|
||||||
} catch (error) {
|
} catch (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(
|
||||||
await runCommand(`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`);
|
"curl -LO --ssl-no-revoke https://get.codex.storage/install.cmd",
|
||||||
|
);
|
||||||
await saveCodexExePath(config, path.join(installPath, "codex.exe"));
|
await runCommand(
|
||||||
|
`set "INSTALL_DIR=${installPath}" && "${process.cwd()}\\install.cmd"`,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
await saveCodexExePath(config, path.join(installPath, "codex.exe"));
|
||||||
await runCommand('del /f install.cmd');
|
|
||||||
} catch (error) {
|
try {
|
||||||
// Ignore cleanup errors
|
await runCommand("del /f install.cmd");
|
||||||
}
|
} catch (error) {
|
||||||
} catch (error) {
|
// Ignore cleanup errors
|
||||||
if (error.message.includes('Access is denied')) {
|
}
|
||||||
throw new Error('Installation failed. Please run the command prompt as Administrator and try again.');
|
} catch (error) {
|
||||||
} else if (error.message.includes('curl')) {
|
if (error.message.includes("Access is denied")) {
|
||||||
throw new Error(error.message);
|
throw new Error(
|
||||||
} else {
|
"Installation failed. Please run the command prompt as Administrator and try again.",
|
||||||
throw new Error(`Installation failed: "${error.message}"`);
|
);
|
||||||
}
|
} else if (error.message.includes("curl")) {
|
||||||
}
|
throw new Error(error.message);
|
||||||
} else {
|
} else {
|
||||||
try {
|
throw new Error(`Installation failed: "${error.message}"`);
|
||||||
const dependenciesInstalled = await checkDependencies();
|
}
|
||||||
if (!dependenciesInstalled) {
|
}
|
||||||
console.log(showInfoMessage('Please install the required dependencies and try again.'));
|
} else {
|
||||||
throw new Error("Missing dependencies.");
|
try {
|
||||||
}
|
const dependenciesInstalled = await checkDependencies();
|
||||||
|
if (!dependenciesInstalled) {
|
||||||
|
console.log(
|
||||||
|
showInfoMessage(
|
||||||
|
"Please install the required dependencies and try again.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
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';
|
const downloadCommand =
|
||||||
await runCommand(downloadCommand);
|
"curl -# --connect-timeout 10 --max-time 60 -L https://get.codex.storage/install.sh -o install.sh && chmod +x install.sh";
|
||||||
|
await runCommand(downloadCommand);
|
||||||
if (platform === 'darwin') {
|
|
||||||
const timeoutCommand = `perl -e '
|
if (platform === "darwin") {
|
||||||
|
const timeoutCommand = `perl -e '
|
||||||
eval {
|
eval {
|
||||||
local $SIG{ALRM} = sub { die "timeout\\n" };
|
local $SIG{ALRM} = sub { die "timeout\\n" };
|
||||||
alarm(120);
|
alarm(120);
|
||||||
@ -161,90 +195,112 @@ async function performInstall(config) {
|
|||||||
};
|
};
|
||||||
die if $@;
|
die if $@;
|
||||||
'`;
|
'`;
|
||||||
await runCommand(timeoutCommand);
|
await runCommand(timeoutCommand);
|
||||||
} else {
|
} else {
|
||||||
await runCommand(`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`);
|
await runCommand(
|
||||||
}
|
`INSTALL_DIR="${installPath}" timeout 120 bash install.sh`,
|
||||||
|
);
|
||||||
await saveCodexExePath(config, path.join(installPath, "codex"));
|
|
||||||
|
|
||||||
} catch (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')) {
|
|
||||||
throw new Error('Permission denied. Please try running the command with sudo.');
|
|
||||||
} else if (error.message.includes('timeout')) {
|
|
||||||
throw new Error('Installation is taking longer than expected. Please try running with sudo.');
|
|
||||||
} else {
|
|
||||||
throw new Error('Installation failed. Please try running with sudo if you haven\'t already.');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await runCommand('rm -f install.sh').catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const version = await getCodexVersion(config);
|
|
||||||
console.log(chalk.green(version));
|
|
||||||
|
|
||||||
console.log(showSuccessMessage(
|
|
||||||
'Codex is successfully installed!\n' +
|
|
||||||
`Install path: "${config.codexExe}"\n\n` +
|
|
||||||
'The default configuration should work for most platforms.\n' +
|
|
||||||
'Please review the configuration before starting Codex.\n'
|
|
||||||
));
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Installation completed but Codex command is not available. Please restart your terminal and try again.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(showInfoMessage(
|
await saveCodexExePath(config, path.join(installPath, "codex"));
|
||||||
"Please review the configuration before starting Codex."
|
} catch (error) {
|
||||||
));
|
if (
|
||||||
|
error.message.includes("ECONNREFUSED") ||
|
||||||
spinner.success();
|
error.message.includes("ETIMEDOUT")
|
||||||
return true;
|
) {
|
||||||
} catch (error) {
|
throw new Error(
|
||||||
spinner.error();
|
"Installation failed. Please check your internet connection and try again.",
|
||||||
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
|
);
|
||||||
return false;
|
} else if (error.message.includes("Permission denied")) {
|
||||||
}
|
throw new Error(
|
||||||
}
|
"Permission denied. Please try running the command with sudo.",
|
||||||
|
);
|
||||||
function removeDir(dir) {
|
} else if (error.message.includes("timeout")) {
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
throw new Error(
|
||||||
}
|
"Installation is taking longer than expected. Please try running with sudo.",
|
||||||
|
);
|
||||||
export async function uninstallCodex(config, showNavigationMenu) {
|
} else {
|
||||||
const { confirm } = await inquirer.prompt([
|
throw new Error(
|
||||||
{
|
"Installation failed. Please try running with sudo if you haven't already.",
|
||||||
type: 'confirm',
|
);
|
||||||
name: 'confirm',
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
]);
|
} finally {
|
||||||
|
await runCommand("rm -f install.sh").catch(() => {});
|
||||||
if (!confirm) {
|
}
|
||||||
console.log(showInfoMessage('Uninstall cancelled.'));
|
|
||||||
await showNavigationMenu();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
removeDir(getCodexRootPath());
|
const version = await getCodexVersion(config);
|
||||||
clearCodexExePathFromConfig(config);
|
console.log(chalk.green(version));
|
||||||
|
|
||||||
console.log(showSuccessMessage('Codex has been successfully uninstalled.'));
|
console.log(
|
||||||
await showNavigationMenu();
|
showSuccessMessage(
|
||||||
|
"Codex is successfully installed!\n" +
|
||||||
|
`Install path: "${config.codexExe}"\n\n` +
|
||||||
|
"The default configuration should work for most platforms.\n" +
|
||||||
|
"Please review the configuration before starting Codex.\n",
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') {
|
throw new Error(
|
||||||
console.log(showInfoMessage('Codex binary not found, nothing to uninstall.'));
|
"Installation completed but Codex command is not available. Please restart your terminal and try again.",
|
||||||
} else {
|
);
|
||||||
console.log(showErrorMessage('Failed to uninstall Codex. Please make sure Codex is installed before trying to remove it.'));
|
|
||||||
}
|
|
||||||
await showNavigationMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
showInfoMessage("Please review the configuration before starting Codex."),
|
||||||
|
);
|
||||||
|
|
||||||
|
spinner.success();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
spinner.error();
|
||||||
|
console.log(showErrorMessage(`Failed to install Codex: ${error.message}`));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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. \n" +
|
||||||
|
"All data stored in the local Codex node will be deleted as well.",
|
||||||
|
),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!confirm) {
|
||||||
|
console.log(showInfoMessage("Uninstall cancelled."));
|
||||||
|
await showNavigationMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
removeDir(getCodexRootPath());
|
||||||
|
clearCodexExePathFromConfig(config);
|
||||||
|
|
||||||
|
console.log(showSuccessMessage("Codex has been successfully uninstalled."));
|
||||||
|
await showNavigationMenu();
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === "ENOENT") {
|
||||||
|
console.log(
|
||||||
|
showInfoMessage("Codex binary not found, nothing to uninstall."),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
"Failed to uninstall Codex. Please make sure Codex is installed before trying to remove it.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await showNavigationMenu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,289 +1,349 @@
|
|||||||
import path from 'path';
|
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 {
|
||||||
import { isNodeRunning, isCodexInstalled, startPeriodicLogging, getWalletAddress, setWalletAddress } from '../services/nodeService.js';
|
showErrorMessage,
|
||||||
import inquirer from 'inquirer';
|
showInfoMessage,
|
||||||
import boxen from 'boxen';
|
showSuccessMessage,
|
||||||
import chalk from 'chalk';
|
} from "../utils/messages.js";
|
||||||
import os from 'os';
|
import {
|
||||||
import { exec } from 'child_process';
|
isNodeRunning,
|
||||||
import axios from 'axios';
|
isCodexInstalled,
|
||||||
|
startPeriodicLogging,
|
||||||
|
getWalletAddress,
|
||||||
|
setWalletAddress,
|
||||||
|
} from "../services/nodeService.js";
|
||||||
|
import inquirer from "inquirer";
|
||||||
|
import boxen from "boxen";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import os from "os";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
async function promptForWalletAddress() {
|
async function promptForWalletAddress() {
|
||||||
const { wallet } = await inquirer.prompt([
|
const { wallet } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'wallet',
|
name: "wallet",
|
||||||
message: 'Please enter your ERC20 wallet address (or press enter to skip):',
|
message:
|
||||||
validate: (input) => {
|
"Please enter your ERC20 wallet address (or press enter to skip):",
|
||||||
if (!input) return true; // Allow empty input
|
validate: (input) => {
|
||||||
if (/^0x[a-fA-F0-9]{40}$/.test(input)) return true;
|
if (!input) return true; // Allow empty input
|
||||||
return 'Please enter a valid ERC20 wallet address (0x followed by 40 hexadecimal characters) or press enter to skip';
|
if (/^0x[a-fA-F0-9]{40}$/.test(input)) return true;
|
||||||
}
|
return "Please enter a valid ERC20 wallet address (0x followed by 40 hexadecimal characters) or press enter to skip";
|
||||||
}
|
},
|
||||||
]);
|
},
|
||||||
return wallet || null;
|
]);
|
||||||
|
return wallet || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentLogFile(config) {
|
function getCurrentLogFile(config) {
|
||||||
const timestamp = new Date().toISOString()
|
const timestamp = new Date()
|
||||||
.replaceAll(":", "-")
|
.toISOString()
|
||||||
.replaceAll(".", "-");
|
.replaceAll(":", "-")
|
||||||
return path.join(config.logsDir, `codex_${timestamp}.log`);
|
.replaceAll(".", "-");
|
||||||
|
return path.join(config.logsDir, `codex_${timestamp}.log`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runCodex(config, showNavigationMenu) {
|
export async function runCodex(config, showNavigationMenu) {
|
||||||
const isInstalled = await isCodexInstalled(config);
|
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(
|
||||||
await showNavigationMenu();
|
showErrorMessage(
|
||||||
return;
|
"Codex is not installed. Please install Codex first using option 1 from the main menu.",
|
||||||
}
|
),
|
||||||
|
);
|
||||||
|
await showNavigationMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const nodeAlreadyRunning = await isNodeRunning(config);
|
const nodeAlreadyRunning = await isNodeRunning(config);
|
||||||
|
|
||||||
if (nodeAlreadyRunning) {
|
if (nodeAlreadyRunning) {
|
||||||
console.log(showInfoMessage('A Codex node is already running.'));
|
console.log(showInfoMessage("A Codex node is already running."));
|
||||||
await showNavigationMenu();
|
await showNavigationMenu();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
let nat;
|
let nat;
|
||||||
if (platform === 'win32') {
|
if (platform === "win32") {
|
||||||
const result = await runCommand('for /f "delims=" %a in (\'curl -s --ssl-reqd ip.codex.storage\') do @echo %a');
|
const result = await runCommand(
|
||||||
nat = result.trim();
|
"for /f \"delims=\" %a in ('curl -s --ssl-reqd ip.codex.storage') do @echo %a",
|
||||||
} else {
|
);
|
||||||
nat = await runCommand('curl -s https://ip.codex.storage');
|
nat = result.trim();
|
||||||
|
} else {
|
||||||
|
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}\n` +
|
||||||
|
`API port: ${config.ports.apiPort}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const executable = config.codexExe;
|
||||||
|
const args = [
|
||||||
|
`--data-dir="${config.dataDir}"`,
|
||||||
|
`--log-level=DEBUG`,
|
||||||
|
`--log-file="${logFilePath}"`,
|
||||||
|
`--storage-quota="${config.storageQuota}"`,
|
||||||
|
`--disc-port=${config.ports.discPort}`,
|
||||||
|
`--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`,
|
||||||
|
`--api-port=${config.ports.apiPort}`,
|
||||||
|
`--nat=${nat}`,
|
||||||
|
`--api-cors-origin="*"`,
|
||||||
|
`--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const command = `"${executable}" ${args.join(" ")}`;
|
||||||
|
|
||||||
|
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",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodeProcess = exec(command);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`,
|
||||||
|
);
|
||||||
|
if (response.status === 200) {
|
||||||
|
// Check if wallet exists
|
||||||
|
try {
|
||||||
|
const existingWallet = await getWalletAddress();
|
||||||
|
if (!existingWallet) {
|
||||||
|
console.log(
|
||||||
|
showInfoMessage(
|
||||||
|
"[OPTIONAL] Please provide your ERC20 wallet address.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const wallet = await promptForWalletAddress();
|
||||||
|
if (wallet) {
|
||||||
|
await setWalletAddress(wallet);
|
||||||
|
console.log(
|
||||||
|
showSuccessMessage("Wallet address saved successfully!"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
"Failed to process wallet address. Continuing without wallet update.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.dataDir.length < 1) throw new Error("Missing config: dataDir");
|
// Start periodic logging
|
||||||
if (config.logsDir.length < 1) throw new Error("Missing config: logsDir");
|
const stopLogging = await startPeriodicLogging(config);
|
||||||
const logFilePath = getCurrentLogFile(config);
|
|
||||||
|
|
||||||
console.log(showInfoMessage(
|
nodeProcess.on("exit", () => {
|
||||||
`Data location: ${config.dataDir}\n` +
|
stopLogging();
|
||||||
`Logs: ${logFilePath}\n` +
|
});
|
||||||
`API port: ${config.ports.apiPort}`
|
|
||||||
));
|
|
||||||
|
|
||||||
const executable = config.codexExe;
|
console.log(
|
||||||
const args = [
|
boxen(
|
||||||
`--data-dir="${config.dataDir}"`,
|
chalk.cyan(
|
||||||
`--log-level=DEBUG`,
|
"We are logging some of your node's public data for improving the Codex experience",
|
||||||
`--log-file="${logFilePath}"`,
|
),
|
||||||
`--storage-quota="${config.storageQuota}"`,
|
{
|
||||||
`--disc-port=${config.ports.discPort}`,
|
padding: 1,
|
||||||
`--listen-addrs=/ip4/0.0.0.0/tcp/${config.ports.listenPort}`,
|
margin: 1,
|
||||||
`--api-port=${config.ports.apiPort}`,
|
borderStyle: "round",
|
||||||
`--nat=${nat}`,
|
borderColor: "cyan",
|
||||||
`--api-cors-origin="*"`,
|
title: "🔒 Privacy Notice",
|
||||||
`--bootstrap-node=spr:CiUIAhIhAiJvIcA_ZwPZ9ugVKDbmqwhJZaig5zKyLiuaicRcCGqLEgIDARo8CicAJQgCEiECIm8hwD9nA9n26BUoNuarCEllqKDnMrIuK5qJxFwIaosQ3d6esAYaCwoJBJ_f8zKRAnU6KkYwRAIgM0MvWNJL296kJ9gWvfatfmVvT-A7O2s8Mxp8l9c8EW0CIC-h-H-jBVSgFjg3Eny2u33qF7BDnWFzo7fGfZ7_qc9P`
|
titleAlignment: "center",
|
||||||
];
|
dimBorder: true,
|
||||||
|
},
|
||||||
const command =
|
),
|
||||||
`"${executable}" ${args.join(" ")}`
|
);
|
||||||
|
|
||||||
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'
|
|
||||||
));
|
|
||||||
|
|
||||||
const nodeProcess = exec(command);
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
|
|
||||||
if (response.status === 200) {
|
|
||||||
// Check if wallet exists
|
|
||||||
try {
|
|
||||||
const existingWallet = await getWalletAddress();
|
|
||||||
if (!existingWallet) {
|
|
||||||
console.log(showInfoMessage('[OPTIONAL] Please provide your ERC20 wallet address.'));
|
|
||||||
const wallet = await promptForWalletAddress();
|
|
||||||
if (wallet) {
|
|
||||||
await setWalletAddress(wallet);
|
|
||||||
console.log(showSuccessMessage('Wallet address saved successfully!'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(showErrorMessage('Failed to process wallet address. Continuing without wallet update.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start periodic logging
|
|
||||||
const stopLogging = await startPeriodicLogging(config);
|
|
||||||
|
|
||||||
nodeProcess.on('exit', () => {
|
|
||||||
stopLogging();
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(boxen(
|
|
||||||
chalk.cyan('We are logging some of your node\'s public data for improving the Codex experience'),
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'cyan',
|
|
||||||
title: '🔒 Privacy Notice',
|
|
||||||
titleAlignment: 'center',
|
|
||||||
dimBorder: true
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Silently handle any logging errors
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
nodeProcess.on('exit', (code) => {
|
|
||||||
if (code === 0) resolve();
|
|
||||||
else reject(new Error(`Node exited with code ${code}`));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (platform === 'win32') {
|
|
||||||
console.log(showInfoMessage('Cleaning up firewall rules...'));
|
|
||||||
await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (TCP-In)"');
|
|
||||||
await runCommand('netsh advfirewall firewall delete rule name="Allow Codex (UDP-In)"');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log(showErrorMessage(`Failed to run Codex: ${error.message}`));
|
|
||||||
}
|
}
|
||||||
await showNavigationMenu();
|
} catch (error) {
|
||||||
|
// Silently handle any logging errors
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
nodeProcess.on("exit", (code) => {
|
||||||
|
if (code === 0) resolve();
|
||||||
|
else reject(new Error(`Node exited with code ${code}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (platform === "win32") {
|
||||||
|
console.log(showInfoMessage("Cleaning up firewall rules..."));
|
||||||
|
await runCommand(
|
||||||
|
'netsh advfirewall firewall delete rule name="Allow Codex (TCP-In)"',
|
||||||
|
);
|
||||||
|
await runCommand(
|
||||||
|
'netsh advfirewall firewall delete rule name="Allow Codex (UDP-In)"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(showErrorMessage(`Failed to run Codex: ${error.message}`));
|
||||||
}
|
}
|
||||||
|
await showNavigationMenu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showNodeDetails(data, showNavigationMenu) {
|
async function showNodeDetails(data, showNavigationMenu) {
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: "list",
|
||||||
name: 'choice',
|
name: "choice",
|
||||||
message: 'Select information to view:',
|
message: "Select information to view:",
|
||||||
choices: [
|
choices: [
|
||||||
'1. View Connected Peers',
|
"1. View Connected Peers",
|
||||||
'2. View Node Information',
|
"2. View Node Information",
|
||||||
'3. Update Wallet Address',
|
"3. Update Wallet Address",
|
||||||
'4. Back to Main Menu',
|
"4. Back to Main Menu",
|
||||||
'5. Exit'
|
"5. Exit",
|
||||||
],
|
],
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
loop: true
|
loop: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
switch (choice.split(".")[0].trim()) {
|
||||||
|
case "1":
|
||||||
|
const peerCount = data.table.nodes.length;
|
||||||
|
if (peerCount > 0) {
|
||||||
|
console.log(showInfoMessage("Connected Peers"));
|
||||||
|
data.table.nodes.forEach((node, index) => {
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
`Peer ${index + 1}:\n` +
|
||||||
|
`${chalk.cyan("Peer ID:")} ${node.peerId}\n` +
|
||||||
|
`${chalk.cyan("Address:")} ${node.address}\n` +
|
||||||
|
`${chalk.cyan("Status:")} ${node.seen ? chalk.green("Active") : chalk.gray("Inactive")}`,
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "blue",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(showInfoMessage("No connected peers found."));
|
||||||
|
}
|
||||||
|
return showNodeDetails(data, showNavigationMenu);
|
||||||
|
case "2":
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
`${chalk.cyan("Version:")} ${data.codex.version}\n` +
|
||||||
|
`${chalk.cyan("Revision:")} ${data.codex.revision}\n\n` +
|
||||||
|
`${chalk.cyan("Node ID:")} ${data.table.localNode.nodeId}\n` +
|
||||||
|
`${chalk.cyan("Peer ID:")} ${data.table.localNode.peerId}\n` +
|
||||||
|
`${chalk.cyan("Listening Address:")} ${data.table.localNode.address}\n\n` +
|
||||||
|
`${chalk.cyan("Public IP:")} ${data.announceAddresses[0].split("/")[2]}\n` +
|
||||||
|
`${chalk.cyan("Port:")} ${data.announceAddresses[0].split("/")[4]}\n` +
|
||||||
|
`${chalk.cyan("Connected Peers:")} ${data.table.nodes.length}`,
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "yellow",
|
||||||
|
title: "📊 Node Information",
|
||||||
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return showNodeDetails(data, showNavigationMenu);
|
||||||
|
case "3":
|
||||||
|
try {
|
||||||
|
const existingWallet = await getWalletAddress();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
boxen(
|
||||||
|
`${chalk.cyan("Current wallet address:")}\n${existingWallet || "Not set"}`,
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "blue",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const wallet = await promptForWalletAddress();
|
||||||
|
if (wallet) {
|
||||||
|
await setWalletAddress(wallet);
|
||||||
|
console.log(
|
||||||
|
showSuccessMessage("Wallet address updated successfully!"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
]);
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
switch (choice.split('.')[0].trim()) {
|
showErrorMessage(`Failed to update wallet address: ${error.message}`),
|
||||||
case '1':
|
);
|
||||||
const peerCount = data.table.nodes.length;
|
}
|
||||||
if (peerCount > 0) {
|
return showNodeDetails(data, showNavigationMenu);
|
||||||
console.log(showInfoMessage('Connected Peers'));
|
case "4":
|
||||||
data.table.nodes.forEach((node, index) => {
|
return showNavigationMenu();
|
||||||
console.log(boxen(
|
case "5":
|
||||||
`Peer ${index + 1}:\n` +
|
process.exit(0);
|
||||||
`${chalk.cyan('Peer ID:')} ${node.peerId}\n` +
|
}
|
||||||
`${chalk.cyan('Address:')} ${node.address}\n` +
|
|
||||||
`${chalk.cyan('Status:')} ${node.seen ? chalk.green('Active') : chalk.gray('Inactive')}`,
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'blue'
|
|
||||||
}
|
|
||||||
));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(showInfoMessage('No connected peers found.'));
|
|
||||||
}
|
|
||||||
return showNodeDetails(data, showNavigationMenu);
|
|
||||||
case '2':
|
|
||||||
console.log(boxen(
|
|
||||||
`${chalk.cyan('Version:')} ${data.codex.version}\n` +
|
|
||||||
`${chalk.cyan('Revision:')} ${data.codex.revision}\n\n` +
|
|
||||||
`${chalk.cyan('Node ID:')} ${data.table.localNode.nodeId}\n` +
|
|
||||||
`${chalk.cyan('Peer ID:')} ${data.table.localNode.peerId}\n` +
|
|
||||||
`${chalk.cyan('Listening Address:')} ${data.table.localNode.address}\n\n` +
|
|
||||||
`${chalk.cyan('Public IP:')} ${data.announceAddresses[0].split('/')[2]}\n` +
|
|
||||||
`${chalk.cyan('Port:')} ${data.announceAddresses[0].split('/')[4]}\n` +
|
|
||||||
`${chalk.cyan('Connected Peers:')} ${data.table.nodes.length}`,
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'yellow',
|
|
||||||
title: '📊 Node Information',
|
|
||||||
titleAlignment: 'center'
|
|
||||||
}
|
|
||||||
));
|
|
||||||
return showNodeDetails(data, showNavigationMenu);
|
|
||||||
case '3':
|
|
||||||
try {
|
|
||||||
const existingWallet = await getWalletAddress();
|
|
||||||
|
|
||||||
console.log(boxen(
|
|
||||||
`${chalk.cyan('Current wallet address:')}\n${existingWallet || 'Not set'}`,
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: 'blue'
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
const wallet = await promptForWalletAddress();
|
|
||||||
if (wallet) {
|
|
||||||
await setWalletAddress(wallet);
|
|
||||||
console.log(showSuccessMessage('Wallet address updated successfully!'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(showErrorMessage(`Failed to update wallet address: ${error.message}`));
|
|
||||||
}
|
|
||||||
return showNodeDetails(data, showNavigationMenu);
|
|
||||||
case '4':
|
|
||||||
return showNavigationMenu();
|
|
||||||
case '5':
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkNodeStatus(config, showNavigationMenu) {
|
export async function checkNodeStatus(config, showNavigationMenu) {
|
||||||
try {
|
try {
|
||||||
const nodeRunning = await isNodeRunning(config);
|
const nodeRunning = await isNodeRunning(config);
|
||||||
|
|
||||||
if (nodeRunning) {
|
if (nodeRunning) {
|
||||||
const spinner = createSpinner('Checking node status...').start();
|
const spinner = createSpinner("Checking node status...").start();
|
||||||
const response = await runCommand(`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
|
const response = await runCommand(
|
||||||
spinner.success();
|
`curl http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`,
|
||||||
|
);
|
||||||
const data = JSON.parse(response);
|
spinner.success();
|
||||||
|
|
||||||
const peerCount = data.table.nodes.length;
|
|
||||||
const isOnline = peerCount > 2;
|
|
||||||
|
|
||||||
console.log(boxen(
|
|
||||||
isOnline
|
|
||||||
? chalk.green('Node is ONLINE & DISCOVERABLE')
|
|
||||||
: chalk.yellow('Node is ONLINE but has few peers'),
|
|
||||||
{
|
|
||||||
padding: 1,
|
|
||||||
margin: 1,
|
|
||||||
borderStyle: 'round',
|
|
||||||
borderColor: isOnline ? 'green' : 'yellow',
|
|
||||||
title: '🔌 Node Status',
|
|
||||||
titleAlignment: 'center'
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
await showNodeDetails(data, showNavigationMenu);
|
const data = JSON.parse(response);
|
||||||
} else {
|
|
||||||
console.log(showErrorMessage('Codex node is not running. Try again after starting the node'));
|
const peerCount = data.table.nodes.length;
|
||||||
await showNavigationMenu();
|
const isOnline = peerCount > 2;
|
||||||
}
|
|
||||||
} catch (error) {
|
console.log(
|
||||||
console.log(showErrorMessage(`Failed to check node status: ${error.message}`));
|
boxen(
|
||||||
await showNavigationMenu();
|
isOnline
|
||||||
|
? chalk.green("Node is ONLINE & DISCOVERABLE")
|
||||||
|
: chalk.yellow("Node is ONLINE but has few peers"),
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: isOnline ? "green" : "yellow",
|
||||||
|
title: "🔌 Node Status",
|
||||||
|
titleAlignment: "center",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await showNodeDetails(data, showNavigationMenu);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(
|
||||||
|
"Codex node is not running. Try again after starting the node",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await showNavigationMenu();
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
showErrorMessage(`Failed to check node status: ${error.message}`),
|
||||||
|
);
|
||||||
|
await showNavigationMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
325
src/main.js
325
src/main.js
@ -1,155 +1,200 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from "inquirer";
|
||||||
import chalk from 'chalk';
|
import chalk from "chalk";
|
||||||
import boxen from 'boxen';
|
import boxen from "boxen";
|
||||||
import { ASCII_ART } from './constants/ascii.js';
|
import { ASCII_ART } from "./constants/ascii.js";
|
||||||
import { handleCommandLineOperation, parseCommandLineArgs } from './cli/commandParser.js';
|
import {
|
||||||
import { uploadFile, downloadFile, showLocalFiles } from './handlers/fileHandlers.js';
|
handleCommandLineOperation,
|
||||||
import { installCodex, uninstallCodex } from './handlers/installationHandlers.js';
|
parseCommandLineArgs,
|
||||||
import { runCodex, checkNodeStatus } from './handlers/nodeHandlers.js';
|
} from "./cli/commandParser.js";
|
||||||
import { showInfoMessage } from './utils/messages.js';
|
import {
|
||||||
import { loadConfig } from './services/config.js';
|
uploadFile,
|
||||||
import { showConfigMenu } from './configmenu.js';
|
downloadFile,
|
||||||
import { openCodexApp } from './services/codexapp.js';
|
showLocalFiles,
|
||||||
|
} from "./handlers/fileHandlers.js";
|
||||||
|
import {
|
||||||
|
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";
|
||||||
|
import { showConfigMenu } from "./configmenu.js";
|
||||||
|
import { openCodexApp } from "./services/codexapp.js";
|
||||||
|
|
||||||
|
import { MainMenu } from "./ui/mainmenu.js";
|
||||||
|
import { UiService } from "./services/uiservice.js";
|
||||||
|
|
||||||
async function showNavigationMenu() {
|
async function showNavigationMenu() {
|
||||||
console.log('\n')
|
console.log("\n");
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'list',
|
type: "list",
|
||||||
name: 'choice',
|
name: "choice",
|
||||||
message: 'What would you like to do?',
|
message: "What would you like to do?",
|
||||||
choices: [
|
choices: ["1. Back to main menu", "2. Exit"],
|
||||||
'1. Back to main menu',
|
pageSize: 2,
|
||||||
'2. Exit'
|
loop: true,
|
||||||
],
|
},
|
||||||
pageSize: 2,
|
]);
|
||||||
loop: true
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
switch (choice.split('.')[0]) {
|
switch (choice.split(".")[0]) {
|
||||||
case '1':
|
case "1":
|
||||||
return main();
|
return main();
|
||||||
case '2':
|
case "2":
|
||||||
handleExit();
|
handleExit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleExit() {
|
function handleExit() {
|
||||||
console.log(boxen(
|
console.log(
|
||||||
chalk.cyanBright('👋 Thank you for using Codex Storage CLI! Goodbye!'),
|
boxen(
|
||||||
{
|
chalk.cyanBright("👋 Thank you for using Codex Storage CLI! Goodbye!"),
|
||||||
padding: 1,
|
{
|
||||||
margin: 1,
|
padding: 1,
|
||||||
borderStyle: 'round',
|
margin: 1,
|
||||||
borderColor: 'cyan',
|
borderStyle: "round",
|
||||||
title: '👋 GOODBYE',
|
borderColor: "cyan",
|
||||||
titleAlignment: 'center'
|
title: "👋 GOODBYE",
|
||||||
}
|
titleAlignment: "center",
|
||||||
));
|
},
|
||||||
process.exit(0);
|
),
|
||||||
|
);
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const commandArgs = parseCommandLineArgs();
|
const commandArgs = parseCommandLineArgs();
|
||||||
if (commandArgs) {
|
if (commandArgs) {
|
||||||
switch (commandArgs.command) {
|
switch (commandArgs.command) {
|
||||||
case 'upload':
|
case "upload":
|
||||||
await uploadFile(commandArgs.value, handleCommandLineOperation, showNavigationMenu);
|
await uploadFile(
|
||||||
return;
|
commandArgs.value,
|
||||||
case 'download':
|
handleCommandLineOperation,
|
||||||
await downloadFile(commandArgs.value, handleCommandLineOperation, showNavigationMenu);
|
showNavigationMenu,
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
|
case "download":
|
||||||
|
await downloadFile(
|
||||||
|
commandArgs.value,
|
||||||
|
handleCommandLineOperation,
|
||||||
|
showNavigationMenu,
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
process.on('SIGINT', handleExit);
|
process.on("SIGINT", handleExit);
|
||||||
process.on('SIGTERM', handleExit);
|
process.on("SIGTERM", handleExit);
|
||||||
process.on('SIGQUIT', handleExit);
|
process.on("SIGQUIT", handleExit);
|
||||||
|
|
||||||
try {
|
|
||||||
const config = loadConfig();
|
|
||||||
while (true) {
|
|
||||||
console.log('\n' + chalk.cyanBright(ASCII_ART));
|
|
||||||
const { choice } = await inquirer.prompt([
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'choice',
|
|
||||||
message: 'Select an option:',
|
|
||||||
choices: [
|
|
||||||
'1. Download and install Codex',
|
|
||||||
'2. Run Codex node',
|
|
||||||
'3. Check node status',
|
|
||||||
'4. Edit Codex configuration',
|
|
||||||
'5. Open Codex App',
|
|
||||||
'6. Upload a file',
|
|
||||||
'7. Download a file',
|
|
||||||
'8. Show local data',
|
|
||||||
'9. Uninstall Codex node',
|
|
||||||
'10. Submit feedback',
|
|
||||||
'11. Exit'
|
|
||||||
],
|
|
||||||
pageSize: 11,
|
|
||||||
loop: true
|
|
||||||
}
|
|
||||||
]).catch(() => {
|
|
||||||
handleExit();
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (choice.split('.')[0]) {
|
|
||||||
case '1':
|
|
||||||
const installed = await installCodex(config, showNavigationMenu);
|
|
||||||
if (installed) {
|
|
||||||
await showConfigMenu(config);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
await runCodex(config, showNavigationMenu);
|
|
||||||
return;
|
|
||||||
case '3':
|
|
||||||
await checkNodeStatus(config, showNavigationMenu);
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
await showConfigMenu(config);
|
|
||||||
break;
|
|
||||||
case '5':
|
|
||||||
openCodexApp(config);
|
|
||||||
break;
|
|
||||||
case '6':
|
|
||||||
await uploadFile(config, null, handleCommandLineOperation, showNavigationMenu);
|
|
||||||
break;
|
|
||||||
case '7':
|
|
||||||
await downloadFile(config, null, handleCommandLineOperation, showNavigationMenu);
|
|
||||||
break;
|
|
||||||
case '8':
|
|
||||||
await showLocalFiles(config, showNavigationMenu);
|
|
||||||
break;
|
|
||||||
case '9':
|
|
||||||
await uninstallCodex(config, showNavigationMenu);
|
|
||||||
break;
|
|
||||||
case '10':
|
|
||||||
const { exec } = await import('child_process');
|
|
||||||
const url = 'https://tally.so/r/w2DlXb';
|
|
||||||
const command = process.platform === 'win32' ? `start ${url}` : process.platform === 'darwin' ? `open ${url}` : `xdg-open ${url}`;
|
|
||||||
exec(command);
|
|
||||||
console.log(showInfoMessage('Opening feedback form in your browser...'));
|
|
||||||
break;
|
|
||||||
case '11':
|
|
||||||
handleExit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n');
|
const uiService = new UiService();
|
||||||
}
|
const mainMenu = new MainMenu(uiService);
|
||||||
} catch (error) {
|
|
||||||
if (error.message.includes('ExitPromptError')) {
|
mainMenu.show();
|
||||||
handleExit();
|
return;
|
||||||
} else {
|
|
||||||
console.error(chalk.red('An error occurred:', error.message));
|
try {
|
||||||
handleExit();
|
const config = loadConfig();
|
||||||
}
|
while (true) {
|
||||||
|
console.log("\n" + chalk.cyanBright(ASCII_ART));
|
||||||
|
const { choice } = await inquirer
|
||||||
|
.prompt([
|
||||||
|
{
|
||||||
|
type: "list",
|
||||||
|
name: "choice",
|
||||||
|
message: "Select an option:",
|
||||||
|
choices: [
|
||||||
|
"1. Download and install Codex",
|
||||||
|
"2. Run Codex node",
|
||||||
|
"3. Check node status",
|
||||||
|
"4. Edit Codex configuration",
|
||||||
|
"5. Open Codex App",
|
||||||
|
"6. Upload a file",
|
||||||
|
"7. Download a file",
|
||||||
|
"8. Show local data",
|
||||||
|
"9. Uninstall Codex node",
|
||||||
|
"10. Submit feedback",
|
||||||
|
"11. Exit",
|
||||||
|
],
|
||||||
|
pageSize: 11,
|
||||||
|
loop: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.catch(() => {
|
||||||
|
handleExit();
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (choice.split(".")[0]) {
|
||||||
|
case "1":
|
||||||
|
const installed = await installCodex(config, showNavigationMenu);
|
||||||
|
if (installed) {
|
||||||
|
await showConfigMenu(config);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
await runCodex(config, showNavigationMenu);
|
||||||
|
return;
|
||||||
|
case "3":
|
||||||
|
await checkNodeStatus(config, showNavigationMenu);
|
||||||
|
break;
|
||||||
|
case "4":
|
||||||
|
await showConfigMenu(config);
|
||||||
|
break;
|
||||||
|
case "5":
|
||||||
|
openCodexApp(config);
|
||||||
|
break;
|
||||||
|
case "6":
|
||||||
|
await uploadFile(
|
||||||
|
config,
|
||||||
|
null,
|
||||||
|
handleCommandLineOperation,
|
||||||
|
showNavigationMenu,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "7":
|
||||||
|
await downloadFile(
|
||||||
|
config,
|
||||||
|
null,
|
||||||
|
handleCommandLineOperation,
|
||||||
|
showNavigationMenu,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "8":
|
||||||
|
await showLocalFiles(config, showNavigationMenu);
|
||||||
|
break;
|
||||||
|
case "9":
|
||||||
|
await uninstallCodex(config, showNavigationMenu);
|
||||||
|
break;
|
||||||
|
case "10":
|
||||||
|
const { exec } = await import("child_process");
|
||||||
|
const url = "https://tally.so/r/w2DlXb";
|
||||||
|
const command =
|
||||||
|
process.platform === "win32"
|
||||||
|
? `start ${url}`
|
||||||
|
: process.platform === "darwin"
|
||||||
|
? `open ${url}`
|
||||||
|
: `xdg-open ${url}`;
|
||||||
|
exec(command);
|
||||||
|
console.log(
|
||||||
|
showInfoMessage("Opening feedback form in your browser..."),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "11":
|
||||||
|
handleExit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n");
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
if (error.message.includes("ExitPromptError")) {
|
||||||
|
handleExit();
|
||||||
|
} else {
|
||||||
|
console.error(chalk.red("An error occurred:", error.message));
|
||||||
|
handleExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import open from 'open';
|
import open from "open";
|
||||||
|
|
||||||
export function openCodexApp(config) {
|
export function openCodexApp(config) {
|
||||||
// TODO: Update this to the main URL when the PR for adding api-port query parameter support
|
// TODO: Update this to the main URL when the PR for adding api-port query parameter support
|
||||||
@ -6,12 +6,12 @@ export function openCodexApp(config) {
|
|||||||
// See: https://github.com/codex-storage/codex-marketplace-ui/issues/92
|
// See: https://github.com/codex-storage/codex-marketplace-ui/issues/92
|
||||||
|
|
||||||
const segments = [
|
const segments = [
|
||||||
'https://releases-v0-0-14.codex-marketplace-ui.pages.dev/',
|
"https://releases-v0-0-14.codex-marketplace-ui.pages.dev/",
|
||||||
'?',
|
"?",
|
||||||
`api-port=${config.ports.apiPort}`
|
`api-port=${config.ports.apiPort}`,
|
||||||
]
|
];
|
||||||
|
|
||||||
const url = segments.join("");
|
const url = segments.join("");
|
||||||
|
|
||||||
open(url);
|
open(url);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
import path from 'path';
|
import path from "path";
|
||||||
import { getAppDataDir } from '../utils/appdata.js';
|
import { getAppDataDir } from "../utils/appdata.js";
|
||||||
import { getCodexDataDirDefaultPath, getCodexLogsDefaultPath } from '../utils/appdata.js';
|
import {
|
||||||
|
getCodexDataDirDefaultPath,
|
||||||
|
getCodexLogsDefaultPath,
|
||||||
|
} from "../utils/appdata.js";
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
codexExe: "",
|
codexExe: "",
|
||||||
@ -12,8 +15,8 @@ const defaultConfig = {
|
|||||||
ports: {
|
ports: {
|
||||||
discPort: 8090,
|
discPort: 8090,
|
||||||
listenPort: 8070,
|
listenPort: 8070,
|
||||||
apiPort: 8080
|
apiPort: 8080,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getConfigFilename() {
|
function getConfigFilename() {
|
||||||
@ -25,7 +28,9 @@ export function saveConfig(config) {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(filePath, JSON.stringify(config));
|
fs.writeFileSync(filePath, JSON.stringify(config));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to save config file to '${filePath}' error: '${error}'.`);
|
console.error(
|
||||||
|
`Failed to save config file to '${filePath}' error: '${error}'.`,
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +44,9 @@ export function loadConfig() {
|
|||||||
}
|
}
|
||||||
return JSON.parse(fs.readFileSync(filePath));
|
return JSON.parse(fs.readFileSync(filePath));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to load config file from '${filePath}' error: '${error}'.`);
|
console.error(
|
||||||
|
`Failed to load config file from '${filePath}' error: '${error}'.`,
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import axios from 'axios';
|
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 {
|
||||||
import os from 'os';
|
showErrorMessage,
|
||||||
import { getCodexVersion } from '../handlers/installationHandlers.js';
|
showInfoMessage,
|
||||||
|
showSuccessMessage,
|
||||||
|
} from "../utils/messages.js";
|
||||||
|
import os from "os";
|
||||||
|
import { getCodexVersion } from "../handlers/installationHandlers.js";
|
||||||
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
@ -10,145 +14,175 @@ const platform = os.platform();
|
|||||||
let currentWallet = null;
|
let currentWallet = null;
|
||||||
|
|
||||||
export async function setWalletAddress(wallet) {
|
export async function setWalletAddress(wallet) {
|
||||||
// Basic ERC20 address validation
|
// Basic ERC20 address validation
|
||||||
if (wallet && !/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
if (wallet && !/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
||||||
throw new Error('Invalid ERC20 wallet address format');
|
throw new Error("Invalid ERC20 wallet address format");
|
||||||
}
|
}
|
||||||
currentWallet = wallet;
|
currentWallet = wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWalletAddress() {
|
export async function getWalletAddress() {
|
||||||
return currentWallet;
|
return currentWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isNodeRunning(config) {
|
export async function isNodeRunning(config) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
|
const response = await axios.get(
|
||||||
return response.status === 200;
|
`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`,
|
||||||
} catch (error) {
|
);
|
||||||
return false;
|
return response.status === 200;
|
||||||
}
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isCodexInstalled(config) {
|
export async function isCodexInstalled(config) {
|
||||||
try {
|
try {
|
||||||
const version = await getCodexVersion(config);
|
const version = await getCodexVersion(config);
|
||||||
return version.length > 0;
|
return version.length > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logToSupabase(nodeData, retryCount = 3, retryDelay = 1000) {
|
export async function logToSupabase(
|
||||||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
nodeData,
|
||||||
|
retryCount = 3,
|
||||||
|
retryDelay = 1000,
|
||||||
|
) {
|
||||||
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= retryCount; attempt++) {
|
for (let attempt = 1; attempt <= retryCount; attempt++) {
|
||||||
try {
|
try {
|
||||||
const peerCount = nodeData.table.nodes ? nodeData.table.nodes.length : "0";
|
const peerCount = nodeData.table.nodes
|
||||||
const payload = {
|
? nodeData.table.nodes.length
|
||||||
nodeId: nodeData.table.localNode.nodeId,
|
: "0";
|
||||||
peerId: nodeData.table.localNode.peerId,
|
const payload = {
|
||||||
publicIp: nodeData.announceAddresses[0].split('/')[2],
|
nodeId: nodeData.table.localNode.nodeId,
|
||||||
version: nodeData.codex.version,
|
peerId: nodeData.table.localNode.peerId,
|
||||||
peerCount: peerCount == 0 ? "0" : peerCount,
|
publicIp: nodeData.announceAddresses[0].split("/")[2],
|
||||||
port: nodeData.announceAddresses[0].split('/')[4],
|
version: nodeData.codex.version,
|
||||||
listeningAddress: nodeData.table.localNode.address,
|
peerCount: peerCount == 0 ? "0" : peerCount,
|
||||||
timestamp: new Date().toISOString(),
|
port: nodeData.announceAddresses[0].split("/")[4],
|
||||||
wallet: currentWallet
|
listeningAddress: nodeData.table.localNode.address,
|
||||||
};
|
timestamp: new Date().toISOString(),
|
||||||
|
wallet: currentWallet,
|
||||||
|
};
|
||||||
|
|
||||||
const response = await axios.post('https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/codexnodes', payload, {
|
const response = await axios.post(
|
||||||
headers: {
|
"https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/codexnodes",
|
||||||
'Content-Type': 'application/json'
|
payload,
|
||||||
},
|
{
|
||||||
timeout: 5000
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
return response.status === 200;
|
timeout: 5000,
|
||||||
} catch (error) {
|
},
|
||||||
const isLastAttempt = attempt === retryCount;
|
);
|
||||||
const isNetworkError = error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT' || error.code === 'ECONNREFUSED';
|
|
||||||
|
|
||||||
if (isLastAttempt || !isNetworkError) {
|
return response.status === 200;
|
||||||
console.error(`Failed to log node data (attempt ${attempt}/${retryCount}):`, error.message);
|
} catch (error) {
|
||||||
if (error.response) {
|
const isLastAttempt = attempt === retryCount;
|
||||||
console.error('Error response:', {
|
const isNetworkError =
|
||||||
status: error.response.status,
|
error.code === "ENOTFOUND" ||
|
||||||
data: error.response.data
|
error.code === "ETIMEDOUT" ||
|
||||||
});
|
error.code === "ECONNREFUSED";
|
||||||
}
|
|
||||||
if (isLastAttempt) return false;
|
if (isLastAttempt || !isNetworkError) {
|
||||||
} else {
|
console.error(
|
||||||
// Only log retry attempts for network errors
|
`Failed to log node data (attempt ${attempt}/${retryCount}):`,
|
||||||
console.log(`Retrying to log data (attempt ${attempt}/${retryCount})...`);
|
error.message,
|
||||||
await delay(retryDelay);
|
);
|
||||||
}
|
if (error.response) {
|
||||||
|
console.error("Error response:", {
|
||||||
|
status: error.response.status,
|
||||||
|
data: error.response.data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
if (isLastAttempt) return false;
|
||||||
|
} else {
|
||||||
|
// Only log retry attempts for network errors
|
||||||
|
console.log(
|
||||||
|
`Retrying to log data (attempt ${attempt}/${retryCount})...`,
|
||||||
|
);
|
||||||
|
await delay(retryDelay);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDependencies() {
|
export async function checkDependencies() {
|
||||||
if (platform === 'linux') {
|
if (platform === "linux") {
|
||||||
try {
|
try {
|
||||||
await runCommand('ldconfig -p | grep libgomp');
|
await runCommand("ldconfig -p | grep libgomp");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(showErrorMessage('Required dependency libgomp1 is not installed.'));
|
console.log(
|
||||||
console.log(showInfoMessage(
|
showErrorMessage("Required dependency libgomp1 is not installed."),
|
||||||
'For Debian-based Linux systems, please install it manually using:\n\n' +
|
);
|
||||||
'sudo apt update && sudo apt install libgomp1'
|
console.log(
|
||||||
));
|
showInfoMessage(
|
||||||
return false;
|
"For Debian-based Linux systems, please install it manually using:\n\n" +
|
||||||
}
|
"sudo apt update && sudo apt install libgomp1",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startPeriodicLogging(config) {
|
export async function startPeriodicLogging(config) {
|
||||||
const FIFTEEN_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds
|
const FIFTEEN_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds
|
||||||
|
|
||||||
const logNodeInfo = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`);
|
|
||||||
if (response.status === 200) {
|
|
||||||
await logToSupabase(response.data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Silently handle any logging errors to not disrupt the node operation
|
|
||||||
console.error('Failed to log node data:', error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initial log
|
const logNodeInfo = async () => {
|
||||||
await logNodeInfo();
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`,
|
||||||
|
);
|
||||||
|
if (response.status === 200) {
|
||||||
|
await logToSupabase(response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Silently handle any logging errors to not disrupt the node operation
|
||||||
|
console.error("Failed to log node data:", error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Set up periodic logging
|
// Initial log
|
||||||
const intervalId = setInterval(logNodeInfo, FIFTEEN_MINUTES);
|
await logNodeInfo();
|
||||||
|
|
||||||
// Return cleanup function
|
// Set up periodic logging
|
||||||
return () => clearInterval(intervalId);
|
const intervalId = setInterval(logNodeInfo, FIFTEEN_MINUTES);
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateWalletAddress(nodeId, wallet) {
|
export async function updateWalletAddress(nodeId, wallet) {
|
||||||
// Basic ERC20 address validation
|
// Basic ERC20 address validation
|
||||||
if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) {
|
||||||
throw new Error('Invalid ERC20 wallet address format');
|
throw new Error("Invalid ERC20 wallet address format");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/wallet', {
|
const response = await axios.post(
|
||||||
nodeId,
|
"https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/wallet",
|
||||||
wallet
|
{
|
||||||
}, {
|
nodeId,
|
||||||
headers: {
|
wallet,
|
||||||
'Content-Type': 'application/json'
|
},
|
||||||
},
|
{
|
||||||
timeout: 5000
|
headers: {
|
||||||
});
|
"Content-Type": "application/json",
|
||||||
return response.status === 200;
|
},
|
||||||
} catch (error) {
|
timeout: 5000,
|
||||||
console.error('Failed to update wallet address:', error.message);
|
},
|
||||||
throw error;
|
);
|
||||||
}
|
return response.status === 200;
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Failed to update wallet address:", error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
47
src/services/uiservice.js
Normal file
47
src/services/uiservice.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import boxen from "boxen";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
function show(msg) {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UiService {
|
||||||
|
showSuccessMessage = (message) => {
|
||||||
|
show(
|
||||||
|
boxen(chalk.green(message), {
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "green",
|
||||||
|
title: "✅ SUCCESS",
|
||||||
|
titleAlignment: "center",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
showErrorMessage = (message) => {
|
||||||
|
show(
|
||||||
|
boxen(chalk.red(message), {
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "red",
|
||||||
|
title: "❌ ERROR",
|
||||||
|
titleAlignment: "center",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
showInfoMessage(message) {
|
||||||
|
show(
|
||||||
|
boxen(chalk.cyan(message), {
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: "round",
|
||||||
|
borderColor: "cyan",
|
||||||
|
title: "ℹ️ INFO",
|
||||||
|
titleAlignment: "center",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/ui/mainmenu.js
Normal file
9
src/ui/mainmenu.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export class MainMenu {
|
||||||
|
constructor(uiService) {
|
||||||
|
this.ui = uiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
show = () => {
|
||||||
|
this.ui.showInfoMessage("hello");
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import path from 'path';
|
import path from "path";
|
||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
|
|
||||||
export function getAppDataDir() {
|
export function getAppDataDir() {
|
||||||
return ensureExists(appData("codex-cli"));
|
return ensureExists(appData("codex-cli"));
|
||||||
@ -32,10 +32,15 @@ function ensureExists(dir) {
|
|||||||
|
|
||||||
function appData(...app) {
|
function appData(...app) {
|
||||||
let appData;
|
let appData;
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === "win32") {
|
||||||
appData = path.join(process.env.APPDATA, ...app);
|
appData = path.join(process.env.APPDATA, ...app);
|
||||||
} else if (process.platform === 'darwin') {
|
} else if (process.platform === "darwin") {
|
||||||
appData = path.join(process.env.HOME, 'Library', 'Application Support', ...app);
|
appData = path.join(
|
||||||
|
process.env.HOME,
|
||||||
|
"Library",
|
||||||
|
"Application Support",
|
||||||
|
...app,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
appData = path.join(process.env.HOME, ...prependDot(...app));
|
appData = path.join(process.env.HOME, ...prependDot(...app));
|
||||||
}
|
}
|
||||||
@ -51,4 +56,3 @@ function prependDot(...app) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from "child_process";
|
||||||
import { promisify } from 'util';
|
import { promisify } from "util";
|
||||||
|
|
||||||
export const execAsync = promisify(exec);
|
export const execAsync = promisify(exec);
|
||||||
|
|
||||||
export async function runCommand(command) {
|
export async function runCommand(command) {
|
||||||
try {
|
try {
|
||||||
const { stdout, stderr } = await execAsync(command);
|
const { stdout, stderr } = await execAsync(command);
|
||||||
return stdout;
|
return stdout;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error.message);
|
console.error("Error:", error.message);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,35 @@
|
|||||||
import boxen from 'boxen';
|
import boxen from "boxen";
|
||||||
import chalk from 'chalk';
|
import chalk from "chalk";
|
||||||
|
|
||||||
export function showSuccessMessage(message) {
|
export function showSuccessMessage(message) {
|
||||||
return boxen(chalk.green(message), {
|
return boxen(chalk.green(message), {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
margin: 1,
|
margin: 1,
|
||||||
borderStyle: 'round',
|
borderStyle: "round",
|
||||||
borderColor: 'green',
|
borderColor: "green",
|
||||||
title: '✅ SUCCESS',
|
title: "✅ SUCCESS",
|
||||||
titleAlignment: 'center'
|
titleAlignment: "center",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showErrorMessage(message) {
|
export function showErrorMessage(message) {
|
||||||
return boxen(chalk.red(message), {
|
return boxen(chalk.red(message), {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
margin: 1,
|
margin: 1,
|
||||||
borderStyle: 'round',
|
borderStyle: "round",
|
||||||
borderColor: 'red',
|
borderColor: "red",
|
||||||
title: '❌ ERROR',
|
title: "❌ ERROR",
|
||||||
titleAlignment: 'center'
|
titleAlignment: "center",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showInfoMessage(message) {
|
export function showInfoMessage(message) {
|
||||||
return boxen(chalk.cyan(message), {
|
return boxen(chalk.cyan(message), {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
margin: 1,
|
margin: 1,
|
||||||
borderStyle: 'round',
|
borderStyle: "round",
|
||||||
borderColor: 'cyan',
|
borderColor: "cyan",
|
||||||
title: 'ℹ️ INFO',
|
title: "ℹ️ INFO",
|
||||||
titleAlignment: 'center'
|
titleAlignment: "center",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import inquirer from 'inquirer';
|
import inquirer from "inquirer";
|
||||||
|
|
||||||
function getMetricsMult(valueStr, allowMetricPostfixes) {
|
function getMetricsMult(valueStr, allowMetricPostfixes) {
|
||||||
if (!allowMetricPostfixes) return 1;
|
if (!allowMetricPostfixes) return 1;
|
||||||
@ -27,14 +27,19 @@ function getNumericValue(valueStr) {
|
|||||||
async function promptForValueStr(promptMessage) {
|
async function promptForValueStr(promptMessage) {
|
||||||
const response = await inquirer.prompt([
|
const response = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'valueStr',
|
name: "valueStr",
|
||||||
message: promptMessage
|
message: promptMessage,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
return response.valueStr;
|
return response.valueStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showNumberSelector(currentValue, promptMessage, allowMetricPostfixes) {
|
export async function showNumberSelector(
|
||||||
|
currentValue,
|
||||||
|
promptMessage,
|
||||||
|
allowMetricPostfixes,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
var valueStr = await promptForValueStr(promptMessage);
|
var valueStr = await promptForValueStr(promptMessage);
|
||||||
valueStr = valueStr.replaceAll(" ", "");
|
valueStr = valueStr.replaceAll(" ", "");
|
||||||
|
|||||||
@ -1,29 +1,31 @@
|
|||||||
import path from 'path';
|
import path from "path";
|
||||||
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 fs from 'fs';
|
import fs from "fs";
|
||||||
import { filesystemSync } from 'fs-filesystem';
|
import { filesystemSync } from "fs-filesystem";
|
||||||
|
|
||||||
function showMsg(msg) {
|
function showMsg(msg) {
|
||||||
console.log(boxen(chalk.white(msg), {
|
console.log(
|
||||||
padding: 1,
|
boxen(chalk.white(msg), {
|
||||||
margin: 1,
|
padding: 1,
|
||||||
borderStyle: 'round',
|
margin: 1,
|
||||||
borderColor: 'white',
|
borderStyle: "round",
|
||||||
titleAlignment: 'center'
|
borderColor: "white",
|
||||||
}));
|
titleAlignment: "center",
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableRoots() {
|
function getAvailableRoots() {
|
||||||
const devices = filesystemSync();
|
const devices = filesystemSync();
|
||||||
var mountPoints = [];
|
var mountPoints = [];
|
||||||
Object.keys(devices).forEach(function(key) {
|
Object.keys(devices).forEach(function (key) {
|
||||||
var val = devices[key];
|
var val = devices[key];
|
||||||
val.volumes.forEach(function(volume) {
|
val.volumes.forEach(function (volume) {
|
||||||
mountPoints.push(volume.mountPoint);
|
mountPoints.push(volume.mountPoint);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (mountPoints.length < 1) {
|
if (mountPoints.length < 1) {
|
||||||
throw new Error("Failed to detect file system devices.");
|
throw new Error("Failed to detect file system devices.");
|
||||||
@ -37,11 +39,11 @@ function splitPath(str) {
|
|||||||
|
|
||||||
function dropEmptyParts(parts) {
|
function dropEmptyParts(parts) {
|
||||||
var result = [];
|
var result = [];
|
||||||
parts.forEach(function(part) {
|
parts.forEach(function (part) {
|
||||||
if (part.length > 0) {
|
if (part.length > 0) {
|
||||||
result.push(part);
|
result.push(part);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +65,10 @@ function showCurrent(currentPath) {
|
|||||||
|
|
||||||
if (len < 2) {
|
if (len < 2) {
|
||||||
showMsg(
|
showMsg(
|
||||||
'Warning - Known issue:\n' +
|
"Warning - Known issue:\n" +
|
||||||
'Path selection does not work in root paths on some platforms.\n' +
|
"Path selection does not work in root paths on some platforms.\n" +
|
||||||
'Use "Enter path" or "Create new folder" to navigate and create folders\n' +
|
'Use "Enter path" or "Create new folder" to navigate and create folders\n' +
|
||||||
'if this is the case for you.'
|
"if this is the case for you.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +76,7 @@ function showCurrent(currentPath) {
|
|||||||
function hasValidRoot(roots, checkPath) {
|
function hasValidRoot(roots, checkPath) {
|
||||||
if (checkPath.length < 1) return false;
|
if (checkPath.length < 1) return false;
|
||||||
var result = false;
|
var result = false;
|
||||||
roots.forEach(function(root) {
|
roots.forEach(function (root) {
|
||||||
if (root.toLowerCase() == checkPath[0].toLowerCase()) {
|
if (root.toLowerCase() == checkPath[0].toLowerCase()) {
|
||||||
console.log("valid root: " + combine(checkPath));
|
console.log("valid root: " + combine(checkPath));
|
||||||
result = true;
|
result = true;
|
||||||
@ -86,26 +88,28 @@ function hasValidRoot(roots, checkPath) {
|
|||||||
|
|
||||||
async function showMain(currentPath) {
|
async function showMain(currentPath) {
|
||||||
showCurrent(currentPath);
|
showCurrent(currentPath);
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer
|
||||||
{
|
.prompt([
|
||||||
type: 'list',
|
{
|
||||||
name: 'choice',
|
type: "list",
|
||||||
message: 'Select an option:',
|
name: "choice",
|
||||||
|
message: "Select an option:",
|
||||||
choices: [
|
choices: [
|
||||||
'1. Enter path',
|
"1. Enter path",
|
||||||
'2. Go up one',
|
"2. Go up one",
|
||||||
'3. Go down one',
|
"3. Go down one",
|
||||||
'4. Create new folder here',
|
"4. Create new folder here",
|
||||||
'5. Select this path',
|
"5. Select this path",
|
||||||
'6. Cancel'
|
"6. Cancel",
|
||||||
],
|
],
|
||||||
pageSize: 6,
|
pageSize: 6,
|
||||||
loop: true
|
loop: true,
|
||||||
}
|
},
|
||||||
]).catch(() => {
|
])
|
||||||
handleExit();
|
.catch(() => {
|
||||||
return { choice: '6' };
|
handleExit();
|
||||||
});
|
return { choice: "6" };
|
||||||
|
});
|
||||||
|
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
@ -121,28 +125,28 @@ export async function showPathSelector(startingPath, pathMustExist) {
|
|||||||
const choice = await showMain(currentPath);
|
const choice = await showMain(currentPath);
|
||||||
|
|
||||||
var newCurrentPath = currentPath;
|
var newCurrentPath = currentPath;
|
||||||
switch (choice.split('.')[0]) {
|
switch (choice.split(".")[0]) {
|
||||||
case '1':
|
case "1":
|
||||||
newCurrentPath = await enterPath(currentPath, pathMustExist);
|
newCurrentPath = await enterPath(currentPath, pathMustExist);
|
||||||
break;
|
break;
|
||||||
case '2':
|
case "2":
|
||||||
newCurrentPath = upOne(currentPath);
|
newCurrentPath = upOne(currentPath);
|
||||||
break;
|
break;
|
||||||
case '3':
|
case "3":
|
||||||
newCurrentPath = await downOne(currentPath);
|
newCurrentPath = await downOne(currentPath);
|
||||||
break;
|
break;
|
||||||
case '4':
|
case "4":
|
||||||
newCurrentPath = await createSubDir(currentPath, pathMustExist);
|
newCurrentPath = await createSubDir(currentPath, pathMustExist);
|
||||||
break;
|
break;
|
||||||
case '5':
|
case "5":
|
||||||
if (pathMustExist && !isDir(combine(currentPath))) {
|
if (pathMustExist && !isDir(combine(currentPath))) {
|
||||||
console.log("Current path does not exist.");
|
console.log("Current path does not exist.");
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
return combine(currentPath);
|
return combine(currentPath);
|
||||||
}
|
}
|
||||||
case '6':
|
case "6":
|
||||||
return combine(currentPath);
|
return combine(currentPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasValidRoot(roots, newCurrentPath)) {
|
if (hasValidRoot(roots, newCurrentPath)) {
|
||||||
@ -156,10 +160,11 @@ export async function showPathSelector(startingPath, pathMustExist) {
|
|||||||
async function enterPath(currentPath, pathMustExist) {
|
async function enterPath(currentPath, pathMustExist) {
|
||||||
const response = await inquirer.prompt([
|
const response = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'path',
|
name: "path",
|
||||||
message: 'Enter Path:'
|
message: "Enter Path:",
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const newPath = response.path;
|
const newPath = response.path;
|
||||||
if (pathMustExist && !isDir(newPath)) {
|
if (pathMustExist && !isDir(newPath)) {
|
||||||
@ -191,9 +196,9 @@ function getSubDirOptions(currentPath) {
|
|||||||
const entries = fs.readdirSync(fullPath);
|
const entries = fs.readdirSync(fullPath);
|
||||||
var result = [];
|
var result = [];
|
||||||
var counter = 1;
|
var counter = 1;
|
||||||
entries.forEach(function(entry) {
|
entries.forEach(function (entry) {
|
||||||
if (isSubDir(currentPath, entry)) {
|
if (isSubDir(currentPath, entry)) {
|
||||||
result.push(counter + '. ' + entry);
|
result.push(counter + ". " + entry);
|
||||||
counter = counter + 1;
|
counter = counter + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -207,30 +212,33 @@ async function downOne(currentPath) {
|
|||||||
return currentPath;
|
return currentPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { choice } = await inquirer.prompt([
|
const { choice } = await inquirer
|
||||||
{
|
.prompt([
|
||||||
type: 'list',
|
{
|
||||||
name: 'choice',
|
type: "list",
|
||||||
message: 'Select an subdir:',
|
name: "choice",
|
||||||
|
message: "Select an subdir:",
|
||||||
choices: options,
|
choices: options,
|
||||||
pageSize: options.length,
|
pageSize: options.length,
|
||||||
loop: true
|
loop: true,
|
||||||
}
|
},
|
||||||
]).catch(() => {
|
])
|
||||||
return currentPath;
|
.catch(() => {
|
||||||
});
|
return currentPath;
|
||||||
|
});
|
||||||
|
|
||||||
const subDir = choice.split('. ')[1];
|
const subDir = choice.split(". ")[1];
|
||||||
return [...currentPath, subDir];
|
return [...currentPath, subDir];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSubDir(currentPath, pathMustExist) {
|
async function createSubDir(currentPath, pathMustExist) {
|
||||||
const response = await inquirer.prompt([
|
const response = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'input',
|
type: "input",
|
||||||
name: 'name',
|
name: "name",
|
||||||
message: 'Enter name:'
|
message: "Enter name:",
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const name = response.name;
|
const name = response.name;
|
||||||
if (name.length < 1) return;
|
if (name.length < 1) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user