CLI
@pocketjs/cli installs the pocket executable. pocket runs on Bun 1.2 or
later: it resolves a PocketJS framework — a source checkout or a project's
installed @pocketjs/framework — and launches that framework's Bun scripts with
its own Bun executable. Under Node, pocket prints the Bun install hint and
exits with status 1.
bun add -g @pocketjs/cli # or: npm install -g @pocketjs/cli
bunx @pocketjs/cli show # one invocation without a global installCommands
| Command | Runs | Where |
|---|---|---|
pocket create <name> [options] |
scaffold, in the CLI | any directory |
pocket [+<id>] check, compile, build |
target selection, then tools/pocket.ts |
checkout or project |
pocket [+<id>] show [--json] |
the framework, project, selection, and the install state of every target | any directory |
pocket target add, list, remove |
target toolchains, in tools/pocket.ts |
checkout or project |
pocket default, pocket override |
the user default and directory overrides | any directory |
pocket dev, psp, vita, hw, psplink, devtools, tape, play, symbian, ios |
tools/<command>.ts |
checkout |
pocket --version |
CLI version and resolved framework version | any directory |
pocket with no command, help or --help prints the command list.
Framework resolution
A framework is a directory whose package.json carries
"pocketjs": { "protocol": <n> }. The package name is not read, so a fork
published under another name is recognized. check, compile, build,
show, target and --version take:
POCKETJS_FRAMEWORK_ROOT, when set. A directory without the marker fails the command.- Otherwise the nearest of these above the current directory: an ancestor
that carries the marker (a checkout), or an ancestor's
node_modules/@pocketjs/framework. When one directory is both, the checkout wins andshownames the install it passed over.
The CLI speaks protocol 1. A framework on another protocol stops every
command that runs it, with both versions named. show reports the
framework's source: POCKETJS_FRAMEWORK_ROOT, checkout, registry, a git
dependency from the lockfile, a file: or link: dependency, or
link → <path> for a symlinked install.
Switching to a fork
| Situation | Operation |
|---|---|
| develop a fork against an application | bun link in the fork, then bun link @pocketjs/framework in the application; package.json is not edited, and bun install restores the registry version |
| a fork with another package name | "@pocketjs/framework": "link:<fork-name>" |
| pin a fork commit for a team or CI | "@pocketjs/framework": "github:<owner>/pocketjs#<ref>" |
| a fork published under another name | "@pocketjs/framework": "npm:<name>@<range>" |
| one command | POCKETJS_FRAMEWORK_ROOT=<dir> pocket … |
Manifest commands run in the current directory. --manifest,
--host-profile, --project-root and --compiler-receipt resolve against it;
--outdir, --plan-dir, --output and --depfile resolve against the
project root. The host-development commands use step 2 alone and run with the
checkout root as their working directory.
Target selection
check, compile, build and show act on one target, the first of these
that is present:
- Explicit:
pocket +<id> <command>,pocket <command> +<id>,--target <id>, or--host-profile <file>. Two that differ are an error;+<id>anywhere else before--is an error. POCKET_TARGET.- A directory override of the current directory or its nearest ancestor
that has one (
pocket override). - The project: the first entry of
targetsin the project configuration. - The user default (
pocket default).
None present stops the command with the five ways to name a target. A command prints the target and its source before it runs:
pocket: target vita · directory override /work/gamepocket build --all builds every target of the project configuration in
order and stops at the first failure.
Project configuration
targets in pocket.config.ts lists the targets a project ships to:
import { definePocketConfig } from "@pocketjs/framework/config";
export default definePocketConfig({
targets: [
"psp",
"vita",
{ target: "moto-g-play", manifest: "pocket.android.json" },
{ hostProfile: "../firmware/pocket.host.json" },
],
});The project configuration is the nearest pocket.config.ts above the
manifest's directory, the file tools/build.ts reads theme from; the
lookup stops there, and a file without targets names no project default.
manifest and hostProfile resolve against the file's directory. An entry's
manifest replaces the default pocket.json when the command names no
--manifest. A hostProfile entry is a target named by the profile's id,
which may not equal a catalog id or a pocket command word. An unknown id, a
missing profile and a duplicate entry are errors. The file is bundled with
@pocketjs/framework mapped to the resolved framework, so it evaluates in a
project without node_modules.
show, default, override
| Command | Effect |
|---|---|
pocket show [--json] [--manifest <file>] |
the framework with its source, the project configuration and its targets, the selected target with its source, the host, and every catalog target with its install state; exits 0 |
pocket default |
prints the user default |
pocket default <id>, pocket default --unset |
sets or removes it |
pocket override set <id> [--path <dir>] |
selects <id> for <dir> (default the current directory) and every directory below it |
pocket override unset [--path <dir>], pocket override list |
removes one, or lists them with the one active here |
default and override set accept a catalog id or a host profile id of the
current project. Both live in <config>/pocket-nexus/settings.json, where
<config> is XDG_CONFIG_HOME or ~/.config; the file is written by rename.
Target toolchains
The framework pins each target's native toolchain in
tools/targets/<id>.toolchain.json; the CLI holds no pins. pocket target add
installs the pinned components into the cache, and pocket build reads every
tool path from the install record of the toolchain the framework pins.
| Command | Effect |
|---|---|
pocket target add [<id> …] |
installs each target's components; with no id, the targets of the project configuration. A second run downloads nothing |
pocket target list [--json] |
every catalog target and every host profile of the project, with its install state |
pocket target list --check |
exits 1 unless every target of the project configuration is ready to build |
pocket target remove <id> … |
deletes the target's install records and the store entries no other record names, after running builds of the target finish |
In a checkout, bun run pocket target add psp runs the same command without
the CLI.
Install states
| State | Meaning |
|---|---|
installed |
the record for the toolchain digest the framework pins exists, and every component it names is present |
not installed |
no record on this host |
outdated |
a record for another toolchain digest, written by another framework version |
broken |
the record names a component that is gone: a deleted store entry, or a rustup toolchain, component or target that was uninstalled |
unsupported |
the toolchain does not install on this host |
nothing to install |
pocket build runs the compiler alone: web-app, macos-app, linux-app, macos-widget |
no manifest |
the framework carries no toolchain manifest for the target; its build script finds its own tools |
host profile |
an ESP-IDF host profile of the project; the compiler builds it |
pocket build for a target that is not ready stops before type checking,
naming the command that installs it:
pocket: target psp is not installed for @pocketjs/framework 0.14.0 (toolchain 3f9a12c0)
run: pocket target add pspA build holds the target's install record shared until its backend finishes;
target add and target remove hold it exclusive.
Components
| Kind | Pin | Install | Verification |
|---|---|---|---|
rustup |
a version or a dated channel, never stable; components; targets |
rustup toolchain install --profile minimal, component add, target add |
the toolchain, its components and its targets in rustup's lists |
archive |
URL and SHA-256, one source or one per host; zip, tar.gz or tar.xz; the directory that becomes the tree; marker files |
download, then unzip or tar |
SHA-256 before extraction; entries and symbolic links that leave the directory are rejected; the markers after |
file |
URL and SHA-256, one source or one per host; the file name | download | SHA-256 |
cargo-install |
git repository and full revision, package, binaries, a rustup toolchain of the same manifest | cargo install --locked --root |
the binaries exist |
rustup is a requirement, not a component. pocket target add finds rustup
on PATH or in $CARGO_HOME/bin and never uninstalls a rustup toolchain,
which other projects share. Backends run Rust with RUSTUP_AUTO_INSTALL=0, so
a rust-toolchain.toml cannot install a toolchain the record does not name.
PSP
| Component | Pin |
|---|---|
rust |
nightly-2026-05-28 with rust-src |
cargo-psp |
cargo-psp, prxgen, prxmin, pack-pbp and mksfo from pocket-nexus/rust-psp at the revision hosts/psp links |
sdk |
the pocket-nexus/pspdev release sdk-noabicalls-normalized-2026-06-19: headers and the libraries the EBOOT links |
quickjs |
libquickjs-mipsel-sony-psp.a, which pocket-nexus/quickjs-rs publishes as prebuilt-psp-<commit> for the revision hosts/psp links |
A PSP build runs no C compiler. QuickJS is the PSP host's one C dependency,
and libquickjs-sys links the archive QUICKJS_PREBUILT_LIB names. The PSP
toolchain installs on macOS and Linux, arm64 and x64.
Cache
<cache>/store/<component digest>/receipt.json a published component
<cache>/store/<component digest>/tree/ its files
<cache>/targets/<id>/<toolchain digest>.json an install record
<cache>/locks/ <cache>/tmp/<cache> is POCKET_NEXUS_CACHE_DIR, or pocket-nexus under XDG_CACHE_HOME
or ~/.cache. A component digest is the SHA-256 of the component's pin, so
two targets or two framework versions with equal pins share one store entry. A
toolchain digest covers a target's components for one host; a pin change gives
a new digest and a new record, and the previous record stays until
pocket target remove. Every install stages under tmp/ and publishes by
rename, so an interrupted install leaves no store entry and no record.
create
pocket create <name> [--target <target>] [--framework <framework>] [--host-profile <file>]| Option | Values | Default |
|---|---|---|
<name> |
lowercase kebab-case, ^[a-z][a-z0-9-]*$ |
required |
--target |
psp, vita, pocketbook, web-app, macos-app, linux-app, esp-idf |
psp; esp-idf with --host-profile |
--framework |
solid, vue-vapor, octane |
solid |
--host-profile |
path to an ESP-IDF pocket.host.json |
none |
--host-profile with a target other than esp-idf is an error. macos-widget
is not a create target: widget-form targets host dynamic viewports, and the
scaffold declares a fixed viewport. An existing destination directory is an
error.
Output
Inside a checkout, create writes apps/<name>/ with pocket.json,
pocket.config.ts, main.tsx and app.tsx, and prints the check and
build commands with --manifest apps/<name>/pocket.json. Anywhere else it writes a standalone
project at ./<name>/:
| File | Content |
|---|---|
pocket.json |
manifest v2: id dev.example.<name> with dots for hyphens, version 0.1.0, entry main.tsx, output <name>-main, the framework, viewport and requires |
pocket.config.ts |
targets with the target, or the host profile for esp-idf |
main.tsx |
the mount entry for the framework (Octane imports mount from @pocketjs/framework) |
app.tsx |
a counter component; CROSS increments it when the target has buttons |
package.json |
@pocketjs/framework ^<CLI version>, the framework's runtime package (solid-js, vue or octane) at the version the framework pins, @pocketjs/cli as a dev dependency, and check, compile, build scripts |
tsconfig.json |
editor type checking with jsx: preserve, including the framework's jsx.d.ts (and vue-sfc.d.ts for Vue Vapor); pocket check extends this file |
README.md |
the build commands; for esp-idf, the CMake integration |
.gitignore |
node_modules/, dist/, .pocket/ |
pocket.host.json |
esp-idf without --host-profile |
Outside ESP-IDF the manifest declares a 480×272 integer-fit viewport and
requires text.glyphs.baked and input.buttons. pocket.config.ts names the
target, so the package.json scripts carry none: pocket check,
pocket compile, and pocket build, with -- --release for psp and vita
and --output dist/<name>.pocket for esp-idf. A standalone project builds with
bun install, bun run check and bun run build.
ESP-IDF scaffold
--host-profile reads a firmware's pocket.host.json and derives the manifest
from it:
| Manifest field | Source |
|---|---|
app.viewport.logical |
the first display.logicalViewports entry |
app.viewport.presentation |
native when display.presentations lists it; the first entry otherwise |
input.buttons in requires, and the CROSS handler |
present when capabilities lists input.buttons |
pocket.config.ts records the profile path relative to the application
directory.
create reads the three fields above; pocket check validates the full
profile schema. --target esp-idf without --host-profile writes a starting
pocket.host.json for the firmware repository to own: id <name> cut to 15
bytes, takeover form, 60 Hz, a 320×240 physical and logical viewport,
native presentation, density 1, input.buttons and text.glyphs.baked.
The README carries pocketjs_compile_app and pocketjs_embed_package calls
for an application directory at <firmware root>/<name>/. See
ESP-IDF for both integrations.
check, compile, build
pocket [+<id>] <check|compile|build> [+<id>] [--target <id> | --host-profile <file>] [options] [-- <backend args>]| Option | Default | Effect |
|---|---|---|
--target <id> |
selection | a target of the catalog |
--host-profile <file> |
selection | an ESP-IDF host profile |
--all |
— | build every target of the project configuration |
--manifest <file> |
pocket.json |
the application manifest |
--project-root <dir> |
the manifest's directory | base for the entry, tsconfig.json, --outdir, --plan-dir, --output and --depfile |
--outdir <dir> |
dist |
compiled output |
--plan-dir <dir> |
.pocket |
resolved plans, one directory per target |
--output <file> |
<outdir>/<output>.pocket |
package path; build --host-profile |
--depfile <file> |
— | Ninja depfile of every compiler input; build --host-profile |
--compiler-receipt <file> |
— | an extra depfile input for a product-owned toolchain receipt |
-- <args> |
— | arguments for the PSP or Vita backend, such as --release |
An unrecognized option before -- is an error.
checkwrites nothing. It validates thepocket.jsonv2 schema, the target's capabilities againstrequires, the viewport, and the TypeScript of the entry and its reachable imports, using the project'stsconfig.jsonwhen one exists. It prints the plan hash.compilerunscheck, writes<plan-dir>/<contract target>/plan.json, and compiles<outdir>/<output>.jsand<outdir>/<output>.pakfrom that plan.buildrunscompileand hands the plan to the target's backend:
| Target | build result |
|---|---|
psp |
tools/psp.ts links the EBOOT with the installed PSP toolchain: <framework>/hosts/psp/target/mipsel-sony-psp/<profile>/EBOOT.PBP, where <profile> is release with -- --release and debug otherwise |
vita |
tools/vita.ts builds with cargo vita and writes <outdir>/vita/<output>.vpk, .self and .runtime.json; requires VitaSDK in VITASDK or ~/vitasdk |
pocketbook, web-app, macos-app, linux-app, macos-widget |
the compile output; build prints the bundle path and the host that loads it |
device targets (3ds, moto-g-play, nokia-e7, …) |
check and compile; build stops with the path compile wrote, because these targets build through their own scripts |
--host-profile |
a .pocket package holding the manifest and one variant: the target id, HostOps ABI, canonical plan, JS and pak |
pocketjs_compile_app in the ESP-IDF component runs
pocket build --host-profile with --depfile, so Ninja rebuilds the package
when an imported module, asset or the profile changes.
Platform contracts describes the plan and the
backend table; Build pipeline describes the compiler
that compile runs.
Targets
--target takes a catalog id. Registered targets keep the id of
contracts/spec/platforms.ts; a device target is named after the device and
resolves against its profile module, whose contract id the plan and the native
host carry. A contract id given to --target stops with the catalog id it
belongs to.
| Catalog id | Contract target | Toolchain hosts |
|---|---|---|
psp, vita, pocketbook, web-app |
the same id | macOS, Linux |
macos-app, macos-widget |
the same id | macOS |
linux-app |
the same id | Linux |
3ds |
3ds-dev |
macOS, Linux |
moto-g-play, redmi-1s |
moto-g-play-dev, redmi-1s-dev |
macOS, Linux x64 |
blackberry-classic |
blackberry-qnx-dev |
macOS, Linux |
meizu-m8 |
meizu-m8-dev |
macOS, Linux |
nokia-e7 |
symbian-e7-dev |
macOS, Linux |
ios-simulator |
ios-dev |
macOS on Apple silicon |
iphone2g, iphone4s, ipodtouch6, ipodtouch4 |
iphone2g-dev, iphone4s-dev, ipodtouch-dev, ipodtouch4-dev |
macOS |
bun tools/pocket.ts catalog --json prints the catalog with each target's
contract and hosts; pocket target list adds the install state.
Host-development commands
These commands run inside a checkout with the checkout root as working directory, and pass their arguments to the script unchanged.
| Command | Script | Purpose |
|---|---|---|
pocket dev [<app>-main …] |
tools/dev.ts |
build the wasm core and the named demos (hero-main when none is named), then serve the browser host on 127.0.0.1:8130 |
pocket psp <app> |
tools/psp.ts |
build a demo's EBOOT |
pocket vita <app> |
tools/vita.ts |
build a demo's VPK |
pocket hw [<app>] |
tools/hw.ts |
build a demo and run it on a PSP over PSPLINK |
pocket psplink |
tools/psplink.ts |
an interactive multi-app switcher for a PSP over PSPLINK |
pocket devtools [<app>] |
tools/devtools.ts |
the DevTools panel and the USB debug bridge |
pocket tape record|replay|tree … |
tools/tape.ts |
record, replay and inspect input tapes against a demo bundle without a screen |
pocket play vita <demo> |
tools/play.ts |
build a demo's VPK, install it under a per-demo Title ID and launch it in Vita3K |
pocket play ios <demo> |
tools/play.ts |
stage a demo into the NativeScript shell and launch it on the iOS simulator |
pocket symbian doctor|setup|build|deploy|coda … |
tools/symbian.ts |
the Nokia E7 toolchain, SIS packaging and CODA USB transport |
pocket ios doctor|setup|devices|native|build|stage|play … |
tools/ios.ts |
the Apple iOS toolchain and simulator flow |
Environment variables
| Variable | Read by | Effect |
|---|---|---|
POCKETJS_FRAMEWORK_ROOT |
manifest commands, show, target, --version |
the framework directory; step 1 of resolution |
POCKET_TARGET |
manifest commands, show |
the target; step 2 of selection |
XDG_CONFIG_HOME |
show, default, override, manifest commands |
parent of pocket-nexus/settings.json; ~/.config when unset |
POCKET_NEXUS_CACHE_DIR |
target, show, build |
the cache root |
XDG_CACHE_HOME |
target, show, build |
parent of pocket-nexus/ when POCKET_NEXUS_CACHE_DIR is unset |
CARGO_HOME |
target, show, build |
bin/rustup there when rustup is not on PATH; ~/.cargo when unset |
VITASDK |
Vita builds | the VitaSDK root; ~/vitasdk when unset |
PORT |
pocket dev, pocket devtools |
the dev server port; 8130 when unset |