@btc-vision/cli 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.
Files changed (110) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/dependabot.yml +9 -0
  3. package/.github/workflows/ci.yml +48 -0
  4. package/.prettierrc.json +12 -0
  5. package/CONTRIBUTING.md +56 -0
  6. package/LICENSE +190 -0
  7. package/NOTICE +17 -0
  8. package/README.md +509 -0
  9. package/SECURITY.md +35 -0
  10. package/build/commands/AcceptCommand.d.ts +7 -0
  11. package/build/commands/AcceptCommand.js +110 -0
  12. package/build/commands/BaseCommand.d.ts +12 -0
  13. package/build/commands/BaseCommand.js +27 -0
  14. package/build/commands/CompileCommand.d.ts +7 -0
  15. package/build/commands/CompileCommand.js +138 -0
  16. package/build/commands/ConfigCommand.d.ts +17 -0
  17. package/build/commands/ConfigCommand.js +124 -0
  18. package/build/commands/DeprecateCommand.d.ts +7 -0
  19. package/build/commands/DeprecateCommand.js +112 -0
  20. package/build/commands/InfoCommand.d.ts +10 -0
  21. package/build/commands/InfoCommand.js +223 -0
  22. package/build/commands/InitCommand.d.ts +16 -0
  23. package/build/commands/InitCommand.js +336 -0
  24. package/build/commands/InstallCommand.d.ts +7 -0
  25. package/build/commands/InstallCommand.js +130 -0
  26. package/build/commands/KeygenCommand.d.ts +13 -0
  27. package/build/commands/KeygenCommand.js +133 -0
  28. package/build/commands/ListCommand.d.ts +7 -0
  29. package/build/commands/ListCommand.js +117 -0
  30. package/build/commands/LoginCommand.d.ts +9 -0
  31. package/build/commands/LoginCommand.js +139 -0
  32. package/build/commands/LogoutCommand.d.ts +7 -0
  33. package/build/commands/LogoutCommand.js +57 -0
  34. package/build/commands/PublishCommand.d.ts +7 -0
  35. package/build/commands/PublishCommand.js +163 -0
  36. package/build/commands/SearchCommand.d.ts +7 -0
  37. package/build/commands/SearchCommand.js +97 -0
  38. package/build/commands/SignCommand.d.ts +7 -0
  39. package/build/commands/SignCommand.js +80 -0
  40. package/build/commands/TransferCommand.d.ts +8 -0
  41. package/build/commands/TransferCommand.js +179 -0
  42. package/build/commands/UndeprecateCommand.d.ts +7 -0
  43. package/build/commands/UndeprecateCommand.js +95 -0
  44. package/build/commands/UpdateCommand.d.ts +7 -0
  45. package/build/commands/UpdateCommand.js +130 -0
  46. package/build/commands/VerifyCommand.d.ts +7 -0
  47. package/build/commands/VerifyCommand.js +167 -0
  48. package/build/commands/WhoamiCommand.d.ts +7 -0
  49. package/build/commands/WhoamiCommand.js +84 -0
  50. package/build/index.d.ts +2 -0
  51. package/build/index.js +64 -0
  52. package/build/lib/PackageRegistry.abi.d.ts +2 -0
  53. package/build/lib/PackageRegistry.abi.js +356 -0
  54. package/build/lib/binary.d.ts +16 -0
  55. package/build/lib/binary.js +165 -0
  56. package/build/lib/config.d.ts +11 -0
  57. package/build/lib/config.js +160 -0
  58. package/build/lib/credentials.d.ts +10 -0
  59. package/build/lib/credentials.js +89 -0
  60. package/build/lib/ipfs.d.ts +16 -0
  61. package/build/lib/ipfs.js +209 -0
  62. package/build/lib/manifest.d.ts +14 -0
  63. package/build/lib/manifest.js +88 -0
  64. package/build/lib/provider.d.ts +9 -0
  65. package/build/lib/provider.js +48 -0
  66. package/build/lib/registry.d.ts +58 -0
  67. package/build/lib/registry.js +197 -0
  68. package/build/lib/wallet.d.ts +32 -0
  69. package/build/lib/wallet.js +114 -0
  70. package/build/types/PackageRegistry.d.ts +177 -0
  71. package/build/types/PackageRegistry.js +1 -0
  72. package/build/types/index.d.ts +30 -0
  73. package/build/types/index.js +52 -0
  74. package/eslint.config.js +41 -0
  75. package/gulpfile.js +41 -0
  76. package/package.json +83 -0
  77. package/src/commands/AcceptCommand.ts +151 -0
  78. package/src/commands/BaseCommand.ts +59 -0
  79. package/src/commands/CompileCommand.ts +196 -0
  80. package/src/commands/ConfigCommand.ts +144 -0
  81. package/src/commands/DeprecateCommand.ts +156 -0
  82. package/src/commands/InfoCommand.ts +293 -0
  83. package/src/commands/InitCommand.ts +465 -0
  84. package/src/commands/InstallCommand.ts +179 -0
  85. package/src/commands/KeygenCommand.ts +157 -0
  86. package/src/commands/ListCommand.ts +169 -0
  87. package/src/commands/LoginCommand.ts +197 -0
  88. package/src/commands/LogoutCommand.ts +76 -0
  89. package/src/commands/PublishCommand.ts +230 -0
  90. package/src/commands/SearchCommand.ts +141 -0
  91. package/src/commands/SignCommand.ts +122 -0
  92. package/src/commands/TransferCommand.ts +235 -0
  93. package/src/commands/UndeprecateCommand.ts +134 -0
  94. package/src/commands/UpdateCommand.ts +200 -0
  95. package/src/commands/VerifyCommand.ts +228 -0
  96. package/src/commands/WhoamiCommand.ts +113 -0
  97. package/src/index.ts +86 -0
  98. package/src/lib/PackageRegistry.abi.json +765 -0
  99. package/src/lib/PackageRegistry.abi.ts +365 -0
  100. package/src/lib/binary.ts +336 -0
  101. package/src/lib/config.ts +265 -0
  102. package/src/lib/credentials.ts +176 -0
  103. package/src/lib/ipfs.ts +369 -0
  104. package/src/lib/manifest.ts +172 -0
  105. package/src/lib/provider.ts +121 -0
  106. package/src/lib/registry.ts +464 -0
  107. package/src/lib/wallet.ts +271 -0
  108. package/src/types/PackageRegistry.ts +344 -0
  109. package/src/types/index.ts +145 -0
  110. package/tsconfig.json +25 -0
@@ -0,0 +1,336 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { input, select, confirm } from '@inquirer/prompts';
4
+ import { BaseCommand } from './BaseCommand.js';
5
+ import { validatePluginName } from '../lib/manifest.js';
6
+ export class InitCommand extends BaseCommand {
7
+ constructor() {
8
+ super('init', 'Initialize a new OPNet plugin project');
9
+ }
10
+ configure() {
11
+ this.command
12
+ .argument('[name]', 'Plugin name')
13
+ .option('-t, --template <type>', 'Template type (standalone, library)', 'standalone')
14
+ .option('-y, --yes', 'Skip prompts and use defaults')
15
+ .option('--force', 'Overwrite existing files')
16
+ .action((name, options) => this.execute(name, options));
17
+ }
18
+ async execute(name, options) {
19
+ try {
20
+ const config = await this.gatherConfig(name, options);
21
+ await this.createProject(config, options?.force);
22
+ this.logger.success('Plugin initialized successfully!');
23
+ this.logger.log('');
24
+ this.logger.log('Next steps:');
25
+ this.logger.log(' 1. npm install');
26
+ this.logger.log(' 2. Edit src/index.ts');
27
+ this.logger.log(' 3. npm run build');
28
+ this.logger.log(' 4. opnet compile');
29
+ this.logger.log('');
30
+ }
31
+ catch (error) {
32
+ if (this.isUserCancelled(error)) {
33
+ this.logger.warn('Initialization cancelled.');
34
+ process.exit(0);
35
+ }
36
+ this.exitWithError(this.formatError(error));
37
+ }
38
+ }
39
+ async gatherConfig(name, options) {
40
+ if (options?.yes && name) {
41
+ return {
42
+ pluginName: name,
43
+ authorName: 'Author',
44
+ pluginType: options.template || 'standalone',
45
+ };
46
+ }
47
+ this.logger.info('\nOPNet Plugin Initialization\n');
48
+ const pluginName = name || await input({
49
+ message: 'Plugin name:',
50
+ default: path.basename(process.cwd()),
51
+ validate: (value) => {
52
+ const errors = validatePluginName(value);
53
+ return errors.length > 0 ? errors[0] : true;
54
+ },
55
+ });
56
+ const description = await input({ message: 'Description:', default: '' }) || undefined;
57
+ const authorName = await input({ message: 'Author name:', default: process.env.USER || 'Author' });
58
+ const authorEmail = await input({ message: 'Author email (optional):', default: '' }) || undefined;
59
+ const pluginType = await select({
60
+ message: 'Plugin type:',
61
+ choices: [
62
+ { name: 'Standalone', value: 'standalone', description: 'Independent plugin' },
63
+ { name: 'Library', value: 'library', description: 'Shared library' },
64
+ ],
65
+ default: options?.template || 'standalone',
66
+ });
67
+ return { pluginName, authorName, authorEmail, description, pluginType };
68
+ }
69
+ async createProject(config, force) {
70
+ const nameErrors = validatePluginName(config.pluginName);
71
+ if (nameErrors.length > 0) {
72
+ this.exitWithError(`Invalid plugin name: ${nameErrors.join(', ')}`);
73
+ }
74
+ const projectDir = process.cwd();
75
+ const pluginJsonPath = path.join(projectDir, 'plugin.json');
76
+ if (fs.existsSync(pluginJsonPath) && !force) {
77
+ const overwrite = await confirm({ message: 'plugin.json exists. Overwrite?', default: false });
78
+ if (!overwrite) {
79
+ this.logger.warn('Initialization cancelled.');
80
+ return;
81
+ }
82
+ }
83
+ this.logger.info('Creating project structure...');
84
+ for (const dir of ['src', 'dist', 'build', 'test']) {
85
+ const dirPath = path.join(projectDir, dir);
86
+ if (!fs.existsSync(dirPath)) {
87
+ fs.mkdirSync(dirPath, { recursive: true });
88
+ }
89
+ }
90
+ this.createPluginJson(projectDir, config);
91
+ this.logger.success(' Created plugin.json');
92
+ this.createPackageJson(projectDir, config, force);
93
+ this.createTsConfig(projectDir, force);
94
+ this.createEntryPoint(projectDir, config, force);
95
+ this.createGitignore(projectDir, force);
96
+ this.createReadme(projectDir, config, force);
97
+ }
98
+ createPluginJson(projectDir, config) {
99
+ const manifest = {
100
+ name: config.pluginName,
101
+ version: '1.0.0',
102
+ opnetVersion: '^1.0.0',
103
+ main: 'dist/index.jsc',
104
+ target: 'bytenode',
105
+ type: 'plugin',
106
+ checksum: '',
107
+ author: config.authorEmail
108
+ ? { name: config.authorName, email: config.authorEmail }
109
+ : { name: config.authorName },
110
+ pluginType: config.pluginType,
111
+ permissions: {
112
+ database: {
113
+ enabled: false,
114
+ collections: [],
115
+ },
116
+ blocks: {
117
+ preProcess: false,
118
+ postProcess: false,
119
+ onChange: false,
120
+ },
121
+ epochs: {
122
+ onChange: false,
123
+ onFinalized: false,
124
+ },
125
+ mempool: {
126
+ txFeed: false,
127
+ txSubmit: false,
128
+ },
129
+ api: {
130
+ addEndpoints: false,
131
+ addWebsocket: false,
132
+ },
133
+ threading: {
134
+ maxWorkers: 1,
135
+ maxMemoryMB: 256,
136
+ },
137
+ filesystem: {
138
+ configDir: false,
139
+ tempDir: false,
140
+ },
141
+ blockchain: {
142
+ blocks: false,
143
+ transactions: false,
144
+ contracts: false,
145
+ utxos: false,
146
+ },
147
+ },
148
+ resources: {
149
+ memory: {
150
+ maxHeapMB: 256,
151
+ maxOldGenMB: 128,
152
+ maxYoungGenMB: 64,
153
+ },
154
+ cpu: {
155
+ maxThreads: 2,
156
+ priority: 'normal',
157
+ },
158
+ timeout: {
159
+ initMs: 30000,
160
+ hookMs: 5000,
161
+ shutdownMs: 10000,
162
+ },
163
+ },
164
+ lifecycle: {
165
+ loadPriority: 100,
166
+ enabledByDefault: true,
167
+ requiresRestart: false,
168
+ },
169
+ dependencies: {},
170
+ };
171
+ if (config.description) {
172
+ manifest.description = config.description;
173
+ }
174
+ fs.writeFileSync(path.join(projectDir, 'plugin.json'), JSON.stringify(manifest, null, 4));
175
+ }
176
+ createPackageJson(projectDir, config, force) {
177
+ const packageJsonPath = path.join(projectDir, 'package.json');
178
+ if (fs.existsSync(packageJsonPath) && !force)
179
+ return;
180
+ const packageJson = {
181
+ name: config.pluginName,
182
+ version: '1.0.0',
183
+ description: config.description || 'OPNet plugin',
184
+ type: 'module',
185
+ main: 'dist/index.js',
186
+ scripts: {
187
+ build: 'tsc',
188
+ compile: 'opnet compile',
189
+ verify: 'opnet verify',
190
+ lint: 'eslint src/',
191
+ },
192
+ author: config.authorEmail ? `${config.authorName} <${config.authorEmail}>` : config.authorName,
193
+ license: 'Apache-2.0',
194
+ dependencies: { '@btc-vision/plugin-sdk': '^1.0.0' },
195
+ devDependencies: { '@types/node': '^22.0.0', typescript: '^5.8.0', '@btc-vision/cli': '^1.0.0' },
196
+ };
197
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4));
198
+ this.logger.success(' Created package.json');
199
+ }
200
+ createTsConfig(projectDir, force) {
201
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
202
+ if (fs.existsSync(tsconfigPath) && !force)
203
+ return;
204
+ const tsconfig = {
205
+ compilerOptions: {
206
+ target: 'ES2022',
207
+ module: 'NodeNext',
208
+ moduleResolution: 'NodeNext',
209
+ lib: ['ES2022'],
210
+ outDir: './dist',
211
+ rootDir: './src',
212
+ strict: true,
213
+ esModuleInterop: true,
214
+ skipLibCheck: true,
215
+ forceConsistentCasingInFileNames: true,
216
+ declaration: true,
217
+ sourceMap: true,
218
+ },
219
+ include: ['src/**/*'],
220
+ exclude: ['node_modules', 'dist', 'build'],
221
+ };
222
+ fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 4));
223
+ this.logger.success(' Created tsconfig.json');
224
+ }
225
+ createEntryPoint(projectDir, config, force) {
226
+ const indexPath = path.join(projectDir, 'src', 'index.ts');
227
+ if (fs.existsSync(indexPath) && !force)
228
+ return;
229
+ const className = this.toPascalCase(config.pluginName);
230
+ const content = config.pluginType === 'standalone'
231
+ ? `import { PluginBase, IPluginContext } from '@btc-vision/plugin-sdk';
232
+
233
+ /**
234
+ * ${className} Plugin
235
+ *
236
+ * Extend PluginBase and override only the hooks you need.
237
+ * See the plugin-sdk documentation for available hooks.
238
+ */
239
+ export default class ${className}Plugin extends PluginBase {
240
+ /**
241
+ * Called when the plugin is loaded.
242
+ * Always call super.onLoad(context) first to initialize this.context.
243
+ */
244
+ public async onLoad(context: IPluginContext): Promise<void> {
245
+ await super.onLoad(context);
246
+ this.context.logger.info('${className} plugin loaded');
247
+ }
248
+
249
+ /**
250
+ * Called when the plugin is being unloaded.
251
+ * Clean up any resources here.
252
+ */
253
+ public async onUnload(): Promise<void> {
254
+ this.context.logger.info('${className} plugin unloading');
255
+ }
256
+
257
+ /**
258
+ * Called when the plugin is enabled.
259
+ */
260
+ public async onEnable(): Promise<void> {
261
+ this.context.logger.info('${className} plugin enabled');
262
+ }
263
+
264
+ /**
265
+ * Called when the plugin is disabled.
266
+ */
267
+ public async onDisable(): Promise<void> {
268
+ this.context.logger.info('${className} plugin disabled');
269
+ }
270
+ }
271
+ `
272
+ : `export * from './lib/index.js';
273
+ `;
274
+ fs.writeFileSync(indexPath, content);
275
+ this.logger.success(' Created src/index.ts');
276
+ if (config.pluginType === 'library') {
277
+ const libDir = path.join(projectDir, 'src', 'lib');
278
+ fs.mkdirSync(libDir, { recursive: true });
279
+ fs.writeFileSync(path.join(libDir, 'index.ts'), `export function hello(): string {
280
+ return 'Hello from ${config.pluginName}!';
281
+ }
282
+ `);
283
+ this.logger.success(' Created src/lib/index.ts');
284
+ }
285
+ }
286
+ createGitignore(projectDir, force) {
287
+ const gitignorePath = path.join(projectDir, '.gitignore');
288
+ if (fs.existsSync(gitignorePath) && !force)
289
+ return;
290
+ fs.writeFileSync(gitignorePath, `node_modules/
291
+ dist/
292
+ build/
293
+ *.jsc
294
+ *.opnet
295
+ .idea/
296
+ .vscode/
297
+ .DS_Store
298
+ .env
299
+ *.log
300
+ coverage/
301
+ `);
302
+ this.logger.success(' Created .gitignore');
303
+ }
304
+ createReadme(projectDir, config, force) {
305
+ const readmePath = path.join(projectDir, 'README.md');
306
+ if (fs.existsSync(readmePath) && !force)
307
+ return;
308
+ fs.writeFileSync(readmePath, `# ${config.pluginName}
309
+
310
+ ${config.description || `An OPNet ${config.pluginType} plugin.`}
311
+
312
+ ## Installation
313
+
314
+ \`\`\`bash
315
+ npm install
316
+ \`\`\`
317
+
318
+ ## Development
319
+
320
+ \`\`\`bash
321
+ npm run build # Build TypeScript
322
+ npm run compile # Compile to .opnet
323
+ npm run verify # Verify binary
324
+ \`\`\`
325
+
326
+ ## License
327
+
328
+ Apache-2.0
329
+ `);
330
+ this.logger.success(' Created README.md');
331
+ }
332
+ toPascalCase(str) {
333
+ return str.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
334
+ }
335
+ }
336
+ export const initCommand = new InitCommand().getCommand();
@@ -0,0 +1,7 @@
1
+ import { BaseCommand } from './BaseCommand.js';
2
+ export declare class InstallCommand extends BaseCommand {
3
+ constructor();
4
+ protected configure(): void;
5
+ private execute;
6
+ }
7
+ export declare const installCommand: import("commander").Command;
@@ -0,0 +1,130 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { BaseCommand } from './BaseCommand.js';
4
+ import { getPackage, getVersion, registryToMldsaLevel } from '../lib/registry.js';
5
+ import { fetchFromIPFS, isValidCid } from '../lib/ipfs.js';
6
+ import { parseOpnetBinary, verifyChecksum, formatFileSize } from '../lib/binary.js';
7
+ import { CLIWallet } from '../lib/wallet.js';
8
+ export class InstallCommand extends BaseCommand {
9
+ constructor() {
10
+ super('install', 'Download and verify a plugin from the registry');
11
+ }
12
+ configure() {
13
+ this.command
14
+ .argument('<package>', 'Package name[@version] or IPFS CID')
15
+ .option('-o, --output <path>', 'Output directory (default: ./plugins/)')
16
+ .option('-n, --network <network>', 'Network', 'mainnet')
17
+ .option('--skip-verify', 'Skip signature verification')
18
+ .action((packageInput, options) => this.execute(packageInput, options || { network: 'mainnet' }));
19
+ }
20
+ async execute(packageInput, options) {
21
+ try {
22
+ let cid;
23
+ let packageName;
24
+ let version;
25
+ if (isValidCid(packageInput)) {
26
+ cid = packageInput;
27
+ packageName = 'unknown';
28
+ version = 'unknown';
29
+ this.logger.info(`Installing from CID: ${cid}`);
30
+ }
31
+ else {
32
+ const atIndex = packageInput.lastIndexOf('@');
33
+ if (atIndex > 0 && !packageInput.startsWith('@')) {
34
+ packageName = packageInput.substring(0, atIndex);
35
+ version = packageInput.substring(atIndex + 1);
36
+ }
37
+ else if (packageInput.startsWith('@') && packageInput.indexOf('@', 1) > 0) {
38
+ const secondAt = packageInput.indexOf('@', 1);
39
+ packageName = packageInput.substring(0, secondAt);
40
+ version = packageInput.substring(secondAt + 1);
41
+ }
42
+ else {
43
+ packageName = packageInput;
44
+ version = 'latest';
45
+ }
46
+ this.logger.info(`Fetching ${packageName}...`);
47
+ const network = (options?.network || 'mainnet');
48
+ const packageInfo = await getPackage(packageName, network);
49
+ if (!packageInfo) {
50
+ this.logger.fail('Package not found');
51
+ this.logger.error(`Package "${packageName}" does not exist.`);
52
+ process.exit(1);
53
+ }
54
+ if (version === 'latest') {
55
+ version = packageInfo.latestVersion;
56
+ }
57
+ const versionInfo = await getVersion(packageName, version, network);
58
+ if (!versionInfo) {
59
+ this.logger.fail('Version not found');
60
+ this.logger.error(`Version "${version}" does not exist.`);
61
+ process.exit(1);
62
+ }
63
+ if (versionInfo.deprecated) {
64
+ this.logger.warn(`Version ${version} is deprecated`);
65
+ }
66
+ cid = versionInfo.ipfsCid;
67
+ this.logger.success(`Found: ${packageName}@${version}`);
68
+ this.logger.log('');
69
+ this.logger.log(`IPFS CID: ${cid}`);
70
+ this.logger.log(`MLDSA Level: ${registryToMldsaLevel(versionInfo.mldsaLevel)}`);
71
+ this.logger.log(`Publisher: ${versionInfo.publisher}`);
72
+ this.logger.log('');
73
+ }
74
+ this.logger.info('Downloading from IPFS...');
75
+ const result = await fetchFromIPFS(cid);
76
+ this.logger.success(`Downloaded (${formatFileSize(result.size)})`);
77
+ this.logger.info('Verifying binary...');
78
+ const parsed = parseOpnetBinary(result.data);
79
+ if (!verifyChecksum(parsed)) {
80
+ this.logger.fail('Checksum verification failed');
81
+ this.logger.error('The binary appears to be corrupted.');
82
+ process.exit(1);
83
+ }
84
+ if (!options?.skipVerify) {
85
+ const isUnsigned = parsed.publicKey.every((b) => b === 0);
86
+ if (isUnsigned) {
87
+ this.logger.warn('Binary is unsigned');
88
+ }
89
+ else {
90
+ const actualMldsaLevel = [44, 65, 87][parsed.mldsaLevel];
91
+ const signatureValid = CLIWallet.verifyMLDSA(parsed.checksum, parsed.signature, parsed.publicKey, actualMldsaLevel);
92
+ if (!signatureValid) {
93
+ this.logger.fail('Signature verification failed');
94
+ this.logger.error('The binary signature is invalid.');
95
+ process.exit(1);
96
+ }
97
+ this.logger.success('Signature verified');
98
+ }
99
+ }
100
+ else {
101
+ this.logger.warn('Skipping signature verification');
102
+ }
103
+ if (packageName === 'unknown') {
104
+ packageName = parsed.metadata.name;
105
+ version = parsed.metadata.version;
106
+ }
107
+ const outputDir = options?.output || path.join(process.cwd(), 'plugins');
108
+ fs.mkdirSync(outputDir, { recursive: true });
109
+ const fileName = `${packageName.replace(/^@/, '').replace(/\//g, '-')}-${version}.opnet`;
110
+ const outputPath = path.join(outputDir, fileName);
111
+ this.logger.info('Saving plugin...');
112
+ fs.writeFileSync(outputPath, result.data);
113
+ this.logger.success('Plugin installed');
114
+ this.logger.log('');
115
+ this.logger.success('Plugin installed successfully!');
116
+ this.logger.log('');
117
+ this.logger.log(`Package: ${parsed.metadata.name}`);
118
+ this.logger.log(`Version: ${parsed.metadata.version}`);
119
+ this.logger.log(`Type: ${parsed.metadata.pluginType}`);
120
+ this.logger.log(`Size: ${formatFileSize(result.size)}`);
121
+ this.logger.log(`Output: ${outputPath}`);
122
+ this.logger.log('');
123
+ }
124
+ catch (error) {
125
+ this.logger.fail('Installation failed');
126
+ this.exitWithError(this.formatError(error));
127
+ }
128
+ }
129
+ }
130
+ export const installCommand = new InstallCommand().getCommand();
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import { BaseCommand } from './BaseCommand.js';
3
+ export declare class KeygenCommand extends BaseCommand {
4
+ constructor();
5
+ protected configure(): void;
6
+ private createMnemonicCommand;
7
+ private createMldsaCommand;
8
+ private createInfoCommand;
9
+ private handleMnemonic;
10
+ private handleMldsa;
11
+ private handleInfo;
12
+ }
13
+ export declare const keygenCommand: Command;
@@ -0,0 +1,133 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import { BaseCommand } from './BaseCommand.js';
4
+ import { generateMLDSAKeypair, generateMnemonic, computePublicKeyHash } from '../lib/wallet.js';
5
+ import { isValidMldsaLevel } from '../lib/credentials.js';
6
+ export class KeygenCommand extends BaseCommand {
7
+ constructor() {
8
+ super('keygen', 'Generate cryptographic keys');
9
+ }
10
+ configure() {
11
+ this.command
12
+ .addCommand(this.createMnemonicCommand())
13
+ .addCommand(this.createMldsaCommand())
14
+ .addCommand(this.createInfoCommand());
15
+ }
16
+ createMnemonicCommand() {
17
+ return new Command('mnemonic')
18
+ .description('Generate a new BIP-39 mnemonic phrase')
19
+ .option('-o, --output <file>', 'Write mnemonic to file (secure permissions)')
20
+ .action((options) => this.handleMnemonic(options));
21
+ }
22
+ createMldsaCommand() {
23
+ return new Command('mldsa')
24
+ .description('Generate a standalone MLDSA keypair')
25
+ .option('-l, --level <level>', 'MLDSA security level (44, 65, 87)', '44')
26
+ .option('-o, --output <prefix>', 'Write keys to files with prefix')
27
+ .option('--json', 'Output as JSON')
28
+ .action((options) => {
29
+ this.handleMldsa(options);
30
+ });
31
+ }
32
+ createInfoCommand() {
33
+ return new Command('info')
34
+ .description('Show information about MLDSA key sizes')
35
+ .action(() => this.handleInfo());
36
+ }
37
+ handleMnemonic(options) {
38
+ try {
39
+ const mnemonic = generateMnemonic();
40
+ if (options.output) {
41
+ fs.writeFileSync(options.output, mnemonic + '\n', { mode: 0o600 });
42
+ this.logger.success(`Mnemonic saved to: ${options.output}`);
43
+ this.logger.warn('Keep this file secure and backed up!');
44
+ }
45
+ else {
46
+ this.logger.info('\nNew BIP-39 Mnemonic Phrase:\n');
47
+ this.logger.log(mnemonic);
48
+ this.logger.log('');
49
+ this.logger.warn('IMPORTANT: Write down these words and store them securely.');
50
+ this.logger.warn('Anyone with this phrase can access your wallet.');
51
+ this.logger.warn('Never share this phrase with anyone.');
52
+ }
53
+ }
54
+ catch (error) {
55
+ this.exitWithError(this.formatError(error));
56
+ }
57
+ }
58
+ handleMldsa(options) {
59
+ try {
60
+ const levelNum = parseInt(options.level, 10);
61
+ if (!isValidMldsaLevel(levelNum)) {
62
+ this.exitWithError(`Invalid MLDSA level: ${options.level}. Valid: 44, 65, 87`);
63
+ return;
64
+ }
65
+ this.logger.info(`Generating MLDSA-${levelNum} keypair...`);
66
+ const keypair = generateMLDSAKeypair(levelNum);
67
+ const publicKeyHash = computePublicKeyHash(keypair.publicKey);
68
+ if (options.output) {
69
+ const privateKeyPath = `${options.output}.private.key`;
70
+ const publicKeyPath = `${options.output}.public.key`;
71
+ fs.writeFileSync(privateKeyPath, keypair.privateKey.toString('hex') + '\n', {
72
+ mode: 0o600,
73
+ });
74
+ fs.writeFileSync(publicKeyPath, keypair.publicKey.toString('hex') + '\n', {
75
+ mode: 0o644,
76
+ });
77
+ this.logger.success('Keys generated successfully!');
78
+ this.logger.info(` Private key: ${privateKeyPath}`);
79
+ this.logger.info(` Public key: ${publicKeyPath}`);
80
+ this.logger.log('');
81
+ this.logger.log(`Public Key Hash: ${publicKeyHash}`);
82
+ this.logger.log('');
83
+ this.logger.warn('IMPORTANT: Keep the private key secure!');
84
+ }
85
+ else if (options.json) {
86
+ const output = {
87
+ level: levelNum,
88
+ privateKey: keypair.privateKey.toString('hex'),
89
+ publicKey: keypair.publicKey.toString('hex'),
90
+ publicKeyHash,
91
+ privateKeySize: keypair.privateKey.length,
92
+ publicKeySize: keypair.publicKey.length,
93
+ };
94
+ this.logger.log(JSON.stringify(output, null, 2));
95
+ }
96
+ else {
97
+ this.logger.info(`\nMLDSA-${levelNum} Keypair:\n`);
98
+ this.logger.log(`Public Key Hash: ${publicKeyHash}`);
99
+ this.logger.log(`Public Key Size: ${keypair.publicKey.length} bytes`);
100
+ this.logger.log(`Private Key Size: ${keypair.privateKey.length} bytes`);
101
+ this.logger.log('');
102
+ this.logger.log('Public Key (hex):');
103
+ this.logger.log(keypair.publicKey.toString('hex'));
104
+ this.logger.log('');
105
+ this.logger.log('Private Key (hex):');
106
+ this.logger.log(keypair.privateKey.toString('hex'));
107
+ this.logger.log('');
108
+ this.logger.warn('IMPORTANT: Store the private key securely!');
109
+ this.logger.warn('Use --output <prefix> to save to files.');
110
+ }
111
+ }
112
+ catch (error) {
113
+ this.exitWithError(this.formatError(error));
114
+ }
115
+ }
116
+ handleInfo() {
117
+ this.logger.info('\nMLDSA Key Sizes:\n');
118
+ this.logger.log('─'.repeat(60));
119
+ this.logger.log(`${'Level'.padEnd(12)}${'Public Key'.padEnd(15)}${'Private Key'.padEnd(15)}${'Signature'.padEnd(15)}`);
120
+ this.logger.log('─'.repeat(60));
121
+ this.logger.log(`${'MLDSA-44'.padEnd(12)}${'1,312 bytes'.padEnd(15)}${'2,560 bytes'.padEnd(15)}${'2,420 bytes'.padEnd(15)}`);
122
+ this.logger.log(`${'MLDSA-65'.padEnd(12)}${'1,952 bytes'.padEnd(15)}${'4,032 bytes'.padEnd(15)}${'3,309 bytes'.padEnd(15)}`);
123
+ this.logger.log(`${'MLDSA-87'.padEnd(12)}${'2,592 bytes'.padEnd(15)}${'4,896 bytes'.padEnd(15)}${'4,627 bytes'.padEnd(15)}`);
124
+ this.logger.log('─'.repeat(60));
125
+ this.logger.log('');
126
+ this.logger.log('Security levels:');
127
+ this.logger.log(' MLDSA-44: ~128-bit security (fastest, smallest)');
128
+ this.logger.log(' MLDSA-65: ~192-bit security (balanced)');
129
+ this.logger.log(' MLDSA-87: ~256-bit security (highest security)');
130
+ this.logger.log('');
131
+ }
132
+ }
133
+ export const keygenCommand = new KeygenCommand().getCommand();
@@ -0,0 +1,7 @@
1
+ import { BaseCommand } from './BaseCommand.js';
2
+ export declare class ListCommand extends BaseCommand {
3
+ constructor();
4
+ protected configure(): void;
5
+ private execute;
6
+ }
7
+ export declare const listCommand: import("commander").Command;