collabSupport/.env.example
jmcqueen 351f89a9a4 Initial commit: CollabFinder Webex bot
Multi-integration Webex chat/HTTP bot that unifies phone, AV, and
network status for retail store support. Consolidates data from
Webex Calling, Meraki, Workspace ONE (MDM), Atlas AMP, RED digital
signage, and OptiSigns into rich per-store status commands.

Key surfaces:
- /phonestatus, /avstatus — per-store phone & AV device reports with
  clickable Meraki deep-links and per-port detail.
- /webexhost — check/assign Webex Meetings host licenses via the
  Service App; adaptive-card confirmation flow, HTTP-API-gated.
- /offboarduser — full Webex Admin offboarding (auth revoke, device
  wipe, license removal); adaptive-card confirmation.
- /jirapoll — on-demand trigger for the hourly Jira poller.
- /bulkavstatuscsv — bulk store CSV export with concurrency limits.

Automation:
- Hourly Jira poller (node-cron) with an X.AI (Grok) ticket classifier
  that categorizes unassigned tickets as phone/av/skip, extracts store
  numbers from free-text, and enriches Jira with the same detailed
  markdown the chat commands emit (converted to Jira ADF, preserves
  bold + Meraki links). Idempotent via a `bot-enriched` Jira label.

Architecture:
- Node.js 20+, ESM, Express 5, webex-node-bot-framework.
- Layered integrations (integrations/*), services (services/*),
  commands (commands/*), utils (utils/*).
- Shared markdown renderers (services/renderers/*) feed both chat
  handlers and the Jira poller so the two surfaces stay in sync.
- Hand-rolled markdown-to-ADF converter (utils/markdownToAdf.js) —
  no new npm dependency.
- Node built-in test runner (`node --test tests/*.test.js`), 30 tests
  covering the converter, renderers, and poller ADF assembly.

Docker + docker-compose deployment. Config via .env
(see .env.example for the full option surface).
2026-07-01 16:55:03 -04:00

222 lines
10 KiB
Text

# =============================================================================
# CollabFinder / CollabSupport Environment Variables
# Copy this file to .env and fill in your values.
# =============================================================================
# -----------------------------------------------------------------------------
# Server
# -----------------------------------------------------------------------------
SERVER_PORT=1800
# Logging level: info (default - clean), debug (verbose, includes per-fetch details)
LOG_LEVEL=info
# Verbose Webex framework debug logs. Default off; auto-enabled when LOG_LEVEL=debug.
# WEBEX_FRAMEWORK_DEBUG=false
# -----------------------------------------------------------------------------
# HTTP API authentication
# -----------------------------------------------------------------------------
# Shared secret required to call destructive /:command HTTP endpoints such as
# /offboarduser, /provision-dect, /provision-vc, /vcmonitor, /bulkavstatuscsv,
# /bulkavswitchcsv, /devicesbymodel. Without it, those endpoints fail-closed
# with HTTP 503 — set this to any high-entropy string (e.g. `openssl rand -hex 32`).
# Callers send the token as `Authorization: Bearer <token>` or `X-API-Token: <token>`.
HTTP_API_TOKEN=
# Optional. When set to "true" / "1" / "yes", the same token is also required
# for read-only endpoints (/avstatus, /phonestatus, /av/devices/build/…,
# /phone/devices/build/…, /api/av/*, etc.). Default = false (those endpoints
# stay open so the bundled dashboards keep working without auth headers).
HTTP_API_REQUIRE_AUTH=false
# -----------------------------------------------------------------------------
# Webex Bot (required)
# -----------------------------------------------------------------------------
# Bot token from developer.webex.com. The framework connects to Webex over
# websockets using this token, so no public ingress / webhook URL is needed.
WEBEX_BOT_TOKEN=your-bot-token-here
# Service App credentials (used by WebexServiceAppAuth for backend Webex API
# calls — people lookup, room operations, etc.). Separate from the bot token.
#
# Required scopes (set when creating the service app at developer.webex.com):
# - spark-admin:people_read (people lookup)
# - identity:tokens_read (offboarduser: list a user's authorizations)
# - identity:tokens_write (offboarduser: revoke a user's authorizations)
# The authorizing admin must also hold Full / User / Device Admin role for the
# token-management calls to succeed.
WEBEX_CLIENT_ID=your-service-app-client-id
WEBEX_CLIENT_SECRET=your-service-app-client-secret
# Path to the rotating service app tokens file (must be writable).
# IMPORTANT: Use a *relative* path (e.g. ./config/...). The same .env works for both:
# - Local runs (resolved against your project root cwd)
# - Docker (resolved against /app inside container; see docker-compose volume mount)
# Do NOT use an absolute host path here — it will break inside the container.
WEBEX_TOKENS_PATH=./config/webex-service-tokens.json
# Optional override for the Webex API base URL (default https://webexapis.com/v1).
# WEBEX_BASE_URL=https://webexapis.com/v1
# -----------------------------------------------------------------------------
# /webexhost — Webex Meetings host license helper
# -----------------------------------------------------------------------------
# Site to evaluate host status against. Default: aeo2go.webex.com.
# WEBEX_HOST_SITE_URL=aeo2go.webex.com
# License ID auto-assigned by `/webexhost <email>` when the user is missing a
# host license on the site. Discover the right id by running `/webexhost list`
# (lists every meeting license on the site with id + remaining seats).
# Until this is set, `/webexhost <email>` will still report status, but the
# confirm-assign step refuses with a clear message pointing at /webexhost list.
WEBEX_HOST_LICENSE_ID=
# -----------------------------------------------------------------------------
# Jira (required for /jira* commands)
# Use JIRA_CLOUD_ID for service accounts / new Atlassian API gateway endpoints:
# JIRA_CLOUD_ID=<uuid-from-atlassian>
# (constructs https://api.atlassian.com/ex/jira/<id>/rest/api/3 ...)
# Otherwise fall back to classic site base:
# JIRA_BASE_URL=https://your-org.atlassian.net
# -----------------------------------------------------------------------------
JIRA_CLOUD_ID=
JIRA_BASE_URL=https://your-org.atlassian.net
JIRA_EMAIL=your-email@company.com
JIRA_API_TOKEN=your-jira-api-token
JIRA_MAX_RESULTS=30
# -----------------------------------------------------------------------------
# Jira Poller (hourly ticket enrichment)
# -----------------------------------------------------------------------------
# Cron poller that scans unassigned tickets in the AV / Comm Services /
# Mobility queue every hour, posts a phone or AV status snapshot as a
# Jira comment on each store-scoped ticket, labels the ticket
# `bot-enriched` so it's not re-processed, and posts a summary of newly
# enriched tickets to a Webex space.
#
# Required scopes on JIRA_API_TOKEN: read + write on issues in the
# target projects (comment + edit-labels). The token owner needs "Add
# Comments" and "Edit Issues" permission — a plain read-only integration
# token WILL NOT work.
#
# JIRA_POLLER_ROOM_ID
# Webex space roomId to post the per-poll "N new tickets" summary to.
# Poller stays DISABLED (cron never registered) if unset — safe default
# for dev instances that share the same Jira credentials.
#
# JIRA_POLLER_PRIME_ON_START
# One-shot backlog-prime toggle. Set to `true` for a SINGLE deploy to
# bulk-label every ticket currently matching the poller's JQL as
# `bot-enriched` WITHOUT enriching them or posting a summary. Prevents
# day-one spam from a queue that already has dozens of open tickets.
# Flip back to `false` (or remove) before the next restart or the
# prime pass runs again.
#
# JIRA_STORE_FIELD_ID
# Optional. Numeric custom-field id for the `Store Number` field
# (e.g. `customfield_10042`). If unset, the poller discovers it at
# first use via GET /rest/api/3/field. Set explicitly to skip
# discovery (saves one API call at startup) or when the display
# name resolves ambiguously in your Jira schema.
#
# Note: on tenants where Store Number is an Atlassian Assets object
# reference (not a plain string), the field value the poller reads
# will be an opaque object like {"objectId":"81255"}. The AI
# classifier handles this by extracting the store number from the
# ticket summary / description text instead ("Store 3860 - ..."), so
# the field being unreadable is not fatal.
#
# JIRA_POLLER_MODEL
# Optional model override for the AI ticket classifier. Defaults to
# XAI_MODEL if unset. Classification is a small, deterministic
# structured task (~50-token JSON output per ticket) that doesn't
# need the reasoning depth of the summary model — a cheaper/faster
# model (e.g. `grok-3-mini`) saves noticeable money at scale without
# hurting classification accuracy on the phone/av/skip taxonomy.
# -----------------------------------------------------------------------------
JIRA_POLLER_ROOM_ID=
JIRA_POLLER_PRIME_ON_START=false
JIRA_STORE_FIELD_ID=
JIRA_POLLER_MODEL=
# -----------------------------------------------------------------------------
# xAI / Grok (used for ticket and work order summarization)
# -----------------------------------------------------------------------------
XAI_URL=https://api.x.ai/v1/chat/completions
XAI_API_KEY=your-xai-api-key
XAI_MODEL=grok-2-latest
# -----------------------------------------------------------------------------
# Meraki
# -----------------------------------------------------------------------------
MERAKI_API_KEY=your-meraki-api-key
MERAKI_ORG_ID=your-org-id
# -----------------------------------------------------------------------------
# RED (Digital Signage)
# Comma-separated list of company IDs (one per company tenant).
# -----------------------------------------------------------------------------
RED_BASE_URL=https://api.red.com
RED_CLIENT_ID=your-red-client-id
RED_API_KEY=your-red-api-key
RED_COMPANY_IDS=company-id-1,company-id-2,company-id-3
# -----------------------------------------------------------------------------
# OptiSigns
# -----------------------------------------------------------------------------
OPTISIGN_API_KEY=your-optisigns-api-key
# -----------------------------------------------------------------------------
# DigiCert (for VC provisioning)
# -----------------------------------------------------------------------------
DIGICERT_API_KEY=your-digicert-key
DIGICERT_BASE_URL=https://one.digicert.com
DIGICERT_PROFILE_ID=your-profile-id
DIGICERT_SEAT_EMAIL=your-email@company.com
# -----------------------------------------------------------------------------
# MDM / Workspace ONE (two instances)
# -----------------------------------------------------------------------------
# Standard / Store MDM
WS1_CLIENT_ID=...
WS1_CLIENT_SECRET=...
WS1_TENANT_CODE=...
# CORP MDM (used for offboarding / enterprise wipes)
CORP_WS1_API_BASE=https://...
CORP_WS1_CLIENT_ID=...
CORP_WS1_CLIENT_SECRET=...
CORP_WS1_TENANT_CODE=...
# -----------------------------------------------------------------------------
# Atlas
# -----------------------------------------------------------------------------
ATLAS_AUTH_KEY=your-atlas-key
# -----------------------------------------------------------------------------
# ServiceChannel
# OAuth password grant against ServiceChannel's identity endpoint.
# -----------------------------------------------------------------------------
SC_BASE_URL=https://api.servicechannel.com/v3
SC_OAUTH_URL=https://login.servicechannel.com/oauth/token
SC_CLIENT_ID=your-sc-client-id
SC_CLIENT_SECRET=your-sc-client-secret
SC_USERNAME=your-sc-username@company.com
SC_PASSWORD=your-sc-password
# -----------------------------------------------------------------------------
# VC Provisioning "backdoor" account
# Used by vcProvisionService for local-device authentication during certificate
# enrollment. DIGICERT_SEAT_EMAIL above is unrelated.
# -----------------------------------------------------------------------------
BACKDOOR_USERNAME=monitor
BACKDOOR_PASSWORD=...
# -----------------------------------------------------------------------------
# Notes
# -----------------------------------------------------------------------------
# - config/config.json has been fully removed. All configuration is via env vars.
# - The rotating Webex service token lives in config/webex-service-tokens.json
# and is mounted separately when running in Docker.
# - Add any new integration keys above following the same pattern.