@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,230 @@
1
+ /**
2
+ * Publish command - Publish plugin to the OPNet registry
3
+ *
4
+ * @module commands/PublishCommand
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as crypto from 'crypto';
10
+ import { confirm } from '@inquirer/prompts';
11
+ import { BaseCommand } from './BaseCommand.js';
12
+ import { formatFileSize, parseOpnetBinary, verifyChecksum } from '../lib/binary.js';
13
+ import { CLIWallet } from '../lib/wallet.js';
14
+ import { canSign, loadCredentials } from '../lib/credentials.js';
15
+ import { uploadPlugin } from '../lib/ipfs.js';
16
+ import {
17
+ computePermissionsHash,
18
+ encodeDependencies,
19
+ getPackage,
20
+ getScope,
21
+ mldsaLevelToRegistry,
22
+ parsePackageName,
23
+ pluginTypeToRegistry,
24
+ } from '../lib/registry.js';
25
+ import { CLIMldsaLevel, NetworkName } from '../types/index.js';
26
+
27
+ interface PublishOptions {
28
+ network: string;
29
+ dryRun?: boolean;
30
+ yes?: boolean;
31
+ }
32
+
33
+ export class PublishCommand extends BaseCommand {
34
+ constructor() {
35
+ super('publish', 'Publish a plugin to the OPNet registry');
36
+ }
37
+
38
+ protected configure(): void {
39
+ this.command
40
+ .argument('[file]', 'Path to .opnet file (default: ./build/<name>.opnet)')
41
+ .option('-n, --network <network>', 'Network to publish to', 'mainnet')
42
+ .option('--dry-run', 'Show what would be published without publishing')
43
+ .option('-y, --yes', 'Skip confirmation prompts')
44
+ .action((file?: string, options?: PublishOptions) =>
45
+ this.execute(file, options || { network: 'mainnet' }),
46
+ );
47
+ }
48
+
49
+ private async execute(file?: string, options?: PublishOptions): Promise<void> {
50
+ try {
51
+ // Find the .opnet file
52
+ let binaryPath = file;
53
+ if (!binaryPath) {
54
+ const manifestPath = path.join(process.cwd(), 'plugin.json');
55
+ if (fs.existsSync(manifestPath)) {
56
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as {
57
+ name: string;
58
+ };
59
+ const name = manifest.name.replace(/^@/, '').replace(/\//g, '-');
60
+ binaryPath = path.join(process.cwd(), 'build', `${name}.opnet`);
61
+ }
62
+ }
63
+
64
+ if (!binaryPath || !fs.existsSync(binaryPath)) {
65
+ this.logger.fail('No .opnet file found.');
66
+ this.logger.info('Run `opnet compile` first or specify the file path.');
67
+ process.exit(1);
68
+ }
69
+
70
+ // Parse and validate binary
71
+ this.logger.info('Parsing plugin binary...');
72
+ const data = fs.readFileSync(binaryPath);
73
+ const parsed = parseOpnetBinary(data);
74
+
75
+ // Verify checksum
76
+ if (!verifyChecksum(parsed)) {
77
+ this.logger.fail('Checksum verification failed');
78
+ this.logger.error('The binary appears to be corrupted.');
79
+ process.exit(1);
80
+ }
81
+
82
+ // Check if signed
83
+ const isUnsigned = parsed.publicKey.every((b) => b === 0);
84
+ if (isUnsigned) {
85
+ this.logger.fail('Binary is unsigned');
86
+ this.logger.error('Cannot publish unsigned binaries.');
87
+ this.logger.info('Run `opnet sign` to sign the binary.');
88
+ process.exit(1);
89
+ }
90
+
91
+ const meta = parsed.metadata;
92
+ const mldsaLevel = ([44, 65, 87] as const)[parsed.mldsaLevel] as CLIMldsaLevel;
93
+ this.logger.success(`Parsed: ${meta.name}@${meta.version}`);
94
+
95
+ // Load wallet and verify ownership
96
+ this.logger.info('Loading wallet...');
97
+ const credentials = loadCredentials();
98
+ if (!credentials || !canSign(credentials)) {
99
+ this.logger.fail('No credentials configured');
100
+ this.logger.warn('Run `opnet login` to configure your wallet.');
101
+ process.exit(1);
102
+ }
103
+
104
+ const wallet = CLIWallet.fromCredentials(credentials);
105
+ const walletPkHash = wallet.mldsaPublicKeyHash;
106
+ const binaryPkHash = crypto.createHash('sha256').update(parsed.publicKey).digest('hex');
107
+
108
+ if (walletPkHash !== binaryPkHash) {
109
+ this.logger.fail('Wallet mismatch');
110
+ this.logger.error('The binary was signed with a different key.');
111
+ this.logger.log(`Binary signer: ${binaryPkHash.substring(0, 32)}...`);
112
+ this.logger.log(`Your key: ${walletPkHash.substring(0, 32)}...`);
113
+ process.exit(1);
114
+ }
115
+ this.logger.success('Wallet verified');
116
+
117
+ // Check registry status
118
+ this.logger.info('Checking registry status...');
119
+ const { scope, name } = parsePackageName(meta.name);
120
+ const network = (options?.network || 'mainnet') as NetworkName;
121
+
122
+ // Check if scoped package
123
+ if (scope) {
124
+ const scopeInfo = await getScope(scope, network);
125
+ if (!scopeInfo) {
126
+ this.logger.fail(`Scope @${scope} is not registered`);
127
+ this.logger.warn(
128
+ `Register the scope first with: opnet scope register ${scope}`,
129
+ );
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ const packageInfo = await getPackage(meta.name, network);
135
+ const isNewPackage = !packageInfo;
136
+ this.logger.success(
137
+ isNewPackage
138
+ ? 'New package registration'
139
+ : `Existing package (${packageInfo.versionCount} versions)`,
140
+ );
141
+
142
+ // Display summary
143
+ this.logger.log('');
144
+ this.logger.info('Publishing Summary');
145
+ this.logger.log('─'.repeat(50));
146
+ this.logger.log(`Package: ${meta.name}`);
147
+ this.logger.log(`Version: ${meta.version}`);
148
+ this.logger.log(`Type: ${meta.pluginType}`);
149
+ this.logger.log(`OPNet: ${meta.opnetVersion}`);
150
+ this.logger.log(`Size: ${formatFileSize(data.length)}`);
151
+ this.logger.log(`MLDSA Level: ${mldsaLevel}`);
152
+ this.logger.log(`Network: ${options?.network}`);
153
+ this.logger.log(`Status: ${isNewPackage ? 'New package' : 'New version'}`);
154
+ this.logger.log('');
155
+
156
+ if (options?.dryRun) {
157
+ this.logger.warn('Dry run - no changes made.');
158
+ return;
159
+ }
160
+
161
+ // Confirmation
162
+ if (!options?.yes) {
163
+ const confirmed = await confirm({
164
+ message: 'Publish this plugin?',
165
+ default: true,
166
+ });
167
+
168
+ if (!confirmed) {
169
+ this.logger.warn('Publishing cancelled.');
170
+ return;
171
+ }
172
+ }
173
+
174
+ // Upload to IPFS
175
+ this.logger.info('Uploading to IPFS...');
176
+ const pinResult = await uploadPlugin(binaryPath);
177
+ this.logger.success(`Uploaded to IPFS: ${pinResult.cid}`);
178
+
179
+ // Prepare registry data
180
+ const permissionsHash = computePermissionsHash(meta.permissions);
181
+ const dependencies = encodeDependencies(meta.dependencies || {});
182
+
183
+ // Register package if new
184
+ if (isNewPackage) {
185
+ this.logger.info('Registering package...');
186
+ this.logger.warn('Package registration required.');
187
+ this.logger.log(`Transaction would call: registerPackage("${meta.name}")`);
188
+ this.logger.info('Package registration (transaction pending)');
189
+ }
190
+
191
+ // Publish version
192
+ this.logger.info('Publishing version...');
193
+ this.logger.warn('Version publishing required.');
194
+ this.logger.log('Transaction would call: publishVersion(');
195
+ this.logger.log(` packageName: "${meta.name}",`);
196
+ this.logger.log(` version: "${meta.version}",`);
197
+ this.logger.log(` ipfsCid: "${pinResult.cid}",`);
198
+ this.logger.log(` checksum: <32 bytes>,`);
199
+ this.logger.log(` signature: <${parsed.signature.length} bytes>,`);
200
+ this.logger.log(` mldsaLevel: ${mldsaLevelToRegistry(mldsaLevel)},`);
201
+ this.logger.log(` opnetVersionRange: "${meta.opnetVersion}",`);
202
+ this.logger.log(` pluginType: ${pluginTypeToRegistry(meta.pluginType)},`);
203
+ this.logger.log(` permissionsHash: <32 bytes>,`);
204
+ this.logger.log(` dependencies: <${dependencies.length} bytes>`);
205
+ this.logger.log(')');
206
+ this.logger.info('Version publishing (transaction pending)');
207
+
208
+ this.logger.log('');
209
+ this.logger.success('Plugin uploaded successfully!');
210
+ this.logger.log('');
211
+ this.logger.log(`IPFS CID: ${pinResult.cid}`);
212
+ this.logger.log(`Gateway: https://ipfs.opnet.org/ipfs/${pinResult.cid}`);
213
+ this.logger.log('');
214
+ this.logger.warn('Note: Registry transaction support is coming soon.');
215
+ this.logger.warn(
216
+ 'The binary has been uploaded to IPFS and is ready for registry submission.',
217
+ );
218
+ this.logger.log('');
219
+ } catch (error) {
220
+ this.logger.fail('Publishing failed');
221
+ if (this.isUserCancelled(error)) {
222
+ this.logger.warn('Publishing cancelled.');
223
+ process.exit(0);
224
+ }
225
+ this.exitWithError(this.formatError(error));
226
+ }
227
+ }
228
+ }
229
+
230
+ export const publishCommand = new PublishCommand().getCommand();
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Search command - Search for plugins in the registry
3
+ *
4
+ * @module commands/SearchCommand
5
+ */
6
+
7
+ import { BaseCommand } from './BaseCommand.js';
8
+ import {
9
+ getPackage,
10
+ getVersion,
11
+ registryToMldsaLevel,
12
+ registryToPluginType,
13
+ VersionInfo,
14
+ } from '../lib/registry.js';
15
+ import { NetworkName } from '../types/index.js';
16
+
17
+ interface SearchOptions {
18
+ network: string;
19
+ json?: boolean;
20
+ }
21
+
22
+ export class SearchCommand extends BaseCommand {
23
+ constructor() {
24
+ super('search', 'Search for plugins in the registry');
25
+ }
26
+
27
+ protected configure(): void {
28
+ this.command
29
+ .argument('<query>', 'Package name or search query')
30
+ .option('-n, --network <network>', 'Network', 'mainnet')
31
+ .option('--json', 'Output as JSON')
32
+ .action((query: string, options?: SearchOptions) =>
33
+ this.execute(query, options || { network: 'mainnet' }),
34
+ );
35
+ }
36
+
37
+ private async execute(query: string, options?: SearchOptions): Promise<void> {
38
+ try {
39
+ const network = (options?.network || 'mainnet') as NetworkName;
40
+
41
+ // Direct package lookup
42
+ this.logger.info(`Searching for "${query}"...`);
43
+
44
+ const packageInfo = await getPackage(query, network);
45
+
46
+ if (!packageInfo) {
47
+ this.logger.fail('No results');
48
+
49
+ if (options?.json) {
50
+ this.logger.log(JSON.stringify({ results: [], query }));
51
+ } else {
52
+ this.logger.log('');
53
+ this.logger.warn(`No package found matching "${query}".`);
54
+ this.logger.log('');
55
+ this.logger.info('Tips:');
56
+ this.logger.info(' - For scoped packages, use @scope/name');
57
+ this.logger.info(' - Package names are case-sensitive');
58
+ this.logger.info(' - Try searching without the version');
59
+ }
60
+ return;
61
+ }
62
+
63
+ // Get latest version details
64
+ let latestVersionInfo: VersionInfo | null = null;
65
+ if (packageInfo.latestVersion) {
66
+ latestVersionInfo = await getVersion(query, packageInfo.latestVersion, network);
67
+ }
68
+
69
+ this.logger.success('Package found');
70
+
71
+ if (options?.json) {
72
+ const output = {
73
+ results: [
74
+ {
75
+ name: query,
76
+ latestVersion: packageInfo.latestVersion,
77
+ versionCount: Number(packageInfo.versionCount),
78
+ createdAt: Number(packageInfo.createdAt),
79
+ owner: packageInfo.owner.toString(),
80
+ details: latestVersionInfo
81
+ ? {
82
+ ipfsCid: latestVersionInfo.ipfsCid,
83
+ mldsaLevel: registryToMldsaLevel(
84
+ latestVersionInfo.mldsaLevel,
85
+ ),
86
+ pluginType: registryToPluginType(
87
+ latestVersionInfo.pluginType,
88
+ ),
89
+ opnetVersion: latestVersionInfo.opnetVersionRange,
90
+ deprecated: latestVersionInfo.deprecated,
91
+ publishedAt: Number(latestVersionInfo.publishedAt),
92
+ }
93
+ : null,
94
+ },
95
+ ],
96
+ query,
97
+ };
98
+ this.logger.log(JSON.stringify(output, null, 2));
99
+ return;
100
+ }
101
+
102
+ // Display results
103
+ this.logger.log('');
104
+ this.logger.info('Package Information');
105
+ this.logger.info('─'.repeat(60));
106
+ this.logger.log('');
107
+ this.logger.info(`Name: ${query}`);
108
+ this.logger.info(`Latest: ${packageInfo.latestVersion || 'N/A'}`);
109
+ this.logger.info(`Versions: ${packageInfo.versionCount}`);
110
+ this.logger.info(`Owner: ${packageInfo.owner}`);
111
+
112
+ if (latestVersionInfo) {
113
+ this.logger.log('');
114
+ this.logger.info('Latest Version Details:');
115
+ this.logger.info(
116
+ ` Type: ${registryToPluginType(latestVersionInfo.pluginType)}`,
117
+ );
118
+ this.logger.info(
119
+ ` MLDSA Level: ${registryToMldsaLevel(latestVersionInfo.mldsaLevel)}`,
120
+ );
121
+ this.logger.info(` OPNet Range: ${latestVersionInfo.opnetVersionRange}`);
122
+ this.logger.info(` IPFS CID: ${latestVersionInfo.ipfsCid}`);
123
+ this.logger.info(` Deprecated: ${latestVersionInfo.deprecated ? 'Yes' : 'No'}`);
124
+
125
+ const publishDate = new Date(Number(latestVersionInfo.publishedAt) * 1000);
126
+ this.logger.info(` Published: ${publishDate.toISOString().split('T')[0]}`);
127
+ }
128
+
129
+ this.logger.log('');
130
+ this.logger.info('Install with:');
131
+ this.logger.info(` opnet install ${query}`);
132
+ this.logger.info(` opnet install ${query}@${packageInfo.latestVersion}`);
133
+ this.logger.log('');
134
+ } catch (error) {
135
+ this.logger.fail('Search failed');
136
+ this.exitWithError(this.formatError(error));
137
+ }
138
+ }
139
+ }
140
+
141
+ export const searchCommand = new SearchCommand().getCommand();
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Sign command - Sign or re-sign a .opnet binary
3
+ *
4
+ * @module commands/SignCommand
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as crypto from 'crypto';
9
+ import { BaseCommand } from './BaseCommand.js';
10
+ import {
11
+ buildOpnetBinary,
12
+ computeChecksum,
13
+ formatFileSize,
14
+ parseOpnetBinary,
15
+ } from '../lib/binary.js';
16
+ import { CLIWallet } from '../lib/wallet.js';
17
+ import { canSign, loadCredentials } from '../lib/credentials.js';
18
+
19
+ interface SignOptions {
20
+ output?: string;
21
+ force?: boolean;
22
+ }
23
+
24
+ export class SignCommand extends BaseCommand {
25
+ constructor() {
26
+ super('sign', 'Sign or re-sign a .opnet binary with your MLDSA key');
27
+ }
28
+
29
+ protected configure(): void {
30
+ this.command
31
+ .argument('<file>', 'Path to .opnet file')
32
+ .option('-o, --output <path>', 'Output file path (default: overwrites input)')
33
+ .option('--force', 'Force re-signing even if already signed by different key')
34
+ .action((file: string, options: SignOptions) => this.execute(file, options));
35
+ }
36
+
37
+ private execute(file: string, options: SignOptions): void {
38
+ try {
39
+ if (!fs.existsSync(file)) {
40
+ this.exitWithError(`File not found: ${file}`);
41
+ }
42
+
43
+ // Load credentials
44
+ this.logger.info('Loading wallet...');
45
+ const credentials = loadCredentials();
46
+
47
+ if (!credentials || !canSign(credentials)) {
48
+ this.logger.fail('No credentials configured');
49
+ this.logger.warn('To sign plugins, run: opnet login');
50
+ process.exit(1);
51
+ }
52
+
53
+ const wallet = CLIWallet.fromCredentials(credentials);
54
+ this.logger.success(`Wallet loaded (MLDSA-${wallet.securityLevel})`);
55
+
56
+ // Parse existing binary
57
+ this.logger.info('Parsing binary...');
58
+ const data = fs.readFileSync(file);
59
+ const parsed = parseOpnetBinary(data);
60
+ this.logger.success(`Parsed: ${parsed.metadata.name}@${parsed.metadata.version}`);
61
+
62
+ // Check if already signed by a different key
63
+ const isUnsigned = parsed.publicKey.every((b) => b === 0);
64
+ const currentPkHash = crypto
65
+ .createHash('sha256')
66
+ .update(parsed.publicKey)
67
+ .digest('hex');
68
+ const newPkHash = wallet.mldsaPublicKeyHash;
69
+
70
+ if (!isUnsigned && currentPkHash !== newPkHash && !options.force) {
71
+ this.logger.log('');
72
+ this.logger.warn('Warning: This binary is already signed by a different key.');
73
+ this.logger.log(` Current signer: ${currentPkHash.substring(0, 32)}...`);
74
+ this.logger.log(` Your key: ${newPkHash.substring(0, 32)}...`);
75
+ this.logger.log('');
76
+ this.logger.log('Use --force to re-sign with your key.');
77
+ process.exit(1);
78
+ }
79
+
80
+ // Compute new signature
81
+ this.logger.info('Signing...');
82
+ const metadataBytes = Buffer.from(parsed.rawMetadata, 'utf-8');
83
+ const checksum = computeChecksum(
84
+ metadataBytes,
85
+ parsed.bytecode,
86
+ parsed.proto ?? Buffer.alloc(0),
87
+ );
88
+ const signature = wallet.signMLDSA(checksum);
89
+ this.logger.success(`Signed (${formatFileSize(signature.length)} signature)`);
90
+
91
+ // Rebuild binary
92
+ this.logger.info('Rebuilding binary...');
93
+ const newBinary = buildOpnetBinary({
94
+ mldsaLevel: wallet.securityLevel,
95
+ publicKey: wallet.mldsaPublicKey,
96
+ signature,
97
+ metadata: parsed.metadata,
98
+ bytecode: parsed.bytecode,
99
+ proto: parsed.proto ?? Buffer.alloc(0),
100
+ });
101
+ this.logger.success(`Binary rebuilt (${formatFileSize(newBinary.length)})`);
102
+
103
+ // Write output
104
+ const outputPath = options.output || file;
105
+ fs.writeFileSync(outputPath, newBinary);
106
+
107
+ this.logger.log('');
108
+ this.logger.success('Plugin signed successfully!');
109
+ this.logger.log('');
110
+ this.logger.log(`Output: ${outputPath}`);
111
+ this.logger.log(`Plugin: ${parsed.metadata.name}@${parsed.metadata.version}`);
112
+ this.logger.log(`MLDSA Level: ${wallet.securityLevel}`);
113
+ this.logger.log(`Publisher: ${newPkHash.substring(0, 32)}...`);
114
+ this.logger.log('');
115
+ } catch (error) {
116
+ this.logger.fail('Signing failed');
117
+ this.exitWithError(this.formatError(error));
118
+ }
119
+ }
120
+ }
121
+
122
+ export const signCommand = new SignCommand().getCommand();