# wxcc-ai Node/Express service that wires Webex Contact Center summaries, Jira tickets (core + JSM Store Support), and xAI Grok summarization into a single set of internal HTTP endpoints. ## Setup ```bash npm install cp .env.example .env # fill in JIRA_*, XAI_*, and optionally the Assets values npm run dev ``` The server listens on `PORT` (default `1866`). ## Environment variables See [`.env.example`](./.env.example) for the full list. The important ones: | Var | Purpose | | --- | --- | | `JIRA_CLOUD_ID` | If set, requests use `https://api.atlassian.com/ex/jira/{cloudId}`. | | `JIRA_BASE_URL` | Fallback for legacy site URLs (`https://your-site.atlassian.net`). | | `JIRA_EMAIL`, `JIRA_API_TOKEN` | Basic auth to Jira. `JIRA_AUTH_TYPE=bearer` switches to bearer. | | `JIRA_SERVICE_DESK_ID` | Numeric JSM service desk id for Store Support. | | `JIRA_ASSETS_WORKSPACE_ID` | Assets workspace id (auto-discovered if omitted). | | `JIRA_ASSETS_STORE_SCHEMA_ID`, `_OBJECT_TYPE_ID` | Locate the Store schema/type. | | `JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE[_ID]` | Attribute name (or id) holding the store number in Assets. | | `JIRA_STORE_CUSTOM_FIELD_ID` | Custom field on the JSM request that holds the Store Assets reference. | | `XAI_API_KEY`, `XAI_BASE_URL` | Grok credentials for summary generation. | ## Endpoints Base path: `/api/wxccai`. ### Read - `GET /getticket?jiraKey=CS-1234` — Grok-summarized single ticket. - `GET /open-tickets-by-reporter?email=user@example.com` — Grok-summarized list of open tickets a person reported. - `GET /ticket/:key/status` — raw status fields (no Grok). - `GET /ticket/:key/transitions` — available workflow transitions. ### Write - `PATCH /ticket/:key` — body: `{ summary?, description?, priority?, labels?, assigneeAccountId?, additional? }` - `POST /ticket/:key/comment` — body: `{ text, internal? }` - `POST /ticket/:key/close` — body: `{ transitionName?, resolution?, comment?, internal? }` (auto-picks the first "done" transition when `transitionName` omitted). ### Store Support (JSM requests) - `GET /ssRequestTypes` — supported `subType` values + Assets config summary. - `POST /createSSRequest` — body includes `subType`, `summary`, `storeNumber` (auto-resolved via Assets), `onBehalfOf`, `description`, `additional`. ### Webex webhook - `POST /issueTranscript/:jiraKey` — attaches audio + JSON transcript + human-readable transcript, then posts a restricted-visibility summary comment. ### Admin - `GET /admin/storesCache/status` — snapshot of the local Assets store cache: `{ storeCount, lastSyncAt, ageSeconds, syncing, assetsSyncConfigured, ... }`. Safe for health checks. - `POST /admin/storesCache/refresh` — force an immediate resync via the personal PAT (see below). Takes a few seconds and returns the new status. ### Debug (non-production only) - `GET /debug/assetsProbe?storeNumber=305` — runs several AQL variants against Jira Assets and returns visible schemas + object-type detail + a computed diagnosis. Returns 404 when `NODE_ENV=production`. ## Stores cache (Assets workaround) `createSSRequest` needs to translate a store number into an Assets object id. The shared service account is silently filtered out of the Store object type, so the app maintains a local `storeNumber → objectId` cache that's populated from a **personal Atlassian PAT** (a real human account with the right Assets role). The service account is still used for everything else (creating tickets, comments, attachments). Both storage patterns end up in the same place — `process.env.ASSETS_SYNC_TOKEN` — so the runtime code path is identical. Pick whichever fits the host. ### Setup A — production / Linux host (`.env`) Put the values directly in `.env` (which is gitignored) and lock the file down: ```bash cat >> .env <<'EOF' ASSETS_SYNC_EMAIL=you@ae.com ASSETS_SYNC_TOKEN= EOF chmod 600 .env # only the bot user can read it ``` Then start normally: ```bash npm start ``` Rotate the token in Atlassian → Account → Security → API tokens whenever the trust boundary on the host changes (new operator, offboarding, suspected leak). The app reloads it on the next process start. ### Setup B — local dev on macOS (Keychain) If you're running the app on a Mac and would rather not keep the PAT in `.env`, use the wrapper script — it pulls the token from Keychain into `ASSETS_SYNC_TOKEN` before exec'ing the process: ```bash security add-generic-password \ -s jira-assets-sync \ -a you@ae.com \ -w '' \ -U echo 'ASSETS_SYNC_EMAIL=you@ae.com' >> .env # email in .env, token stays in Keychain ./bin/load-assets-sync-secret.sh npm start ``` If `ASSETS_SYNC_TOKEN` is already in the process env (Setup A above), the wrapper is a no-op and skips the Keychain lookup. ### Runtime behavior On boot the app loads the on-disk cache at `data/stores.json`, kicks off a background refresh if the snapshot is missing or older than `STORES_CACHE_STALE_AFTER_HOURS`, and schedules a periodic full resync every `STORES_CACHE_REFRESH_HOURS`. `resolveStoreAssetReference` then serves lookups from memory (sub-ms) with a live PAT lookup as fallback for brand-new stores. Force a refresh at any time: ```bash curl -X POST http://localhost:1866/api/wxccai/admin/storesCache/refresh ``` ## Jira Assets gotcha `createSSRequest` resolves a store number to an Assets object reference (`customfield_10261`). Two independent permission layers must both grant access, or every AQL query silently returns `total: 0`: 1. **OAuth scopes** on the API token: `read:cmdb-schema:jira`, `read:cmdb-type:jira`, `read:cmdb-object:jira`, `read:cmdb-attribute:jira` (and the `write:` equivalents for updates). 2. **Object Schema role membership** in Jira Assets itself — the underlying user needs to be added to a role on the Store schema in Jira → Assets → Object schemas → Configure → Roles. If AQL keeps returning `total: 0` with HTTP 200, run the probe endpoint above; the `diagnosis` field will tell you exactly which layer is missing. ## Docker ```bash npm run docker:build npm run docker:run ``` The image runs as a non-root user and exposes `1866`. `HEALTHCHECK` pings `GET /health`. ## Repo hygiene - Secrets live only in `.env`, which is `.gitignore`d. - The two `discover-ss-*.js` helper scripts read from `.env` — never hardcode credentials in them. - The debug logger no longer echoes the outbound `Authorization` header. Rotate any token that appears in older `logs/*.log` files.