scmJS docs

For developers

Writing a plugin#

scmjs plugin new "Base Check" writes a plugin to start from in plugin-base-check/: its manifest, a plugin.ts that adds a Tools menu item, the Korean catalogue and the test that keeps it complete, the typings, and the build and CI every scm-js plugin uses. Then:

cd plugin-base-check
npm install
npm start

npm start runs scmjs plugin dev. It builds the plugin, serves its folder on your own computer and opens the editor with it. The first time, the editor asks you to add the plugin, as for any plugin. After that, each time you save a file the plugin is built again and the editor reloads it. A build that fails is shown in the terminal and in the editor, and nothing is reloaded until it is fixed. Ctrl+C stops it.

The editor remembers the plugin by its address, http://127.0.0.1:5310/, so it is the same plugin next time. --port serves it on another port, and --no-open prints the editor's address instead of opening it.

scmjs plugin check reads the folder the way the editor loads it and says what would go wrong: a manifest the editor would refuse, an API version it does not have, a missing icon, entry or build, a build older than its source, and, for a plugin without a build, imports of files that are not there or of npm packages. It exits with 1 when the editor would not load the plugin, so it can stop a CI step. The plugin guide covers the rest, and map run runs a plugin over a map from the terminal.

AI tools#

scmjs mcp speaks the Model Context Protocol on standard input and output, so an AI tool on your computer, such as Claude Code, Cursor or VS Code, can work on your maps with the same code these commands use. The tool starts scmjs mcp in your maps folder and talks to it over the pipe; the paths it gives are files in that folder. Nothing goes on the network except what the commands themselves fetch: the game engine the first time a map is run in the game, and npm's newest version for doctor.

With Claude Code, once, in the folder your maps are in:

claude mcp add scmjs -- npx -y @scm-js/cli mcp

Other tools take the same thing as JSON in their MCP settings:

{ "mcpServers": { "scmjs": { "command": "npx", "args": ["-y", "@scm-js/cli", "mcp"] } } }

Each tool is a command from this guide. It takes the same arguments by name and answers with the command's text and its --json data:

Tools The commands
map_info, map_check, map_dump, map_find, map_diff Reading, checking, searching and comparing maps. map_check takes a baseline file and lists only the findings it does not hold
map_triggers_export, map_triggers_import, map_strings_export, map_strings_import, map_strings_prune The triggers and strings as text, in and out. The text is passed inline, so the model edits triggers in the trigger window's text form
map_set, map_resize, map_new, map_build, map_run The Scenario menu's edits, a new map, the TrigScript build, a plugin run over the map
map_optimize The map made smaller; a dry run unless the model asks to write
map_preview The answer is the picture itself, so a model that can see images sees the map
map_trace, map_cost, map_coverage The triggers run in the game, what they cost, and which fired across many replays
map_balance Games played on a melee map with your bots, to see whether its starts are fair; it takes minutes
replay_info, replay_map What a replay holds, and its map written out
doctor What this computer has, so the model knows what it can call

A map reads as the editor reads it, so what the model is told is what the editor would show. Tools that change a map write over it unless given out, like the commands, so keep your maps in git or give the tool copies. map_run runs a plugin, which is code, with your privileges, as scmjs map run does; the AI tool asks you before it calls a tool, and you can leave that one unapproved.

map_preview, map_trace, map_cost and map_coverage need your copy of the game, and the runs fetch the engine the first time; a trace or cost run takes a few seconds and plays at most ten minutes of game. map_coverage plays whole replays, so it takes as long as the command does. doctor tells the model whether the game and the engine are there and what you would need to install, so it can say so rather than fail. When the game is not in one of the usual places, the model can pass its folder as starcraft, or you can set SCMJS_STARCRAFT in the tool's MCP settings.

TrigScript in your text editor#

scmjs lsp is a language server for TrigScript. It speaks the Language Server Protocol on standard input and output, so VS Code, Neovim, Helix or any editor with an LSP client can check a script as you type, outside scmJS's TrigScript window. It gives what that window gives:

A script is the trigscript/ folder of a map unpacked with scmjs map unpack, where its files are kept at their paths in the archive. The map's locations, switches, unit names and sounds are read from staredit/scenario.chk beside that folder, and read again when the file changes. The copies of other plugins' libraries that the map keeps are in trigscript/@libraries/. The round trip is:

scmjs map unpack ridge.scx            # ridge/trigscript/main.ts, ridge/staredit/scenario.chk, …
# edit ridge/trigscript/*.ts in your editor
scmjs map pack ridge                  # back into ridge.scx
scmjs map build ridge.scx --test      # the triggers applied and the programs built, as Save does

Any other folder called trigscript is a script too, checked against the names every map has. For a script kept somewhere else, --map names the map whose names it uses, as a map file or an unpacked map's folder. With a map file, a .ts file belongs to the nearest folder above it that has a main.ts. Files that are not part of a script get nothing from the server, so the server can be on for every TypeScript file.

The server needs TypeScript, as map build does, either in the maps folder's project or installed globally. Install the command once rather than running it through npx, so the editor starts it quickly:

npm install -g @scm-js/cli typescript

VS Code. TrigScript's repository has a small extension in editors/vscode/ that starts the server for the files under a trigscript/ folder. Its README says how to install it. VS Code's own TypeScript checks the same files without TrigScript's names and marks program, units and the rest as unknown, so turn that check off for the maps folder in .vscode/settings.json:

{ "typescript.validate.enable": false }

Neovim (0.11 or later), in init.lua:

vim.lsp.config("scmjs", {
  cmd = { "scmjs", "lsp" },
  filetypes = { "typescript", "typescriptreact" },
  root_markers = { "archive.json" },
})
vim.lsp.enable("scmjs")

On an older Neovim, vim.lsp.start does the same from an autocommand:

vim.api.nvim_create_autocmd("FileType", {
  pattern = { "typescript", "typescriptreact" },
  callback = function(args)
    local root = vim.fs.root(args.buf, "archive.json")
    if root then vim.lsp.start({ name = "scmjs", cmd = { "scmjs", "lsp" }, root_dir = root }) end
  end,
})

If ts_ls also runs on these files, it reports TrigScript's names as unknown. Leave the maps folder out of its root_dir.

Helix, in a .helix/languages.toml in the maps folder, so it applies there only:

[language-server.scmjs]
command = "scmjs"
args = ["lsp"]

[[language]]
name = "typescript"
language-servers = ["scmjs"]

Other editors take the same three things: the command scmjs lsp, standard input and output, and the TypeScript file types. Zed starts language servers only from its extensions, and there is no TrigScript extension for it yet.

What the server says about each check goes to standard error, which an editor shows as the server's log. It names the script and the map it found, and how long each check took, about half a second for a script that imports the standard library.

In scripts and pipes#

--json prints what a command found as JSON: one object for one file, a list for several. The text output may change between versions, so scripts should read the JSON.

- as a map's path is standard input, and --out - is standard output. A map read from - is written to standard output when there is no --out. So the commands chain in a pipe:

scmjs map new - --size 64x64 --players 2 | scmjs map set - --name "Ridge" | scmjs map check -
cat ridge.scx | scmjs map resize - --size 128x96 --out - > ridge-wide.scx

While a map is on standard output, what the command says goes to standard error, and --json is refused, since both want the pipe. A map from a pipe has no name: it is stdin.scx or stdin.chk by its contents, and is written back in the same form.

Colour is off when the output is not a terminal, when NO_COLOR is set, and with --no-color.

For what the commands do not do, such as a script that edits maps or a check of your own, the same functions are the @scm-js/core library; the command is built on it.

Exit codes#

Code Means
0 All went well
1 A map failed its check, two maps differed, a merge had conflicts it was not told how to settle, nothing matched a search, a build did not go through, a map test or a bot failed, a trigger never fired in map coverage --strict, a replay could not be played, or something went wrong
2 A file was missing or the wrong kind for the command, an option was not valid, you need to sign in first, or the game could not start

scmJS 0.12.1 · Generated from the repository. The source is under the MIT license, which does not cover what ATTRIBUTION.md lists. StarCraft and Brood War are trademarks of Blizzard Entertainment; this project ships none of their data.