The @gjsify/cli package ships the gjsify binary. Run it via npx @gjsify/cli <command> or add it as a dev dependency.
Tip:npx @gjsify/cli --help lists all commands and flags. The output ends with a Running on … line showing the active runtime — Running on Node.js v24.x.y when invoked via the Node bin, Running on GJS 1.x.y (SpiderMonkey) when running the GJS bundle, or Running on Bun x.y.z / Running on Deno x.y.z when invoked via bunx / deno run. The CLI itself runs on all four, and this detected host runtime is also what drives the default --app build target and the default --runtime for run / showcase / storybook below.
Target runtime. Defaults to the target of the host runtime running the CLI: gjs under a GJS host, node under Node/Bun/Deno — the node bundle runs on Bun and Deno unchanged too (Node-API is their shared addon ABI), so there is no separate deno/bun target. Override with --app or package.json#gjsify.app
--outfile, -o
path
from package.json
Output file
--outdir, -d
path
from package.json
Output directory (library mode)
--minify
bool
false
Minify the output
--globals
string
"auto"
Globals mode (see below)
--shebang
bool
false
Prepend a target-appropriate shebang to the outfile and chmod it 0o755: #!/usr/bin/env -S gjs -m for --app gjs, #!/usr/bin/env node for --app node. Applies to GJS and Node app builds with a single --outfile.
--watch, -w
bool
false
Watch source files and rebuild on change. Logs each rebuild with duration; SIGINT cleanly stops the watcher. Rejected with --library. Requires the npm rolldown engine — run under Node.
--verbose
bool
false
Show detected globals and build details
All build options
Option
Values
Default
Description
--format
iife | esm | cjs
auto
Override output format
--library
bool
false
Build as a reusable library
--reflection, -r
bool
false
Enable TypeScript runtime types via Deepkit
--console-shim
bool
true
Inject the GJS console shim. Disable with --no-console-shim
--exclude
glob[]
[]
Glob patterns to exclude from entry points and aliases
--log-level
silent | error | warning | info | debug | verbose
warning
Bundler log level
--external
name[]
[]
Module specifiers that should NOT be bundled — they remain as runtime imports. Repeatable; comma-separated values are split. Merged with the platform’s built-in externals.
--define
KEY=VALUE[]
[]
Bundler define pass-through. VALUE is a JS expression — string literals must be JSON-quoted (--define VERSION='"1.2.3"'). Repeatable. Merged with built-in defines like global: 'globalThis'.
--alias
FROM=TO[]
[]
Layered on top of the built-in alias map. Useful for stubbing heavy deps (--alias typedoc=@gjsify/empty). Repeatable.
For --app gjs, the target is firefox140 (SpiderMonkey 140) and gi://*, cairo, system and gettext are externalised. For --app node, the target is node24.
Native N-API addons under --app gjs. A --app gjs build transparently routes a compiled .node addon through @gjsify/napi’s loadAddon — intercepting the addon’s own bindings / node-gyp-build helper (or a direct .node import) and locating the binary with node-gyp-build’s probe order. So import Database from 'better-sqlite3' works after gjsify install @gjsify/napi, with no config. It is inert when no native addon is in the graph, and never active for --app node/browser/nativescript.
Bundling third-party CLIs that read their own package.json at load time
Tools like typedoc and prettier read their own package.json via Path.join(fileURLToPath(import.meta.url), '../../../package.json') during top-level evaluation. When bundled, import.meta.url resolves to the bundle file, not the original main, so the lookup escapes the package and crashes. Combine --external (so Node’s runtime resolver finds the package in node_modules) with --define for any build-time version constants the bundled code expects:
--external is the right answer on Node. On GJS, gjsify run does not yet have a node_modules-style runtime resolver, so externalised packages fail with ImportError: Module not found. Bundling them is the only option on GJS today; the alternative is upstream switching to a --define-injected version constant.
bundler is a RolldownOptions subset. The orchestrator applies platform-specific defaults on top.
define placement:define belongs under bundler.transform.define, not at the top level of bundler. If you write bundler.define (a common mistake when flat-renaming the old esbuild key), GJSify auto-maps it to bundler.transform.define and emits a build-time warning. To suppress the warning, move it:
JSON
// ❌ silently wrong without the alias — now warned + auto-fixed
Map of file extension (with leading .) to a loader kind. Rolldown does not classify unknown extensions natively; without a loader it tries to parse them as JS and fails.
JSON
{
"gjsify": {
"loaders": {
".glsl": "text",
".ui": "text",
".asm": "text",
".png": "dataurl"
}
}
}
Kind
Output
Use case
'text'
export default "<file contents>"
GLSL shaders, GtkBuilder .ui XML, assembly source, etc.
'dataurl'
export default "data:<mime>;base64,<b64>"
Images for Excalibur ImageSource, any API that accepts a data: URL string
The default --globals auto detects which globals your code needs from the bundled output. No configuration needed for most projects.
Mode
Usage
Description
auto
--globals auto (default)
Automatic detection from bundled output
auto,<extras>
--globals auto,dom
Auto + explicit extras for hard-to-detect cases
explicit list
--globals fetch,Buffer
Fully explicit, no auto detection
none
--globals none
Disable globals injection
Groups: node (Buffer, process, URL, …), web (fetch, streams, crypto, …), dom (document, Image, navigator, …).
How auto detection works
The CLI runs an iterative multi-pass build:
Pass 1 bundles your code in memory (no globals injected). acorn parses the output and finds free identifiers (Buffer, fetch) plus host-object member expressions (globalThis.Buffer, self.Buffer).
Pass N injects the discovered globals and rebuilds. Newly injected modules can reference more globals — the loop repeats until the set stabilises (typically 2–3 iterations, capped at 5).
The final build uses the converged set.
Because detection runs on the tree-shaken bundle, isomorphic guards (typeof document !== 'undefined') never produce false positives.
Use --verbose to see what auto mode detected:
Terminal
gjsifybuildsrc/index.ts-odist/index.js--verbose
# [gjsify] --globals auto: converged after 2 iteration(s), 11 global(s):
The analyser cannot follow value-flow indirection — e.g. Excalibur stores globalThis in a variable and calls methods through it, hiding the access. Keep auto on and add extras:
Extras are seeded into pass 1 so any code reachable only through them is also visible.
Known identifiers
Any identifier below can appear in --globals (or be detected automatically). Granular subpaths mean selecting Buffer does NOT also pull in process or URL.
These are not part of the coarse dom group — injecting them requires the
corresponding package (and, for the iframe, WebKitGTK) to be installed, so
auto-detection only pulls them in when the identifier actually appears in the
bundled output.
Identifier(s)
Register subpath
ImageData, Path2D
@gjsify/canvas2d/register
HTMLIFrameElement
@gjsify/iframe/register
WebGLRenderingContext, WebGL2RenderingContext
@gjsify/webgl/register
Unknown identifiers are silently ignored. If a ReferenceError: X is not defined still happens, add X as an extra: --globals auto,X.
Install npm dependencies for a project (or globally with -g). Drop-in for npm install / yarn install. Uses a native install backend (@gjsify/semver + @gjsify/npm-registry + @gjsify/tar) — no Node or npm CLI at runtime.
Terminal
gjsifyinstall# full project install
gjsifyinstall--immutable# CI mode — install strictly from gjsify-lock.json
gjsifyinstalllodash# add lodash, save to dependencies
gjsifyinstall-Dvitest# save to devDependencies
gjsifyinstall-g@gjsify/cli# global install under ~/.local/share/gjsify/global/
Option
Default
Description
[packages..]
—
Optional package specs. Omit for full project install.
-g, --global
false
Install into ~/.local/share/gjsify/global/ and symlink bins into ~/.local/bin/.
-D, --save-dev
false
Save to devDependencies.
--save-peer
false
Save to peerDependencies.
-O, --save-optional
false
Save to optionalDependencies.
--immutable
false
Refuse to update gjsify-lock.json; fail if it’s missing or stale. Equivalent to yarn --immutable / npm ci --frozen-lockfile.
--verbose
false
Per-package install log.
Resolver mirrors npm v3+ semantics: each (requester → dep → range) edge is checked against the ancestor node_modules chain; compatible placements are reused, conflicting ones are nested. Supports npm-style overrides and yarn-style resolutions in package.json. Lockfile schema is v2 (path-keyed packages map).
Run the GJS bundle of an npm-published package without persisting it in your project. Pattern follows npx / yarn dlx / pnpm dlx, but dlx here is strictly a GJS-bundle runner: it always invokes gjs -m <bundle> after resolving the package’s GJS entry. Packages without a GJS entry fail loudly.
Terminal
gjsifydlx@gjsify/example-dom-canvas2d-fireworks
gjsifydlx@scope/pkg@1.2.3# version-pinned
gjsifydlx@scope/pkgmy-bin----optvalue# pick a bin from gjsify.bin, forward args
gjsifydlx./local/path# local dir (no install, no cache)
Option
Description
<spec>
Package spec (name, name@version, @scope/name@spec, or local path).
[binOrArg]
Bin name when gjsify.bin has multiple entries; otherwise treated as the first arg forwarded to the bundle.
[extraArgs..]
Extra args forwarded to gjs -m <bundle>.
--cache-max-age <minutes>
Cache TTL. Defaults to 7 days. Use 0 to bypass cache.
--registry <url>
npm registry override (writes a temp .npmrc in the cache dir).
--verbose
Pass --loglevel verbose to npm.
Cache layout: $XDG_CACHE_HOME/gjsify/dlx/<sha256>/. Cache-key is sha-256 over the sorted package specs and registries. Atomic symlink swap on first install means parallel dlx invocations of the same spec are safe.
Build + run the package’s src/test.mts aggregator on GJS and Node and aggregate the results. Replaces the per-package build:test:{gjs,node} + test:{gjs,node} + test script boilerplate.
Terminal
# Default: build + run on both runtimes, aggregate exit codes
npx@gjsify/clitest
# Only one runtime
npx@gjsify/clitest--runtimegjs
npx@gjsify/clitest--runtimenode
# Skip building (assume bundles already exist)
npx@gjsify/clitest--no-build
# Force rebuild even if bundles look fresh
npx@gjsify/clitest--rebuild
Option
Default
Description
--runtime <gjs|node|all>
all
Which runtime(s) to build + run.
--entry <path>
gjsify.test.entry or src/test.mts
Test entry.
--outdir <path>
gjsify.test.outdir or dist/
Where to write test.{gjs,node}.mjs.
--rebuild
false
Always rebuild, even when outputs look fresh (mtime-based check).
--build
true
Build before running. --no-build skips when bundles already exist.
Run a built GJS bundle. Automatically sets GI_TYPELIB_PATH plus the host’s library-search variable (LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS, PATH on Windows) for native prebuilds.
Terminal
npx@gjsify/clirundist/index.js
Argument
Description
<target>
A GJS bundle file to run, or a script name from the current package.json
[args..]
Extra arguments passed through to the script / gjs
-w, --workspace <name>
Run <target> as a script in the named workspace (by package name or path), like npm run <script> -w <name>
--runtime <gjs|node|bun|deno>
Runtime to launch a bundle file on. Forces file mode. Defaults to the host runtime (gjs on gjs, else node/bun/deno) — see below.
If <target> resolves to a file on disk (or has a path-like prefix), it is launched via gjs. Otherwise it is looked up in the current package.json’s scripts (yarn-run-style).
Terminal
gjsifyrunstart# run the `start` script from ./package.json
gjsifyrunbuild-wcli# run the `build` script in the `cli` workspace
--runtime — run a bundle on gjs / node / bun / deno
--runtime selects which runtime launches a built bundle file, generalizing the
gjsify storybook --runtime mechanism. Its default follows the host runtime the CLI
itself is running on — gjs when invoked via the GJS bundle, node/bun/deno when
invoked via npx/bunx/deno run — except a --app gjs bundle (gi:// imports, a gjs
shebang) always runs on gjs regardless of host, since it has no node-gi shim. gjs runs
a --app gjs bundle via gjs -m; node/bun/deno run a --app node bundle — the
same bundle for all three, since Node-API is their common ABI (bun/deno reuse the node
build). Native typelib env is wired exactly as for gjs, and @gjsify/node-gi is required
only when the bundle actually uses gi://. Pass --runtime explicitly to override the
host default.
gjsifyrun--runtimebundist/app.node.mjs# bun — same node bundle
gjsifyrun--runtimedenodist/app.node.mjs# deno run -A --node-modules-dir=manual
Passing --runtime forces file mode (it beats the script-name lookup). If the current
package.json declares gjsify.example.runtimes, the
requested runtime is validated against it — a runtime the example doesn’t support fails with
a clear, actionable message instead of a bundle crash.
Running without gjsify run
gjsify run is a convenience wrapper. Without native prebuilds, you can run gjs directly:
With --cached (or GJSIFY_BUILD_CACHE=1; explicit --no-cached wins), gjsify foreach <script> and gjsify workspace <name> <script> skip workspaces whose build inputs are unchanged and restore the previously stored outputs instead of re-running the script.
Terminal
gjsifyforeachbuild-tp--cached# rebuild only what changed
GJSIFY_BUILD_CACHE=1gjsifyrunbuild# opt in a whole root script chain
Key — sha256 over the script name + forwarded args, a toolchain salt (the workspace-resolved @gjsify/cli, @gjsify/tsc, rolldown and typescript versions), the package’s own input hash (src/**, package.json, root tsconfig*.json — real file contents, sorted) and the input hashes of its full transitive workspace-dependency closure. Editing a dependency re-runs every dependent.
Storage — node_modules/.cache/gjsify/build/<pkg>/<key>/, at most 2 keys per package (oldest evicted).
Outputs — only the conventional output dirs (lib/, dist/, dist-templates/) that the script actually modified are stored; a cache hit replaces exactly those dirs and nothing else. A package that does not define the script — or a committed, no-build lib/ the script never touches — is never written to.
Script mode only (--exec is rejected); any cache error falls back to running the script uncached.
npx@gjsify/clicheck--include'@gjsify/process'# filter to one package
npx@gjsify/clicheck--no-parallel--verbose# sequential with per-workspace output
Behaviour:
In a workspace root: walks every package whose package.json#scripts.check is defined (@girs/* always excluded), runs npm run check in each, aggregates exit codes. Parallel by default with os.cpus().length workers; --jobs N caps it, --no-parallel switches to sequential.
In a single package (cwd inside a workspace package with a check script): runs that package’s check script directly. Useful for npm run check ergonomics from gjsify.
Exits with code 1 if any workspace’s check fails; 0 if all pass. With --no-parallel, exits with the first non-zero code; with --parallel (default), prints a summary of failed workspaces.
Option
Default
Description
--include <glob>
(all)
Repeatable. Only run in workspaces matching these glob patterns.
--exclude <glob>
@girs/*
Repeatable. Skip workspaces matching these glob patterns.
--parallel, -p
true
Run workspace checks in parallel. --no-parallel for sequential.
List native GJSify packages and show the environment gjs needs to load their prebuilds:
GI_TYPELIB_PATH plus the library-search variable this host’s loader reads —
LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS (dyld ignores the Linux one),
PATH on Windows. Only the variables that apply to the running host are emitted.
Terminal
npx@gjsify/cliinfodist/index.js
eval $(npx@gjsify/cliinfo--export)
Argument / Option
Description
[file]
Bundle path used in the generated example command
--export
Emit only shell export statements, suitable for eval
The default follows the host runtime the CLI itself is running on — gjs when invoked
via the GJS bundle, node/bun/deno when invoked via npx/bunx/deno run. gjs runs
the showcase’s GJS bundle via gjsify dlx (unchanged). node/bun/deno resolve the
showcase’s --app node bundle and run it on that runtime — the Node-API showcases
(showcases/node/*). The runtime is validated against the showcase’s
gjsify.example.runtimes declaration: a GTK/Adw showcase
requested under node fails with a clear message rather than crashing. Pass --runtime
explicitly to override the host default.
Terminal
gjsifyshowcaseexpress-webserver# GJS (default)
gjsifyshowcaseexpress-webserver--runtimenode# the --app node bundle, on Node.js
gjsifyshowcaseexpress-webserver--runtimebun# same node bundle, on Bun
gjsifyshowcaseexpress-webserver--runtimedeno# same node bundle, on Deno
runtimes is optional and back-compat — a package without it runs on any requested runtime. A
GTK/Adw showcase declares ["gjs"] so --runtime node errors cleanly (node-gi has no GTK layer
yet). When node is omitted, the node bundle is derived from the GJS entry by convention
(dist/<name>.gjs.js → dist/<name>.node.mjs).
Discover every *.story.ts in the project and launch the GTK/Adwaita component browser from @gjsify/storybook — a sidebar grouped by category, a live preview pane, and an auto-generated controls panel. No per-project storybook application to maintain.
Terminal
gjsifystorybook# discover src/**/*.story.ts and launch
gjsifystorybook--storiespackages# scan a different directory
gjsifystorybook--watch# rebuild + relaunch on story change
Option
Default
Description
--stories <dir>
src or gjsify.storybook.stories
Directory scanned recursively for *.story.ts.
--app-id <id>
gjsify.storybook.applicationId or derived from the package name
GApplication id.
--title <text>
—
Window title.
--globals <value>
auto
Value for gjsify build --globals (use auto,dom for canvas/DOM stories).
--runtime <gjs|node|bun|deno>
host runtime (gjs on gjs, else node/bun/deno)
Runtime to build for and launch on. node/bun/deno build the SAME --app node bundle (gi:// → @gjsify/node-gi) and require @gjsify/node-gi installed in the project.
--out <path>
node_modules/.cache/gjsify-storybook
Output bundle path.
--watch
false
Rebuild and relaunch when a story file changes.
--build-only
false
Build the bundle but do not launch it.
Configure defaults in package.json#gjsify.storybook (applicationId, title, stories, globals, runtime). Precedence for the runtime: --runtime flag > gjsify.storybook.runtime config > host default. --runtime mirrors gjsify run --runtime / gjsify showcase --runtime — pass it to build + launch the SAME storybook on a specific runtime, e.g. gjsify storybook --runtime bun. With GJSIFY_DEVTOOLS=1, the storybook host also exposes the devtools control plane on any of the four runtimes, so an agent can drive it via gjsify debug --profile storybook. See the Debugging & remote control guide.
Launch an MCP↔devtools bridge for a running, devtools-enabled GJSify app (its org.gjsify.Devtools DBus control plane). An MCP client (e.g. Claude Code) uses this as its server command; the bridge speaks JSON-RPC on stdio and translates each tool call to the app’s DBus interface. Provided by @gjsify/devtools-mcp.
gjsifydebug--build-only--outdist/bridge.gjs.mjs# build once; point .mcp.json at the bundle
Option
Default
Description
--bus-name <name>
gjsify.devtools.busNameBase, or the storybook/browser app-id
App DBus base name.
--profile <kind>
auto
generic | storybook | browser | cdp. Auto: storybook if @gjsify/storybook is a dep, browser if @gjsify/devtools-browser is, cdp if @gjsify/devtools-cdp is, else generic.
--globals <value>
auto
Value for gjsify build --globals.
--out <path>
node_modules/.cache/gjsify-debug
Output bundle path.
--build-only
false
Build the bridge bundle but do not launch it (for a .mcp.json command pointing at the bundle).
MCP cleanliness:gjsify debug logs only to stderr — stdout is the JSON-RPC channel. The bridge resolves @gjsify/devtools-mcp from the consumer’snode_modules. Full workflow: Debugging & remote control guide.
Launch the minimalist Adwaita web browser from @gjsify/devtools-browser, optionally pointed at a URL. With --devtools it exposes the same org.gjsify.Devtools control plane (browser profile), so an agent can navigate, screenshot the rendered page, eval JS, inspect elements, and read the DOM/network/accessibility trees over MCP — purpose-built for debugging gjsify-built web apps.
Terminal
gjsifybrowse# open page:welcome
gjsifybrowsehttps://gnome.org# open a URL
gjsifybrowsehttps://localhost:8080--devtools# + MCP control plane (drive via `gjsify debug --profile browser`)
Option
Default
Description
[url]
page:welcome
Initial URL (a page:* built-in page or https://…).
--app-id <id>
gjsify.browse.applicationId or derived from the package name
GApplication id.
--title <text>
—
Window title.
--globals <value>
auto,dom
Value for gjsify build --globals (WebKit/iframe need DOM globals).
--out <path>
node_modules/.cache/gjsify-browse
Output bundle path.
--devtools
false
Enable the MCP devtools control plane (sets GJSIFY_DEVTOOLS=1).
--inspector-port <n>
—
Enable WebKit’s remote inspector protocol on this port + the Cdp* deep-protocol methods (implies --devtools).
--build-only
false
Build the bundle but do not launch it.
The browser is built on @gjsify/iframe (a WebKit.WebView postMessage bridge). With --inspector-port, it also sets WEBKIT_INSPECTOR_HTTP_SERVER and exposes the @gjsify/devtools-cdpCdp* methods (CdpDiscoverTargets / CdpConnect / CdpSend / CdpDrainEvents) over the control plane — the deep Runtime/DOM/CSS/Network/Console/Debugger protocol. See the Debugging & remote control guide for the agent workflow.
Compile a GResource XML descriptor into a binary .gresource bundle. Thin wrapper around glib-compile-resources — useful when you want to package UI templates and assets into the app bundle without pulling in meson/autotools.
Compile gettext .po files for GNOME apps. Wraps msgfmt with the common output shapes — per-language locale tree (.mo), metainfo template substitution (.xml), and two less-common formats (.desktop, .json).
Non-destructive contract: each output (manifest / metainfo / desktop / flathub.json) is independently checked for existence; existing files are skipped with a log line. Re-run with --force to overwrite. User edits to one file (e.g. a hand-tuned .desktop) don’t block re-running init to refresh the others.
Config gaps degrade gracefully: missing MetaInfo fields are reported with per-field hints pointing at the exact gjsify.flatpak.<key> to set. The manifest still writes; MetaInfo + .desktop are skipped until you fill the gaps and re-run. Full field list:
gjsify.flatpak.<key>
Required for
Notes
appId
both
Reverse-DNS.
kind
both
"app" (default) or "cli".
name
optional
Human-readable display name for <name> + .desktopName=. Defaults to a friendly derivation of package.json#name — set explicitly when the npm name doesn’t match the display name (e.g. npm name learn6502 vs display name "Learn 6502 Assembly").
developer.id / developer.name
metainfo
AppStream OARS 1.1+ requires <developer id="…">.
developer.email
optional
Emits <email> inside <developer>.
developer.nameTranslatable
optional
Default false → emits translate="no". Set true for descriptive names.
summary
metainfo
≤80 chars, no trailing period.
summaryTranslatorHint
optional
Emits <!-- TRANSLATORS: ... --> before <summary>.
description
metainfo
String form (blank-line-split into <p>) or DescriptionBlock[] ({p, translatorHint?} paragraphs and {ul:[...], translatorHint?} bullet lists).
Minimum display length in pixels. Phone-portrait min ≈ 360, tablet recommendation ≈ 480.
requires.controls / recommends.controls
optional
Hard / soft control requirements.
For multilingual apps, every translatable string (summary, description paragraphs + list items, screenshot captions, release notes) supports a parallel translatorHint field that emits a <!-- TRANSLATORS: ... --> comment in the generated .metainfo.xml.in — xgettext / msgfmt --xml --template picks these up and forwards them to the .po files so contributors on Weblate / Crowdin see the context. See the flatpak-app guide → Rich AppStream features for a worked example.
npx@gjsify/cliflatpakcheck--reporepo# also lint a built repo
Option
Default
Description
[manifest]
auto
Manifest path; defaults to <app-id>.json or the single .json matching a manifest shape.
--metainfo <path>
data/<app-id>.metainfo.xml.in
MetaInfo to validate; skipped if missing.
--repo <path>
—
If set, also runs flatpak-builder-lint repo <path> (post-build).
--appstream
true
Toggle appstreamcli validate --strict. --no-appstream to skip.
--builder-lint
true
Toggle flatpak-builder-lint. --no-builder-lint to skip.
--verbose
false
Stream linter stdout/stderr through.
Requires appstreamcli and flatpak-builder-lint on PATH. Both ship inside the org.flatpak.Builder Flatpak — install via flatpak install -y flathub org.flatpak.Builder; check prints this hint on ENOENT. Exit code aggregates: non-zero if any linter fails or any required binary is missing.
Sync the per-app flathub/<app-id> tracking-repo manifest to a new git tag + commit. Flathub publishes each app from a separate repo whose manifest pins the upstream tag + commit; after cutting a release in your source repo, this command updates that pin and (optionally) opens the PR.
Terminal
# Use the latest local git tag + its commit; auto-resolves flathub-repo from appId
Report resolution + intended branch + commit, touch no files.
--verbose
false
Echo every git / gh invocation.
Workflow:
Clones (or updates) flathub/<app-id> into $XDG_CACHE_HOME/gjsify/flathub-sync/.
Surgically edits modules[0].sources[<i>] — sets tag + commit, injects an x-checker-data block if missing (so Flathub’s auto-update bot can pick up future releases).
Preserves the manifest’s original 2-space indent + key ordering.
Creates branch update-to-<version>, commits, pushes, opens a PR via gh pr create.
Requires git (always) and gh (unless --no-pr). Both surface install hints on ENOENT. Idempotent: re-running with the same --version is a no-op when the manifest is already pinned.
gjsify.flatpak.flathubRepo in package.json#gjsify.flatpak overrides the default flathub/<app-id> derivation for repos that don’t follow the convention.
Also print the full flathub manifest source entry.
--source-index <n>
first type: git source
Which modules[0].sources[] entry to inspect.
--verbose
false
Echo fetch URL + resolved values.
Exit 0 when the local tag matches the flathub-pinned tag; exit 1 on drift with a hint at the exact gjsify flatpak sync-flathub command that would fix it.
Cut a release end-to-end: chain flatpak init (regenerate assets) → flatpak check (lint locally) → git tag (+ push) → flatpak sync-flathub (open the Flathub PR). One command for the maintainer flow that otherwise spans four manual invocations.
Terminal
# Full chain
npx@gjsify/cliflatpakreleasev0.6.6
# Show the plan without running anything
npx@gjsify/cliflatpakreleasev0.6.6--dry-run
# Tag was already created
npx@gjsify/cliflatpakreleasev0.6.6--skip-tag
Option
Default
Description
<version>
—
Required. Release tag, e.g. v0.6.6.
--skip-init
false
Skip the flatpak init --force regen step.
--skip-check
false
Skip the flatpak check linter step.
--skip-tag
false
Skip git tag + git push origin <version>.
--push-tag
true
Whether to push the created tag (only relevant without --skip-tag).
--flathub-repo <owner/name>
—
Override forwarded to sync-flathub.
--dry-run
false
Print the plan, take no action.
--verbose
false
Echo every sub-command invocation.
The orchestrator runs init + checkbefore creating the tag — if either fails, no tag is created (the release is aborted, not half-released).
Refresh the installed @gjsify/cli to the latest release (or a pinned dist-tag).
Terminal
gjsifyself-update# install latest
gjsifyself-update--check# diff only, exit 1 if outdated
gjsifyself-update--force# reinstall identical version
gjsifyself-update--tagnext# install a specific dist-tag (or version)
Walks import.meta.url to find the CLI’s own package.json, fetches the matching dist-tag via @gjsify/npm-registry, then re-uses the install backend that powers gjsify install -g (handles transitive native prebuilds, lockfile, bin shims).
Only works for CLIs installed under ~/.local/share/gjsify/global/ (the install.mjs bootstrap or gjsify install -g). Installs from npm install -g land outside that prefix; self-update surfaces a warning pointing at the new bootstrap.
Option
Default
Description
--check
false
Compare current vs target without installing. Exit 0 if up-to-date, 1 if outdated.
Scaffold an install.mjs for your own GJS-runnable npm package — your users get the same curl ... | gjs -m - install story as GJSify itself.
Terminal
cdmy-gjs-app
gjsifygenerate-installer
# → install.mjs written. Commit it.
# Optional flags:
gjsifygenerate-installer\
--target@my-org/my-app\
--bin-namemy-app\
--bootstrap-urlhttps://example.com/cli.gjs.mjs\
--outputbin/install.mjs--force
The generated install.mjs is a verbatim copy of GJSify’s own root install.mjs with three constants substituted (DEFAULT_TARGET, DEFAULT_BIN_NAME, DEFAULT_BOOTSTRAP_URL). See the Distributing GJS apps guide for the full publication workflow.
Symmetric inverse of gjsify install -g. Removes a previously installed package tree from ~/.local/share/gjsify/global/node_modules/<pkg>/ plus any bin shims under ~/.local/bin/ that point into it.
Terminal
gjsifyuninstall-g<pkg># remove pkg and its bin shim(s)
gjsifyuninstall-g<pkg>--dry-run# show what would be removed
Scoped to --global only. Project-local removal (mirror of npm uninstall <pkg> without -g) requires rewriting package.json + refreshing the lockfile, which is a separate workstream.
Option
Default
Description
<packages..> (positional)
—
One or more package names.
--global / -g
false
Required; removes from defaultGlobalLayout() paths.
--dry-run
false
Print “would remove” lines, touch no files.
--verbose
false
Surface inspection failures (rare).
Exits non-zero when nothing was removed (no matching install found).
Produce an npm-compatible .tgz tarball for a workspace. Drop-in for npm pack. Always rewrites workspace:^/~/* deps to resolved version ranges so the published tarball is portable. Honors the files allowlist + .npmignore/.gitignore with the same precedence as npm.
Terminal
gjsifypack# pack the current workspace
gjsifypackpackages/infra/cli# pack a specific workspace
gjsifypack--pack-destinationdist# write the .tgz somewhere else
Pack + upload a workspace to its npm registry. Drop-in for npm publish. Uses gjsify pack under the hood (so the workspace:^ rewrite happens automatically). Authenticates by reading process.env.NPM_CONFIG_USERCONFIG first (where actions/setup-node@v6 writes the auth-token npmrc) with ~/.npmrc as fallback.
Terminal
gjsifypublish# publish current workspace
gjsifypublishpackages/infra/cli--taglatest
gjsifypublish--accesspublic# required for first publish of scoped pkg
gjsifypublish--accesspublic--otp123456# first publish with 2FA OTP (Node-free bootstrap)
gjsifypublish--tolerate-republish# treat 409 conflict as success
gjsifypublish--dry-run# pack only, don't PUT
Option
Default
Description
[path]
cwd
Workspace path to publish.
--tag <tag>
latest
npm dist-tag.
--access <kind>
—
public or restricted (required on first publish of scoped packages).
--otp <code>
—
npm 2FA one-time code; forwarded as the npm-otp HTTP header on the PUT request. Required for manual publishes from a 2FA-enabled npm account. If the registry responds with 401 OTP-required and --otp was not supplied: on an interactive TTY the CLI prompts once and retries; on a non-TTY it exits non-zero with an actionable message.
--tolerate-republish
false
Treat a 409 conflict (version already published) as success. Matches yarn --tolerate-republish.
--provenance
false
Recorded in payload but no signing happens (no sigstore signer yet).
--dry-run
false
Pack only, do not PUT.
--json
false
Emit publish metadata as JSON.
Combine with gjsify foreach to publish every workspace in one go:
Ensure every publishable @gjsify/* workspace is both published on npm and has a Trusted Publisher configured for this repo’s release.yml — doing the minimum work. It folds the whole manual first-publish + Trusted-Publisher bootstrap into a single idempotent sweep:
Auth gate — runs the whoami liveness check first. If the token is live, it proceeds without asking for credentials; only when the token is dead/missing does it run the gjsify login flow (or fail clearly under --yes on a non-TTY).
Enumerate the publishable workspaces (non-private, excluding @girs/*).
Determine each package’s state concurrently (bounded) by reading npm’s Trusted-Publisher config: unpublished / published-but-untrusted / published-and-trusted. Reads never prompt for 2FA.
Act on the gaps, minimally — build + publish --access public each unpublished package, then configure its Trusted Publisher; configure a published-but-untrusted one; skip an already-done one.
One shared OTP — a single 2FA code is reused across every publish + trust operation (cache-first: the cached code is tried before prompting). A whole sweep of N packages typically needs you to type an OTP once, never once-per-package.
Re-running when everything is already published + trusted does nothing and exits 0.
Terminal
gjsifyonboard# publish + trust every missing package (prompts for OTP once)
gjsifyonboard--dry-run# report the plan; change nothing
gjsifyonboard--otp123456# seed the shared 2FA code (no prompt if it stays valid)
gjsifyonboard--json# machine-readable summary object as the final stdout line
gjsifyonboard--yes# non-interactive: fail clearly if a login/OTP is needed
Option
Default
Description
--repository <owner/repo>
inferred from origin
GitHub repo the Trusted Publisher is scoped to.
--workflow <file>
release.yml
Workflow filename allowed to publish via OIDC (basename only).
--environment <env>
—
Optional GitHub Actions environment the workflow must run in.
--registry <url>
scope-aware .npmrc
Registry override.
--otp <code>
—
Seeds the shared 2FA code; prompted once on demand if omitted and a challenge occurs.
--concurrency <n>
8
How many packages to probe (read state) in parallel.
--dry-run
false
Report the plan without publishing or configuring anything.
--json
false
Emit a machine-readable summary object as the final stdout line.
--yes
false
Non-interactive: never prompt. Fail clearly if a login or an OTP is required and not supplied via flags.
Format JS/TS source files via oxfmt (oxc’s formatter). Dual-engine:
Under GJS (the gjs -m cli.gjs.mjs bundle), GJSify prefers the @gjsify/oxfmt-native GI bridge — the full oxfmt CLI runs in-process, Node-free (config resolution, ignore handling, file walking, --write/--check/--list-different all included). Override with GJSIFY_OXFMT=npm (force the Node launcher) or GJSIFY_OXFMT=native (error instead of falling back when the prebuild is unavailable).
On Node (or when the native prebuild is missing), GJSify resolves oxfmt’s npm package Node launcher from node_modules/oxfmt/bin/oxfmt and spawns it with the current Node executable. The per-platform native code ships as the @oxfmt/binding-<target> napi optionalDependency.
CSS/JSON formatting is not supported. oxfmt formats JS/TS (+TOML) only. The previous Biome toolchain formatted CSS and JSON too; that is intentionally dropped in the oxc migration and not replaced by another formatter.
Used with --init to overwrite existing config files.
--verbose
false
Echo the resolved oxfmt launcher + args before spawning.
A bare gjsify format writes. There is no flagless report mode: --check is the read-only CI mode (exit non-zero on drift) and --no-write the read-only local one (oxfmt’s --list-different). This matches oxfmt’s own bare default, cargo fmt, and the sibling gjsify fix.
Workspace-aware resolution — when run from inside a sub-workspace, GJSify walks up to the workspace root to find node_modules/oxfmt/bin/oxfmt. A single .oxfmtrc.json at the workspace root applies everywhere oxfmt discovers it.
Standalone projects — same shape, single .oxfmtrc.json at the project root.
Run oxlint diagnostics. Default reports findings (exit non-zero); --fix applies safe fixes in place. oxlint is spawned via its Node launcher (node_modules/oxlint/bin/oxlint) so its JS-plugin host — including GJSify’s internal gjsify/register-class-order rule — is available.
Terminal
gjsifylint# lint all
gjsifylintsrc/# lint specific paths
gjsifylint--fix# apply safe fixes (skips unsafe)
Option
Default
Description
[paths..]
.
Files/directories to lint.
--fix
false
Apply safe lint fixes in place.
--config-path <path>
walks up
.oxlintrc.json path override.
--verbose
false
Echo resolved oxlint launcher + args.
Use gjsify fix for the combined format + safe-lint-fix pass.
GJSify ships an internal oxlint JS plugin (@gjsify/oxlint-plugin-gjsify, not published to npm) with one rule, wired into the workspace .oxlintrc.json via jsPlugins. It flags static GObject metadata fields (GTypeName, Properties, InternalChildren, Signals, Template, CssName, …) declared after a static { GObject.registerClass(…) } block — where registerClass runs before the field is assigned, so the metadata is silently ignored — and autofixes by hoisting those fields above the static block. (GNOME/gjs#704, gjsify/ts-for-gir#410.)
Combined fix pass: oxfmt --write (format JS/TS) followed by oxlint --fix (safe lint fixes). Different from gjsify system-check (which verifies system dependencies) and gjsify check (the tsc orchestrator).
Terminal
gjsifyfix# default: format + apply all safe lint fixes
gjsifyupgrade--filter'@gjsify,vite'# narrow scope by substring
gjsifyupgrade-y# interactive-all (no prompt, select everything)
Workspace-aware: workspace:, file:, link:, git:, git+, http(s):, npm:, *, latest ranges are skipped — those are not external npm dependencies. Range prefix is preserved (^1.2.3 → ^2.0.0, ~0.4.0 → ~0.5.0).
The registry URL is resolved from ~/.npmrc → <cwd>/.npmrc, with npm_config_registry env var overriding both. Scope-specific registries + auth tokens from .npmrc are honored.
Option
Default
Description
--latest
false
Non-interactive bulk-update — allows major bumps.
--minor
false
Non-interactive — semver-minor + patch only.
--patch
false
Non-interactive — semver-patch only.
--filter <substring>
—
Comma-separated list of substrings (case-insensitive) to match against package names.
--dry-run
false
Print upgrade plan, don’t write package.json.
--yes / -y
false
In interactive mode, select all candidates without prompting.
--cwd <path>
process.cwd()
Project directory.
--verbose
false
Print resolution details.
Output is a color-coded table (red=major, yellow=minor, green=patch, cyan=prerelease) listing every dependency with a newer version available. After write-back, gjsify install must be run to actually fetch the new versions.