@proofofprotocol/inscribe-mcp 0.1.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.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * init command
3
+ *
4
+ * Initialize inscribe-mcp configuration.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { createInterface } from 'readline';
9
+ import { writeConfig, configExists, getConfigPath } from '../lib/config.js';
10
+ import { EXIT_CODES } from '../lib/exit-codes.js';
11
+
12
+ // Simple prompt helper (no external dependency)
13
+ function prompt(question) {
14
+ const rl = createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout
17
+ });
18
+
19
+ return new Promise((resolve) => {
20
+ rl.question(question, (answer) => {
21
+ rl.close();
22
+ resolve(answer.trim());
23
+ });
24
+ });
25
+ }
26
+
27
+ // ANSI colors
28
+ const colors = {
29
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
30
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
31
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
32
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
33
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
34
+ dim: (s) => `\x1b[2m${s}\x1b[0m`
35
+ };
36
+
37
+ export const initCommand = new Command('init')
38
+ .description('Initialize inscribe-mcp configuration')
39
+ .option('-f, --force', 'Overwrite existing configuration')
40
+ .action(async (options) => {
41
+ console.log('');
42
+ console.log(colors.bold('inscribe-mcp Setup'));
43
+ console.log('─'.repeat(40));
44
+ console.log('');
45
+
46
+ // Check existing config
47
+ if (configExists() && !options.force) {
48
+ console.log(colors.yellow('Config already exists: ') + getConfigPath());
49
+ console.log('');
50
+ console.log('Use --force to overwrite.');
51
+ process.exit(EXIT_CODES.SUCCESS);
52
+ }
53
+
54
+ // Network selection
55
+ console.log(colors.bold('1. Select Network'));
56
+ console.log('');
57
+ console.log(' 1) testnet ' + colors.dim('(recommended for testing)'));
58
+ console.log(' 2) mainnet ' + colors.dim('(real HBAR costs)'));
59
+ console.log('');
60
+
61
+ let network;
62
+ while (!network) {
63
+ const choice = await prompt('Select (1 or 2): ');
64
+ if (choice === '1') network = 'testnet';
65
+ else if (choice === '2') network = 'mainnet';
66
+ else console.log(colors.red('Invalid selection. Enter 1 or 2.'));
67
+ }
68
+
69
+ console.log('');
70
+ console.log(colors.green('✓') + ` Network: ${network}`);
71
+ console.log('');
72
+
73
+ // Mainnet warning
74
+ if (network === 'mainnet') {
75
+ console.log(colors.yellow('⚠ Mainnet Warning'));
76
+ console.log('─'.repeat(40));
77
+ console.log('• Real HBAR will be consumed');
78
+ console.log('• Transactions are irreversible');
79
+ console.log('• Secure your private key');
80
+ console.log('');
81
+ const confirm = await prompt('Continue? (yes/no): ');
82
+ if (confirm.toLowerCase() !== 'yes') {
83
+ console.log('Aborted.');
84
+ process.exit(EXIT_CODES.SUCCESS);
85
+ }
86
+ console.log('');
87
+ }
88
+
89
+ // Account setup
90
+ console.log(colors.bold('2. Hedera Account'));
91
+ console.log('');
92
+
93
+ if (network === 'testnet') {
94
+ console.log(colors.cyan('Testnet Faucet:'));
95
+ console.log(' https://portal.hedera.com/faucet');
96
+ console.log('');
97
+ console.log('Create an account and get free HBAR for testing.');
98
+ console.log('');
99
+ } else {
100
+ console.log(colors.cyan('Create Mainnet Account:'));
101
+ console.log(' HashPack: https://www.hashpack.app/');
102
+ console.log(' Blade: https://www.bladewallet.io/');
103
+ console.log('');
104
+ console.log('Transfer a small amount of HBAR (1-5 HBAR recommended).');
105
+ console.log('');
106
+ }
107
+
108
+ // Account ID input
109
+ let accountId;
110
+ while (!accountId) {
111
+ const input = await prompt('Account ID (e.g., 0.0.123456): ');
112
+ if (/^0\.0\.\d+$/.test(input)) {
113
+ accountId = input;
114
+ } else {
115
+ console.log(colors.red('Invalid format. Use 0.0.XXXXXX'));
116
+ }
117
+ }
118
+
119
+ console.log(colors.green('✓') + ` Account ID: ${accountId}`);
120
+ console.log('');
121
+
122
+ // Private key input
123
+ console.log(colors.bold('3. Private Key'));
124
+ console.log('');
125
+ console.log(colors.dim('Your private key is stored locally and never transmitted.'));
126
+ console.log('');
127
+
128
+ let privateKey;
129
+ while (!privateKey) {
130
+ const input = await prompt('Private Key (hex or DER format): ');
131
+ if (input.length >= 32) {
132
+ privateKey = input;
133
+ } else {
134
+ console.log(colors.red('Private key seems too short.'));
135
+ }
136
+ }
137
+
138
+ console.log(colors.green('✓') + ' Private key received');
139
+ console.log('');
140
+
141
+ // Topic ID (optional)
142
+ console.log(colors.bold('4. Topic ID (optional)'));
143
+ console.log('');
144
+ console.log(colors.dim('Leave empty to auto-create on first inscribe.'));
145
+ console.log('');
146
+
147
+ const topicInput = await prompt('Topic ID (e.g., 0.0.123456) or Enter to skip: ');
148
+ let topicId = null;
149
+ if (topicInput && /^0\.0\.\d+$/.test(topicInput)) {
150
+ topicId = topicInput;
151
+ console.log(colors.green('✓') + ` Topic ID: ${topicId}`);
152
+ } else if (topicInput) {
153
+ console.log(colors.yellow('Invalid format, skipping.'));
154
+ } else {
155
+ console.log(colors.dim('Topic will be created on first inscribe.'));
156
+ }
157
+
158
+ console.log('');
159
+
160
+ // Build and save config
161
+ const config = {
162
+ network,
163
+ operatorAccountId: accountId,
164
+ operatorPrivateKey: privateKey
165
+ };
166
+
167
+ if (topicId) {
168
+ config.defaultTopicId = topicId;
169
+ }
170
+
171
+ writeConfig(config);
172
+
173
+ // Summary
174
+ console.log('─'.repeat(40));
175
+ console.log(colors.green(colors.bold('✓ Setup Complete!')));
176
+ console.log('');
177
+ console.log('Config saved to:');
178
+ console.log(` ${getConfigPath()}`);
179
+ console.log('');
180
+ console.log('Next steps:');
181
+ console.log(' • Run ' + colors.cyan('inscribe-mcp config') + ' to verify');
182
+ console.log(' • Run ' + colors.cyan('inscribe-mcp balance') + ' to check balance');
183
+ console.log('');
184
+
185
+ process.exit(EXIT_CODES.SUCCESS);
186
+ });
@@ -0,0 +1,193 @@
1
+ /**
2
+ * log command
3
+ *
4
+ * Display MCP execution logs (Read-Only).
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { existsSync, readdirSync, readFileSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { getLogsDir } from '../../lib/logger.js';
11
+ import { EXIT_CODES } from '../lib/exit-codes.js';
12
+
13
+ // ANSI colors
14
+ const colors = {
15
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
16
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
17
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
18
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
19
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
20
+ dim: (s) => `\x1b[2m${s}\x1b[0m`
21
+ };
22
+
23
+ /**
24
+ * Parse time duration string (e.g., "1h", "30m", "7d")
25
+ * @returns {number} milliseconds
26
+ */
27
+ function parseDuration(str) {
28
+ const match = str.match(/^(\d+)([smhd])$/);
29
+ if (!match) return null;
30
+
31
+ const value = parseInt(match[1], 10);
32
+ const unit = match[2];
33
+
34
+ const multipliers = {
35
+ s: 1000,
36
+ m: 60 * 1000,
37
+ h: 60 * 60 * 1000,
38
+ d: 24 * 60 * 60 * 1000
39
+ };
40
+
41
+ return value * multipliers[unit];
42
+ }
43
+
44
+ /**
45
+ * Format timestamp for display
46
+ */
47
+ function formatTime(isoString) {
48
+ const date = new Date(isoString);
49
+ return date.toLocaleString('ja-JP', {
50
+ month: '2-digit',
51
+ day: '2-digit',
52
+ hour: '2-digit',
53
+ minute: '2-digit',
54
+ second: '2-digit'
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Format log entry for display
60
+ */
61
+ function formatLogEntry(entry) {
62
+ const time = formatTime(entry.timestamp);
63
+ const cmd = (entry.command || 'unknown').toUpperCase().padEnd(12);
64
+ const topic = entry.topicId ? `topic=${entry.topicId}` : '';
65
+ const latency = entry.latency ? `${entry.latency}ms` : '—';
66
+
67
+ let status;
68
+ if (entry.status === 'success') {
69
+ status = colors.green('OK');
70
+ } else {
71
+ status = colors.red('FAILED');
72
+ }
73
+
74
+ let line = `[${time}] ${cmd} ${topic.padEnd(20)} latency=${latency.padEnd(8)} ${status}`;
75
+
76
+ if (entry.error) {
77
+ line += ` (${entry.error})`;
78
+ }
79
+
80
+ return line;
81
+ }
82
+
83
+ /**
84
+ * Read all log entries from log files
85
+ */
86
+ function readLogs(logsDir, options = {}) {
87
+ const { since, errorOnly } = options;
88
+ const entries = [];
89
+
90
+ if (!existsSync(logsDir)) {
91
+ return entries;
92
+ }
93
+
94
+ // Get log files sorted by date (newest first)
95
+ const files = readdirSync(logsDir)
96
+ .filter(f => f.endsWith('.jsonl'))
97
+ .sort()
98
+ .reverse();
99
+
100
+ const sinceMs = since ? parseDuration(since) : null;
101
+ const cutoff = sinceMs ? Date.now() - sinceMs : null;
102
+
103
+ for (const file of files) {
104
+ const filePath = join(logsDir, file);
105
+ const content = readFileSync(filePath, 'utf-8');
106
+ const lines = content.trim().split('\n').filter(Boolean);
107
+
108
+ for (const line of lines) {
109
+ try {
110
+ const entry = JSON.parse(line);
111
+
112
+ // Skip debug entries
113
+ if (entry.level === 'debug') continue;
114
+
115
+ // Apply time filter
116
+ if (cutoff) {
117
+ const entryTime = new Date(entry.timestamp).getTime();
118
+ if (entryTime < cutoff) continue;
119
+ }
120
+
121
+ // Apply error filter
122
+ if (errorOnly && entry.status !== 'error') continue;
123
+
124
+ entries.push(entry);
125
+ } catch {
126
+ // Skip invalid lines
127
+ }
128
+ }
129
+ }
130
+
131
+ // Sort by timestamp descending
132
+ entries.sort((a, b) =>
133
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
134
+ );
135
+
136
+ return entries;
137
+ }
138
+
139
+ export const logCommand = new Command('log')
140
+ .description('Display MCP execution logs')
141
+ .option('-n, --tail <n>', 'Show last N entries', '20')
142
+ .option('-s, --since <duration>', 'Show entries since (e.g., 1h, 30m, 7d)')
143
+ .option('-e, --error', 'Show only errors')
144
+ .option('--json', 'Output as JSON')
145
+ .action(async (options) => {
146
+ const logsDir = getLogsDir();
147
+
148
+ if (!existsSync(logsDir)) {
149
+ console.log('');
150
+ console.log(colors.yellow('No logs found.'));
151
+ console.log('');
152
+ console.log('Logs are created when MCP tools are executed.');
153
+ console.log('');
154
+ process.exit(EXIT_CODES.LOG_NOT_FOUND);
155
+ }
156
+
157
+ const entries = readLogs(logsDir, {
158
+ since: options.since,
159
+ errorOnly: options.error
160
+ });
161
+
162
+ if (entries.length === 0) {
163
+ console.log('');
164
+ console.log(colors.yellow('No matching log entries.'));
165
+ console.log('');
166
+ process.exit(EXIT_CODES.SUCCESS);
167
+ }
168
+
169
+ // Apply tail limit
170
+ const limit = parseInt(options.tail, 10) || 20;
171
+ const displayEntries = entries.slice(0, limit);
172
+
173
+ if (options.json) {
174
+ console.log(JSON.stringify(displayEntries, null, 2));
175
+ process.exit(EXIT_CODES.SUCCESS);
176
+ }
177
+
178
+ // Display logs
179
+ console.log('');
180
+ console.log(colors.bold('inscribe-mcp Logs'));
181
+ console.log('─'.repeat(80));
182
+
183
+ // Reverse for chronological display
184
+ displayEntries.reverse().forEach(entry => {
185
+ console.log(formatLogEntry(entry));
186
+ });
187
+
188
+ console.log('─'.repeat(80));
189
+ console.log(colors.dim(`Showing ${displayEntries.length} of ${entries.length} entries`));
190
+ console.log('');
191
+
192
+ process.exit(EXIT_CODES.SUCCESS);
193
+ });
@@ -0,0 +1,249 @@
1
+ /**
2
+ * show command
3
+ *
4
+ * Display nvidia-smi style dashboard for inscribe-mcp status.
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import { existsSync, readdirSync, readFileSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { getLogsDir } from '../../lib/logger.js';
11
+ import { configExists, readConfig, maskPrivateKey } from '../lib/config.js';
12
+ import { EXIT_CODES } from '../lib/exit-codes.js';
13
+
14
+ // ANSI colors
15
+ const colors = {
16
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
17
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
18
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
19
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
20
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
21
+ dim: (s) => `\x1b[2m${s}\x1b[0m`
22
+ };
23
+
24
+ /**
25
+ * Get Mirror Node URL
26
+ */
27
+ function getMirrorUrl(network) {
28
+ return network === 'mainnet'
29
+ ? 'https://mainnet.mirrornode.hedera.com'
30
+ : 'https://testnet.mirrornode.hedera.com';
31
+ }
32
+
33
+ /**
34
+ * Format relative time
35
+ */
36
+ function formatRelativeTime(isoString) {
37
+ if (!isoString) return 'never';
38
+
39
+ const diff = Date.now() - new Date(isoString).getTime();
40
+ const minutes = Math.floor(diff / 60000);
41
+ const hours = Math.floor(diff / 3600000);
42
+ const days = Math.floor(diff / 86400000);
43
+
44
+ if (days > 0) return `${days}d ago`;
45
+ if (hours > 0) return `${hours}h ago`;
46
+ if (minutes > 0) return `${minutes}m ago`;
47
+ return 'just now';
48
+ }
49
+
50
+ /**
51
+ * Read all log entries and compute stats
52
+ */
53
+ function computeStats(logsDir) {
54
+ const stats = {
55
+ total: 0,
56
+ success: 0,
57
+ error: 0,
58
+ byCommand: {},
59
+ totalLatency: 0,
60
+ latencyCount: 0,
61
+ lastInscribe: null,
62
+ lastVerify: null,
63
+ lastError: null,
64
+ lastErrorMessage: null
65
+ };
66
+
67
+ if (!existsSync(logsDir)) {
68
+ return stats;
69
+ }
70
+
71
+ const files = readdirSync(logsDir).filter(f => f.endsWith('.jsonl'));
72
+
73
+ for (const file of files) {
74
+ const filePath = join(logsDir, file);
75
+ const content = readFileSync(filePath, 'utf-8');
76
+ const lines = content.trim().split('\n').filter(Boolean);
77
+
78
+ for (const line of lines) {
79
+ try {
80
+ const entry = JSON.parse(line);
81
+
82
+ // Skip debug entries
83
+ if (entry.level === 'debug') continue;
84
+
85
+ stats.total++;
86
+
87
+ if (entry.status === 'success') {
88
+ stats.success++;
89
+ } else {
90
+ stats.error++;
91
+ if (!stats.lastError || entry.timestamp > stats.lastError) {
92
+ stats.lastError = entry.timestamp;
93
+ stats.lastErrorMessage = entry.error;
94
+ }
95
+ }
96
+
97
+ // Track by command
98
+ const cmd = entry.command || 'unknown';
99
+ if (!stats.byCommand[cmd]) {
100
+ stats.byCommand[cmd] = { success: 0, error: 0 };
101
+ }
102
+ if (entry.status === 'success') {
103
+ stats.byCommand[cmd].success++;
104
+ } else {
105
+ stats.byCommand[cmd].error++;
106
+ }
107
+
108
+ // Track latency
109
+ if (entry.latency) {
110
+ stats.totalLatency += entry.latency;
111
+ stats.latencyCount++;
112
+ }
113
+
114
+ // Track last operations
115
+ if (entry.command === 'inscribe' && entry.status === 'success') {
116
+ if (!stats.lastInscribe || entry.timestamp > stats.lastInscribe) {
117
+ stats.lastInscribe = entry.timestamp;
118
+ }
119
+ }
120
+ if (entry.command === 'verify' && entry.status === 'success') {
121
+ if (!stats.lastVerify || entry.timestamp > stats.lastVerify) {
122
+ stats.lastVerify = entry.timestamp;
123
+ }
124
+ }
125
+ } catch {
126
+ // Skip invalid lines
127
+ }
128
+ }
129
+ }
130
+
131
+ stats.avgLatency = stats.latencyCount > 0
132
+ ? Math.round(stats.totalLatency / stats.latencyCount)
133
+ : 0;
134
+
135
+ return stats;
136
+ }
137
+
138
+ export const showCommand = new Command('show')
139
+ .description('Display inscribe-mcp status dashboard')
140
+ .option('--json', 'Output as JSON')
141
+ .action(async (options) => {
142
+ // Check config
143
+ if (!configExists()) {
144
+ console.log('');
145
+ console.log(colors.red('Config not found.'));
146
+ console.log('');
147
+ console.log('Run ' + colors.cyan('inscribe-mcp init') + ' to configure.');
148
+ console.log('');
149
+ process.exit(EXIT_CODES.CONFIG_ERROR);
150
+ }
151
+
152
+ const config = readConfig();
153
+ const { network, operatorAccountId, defaultTopicId } = config;
154
+
155
+ // Fetch balance
156
+ let balance = '—';
157
+ try {
158
+ const mirrorUrl = getMirrorUrl(network);
159
+ const response = await fetch(`${mirrorUrl}/api/v1/accounts/${operatorAccountId}`);
160
+ if (response.ok) {
161
+ const data = await response.json();
162
+ const hbar = (data.balance?.balance || 0) / 100_000_000;
163
+ balance = hbar.toFixed(2) + ' HBAR';
164
+ }
165
+ } catch {
166
+ balance = colors.red('fetch error');
167
+ }
168
+
169
+ // Compute stats from logs
170
+ const logsDir = getLogsDir();
171
+ const stats = computeStats(logsDir);
172
+
173
+ if (options.json) {
174
+ console.log(JSON.stringify({
175
+ config: {
176
+ network,
177
+ operatorAccountId,
178
+ defaultTopicId,
179
+ operatorPrivateKey: maskPrivateKey(config.operatorPrivateKey)
180
+ },
181
+ balance,
182
+ stats
183
+ }, null, 2));
184
+ process.exit(EXIT_CODES.SUCCESS);
185
+ }
186
+
187
+ // Dashboard display
188
+ const width = 60;
189
+ const line = '─'.repeat(width);
190
+ const doubleLine = '═'.repeat(width);
191
+
192
+ console.log('');
193
+ console.log(colors.bold(`inscribe-mcp status (network: ${colors.cyan(network)})`));
194
+ console.log(doubleLine);
195
+
196
+ // Account info
197
+ console.log(`Operator: ${operatorAccountId}`);
198
+ console.log(`Balance: ${colors.green(balance)}`);
199
+ if (defaultTopicId) {
200
+ console.log(`Topic: ${defaultTopicId}`);
201
+ } else {
202
+ console.log(`Topic: ${colors.dim('(not configured)')}`);
203
+ }
204
+
205
+ console.log('');
206
+ console.log(colors.bold('MCP Activity (total)'));
207
+ console.log(line);
208
+
209
+ // Command stats
210
+ const inscribeStats = stats.byCommand['inscribe'] || { success: 0, error: 0 };
211
+ const verifyStats = stats.byCommand['verify'] || { success: 0, error: 0 };
212
+ const historyStats = stats.byCommand['history'] || { success: 0, error: 0 };
213
+
214
+ const formatStat = (s) => `${s.success + s.error} (success ${colors.green(s.success)} / fail ${s.error > 0 ? colors.red(s.error) : s.error})`;
215
+
216
+ console.log(`Inscribe calls: ${formatStat(inscribeStats)}`);
217
+ console.log(`Verify calls: ${formatStat(verifyStats)}`);
218
+ console.log(`History calls: ${formatStat(historyStats)}`);
219
+ console.log(`Avg latency: ${stats.avgLatency || 0} ms`);
220
+
221
+ console.log('');
222
+ console.log(colors.bold('Timeline'));
223
+ console.log(line);
224
+
225
+ console.log(`Last inscribe: ${stats.lastInscribe ? formatRelativeTime(stats.lastInscribe) : colors.dim('never')}`);
226
+ console.log(`Last verify: ${stats.lastVerify ? formatRelativeTime(stats.lastVerify) : colors.dim('never')}`);
227
+ if (stats.lastError) {
228
+ console.log(`Last error: ${colors.red(stats.lastErrorMessage || 'unknown')} (${formatRelativeTime(stats.lastError)})`);
229
+ } else {
230
+ console.log(`Last error: ${colors.dim('none')}`);
231
+ }
232
+
233
+ console.log('');
234
+ console.log(colors.bold('Links'));
235
+ console.log(line);
236
+
237
+ const hashscanBase = network === 'mainnet'
238
+ ? 'https://hashscan.io/mainnet'
239
+ : 'https://hashscan.io/testnet';
240
+
241
+ console.log(`Account: ${hashscanBase}/account/${operatorAccountId}`);
242
+ if (defaultTopicId) {
243
+ console.log(`Topic: ${hashscanBase}/topic/${defaultTopicId}`);
244
+ }
245
+
246
+ console.log('');
247
+
248
+ process.exit(EXIT_CODES.SUCCESS);
249
+ });
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * inscribe-mcp CLI
5
+ *
6
+ * Observation and configuration layer for inscribe-mcp.
7
+ * Read-Only: This CLI does not send transactions to Hedera.
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import { readFileSync } from 'fs';
12
+ import { fileURLToPath } from 'url';
13
+ import { dirname, join } from 'path';
14
+
15
+ import { initCommand } from './commands/init.js';
16
+ import { configCommand } from './commands/config.js';
17
+ import { balanceCommand } from './commands/balance.js';
18
+ import { logCommand } from './commands/log.js';
19
+ import { showCommand } from './commands/show.js';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+
24
+ // Read version from package.json
25
+ const packageJson = JSON.parse(
26
+ readFileSync(join(__dirname, '../../package.json'), 'utf-8')
27
+ );
28
+
29
+ const program = new Command();
30
+
31
+ program
32
+ .name('inscribe-mcp')
33
+ .description('Observation and configuration CLI for inscribe-mcp')
34
+ .version(packageJson.version);
35
+
36
+ // Register commands
37
+ program.addCommand(initCommand);
38
+ program.addCommand(configCommand);
39
+ program.addCommand(balanceCommand);
40
+ program.addCommand(logCommand);
41
+ program.addCommand(showCommand);
42
+
43
+ // Parse and execute
44
+ program.parse();