Claude-ops ops-package

Ship parcels via any configured carrier — MyParcel, Sendcloud, DHL Parcel NL, PostNL, DPD, UPS, FedEx. Auto-selects the first carrier whose credentials are configured, or pass --carrier <name> to override. Verbs: ship, label, track, list, carriers.

install
source · Clone the upstream repo
git clone https://github.com/Lifecycle-Innovations-Limited/claude-ops
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Lifecycle-Innovations-Limited/claude-ops "$T" && mkdir -p ~/.claude/skills && cp -r "$T/claude-ops/skills/ops-package" ~/.claude/skills/lifecycle-innovations-limited-claude-ops-ops-package && rm -rf "$T"
manifest: claude-ops/skills/ops-package/SKILL.md
source content

OPS ► PACKAGE — multi-carrier shipping

One skill, seven carriers. The router picks the carrier automatically based on which credentials are configured.

Which carrier do I get?

ConditionSelected carrier
--carrier <name>
flag passed
That carrier (validated)
Exactly one carrier has credentialsThat carrier
Multiple carriers have credentialsFirst match in this order
No carrier has credentialsExit 2 with setup help

Preference order:

myparcel
sendcloud
dhl
postnl
dpd
ups
fedex
.

Credential resolution

Each carrier resolves its credential(s) from, in order:

  1. Environment variable(s) listed below.
  2. preferences.json
    key (lowercase of the env var name) at
    ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json
    .
  3. Doppler:
    doppler secrets get <NAME> --plain
    .
CarrierEnv vars requiredDocs
MyParcel
MYPARCEL_API_KEY
https://developer.myparcel.nl/api-reference/
Sendcloud
SENDCLOUD_PUBLIC_KEY
+
SENDCLOUD_PRIVATE_KEY
https://api.sendcloud.dev/docs/
DHL NL
DHL_PARCEL_USER_ID
+
DHL_PARCEL_KEY
https://api-gw.dhlparcel.nl/docs
PostNL
POSTNL_API_KEY
+
POSTNL_CUSTOMER_CODE
+
POSTNL_CUSTOMER_NUMBER
https://developer.postnl.nl/
DPD
DPD_DELIS_ID
+
DPD_PASSWORD
https://esolutions.dpd.com/
UPS
UPS_CLIENT_ID
+
UPS_CLIENT_SECRET
+
UPS_SHIPPER_NUMBER
https://developer.ups.com/api/reference/shipping
FedEx
FEDEX_CLIENT_ID
+
FEDEX_CLIENT_SECRET
+
FEDEX_ACCOUNT_NUMBER
https://developer.fedex.com/api/en-us/catalog/

Routing

All API calls are delegated to

${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh
. Do not re-implement curl logic inline — pass args through.

First tokenAction
carriers
Show configured vs unconfigured carriers
ship
Create a shipment
label
Download + open label PDF
track
Show status + tracking barcode
list
List last 10 shipments (MyParcel / Sendcloud; others unsupported)
(empty)Show usage

ship

Required:

--to "<address>"
. Address format:

"Person / Company, Street 12A, 1011AB Amsterdam, NL"
  • / Company
    segment is optional.
  • Postcode accepts with/without space; NL postcodes are normalised to uppercase without space.
  • Country defaults to NL; common names (Netherlands, Belgium, Germany, France, UK, USA) map to ISO codes.

Flags (not every carrier honours every flag — unsupported flags are safely ignored per adapter):

  • --from "<address>"
    override sender; default is the account's configured sender.
  • --weight <grams>
    integer grams.
  • --package-type 1|2|3
    1=parcel (default), 2=mailbox, 3=letter (MyParcel only).
  • --signature
    require signature on delivery.
  • --insurance <EUR>
    integer EUR; 0 disables (MyParcel only).
  • --description "<text>"
    label reference (carriers differ; ~45 char max).
  • --pickup
    request home pickup at sender (MyParcel only).

Invocation

${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh \
  [--carrier myparcel|sendcloud|dhl|postnl|dpd|ups|fedex] \
  ship --to "$TO" [--from "$FROM"] [--weight "$W"] \
       [--signature] [--insurance "$INS"] [--description "$DESC"] \
       [--package-type "$TYPE"] [--pickup]

Returns:

{"carrier": "<name>", "shipment_id": "<id>", "response": { /* raw carrier JSON */ }}

Summarise to the user as:

Shipment created — <carrier> #<id>
Next: /ops:ops-package label <id>  to download the PDF.

Offer via

AskUserQuestion
(≤4 options):

  • [Download label now]
    — call
    label <id>
    immediately
  • [Track it]
    — call
    track <id>
  • [Ship another]
  • [Done]

label
<shipment-id>

${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh label "$ID"

Behaviour per carrier:

  • MyParcel: PDF saved to
    /tmp/myparcel_label_<id>.pdf
    and opened on macOS. If unpaid, returns
    {"status":"payment_required","payment_url":"..."}
    and opens the MultiSafepay URL.
  • Sendcloud: Fetches the CDN PDF URL (v2
    /labels/<id>
    ) and saves locally.
  • DHL NL: Fetches
    /labels/<id>?format=PDF&printerType=A4
    .
  • DPD: Fetches
    /v1/parcellabelnumber/<id>?paperFormat=A4
    .
  • PostNL / UPS / FedEx: PDF is returned inline with the
    ship
    call and cached to
    /tmp/<carrier>_label_<id>.pdf
    . Calling
    label
    re-opens that cached file. If the cache is gone, re-run
    ship
    (none of these carriers expose a stable label-recovery endpoint here).

track
<shipment-id>

Returns a normalised object across all carriers:

{"carrier":"<n>","id":"<id>","status":"<s>","barcode":"<b>","tracking_url":"<u>","recipient":<o>,"updated":"<ts>"}

list

MyParcel
returns the last 10 shipments.
Sendcloud
returns the last 10 parcels. All other carriers return
{"shipments":[],"note":"..."}
because their APIs don't expose a customer-facing list endpoint — track by id instead.

carriers

Prints which carriers are configured (

) vs which are missing credentials (
·
). Use this when deciding which
--carrier
to pass.

Error handling

  • No carrier configured → script exits 2 with a checklist of envs. Surface it verbatim, then via
    AskUserQuestion
    :
    [Paste key now]
    [Open carriers doc]
    [Skip — configure later]
    On "Paste key now", collect via
    AskUserQuestion
    free-text and write to
    preferences.json
    .
  • 4xx from carrier → dump the JSON error body and stop. Do not retry silently.
  • Address parser looks wrong → re-prompt the user with the parsed breakdown and ask for corrections before POSTing.

Verification status

CarrierStatus
MyParcelVerified against api.myparcel.nl v1.1 (same working reference)
SendcloudVerified request/response shapes against Sendcloud Panel API v3
DHL NLUNVERIFIED — modelled on My DHL Parcel Swagger, needs live account
PostNLUNVERIFIED — modelled on Send API v2.2 docs, needs live account
DPDUNVERIFIED — modelled on DPD eSolutions REST, needs live account
UPSUNVERIFIED — modelled on UPS v2403 Ship/Track REST, needs account
FedExUNVERIFIED — modelled on FedEx Ship v1 + Track v1 REST

Unverified adapters are tagged with

# UNVERIFIED - pending live test with account
in the source. Payloads match the documented contract; adjustments may be needed when tested against live accounts.