Files
claudetools/.claude/skills/packetdial/SKILL.md
Mike Swanson e6056433a2 harness: fix py-vs-python3 doc gap — add py.sh resolver, repoint skill/command docs
The skill/command DOCS instructed Claude to run a bare `py ...`, which is the
Windows py-launcher — absent on Linux/macOS (exit 127, hit on GURU-KALI). A blind
py->python3 swap is wrong too: python3 is a broken MS Store shim on some Windows
boxes where `py` is the correct launcher.

Fix mirrors the resolution the .sh skill scripts already do:
- New .claude/scripts/py.sh: picks the interpreter that actually RUNS —
  identity.json python.command first, then py -> python3 -> python, each
  validated with `-c 'import sys'` so the MS Store stub is skipped. exec's it.
- Repointed all DOC invocations (10 files, ~70 sites) from `py ...` to
  `bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ...` (incl. the `py -c` and
  `py -` heredoc forms in checkpoint.md / mailbox.md).
- Left the .sh skill scripts untouched — they already resolve py/python/python3.
- errorlog.md: marked the GURU-KALI entry RESOLVED.

Depends on CLAUDETOOLS_ROOT (seeded by ensure-settings-env.py); py.sh also
self-resolves the repo root via git/cwd as a fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:48:54 -07:00

5.5 KiB

name, description
name description
packetdial Manage the ACG PacketDial/OITVOIP hosted VoIP via the NetSapiens API (pbx.packetdial.com). List/inspect domains, users, devices, DIDs, resellers; pull CDRs; provision domains/users/SIP/numbers (writes gated --confirm; read-only default). Triggers: packetdial, oitvoip, netsapiens, voip domain/user/extension, provision phone, add did, CDR. Live production PBX.

PacketDial / NetSapiens (OITVOIP) Skill

Standalone CLI client for the NetSapiens SNAPsolution API v2 that backs ACG's hosted-VoIP offering through OITVOIP / PacketDial. Read-only by default; every write (create / update / delete) is gated behind --confirm.

The two hostnames (important)

Host What it is API?
voip.packetdial.com Customer-facing white-label portal / UC & fax dashboard (e.g. Cascades fax account 28598). Login-gated UI. No
pbx.packetdial.com Reseller PBX platform — NetSapiens v44.4. Yes — this skill targets it

Credentials — ONE-TIME SETUP (not yet provisioned)

As of this skill's creation no API key exists yet — the vault entry msp-tools/oitvoip.sops.yaml is empty/absent, so every command will fail with a clear "No credentials found" error until you do this once:

  1. Log into pbx.packetdial.com -> Admin > API Keys and create a reseller-scoped key (prefix nsr_). If self-service key creation is not available, reply to Darwin Escaro (OITVOIP) for reseller OAuth client credentials.
  2. Store it in the SOPS vault. Preferred (static bearer key):
    # msp-tools/oitvoip.sops.yaml
    credentials:
      api_key: nsr_xxxxxxxxxxxxxxxx
    
    Or, for OAuth2 password-grant credentials:
    credentials:
      client_id:     <client id>
      client_secret: <client secret>
      username:      <portal user@domain>
      password:      <portal password>
    
  3. That's it — the client auto-detects which shape is present.

The client never hardcodes secrets. Resolution order: PACKETDIAL_API_KEY env -> PACKETDIAL_CLIENT_ID+friends env -> vault credentials.api_key -> vault OAuth fields. Env overrides exist for quick testing without touching the vault.

Running the CLI

This machine's Python launcher is py (per identity.json); python / python3 also work. Run from the scripts dir so the two modules resolve.

cd C:/claudetools/.claude/skills/packetdial/scripts

bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py status                 # API version + authenticated key identity
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py domains                # list all domains
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py domain <domain>        # one domain's config
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py users <domain>         # users / extensions in a domain
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py user <domain> <user>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py phones <domain>        # SIP devices registered in a domain
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py dids <domain>          # phone numbers (DIDs) on a domain
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py devices <domain> <user>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py cdrs --domain <domain> --start 2026-06-01 --end 2026-06-02
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py resellers

Writes (gated)

Every mutating command prints a [DRY RUN] line and exits non-zero unless you pass --confirm. Bodies are raw JSON matching the NetSapiens v2 schema.

bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-domain --body '{"domain":"acme","description":"Acme Inc"}' --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-user acme --body '{"user":"101","name-first-name":"Jane"}' --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-phone acme --body '{...}' --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-did acme --body '{"phonenumber":"15205551234"}' --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py update-user acme 101 --body '{"name-last-name":"Doe"}' --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py delete-user acme 101 --confirm

Raw escape hatch (any of the 239 v2 paths)

The named commands cover the common surface; for anything else, hit the path directly. Non-GET methods still require --confirm.

bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw GET  domains/acme/users/101/answerrules
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw POST domains/acme/users --body '{...}' --confirm

Standard provisioning flow (new customer)

  1. create-domain -> dial plan auto-generates
  2. create-user per extension
  3. create-phone per SIP device (MAC-provisioned)
  4. create-did to attach DIDs and route them to users
  5. Log the work back to the Syncro ticket

Notes

  • This is the LIVE production reseller PBX. A bad create-domain or delete-user affects real customers — confirm the target domain first with a read command before any write.
  • CDR queries can be large; always pass --start/--end and a --limit.
  • Reference detail (auth shapes, full endpoint inventory) lives in references/api.md.