diff --git a/session-logs/2026-06/2026-06-22-mike-yealink-ymcs-skill.md b/session-logs/2026-06/2026-06-22-mike-yealink-ymcs-skill.md new file mode 100644 index 00000000..ee7ab0d7 --- /dev/null +++ b/session-logs/2026-06/2026-06-22-mike-yealink-ymcs-skill.md @@ -0,0 +1,125 @@ +# Session — yealink-ymcs skill built (YMCS v2 API), pairs with packetdial; vendor-stack correction + +## User +- **User:** Mike Swanson (mike) +- **Machine:** GURU-5070 +- **Role:** admin + +## Session Summary + +Continuation of the VoIP onboarding work. Kept the `vwp.91912.service` domain created last +session and fixed its `domain-type` to `Standard` (the create had stored `"no"`). Then built the +`onboard-domain` wrapper into the packetdial skill — one gated command running the GUI Add-a-Domain +flow as 3 API calls (POST /domains → addresses/validate → addresses/create), verified end-to-end +with a throwaway create→delete. Mike then clarified the vendor stack (PacketDial = ACG's VoIP-dept +brand; NetSapiens = the platform; OIT/OITVOIP = white-label wholesaler) — captured as a reference +memory + logged correction. + +Mike's main ask: add the related service — **Yealink cloud device manager** — and make sure we have +access + can use it with the PBX skill. Discovery found ACG already has a Yealink admin +(`admin@azcomputerguru.com`, vault `infrastructure/voip-phones.sops.yaml`) and that the cloud +manager is **YMCS** (`us.ymcs.yealink.com`); the wiki documents VWP's Yealink T54W fleet. Mike then +supplied **YMCS API credentials** (AccessKey ID + Secret) via screenshot, which were vaulted at +`services/yealink-ymcs.sops.yaml`. + +On Mike's go, RTFM'd the YMCS open API and built a new **`yealink-ymcs` skill**. The current v2 API +uses token auth (`POST /v2/token` Basic → bearer), not the legacy RPS HMAC signing; Yealink servers +require legacy TLS renegotiation. Harvested the full `/v2/dm/*` + `/v2/rps/*` endpoint map from the +`dszp/n8n-nodes-yealinkymcs` community node. Implemented `ymcs_client.py` (token+bearer, legacy-TLS +SSL context, paginated `_list`, vault creds) + `ymcs.py` CLI (reads + gated writes + raw). Live- +verified: token auth, sites, devices (21), accounts, rps-servers. **The ACG AccessKey is the parent +account that sees ALL client sites** (VWP, GuruHQ, Ace Pick Up Parks as children) — so one key +covers every client fleet; answers Mike's per-client-vs-one-key question. + +The pbx+phone pipeline now exists end to end: `packetdial onboard-domain` (PBX domain + SIP +accounts) → `yealink-ymcs` (`add-devices-by-mac`, `add-sipaccount` to push a NetSapiens SIP cred +onto a phone, `rps-add` for zero-touch via RPS server `WL - ACG` = `ftp://p.packetdials.net`). VWP +is the live pilot (onboarded to the PBX; 21 devices in YMCS, fleet partly pending). Everything +committed, pushed, deployed. + +## Key Decisions +- **Token auth, not legacy HMAC.** The first sources described the old RPS Management Platform HMAC + signing (X-Ca-* + Content-MD5); the n8n node revealed the current v2 API uses `POST /v2/token` + (Basic) → bearer. Built to v2 (simpler, current). +- **Legacy TLS handled in-client** via an SSL context with `OP_LEGACY_SERVER_CONNECT` (Yealink + servers require renegotiation; default Python TLS would fail). +- **One AccessKey for all clients** — verified the key is the ACG parent account; client sites are + children. No per-client API keys. +- **`add-sipaccount` instead of a sip-account list.** `/v2/dm/sipAccounts` (POST) is the CREATE + endpoint (not a list); wrapped it as the gated write since pushing a SIP cred onto a phone is the + actual PBX↔phone integration glue. No list-sip-accounts exists in v2. +- **Read-first, writes gated --confirm** (mirrors packetdial); `raw` passthrough for the rest. +- **Honest [V]/[P] status in docs** — only token/sites/devices/accounts/rps-servers lifecycle- + verified; writes + official-firmwares + `--site` filter are plumbed-but-unverified (no throwaway + device to safely test writes on). + +## Problems Encountered +- **MSYS path conversion on `raw`:** a leading `/v2/...` arg gets rewritten by Git-Bash to + `C:/Program Files/Git/v2/...`. Added in-code recovery (cut back to `/v2/`). `MSYS_NO_PATHCONV=1` + is NOT a fix — it broke the vault subprocess (creds failed). Named wrappers (hardcoded paths) + are unaffected. (Documented Windows gotcha class.) +- **`/v2/dm/sipAccounts` is a create, not a list** — my initial `sipaccounts` read wrapper hit it + and got HTTP 400 (`sipServer1 cannot be null`). Replaced with the gated `add-sipaccount`. +- **`official-firmwares`** returns a non-standard shape (likely needs a model param) — marked [P]. +- **`--site` filter on `devices`** returned all 21 (siteId in the body didn't scope) — best-effort, + marked [P]; full data still returned. +- **cp1252 + WebFetch 403** while RTFMing — used `encoding='utf-8'` for spec reads and pivoted from + the 403'd Yealink doc to the public GitHub n8n node source via the GitHub API + raw files. +- **`domain-type` stored `"no"` on packetdial create** — fixed vwp via `raw PUT` (carryover). + +## Configuration Changes +Created: +- `.claude/skills/yealink-ymcs/SKILL.md`, `scripts/ymcs.py`, `scripts/ymcs_client.py` (new skill). +- `services/yealink-ymcs.sops.yaml` (VAULT) — YMCS API AccessKey. +- `.claude/memory/reference_packetdial_oit_netsapiens.md` (+ MEMORY.md index line). +- This session log. +Modified: +- `.claude/skills/packetdial/SKILL.md`, `scripts/ns.py`, `scripts/ns_client.py` — `onboard-domain` + wrapper. +- `errorlog.md` — vendor-model correction. +Live changes: vwp `domain-type` → Standard; threw away `acg-onbtest.91912.service` (wrapper test). + +## Credentials & Secrets +- **YMCS API AccessKey** — vaulted `services/yealink-ymcs.sops.yaml`: + `credentials.access_key_id` / `credentials.access_key_secret`. Region us + (`us-api.ymcs.yealink.com`). Read with `vault.sh get-field services/yealink-ymcs.sops.yaml + credentials.access_key_id`. (Values intentionally not echoed here — they're vaulted.) +- Existing: Yealink portal admin `admin@azcomputerguru.com` → `infrastructure/voip-phones.sops.yaml`; + per-client portal logins e.g. `clients/valleywide/`. PBX reseller key `msp-tools/oitvoip.sops.yaml`. + +## Infrastructure & Servers +- YMCS API: `https://us-api.ymcs.yealink.com` (v2). Auth `POST /v2/token` Basic→bearer; legacy TLS. +- YMCS account tree (ACG parent key): `Arizona Computer Guru LLC` (id 34a602e7…) → `VWP` + (1e7578a6…), `GuruHQ` (04117c54…), `Ace Pick Up Parks` (0320c810…). +- RPS server: `WL - ACG` (id 019c908f…), url `ftp://p.packetdials.net`, authName `lrshwh`. +- Device object keys: id, mac, sn, name, modelId/modelName, siteId/siteName, programVersion, + deviceStatus, connectWay, wanIp, lanIp, lastReportTime, createTime. 21 devices total. +- PBX: `pbx.packetdial.com/ns-api/v2` (NetSapiens via OIT); domain `vwp.91912.service` live. + +## Commands & Outputs +- YMCS reads: `ymcs.py sites|devices|accounts|rps-servers|firmwares|models|alarms|oplogs|device-groups|device-configs` +- YMCS gated writes: `ymcs.py add-devices-by-mac|add-sipaccount|reboot|reset|rps-add|rps-del --body '{...}' --confirm` +- `ymcs.py raw POST /v2/dm/listDevices --body '{"skip":0,"limit":50,"autoCount":true}'` (path auto-recovers MSYS mangling) +- packetdial onboarding: `ns.py onboard-domain --body-file new-client.json --confirm` +- vwp fix: `ns.py raw PUT domains/vwp.91912.service --body '{"domain-type":"Standard"}' --confirm` + +## Pending / Incomplete Tasks +- **[P] YMCS writes unverified** — add-devices-by-mac, add-sipaccount, reboot, reset, rps add/del; + official-firmwares shape; `--site` device filter. Exercise on real devices (VWP's pending fleet) + when a task needs them. +- **Proposed (not started):** a combined `onboard-client` flow = packetdial onboard-domain + + users/devices + YMCS phone provisioning (add-by-mac + add-sipaccount/rps) in one gated run, + piloted on VWP's 11 pending phones. +- **packetdial gaps (carryover):** voicemail user-defaults (not in POST /domains), `email-send-from-address` + pending `voicemail@`/`noreply@packetdial.com` mailbox creation. +- `OIT-API.txt` plaintext in Downloads — Mike to delete. + +## Reference Information +- New skill: `.claude/skills/yealink-ymcs/`. Vendor stack memory: `reference_packetdial_oit_netsapiens`. +- Endpoint map source: `github.com/dszp/n8n-nodes-yealinkymcs` (n8n community node) + Yealink "Open + API for YMCS V4X" doc. Auth doc patterns: support.yealink.com (legacy RPS HMAC — superseded by v2 token). +- Commits this session: `onboard-domain` `d1d1302d`; vendor-stack memory `1dbefd54`; yealink-ymcs + skill `850e685d`. Vault: oitvoip key + YMCS key pushed. +- YMCS endpoints: `/v2/token`, `/v2/dm/{listSites,listDevices,listAccounts,sipAccounts,addDevicesByMac, + addDevices,delDevices,device/reboot,device/reset,listFirmwares,models,...}`, `/v2/rps/{listServers, + listDevices,addDevices,deleteDevices}`.