openpets 1.0.8 → 1.0.10

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 (46) hide show
  1. package/dist/data/api.json +6746 -4404
  2. package/dist/src/core/cli.js +1160 -36
  3. package/dist/src/core/cli.js.map +1 -1
  4. package/dist/src/core/discovery.d.ts +130 -0
  5. package/dist/src/core/discovery.d.ts.map +1 -0
  6. package/dist/src/core/discovery.js +327 -0
  7. package/dist/src/core/discovery.js.map +1 -0
  8. package/dist/src/core/index.d.ts +5 -5
  9. package/dist/src/core/index.d.ts.map +1 -1
  10. package/dist/src/core/index.js +7 -5
  11. package/dist/src/core/index.js.map +1 -1
  12. package/dist/src/core/mautrix-bridge.d.ts +17 -0
  13. package/dist/src/core/mautrix-bridge.d.ts.map +1 -1
  14. package/dist/src/core/mautrix-bridge.js +257 -0
  15. package/dist/src/core/mautrix-bridge.js.map +1 -1
  16. package/dist/src/core/pet-config.d.ts +611 -0
  17. package/dist/src/core/pet-config.d.ts.map +1 -0
  18. package/dist/src/core/pet-config.js +281 -0
  19. package/dist/src/core/pet-config.js.map +1 -0
  20. package/dist/src/core/pet-downloader.d.ts +50 -0
  21. package/dist/src/core/pet-downloader.d.ts.map +1 -0
  22. package/dist/src/core/pet-downloader.js +298 -0
  23. package/dist/src/core/pet-downloader.js.map +1 -0
  24. package/dist/src/core/pet-scanner.d.ts +14 -0
  25. package/dist/src/core/pet-scanner.d.ts.map +1 -0
  26. package/dist/src/core/pet-scanner.js +87 -0
  27. package/dist/src/core/pet-scanner.js.map +1 -0
  28. package/dist/src/core/registry-client.d.ts +97 -0
  29. package/dist/src/core/registry-client.d.ts.map +1 -0
  30. package/dist/src/core/registry-client.js +283 -0
  31. package/dist/src/core/registry-client.js.map +1 -0
  32. package/dist/src/core/search-pets.d.ts.map +1 -1
  33. package/dist/src/core/search-pets.js +31 -1
  34. package/dist/src/core/search-pets.js.map +1 -1
  35. package/dist/src/core/validate-pet.d.ts.map +1 -1
  36. package/dist/src/core/validate-pet.js +17 -19
  37. package/dist/src/core/validate-pet.js.map +1 -1
  38. package/dist/src/sdk/logger.d.ts +32 -0
  39. package/dist/src/sdk/logger.d.ts.map +1 -0
  40. package/dist/src/sdk/logger.js +119 -0
  41. package/dist/src/sdk/logger.js.map +1 -0
  42. package/dist/src/sdk/plugin-factory.d.ts +104 -0
  43. package/dist/src/sdk/plugin-factory.d.ts.map +1 -0
  44. package/dist/src/sdk/plugin-factory.js +540 -0
  45. package/dist/src/sdk/plugin-factory.js.map +1 -0
  46. package/package.json +3 -3
@@ -2,21 +2,30 @@
2
2
  import { buildPet } from './build-pet.js';
3
3
  import { deployPet } from './deploy-pet.js';
4
4
  import { addFolderToHistory } from './config-manager.js';
5
+ import { getRegistryClient } from './registry-client.js';
6
+ import { loadPetConfig, validatePetConfig, generateOpenpetsYamlTemplate } from './pet-config.js';
7
+ import { getPackageDiscovery, getRegistryAggregator } from './discovery.js';
8
+ import { getPetDownloader } from './pet-downloader.js';
9
+ import { getAllPets } from './search-pets.js';
10
+ import { syncEnvToConfig } from '../sdk/plugin-factory.js';
5
11
  import { spawn } from 'child_process';
6
- import { resolve } from 'path';
7
- import { readFileSync, existsSync } from 'fs';
12
+ import { resolve, join } from 'path';
13
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
14
+ import * as readline from 'readline';
8
15
  async function publishPet(petName, options = {}) {
9
16
  const { preview = false, channel = 'latest' } = options;
10
17
  // Find the script path - try multiple locations
11
- let scriptPath = resolve(__dirname, '../../scripts/publish-pet.ts');
12
- if (!existsSync(scriptPath)) {
13
- // We might be in a different location, try relative to cwd
14
- scriptPath = resolve(process.cwd(), '../../scripts/publish-pet.ts');
15
- }
16
- if (!existsSync(scriptPath)) {
17
- console.error(`❌ Publish script not found: ${scriptPath}`);
18
- console.error(` Tried: ${resolve(__dirname, '../../scripts/publish-pet.ts')}`);
19
- console.error(` And: ${resolve(process.cwd(), '../../scripts/publish-pet.ts')}`);
18
+ const possiblePaths = [
19
+ resolve(__dirname, '../../scripts/publish-pet.ts'),
20
+ resolve(__dirname, '../../../scripts/publish-pet.ts'),
21
+ resolve(process.cwd(), 'scripts/publish-pet.ts'),
22
+ resolve(process.cwd(), '../../scripts/publish-pet.ts')
23
+ ];
24
+ let scriptPath = possiblePaths.find(p => existsSync(p));
25
+ if (!scriptPath) {
26
+ console.error(`❌ Publish script not found`);
27
+ console.error(` Tried:`);
28
+ possiblePaths.forEach(p => console.error(` - ${p}`));
20
29
  process.exit(1);
21
30
  }
22
31
  // If no petName provided, try to infer from current directory
@@ -121,6 +130,14 @@ function launchManager() {
121
130
  const uiDir = resolve(__dirname, '../../apps/desktop');
122
131
  const projectDir = process.cwd();
123
132
  addFolderToHistory(projectDir);
133
+ // Sync .env to .pets/config.json before launching
134
+ // This ensures .pets/config.json is the single source of truth for env vars
135
+ console.log('Syncing environment configuration...');
136
+ const syncResult = syncEnvToConfig(projectDir);
137
+ if (syncResult.success && syncResult.synced > 0) {
138
+ console.log(`✅ Synced ${syncResult.synced} env vars to .pets/config.json`);
139
+ }
140
+ log('Env sync result:', syncResult);
124
141
  console.log('Launching OpenPets Plugin Manager...');
125
142
  console.log(`Project directory: ${projectDir}`);
126
143
  console.log(`UI directory: ${uiDir}`);
@@ -143,8 +160,8 @@ function launchManager() {
143
160
  process.exit(1);
144
161
  }
145
162
  killPort(1420);
146
- log('Spawning npm process with tauri:dev');
147
- const child = spawn('npm', ['run', 'tauri:dev'], {
163
+ log('Spawning bun process with tauri:dev');
164
+ const child = spawn('bun', ['run', 'tauri:dev'], {
148
165
  cwd: uiDir,
149
166
  stdio: 'inherit',
150
167
  shell: true,
@@ -210,35 +227,1142 @@ else if (command === 'publish') {
210
227
  process.exit(1);
211
228
  });
212
229
  }
230
+ else if (command === 'install') {
231
+ handleInstall(args.slice(1));
232
+ }
233
+ else if (command === 'add') {
234
+ handleAdd(args.slice(1));
235
+ }
236
+ else if (command === 'uninstall' || command === 'remove' || command === 'delete') {
237
+ handleUninstall(args.slice(1));
238
+ }
239
+ else if (command === 'inspect') {
240
+ handleInspect(args.slice(1));
241
+ }
242
+ else if (command === 'search') {
243
+ handleSearch(args.slice(1));
244
+ }
245
+ else if (command === 'list') {
246
+ handleList(args.slice(1));
247
+ }
248
+ else if (command === 'run') {
249
+ handleRun(args.slice(1));
250
+ }
251
+ else if (command === 'init') {
252
+ handleInit(args.slice(1));
253
+ }
254
+ else if (command === 'validate') {
255
+ handleValidate(args.slice(1));
256
+ }
257
+ else if (command === 'playground') {
258
+ handlePlayground();
259
+ }
260
+ else if (command === 'discover') {
261
+ handleDiscover(args.slice(1));
262
+ }
263
+ else if (command === 'registry') {
264
+ handleRegistry(args.slice(1));
265
+ }
266
+ else if (command === 'prune') {
267
+ handlePrune(args.slice(1));
268
+ }
269
+ else if (command === 'reload') {
270
+ handleReload(args.slice(1));
271
+ }
272
+ else if (command === 'help' || command === '--help' || command === '-h') {
273
+ showHelp();
274
+ }
213
275
  else if (!command) {
214
276
  log('No command provided, launching manager UI');
215
277
  launchManager();
216
278
  }
217
279
  else {
218
280
  log('Unknown command:', command);
219
- console.error('Usage: pets [command] [options]');
220
- console.error('');
221
- console.error('Commands:');
222
- console.error(' (none) Launch the plugin manager UI (default)');
223
- console.error(' build <pet-name> Build and validate a pet package');
224
- console.error(' deploy <pet-name> Build and deploy a pet package with metadata');
225
- console.error(' publish [pet-name] Publish a pet package to npm (auto-detects if in pet dir)');
226
- console.error('');
227
- console.error('Publish Options:');
228
- console.error(' --preview Dry-run mode (no actual publish)');
229
- console.error(' --channel <name> Publish to a specific npm tag (default: latest)');
230
- console.error('');
231
- console.error('Examples:');
232
- console.error(' pets # Launch the desktop plugin manager');
233
- console.error(' pets build postgres');
234
- console.error(' pets deploy maps');
235
- console.error(' pets publish maps # From root directory');
236
- console.error(' cd pets/maps && pets publish # From pet directory');
237
- console.error(' pets publish --preview # Dry-run from pet directory');
238
- console.error(' pets publish maps --channel beta');
239
- console.error('');
240
- console.error('Environment:');
241
- console.error(' PETS_DEBUG=true Enable detailed debug logging');
281
+ showUnknownCommandError(command);
242
282
  process.exit(1);
243
283
  }
284
+ async function handleInstall(args) {
285
+ const packageName = args.find(a => !a.startsWith('--'));
286
+ if (!packageName) {
287
+ console.error('❌ Package name required');
288
+ console.error('Usage: pets install <package-name> --client <client>');
289
+ process.exit(1);
290
+ }
291
+ const clientIndex = args.indexOf('--client');
292
+ const client = (clientIndex >= 0 ? args[clientIndex + 1] : 'opencode');
293
+ const configIndex = args.indexOf('--config');
294
+ const configStr = configIndex >= 0 ? args[configIndex + 1] : undefined;
295
+ const config = configStr ? JSON.parse(configStr) : undefined;
296
+ const global = args.includes('--global') || args.includes('-g');
297
+ const versionIndex = args.indexOf('--version');
298
+ const version = versionIndex >= 0 ? args[versionIndex + 1] : undefined;
299
+ console.log(`📦 Installing ${packageName}...`);
300
+ log('Install options:', { client, config, global, version });
301
+ const registry = getRegistryClient();
302
+ const result = await registry.install(packageName, { client, config, global, version });
303
+ if (result.success) {
304
+ console.log(`✅ ${result.message}`);
305
+ if (result.path) {
306
+ console.log(` 📁 Installed to: ${result.path}`);
307
+ }
308
+ console.log(`\n💡 Remember to restart your AI client to apply changes.`);
309
+ }
310
+ else {
311
+ console.error(`❌ ${result.message}`);
312
+ process.exit(1);
313
+ }
314
+ }
315
+ async function handleUninstall(args) {
316
+ const packageName = args.find(a => !a.startsWith('--'));
317
+ if (!packageName) {
318
+ console.error('❌ Package name required');
319
+ console.error('Usage: pets uninstall <package-name> --client <client>');
320
+ process.exit(1);
321
+ }
322
+ const clientIndex = args.indexOf('--client');
323
+ const client = (clientIndex >= 0 ? args[clientIndex + 1] : 'opencode');
324
+ const global = args.includes('--global') || args.includes('-g');
325
+ console.log(`🗑️ Uninstalling ${packageName}...`);
326
+ const registry = getRegistryClient();
327
+ const result = await registry.uninstall(packageName, { client, global });
328
+ if (result.success) {
329
+ console.log(`✅ ${result.message}`);
330
+ console.log(`\n💡 Remember to restart your AI client to apply changes.`);
331
+ }
332
+ else {
333
+ console.error(`❌ ${result.message}`);
334
+ process.exit(1);
335
+ }
336
+ }
337
+ async function handleInspect(args) {
338
+ const packageName = args.find(a => !a.startsWith('--'));
339
+ if (!packageName) {
340
+ console.error('❌ Package name required');
341
+ console.error('Usage: pets inspect <package-name>');
342
+ process.exit(1);
343
+ }
344
+ console.log(`🔍 Inspecting ${packageName}...`);
345
+ const registry = getRegistryClient();
346
+ const { package: pkg, installed, versions } = await registry.inspect(packageName);
347
+ if (!pkg) {
348
+ console.error(`❌ Package not found: ${packageName}`);
349
+ process.exit(1);
350
+ }
351
+ console.log('');
352
+ console.log(`📦 ${pkg.name}`);
353
+ console.log(` Version: ${pkg.version}`);
354
+ console.log(` Description: ${pkg.description || 'No description'}`);
355
+ if (pkg.keywords && pkg.keywords.length > 0) {
356
+ console.log(` Keywords: ${pkg.keywords.join(', ')}`);
357
+ }
358
+ if (pkg.homepage) {
359
+ console.log(` Homepage: ${pkg.homepage}`);
360
+ }
361
+ if (pkg.repository) {
362
+ const repoUrl = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository.url;
363
+ console.log(` Repository: ${repoUrl}`);
364
+ }
365
+ if (pkg.author) {
366
+ const authorStr = typeof pkg.author === 'string' ? pkg.author : pkg.author.name;
367
+ console.log(` Author: ${authorStr}`);
368
+ }
369
+ console.log('');
370
+ if (installed) {
371
+ console.log(`✅ Installed locally`);
372
+ console.log(` Version: ${installed.version}`);
373
+ console.log(` Path: ${installed.path}`);
374
+ console.log(` Installed: ${new Date(installed.installedAt).toLocaleString()}`);
375
+ }
376
+ else {
377
+ console.log(`❌ Not installed locally`);
378
+ }
379
+ if (versions.length > 0) {
380
+ console.log('');
381
+ console.log(`📋 Available versions: ${versions.slice(0, 5).join(', ')}${versions.length > 5 ? '...' : ''}`);
382
+ }
383
+ }
384
+ async function handleSearch(args) {
385
+ const query = args.filter(a => !a.startsWith('--')).join(' ');
386
+ const limitIndex = args.indexOf('--limit');
387
+ const limit = limitIndex >= 0 ? parseInt(args[limitIndex + 1], 10) : 20;
388
+ console.log(query ? `🔍 Searching for "${query}"...` : '🔍 Listing all @openpets packages...');
389
+ const registry = getRegistryClient();
390
+ const results = await registry.search(query, limit);
391
+ if (results.objects.length === 0) {
392
+ console.log('No packages found.');
393
+ return;
394
+ }
395
+ console.log(`\nFound ${results.total} packages:\n`);
396
+ for (const { package: pkg, score } of results.objects) {
397
+ const shortName = pkg.name.replace('@openpets/', '');
398
+ console.log(` 📦 ${shortName}`);
399
+ console.log(` ${pkg.description || 'No description'}`);
400
+ console.log(` v${pkg.version} | Score: ${(score.final * 100).toFixed(0)}%`);
401
+ console.log('');
402
+ }
403
+ console.log(`💡 Use "pets install <name>" to install a package`);
404
+ console.log(`💡 Use "pets inspect <name>" to see more details`);
405
+ }
406
+ async function handleList(args) {
407
+ const listType = args[0] || 'installed';
408
+ const registry = getRegistryClient();
409
+ if (listType === 'clients') {
410
+ console.log('📋 Supported clients:\n');
411
+ const clients = registry.listClients();
412
+ for (const client of clients) {
413
+ const configPath = registry.getClientConfigPath(client);
414
+ const exists = configPath && existsSync(configPath);
415
+ console.log(` ${exists ? '✅' : '❌'} ${client}`);
416
+ if (configPath && configPath !== 'custom') {
417
+ console.log(` ${configPath}`);
418
+ }
419
+ }
420
+ return;
421
+ }
422
+ if (listType === 'servers' || listType === 'installed') {
423
+ const clientIndex = args.indexOf('--client');
424
+ const client = (clientIndex >= 0 ? args[clientIndex + 1] : undefined);
425
+ console.log('📋 Installed pets:\n');
426
+ const opencodeJsonPath = resolve(process.cwd(), 'opencode.json');
427
+ const petsConfigPath = resolve(process.cwd(), '.pets', 'config.json');
428
+ let localPlugins = [];
429
+ let disabledPets = [];
430
+ if (existsSync(opencodeJsonPath)) {
431
+ try {
432
+ const config = JSON.parse(readFileSync(opencodeJsonPath, 'utf-8'));
433
+ if (config.plugin) {
434
+ localPlugins = Array.isArray(config.plugin) ? config.plugin : [config.plugin];
435
+ }
436
+ }
437
+ catch (e) {
438
+ log('Error reading opencode.json:', e);
439
+ }
440
+ }
441
+ if (existsSync(petsConfigPath)) {
442
+ try {
443
+ const petsConfig = JSON.parse(readFileSync(petsConfigPath, 'utf-8'));
444
+ if (petsConfig.disabled && Array.isArray(petsConfig.disabled)) {
445
+ disabledPets = petsConfig.disabled;
446
+ }
447
+ }
448
+ catch (e) {
449
+ log('Error reading .pets/config.json:', e);
450
+ }
451
+ }
452
+ let remoteApiData = null;
453
+ const openpetsPlugins = localPlugins.filter(p => p.startsWith('@openpets/'));
454
+ const allPetsToLookup = [...openpetsPlugins, ...disabledPets.map(p => `@openpets/${p}`)];
455
+ if (allPetsToLookup.length > 0) {
456
+ try {
457
+ const allPets = await getAllPets();
458
+ remoteApiData = new Map();
459
+ for (const pet of allPets) {
460
+ remoteApiData.set(pet.id, pet);
461
+ remoteApiData.set(`@openpets/${pet.id}`, pet);
462
+ }
463
+ }
464
+ catch (e) {
465
+ log('Failed to fetch remote API data:', e);
466
+ }
467
+ }
468
+ if (localPlugins.length > 0) {
469
+ console.log(' 📁 Local plugins (from opencode.json):\n');
470
+ for (const plugin of localPlugins) {
471
+ const isOpenpetsPackage = plugin.startsWith('@openpets/');
472
+ if (isOpenpetsPackage) {
473
+ const shortName = plugin.replace('@openpets/', '');
474
+ const remotePet = remoteApiData?.get(shortName) || remoteApiData?.get(plugin);
475
+ if (remotePet) {
476
+ console.log(` ✅ ${remotePet.displayName || remotePet.name}`);
477
+ if (remotePet.description) {
478
+ console.log(` ${remotePet.description}`);
479
+ }
480
+ if (remotePet.version) {
481
+ console.log(` v${remotePet.version}`);
482
+ }
483
+ if (remotePet.tools && remotePet.tools.length > 0) {
484
+ console.log(` 🔧 ${remotePet.tools.length} tools`);
485
+ }
486
+ console.log(` 📦 ${plugin}`);
487
+ }
488
+ else {
489
+ console.log(` ❓ ${plugin}`);
490
+ console.log(` (not found in registry)`);
491
+ }
492
+ console.log('');
493
+ continue;
494
+ }
495
+ const pluginPath = resolve(process.cwd(), plugin);
496
+ const exists = existsSync(pluginPath) || existsSync(pluginPath.replace('.ts', ''));
497
+ const status = exists ? '✅' : '❌';
498
+ let pkgInfo = { name: plugin, description: '', version: '' };
499
+ const pluginDir = pluginPath.replace(/\/index\.ts$/, '').replace(/\.ts$/, '');
500
+ const pkgJsonPath = join(pluginDir, 'package.json');
501
+ if (existsSync(pkgJsonPath)) {
502
+ try {
503
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
504
+ pkgInfo = {
505
+ name: pkg.title || pkg.name || plugin,
506
+ description: pkg.subtitle || pkg.description || '',
507
+ version: pkg.version || ''
508
+ };
509
+ }
510
+ catch (e) { }
511
+ }
512
+ console.log(` ${status} ${pkgInfo.name}`);
513
+ if (pkgInfo.description) {
514
+ console.log(` ${pkgInfo.description}`);
515
+ }
516
+ if (pkgInfo.version) {
517
+ console.log(` v${pkgInfo.version}`);
518
+ }
519
+ console.log(` 📁 ${plugin}`);
520
+ console.log('');
521
+ }
522
+ }
523
+ if (disabledPets.length > 0) {
524
+ console.log(' 🚫 Disabled pets (from .pets/config.json):\n');
525
+ for (const pet of disabledPets) {
526
+ const remotePet = remoteApiData?.get(pet) || remoteApiData?.get(`@openpets/${pet}`);
527
+ if (remotePet) {
528
+ console.log(` ❌ ${remotePet.displayName || remotePet.name}`);
529
+ if (remotePet.description) {
530
+ console.log(` ${remotePet.description}`);
531
+ }
532
+ console.log(` 📦 @openpets/${pet}`);
533
+ }
534
+ else {
535
+ console.log(` ❌ ${pet}`);
536
+ }
537
+ console.log('');
538
+ }
539
+ }
540
+ const installed = registry.getInstalledPets();
541
+ if (installed.length > 0) {
542
+ console.log(' 📦 Registry-installed pets:\n');
543
+ for (const pet of installed) {
544
+ const shortName = pet.name.replace('@openpets/', '');
545
+ console.log(` 📦 ${shortName}`);
546
+ console.log(` Version: ${pet.version}`);
547
+ console.log(` Installed: ${new Date(pet.installedAt).toLocaleDateString()}`);
548
+ console.log('');
549
+ }
550
+ }
551
+ if (localPlugins.length === 0 && installed.length === 0 && disabledPets.length === 0) {
552
+ console.log(' No pets installed yet.');
553
+ console.log(' Use "pets search" to find packages');
554
+ console.log(' Use "pets install <name>" to install');
555
+ }
556
+ return;
557
+ }
558
+ console.error(`Unknown list type: ${listType}`);
559
+ console.log('Usage: pets list [clients|servers|installed]');
560
+ }
561
+ async function handleRun(args) {
562
+ const packageName = args.find(a => !a.startsWith('--'));
563
+ if (!packageName) {
564
+ console.error('❌ Package name required');
565
+ console.error('Usage: pets run <package-name> [--config \'{"key":"value"}\']');
566
+ process.exit(1);
567
+ }
568
+ const configIndex = args.indexOf('--config');
569
+ const configStr = configIndex >= 0 ? args[configIndex + 1] : undefined;
570
+ const config = configStr ? JSON.parse(configStr) : undefined;
571
+ const transportIndex = args.indexOf('--transport');
572
+ const transport = (transportIndex >= 0 ? args[transportIndex + 1] : 'stdio');
573
+ console.log(`🚀 Running ${packageName}...`);
574
+ const registry = getRegistryClient();
575
+ await registry.run(packageName, { config, transport });
576
+ }
577
+ async function handleInit(args) {
578
+ const name = args.find(a => !a.startsWith('--'));
579
+ const targetDir = args.includes('--dir')
580
+ ? args[args.indexOf('--dir') + 1]
581
+ : process.cwd();
582
+ const format = args.includes('--yaml') ? 'yaml' : 'json';
583
+ const runtimeIndex = args.indexOf('--runtime');
584
+ const runtime = (runtimeIndex >= 0 ? args[runtimeIndex + 1] : 'typescript');
585
+ const petName = name || resolve(targetDir).split('/').pop() || 'my-pet';
586
+ console.log(`🐾 Initializing new pet: ${petName}`);
587
+ if (format === 'yaml') {
588
+ const yamlContent = generateOpenpetsYamlTemplate({
589
+ name: petName,
590
+ runtime
591
+ });
592
+ const yamlPath = join(targetDir, 'openpets.yaml');
593
+ writeFileSync(yamlPath, yamlContent);
594
+ console.log(`✅ Created openpets.yaml`);
595
+ }
596
+ else {
597
+ const config = {
598
+ $schema: 'https://pets.studio/config.json',
599
+ name: `@openpets/${petName}`,
600
+ version: '1.0.0',
601
+ title: petName.charAt(0).toUpperCase() + petName.slice(1),
602
+ subtitle: `${petName} plugin`,
603
+ description: `${petName} plugin for OpenPets`,
604
+ categories: ['general'],
605
+ keywords: ['openpets', 'plugin', petName],
606
+ main: 'index.ts',
607
+ types: 'index.ts',
608
+ envVariables: {},
609
+ queries: ['example query'],
610
+ scenarios: {
611
+ 'basic-test': ['example query']
612
+ },
613
+ scripts: {
614
+ build: 'pets build',
615
+ test: 'pets validate'
616
+ },
617
+ dependencies: {
618
+ openpets: 'workspace:*'
619
+ }
620
+ };
621
+ const jsonPath = join(targetDir, 'package.json');
622
+ writeFileSync(jsonPath, JSON.stringify(config, null, 2));
623
+ console.log(`✅ Created package.json`);
624
+ }
625
+ const opencodeConfig = {
626
+ $schema: 'https://opencode.ai/config.json',
627
+ plugin: ['./index.ts']
628
+ };
629
+ const opencodePath = join(targetDir, 'opencode.json');
630
+ writeFileSync(opencodePath, JSON.stringify(opencodeConfig, null, 2));
631
+ console.log(`✅ Created opencode.json`);
632
+ const indexPath = join(targetDir, 'index.ts');
633
+ if (!existsSync(indexPath)) {
634
+ const indexContent = `import { createPlugin } from 'openpets'
635
+
636
+ export default createPlugin({
637
+ name: '${petName}',
638
+ description: '${petName} plugin',
639
+ tools: [
640
+ {
641
+ name: '${petName}-hello',
642
+ description: 'Say hello from ${petName}',
643
+ schema: {
644
+ type: 'object',
645
+ properties: {
646
+ name: { type: 'string', description: 'Name to greet' }
647
+ }
648
+ },
649
+ execute: async ({ name = 'World' }) => {
650
+ return { message: \`Hello, \${name}! From ${petName}.\` }
651
+ }
652
+ }
653
+ ]
654
+ })
655
+ `;
656
+ writeFileSync(indexPath, indexContent);
657
+ console.log(`✅ Created index.ts`);
658
+ }
659
+ console.log(`
660
+ 🎉 Pet initialized! Next steps:
661
+
662
+ 1. Edit index.ts to add your tools
663
+ 2. Run "pets validate" to check your configuration
664
+ 3. Run "pets build" to build your pet
665
+ 4. Run "pets publish" to publish to npm
666
+ `);
667
+ }
668
+ async function handleValidate(args) {
669
+ const targetDir = args.find(a => !a.startsWith('--')) || process.cwd();
670
+ console.log(`🔍 Validating pet configuration in ${targetDir}...`);
671
+ const config = loadPetConfig(targetDir);
672
+ if (!config) {
673
+ console.error('❌ Could not load configuration');
674
+ process.exit(1);
675
+ }
676
+ const result = validatePetConfig(config);
677
+ if (result.valid) {
678
+ console.log('✅ Configuration is valid');
679
+ }
680
+ else {
681
+ console.log('❌ Configuration has errors:');
682
+ result.errors.forEach(err => console.log(` ❌ ${err}`));
683
+ }
684
+ if (result.warnings.length > 0) {
685
+ console.log('\n⚠️ Warnings:');
686
+ result.warnings.forEach(warn => console.log(` ⚠️ ${warn}`));
687
+ }
688
+ console.log(`\n📦 Pet: ${config.name}`);
689
+ console.log(` Version: ${config.version}`);
690
+ console.log(` Description: ${config.description}`);
691
+ if (!result.valid) {
692
+ process.exit(1);
693
+ }
694
+ }
695
+ function handlePlayground() {
696
+ console.log('🎮 Opening OpenPets Playground...');
697
+ const playgroundUrl = 'https://pets.studio/playground';
698
+ const openCommand = process.platform === 'darwin' ? 'open' :
699
+ process.platform === 'win32' ? 'start' : 'xdg-open';
700
+ spawn(openCommand, [playgroundUrl], { stdio: 'ignore', detached: true }).unref();
701
+ console.log(`\n📌 Playground URL: ${playgroundUrl}`);
702
+ }
703
+ async function handleDiscover(args) {
704
+ const includeGithub = args.includes('--github');
705
+ const npmOnly = args.includes('--npm-only');
706
+ const limitIndex = args.indexOf('--limit');
707
+ const limit = limitIndex >= 0 ? parseInt(args[limitIndex + 1], 10) : 50;
708
+ const githubToken = process.env.GITHUB_TOKEN;
709
+ console.log('🔍 Discovering OpenPets-compatible packages...\n');
710
+ if (includeGithub && !githubToken) {
711
+ console.log('⚠️ GitHub discovery requires GITHUB_TOKEN environment variable');
712
+ console.log(' Set it to search for packages with $schema field in package.json\n');
713
+ }
714
+ const discovery = getPackageDiscovery();
715
+ try {
716
+ const packages = await discovery.discoverAll({
717
+ includeNpm: true,
718
+ includeGithub: includeGithub && !!githubToken,
719
+ githubToken,
720
+ limit
721
+ });
722
+ if (packages.length === 0) {
723
+ console.log('No packages found.');
724
+ return;
725
+ }
726
+ console.log(`Found ${packages.length} compatible packages:\n`);
727
+ const npmPackages = packages.filter(p => p.source === 'npm');
728
+ const githubPackages = packages.filter(p => p.source === 'github');
729
+ if (npmPackages.length > 0) {
730
+ console.log('📦 From npm registry:');
731
+ console.log('─'.repeat(50));
732
+ for (const pkg of npmPackages.slice(0, 20)) {
733
+ const shortName = pkg.name.replace('@openpets/', '');
734
+ const score = pkg.score ? ` (score: ${(pkg.score * 100).toFixed(0)}%)` : '';
735
+ console.log(` ${shortName}${score}`);
736
+ console.log(` ${pkg.description || 'No description'}`);
737
+ console.log(` v${pkg.version}`);
738
+ console.log('');
739
+ }
740
+ if (npmPackages.length > 20) {
741
+ console.log(` ... and ${npmPackages.length - 20} more\n`);
742
+ }
743
+ }
744
+ if (githubPackages.length > 0) {
745
+ console.log('🐙 From GitHub repositories:');
746
+ console.log('─'.repeat(50));
747
+ for (const pkg of githubPackages.slice(0, 10)) {
748
+ const stars = pkg.stars ? ` ⭐ ${pkg.stars}` : '';
749
+ console.log(` ${pkg.name}${stars}`);
750
+ console.log(` ${pkg.description || 'No description'}`);
751
+ if (pkg.repository) {
752
+ console.log(` ${pkg.repository}`);
753
+ }
754
+ console.log('');
755
+ }
756
+ if (githubPackages.length > 10) {
757
+ console.log(` ... and ${githubPackages.length - 10} more\n`);
758
+ }
759
+ }
760
+ console.log('─'.repeat(50));
761
+ console.log(`\n💡 Discovery methods used:`);
762
+ console.log(` • npm: Search @openpets/* scope and "openpets" keyword`);
763
+ if (includeGithub && githubToken) {
764
+ console.log(` • GitHub: Search for $schema field pointing to pets.studio`);
765
+ }
766
+ console.log(`\n💡 To include GitHub results: pets discover --github`);
767
+ console.log(` (requires GITHUB_TOKEN environment variable)`);
768
+ }
769
+ catch (error) {
770
+ console.error(`❌ Discovery failed: ${error.message}`);
771
+ process.exit(1);
772
+ }
773
+ }
774
+ async function handleRegistry(args) {
775
+ const subcommand = args[0] || 'status';
776
+ const aggregator = getRegistryAggregator();
777
+ if (subcommand === 'update' || subcommand === 'refresh') {
778
+ console.log('🔄 Updating package registry...');
779
+ const githubToken = process.env.GITHUB_TOKEN;
780
+ await aggregator.updateRegistry({
781
+ includeNpm: true,
782
+ includeGithub: !!githubToken,
783
+ githubToken
784
+ });
785
+ const packages = await aggregator.getRegistry();
786
+ console.log(`✅ Registry updated: ${packages.length} packages indexed`);
787
+ return;
788
+ }
789
+ if (subcommand === 'export') {
790
+ const outputPath = args[1] || 'registry.json';
791
+ const json = await aggregator.exportRegistry();
792
+ writeFileSync(outputPath, json);
793
+ console.log(`✅ Registry exported to ${outputPath}`);
794
+ return;
795
+ }
796
+ if (subcommand === 'import') {
797
+ const inputPath = args[1];
798
+ if (!inputPath || !existsSync(inputPath)) {
799
+ console.error('❌ Input file required');
800
+ console.error('Usage: pets registry import <file.json>');
801
+ process.exit(1);
802
+ }
803
+ const json = readFileSync(inputPath, 'utf-8');
804
+ await aggregator.importRegistry(json);
805
+ console.log(`✅ Registry imported from ${inputPath}`);
806
+ return;
807
+ }
808
+ if (subcommand === 'status') {
809
+ const packages = await aggregator.getRegistry();
810
+ console.log('📊 Registry Status\n');
811
+ console.log(`Total packages: ${packages.length}`);
812
+ const npmCount = packages.filter(p => p.source === 'npm').length;
813
+ const githubCount = packages.filter(p => p.source === 'github').length;
814
+ console.log(` • npm packages: ${npmCount}`);
815
+ console.log(` • GitHub packages: ${githubCount}`);
816
+ const withSchema = packages.filter(p => p.schemaVersion).length;
817
+ console.log(` • With $schema field: ${withSchema}`);
818
+ console.log(`\n💡 Run "pets registry update" to refresh the registry`);
819
+ return;
820
+ }
821
+ console.error(`Unknown registry subcommand: ${subcommand}`);
822
+ console.log('Usage: pets registry [status|update|export|import]');
823
+ }
824
+ function ensureOpencodeJson(targetDir = process.cwd()) {
825
+ const opencodeJsonPath = resolve(targetDir, 'opencode.json');
826
+ const opencodeJsoncPath = resolve(targetDir, 'opencode.jsonc');
827
+ if (existsSync(opencodeJsonPath)) {
828
+ return opencodeJsonPath;
829
+ }
830
+ if (existsSync(opencodeJsoncPath)) {
831
+ return opencodeJsoncPath;
832
+ }
833
+ const defaultConfig = {
834
+ "$schema": "https://opencode.ai/config.json",
835
+ "plugin": []
836
+ };
837
+ writeFileSync(opencodeJsonPath, JSON.stringify(defaultConfig, null, 2));
838
+ console.log('✅ Created opencode.json');
839
+ return opencodeJsonPath;
840
+ }
841
+ async function handleAdd(args) {
842
+ const packageName = args.find(a => !a.startsWith('--'));
843
+ if (!packageName) {
844
+ console.error('❌ Package name required');
845
+ console.error('Usage: pets add <package-name> [--skip-config] [--version <version>]');
846
+ process.exit(1);
847
+ }
848
+ const skipConfig = args.includes('--skip-config');
849
+ const versionIndex = args.indexOf('--version');
850
+ const version = versionIndex >= 0 ? args[versionIndex + 1] : undefined;
851
+ const configIndex = args.indexOf('--config');
852
+ const configStr = configIndex >= 0 ? args[configIndex + 1] : undefined;
853
+ const presetEnv = configStr ? JSON.parse(configStr) : undefined;
854
+ ensureOpencodeJson();
855
+ console.log(`\n📦 Adding ${packageName}...`);
856
+ const downloader = getPetDownloader();
857
+ const result = await downloader.add(packageName, {
858
+ targetDir: process.cwd(),
859
+ version,
860
+ skipConfig,
861
+ envValues: presetEnv
862
+ });
863
+ if (!result.success) {
864
+ console.error(`\n❌ ${result.message}`);
865
+ process.exit(1);
866
+ }
867
+ console.log(`\n✅ ${result.message}`);
868
+ if (result.packagePath) {
869
+ console.log(` 📁 Downloaded to: ${result.packagePath}`);
870
+ }
871
+ if (result.config) {
872
+ console.log(` 📋 ${result.config.title || result.config.name}`);
873
+ if (result.config.description) {
874
+ console.log(` ${result.config.description}`);
875
+ }
876
+ }
877
+ if (result.envVariables && result.envVariables.length > 0 && !skipConfig) {
878
+ console.log('\n🔧 Configuration required:\n');
879
+ await promptForConfiguration(result.envVariables, result.packagePath);
880
+ }
881
+ else if (result.envVariables && result.envVariables.length > 0) {
882
+ console.log('\n⚠️ Environment variables needed (skipped with --skip-config):');
883
+ for (const env of result.envVariables) {
884
+ const status = env.currentValue ? '✅' : (env.required ? '❌' : '⚪');
885
+ console.log(` ${status} ${env.name}: ${env.description}`);
886
+ }
887
+ }
888
+ console.log('\n💡 Plugin added to opencode.json');
889
+ console.log(' To apply changes:');
890
+ console.log(' • New session: restart opencode');
891
+ console.log(' • Keep session: run "opencode --continue" or "pets reload"\n');
892
+ }
893
+ async function promptForConfiguration(envVariables, packagePath) {
894
+ const rl = readline.createInterface({
895
+ input: process.stdin,
896
+ output: process.stdout
897
+ });
898
+ const prompt = (question) => {
899
+ return new Promise((resolve) => {
900
+ rl.question(question, (answer) => {
901
+ resolve(answer);
902
+ });
903
+ });
904
+ };
905
+ const envValues = {};
906
+ const requiredVars = envVariables.filter(v => v.required);
907
+ const optionalVars = envVariables.filter(v => !v.required);
908
+ if (requiredVars.length > 0) {
909
+ console.log('Required environment variables:\n');
910
+ for (const env of requiredVars) {
911
+ const currentValue = env.currentValue ? ` [current: ${env.secret ? '***' : env.currentValue}]` : '';
912
+ const providerInfo = env.provider ? ` (${env.provider})` : '';
913
+ console.log(` ${env.name}${providerInfo}`);
914
+ console.log(` ${env.description}`);
915
+ const value = await prompt(` Enter value${currentValue}: `);
916
+ if (value.trim()) {
917
+ envValues[env.name] = value.trim();
918
+ }
919
+ console.log('');
920
+ }
921
+ }
922
+ if (optionalVars.length > 0) {
923
+ const configureOptional = await prompt('Configure optional variables? (y/N): ');
924
+ if (configureOptional.toLowerCase() === 'y') {
925
+ console.log('\nOptional environment variables:\n');
926
+ for (const env of optionalVars) {
927
+ const currentValue = env.currentValue ? ` [current: ${env.secret ? '***' : env.currentValue}]` : '';
928
+ const providerInfo = env.provider ? ` (${env.provider})` : '';
929
+ console.log(` ${env.name}${providerInfo}`);
930
+ console.log(` ${env.description}`);
931
+ const value = await prompt(` Enter value${currentValue} (press Enter to skip): `);
932
+ if (value.trim()) {
933
+ envValues[env.name] = value.trim();
934
+ }
935
+ console.log('');
936
+ }
937
+ }
938
+ }
939
+ rl.close();
940
+ if (Object.keys(envValues).length > 0) {
941
+ // Extract petId from the package path (e.g., /path/to/@openpets/maps -> maps)
942
+ const petId = packagePath.split('/').pop() || 'unknown';
943
+ // Save to .pets/config.json (proper location for pet environment variables)
944
+ const petsConfigDir = resolve(process.cwd(), '.pets');
945
+ const petsConfigPath = resolve(petsConfigDir, 'config.json');
946
+ try {
947
+ // Ensure .pets directory exists
948
+ if (!existsSync(petsConfigDir)) {
949
+ mkdirSync(petsConfigDir, { recursive: true });
950
+ }
951
+ // Load existing config or create new one
952
+ let petsConfig = {
953
+ enabled: [],
954
+ disabled: [],
955
+ envConfig: {},
956
+ };
957
+ if (existsSync(petsConfigPath)) {
958
+ try {
959
+ const existing = JSON.parse(readFileSync(petsConfigPath, 'utf-8'));
960
+ petsConfig = {
961
+ enabled: existing.enabled || [],
962
+ disabled: existing.disabled || [],
963
+ envConfig: existing.envConfig || {},
964
+ ...existing
965
+ };
966
+ }
967
+ catch (e) {
968
+ log('Could not parse existing .pets/config.json, creating new structure');
969
+ }
970
+ }
971
+ // Ensure envConfig structure exists
972
+ if (!petsConfig.envConfig) {
973
+ petsConfig.envConfig = {};
974
+ }
975
+ // Create or update pet-specific env config
976
+ if (!petsConfig.envConfig[petId]) {
977
+ petsConfig.envConfig[petId] = {};
978
+ }
979
+ // Set the environment variables
980
+ Object.assign(petsConfig.envConfig[petId], envValues);
981
+ // Update timestamp
982
+ petsConfig.last_updated = new Date().toISOString();
983
+ // Write config
984
+ writeFileSync(petsConfigPath, JSON.stringify(petsConfig, null, 2));
985
+ console.log('✅ Configuration saved to .pets/config.json');
986
+ }
987
+ catch (error) {
988
+ console.error(`⚠️ Could not save configuration to .pets/config.json: ${error.message}`);
989
+ console.log('\nAdd these to your .env file or .pets/config.json manually:');
990
+ for (const [key, value] of Object.entries(envValues)) {
991
+ console.log(` ${key}=${value}`);
992
+ }
993
+ }
994
+ }
995
+ }
996
+ async function launchConfigModal(packageName, envVariables) {
997
+ const uiDir = resolve(__dirname, '../../apps/desktop');
998
+ if (!existsSync(uiDir)) {
999
+ console.log('Desktop UI not available, using CLI configuration...');
1000
+ await promptForConfiguration(envVariables, '');
1001
+ return;
1002
+ }
1003
+ console.log('🖥️ Launching configuration modal...');
1004
+ const configData = {
1005
+ packageName,
1006
+ envVariables,
1007
+ projectDir: process.cwd()
1008
+ };
1009
+ const child = spawn('bun', ['run', 'tauri:dev'], {
1010
+ cwd: uiDir,
1011
+ stdio: 'inherit',
1012
+ shell: true,
1013
+ env: {
1014
+ ...process.env,
1015
+ OPENPETS_CONFIG_MODE: 'true',
1016
+ OPENPETS_CONFIG_DATA: JSON.stringify(configData),
1017
+ OPENPETS_PROJECT_DIR: process.cwd()
1018
+ }
1019
+ });
1020
+ child.on('error', (error) => {
1021
+ console.error('Failed to launch configuration modal:', error);
1022
+ console.log('Falling back to CLI configuration...');
1023
+ promptForConfiguration(envVariables, '');
1024
+ });
1025
+ }
1026
+ async function handlePrune(args) {
1027
+ const keepPets = args.filter(a => !a.startsWith('--'));
1028
+ const dryRun = args.includes('--dry-run');
1029
+ if (keepPets.length === 0) {
1030
+ console.error('❌ At least one pet name required');
1031
+ console.error('Usage: pets prune <pet-name> [pet-name2...] [--dry-run]');
1032
+ console.error('\nExamples:');
1033
+ console.error(' pets prune maps # Keep only maps, disable all others');
1034
+ console.error(' pets prune maps github # Keep maps and github, disable all others');
1035
+ console.error(' pets prune maps --dry-run # Preview changes without applying');
1036
+ process.exit(1);
1037
+ }
1038
+ const opencodeJsonPath = ensureOpencodeJson();
1039
+ const petsConfigDir = resolve(process.cwd(), '.pets');
1040
+ const petsConfigPath = resolve(petsConfigDir, 'config.json');
1041
+ console.log(`\n🔍 Pruning plugins to keep only: ${keepPets.join(', ')}`);
1042
+ if (dryRun) {
1043
+ console.log(' (dry-run mode - no changes will be made)\n');
1044
+ }
1045
+ else {
1046
+ console.log('');
1047
+ }
1048
+ const opencodeConfig = JSON.parse(readFileSync(opencodeJsonPath, 'utf-8'));
1049
+ const currentPlugins = Array.isArray(opencodeConfig.plugin)
1050
+ ? opencodeConfig.plugin
1051
+ : (opencodeConfig.plugin ? [opencodeConfig.plugin] : []);
1052
+ const normalizeId = (id) => {
1053
+ return id
1054
+ .replace(/^@openpets\//, '')
1055
+ .replace(/^openpets\//, '')
1056
+ .replace(/^pets\//, '')
1057
+ .replace(/^\.\/pets\//, '')
1058
+ .replace(/\/index\.ts$/, '')
1059
+ .replace(/\/index\.js$/, '');
1060
+ };
1061
+ const keepNormalized = new Set(keepPets.map(normalizeId));
1062
+ const petsToVerify = keepPets.map(normalizeId);
1063
+ const toKeep = [];
1064
+ const toDisable = [];
1065
+ for (const plugin of currentPlugins) {
1066
+ const normalized = normalizeId(plugin);
1067
+ if (!keepNormalized.has(normalized)) {
1068
+ toDisable.push(plugin);
1069
+ }
1070
+ }
1071
+ if (dryRun) {
1072
+ console.log('📦 Plugins to VERIFY and KEEP (will check registry):');
1073
+ for (const pet of petsToVerify) {
1074
+ console.log(` ✅ @openpets/${pet}`);
1075
+ }
1076
+ console.log('\n🚫 Plugins to DISABLE:');
1077
+ if (toDisable.length === 0) {
1078
+ console.log(' (none)');
1079
+ }
1080
+ else {
1081
+ for (const plugin of toDisable) {
1082
+ console.log(` ❌ ${plugin}`);
1083
+ }
1084
+ }
1085
+ console.log('\n💡 Run without --dry-run to apply these changes');
1086
+ return;
1087
+ }
1088
+ console.log('📦 Verifying plugins from registry...\n');
1089
+ const downloader = getPetDownloader();
1090
+ for (const petName of petsToVerify) {
1091
+ console.log(` 📦 ${petName}...`);
1092
+ const result = await downloader.add(petName, {
1093
+ targetDir: process.cwd(),
1094
+ skipConfig: true
1095
+ });
1096
+ if (!result.success) {
1097
+ console.error(`\n❌ ${result.message}`);
1098
+ console.error(`\n⚠️ Prune aborted - fix the above error and try again`);
1099
+ process.exit(1);
1100
+ }
1101
+ console.log(` ✅ ${result.message}`);
1102
+ if (result.packagePath) {
1103
+ console.log(` 📁 ${result.packagePath}`);
1104
+ }
1105
+ }
1106
+ console.log('');
1107
+ const updatedConfig = JSON.parse(readFileSync(opencodeJsonPath, 'utf-8'));
1108
+ const updatedPlugins = Array.isArray(updatedConfig.plugin)
1109
+ ? updatedConfig.plugin
1110
+ : (updatedConfig.plugin ? [updatedConfig.plugin] : []);
1111
+ for (const plugin of updatedPlugins) {
1112
+ const normalized = normalizeId(plugin);
1113
+ if (keepNormalized.has(normalized)) {
1114
+ toKeep.push(plugin);
1115
+ }
1116
+ }
1117
+ console.log('📦 Plugins KEPT enabled:');
1118
+ for (const plugin of toKeep) {
1119
+ console.log(` ✅ ${plugin}`);
1120
+ }
1121
+ console.log('\n🚫 Plugins to DISABLE:');
1122
+ if (toDisable.length === 0) {
1123
+ console.log(' (none)');
1124
+ }
1125
+ else {
1126
+ for (const plugin of toDisable) {
1127
+ console.log(` ❌ ${plugin}`);
1128
+ }
1129
+ }
1130
+ const finalConfig = JSON.parse(readFileSync(opencodeJsonPath, 'utf-8'));
1131
+ finalConfig.plugin = toKeep;
1132
+ writeFileSync(opencodeJsonPath, JSON.stringify(finalConfig, null, 2));
1133
+ console.log('\n✅ Updated opencode.json');
1134
+ let petsConfig = {
1135
+ enabled: [],
1136
+ disabled: [],
1137
+ };
1138
+ if (existsSync(petsConfigPath)) {
1139
+ try {
1140
+ petsConfig = JSON.parse(readFileSync(petsConfigPath, 'utf-8'));
1141
+ }
1142
+ catch (e) {
1143
+ log('Could not parse existing .pets/config.json, creating new one');
1144
+ }
1145
+ }
1146
+ if (!Array.isArray(petsConfig.enabled))
1147
+ petsConfig.enabled = [];
1148
+ if (!Array.isArray(petsConfig.disabled))
1149
+ petsConfig.disabled = [];
1150
+ for (const plugin of toKeep) {
1151
+ const normalized = normalizeId(plugin);
1152
+ petsConfig.disabled = petsConfig.disabled.filter(p => normalizeId(p) !== normalized);
1153
+ if (!petsConfig.enabled.some(p => normalizeId(p) === normalized)) {
1154
+ petsConfig.enabled.push(normalized);
1155
+ }
1156
+ }
1157
+ for (const plugin of toDisable) {
1158
+ const normalized = normalizeId(plugin);
1159
+ petsConfig.enabled = petsConfig.enabled.filter(p => normalizeId(p) !== normalized);
1160
+ if (!petsConfig.disabled.some(p => normalizeId(p) === normalized)) {
1161
+ petsConfig.disabled.push(normalized);
1162
+ }
1163
+ }
1164
+ petsConfig.last_updated = new Date().toISOString();
1165
+ if (!existsSync(petsConfigDir)) {
1166
+ mkdirSync(petsConfigDir, { recursive: true });
1167
+ }
1168
+ writeFileSync(petsConfigPath, JSON.stringify(petsConfig, null, 2));
1169
+ console.log('✅ Updated .pets/config.json');
1170
+ console.log(`\n🎉 Pruned ${toDisable.length} plugin(s), kept ${toKeep.length} plugin(s)`);
1171
+ console.log('💡 To apply changes:');
1172
+ console.log(' • New session: restart opencode');
1173
+ console.log(' • Keep session: run "opencode --continue" or "pets reload"');
1174
+ }
1175
+ async function handleReload(args) {
1176
+ const sessionIndex = args.indexOf('--session');
1177
+ const sessionId = sessionIndex >= 0 ? args[sessionIndex + 1] : undefined;
1178
+ const message = args.filter(a => !a.startsWith('--') && a !== sessionId).join(' ') || undefined;
1179
+ console.log('🔄 Reloading OpenCode session with updated plugins...\n');
1180
+ const opencodeJsonPath = resolve(process.cwd(), 'opencode.json');
1181
+ const opencodeJsoncPath = resolve(process.cwd(), 'opencode.jsonc');
1182
+ if (!existsSync(opencodeJsonPath) && !existsSync(opencodeJsoncPath)) {
1183
+ console.error('❌ No opencode.json found in current directory');
1184
+ console.error(' Run this command from a directory with an opencode.json file');
1185
+ process.exit(1);
1186
+ }
1187
+ const configPath = existsSync(opencodeJsonPath) ? opencodeJsonPath : opencodeJsoncPath;
1188
+ try {
1189
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
1190
+ const plugins = Array.isArray(config.plugin) ? config.plugin : (config.plugin ? [config.plugin] : []);
1191
+ if (plugins.length > 0) {
1192
+ console.log(`📦 Plugins to load: ${plugins.length}`);
1193
+ for (const p of plugins.slice(0, 5)) {
1194
+ const shortName = typeof p === 'string' ? p.replace('@openpets/', '').replace('./pets/', '') : p;
1195
+ console.log(` • ${shortName}`);
1196
+ }
1197
+ if (plugins.length > 5) {
1198
+ console.log(` ... and ${plugins.length - 5} more`);
1199
+ }
1200
+ console.log('');
1201
+ }
1202
+ }
1203
+ catch (e) {
1204
+ log('Could not read opencode.json:', e);
1205
+ }
1206
+ const opencodeArgs = ['--continue'];
1207
+ if (sessionId) {
1208
+ opencodeArgs.push('--session', sessionId);
1209
+ }
1210
+ if (message) {
1211
+ console.log(`💬 Starting with message: "${message}"\n`);
1212
+ const child = spawn('opencode', ['run', message, ...opencodeArgs], {
1213
+ stdio: 'inherit',
1214
+ shell: true,
1215
+ cwd: process.cwd()
1216
+ });
1217
+ child.on('error', (error) => {
1218
+ console.error('❌ Failed to start opencode:', error.message);
1219
+ process.exit(1);
1220
+ });
1221
+ child.on('exit', (code) => {
1222
+ process.exit(code || 0);
1223
+ });
1224
+ }
1225
+ else {
1226
+ console.log('🚀 Launching OpenCode TUI...\n');
1227
+ const child = spawn('opencode', opencodeArgs, {
1228
+ stdio: 'inherit',
1229
+ shell: true,
1230
+ cwd: process.cwd()
1231
+ });
1232
+ child.on('error', (error) => {
1233
+ console.error('❌ Failed to start opencode:', error.message);
1234
+ process.exit(1);
1235
+ });
1236
+ child.on('exit', (code) => {
1237
+ process.exit(code || 0);
1238
+ });
1239
+ }
1240
+ }
1241
+ function showUnknownCommandError(command) {
1242
+ const validCommands = [
1243
+ 'add', 'install', 'uninstall', 'remove', 'delete', 'inspect', 'search', 'list', 'run', 'prune', 'reload',
1244
+ 'init', 'build', 'deploy', 'validate', 'publish', 'playground',
1245
+ 'discover', 'registry', 'help'
1246
+ ];
1247
+ const similar = validCommands.find(cmd => cmd.startsWith(command.slice(0, 2)) ||
1248
+ command.startsWith(cmd.slice(0, 2)));
1249
+ console.error(`❌ Unknown command: "${command}"`);
1250
+ if (similar) {
1251
+ console.error(` Did you mean: pets ${similar}?`);
1252
+ }
1253
+ console.error(`\nRun "pets help" for available commands.`);
1254
+ }
1255
+ function showHelp() {
1256
+ console.log(`
1257
+ 🐾 OpenPets CLI - Plugin Registry & Management Tool
1258
+
1259
+ USAGE:
1260
+ pets <command> [options]
1261
+
1262
+ REGISTRY COMMANDS:
1263
+ add <name> Download pet and add to local opencode.json
1264
+ --version <version> Install specific version
1265
+ --skip-config Skip environment variable configuration
1266
+ --config '{"k":"v"}' Pre-set environment variables
1267
+
1268
+ install <name> Install a pet from the registry (npm install)
1269
+ --client <client> Target client (claude, opencode, cursor, vscode)
1270
+ --config '{"k":"v"}' Pre-configure environment variables
1271
+ --global, -g Install globally
1272
+ --version <version> Install specific version
1273
+
1274
+ uninstall <name> Uninstall a pet (aliases: remove, delete)
1275
+ --client <client> Target client to remove from
1276
+ --global, -g Uninstall globally
1277
+
1278
+ inspect <name> Show detailed package information
1279
+ search [query] Search for pets in the registry
1280
+ --limit <n> Limit results (default: 20)
1281
+
1282
+ list [type] List resources
1283
+ clients Show supported AI clients
1284
+ servers Show installed pets for a client
1285
+ installed Show all installed pets (default)
1286
+ --client <client> Filter by client
1287
+
1288
+ run <name> Run a pet locally
1289
+ --config '{"k":"v"}' Configuration to pass
1290
+ --transport <type> Transport type (stdio, http)
1291
+
1292
+ prune <pet> [pets...] Keep only specified pets, disable all others
1293
+ --dry-run Preview changes without applying
1294
+
1295
+ reload [message] Reload OpenCode with updated plugins (continues session)
1296
+ --session <id> Continue a specific session instead of the last one
1297
+
1298
+ DEVELOPMENT COMMANDS:
1299
+ init [name] Initialize a new pet project
1300
+ --yaml Create openpets.yaml instead of package.json
1301
+ --runtime <type> Runtime type (typescript, bun, container)
1302
+ --dir <path> Target directory
1303
+
1304
+ build <pet-name> Build and validate a pet package
1305
+ deploy <pet-name> Build and deploy with metadata
1306
+ validate [dir] Validate pet configuration
1307
+
1308
+ publish [pet-name] Publish a pet to npm
1309
+ --preview Dry-run mode
1310
+ --channel <name> npm tag (default: latest)
1311
+
1312
+ playground Open the web playground
1313
+
1314
+ DISCOVERY COMMANDS:
1315
+ discover Find OpenPets-compatible packages
1316
+ --github Include GitHub repository search
1317
+ --limit <n> Maximum results (default: 50)
1318
+
1319
+ registry [subcommand] Manage the package registry
1320
+ status Show registry statistics (default)
1321
+ update Refresh the registry from sources
1322
+ export [file] Export registry to JSON file
1323
+ import <file> Import registry from JSON file
1324
+
1325
+ OTHER:
1326
+ (no command) Launch the desktop plugin manager
1327
+ help, --help, -h Show this help message
1328
+
1329
+ EXAMPLES:
1330
+ pets add maps # Download @openpets/maps and configure
1331
+ pets add @openpets/maps --skip-config # Download without configuration prompts
1332
+ pets add postgres --config '{"DATABASE_URL":"postgres://..."}'
1333
+ pets install maps --client claude # Install via npm for specific client
1334
+ pets search github
1335
+ pets inspect linear
1336
+ pets list clients
1337
+ pets list servers --client opencode
1338
+ pets run hackernews
1339
+ pets init my-awesome-pet
1340
+ pets validate ./pets/my-pet
1341
+ pets publish --preview
1342
+ pets discover --github
1343
+ pets registry update
1344
+ pets prune maps # Keep only maps, disable all others
1345
+ pets prune maps github --dry-run # Preview pruning to maps and github
1346
+ pets reload # Reload OpenCode with updated plugins
1347
+ pets reload "test notion" # Reload and send a message
1348
+
1349
+ ENVIRONMENT:
1350
+ PETS_DEBUG=true Enable detailed debug logging
1351
+ GITHUB_TOKEN=<token> GitHub token for repository discovery
1352
+
1353
+ HOW DISCOVERY WORKS:
1354
+ The discovery system finds OpenPets-compatible packages via:
1355
+
1356
+ 1. npm Registry: Searches @openpets/* scope and "openpets" keyword
1357
+ 2. GitHub (optional): Searches for package.json files containing
1358
+ "$schema": "https://pets.studio/config.json"
1359
+
1360
+ To make your package discoverable:
1361
+ - Publish to npm under @openpets/* scope, OR
1362
+ - Add "openpets" to your package.json keywords, OR
1363
+ - Add "$schema": "https://pets.studio/config.json" to package.json
1364
+
1365
+ Learn more: https://pets.studio/docs
1366
+ `);
1367
+ }
244
1368
  //# sourceMappingURL=cli.js.map