-
Notifications
You must be signed in to change notification settings - Fork 1
npm Packages
Two packages let you use the toolkit's engine without VS Code and without the release tarballs: @px-lsp/server (the language server) and @px-lsp/protocol (the wire contract and shared helpers). They version independently of the extension and of each other: @px-lsp/server 0.3.1 and @px-lsp/protocol 0.2.1 ship with the toolkit's 0.4.0 release. Each package bumps only when it changes, and each ships its own CHANGELOG.md.
Who this page is for: you are wiring the server into an editor we do not document, embedding it in your own application (a mod manager, a web tool, a custom editor), or writing a client that talks the custom paradox/* protocol. For neovim, Zed or Helix as a plain editor, Outside VS Code plus the server README already cover it.
The full language server: parser, index, scope engine, all features, the per-game profiles and the bundled fallback data for CK3 and Victoria 3.
npm install -g @px-lsp/server
px-lsp --version # prints the server version
px-lsp # runs the server over stdioThree things the bin does for you:
-
--stdiois the default. A barepx-lspspeaks LSP on stdin/stdout.--node-ipc,--socket={port}and--pipe={name}work as with every vscode-languageserver-based server. -
px-lsp --versionprints the version and exits, so install scripts and health checks do not need an LSP handshake. -
The bundled data travels with the package. The tarball's "do not flatten" trap does not exist here: npm keeps
dist/anddata/siblings, and the server finds its CK3/Vic3 fallback data automatically. The startupwindow/logMessageline names the resolved data directory.
A local (non-global) install works the same; the bin lands in node_modules/.bin/px-lsp, or resolve the bundle directly:
const serverJs = require
.resolve("@px-lsp/server/package.json")
.replace(/package\.json$/, "dist/server.js");One game per server instance. There is no auto-detection outside VS Code: pass gameId ("ck3" (default), "vic3", "eu5") in the initialization options, and gamePath/logsPath for that game. The full settings shape and the per-game paths are in Outside VS Code; the reference for everything an embedder must know (process contract, orphan watchdog, URIs, document sync) is the Embedding page.
New in server 0.3.0. A web page cannot spawn a process, so the browser subpath is the same parser, schema, token tables and scope engine assembled as a plain library against one in-memory document. No child process, no JSON-RPC, no workspace scan, no filesystem.
import { createBrowserLanguageService } from "@px-lsp/server/browser";
const tokens = await (await fetch("/px/tokens.json")).json();
const freqs = await (await fetch("/px/freqs.json")).json();
const service = createBrowserLanguageService({ tokens, freqs });
const doc = service.openDocument("events/tutorial.txt", text);
doc.diagnostics();
doc.completions(offset);
doc.hover(offset);
doc.scopeAt(offset);openDocument takes a mod-relative path, because the schema classifies a file by its folder: events/tutorial.txt gets the event grammar and root scope, common/scripted_effects/00_x.txt gets that one, and doc.kind is null for a folder the schema does not know.
The token tables are baked at build time rather than parsed in the page. pnpm run bake:browser runs the same script_docs and wikidocs parsers the node server runs at startup and writes dist/browser-data/<gameId>/{tokens,docs,freqs}.json, published as @px-lsp/server/browser-data/<gameId>/…. The split is what makes it usable over a network: completions and diagnostics answer after 225 KB brotli (the bundle, the token table and the ranking frequencies), and the 72 KB of hover prose in docs.json can arrive at the first hover, or never.
service.attachDocs(await (await fetch("/px/docs.json")).json());The service has exactly one file, and it says so on capabilities: no workspace index, so a reference to a trait or scripted effect defined in another file does not resolve, and the unknown-reference diagnostics are omitted rather than guessed. Diagnostics are the structural and file-layout class only. .gui layout, DDS decoding, [ … ] datafunctions and the tiger runner need node or a game install and are absent. The Embedding page has the full contract, including the payload table and how the node builtins are shimmed.
The wire contract: every custom request/notification name, its payload types, the settings and client-capability shapes, plus pure helpers the server and clients share (tiger JSON parsing, descriptor.mod parsing, diagnostic suppression, localization helpers). Zero runtime dependencies, no vscode import, compiled JavaScript with type declarations.
// The root export is the wire contract:
import { modOverviewRequest, type ModOverview, type ParadoxSettings } from "@px-lsp/protocol";
// Helpers live in named modules:
import { parseTigerJson } from "@px-lsp/protocol/tigerParser";
import { parseDescriptor, validateDescriptor } from "@px-lsp/protocol/descriptorMod";The request exports are plain method-name strings (modOverviewRequest === "paradox/modOverview"), so they plug into any JSON-RPC library; the value of the package is that your payloads compile against the same types the server compiles against. Clients in other languages code against the Protocol Reference instead, which documents the same contract method by method.
0.2.0 added the two Examples Wiki requests (paradox/exampleWiki and paradox/exampleWikiEntry), the hoverIcons client capability, the hoverDetail setting, the kinds map that gives one concept one glyph across hover, completion and tree views, and the workshopMeta reader/writer behind Steam Workshop publishing.
0.2.1 added the wire pair the Content Creators are built on (paradox/definitionForm and paradox/definitionEdit), paradox/dynastyTree, paradox/modifierFormats (how the game prints a modifier), paradox/locText (a loc value as the player reads it), paradox/snippets, and the calendarFile helpers for <mod>/.px-toolkit/calendar.json. Nothing was renamed or removed in either release.
A complete, runnable Node client: spawn the server, initialize it for CK3, watch the status notifications, call a custom request. Dependencies: @px-lsp/server, @px-lsp/protocol, vscode-jsonrpc.
import { spawn } from "node:child_process";
import { createRequire } from "node:module";
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node";
import { indexStatsRequest, statusNotification } from "@px-lsp/protocol";
const require = createRequire(import.meta.url);
const serverJs = require.resolve("@px-lsp/server/package.json").replace(/package\.json$/, "dist/server.js");
const server = spawn(process.execPath, [serverJs], { stdio: ["pipe", "pipe", "inherit"] });
const connection = createMessageConnection(
new StreamMessageReader(server.stdout),
new StreamMessageWriter(server.stdin)
);
connection.onNotification(statusNotification, (status) => {
console.log("status:", status); // tokens, definitions, indexing: the "is it alive" mirror
});
connection.listen();
const init = await connection.sendRequest("initialize", {
processId: process.pid,
rootUri: null, // or the file:// URI of the mod folder you want indexed
capabilities: {},
initializationOptions: {
settings: {
gameId: "ck3", // "ck3" | "vic3" | "eu5", one game per server instance
gamePath: null, // "<steam>/steamapps/common/Crusader Kings III/game"
logsPath: null, // your script_docs dump folder
locLanguage: "english",
},
},
});
console.log("connected to", init.serverInfo.name, init.serverInfo.version);
await connection.sendNotification("initialized", {});
const stats = await connection.sendRequest(indexStatsRequest);
console.log("index:", stats);
await connection.sendRequest("shutdown");
await connection.sendNotification("exit");With no rootUri and no gamePath this still connects and loads the bundled CK3 vocabulary (the status line reports the token count); point rootUri at a mod folder and the index fills with its definitions. From here, standard LSP works as in any client (textDocument/didOpen, then completion, hover, diagnostics via textDocument/publishDiagnostics), and the paradox/* methods in the Protocol Reference add the mod overview, the event graph, GUI layout and scope inference. The process contract an application should follow (heap ceiling, orphan watchdog, shutdown sequence, URIs, document sync) is on the Embedding page.
Do not set the client capability object: it is how the VS Code client announces the editor commands it registers. Leaving it out gives clean markdown hovers and real WorkspaceEdit quick fixes, which is what an embedder wants.
- The packages version independently of the extension and of each other; a package bumps only when it changes. Current:
@px-lsp/server0.3.1 and@px-lsp/protocol0.2.1, both shipping with the toolkit's 0.4.0 release. First release: 0.1.0 for both. - Wire-contract changes are treated as API changes: they bump
@px-lsp/protocoland land in the Protocol Reference in the same release. Additions are backward-compatible; a rename or a removal is called out in the package changelog. - License: GPL-3.0-or-later. The server package bundles third-party data with its own terms (
THIRD-PARTY-NOTICES.md,data/ck3/wikidocs/ATTRIBUTION.md). - Something broken or missing? Open an issue.
Wiki notice: This wiki is mainly AI-generated, with limited human review and moderation. Pages primarily describe the latest preview version of the toolkit and may contain errors or differ from stable and older releases.
Repository · Releases · Changelog · Report a bug · Credits
Extension id JDeffner.px-toolkit. Licensed GPL-3.0-or-later; bundled third-party data keeps its own terms (notices).