{"slug":"ops-package","title":"ops-package","summary":"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.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-14T20:43:21.698408Z","repo":{"url":"https://github.com/davepoon/buildwithclaude","stars":3556,"forks":535,"license":"MIT","updatedAt":"2026-09-27T03:56:38Z"},"bodyHtml":"<hr>\n<p>name: ops-package\ndescription: 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 </p>\n<ul>\n<li>Bash</li>\n<li>Read</li>\n<li>AskUserQuestion\neffort: low\nmaxTurns: 15\ndisallowedTools:</li>\n<li>Edit</li>\n<li>Write</li>\n<li>NotebookEdit</li>\n</ul>\n<hr>\n<h1>OPS ► PACKAGE — multi-carrier shipping</h1>\n<p>One skill, seven carriers. The router picks the carrier automatically based on which credentials are configured.</p>\n<h2>Which carrier do I get?</h2>\n<table>\n<thead>\n<tr>\n<th>Condition</th>\n<th>Selected carrier</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>--carrier &lt;name&gt;</code> flag passed</td>\n<td>That carrier (validated)</td>\n</tr>\n<tr>\n<td>Exactly one carrier has credentials</td>\n<td>That carrier</td>\n</tr>\n<tr>\n<td>Multiple carriers have credentials</td>\n<td>First match in this order</td>\n</tr>\n<tr>\n<td>No carrier has credentials</td>\n<td>Exit 2 with setup help</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Preference order:</strong> <code>myparcel</code> → <code>sendcloud</code> → <code>dhl</code> → <code>postnl</code> → <code>dpd</code> → <code>ups</code> → <code>fedex</code>.</p>\n<h2>Credential resolution</h2>\n<p>Each carrier resolves its credential(s) from, in order:</p>\n<ol>\n<li>Environment variable(s) listed below.</li>\n<li><code>preferences.json</code> key (lowercase of the env var name) at <code>${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json</code>.</li>\n<li>Doppler: <code>doppler secrets get &lt;NAME&gt; --plain</code>.</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th>Carrier</th>\n<th>Env vars required</th>\n<th>Docs</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>MyParcel</td>\n<td><code>MYPARCEL_API_KEY</code></td>\n<td><a href=\"https://developer.myparcel.nl/api-reference/\">https://developer.myparcel.nl/api-reference/</a></td>\n</tr>\n<tr>\n<td>Sendcloud</td>\n<td><code>SENDCLOUD_PUBLIC_KEY</code> + <code>SENDCLOUD_PRIVATE_KEY</code></td>\n<td><a href=\"https://api.sendcloud.dev/docs/\">https://api.sendcloud.dev/docs/</a></td>\n</tr>\n<tr>\n<td>DHL NL</td>\n<td><code>DHL_PARCEL_USER_ID</code> + <code>DHL_PARCEL_KEY</code></td>\n<td><a href=\"https://api-gw.dhlparcel.nl/docs\">https://api-gw.dhlparcel.nl/docs</a></td>\n</tr>\n<tr>\n<td>PostNL</td>\n<td><code>POSTNL_API_KEY</code> + <code>POSTNL_CUSTOMER_CODE</code> + <code>POSTNL_CUSTOMER_NUMBER</code></td>\n<td><a href=\"https://developer.postnl.nl/\">https://developer.postnl.nl/</a></td>\n</tr>\n<tr>\n<td>DPD</td>\n<td><code>DPD_DELIS_ID</code> + <code>DPD_PASSWORD</code></td>\n<td><a href=\"https://esolutions.dpd.com/\">https://esolutions.dpd.com/</a></td>\n</tr>\n<tr>\n<td>UPS</td>\n<td><code>UPS_CLIENT_ID</code> + <code>UPS_CLIENT_SECRET</code> + <code>UPS_SHIPPER_NUMBER</code></td>\n<td><a href=\"https://developer.ups.com/api/reference/shipping\">https://developer.ups.com/api/reference/shipping</a></td>\n</tr>\n<tr>\n<td>FedEx</td>\n<td><code>FEDEX_CLIENT_ID</code> + <code>FEDEX_CLIENT_SECRET</code> + <code>FEDEX_ACCOUNT_NUMBER</code></td>\n<td><a href=\"https://developer.fedex.com/api/en-us/catalog/\">https://developer.fedex.com/api/en-us/catalog/</a></td>\n</tr>\n</tbody>\n</table>\n<h2>Routing</h2>\n<p>All API calls are delegated to <code>${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh</code>. Do not re-implement curl logic inline — pass args through.</p>\n<table>\n<thead>\n<tr>\n<th>First token</th>\n<th>Action</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>carriers</code></td>\n<td>Show configured vs unconfigured carriers</td>\n</tr>\n<tr>\n<td><code>ship</code></td>\n<td>Create a shipment</td>\n</tr>\n<tr>\n<td><code>label</code></td>\n<td>Download + open label PDF</td>\n</tr>\n<tr>\n<td><code>track</code></td>\n<td>Show status + tracking barcode</td>\n</tr>\n<tr>\n<td><code>list</code></td>\n<td>List last 10 shipments (MyParcel / Sendcloud; others unsupported)</td>\n</tr>\n<tr>\n<td>(empty)</td>\n<td>Show usage</td>\n</tr>\n</tbody>\n</table>\n<h2>ship</h2>\n<p>Required: <code>--to \"&lt;address&gt;\"</code>. Address format:</p>\n<pre><code>\"Person / Company, Street 12A, 1011AB Amsterdam, NL\"\n</code></pre>\n<ul>\n<li><code>/ Company</code> segment is optional.</li>\n<li>Postcode accepts with/without space; NL postcodes are normalised to uppercase without space.</li>\n<li>Country defaults to NL; common names (Netherlands, Belgium, Germany, France, UK, USA) map to ISO codes.</li>\n</ul>\n<p>Flags (not every carrier honours every flag — unsupported flags are safely ignored per adapter):</p>\n<ul>\n<li><code>--from \"&lt;address&gt;\"</code> override sender; default is the account's configured sender.</li>\n<li><code>--weight &lt;grams&gt;</code> integer grams.</li>\n<li><code>--package-type 1|2|3</code> 1=parcel (default), 2=mailbox, 3=letter (MyParcel only).</li>\n<li><code>--signature</code> require signature on delivery.</li>\n<li><code>--insurance &lt;EUR&gt;</code> integer EUR; 0 disables (MyParcel only).</li>\n<li><code>--description \"&lt;text&gt;\"</code> label reference (carriers differ; ~45 char max).</li>\n<li><code>--pickup</code> request home pickup at sender (MyParcel only).</li>\n</ul>\n<h3>Invocation</h3>\n<pre><code>${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh \\\n  [--carrier myparcel|sendcloud|dhl|postnl|dpd|ups|fedex] \\\n  ship --to \"$TO\" [--from \"$FROM\"] [--weight \"$W\"] \\\n       [--signature] [--insurance \"$INS\"] [--description \"$DESC\"] \\\n       [--package-type \"$TYPE\"] [--pickup]\n</code></pre>\n<p>Returns:</p>\n<pre><code>{\"carrier\": \"&lt;name&gt;\", \"shipment_id\": \"&lt;id&gt;\", \"response\": { /* raw carrier JSON */ }}\n</code></pre>\n<p>Summarise to the user as:</p>\n<pre><code>Shipment created — &lt;carrier&gt; #&lt;id&gt;\nNext: /ops:ops-package label &lt;id&gt;  to download the PDF.\n</code></pre>\n<p>Offer via <code>AskUserQuestion</code> (≤4 options):</p>\n<ul>\n<li><code>[Download label now]</code> — call <code>label &lt;id&gt;</code> immediately</li>\n<li><code>[Track it]</code> — call <code>track &lt;id&gt;</code></li>\n<li><code>[Ship another]</code></li>\n<li><code>[Done]</code></li>\n</ul>\n<h2>label <code>&lt;shipment-id&gt;</code></h2>\n<pre><code>${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh label \"$ID\"\n</code></pre>\n<p>Behaviour per carrier:</p>\n<ul>\n<li><strong>MyParcel</strong>: PDF saved to <code>/tmp/myparcel_label_&lt;id&gt;.pdf</code> and opened on macOS. If unpaid, returns <code>{\"status\":\"payment_required\",\"payment_url\":\"...\"}</code> and opens the MultiSafepay URL.</li>\n<li><strong>Sendcloud</strong>: Fetches the CDN PDF URL (v2 <code>/labels/&lt;id&gt;</code>) and saves locally.</li>\n<li><strong>DHL NL</strong>: Fetches <code>/labels/&lt;id&gt;?format=PDF&amp;printerType=A4</code>.</li>\n<li><strong>DPD</strong>: Fetches <code>/v1/parcellabelnumber/&lt;id&gt;?paperFormat=A4</code>.</li>\n<li><strong>PostNL / UPS / FedEx</strong>: PDF is returned inline with the <code>ship</code> call and cached to <code>/tmp/&lt;carrier&gt;_label_&lt;id&gt;.pdf</code>. Calling <code>label</code> re-opens that cached file. If the cache is gone, re-run <code>ship</code> (none of these carriers expose a stable label-recovery endpoint here).</li>\n</ul>\n<h2>track <code>&lt;shipment-id&gt;</code></h2>\n<p>Returns a normalised object across all carriers:</p>\n<pre><code>{\"carrier\":\"&lt;n&gt;\",\"id\":\"&lt;id&gt;\",\"status\":\"&lt;s&gt;\",\"barcode\":\"&lt;b&gt;\",\"tracking_url\":\"&lt;u&gt;\",\"recipient\":&lt;o&gt;,\"updated\":\"&lt;ts&gt;\"}\n</code></pre>\n<h2>list</h2>\n<p><code>MyParcel</code> returns the last 10 shipments. <code>Sendcloud</code> returns the last 10 parcels. All other carriers return <code>{\"shipments\":[],\"note\":\"...\"}</code> because their APIs don't expose a customer-facing list endpoint — track by id instead.</p>\n<h2>carriers</h2>\n<p>Prints which carriers are configured (<code>✓</code>) vs which are missing credentials (<code>·</code>). Use this when deciding which <code>--carrier</code> to pass.</p>\n<h2>Error handling</h2>\n<ul>\n<li><strong>No carrier configured</strong> → script exits 2 with a checklist of envs. Surface it verbatim, then via <code>AskUserQuestion</code>:\n<code>[Paste key now]</code>  <code>[Open carriers doc]</code>  <code>[Skip — configure later]</code>\nOn \"Paste key now\", collect via <code>AskUserQuestion</code> free-text and write to <code>preferences.json</code>.</li>\n<li><strong>4xx from carrier</strong> → dump the JSON error body and stop. Do not retry silently.</li>\n<li><strong>Address parser looks wrong</strong> → re-prompt the user with the parsed breakdown and ask for corrections before POSTing.</li>\n</ul>\n<h2>Verification status</h2>\n<table>\n<thead>\n<tr>\n<th>Carrier</th>\n<th>Status</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>MyParcel</td>\n<td>Verified against api.myparcel.nl v1.1 (same working reference)</td>\n</tr>\n<tr>\n<td>Sendcloud</td>\n<td>Verified request/response shapes against Sendcloud Panel API v3</td>\n</tr>\n<tr>\n<td>DHL NL</td>\n<td>UNVERIFIED — modelled on My DHL Parcel Swagger, needs live account</td>\n</tr>\n<tr>\n<td>PostNL</td>\n<td>UNVERIFIED — modelled on Send API v2.2 docs, needs live account</td>\n</tr>\n<tr>\n<td>DPD</td>\n<td>UNVERIFIED — modelled on DPD eSolutions REST, needs live account</td>\n</tr>\n<tr>\n<td>UPS</td>\n<td>UNVERIFIED — modelled on UPS v2403 Ship/Track REST, needs account</td>\n</tr>\n<tr>\n<td>FedEx</td>\n<td>UNVERIFIED — modelled on FedEx Ship v1 + Track v1 REST</td>\n</tr>\n</tbody>\n</table>\n<p>Unverified adapters are tagged with <code># UNVERIFIED - pending live test with account</code> in the source. Payloads match the documented contract; adjustments may be needed when tested against live accounts.</p>\n","files":[{"path":"lib/carriers/dhl.sh","sizeBytes":6267,"isText":true},{"path":"lib/carriers/dpd.sh","sizeBytes":6683,"isText":true},{"path":"lib/carriers/fedex.sh","sizeBytes":7560,"isText":true},{"path":"lib/carriers/myparcel.sh","sizeBytes":6649,"isText":true},{"path":"lib/carriers/postnl.sh","sizeBytes":6662,"isText":true},{"path":"lib/carriers/sendcloud.sh","sizeBytes":5560,"isText":true},{"path":"lib/carriers/ups.sh","sizeBytes":7147,"isText":true},{"path":"lib/common.sh","sizeBytes":5507,"isText":true},{"path":"ops-package.sh","sizeBytes":4541,"isText":true},{"path":"SKILL.md","sizeBytes":8372,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"notes-only","suspicious":0,"notes":1,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-14T20:43:55.532887Z","sha256":"76CA8154108F55CA33EBABF76BCF9FB1FCC2C07EA53352B084CC4628ECF53608","sizeBytes":25260},"review":null,"source":{"repositoryUrl":"https://github.com/davepoon/buildwithclaude","path":"plugins/claude-ops/skills/ops-package","license":"MIT","commit":"6178ae2b0900b3600419853206de1dddf0f28311","subtreeSha":"9FE5B4C5DFBB62D3BEC5A52D61C03DE1D5504778D187466B71997E4339B78D82","lastSyncedAt":"2026-09-27T19:47:46.68834Z"},"reviewedAt":"2026-09-14T20:44:54.697289Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/davepoon/buildwithclaude/tree/main/plugins/claude-ops/skills/ops-package"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install davepoon-buildwithclaude@llmmart"},{"target":"git","command":"git clone https://github.com/davepoon/buildwithclaude.git"}]}