pingthings 0.4.0 → 0.6.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 (487) hide show
  1. package/README.md +12 -0
  2. package/bin/pingthings.js +17 -0
  3. package/package.json +1 -1
  4. package/packs/0ad-civilizations/manifest.json +56 -56
  5. package/packs/0ad-civilizations/sounds/alarm-alert.ogg +0 -0
  6. package/packs/0ad-civilizations/sounds/alarm-attack.ogg +0 -0
  7. package/packs/0ad-civilizations/sounds/alarm-defeat.ogg +0 -0
  8. package/packs/0ad-civilizations/sounds/alarm-no-resource.ogg +0 -0
  9. package/packs/0ad-civilizations/sounds/alarm-objective.ogg +0 -0
  10. package/packs/0ad-civilizations/sounds/alarm-research-complete.ogg +0 -0
  11. package/packs/0ad-civilizations/sounds/alarm-victory.ogg +0 -0
  12. package/packs/0ad-civilizations/sounds/greek-as-you-wish.ogg +0 -0
  13. package/packs/0ad-civilizations/sounds/greek-attack-1.ogg +0 -0
  14. package/packs/0ad-civilizations/sounds/greek-build-1.ogg +0 -0
  15. package/packs/0ad-civilizations/sounds/greek-heal-1.ogg +0 -0
  16. package/packs/0ad-civilizations/sounds/greek-march-1.ogg +0 -0
  17. package/packs/0ad-civilizations/sounds/greek-my-lord-1.ogg +0 -0
  18. package/packs/0ad-civilizations/sounds/greek-my-lord-2.ogg +0 -0
  19. package/packs/0ad-civilizations/sounds/greek-retreat-1.ogg +0 -0
  20. package/packs/0ad-civilizations/sounds/greek-what-is-it-1.ogg +0 -0
  21. package/packs/0ad-civilizations/sounds/greek-yes.ogg +0 -0
  22. package/packs/0ad-civilizations/sounds/latin-attack-1.ogg +0 -0
  23. package/packs/0ad-civilizations/sounds/latin-build-1.ogg +0 -0
  24. package/packs/0ad-civilizations/sounds/latin-fight-1.ogg +0 -0
  25. package/packs/0ad-civilizations/sounds/latin-heal-1.ogg +0 -0
  26. package/packs/0ad-civilizations/sounds/latin-hello-1.ogg +0 -0
  27. package/packs/0ad-civilizations/sounds/latin-my-lord-1.ogg +0 -0
  28. package/packs/0ad-civilizations/sounds/latin-what-is-it.ogg +0 -0
  29. package/packs/0ad-civilizations/sounds/persian-attack-1.ogg +0 -0
  30. package/packs/0ad-civilizations/sounds/persian-by-your-order.ogg +0 -0
  31. package/packs/0ad-civilizations/sounds/persian-forward-1.ogg +0 -0
  32. package/packs/0ad-civilizations/sounds/persian-i-will-go.ogg +0 -0
  33. package/packs/7kaa-soldiers/manifest.json +90 -90
  34. package/packs/7kaa-soldiers/sounds/00001-CH_A2.ogg +0 -0
  35. package/packs/7kaa-soldiers/sounds/00006-NO_V2.ogg +0 -0
  36. package/packs/7kaa-soldiers/sounds/00007-NO_V3.ogg +0 -0
  37. package/packs/7kaa-soldiers/sounds/00008-MA_V1.ogg +0 -0
  38. package/packs/7kaa-soldiers/sounds/00009-GR_V2.ogg +0 -0
  39. package/packs/7kaa-soldiers/sounds/00010-GR_V3.ogg +0 -0
  40. package/packs/7kaa-soldiers/sounds/00011-VI_V1.ogg +0 -0
  41. package/packs/7kaa-soldiers/sounds/00012-NO_A1.ogg +0 -0
  42. package/packs/7kaa-soldiers/sounds/00012-VI_V2.ogg +0 -0
  43. package/packs/7kaa-soldiers/sounds/00013-GR_V1.ogg +0 -0
  44. package/packs/7kaa-soldiers/sounds/00013-NO_A2.ogg +0 -0
  45. package/packs/7kaa-soldiers/sounds/00014-MA_A1.ogg +0 -0
  46. package/packs/7kaa-soldiers/sounds/00014-PE_V1.ogg +0 -0
  47. package/packs/7kaa-soldiers/sounds/00015-GR_A1.ogg +0 -0
  48. package/packs/7kaa-soldiers/sounds/00015-MA_V2.ogg +0 -0
  49. package/packs/7kaa-soldiers/sounds/00016-MA_V3.ogg +0 -0
  50. package/packs/7kaa-soldiers/sounds/00016-VI_A1.ogg +0 -0
  51. package/packs/7kaa-soldiers/sounds/00017-MA_V4.ogg +0 -0
  52. package/packs/7kaa-soldiers/sounds/00017-VI_A2.ogg +0 -0
  53. package/packs/7kaa-soldiers/sounds/00018-PE_A2.ogg +0 -0
  54. package/packs/7kaa-soldiers/sounds/00018-PE_V2.ogg +0 -0
  55. package/packs/7kaa-soldiers/sounds/00019-PE_V3.ogg +0 -0
  56. package/packs/7kaa-soldiers/sounds/00020-PE_A1.ogg +0 -0
  57. package/packs/7kaa-soldiers/sounds/00020-PE_V4.ogg +0 -0
  58. package/packs/7kaa-soldiers/sounds/00021-CH_A1.ogg +0 -0
  59. package/packs/7kaa-soldiers/sounds/00021-CH_V1.ogg +0 -0
  60. package/packs/7kaa-soldiers/sounds/00022-CH_V2.ogg +0 -0
  61. package/packs/7kaa-soldiers/sounds/00022-JA_A1.ogg +0 -0
  62. package/packs/7kaa-soldiers/sounds/00023-CH_V3.ogg +0 -0
  63. package/packs/7kaa-soldiers/sounds/00023-JA_A2.ogg +0 -0
  64. package/packs/7kaa-soldiers/sounds/00024-CH_V4.ogg +0 -0
  65. package/packs/7kaa-soldiers/sounds/00025-CH_V5.ogg +0 -0
  66. package/packs/7kaa-soldiers/sounds/00026-JA_V1.ogg +0 -0
  67. package/packs/7kaa-soldiers/sounds/00027-JA_V2.ogg +0 -0
  68. package/packs/7kaa-soldiers/sounds/00028-JA_V3.ogg +0 -0
  69. package/packs/7kaa-soldiers/sounds/00029-JA_V4.ogg +0 -0
  70. package/packs/7kaa-soldiers/sounds/00030-JA_V5.ogg +0 -0
  71. package/packs/7kaa-soldiers/sounds/00035-MA_V5.ogg +0 -0
  72. package/packs/7kaa-soldiers/sounds/00083-READY.ogg +0 -0
  73. package/packs/7kaa-soldiers/sounds/00084-NO_V1.ogg +0 -0
  74. package/packs/7kaa-soldiers/sounds/00085-EG_V1.ogg +0 -0
  75. package/packs/7kaa-soldiers/sounds/00086-EG_V2.ogg +0 -0
  76. package/packs/7kaa-soldiers/sounds/00087-EG_V3.ogg +0 -0
  77. package/packs/7kaa-soldiers/sounds/00088-EG_V4.ogg +0 -0
  78. package/packs/7kaa-soldiers/sounds/00089-IN_V1.ogg +0 -0
  79. package/packs/7kaa-soldiers/sounds/00090-IN_V2.ogg +0 -0
  80. package/packs/7kaa-soldiers/sounds/00091-IN_V3.ogg +0 -0
  81. package/packs/7kaa-soldiers/sounds/00092-IN_V4.ogg +0 -0
  82. package/packs/7kaa-soldiers/sounds/00093-ZU_V2.ogg +0 -0
  83. package/packs/7kaa-soldiers/sounds/00094-ZU_V3.ogg +0 -0
  84. package/packs/7kaa-soldiers/sounds/00095-ZU_V5.ogg +0 -0
  85. package/packs/7kaa-soldiers/sounds/00096-ZU_V7.ogg +0 -0
  86. package/packs/7kaa-soldiers/sounds/00097-ZU_V8.ogg +0 -0
  87. package/packs/fighting-announcer/manifest.json +62 -0
  88. package/packs/fighting-announcer/sounds/begin.ogg +0 -0
  89. package/packs/fighting-announcer/sounds/complete.ogg +0 -0
  90. package/packs/fighting-announcer/sounds/congratulations.ogg +0 -0
  91. package/packs/fighting-announcer/sounds/defeat.ogg +0 -0
  92. package/packs/fighting-announcer/sounds/failure.ogg +0 -0
  93. package/packs/fighting-announcer/sounds/fight.ogg +0 -0
  94. package/packs/fighting-announcer/sounds/final-round.ogg +0 -0
  95. package/packs/fighting-announcer/sounds/finish.ogg +0 -0
  96. package/packs/fighting-announcer/sounds/game-over.ogg +0 -0
  97. package/packs/fighting-announcer/sounds/get-set.ogg +0 -0
  98. package/packs/fighting-announcer/sounds/go.ogg +0 -0
  99. package/packs/fighting-announcer/sounds/great.ogg +0 -0
  100. package/packs/fighting-announcer/sounds/high-score.ogg +0 -0
  101. package/packs/fighting-announcer/sounds/new-record.ogg +0 -0
  102. package/packs/fighting-announcer/sounds/no-contest.ogg +0 -0
  103. package/packs/fighting-announcer/sounds/ready.ogg +0 -0
  104. package/packs/fighting-announcer/sounds/sudden-death.ogg +0 -0
  105. package/packs/fighting-announcer/sounds/victory.ogg +0 -0
  106. package/packs/fighting-announcer/sounds/you-lose.ogg +0 -0
  107. package/packs/fighting-announcer/sounds/you-win.ogg +0 -0
  108. package/packs/freedoom-arsenal/manifest.json +36 -36
  109. package/packs/freedoom-arsenal/sounds/dsbarexp.ogg +0 -0
  110. package/packs/freedoom-arsenal/sounds/dsbfg.ogg +0 -0
  111. package/packs/freedoom-arsenal/sounds/dsdoropn.ogg +0 -0
  112. package/packs/freedoom-arsenal/sounds/dsdshtgn.ogg +0 -0
  113. package/packs/freedoom-arsenal/sounds/dsgetpow.ogg +0 -0
  114. package/packs/freedoom-arsenal/sounds/dsitemup.ogg +0 -0
  115. package/packs/freedoom-arsenal/sounds/dsoof.ogg +0 -0
  116. package/packs/freedoom-arsenal/sounds/dspistol.ogg +0 -0
  117. package/packs/freedoom-arsenal/sounds/dsplasma.ogg +0 -0
  118. package/packs/freedoom-arsenal/sounds/dspldeth.ogg +0 -0
  119. package/packs/freedoom-arsenal/sounds/dspunch.ogg +0 -0
  120. package/packs/freedoom-arsenal/sounds/dsrlaunc.ogg +0 -0
  121. package/packs/freedoom-arsenal/sounds/dsrxplod.ogg +0 -0
  122. package/packs/freedoom-arsenal/sounds/dssawup.ogg +0 -0
  123. package/packs/freedoom-arsenal/sounds/dssgcock.ogg +0 -0
  124. package/packs/freedoom-arsenal/sounds/dsshotgn.ogg +0 -0
  125. package/packs/freedoom-arsenal/sounds/dsswtchn.ogg +0 -0
  126. package/packs/freedoom-arsenal/sounds/dstelept.ogg +0 -0
  127. package/packs/freedoom-arsenal/sounds/dswpnup.ogg +0 -0
  128. package/packs/kenney-interface/manifest.json +36 -36
  129. package/packs/kenney-interface/sounds/back-1.ogg +0 -0
  130. package/packs/kenney-interface/sounds/bong.ogg +0 -0
  131. package/packs/kenney-interface/sounds/click-1.ogg +0 -0
  132. package/packs/kenney-interface/sounds/close-1.ogg +0 -0
  133. package/packs/kenney-interface/sounds/confirmation-1.ogg +0 -0
  134. package/packs/kenney-interface/sounds/confirmation-2.ogg +0 -0
  135. package/packs/kenney-interface/sounds/confirmation-3.ogg +0 -0
  136. package/packs/kenney-interface/sounds/error-1.ogg +0 -0
  137. package/packs/kenney-interface/sounds/error-2.ogg +0 -0
  138. package/packs/kenney-interface/sounds/error-3.ogg +0 -0
  139. package/packs/kenney-interface/sounds/glitch-1.ogg +0 -0
  140. package/packs/kenney-interface/sounds/maximize-1.ogg +0 -0
  141. package/packs/kenney-interface/sounds/maximize-2.ogg +0 -0
  142. package/packs/kenney-interface/sounds/open-1.ogg +0 -0
  143. package/packs/kenney-interface/sounds/pluck-1.ogg +0 -0
  144. package/packs/kenney-interface/sounds/question-1.ogg +0 -0
  145. package/packs/kenney-interface/sounds/question-2.ogg +0 -0
  146. package/packs/kenney-interface/sounds/question-3.ogg +0 -0
  147. package/packs/kenney-scifi/manifest.json +36 -36
  148. package/packs/kenney-scifi/sounds/computer-1.ogg +0 -0
  149. package/packs/kenney-scifi/sounds/computer-2.ogg +0 -0
  150. package/packs/kenney-scifi/sounds/computer-3.ogg +0 -0
  151. package/packs/kenney-scifi/sounds/door-close.ogg +0 -0
  152. package/packs/kenney-scifi/sounds/door-open.ogg +0 -0
  153. package/packs/kenney-scifi/sounds/engine-1.ogg +0 -0
  154. package/packs/kenney-scifi/sounds/explosion-1.ogg +0 -0
  155. package/packs/kenney-scifi/sounds/explosion-2.ogg +0 -0
  156. package/packs/kenney-scifi/sounds/forcefield-1.ogg +0 -0
  157. package/packs/kenney-scifi/sounds/forcefield-2.ogg +0 -0
  158. package/packs/kenney-scifi/sounds/impact-1.ogg +0 -0
  159. package/packs/kenney-scifi/sounds/impact-2.ogg +0 -0
  160. package/packs/kenney-scifi/sounds/laser-large.ogg +0 -0
  161. package/packs/kenney-scifi/sounds/laser-retro.ogg +0 -0
  162. package/packs/kenney-scifi/sounds/low-explosion.ogg +0 -0
  163. package/packs/kenney-scifi/sounds/slime.ogg +0 -0
  164. package/packs/kenney-scifi/sounds/thruster-1.ogg +0 -0
  165. package/packs/kenney-scifi/sounds/thruster-2.ogg +0 -0
  166. package/packs/openarena-announcer/manifest.json +33 -33
  167. package/packs/openarena-announcer/sounds/accuracy.ogg +0 -0
  168. package/packs/openarena-announcer/sounds/assist.ogg +0 -0
  169. package/packs/openarena-announcer/sounds/defense.ogg +0 -0
  170. package/packs/openarena-announcer/sounds/denied.ogg +0 -0
  171. package/packs/openarena-announcer/sounds/excellent.ogg +0 -0
  172. package/packs/openarena-announcer/sounds/fight.ogg +0 -0
  173. package/packs/openarena-announcer/sounds/gauntlet.ogg +0 -0
  174. package/packs/openarena-announcer/sounds/humiliation.ogg +0 -0
  175. package/packs/openarena-announcer/sounds/impressive.ogg +0 -0
  176. package/packs/openarena-announcer/sounds/one.ogg +0 -0
  177. package/packs/openarena-announcer/sounds/perfect.ogg +0 -0
  178. package/packs/openarena-announcer/sounds/prepare.ogg +0 -0
  179. package/packs/openarena-announcer/sounds/sudden_death.ogg +0 -0
  180. package/packs/openarena-announcer/sounds/takenlead.ogg +0 -0
  181. package/packs/openarena-announcer/sounds/three.ogg +0 -0
  182. package/packs/openarena-announcer/sounds/two.ogg +0 -0
  183. package/packs/openarena-announcer/sounds/voc_holyshit.ogg +0 -0
  184. package/packs/openarena-announcer/sounds/youwin.ogg +0 -0
  185. package/packs/retro-8bit/manifest.json +36 -36
  186. package/packs/retro-8bit/sounds/button-1.ogg +0 -0
  187. package/packs/retro-8bit/sounds/button-2.ogg +0 -0
  188. package/packs/retro-8bit/sounds/button-3.ogg +0 -0
  189. package/packs/retro-8bit/sounds/coin-1.ogg +0 -0
  190. package/packs/retro-8bit/sounds/coin-2.ogg +0 -0
  191. package/packs/retro-8bit/sounds/coin-cluster.ogg +0 -0
  192. package/packs/retro-8bit/sounds/coin-double.ogg +0 -0
  193. package/packs/retro-8bit/sounds/damage-1.ogg +0 -0
  194. package/packs/retro-8bit/sounds/error-1.ogg +0 -0
  195. package/packs/retro-8bit/sounds/error-2.ogg +0 -0
  196. package/packs/retro-8bit/sounds/error-3.ogg +0 -0
  197. package/packs/retro-8bit/sounds/fanfare-1.ogg +0 -0
  198. package/packs/retro-8bit/sounds/fanfare-2.ogg +0 -0
  199. package/packs/retro-8bit/sounds/fanfare-3.ogg +0 -0
  200. package/packs/retro-8bit/sounds/negative-1.ogg +0 -0
  201. package/packs/retro-8bit/sounds/negative-2.ogg +0 -0
  202. package/packs/retro-8bit/sounds/powerup-1.ogg +0 -0
  203. package/packs/retro-8bit/sounds/powerup-2.ogg +0 -0
  204. package/packs/warzone2100-command/manifest.json +42 -42
  205. package/packs/warzone2100-command/sounds/construction-completed.ogg +0 -0
  206. package/packs/warzone2100-command/sounds/design-completed.ogg +0 -0
  207. package/packs/warzone2100-command/sounds/enemy-detected.ogg +0 -0
  208. package/packs/warzone2100-command/sounds/incoming-enemy-transport.ogg +0 -0
  209. package/packs/warzone2100-command/sounds/incoming-transmission.ogg +0 -0
  210. package/packs/warzone2100-command/sounds/major-research-completed.ogg +0 -0
  211. package/packs/warzone2100-command/sounds/mission-failed.ogg +0 -0
  212. package/packs/warzone2100-command/sounds/mission-objective.ogg +0 -0
  213. package/packs/warzone2100-command/sounds/mission-successful.ogg +0 -0
  214. package/packs/warzone2100-command/sounds/mission-update.ogg +0 -0
  215. package/packs/warzone2100-command/sounds/new-research-available.ogg +0 -0
  216. package/packs/warzone2100-command/sounds/nexus-laugh1.ogg +0 -0
  217. package/packs/warzone2100-command/sounds/nexus-laugh2.ogg +0 -0
  218. package/packs/warzone2100-command/sounds/power-low.ogg +0 -0
  219. package/packs/warzone2100-command/sounds/production-cancelled.ogg +0 -0
  220. package/packs/warzone2100-command/sounds/production-completed.ogg +0 -0
  221. package/packs/warzone2100-command/sounds/reinforcements-available.ogg +0 -0
  222. package/packs/warzone2100-command/sounds/research-completed.ogg +0 -0
  223. package/packs/warzone2100-command/sounds/retreat-heavy-damage.ogg +0 -0
  224. package/packs/warzone2100-command/sounds/structure-under-attack.ogg +0 -0
  225. package/packs/warzone2100-command/sounds/unit-repaired.ogg +0 -0
  226. package/packs/wesnoth-combat/manifest.json +38 -38
  227. package/packs/wesnoth-combat/sounds/axe.ogg +0 -0
  228. package/packs/wesnoth-combat/sounds/bow.ogg +0 -0
  229. package/packs/wesnoth-combat/sounds/crossbow.ogg +0 -0
  230. package/packs/wesnoth-combat/sounds/dwarf-laugh.ogg +0 -0
  231. package/packs/wesnoth-combat/sounds/explosion.ogg +0 -0
  232. package/packs/wesnoth-combat/sounds/fanfare-short.ogg +0 -0
  233. package/packs/wesnoth-combat/sounds/flame-big.ogg +0 -0
  234. package/packs/wesnoth-combat/sounds/gold.ogg +0 -0
  235. package/packs/wesnoth-combat/sounds/gryphon-shriek-1.ogg +0 -0
  236. package/packs/wesnoth-combat/sounds/hatchet.ogg +0 -0
  237. package/packs/wesnoth-combat/sounds/heal.ogg +0 -0
  238. package/packs/wesnoth-combat/sounds/lightning.ogg +0 -0
  239. package/packs/wesnoth-combat/sounds/magic-dark.ogg +0 -0
  240. package/packs/wesnoth-combat/sounds/magic-holy-1.ogg +0 -0
  241. package/packs/wesnoth-combat/sounds/magic-missile-1.ogg +0 -0
  242. package/packs/wesnoth-combat/sounds/open-chest.ogg +0 -0
  243. package/packs/wesnoth-combat/sounds/potion.ogg +0 -0
  244. package/packs/wesnoth-combat/sounds/sword-1.ogg +0 -0
  245. package/packs/wesnoth-combat/sounds/wolf-growl-1.ogg +0 -0
  246. package/packs/xonotic-announcer/manifest.json +52 -0
  247. package/packs/xonotic-announcer/sounds/1fragleft.ogg +0 -0
  248. package/packs/xonotic-announcer/sounds/airshot.ogg +0 -0
  249. package/packs/xonotic-announcer/sounds/amazing.ogg +0 -0
  250. package/packs/xonotic-announcer/sounds/awesome.ogg +0 -0
  251. package/packs/xonotic-announcer/sounds/begin.ogg +0 -0
  252. package/packs/xonotic-announcer/sounds/botlike.ogg +0 -0
  253. package/packs/xonotic-announcer/sounds/electrobitch.ogg +0 -0
  254. package/packs/xonotic-announcer/sounds/headshot.ogg +0 -0
  255. package/packs/xonotic-announcer/sounds/impressive.ogg +0 -0
  256. package/packs/xonotic-announcer/sounds/lastsecond.ogg +0 -0
  257. package/packs/xonotic-announcer/sounds/leadgained.ogg +0 -0
  258. package/packs/xonotic-announcer/sounds/narrowly.ogg +0 -0
  259. package/packs/xonotic-announcer/sounds/prepareforbattle.ogg +0 -0
  260. package/packs/xonotic-announcer/sounds/terminated.ogg +0 -0
  261. package/packs/xonotic-announcer/sounds/yoda.ogg +0 -0
  262. package/src/cli/completions.js +182 -0
  263. package/src/cli/config.js +32 -3
  264. package/src/cli/create.js +1 -1
  265. package/src/cli/doctor.js +134 -0
  266. package/src/cli/first-run.js +59 -0
  267. package/src/cli/init.js +11 -8
  268. package/src/cli/play.js +32 -2
  269. package/src/cli/random-pack.js +44 -0
  270. package/src/cli/select.js +36 -32
  271. package/src/cli/uninstall.js +5 -0
  272. package/src/cli/update.js +43 -0
  273. package/src/config.js +35 -0
  274. package/src/notify.js +19 -0
  275. package/src/player.js +17 -12
  276. package/packs/0ad-civilizations/sounds/alarm-alert.wav +0 -0
  277. package/packs/0ad-civilizations/sounds/alarm-attack.wav +0 -0
  278. package/packs/0ad-civilizations/sounds/alarm-defeat.wav +0 -0
  279. package/packs/0ad-civilizations/sounds/alarm-no-resource.wav +0 -0
  280. package/packs/0ad-civilizations/sounds/alarm-objective.wav +0 -0
  281. package/packs/0ad-civilizations/sounds/alarm-research-complete.wav +0 -0
  282. package/packs/0ad-civilizations/sounds/alarm-victory.wav +0 -0
  283. package/packs/0ad-civilizations/sounds/greek-as-you-wish.wav +0 -0
  284. package/packs/0ad-civilizations/sounds/greek-attack-1.wav +0 -0
  285. package/packs/0ad-civilizations/sounds/greek-build-1.wav +0 -0
  286. package/packs/0ad-civilizations/sounds/greek-heal-1.wav +0 -0
  287. package/packs/0ad-civilizations/sounds/greek-march-1.wav +0 -0
  288. package/packs/0ad-civilizations/sounds/greek-my-lord-1.wav +0 -0
  289. package/packs/0ad-civilizations/sounds/greek-my-lord-2.wav +0 -0
  290. package/packs/0ad-civilizations/sounds/greek-retreat-1.wav +0 -0
  291. package/packs/0ad-civilizations/sounds/greek-what-is-it-1.wav +0 -0
  292. package/packs/0ad-civilizations/sounds/greek-yes.wav +0 -0
  293. package/packs/0ad-civilizations/sounds/latin-attack-1.wav +0 -0
  294. package/packs/0ad-civilizations/sounds/latin-build-1.wav +0 -0
  295. package/packs/0ad-civilizations/sounds/latin-fight-1.wav +0 -0
  296. package/packs/0ad-civilizations/sounds/latin-heal-1.wav +0 -0
  297. package/packs/0ad-civilizations/sounds/latin-hello-1.wav +0 -0
  298. package/packs/0ad-civilizations/sounds/latin-my-lord-1.wav +0 -0
  299. package/packs/0ad-civilizations/sounds/latin-what-is-it.wav +0 -0
  300. package/packs/0ad-civilizations/sounds/persian-attack-1.wav +0 -0
  301. package/packs/0ad-civilizations/sounds/persian-by-your-order.wav +0 -0
  302. package/packs/0ad-civilizations/sounds/persian-forward-1.wav +0 -0
  303. package/packs/0ad-civilizations/sounds/persian-i-will-go.wav +0 -0
  304. package/packs/7kaa-soldiers/sounds/00001-CH_A2.wav +0 -0
  305. package/packs/7kaa-soldiers/sounds/00006-NO_V2.wav +0 -0
  306. package/packs/7kaa-soldiers/sounds/00007-NO_V3.wav +0 -0
  307. package/packs/7kaa-soldiers/sounds/00008-MA_V1.wav +0 -0
  308. package/packs/7kaa-soldiers/sounds/00009-GR_V2.wav +0 -0
  309. package/packs/7kaa-soldiers/sounds/00010-GR_V3.wav +0 -0
  310. package/packs/7kaa-soldiers/sounds/00011-VI_V1.wav +0 -0
  311. package/packs/7kaa-soldiers/sounds/00012-NO_A1.wav +0 -0
  312. package/packs/7kaa-soldiers/sounds/00012-VI_V2.wav +0 -0
  313. package/packs/7kaa-soldiers/sounds/00013-GR_V1.wav +0 -0
  314. package/packs/7kaa-soldiers/sounds/00013-NO_A2.wav +0 -0
  315. package/packs/7kaa-soldiers/sounds/00014-MA_A1.wav +0 -0
  316. package/packs/7kaa-soldiers/sounds/00014-PE_V1.wav +0 -0
  317. package/packs/7kaa-soldiers/sounds/00015-GR_A1.wav +0 -0
  318. package/packs/7kaa-soldiers/sounds/00015-MA_V2.wav +0 -0
  319. package/packs/7kaa-soldiers/sounds/00016-MA_V3.wav +0 -0
  320. package/packs/7kaa-soldiers/sounds/00016-VI_A1.wav +0 -0
  321. package/packs/7kaa-soldiers/sounds/00017-MA_V4.wav +0 -0
  322. package/packs/7kaa-soldiers/sounds/00017-VI_A2.wav +0 -0
  323. package/packs/7kaa-soldiers/sounds/00018-PE_A2.wav +0 -0
  324. package/packs/7kaa-soldiers/sounds/00018-PE_V2.wav +0 -0
  325. package/packs/7kaa-soldiers/sounds/00019-PE_V3.wav +0 -0
  326. package/packs/7kaa-soldiers/sounds/00020-PE_A1.wav +0 -0
  327. package/packs/7kaa-soldiers/sounds/00020-PE_V4.wav +0 -0
  328. package/packs/7kaa-soldiers/sounds/00021-CH_A1.wav +0 -0
  329. package/packs/7kaa-soldiers/sounds/00021-CH_V1.wav +0 -0
  330. package/packs/7kaa-soldiers/sounds/00022-CH_V2.wav +0 -0
  331. package/packs/7kaa-soldiers/sounds/00022-JA_A1.wav +0 -0
  332. package/packs/7kaa-soldiers/sounds/00023-CH_V3.wav +0 -0
  333. package/packs/7kaa-soldiers/sounds/00023-JA_A2.wav +0 -0
  334. package/packs/7kaa-soldiers/sounds/00024-CH_V4.wav +0 -0
  335. package/packs/7kaa-soldiers/sounds/00025-CH_V5.wav +0 -0
  336. package/packs/7kaa-soldiers/sounds/00026-JA_V1.wav +0 -0
  337. package/packs/7kaa-soldiers/sounds/00027-JA_V2.wav +0 -0
  338. package/packs/7kaa-soldiers/sounds/00028-JA_V3.wav +0 -0
  339. package/packs/7kaa-soldiers/sounds/00029-JA_V4.wav +0 -0
  340. package/packs/7kaa-soldiers/sounds/00030-JA_V5.wav +0 -0
  341. package/packs/7kaa-soldiers/sounds/00035-MA_V5.wav +0 -0
  342. package/packs/7kaa-soldiers/sounds/00083-READY.wav +0 -0
  343. package/packs/7kaa-soldiers/sounds/00084-NO_V1.wav +0 -0
  344. package/packs/7kaa-soldiers/sounds/00085-EG_V1.wav +0 -0
  345. package/packs/7kaa-soldiers/sounds/00086-EG_V2.wav +0 -0
  346. package/packs/7kaa-soldiers/sounds/00087-EG_V3.wav +0 -0
  347. package/packs/7kaa-soldiers/sounds/00088-EG_V4.wav +0 -0
  348. package/packs/7kaa-soldiers/sounds/00089-IN_V1.wav +0 -0
  349. package/packs/7kaa-soldiers/sounds/00090-IN_V2.wav +0 -0
  350. package/packs/7kaa-soldiers/sounds/00091-IN_V3.wav +0 -0
  351. package/packs/7kaa-soldiers/sounds/00092-IN_V4.wav +0 -0
  352. package/packs/7kaa-soldiers/sounds/00093-ZU_V2.wav +0 -0
  353. package/packs/7kaa-soldiers/sounds/00094-ZU_V3.wav +0 -0
  354. package/packs/7kaa-soldiers/sounds/00095-ZU_V5.wav +0 -0
  355. package/packs/7kaa-soldiers/sounds/00096-ZU_V7.wav +0 -0
  356. package/packs/7kaa-soldiers/sounds/00097-ZU_V8.wav +0 -0
  357. package/packs/freedoom-arsenal/sounds/dsbarexp.wav +0 -0
  358. package/packs/freedoom-arsenal/sounds/dsbfg.wav +0 -0
  359. package/packs/freedoom-arsenal/sounds/dsdoropn.wav +0 -0
  360. package/packs/freedoom-arsenal/sounds/dsdshtgn.wav +0 -0
  361. package/packs/freedoom-arsenal/sounds/dsgetpow.wav +0 -0
  362. package/packs/freedoom-arsenal/sounds/dsitemup.wav +0 -0
  363. package/packs/freedoom-arsenal/sounds/dsoof.wav +0 -0
  364. package/packs/freedoom-arsenal/sounds/dspistol.wav +0 -0
  365. package/packs/freedoom-arsenal/sounds/dsplasma.wav +0 -0
  366. package/packs/freedoom-arsenal/sounds/dspldeth.wav +0 -0
  367. package/packs/freedoom-arsenal/sounds/dspunch.wav +0 -0
  368. package/packs/freedoom-arsenal/sounds/dsrlaunc.wav +0 -0
  369. package/packs/freedoom-arsenal/sounds/dsrxplod.wav +0 -0
  370. package/packs/freedoom-arsenal/sounds/dssawup.wav +0 -0
  371. package/packs/freedoom-arsenal/sounds/dssgcock.wav +0 -0
  372. package/packs/freedoom-arsenal/sounds/dsshotgn.wav +0 -0
  373. package/packs/freedoom-arsenal/sounds/dsswtchn.wav +0 -0
  374. package/packs/freedoom-arsenal/sounds/dstelept.wav +0 -0
  375. package/packs/freedoom-arsenal/sounds/dswpnup.wav +0 -0
  376. package/packs/kenney-interface/sounds/back-1.wav +0 -0
  377. package/packs/kenney-interface/sounds/bong.wav +0 -0
  378. package/packs/kenney-interface/sounds/click-1.wav +0 -0
  379. package/packs/kenney-interface/sounds/close-1.wav +0 -0
  380. package/packs/kenney-interface/sounds/confirmation-1.wav +0 -0
  381. package/packs/kenney-interface/sounds/confirmation-2.wav +0 -0
  382. package/packs/kenney-interface/sounds/confirmation-3.wav +0 -0
  383. package/packs/kenney-interface/sounds/error-1.wav +0 -0
  384. package/packs/kenney-interface/sounds/error-2.wav +0 -0
  385. package/packs/kenney-interface/sounds/error-3.wav +0 -0
  386. package/packs/kenney-interface/sounds/glitch-1.wav +0 -0
  387. package/packs/kenney-interface/sounds/maximize-1.wav +0 -0
  388. package/packs/kenney-interface/sounds/maximize-2.wav +0 -0
  389. package/packs/kenney-interface/sounds/open-1.wav +0 -0
  390. package/packs/kenney-interface/sounds/pluck-1.wav +0 -0
  391. package/packs/kenney-interface/sounds/question-1.wav +0 -0
  392. package/packs/kenney-interface/sounds/question-2.wav +0 -0
  393. package/packs/kenney-interface/sounds/question-3.wav +0 -0
  394. package/packs/kenney-scifi/sounds/computer-1.wav +0 -0
  395. package/packs/kenney-scifi/sounds/computer-2.wav +0 -0
  396. package/packs/kenney-scifi/sounds/computer-3.wav +0 -0
  397. package/packs/kenney-scifi/sounds/door-close.wav +0 -0
  398. package/packs/kenney-scifi/sounds/door-open.wav +0 -0
  399. package/packs/kenney-scifi/sounds/engine-1.wav +0 -0
  400. package/packs/kenney-scifi/sounds/explosion-1.wav +0 -0
  401. package/packs/kenney-scifi/sounds/explosion-2.wav +0 -0
  402. package/packs/kenney-scifi/sounds/forcefield-1.wav +0 -0
  403. package/packs/kenney-scifi/sounds/forcefield-2.wav +0 -0
  404. package/packs/kenney-scifi/sounds/impact-1.wav +0 -0
  405. package/packs/kenney-scifi/sounds/impact-2.wav +0 -0
  406. package/packs/kenney-scifi/sounds/laser-large.wav +0 -0
  407. package/packs/kenney-scifi/sounds/laser-retro.wav +0 -0
  408. package/packs/kenney-scifi/sounds/low-explosion.wav +0 -0
  409. package/packs/kenney-scifi/sounds/slime.wav +0 -0
  410. package/packs/kenney-scifi/sounds/thruster-1.wav +0 -0
  411. package/packs/kenney-scifi/sounds/thruster-2.wav +0 -0
  412. package/packs/openarena-announcer/sounds/accuracy.wav +0 -0
  413. package/packs/openarena-announcer/sounds/assist.wav +0 -0
  414. package/packs/openarena-announcer/sounds/defense.wav +0 -0
  415. package/packs/openarena-announcer/sounds/denied.wav +0 -0
  416. package/packs/openarena-announcer/sounds/excellent.wav +0 -0
  417. package/packs/openarena-announcer/sounds/fight.wav +0 -0
  418. package/packs/openarena-announcer/sounds/gauntlet.wav +0 -0
  419. package/packs/openarena-announcer/sounds/humiliation.wav +0 -0
  420. package/packs/openarena-announcer/sounds/impressive.wav +0 -0
  421. package/packs/openarena-announcer/sounds/one.wav +0 -0
  422. package/packs/openarena-announcer/sounds/perfect.wav +0 -0
  423. package/packs/openarena-announcer/sounds/prepare.wav +0 -0
  424. package/packs/openarena-announcer/sounds/sudden_death.wav +0 -0
  425. package/packs/openarena-announcer/sounds/takenlead.wav +0 -0
  426. package/packs/openarena-announcer/sounds/three.wav +0 -0
  427. package/packs/openarena-announcer/sounds/two.wav +0 -0
  428. package/packs/openarena-announcer/sounds/voc_holyshit.wav +0 -0
  429. package/packs/openarena-announcer/sounds/youwin.wav +0 -0
  430. package/packs/retro-8bit/sounds/button-1.wav +0 -0
  431. package/packs/retro-8bit/sounds/button-2.wav +0 -0
  432. package/packs/retro-8bit/sounds/button-3.wav +0 -0
  433. package/packs/retro-8bit/sounds/coin-1.wav +0 -0
  434. package/packs/retro-8bit/sounds/coin-2.wav +0 -0
  435. package/packs/retro-8bit/sounds/coin-cluster.wav +0 -0
  436. package/packs/retro-8bit/sounds/coin-double.wav +0 -0
  437. package/packs/retro-8bit/sounds/damage-1.wav +0 -0
  438. package/packs/retro-8bit/sounds/error-1.wav +0 -0
  439. package/packs/retro-8bit/sounds/error-2.wav +0 -0
  440. package/packs/retro-8bit/sounds/error-3.wav +0 -0
  441. package/packs/retro-8bit/sounds/fanfare-1.wav +0 -0
  442. package/packs/retro-8bit/sounds/fanfare-2.wav +0 -0
  443. package/packs/retro-8bit/sounds/fanfare-3.wav +0 -0
  444. package/packs/retro-8bit/sounds/negative-1.wav +0 -0
  445. package/packs/retro-8bit/sounds/negative-2.wav +0 -0
  446. package/packs/retro-8bit/sounds/powerup-1.wav +0 -0
  447. package/packs/retro-8bit/sounds/powerup-2.wav +0 -0
  448. package/packs/warzone2100-command/sounds/construction-completed.wav +0 -0
  449. package/packs/warzone2100-command/sounds/design-completed.wav +0 -0
  450. package/packs/warzone2100-command/sounds/enemy-detected.wav +0 -0
  451. package/packs/warzone2100-command/sounds/incoming-enemy-transport.wav +0 -0
  452. package/packs/warzone2100-command/sounds/incoming-transmission.wav +0 -0
  453. package/packs/warzone2100-command/sounds/major-research-completed.wav +0 -0
  454. package/packs/warzone2100-command/sounds/mission-failed.wav +0 -0
  455. package/packs/warzone2100-command/sounds/mission-objective.wav +0 -0
  456. package/packs/warzone2100-command/sounds/mission-successful.wav +0 -0
  457. package/packs/warzone2100-command/sounds/mission-update.wav +0 -0
  458. package/packs/warzone2100-command/sounds/new-research-available.wav +0 -0
  459. package/packs/warzone2100-command/sounds/nexus-laugh1.wav +0 -0
  460. package/packs/warzone2100-command/sounds/nexus-laugh2.wav +0 -0
  461. package/packs/warzone2100-command/sounds/power-low.wav +0 -0
  462. package/packs/warzone2100-command/sounds/production-cancelled.wav +0 -0
  463. package/packs/warzone2100-command/sounds/production-completed.wav +0 -0
  464. package/packs/warzone2100-command/sounds/reinforcements-available.wav +0 -0
  465. package/packs/warzone2100-command/sounds/research-completed.wav +0 -0
  466. package/packs/warzone2100-command/sounds/retreat-heavy-damage.wav +0 -0
  467. package/packs/warzone2100-command/sounds/structure-under-attack.wav +0 -0
  468. package/packs/warzone2100-command/sounds/unit-repaired.wav +0 -0
  469. package/packs/wesnoth-combat/sounds/axe.wav +0 -0
  470. package/packs/wesnoth-combat/sounds/bow.wav +0 -0
  471. package/packs/wesnoth-combat/sounds/crossbow.wav +0 -0
  472. package/packs/wesnoth-combat/sounds/dwarf-laugh.wav +0 -0
  473. package/packs/wesnoth-combat/sounds/explosion.wav +0 -0
  474. package/packs/wesnoth-combat/sounds/fanfare-short.wav +0 -0
  475. package/packs/wesnoth-combat/sounds/flame-big.wav +0 -0
  476. package/packs/wesnoth-combat/sounds/gold.wav +0 -0
  477. package/packs/wesnoth-combat/sounds/gryphon-shriek-1.wav +0 -0
  478. package/packs/wesnoth-combat/sounds/hatchet.wav +0 -0
  479. package/packs/wesnoth-combat/sounds/heal.wav +0 -0
  480. package/packs/wesnoth-combat/sounds/lightning.wav +0 -0
  481. package/packs/wesnoth-combat/sounds/magic-dark.wav +0 -0
  482. package/packs/wesnoth-combat/sounds/magic-holy-1.wav +0 -0
  483. package/packs/wesnoth-combat/sounds/magic-missile-1.wav +0 -0
  484. package/packs/wesnoth-combat/sounds/open-chest.wav +0 -0
  485. package/packs/wesnoth-combat/sounds/potion.wav +0 -0
  486. package/packs/wesnoth-combat/sounds/sword-1.wav +0 -0
  487. package/packs/wesnoth-combat/sounds/wolf-growl-1.wav +0 -0
@@ -0,0 +1,182 @@
1
+ function showHelp() {
2
+ console.log(`
3
+ Usage: pingthings completions <shell>
4
+
5
+ Generate shell completion script.
6
+
7
+ Arguments:
8
+ shell bash, zsh, or fish
9
+
10
+ Install:
11
+ pingthings completions bash >> ~/.bashrc
12
+ pingthings completions zsh >> ~/.zshrc
13
+ pingthings completions fish > ~/.config/fish/completions/pingthings.fish
14
+ `);
15
+ }
16
+
17
+ const COMMANDS = [
18
+ 'play', 'list', 'select', 'browse', 'search', 'sounds',
19
+ 'use', 'preview', 'test-events', 'theme', 'config',
20
+ 'init', 'create', 'install', 'uninstall', 'random-pack', 'doctor', 'completions',
21
+ ];
22
+
23
+ const THEMES = [
24
+ 'retro', 'sci-fi', 'arena', 'fantasy', 'ancient',
25
+ 'professional', '8bit', 'space', 'chaos', 'reset',
26
+ ];
27
+
28
+ const EVENTS = ['done', 'permission', 'complete', 'error', 'blocked'];
29
+
30
+ function bash() {
31
+ return `# pingthings bash completions
32
+ _pingthings() {
33
+ local cur prev commands
34
+ COMPREPLY=()
35
+ cur="\${COMP_WORDS[COMP_CWORD]}"
36
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
37
+ commands="${COMMANDS.join(' ')}"
38
+
39
+ case "\${prev}" in
40
+ pingthings)
41
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
42
+ return 0
43
+ ;;
44
+ use|preview|sounds|test-events|uninstall)
45
+ local packs=$(pingthings list 2>/dev/null | grep -oE '\\S+-\\S+' | head -20)
46
+ COMPREPLY=( $(compgen -W "\${packs}" -- "\${cur}") )
47
+ return 0
48
+ ;;
49
+ theme)
50
+ COMPREPLY=( $(compgen -W "${THEMES.join(' ')}" -- "\${cur}") )
51
+ return 0
52
+ ;;
53
+ --event|-e)
54
+ COMPREPLY=( $(compgen -W "${EVENTS.join(' ')}" -- "\${cur}") )
55
+ return 0
56
+ ;;
57
+ browse)
58
+ local cats="military arena fantasy sci-fi fps retro ui transport spooky racing"
59
+ COMPREPLY=( $(compgen -W "\${cats}" -- "\${cur}") )
60
+ return 0
61
+ ;;
62
+ completions)
63
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
64
+ return 0
65
+ ;;
66
+ esac
67
+
68
+ return 0
69
+ }
70
+ complete -F _pingthings pingthings`;
71
+ }
72
+
73
+ function zsh() {
74
+ return `# pingthings zsh completions
75
+ _pingthings() {
76
+ local -a commands themes events
77
+ commands=(
78
+ 'play:Play a sound from the active pack'
79
+ 'list:Show available sound packs'
80
+ 'select:Interactive pack selector'
81
+ 'browse:Browse packs by category'
82
+ 'search:Search packs and sounds'
83
+ 'sounds:List individual sounds in a pack'
84
+ 'use:Set the active sound pack'
85
+ 'preview:Preview a random sound from a pack'
86
+ 'test-events:Play all event sounds'
87
+ 'theme:Apply a sound theme'
88
+ 'config:Show or update configuration'
89
+ 'init:Set up Claude Code hooks'
90
+ 'create:Create a pack from audio files'
91
+ 'install:Install a pack from GitHub or local path'
92
+ 'uninstall:Remove a user-installed pack'
93
+ 'random-pack:Switch to a random pack'
94
+ 'doctor:Diagnose audio setup'
95
+ 'completions:Generate shell completions'
96
+ )
97
+ themes=(${THEMES.join(' ')})
98
+ events=(${EVENTS.join(' ')})
99
+
100
+ _arguments '1:command:->cmds' '*::arg:->args'
101
+
102
+ case "$state" in
103
+ cmds)
104
+ _describe -t commands 'pingthings command' commands
105
+ ;;
106
+ args)
107
+ case $words[1] in
108
+ use|preview|sounds|test-events|uninstall)
109
+ local packs=($(pingthings list 2>/dev/null | grep -oE '\\S+-\\S+' | head -20))
110
+ _describe -t packs 'pack' packs
111
+ ;;
112
+ theme)
113
+ _describe -t themes 'theme' themes
114
+ ;;
115
+ browse)
116
+ local cats=(military arena fantasy sci-fi fps retro ui transport spooky racing)
117
+ _describe -t categories 'category' cats
118
+ ;;
119
+ completions)
120
+ _describe -t shells 'shell' '(bash zsh fish)'
121
+ ;;
122
+ esac
123
+ ;;
124
+ esac
125
+ }
126
+ compdef _pingthings pingthings`;
127
+ }
128
+
129
+ function fish() {
130
+ const lines = [
131
+ '# pingthings fish completions',
132
+ 'complete -c pingthings -f',
133
+ ];
134
+
135
+ const cmdDescs = {
136
+ play: 'Play a sound', list: 'Show packs', select: 'Interactive picker',
137
+ browse: 'Browse by category', search: 'Search packs', sounds: 'List sounds',
138
+ use: 'Set active pack', preview: 'Preview a pack', 'test-events': 'Test event sounds',
139
+ theme: 'Apply a theme', config: 'Configuration', init: 'Set up hooks',
140
+ create: 'Create a pack', install: 'Install a pack', uninstall: 'Remove a pack',
141
+ 'random-pack': 'Random pack', doctor: 'Diagnose setup', completions: 'Shell completions',
142
+ };
143
+
144
+ for (const [cmd, desc] of Object.entries(cmdDescs)) {
145
+ lines.push(`complete -c pingthings -n '__fish_use_subcommand' -a '${cmd}' -d '${desc}'`);
146
+ }
147
+
148
+ for (const t of THEMES) {
149
+ lines.push(`complete -c pingthings -n '__fish_seen_subcommand_from theme' -a '${t}'`);
150
+ }
151
+
152
+ for (const e of EVENTS) {
153
+ lines.push(`complete -c pingthings -n '__fish_seen_subcommand_from play' -l 'event' -a '${e}'`);
154
+ }
155
+
156
+ return lines.join('\n');
157
+ }
158
+
159
+ export default function completions(args) {
160
+ const shell = args[0];
161
+
162
+ if (!shell || shell === '--help' || shell === '-h') {
163
+ showHelp();
164
+ if (!shell) process.exit(1);
165
+ return;
166
+ }
167
+
168
+ switch (shell) {
169
+ case 'bash':
170
+ console.log(bash());
171
+ break;
172
+ case 'zsh':
173
+ console.log(zsh());
174
+ break;
175
+ case 'fish':
176
+ console.log(fish());
177
+ break;
178
+ default:
179
+ console.error(`Unknown shell: ${shell}. Use bash, zsh, or fish.`);
180
+ process.exit(1);
181
+ }
182
+ }
package/src/cli/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readConfig, writeConfig, VALID_MODES, VALID_EVENTS } from '../config.js';
2
2
 
3
- const VALID_KEYS = ['activePack', 'mode', 'specificSound', 'volume', 'eventPacks'];
3
+ const VALID_KEYS = ['activePack', 'mode', 'specificSound', 'volume', 'eventPacks', 'cooldown', 'quietHours', 'notifications'];
4
4
 
5
5
  function showHelp() {
6
6
  console.log(`
@@ -17,13 +17,16 @@ Keys:
17
17
  mode random, specific, or informational
18
18
  specificSound Sound name for specific mode
19
19
  volume Playback volume (0-100)
20
+ cooldown Avoid repeating the same sound (true/false)
21
+ quietHours Mute during hours, e.g. "22-7" (10pm-7am)
20
22
  eventPacks Per-event pack overrides (use "pingthings config eventPacks.<event> <pack>")
21
23
 
22
24
  Examples:
23
25
  pingthings config Show full config
24
- pingthings config volume Show current volume
25
26
  pingthings config volume 50 Set volume to 50%
26
- pingthings config mode informational Enable event-based sounds
27
+ pingthings config cooldown false Disable cooldown
28
+ pingthings config quietHours 22-7 Mute 10pm to 7am
29
+ pingthings config quietHours null Disable quiet hours
27
30
  pingthings config eventPacks.error openarena-announcer
28
31
  `);
29
32
  }
@@ -109,6 +112,32 @@ export default function config(args) {
109
112
  return;
110
113
  }
111
114
 
115
+ // Validate boolean keys
116
+ if (key === 'notifications' || key === 'cooldown') {
117
+ if (value !== 'true' && value !== 'false') {
118
+ console.error(`${key} must be true or false.`);
119
+ process.exit(1);
120
+ }
121
+ const cfg = readConfig();
122
+ cfg[key] = value === 'true';
123
+ writeConfig(cfg);
124
+ console.log(`${key} set to: ${cfg[key]}`);
125
+ return;
126
+ }
127
+
128
+ // Validate quietHours
129
+ if (key === 'quietHours') {
130
+ if (value !== 'null' && !/^\d{1,2}-\d{1,2}$/.test(value)) {
131
+ console.error('Quiet hours must be in format "HH-HH" (e.g., "22-7") or "null" to disable.');
132
+ process.exit(1);
133
+ }
134
+ const cfg = readConfig();
135
+ cfg.quietHours = value === 'null' ? null : value;
136
+ writeConfig(cfg);
137
+ console.log(`quietHours set to: ${cfg.quietHours ?? '(disabled)'}`);
138
+ return;
139
+ }
140
+
112
141
  // Two args: set value
113
142
  const cfg = readConfig();
114
143
  cfg[key] = value === 'null' ? null : value;
package/src/cli/create.js CHANGED
@@ -24,7 +24,7 @@ Examples:
24
24
  `);
25
25
  }
26
26
 
27
- export default async function create(args) {
27
+ export default function create(args) {
28
28
  if (args.includes('--help') || args.includes('-h')) {
29
29
  showHelp();
30
30
  return;
@@ -0,0 +1,134 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { platform, homedir } from 'node:os';
5
+ import { readConfig, getConfigDir } from '../config.js';
6
+ import { listPacks } from '../packs.js';
7
+
8
+ function showHelp() {
9
+ console.log(`
10
+ Usage: pingthings doctor
11
+
12
+ Diagnose audio setup, check player availability, verify packs,
13
+ and validate Claude Code hook configuration.
14
+ `);
15
+ }
16
+
17
+ function check(label, fn) {
18
+ try {
19
+ const result = fn();
20
+ if (result === true) {
21
+ console.log(` OK ${label}`);
22
+ } else if (result === false) {
23
+ console.log(` !! ${label}`);
24
+ } else {
25
+ console.log(` OK ${label}: ${result}`);
26
+ }
27
+ return result;
28
+ } catch (err) {
29
+ console.log(` !! ${label}: ${err.message}`);
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function commandExists(cmd) {
35
+ try {
36
+ execFileSync('which', [cmd], { stdio: 'pipe' });
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ export default function doctor(args) {
44
+ if (args.includes('--help') || args.includes('-h')) {
45
+ showHelp();
46
+ return;
47
+ }
48
+
49
+ console.log('\npingthings doctor\n');
50
+
51
+ // Platform
52
+ console.log('Platform:');
53
+ check('Operating system', () => {
54
+ const os = platform();
55
+ const names = { darwin: 'macOS', linux: 'Linux', win32: 'Windows' };
56
+ return names[os] || os;
57
+ });
58
+
59
+ // Audio player
60
+ console.log('\nAudio player:');
61
+ let hasPlayer = false;
62
+ if (platform() === 'darwin') {
63
+ hasPlayer = check('afplay available', () => commandExists('afplay'));
64
+ } else if (platform() === 'linux') {
65
+ const pa = check('paplay available (PulseAudio/PipeWire)', () => commandExists('paplay'));
66
+ const al = check('aplay available (ALSA)', () => commandExists('aplay'));
67
+ hasPlayer = pa || al;
68
+ } else if (platform() === 'win32') {
69
+ hasPlayer = check('PowerShell available', () => commandExists('powershell'));
70
+ }
71
+ if (!hasPlayer) {
72
+ console.log('\n No audio player found! Sounds will not play.');
73
+ if (platform() === 'linux') {
74
+ console.log(' Install: sudo apt install pulseaudio-utils (Ubuntu/Debian)');
75
+ console.log(' Or: sudo apt install alsa-utils');
76
+ }
77
+ }
78
+
79
+ // Config
80
+ console.log('\nConfiguration:');
81
+ const config = readConfig();
82
+ check('Config directory', () => getConfigDir());
83
+ check('Active pack', () => config.activePack);
84
+ check('Mode', () => config.mode);
85
+ check('Volume', () => `${config.volume}%`);
86
+ if (config.quietHours) {
87
+ check('Quiet hours', () => config.quietHours);
88
+ }
89
+
90
+ // Packs
91
+ console.log('\nPacks:');
92
+ const packs = listPacks();
93
+ check('Built-in packs', () => {
94
+ const builtIn = packs.filter(p => p.isBuiltIn);
95
+ return `${builtIn.length} packs, ${builtIn.reduce((s, p) => s + p.soundCount, 0)} sounds`;
96
+ });
97
+ check('User packs', () => {
98
+ const user = packs.filter(p => !p.isBuiltIn);
99
+ return user.length > 0 ? `${user.length} packs` : 'none';
100
+ });
101
+
102
+ // Active pack validation
103
+ const activePack = packs.find(p => p.name === config.activePack);
104
+ if (!activePack) {
105
+ check('Active pack exists', () => { throw new Error(`"${config.activePack}" not found`); });
106
+ } else {
107
+ check('Active pack valid', () => `${activePack.name} (${activePack.soundCount} sounds)`);
108
+ }
109
+
110
+ // Claude Code hooks
111
+ console.log('\nClaude Code integration:');
112
+ const settingsPath = join(homedir(), '.claude', 'settings.json');
113
+ if (existsSync(settingsPath)) {
114
+ try {
115
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
116
+ const hooks = settings.hooks || {};
117
+ const hasPingthings = JSON.stringify(hooks).includes('pingthings');
118
+ check('settings.json found', () => true);
119
+ check('pingthings hooks configured', () => hasPingthings);
120
+ if (hasPingthings) {
121
+ const hookTypes = Object.keys(hooks).filter(k =>
122
+ JSON.stringify(hooks[k]).includes('pingthings')
123
+ );
124
+ check('Hook events', () => hookTypes.join(', '));
125
+ }
126
+ } catch {
127
+ check('settings.json valid', () => false);
128
+ }
129
+ } else {
130
+ check('settings.json found', () => { throw new Error('Run "pingthings init" to set up'); });
131
+ }
132
+
133
+ console.log('');
134
+ }
@@ -0,0 +1,59 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { writeConfig, getDefaults } from '../config.js';
3
+ import { listPacks, getPackSounds, pickRandom } from '../packs.js';
4
+ import { playSound } from '../player.js';
5
+ import { basename } from 'node:path';
6
+
7
+ export async function runFirstTimeSetup() {
8
+ const packs = listPacks();
9
+
10
+ console.log('\n ================================================');
11
+ console.log(' WELCOME TO PINGTHINGS!');
12
+ console.log(' Your terminal is about to get a LOT cooler.');
13
+ console.log(' ================================================\n');
14
+ console.log(' Let\'s pick your first sound pack.\n');
15
+
16
+ for (let i = 0; i < packs.length; i++) {
17
+ console.log(` ${i + 1}. ${packs[i].name} (${packs[i].category})`);
18
+ console.log(` ${packs[i].description}`);
19
+ }
20
+
21
+ console.log('\n Enter a number to preview, then press enter to confirm.\n');
22
+
23
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
24
+
25
+ const chosen = await new Promise((resolve) => {
26
+ const ask = () => {
27
+ rl.question(' Pick a pack (1-' + packs.length + '): ', (answer) => {
28
+ const num = parseInt(answer.trim(), 10);
29
+ if (isNaN(num) || num < 1 || num > packs.length) {
30
+ console.log(` Enter a number between 1 and ${packs.length}.`);
31
+ ask();
32
+ return;
33
+ }
34
+
35
+ const pack = packs[num - 1];
36
+ const sounds = getPackSounds(pack.name);
37
+ if (sounds.length > 0) {
38
+ const sample = pickRandom(sounds);
39
+ console.log(`\n Preview: ${basename(sample)}`);
40
+ playSound(sample);
41
+ }
42
+
43
+ resolve(pack);
44
+ });
45
+ };
46
+ ask();
47
+ });
48
+
49
+ // Save config
50
+ const config = getDefaults();
51
+ config.activePack = chosen.name;
52
+ writeConfig(config);
53
+
54
+ console.log(`\n Active pack set to: ${chosen.name}`);
55
+ console.log('\n Quick tip: Run "pingthings init" to set up Claude Code hooks.');
56
+ console.log(' Or just run "pingthings play" anytime to hear a sound.\n');
57
+
58
+ rl.close();
59
+ }
package/src/cli/init.js CHANGED
@@ -113,13 +113,16 @@ export default async function init(args) {
113
113
  output: process.stdout,
114
114
  });
115
115
 
116
- rl.question('Choose (1 or 2): ', (answer) => {
117
- const choice = answer.trim();
118
- if (choice === '2') {
119
- applyHooks('informational');
120
- } else {
121
- applyHooks('basic');
122
- }
123
- rl.close();
116
+ await new Promise((resolve) => {
117
+ rl.question('Choose (1 or 2): ', (answer) => {
118
+ const choice = answer.trim();
119
+ if (choice === '2') {
120
+ applyHooks('informational');
121
+ } else {
122
+ applyHooks('basic');
123
+ }
124
+ rl.close();
125
+ resolve();
126
+ });
124
127
  });
125
128
  }
package/src/cli/play.js CHANGED
@@ -1,15 +1,18 @@
1
- import { readConfig, VALID_EVENTS } from '../config.js';
1
+ import { readConfig, VALID_EVENTS, getLastPlayed, setLastPlayed, isQuietHours } from '../config.js';
2
2
  import { getPackSounds, getEventSounds, pickRandom, resolvePack } from '../packs.js';
3
3
  import { playSound } from '../player.js';
4
+ import { sendNotification } from '../notify.js';
4
5
  import { basename } from 'node:path';
5
6
 
6
7
  function parseArgs(args) {
7
- const result = { sound: null, event: null };
8
+ const result = { sound: null, event: null, notify: false };
8
9
 
9
10
  for (let i = 0; i < args.length; i++) {
10
11
  if ((args[i] === '--event' || args[i] === '-e') && args[i + 1]) {
11
12
  result.event = args[i + 1];
12
13
  i++;
14
+ } else if (args[i] === '--notify' || args[i] === '-n') {
15
+ result.notify = true;
13
16
  } else if (args[i] === '--help' || args[i] === '-h') {
14
17
  showHelp();
15
18
  process.exit(0);
@@ -133,5 +136,32 @@ export default function play(args) {
133
136
  process.exit(1);
134
137
  }
135
138
 
139
+ // Quiet hours check
140
+ if (isQuietHours(config)) {
141
+ return;
142
+ }
143
+
144
+ // Cooldown: avoid playing the same sound twice in a row
145
+ if (config.cooldown && !parsed.sound) {
146
+ const lastPlayed = getLastPlayed();
147
+ if (soundFile === lastPlayed) {
148
+ // Pick a different sound from the same pool
149
+ const pool = parsed.event
150
+ ? getEventSounds(config.eventPacks?.[parsed.event] || packName, parsed.event)
151
+ : getPackSounds(packName);
152
+ if (pool.length > 1) {
153
+ const alternatives = pool.filter(s => s !== lastPlayed);
154
+ soundFile = pickRandom(alternatives);
155
+ }
156
+ }
157
+ }
158
+
159
+ setLastPlayed(soundFile);
136
160
  playSound(soundFile, config.volume);
161
+
162
+ // Desktop notification
163
+ if (parsed.notify || config.notifications) {
164
+ const event = parsed.event || 'notification';
165
+ sendNotification('pingthings', `${event}: ${basename(soundFile)}`);
166
+ }
137
167
  }
@@ -0,0 +1,44 @@
1
+ import { readConfig, writeConfig } from '../config.js';
2
+ import { listPacks, getPackSounds, pickRandom } from '../packs.js';
3
+ import { playSound } from '../player.js';
4
+ import { basename } from 'node:path';
5
+
6
+ function showHelp() {
7
+ console.log(`
8
+ Usage: pingthings random-pack
9
+
10
+ Switch to a random pack for variety. Plays a preview sound from the new pack.
11
+ `);
12
+ }
13
+
14
+ export default function randomPack(args) {
15
+ if (args.includes('--help') || args.includes('-h')) {
16
+ showHelp();
17
+ return;
18
+ }
19
+
20
+ const config = readConfig();
21
+ const packs = listPacks();
22
+
23
+ if (packs.length <= 1) {
24
+ console.log('Only one pack available.');
25
+ return;
26
+ }
27
+
28
+ // Pick a different pack than the current one
29
+ const others = packs.filter(p => p.name !== config.activePack);
30
+ const chosen = others[Math.floor(Math.random() * others.length)];
31
+
32
+ config.activePack = chosen.name;
33
+ writeConfig(config);
34
+
35
+ const sounds = getPackSounds(chosen.name);
36
+ if (sounds.length > 0) {
37
+ const sample = pickRandom(sounds);
38
+ console.log(`Switched to: ${chosen.name} (${chosen.category})`);
39
+ console.log(`Preview: ${basename(sample)}`);
40
+ playSound(sample, config.volume);
41
+ } else {
42
+ console.log(`Switched to: ${chosen.name}`);
43
+ }
44
+ }
package/src/cli/select.js CHANGED
@@ -45,37 +45,41 @@ export default async function select(args) {
45
45
  output: process.stdout,
46
46
  });
47
47
 
48
- const ask = () => {
49
- rl.question('> ', (answer) => {
50
- const trimmed = answer.trim().toLowerCase();
51
-
52
- if (trimmed === 'q' || trimmed === 'quit' || trimmed === '') {
48
+ await new Promise((resolve) => {
49
+ const ask = () => {
50
+ rl.question('> ', (answer) => {
51
+ const trimmed = answer.trim().toLowerCase();
52
+
53
+ if (trimmed === 'q' || trimmed === 'quit' || trimmed === '') {
54
+ rl.close();
55
+ resolve();
56
+ return;
57
+ }
58
+
59
+ const num = parseInt(trimmed, 10);
60
+ if (isNaN(num) || num < 1 || num > packs.length) {
61
+ console.log(`Enter 1-${packs.length} or q to quit.`);
62
+ ask();
63
+ return;
64
+ }
65
+
66
+ const chosen = packs[num - 1];
67
+ config.activePack = chosen.name;
68
+ writeConfig(config);
69
+
70
+ // Preview a sound from the chosen pack
71
+ const sounds = getPackSounds(chosen.name);
72
+ if (sounds.length > 0) {
73
+ const sample = sounds[Math.floor(Math.random() * sounds.length)];
74
+ playSound(sample, config.volume);
75
+ }
76
+
77
+ console.log(`\nActive pack set to: ${chosen.name}`);
53
78
  rl.close();
54
- return;
55
- }
56
-
57
- const num = parseInt(trimmed, 10);
58
- if (isNaN(num) || num < 1 || num > packs.length) {
59
- console.log(`Enter 1-${packs.length} or q to quit.`);
60
- ask();
61
- return;
62
- }
63
-
64
- const chosen = packs[num - 1];
65
- config.activePack = chosen.name;
66
- writeConfig(config);
67
-
68
- // Preview a sound from the chosen pack
69
- const sounds = getPackSounds(chosen.name);
70
- if (sounds.length > 0) {
71
- const sample = sounds[Math.floor(Math.random() * sounds.length)];
72
- playSound(sample, config.volume);
73
- }
74
-
75
- console.log(`\nActive pack set to: ${chosen.name}`);
76
- rl.close();
77
- });
78
- };
79
-
80
- ask();
79
+ resolve();
80
+ });
81
+ };
82
+
83
+ ask();
84
+ });
81
85
  }
@@ -25,6 +25,11 @@ export default function uninstall(args) {
25
25
  return;
26
26
  }
27
27
 
28
+ if (packName.includes('..') || packName.includes('/') || packName.includes('\\')) {
29
+ console.error('Invalid pack name.');
30
+ process.exit(1);
31
+ }
32
+
28
33
  const userPackDir = join(getConfigDir(), 'packs', packName);
29
34
 
30
35
  if (!existsSync(userPackDir)) {