Skip to main content

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 in packages/qomicex-cli/)

Installation

You can also publish it straight to npm:

Global flags

Environment variables

Command overview

create

Generates a valid plugin project from a built-in template, automatically substituting the id in 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 uses config:read / ui:toast; the lib template uses permissions:["config:write"]; the wasm template uses permissions:["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.rs imports the qomicex host module and exports on_load/on_unload/get_manifest
  • After generating, it prompts you to run pnpm install (for wasm, rustup target add wasm32-unknown-unknown)
Examples:
pack / verify / lint / publish work with all four templates: pnpm run build runs when there is a package.json, cargo build runs when there is a Cargo.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:execute is consumed by the gateway).

dev

Starts a local plugin debugging environment in one of two modes:
  • Inside the repo (harness mode): when the plugin lives at plugins-dev/{id} in the repo, it detects this and runs scripts/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 --port has no effect (the harness uses fixed ports), and exiting with Ctrl+C cleans 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.json at the project root (dev-source plugin configuration: a plugin id → localhost URL mapping) for manually registering a dev source.

pack

A .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.permissions with the bridge methods the source actually calls (the METHOD_PERMISSIONS table). 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 manifest icon field 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 own contributes.iconTheme count as valid
  • Long-loop warnings: while(true), for(;;), and unbounded setInterval polling (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_KEY is 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 by pack --key being rejected by the root key) exits non-zero
Examples (pre-publish quick check plus re-verifying a signed package):

lint

A stricter pre-publish static gate than 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/backend or contributes.overlay.file do not exist → error (a dist/ reference that has not been built raises a warning)
  • Icon name validation: same as verify (manifest icon fields plus fa-* literals in source); names outside the built-in table → error
Examples (CI gate):

bump

Rewrites the 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

For 30 days after a successful login, 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

The flow (matching the store contract; see Publishing guidelines and ADR-050):
  • Authentication: skipped when QOMICEX_API_KEY exists (CI) or a persisted session renews successfully; otherwise device-flow login (RFC 8628) through POST /api/v1/auth/device/code
  • POST /api/v1/developer/keys uploads 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 .qplugin together with the certificate
  • It looks up or creates the plugin record (/plugins/mine → POST /plugins if missing), then uploads with a multipart POST /plugins/:id/versions after confirmation
  • On success the signed package is saved as release/<id>-<version>.signed.qplugin for re-verification
Flags: Examples (publish plus version update):

doctor

Checks each item: Node ≥ 20, pnpm, the plugin project and manifest validity, @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

It locates the launcher (--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> sets WEBVIEW2_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_SERVER on 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 (the FileLog writer flushes line by line to disk, including [plugin:...] / [frontend:...] trace lines) and printing them as [trace] .... Neither depends on backend TCP
  • Ctrl+C stops it and terminates the launcher child process (keep it alive with --no-kill)
Examples (Playwright connectOverCDP automation):

Signing keys

Generate an Ed25519 key pair (PKCS#8 PEM or a raw 32-byte seed in base64):
You can also generate a key pair with the repository script 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:zlib plus 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.ts and the launcher’s plugin_signature.rs byte 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