pMate CLI
A TypeScript-powered command-line tool scaffold for future pMate automation.
Deploy from an isolated checkout
Vite deployments reject --cwd. They use the registered repository's conventional local checkout (~/dev/pmate/<repository>), refuse any uncommitted changes (including untracked files), fetch the latest GitHub main, switch to main and fast-forward to that exact revision. A missing remote main, failed switch/fetch, or local main with unpushed/divergent commits aborts before dependency installation, build or upload. No changes are stashed, reset or discarded. Dependencies are installed with pnpm install --frozen-lockfile; the deployed commit SHA is printed. Commit and push changes to main before deploying.
Static and FC services can be released without modifying the conventional ~/dev/pmate checkout. Use pmate service deploy pmate:<service> --env prod --cwd /absolute/path/to/repo for static or FC services. The registered service directory is resolved relative to that repository root; --cwd does not change the service's registered repository or environment.
Remote Node/Python/Rust deployments still use the registered repository and reject --cwd instead of silently ignoring it. Merge and push the intended release to main before deployment. The normal service deploy-history and log flow is unchanged.
Development
npm install
npm run dev -- --help
npm run devexecutes the TypeScript entry point withts-node.npm run buildemits compiled JavaScript intodist/.npm startruns the compiled CLI (dist/index.js).
Usage
After running npm run build, you can invoke the CLI locally via:
node dist/index.js --name Alice
When published or linked (e.g., npm link), the binary will be available as pmate:
pmate --help
pmate --name Bob
pmate add-dns --type A --value 1.2.3.4 --rr api
pmate login
pmate login --app @pmate/chat
pmate sts
pmate oss list
pmate oss upload --file ./path/to/file --key assets/file.txt
pmate oss upload --file ./path/to/file --key upload/file.txt --bucket parrot-static
pmate pr
pmate commit
pmate commit --push
pmate pr merge
pmate linear update ENG-123 --title "Updated title"
pmate linear update ENG-123 --desc $'line1\nline2'
pmate linear create --account work --title "Issue title"
pmate linear cycle
pmate linear teams --account pcs
pmate notion view <page-id-or-url>
pmate notion create --parent <page-id-or-url> --title "Sprint Notes" --content "Initial notes"
pmate notion create --private --title "Private Notes" --file ./notes.md
pmate notion update <page-id-or-url> --file ./notes.md --append
pmate vercel projects
pmate vercel deploys --project web --last-week
pmate im1 health
pmate im1 threads --limit 20
pmate im1 threads --search "titlte" --limit 5
pmate im1 messages --thread "<conversationId-or-thread-name>" --limit 50
pmate im1 pending-mentions --hours 24 --context 10
pmate im1 reply --thread "<conversationId-or-thread-name>" --text "hello"
Notes:
pmate prnormalizes generated PR titles to start withfeat:,fix:, orchore:(exactly one space after:).pmate commitcreates an AI-generated commit from local changes.pmate commit --pushalso pushes the current branch to origin.pmate pr mergeuses squash merge and deletes the branch by default (--squash --delete-branch).- Each app in
.pmate.yamlmust define bothapp(CLI key) anddir(repo-relative directory, e.g.apps/auth).
Aliyun credentials config
You can set Aliyun credentials in ~/.pmate/config.yaml:
aliyun:
ak: your_ak
sk: your_sk
region: cn-hangzhou
domain: example.com
Linear access guide
Background/背景
When multiple teams or workspaces share one machine, running Linear actions from CLI often fails due to mixed API keys and unclear team defaults. pmate linear solves this by supporting multi-account config, per-repo overrides, and explicit team/account flags.
The goal is to make ticket operations reproducible in scripts and local workflows: pick the correct account, resolve the target team deterministically, then run create/view/update/list commands without re-authing in browser.
1) Add Linear account(s)
Add one or more Linear accounts to ~/.pmate/config.yaml:
linear:
teamKey: ENG
accounts:
- title: work
apiKey: your_linear_api_key
# optional default team for this account
teamKey: ENG
- title: personal
apiKey: your_other_linear_api_key
Rules:
accounts[].titlemust be unique.accounts[].apiKeyis required.- If multiple accounts exist and you do not pass
--account,pmateasks once and caches selection in~/.pmate/linear-selection.json.
2) (Optional) Pin account/team per repository
Add repo-local defaults in .pmate.yaml:
linear:
teamKey: ENG
account:
title: repo-work
apiKey: your_repo_specific_linear_api_key
When linear.account is present in .pmate.yaml, it is used by default for that repo.
3) Verify access
Use these commands to confirm credentials and team visibility:
pmate linear whoami --account work
pmate linear teams --account work
pmate linear cycle --account work --team-key ENG
4) Common workflows
# create
pmate linear create --account work --team-key ENG --title "Fix login timeout"
pmate linear create --account work --repl
# read
pmate linear view ENG-123 --account work
pmate linear comments ENG-123 --account work
pmate linear sub-issues ENG-123 --account work
# update
pmate linear update ENG-123 --account work --title "Updated title"
pmate linear update ENG-123 --account work --desc $'line1\nline2'
pmate linear comment ENG-123 --account work --body "Investigated and reproduced"
# list/filter
pmate linear user-issues "Ryan" --account work --status "In Progress"
pmate linear issues --account work --team-key ENG --cycle 82 --status "Todo" --limit 100
# create issue from current GitHub PR
pmate linear pr --account work
Auth session
pmate login stores sessions in ~/.pmate/session.json. The file now keeps a
multi-app session registry keyed by app id, so logging into one app no longer
overwrites another app's session. Use --app <app-id> to authenticate against
a specific app; the default remains @pmate/cli.
Notes:
- Existing single-session files are migrated automatically on first read.
- Platform commands such as
pmate sts,pmate function, andpmate appstill default to the@pmate/clisession.
IM1 / CC messages
pmate im1 reads CC messages through the local cc-bot service at
http://localhost:39223 by default. Override the base URL with
PMATE_IM1_BASE_URL only when debugging another local service instance.
Useful commands:
pmate im1 health
pmate im1 threads --limit 20
pmate im1 threads --q Bug --limit 10
pmate im1 threads --search "titlte" --limit 5
pmate im1 messages --thread "<conversationId-or-thread-name>" --limit 50
pmate im1 reply --thread "<conversationId-or-thread-name>" --text "hello"
threads and messages output JSON by default. Thread rows include both
conversationId and threadKey; prefer conversationId when scripting or
passing a thread back into messages/reply. Add --text for compact
human-readable output.
Use pmate im1 threads --search "<title>" for fuzzy title lookup. It compares
the lowercased search text against thread titles with edit distance, including
nearby token/window matches for typos such as titlte vs title. JSON output
includes searchDistance and searchScore.
CC sync is owned by the background service in ~/dev/pcs/cc-bot, which runs
under PM2, keeps TimescaleDB running, listens for new CC messages, and performs
bounded 3-day backfill. pmate im1 is CC-only and does not accept --source
or custom --endpoint options.
Notion config
Add a Notion integration token to ~/.pmate/config.yaml:
notion:
token: your_notion_integration_token
Vercel config
Add your default Vercel scope to ~/.pmate/config.yaml:
vercel:
scope: pancakeswap
Usage:
pmate vercel projects
pmate vercel projects --scope another-team
pmate vercel deploys --project web --last-week
pmate vercel deploys --project web --scope pancakeswap
Notes:
pmate vercelis a thin wrapper around the installedvercelCLI.- v1 only supports
vercel.scopein PMate config. pmate vercel deploysrequires--project <name>.
Service logs
Service logs use the same authenticated API as dev.pmate.chat. Sources are registered Service/topic pairs on the dedicated log chain.
pmate service log pmate:issues-api --topic application --page 0 --json
pmate service log pmate:issues-api --topic nginx --field status=500
pmate service log pmate:issues-api --field requestId=<id>
Pages are zero-based. Lists and --search cover the chain's configured retention window (30 days in production). --since-ms adds an epoch-millisecond lower bound. --field name=value (repeatable, scalar JSON values) and --env filter on the server. Topics are independent sources such as application, nginx, pm2, deployment, pv, and vitals; custom fields live in data. Old --type, --level, --source, and --request-id flags are removed. The legacy --app argument is optional and does not select a different source.
A current CLI session is exchanged in memory for pmate:developers; the active App and session file are unchanged. If needed, run pmate login --app pmate:developers. Permission denial never falls back to anonymous Indexer reads. Credentials are never printed.
Log analysis tools
pmate logs exposes the same read-only tools used by Meili. The server calculates results over a bounded retained window, with query IDs and coverage metadata; the model does not count sampled pages.
pmate logs schema --json
pmate logs query pmate:apps-api --topic application --field level=error --json
pmate logs aggregate pmate:hello --topic pv --from 2026-10-08T00:00:00+08:00 --to 2026-10-08T10:00:00+08:00 --group-by path --interval hour --timezone Asia/Shanghai --json
pmate logs trace pmate:developers-api --request-id REQUEST_ID --json
pmate logs compare pmate:hello --topic pv --from 2026-10-08T00:00:00+08:00 --to 2026-10-08T10:00:00+08:00 --compare-from 2026-10-07T00:00:00+08:00 --compare-to 2026-10-07T10:00:00+08:00 --json
PV counts page-view events; sessions deduplicate appId/accessId; Profile UV deduplicates appId/profileId. Missing IDs and anonymous events remain separate. Query/trace default to application; aggregate/compare default to pv. Vitals percentiles require --field name=LCP (or another specific metric). Current user classification is visit frequency within the query window; first-ever/new users cannot be inferred from retention-limited history.
Time windows are [from,to) and at most 31 days, inside the configured retention. Times accept Unix milliseconds or ISO with explicit timezone. Comparisons require equal duration. More than 10,000 records/100 pages/100 groups is rejected instead of reporting partial totals. Indexing and browser telemetry remain best effort; completeRead is not proof of complete real-world activity. Query pages are zero-based.
Meili's Host may pass --actor-profile-id with PMATE_LOG_DELEGATE_TOKEN in the child environment. This mode never reads or exchanges the host's personal CLI session. Auth API verifies the allowlisted Bot and the actual sender's pmate:dev permission on every call. Models cannot supply the actor or token.
Package a desktop app
Packaged installers are automatically uploaded to Aliyun OSS (parrot-static), using the same credentials as Android APK releases. The completion output and build report include public download URLs, SHA-256 checksums, and a release.json URL. Configure oss.public.region and aliyun.ak/sk in your PMate configuration. Conflicting published versions require an app version bump. --dir produces local unpacked output only; --dry-run does not upload. The CLI owns publication after electron-builder completes; electron-builder receives the immutable generic release URL only to generate valid package metadata and runs with publishing disabled.
pmate service deploy <ns:electron-service> packages a native shell that loads its configured HTTPS origin directly, without building or embedding web resources, and produces macOS DMG/ZIP or Windows NSIS/ZIP artifacts. Packaged releases require a CLI login with admin permission in the app namespace; --dry-run only prints a plan; --local keeps build artifacts local, and is required with --unsigned or --dir. Signed builds are the default; --signed remains accepted. The service type must be electron, repository/dir locate the source, appIds must contain exactly one application identity, and optional build settings include config (relative to service dir, default desktop.config.json), platform and arch. The desktop config app must match the associated application, not the service ID. Platform and architecture default to the host and Node architecture; output defaults to release/ at the repository root. Optional desktop config notaryProfile selects a local Keychain profile; APPLE_KEYCHAIN_PROFILE overrides it. macOS publication requires Developer ID signing, notarization and Gatekeeper acceptance. The first build downloads pinned Electron and electron-builder versions. Current targets: macOS and Windows, arm64 and x64; macOS packages require a macOS build host.
pmate service deploy pmate:merry-electron
Config schema 2 (icon is relative to the config file; migrate schema 1 by removing web and changing schemaVersion to 2):
{
"schemaVersion": 2,
"app": "pmate:hello",
"productName": "Going Merry",
"bundleId": "chat.pmate.goingmerry",
"version": "0.1.0",
"origin": "https://hello.pmate.chat",
"authOrigins": ["https://auth.pmate.chat"],
"externalOrigins": ["https://github.com"],
"icon": "desktop/icon.png"
}
externalOrigins is optional. Links to these exact origins open in the system browser when initiated by the application document, even when the same origin is also allowed in authOrigins; isolated authentication windows continue navigating internally.
Use --dry-run to inspect the plan, --dir for an unpacked .app, or --signed to require a configured macOS signing identity. Default builds are ad-hoc signed for local use; they are not notarized distribution releases. Signed builds use electron-builder's standard signing/notarization environment variables; verify notarization separately before distributing.
The shell loads the deployed HTTPS website. Packaging requires only the desktop configuration, icon and native runtime dependencies: no frontend package manager, source tree or dist is needed. Web UI, business logic and compatible frontend bridge SDK changes ship through web deployment; the next open or user refresh receives them. Do not reload an active conversation or call automatically. Rebuild installers only when installed preload/main-process bridge code, native capabilities/permissions, or the Electron runtime must change. The first migration from an embedded renderer needs one new installer.
@pmate/bridge provides getDesktopBridge(), runtime appVersion, electronVersion, bridgeVersion, contentSource, and callable capability detection. npm SDK versions are independent of the installed host protocol. Missing host capabilities must be shown as unsupported or requiring a shell upgrade; never infer them from the Electron engine version. Preload APIs are exposed only to the configured origin's main frame; auth windows do not receive native APIs. IPC validates the sender and frame, with Node disabled, sandbox and context isolation enabled. The local offline page offers retry without clearing the existing user-data partition. Website CSP and cache policy apply to remote pages.
Verify changes with pnpm exec vitest run src/tests/electron*.test.ts and pnpm typecheck. With PMATE_TEST_ELECTRON_PATH pointing to an Electron executable, run node scripts/test-electron-online-runtime.cjs for real HTTPS loading, web revision changes without a rebuild, IPC isolation, offline retry and data persistence. Set PMATE_TEST_LIVE_ORIGIN=https://merry.pmate.chat to include production SPA loading. Run node scripts/test-electron-calls-runtime.cjs for the isolated incoming-call flow. Certificate bypass is restricted to the local test process; production code keeps certificate validation enabled.
The native application menu includes 检查更新…, which checks installed shell releases through apps-api and opens the verified installer after confirmation. Installation remains manual. Each build prints its artifact directory and signing report; packaged releases upload installers and register the version; --dir stays local.
日志统计默认口径
请求次数与日均可使用 pmate logs aggregate pmate:issues-api --metric count --topic application --field event=http.access --interval day --timezone Asia/Shanghai --from <ISO> --to <ISO> --json。计数由数据库完成,返回 count、durationDays、averagePerDay,时间桶包含零值;不返回 UV 或延迟百分位。JSON 模式失败以非零状态退出并返回固定 error.code,区分权限、保留期、查询预算和上游故障,避免泄露上游原始错误。
Desktop version registry
Desktop clients need a stable way to discover releases without knowing their versioned OSS path. Apps-api owns this index; installer metadata and checksums remain in OSS release.json.
GET https://apps-api.pmate.chat/versions/pmate:hello?platform=mac&arch=arm64 is public and returns { success: true, data: { app, version, platform, arch, manifestUrl, publishedAt, publishedBy, id } }. A missing release returns 404; it does not mean the installed client is current. Supported targets are mac|win and arm64|x64. Only stable major.minor.patch versions are published.
After installers and their immutable manifest upload successfully, the build calls POST /versions/:appRef with { version, platform, arch, manifestUrl } using the existing CLI session. Apps-api requires namespace admin permission and validates the official manifest identity and installer metadata. Older releases never supersede a higher version for the same target. PMATE_APPS_API_BASE_URL selects the publisher API; distributed clients query the production API.
A registration failure fails the release command, while preserving already uploaded installers. Once API access is repaired, retry the same POST with the manifest URL in the build report; do not rebuild different bytes under that version. Manual registration of an already published release is idempotent. Native checks have a timeout, reject mismatched targets and external download hosts, and never clear user data or restart the app.
Android release signing
pmate service deploy <ns:android-service> resolves signing credentials from that service's server-side environment (host variables and local signing environment variables are not used). Configure ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD as Service Secrets, plus ANDROID_SIGNING_CERT_SHA256 as the expected certificate fingerprint. Android SDK apksigner must be installed. The CLI signs the Gradle release APK and verifies its certificate before uploading; missing credentials, invalid signatures, or fingerprint mismatches stop publication. Keystore files use restricted temporary storage and are removed after signing. Existing apps must retain their compatible signing key. Debug builds keep their existing Gradle signing behavior.
iOS TestFlight builds
On macOS with Xcode, a signed-in Apple developer account, valid signing profiles and an existing App Store Connect app:
# First build on this machine: import the highest Build already used in Apple.
pmate service deploy td:time-duck-ios --last-build 2026101501
# Subsequent builds on this machine:
pmate service deploy td:time-duck-ios
service deploy for a service with type ios archives Release and uploads directly to App Store Connect for TestFlight. It preserves the marketing version (for example 0.1.0) and increments only the integer Build by one. It does not publish an App Store release. uploaded-processing means Apple accepted the upload; processing and TestFlight download availability require separate verification.
Register an ios service with repository and dir. --cwd overrides the repository root; --project / --workspace paths are relative to the app directory. Exactly one discovered project/workspace is required unless explicitly selected. Always provide --scheme; --team overrides the project's signing team. --json returns version, Build, archive and log paths after upload.
The first invocation for each marketing version requires --last-build <highest-used-build-for-this-version> (0 only for a new version). The next Build is one greater than the maximum of the project value, imported value and locally reserved sequence for that version. Reservations, including failed builds, are kept separately per version. To migrate published date-based numbers, first increase MARKETING_VERSION and set CURRENT_PROJECT_VERSION to 0, then import 0 for the new version to start at Build 1. Import the current Apple maximum again after uploads outside this CLI or on another machine; this command does not query Apple build history. Do not lower or delete the sequence to reuse a number. Burgess performs the Apple history check and version migration automatically.
Sequences, retained archives, derived data and logs live in ~/.pmate/ios-builds/. Project/source version files are not changed. A bundle-level exclusive lock prevents simultaneous builds on the same machine; it does not coordinate different machines. Build/upload failures keep the reserved number and logs; retry consumes the next number. After an interrupted process, remove a leftover .lock file only after confirming no build is still running. Builds require automatic signing; resolve Apple account/certificate/profile errors in Xcode. Extension or App targets with fixed plist versions must produce matching archive metadata or the upload is stopped.
pmate service deploy <ns:service> defaults to --env prod for every service. Use explicit --env preview or --env dev to override it. For example, pmate service deploy pmate:merry deploys the production site https://merry.pmate.chat.
Unified native service deployment
Web, iOS and Electron use separate services. Deploy dispatches by registered type, without --target. For iOS store scheme/team and optional project/workspace under build; existing mobile bundle identity stays unchanged. iOS and Electron deployment currently support prod only; Android supports dev/preview/prod. Override registered settings with --cwd, --scheme, --team, --project, --workspace or Electron-specific --config, --platform, --arch and --out-dir. Unsupported options fail explicitly. iOS --dry-run resolves the registered plan without running Xcode or uploading.
pmate service deploy pmate:merry
pmate service deploy pmate:merry-ios
pmate service deploy pmate:merry-electron
pmate service deploy pmate:merry-electron --local --unsigned
app build and service ios build were removed; use service deploy. Local and dry-run runs do not create successful deploy-history entries. iOS upload success is recorded as uploaded-processing, not TestFlight availability.
Android through service deploy
Android services use pmate service deploy pmate:merry-android. The primary service is pmate:merry-android. Its build.artifactServiceId is pmate:factory-android: APK paths and the legacy update manifest remain compatible with installed clients, which also restrict APK downloads to the old path. Production publishes identical latest.json to both the new service and legacy artifact identity. Package factory.pmate.app and signing identity are unchanged. Copy the existing signing secrets server-side when migrating. Service build settings accept variant (release/debug) and bump (patch/minor/major); defaults are release and patch. Production rejects debug variants. Android supports dev/preview/prod; preview does not update the production latest.json.
Use --variant, --bump, --force, --release-notes or --minimum-version-code to override release options. --dry-run prints the plan without incrementing versions, resolving signing secrets, building or uploading. Android currently does not support --local/--unsigned. service apk release was removed; service apk list remains a query.
HarmonyOS NEXT projects exist in parrot-native (apps/pmate-app-harmony and apps/time-duck-harmony). They have signed build scripts and a GitHub Actions workflow; CLI 0.42.0+ also supports a harmonyos service through service deploy. AppGallery submission is separate.
Android production releases check the existing current and compatibility update manifests before changing source versions or loading signing secrets. A candidate versionCode must exceed every published versionCode; stale checkouts are rejected and must be updated explicitly. APK package identity must match the registered service before upload.
HarmonyOS distribution
pmate service deploy pmate:merry-harmony dispatches a harmonyos service. mobile holds bundleName, repository and dir; build.script defaults to scripts/build-release.sh. The service must associate exactly one app ID. The source AppScope/app.json5 controls the published version. Only prod is supported. --dry-run inspects the plan without SDK or secrets; --local builds verified signed artifacts without upload; --out-dir overrides the local output base. Other platforms' flags are rejected.
Install the DevEco SDK, ohpm, Hvigor and Java. Configure DEVECO_SDK_HOME, OHPM_BIN, HVIGORW_BIN, optionally NODE_BIN, JAVA_BIN and HAP_SIGN_TOOL for non-default installations. Service prod Secrets are HARMONY_KEYSTORE_BASE64, HARMONY_CERTIFICATE_BASE64, HARMONY_PROFILE_BASE64, HARMONY_STORE_PASSWORD, HARMONY_KEY_ALIAS and HARMONY_KEY_PASSWORD. Local signing variables are not used. Missing tools or credentials stop the real build.
Builds run in private temporary source/SDK copies. The CLI verifies APP, HAP and embedded module signatures, signer certificate and compiled identity/version, then uploads both files and immutable release.json under harmony///// in parrot-static, followed by latest.json. Existing or older versions are rejected. Native service history records the manifest URL. Signing keys and temporary sources are cleaned; logs and verified local outputs remain under ~/.pmate/harmony-builds/ by default. Publication means OSS distribution, not AppGallery submission, approval or device installation.
HarmonyOS publication uses an ownership-checked service lock in an unversioned OSS bucket and forbid-overwrite for immutable files. It checks the latest version again under the lock. Interrupted publishers may leave a lock; confirm they have stopped before clearing it. The CLI does not automatically steal stale locks.
Bot profile nicknames
Background
Chat displays a bot profile's nickName. To change that display name without changing the bot's internal name, account, credentials, or agent configuration, update the existing profile:
pmate account bot profile update --app pmate:merry --name blackbeard --nickname "黑胡子BOT" --json
# Equivalent shortcut:
pmate account bot nickname --app pmate:merry --name blackbeard --nickname "黑胡子BOT" --json
--name is the bot's internal name, as returned by pmate account bot list --app pmate:merry --json. Both commands require an existing CLI login, use the active session, and rely on the auth API's profile update permissions. They require exactly one profile for the bot account in the specified app; missing or multiple profiles cause an error without a write. These commands never create a profile.
The CLI updates only nickName through PUT /profile, then polls the profile index for up to approximately 10 seconds and returns the verified profile. A matching nickname is a no-op. If verification times out, the update may already have been applied; inspect the profile before retrying. --base-url selects the auth API for all requests.