@pitcher/cli-scripts
v2.16.0
Published
The Pitcher CLI for various tasks in its ecosystem.
Readme
@pitcher/cli-scripts
Pitcher CLI Tools provide a suite of command-line utilities to streamline your workflow. For the best experience, we recommend installing the tools globally using
npm install -g @pitcher/cli-scripts.
Help
p --help
# or for specific command help:
p command --helpMCP server
Run p mcp to start a stdio MCP server that exposes this CLI's commands as tools to an MCP-capable host (Claude Desktop, Claude Code, etc). It wraps 121 tools, one per CLI run* function: every command above (including dsr create + dsr bind-landing and the full ld surface) except the five local scaffold subcommands (which just print/write a starter file and make no request). dev-sync is exposed as a one-shot sync only; the continuous watch mode is not available as a tool.
p mcp
# or without a global install
npx @pitcher/cli-scripts mcpEnvironment variables
Auth and org scope are resolved once at boot from environment variables. They are never tool inputs, so the model cannot change org or credentials mid-session.
NG_API_KEY(required, secret): the API key the server authenticates as.NG_ORG(defaultdev): organization subdomain, or setNG_URLdirectly for a full origin.NG_ENV(prodortest): controls the destructive-hint guard on write tools (see below). Defaults to a guess based onNG_ORGwhen unset.
One config entry = one org, because the API key is org-specific. To work against multiple orgs, add multiple entries (e.g. pitcher-dev, pitcher-prod) with different env blocks; do not try to pass org per tool call.
Host config example (Claude Desktop / .mcp.json)
"pitcher": {
"command": "npx",
"args": ["-y", "@pitcher/cli-scripts", "mcp"],
"env": { "NG_API_KEY": "<key>", "NG_ORG": "dev" }
}Tool annotations
- Read commands are marked
readOnlyHint.dev-synccounts as a write (it mutates remote dev-override state). publish-appis always markeddestructiveHint(it creates a live, org-visible release). Other write tools getdestructiveHintonly when the resolved environment is production (NG_ENV=prod, orNG_ORGother thandevwithNG_ENVunset). This steers hosts toward requiring extra confirmation before mutating a production org.
Desktop Extension (.mcpb)
For one-click install in Claude Desktop (name + icon, an install-time API-key/org form, no hand-edited config, no Node prerequisite), build a .mcpb bundle:
yarn mcpb:pack # → pitcher.mcpb (validated + packed)Install by double-clicking pitcher.mcpb (or Settings → Extensions → Install Extension). Claude Desktop prompts for the API key (stored in the OS keychain) and organization; both are editable afterward, so switching orgs needs no reinstall.
The bundle reuses the same server as p mcp but registers a content-tool subset (118) — it omits the developer-only publish-app and dev-sync/dev-sync-revert. The organization field accepts a subdomain (acme), a bare hostname (acme.my.pitcher.com), or a full https:// URL. Build details (esbuild single-file, version injection) live in mcpb/ and CLAUDE.md.
This section is a reference only. For app-development conventions (modules, settings, mount points, SFDC), see the pitcher-apps skill, not this README.
To run the publish-app command:
# Basic usage
p publish-app <folder> --url https://pitcher.my.pitcher.com --api-key myapikey
# Publish to current folder and default org (dev)
p publish-app --api-key myapikey
# Publish to a specific organization subdomain (e.g., pitcher.my.pitcher.com)
p publish-app --api-key myapikey --org pitcher
# Dry run with version increment
p publish-app --api-key myapikey --org pitcher --dry-run --increment-version
# Publish and install to specific instances
p publish-app --api-key myapikey --org pitcher --increment-version --install-to 01JB8KQRM3XYZ2P4N5W7T9VHFA,01JB8KR0P6QS4T8X1M9Y3C7ZBE
# Publish and install to all instances with a specific environment name (case-insensitive)
p publish-app --api-key myapikey --org pitcher --increment-version --install-to-env-name Demo
# Combine both: install to specific instances AND all instances in an environment
p publish-app --api-key myapikey --org pitcher --increment-version --install-to 01JB8KS5F2WH9V6D8R3N7JXMQK --install-to-env-name Demo
# Alternative: Set API key via environment variable
export NG_API_KEY=myapikey
p publish-app --org pitcher --increment-version --install-to-env-name ProductionOptions
--api-key <apiKey>: API Key for authentication (can also useNG_API_KEYenv variable)--org <organization>: Organization subdomain (e.g., "pitcher" for pitcher.my.pitcher.com, default: "dev")--increment-version: Increment version in app.json with timestamp--dry-run: Perform a dry run without publishing--publish-url <url>: Explicit publish URL--install-to <instanceIds>: Comma-separated list of instance IDs to install the app to after publishing--install-to-env-name <envName>: Environment name to filter instances and install the app to after publishing (case-insensitive)
To run the dev-sync command:
Sync a marketplace app directly to S3 for live development testing without publishing. This is useful for testing app changes in a pre-production environment.
# One-time sync from dist folder
p dev-sync sync --file-id <pitcher_file_id> --api-key myapikey
# Sync to a specific organization (default: dev)
p dev-sync sync --file-id <pitcher_file_id> --org pitcher
# Watch mode - auto-sync on file changes (reverts on Ctrl+C)
p dev-sync watch --file-id <pitcher_file_id> --api-key myapikey
# Watch with custom debounce (default: 2500ms)
p dev-sync watch --file-id <pitcher_file_id> --debounce 5000
# Manually revert dev override to restore original version
p dev-sync revert --file-id <pitcher_file_id> --api-key myapikey
# Alternative: Set API key via environment variable
export NG_API_KEY=myapikey
p dev-sync watch --file-id <pitcher_file_id> --org pitcherOptions
--file-id <id>: (Required) File ID of the installed app (pitcher_file.id) - copy this from the installed apps view--api-key <apiKey>: API Key for authentication (can also useNG_API_KEYenv variable)--org <organization>: Organization subdomain (default: "dev")--debounce <ms>: Debounce time in milliseconds for watch mode (default: 2500)
Notes
- Run from your app's project root (with
dist/folder) or directly from thedist/folder - The
watchcommand automatically reverts changes on graceful shutdown (Ctrl+C) - Apps with dev override show a badge in the installed apps view
To run the files command:
Manage Pitcher content files — list within an instance, fetch by id, create (upload or link), or update.
# List files in an instance (paginated)
p files list --instance-id 01... --api-key myapikey
# Filter by tags or specific IDs
p files list --instance-id 01... --tags brochure,demo --api-key myapikey
# Get a single file by id
p files get 01FILEID... --api-key myapikey
# Create a file from a local upload
p files create --instance-id 01... --name "Q3 Deck" --file ./deck.pdf --api-key myapikey
# Create a link/WEB file from a source URL
p files create --instance-id 01... --name "Product Page" --source-url https://example.com --api-key myapikey
# Update a file's tags or metadata (metadata/app-metadata are deep-merged, not replaced)
p files update 01FILEID... --tags updated,reviewed --api-key myapikeyTo run the folders command:
Manage Pitcher content folders — list, fetch (or the instance root), create, and update smart-folder rules.
# List folders in an instance (paginated)
p folders list --instance-id 01... --api-key myapikey
# Get a folder by id
p folders get 01FOLDERID... --api-key myapikey
# Get the instance root folder
p folders get --root --instance-id 01... --api-key myapikey
# Create a folder under a parent
p folders create --name "Marketing" --parent-folder-id 01PARENTID... --api-key myapikey
# Update a folder's name or access type
p folders update 01FOLDERID... --name "Sales Enablement" --access-type restricted --api-key myapikeyTo run the search command:
# Ranked cross-type search (files, canvases, users, apps) within an instance
p search --instance-id 01... --query "quarterly deck" --api-key myapikeyTo run the instances command:
Discover Pitcher instances — resolve names to instance IDs before running other commands.
# List instances (optionally filter by region/storage-region/environment)
p instances list --api-key myapikey
p instances list --environment Demo --api-key myapikey
# Get a single instance by id
p instances info 01INSTANCEID... --api-key myapikeyTo run the canvases command:
Read Pitcher canvases — presentations, sections, and blocks.
# List canvases in an instance (paginated)
p canvases list --instance-id 01... --api-key myapikey
# Only templates or only sections
p canvases list --instance-id 01... --is-template --api-key myapikey
p canvases list --instance-id 01... --is-section --api-key myapikey
# Only blocks (blocks are hidden unless --is-block is passed)
p canvases list --instance-id 01... --is-block --api-key myapikey
# Get a single canvas by id
p canvases get 01CANVASID... --api-key myapikeyTo run the shared-links command:
Create and read share links for canvases or files (returns the short_url).
# Create a share link for a canvas
p shared-links create --canvas-id 01CANVASID... --api-key myapikey
# Create a share link for a file
p shared-links create --file-id 01FILEID... --api-key myapikey
# Fetch the existing share link for a canvas
p shared-links get --canvas-id 01CANVASID... --api-key myapikeyTo run the tags command:
List tags in an instance (the server requires a search term of at least 3 characters).
# List tags matching a search term
p tags list --instance-id 01... --search "demo" --api-key myapikey
# Filter by type
p tags list --instance-id 01... --search "demo" --type canvases --api-key myapikeyTo run the email-template command:
Create/list/get HTML email templates for the emails app. create, list, and get require --instance-id (scaffold does not).
# Create a template from an HTML file (inline CSS)
p email-template create --instance-id 01... --name "Welcome" --body-file ./welcome.html --api-key myapikey
# ...or pass the HTML inline instead of a file (use one, not both)
p email-template create --instance-id 01... --name "Welcome" --body-html "<p>Hi {{recipient.firstName}}</p>" --api-key myapikey
# List templates (optionally filter by name/status/category/target)
p email-template list --instance-id 01... --api-key myapikey
# Get a template by id
p email-template get --instance-id 01... --id 01TEMPLATEID... --api-key myapikey
# Generate a starter HTML file with tokens in context
p email-template scaffold --out ./welcome.html --api-key myapikeyRun p email-template create --help for the full token/marker reference.
scaffold returns the starter HTML in the envelope data (never printed raw to stdout); pass --out <path> to write it to a file instead.
To run the dsr-landing command:
Create/list DSR (Digital Sales Room) landing-page templates, and set one as the instance-scope default. create, list, and set-default require --instance-id (scaffold does not).
# Create a landing template from an HTML file (must contain the email-gate marker)
p dsr-landing create --instance-id 01... --name "Default" --html-file ./landing.html --api-key myapikey
# ...or pass the HTML (and optionally CSS) inline instead of a file (use one, not both)
p dsr-landing create --instance-id 01... --name "Default" --html "<div data-pitcher-email-gate></div>" --api-key myapikey
# Create and set as default in one call
p dsr-landing create --instance-id 01... --name "Default" --html-file ./landing.html --default --api-key myapikey
# Set an existing template as the default
p dsr-landing set-default --instance-id 01... --id 01TEMPLATEID... --api-key myapikey
# List templates in an instance
p dsr-landing list --instance-id 01... --api-key myapikey
# Generate a starter HTML file with the required marker and tokens in context
p dsr-landing scaffold --out ./landing.html --api-key myapikeyThe landing HTML (whether via --html-file or --html) must contain the data-pitcher-email-gate marker verbatim (the platform injects the email-gate form there) — create fails client-side before any request if it's missing. Provide exactly one of --html / --html-file (and at most one of --css / --css-file).
Run p dsr-landing create --help for the full token/marker reference (editable-region attributes, recipient/customer/org/rep tokens).
scaffold returns the starter HTML in the envelope data; pass --out <path> to write it to a file instead.
To run the announcements command:
Create/list announcement records for the wa-announcements app. create and list require --instance-id (scaffold does not).
# Create from a JSON data file
p announcements create --instance-id 01... --from-file ./announcement.json --api-key myapikey
# Create from convenience flags only
p announcements create --instance-id 01... --title "New feature" --body "<p>Check it out</p>" --api-key myapikey
# List announcements
p announcements list --instance-id 01... --api-key myapikey
# Generate a fully populated starter data.json
p announcements scaffold --out ./announcement.json --api-key myapikeycreate requires either --from-file or both --title and --body. --title/--body/--priority override matching keys from --from-file when both are given. Run p announcements create --help for the full data field catalog (media, links, scheduling, target groups, popup overrides) — note start_at/expires_at are naive local dates (YYYY-MM-DD[THH:mm]), not ISO/UTC.
scaffold returns the starter JSON in the envelope data; pass --out <path> to write it to a file instead.
To run the themes command:
Read canvas/branding themes (colors via settings, fonts via typography, plus assets/logo) to reference when authoring email/DSR/canvas content. Read-only.
# List all themes in the org (returns full theme objects)
p themes list --api-key myapikey
# Narrow to one instance; add per-theme canvas usage and canvas ids
p themes list --instance-id 01... --with-usage --with-canvas-ids --api-key myapikey
# Get one theme by id
p themes get --id 01THEMEID... --api-key myapikeylist is server-capped (newest-first) with no --limit/--page; when the cap is hit the envelope carries a truncation warning. --instance-id narrows to themes bound to that instance and excludes org-level (default) themes, so omit it to see everything usable in the org.
To run the org command:
Read the organization the API key belongs to.
p org get --api-key myapikeyTo run the analytics command:
Read rollup analytics totals (org/instance), KPI metrics, and content/user engagement analytics. org/instance are Django rollups; kpi and the 7 verbs below hit the clickhouse-api service (clickhouse.pitcher.com) via the same env resolution as everything else — --org/--url pick the environment (routed via x-original-domain). Access follows the key user's real role: popularity, users, files, and canvases need an org-admin key; engagement, viewers, and sessions work with a member key. kpi and sessions return the raw payload; the other row-heavy verbs return a shaped envelope (summary, rows, total_count, returned, has_more, and sometimes truncated/warnings) capped by --limit (default 20, max 100). Date-range verbs default to a 30-day window when --start-date/--end-date are omitted. viewers and sessions require --canvas-id; sessions also requires --user.
# Organization-wide rollups (org-admin key required)
p analytics org --api-key myapikey
# Instance rollups (instance-admin or org-admin key required)
p analytics instance --instance-id 01... --api-key myapikey
# KPI metrics from the clickhouse analytics service (PIT-7647).
# Access follows the key user's real role on the instance; --org/--url pick the
# env exactly as for other commands (they route via x-original-domain).
# Activity metrics need a date window; inventory counts (total-members, ...) do not.
p analytics kpi --instance-id 01... --type total-members --api-key myapikey
p analytics kpi --instance-id 01... --type total-active-users \
--start-date 2026-01-01 --end-date 2026-01-31 --api-key myapikey
# DSR/content engagement rows (member key)
p analytics engagement --instance-id 01... --engagement-level high --limit 50 --api-key myapikey
# Most-viewed canvases / view-analysis (org-admin key)
p analytics popularity --instance-id 01... --sort-by total_views --sort-order DESC --api-key myapikey
# User roster (org-admin key)
p analytics users --instance-id 01... --api-key myapikey
# Active-user statistics instead of the roster (org-admin key)
p analytics users --instance-id 01... --stats --start-date 2026-01-01 --end-date 2026-01-31 --api-key myapikey
# File usage analytics (org-admin key)
p analytics files --instance-id 01... --file-type pdf --sort-by views --sort-order desc --api-key myapikey
# Per-section canvas engagement list (org-admin key)
p analytics canvases --instance-id 01... --group-ids 01GROUPID... --api-key myapikey
# DSR canvas viewers — who viewed a given canvas (member key)
p analytics viewers --instance-id 01... --canvas-id 01CANVASID... --viewer-type external --api-key myapikey
# Per-viewer session event timeline for a DSR canvas (member key)
p analytics sessions --instance-id 01... --canvas-id 01CANVASID... --user [email protected] --api-key myapikeyMCP: all ten verbs are exposed as read-only pitcher_analytics_* tools, alongside a pitcher://analytics/catalog resource (which verb answers which question — returns, time model, key tier) and four query prompts — top_content, content_engagement_summary, who_viewed_canvas, adoption_snapshot.
To run the users command:
Read users in the org (read-only). Org scope comes from the API key via the proxy x-org-id header — there is no --org-id flag.
# List users in the org (paginated)
p users list --api-key myapikey
# Filter by instance membership, roles, active status, or content-distribution group
p users list --instance-id 01... --api-key myapikey
p users list --instance-id 01... --instance-role editor --api-key myapikey
p users list --role admin --api-key myapikey
p users list --is-active --api-key myapikey
p users list --cdg 01GROUPID... --api-key myapikey
# Get a single user by id (user ids are integers, not ULIDs)
p users get --id 2446 --api-key myapikeyList filters: --instance-id, --instance-role (server-side requires --instance-id), --role, --is-active, --cdg, plus --page/--page-size (max 500). ID shapes: user ids are integers; instance and group ids are ULIDs.
To run the invitations command:
Read pending user invitations (read-only).
# List pending invitations (paginated)
p invitations list --api-key myapikey
# Get a single invitation by id (invitation ids are 'uinv_...' strings, not ULIDs)
p invitations get --id uinv_0yKQ41LLqOI2kn4E --api-key myapikeyTo run the memberships command:
Read instance memberships, add members, and change their role.
# List memberships (filter by instance or user; user ids are integers)
p memberships list --instance-id 01... --api-key myapikey
p memberships list --user-id 2446 --api-key myapikey
# Add a user to an instance (always created as 'member')
p memberships add --instance-id 01... --user-id 2446 --api-key myapikey
# Change a membership's role (org-admin key required; membership ids are integers)
p memberships set-role --membership-id 3537 --role editor --api-key myapikeyadd always creates the membership as member; use set-role to change it. set-role needs an org-admin key (an instance-admin key can add but not set-role). --membership-id is the integer membership row id from memberships list (not a user id). --role is the short role name (e.g. editor) discovered via p roles list — the prefixed key (instance:editor) is rejected. --scope defaults to instance; --scope organization targets the organization membership row instead — note its row id is NOT discoverable via this CLI today (memberships list returns instance memberships only), so use it only when you already have the org-membership id from another source.
To run the groups command:
Read/create content-distribution groups and manage their members.
# List groups (filter by instance)
p groups list --instance-id 01... --api-key myapikey
# Get a group by id
p groups get --id 01GROUPID... --api-key myapikey
# Create a group (name min 3 chars)
p groups create --instance-id 01... --name "West Region" --api-key myapikey
# Add a user to a group (user ids are integers)
p groups add-user --group-id 01GROUPID... --user-id 2446 --api-key myapikey
# List a group's members
p groups list-users --group-id 01GROUPID... --api-key myapikeyadd-user is rejected on auto-login-managed groups (their membership is derived, not manually editable).
To run the roles command:
Read the org's role set (6 system roles + any custom roles) — the discovery aid for p memberships set-role.
# List the org's roles
p roles list --api-key myapikey
# Filter by scope
p roles list --scope instance --api-key myapikeylist returns a { items: [...] } object; each item's role_key is prefixed (org:admin, instance:editor). Pass the short name (e.g. editor) to set-role, not the prefixed key. Like set-role, roles list currently needs an org-admin key: while the granular-permissions rollout is off (the production default), the endpoint 403s non-org-admin callers.
To run the metadata-template command:
Read and additively build an instance's metadata-field schema. list and upsert require --instance-id (get only needs --id; scaffold makes no request).
# Read the schema for an instance (0 or 1 template — one per instance)
p metadata-template list --instance-id 01... --api-key myapikey
# Get one template by id (ids are ULIDs)
p metadata-template get --id 01TEMPLATEID... --api-key myapikey
# Print a starter schema to fill in (optionally to a file)
p metadata-template scaffold --out ./schema.json
# Additively upsert the schema (merges by section label)
p metadata-template upsert --instance-id 01... --sections-file ./schema.json --api-key myapikeyupsert merges the incoming sections by section label — a section whose label matches an existing one is replaced, other sections are preserved. It cannot remove a section or field (that's a UI operation). options for select/multi-select fields are { label, value } objects (bare strings are rejected). upsert requires an instance- or org-admin key; a non-admin member key is rejected (400 → VALIDATION).
To run the dsr command:
Create and share a Digital Sales Room (a canvas share link) and get a link back in one command. Three subcommands: create (build + share, with optional landing-page binding), bind-landing (bind/unbind a landing template on an existing share), and scaffold (print a starter {source,security,landing} spec for --config-file).
# simplest — email-gated link (server default auth_strategy=any_email)
p dsr create --instance-id 01... --canvas-id 01... --api-key myapikey
# open link
p dsr create --instance-id 01... --canvas-id 01... --auth-strategy none
# ShareBox + security + branded landing
p dsr create --instance-id 01... --sharebox-file-ids 01A,01B --account-id 01... \
--auth-strategy specified_domains --allowed-domains acme.com --valid-until 2026-09-01T00:00:00Z \
--landing-template-id 01... --landing-context '{"rep":"Sam"}'
# bind/unbind a landing template on an existing share
p dsr bind-landing --id 01... --landing-template-id 01...
p dsr bind-landing --id 01... --landing-template-id none
# scaffold a spec for --config-file
p dsr scaffold --out share.jsonCreating needs a share-capable key; binding a landing page currently needs an editor key (PIT-7525 will open it to reps for their own DSRs) — a denied bind returns the link with a warning. Discover landing template IDs via p dsr-landing list.
To run the ld command:
Author L&D (MicroLearn) content — courses, modules, certificates, and assessments — against the standalone L&D backend. The general form is p ld <resource> <verb>, plus the top-level p ld create-canvas recipe (create → set-canvas → publish in one step). --instance-id is required on every subcommand.
Unlike the other commands, L&D targets a separate backend (not <org>.my.pitcher.com). The base URL is resolved automatically from the instance's ld-app config; override with --ld-url or the NG_LD_API_URL env var (e.g. for a region whose ld-app config is unavailable). Auth is the same org x-api-key. The surface is authoring-only — enrollment, attempts, progress, groups, and analytics are not exposed (the org key cannot reach them). set-canvas likewise has no --require-recompletion: the backend rejects that flag for API-key callers (the bulk rep-completion reset is human-admin-only), so it could only ever 403.
# headline: author a designed canvas course in one step (create → set-canvas → publish)
p ld create-canvas --instance-id 01... --title "Onboarding 101" --canvas '{"content":{"data":[]}}' --publish
# courses
p ld course list --instance-id 01... --status draft
p ld course create --instance-id 01... --title "Intro" --description "A short intro course." --level Beginner --duration 10 --sections-count 1
p ld course set-canvas --instance-id 01... --id 01... --canvas-file ./canvas.json
p ld course publish --instance-id 01... --id 01...
p ld course add-to-modules --instance-id 01... --id 01... --module-ids m1,m2
# modules / certificates / assessments
p ld module create --instance-id 01... --title "Basics" --subtitle "Getting started" --description "Foundational module."
p ld module add-courses --instance-id 01... --id m1 --course-ids c1,c2
p ld cert create --instance-id 01... --title "Certified Rep"
p ld assessment create --instance-id 01... --title "Quiz 1"
p ld assessment add-question --instance-id 01... --id a1 --question '{"type":"true_false","questionText":"...","config":{},"points":1}'
# override the L&D base URL (else resolved from ld-app config)
NG_LD_API_URL=https://<lambda-host>/dev/api p ld course list --instance-id 01...MCP: the same surface is exposed as pitcher_ld_* tools, plus a create_ld_canvas_course recipe prompt and a pitcher://design/ld-canvas-course resource. NG_LD_API_URL is the only way to override the L&D base URL under MCP (no per-call override, by design).
