@lightward/mechanic-cli
v0.1.15
Published
Develop, preview, diff, and publish Mechanic Shopify automation tasks from local files
Downloads
2,154
Readme
Mechanic CLI
Develop Mechanic tasks locally: pull Shopify automation tasks from Mechanic, edit Liquid and docs in normal files, preview changes safely, review diffs, and publish intentionally.
Mechanic is a Shopify automation platform for teams that need flexible, code-backed workflows. Install Mechanic from the Shopify App Store at https://apps.shopify.com/mechanic, read the docs at https://learn.mechanic.dev/, or start with the CLI guide at https://learn.mechanic.dev/platform/mechanic-cli.
This CLI is built around the same task export shape used by Mechanic, so tasks move cleanly between the app, local files, version control, and pull requests.
Use it to:
- pull tasks from Mechanic into a local repository
- edit Liquid, Markdown, and task configuration in normal files
- preview task changes before saving them to Mechanic
- diff local files against the current Mechanic task
- publish one task intentionally, with dry-run and conflict protection
- automate single-shop task updates with optional GitHub Actions workflows
Mechanic is made by Lightward. You can also meet Lightward AI at https://lightward.com.
Requirements
- Node 22 or newer
- A Mechanic shop with an API token created in the Mechanic app
Install
Install from npm:
npm install -g @lightward/mechanic-cliIn interactive terminals, Mechanic occasionally checks npm and prints the update command when a newer CLI is available.
To check your installed CLI version against the latest published version:
mechanic versionQuick Start
Create an API token in Mechanic:
- Open the shop in Mechanic.
- Go to Settings -> API tokens.
- Create a token named for the place it will be used, like
LaptoporGitHub Actions. - Copy the token immediately. Mechanic only shows it once.
Initialize a local project for that Shopify shop, then paste the token when prompted:
mechanic init --shop example.myshopify.comChoose how to start.
To bring one existing task into local files, find its remote task ID, then pull that task:
mechanic tasks list --verbose
mechanic tasks pull <remote-task-id>To intentionally bootstrap a repo with every task in the shop:
mechanic tasks pullTo start from scratch instead, create a new blank local task. This writes a starter JSON file and matching helper directory. You can use this in any initialized CLI project; it does not require a fresh repository:
mechanic tasks new order-taggerFor most Liquid or documentation edits, use the helper directory beside the JSON
file. Existing pulled tasks can be unbundled first; tasks created with
tasks new already have the helper directory.
# for an existing pulled task only
mechanic tasks unbundle order-tagger
# edit tasks/order-tagger/script.liquid, docs.md, or task.json
mechanic tasks bundle order-tagger
mechanic tasks status
mechanic tasks preview order-tagger
mechanic tasks diff order-tagger
mechanic tasks publish order-tagger --dry-run
mechanic tasks publish order-taggerFor example, edit tasks/order-tagger/docs.md or
tasks/order-tagger/script.liquid, bundle it, preview it, then publish only
when the diff and dry-run plan look right.
For normal setup, paste the token into the masked prompt or run
mechanic auth login after mechanic init. In CI, store the token as
MECHANIC_API_TOKEN. The --token flag exists for controlled automation, but
avoid typing secrets into shared shells because shell history or process listings
can expose them.
To check whether the shop is currently busy or backlogged:
mechanic shop statusMost commands that editor integrations or agents would call support --json.
Use JSON output for automation instead of parsing tables or colored text.
When a --json command fails before producing its normal output (auth
problems, network failures, rate limits), it prints a JSON error envelope to
stdout instead, with exit codes unchanged:
{ "error": { "message": "Mechanic API rate limit exceeded.", "status": 429, "retry_after_seconds": 7 } }status is present when the Mechanic API rejected the request, and
retry_after_seconds is present when a rate limit response includes
Retry-After. With --json, stdout is always a single JSON document: the
command's normal output, or this error envelope.
Commands
mechanic init --shop example.myshopify.com [--token <token>] [--api-base-url <url>] [--app-url <url>] [--force]
mechanic help [command]
mechanic version [--json]
mechanic doctor
mechanic auth login [--token <token>]
mechanic auth logout
mechanic github init [--force]
mechanic shop status [--json]
mechanic shop deprecations [task] [--json]
mechanic tasks list [--verbose] [--json]
mechanic tasks new <name> [--force] [--json]
mechanic tasks open <task>
mechanic tasks status [task] [--local] [--json]
mechanic tasks pull [--force]
mechanic tasks pull <task> [--force]
mechanic tasks pull --all [--force]
mechanic tasks preview [task] [--stdin] [--remote] [--verbose] [--json]
mechanic tasks diff <task> [--exit-code] [--json]
mechanic tasks diff --all [--exit-code] [--json]
mechanic tasks publish <task> [--force] [--dry-run] [--json]
mechanic tasks publish --all [--force] [--dry-run] [--json]
mechanic tasks unbundle <task> [--out <dir>] [--json]
mechanic tasks bundle <task|dir|file> [--out <file>] [--json]
mechanic tasks validate <task|dir> [--json]
mechanic globals list [--json]
mechanic globals pull [--out <file>] [--force]
mechanic globals push [--file <file>] [--dry-run] [--force]
mechanic globals set <key> --json <value>
mechanic globals delete <key> --force
mechanic secrets list [--json]
mechanic secrets set <key> [--from-stdin | --value-env <env>] [--force]
mechanic secrets delete <key> --forceMost task commands accept a <task> selector. In day-to-day use, prefer the
short local task slug:
mechanic tasks preview order-taggerThe CLI also accepts a full task JSON path, matching helper directory, or linked remote task ID:
mechanic tasks preview tasks/order-tagger.json
mechanic tasks preview tasks/order-tagger
mechanic tasks preview 171578bf-79e2-46af-857a-dbd71c6b7b2bThe local file is the working copy. The remote task ID is the Mechanic app's
address for that task. tasks list shows linked local files when the CLI knows
them. If a short slug matches more than one local file, the CLI asks for the
full file path; that is the main reason to use tasks/<name>.json directly.
Task names and local file names are related, but they are not the same identity.
When the CLI first pulls or creates a task, it uses the task name to choose a
readable local slug like order-tagger, which becomes tasks/order-tagger.json.
After that, the remote task ID stored in .mechanic/links.json is the sync
identity. If you rename the task in Mechanic, the next pull keeps the existing
local file name and updates the name field inside the JSON. If you rename the
local JSON file or helper directory by hand, the CLI treats that as a new local
slug and the task may appear unlinked. Keep local filenames stable unless you
also intentionally update .mechanic/links.json and verify with tasks status
and tasks publish --dry-run.
tasks pull pulls every task when you run it without arguments. Pass a task
selector when you only want one task. tasks diff and tasks publish operate
on one task when you pass a selector. They operate on every task only when you
explicitly pass --all.
tasks new creates a new blank local starter task. It writes both
tasks/<slug>.json and tasks/<slug>/, so you can edit the helper files first
and bundle them into the JSON file before publishing. It can be used any time in
an initialized CLI project, and it refuses to overwrite existing local task files
unless you pass --force. It does not create anything in Mechanic until you run
tasks publish; new published tasks are created disabled.
shop status shows the current Mechanic run queue for the configured shop:
running runs, waiting runs, queue lag, and the largest backlog groups by task,
action, and event topic.
shop deprecations shows unresolved Shopify API deprecations reported by tasks
in the configured shop. Pass a task slug, task file, helper directory, or linked
remote task ID to focus on one task:
mechanic shop deprecations
mechanic shop deprecations order-taggerThe output includes the task's configured Shopify API version and the Shopify API
version seen in the deprecated request. Use --json for monitoring scripts,
agents, or dashboards.
tasks status shows whether local task JSON files are linked, ready to publish,
and in sync with their remote Mechanic tasks. Use --local when you only want
the fast offline check for JSON validity, link state, and helper-folder drift.
Add --json when an editor integration needs task readiness, link state, and
remote sync state without parsing table output.
tasks diff compares current Mechanic with your local file, groups output by
changed field, and only shows nearby changed lines. In diff output, - means
current Mechanic and + means local file. Differences are informational by
default; add --exit-code when a script or CI job should treat differences as a
nonzero result.
tasks preview is the confidence check before publishing. It sends one local
task to Mechanic's preview engine without saving it, then reports whether the
sample events passed, which actions would run, validation errors, and Shopify
permissions detected by the previewed paths. Add --remote to preview the
current task already in Mechanic instead of your local draft. Add --verbose
to show event, task run, and action run result details in the terminal. Add
--json when an agent, script, or CI job needs the raw preview response.
Missing Shopify permissions are approved in the Mechanic app after publishing
or enabling the task. Preview exits 0 when the preview passes, 1 when
sample runs fail, and 2 when the task is invalid.
tasks preview --stdin reads task JSON from stdin instead of local files, so
editors and other tools can preview in-memory task content without writing it
to disk first:
cat build/order-tagger.json | mechanic tasks preview order-tagger --stdin
generate-task | mechanic tasks preview --stdin --jsonPass a task selector alongside --stdin to preview the piped content in the
context of that linked Mechanic task, or omit the selector to preview the
content as a new unlinked task. --stdin trusts the piped content as the
source of truth, so it skips the stale helper directory check. Previews are
rate limited per token; tools that preview on every edit should debounce and
respect 429 Retry-After responses. With --json, a rate limited preview
prints the JSON error envelope, including retry_after_seconds.
tasks publish sends local task JSON back to Mechanic.
tasks publish --dry-run is a publish preflight. It checks whether publishing
would be safe, including helper-folder drift, linked remote task changes, and
unlinked files that look like existing remote tasks. It then prints what would
create, update, stay unchanged, or stop as a conflict. It does not write to
Mechanic, .mechanic/links.json, or local task JSON. New tasks created by
tasks publish are created disabled; review and enable them in Mechanic when
they are ready to run.
For automation or editor integrations, add --json to tasks list, tasks
status, tasks preview, tasks diff, tasks validate, tasks bundle,
tasks unbundle, tasks publish, or shop status.
tasks unbundle and tasks bundle operate on one task at a time. By default,
mechanic tasks unbundle order-tagger writes to tasks/order-tagger/, and
mechanic tasks bundle order-tagger writes back to tasks/order-tagger.json.
You can pass the full JSON path when you want to be explicit.
When a task has subscriptions_template, the editable helper file is
subscriptions.liquid. Mechanic renders that template into subscriptions, so
the CLI treats subscriptions as generated state instead of something to edit
or publish by hand.
mechanic init accepts --token, reads MECHANIC_API_TOKEN, or prompts for an
API token in an interactive terminal. It verifies the token and stores it
outside the project. mechanic auth login does the same thing later if you skip
token setup during init. For automation, set MECHANIC_API_TOKEN; it takes
precedence over any locally stored token for authenticated commands.
mechanic doctor checks the local project config, token source, verified shop,
and task API access. Run it after mechanic auth login or when a project stops
syncing cleanly.
mechanic init defaults to https://api.mechanic.dev for the API and refuses
to overwrite an existing project unless you pass --force. The CLI only sends
API tokens to trusted Mechanic API hosts by default. Use --api-base-url or
MECHANIC_API_BASE_URL only for a staging or dedicated Mechanic API host you
control; set MECHANIC_TRUST_API_BASE_URL=1 only when you intentionally trust
that host. Task IDs in command output link back to the Mechanic embedded app in
terminals that support hyperlinks; use mechanic tasks open when you want to
open a task explicitly. Use --app-url or MECHANIC_APP_URL if your shop uses
a non-production Shopify app alias.
Use mechanic tasks open <task> to open a task in the Mechanic app from its
local task slug, linked local task JSON file, helper directory, or remote ID.
Advanced: GitHub Actions Task Workflows
Local CLI usage works without GitHub Actions. If you want optional single-shop Git sync automation, start from a populated Mechanic CLI project, then generate the recommended workflows:
mechanic tasks pull
mechanic github initThis creates:
.github/workflows/mechanic-validate.yml
.github/workflows/mechanic-deploy.yml
.github/workflows/mechanic-sync-from-app.ymlRe-run with --force only when you intentionally want to replace those
workflow files.
Add one repository or organization secret named MECHANIC_API_TOKEN containing
a Mechanic API token for the shop in mechanic.json.
From the task repository, the safest repository-secret setup is:
gh secret set MECHANIC_API_TOKENPaste the token when prompted. If you are not running the command from the task
repository checkout, add --repo OWNER/REPO.
The generated workflows install the same @lightward/mechanic-cli version that
generated them and use the api_base_url committed in mechanic.json.
Only enable the deploy and sync workflows in repos whose maintainers you trust. Those workflows use the Mechanic API token, and the sync workflow also needs repository write permissions so it can open or update sync-back pull requests.
The generated workflows do three jobs:
mechanic-validate.ymlruns on pull requests and manually, with no Mechanic token, validatestasks/, and fails if helper task directories need bundling.mechanic-deploy.ymlruns manually. It requires eithertask_pathordeploy_all=true, always runsmechanic tasks publish ... --dry-run, and only writes to Mechanic when you choosemode=deploy. The default isdry-run; inspect that plan first, then rerun withmode=deploywhen you mean to publish. After a deploy, the workflow opens or updates a sync-state PR so.mechanic/links.jsonand task hashes stay current.mechanic-sync-from-app.ymlruns manually, pulls app updates withmechanic tasks pull --all --force, validates the result, and opens or updates a PR frommechanic-sync/from-appwhen files changed. The PR body includes a changed-file summary and the V1 deletion caveat. Add a schedule to this workflow later only if you want automatic app-to-Git sync PRs.
The pull-from-app workflow is intentionally update-only in V1: it does not prune local files or links for tasks that were deleted in Mechanic.
Generated GitHub workflows do not sync mechanic.globals.json. Add a separate
workflow step later if you intentionally want globals sync in automation.
Project Layout
mechanic.json
.mechanic/
links.json
tasks/
order-tagger.jsonmechanic.json stores the Shopify shop domain, API base URL, embedded app URL
for task links, and tasks directory. Because this file is repo-controlled, the
CLI validates configured API and app URLs before using them with a token.
.mechanic/links.json maps local task slugs to remote Mechanic task IDs and
the last known remote content hash. Commit this file when the repo represents a
shared source of truth for that shop.
API tokens are stored outside the project under the local user config directory,
not in mechanic.json. Token files are written with owner-only permissions.
Task Files
Canonical task files are JSON files at tasks/<slug>.json. They use Mechanic's
task export fields, including:
namescriptdocssubscriptions_templatesubscriptionsoptionspreview_event_definitionstagsshopify_api_versiononline_store_javascriptorder_status_javascript
Remote IDs are never written into task JSON. mechanic tasks publish also
removes enabled from the API payload so local sync cannot accidentally enable
or disable a production task.
Direct-subscription tasks may use null for script, docs, and
subscriptions_template.
Bundle And Unbundle
JSON is the canonical file format, but editing long Liquid and Markdown strings inside JSON is unpleasant. The helper workflow splits a task into separate editable files:
mechanic tasks unbundle order-tagger --out order-taggertasks unbundle also accepts the same local task selectors as preview, diff,
and publish:
mechanic tasks unbundle order-tagger
mechanic tasks unbundle tasks/order-tagger.json
mechanic tasks unbundle tasks/order-tagger
mechanic tasks unbundle 171578bf-79e2-46af-857a-dbd71c6b7b2border-tagger/
task.json
script.liquid
docs.md
subscriptions.liquid
online_store_javascript.js.liquid
order_status_javascript.js.liquidAfter editing, bundle the helper directory back to canonical JSON:
mechanic tasks bundle order-tagger
mechanic tasks validate order-taggertasks bundle accepts the local task slug or either side of the pair.
mechanic tasks bundle order-tagger and mechanic tasks bundle
tasks/order-tagger both write tasks/order-tagger.json. mechanic tasks bundle
tasks/order-tagger.json reads tasks/order-tagger/ and writes back to that
JSON file.
When unbundling, helper files are written only for string fields. null fields
stay in task.json, and stale helper files for non-string fields are removed.
When publishing a JSON file, the CLI checks for a matching helper directory. If
tasks/order-tagger/ has changes that are not bundled into
tasks/order-tagger.json, publishing stops before making any API request.
mechanic init adds Mechanic Liquid helper files to .prettierignore.
Formatter rewrites can change task behavior, especially for newline-delimited
subscriptions.liquid and exact Liquid string quotes in script.liquid.
Sync Safety
The CLI uses remote content hashes to avoid overwriting task edits made in the Mechanic app or by another local checkout.
tasks pullrecords the latest remote content hash in.mechanic/links.json.tasks pull <task>can refresh a linked local file without--forcewhen the local file still matches the last synced hash. If local edits would be lost, it stops and asks for an explicit--force.tasks publishsends that hash asprevious_content_hash.- The API rejects stale publishes with a conflict.
tasks diffwarns when Mechanic changed since the file was last synced, then compares current Mechanic with your local file. If only Mechanic changed, pull normally to update your local file. If both Mechanic and your local file changed, choose explicitly: pull with--forceto replace local with Mechanic, or publish with--forceto overwrite Mechanic with local.tasks publish --allchecks every linked remote task before writing anything, so one stale task blocks the whole publish instead of partially publishing earlier files first.tasks publish --forcebypasses the hash guard when you intentionally want the local file to win.
Creating a new remote task sends a deterministic idempotency key for the local shop and task slug, so retrying after a timeout returns the same remote task instead of creating duplicates.
Before creating a task from an unlinked local file, the CLI checks existing remote task names for the same normalized slug. If a likely match already exists in Mechanic, publishing stops and asks you to pull/link the remote task first.
tasks publish --all rejects local files whose names normalize to the same task
slug, for example foo-bar.json and foo_bar.json.
Globals and Secrets
Shop globals are visible JSON configuration shared across tasks. The
mechanic globals pull command writes mechanic.globals.json, which is
repo-safe and may be committed. By default it refuses to overwrite an existing
file whose contents differ; pass --force when the remote globals should
replace the local file. The mechanic globals push command validates the whole
file and checks remote globals before sending any writes. By default it creates
new globals but refuses to overwrite existing remote globals whose values differ;
use mechanic globals push --dry-run to review the plan, then pass --force
when the local file should replace those remote values.
For one-off global changes:
mechanic globals list
mechanic globals set warehouse_id --json '"main"'
mechanic globals set shipping_rules --json '{"regions":["CA","US"]}'
mechanic globals delete warehouse_id --forceShop secrets are write-only string values. The CLI never creates a secrets file,
and task pull, preview, and publish never write secret values into task JSON.
Use mechanic secrets set <key> --from-stdin or --value-env <env> for
non-interactive use; --from-stdin preserves exact stdin content, including
trailing newlines. Secret values are not printed after save. Replacing or
deleting a secret can break tasks until they are updated, so existing secret
values and secret deletes require --force.
For one-off secret changes:
mechanic secrets list
mechanic secrets set api_token
printf %s "$API_TOKEN" | mechanic secrets set api_token --from-stdin
mechanic secrets set api_token --value-env API_TOKEN --force
mechanic secrets delete api_token --forceTask Sync API
This package targets Mechanic's v1 task sync API. The CLI is the recommended client; direct HTTP use is intended for trusted, CLI-compatible automation.
GET /v1/auth/verifyGET /v1/shop/statusGET /v1/tasksGET /v1/tasks/:idPOST /v1/tasks/previewPOST /v1/tasks/:id/previewPOST /v1/tasksPUT /v1/tasks/:idGET /v1/globalsPUT /v1/globals/:keyDELETE /v1/globals/:keyGET /v1/secretsPUT /v1/secrets/:keyDELETE /v1/secrets/:key
Preview endpoints run local or remote task content through Mechanic's preview engine without creating, updating, or saving a task.
Requests use Bearer token auth with API tokens created in the Mechanic app.
The CLI does not make separate telemetry calls. Authenticated API requests
include a MechanicCLI/<version> user agent so Mechanic can observe CLI usage
from server-side operational logs.
This API surface is intentionally narrow: task sync, task preview, shop status, and shop-level globals/secrets for the authenticated shop. It is not a general Mechanic API for arbitrary integrations.
