byreal-test 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { Command } = require('commander');
5
+ const chalk = require('chalk');
6
+ const path = require('path');
7
+ const { runSuites, listTests } = require('./src/runner');
8
+ const { printReport, saveReport } = require('./src/reporter');
9
+ const allSuites = require('./src/suites');
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('byreal-test')
15
+ .description('Byreal CLI test suite — validates byreal-cli commands and generates reports')
16
+ .version('1.0.0');
17
+
18
+ program
19
+ .command('run')
20
+ .description('Run test suites against byreal-cli')
21
+ .option('--suite <name>', `Run specific suite (${allSuites.map(s => s.name).join(', ')})`)
22
+ .option('--all', 'Include auth-required suites (requires wallet configured)')
23
+ .option('--timeout <ms>', 'Per-test timeout in milliseconds', '20000')
24
+ .option('--output <file>', 'Save markdown report to file (e.g. report.md)')
25
+ .option('--verbose', 'Show raw command output for each test')
26
+ .action(async (opts) => {
27
+ const timeout = parseInt(opts.timeout, 10);
28
+
29
+ let suites = allSuites;
30
+
31
+ if (opts.suite) {
32
+ suites = allSuites.filter(s => s.name === opts.suite);
33
+ if (suites.length === 0) {
34
+ console.error(chalk.red(
35
+ `\n Suite "${opts.suite}" not found.\n Available: ${allSuites.map(s => s.name).join(', ')}\n`
36
+ ));
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ if (!opts.all) {
42
+ suites = suites
43
+ .map(s => ({ ...s, cases: s.cases.filter(c => !c.requiresAuth) }))
44
+ .filter(s => s.cases.length > 0);
45
+ }
46
+
47
+ console.log(chalk.bold('\n byreal-test v1.0.0\n'));
48
+ if (!opts.all) {
49
+ console.log(chalk.dim(' (Auth-required tests skipped. Use --all to include wallet tests.)\n'));
50
+ }
51
+
52
+ const results = await runSuites(suites, { timeout, verbose: opts.verbose });
53
+ printReport(results);
54
+
55
+ if (opts.output) {
56
+ const outPath = path.resolve(opts.output);
57
+ await saveReport(results, outPath);
58
+ console.log(chalk.dim(` Report saved to: ${outPath}\n`));
59
+ }
60
+
61
+ const failed = results.flatMap(r => r.cases).filter(c => c.status === 'FAIL').length;
62
+ process.exit(failed > 0 ? 1 : 0);
63
+ });
64
+
65
+ program
66
+ .command('list')
67
+ .description('List all available test cases')
68
+ .option('--suite <name>', 'Filter by suite name')
69
+ .option('--all', 'Include auth-required tests')
70
+ .action((opts) => {
71
+ listTests(allSuites, opts);
72
+ });
73
+
74
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "byreal-test",
3
+ "version": "1.0.0",
4
+ "description": "Byreal CLI test suite - runs byreal-cli commands and generates test reports",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "byreal-test": "cli.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node cli.js run",
11
+ "test": "node cli.js run"
12
+ },
13
+ "files": [
14
+ "cli.js",
15
+ "src/"
16
+ ],
17
+ "dependencies": {
18
+ "chalk": "^4.1.2",
19
+ "cli-table3": "^0.6.5",
20
+ "commander": "^12.0.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=18.0.0"
24
+ },
25
+ "license": "MIT"
26
+ }
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function printReport(results) {
8
+ const allCases = results.flatMap(r => r.cases);
9
+ const passed = allCases.filter(c => c.status === 'PASS').length;
10
+ const failed = allCases.filter(c => c.status === 'FAIL').length;
11
+ const skipped = allCases.filter(c => c.status === 'SKIP').length;
12
+ const total = allCases.length;
13
+ const totalElapsed = allCases.reduce((sum, c) => sum + (c.elapsed || 0), 0);
14
+
15
+ const separator = '─'.repeat(62);
16
+ console.log(` ${separator}`);
17
+
18
+ const passStr = chalk.green(`${passed} passed`);
19
+ const failStr = failed > 0 ? chalk.red(` | ${failed} failed`) : '';
20
+ const skipStr = skipped > 0 ? chalk.yellow(` | ${skipped} skipped`) : '';
21
+ const timeStr = chalk.dim(` | ${totalElapsed}ms total`);
22
+
23
+ console.log(` ${passStr}${failStr}${skipStr}${timeStr}`);
24
+ console.log(` ${separator}\n`);
25
+
26
+ if (failed > 0) {
27
+ console.log(chalk.red.bold(' Failed tests:'));
28
+ for (const suite of results) {
29
+ for (const c of suite.cases) {
30
+ if (c.status === 'FAIL') {
31
+ console.log(` ${chalk.red('✗')} [${suite.name}] ${c.name}`);
32
+ if (c.failReason) {
33
+ console.log(` ${chalk.dim('→')} ${chalk.dim(c.failReason)}`);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ console.log('');
39
+ }
40
+ }
41
+
42
+ function buildMarkdown(results) {
43
+ const allCases = results.flatMap(r => r.cases);
44
+ const passed = allCases.filter(c => c.status === 'PASS').length;
45
+ const failed = allCases.filter(c => c.status === 'FAIL').length;
46
+ const skipped = allCases.filter(c => c.status === 'SKIP').length;
47
+ const totalElapsed = allCases.reduce((sum, c) => sum + (c.elapsed || 0), 0);
48
+
49
+ const now = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
50
+ const statusBadge = failed === 0 ? '✅ PASSED' : '❌ FAILED';
51
+
52
+ let md = `# Byreal-CLI Test Report\n\n`;
53
+ md += `| | |\n|---|---|\n`;
54
+ md += `| **Status** | ${statusBadge} |\n`;
55
+ md += `| **Generated** | ${now} |\n`;
56
+ md += `| **Passed** | ${passed} |\n`;
57
+ if (failed > 0) md += `| **Failed** | ${failed} |\n`;
58
+ if (skipped > 0) md += `| **Skipped** | ${skipped} |\n`;
59
+ md += `| **Total time** | ${totalElapsed}ms |\n\n`;
60
+
61
+ for (const suite of results) {
62
+ const suiteFailed = suite.cases.filter(c => c.status === 'FAIL').length;
63
+ const suiteIcon = suiteFailed > 0 ? '❌' : '✅';
64
+ md += `## ${suiteIcon} Suite: ${suite.name}\n\n`;
65
+ md += `| # | Test | Status | Time | Notes |\n`;
66
+ md += `|---|------|:------:|-----:|-------|\n`;
67
+ suite.cases.forEach((c, i) => {
68
+ const icon = c.status === 'PASS' ? '✅' : c.status === 'SKIP' ? '⏭️' : '❌';
69
+ const note = c.failReason || c.reason || c.note || '';
70
+ md += `| ${i + 1} | ${c.name} | ${icon} | ${c.elapsed || 0}ms | ${note} |\n`;
71
+ });
72
+ md += '\n';
73
+ }
74
+
75
+ return md;
76
+ }
77
+
78
+ async function saveReport(results, filePath) {
79
+ const md = buildMarkdown(results);
80
+ const dir = path.dirname(filePath);
81
+ if (dir && dir !== '.') {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ fs.writeFileSync(filePath, md, 'utf8');
85
+ }
86
+
87
+ module.exports = { printReport, saveReport };
package/src/runner.js ADDED
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const { execSync } = require('child_process');
4
+ const chalk = require('chalk');
5
+
6
+ function stripAnsi(str) {
7
+ return str
8
+ .replace(/\x1b\[[0-9;]*m/g, '')
9
+ .replace(/\x1b\][^\x07]*\x07/g, '')
10
+ .replace(/\x1b[()][AB012]/g, '');
11
+ }
12
+
13
+ /**
14
+ * Try to extract JSON from CLI output.
15
+ * byreal-cli may prepend update banners or other text before the JSON payload.
16
+ */
17
+ function parseJsonOutput(stdout) {
18
+ const clean = stripAnsi(stdout).trim();
19
+ const jsonStart = clean.search(/[[\{]/);
20
+ if (jsonStart === -1) return null;
21
+ try {
22
+ return JSON.parse(clean.slice(jsonStart));
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ async function runCase(testCase, opts = {}) {
29
+ const {
30
+ cmd,
31
+ validate,
32
+ setupFn,
33
+ expectError = false,
34
+ timeout = opts.timeout || 20000,
35
+ } = testCase;
36
+
37
+ let finalCmd = typeof cmd === 'function' ? null : cmd;
38
+
39
+ if (setupFn) {
40
+ let setupData;
41
+ try {
42
+ setupData = setupFn();
43
+ } catch (err) {
44
+ return {
45
+ ...testCase,
46
+ status: 'SKIP',
47
+ reason: `setup failed: ${err.message}`,
48
+ elapsed: 0,
49
+ };
50
+ }
51
+ if (!setupData) {
52
+ return {
53
+ ...testCase,
54
+ status: 'SKIP',
55
+ reason: 'setup returned null (dependency unavailable)',
56
+ elapsed: 0,
57
+ };
58
+ }
59
+ finalCmd = typeof cmd === 'function' ? cmd(setupData) : cmd;
60
+ }
61
+
62
+ if (opts.verbose) {
63
+ process.stdout.write(chalk.dim(`\n $ ${finalCmd}\n`));
64
+ }
65
+
66
+ const start = Date.now();
67
+ try {
68
+ const stdout = execSync(finalCmd, {
69
+ timeout,
70
+ encoding: 'utf8',
71
+ maxBuffer: 10 * 1024 * 1024,
72
+ });
73
+ const elapsed = Date.now() - start;
74
+
75
+ let passed = true;
76
+ let failReason = '';
77
+
78
+ if (validate) {
79
+ try {
80
+ const result = validate(stdout, parseJsonOutput);
81
+ if (result === false) {
82
+ passed = false;
83
+ failReason = 'validation failed';
84
+ } else if (typeof result === 'string') {
85
+ passed = false;
86
+ failReason = result;
87
+ }
88
+ } catch (err) {
89
+ passed = false;
90
+ failReason = `validate threw: ${err.message}`;
91
+ }
92
+ }
93
+
94
+ return {
95
+ ...testCase,
96
+ status: passed ? 'PASS' : 'FAIL',
97
+ stdout: opts.verbose ? stdout : undefined,
98
+ elapsed,
99
+ failReason,
100
+ };
101
+ } catch (err) {
102
+ const elapsed = Date.now() - start;
103
+
104
+ if (expectError) {
105
+ return {
106
+ ...testCase,
107
+ status: 'PASS',
108
+ elapsed,
109
+ note: 'expected error received',
110
+ };
111
+ }
112
+
113
+ const isTimeout = err.killed || String(err.message).includes('ETIMEDOUT');
114
+ const errorDetail = isTimeout
115
+ ? `timeout after ${timeout}ms`
116
+ : (err.stderr?.toString()?.slice(0, 200) || err.message?.slice(0, 200) || 'unknown error');
117
+
118
+ return {
119
+ ...testCase,
120
+ status: 'FAIL',
121
+ elapsed,
122
+ failReason: errorDetail,
123
+ stdout: opts.verbose ? err.stdout?.toString() : undefined,
124
+ };
125
+ }
126
+ }
127
+
128
+ async function runSuites(suites, opts = {}) {
129
+ const results = [];
130
+
131
+ for (const suite of suites) {
132
+ console.log(chalk.bold(` Suite: ${suite.name}`));
133
+ const suiteResult = { name: suite.name, cases: [] };
134
+
135
+ for (const testCase of suite.cases) {
136
+ const label = testCase.name.padEnd(48);
137
+ process.stdout.write(` ${chalk.dim('○')} ${chalk.dim(label)}`);
138
+
139
+ const result = await runCase(testCase, opts);
140
+ suiteResult.cases.push(result);
141
+
142
+ if (result.status === 'PASS') {
143
+ process.stdout.write(
144
+ `\r ${chalk.green('✓')} ${label} ${chalk.dim(`[${result.elapsed}ms]`)}\n`
145
+ );
146
+ } else if (result.status === 'SKIP') {
147
+ process.stdout.write(
148
+ `\r ${chalk.yellow('–')} ${label} ${chalk.yellow('SKIPPED')} ${chalk.dim(result.reason || '')}\n`
149
+ );
150
+ } else {
151
+ process.stdout.write(
152
+ `\r ${chalk.red('✗')} ${label} ${chalk.dim(`[${result.elapsed}ms]`)} ${chalk.red(result.failReason || '')}\n`
153
+ );
154
+ }
155
+ }
156
+
157
+ console.log('');
158
+ results.push(suiteResult);
159
+ }
160
+
161
+ return results;
162
+ }
163
+
164
+ function listTests(suites, opts = {}) {
165
+ const Table = require('cli-table3');
166
+ const table = new Table({
167
+ head: ['Suite', 'ID', 'Test name', 'Auth required'],
168
+ style: { head: ['cyan'] },
169
+ colWidths: [12, 28, 42, 14],
170
+ });
171
+
172
+ for (const suite of suites) {
173
+ if (opts.suite && suite.name !== opts.suite) continue;
174
+ for (const c of suite.cases) {
175
+ if (!opts.all && c.requiresAuth) continue;
176
+ table.push([
177
+ suite.name,
178
+ c.id,
179
+ c.name,
180
+ c.requiresAuth ? chalk.yellow('Yes') : chalk.dim('No'),
181
+ ]);
182
+ }
183
+ }
184
+
185
+ console.log('\n' + table.toString() + '\n');
186
+ }
187
+
188
+ module.exports = { runSuites, listTests, parseJsonOutput };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'health',
5
+ cases: [
6
+ {
7
+ id: 'health.version',
8
+ name: 'CLI version check',
9
+ cmd: 'byreal-cli --version',
10
+ validate: (stdout) => {
11
+ if (!/\d+\.\d+\.\d+/.test(stdout.trim())) return 'version string not found';
12
+ return true;
13
+ },
14
+ requiresAuth: false,
15
+ },
16
+ {
17
+ id: 'health.catalog',
18
+ name: 'Catalog list (all capabilities)',
19
+ cmd: 'byreal-cli catalog list',
20
+ validate: (stdout) => {
21
+ if (!stdout.includes('dex.pool.list')) return 'expected dex.pool.list in output';
22
+ if (!stdout.includes('dex.token.list')) return 'expected dex.token.list in output';
23
+ return true;
24
+ },
25
+ requiresAuth: false,
26
+ },
27
+ {
28
+ id: 'health.config',
29
+ name: 'Config list',
30
+ cmd: 'byreal-cli config list',
31
+ validate: (stdout) => {
32
+ if (stdout.trim().length === 0) return 'empty output';
33
+ return true;
34
+ },
35
+ requiresAuth: false,
36
+ },
37
+ {
38
+ id: 'health.update-check',
39
+ name: 'Update check',
40
+ cmd: 'byreal-cli update check',
41
+ validate: (stdout) => {
42
+ if (stdout.trim().length === 0) return 'empty output';
43
+ return true;
44
+ },
45
+ requiresAuth: false,
46
+ },
47
+ ],
48
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const health = require('./health');
4
+ const pools = require('./pools');
5
+ const tokens = require('./tokens');
6
+ const overview = require('./overview');
7
+ const wallet = require('./wallet');
8
+
9
+ module.exports = [health, pools, tokens, overview, wallet];
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'overview',
5
+ cases: [
6
+ {
7
+ id: 'overview.global',
8
+ name: 'Global overview (JSON)',
9
+ cmd: 'byreal-cli overview -o json',
10
+ validate: (stdout, parse) => {
11
+ const res = parse(stdout);
12
+ if (!res?.success) return 'success != true';
13
+ const data = res?.data;
14
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return 'data field missing or wrong type';
15
+ if (data.tvl === undefined) return 'missing field: tvl';
16
+ return true;
17
+ },
18
+ requiresAuth: false,
19
+ },
20
+ {
21
+ id: 'overview.global.table',
22
+ name: 'Global overview (table render)',
23
+ cmd: 'byreal-cli overview',
24
+ validate: (stdout) => {
25
+ if (stdout.trim().length === 0) return 'empty output';
26
+ return true;
27
+ },
28
+ requiresAuth: false,
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ const { execSync } = require('child_process');
4
+ const { parseJsonOutput } = require('../runner');
5
+
6
+ /** Fetch the first pool ID from the list; used by info/analyze/top-positions tests. */
7
+ function fetchFirstPoolId() {
8
+ try {
9
+ const out = execSync('byreal-cli pools list --page-size 1 -o json', {
10
+ encoding: 'utf8',
11
+ timeout: 15000,
12
+ });
13
+ const res = parseJsonOutput(out);
14
+ const pools = res?.data?.pools;
15
+ if (!Array.isArray(pools) || pools.length === 0) return null;
16
+ const pool = pools[0];
17
+ return pool.id || pool.address || pool.poolAddress || null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ name: 'pools',
25
+ cases: [
26
+ {
27
+ id: 'pools.list.basic',
28
+ name: 'List pools (default sort: tvl)',
29
+ cmd: 'byreal-cli pools list -o json',
30
+ validate: (stdout, parse) => {
31
+ const pools = parse(stdout)?.data?.pools;
32
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
33
+ if (pools.length === 0) return 'expected non-empty array';
34
+ return true;
35
+ },
36
+ requiresAuth: false,
37
+ },
38
+ {
39
+ id: 'pools.list.sort-apr',
40
+ name: 'List pools sorted by apr24h',
41
+ cmd: 'byreal-cli pools list --sort-field apr24h -o json',
42
+ validate: (stdout, parse) => {
43
+ const pools = parse(stdout)?.data?.pools;
44
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
45
+ if (pools.length === 0) return 'expected non-empty array';
46
+ return true;
47
+ },
48
+ requiresAuth: false,
49
+ },
50
+ {
51
+ id: 'pools.list.sort-volume',
52
+ name: 'List pools sorted by volumeUsd24h',
53
+ cmd: 'byreal-cli pools list --sort-field volumeUsd24h -o json',
54
+ validate: (stdout, parse) => {
55
+ const pools = parse(stdout)?.data?.pools;
56
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
57
+ if (pools.length === 0) return 'expected non-empty array';
58
+ return true;
59
+ },
60
+ requiresAuth: false,
61
+ },
62
+ {
63
+ id: 'pools.list.sort-fee',
64
+ name: 'List pools sorted by feeUsd24h',
65
+ cmd: 'byreal-cli pools list --sort-field feeUsd24h -o json',
66
+ validate: (stdout, parse) => {
67
+ const pools = parse(stdout)?.data?.pools;
68
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
69
+ return true;
70
+ },
71
+ requiresAuth: false,
72
+ },
73
+ {
74
+ id: 'pools.list.page-size',
75
+ name: 'List pools page-size=5',
76
+ cmd: 'byreal-cli pools list --page-size 5 -o json',
77
+ validate: (stdout, parse) => {
78
+ const pools = parse(stdout)?.data?.pools;
79
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
80
+ if (pools.length > 5) return `expected ≤5 items, got ${pools.length}`;
81
+ return true;
82
+ },
83
+ requiresAuth: false,
84
+ },
85
+ {
86
+ id: 'pools.list.stable',
87
+ name: 'List stable pools (category=1)',
88
+ cmd: 'byreal-cli pools list --category 1 -o json',
89
+ validate: (stdout, parse) => {
90
+ const pools = parse(stdout)?.data?.pools;
91
+ if (!Array.isArray(pools)) return 'data.pools is not an array';
92
+ return true;
93
+ },
94
+ requiresAuth: false,
95
+ },
96
+ {
97
+ id: 'pools.info.basic',
98
+ name: 'Pool info (first pool from list)',
99
+ requiresAuth: false,
100
+ setupFn: fetchFirstPoolId,
101
+ cmd: (poolId) => `byreal-cli pools info ${poolId} -o json`,
102
+ validate: (stdout, parse) => {
103
+ const res = parse(stdout);
104
+ if (!res?.success) return 'success != true';
105
+ if (!res?.data || typeof res.data !== 'object') return 'data field missing';
106
+ return true;
107
+ },
108
+ },
109
+ {
110
+ id: 'pools.analyze.basic',
111
+ name: 'Pool analyze (first pool from list)',
112
+ requiresAuth: false,
113
+ setupFn: fetchFirstPoolId,
114
+ cmd: (poolId) => `byreal-cli pools analyze ${poolId}`,
115
+ validate: (stdout) => {
116
+ if (stdout.trim().length === 0) return 'empty output';
117
+ return true;
118
+ },
119
+ },
120
+ {
121
+ id: 'pools.top-positions.basic',
122
+ name: 'Top positions in pool',
123
+ requiresAuth: false,
124
+ setupFn: fetchFirstPoolId,
125
+ cmd: (poolId) => `byreal-cli positions top-positions --pool ${poolId}`,
126
+ validate: (stdout) => {
127
+ if (stdout.trim().length === 0) return 'empty output';
128
+ return true;
129
+ },
130
+ },
131
+ ],
132
+ };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'tokens',
5
+ cases: [
6
+ {
7
+ id: 'tokens.list.basic',
8
+ name: 'List tokens (default)',
9
+ cmd: 'byreal-cli tokens list -o json',
10
+ validate: (stdout, parse) => {
11
+ const tokens = parse(stdout)?.data?.tokens;
12
+ if (!Array.isArray(tokens)) return 'data.tokens is not an array';
13
+ if (tokens.length === 0) return 'expected non-empty array';
14
+ return true;
15
+ },
16
+ requiresAuth: false,
17
+ },
18
+ {
19
+ id: 'tokens.list.fields',
20
+ name: 'Token items have required fields',
21
+ cmd: 'byreal-cli tokens list --page-size 5 -o json',
22
+ validate: (stdout, parse) => {
23
+ const tokens = parse(stdout)?.data?.tokens;
24
+ if (!Array.isArray(tokens) || tokens.length === 0) return 'expected non-empty array';
25
+ const token = tokens[0];
26
+ const requiredFields = ['symbol', 'mint'];
27
+ for (const f of requiredFields) {
28
+ if (token[f] === undefined) return `missing field: ${f}`;
29
+ }
30
+ return true;
31
+ },
32
+ requiresAuth: false,
33
+ },
34
+ ],
35
+ };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'wallet',
5
+ cases: [
6
+ {
7
+ id: 'wallet.address',
8
+ name: 'Wallet address configured',
9
+ cmd: 'byreal-cli wallet address',
10
+ validate: (stdout) => {
11
+ const trimmed = stdout.trim();
12
+ if (trimmed.length === 0) return 'empty output';
13
+ if (trimmed.includes('WALLET_NOT_CONFIGURED')) return 'wallet not configured';
14
+ return true;
15
+ },
16
+ requiresAuth: true,
17
+ },
18
+ {
19
+ id: 'wallet.balance',
20
+ name: 'Wallet balance',
21
+ cmd: 'byreal-cli wallet balance',
22
+ validate: (stdout) => {
23
+ if (stdout.trim().length === 0) return 'empty output';
24
+ return true;
25
+ },
26
+ requiresAuth: true,
27
+ },
28
+ {
29
+ id: 'wallet.info',
30
+ name: 'Wallet info',
31
+ cmd: 'byreal-cli wallet info',
32
+ validate: (stdout) => {
33
+ if (stdout.trim().length === 0) return 'empty output';
34
+ return true;
35
+ },
36
+ requiresAuth: true,
37
+ },
38
+ ],
39
+ };