papermc-skill 1.1.0 → 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 (26) 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 +31 -0
  7. package/CLAUDE.md +27 -0
  8. package/CODEX.md +12 -0
  9. package/DEEPSEEK.md +18 -0
  10. package/README.md +66 -26
  11. package/bin/cli.js +461 -36
  12. package/docs/12-data-components-and-display-entities.md +264 -0
  13. package/docs/13-multi-agent-system-and-tooling.md +90 -0
  14. package/package.json +16 -4
  15. package/skills/paper-plugin-dev/SKILL.md +144 -110
  16. package/templates/paper-modern-template/.gradle/9.7.1/checksums/checksums.lock +0 -0
  17. package/templates/paper-modern-template/.gradle/9.7.1/checksums/md5-checksums.bin +0 -0
  18. package/templates/paper-modern-template/.gradle/9.7.1/checksums/sha1-checksums.bin +0 -0
  19. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.bin +0 -0
  20. package/templates/paper-modern-template/.gradle/9.7.1/executionHistory/executionHistory.lock +0 -0
  21. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.bin +0 -0
  22. package/templates/paper-modern-template/.gradle/9.7.1/fileHashes/fileHashes.lock +0 -0
  23. package/templates/paper-modern-template/.gradle/9.7.1/kotlin-dsl-plugin-entries/kotlin-dsl-plugin-entries.lock +0 -0
  24. package/templates/paper-modern-template/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  25. package/templates/paper-modern-template/.gradle/buildOutputCleanup/cache.properties +1 -1
  26. package/templates/paper-modern-template/build/tmp/compileJava/previous-compilation-data.bin +0 -0
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
+ }
203
350
 
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.`);
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
+ }
594
+
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.1.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.1.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');
@@ -375,6 +767,18 @@ async function handleAudit(targetDirInput) {
375
767
  level: 'CRITICAL',
376
768
  msg: 'Static collection holding Player instances detected! This causes massive Memory Leaks. Store UUID instead.'
377
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
+ },
378
782
  {
379
783
  id: 'SYNC_CHUNK_LOAD',
380
784
  regex: /world\s*\.\s*getChunkAt\s*\(/,
@@ -387,6 +791,12 @@ async function handleAudit(targetDirInput) {
387
791
  level: 'CRITICAL',
388
792
  msg: 'Potential SQL Injection via string concatenation detected in query. Use PreparedStatement with ? placeholders.'
389
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
+ },
390
800
  {
391
801
  id: 'RAW_DOUBLE_NAN_CHECK',
392
802
  regex: /DoubleArgumentType\.doubleArg\s*\(/,
@@ -418,7 +828,7 @@ async function handleAudit(targetDirInput) {
418
828
  console.log(`${c.green}${c.bold}✔ 0 Vulnerabilities or Anti-Patterns Found!${c.reset}`);
419
829
  console.log(`${c.green}Codebase adheres to modern Paper & Folia exploit-proof security standards.${c.reset}\n`);
420
830
  } else {
421
- console.log(`${c.yellow}${c.bold}Found ${issueCount} potential issue(s).${c.reset} Review docs/11-security-and-exploit-prevention.md for guidelines.\n`);
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`);
422
832
  }
423
833
  }
424
834
 
@@ -428,26 +838,37 @@ function printHelp() {
428
838
  npx papermc-skill <command> [options]
429
839
 
430
840
  ${c.bold}Commands:${c.reset}
431
- ${c.cyan}create [name]${c.reset} / ${c.cyan}init [name]${c.reset} Scaffold a modern Paper & Folia plugin project
432
- ${c.cyan}check-update [--apply]${c.reset} Query PaperMC Fill API for new Minecraft & Paper releases
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
433
843
  ${c.cyan}audit [path]${c.reset} Audit Java plugin codebase for security exploits & anti-patterns
434
- ${c.cyan}install-skill [--global]${c.reset} Install the AI skill into workspace (.agents) or global (~/.gemini)
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)
435
846
  ${c.cyan}docs [number|keyword]${c.reset} Browse or print documentation guides
436
847
  ${c.cyan}help${c.reset} / ${c.cyan}--help${c.reset} Display this help screen
437
848
  ${c.cyan}--version${c.reset} Show version information
438
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
+
439
856
  ${c.bold}Examples:${c.reset}
440
- ${c.dim}# Create a new Paper plugin in current directory${c.reset}
441
- npx papermc-skill create MyAwesomePlugin
857
+ ${c.dim}# Install skill to all AI coding agents${c.reset}
858
+ npx papermc-skill install-skill --all
442
859
 
443
- ${c.dim}# Check if there is a new Minecraft/Paper update from PaperMC API${c.reset}
444
- npx papermc-skill check-update
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
445
863
 
446
- ${c.dim}# Install AI skill globally for your coding agent${c.reset}
447
- npx papermc-skill install-skill --global
864
+ ${c.dim}# Generate modern DataComponent item builder${c.reset}
865
+ npx papermc-skill generate component-item EpicSwordBuilder
448
866
 
449
- ${c.dim}# View the Adventure API and MiniMessage guide${c.reset}
450
- npx papermc-skill docs adventure
867
+ ${c.dim}# Generate zero-lag Display Entity hologram engine${c.reset}
868
+ npx papermc-skill generate display-entity HologramController
869
+
870
+ ${c.dim}# Audit current project for exploit vectors${c.reset}
871
+ npx papermc-skill audit
451
872
  `);
452
873
  }
453
874
 
@@ -471,6 +892,10 @@ async function main() {
471
892
  case 'skill':
472
893
  await handleInstallSkill(args.slice(1));
473
894
  break;
895
+ case 'generate':
896
+ case 'gen':
897
+ await handleGenerate(args[1], args[2]);
898
+ break;
474
899
  case 'docs':
475
900
  case 'doc':
476
901
  case 'guide':