packetdial: build out + document the skill against the live NetSapiens v2 API

Key is now provisioned + live-verified, so grounded the skill in the real spec (RTFM):
- Mapped the OpenAPI surface (v44.4.10, 239 paths / 354 ops) — capability map added to
  SKILL.md (what the platform exposes vs what's wrapped vs raw-only).
- Added 6 live-verified read wrappers (ns.py + ns_client.py): callqueues, timeframes,
  sites, contacts, autoattendants, billing (domain limits/usage).
- Replaced the stale "not yet provisioned" credentials section with the live status
  (vaulted nsr_ reseller key, key-id nsr_hSGUB5Wo, scope Reseller 91912.service, RW) +
  the pbx.packetdial.com vs api.ucaasnetwork.com hostname note + override.
- api.md history updated. Writes remain gated behind --confirm; everything unwrapped
  reachable via `raw`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 07:36:12 -07:00
parent 567986fa49
commit d75c367bf7
4 changed files with 98 additions and 27 deletions

View File

@@ -97,6 +97,12 @@ def main(argv=None) -> int:
sp = sub.add_parser("phones", help="phones (devices) in a domain"); sp.add_argument("domain")
sp = sub.add_parser("dids", help="phone numbers in a domain"); sp.add_argument("domain")
sp = sub.add_parser("devices", help="devices for a user"); sp.add_argument("domain"); sp.add_argument("user")
sp = sub.add_parser("callqueues", help="ACD call queues in a domain"); sp.add_argument("domain")
sp = sub.add_parser("timeframes", help="time-based routing schedules in a domain"); sp.add_argument("domain")
sp = sub.add_parser("sites", help="multi-site definitions in a domain"); sp.add_argument("domain")
sp = sub.add_parser("contacts", help="shared/domain contacts"); sp.add_argument("domain")
sp = sub.add_parser("autoattendants", help="auto-attendants (IVR) in a domain"); sp.add_argument("domain")
sp = sub.add_parser("billing", help="domain limits + current usage counts"); sp.add_argument("domain")
sub.add_parser("resellers", help="list resellers")
sp = sub.add_parser("cdrs", help="call detail records")
sp.add_argument("--domain"); sp.add_argument("--start"); sp.add_argument("--end")
@@ -138,6 +144,18 @@ def main(argv=None) -> int:
_emit(client.phonenumbers(args.domain))
elif args.cmd == "devices":
_emit(client.devices(args.domain, args.user))
elif args.cmd == "callqueues":
_emit(client.callqueues(args.domain))
elif args.cmd == "timeframes":
_emit(client.timeframes(args.domain))
elif args.cmd == "sites":
_emit(client.sites(args.domain))
elif args.cmd == "contacts":
_emit(client.contacts(args.domain))
elif args.cmd == "autoattendants":
_emit(client.autoattendants(args.domain))
elif args.cmd == "billing":
_emit(client.billing(args.domain))
elif args.cmd == "resellers":
_emit(client.resellers())
elif args.cmd == "cdrs":

View File

@@ -351,6 +351,31 @@ class NetSapiensClient:
def subscriptions(self) -> Any:
return self.request("GET", "subscriptions")
# --- per-domain feature resources (live-verified shapes, 2026-06-22) ---
def callqueues(self, domain: str) -> Any:
"""ACD call queues in a domain (agents, dispatch type, live queued count)."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/callqueues")
def timeframes(self, domain: str) -> Any:
"""Time-based routing schedules (business hours / holidays) for a domain."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/timeframes")
def sites(self, domain: str) -> Any:
"""Multi-site definitions within a domain."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/sites")
def contacts(self, domain: str) -> Any:
"""Shared/domain contacts (address book)."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/contacts")
def autoattendants(self, domain: str) -> Any:
"""Auto-attendants (IVR menus) in a domain."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/autoattendants")
def billing(self, domain: str) -> Any:
"""Domain billing/limits snapshot: max + current counts (users, queues, AAs, calls)."""
return self.request("GET", f"domains/{urllib.parse.quote(domain)}/billing")
# ======================================================================
# WRITE METHODS (gated — the CLI requires --confirm before calling these)
# ======================================================================