papermc-skill 1.0.1 → 1.2.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 (53) hide show
  1. package/.cursor/rules/paper-plugin-dev.mdc +23 -0
  2. package/.cursorrules +6 -0
  3. package/.paseo/rules.md +8 -0
  4. package/.pi/rules.md +8 -0
  5. package/.windsurfrules +7 -0
  6. package/CHANGELOG.md +50 -0
  7. package/CLAUDE.md +27 -0
  8. package/CODEX.md +12 -0
  9. package/DEEPSEEK.md +18 -0
  10. package/README.md +73 -33
  11. package/assets/banner-animated.svg +360 -0
  12. package/assets/banner.svg +360 -0
  13. package/bin/cli.js +565 -35
  14. package/docs/01-modern-paper-architecture.md +48 -51
  15. package/docs/02-adventure-and-minimessage.md +48 -52
  16. package/docs/03-persistent-data-container.md +39 -39
  17. package/docs/04-commands-and-brigadier.md +32 -35
  18. package/docs/05-concurrency-and-folia.md +44 -44
  19. package/docs/06-events-and-listeners.md +30 -31
  20. package/docs/07-best-practices-and-perf.md +39 -40
  21. package/docs/08-gui-and-menus.md +34 -37
  22. package/docs/09-custom-items-and-combat.md +25 -33
  23. package/docs/10-database-and-storage.md +27 -27
  24. package/docs/11-security-and-exploit-prevention.md +233 -0
  25. package/docs/12-data-components-and-display-entities.md +264 -0
  26. package/docs/13-multi-agent-system-and-tooling.md +90 -0
  27. package/package.json +17 -4
  28. package/skills/paper-plugin-dev/SKILL.md +425 -114
  29. package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
  30. package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
  31. package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
  32. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.bin +0 -0
  33. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
  34. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.bin +0 -0
  35. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
  36. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/resourceHashesCache.bin +0 -0
  37. package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
  38. package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  39. package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +1 -1
  40. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/ExamplePaperPlugin.class +0 -0
  41. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/PaperPluginBootstrap.class +0 -0
  42. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/command/HelloCommand.class +0 -0
  43. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/listener/PlayerEventListener.class +0 -0
  44. package/templates/paper-modern-template/build/classes/java/main/com/example/paperplugin/util/SchedulerAdapter.class +0 -0
  45. package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
  46. package/templates/paper-modern-template/build.gradle.kts +7 -7
  47. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/ExamplePaperPlugin.java +6 -6
  48. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/PaperPluginBootstrap.java +2 -2
  49. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/command/HelloCommand.java +2 -4
  50. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/data/PluginDataKeys.java +2 -2
  51. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/listener/PlayerEventListener.java +6 -7
  52. package/templates/paper-modern-template/src/main/java/com/example/paperplugin/util/SchedulerAdapter.java +6 -6
  53. package/templates/paper-modern-template/src/main/resources/config.yml +5 -5
package/bin/cli.js CHANGED
@@ -18,6 +18,7 @@ const c = {
18
18
  green: '\x1b[32m',
19
19
  yellow: '\x1b[33m',
20
20
  red: '\x1b[31m',
21
+ magenta: '\x1b[35m',
21
22
  gray: '\x1b[90m'
22
23
  };
23
24
 
@@ -28,7 +29,7 @@ ${c.cyan}${c.bold} ____ __ __ ____
28
29
  | |_) / _\` | '_ \\ / _ \\ '__|____| |\\/| | | / ___|| | __(_) | |
29
30
  | __/ (_| | |_) | __/ | |_____| | | | |___ \\___ \\| |/ / | | |
30
31
  |_| \\__,_| .__/ \\___|_| |_| |_|\\____| |____/|_|\\_\\_|_|_|
31
- |_| ${c.reset}${c.dim}Modern Paper & Folia Plugin Toolchain${c.reset}
32
+ |_| ${c.reset}${c.dim}Modern Paper & Folia Plugin Toolchain (1.21+ / 26.x)${c.reset}
32
33
  `);
33
34
  }
34
35
 
@@ -118,7 +119,7 @@ function copyDirRecursive(src, dest, replacements = {}) {
118
119
 
119
120
  async function handleCreate(targetDirArg) {
120
121
  printBanner();
121
- console.log(`${c.bold}Scaffolding a new Modern Paper Plugin...${c.reset}\n`);
122
+ console.log(`${c.bold}Scaffolding a new Modern Paper & Folia Plugin...${c.reset}\n`);
122
123
 
123
124
  const rawPluginName = await prompt('Plugin Name', targetDirArg || 'MyPaperPlugin');
124
125
  const pluginName = sanitizePluginName(rawPluginName);
@@ -178,32 +179,427 @@ async function handleCreate(targetDirArg) {
178
179
  console.log(` ${c.cyan}./gradlew runFoliaServer${c.reset} # Launches Folia multi-threaded test server${c.reset}\n`);
179
180
  }
180
181
 
181
- async function handleInstallSkill(options) {
182
+ // Universal Multi-Agent Installer
183
+ async function handleInstallSkill(options = []) {
182
184
  printBanner();
183
- console.log(`${c.bold}Installing 'paper-plugin-dev' Agent Skill...${c.reset}\n`);
185
+ console.log(`${c.bold}Universal AI Coding Agent Skill Installer${c.reset}\n`);
184
186
 
185
- const isGlobal = options.includes('--global') || options.includes('-g');
186
187
  const homeDir = process.env.HOME || process.env.USERPROFILE;
187
-
188
- let targetSkillDir;
189
- if (isGlobal) {
190
- targetSkillDir = path.join(homeDir, '.gemini', 'config', 'skills', 'paper-plugin-dev');
191
- } else {
192
- // Current workspace customization root
193
- targetSkillDir = path.resolve(process.cwd(), '.agents', 'skills', 'paper-plugin-dev');
194
- }
195
-
188
+ const cwd = process.cwd();
196
189
  const srcSkillDir = path.join(ROOT_DIR, 'skills', 'paper-plugin-dev');
190
+
197
191
  if (!fs.existsSync(srcSkillDir)) {
198
192
  console.error(`${c.red}Error:${c.reset} Source skill directory not found at ${srcSkillDir}`);
199
193
  process.exit(1);
200
194
  }
201
195
 
202
- copyDirRecursive(srcSkillDir, targetSkillDir);
196
+ // Parse target flags
197
+ let targetAgent = null;
198
+ const isGlobal = options.includes('--global') || options.includes('-g');
199
+ const isLocal = options.includes('--local') || options.includes('-l');
200
+ const isAll = options.includes('--all') || options.includes('-a');
201
+
202
+ for (let i = 0; i < options.length; i++) {
203
+ if (options[i].startsWith('--agent=')) {
204
+ targetAgent = options[i].split('=')[1].toLowerCase();
205
+ } else if (options[i] === '--agent' && options[i + 1]) {
206
+ targetAgent = options[i + 1].toLowerCase();
207
+ }
208
+ }
209
+
210
+ // If no target specified and not --all, offer interactive prompt
211
+ if (!targetAgent && !isAll) {
212
+ console.log(`${c.bold}Supported AI Coding Agent Platforms:${c.reset}`);
213
+ console.log(` ${c.cyan}1.${c.reset} All Agents (Antigravity, Claude, Codex, Cursor, DeepSeek, Paseo, pi.dev, Windsurf)`);
214
+ console.log(` ${c.cyan}2.${c.reset} Antigravity / agy (~/.gemini/config/skills)`);
215
+ console.log(` ${c.cyan}3.${c.reset} Claude Code (~/.claude/skills & CLAUDE.md)`);
216
+ console.log(` ${c.cyan}4.${c.reset} OpenAI Codex (~/.codex/skills & CODEX.md)`);
217
+ console.log(` ${c.cyan}5.${c.reset} Cursor (.cursor/rules & .cursorrules)`);
218
+ console.log(` ${c.cyan}6.${c.reset} DeepSeek Harness (~/.deepseek & DEEPSEEK.md)`);
219
+ console.log(` ${c.cyan}7.${c.reset} Paseo (~/.paseo & .paseo/rules.md)`);
220
+ console.log(` ${c.cyan}8.${c.reset} pi.dev (~/.pi & .pi/rules.md)`);
221
+ console.log(` ${c.cyan}9.${c.reset} Windsurf (.windsurfrules)\n`);
222
+
223
+ const selection = await prompt('Select target agent platform [1-9]', '1');
224
+ const mapping = {
225
+ '1': 'all',
226
+ '2': 'agy',
227
+ '3': 'claude',
228
+ '4': 'codex',
229
+ '5': 'cursor',
230
+ '6': 'deepseek',
231
+ '7': 'paseo',
232
+ '8': 'pi',
233
+ '9': 'windsurf'
234
+ };
235
+ targetAgent = mapping[selection] || 'all';
236
+ }
237
+
238
+ const agentsToInstall = (targetAgent === 'all' || isAll)
239
+ ? ['agy', 'claude', 'codex', 'cursor', 'deepseek', 'paseo', 'pi', 'windsurf']
240
+ : [targetAgent];
241
+
242
+ console.log(`\n${c.yellow}Installing skill for:${c.reset} ${c.bold}${agentsToInstall.join(', ')}${c.reset}\n`);
243
+
244
+ for (const agent of agentsToInstall) {
245
+ try {
246
+ switch (agent) {
247
+ case 'agy':
248
+ case 'antigravity': {
249
+ const dest = (isLocal && !isGlobal)
250
+ ? path.join(cwd, '.agents', 'skills', 'paper-plugin-dev')
251
+ : path.join(homeDir, '.gemini', 'config', 'skills', 'paper-plugin-dev');
252
+ copyDirRecursive(srcSkillDir, dest);
253
+ console.log(` ${c.green}✔${c.reset} ${c.bold}Antigravity (agy):${c.reset} Installed to ${c.dim}${dest}${c.reset}`);
254
+ break;
255
+ }
256
+
257
+ case 'claude':
258
+ case 'claudecode': {
259
+ const globalDir = path.join(homeDir, '.claude', 'skills', 'paper-plugin-dev');
260
+ copyDirRecursive(srcSkillDir, globalDir);
261
+ const claudeMdSrc = path.join(ROOT_DIR, 'CLAUDE.md');
262
+ if (fs.existsSync(claudeMdSrc)) {
263
+ fs.copyFileSync(claudeMdSrc, path.join(cwd, 'CLAUDE.md'));
264
+ }
265
+ console.log(` ${c.green}✔${c.reset} ${c.bold}Claude Code:${c.reset} Skill placed in ~/.claude/skills and workspace ${c.dim}CLAUDE.md${c.reset}`);
266
+ break;
267
+ }
268
+
269
+ case 'codex':
270
+ case 'openai': {
271
+ const globalDir = path.join(homeDir, '.codex', 'skills', 'paper-plugin-dev');
272
+ copyDirRecursive(srcSkillDir, globalDir);
273
+ const codexMdSrc = path.join(ROOT_DIR, 'CODEX.md');
274
+ if (fs.existsSync(codexMdSrc)) {
275
+ fs.copyFileSync(codexMdSrc, path.join(cwd, 'CODEX.md'));
276
+ }
277
+ console.log(` ${c.green}✔${c.reset} ${c.bold}OpenAI Codex:${c.reset} Placed in ~/.codex/skills and workspace ${c.dim}CODEX.md${c.reset}`);
278
+ break;
279
+ }
280
+
281
+ case 'cursor': {
282
+ const cursorRulesDir = path.join(cwd, '.cursor', 'rules');
283
+ fs.mkdirSync(cursorRulesDir, { recursive: true });
284
+ const mdcSrc = path.join(ROOT_DIR, '.cursor', 'rules', 'paper-plugin-dev.mdc');
285
+ if (fs.existsSync(mdcSrc)) {
286
+ fs.copyFileSync(mdcSrc, path.join(cursorRulesDir, 'paper-plugin-dev.mdc'));
287
+ }
288
+ const cursorRulesSrc = path.join(ROOT_DIR, '.cursorrules');
289
+ if (fs.existsSync(cursorRulesSrc)) {
290
+ fs.copyFileSync(cursorRulesSrc, path.join(cwd, '.cursorrules'));
291
+ }
292
+ console.log(` ${c.green}✔${c.reset} ${c.bold}Cursor:${c.reset} Created ${c.dim}.cursor/rules/paper-plugin-dev.mdc${c.reset} and ${c.dim}.cursorrules${c.reset}`);
293
+ break;
294
+ }
295
+
296
+ case 'deepseek': {
297
+ const globalDir = path.join(homeDir, '.deepseek', 'skills', 'paper-plugin-dev');
298
+ copyDirRecursive(srcSkillDir, globalDir);
299
+ const deepseekSrc = path.join(ROOT_DIR, 'DEEPSEEK.md');
300
+ if (fs.existsSync(deepseekSrc)) {
301
+ fs.copyFileSync(deepseekSrc, path.join(cwd, 'DEEPSEEK.md'));
302
+ }
303
+ console.log(` ${c.green}✔${c.reset} ${c.bold}DeepSeek Harness:${c.reset} Placed in ~/.deepseek/skills and workspace ${c.dim}DEEPSEEK.md${c.reset}`);
304
+ break;
305
+ }
306
+
307
+ case 'paseo': {
308
+ const globalDir = path.join(homeDir, '.paseo', 'skills', 'paper-plugin-dev');
309
+ copyDirRecursive(srcSkillDir, globalDir);
310
+ const paseoRulesDir = path.join(cwd, '.paseo');
311
+ fs.mkdirSync(paseoRulesDir, { recursive: true });
312
+ const paseoSrc = path.join(ROOT_DIR, '.paseo', 'rules.md');
313
+ if (fs.existsSync(paseoSrc)) {
314
+ fs.copyFileSync(paseoSrc, path.join(paseoRulesDir, 'rules.md'));
315
+ }
316
+ console.log(` ${c.green}✔${c.reset} ${c.bold}Paseo:${c.reset} Placed in ~/.paseo/skills and workspace ${c.dim}.paseo/rules.md${c.reset}`);
317
+ break;
318
+ }
319
+
320
+ case 'pi':
321
+ case 'pidev': {
322
+ const globalDir = path.join(homeDir, '.pi', 'skills', 'paper-plugin-dev');
323
+ copyDirRecursive(srcSkillDir, globalDir);
324
+ const piRulesDir = path.join(cwd, '.pi');
325
+ fs.mkdirSync(piRulesDir, { recursive: true });
326
+ const piSrc = path.join(ROOT_DIR, '.pi', 'rules.md');
327
+ if (fs.existsSync(piSrc)) {
328
+ fs.copyFileSync(piSrc, path.join(piRulesDir, 'rules.md'));
329
+ }
330
+ console.log(` ${c.green}✔${c.reset} ${c.bold}pi.dev:${c.reset} Placed in ~/.pi/skills and workspace ${c.dim}.pi/rules.md${c.reset}`);
331
+ break;
332
+ }
333
+
334
+ case 'windsurf': {
335
+ const windsurfSrc = path.join(ROOT_DIR, '.windsurfrules');
336
+ if (fs.existsSync(windsurfSrc)) {
337
+ fs.copyFileSync(windsurfSrc, path.join(cwd, '.windsurfrules'));
338
+ }
339
+ console.log(` ${c.green}✔${c.reset} ${c.bold}Windsurf:${c.reset} Placed ${c.dim}.windsurfrules${c.reset} in workspace root`);
340
+ break;
341
+ }
342
+
343
+ default:
344
+ console.log(` ${c.yellow}⚠ Unknown agent '${agent}'. Skipped.${c.reset}`);
345
+ }
346
+ } catch (err) {
347
+ console.error(` ${c.red}✘ Failed installing for ${agent}:${c.reset}`, err.message);
348
+ }
349
+ }
350
+
351
+ console.log(`\n${c.green}${c.bold}✔ Skill configuration completed successfully!${c.reset}`);
352
+ console.log(`Your AI coding agents now enforce modern Paper & Folia exploit-proof standards.\n`);
353
+ }
354
+
355
+ // Scaffolder for Overpowered (OP) Recipes
356
+ async function handleGenerate(recipeArg, nameArg) {
357
+ printBanner();
358
+ const recipe = (recipeArg || '').toLowerCase();
359
+
360
+ const supported = ['component-item', 'display-entity', 'mace-combat', 'menu-gui'];
361
+
362
+ if (!recipe || !supported.some(s => s.includes(recipe) || recipe.includes(s.split('-')[0]))) {
363
+ console.log(`${c.bold}Available Overpowered (OP) Code Generators:${c.reset}\n`);
364
+ console.log(` ${c.cyan}1. component-item${c.reset} ${c.gray}Modern 1.20.5+ / 1.21+ / 26.x DataComponent item builder${c.reset}`);
365
+ console.log(` ${c.cyan}2. display-entity${c.reset} ${c.gray}Zero-lag interpolated TextDisplay & ItemDisplay hologram engine${c.reset}`);
366
+ console.log(` ${c.cyan}3. mace-combat${c.reset} ${c.gray}1.21 Tricky Trials Mace smash & wind burst kinetic listener${c.reset}`);
367
+ console.log(` ${c.cyan}4. menu-gui${c.reset} ${c.gray}Anti-dupe debounce-protected inventory menu controller${c.reset}\n`);
368
+ console.log(`Usage: ${c.cyan}npx papermc-skill generate <recipe> [ClassName]${c.reset}\n`);
369
+ return;
370
+ }
371
+
372
+ const cwd = process.cwd();
373
+
374
+ if (recipe.includes('item') || recipe.includes('component')) {
375
+ const className = nameArg || 'ModernItemBuilder';
376
+ const filePath = path.join(cwd, `${className}.java`);
377
+ const code = `package com.example.item;
378
+
379
+ import io.papermc.paper.datacomponent.DataComponentTypes;
380
+ import io.papermc.paper.datacomponent.item.CustomModelData;
381
+ import io.papermc.paper.datacomponent.item.FoodProperties;
382
+ import io.papermc.paper.datacomponent.item.ItemLore;
383
+ import net.kyori.adventure.text.Component;
384
+ import net.kyori.adventure.text.format.NamedTextColor;
385
+ import net.kyori.adventure.text.format.TextDecoration;
386
+ import org.bukkit.Material;
387
+ import org.bukkit.inventory.ItemStack;
388
+
389
+ import java.util.List;
390
+
391
+ /**
392
+ * Modern Data Component Item Builder (MC 1.20.5 - 26.x compatible).
393
+ * Zero legacy NBT. Type-safe and crash-proof.
394
+ */
395
+ public final class ${className} {
396
+
397
+ public static ItemStack createMythicRelic() {
398
+ ItemStack item = ItemStack.of(Material.NETHERITE_SWORD);
399
+
400
+ // Direct Adventure Item Name & Lore
401
+ item.setData(DataComponentTypes.ITEM_NAME, Component.text("Aegis of the Void", NamedTextColor.LIGHT_PURPLE, TextDecoration.BOLD));
402
+ item.setData(DataComponentTypes.LORE, ItemLore.lore(List.of(
403
+ Component.text("Harvested from trial chambers.", NamedTextColor.GRAY),
404
+ Component.empty(),
405
+ Component.text("✦ Ability: Kinetic Shockwave on hit", NamedTextColor.AQUA)
406
+ )));
407
+
408
+ // Modern Custom Model Data (floats, strings, flags)
409
+ item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData()
410
+ .addFloat(2.5f)
411
+ .addString("aegis_void_model")
412
+ .build()
413
+ );
414
+
415
+ // Component Overrides
416
+ item.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
417
+ item.setData(DataComponentTypes.MAX_STACK_SIZE, 1);
418
+ item.setData(DataComponentTypes.UNBREAKABLE, true);
419
+
420
+ return item;
421
+ }
422
+ }
423
+ `;
424
+ fs.writeFileSync(filePath, code, 'utf-8');
425
+ console.log(`${c.green}✔ Generated Modern Data-Component Item Builder:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
426
+ } else if (recipe.includes('display') || recipe.includes('hologram')) {
427
+ const className = nameArg || 'DisplayHologramEngine';
428
+ const filePath = path.join(cwd, `${className}.java`);
429
+ const code = `package com.example.display;
430
+
431
+ import net.kyori.adventure.text.Component;
432
+ import org.bukkit.Color;
433
+ import org.bukkit.Location;
434
+ import org.bukkit.entity.Display;
435
+ import org.bukkit.entity.TextDisplay;
436
+ import org.bukkit.util.Transformation;
437
+ import org.joml.AxisAngle4f;
438
+ import org.joml.Vector3f;
439
+
440
+ /**
441
+ * High-performance Display Entity Hologram Controller.
442
+ * Zero-tick entity lag. Client-side smooth JOML Matrix4f interpolation.
443
+ */
444
+ public final class ${className} {
445
+
446
+ public static TextDisplay spawnSmoothHologram(Location loc, Component text) {
447
+ return loc.getWorld().spawn(loc, TextDisplay.class, display -> {
448
+ display.text(text);
449
+ display.setBillboard(Display.Billboard.CENTER);
450
+ display.setDefaultBackground(false);
451
+ display.setBackgroundColor(Color.fromARGB(150, 20, 20, 30));
452
+ display.setShadowed(true);
453
+
454
+ // Client-side smooth interpolation (20 ticks = 1s)
455
+ display.setInterpolationDuration(20);
456
+ display.setInterpolationDelay(0);
457
+
458
+ // Scale & Position Transformation
459
+ Transformation transform = new Transformation(
460
+ new Vector3f(0f, 0f, 0f),
461
+ new AxisAngle4f(0f, 0f, 1f, 0f),
462
+ new Vector3f(1.1f, 1.1f, 1.1f),
463
+ new AxisAngle4f(0f, 0f, 1f, 0f)
464
+ );
465
+ display.setTransformation(transform);
466
+ });
467
+ }
468
+ }
469
+ `;
470
+ fs.writeFileSync(filePath, code, 'utf-8');
471
+ console.log(`${c.green}✔ Generated Display Entity Hologram Controller:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
472
+ } else if (recipe.includes('mace') || recipe.includes('combat')) {
473
+ const className = nameArg || 'MaceKineticCombatListener';
474
+ const filePath = path.join(cwd, `${className}.java`);
475
+ const code = `package com.example.combat;
476
+
477
+ import net.kyori.adventure.text.Component;
478
+ import net.kyori.adventure.text.format.NamedTextColor;
479
+ import org.bukkit.Location;
480
+ import org.bukkit.Material;
481
+ import org.bukkit.Particle;
482
+ import org.bukkit.Sound;
483
+ import org.bukkit.entity.LivingEntity;
484
+ import org.bukkit.entity.Player;
485
+ import org.bukkit.event.EventHandler;
486
+ import org.bukkit.event.EventPriority;
487
+ import org.bukkit.event.Listener;
488
+ import org.bukkit.event.entity.EntityDamageByEntityEvent;
489
+ import org.bukkit.util.Vector;
490
+
491
+ /**
492
+ * 1.21 Tricky Trials Mace Smash & Kinetic Shockwave Combat Listener.
493
+ */
494
+ public final class ${className} implements Listener {
495
+
496
+ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
497
+ public void onMaceSmash(EntityDamageByEntityEvent event) {
498
+ if (!(event.getDamager() instanceof Player attacker)) return;
499
+ if (!(event.getEntity() instanceof LivingEntity victim)) return;
500
+
501
+ if (attacker.getInventory().getItemInMainHand().getType() != Material.MACE) return;
502
+
503
+ float fallDistance = attacker.getFallDistance();
504
+ if (fallDistance > 1.5f) {
505
+ double extraDmg = Math.min(fallDistance * 3.5, 45.0);
506
+ event.setDamage(event.getDamage() + extraDmg);
507
+
508
+ Location loc = victim.getLocation();
509
+ loc.getWorld().spawnParticle(Particle.TRIAL_SPAWNER_DETECTION, loc.add(0, 0.5, 0), 30, 0.5, 0.2, 0.5, 0.05);
510
+ loc.getWorld().playSound(loc, Sound.ITEM_MACE_SMASH_GROUND, 1.2f, 1.0f);
511
+
512
+ // Kinetic shockwave knockback to nearby hostiles
513
+ loc.getWorld().getNearbyLivingEntities(loc, 4.5, e -> !e.equals(attacker) && !e.equals(victim))
514
+ .forEach(e -> {
515
+ Vector impulse = e.getLocation().toVector().subtract(loc.toVector()).normalize().multiply(1.3).setY(0.4);
516
+ e.setVelocity(impulse);
517
+ });
518
+
519
+ attacker.sendActionBar(Component.text("⚡ SMASH IMPACT: +" + String.format("%.1f", extraDmg) + " DMG", NamedTextColor.GOLD));
520
+ }
521
+ }
522
+ }
523
+ `;
524
+ fs.writeFileSync(filePath, code, 'utf-8');
525
+ console.log(`${c.green}✔ Generated Mace Kinetic Combat Listener:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
526
+ } else if (recipe.includes('gui') || recipe.includes('menu')) {
527
+ const className = nameArg || 'SafeMenuController';
528
+ const filePath = path.join(cwd, `${className}.java`);
529
+ const code = `package com.example.gui;
530
+
531
+ import net.kyori.adventure.text.Component;
532
+ import org.bukkit.Bukkit;
533
+ import org.bukkit.entity.Player;
534
+ import org.bukkit.event.EventHandler;
535
+ import org.bukkit.event.EventPriority;
536
+ import org.bukkit.event.Listener;
537
+ import org.bukkit.event.inventory.InventoryClickEvent;
538
+ import org.bukkit.event.inventory.InventoryCloseEvent;
539
+ import org.bukkit.event.inventory.InventoryDragEvent;
540
+ import org.bukkit.inventory.Inventory;
541
+ import org.bukkit.inventory.InventoryHolder;
542
+
543
+ import java.util.Map;
544
+ import java.util.UUID;
545
+ import java.util.concurrent.ConcurrentHashMap;
546
+
547
+ /**
548
+ * Exploit-proof Inventory Menu Controller with click debounce.
549
+ */
550
+ public class ${className} implements InventoryHolder, Listener {
551
+
552
+ private final Inventory inventory;
553
+ private final Map<UUID, Long> clickDebounce = new ConcurrentHashMap<>();
554
+
555
+ public ${className}(Component title, int size) {
556
+ this.inventory = Bukkit.createInventory(this, size, title);
557
+ }
558
+
559
+ @Override
560
+ public Inventory getInventory() {
561
+ return inventory;
562
+ }
563
+
564
+ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
565
+ public void onClick(InventoryClickEvent event) {
566
+ if (!(event.getInventory().getHolder() instanceof ${className})) return;
567
+
568
+ event.setCancelled(true);
569
+ if (!(event.getWhoClicked() instanceof Player player)) return;
570
+
571
+ long now = System.currentTimeMillis();
572
+ long lastClick = clickDebounce.getOrDefault(player.getUniqueId(), 0L);
573
+ if (now - lastClick < 200) return;
574
+ clickDebounce.put(player.getUniqueId(), now);
575
+
576
+ if (event.getClickedInventory() == event.getInventory()) {
577
+ handleSlotAction(player, event.getSlot());
578
+ }
579
+ }
580
+
581
+ @EventHandler
582
+ public void onDrag(InventoryDragEvent event) {
583
+ if (event.getInventory().getHolder() instanceof ${className}) {
584
+ event.setCancelled(true);
585
+ }
586
+ }
587
+
588
+ @EventHandler
589
+ public void onClose(InventoryCloseEvent event) {
590
+ if (event.getInventory().getHolder() instanceof ${className}) {
591
+ clickDebounce.remove(event.getPlayer().getUniqueId());
592
+ }
593
+ }
203
594
 
204
- console.log(`${c.green}${c.bold}✔ Skill successfully installed!${c.reset}`);
205
- console.log(` Target: ${c.dim}${targetSkillDir}${c.reset}\n`);
206
- console.log(`Your AI coding agent can now automatically discover and load the ${c.bold}paper-plugin-dev${c.reset} skill.`);
595
+ protected void handleSlotAction(Player player, int slot) {
596
+ // Slot action implementation
597
+ }
598
+ }
599
+ `;
600
+ fs.writeFileSync(filePath, code, 'utf-8');
601
+ console.log(`${c.green}✔ Generated Anti-Dupe Safe Menu Controller:${c.reset} ${c.bold}${filePath}${c.reset}\n`);
602
+ }
207
603
  }
208
604
 
209
605
  function handleDocs(topic) {
@@ -244,7 +640,7 @@ async function handleCheckUpdate(options = []) {
244
640
  try {
245
641
  const res = await fetch('https://fill.papermc.io/v3/projects/paper', {
246
642
  headers: {
247
- 'User-Agent': 'papermc-skill/1.0.0 (https://github.com/ardianryan/papermc-skill)'
643
+ 'User-Agent': 'papermc-skill/1.2.0 (https://github.com/ardianryan/papermc-skill)'
248
644
  }
249
645
  });
250
646
 
@@ -263,15 +659,13 @@ async function handleCheckUpdate(options = []) {
263
659
  console.log(`${c.green}${c.bold}✔ Successfully queried PaperMC Fill v3 API${c.reset}\n`);
264
660
  console.log(`${c.bold}Available Major Version Groups:${c.reset} ${versionGroups.slice(0, 5).join(', ')}`);
265
661
 
266
- // Candidate group (latest supported group)
267
662
  const candidateGroup = versionGroups[0];
268
663
  const latestVersions = data.versions[candidateGroup] || [];
269
664
  const latestVersionId = latestVersions[0] || candidateGroup;
270
665
 
271
- // Fetch details for candidate version
272
666
  const verRes = await fetch(`https://fill.papermc.io/v3/projects/paper/versions/${latestVersionId}`, {
273
667
  headers: {
274
- 'User-Agent': 'papermc-skill/1.0.0 (https://github.com/ardianryan/papermc-skill)'
668
+ 'User-Agent': 'papermc-skill/1.2.0 (https://github.com/ardianryan/papermc-skill)'
275
669
  }
276
670
  });
277
671
 
@@ -290,7 +684,6 @@ async function handleCheckUpdate(options = []) {
290
684
  console.log(` Support: ${status === 'SUPPORTED' ? c.green : c.gray}${status}${c.reset}`);
291
685
  console.log(` Minimum Java: ${c.bold}Java ${minJava}+${c.reset}\n`);
292
686
 
293
- // Check template version
294
687
  const templateBuildGradle = path.join(ROOT_DIR, 'templates', 'paper-modern-template', 'build.gradle.kts');
295
688
  if (fs.existsSync(templateBuildGradle)) {
296
689
  const content = fs.readFileSync(templateBuildGradle, 'utf-8');
@@ -303,7 +696,6 @@ async function handleCheckUpdate(options = []) {
303
696
  let updated = content.replaceAll(currentConfigured, latestVersionId);
304
697
  fs.writeFileSync(templateBuildGradle, updated, 'utf-8');
305
698
 
306
- // Update paper-plugin.yml
307
699
  const pluginYamlPath = path.join(ROOT_DIR, 'templates', 'paper-modern-template', 'src', 'main', 'resources', 'paper-plugin.yml');
308
700
  if (fs.existsSync(pluginYamlPath)) {
309
701
  let yml = fs.readFileSync(pluginYamlPath, 'utf-8');
@@ -323,31 +715,160 @@ async function handleCheckUpdate(options = []) {
323
715
  }
324
716
  }
325
717
 
718
+ async function handleAudit(targetDirInput) {
719
+ printBanner();
720
+ const targetDir = path.resolve(process.cwd(), targetDirInput || '.');
721
+ console.log(`${c.cyan}${c.bold}Running PaperMC Security & Exploit Audit on:${c.reset} ${c.dim}${targetDir}${c.reset}\n`);
722
+
723
+ if (!fs.existsSync(targetDir)) {
724
+ console.error(`${c.red}Directory does not exist:${c.reset} ${targetDir}`);
725
+ return;
726
+ }
727
+
728
+ const javaFiles = [];
729
+ function scan(dir) {
730
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
731
+ for (const entry of entries) {
732
+ if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === 'build' || entry.name === '.gradle') continue;
733
+ const full = path.join(dir, entry.name);
734
+ if (entry.isDirectory()) {
735
+ scan(full);
736
+ } else if (entry.name.endsWith('.java')) {
737
+ javaFiles.push(full);
738
+ }
739
+ }
740
+ }
741
+
742
+ scan(targetDir);
743
+
744
+ if (javaFiles.length === 0) {
745
+ console.log(`${c.yellow}No .java files found to audit in this directory.${c.reset}`);
746
+ return;
747
+ }
748
+
749
+ console.log(`Auditing ${c.bold}${javaFiles.length}${c.reset} Java source files for exploit vectors & anti-patterns...\n`);
750
+
751
+ const checks = [
752
+ {
753
+ id: 'CHATCOLOR_LEGACY',
754
+ regex: /ChatColor\.[A-Z_]+/,
755
+ level: 'HIGH',
756
+ msg: 'Legacy ChatColor usage detected. Migrate to Adventure Component & MiniMessage to prevent console formatting corruption.'
757
+ },
758
+ {
759
+ id: 'SECTION_SIGN_COLOR',
760
+ regex: /["'].*?§[0-9a-fk-or].*?["']/,
761
+ level: 'HIGH',
762
+ msg: 'Hardcoded legacy section sign (§) detected. Use MiniMessage.miniMessage().deserialize(...) instead.'
763
+ },
764
+ {
765
+ id: 'PLAYER_STATIC_LEAK',
766
+ regex: /public\s+static\s+(?:final\s+)?(?:List|Set|Map|Collection)<(?:[^,>]+,\s*)?Player>/,
767
+ level: 'CRITICAL',
768
+ msg: 'Static collection holding Player instances detected! This causes massive Memory Leaks. Store UUID instead.'
769
+ },
770
+ {
771
+ id: 'LEGACY_NBT_USAGE',
772
+ regex: /net\.minecraft\.nbt\.|NBTTagCompound|NBTTagList/,
773
+ level: 'CRITICAL',
774
+ msg: 'Direct NBT manipulation detected! In MC 1.20.5+ through 26.x, use DataComponentTypes directly on ItemStack.'
775
+ },
776
+ {
777
+ id: 'BUKKIT_RUNNABLE',
778
+ regex: /new\s+BukkitRunnable\s*\(\s*\)/,
779
+ level: 'HIGH',
780
+ msg: 'BukkitRunnable is incompatible with Folia region threading. Migrate to UniversalScheduler or Entity/Region schedulers.'
781
+ },
782
+ {
783
+ id: 'SYNC_CHUNK_LOAD',
784
+ regex: /world\s*\.\s*getChunkAt\s*\(/,
785
+ level: 'HIGH',
786
+ msg: 'Synchronous chunk loading (world.getChunkAt) freezes the main server tick thread. Use getChunkAtAsync instead.'
787
+ },
788
+ {
789
+ id: 'INSECURE_SQL_CONCAT',
790
+ regex: /(?:executeQuery|executeUpdate|execute)\s*\(\s*["'].*?\+/i,
791
+ level: 'CRITICAL',
792
+ msg: 'Potential SQL Injection via string concatenation detected in query. Use PreparedStatement with ? placeholders.'
793
+ },
794
+ {
795
+ id: 'ARMOR_STAND_HOLOGRAM',
796
+ regex: /spawn\s*\([^,]+,\s*ArmorStand\.class/i,
797
+ level: 'INFO',
798
+ msg: 'ArmorStand spawned. For floating text or item pedestals, prefer modern TextDisplay/ItemDisplay for zero-lag smooth interpolation.'
799
+ },
800
+ {
801
+ id: 'RAW_DOUBLE_NAN_CHECK',
802
+ regex: /DoubleArgumentType\.doubleArg\s*\(/,
803
+ level: 'INFO',
804
+ msg: 'Double argument registered. Ensure execution validates !Double.isFinite(val) and checks for positive numbers.'
805
+ }
806
+ ];
807
+
808
+ let issueCount = 0;
809
+ for (const file of javaFiles) {
810
+ const content = fs.readFileSync(file, 'utf-8');
811
+ const lines = content.split('\n');
812
+ const relPath = path.relative(targetDir, file);
813
+
814
+ lines.forEach((line, idx) => {
815
+ for (const check of checks) {
816
+ if (check.regex.test(line)) {
817
+ issueCount++;
818
+ const color = check.level === 'CRITICAL' ? c.red : (check.level === 'HIGH' ? c.yellow : c.cyan);
819
+ console.log(`[${color}${check.level}${c.reset}] ${c.bold}${relPath}:${idx + 1}${c.reset}`);
820
+ console.log(` ${c.dim}${line.trim()}${c.reset}`);
821
+ console.log(` ↳ ${check.msg}\n`);
822
+ }
823
+ }
824
+ });
825
+ }
826
+
827
+ if (issueCount === 0) {
828
+ console.log(`${c.green}${c.bold}✔ 0 Vulnerabilities or Anti-Patterns Found!${c.reset}`);
829
+ console.log(`${c.green}Codebase adheres to modern Paper & Folia exploit-proof security standards.${c.reset}\n`);
830
+ } else {
831
+ console.log(`${c.yellow}${c.bold}Found ${issueCount} potential issue(s).${c.reset} Review docs/11-security-and-exploit-prevention.md and docs/12-data-components-and-display-entities.md for guidelines.\n`);
832
+ }
833
+ }
834
+
326
835
  function printHelp() {
327
836
  printBanner();
328
837
  console.log(`${c.bold}Usage:${c.reset}
329
838
  npx papermc-skill <command> [options]
330
839
 
331
840
  ${c.bold}Commands:${c.reset}
332
- ${c.cyan}create [name]${c.reset} / ${c.cyan}init [name]${c.reset} Scaffold a modern Paper & Folia plugin project
333
- ${c.cyan}check-update [--apply]${c.reset} Query PaperMC Fill API for new Minecraft & Paper releases
334
- ${c.cyan}install-skill [--global]${c.reset} Install the AI skill into workspace (.agents) or global (~/.gemini)
841
+ ${c.cyan}create [name]${c.reset} / ${c.cyan}init${c.reset} Scaffold a modern Paper & Folia plugin project
842
+ ${c.cyan}check-update [--apply]${c.reset} Query PaperMC Fill API for latest Minecraft & Paper releases
843
+ ${c.cyan}audit [path]${c.reset} Audit Java plugin codebase for security exploits & anti-patterns
844
+ ${c.cyan}install-skill [options]${c.reset} Install skill to AI agents (agy, claude, codex, cursor, deepseek, paseo, pi, windsurf)
845
+ ${c.cyan}generate <recipe> [name]${c.reset} Generate OP recipes (component-item, display-entity, mace-combat, menu-gui)
335
846
  ${c.cyan}docs [number|keyword]${c.reset} Browse or print documentation guides
336
847
  ${c.cyan}help${c.reset} / ${c.cyan}--help${c.reset} Display this help screen
337
848
  ${c.cyan}--version${c.reset} Show version information
338
849
 
850
+ ${c.bold}Install Skill Options:${c.reset}
851
+ ${c.cyan}--all${c.reset} Install to all supported agent configurations
852
+ ${c.cyan}--agent <name>${c.reset} Target specific agent: agy, claude, codex, cursor, deepseek, paseo, pi, windsurf
853
+ ${c.cyan}--global${c.reset} / ${c.cyan}-g${c.reset} Install globally to home directory
854
+ ${c.cyan}--local${c.reset} / ${c.cyan}-l${c.reset} Install rules to current workspace directory
855
+
339
856
  ${c.bold}Examples:${c.reset}
340
- ${c.dim}# Create a new Paper plugin in current directory${c.reset}
341
- npx papermc-skill create MyAwesomePlugin
857
+ ${c.dim}# Install skill to all AI coding agents${c.reset}
858
+ npx papermc-skill install-skill --all
859
+
860
+ ${c.dim}# Install skill specifically for Claude Code and Cursor${c.reset}
861
+ npx papermc-skill install-skill --agent claude
862
+ npx papermc-skill install-skill --agent cursor
342
863
 
343
- ${c.dim}# Check if there is a new Minecraft/Paper update from PaperMC API${c.reset}
344
- npx papermc-skill check-update
864
+ ${c.dim}# Generate modern DataComponent item builder${c.reset}
865
+ npx papermc-skill generate component-item EpicSwordBuilder
345
866
 
346
- ${c.dim}# Install AI skill globally for your coding agent${c.reset}
347
- npx papermc-skill install-skill --global
867
+ ${c.dim}# Generate zero-lag Display Entity hologram engine${c.reset}
868
+ npx papermc-skill generate display-entity HologramController
348
869
 
349
- ${c.dim}# View the Adventure API and MiniMessage guide${c.reset}
350
- npx papermc-skill docs adventure
870
+ ${c.dim}# Audit current project for exploit vectors${c.reset}
871
+ npx papermc-skill audit
351
872
  `);
352
873
  }
353
874
 
@@ -371,11 +892,20 @@ async function main() {
371
892
  case 'skill':
372
893
  await handleInstallSkill(args.slice(1));
373
894
  break;
895
+ case 'generate':
896
+ case 'gen':
897
+ await handleGenerate(args[1], args[2]);
898
+ break;
374
899
  case 'docs':
375
900
  case 'doc':
376
901
  case 'guide':
377
902
  handleDocs(args[1]);
378
903
  break;
904
+ case 'audit':
905
+ case 'scan':
906
+ case 'security':
907
+ await handleAudit(args[1]);
908
+ break;
379
909
  case '--version':
380
910
  case '-v': {
381
911
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf-8'));