c8ctl (pronounced: "cocktail") — a minimal-dependency CLI for Camunda 8 operations built on top of @camunda8/orchestration-cluster-api.
- Minimal Dependencies: Only one runtime dependency (
@camunda8/orchestration-cluster-api) - Multi-Tenant Support: Full support for multi-tenancy across all operations
- Profile Management: Store and manage multiple cluster configurations
- Camunda Modeler Integration: Automatically import and use profiles from Camunda Modeler
- Plugin System: Extend c8ctl with custom commands via npm packages
- Building Block Deployment: Automatic prioritization of
*_bb-*folders during deployment, marked with 🧱 in results - Process Application Support: Resources in folders with
.process-applicationfile marked with 📦 in results - Enhanced Deployment Results: Table view showing file paths, visual indicators, resource details, and versions
- Watch Mode: Monitors a folder for changes to
*.{bpmn,dmn,form}and auto-redeploys .c8ignoreSupport: Filter deploy/watch file scanning with.gitignore-style patterns;node_modules/,target/,.git/ignored by default- Open Applications: Open Camunda web applications (Operate, Tasklist, Modeler, Optimize) in the browser directly from the CLI
- Search: Powerful search across process definitions, process instances, user tasks, incidents, jobs, and variables with filter, wildcard, and case-insensitive support
- Flexible Output: Switch between human-readable text and JSON output modes
Full transparency:
this cli is also a pilot-coding experiment, practicing Agentic Engineering.
Guided by humans, the codebase is (mostly) built by your friendly neighborhood LLM, fully dogfooding the Human-in-the-Loop pattern.
- Node.js >= 22.18.0 (for native TypeScript support)
npm install @camunda8/cli -gAfter installation, the CLI is available as c8ctl (or its alias c8).
Note: The c8 alias provides typing ergonomics for common keyboard layouts - the c key (left index finger) followed by 8 (right middle finger) makes for a comfortable typing experience on both QWERTY and QWERTZ keyboards.
# Show general help
c8ctl help
# Show detailed help for specific commands with all flags
c8ctl help list # Shows all list resources and their flags
c8ctl help get # Shows all get resources and their flags
c8ctl help create # Shows all create resources and their flags
c8ctl help complete # Shows all complete resources and their flags
c8ctl help await # Shows await command with all flags
c8ctl help search # Shows all search resources and their flags
c8ctl help deploy # Shows deploy command with all flags
c8ctl help run # Shows run command with all flags
c8ctl help watch # Shows watch command with all flags
c8ctl help open # Shows open command with all apps
c8ctl help cancel # Shows cancel command with all flags
c8ctl help resolve # Shows resolve command with all flags
c8ctl help fail # Shows fail command with all flags
c8ctl help activate # Shows activate command with all flags
c8ctl help publish # Shows publish command with all flags
c8ctl help correlate # Shows correlate command with all flags
c8ctl help profiles # Shows profile management help
c8ctl help plugin # Shows plugin management help
# Show version
c8ctl --version# List and get resources (use aliases pi, pd, ut, inc for convenience)
c8ctl list pi # List process instances
c8ctl list pd # List process definitions
c8ctl get pi 123456 # Get process instance by key
c8ctl get pi 123456 --variables # Get process instance with variables
c8ctl get pd 123456 --xml # Get process definition as XML
# Create process instance
c8ctl create pi --id=myProcess
c8ctl create process-instance --id=myProcess
# Create process instance and wait for completion
c8ctl create pi --id=myProcess --awaitCompletion
# Create process instance with custom timeout (30 seconds)
c8ctl create pi --id=myProcess --awaitCompletion --requestTimeout=30000
# Await process instance completion (alias for create with --awaitCompletion)
c8ctl await pi --id=myProcess
c8ctl await process-instance --id=myProcess
# Await with custom timeout
c8ctl await pi --id=myProcess --requestTimeout=60000
# Cancel process instance
c8ctl cancel pi 123456
# Get forms
c8ctl get form 123456 # Get form (searches both user task and process definition)
c8ctl get form 123456 --ut # Get form for user task only
c8ctl get form 123456 --pd # Get start form for process definition only
# Search resources with filters
c8ctl search pi --state=ACTIVE # Search active process instances
c8ctl search pi --id=myProcess --version=2 # Search process instances by ID and version
c8ctl search pd --id=myProcess # Search process definitions by ID
c8ctl search pd --version=2 # Search process definitions by version
c8ctl search pd --name='*order*' # Wildcard search (* = any chars, ? = single char)
c8ctl search pd --id='process-v?' # Single-char wildcard (matches process-v1, process-v2, …)
c8ctl search pd --iname='*ORDER*' # Case-insensitive search (--i prefix)
c8ctl search ut --iassignee=John # Case-insensitive assignee search
c8ctl search ut --assignee=john # Search user tasks by assignee
c8ctl search inc --state=ACTIVE # Search active incidents
c8ctl search jobs --type='*service*' # Search jobs with type containing "service"
c8ctl search variables --name=myVar # Search variables by name
c8ctl search variables --fullValue # Search with full (non-truncated) values
# Deploy and run
c8ctl deploy ./my-process.bpmn # Deploy a single file
c8ctl deploy # Deploy current directory
c8ctl run ./my-process.bpmn # Deploy and start process
c8ctl watch # Watch for changes and auto-deploy
c8ctl watch --force # Keep watching after all deployment errors
# Open Camunda web applications
c8ctl open operate # Open Camunda Operate in browser
c8ctl open tasklist # Open Camunda Tasklist in browser
c8ctl open modeler # Open Camunda Web Modeler in browser
c8ctl open optimize # Open Camunda Optimize in browser
c8ctl open operate --profile=prod # Open Operate with a specific profileWhen scanning directories for deployment artifacts, c8ctl automatically ignores:
node_modules/target/.git/
Create a .c8ignore file in your project root to add custom patterns (.gitignore syntax):
# Ignore build output
dist/
build/
# Ignore draft processes
**/draft-*.bpmn
# But keep this specific one
!draft-approved.bpmn.c8ignore rules apply to both deploy (directory scan) and watch (file monitoring).
For comprehensive examples of all commands and their flags, see EXAMPLES.md.
c8ctl supports shell completion for bash, zsh, and fish. To enable completion:
# Generate and source completion script
c8ctl completion bash > ~/.c8ctl-completion.bash
echo 'source ~/.c8ctl-completion.bash' >> ~/.bashrc
source ~/.bashrcOr for immediate use in the current session:
source <(c8ctl completion bash)# Generate and source completion script
c8ctl completion zsh > ~/.c8ctl-completion.zsh
echo 'source ~/.c8ctl-completion.zsh' >> ~/.zshrc
source ~/.zshrcOr for immediate use in the current session:
source <(c8ctl completion zsh)# Generate and install completion script
c8ctl completion fish > ~/.config/fish/completions/c8ctl.fishFish will automatically load the completion on next shell start.
Credentials are resolved in the following order:
--profileflag (one-off override)- Active profile from session state (⚠ warns if
CAMUNDA_*env vars are also present) - Environment variables (
CAMUNDA_*) - Default
localprofile (http://localhost:8080/v2)
Note: Credential configuration via environment variables follows the same conventions as the @camunda8/orchestration-cluster-api module.
# Using environment variables
export CAMUNDA_BASE_URL=https://camunda.example.com
export CAMUNDA_CLIENT_ID=your-client-id
export CAMUNDA_CLIENT_SECRET=your-client-secret
c8ctl list process-instances
# Create a profile from a .env file
c8ctl add profile staging --from-file .env.staging
# Create a profile from current environment variables
source .env.prod
c8ctl add profile prod --from-env
# Clear the active session profile (so env vars take effect)
c8ctl use profile --none
# Using profile override
c8ctl list process-instances --profile prodTenants are resolved in the following order:
- Active tenant from session state
- Default tenant from active profile
CAMUNDA_DEFAULT_TENANT_IDenvironment variable<default>tenant
# Set active tenant for the session
c8ctl use tenant my-tenant-id
# Now all commands use this tenant
c8ctl list process-instancesc8ctl supports two types of profiles:
- c8ctl profiles: Managed directly by c8ctl
- Camunda Modeler profiles: Automatically imported from Camunda Modeler (with
modeler:prefix)
For profile-related commands and flags, run:
c8ctl help profiles# Add a c8ctl profile
c8 add profile prod --baseUrl=https://camunda.example.com --clientId=xxx --clientSecret=yyy
# List all profiles (includes both c8ctl and modeler profiles)
c8 list profiles
# Set active profile (works with both types)
c8 use profile prod
c8 use profile "modeler:Local Dev"
# Remove c8ctl profile (modeler profiles are read-only)
c8 remove profile prodc8ctl automatically reads profiles from Camunda Modeler's profiles.json file. These profiles are:
- Read-only: Cannot be modified or deleted via c8ctl
- Prefixed: Always displayed with
modeler:prefix (e.g.,modeler:Local Dev) - Dynamic: Loaded fresh on each command execution (no caching)
- Platform-specific locations:
- Linux:
~/.config/camunda-modeler/profiles.json - macOS:
~/Library/Application Support/camunda-modeler/profiles.json - Windows:
%APPDATA%\camunda-modeler\profiles.json
- Linux:
Using modeler profiles:
# List includes modeler profiles with 'modeler:' prefix
c8 list profiles
# Use a modeler profile by name
c8 use profile modeler:Local Dev
# Use a modeler profile by cluster ID
c8 use profile modeler:abc123-def456
# One-off command with modeler profile
c8 list pi --profile=modeler:Cloud ClusterURL Construction:
- Self-managed (localhost): Appends
/v2to the URL (e.g.,http://localhost:8080/v2) - Cloud: Uses the cluster URL as-is (e.g.,
https://abc123.region.zeebe.camunda.io) - Any port: Supports any port number in the URL
# Show current output mode
c8ctl output
# Switch to JSON output
c8ctl output json
# Switch back to text output
c8ctl output textEnable debug logging to see detailed information about plugin loading and other internal operations:
# Enable debug mode with environment variable
DEBUG=1 c8 <command>
# Or use C8CTL_DEBUG
C8CTL_DEBUG=true c8 <command>
# Example: See plugin loading details
DEBUG=1 c8 list pluginsDebug output is written to stderr with timestamps and won't interfere with normal command output.
c8ctl supports a global plugin system that allows extending the CLI with custom commands via npm packages. Plugins are installed globally to a user-specific directory and tracked in a registry file.
Plugin Storage Locations:
The plugin system uses OS-specific directories:
| OS | Plugins Directory | Registry File |
|---|---|---|
| Linux | ~/.config/c8ctl/plugins/node_modules |
~/.config/c8ctl/plugins.json |
| macOS | ~/Library/Application Support/c8ctl/plugins/node_modules |
~/Library/Application Support/c8ctl/plugins.json |
| Windows | %APPDATA%\c8ctl\plugins\node_modules |
%APPDATA%\c8ctl\plugins.json |
Note: You can override the data directory with the
C8CTL_DATA_DIRenvironment variable.
# Create a new plugin from template
c8ctl init plugin my-plugin
# Load a plugin from npm registry
c8ctl load plugin <package-name>
# Load a plugin from a URL (including file URLs)
c8ctl load plugin --from <url>
c8ctl load plugin --from file:///path/to/plugin
c8ctl load plugin --from https://github.com/user/repo
c8ctl load plugin --from git://github.com/user/repo.git
# Upgrade a plugin to latest or specific version
c8ctl upgrade plugin <package-name>
c8ctl upgrade plugin <package-name> 1.2.3
# Downgrade a plugin to a specific version
c8ctl downgrade plugin <package-name> 1.0.0
# Unload a plugin
c8ctl unload plugin <package-name>
# List installed plugins (shows version and sync status)
c8ctl list plugins
# Synchronize plugins from registry
# - First tries npm rebuild for installed plugins
# - Falls back to fresh npm install if rebuild fails
c8ctl sync plugins
# View help including plugin commands
c8ctl helpGlobal Plugin System:
- Plugins are installed to a global directory (OS-specific, see table above)
- Plugin registry file (
plugins.json) tracks all installed plugins - No local
package.jsonis required in your working directory - Plugins are available globally from any directory
- The registry serves as the source of truth for installed plugins
- Default plugins are bundled with c8ctl and loaded automatically
- Plugin commands cannot override built-in commands - built-in commands always take precedence
c8ctl list pluginsshows plugin versions and sync status:✓ Installed- Plugin is in registry and installed⚠ Not installed- Plugin is in registry but not in global directory (runsync)⚠ Not in registry- Plugin is installed but not tracked in registry
c8ctl sync pluginssynchronizes plugins from the registry, rebuilding or reinstalling as neededc8ctl upgrade plugin <name> [version]respects the plugin source from the registry:- without
version: reinstalls the registered source as-is - npm package source with
version: installs<name>@<version> - URL/git source with
version: installs<source>#<version> - file source (
file://) withversion: version upgrade is not supported; useload plugin --fromwith the desired local plugin checkout
- without
c8ctl downgrade plugin <name> <version>respects the plugin source from the registry:- npm package source: installs
<name>@<version> - URL/git source: installs
<source>#<version> - file source (
file://): version downgrade is not supported; useload plugin --fromwith the desired local plugin checkout
- npm package source: installs
Plugin Development:
- Use
c8ctl init plugin <name>to scaffold a new plugin with TypeScript template - Convention over configuration: the directory is always prefixed with
c8ctl-plugin-, and the plugin is registered by the suffix after the prefix (e.g.,c8ctl init plugin c8ctl-plugin-foocreates directoryc8ctl-plugin-fooand registers plugin namefoo) - Generated scaffold includes all necessary files, build configuration, and an
AGENTS.mdguide for autonomous plugin implementation - Plugins have access to the c8ctl runtime via
globalThis.c8ctl - Plugins can create SDK clients via
globalThis.c8ctl.createClient(profile?, sdkConfig?) - Plugins can resolve tenant IDs via
globalThis.c8ctl.resolveTenantId(profile?) - Plugins can access c8ctl output-aware logging via
globalThis.c8ctl.getLogger() - See the bundled
hello-worldplugin indefault-plugins/for a complete example
Plugin Requirements:
- Plugin packages must be regular Node.js modules
- They must include a
c8ctl-plugin.jsorc8ctl-plugin.tsfile in the root directory - The plugin file must export a
commandsobject - Optionally export a
metadataobject to provide help text - Plugins are installed globally and work from any directory
- The runtime object
c8ctlprovides environment information to plugins - The runtime object
c8ctlexposescreateClient(profile?, sdkConfig?)for creating Camunda SDK clients from plugins - The runtime object
c8ctlexposesresolveTenantId(profile?)using the same fallback logic as built-in commands - The runtime object
c8ctlexposesgetLogger()returning the c8ctl logger instance (respects current output mode) - Important:
c8ctl-plugin.jsmust be JavaScript. Node.js doesn't support type stripping innode_modules. If writing in TypeScript, transpile to JS before publishing.
TypeScript Plugin Autocomplete:
import type { C8ctlPluginRuntime } from '@camunda8/cli/runtime';
const c8ctl = globalThis.c8ctl as C8ctlPluginRuntime;
const tenantId = c8ctl.resolveTenantId();
const logger = c8ctl.getLogger();
logger.info(`Tenant: ${tenantId}`);Example Plugin Structure:
// c8ctl-plugin.ts
export const metadata = {
name: 'my-plugin',
description: 'My custom c8ctl plugin',
commands: {
analyze: {
description: 'Analyze BPMN processes'
}
}
};
export const commands = {
analyze: async (args: string[]) => {
console.log('Analyzing...', args);
}
};When plugins are loaded, their commands automatically appear in c8ctl help output. See PLUGIN-HELP.md for detailed documentation on plugin help integration.
pi= process-instance(s)pd= process-definition(s)ut= user-task(s)inc= incident(s)msg= message
c8ctl ships two flags designed specifically for AI agents and programmatic consumers.
They appear in their own clearly labelled section in c8ctl help.
For a full machine-readable reference, see
CONTEXT.md.
Filters output to only the specified field names. Applies to all list, search,
and get commands. Field matching is case-insensitive.
# Only return Key and State columns — reduces context window size
c8ctl list pi --fields Key,State
c8ctl search pd --fields Key,processDefinitionId,name
# Works in both text and JSON modes
c8ctl output json
c8ctl list pi --fields Key,State,processDefinitionId | jq .Previews the API request that would be sent without executing it.
Works on all commands: queries (list, search, get) and mutations
(create, cancel, deploy, complete, fail, activate, resolve,
publish, correlate).
Emits a JSON object to stdout and exits 0:
{
"dryRun": true,
"command": "create process-instance",
"method": "POST",
"url": "http://localhost:8080/v2/process-instances",
"body": { "processDefinitionId": "my-process", "tenantId": "<default>" }
}Recommended agent workflow for mutations:
- Run with
--dry-runand show the user the would-be API call - Wait for user confirmation
- Re-run without
--dry-runto execute
# Preview before creating
c8ctl create pi --id=my-process --dry-run
# Preview a deployment
c8ctl deploy ./my-process.bpmn --dry-run
# Preview cancelling a process instance
c8ctl cancel pi 2251799813685249 --dry-run
# Debug a search query — see the filter body that would be sent
c8ctl search pi --state ACTIVE --between 2024-01-01..2024-12-31 --dry-run
# Inspect a list operation
c8ctl list pd --dry-run
# Preview a get request
c8ctl get pi 12345 --dry-runIn JSON output mode, c8ctl help emits structured JSON containing the full
command tree, flags (with types), and agent flags:
c8ctl output json
c8ctl help # → JSON with commands[], globalFlags[], agentFlags[], resourceAliases
c8ctl help list # → JSON for specific command- Logger (
src/logger.ts): Handles output in text or JSON mode - Config (
src/config.ts): Manages profiles, session state, and credential resolution - Client (
src/client.ts): Factory for creating Camunda 8 SDK clients - Commands (
src/commands/): Domain-specific command handlers
c8ctl <verb> <resource> [arguments] [flags]Verbs:
list- List resourcessearch- Search resources with filtersget- Get resource by keycreate- Create resourcecancel- Cancel resourcecomplete- Complete resourcefail- Fail a jobactivate- Activate jobsresolve- Resolve incidentpublish- Publish messagecorrelate- Correlate messagedeploy- Deploy BPMN/DMN/formsrun- Deploy and start processwatch(alias:w) - Watch for changes and auto-deployadd- Add a profileremove(alias:rm) - Remove a profileload- Load a pluginunload- Unload a pluginsync- Synchronize pluginsuse- Set active profile or tenantoutput- Show or set output formatcompletion- Generate shell completion scriptfeedback- Open the feedback page to report issues or request features
Resources: process-instance (pi), process-definition (pd), user-task (ut), incident (inc), job, jobs, variables (vars), message (msg), topology, profile, tenant, plugin
Tip: Run c8ctl help <command> to see detailed help for specific commands with all available flags.
npm testnpm run test:unitIntegration tests require a running Camunda 8 instance at http://localhost:8080.
- Start a local Camunda 8 instance (e.g., using
c8ctl cluster start) - Run:
npm run test:integration
- Native TypeScript: Runs directly with Node.js 22.18+ (no compilation needed)
c8ctl/
├── src/
│ ├── index.ts # CLI entry point
│ ├── logger.ts # Output handling
│ ├── config.ts # Configuration management
│ ├── client.ts # SDK client factory
│ └── commands/ # Command handlers
│ └── ...
├── tests/
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── fixtures/ # Test fixtures
├── package.json
├── tsconfig.json
└── README.md# If installed globally
c8ctl <command>
# Or using the alias
c8 <command>
# For local development with Node.js 22.18+ (native TypeScript)
node src/index.ts <command>
# Testing with npm link (requires build first)
npm run build
npm link
c8ctl <command>Note: The build step is only required for publishing or using npm link. Development uses native TypeScript execution via node src/index.ts.
- Create command handler in
src/commands/ - Wire into
src/index.tscommand routing - Add tests in
tests/unit/andtests/integration/ - Update help text in
src/commands/help.ts - Document in
EXAMPLES.md
CAMUNDA_BASE_URL: Cluster base URLCAMUNDA_CLIENT_ID: OAuth client IDCAMUNDA_CLIENT_SECRET: OAuth client secretCAMUNDA_TOKEN_AUDIENCE: OAuth token audienceCAMUNDA_OAUTH_URL: OAuth token endpointCAMUNDA_DEFAULT_TENANT_ID: Default tenant ID
Configuration is stored in platform-specific user data directories:
- Linux:
~/.config/c8ctl/ - macOS:
~/Library/Application Support/c8ctl/ - Windows:
%APPDATA%\c8ctl\
Files:
profiles.json: Saved cluster configurationssession.json: Active profile, tenant, and output modeplugins.json: Plugin registry tracking installed plugins
c8ctl automatically reads profiles from Camunda Modeler (if installed):
- Linux:
~/.config/camunda-modeler/profiles.json - macOS:
~/Library/Application Support/camunda-modeler/profiles.json - Windows:
%APPDATA%\camunda-modeler\profiles.json
Modeler profiles are:
- Read-only in c8ctl (managed via Camunda Modeler)
- Automatically loaded on each command execution
- Prefixed with
modeler:when used in c8ctl - Support both cloud and self-managed clusters
Apache 2.0 - see LICENSE.md
See COMMIT-MESSAGE-GUIDELINE.md for commit message conventions.