@monoes/monomindcli 1.10.1 ā 1.10.3
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/.claude/commands/monomind/understand.md +111 -70
- package/package.json +3 -2
- package/scripts/deploy-ipfs-node.sh +153 -0
- package/scripts/publish-registry.ts +345 -0
- package/scripts/publish.sh +55 -0
- package/scripts/setup-ipfs-registry.md +366 -0
- package/scripts/sync-claude-assets.sh +34 -0
- package/scripts/understand-analyze.mjs +885 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Registry Publisher
|
|
4
|
+
*
|
|
5
|
+
* Publishes the plugin registry to IPFS via Pinata and updates IPNS pointer.
|
|
6
|
+
*
|
|
7
|
+
* Setup:
|
|
8
|
+
* 1. Create Pinata account at https://pinata.cloud
|
|
9
|
+
* 2. Generate API keys (JWT)
|
|
10
|
+
* 3. Set environment variables:
|
|
11
|
+
* - PINATA_JWT: Your Pinata JWT token
|
|
12
|
+
* - REGISTRY_PRIVATE_KEY: Ed25519 private key (hex) for signing
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* npx tsx scripts/publish-registry.ts
|
|
16
|
+
* npx tsx scripts/publish-registry.ts --dry-run
|
|
17
|
+
* npx tsx scripts/publish-registry.ts --registry ./custom-registry.json
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as fs from 'fs';
|
|
21
|
+
import * as path from 'path';
|
|
22
|
+
import * as crypto from 'crypto';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
|
|
28
|
+
// Types
|
|
29
|
+
interface PluginEntry {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
displayName: string;
|
|
33
|
+
description: string;
|
|
34
|
+
version: string;
|
|
35
|
+
cid?: string;
|
|
36
|
+
size: number;
|
|
37
|
+
checksum: string;
|
|
38
|
+
author: {
|
|
39
|
+
id: string;
|
|
40
|
+
displayName: string;
|
|
41
|
+
verified: boolean;
|
|
42
|
+
};
|
|
43
|
+
license: string;
|
|
44
|
+
categories: string[];
|
|
45
|
+
tags: string[];
|
|
46
|
+
downloads: number;
|
|
47
|
+
rating: number;
|
|
48
|
+
lastUpdated: string;
|
|
49
|
+
minMonomindVersion: string;
|
|
50
|
+
type: string;
|
|
51
|
+
hooks: string[];
|
|
52
|
+
commands: string[];
|
|
53
|
+
permissions: string[];
|
|
54
|
+
exports: string[];
|
|
55
|
+
verified: boolean;
|
|
56
|
+
trustLevel: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface PluginRegistry {
|
|
60
|
+
version: string;
|
|
61
|
+
type: 'plugins';
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
ipnsName: string;
|
|
64
|
+
plugins: PluginEntry[];
|
|
65
|
+
categories: Array<{ id: string; name: string; description: string; pluginCount: number }>;
|
|
66
|
+
totalPlugins: number;
|
|
67
|
+
totalDownloads: number;
|
|
68
|
+
featured: string[];
|
|
69
|
+
trending: string[];
|
|
70
|
+
newest: string[];
|
|
71
|
+
official: string[];
|
|
72
|
+
registrySignature?: string;
|
|
73
|
+
registryPublicKey?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface PinataResponse {
|
|
77
|
+
IpfsHash: string;
|
|
78
|
+
PinSize: number;
|
|
79
|
+
Timestamp: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Configuration
|
|
83
|
+
const PINATA_API_URL = 'https://api.pinata.cloud';
|
|
84
|
+
const DEFAULT_REGISTRY_PATH = path.join(__dirname, '../src/plugins/store/registry.json');
|
|
85
|
+
|
|
86
|
+
// Parse command line arguments
|
|
87
|
+
const args = process.argv.slice(2);
|
|
88
|
+
const isDryRun = args.includes('--dry-run');
|
|
89
|
+
const registryPathArg = args.find(a => a.startsWith('--registry='));
|
|
90
|
+
const registryPath = registryPathArg
|
|
91
|
+
? registryPathArg.split('=')[1]
|
|
92
|
+
: DEFAULT_REGISTRY_PATH;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Fetch npm stats for a package
|
|
96
|
+
*/
|
|
97
|
+
async function fetchNpmStats(packageName: string): Promise<{ downloads: number; version: string } | null> {
|
|
98
|
+
try {
|
|
99
|
+
const downloadsUrl = `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`;
|
|
100
|
+
const downloadsRes = await fetch(downloadsUrl, { signal: AbortSignal.timeout(5000) });
|
|
101
|
+
|
|
102
|
+
if (!downloadsRes.ok) return null;
|
|
103
|
+
|
|
104
|
+
const downloadsData = await downloadsRes.json() as { downloads?: number };
|
|
105
|
+
|
|
106
|
+
const packageUrl = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
107
|
+
const packageRes = await fetch(packageUrl, { signal: AbortSignal.timeout(5000) });
|
|
108
|
+
|
|
109
|
+
let version = 'unknown';
|
|
110
|
+
if (packageRes.ok) {
|
|
111
|
+
const packageData = await packageRes.json() as { version?: string };
|
|
112
|
+
version = packageData.version || 'unknown';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
downloads: downloadsData.downloads || 0,
|
|
117
|
+
version,
|
|
118
|
+
};
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Sign registry with Ed25519
|
|
126
|
+
*/
|
|
127
|
+
async function signRegistry(registry: PluginRegistry, privateKeyHex: string): Promise<{
|
|
128
|
+
signature: string;
|
|
129
|
+
publicKey: string;
|
|
130
|
+
}> {
|
|
131
|
+
const ed = await import('@noble/ed25519');
|
|
132
|
+
|
|
133
|
+
const privateKey = Buffer.from(privateKeyHex, 'hex');
|
|
134
|
+
const publicKey = await ed.getPublicKeyAsync(privateKey);
|
|
135
|
+
|
|
136
|
+
// Create a copy without signature fields for signing
|
|
137
|
+
const registryToSign = { ...registry };
|
|
138
|
+
delete registryToSign.registrySignature;
|
|
139
|
+
delete registryToSign.registryPublicKey;
|
|
140
|
+
|
|
141
|
+
const message = JSON.stringify(registryToSign);
|
|
142
|
+
const signature = await ed.signAsync(
|
|
143
|
+
new TextEncoder().encode(message),
|
|
144
|
+
privateKey
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
signature: Buffer.from(signature).toString('hex'),
|
|
149
|
+
publicKey: `ed25519:${Buffer.from(publicKey).toString('hex')}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Pin JSON to IPFS via Pinata
|
|
155
|
+
*/
|
|
156
|
+
async function pinToIPFS(data: unknown, name: string, jwt: string): Promise<PinataResponse> {
|
|
157
|
+
const response = await fetch(`${PINATA_API_URL}/pinning/pinJSONToIPFS`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: {
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
'Authorization': `Bearer ${jwt}`,
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
pinataContent: data,
|
|
165
|
+
pinataMetadata: {
|
|
166
|
+
name,
|
|
167
|
+
keyvalues: {
|
|
168
|
+
type: 'plugin-registry',
|
|
169
|
+
publishedAt: new Date().toISOString(),
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
pinataOptions: {
|
|
173
|
+
cidVersion: 1,
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
const error = await response.text();
|
|
180
|
+
throw new Error(`Pinata error: ${response.status} - ${error}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return response.json() as Promise<PinataResponse>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generate a demo registry from npm packages
|
|
188
|
+
*/
|
|
189
|
+
async function generateRegistry(): Promise<PluginRegistry> {
|
|
190
|
+
console.log('š¦ Fetching npm stats for plugins...');
|
|
191
|
+
|
|
192
|
+
const officialPackages = [
|
|
193
|
+
'@monomind/plugin-agentic-qe',
|
|
194
|
+
'@monomind/plugin-prime-radiant',
|
|
195
|
+
'@monomind/plugin-gastown-bridge',
|
|
196
|
+
'@monomind/security',
|
|
197
|
+
'@monomind/claims',
|
|
198
|
+
'@monomind/embeddings',
|
|
199
|
+
'@monomind/neural',
|
|
200
|
+
'@monomind/performance',
|
|
201
|
+
'@monomind/plugins',
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const plugins: PluginEntry[] = [];
|
|
205
|
+
const now = new Date().toISOString();
|
|
206
|
+
|
|
207
|
+
for (const pkg of officialPackages) {
|
|
208
|
+
console.log(` Fetching ${pkg}...`);
|
|
209
|
+
const stats = await fetchNpmStats(pkg);
|
|
210
|
+
|
|
211
|
+
plugins.push({
|
|
212
|
+
id: pkg,
|
|
213
|
+
name: pkg,
|
|
214
|
+
displayName: pkg.replace('@monomind/plugin-', '').replace('@monomind/', ''),
|
|
215
|
+
description: `Official Monomind plugin: ${pkg}`,
|
|
216
|
+
version: stats?.version || '0.0.0',
|
|
217
|
+
size: 100000,
|
|
218
|
+
checksum: `sha256:${crypto.randomBytes(32).toString('hex')}`,
|
|
219
|
+
author: {
|
|
220
|
+
id: 'monomind-team',
|
|
221
|
+
displayName: 'Monomind Team',
|
|
222
|
+
verified: true,
|
|
223
|
+
},
|
|
224
|
+
license: 'MIT',
|
|
225
|
+
categories: ['official'],
|
|
226
|
+
tags: [pkg.split('/').pop() || ''],
|
|
227
|
+
downloads: stats?.downloads || 0,
|
|
228
|
+
rating: 0,
|
|
229
|
+
lastUpdated: now,
|
|
230
|
+
minMonomindVersion: '3.0.0',
|
|
231
|
+
type: 'integration',
|
|
232
|
+
hooks: [],
|
|
233
|
+
commands: [],
|
|
234
|
+
permissions: ['memory', 'filesystem'],
|
|
235
|
+
exports: [],
|
|
236
|
+
verified: true,
|
|
237
|
+
trustLevel: 'official',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const totalDownloads = plugins.reduce((sum, p) => sum + p.downloads, 0);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
version: '1.0.0',
|
|
245
|
+
type: 'plugins',
|
|
246
|
+
updatedAt: now,
|
|
247
|
+
ipnsName: '', // Will be set after publishing
|
|
248
|
+
plugins,
|
|
249
|
+
categories: [
|
|
250
|
+
{ id: 'official', name: 'Official', description: 'Official Monomind plugins', pluginCount: plugins.length },
|
|
251
|
+
],
|
|
252
|
+
totalPlugins: plugins.length,
|
|
253
|
+
totalDownloads,
|
|
254
|
+
featured: plugins.slice(0, 3).map(p => p.id),
|
|
255
|
+
trending: plugins.sort((a, b) => b.downloads - a.downloads).slice(0, 3).map(p => p.id),
|
|
256
|
+
newest: plugins.slice(-3).map(p => p.id),
|
|
257
|
+
official: plugins.map(p => p.id),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Main publish function
|
|
263
|
+
*/
|
|
264
|
+
async function main() {
|
|
265
|
+
console.log('š Plugin Registry Publisher\n');
|
|
266
|
+
|
|
267
|
+
// Check environment
|
|
268
|
+
const jwt = process.env.PINATA_JWT;
|
|
269
|
+
const privateKey = process.env.REGISTRY_PRIVATE_KEY;
|
|
270
|
+
|
|
271
|
+
if (!jwt) {
|
|
272
|
+
console.error('ā PINATA_JWT environment variable is required');
|
|
273
|
+
console.log('\nGet your JWT from https://pinata.cloud/keys');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Load or generate registry
|
|
278
|
+
let registry: PluginRegistry;
|
|
279
|
+
|
|
280
|
+
if (fs.existsSync(registryPath)) {
|
|
281
|
+
console.log(`š Loading registry from ${registryPath}`);
|
|
282
|
+
const content = fs.readFileSync(registryPath, 'utf-8');
|
|
283
|
+
registry = JSON.parse(content);
|
|
284
|
+
} else {
|
|
285
|
+
console.log('š Generating registry from npm packages...');
|
|
286
|
+
registry = await generateRegistry();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Update timestamp
|
|
290
|
+
registry.updatedAt = new Date().toISOString();
|
|
291
|
+
|
|
292
|
+
console.log(`\nš Registry Stats:`);
|
|
293
|
+
console.log(` Plugins: ${registry.plugins.length}`);
|
|
294
|
+
console.log(` Total Downloads: ${registry.totalDownloads.toLocaleString()}`);
|
|
295
|
+
console.log(` Updated: ${registry.updatedAt}`);
|
|
296
|
+
|
|
297
|
+
// Sign registry if private key is available
|
|
298
|
+
if (privateKey) {
|
|
299
|
+
console.log('\nš Signing registry with Ed25519...');
|
|
300
|
+
const { signature, publicKey } = await signRegistry(registry, privateKey);
|
|
301
|
+
registry.registrySignature = signature;
|
|
302
|
+
registry.registryPublicKey = publicKey;
|
|
303
|
+
console.log(` Public Key: ${publicKey.slice(0, 30)}...`);
|
|
304
|
+
} else {
|
|
305
|
+
console.log('\nā ļø No REGISTRY_PRIVATE_KEY set, skipping signature');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isDryRun) {
|
|
309
|
+
console.log('\nš Dry run - would publish:');
|
|
310
|
+
console.log(JSON.stringify(registry, null, 2).slice(0, 1000) + '...');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Pin to IPFS
|
|
315
|
+
console.log('\nš Pinning to IPFS via Pinata...');
|
|
316
|
+
try {
|
|
317
|
+
const result = await pinToIPFS(registry, 'monomind-plugin-registry', jwt);
|
|
318
|
+
|
|
319
|
+
console.log('\nā
Published successfully!');
|
|
320
|
+
console.log(` CID: ${result.IpfsHash}`);
|
|
321
|
+
console.log(` Size: ${(result.PinSize / 1024).toFixed(2)} KB`);
|
|
322
|
+
console.log(`\nš Gateway URLs:`);
|
|
323
|
+
console.log(` https://gateway.pinata.cloud/ipfs/${result.IpfsHash}`);
|
|
324
|
+
console.log(` https://ipfs.io/ipfs/${result.IpfsHash}`);
|
|
325
|
+
console.log(` https://cloudflare-ipfs.com/ipfs/${result.IpfsHash}`);
|
|
326
|
+
console.log(` https://dweb.link/ipfs/${result.IpfsHash}`);
|
|
327
|
+
|
|
328
|
+
// Save CID for reference
|
|
329
|
+
const cidFile = path.join(__dirname, '../.registry-cid');
|
|
330
|
+
fs.writeFileSync(cidFile, result.IpfsHash);
|
|
331
|
+
console.log(`\nš¾ CID saved to ${cidFile}`);
|
|
332
|
+
|
|
333
|
+
// Update discovery.ts config (manual step reminder)
|
|
334
|
+
console.log('\nš Next steps:');
|
|
335
|
+
console.log(' 1. Update DEFAULT_PLUGIN_STORE_CONFIG in discovery.ts with the new CID');
|
|
336
|
+
console.log(' 2. If using IPNS, update the IPNS pointer via Pinata dashboard');
|
|
337
|
+
console.log(' 3. Test with: npx monomind@latest plugins list');
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('\nā Publish failed:', error);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Run
|
|
345
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Publish script for @monomind/cli
|
|
3
|
+
# Publishes to both @monomind/cli@alpha AND monomind@alpha
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
CLI_DIR="$(dirname "$SCRIPT_DIR")"
|
|
9
|
+
|
|
10
|
+
cd "$CLI_DIR"
|
|
11
|
+
|
|
12
|
+
# Get current version
|
|
13
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
14
|
+
echo "Publishing version: $VERSION"
|
|
15
|
+
|
|
16
|
+
# 1. Publish @monomind/cli with alpha tag
|
|
17
|
+
echo ""
|
|
18
|
+
echo "=== Publishing @monomind/cli@$VERSION (alpha tag) ==="
|
|
19
|
+
npm publish --tag alpha
|
|
20
|
+
|
|
21
|
+
# 2. Publish to monomind with alpha tag
|
|
22
|
+
echo ""
|
|
23
|
+
echo "=== Publishing monomind@$VERSION (alpha tag) ==="
|
|
24
|
+
|
|
25
|
+
# Create temp directory
|
|
26
|
+
TEMP_DIR=$(mktemp -d)
|
|
27
|
+
trap "rm -rf $TEMP_DIR" EXIT
|
|
28
|
+
|
|
29
|
+
# Copy necessary files
|
|
30
|
+
cp -r dist bin src package.json README.md "$TEMP_DIR/"
|
|
31
|
+
|
|
32
|
+
# Change package name to unscoped
|
|
33
|
+
cd "$TEMP_DIR"
|
|
34
|
+
sed -i 's/"name": "@monomind\/cli"/"name": "monomind"/' package.json
|
|
35
|
+
|
|
36
|
+
# Publish with alpha tag
|
|
37
|
+
npm publish --tag alpha
|
|
38
|
+
|
|
39
|
+
echo ""
|
|
40
|
+
echo "=== Updating dist-tags ==="
|
|
41
|
+
|
|
42
|
+
# Update all tags to point to the new version
|
|
43
|
+
npm dist-tag add @monomind/cli@$VERSION alpha
|
|
44
|
+
npm dist-tag add @monomind/cli@$VERSION latest
|
|
45
|
+
npm dist-tag add monomind@$VERSION alpha
|
|
46
|
+
npm dist-tag add monomind@$VERSION latest
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
echo "=== Published successfully ==="
|
|
50
|
+
echo " @monomind/cli@$VERSION (alpha, latest)"
|
|
51
|
+
echo " monomind@$VERSION (alpha, latest)"
|
|
52
|
+
echo ""
|
|
53
|
+
echo "Install with:"
|
|
54
|
+
echo " npx monomind@alpha"
|
|
55
|
+
echo " npx @monomind/cli@latest"
|