openpets 1.0.11 → 1.0.12

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 (72) hide show
  1. package/dist/data/api.json +3758 -7222
  2. package/dist/src/core/build-pet.d.ts.map +1 -1
  3. package/dist/src/core/build-pet.js +7 -0
  4. package/dist/src/core/build-pet.js.map +1 -1
  5. package/dist/src/core/cli.js +456 -130
  6. package/dist/src/core/cli.js.map +1 -1
  7. package/dist/src/core/ensure-npmignore.d.ts +30 -0
  8. package/dist/src/core/ensure-npmignore.d.ts.map +1 -0
  9. package/dist/src/core/ensure-npmignore.js +121 -0
  10. package/dist/src/core/ensure-npmignore.js.map +1 -0
  11. package/dist/src/core/index.d.ts +6 -3
  12. package/dist/src/core/index.d.ts.map +1 -1
  13. package/dist/src/core/index.js +9 -3
  14. package/dist/src/core/index.js.map +1 -1
  15. package/dist/src/core/mcp-generator.d.ts +56 -0
  16. package/dist/src/core/mcp-generator.d.ts.map +1 -0
  17. package/dist/src/core/mcp-generator.js +1438 -0
  18. package/dist/src/core/mcp-generator.js.map +1 -0
  19. package/dist/src/core/mcp-server.js +0 -0
  20. package/dist/src/core/openapi-generator.d.ts +59 -0
  21. package/dist/src/core/openapi-generator.d.ts.map +1 -0
  22. package/dist/src/core/openapi-generator.js +800 -0
  23. package/dist/src/core/openapi-generator.js.map +1 -0
  24. package/dist/src/core/pet-config.d.ts +107 -49
  25. package/dist/src/core/pet-config.d.ts.map +1 -1
  26. package/dist/src/core/pet-config.js +6 -4
  27. package/dist/src/core/pet-config.js.map +1 -1
  28. package/dist/src/core/pet-downloader.d.ts +16 -0
  29. package/dist/src/core/pet-downloader.d.ts.map +1 -1
  30. package/dist/src/core/pet-downloader.js +145 -3
  31. package/dist/src/core/pet-downloader.js.map +1 -1
  32. package/dist/src/core/publish-pet.d.ts +29 -0
  33. package/dist/src/core/publish-pet.d.ts.map +1 -0
  34. package/dist/src/core/publish-pet.js +372 -0
  35. package/dist/src/core/publish-pet.js.map +1 -0
  36. package/dist/src/core/sdk-generator.d.ts +92 -0
  37. package/dist/src/core/sdk-generator.d.ts.map +1 -0
  38. package/dist/src/core/sdk-generator.js +567 -0
  39. package/dist/src/core/sdk-generator.js.map +1 -0
  40. package/dist/src/core/search-pets.d.ts +5 -0
  41. package/dist/src/core/search-pets.d.ts.map +1 -1
  42. package/dist/src/core/search-pets.js +43 -0
  43. package/dist/src/core/search-pets.js.map +1 -1
  44. package/dist/src/core/security-scanner.d.ts +49 -0
  45. package/dist/src/core/security-scanner.d.ts.map +1 -0
  46. package/dist/src/core/security-scanner.js +255 -0
  47. package/dist/src/core/security-scanner.js.map +1 -0
  48. package/dist/src/core/tool-lister.d.ts +61 -0
  49. package/dist/src/core/tool-lister.d.ts.map +1 -0
  50. package/dist/src/core/tool-lister.js +333 -0
  51. package/dist/src/core/tool-lister.js.map +1 -0
  52. package/dist/src/core/validate-pet.d.ts +2 -0
  53. package/dist/src/core/validate-pet.d.ts.map +1 -1
  54. package/dist/src/core/validate-pet.js +93 -1
  55. package/dist/src/core/validate-pet.js.map +1 -1
  56. package/dist/src/sdk/plugin-factory.d.ts +86 -0
  57. package/dist/src/sdk/plugin-factory.d.ts.map +1 -1
  58. package/dist/src/sdk/plugin-factory.js +450 -53
  59. package/dist/src/sdk/plugin-factory.js.map +1 -1
  60. package/dist/src/sdk/prompts-manager.d.ts +6 -0
  61. package/dist/src/sdk/prompts-manager.d.ts.map +1 -0
  62. package/dist/src/sdk/prompts-manager.js +162 -0
  63. package/dist/src/sdk/prompts-manager.js.map +1 -0
  64. package/package.json +1 -1
  65. package/dist/src/core/local-cache.d.ts +0 -69
  66. package/dist/src/core/local-cache.d.ts.map +0 -1
  67. package/dist/src/core/local-cache.js +0 -212
  68. package/dist/src/core/local-cache.js.map +0 -1
  69. package/dist/src/core/plugin-factory.d.ts +0 -58
  70. package/dist/src/core/plugin-factory.d.ts.map +0 -1
  71. package/dist/src/core/plugin-factory.js +0 -212
  72. package/dist/src/core/plugin-factory.js.map +0 -1
@@ -1,82 +1,20 @@
1
1
  #!/usr/bin/env bun
2
2
  import { buildPet } from './build-pet.js';
3
3
  import { deployPet } from './deploy-pet.js';
4
+ import { publishPet } from './publish-pet.js';
5
+ import { generateOpenAPITools } from './openapi-generator.js';
4
6
  import { addFolderToHistory } from './config-manager.js';
5
7
  import { getRegistryClient } from './registry-client.js';
6
8
  import { loadPetConfig, validatePetConfig, generateOpenpetsYamlTemplate } from './pet-config.js';
7
9
  import { getPackageDiscovery, getRegistryAggregator } from './discovery.js';
8
10
  import { getPetDownloader } from './pet-downloader.js';
9
- import { getAllPets } from './search-pets.js';
11
+ import { getAllPets, findSimilarPets } from './search-pets.js';
10
12
  import { syncEnvToConfig } from '../sdk/plugin-factory.js';
13
+ import { fetchFromRegistry, readLocalCommands, getLiveTools, formatTools, listProjectTools, printProjectToolsSummary, getOpencodeCacheDir } from './tool-lister.js';
11
14
  import { spawn } from 'child_process';
12
15
  import { resolve, join } from 'path';
13
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
16
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, rmSync } from 'fs';
14
17
  import * as readline from 'readline';
15
- async function publishPet(petName, options = {}) {
16
- const { preview = false, channel = 'latest' } = options;
17
- // Find the script path - try multiple locations
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}`));
29
- process.exit(1);
30
- }
31
- // If no petName provided, try to infer from current directory
32
- let finalPetName = petName;
33
- if (!finalPetName) {
34
- const cwd = process.cwd();
35
- const pkgPath = resolve(cwd, 'package.json');
36
- if (existsSync(pkgPath)) {
37
- try {
38
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
39
- // Extract pet name from @openpets/maps -> maps
40
- if (pkg.name && pkg.name.startsWith('@openpets/')) {
41
- finalPetName = pkg.name.replace('@openpets/', '');
42
- console.log(`šŸ“¦ Detected pet: ${finalPetName}`);
43
- }
44
- }
45
- catch (e) {
46
- // Ignore parse errors
47
- }
48
- }
49
- if (!finalPetName) {
50
- console.error('āŒ Could not determine pet name. Run from pet directory or specify pet name.');
51
- process.exit(1);
52
- }
53
- }
54
- const bunArgs = [scriptPath, finalPetName];
55
- if (preview)
56
- bunArgs.push('--preview');
57
- if (channel && channel !== 'latest')
58
- bunArgs.push('--channel', channel);
59
- return new Promise((resolve, reject) => {
60
- const child = spawn('bun', bunArgs, {
61
- stdio: 'inherit',
62
- shell: true,
63
- env: {
64
- ...process.env,
65
- OPENPETS_PREVIEW: preview ? 'true' : 'false',
66
- OPENPETS_CHANNEL: channel
67
- }
68
- });
69
- child.on('error', reject);
70
- child.on('exit', (code) => {
71
- if (code !== 0) {
72
- reject(new Error(`Publish exited with code ${code}`));
73
- }
74
- else {
75
- resolve();
76
- }
77
- });
78
- });
79
- }
80
18
  const args = process.argv.slice(2);
81
19
  const command = args[0];
82
20
  const DEBUG = process.env.PETS_DEBUG === 'true';
@@ -220,8 +158,17 @@ else if (command === 'publish') {
220
158
  const preview = args.includes('--preview');
221
159
  const channelIndex = args.indexOf('--channel');
222
160
  const channel = channelIndex >= 0 ? args[channelIndex + 1] : 'latest';
223
- log('Publishing pet:', petName || '(auto-detect)', { preview, channel });
224
- publishPet(petName, { preview, channel }).catch(error => {
161
+ const scopeIndex = args.indexOf('--scope');
162
+ const scope = scopeIndex >= 0 ? args[scopeIndex + 1] : '@openpets';
163
+ const otpIndex = args.indexOf('--otp');
164
+ const otp = otpIndex >= 0 ? args[otpIndex + 1] : undefined;
165
+ log('Publishing pet:', petName || '(auto-detect)', { preview, channel, scope, otp: otp ? '***' : undefined });
166
+ publishPet(petName, { preview, channel, scope, otp }).then(result => {
167
+ if (!result.success) {
168
+ console.error('Error publishing pet:', result.message);
169
+ process.exit(1);
170
+ }
171
+ }).catch(error => {
225
172
  console.error('Error publishing pet:', error);
226
173
  log('Publish error details:', error);
227
174
  process.exit(1);
@@ -266,9 +213,21 @@ else if (command === 'registry') {
266
213
  else if (command === 'prune') {
267
214
  handlePrune(args.slice(1));
268
215
  }
216
+ else if (command === 'clear') {
217
+ handleClear(args.slice(1));
218
+ }
219
+ else if (command === 'config') {
220
+ handleConfig(args.slice(1));
221
+ }
269
222
  else if (command === 'reload') {
270
223
  handleReload(args.slice(1));
271
224
  }
225
+ else if (command === 'tools') {
226
+ handleTools(args.slice(1));
227
+ }
228
+ else if (command === 'generate-openapi') {
229
+ handleGenerateOpenAPI(args.slice(1));
230
+ }
272
231
  else if (command === 'help' || command === '--help' || command === '-h') {
273
232
  showHelp();
274
233
  }
@@ -842,26 +801,55 @@ async function handleAdd(args) {
842
801
  const packageName = args.find(a => !a.startsWith('--'));
843
802
  if (!packageName) {
844
803
  console.error('āŒ Package name required');
845
- console.error('Usage: pets add <package-name> [--skip-config] [--version <version>]');
804
+ console.error('Usage: pets add <package-name> [--version <version>]');
846
805
  process.exit(1);
847
806
  }
848
- const skipConfig = args.includes('--skip-config');
849
807
  const versionIndex = args.indexOf('--version');
850
808
  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
809
  ensureOpencodeJson();
855
810
  console.log(`\nšŸ“¦ Adding ${packageName}...`);
856
811
  const downloader = getPetDownloader();
857
812
  const result = await downloader.add(packageName, {
858
813
  targetDir: process.cwd(),
859
- version,
860
- skipConfig,
861
- envValues: presetEnv
814
+ version
862
815
  });
863
816
  if (!result.success) {
864
817
  console.error(`\nāŒ ${result.message}`);
818
+ // If package not found, suggest similar pets
819
+ if (result.message.includes('Package not found')) {
820
+ const shortName = packageName
821
+ .replace(/^@openpets\//, '')
822
+ .replace(/^openpets\//, '');
823
+ try {
824
+ const similar = await findSimilarPets(shortName, 5);
825
+ if (similar.length > 0) {
826
+ console.log('\nšŸ’” Did you mean one of these?\n');
827
+ for (const pet of similar) {
828
+ // Strip all prefixes to get clean pet ID
829
+ const petId = pet.id
830
+ .replace(/^@openpets\//, '')
831
+ .replace(/^openpets\//, '')
832
+ .replace(/^@/, '');
833
+ console.log(` šŸ“¦ ${petId}`);
834
+ if (pet.description) {
835
+ console.log(` ${pet.description}`);
836
+ }
837
+ console.log(` Run: pets add ${petId}`);
838
+ console.log('');
839
+ }
840
+ }
841
+ else {
842
+ console.log('\nšŸ’” Try searching for available pets:');
843
+ console.log(` pets search ${shortName}`);
844
+ }
845
+ }
846
+ catch (e) {
847
+ // Silently fail if we can't fetch suggestions
848
+ log('Failed to fetch similar pets:', e);
849
+ console.log('\nšŸ’” Try searching for available pets:');
850
+ console.log(` pets search ${shortName}`);
851
+ }
852
+ }
865
853
  process.exit(1);
866
854
  }
867
855
  console.log(`\nāœ… ${result.message}`);
@@ -874,23 +862,100 @@ async function handleAdd(args) {
874
862
  console.log(` ${result.config.description}`);
875
863
  }
876
864
  }
877
- if (result.envVariables && result.envVariables.length > 0 && !skipConfig) {
878
- console.log('\nšŸ”§ Configuration required:\n');
879
- await promptForConfiguration(result.envVariables, result.packagePath);
865
+ // Display prompts/agents copied info
866
+ if (result.promptsCopied && result.promptsCopied.copied > 0) {
867
+ console.log(` šŸ¤– Installed ${result.promptsCopied.copied} agent(s) to .opencode/agent/${packageName.replace('@openpets/', '')}/`);
868
+ for (const file of result.promptsCopied.files) {
869
+ console.log(` • ${file.replace('.md', '')}`);
870
+ }
880
871
  }
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}`);
872
+ // Show required env vars if any are missing
873
+ if (result.envVariables && result.envVariables.length > 0) {
874
+ const missing = result.envVariables.filter(e => e.required && !e.currentValue);
875
+ const optional = result.envVariables.filter(e => !e.required && !e.currentValue);
876
+ if (missing.length > 0) {
877
+ console.log('\nāš ļø Required environment variables:');
878
+ for (const env of missing) {
879
+ console.log(` āŒ ${env.name}: ${env.description}`);
880
+ }
881
+ console.log(`\n Run "pets config ${packageName.replace('@openpets/', '')}" to configure`);
882
+ }
883
+ if (optional.length > 0 && missing.length === 0) {
884
+ console.log('\nšŸ’” Optional environment variables available:');
885
+ console.log(` Run "pets config ${packageName.replace('@openpets/', '')}" to configure`);
886
886
  }
887
887
  }
888
- console.log('\nšŸ’” Plugin added to opencode.json');
888
+ console.log('\nāœ… Plugin added to opencode.json');
889
889
  console.log(' To apply changes:');
890
890
  console.log(' • New session: restart opencode');
891
891
  console.log(' • Keep session: run "opencode --continue" or "pets reload"\n');
892
892
  }
893
- async function promptForConfiguration(envVariables, packagePath) {
893
+ async function handleConfig(args) {
894
+ const petName = args.find(a => !a.startsWith('--'));
895
+ if (!petName) {
896
+ console.error('āŒ Pet name required');
897
+ console.error('Usage: pets config <pet-name>');
898
+ console.error('\nExamples:');
899
+ console.error(' pets config maps');
900
+ console.error(' pets config github');
901
+ process.exit(1);
902
+ }
903
+ const shortName = petName
904
+ .replace(/^@openpets\//, '')
905
+ .replace(/^openpets\//, '');
906
+ console.log(`\nšŸ”§ Configuring ${shortName}...\n`);
907
+ // Try to load pet config to get env variable definitions
908
+ const downloader = getPetDownloader();
909
+ const petPath = downloader.getInstalledPetPath(shortName);
910
+ if (!petPath) {
911
+ console.error(`āŒ Pet "${shortName}" is not installed`);
912
+ console.error(` Run "pets add ${shortName}" first`);
913
+ process.exit(1);
914
+ }
915
+ const { loadPetConfig } = await import('./pet-config.js');
916
+ const petConfig = loadPetConfig(petPath);
917
+ if (!petConfig) {
918
+ console.error(`āŒ Could not load configuration for ${shortName}`);
919
+ process.exit(1);
920
+ }
921
+ // Extract env variables from pet config
922
+ const envVariables = [];
923
+ if (petConfig.envVariables) {
924
+ const required = petConfig.envVariables.required || [];
925
+ const optional = petConfig.envVariables.optional || [];
926
+ for (const v of required) {
927
+ const varName = v.name || '';
928
+ const isSecret = v.secret || varName.includes('TOKEN') || varName.includes('KEY') || varName.includes('SECRET');
929
+ envVariables.push({
930
+ name: varName,
931
+ description: v.description || '',
932
+ required: true,
933
+ secret: isSecret,
934
+ currentValue: process.env[varName]
935
+ });
936
+ }
937
+ for (const v of optional) {
938
+ const varName = v.name || '';
939
+ const isSecret = v.secret || varName.includes('TOKEN') || varName.includes('KEY') || varName.includes('SECRET');
940
+ envVariables.push({
941
+ name: varName,
942
+ description: v.description || '',
943
+ required: false,
944
+ secret: isSecret,
945
+ currentValue: process.env[varName]
946
+ });
947
+ }
948
+ }
949
+ if (envVariables.length === 0) {
950
+ console.log(`āœ… ${shortName} has no configurable environment variables`);
951
+ return;
952
+ }
953
+ await promptForConfiguration(envVariables, petPath, shortName);
954
+ console.log('\nšŸ’” To apply changes:');
955
+ console.log(' • New session: restart opencode');
956
+ console.log(' • Keep session: run "opencode --continue" or "pets reload"\n');
957
+ }
958
+ async function promptForConfiguration(envVariables, packagePath, petId) {
894
959
  const rl = readline.createInterface({
895
960
  input: process.stdin,
896
961
  output: process.stdout
@@ -938,8 +1003,8 @@ async function promptForConfiguration(envVariables, packagePath) {
938
1003
  }
939
1004
  rl.close();
940
1005
  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';
1006
+ // Use provided petId or extract from package path
1007
+ const resolvedPetId = petId || packagePath.split('/').pop() || 'unknown';
943
1008
  // Save to .pets/config.json (proper location for pet environment variables)
944
1009
  const petsConfigDir = resolve(process.cwd(), '.pets');
945
1010
  const petsConfigPath = resolve(petsConfigDir, 'config.json');
@@ -973,11 +1038,11 @@ async function promptForConfiguration(envVariables, packagePath) {
973
1038
  petsConfig.envConfig = {};
974
1039
  }
975
1040
  // Create or update pet-specific env config
976
- if (!petsConfig.envConfig[petId]) {
977
- petsConfig.envConfig[petId] = {};
1041
+ if (!petsConfig.envConfig[resolvedPetId]) {
1042
+ petsConfig.envConfig[resolvedPetId] = {};
978
1043
  }
979
1044
  // Set the environment variables
980
- Object.assign(petsConfig.envConfig[petId], envValues);
1045
+ Object.assign(petsConfig.envConfig[resolvedPetId], envValues);
981
1046
  // Update timestamp
982
1047
  petsConfig.last_updated = new Date().toISOString();
983
1048
  // Write config
@@ -993,36 +1058,6 @@ async function promptForConfiguration(envVariables, packagePath) {
993
1058
  }
994
1059
  }
995
1060
  }
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
1061
  async function handlePrune(args) {
1027
1062
  const keepPets = args.filter(a => !a.startsWith('--'));
1028
1063
  const dryRun = args.includes('--dry-run');
@@ -1167,11 +1202,240 @@ async function handlePrune(args) {
1167
1202
  }
1168
1203
  writeFileSync(petsConfigPath, JSON.stringify(petsConfig, null, 2));
1169
1204
  console.log('āœ… Updated .pets/config.json');
1205
+ // Remove agent folders for disabled plugins
1206
+ const agentRemovals = [];
1207
+ for (const plugin of toDisable) {
1208
+ const normalized = normalizeId(plugin);
1209
+ const agentDir = resolve(process.cwd(), '.opencode', 'agent', normalized);
1210
+ if (existsSync(agentDir)) {
1211
+ try {
1212
+ rmSync(agentDir, { recursive: true, force: true });
1213
+ agentRemovals.push(normalized);
1214
+ log(`Removed agent folder: ${agentDir}`);
1215
+ }
1216
+ catch (e) {
1217
+ log(`Failed to remove agent folder ${agentDir}:`, e);
1218
+ }
1219
+ }
1220
+ }
1221
+ if (agentRemovals.length > 0) {
1222
+ console.log(`šŸ—‘ļø Removed ${agentRemovals.length} agent folder(s) from .opencode/agent/`);
1223
+ for (const agent of agentRemovals) {
1224
+ console.log(` • ${agent}`);
1225
+ }
1226
+ }
1170
1227
  console.log(`\nšŸŽ‰ Pruned ${toDisable.length} plugin(s), kept ${toKeep.length} plugin(s)`);
1171
1228
  console.log('šŸ’” To apply changes:');
1172
1229
  console.log(' • New session: restart opencode');
1173
1230
  console.log(' • Keep session: run "opencode --continue" or "pets reload"');
1174
1231
  }
1232
+ async function handleTools(args) {
1233
+ const petName = args.find(a => !a.startsWith('--'));
1234
+ const showSchema = !args.includes('--no-schema'); // Default: show schema
1235
+ const jsonOutput = args.includes('--json');
1236
+ const liveMode = !args.includes('--static'); // Default: live mode
1237
+ const expanded = args.includes('--expanded'); // Show detailed expanded view
1238
+ const options = { showSchema, jsonOutput, liveMode, expanded };
1239
+ let commandsData = null;
1240
+ // Determine the plugin directory
1241
+ let pluginDir = null;
1242
+ if (petName) {
1243
+ // Check local pets/ directory first
1244
+ const localPetDir = resolve(process.cwd(), 'pets', petName);
1245
+ if (existsSync(localPetDir)) {
1246
+ pluginDir = localPetDir;
1247
+ }
1248
+ else {
1249
+ // Check opencode cache
1250
+ const cachePath = join(getOpencodeCacheDir(), '@openpets', petName);
1251
+ if (existsSync(cachePath)) {
1252
+ pluginDir = cachePath;
1253
+ }
1254
+ }
1255
+ }
1256
+ else {
1257
+ // Current directory - only if it's a pet directory
1258
+ if (existsSync(resolve(process.cwd(), 'index.ts'))) {
1259
+ pluginDir = process.cwd();
1260
+ }
1261
+ }
1262
+ // If we have a plugin directory, try to get tools
1263
+ if (pluginDir) {
1264
+ if (liveMode) {
1265
+ commandsData = await getLiveTools(pluginDir, options);
1266
+ if (!commandsData) {
1267
+ console.log('āš ļø Could not load live tools, falling back to static commands.json...');
1268
+ }
1269
+ }
1270
+ if (!commandsData) {
1271
+ commandsData = readLocalCommands(pluginDir);
1272
+ }
1273
+ if (commandsData) {
1274
+ if (jsonOutput) {
1275
+ console.log(JSON.stringify(commandsData, null, 2));
1276
+ }
1277
+ else {
1278
+ formatTools(commandsData, showSchema, expanded);
1279
+ }
1280
+ return;
1281
+ }
1282
+ }
1283
+ // If pet name specified but not found locally, fetch from registry
1284
+ if (petName && !commandsData) {
1285
+ console.log(`šŸ” Fetching tools for ${petName} from registry...`);
1286
+ commandsData = await fetchFromRegistry(petName);
1287
+ if (commandsData) {
1288
+ if (jsonOutput) {
1289
+ console.log(JSON.stringify(commandsData, null, 2));
1290
+ }
1291
+ else {
1292
+ formatTools(commandsData, showSchema, expanded);
1293
+ }
1294
+ return;
1295
+ }
1296
+ console.error(`āŒ Could not find tools for '${petName}'`);
1297
+ console.error(' Make sure the pet exists and has been built with "pets build"');
1298
+ console.error(' Or check if it\'s published to npm under @openpets/ scope');
1299
+ process.exit(1);
1300
+ }
1301
+ // No pet name - check if this is a project directory with installed plugins
1302
+ const opencodeJsonPath = resolve(process.cwd(), 'opencode.json');
1303
+ if (existsSync(opencodeJsonPath)) {
1304
+ const { plugins, totalTools } = await listProjectTools(process.cwd(), options);
1305
+ if (plugins.length > 0) {
1306
+ if (jsonOutput) {
1307
+ console.log(JSON.stringify({ plugins, totalTools }, null, 2));
1308
+ }
1309
+ else {
1310
+ printProjectToolsSummary(plugins, totalTools, showSchema, expanded);
1311
+ }
1312
+ return;
1313
+ }
1314
+ }
1315
+ // Nothing found
1316
+ console.error('āŒ No tools found');
1317
+ console.error(' - In a pet directory: run "pets build" to generate commands.json');
1318
+ console.error(' - In a project: ensure opencode.json has plugins configured');
1319
+ console.error(' - Or specify a pet name: pets tools <pet-name>');
1320
+ process.exit(1);
1321
+ }
1322
+ async function handleClear(args) {
1323
+ const dryRun = args.includes('--dry-run');
1324
+ const keepAgents = args.includes('--keep-agents');
1325
+ const opencodeJsonPath = resolve(process.cwd(), 'opencode.json');
1326
+ const opencodeJsoncPath = resolve(process.cwd(), 'opencode.jsonc');
1327
+ const petsConfigDir = resolve(process.cwd(), '.pets');
1328
+ const petsConfigPath = resolve(petsConfigDir, 'config.json');
1329
+ const agentBaseDir = resolve(process.cwd(), '.opencode', 'agent');
1330
+ const configPath = existsSync(opencodeJsonPath) ? opencodeJsonPath :
1331
+ existsSync(opencodeJsoncPath) ? opencodeJsoncPath : null;
1332
+ if (!configPath) {
1333
+ console.error('āŒ No opencode.json found in current directory');
1334
+ process.exit(1);
1335
+ }
1336
+ console.log('\n🧹 Clearing all pets plugins...');
1337
+ if (dryRun) {
1338
+ console.log(' (dry-run mode - no changes will be made)\n');
1339
+ }
1340
+ else {
1341
+ console.log('');
1342
+ }
1343
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
1344
+ const currentPlugins = Array.isArray(config.plugin)
1345
+ ? config.plugin
1346
+ : (config.plugin ? [config.plugin] : []);
1347
+ const normalizeId = (id) => {
1348
+ return id
1349
+ .replace(/^@openpets\//, '')
1350
+ .replace(/^openpets\//, '')
1351
+ .replace(/^pets\//, '')
1352
+ .replace(/^\.\/pets\//, '')
1353
+ .replace(/\/index\.ts$/, '')
1354
+ .replace(/\/index\.js$/, '');
1355
+ };
1356
+ if (currentPlugins.length === 0) {
1357
+ console.log('šŸ“¦ No plugins currently installed');
1358
+ return;
1359
+ }
1360
+ console.log('šŸ“¦ Plugins to REMOVE:');
1361
+ for (const plugin of currentPlugins) {
1362
+ console.log(` āŒ ${plugin}`);
1363
+ }
1364
+ // Find agent folders to remove
1365
+ const agentFoldersToRemove = [];
1366
+ if (!keepAgents && existsSync(agentBaseDir)) {
1367
+ for (const plugin of currentPlugins) {
1368
+ const normalized = normalizeId(plugin);
1369
+ const agentDir = resolve(agentBaseDir, normalized);
1370
+ if (existsSync(agentDir)) {
1371
+ agentFoldersToRemove.push(normalized);
1372
+ }
1373
+ }
1374
+ if (agentFoldersToRemove.length > 0) {
1375
+ console.log('\nšŸ¤– Agent folders to REMOVE:');
1376
+ for (const agent of agentFoldersToRemove) {
1377
+ console.log(` āŒ .opencode/agent/${agent}/`);
1378
+ }
1379
+ }
1380
+ }
1381
+ if (dryRun) {
1382
+ console.log('\nšŸ’” Run without --dry-run to apply these changes');
1383
+ return;
1384
+ }
1385
+ // Clear plugins from opencode.json
1386
+ config.plugin = [];
1387
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1388
+ console.log('\nāœ… Cleared all plugins from opencode.json');
1389
+ // Update .pets/config.json
1390
+ let petsConfig = {
1391
+ enabled: [],
1392
+ disabled: [],
1393
+ };
1394
+ if (existsSync(petsConfigPath)) {
1395
+ try {
1396
+ petsConfig = JSON.parse(readFileSync(petsConfigPath, 'utf-8'));
1397
+ }
1398
+ catch (e) {
1399
+ log('Could not parse existing .pets/config.json, creating new one');
1400
+ }
1401
+ }
1402
+ if (!Array.isArray(petsConfig.enabled))
1403
+ petsConfig.enabled = [];
1404
+ if (!Array.isArray(petsConfig.disabled))
1405
+ petsConfig.disabled = [];
1406
+ // Move all enabled to disabled
1407
+ for (const plugin of currentPlugins) {
1408
+ const normalized = normalizeId(plugin);
1409
+ petsConfig.enabled = petsConfig.enabled.filter(p => normalizeId(p) !== normalized);
1410
+ if (!petsConfig.disabled.some(p => normalizeId(p) === normalized)) {
1411
+ petsConfig.disabled.push(normalized);
1412
+ }
1413
+ }
1414
+ petsConfig.last_updated = new Date().toISOString();
1415
+ if (!existsSync(petsConfigDir)) {
1416
+ mkdirSync(petsConfigDir, { recursive: true });
1417
+ }
1418
+ writeFileSync(petsConfigPath, JSON.stringify(petsConfig, null, 2));
1419
+ console.log('āœ… Updated .pets/config.json');
1420
+ // Remove agent folders
1421
+ if (!keepAgents && agentFoldersToRemove.length > 0) {
1422
+ for (const agent of agentFoldersToRemove) {
1423
+ const agentDir = resolve(agentBaseDir, agent);
1424
+ try {
1425
+ rmSync(agentDir, { recursive: true, force: true });
1426
+ log(`Removed agent folder: ${agentDir}`);
1427
+ }
1428
+ catch (e) {
1429
+ log(`Failed to remove agent folder ${agentDir}:`, e);
1430
+ }
1431
+ }
1432
+ console.log(`šŸ—‘ļø Removed ${agentFoldersToRemove.length} agent folder(s)`);
1433
+ }
1434
+ console.log(`\nšŸŽ‰ Cleared ${currentPlugins.length} plugin(s)`);
1435
+ console.log('šŸ’” To apply changes:');
1436
+ console.log(' • New session: restart opencode');
1437
+ console.log(' • Keep session: run "opencode --continue" or "pets reload"');
1438
+ }
1175
1439
  async function handleReload(args) {
1176
1440
  const sessionIndex = args.indexOf('--session');
1177
1441
  const sessionId = sessionIndex >= 0 ? args[sessionIndex + 1] : undefined;
@@ -1238,10 +1502,37 @@ async function handleReload(args) {
1238
1502
  });
1239
1503
  }
1240
1504
  }
1505
+ async function handleGenerateOpenAPI(args) {
1506
+ const urlIndex = args.indexOf('--url');
1507
+ const fileIndex = args.indexOf('--file');
1508
+ const baseUrlIndex = args.indexOf('--base-url');
1509
+ const outputIndex = args.indexOf('--output');
1510
+ const options = {
1511
+ url: urlIndex >= 0 ? args[urlIndex + 1] : undefined,
1512
+ file: fileIndex >= 0 ? args[fileIndex + 1] : undefined,
1513
+ baseUrl: baseUrlIndex >= 0 ? args[baseUrlIndex + 1] : undefined,
1514
+ outputFile: outputIndex >= 0 ? args[outputIndex + 1] : undefined,
1515
+ dryRun: args.includes('--dry-run') || args.includes('-n'),
1516
+ verbose: args.includes('--verbose') || args.includes('-v'),
1517
+ dumpSpec: args.includes('--dump-spec'),
1518
+ };
1519
+ console.log('šŸ”§ Generating OpenAPI tools...');
1520
+ const result = await generateOpenAPITools(options);
1521
+ if (result.success) {
1522
+ console.log(`āœ… ${result.message}`);
1523
+ if (result.outputPath && !options.dryRun) {
1524
+ console.log(`šŸ“ Output: ${result.outputPath}`);
1525
+ }
1526
+ }
1527
+ else {
1528
+ console.error(`āŒ ${result.message}`);
1529
+ process.exit(1);
1530
+ }
1531
+ }
1241
1532
  function showUnknownCommandError(command) {
1242
1533
  const validCommands = [
1243
- 'add', 'install', 'uninstall', 'remove', 'delete', 'inspect', 'search', 'list', 'run', 'prune', 'reload',
1244
- 'init', 'build', 'deploy', 'validate', 'publish', 'playground',
1534
+ 'add', 'install', 'uninstall', 'remove', 'delete', 'inspect', 'search', 'list', 'run', 'prune', 'clear', 'config', 'reload',
1535
+ 'init', 'build', 'deploy', 'validate', 'publish', 'playground', 'generate-openapi',
1245
1536
  'discover', 'registry', 'help'
1246
1537
  ];
1247
1538
  const similar = validCommands.find(cmd => cmd.startsWith(command.slice(0, 2)) ||
@@ -1261,13 +1552,14 @@ USAGE:
1261
1552
 
1262
1553
  REGISTRY COMMANDS:
1263
1554
  add <name> Download pet and add to local opencode.json
1555
+ Also copies prompts/ to .opencode/agent/<name>/
1264
1556
  --version <version> Install specific version
1265
- --skip-config Skip environment variable configuration
1266
- --config '{"k":"v"}' Pre-set environment variables
1557
+
1558
+ config <name> Configure environment variables for a pet
1559
+ Interactive prompt for required and optional vars
1267
1560
 
1268
1561
  install <name> Install a pet from the registry (npm install)
1269
1562
  --client <client> Target client (claude, opencode, cursor, vscode)
1270
- --config '{"k":"v"}' Pre-configure environment variables
1271
1563
  --global, -g Install globally
1272
1564
  --version <version> Install specific version
1273
1565
 
@@ -1290,7 +1582,13 @@ REGISTRY COMMANDS:
1290
1582
  --transport <type> Transport type (stdio, http)
1291
1583
 
1292
1584
  prune <pet> [pets...] Keep only specified pets, disable all others
1585
+ Also removes agent folders for disabled pets
1586
+ --dry-run Preview changes without applying
1587
+
1588
+ clear Remove ALL pets plugins from opencode.json
1589
+ Also removes all pet agent folders
1293
1590
  --dry-run Preview changes without applying
1591
+ --keep-agents Keep agent folders (only clear opencode.json)
1294
1592
 
1295
1593
  reload [message] Reload OpenCode with updated plugins (continues session)
1296
1594
  --session <id> Continue a specific session instead of the last one
@@ -1304,10 +1602,25 @@ DEVELOPMENT COMMANDS:
1304
1602
  build <pet-name> Build and validate a pet package
1305
1603
  deploy <pet-name> Build and deploy with metadata
1306
1604
  validate [dir] Validate pet configuration
1605
+ generate-openapi Generate tools from OpenAPI/Swagger spec
1606
+ --url <url> URL to fetch OpenAPI spec from
1607
+ --file <path> Local file path to OpenAPI spec
1608
+ --base-url <url> Base URL for API calls (overrides spec servers)
1609
+ --output <file> Output file name (default: openapi-client.ts)
1610
+ -n, --dry-run Preview without writing files
1611
+ -v, --verbose Show detailed output
1612
+ --dump-spec Dump raw spec to JSON for debugging
1613
+ tools [pet-name] List tools provided by a pet (single line by default)
1614
+ --expanded Show detailed view with parameters
1615
+ --static Use static commands.json instead of running plugin
1616
+ --no-schema Hide parameter details (in expanded mode)
1617
+ --json Output as JSON
1307
1618
 
1308
1619
  publish [pet-name] Publish a pet to npm
1309
1620
  --preview Dry-run mode
1310
1621
  --channel <name> npm tag (default: latest)
1622
+ --scope <scope> npm scope (default: @openpets, use "" for unscoped)
1623
+ --otp <code> One-time password for 2FA-enabled accounts
1311
1624
 
1312
1625
  playground Open the web playground
1313
1626
 
@@ -1327,9 +1640,9 @@ OTHER:
1327
1640
  help, --help, -h Show this help message
1328
1641
 
1329
1642
  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://..."}'
1643
+ pets add maps # Download @openpets/maps
1644
+ pets add maps --version 1.0.0 # Install specific version
1645
+ pets config maps # Configure environment variables
1333
1646
  pets install maps --client claude # Install via npm for specific client
1334
1647
  pets search github
1335
1648
  pets inspect linear
@@ -1338,6 +1651,10 @@ EXAMPLES:
1338
1651
  pets run hackernews
1339
1652
  pets init my-awesome-pet
1340
1653
  pets validate ./pets/my-pet
1654
+ pets tools # List tools in current dir (single line)
1655
+ pets tools coder # List tools for coder pet
1656
+ pets tools coder --expanded # List tools with detailed parameters
1657
+ pets tools asana --static # List tools from static commands.json
1341
1658
  pets publish --preview
1342
1659
  pets discover --github
1343
1660
  pets registry update
@@ -1350,6 +1667,15 @@ ENVIRONMENT:
1350
1667
  PETS_DEBUG=true Enable detailed debug logging
1351
1668
  GITHUB_TOKEN=<token> GitHub token for repository discovery
1352
1669
 
1670
+ AGENT PROMPTS:
1671
+ Pets can include agent prompts in their prompts/ directory.
1672
+ When you run "pets add <name>", these are automatically copied to:
1673
+ .opencode/agent/<pet-name>/
1674
+
1675
+ OpenCode discovers agents from .opencode/agent/ directories and makes
1676
+ them available for @mentions in your conversations. Agent files are
1677
+ markdown with YAML frontmatter defining behavior (model, tools, etc.)
1678
+
1353
1679
  HOW DISCOVERY WORKS:
1354
1680
  The discovery system finds OpenPets-compatible packages via:
1355
1681