@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,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();
|