Skip to content

Commit c6e7be1

Browse files
committed
feat(cmd): add status command with ckb-tui
1 parent 81e67f0 commit c6e7be1

5 files changed

Lines changed: 184 additions & 0 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Commands:
7979
transfer-all [options] [toAddress] Transfer All CKB tokens to address, only devnet and testnet
8080
balance [options] [toAddress] Check account balance, only devnet and testnet
8181
debugger Port of the raw CKB Standalone Debugger
82+
status [options] Show ckb-tui status interface
8283
config <action> [item] [value] do a configuration action
8384
help [command] display help for command
8485
```
@@ -130,8 +131,13 @@ You can also start a proxy RPC server for public networks:
130131
```sh
131132
offckb node --network <testnet or mainnet>
132133
```
134+
133135
Using a proxy RPC server for Testnet/Mainnet is especially helpful for debugging transactions, since failed transactions are dumped automatically.
134136

137+
**Watch Network With TUI**
138+
139+
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.
140+
135141
### 2. Create a New Contract Project {#create-project}
136142

137143
Generate a ready-to-use smart-contract project in JS/TS using templates:

src/cfg/setting.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ export interface Settings {
5252
transactionsPath: string;
5353
};
5454
tools: {
55+
rootFolder: string;
5556
ckbDebugger: {
5657
minVersion: string;
5758
};
59+
ckbTui: {
60+
version: string;
61+
};
5862
};
5963
}
6064

@@ -88,9 +92,13 @@ export const defaultSettings: Settings = {
8892
transactionsPath: path.resolve(dataPath, 'mainnet/transactions'),
8993
},
9094
tools: {
95+
rootFolder: path.resolve(dataPath, 'tools'),
9196
ckbDebugger: {
9297
minVersion: '0.200.0',
9398
},
99+
ckbTui: {
100+
version: 'v0.1.0',
101+
},
94102
},
95103
};
96104

src/cli.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { genSystemScriptsJsonFile } from './scripts/gen';
1717
import { CKBDebugger } from './tools/ckb-debugger';
1818
import { logger } from './util/logger';
1919
import { Network } from './type/base';
20+
import { status } from './cmd/status';
2021

2122
const version = require('../package.json').version;
2223
const description = require('../package.json').description;
@@ -154,6 +155,14 @@ program
154155
return CKBDebugger.runWithArgs(process.argv.slice(2));
155156
});
156157

158+
program
159+
.command('status')
160+
.description('Show ckb-tui status interface')
161+
.option('--network <network>', 'Specify the network to deploy to', 'devnet')
162+
.action(async (option) => {
163+
status({ network: option.network });
164+
});
165+
157166
program
158167
.command('config <action> [item] [value]')
159168
.description('do a configuration action')

src/cmd/status.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { readSettings } from '../cfg/setting';
2+
import { CKBTui } from '../tools/ckb-tui';
3+
import { Network } from '../type/base';
4+
import { logger } from '../util/logger';
5+
6+
export interface StatusOptions {
7+
network?: Network;
8+
}
9+
10+
export async function status({ network }: StatusOptions) {
11+
const settings = readSettings();
12+
const port =
13+
network === Network.devnet
14+
? settings.devnet.rpcProxyPort
15+
: network === Network.testnet
16+
? settings.testnet.rpcProxyPort
17+
: settings.mainnet.rpcProxyPort;
18+
const url = `http://127.0.0.1:${port}`;
19+
const isListening = await isRPCPortListening(port);
20+
if (!isListening) {
21+
return logger.error(
22+
`RPC port ${port} is not listening. Please make sure the ${network} node is running and Proxy RPC is enabled.`,
23+
);
24+
}
25+
return CKBTui.runWithArgs(['-r', url]);
26+
}
27+
28+
async function isRPCPortListening(port: number): Promise<boolean> {
29+
const net = require('net');
30+
const client = new net.Socket();
31+
return new Promise<boolean>((resolve) => {
32+
client.once('error', () => {
33+
resolve(false);
34+
});
35+
client.connect(port, '127.0.0.1');
36+
client.once('connect', () => {
37+
client.end();
38+
resolve(true);
39+
});
40+
});
41+
}

src/tools/ckb-tui.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { spawnSync, execSync } from 'child_process';
2+
import * as path from 'path';
3+
import * as fs from 'fs';
4+
import { readSettings } from '../cfg/setting';
5+
import { logger } from '../util/logger';
6+
7+
export class CKBTui {
8+
private static binaryPath: string | null = null;
9+
10+
private static getBinaryPath(): string {
11+
if (!this.binaryPath) {
12+
const settings = readSettings();
13+
const binDir = settings.tools.rootFolder;
14+
const version = settings.tools.ckbTui.version;
15+
this.binaryPath = path.join(binDir, 'ckb-tui');
16+
17+
if (!fs.existsSync(this.binaryPath)) {
18+
this.downloadBinary(version);
19+
}
20+
}
21+
return this.binaryPath;
22+
}
23+
24+
private static downloadBinary(version: string) {
25+
const platform = process.platform;
26+
const arch = process.arch;
27+
let assetName: string;
28+
29+
if (platform === 'darwin') {
30+
if (arch === 'arm64') {
31+
assetName = `ckb-tui-with-node-macos-aarch64.tar.gz`;
32+
} else {
33+
throw new Error(`Unsupported architecture for macOS: ${arch}`);
34+
}
35+
} else if (platform === 'linux') {
36+
if (arch === 'x64') {
37+
assetName = `ckb-tui-with-node-linux-amd64.tar.gz`;
38+
} else {
39+
throw new Error(`Unsupported architecture for Linux: ${arch}`);
40+
}
41+
} else if (platform === 'win32') {
42+
if (arch === 'x64') {
43+
assetName = `ckb-tui-with-node-windows-amd64.zip`;
44+
} else {
45+
throw new Error(`Unsupported architecture for Windows: ${arch}`);
46+
}
47+
} else {
48+
throw new Error(`Unsupported platform: ${platform}`);
49+
}
50+
51+
const downloadUrl = `https://github.com/Officeyutong/ckb-tui/releases/download/${version}/${assetName}`;
52+
const binDir = path.dirname(this.binaryPath!);
53+
const archivePath = path.join(binDir, assetName);
54+
55+
try {
56+
logger.info(`Downloading ckb-tui from ${downloadUrl}...`);
57+
execSync(`curl -L -o "${archivePath}" "${downloadUrl}"`, { stdio: 'inherit' });
58+
59+
logger.info('Extracting...');
60+
if (assetName.endsWith('.tar.gz')) {
61+
execSync(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: 'inherit' });
62+
} else if (assetName.endsWith('.zip')) {
63+
execSync(`unzip "${archivePath}" -d "${binDir}"`, { stdio: 'inherit' });
64+
}
65+
66+
// Assume the binary is extracted as 'ckb-tui' or 'ckb-tui.exe'
67+
// todo: fix the bin name
68+
const extractedBinary = platform === 'win32' ? 'ckb-tui.exe' : 'ckb-tui-macos-amd64';
69+
const extractedPath = path.join(binDir, extractedBinary);
70+
if (fs.existsSync(extractedPath)) {
71+
fs.renameSync(extractedPath, this.binaryPath!);
72+
} else {
73+
// If in a subfolder, find it
74+
const files = fs.readdirSync(binDir);
75+
for (const file of files) {
76+
const filePath = path.join(binDir, file);
77+
if (fs.statSync(filePath).isDirectory()) {
78+
const candidate = path.join(filePath, extractedBinary);
79+
if (fs.existsSync(candidate)) {
80+
fs.renameSync(candidate, this.binaryPath!);
81+
break;
82+
}
83+
}
84+
}
85+
}
86+
87+
// Make executable on Unix
88+
if (platform !== 'win32') {
89+
execSync(`chmod +x "${this.binaryPath}"`);
90+
}
91+
92+
// Clean up archive
93+
fs.unlinkSync(archivePath);
94+
95+
logger.info('ckb-tui installed successfully.');
96+
} catch (error) {
97+
logger.error('Failed to download/install ckb-tui:', (error as Error).message);
98+
throw error;
99+
}
100+
}
101+
102+
static isInstalled(): boolean {
103+
try {
104+
const path = this.getBinaryPath();
105+
return fs.existsSync(path);
106+
} catch {
107+
return false;
108+
}
109+
}
110+
111+
static run(args: string[] = []) {
112+
const binaryPath = this.getBinaryPath();
113+
const command = `"${binaryPath}" ${args.join(' ')}`;
114+
return spawnSync(command, { stdio: 'inherit', shell: true });
115+
}
116+
117+
static runWithArgs(args: string[]) {
118+
this.run(args);
119+
}
120+
}

0 commit comments

Comments
 (0)