@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.
- package/.gitattributes +2 -0
- package/.github/dependabot.yml +9 -0
- package/.github/workflows/ci.yml +48 -0
- package/.prettierrc.json +12 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +190 -0
- package/NOTICE +17 -0
- package/README.md +509 -0
- package/SECURITY.md +35 -0
- package/build/commands/AcceptCommand.d.ts +7 -0
- package/build/commands/AcceptCommand.js +110 -0
- package/build/commands/BaseCommand.d.ts +12 -0
- package/build/commands/BaseCommand.js +27 -0
- package/build/commands/CompileCommand.d.ts +7 -0
- package/build/commands/CompileCommand.js +138 -0
- package/build/commands/ConfigCommand.d.ts +17 -0
- package/build/commands/ConfigCommand.js +124 -0
- package/build/commands/DeprecateCommand.d.ts +7 -0
- package/build/commands/DeprecateCommand.js +112 -0
- package/build/commands/InfoCommand.d.ts +10 -0
- package/build/commands/InfoCommand.js +223 -0
- package/build/commands/InitCommand.d.ts +16 -0
- package/build/commands/InitCommand.js +336 -0
- package/build/commands/InstallCommand.d.ts +7 -0
- package/build/commands/InstallCommand.js +130 -0
- package/build/commands/KeygenCommand.d.ts +13 -0
- package/build/commands/KeygenCommand.js +133 -0
- package/build/commands/ListCommand.d.ts +7 -0
- package/build/commands/ListCommand.js +117 -0
- package/build/commands/LoginCommand.d.ts +9 -0
- package/build/commands/LoginCommand.js +139 -0
- package/build/commands/LogoutCommand.d.ts +7 -0
- package/build/commands/LogoutCommand.js +57 -0
- package/build/commands/PublishCommand.d.ts +7 -0
- package/build/commands/PublishCommand.js +163 -0
- package/build/commands/SearchCommand.d.ts +7 -0
- package/build/commands/SearchCommand.js +97 -0
- package/build/commands/SignCommand.d.ts +7 -0
- package/build/commands/SignCommand.js +80 -0
- package/build/commands/TransferCommand.d.ts +8 -0
- package/build/commands/TransferCommand.js +179 -0
- package/build/commands/UndeprecateCommand.d.ts +7 -0
- package/build/commands/UndeprecateCommand.js +95 -0
- package/build/commands/UpdateCommand.d.ts +7 -0
- package/build/commands/UpdateCommand.js +130 -0
- package/build/commands/VerifyCommand.d.ts +7 -0
- package/build/commands/VerifyCommand.js +167 -0
- package/build/commands/WhoamiCommand.d.ts +7 -0
- package/build/commands/WhoamiCommand.js +84 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +64 -0
- package/build/lib/PackageRegistry.abi.d.ts +2 -0
- package/build/lib/PackageRegistry.abi.js +356 -0
- package/build/lib/binary.d.ts +16 -0
- package/build/lib/binary.js +165 -0
- package/build/lib/config.d.ts +11 -0
- package/build/lib/config.js +160 -0
- package/build/lib/credentials.d.ts +10 -0
- package/build/lib/credentials.js +89 -0
- package/build/lib/ipfs.d.ts +16 -0
- package/build/lib/ipfs.js +209 -0
- package/build/lib/manifest.d.ts +14 -0
- package/build/lib/manifest.js +88 -0
- package/build/lib/provider.d.ts +9 -0
- package/build/lib/provider.js +48 -0
- package/build/lib/registry.d.ts +58 -0
- package/build/lib/registry.js +197 -0
- package/build/lib/wallet.d.ts +32 -0
- package/build/lib/wallet.js +114 -0
- package/build/types/PackageRegistry.d.ts +177 -0
- package/build/types/PackageRegistry.js +1 -0
- package/build/types/index.d.ts +30 -0
- package/build/types/index.js +52 -0
- package/eslint.config.js +41 -0
- package/gulpfile.js +41 -0
- package/package.json +83 -0
- package/src/commands/AcceptCommand.ts +151 -0
- package/src/commands/BaseCommand.ts +59 -0
- package/src/commands/CompileCommand.ts +196 -0
- package/src/commands/ConfigCommand.ts +144 -0
- package/src/commands/DeprecateCommand.ts +156 -0
- package/src/commands/InfoCommand.ts +293 -0
- package/src/commands/InitCommand.ts +465 -0
- package/src/commands/InstallCommand.ts +179 -0
- package/src/commands/KeygenCommand.ts +157 -0
- package/src/commands/ListCommand.ts +169 -0
- package/src/commands/LoginCommand.ts +197 -0
- package/src/commands/LogoutCommand.ts +76 -0
- package/src/commands/PublishCommand.ts +230 -0
- package/src/commands/SearchCommand.ts +141 -0
- package/src/commands/SignCommand.ts +122 -0
- package/src/commands/TransferCommand.ts +235 -0
- package/src/commands/UndeprecateCommand.ts +134 -0
- package/src/commands/UpdateCommand.ts +200 -0
- package/src/commands/VerifyCommand.ts +228 -0
- package/src/commands/WhoamiCommand.ts +113 -0
- package/src/index.ts +86 -0
- package/src/lib/PackageRegistry.abi.json +765 -0
- package/src/lib/PackageRegistry.abi.ts +365 -0
- package/src/lib/binary.ts +336 -0
- package/src/lib/config.ts +265 -0
- package/src/lib/credentials.ts +176 -0
- package/src/lib/ipfs.ts +369 -0
- package/src/lib/manifest.ts +172 -0
- package/src/lib/provider.ts +121 -0
- package/src/lib/registry.ts +464 -0
- package/src/lib/wallet.ts +271 -0
- package/src/types/PackageRegistry.ts +344 -0
- package/src/types/index.ts +145 -0
- 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,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();
|