@camunda/app-integration-cli
v2.0.0
Published
CLI tool for building Camunda app integrations for Microsoft Teams and Slack
Readme
@camunda/app-integration-cli
CLI tool for building and deploying Camunda app integrations for chat platforms.
c8-teams scaffolds a new Microsoft Teams application project, builds the deployable package from templates, provisions it to your Microsoft 365 tenant via the M365 Agents Toolkit, and outputs the configuration snippet needed by the App Integrations backend.
c8-slack does the same for Slack: it renders a Slack app manifest, publishes it through the Slack app-configuration API, installs it into your workspace and hands you the two credentials the backend needs.
Both binaries share one project directory, one configuration section and one set of commands.
Upgrading from
@camunda/teams-app-integration-cli(v1)? See Migrating from v1. Nothing is required of you beyond installing this package — the first command you run migrates the project.
Prerequisites
- Node.js ≥ 20.0.0
- For Teams: a Microsoft 365 tenant with access for app provisioning
- For Slack: a Slack workspace you can install apps into, and a backend reachable from the internet over HTTPS (Slack calls it directly; there is no Socket Mode)
Installation
npm install -g @camunda/app-integration-cliOr use it as a project-local dependency:
npm install @camunda/app-integration-cliQuick Start
# 1. Scaffold a new project
c8-teams create my-teams-app
# 2. Install dependencies
cd my-teams-app
npm install
# 3. Build the deployable package
npm run build
# 4. Deploy to your Microsoft 365 tenant
npm run deployCommands
c8-teams create [DIRECTORY]
Scaffolds a new Camunda Teams application project via an interactive wizard.
The wizard will prompt you for:
- Project directory and package name
- App Integrations backend URL — shared by every integration, so it is only asked once per project
- Application short name and full name (displayed in Teams)
- User registration URL (optional)
- Teams app configuration — create a new one or provide existing IDs
- Entra (Azure AD) app configuration — create a new one or provide existing IDs
The result is a directory containing a package.json with a c8-appint configuration section, default app icon assets under assets/teams/, and a .gitignore.
Run in a directory that is already an app-integration project, create adds the integration to it rather than overwriting it, reusing the backend URL already on file. If that project was made by an older version of the CLI, it is brought up to date first and you are told what changed — the update is never silent.
c8-teams create
c8-teams create my-teams-appc8-slack create [DIRECTORY]
Scaffolds a new Camunda Slack application project via an interactive wizard.
The wizard prompts for the project directory, package name and App Integrations backend URL as above, then for the fields that end up in the Slack app manifest:
| Question | Default | Limit |
| ----------------- | --------------------------------------- | -------------------------------------- |
| App name | Camunda | 35 characters |
| Bot display name | the app name | 80 characters |
| Short description | Your perfect integration with Camunda | 140 characters |
| Background colour | #000000 | 3- or 6-digit hex |
| Slash command | /camunda | 32 characters, leading /, no spaces |
| Slack app | create a new one | or provide an existing app and team ID |
Accepting every default reproduces the app Camunda ships in SaaS.
Slack's four request URLs (events, slash command, interactivity, options load) are not asked for: they are all <backend URL>/api/slack/events and are derived from the backend URL you already gave. Slack only calls https URLs, so a plain-http backend is rejected.
c8-slack create
c8-slack create my-slack-appc8-slack build
Renders the Slack app manifest from the project's configuration.
Reads and validates the c8-appint.slack section, then writes artifacts/slack/manifest.yaml (paste this into the Slack UI), artifacts/slack/manifest.json (what the Slack API wants) and a copy of assets/slack/icon.png. The JSON is a re-serialisation of the YAML, so the two can never disagree.
Only the app name, bot display name, description, background colour, slash command and the derived request URL come from your configuration. Scopes, bot events, the agent-view prompts and the settings flags are fixed — they are a contract with the App Integrations backend, and changing them produces an app that installs but does not work.
After a successful build you will be prompted to deploy immediately.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
c8-slack build
c8-slack build -p ./my-slack-appc8-slack deploy
Publishes the app to Slack: creates it from the built manifest (or updates it, if this project has been deployed before), installs it into the workspace, uploads the icon, writes the Slack-issued app and workspace IDs back into package.json, and stores the credentials Slack issued.
Deploying needs an app configuration token, which is a short-lived (12 hour) personal credential. It is never written to package.json. The command looks for one in this order:
--token- the
SLACK_CONFIG_TOKENenvironment variable - signing in to Slack — the command prints a
/slackauthticket …slash command, you paste it into any Slack conversation, approve the modal and paste the code back. This needs no admin rights. slack auth token, if the Slack CLI happens to be installed- pasting a token generated at api.slack.com/apps → Your App Configuration Tokens
Option 3 requires a workspace that is eligible for Slack's next-generation platform. Free and scratch workspaces are not, and Slack only says so in the reply to the slash command — leave the code prompt empty and the command falls through to option 5.
One project publishes to exactly one workspace. If the token belongs to a different workspace than the one recorded in package.json, the command stops rather than creating a duplicate app somewhere else.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
| --token TOKEN | App configuration token (or set SLACK_CONFIG_TOKEN) |
c8-slack deploy
c8-slack deploy --token xoxe.xoxp-…
SLACK_CONFIG_TOKEN=xoxe.xoxp-… c8-slack deployNote: You must run
c8-slack buildbefore deploying.
If your workspace requires an admin to approve new apps, the app is still created and its ID and signing secret are saved — get it approved, then run c8-slack deploy again to finish the install. The credential store is merged rather than rewritten, so a deploy that fails half-way never erases a bot token an earlier one obtained.
If c8-slack deploy fails on the call that creates the app, the app may still exist in Slack. The command says so rather than retrying, because a second attempt would publish a second app whose signing secret can never be reissued — check api.slack.com/apps and, if the app is there, put its ID in c8-appint.slack.slack.appId before re-running.
c8-slack show-config
Prints the slack: block for your App Integrations backend configuration plus the two secrets to mount as environment variables, SLACK_BOT_TOKEN and SLACK_BOT_SIGNING_SECRET.
The values are read back from the encrypted store deploy wrote. If that file is gone, the block is printed with ${{ }} placeholders and a pointer at where to find the values by hand.
Secret values are only printed when the output is a terminal. When it is redirected — a CI job log, a pipe, a file — the names and the rest of the configuration are still printed, but each value is withheld; re-run the command interactively to read it.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
c8-slack show-configc8-teams build
Builds the deployable package from the project's template files and configuration.
Reads the c8-appint section from package.json, validates it against the config schema, renders the M365 Agents Toolkit manifest templates (Teams manifest, AAD manifest, agent config), copies the icons from assets/teams/, and writes everything to artifacts/teams/.
After a successful build you will be prompted to deploy immediately.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
c8-teams build
c8-teams build -p ./my-teams-appc8-teams deploy
Deploys the built package to your Microsoft 365 tenant.
Runs atk provision --env prod inside artifacts/teams/ using the M365 Agents Toolkit CLI bundled with this package (no global installation required). After provisioning, the command writes the resulting Teams and Entra IDs back into package.json and prints the backend configuration snippet.
| Option | Description |
| ----------------- | -------------------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
| --auth | Authenticate with M365 before deploying (runs atk auth login m365) |
c8-teams deploy
c8-teams deploy --auth
c8-teams deploy -p ./my-teams-appNote: You must run
c8-teams buildbefore deploying. The deploy command expectsartifacts/teams/to exist.
c8-teams show-config
Displays the YAML configuration snippet to add to your App Integrations backend, along with the client secret that must be mounted as the AAD_APP_CLIENT_SECRET environment variable.
As with c8-slack show-config, the secret value is only printed when the output is a terminal.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
c8-teams show-config
c8-teams show-config -p ./my-teams-appc8-teams migrate
Brings package.json up to the current configuration version. Every other command does this automatically on load; this command exists so you can inspect the change first or run it in CI.
A migration copies the project to .backup/<timestamp>/ before it writes anything. --dry-run writes nothing at all, backup included.
| Option | Description |
| ----------------- | -------------------------------------------------------- |
| -p, --path PATH | Root path of the project (defaults to current directory) |
| --dry-run | Print what would change without writing anything |
c8-teams migrate --dry-run
c8-teams migrateGlobal Options
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------ |
| -h, --help | Show help. Use with a command (e.g. c8-teams deploy -h) for command-specific help. |
| -v, --version | Show the CLI version number |
Project Structure
One directory can carry both integrations. Everything you edit lives under assets/, everything generated lives under artifacts/, split by integration:
my-app/
├── assets/ # committed — replace these with your own icons
│ ├── teams/
│ │ ├── color.png # App icon (colour version)
│ │ └── outline.png # App icon (outline version)
│ └── slack/
│ └── icon.png # App icon (512×512 or larger)
├── artifacts/ # generated — gitignored as a whole
│ ├── teams/ # written by `c8-teams build`
│ │ ├── env/
│ │ │ ├── .env.prod
│ │ │ └── .env.prod.user # secret from `c8-teams deploy` (encrypted)
│ │ ├── aad.manifest.json
│ │ ├── m365agents.yml
│ │ ├── manifest.json
│ │ ├── color.png
│ │ └── outline.png
│ └── slack/ # written by `c8-slack build`
│ ├── manifest.yaml # paste this into the Slack UI
│ ├── manifest.json # sent to the Slack API
│ ├── .env.user # credentials from `c8-slack deploy` (encrypted)
│ └── icon.png
├── .gitignore
└── package.json # holds the `c8-appint` configuration sectionA project with one integration has only that integration's subdirectories.
Both build commands regenerate their output from package.json and assets/, so artifacts/ is disposable — with one exception. The secrets deploy writes (artifacts/teams/env/.env.prod.user, artifacts/slack/.env.user) cannot be regenerated: Microsoft and Slack issue those values once. Delete them and you have to re-provision. They are encrypted at rest, but the key is derived from your project ID, so treat the files as secrets and keep the real values in your secret manager.
The .gitignore written by create covers node_modules, artifacts and .backup. If the directory already had a .gitignore, your rules are kept underneath the managed block and stay in force.
Scripts
create adds a script pair per integration, plus an aggregate over every integration the project has:
| Script | Runs |
| ----------------------------- | ------------------------------------- |
| build:teams, deploy:teams | Teams only |
| build:slack, deploy:slack | Slack only |
| build, deploy | every configured integration, in turn |
Adding a second integration to an existing project rewrites the aggregates, so npm run build keeps meaning "build everything" rather than quietly building only the first integration. You are told when this happens. If you have edited build or deploy yourself, they are left alone and the CLI warns instead of overwriting your version.
Configuration
All configuration lives in the c8-appint section of package.json. It is populated by the create wizard and updated automatically by deploy after provisioning.
shared holds the settings every integration needs; each integration has its own block, so one project directory can carry more than one.
{
"c8-appint": {
"version": 2,
"shared": {
"endpoints": {
"appIntegrationsBackend": "https://your-backend.example.com"
}
},
"teams": {
"app": {
"shortName": "Camunda",
"fullName": "Camunda",
"projectId": "<uuid>"
},
"endpoints": {
"userRegistration": "https://signup.example.com"
},
"teams": {
"appId": "<uuid>",
"tenantId": "<uuid>"
},
"aad": {
"clientId": "<uuid>",
"objectId": "<uuid>",
"accessAsUserPermissionId": "<uuid>",
"authorityHost": "https://login.microsoftonline.com",
"authority": "https://login.microsoftonline.com/<tenant-id>"
}
}
}
}| Field | Required | Description |
| ----------------------------------------- | -------- | ---------------------------------------------------------------- |
| version | Yes | Configuration schema version, managed by the CLI |
| shared.endpoints.appIntegrationsBackend | Yes | URL of your App Integrations backend |
| shared.projectId | No | Generated on first deploy; keys the encrypted credential store |
| teams.app.shortName | Yes | Short display name in Teams (2–30 characters) |
| teams.app.fullName | Yes | Full display name in Teams (2–30 characters) |
| teams.app.projectId | Yes | Auto-generated UUID identifying the project |
| teams.endpoints.userRegistration | No | Optional user registration URL |
| teams.teams | No | Populated after deployment with the Teams app and tenant IDs |
| teams.aad | No | Populated after deployment with the Entra (Azure AD) app details |
A Slack integration adds its own block alongside teams:
{
"c8-appint": {
"slack": {
"app": {
"name": "Camunda",
"botDisplayName": "Camunda",
"description": "Your perfect integration with Camunda",
"backgroundColor": "#000000"
},
"slack": {
"command": "/camunda",
"appId": "A01234ABCDE",
"teamId": "T01234ABCDE"
}
}
}
}| Field | Required | Description |
| --------------------------- | -------- | -------------------------------------------------------------------------------- |
| slack.app.name | Yes | App name shown in Slack (≤ 35 characters) |
| slack.app.botDisplayName | Yes | Name messages are posted under (≤ 80 characters) |
| slack.app.description | Yes | Short description (≤ 140 characters) |
| slack.app.backgroundColor | Yes | Hex colour shown behind the app icon |
| slack.slack.command | Yes | Slash command, e.g. /camunda. Must match slack.command in the backend config |
| slack.slack.appId | No | Populated once the app exists in Slack |
| slack.slack.teamId | No | Workspace the app belongs to; one deployment serves exactly one workspace |
Migrating from v1
Projects created by @camunda/[email protected] use a c8teams section (configuration version 1). You do not need to do anything: the first command you run from this package migrates the project in place and prints a summary of what moved.
Before anything is written — the rewritten package.json included — the project is copied to .backup/<timestamp>/, everything except node_modules. Nothing is deleted, so if the result is not what you expected, the original is still there. Once you are happy, delete .backup/.
The migration rewrites the whole package.json, not just the config section:
| v1 | v2 |
| ------------------------------------------ | --------------------------------------------------- |
| c8teams.app | c8-appint.teams.app |
| c8teams.endpoints.appIntegrationsBackend | c8-appint.shared.endpoints.appIntegrationsBackend |
| c8teams.endpoints.userRegistration | c8-appint.teams.endpoints.userRegistration |
| c8teams.teams / c8teams.aad | c8-appint.teams.teams / c8-appint.teams.aad |
| scripts.build = c8teams build | scripts.build = c8-teams build |
| @camunda/teams-app-integration-cli | @camunda/app-integration-cli |
It also moves the project onto the layout above:
| v1 | v2 |
| -------------- | -------------------- |
| build/ | artifacts/teams/ |
| assets/*.png | assets/teams/*.png |
The build directory is moved wholesale rather than regenerated, so env/.env.prod.user — which holds a client secret nothing can reissue — comes across with it. Files the CLI does not recognise are moved too, not dropped. A build/ that belongs to some other toolchain is left alone.
The .gitignore is replaced with the managed one and your previous rules are kept underneath it, still in force.
Keys the CLI does not recognise are carried across untouched, and running the migration twice is a no-op.
Because the dependency changes, re-run your package manager's install afterwards.
The old c8teams binary still ships, but only as a one-way on-ramp: it runs against a project still on configuration version 1 — which is exactly the case where your own scripts.build still calls it — and then errors, pointing at c8-teams. Use c8-teams from then on.
Typical Workflow
c8-teams create my-app— scaffold the project and fill in your configuration.cd my-app && npm install— install dependencies (including this CLI).npm run build— build the deployable package intoartifacts/teams/.npm run deploy(orc8-teams deploy --auth) — authenticate and provision the app to your M365 tenant.c8-teams show-config— copy the output YAML into your App Integrations backend configuration and set theAAD_APP_CLIENT_SECRETenvironment variable.- After an admin approves the app in the Teams Admin Center, it becomes available to your organization.
For Slack:
c8-slack create my-app— scaffold the project and fill in your configuration.cd my-app && npm installnpm run build— render the manifest intoartifacts/slack/.npm run deploy— sign in to Slack, publish the app, install it and collect its credentials.c8-slack show-config— copy the output into your App Integrations backend configuration, setSLACK_BOT_TOKENandSLACK_BOT_SIGNING_SECRET, and restart the backend.- Run your slash command in Slack to check. If Slack reports it cannot reach your app, open the app's Event Subscriptions page and click Verify once — the request URL can only be verified after the backend is live.
