qomicex CLI reference
qomicex is the command-line tool for the Qomicex plugin ecosystem. It covers the whole plugin development lifecycle: create / dev / pack / verify / lint / bump / publish, plus login / logout (login session management), doctor (environment diagnostics), and debug (launcher CDP debugging).
- Zero runtime dependencies (Node ≥ 20, using only the built-in
node:fs/node:zlib/ WebCrypto) - Package:
@qomicex/cli(source inpackages/qomicex-cli/)
Installation
Global flags
Environment variables
Command overview
create
manifest.json / package.json / Cargo.toml / src/lib.rs. Four templates:
- Minimum permission set for the react template:
config:read/ui:toast/network:cors_proxy; the html template usesconfig:read/ui:toast; the lib template usespermissions:["config:write"]; the wasm template usespermissions:["wasm:execute"] - The lib template has no
contributes.menuItems(it creates no sidebar entry); see MarkdownLibrary - The wasm template follows WASM plugins (L3);
src/lib.rsimports theqomicexhost module and exportson_load/on_unload/get_manifest - After generating, it prompts you to run
pnpm install(for wasm,rustup target add wasm32-unknown-unknown)
pack/verify/lint/publishwork with all four templates:pnpm run buildruns when there is apackage.json,cargo buildruns when there is aCargo.toml, and purely static projects (neither file) skip the build and are packaged directly. Verify skips the JS permission scan for L3 wasm plugins (wasm:executeis consumed by the gateway).
dev
- Inside the repo (harness mode): when the plugin lives at
plugins-dev/{id}in the repo, it detects this and runsscripts/harness/run.mjs— Playwright starts Vite(:1420) + injects the Tauri mock + stubs a mock server(:5100) + hot reloads on source changes (full page reload after a rebuild). In this mode--porthas no effect (the harness uses fixed ports), and exiting withCtrl+Ccleans up the stub and Vite together. See Debugging and hot reload. - Outside the repo (bare Vite): when no harness is detected, it falls back to starting a Vite dev server and writing
.qomicex-dev.jsonat the project root (dev-source plugin configuration: a plugin id → localhost URL mapping) for manually registering a dev source.
pack
.qplugin is a zip with manifest.json at the root plus dist/** (or plugin.wasm). Build detection is automatic: if the project has a package.json with scripts.build, it runs pnpm run build (react/html/lib); if it has a Cargo.toml, it runs scripts/build.sh / build.ps1 (wasm); if it has neither, the build is skipped (purely static project). Without a dist/ it falls back to collecting the root files referenced by the manifest plus plugin.wasm. If entry.theme / contributes.overlay.file reference files under dist/ while the source sits at the root, they are copied in automatically.
Examples (packaging for day-to-day iteration):
verify
- Manifest validity: id (reverse domain / safe characters), name, version (semver), minLauncherVersion, layers, permissions, entry, contributes
- Least privilege: compares
manifest.permissionswith the bridge methods the source actually calls (theMETHOD_PERMISSIONStable). Both declared-but-unused and used-but-undeclared raise errors - Icon name validation:
fa-solid fa-*/fa-brands fa-*literals in the source and the manifesticonfield are checked against the launcher’s built-in icon table; a name outside it is an error (such icons render blank, and used to do so silently) and the closest built-in names are suggested. Keys declared by a plugin’s owncontributes.iconThemecount as valid - Long-loop warnings:
while(true),for(;;), and unboundedsetIntervalpolling (it suggests moving the work to a Worker / WASM / backend) — a heuristic text scan, so false positives and misses are possible - Signature check: in package mode it verifies with the bundled store root public key (ADR-050), unless
QOMICEX_DEV_ROOT_PUBLIC_KEYis set (local dev root, see the publishing docs); an unsigned package reports “unsigned” rather than being rejected; a failed verification (including a self-signed certificate produced bypack --keybeing rejected by the root key) exits non-zero
lint
verify, with these extra checks:
- Relative imports missing an extension:
import ... from './x'with no extension and not a directory barrel → error (a hard Vite rule) - Resource reference existence: files pointed at by
entry.frontend/theme/backendorcontributes.overlay.filedo not exist → error (adist/reference that has not been built raises a warning) - Icon name validation: same as
verify(manifesticonfields plusfa-*literals in source); names outside the built-in table → error
bump
version field of manifest.json directly without triggering a build. Use it to increment the version before publishing a new release, so the store does not reject a version older than the live one (409 version_exists). Versions support semver pre-release and build metadata (1.2.3-beta.1 / 1.2.3+build5).
Examples (standardized release flow):
login / logout
qomicex publish skips re-authorization: when the access token expires after 15 minutes, the refresh token rotates it automatically through POST /auth/refresh. Sessions are scoped per store API base (each store keeps its own login).
Examples (first login plus multiple-store isolation):
publish
- Authentication: skipped when
QOMICEX_API_KEYexists (CI) or a persisted session renews successfully; otherwise device-flow login (RFC 8628) throughPOST /api/v1/auth/device/code POST /api/v1/developer/keysuploads the Ed25519 public key → the store root key issues a developer certificate (keyId+signature.cert.json)- The package body is signed with the private key (canonical JSON payload → Ed25519 →
signature.json) and packed into the.qplugintogether with the certificate - It looks up or creates the plugin record (
/plugins/mine→POST /pluginsif missing), then uploads with a multipartPOST /plugins/:id/versionsafter confirmation - On success the signed package is saved as
release/<id>-<version>.signed.qpluginfor re-verification
Examples (publish plus version update):
doctor
@qomicex/plugin-ui, Vite, openssl + WebCrypto (Ed25519), the :5000 backend, plugin store reachability, and that the debugging harness can be located. It prints ✔/✗ with reasons and modifies no files.
Examples (troubleshooting: run it first when the plugin build or debugging environment misbehaves):
debug
--launcher > QOMICEX_LAUNCHER_PATH > the repo’s src-tauri/target/{release,debug} > the install path) and starts it with a --debug <port> argument.
The launcher itself supports Qomicex Launcher.exe --debug <port> — third-party developers need neither the CLI nor the source; passing the flag on the command line opens CDP and pushes live logs; release builds keep plain IPC by default and are unaffected (it is enabled only when the flag is passed explicitly).
- Windows (WebView2):
--debug <port>setsWEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=<port>to open CDP; it polls/json/list, prints the targets, and shows the DevTools frontend URL - Linux/macOS: sets
WEBKIT_INSPECTOR_SERVER/WEBKIT_INSPECTOR_HTTP_SERVERon a best-effort basis (the app must have the inspector enabled) - Two live-log channels: ① the launcher’s stderr streamed live (backend logs + launcher logs, visible directly via
stdio: inherit); ② tailing{BaseDir}/logs/qomicex-backend.log(theFileLogwriter flushes line by line to disk, including[plugin:...]/[frontend:...]trace lines) and printing them as[trace] .... Neither depends on backend TCP Ctrl+Cstops it and terminates the launcher child process (keep it alive with--no-kill)
Signing keys
Generate an Ed25519 key pair (PKCS#8 PEM or a raw 32-byte seed in base64):node scripts/plugin-keygen.mjs. The script has no generate subcommand; the available flags are --out <path>, --json, and --force, and it writes to plugin-key.json by default.
AI-assisted development
Qomicex’s plugin development AI Skill package is maintained separately in the qomicex-plugin-skills repository. AI agents (Claude / opencode / Cursor and so on) load it while writing plugins to get accurate, current manifest fields, the permission catalog, bridge API signatures, theme tokens, and the signing and debugging flows — so the AI does not invent fields. See AI-assisted development.Design notes
- Zero dependencies: zip reading and writing (
node:zlibplus a hand-written CRC32/directory structure), Ed25519 (WebCrypto), and argument parsing (hand-written) pull in no third-party packages - Signature canonicalization matches the store’s
src/lib/signature.tsand the launcher’splugin_signature.rsbyte for byte (canonicalJson with ordered keys and no whitespace,signedHash= SHA-256 of the payload) - The store upload endpoint is unchanged: the client is implemented against the read-only reference contract