itemscore-helper 1.0.0 → 1.2.1

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.
package/README.md CHANGED
@@ -1,12 +1,33 @@
1
1
  # itemscore-helper
2
2
 
3
- Set up **any** AI assistant to build and edit custom Minecraft items for the [ItemsCore](https://www.coredevelopment.shop/plugins/items-core) plugin. Works with Claude, Codex, Cursor, Gemini, and anything else that supports MCP or custom instructions. You do not need to know how to code.
3
+ Set up **any** AI assistant to build and edit custom Minecraft items for the [ItemsCore](https://www.coredevelopment.shop/plugins/itemscore) plugin. Works with Claude, Codex, Cursor, Gemini, and anything else that supports MCP or custom instructions. You do not need to know how to code.
4
+
5
+ ## Easiest: paste one prompt into your AI
6
+
7
+ Copy the prompt below and paste it into your AI (Claude, Cursor, Gemini, Codex, anything). It downloads and connects the ItemsCore tools itself, then asks what item you want - no terminal knowledge needed:
8
+
9
+ ```text
10
+ You are my coding agent and I do not know how to code, so do the whole setup yourself - actually run the commands, do not just tell me what to run.
11
+
12
+ 1. In my project folder, run this to download and connect the ItemsCore tools:
13
+ npx -y itemscore-helper install
14
+ It installs a local helper and wires an "itemscore" MCP server into this project. When it finishes, reload your MCP servers (or ask me to restart this app) so the itemscore tools load. If you truly cannot run commands, add this to your MCP config instead:
15
+ {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}
16
+
17
+ 2. Read https://www.coredevelopment.shop/llms.txt so you know the ItemsCore item format and every method available.
18
+
19
+ 3. Tell me you are ready, then ask what item I want. Build a valid .import file (clean item JSON), validate it, and tell me to drop it in plugins/ItemsCore/imports/ and run /ic import in-game. The item stays fully editable in the in-game editor.
20
+ ```
21
+
22
+ The same prompt is on the [docs page](https://www.coredevelopment.shop/docs/itemscore) with a one-click Copy button.
23
+
24
+ ## Or set it up in one command
4
25
 
5
26
  ```bash
6
27
  npx itemscore-helper
7
28
  ```
8
29
 
9
- That command drops the ItemsCore skill into a folder and prints exactly what to paste into your AI tool. Then you can ask things like *"make me a sword that calls lightning on left-click"* and your AI produces a ready-to-import item file.
30
+ This auto-detects the AI tools on your machine and connects the local MCP server to each one (a backup of every file it changes is saved beside it). Then just ask your AI for an item.
10
31
 
11
32
  ## What it sets up
12
33
 
@@ -19,8 +40,8 @@ The MCP server is `npx -y itemscore-helper serve`. It speaks the standard stdio
19
40
 
20
41
  | Command | What it does |
21
42
  |---|---|
22
- | `npx itemscore-helper` | Install the skill files into `./itemscore-helper/` and print setup steps |
23
- | `npx itemscore-helper --dir DIR` | Install into a custom folder |
43
+ | `npx itemscore-helper` | Auto-detect your AI tools and connect the local MCP server |
44
+ | `npx itemscore-helper --dry-run` | Show what would change, without writing anything |
24
45
  | `npx itemscore-helper serve` | Run the local MCP server (this is what your AI runs) |
25
46
  | `npx itemscore-helper print` | Print the skill instructions to stdout |
26
47
  | `npx itemscore-helper mcp` | Print the MCP server config |
@@ -68,7 +89,7 @@ After connecting, give your AI the `SKILL.md` file as its instructions (paste it
68
89
  ## How you use it
69
90
 
70
91
  1. Ask your AI for an item.
71
- 2. It writes a small `.item` (JSON) file and validates it.
92
+ 2. It writes a small `.import` (JSON) file and validates it.
72
93
  3. Put that file in `plugins/ItemsCore/imports/` on your server.
73
94
  4. Run `/ic import <name>` in-game. The item is live and stays editable in the in-game editor.
74
95
 
@@ -76,8 +97,8 @@ To edit an existing item, run `/ic export <name>`, share the exported file with
76
97
 
77
98
  ## Links
78
99
 
79
- - Documentation: https://www.coredevelopment.shop/docs/items-core
80
- - Plugin page: https://www.coredevelopment.shop/plugins/items-core
100
+ - Documentation: https://www.coredevelopment.shop/docs/itemscore
101
+ - Plugin page: https://www.coredevelopment.shop/plugins/itemscore
81
102
 
82
103
  ## License
83
104
 
package/bin/cli.js CHANGED
@@ -3,12 +3,26 @@
3
3
 
4
4
  const fs = require("fs")
5
5
  const path = require("path")
6
+ const { autoConfigure } = require("../lib/install.js")
6
7
 
7
8
  const HOSTED_URL = "https://www.coredevelopment.shop/api/mcp"
8
9
  const LLMS_URL = "https://www.coredevelopment.shop/llms.txt"
9
- const DOCS_URL = "https://www.coredevelopment.shop/docs/items-core"
10
+ const DOCS_URL = "https://www.coredevelopment.shop/docs/itemscore"
10
11
  const SKILL_DIR = path.join(__dirname, "..", "skill")
11
12
 
13
+ const SETUP_PROMPT = [
14
+ "You are my coding agent and I do not know how to code, so do the whole setup yourself - actually run the commands, do not just tell me what to run.",
15
+ "",
16
+ "1. In my project folder, run this to download and connect the ItemsCore tools:",
17
+ " npx -y itemscore-helper install",
18
+ ' It installs a local helper and wires an "itemscore" MCP server into this project. When it finishes, reload your MCP servers (or ask me to restart this app) so the itemscore tools load. If you truly cannot run commands, add this to your MCP config instead:',
19
+ ' {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}',
20
+ "",
21
+ "2. Read " + LLMS_URL + " so you know the ItemsCore item format and every method available.",
22
+ "",
23
+ "3. Tell me you are ready, then ask what item I want. Build a valid .import file (clean item JSON), validate it, and tell me to drop it in plugins/ItemsCore/imports/ and run /ic import in-game. The item stays fully editable in the in-game editor.",
24
+ ].join("\n")
25
+
12
26
  function copyDir(src, dest) {
13
27
  fs.mkdirSync(dest, { recursive: true })
14
28
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -20,12 +34,11 @@ function copyDir(src, dest) {
20
34
  }
21
35
 
22
36
  function parseArgs(argv) {
23
- const out = { cmd: "install", dir: "itemscore-helper" }
37
+ const out = { cmd: "install", dryRun: false }
24
38
  const rest = []
25
- for (let i = 0; i < argv.length; i++) {
26
- const a = argv[i]
27
- if (a === "--dir") out.dir = argv[++i]
28
- else if (a === "-h" || a === "--help") out.cmd = "help"
39
+ for (const a of argv) {
40
+ if (a === "-h" || a === "--help") out.cmd = "help"
41
+ else if (a === "--dry-run") out.dryRun = true
29
42
  else rest.push(a)
30
43
  }
31
44
  if (rest[0]) out.cmd = rest[0]
@@ -38,16 +51,15 @@ function printHelp() {
38
51
  "itemscore-helper - set up any AI to build ItemsCore items",
39
52
  "",
40
53
  "Usage",
41
- " npx itemscore-helper Install the skill files here and print setup steps",
42
- " npx itemscore-helper install Same as above",
43
- " npx itemscore-helper --dir DIR Install into a custom folder (default: itemscore-helper)",
54
+ " npx itemscore-helper Auto-detect your AI tools and connect the local MCP server",
55
+ " npx itemscore-helper --dry-run Show what would be changed, without writing anything",
44
56
  " npx itemscore-helper serve Run the local MCP server (this is what your AI runs)",
45
57
  " npx itemscore-helper print Print the skill instructions (SKILL.md) to stdout",
46
58
  " npx itemscore-helper mcp Print the MCP server config",
47
59
  " npx itemscore-helper help Show this help",
48
60
  "",
49
- "The MCP server runs locally on your machine over stdio. Works with Claude, Codex, Cursor,",
50
- "Gemini, and any other AI that supports MCP.",
61
+ "Don't want to use a terminal? Copy the setup prompt from " + DOCS_URL + " and paste it into your AI - it does the rest.",
62
+ "Supports Claude (Code & Desktop), Cursor, Gemini CLI, Codex, Windsurf and any MCP client.",
51
63
  "Docs: " + DOCS_URL,
52
64
  "",
53
65
  ].join("\n"))
@@ -55,11 +67,7 @@ function printHelp() {
55
67
 
56
68
  function printMcp() {
57
69
  console.log(
58
- JSON.stringify(
59
- { mcpServers: { itemscore: { command: "npx", args: ["-y", "itemscore-helper", "serve"] } } },
60
- null,
61
- 2
62
- )
70
+ JSON.stringify({ mcpServers: { itemscore: { command: "npx", args: ["-y", "itemscore-helper", "serve"] } } }, null, 2)
63
71
  )
64
72
  }
65
73
 
@@ -67,58 +75,55 @@ function printSkill() {
67
75
  process.stdout.write(fs.readFileSync(path.join(SKILL_DIR, "SKILL.md"), "utf8"))
68
76
  }
69
77
 
70
- function printGuide(targetDir) {
71
- const skillPath = path.join(targetDir, "SKILL.md")
72
- console.log([
73
- "",
74
- "ItemsCore helper installed.",
75
- "",
76
- "Files written to " + targetDir + ":",
77
- " SKILL.md the instructions to give your AI",
78
- " ITEM_FORMAT.md the full item format reference",
79
- " mcp.json the MCP server config",
80
- " examples/ ready-made example items",
81
- "",
82
- "Two things to set up in your AI:",
83
- "",
84
- "1) Add the local ItemsCore MCP server (runs on your machine over stdio)",
85
- " Same config for every client - command npx, args [-y, itemscore-helper, serve]:",
86
- "",
87
- " Cursor .cursor/mcp.json",
88
- ' {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}',
89
- "",
90
- " Claude Code .mcp.json in your project (or claude_desktop_config.json for Desktop)",
91
- ' {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}',
92
- "",
93
- " Gemini CLI ~/.gemini/settings.json",
94
- ' {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}',
95
- "",
96
- " Codex ~/.codex/config.toml",
97
- " [mcp_servers.itemscore]",
98
- ' command = "npx"',
99
- ' args = ["-y","itemscore-helper","serve"]',
100
- "",
101
- " To match YOUR server's exact API (including addon methods), run /ic exportapi in-game",
102
- " and point the server at the generated file, either with the env var:",
103
- " ITEMSCORE_API=/path/to/plugins/ItemsCore/itemscore-api.json",
104
- ' or by adding "--manifest","/path/to/itemscore-api.json" to the args. Otherwise it uses',
105
- " a bundled snapshot of the standard API.",
106
- "",
107
- " No MCP support at all? An online copy also exists at " + HOSTED_URL,
108
- " and a plain-text guide at " + LLMS_URL,
109
- "",
110
- "2) Give your AI the skill",
111
- " Load " + skillPath + " as the AI's instructions / rules:",
112
- " Cursor copy SKILL.md into .cursor/rules/itemscore.md (or .cursorrules)",
113
- " Claude Code copy SKILL.md into .claude/skills/ or append it to CLAUDE.md",
114
- " Codex append SKILL.md to AGENTS.md",
115
- " Gemini CLI append SKILL.md to GEMINI.md",
116
- " Anything else paste SKILL.md into the chat or system prompt",
117
- "",
118
- 'Then just ask: "make me a sword that calls lightning on left-click".',
119
- "Your AI writes a .item file; drop it in plugins/ItemsCore/imports/ and run /ic import <name>.",
120
- "",
121
- ].join("\n"))
78
+ function manualLines() {
79
+ return [
80
+ " Add this to your AI client's MCP config (same for Claude, Cursor, Gemini):",
81
+ ' {"mcpServers":{"itemscore":{"command":"npx","args":["-y","itemscore-helper","serve"]}}}',
82
+ " Codex (~/.codex/config.toml):",
83
+ " [mcp_servers.itemscore]",
84
+ ' command = "npx"',
85
+ ' args = ["-y","itemscore-helper","serve"]',
86
+ " No MCP client? A hosted copy is at " + HOSTED_URL + " (guide: " + LLMS_URL + ").",
87
+ ]
88
+ }
89
+
90
+ function runInstall(dryRun) {
91
+ const targetDir = path.resolve(process.cwd(), "itemscore-helper")
92
+ if (!dryRun) copyDir(SKILL_DIR, targetDir)
93
+
94
+ const results = autoConfigure({ dryRun })
95
+ const configured = results.filter((r) => r.ok)
96
+
97
+ const out = ["", dryRun ? "ItemsCore helper - dry run (nothing was changed):" : "ItemsCore helper installed.", ""]
98
+
99
+ if (configured.length > 0) {
100
+ out.push(dryRun ? "Would connect the ItemsCore MCP server to:" : "Connected the ItemsCore MCP server to:")
101
+ for (const r of results) {
102
+ if (r.ok) out.push(" + " + r.name + (r.already ? " (already set)" : "") + " -> " + r.file)
103
+ else out.push(" ! " + r.name + " skipped: " + r.reason)
104
+ }
105
+ if (!dryRun) {
106
+ out.push("")
107
+ out.push("Restart your AI app, then just ask it to build an ItemsCore item.")
108
+ out.push("A backup of each changed file was saved beside it (*.itemscore-bak).")
109
+ }
110
+ } else {
111
+ out.push("No AI tool configs were detected on this machine. Two easy options:")
112
+ out.push("")
113
+ out.push(" 1. Paste this prompt into your AI and it sets itself up:")
114
+ out.push("")
115
+ out.push(SETUP_PROMPT.split("\n").map((l) => " " + l).join("\n"))
116
+ out.push("")
117
+ out.push(" 2. Or add the MCP server to your AI client's config yourself:")
118
+ out.push(...manualLines())
119
+ }
120
+
121
+ if (!dryRun) {
122
+ out.push("")
123
+ out.push("Skill files written to " + targetDir + " - hand SKILL.md to your AI if it asks for guidance.")
124
+ }
125
+ out.push("")
126
+ console.log(out.join("\n"))
122
127
  }
123
128
 
124
129
  function main() {
@@ -135,9 +140,7 @@ function main() {
135
140
  process.exitCode = 1
136
141
  return
137
142
  }
138
- const targetDir = path.resolve(process.cwd(), args.dir)
139
- copyDir(SKILL_DIR, targetDir)
140
- printGuide(targetDir)
143
+ runInstall(args.dryRun)
141
144
  }
142
145
 
143
146
  main()
package/bin/mcp.js CHANGED
@@ -106,7 +106,7 @@ async function main() {
106
106
  {
107
107
  title: "Validate an ItemsCore item",
108
108
  description:
109
- "Validate a clean item JSON object against the ItemsCore schema. Reports errors (must fix) and warnings (likely fine). Run this before giving the user a .item file.",
109
+ "Validate a clean item JSON object against the ItemsCore schema. Reports errors (must fix) and warnings (likely fine). Run this before giving the user a .import file.",
110
110
  inputSchema: { item: z.unknown().describe("The clean item JSON object to validate") },
111
111
  },
112
112
  async ({ item }) => jsonResult(M.validateItem(idx, item))
@@ -352,7 +352,7 @@
352
352
  "returnsValue": true,
353
353
  "useless": false,
354
354
  "documented": true,
355
- "description": "Builds a movement equation (using 't' for time) used to shape custom projectile paths.",
355
+ "description": "Builds a movement equation (using 't' for time in ticks) that shapes a custom projectile path. Axes are relative to the shooter's facing: X = forward (the way they look), Y = up, Z = left. A normal forward-flying projectile puts the motion on X, e.g. createEquationVector(\"t * 0.4\", \"0\", \"0\"); use Y for arc and Z to curve sideways.",
356
356
  "category": "Projectiles",
357
357
  "example": "core.createEquationVector(\"cos(t)\", \"0\", \"sin(t)\")"
358
358
  },
@@ -2509,7 +2509,7 @@
2509
2509
  "returnsValue": false,
2510
2510
  "useless": false,
2511
2511
  "documented": true,
2512
- "description": "Fires a custom ItemsCore projectile from a player, following the given path equation for a set number of ticks.",
2512
+ "description": "Fires a custom ItemsCore projectile from a player, following the given path equation for a set number of ticks. The equation axes are relative to where the player looks: X is forward, Y is up, Z is left - so a straight forward shot puts the motion on X.",
2513
2513
  "category": "Projectiles",
2514
2514
  "example": "core.shootProjectile(player, player.getEyeLocation(), \"FIREBALL\", null, 60, true, core.createEquationVector(\"t\", \"0\", \"t\"))"
2515
2515
  },
package/lib/install.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict"
2
+
3
+ const fs = require("fs")
4
+ const path = require("path")
5
+ const os = require("os")
6
+
7
+ const ENTRY = { command: "npx", args: ["-y", "itemscore-helper", "serve"] }
8
+ const TOML_BLOCK = '\n[mcp_servers.itemscore]\ncommand = "npx"\nargs = ["-y", "itemscore-helper", "serve"]\n'
9
+
10
+ function homeDir() {
11
+ return process.env.ITEMSCORE_HOME_OVERRIDE || os.homedir()
12
+ }
13
+
14
+ function backup(file) {
15
+ const bak = file + ".itemscore-bak"
16
+ if (fs.existsSync(file) && !fs.existsSync(bak)) {
17
+ try {
18
+ fs.copyFileSync(file, bak)
19
+ } catch {
20
+ /* best effort */
21
+ }
22
+ }
23
+ }
24
+
25
+ function mergeJsonMcp(file, key) {
26
+ let data = {}
27
+ if (fs.existsSync(file)) {
28
+ const txt = fs.readFileSync(file, "utf8").trim()
29
+ if (txt) {
30
+ try {
31
+ data = JSON.parse(txt)
32
+ } catch {
33
+ return { ok: false, reason: "existing config is not valid JSON; left untouched" }
34
+ }
35
+ }
36
+ }
37
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
38
+ return { ok: false, reason: "existing config is not a JSON object; left untouched" }
39
+ }
40
+ if (!data[key] || typeof data[key] !== "object") data[key] = {}
41
+ const already = JSON.stringify(data[key].itemscore) === JSON.stringify(ENTRY)
42
+ data[key].itemscore = ENTRY
43
+ backup(file)
44
+ fs.mkdirSync(path.dirname(file), { recursive: true })
45
+ fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n")
46
+ return { ok: true, already }
47
+ }
48
+
49
+ function appendTomlMcp(file) {
50
+ const txt = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : ""
51
+ if (txt.includes("[mcp_servers.itemscore]")) return { ok: true, already: true }
52
+ backup(file)
53
+ fs.mkdirSync(path.dirname(file), { recursive: true })
54
+ const next = txt.trim() ? txt.trimEnd() + "\n" + TOML_BLOCK : TOML_BLOCK.replace(/^\n/, "")
55
+ fs.writeFileSync(file, next)
56
+ return { ok: true, already: false }
57
+ }
58
+
59
+ function globalTargets() {
60
+ const h = homeDir()
61
+ const list = [
62
+ { name: "Cursor", dir: path.join(h, ".cursor"), file: path.join(h, ".cursor", "mcp.json"), kind: "json", key: "mcpServers" },
63
+ { name: "Codex", dir: path.join(h, ".codex"), file: path.join(h, ".codex", "config.toml"), kind: "toml" },
64
+ { name: "Gemini CLI", dir: path.join(h, ".gemini"), file: path.join(h, ".gemini", "settings.json"), kind: "json", key: "mcpServers" },
65
+ { name: "Windsurf", dir: path.join(h, ".codeium", "windsurf"), file: path.join(h, ".codeium", "windsurf", "mcp_config.json"), kind: "json", key: "mcpServers" },
66
+ ]
67
+ if (process.platform === "win32") {
68
+ const appData = process.env.APPDATA || path.join(h, "AppData", "Roaming")
69
+ list.push({ name: "Claude Desktop", dir: path.join(appData, "Claude"), file: path.join(appData, "Claude", "claude_desktop_config.json"), kind: "json", key: "mcpServers" })
70
+ } else if (process.platform === "darwin") {
71
+ list.push({ name: "Claude Desktop", dir: path.join(h, "Library", "Application Support", "Claude"), file: path.join(h, "Library", "Application Support", "Claude", "claude_desktop_config.json"), kind: "json", key: "mcpServers" })
72
+ } else {
73
+ list.push({ name: "Claude Desktop", dir: path.join(h, ".config", "Claude"), file: path.join(h, ".config", "Claude", "claude_desktop_config.json"), kind: "json", key: "mcpServers" })
74
+ }
75
+ return list
76
+ }
77
+
78
+ function isProjectDir(cwd) {
79
+ return [".git", "package.json", ".cursor", ".claude", ".vscode"].some((m) => fs.existsSync(path.join(cwd, m)))
80
+ }
81
+
82
+ function applyTarget(t, dryRun) {
83
+ if (dryRun) return { name: t.name, file: t.file, ok: true, already: false, dry: true }
84
+ const r = t.kind === "json" ? mergeJsonMcp(t.file, t.key) : appendTomlMcp(t.file)
85
+ return { name: t.name, file: t.file, ...r }
86
+ }
87
+
88
+ /**
89
+ * Detects installed AI tools and writes the itemscore MCP server into each one's
90
+ * config (creating a backup first). Returns a list describing what happened.
91
+ */
92
+ function autoConfigure(opts) {
93
+ opts = opts || {}
94
+ const dryRun = !!opts.dryRun
95
+ const cwd = opts.cwd || process.cwd()
96
+ const results = []
97
+
98
+ for (const t of globalTargets()) {
99
+ const present = fs.existsSync(t.dir) || fs.existsSync(t.file)
100
+ if (!present) continue
101
+ results.push({ scope: "global", ...applyTarget(t, dryRun) })
102
+ }
103
+
104
+ // Project-scoped .mcp.json for Claude Code, VS Code and project-level Cursor.
105
+ if (isProjectDir(cwd)) {
106
+ const file = path.join(cwd, ".mcp.json")
107
+ results.push({ scope: "project", name: "Project (.mcp.json)", file, ...(dryRun ? { ok: true, dry: true } : mergeJsonMcp(file, "mcpServers")) })
108
+ }
109
+
110
+ return results
111
+ }
112
+
113
+ module.exports = { autoConfigure, globalTargets, isProjectDir, ENTRY }
package/lib/manifest.js CHANGED
@@ -88,6 +88,123 @@ function getMethod(idx, name, binding) {
88
88
  return out
89
89
  }
90
90
 
91
+ const NUMERIC_TYPES = new Set([
92
+ "int", "long", "double", "float", "short", "byte",
93
+ "Integer", "Long", "Double", "Float", "Short", "Byte", "Number",
94
+ ])
95
+
96
+ // Object types that cause a hard ClassCastException when mismatched (cannot be coerced at runtime).
97
+ // Mismatching any of these against each other, or against a primitive/literal, is a real bug.
98
+ const HARD_OBJECT_TYPES = new Set(["Location", "ParticleDisplay", "Vector", "World"])
99
+
100
+ // Return types of the few bukkit getters agents use constantly. Lets us catch a swap even when one
101
+ // side is a raw Spigot call (e.g. player.getLocation()) that is not in the manifest.
102
+ const BUKKIT_GETTER_RETURNS = {
103
+ getlocation: "Location",
104
+ geteyelocation: "Location",
105
+ getworld: "World",
106
+ getdirection: "Vector",
107
+ getvelocity: "Vector",
108
+ }
109
+
110
+ // A handful of action variables whose type we know for sure.
111
+ const KNOWN_VARIABLE_TYPES = {
112
+ landlocation: "Location",
113
+ lastlocation: "Location",
114
+ }
115
+
116
+ function simpleType(t) {
117
+ if (!t) return ""
118
+ const dot = t.lastIndexOf(".")
119
+ return dot >= 0 ? t.slice(dot + 1) : t
120
+ }
121
+
122
+ // Returns a description of what an argument produces, as { kind, type? }.
123
+ // kind is one of: "unknown", "string", "number", "boolean", "type".
124
+ function producedType(idx, arg) {
125
+ if (arg === null) return { kind: "unknown" }
126
+ if (typeof arg === "string") return { kind: "string" }
127
+ if (typeof arg === "number") return { kind: "number" }
128
+ if (typeof arg === "boolean") return { kind: "boolean" }
129
+ if (typeof arg !== "object") return { kind: "unknown" }
130
+
131
+ if (typeof arg.var === "string") {
132
+ const known = KNOWN_VARIABLE_TYPES[arg.var.toLowerCase()]
133
+ return known ? asProduced(known) : { kind: "unknown" }
134
+ }
135
+ if (typeof arg.call === "string") {
136
+ const call = arg.call
137
+ if (call.includes(".")) {
138
+ const receiver = call.slice(0, call.indexOf("."))
139
+ const method = call.slice(call.indexOf(".") + 1)
140
+ if (idx.BINDING_NAMES.includes(receiver)) {
141
+ const found = getMethod(idx, method, receiver)
142
+ if (found.length === 1 && found[0].returns && found[0].returns !== "void") {
143
+ return asProduced(found[0].returns)
144
+ }
145
+ return { kind: "unknown" }
146
+ }
147
+ const getter = BUKKIT_GETTER_RETURNS[method.toLowerCase()]
148
+ return getter ? asProduced(getter) : { kind: "unknown" }
149
+ }
150
+ return { kind: "unknown" }
151
+ }
152
+ return { kind: "unknown" }
153
+ }
154
+
155
+ function asProduced(simpleName) {
156
+ const s = simpleType(simpleName)
157
+ if (NUMERIC_TYPES.has(s)) return { kind: "number" }
158
+ if (s === "boolean" || s === "Boolean") return { kind: "boolean" }
159
+ if (s === "String" || s === "CharSequence") return { kind: "string" }
160
+ return { kind: "type", type: s }
161
+ }
162
+
163
+ // Conservative: returns true unless the arg is a definite, uncoercible mismatch.
164
+ function argCompatible(expectedRaw, produced) {
165
+ const expected = simpleType(expectedRaw)
166
+ if (!expected || expected === "Object") return true
167
+ if (produced.kind === "unknown") return true
168
+
169
+ if (expected === "String" || expected === "CharSequence") return true
170
+ if (NUMERIC_TYPES.has(expected)) {
171
+ if (produced.kind === "type") return NUMERIC_TYPES.has(produced.type)
172
+ return true
173
+ }
174
+ if (expected === "boolean" || expected === "Boolean") {
175
+ if (produced.kind === "type") return false
176
+ return true
177
+ }
178
+ if (HARD_OBJECT_TYPES.has(expected)) {
179
+ if (produced.kind === "type") return produced.type === expected || !HARD_OBJECT_TYPES.has(produced.type)
180
+ return false
181
+ }
182
+ return true
183
+ }
184
+
185
+ function describeProduced(produced) {
186
+ if (produced.kind === "type") return produced.type
187
+ if (produced.kind === "string") return "string"
188
+ if (produced.kind === "number") return "number"
189
+ if (produced.kind === "boolean") return "boolean"
190
+ return "value"
191
+ }
192
+
193
+ function validateArgs(idx, method, args, p, errors) {
194
+ if (!method || !Array.isArray(method.params) || !Array.isArray(args)) return
195
+ for (let i = 0; i < args.length && i < method.params.length; i++) {
196
+ const param = method.params[i]
197
+ const expected = param.type || param.jvmType
198
+ const produced = producedType(idx, args[i])
199
+ if (!argCompatible(expected, produced)) {
200
+ errors.push(
201
+ p + '.args[' + i + '] (parameter "' + param.name + '") expects ' + simpleType(expected) +
202
+ " but a " + describeProduced(produced) + " was passed. Check the argument order against the method signature: " + method.signature
203
+ )
204
+ }
205
+ }
206
+ }
207
+
91
208
  function validateStep(idx, step, p, errors, warnings) {
92
209
  if (typeof step !== "object" || step === null) {
93
210
  errors.push(p + " must be an object")
@@ -105,8 +222,14 @@ function validateStep(idx, step, p, errors, warnings) {
105
222
  const found = getMethod(idx, method, receiver)
106
223
  if (found.length === 0) {
107
224
  errors.push(p + '.call "' + call + '" references unknown method "' + method + '" on binding "' + receiver + '"')
108
- } else if (found[0].useless) {
109
- warnings.push(p + '.call "' + call + '" is flagged useless (no real effect)')
225
+ } else {
226
+ if (found[0].useless) {
227
+ warnings.push(p + '.call "' + call + '" is flagged useless (no real effect)')
228
+ }
229
+ // Only type-check when the method is unambiguous (one overload) so we never guess wrong.
230
+ if (found.length === 1 && Array.isArray(step.args)) {
231
+ validateArgs(idx, found[0], step.args, p, errors)
232
+ }
110
233
  }
111
234
  } else if (!idx.KNOWN_RECEIVERS.has(receiver)) {
112
235
  warnings.push(p + '.call receiver "' + receiver + '" is not a known variable; make sure it is defined earlier or exists at runtime')
@@ -144,6 +267,9 @@ function validateItem(idx, item) {
144
267
  if (action.needBlock !== undefined && (typeof action.needBlock !== "string" || !NEED_BLOCK_VALUES.includes(action.needBlock))) {
145
268
  errors.push(ap + ".needBlock must be one of " + NEED_BLOCK_VALUES.join(", "))
146
269
  }
270
+ if (action.cooldown !== undefined && typeof action.cooldown !== "string" && typeof action.cooldown !== "number") {
271
+ warnings.push(ap + '.cooldown should be a duration like "5s", "1m", "1h" or a number of seconds')
272
+ }
147
273
  if (!Array.isArray(action.steps)) errors.push(ap + ".steps must be an array")
148
274
  else action.steps.forEach((s, si) => validateStep(idx, s, ap + ".steps[" + si + "]", errors, warnings))
149
275
  })
@@ -181,6 +307,7 @@ function itemSchema(idx) {
181
307
  action: {
182
308
  trigger: "string (required) - one of: " + idx.TRIGGER_NAMES.join(", "),
183
309
  needBlock: "BOTH | AIR | BLOCK - optional per-action override",
310
+ cooldown: 'optional - per-player reuse delay for this action. Any duration: a number plus a unit s/m/h, e.g. "5s", "45s", "2m", "90s", "1h" (a bare number means seconds). Omit or "0" for no cooldown.',
184
311
  steps: "Step[] - executed in order, combined by operatorToNext",
185
312
  },
186
313
  step: {
@@ -193,6 +320,8 @@ function itemSchema(idx) {
193
320
  },
194
321
  bukkitObjects:
195
322
  "player, shooter, victim, arrow, event, and any entity, block, world, location, or ItemStack returned by a method are real Bukkit/Spigot objects. You can call any standard Spigot method on them in a step (for example player.sendMessage, player.getHealth, player.getWorld, victim.setFireTicks), not only the ItemsCore methods. These are not in the method list below; see https://hub.spigotmc.org/javadocs/spigot/ for the full Spigot API. Prefer a core method when one exists.",
323
+ projectiles:
324
+ 'For core.shootProjectile + core.createEquationVector the equation axes are relative to where the player looks: X = forward, Y = up, Z = left. A straight forward-flying projectile puts the motion on X, e.g. core.createEquationVector("t * 0.4", "0", "0"). Use Y for arc/gravity and Z to curve sideways.',
196
325
  bindings: idx.bindings.map((b) => ({ name: b.name, description: b.description || "", methodCount: (b.methods || []).length })),
197
326
  variables: idx.variables,
198
327
  triggers: idx.triggers,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itemscore-helper",
3
- "version": "1.0.0",
4
- "description": "Local MCP server + skill that lets any AI (Claude, Codex, Cursor, Gemini, and others) build and edit custom Minecraft items for the ItemsCore plugin. Runs entirely on your machine.",
3
+ "version": "1.2.1",
4
+ "description": "One command sets up any AI (Claude, Codex, Cursor, Gemini, and others) to build and edit custom Minecraft items for the ItemsCore plugin. Auto-detects your AI tools and connects a local MCP server that runs entirely on your machine.",
5
5
  "bin": {
6
6
  "itemscore-helper": "bin/cli.js",
7
7
  "itemscore-mcp": "bin/mcp.js"
@@ -28,10 +28,10 @@
28
28
  "codex",
29
29
  "gemini"
30
30
  ],
31
- "homepage": "https://www.coredevelopment.shop/docs/items-core",
31
+ "homepage": "https://www.coredevelopment.shop/docs/itemscore",
32
32
  "repository": {
33
33
  "type": "git",
34
- "url": "https://github.com/Tc554/itemscore-helper.git"
34
+ "url": "https://github.com/Core-Pluginss/ItemsCore-Helper.git"
35
35
  },
36
36
  "license": "MIT",
37
37
  "author": "TastyCake",
@@ -31,6 +31,7 @@ This is the offline reference for the item format ItemsCore imports. When the li
31
31
  |---|---|---|
32
32
  | `trigger` | string, required | One of the triggers below |
33
33
  | `needBlock` | `BOTH` \| `AIR` \| `BLOCK` | Optional per-action override of the item default |
34
+ | `cooldown` | duration string | Optional per-player reuse delay. Any duration: a number plus a unit s/m/h, e.g. `"5s"`, `"45s"`, `"2m"`, `"90s"`, `"1h"` (a bare number means seconds) |
34
35
  | `steps` | Step[] | Run in order, combined by each step's `operatorToNext` |
35
36
 
36
37
  ### Triggers
@@ -131,6 +132,10 @@ These are not in the `core` / `particles` / `values` / `api` method list. For th
131
132
 
132
133
  Always confirm a method's exact name and parameters with `get_method` / `search_methods` before using it. Do not guess.
133
134
 
135
+ ## Projectiles
136
+
137
+ `core.shootProjectile` and `core.createEquationVector` use a movement equation whose axes are relative to where the player looks: **X = forward, Y = up, Z = left**. A straight forward-flying projectile puts the motion on X, e.g. `core.createEquationVector("t * 0.4", "0", "0")`. Use Y for arc/gravity and Z to curve left/right.
138
+
134
139
  ## Worked example: a healing ability wand
135
140
 
136
141
  ```json
package/skill/SKILL.md CHANGED
@@ -9,7 +9,7 @@ ItemsCore is a Minecraft (Bukkit/Spigot) plugin that lets a server owner create
9
9
 
10
10
  ## The one rule that matters
11
11
 
12
- Always produce a **clean item JSON** (the format described below). Never hand-write the plugin's internal `.item` YAML or its generated JavaScript `code`. When the user imports your JSON, the plugin builds **both** the runnable code **and** the in-game GUI action graph from it. That means an item you create this way still works **and** stays fully editable in the in-game editor. Hand-writing raw code produces an item the GUI cannot open, which is exactly the problem this format avoids.
12
+ Always produce a **clean item JSON** (the format described below). Never hand-write the plugin's internal item YAML or its generated JavaScript `code`. When the user imports your JSON, the plugin builds **both** the runnable code **and** the in-game GUI action graph from it. That means an item you create this way still works **and** stays fully editable in the in-game editor. Hand-writing raw code produces an item the GUI cannot open, which is exactly the problem this format avoids.
13
13
 
14
14
  ## Step 1: Get the API (do this first)
15
15
 
@@ -86,7 +86,7 @@ Run `validate_item` on your JSON. Fix every entry under `errors` before continui
86
86
 
87
87
  ## Step 4: Hand it to the user
88
88
 
89
- 1. Save the JSON as `<name>.item` (or `<name>.json`).
89
+ 1. Save the JSON as `<name>.import` (or `<name>.json`).
90
90
  2. Tell the user to put the file in `plugins/ItemsCore/imports/` on their server.
91
91
  3. Tell them to run `/ic import <name>` in-game.
92
92