CLI Reference
The amc-plugin CLI is a Commander.js-based tool for building, validating, packaging, and publishing Omniscio plugins.
Install
npm install -g @agent-mc/plugin-cliCommands
create
Scaffold a new plugin project with starter files, manifest, and optional git init.
Usage:
amc-plugin create <name> [options]Arguments:
| Name | Description |
|---|---|
name | Plugin name in kebab-case (e.g. my-plugin) |
Options:
| Flag | Description | Default |
|---|---|---|
-t, --template <template> | Template variant: basic, with-backend, full | basic |
--display-name <name> | Display name (skips interactive prompt) | — |
--description <desc> | Plugin description | — |
--author <author> | Plugin author | — |
--category <category> | Category: planning, development, testing, devops, productivity, other | other |
--tags <tags> | Comma-separated discoverability tags (≤10, ≤30 chars each). Blank falls back to the category | category |
--icon <icon> | Path to your logo inside the package, or a Lucide icon name for a generic glyph. A placeholder SVG is written at the default path for you to replace | assets/icon.svg |
--skip-install | Skip npm install after scaffolding | false |
--skip-git | Skip git init and initial commit | false |
Example:
# Interactive mode
amc-plugin create my-plugin
# Non-interactive (CI-friendly)
amc-plugin create my-plugin \
--template with-backend \
--display-name "My Plugin" \
--description "Does cool things" \
--author "Jane Doe" \
--tags "linter, security" \
--skip-gitExit codes: 0 success, 1 error (invalid name, template, or cancelled prompt).
build
Compile TypeScript, copy non-TS UI files to dist/, and scan for banned imports.
Usage:
amc-plugin buildOptions: None.
What it does:
- Validates
manifest.jsonagainst the SDK schema. - Runs
tscto compile TypeScript todist/. - Copies non-TS files from
src/ui/todist/ui/(HTML, CSS, images). - Scans
dist/for banned imports (electron,better-sqlite3,worker_threads,child_process— the last two in both their bare andnode:-prefixed spellings) and warns on a hit.validateruns the same scan and fails instead.
Example:
cd my-plugin
amc-plugin buildExit codes: 0 success, 1 manifest invalid or TypeScript compilation failed.
validate
Run all validation checks without building. CI-friendly -- returns a non-zero exit code on any failure.
Usage:
amc-plugin validateOptions: None.
Checks performed:
| Check | Description |
|---|---|
| Manifest schema | Validates manifest.json against the SDK schema |
| Command tables | Fails on a table that can hold a runnable command; warns on fuzzier shapes — see Command tables |
| SDK version | Ensures sdkVersion field is present |
| UI entry point | Verifies the declared UI entry file exists |
| Backend entry point | Verifies the declared backend entry file exists |
| TypeScript | Runs tsc --noEmit to check for type errors |
| Banned imports | Scans dist/ for disallowed Node/Electron imports — see below |
Banned imports, in detail. The scan reads every .js, .mjs and .cjs file under dist/ (for a flat plugin: the entry folders and single entry files you ship as authored) and rejects electron, better-sqlite3, worker_threads and child_process, matching all four forms a plugin can emit (require('x'), import … from 'x', side-effect import 'x', and dynamic import('x')), for both the bare and node:-prefixed spellings of the two Node builtins. Only the module name inside the quotes is matched, so a package that merely starts with a banned name — electron-store, child_process-promise — is not flagged.
Because the scan walks all of dist/, it also sees any dependency you vendor in there. child_process in particular is common in third-party code, so if you bundle dependencies into dist/ rather than declaring them, expect the scan to find them. That is working as intended: whatever ships in dist/ runs under Node's --permission, which denies spawning a process regardless of which file asked.
Where the scan looks — and its one limit. It reads your project on disk (dist/, or a flat plugin's as-authored entry dirs), not the .amcplugin archive itself. build, validate and publish's preflight all resolve that location the same way, so they never disagree about what "the shipped files" means. The limit: if you build an archive and then delete the output it was made from, there is nothing left to inspect — which is exactly why that case warns instead of passing. Treat this as a guardrail against accident, not a sandbox; the runtime boundary is the worker itself, which cannot reach these modules no matter what passes the scan.
Example:
# In a GitHub Actions workflow
amc-plugin validateExit codes: 0 all checks passed, 1 one or more checks failed.
Command tables
A plugin's backend must act only on commands from its own panel or its own code, never on a table row: AI agents on the person's computer can read every table, and on older Omniscio builds write them too (see Panel commands). So validate, preflight and publish all run one rule over the tables your manifest declares. It reads only manifest.json, so the three always agree. It is the same checkCommandTables function the SDK exports.
Column names are read by their words: payloadJson, payload_json and payload-json all read as "payload", and a trailing json / text / raw word is ignored.
| Result | When |
|---|---|
| Fails | A table has an instruction column (any word type, command, cmd, action, op, operation, kind, method, verb), an argument column (last word payload, args, arguments, params, parameters, input, inputs, body, data, prompt) and a status column (last word status or state) |
| Warns | A prompt-like column (last word prompt, instruction(s), script) with a status; a queue-like table name (queue, job(s), command(s), action(s), outbox…) with a status and an instruction or argument column; a table that is agentWritable and has a status |
Warns (validate only) | Shipped JS that asks a table for its pending or queued rows (.query(…, { where: { status: 'pending' } }), also when the column has a longer name such as summary_status or jobState) |
| Passes with a notice | A failing table that carries a written commandTableWaiver (20–500 characters) and is not agentWritable |
How to fix a failure: move the commands onto the command channel and reduce the table to a job list (label, status, error, times), or — if the table is genuinely safe — add a commandTableWaiver explaining why. Marketplace review sees the waiver.
What it cannot catch. It is a second line of defence, not a proof:
- an instruction without an instruction column (a
prompt+statustable only warns); - programs stored as data (a workflow graph in a row), or a re-armable status over plain data;
- authority fields (an approval flag, a spending cap, a host that receives secrets);
- a JSON blob hiding the shape, or table names built at run time;
- anyone who skips it:
publish --skip-preflight, a CLI older than 3.2.0, or a raw upload.
The plugin-table fence in Omniscio (agentWritable) and the command channel are the real protection; this check stops the classic mistake from shipping by accident.
package
Bundle the plugin into a .amcplugin archive (zip) for distribution or marketplace upload.
Usage:
amc-plugin packageOptions: None.
What it does:
- Validates
manifest.json. - Builds if
dist/does not exist. - Creates
<plugin-id>-<version>.amcpluginin the project root. - Includes
manifest.json,dist/,assets/(if present) and the rootREADME.md(if present — the marketplace renders it as the listing's long description; CLI 3.1 and later). - Warns if the archive exceeds the 50 MB marketplace limit.
Example:
amc-plugin package
# Packaged: my-plugin-1.0.0.amcplugin (0.12 MB)Exit codes: 0 success, 1 manifest invalid or build failed.
preflight
Run publish-readiness checks without uploading. This is the same gate publish runs before an upload, exposed as a standalone dry-run so you can fix issues first.
Usage:
amc-plugin preflight [options]Options:
| Flag | Description | Default |
|---|---|---|
--changelog <text> | Changelog for this version (checked for presence) | — |
Checks performed:
| Check | Fails when | Warns when |
|---|---|---|
| Version | Manifest version is already published (marketplace versions are immutable) or older than the latest published version | — |
| Changelog | — | No changelog provided |
| Permissions | An unknown permission is declared | Duplicate permissions |
| Command tables | A table can hold a runnable command (the rule) and carries no valid waiver | A fuzzier command-like shape |
| Banned imports | The shipped files import a banned module (details) | There is no built output to scan |
| Package size | Archive exceeds the 50 MB marketplace limit | Archive exceeds 25 MB |
| Listing | — | No README.md at the plugin root, an absent or empty plugin.screenshots, or an absent or empty plugin.links — the marketplace detail page would be bare (see Publishing → What your listing shows) |
If the marketplace registry is unreachable, the version check is skipped rather than blocking.
Why the banned-import check appears twice
validate is a check you choose to run; this is the one standing between a banned import and the marketplace. Both use the same rule and look in the same place, so a plugin that passes validate passes here.
"No built output to scan" warns rather than fails — with nothing inspected, zero hits proves nothing, and reporting a pass would be a false all-clear. Run amc-plugin build first so the files you are about to upload are the ones that get checked.
Example:
amc-plugin preflight --changelog "Added dark mode support"
# ✓ Version: 1.1.0 is newer than the published 1.0.0
# ⚠ Permissions: Duplicate permission(s): storage
# → Remove the duplicate entries from manifest.json permissions.
# ⚠ Listing: Listing will be bare — missing: screenshots (plugin.screenshots), support links (plugin.links)
# → Add a README.md next to manifest.json, hosted http(s) image URLs under plugin.screenshots, and support/homepage URLs under plugin.links — the marketplace detail page renders all three.
# ✓ Package size: 0.12 MBExit codes: 0 all checks passed (warnings allowed), 1 one or more checks failed.
publish
Upload a .amcplugin archive to the Omniscio Marketplace for review. Authenticates via GitHub OAuth on first use.
Usage:
amc-plugin publish [options]Options:
| Flag | Description | Default |
|---|---|---|
--changelog <text> | Changelog text for this version | — |
--watch | Poll until the submission is approved or rejected (up to 3 hours) | false |
--skip-preflight | Skip the pre-upload readiness checks | false |
--as <github-user> | Assert the expected GitHub account; aborts before upload on mismatch | — |
--switch-account | Sign out first and re-authenticate as a different account | false |
-y, --yes | Skip the upload-identity confirmation prompt (for CI) | false |
What it does:
- Checks for a stored auth token; prompts GitHub OAuth if missing.
- Confirms the uploading identity before upload (see the account trap below).
- Locates a
.amcpluginfile (runsamc-plugin packageif none found). - Runs
preflightreadiness checks (unless--skip-preflight); aborts on any failure. - Uploads the archive to the marketplace.
- With
--watch, polls every 30 seconds for the review decision.
The GitHub account trap
GitHub sign-in reuses whatever account your default browser is already logged into — there is no account chooser, so a publish can silently go out under the wrong identity (marketplace ownership is tied to the GitHub account).
Before uploading, publish shows Uploading as: <github> and asks you to confirm. If it's the wrong account:
- Run
amc-plugin publish --switch-accountto sign out and re-authenticate, or - Use
--as <github-user>in scripts to hard-assert the intended account (aborts on mismatch).
To land on the right account during sign-in, open an incognito / private window signed into the correct GitHub account first, or sign out at https://github.com/logout.
Example:
# Interactive — confirms "Uploading as: <you>" before upload
amc-plugin publish --changelog "Added dark mode support" --watch
# Publish under a specific account, re-authenticating if needed
amc-plugin publish --switch-account
# CI — assert the account and skip the prompt
amc-plugin publish --as my-org-bot --yesExit codes: 0 success (or approved with --watch), 1 preflight failed, upload failed, rejected, or --as mismatch.
status
Show the marketplace submission status for the current plugin.
Usage:
amc-plugin statusOptions: None.
What it does:
Reads manifest.json to determine the plugin ID, then queries the marketplace for all submissions matching that ID. Displays status, version, and review feedback.
Example:
amc-plugin status
# Submissions for "my-plugin":
# [OK] my-plugin v1.0.0 — approved (5/15/2026)
# [?] my-plugin v1.1.0 — pending (5/17/2026)Exit codes: 0 success, 1 auth failed.
whoami
Show the authenticated GitHub username.
Usage:
amc-plugin whoamiOptions: None.
Example:
amc-plugin whoami
# Signed in as: janedoe (uid: abc123)Exit codes: 0 always.
logout
Clear the stored marketplace authentication token.
Usage:
amc-plugin logoutOptions: None.
Example:
amc-plugin logout
# Signed out (was: janedoe)Exit codes: 0 always.
dev
Launch the dev shell with hot-reload for rapid plugin development.
Usage:
amc-plugin dev [options]Options:
| Flag | Description | Default |
|---|---|---|
--no-build | Skip the initial TypeScript build | false |
What it does:
- Builds the plugin (unless
--no-buildis passed). - Opens the dev shell -- an Electron window that loads your plugin UI with a mock
AgentMCcontext. - Watches
src/for changes and hot-reloads automatically.
Example:
amc-plugin dev
amc-plugin dev --no-buildExit codes: 0 clean exit, 1 build failed.
info
Show a summary of the current plugin project (name, version, author, category, discoverability tags, permissions, entry points).
Usage:
amc-plugin info [options]Options:
| Flag | Description | Default |
|---|---|---|
--json | Output the summary as JSON | false |
Example:
amc-plugin info
# Plugin: my-plugin
# Version: 1.0.0
# Category: other
# Tags: other, productivity
# Permissions: storage, cron
# UI: dist/ui/index.html
# Backend: dist/backend/index.js
amc-plugin info --json
# {"id":"my-plugin","version":"1.0.0", ...}Exit codes: 0 success, 1 no manifest.json found.
update
Self-update the @agent-mc/plugin-cli package to the latest version.
Usage:
amc-plugin update [options]Options:
| Flag | Description | Default |
|---|---|---|
--check | Check for updates without installing | false |
Example:
amc-plugin update --check
# Current: 1.0.0, Latest: 1.2.0 — update available
amc-plugin update
# Updated @agent-mc/plugin-cli to 1.2.0Exit codes: 0 success (or already up to date), 1 update failed.
test
Run the plugin test suite with vitest. Uses the plugin's local vitest if installed, otherwise falls back to npx vitest.
Usage:
amc-plugin test [patterns...] [options]Arguments:
| Name | Description |
|---|---|
patterns | Optional test file patterns to filter (passed through to vitest) |
Options:
| Flag | Description | Default |
|---|---|---|
--watch | Run vitest in watch mode | false |
Testing your backend with the SDK harness:
The SDK ships a test harness at @agent-mc/plugin-sdk/testing that gives you a faithful in-memory PluginContext — real storage and db (query / where / orderBy / limit, paged like Omniscio: 100 rows unless you pass a limit), plus capture surfaces for toasts, notifications, logs, events, sidebar, and inbox, and triggers for cron jobs and CLI handlers. No Omniscio or Electron needed.
import { describe, it, expect } from 'vitest'
import { createTestContext } from '@agent-mc/plugin-sdk/testing'
import activate from './index.js'
describe('my backend', () => {
it('stores a note and shows a toast', async () => {
const h = createTestContext({ settings: { apiKey: 'sk-test' } })
const backend = activate(h.ctx)
backend.onEnable?.()
await h.ctx.db.insert('notes', { title: 'hello' })
expect(await h.ctx.db.query('notes')).toHaveLength(1)
expect(h.toasts.map((t) => t.message)).toContain('Saved')
})
it('runs a cron job and answers a CLI request', async () => {
const h = createTestContext()
activate(h.ctx)
await h.runCron('heartbeat') // invoke a registered cron handler
const res = await h.callCli('status', { method: 'GET', path: 'status' })
expect(res.status).toBe(200)
})
})Inject fetch, ai, and auth via options to control outbound calls:
const h = createTestContext({
fetch: async () => new Response('{"ok":true}', { status: 200 }),
ai: { generateMessage: async () => 'stubbed reply' },
auth: { user: { uid: 'u1', email: 'a@b.co', displayName: null, photoURL: null } },
})The with-backend and full scaffold templates come with vitest, a test script, and a starter src/backend/index.test.ts wired to this harness.
Example:
amc-plugin test
amc-plugin test --watch
amc-plugin test src/backendExit codes: propagates vitest's exit code (0 all passed, 1 failures), 1 if vitest can't run.
doctor
Diagnose your local environment for plugin development. Run it any time something isn't working -- it reports a pass / warning / fail for each dependency the toolchain relies on, with a fix suggestion for anything that isn't right.
Usage:
amc-plugin doctor [options]Options:
| Flag | Description | Default |
|---|---|---|
--json | Output the results as JSON | false |
Checks performed:
| Check | Description |
|---|---|
| Node.js | Node version is supported (>= 18, 20+ recommended) |
| Plugin SDK | @agent-mc/plugin-sdk is installed and up to date with npm |
| Manifest | manifest.json (if present) is valid against the SDK schema |
| Omniscio host | A running Omniscio is reachable on 127.0.0.1:19519 (for local install / hot-reload) |
| Omniscio CLI token | A control-server token is available (~/.amc/cli-token or $AMC_CLI_TOKEN) |
| Marketplace API | The marketplace backend is reachable (needed to publish) |
Warnings never fail the command -- only a hard failure (unsupported Node, or an invalid manifest.json while inside a plugin project) sets a non-zero exit code. Host, token, and marketplace checks are environmental and only ever warn.
Override the host port with the AMC_CLI_PORT environment variable.
Example:
amc-plugin doctor
# Omniscio Plugin Doctor
# ✓ Node.js: Node v22.11.0
# ✓ Plugin SDK: @agent-mc/plugin-sdk 1.1.0
# ✓ Manifest: manifest.json is valid
# ⚠ Omniscio host: No running Omniscio on 127.0.0.1:19519
# → Start Omniscio to enable local install and hot-reload.
# ✓ Omniscio CLI token: Omniscio CLI token available
# ✓ Marketplace API: Reachable
#
# ⚠ 5 passed, 1 warning, 0 failed
amc-plugin doctor --jsonExit codes: 0 no failures, 1 an unsupported Node or invalid manifest.