pingthings 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/bin/pingthings.js +1 -0
  2. package/package.json +1 -1
  3. package/packs/0ad-civilizations/openpeon.json +172 -0
  4. package/packs/7kaa-soldiers/openpeon.json +217 -0
  5. package/packs/droid-announcer/manifest.json +52 -0
  6. package/packs/droid-announcer/openpeon.json +107 -0
  7. package/packs/droid-announcer/sounds/achievement-unlocked.ogg +0 -0
  8. package/packs/droid-announcer/sounds/action-required.ogg +0 -0
  9. package/packs/droid-announcer/sounds/critical-damage.ogg +0 -0
  10. package/packs/droid-announcer/sounds/enemy-forces-detected.ogg +0 -0
  11. package/packs/droid-announcer/sounds/epic.ogg +0 -0
  12. package/packs/droid-announcer/sounds/hostile-units-inbound.ogg +0 -0
  13. package/packs/droid-announcer/sounds/ineffective-strategy.ogg +0 -0
  14. package/packs/droid-announcer/sounds/instruction-unclear.ogg +0 -0
  15. package/packs/droid-announcer/sounds/mission-complete.ogg +0 -0
  16. package/packs/droid-announcer/sounds/new-upgrades-available.ogg +0 -0
  17. package/packs/droid-announcer/sounds/objective-complete.ogg +0 -0
  18. package/packs/droid-announcer/sounds/self-repair.ogg +0 -0
  19. package/packs/droid-announcer/sounds/shields-activated.ogg +0 -0
  20. package/packs/droid-announcer/sounds/system-optimal.ogg +0 -0
  21. package/packs/droid-announcer/sounds/target-neutralized.ogg +0 -0
  22. package/packs/fighting-announcer/openpeon.json +132 -0
  23. package/packs/freedoom-arsenal/openpeon.json +117 -0
  24. package/packs/kenney-digital/manifest.json +58 -0
  25. package/packs/kenney-digital/openpeon.json +122 -0
  26. package/packs/kenney-digital/sounds/high-up.ogg +0 -0
  27. package/packs/kenney-digital/sounds/low-down.ogg +0 -0
  28. package/packs/kenney-digital/sounds/pep-1.ogg +0 -0
  29. package/packs/kenney-digital/sounds/pep-2.ogg +0 -0
  30. package/packs/kenney-digital/sounds/phase-jump-1.ogg +0 -0
  31. package/packs/kenney-digital/sounds/phase-jump-2.ogg +0 -0
  32. package/packs/kenney-digital/sounds/phaser-down.ogg +0 -0
  33. package/packs/kenney-digital/sounds/phaser-up-1.ogg +0 -0
  34. package/packs/kenney-digital/sounds/phaser-up-2.ogg +0 -0
  35. package/packs/kenney-digital/sounds/power-up-1.ogg +0 -0
  36. package/packs/kenney-digital/sounds/power-up-2.ogg +0 -0
  37. package/packs/kenney-digital/sounds/power-up-3.ogg +0 -0
  38. package/packs/kenney-digital/sounds/space-trash.ogg +0 -0
  39. package/packs/kenney-digital/sounds/three-tone.ogg +0 -0
  40. package/packs/kenney-digital/sounds/tone.ogg +0 -0
  41. package/packs/kenney-digital/sounds/two-tone.ogg +0 -0
  42. package/packs/kenney-digital/sounds/zap-1.ogg +0 -0
  43. package/packs/kenney-digital/sounds/zap-2.ogg +0 -0
  44. package/packs/kenney-fighter/manifest.json +58 -0
  45. package/packs/kenney-fighter/openpeon.json +122 -0
  46. package/packs/kenney-fighter/sounds/begin.ogg +0 -0
  47. package/packs/kenney-fighter/sounds/combo-breaker.ogg +0 -0
  48. package/packs/kenney-fighter/sounds/combo.ogg +0 -0
  49. package/packs/kenney-fighter/sounds/fight.ogg +0 -0
  50. package/packs/kenney-fighter/sounds/final-round.ogg +0 -0
  51. package/packs/kenney-fighter/sounds/flawless-victory.ogg +0 -0
  52. package/packs/kenney-fighter/sounds/game-over.ogg +0 -0
  53. package/packs/kenney-fighter/sounds/loser.ogg +0 -0
  54. package/packs/kenney-fighter/sounds/multi-kill.ogg +0 -0
  55. package/packs/kenney-fighter/sounds/prepare-yourself.ogg +0 -0
  56. package/packs/kenney-fighter/sounds/ready.ogg +0 -0
  57. package/packs/kenney-fighter/sounds/round-1.ogg +0 -0
  58. package/packs/kenney-fighter/sounds/sudden-death.ogg +0 -0
  59. package/packs/kenney-fighter/sounds/tie-breaker.ogg +0 -0
  60. package/packs/kenney-fighter/sounds/time.ogg +0 -0
  61. package/packs/kenney-fighter/sounds/winner.ogg +0 -0
  62. package/packs/kenney-fighter/sounds/you-lose.ogg +0 -0
  63. package/packs/kenney-fighter/sounds/you-win.ogg +0 -0
  64. package/packs/kenney-impacts/manifest.json +57 -0
  65. package/packs/kenney-impacts/openpeon.json +116 -0
  66. package/packs/kenney-impacts/sounds/bell-heavy-2.ogg +0 -0
  67. package/packs/kenney-impacts/sounds/bell-heavy.ogg +0 -0
  68. package/packs/kenney-impacts/sounds/glass-heavy.ogg +0 -0
  69. package/packs/kenney-impacts/sounds/glass-light.ogg +0 -0
  70. package/packs/kenney-impacts/sounds/glass-medium.ogg +0 -0
  71. package/packs/kenney-impacts/sounds/mining-1.ogg +0 -0
  72. package/packs/kenney-impacts/sounds/mining-2.ogg +0 -0
  73. package/packs/kenney-impacts/sounds/plate-heavy-2.ogg +0 -0
  74. package/packs/kenney-impacts/sounds/plate-heavy.ogg +0 -0
  75. package/packs/kenney-impacts/sounds/plate-light-1.ogg +0 -0
  76. package/packs/kenney-impacts/sounds/plate-light-2.ogg +0 -0
  77. package/packs/kenney-impacts/sounds/plate-medium.ogg +0 -0
  78. package/packs/kenney-impacts/sounds/soft-heavy.ogg +0 -0
  79. package/packs/kenney-impacts/sounds/soft-medium.ogg +0 -0
  80. package/packs/kenney-impacts/sounds/tin-medium.ogg +0 -0
  81. package/packs/kenney-impacts/sounds/wood-heavy.ogg +0 -0
  82. package/packs/kenney-impacts/sounds/wood-light.ogg +0 -0
  83. package/packs/kenney-impacts/sounds/wood-medium.ogg +0 -0
  84. package/packs/kenney-interface/openpeon.json +122 -0
  85. package/packs/kenney-rpg/manifest.json +57 -0
  86. package/packs/kenney-rpg/openpeon.json +117 -0
  87. package/packs/kenney-rpg/sounds/belt-handle-2.ogg +0 -0
  88. package/packs/kenney-rpg/sounds/belt-handle.ogg +0 -0
  89. package/packs/kenney-rpg/sounds/book-close.ogg +0 -0
  90. package/packs/kenney-rpg/sounds/book-flip.ogg +0 -0
  91. package/packs/kenney-rpg/sounds/book-open.ogg +0 -0
  92. package/packs/kenney-rpg/sounds/book-place.ogg +0 -0
  93. package/packs/kenney-rpg/sounds/cloth-belt.ogg +0 -0
  94. package/packs/kenney-rpg/sounds/creak.ogg +0 -0
  95. package/packs/kenney-rpg/sounds/door-close.ogg +0 -0
  96. package/packs/kenney-rpg/sounds/door-open.ogg +0 -0
  97. package/packs/kenney-rpg/sounds/draw-knife.ogg +0 -0
  98. package/packs/kenney-rpg/sounds/leather-drop.ogg +0 -0
  99. package/packs/kenney-rpg/sounds/leather-handle.ogg +0 -0
  100. package/packs/kenney-rpg/sounds/metal-click.ogg +0 -0
  101. package/packs/kenney-rpg/sounds/metal-latch.ogg +0 -0
  102. package/packs/kenney-rpg/sounds/metal-pot-2.ogg +0 -0
  103. package/packs/kenney-rpg/sounds/metal-pot.ogg +0 -0
  104. package/packs/kenney-rpg/sounds/sword-draw.ogg +0 -0
  105. package/packs/kenney-scifi/openpeon.json +122 -0
  106. package/packs/kenney-voiceover/manifest.json +60 -0
  107. package/packs/kenney-voiceover/openpeon.json +127 -0
  108. package/packs/kenney-voiceover/sounds/congratulations.ogg +0 -0
  109. package/packs/kenney-voiceover/sounds/correct.ogg +0 -0
  110. package/packs/kenney-voiceover/sounds/final-round.ogg +0 -0
  111. package/packs/kenney-voiceover/sounds/game-over.ogg +0 -0
  112. package/packs/kenney-voiceover/sounds/go.ogg +0 -0
  113. package/packs/kenney-voiceover/sounds/hurry-up.ogg +0 -0
  114. package/packs/kenney-voiceover/sounds/level-up.ogg +0 -0
  115. package/packs/kenney-voiceover/sounds/look-out.ogg +0 -0
  116. package/packs/kenney-voiceover/sounds/mission-completed.ogg +0 -0
  117. package/packs/kenney-voiceover/sounds/mission-failed.ogg +0 -0
  118. package/packs/kenney-voiceover/sounds/new-highscore.ogg +0 -0
  119. package/packs/kenney-voiceover/sounds/objective-achieved.ogg +0 -0
  120. package/packs/kenney-voiceover/sounds/power-up.ogg +0 -0
  121. package/packs/kenney-voiceover/sounds/ready.ogg +0 -0
  122. package/packs/kenney-voiceover/sounds/set.ogg +0 -0
  123. package/packs/kenney-voiceover/sounds/time-over.ogg +0 -0
  124. package/packs/kenney-voiceover/sounds/wrong.ogg +0 -0
  125. package/packs/kenney-voiceover/sounds/you-lose.ogg +0 -0
  126. package/packs/kenney-voiceover/sounds/you-win.ogg +0 -0
  127. package/packs/openarena-announcer/openpeon.json +107 -0
  128. package/packs/retro-8bit/openpeon.json +122 -0
  129. package/packs/retro-movement/manifest.json +58 -0
  130. package/packs/retro-movement/openpeon.json +122 -0
  131. package/packs/retro-movement/sounds/blip-1.ogg +0 -0
  132. package/packs/retro-movement/sounds/blip-2.ogg +0 -0
  133. package/packs/retro-movement/sounds/door-open-1.ogg +0 -0
  134. package/packs/retro-movement/sounds/door-open-2.ogg +0 -0
  135. package/packs/retro-movement/sounds/door-open-3.ogg +0 -0
  136. package/packs/retro-movement/sounds/jump-1.ogg +0 -0
  137. package/packs/retro-movement/sounds/jump-2.ogg +0 -0
  138. package/packs/retro-movement/sounds/jump-3.ogg +0 -0
  139. package/packs/retro-movement/sounds/menu-select-1.ogg +0 -0
  140. package/packs/retro-movement/sounds/menu-select-2.ogg +0 -0
  141. package/packs/retro-movement/sounds/pause-in-2.ogg +0 -0
  142. package/packs/retro-movement/sounds/pause-in-3.ogg +0 -0
  143. package/packs/retro-movement/sounds/pause-in.ogg +0 -0
  144. package/packs/retro-movement/sounds/pause-out.ogg +0 -0
  145. package/packs/retro-movement/sounds/portal-1.ogg +0 -0
  146. package/packs/retro-movement/sounds/portal-2.ogg +0 -0
  147. package/packs/retro-movement/sounds/portal-3.ogg +0 -0
  148. package/packs/retro-movement/sounds/portal-4.ogg +0 -0
  149. package/packs/retro-weapons/manifest.json +57 -0
  150. package/packs/retro-weapons/openpeon.json +117 -0
  151. package/packs/retro-weapons/sounds/death-android-2.ogg +0 -0
  152. package/packs/retro-weapons/sounds/death-android.ogg +0 -0
  153. package/packs/retro-weapons/sounds/death-human-1.ogg +0 -0
  154. package/packs/retro-weapons/sounds/death-human-2.ogg +0 -0
  155. package/packs/retro-weapons/sounds/death-robot.ogg +0 -0
  156. package/packs/retro-weapons/sounds/explosion-1.ogg +0 -0
  157. package/packs/retro-weapons/sounds/explosion-2.ogg +0 -0
  158. package/packs/retro-weapons/sounds/explosion-long.ogg +0 -0
  159. package/packs/retro-weapons/sounds/explosion-soft.ogg +0 -0
  160. package/packs/retro-weapons/sounds/impact-1.ogg +0 -0
  161. package/packs/retro-weapons/sounds/impact-2.ogg +0 -0
  162. package/packs/retro-weapons/sounds/impact-3.ogg +0 -0
  163. package/packs/retro-weapons/sounds/interact-1.ogg +0 -0
  164. package/packs/retro-weapons/sounds/interact-2.ogg +0 -0
  165. package/packs/retro-weapons/sounds/interact-3.ogg +0 -0
  166. package/packs/retro-weapons/sounds/shot-1.ogg +0 -0
  167. package/packs/retro-weapons/sounds/shot-2.ogg +0 -0
  168. package/packs/retro-weapons/sounds/shot-3.ogg +0 -0
  169. package/packs/warzone2100-command/openpeon.json +137 -0
  170. package/packs/wesnoth-combat/openpeon.json +122 -0
  171. package/packs/xonotic-announcer/openpeon.json +107 -0
  172. package/src/cli/cesp.js +101 -0
  173. package/src/packs.js +66 -9
@@ -0,0 +1,107 @@
1
+ {
2
+ "cesp_version": "1.0",
3
+ "name": "xonotic-announcer",
4
+ "display_name": "Arena FPS announcer voice lines from Xonotic",
5
+ "version": "1.0.0",
6
+ "license": "GPL-2.0-or-later",
7
+ "author": {
8
+ "name": "Xonotic Project (https://github.com/xonotic/xonotic-data.pk3dir)"
9
+ },
10
+ "categories": {
11
+ "task.acknowledge": {
12
+ "sounds": [
13
+ {
14
+ "file": "sounds/awesome.ogg",
15
+ "label": "awesome",
16
+ "sha256": "a75cf21a39d7faadffe3e2f17b2d0ca0149bab71a31561a7424a149419ca8565"
17
+ },
18
+ {
19
+ "file": "sounds/amazing.ogg",
20
+ "label": "amazing",
21
+ "sha256": "54bc110c31bfb8ea9323cdf9df47826672393aca967421debb601b4d551e20c6"
22
+ },
23
+ {
24
+ "file": "sounds/impressive.ogg",
25
+ "label": "impressive",
26
+ "sha256": "294cef5c449dec7592bc52054e203d5d85277361f7af6da1077a9c4fcd95d340"
27
+ },
28
+ {
29
+ "file": "sounds/airshot.ogg",
30
+ "label": "airshot",
31
+ "sha256": "c07e53928cd4a7e5ef2d56862a43da4f3fafe0b28ddf91554a24174b56e3d527"
32
+ }
33
+ ]
34
+ },
35
+ "input.required": {
36
+ "sounds": [
37
+ {
38
+ "file": "sounds/prepareforbattle.ogg",
39
+ "label": "prepareforbattle",
40
+ "sha256": "20651628c3edf90b414fa8aa063e7568819ff820506920b4a8e86c78c7a336ad"
41
+ },
42
+ {
43
+ "file": "sounds/begin.ogg",
44
+ "label": "begin",
45
+ "sha256": "8bce8664a5735a4df20a7a2667cfeee9d4723238b979db6e87a3f0887050b812"
46
+ },
47
+ {
48
+ "file": "sounds/1fragleft.ogg",
49
+ "label": "1fragleft",
50
+ "sha256": "e1522f8e929369ba4c6f8134598c22caccbbd61439e17ee3a23e165d754580e6"
51
+ }
52
+ ]
53
+ },
54
+ "task.complete": {
55
+ "sounds": [
56
+ {
57
+ "file": "sounds/yoda.ogg",
58
+ "label": "yoda",
59
+ "sha256": "a8bef88c6f1813713f7c1de014b077975a6502f8d0eb87bd223577a299cdce9a"
60
+ },
61
+ {
62
+ "file": "sounds/botlike.ogg",
63
+ "label": "botlike",
64
+ "sha256": "2a05e921c609b5c6be9d74b10bb29e65ada70a89e4d448409bf17a09605dee38"
65
+ },
66
+ {
67
+ "file": "sounds/leadgained.ogg",
68
+ "label": "leadgained",
69
+ "sha256": "f9c1516d185020ca088f14b9ae38709a09f11fbc6acd80fbd610d7022fe5f0cc"
70
+ }
71
+ ]
72
+ },
73
+ "task.error": {
74
+ "sounds": [
75
+ {
76
+ "file": "sounds/terminated.ogg",
77
+ "label": "terminated",
78
+ "sha256": "823553d052e624aaa47f13235906cef52a9a4f74fdc3b323cc2f2a83c77b82bd"
79
+ },
80
+ {
81
+ "file": "sounds/headshot.ogg",
82
+ "label": "headshot",
83
+ "sha256": "571f39f1543a3bbd23d2712845ba7da6d782f24cd370ceffb9912f57a198ee92"
84
+ },
85
+ {
86
+ "file": "sounds/electrobitch.ogg",
87
+ "label": "electrobitch",
88
+ "sha256": "c562648da52d97b775a15610b8284aed6cf283bb7266f0d45f7c0063655cc7af"
89
+ }
90
+ ]
91
+ },
92
+ "resource.limit": {
93
+ "sounds": [
94
+ {
95
+ "file": "sounds/lastsecond.ogg",
96
+ "label": "lastsecond",
97
+ "sha256": "7a0c2f948af8f94bed17ff68a9eb3a45a16a0c75df25cb41ea5ab2106da0cb3b"
98
+ },
99
+ {
100
+ "file": "sounds/narrowly.ogg",
101
+ "label": "narrowly",
102
+ "sha256": "f8d42b5290e1ffd655fbce86c7b3a2021ac146d5afedd464f1d6568b4cc13a53"
103
+ }
104
+ ]
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,101 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { resolvePack, listPacks } from '../packs.js';
5
+
6
+ const PINGTHINGS_TO_CESP = {
7
+ done: 'task.acknowledge',
8
+ permission: 'input.required',
9
+ complete: 'task.complete',
10
+ error: 'task.error',
11
+ blocked: 'resource.limit',
12
+ };
13
+
14
+ function showHelp() {
15
+ console.log(`
16
+ Usage: pingthings cesp [pack|--all]
17
+
18
+ Generate CESP-compatible openpeon.json for a pack.
19
+ Makes your packs work with any CESP-compatible tool (PeonPing, etc.)
20
+
21
+ Arguments:
22
+ pack Generate for a specific pack
23
+ --all Generate for all built-in packs
24
+
25
+ Examples:
26
+ pingthings cesp 7kaa-soldiers Generate for one pack
27
+ pingthings cesp --all Generate for all packs
28
+ `);
29
+ }
30
+
31
+ function generateCesp(packName) {
32
+ const pack = resolvePack(packName);
33
+ if (!pack || !pack.manifest) {
34
+ console.error(`Pack not found: ${packName}`);
35
+ return false;
36
+ }
37
+
38
+ const m = pack.manifest;
39
+ const categories = {};
40
+
41
+ for (const [pingEvent, cespEvent] of Object.entries(PINGTHINGS_TO_CESP)) {
42
+ const eventSounds = m.events?.[pingEvent] || [];
43
+ if (eventSounds.length === 0) continue;
44
+
45
+ categories[cespEvent] = {
46
+ sounds: eventSounds.map(file => {
47
+ const fullPath = join(pack.dir, file);
48
+ let sha256 = '';
49
+ try {
50
+ const data = readFileSync(fullPath);
51
+ sha256 = createHash('sha256').update(data).digest('hex');
52
+ } catch {}
53
+
54
+ return {
55
+ file,
56
+ label: file.split('/').pop().replace(/\.\w+$/, '').replace(/[-_]/g, ' '),
57
+ ...(sha256 ? { sha256 } : {}),
58
+ };
59
+ }),
60
+ };
61
+ }
62
+
63
+ const cesp = {
64
+ cesp_version: '1.0',
65
+ name: m.name,
66
+ display_name: m.description,
67
+ version: m.version || '1.0.0',
68
+ license: m.license,
69
+ author: {
70
+ name: m.credits || 'Unknown',
71
+ },
72
+ categories,
73
+ };
74
+
75
+ const outPath = join(pack.dir, 'openpeon.json');
76
+ writeFileSync(outPath, JSON.stringify(cesp, null, 2) + '\n', 'utf8');
77
+ console.log(` Generated: ${outPath}`);
78
+ return true;
79
+ }
80
+
81
+ export default function cesp(args) {
82
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
83
+ showHelp();
84
+ if (args.length === 0) process.exit(1);
85
+ return;
86
+ }
87
+
88
+ if (args.includes('--all')) {
89
+ console.log('\nGenerating CESP manifests for all packs:\n');
90
+ const packs = listPacks();
91
+ for (const pack of packs) {
92
+ if (pack.isBuiltIn) {
93
+ generateCesp(pack.name);
94
+ }
95
+ }
96
+ console.log('\nDone.\n');
97
+ return;
98
+ }
99
+
100
+ generateCesp(args[0]);
101
+ }
package/src/packs.js CHANGED
@@ -12,19 +12,76 @@ function getUserPacksDir() {
12
12
  return join(getConfigDir(), 'packs');
13
13
  }
14
14
 
15
+ // CESP event → pingthings event mapping
16
+ const CESP_EVENT_MAP = {
17
+ 'session.start': 'permission',
18
+ 'task.acknowledge': 'done',
19
+ 'task.complete': 'complete',
20
+ 'task.error': 'error',
21
+ 'input.required': 'permission',
22
+ 'resource.limit': 'blocked',
23
+ 'user.spam': 'blocked',
24
+ 'session.end': 'complete',
25
+ 'task.progress': 'done',
26
+ };
27
+
28
+ function convertCespManifest(cesp) {
29
+ const events = {};
30
+ for (const [cespEvent, sounds] of Object.entries(cesp.categories || {})) {
31
+ const pingEvent = CESP_EVENT_MAP[cespEvent];
32
+ if (!pingEvent) continue;
33
+ const files = (sounds.sounds || []).map(s => s.file);
34
+ if (!events[pingEvent]) events[pingEvent] = [];
35
+ events[pingEvent].push(...files);
36
+ }
37
+
38
+ const allSounds = [];
39
+ for (const sounds of Object.values(cesp.categories || {})) {
40
+ for (const s of sounds.sounds || []) {
41
+ if (!allSounds.includes(s.file)) allSounds.push(s.file);
42
+ }
43
+ }
44
+
45
+ return {
46
+ name: cesp.name,
47
+ description: cesp.display_name || cesp.description || '',
48
+ version: cesp.version || '1.0.0',
49
+ license: cesp.license || 'Unknown',
50
+ credits: cesp.author?.name || '',
51
+ category: cesp.tags?.[0] || 'other',
52
+ sounds: allSounds,
53
+ events,
54
+ _cesp: true,
55
+ };
56
+ }
57
+
15
58
  function readManifest(packDir) {
59
+ // Prefer manifest.json (our native format) — richer metadata
16
60
  const manifestPath = join(packDir, 'manifest.json');
17
- if (!existsSync(manifestPath)) return null;
18
- try {
19
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
20
- if (!manifest.name) {
21
- console.error(`Warning: manifest.json in ${packDir} is missing "name" field`);
61
+ if (existsSync(manifestPath)) {
62
+ try {
63
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
64
+ if (!manifest.name) {
65
+ console.error(`Warning: manifest.json in ${packDir} is missing "name" field`);
66
+ }
67
+ return manifest;
68
+ } catch (err) {
69
+ console.error(`Warning: Failed to parse ${manifestPath}: ${err.message}`);
70
+ }
71
+ }
72
+
73
+ // Fall back to CESP format (openpeon.json) for community packs
74
+ const cespPath = join(packDir, 'openpeon.json');
75
+ if (existsSync(cespPath)) {
76
+ try {
77
+ const cesp = JSON.parse(readFileSync(cespPath, 'utf8'));
78
+ return convertCespManifest(cesp);
79
+ } catch (err) {
80
+ console.error(`Warning: Failed to parse ${cespPath}: ${err.message}`);
22
81
  }
23
- return manifest;
24
- } catch (err) {
25
- console.error(`Warning: Failed to parse ${manifestPath}: ${err.message}`);
26
- return null;
27
82
  }
83
+
84
+ return null;
28
85
  }
29
86
 
30
87
  function discoverSounds(packDir) {