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).
222 lines
10 KiB
Text
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.
|