praxisjs CLI
praxisjs — maintenance CLI for existing PraxisJS projects. `add kosmesis` for Kosmesis UI components, `ai add` / `ai remove` / `ai sync` for AI integrations, `doctor` for diagnostics, `upgrade` for dependency updates.
praxisjs CLI
praxisjs is a CLI for maintaining an existing PraxisJS project — as opposed to create-praxisjs, which only scaffolds new ones. No install required:
npx praxisjs <command>| Command | Purpose |
|---|---|
add kosmesis | Set up Kosmesis UI components in the project |
ai add | Install an AI integration (Claude Code or Codex skills and the PraxisJS MCP server) |
ai remove | Uninstall an AI integration |
ai sync | Update installed AI integrations (skills, MCP configuration) to this version |
doctor | Diagnose common project issues |
upgrade | Update @praxisjs/*, @morphos/* and kosmesis dependencies to their latest compatible version |
Running praxisjs with no arguments, an unknown command, ai without a subcommand or add without kosmesis prints this usage list and exits with code 1.
add kosmesis
Sets up Kosmesis in the current project, the same way create-praxisjs --kosmesis does for a new one:
npx praxisjs add kosmesis- Checks that
package.jsondepends on@praxisjs/core(exits with code1otherwise) and reports what it found: the closest template (minimal, router, full or blog), a Tauri v2 app (src-tauri/tauri.conf.jsonor@tauri-apps/api), the package manager (from the lockfile) and an installed AI integration. - Stops with a hint when
components.jsonalready exists: Kosmesis is set up, add components withkosmesis add. - Runs the project's install command when
node_modulesis missing. - Runs
kosmesis initthrough the package manager (npx,pnpm dlx,yarn dlxorbunx, withkosmesis@^0.6.0), thenkosmesis addfor the components you asked for. - Prints how to import a component, add more and run the app (
tauri:devin a Tauri project). With an AI integration installed, it ends with the prompt to map the project into the memory, so the agent records the new components.
In a terminal and without flags, it asks whether to add the starter components (button, card) and then lets kosmesis init ask its own questions. Any flag runs it without prompts (--yes alone too), with kosmesis init --yes and the defaults for whatever you don't pass:
| Flag | Effect |
|---|---|
--style-system <tailwind|praxisjs-css> | Styling system (default tailwind) |
--components <a,b> | Components to add after the setup, e.g. button,card (default: none) |
-y, --yes | No prompts |
--icon-library, --css, --root-component, --erase, --no-erase, --overwrite, --no-overwrite, --force | Passed to kosmesis init as given |
-h, --help | Prints the flags |
npx praxisjs add kosmesis --style-system praxisjs-css --components button,card,dialogIf kosmesis fails (no network, or a release without the flags), it shows why, prints the commands to finish by hand in the project and exits with code 1.
ai add
Installs an AI integration into the current project. Prompts for which one:
npx praxisjs ai add| Integration | Skills | MCP server | Also creates if missing |
|---|---|---|---|
| Claude Code | .claude/skills/praxisjs/ plus the DX skills .claude/skills/praxisjs-*/ | added to .mcp.json and approved in .claude/settings.json (enabledMcpjsonServers) | — |
| Codex | .agents/skills/praxisjs/ plus .agents/skills/praxisjs-*/ | added to .codex/config.toml as [mcp_servers.praxisjs] (Codex loads it in trusted projects) | AGENTS.md at the project root |
Existing config files are merged, never replaced: other MCP servers, settings and approvals stay as they are, and running ai add again only refreshes the skills. A config file that isn't valid JSON is left alone with a warning, so you can add the server by hand. A praxisjs entry under mcpServers in .claude/settings.json, written by earlier versions, is removed. Claude Code never reads MCP servers from that file, so with those versions the server never started.
The skills create CLAUDE.md / AGENTS.md, .praxisjs-ai.json and the project memory (.praxis/) the first time they run — see the AI skills guide for what each file is for.
It ends with a prompt to paste into the agent, which maps the project into the memory with the praxisjs-memory-bootstrap skill. Updating an integration that is already installed prints it only when the update added the MCP server.
During create-praxisjs scaffolding, the same integration picker appears as the "Add an AI integration?" step — ai add is only needed for a project that already exists.
ai remove
Uninstalls an AI integration. Prompts for which one, then asks for confirmation before deleting anything:
npx praxisjs ai remove| Integration | Deletes | Leaves untouched |
|---|---|---|
| Claude Code | .claude/skills/praxisjs*/, the praxisjs server in .mcp.json and its approval in .claude/settings.json (a file left empty is deleted) | CLAUDE.md, .praxisjs-ai.json, .praxis/, your other skills and servers |
| Codex | .agents/skills/praxisjs*/, the [mcp_servers.praxisjs] tables in .codex/config.toml | AGENTS.md, .praxisjs-ai.json, .praxis/, the rest of the config |
The memory file, .praxisjs-ai.json and .praxis/ are never deleted — they hold project context and preferences that aren't owned exclusively by the integration you're removing (and may still be read by the other one).
ai sync
Brings every installed AI integration up to the CLI's version — typically after upgrading praxisjs, or in a project set up by an earlier release:
npx praxisjs ai sync --dry-run # show what would change
npx praxisjs ai syncFor each integration it finds (a skill directory or an entry in the lock), it compares the files on disk with the ones this version ships:
| File state | What happens |
|---|---|
| Missing (a new skill or reference) | Added |
| An unedited copy from an earlier release | Replaced |
| Shipped earlier but no longer | Removed (and its folder, once empty) |
| Edited by you | Shows the diff and asks before replacing or removing it; without a terminal (no TTY, or CI set) it is always kept |
| Already current | Left alone |
"Unedited" means the file matches what the CLI recorded when it installed it, in .praxisjs-ai.lock.json (one hash per file, per integration; commit it), or, for installs made before the lock existed, one of the files earlier releases shipped. It also rewrites the MCP server configuration: the praxisjs server in .mcp.json gets the current command (a server pointing at a local praxisjs-mcp binary is kept, and so are extra keys such as env), its approval in .claude/settings.json, and the [mcp_servers.praxisjs] table in .codex/config.toml. Running it twice changes nothing the second time. It ends with a summary per integration: files written, removed and kept with your edits, and the config files it changed.
When the sync adds the MCP server to an integration that didn't have it configured (a new .mcp.json or .codex/config.toml entry, or a server moved out of .claude/settings.json), it ends with the prompt to map the project into the memory.
ai add on an integration that is already installed runs the same update instead of overwriting it. The project memory (.praxis/) is updated separately, by the agent, with praxisjs_memory_migrate.
doctor
Diagnoses common problems in the current project:
npx praxisjs doctor| Check | Flags an issue when |
|---|---|
package.json | Missing, malformed, or has no @praxisjs/* dependency |
| Versions | Two @praxisjs/* packages are on versions that don't work together (see below) |
tsconfig.json | Missing, malformed, or compilerOptions doesn't have jsx: "react-jsx", jsxImportSource: "@praxisjs/jsx", and useDefineForClassFields: true — the tsconfig check only runs once package.json passes |
| Claude Code integration | The skill directory exists but a DX skill, the praxisjs server in .mcp.json, CLAUDE.md, or .praxisjs-ai.json is missing |
| Codex integration | The skill directory exists but a DX skill, the praxisjs server (in .codex/config.toml or ~/.codex/config.toml), AGENTS.md, or .praxisjs-ai.json is missing |
| AI setup version | An installed integration's skills or MCP configuration differ from what this version installs — run ai sync |
The version check reads the installed version from node_modules and falls back to the version in the declared range. It flags these pairs, each with a praxisjs upgrade hint:
| When the project has | It also needs | Because |
|---|---|---|
@praxisjs/vite-plugin 4.1+ | @praxisjs/runtime 0.8+ as a dependency | Compiled templates and the keyed-list rewrite import For and the template helpers from the runtime |
@praxisjs/jsx 0.8+ | @praxisjs/runtime 0.8+ | Fragments are mounted with the runtime's mountChildren |
@praxisjs/decorators 1.8+ | @praxisjs/core 2.3+ | @Selector is built on core's selector() |
@praxisjs/* packages depend on each other at exact versions, so a mismatch also installs a second copy of the older package. The version and tsconfig checks only run once package.json passes. Each AI integration check only runs if its skill directory is present — doctor doesn't flag an integration that was never installed. Exits with code 1 if any check fails, 0 if everything passes.
upgrade
Updates the PraxisJS ecosystem packages in package.json to their latest published version:
npx praxisjs upgrade| Ecosystem | Packages |
|---|---|
| PraxisJS | @praxisjs/* |
| Morphos | @morphos/* |
| Kosmesis | kosmesis (the CLI) |
Only packages the project already lists are upgraded; upgrade never adds one. For each of them in dependencies, devDependencies, and peerDependencies:
- Skips it if the range is
workspace:*(monorepo-internal, not a real npm version) or isn't a plainx.y.z,^x.y.zor~x.y.zrange (a prerelease suffix is fine). - Looks up the latest published version from the registry in
npm_config_registry(the public npm registry by default). Every package is looked up once, all at the same time. - Skips it if it is already on that version or a newer one, such as a prerelease ahead of
latest.upgradenever moves a dependency backwards. - Otherwise rewrites the range, preserving its prefix —
^1.0.0becomes^2.0.0,1.0.0becomes2.0.0,~1.0.0becomes~2.0.0.
Compatible versions
Before writing anything, upgrade reads the peerDependencies of every version it is about to install (and of the installed version of every package it leaves alone, unless that copy is outside the range package.json declares, as an upgrade whose install failed leaves it) and checks them against the other ecosystem packages in the project. Morphos packages declare the @praxisjs/* and @morphos/core ranges they work with, so this is what keeps them in step. When an upgrade would break a peer range, it is held back instead of producing a broken install:
- If the new release of a package needs a peer the project won't have (a Morphos release that needs a newer
@praxisjs/corethan the latest one), that package stays on its current version. - If a package that isn't moving doesn't accept the new release of one of its peers (a Morphos package that doesn't support a new
@praxisjs/coremajor yet), the peer stays on its current version. - Holding back one
@praxisjs/*package holds back every@praxisjs/*package: releases before PraxisJS 0.8.2 depend on each other at exact versions, and splitting them installs a second copy.
Held back packages are listed in yellow with the reason, under their ecosystem. Peer conflicts the project already had before the upgrade, which no upgrade fixes, are listed under Already incompatible without changing anything. Run upgrade again once a compatible release is out.
◇ PraxisJS
│ @praxisjs/core: ^2.2.0 → ^2.3.0
│ @praxisjs/runtime: ^0.7.1 → ^0.8.0
◇ Morphos
│ @morphos/core: ^0.1.3 → ^0.1.4
│ @morphos/inputs: held back at ^1.0.3 (@morphos/[email protected] needs @praxisjs/core >=3.0.0, the project would have 2.3.0)
◇ Kosmesis
│ kosmesis: ^0.4.0 → ^0.5.0Install
If anything changed, upgrade writes package.json and runs the detected package manager's install command — pnpm install, yarn, bun install, or npm install, based on the lockfile present in the project root (pnpm-lock.yaml, yarn.lock, bun.lock or bun.lockb). A package.json that isn't valid JSON is reported and upgrade exits with code 1 without changing anything.
With npm it first removes the @praxisjs/*, @morphos/* and kosmesis entries from package-lock.json, and their folders from node_modules (with npm's hidden lockfile, node_modules/.package-lock.json): the old releases recorded there require exact versions of each other, which made npm install fail with ERESOLVE (or crash in its peer resolution) after an upgrade. Every other dependency keeps its locked version.
When package.json is already up to date but the versions installed in node_modules don't match it, an earlier upgrade did not finish installing. upgrade lists those packages under Installed versions out of date and runs the install again, ending with Dependencies repaired.
If the install fails, upgrade reports it without crashing, shows the end of the package manager's output and the command to run manually.
Before installing, upgrade checks the toolchain against two minimums:
| Tool | Minimum | Why |
|---|---|---|
| Node.js | ^20.19.0 || >=22.12.0 | What Vite 8, which every template builds with, runs on |
| npm | 11.6.0 | Older releases can crash with Cannot read properties of null (reading 'edgesOut') while resolving peer dependencies (vite 8's optional peers reach the vitest packages, which pin each other at exact versions) |
An older Node.js is reported with a link to nodejs.org; upgrade can't update it for you. In an npm project, an older npm makes upgrade ask to update it before installing, to the newest npm release your Node.js supports (the latest npm may need a newer Node.js), with npm install --global npm@<version>. Outside a terminal it prints that command instead. If the install then fails, the Install failed note says which of the two to update first.
A project an older upgrade left broken
Run npx praxisjs@latest upgrade again: it finds the packages that don't match package.json, clears their stale lockfile entries and installs. Deleting node_modules and package-lock.json and running npm install works too, but it also unlocks every other dependency.
Kosmesis components
Kosmesis components are source files copied into the project by kosmesis add, not npm packages, so upgrade doesn't change them. When the project uses Kosmesis (it lists kosmesis or has a components.json with a styleSystem field), upgrade ends with a Kosmesis components section:
- With
kosmesis0.5.0 or later installed innode_modules, it runskosmesis outdatedand shows its report. - Otherwise it prints the command to run yourself, such as
npx kosmesis outdated(pnpm dlx,yarn dlxorbunxfor the other package managers).
Review and apply each component update with kosmesis update <name>, which shows the diff before overwriting your copy.
Finally, when an installed AI integration is outdated, upgrade offers to run ai sync (in a terminal) or prints the command (otherwise).
upgrade only touches the ecosystem packages above. It won't bump unrelated dependencies like vite, typescript or lucide.