From 440fe40c1c3a90460927e8ebdd34b91d8f074df6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Dec 2025 21:56:05 +0000 Subject: [PATCH 1/3] Initial plan From d6354352712f4279a186f9fe3c0eead6b861b5e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Dec 2025 22:01:51 +0000 Subject: [PATCH 2/3] Address PR review comments - fix security, correctness, and style issues Co-authored-by: RetricSu <23436060+RetricSu@users.noreply.github.com> --- .gitignore | 1 + README.md | 2 +- src/cli.ts | 7 ++++- src/cmd/status.ts | 32 ++++++++++++++++++----- src/tools/ckb-tui.ts | 61 ++++++++++++++++++++++++++++++++++---------- 5 files changed, 81 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 1b993bd..d3e662f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist/ .vscode templates/temp-clone-folder build/ +package-lock.json diff --git a/README.md b/README.md index 3748e62..4fb9061 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ offckb node --network Using a proxy RPC server for Testnet/Mainnet is especially helpful for debugging transactions, since failed transactions are dumped automatically. -**Watch Network With TUI** +**Watch Network with TUI** Once you start the CKB Node, you can use `offckb status --network devnet/testnet/mainnet` to start a CKB-TUI interface to monitor the CKB network from your node. diff --git a/src/cli.ts b/src/cli.ts index f3a9b46..4e5d92a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -160,7 +160,12 @@ program .description('Show ckb-tui status interface') .option('--network ', 'Specify the network to deploy to', 'devnet') .action(async (option) => { - status({ network: option.network }); + const validNetworks = ['devnet', 'testnet', 'mainnet']; + if (!validNetworks.includes(option.network)) { + logger.error(`Invalid network: ${option.network}. Must be one of: ${validNetworks.join(', ')}`); + process.exit(1); + } + return await status({ network: option.network }); }); program diff --git a/src/cmd/status.ts b/src/cmd/status.ts index de55454..502e89e 100644 --- a/src/cmd/status.ts +++ b/src/cmd/status.ts @@ -2,6 +2,7 @@ import { readSettings } from '../cfg/setting'; import { CKBTui } from '../tools/ckb-tui'; import { Network } from '../type/base'; import { logger } from '../util/logger'; +import * as net from 'net'; export interface StatusOptions { network?: Network; @@ -18,24 +19,41 @@ export async function status({ network }: StatusOptions) { const url = `http://127.0.0.1:${port}`; const isListening = await isRPCPortListening(port); if (!isListening) { - return logger.error( + logger.error( `RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`, ); + return; } - return CKBTui.runWithArgs(['-r', url]); + CKBTui.run(['-r', url]); } async function isRPCPortListening(port: number): Promise { - const net = require('net'); const client = new net.Socket(); return new Promise((resolve) => { + let settled = false; + const TIMEOUT_MS = 5000; + const timeout = setTimeout(() => { + if (!settled) { + settled = true; + client.destroy(); + resolve(false); + } + }, TIMEOUT_MS); client.once('error', () => { - resolve(false); + if (!settled) { + settled = true; + clearTimeout(timeout); + resolve(false); + } }); - client.connect(port, '127.0.0.1'); client.once('connect', () => { - client.end(); - resolve(true); + if (!settled) { + settled = true; + clearTimeout(timeout); + client.end(); + resolve(true); + } }); + client.connect(port, '127.0.0.1'); }); } diff --git a/src/tools/ckb-tui.ts b/src/tools/ckb-tui.ts index def8615..1b84de9 100644 --- a/src/tools/ckb-tui.ts +++ b/src/tools/ckb-tui.ts @@ -22,6 +22,11 @@ export class CKBTui { } private static downloadBinary(version: string) { + // Validate version format to prevent URL manipulation + if (!/^v\d+\.\d+\.\d+$/.test(version)) { + throw new Error(`Invalid version format: ${version}. Expected format: vX.Y.Z`); + } + const platform = process.platform; const arch = process.arch; let assetName: string; @@ -29,6 +34,8 @@ export class CKBTui { if (platform === 'darwin') { if (arch === 'arm64') { assetName = `ckb-tui-with-node-macos-aarch64.tar.gz`; + } else if (arch === 'x64') { + assetName = `ckb-tui-with-node-macos-amd64.tar.gz`; } else { throw new Error(`Unsupported architecture for macOS: ${arch}`); } @@ -63,9 +70,26 @@ export class CKBTui { execSync(`unzip "${archivePath}" -d "${binDir}"`, { stdio: 'inherit' }); } - // Assume the binary is extracted as 'ckb-tui' or 'ckb-tui.exe' - // todo: fix the bin name - const extractedBinary = platform === 'win32' ? 'ckb-tui.exe' : 'ckb-tui-macos-amd64'; + // Set the correct binary name based on platform and architecture + // TODO: Fix the bin name + let extractedBinary: string; + if (platform === 'win32') { + extractedBinary = 'ckb-tui.exe'; + } else if (platform === 'darwin') { + if (arch === 'arm64') { + extractedBinary = 'ckb-tui-macos-aarch64'; + } else { + extractedBinary = 'ckb-tui-macos-amd64'; + } + } else if (platform === 'linux') { + if (arch === 'x64') { + extractedBinary = 'ckb-tui-linux-amd64'; + } else { + throw new Error(`Unsupported architecture for Linux: ${arch}`); + } + } else { + throw new Error(`Unsupported platform: ${platform}`); + } const extractedPath = path.join(binDir, extractedBinary); if (fs.existsSync(extractedPath)) { fs.renameSync(extractedPath, this.binaryPath!); @@ -84,18 +108,34 @@ export class CKBTui { } } + // Check that the binary was successfully extracted and moved + if (!fs.existsSync(this.binaryPath!)) { + logger.error(`ckb-tui binary was not found after extraction. Expected at: ${this.binaryPath}`); + throw new Error('Failed to extract and locate ckb-tui binary.'); + } + // Make executable on Unix if (platform !== 'win32') { execSync(`chmod +x "${this.binaryPath}"`); } - // Clean up archive - fs.unlinkSync(archivePath); - logger.info('ckb-tui installed successfully.'); } catch (error) { - logger.error('Failed to download/install ckb-tui:', (error as Error).message); + logger.error( + 'Failed to download/install ckb-tui:', + (error as Error).message, + '\nPlease check your network connectivity, verify that the specified version exists in the releases, and ensure you have sufficient file system permissions.' + ); throw error; + } finally { + // Clean up archive even if error occurs + if (fs.existsSync(archivePath)) { + try { + fs.unlinkSync(archivePath); + } catch (cleanupError) { + logger.warn('Failed to clean up archive file:', (cleanupError as Error).message); + } + } } } @@ -110,11 +150,6 @@ export class CKBTui { static run(args: string[] = []) { const binaryPath = this.getBinaryPath(); - const command = `"${binaryPath}" ${args.join(' ')}`; - return spawnSync(command, { stdio: 'inherit', shell: true }); - } - - static runWithArgs(args: string[]) { - this.run(args); + return spawnSync(binaryPath, args, { stdio: 'inherit' }); } } From 945b5bc9e6c12450dc6b56903eb07e11c8b8a9d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Dec 2025 22:03:31 +0000 Subject: [PATCH 3/3] Remove TODO comment and use Network enum for validation Co-authored-by: RetricSu <23436060+RetricSu@users.noreply.github.com> --- src/cli.ts | 2 +- src/tools/ckb-tui.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4e5d92a..4e5c4a3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -160,7 +160,7 @@ program .description('Show ckb-tui status interface') .option('--network ', 'Specify the network to deploy to', 'devnet') .action(async (option) => { - const validNetworks = ['devnet', 'testnet', 'mainnet']; + const validNetworks = Object.values(Network); if (!validNetworks.includes(option.network)) { logger.error(`Invalid network: ${option.network}. Must be one of: ${validNetworks.join(', ')}`); process.exit(1); diff --git a/src/tools/ckb-tui.ts b/src/tools/ckb-tui.ts index 1b84de9..2f8826a 100644 --- a/src/tools/ckb-tui.ts +++ b/src/tools/ckb-tui.ts @@ -71,7 +71,6 @@ export class CKBTui { } // Set the correct binary name based on platform and architecture - // TODO: Fix the bin name let extractedBinary: string; if (platform === 'win32') { extractedBinary = 'ckb-tui.exe';