dsh-domain-managerv0.2.0
beings-labs/dsh-domain-manager
dsh plugin --profile web add github:beings-labs/dsh-domain-manager#d0690c24dd04c35b48b7896589960f99565d962c
介绍
A DeepSeek Harness plugin for managing domains.
标签
概述
@beings-labs/dsh-domain-manager
A DeepSeek Harness plugin for managing a domain portfolio: registrations synced from registrars, expiry dates, auto-renewal flags, yearly renewal costs, and domain availability checks.
No bundled portfolio and no credentials — install it, point it at your registrars if you want syncing, and the data stays in a JSON file on your machine.
Features
- Portfolio store — add, update and remove domains; track registrar, expiry, auto-renewal, renewal price, promo price, purpose, group and notes.
- Cost reporting — total yearly renewal cost, the part charged automatically (auto-renew on) versus the part that lapses unless renewed manually (auto-renew off), per-registrar subtotals, promo-adjusted totals, and domains expiring soon.
- Staleness warnings — registrar-synced facts age between syncs; reports and markdown exports warn when synced domains have not been refreshed for more than
staleAfterDays(manual domains without a sync are not counted). - Registrar sync — pull registrations from Cloudflare Registrar and Namecheap (read-only) into the store, keeping your local purpose/group/notes. Auto mode is the default for both registrars: Namecheap prefers configured API credentials, otherwise uses MCP; Cloudflare remains API-only. Namecheap API mode is the only mode that refreshes the account's own renewal pricing: regular rate, current promo and per-year ICANN fee.
- Browser login instead of API keys (Namecheap, opt-in) — set
providers.namecheap: mcpto reach Namecheap through its official MCP server after a one-time browser sign-in, with no API key or IP allow-listing. MCP mode cannot get price information for the domains you already own (domain list + availability only), so it is an alternative to the default, not a replacement for it. See Namecheap without API keys. - Availability checks — 1–20 domains per call. Uses Cloudflare's registry-accurate
domain-check, Namecheap'sdomains.check, or the Namecheap MCP tool whenproviderismcp, and otherwise asks public RDAP with no credentials at all. RDAP is registry-first: the domain's own RDAP service is discovered from the IANA bootstrap, withrdap.orgkept only as a fallback so a rate-limited redirector cannot break lookups. - Domain search — candidate discovery through the Cloudflare Registrar search API.
- Registrar transfers — API-assisted Namecheap → Cloudflare transfers: eligibility and fee checks, explicit confirmation before paid submission, and persistent progress tracking. DNS migration, unlocking and authorization-code retrieval stay manual.
- Markdown interop — import a portfolio from a markdown table and export the portfolio back to a grouped-by-registrar document.
Registrar sync, availability, search and transfer checks are read-only. Only domain_manager_transfer_start submits a registrar change and can incur a non-refundable transfer charge; it requires explicit confirmation of a checked quote. The plugin never registers new domains, manually renews registrations or changes DNS.
Install
Install from npm:
dsh plugin --profile <profile> add @beings-labs/dsh-domain-manager
Or straight from GitHub:
dsh plugin --profile <profile> add github:beings-labs/dsh-domain-manager
From a local checkout:
dsh plugin --profile <profile> add /path/to/dsh-domain-manager
Restart the Harness app, then check the layer loaded:
dsh --profile <profile> --dump-config | grep dsh-domain-manager
Remove with dsh plugin --profile <profile> remove @beings-labs/dsh-domain-manager.
Configuration
Everything is optional; the local store and RDAP availability work with no configuration.
# profile cordis.patch.yml
- id: dsh-domain-manager
config:
dataFile: /absolute/path/domains.json # default: $DSH_HOME/dsh-domain-manager/domains.json
dueWithinDays: 90 # default expiry window
staleAfterDays: 14 # warn when syncs are older than this
currency: USD # default currency for prices
namecheap:
apiUser: your-api-user
apiKey: your-api-key
userName: your-namecheap-username # defaults to apiUser
clientIp: 203.0.113.10 # must be allow-listed in Namecheap
sandbox: false
cloudflare:
apiToken: your-api-token
accountId: your-account-id
providers: # how each registrar is reached; auto is the default
namecheap: auto # default | api | mcp — prefer API credentials, otherwise MCP
cloudflare: auto # default (alias for api) — no registrar MCP transport
providers.namecheap and providers.cloudflare both default to auto. Namecheap prefers configured API credentials and otherwise uses a connected MCP server. Cloudflare's auto is an alias for api. Namecheap API mode is the only mode that refreshes renewal/promo/ICANN prices; MCP mode has no price feed for registered domains (see Namecheap without API keys). API selection depends on credentials being configured; an API request failure does not switch Namecheap to MCP.
Environment variables are used when the matching config field is absent:
| Config field | Environment variable |
|---|---|
namecheap.apiUser | NAMECHEAP_API_USER |
namecheap.apiKey | NAMECHEAP_API_KEY |
namecheap.userName | NAMECHEAP_USERNAME |
namecheap.clientIp | NAMECHEAP_CLIENT_IP |
cloudflare.apiToken | CLOUDFLARE_API_TOKEN |
cloudflare.accountId | CLOUDFLARE_ACCOUNT_ID |
Credential setup
Cloudflare — Cloudflare is API-only; the default auto mode is an alias for api. Create an API token at . Listing your registered domains needs Registrar read permission; the availability/search endpoints are part of the Registrar API beta and need Registrar write permission. The same endpoints back the Cloudflare MCP servers, so an agent workflow you already use through MCP sees identical data.
Transfers also require Registrar write access and access to the Registrar API beta. Set up a default payment method, default registrant contact and the registration agreement in the Cloudflare dashboard before submission. No Namecheap API credentials are required for transfers; the source domain must already be in the local portfolio.
Namecheap API (preferred in auto mode, and the only path that refreshes renewal pricing) — enable API access under Profile → Tools → Namecheap API Access, generate an API key, and allow-list the public IP you send as clientIp. Namecheap's XML API returns the registered-domain list and availability/premium pricing; for renewal prices it exposes the account's per-TLD rates through namecheap.users.getPricing (regular rate, currently charged promo price, and the per-year ICANN fee). domain_manager_sync uses that feed to keep renewalPrice, promoPrice and icannFee current. The feed is ~3 MB and is cached in-process for 6 hours. If you would rather not create API credentials, auto mode can use the MCP browser-login path below to list domains and check availability, but it cannot read prices for the domains you already own.
Namecheap without API keys (browser login)
Auto mode is the default and prefers configured API credentials; MCP mode cannot get price information for registered domains. MCP lists domains and checks availability only, so stored renewal, promo and ICANN prices are not refreshed. Configure API credentials to keep them current; set providers.namecheap: api to require API access.
Namecheap runs an official MCP server (https://mcp.namecheap.com/mcp) secured with OAuth. DSH's MCP client cannot complete an interactive OAuth flow by itself, so the dsh-namecheap-mcp connector bridges it through mcp-remote over stdio:
dsh plugin --profile <profile> add github:2000lla/dsh-namecheap-mcp
Restart the app, then leave providers.namecheap at its default auto when no API credentials are configured, or set it to mcp to force browser login, and call any domain_manager_* tool that needs Namecheap. The first call opens a browser, you sign in on namecheap.com (password never touches DSH), and the connector caches the OAuth tokens under ~/.mcp-auth/. After that the Namecheap tools are available as mcp__namecheap__domains_list and mcp__namecheap__domains_check_availability.
With the connector installed, providers.namecheap behaves like this:
providers.namecheap | Behaviour |
|---|---|
auto (default) | Use API credentials when configured, otherwise the connected MCP server |
api | Require the Namecheap XML API with apiUser, apiKey and clientIp; the only mode that refreshes renewalPrice, promoPrice and icannFee from namecheap.users.getPricing |
mcp | Require the connected MCP server; fail with install guidance if it is missing. Lists domains and checks availability, but cannot read prices for registered domains |
Notes:
- MCP mode needs no API key and no IP allow-listing; API mode needs both. Auto mode prefers API access when credentials are configured and otherwise uses the connected MCP server.
- MCP mode cannot read prices for registered domains. The Namecheap MCP server exposes no per-domain renewal price and no pricing feed, so a sync in
mcpmode only lists domains (availability checks can also use the MCP server) and keeps therenewalPrice,promoPriceandicannFeealready in the store. Configure API credentials to refresh them fromnamecheap.users.getPricingin auto mode, or selectapito require API access. - With the default
automode, incomplete API credentials cause Namecheap to use the connected MCP server. If neither integration is configured, a sync fails with setup guidance. Explicitapimode requires complete API credentials and does not use MCP. providers.cloudflaredefaults toautoand acceptsapi/auto(autois an alias forapi); there is no Cloudflare registrar MCP path, somcpis rejected with a clear error. A browser-login path would require the Cloudflare API MCP server through another connector.
Tools
| Tool | Purpose |
|---|---|
domain_manager_list | List domains; filter by registrar, group, autoRenew, dueWithinDays |
domain_manager_report | Yearly cost (plus ICANN fees and an all-in figure), auto-renew on/off split, per-registrar subtotals, expiring soon |
domain_manager_add | Add a domain (name, registrar, expires; optional price, ICANN fee and metadata) |
domain_manager_update | Change expiry, price, ICANN fee, auto-renew, registrar, currency, purpose, group, promo, notes |
domain_manager_remove | Remove a domain from the local portfolio |
domain_manager_sync | Sync from namecheap (auto prefers API credentials, otherwise MCP browser login) / cloudflare (API) or all; Namecheap API mode also refreshes renewal/promo/ICANN-fee prices, while MCP mode cannot read prices for registered domains; dryRun previews |
domain_manager_check_availability | Check 1–20 domains; provider: auto, mcp, cloudflare, namecheap, rdap; fallback warnings are included in the result |
domain_manager_search | Cloudflare domain-search candidates for a keyword |
domain_manager_transfer_check | Check a stored Namecheap domain's transfer eligibility/fees; explain blockers in plain language; save a quote (keeping the ID when nothing changed); best-effort warn when the Cloudflare zone is missing or not Active |
domain_manager_transfer_start | Submit a paid, non-refundable transfer after explicit user approval (confirm: true and the checked quoteId); recheck eligibility and pricing |
domain_manager_transfer_status | Refresh a submitted transfer; report the stage (awaiting approval, in progress, action required, blocked, failed, succeeded) with the next action, and update the portfolio only after verified completion |
domain_manager_import_markdown | Import portfolio tables from a markdown file (upsert or replace) |
domain_manager_export_markdown | Render the portfolio to a markdown document (dryRun to preview) |
Example prompts:
- "Show my domains that expire in the next 90 days and don't auto-renew."
- "Sync my Cloudflare registrations, then report the yearly cost."
- "Is example.dev available, and what does Cloudflare charge for it?"
- "Export my portfolio to ~/domains.md."
- "Check moving example.com from Namecheap to Cloudflare and show me the fee and prerequisites. Don't submit it yet."
Moving from Namecheap to Cloudflare
This is a registration transfer, not just a DNS-provider change. Only Namecheap → Cloudflare is currently supported. The workflow uses Cloudflare's transfer eligibility, transfer-in and transfer status APIs.
- Sync or add the domain to the portfolio with
registrar: Namecheap. - Complete the manual Cloudflare prerequisites: resolve transfer locks/holds, back up and migrate all DNS records (including email), disable old DNSSEC/DS records and allow propagation before changing nameservers. Use Cloudflare's assigned nameservers and wait for an Active full-setup zone. Cloudflare Registrar requires Cloudflare authoritative DNS.
- In Namecheap Domain List → Manage → Sharing & Transfer, unlock the domain and request the EPP/authorization code by email. For
.uknamespaces, omit the code and follow the IPS-tag transfer process (CLOUDFLARE). Recent registrations, transfers or registrant-contact changes can trigger a 60-day lock. Premium domains and extensions needing additional registry-specific contact fields may require the Cloudflare dashboard. - Call
domain_manager_transfer_checkwithnameand the rawauthCode(not base64). OptionalautoRenewdefaults to the current portfolio setting. The result includes aquoteId, transfer fee, annual renewal price, auto-renew choice and eligibility reasons such asdomain_locked,dnssec_enabledorzone_status_invalid. Checks only read the registrar; the quote is stored locally. The result explains each eligibility reason in plain language and warns if the target account's Cloudflare zone is missing or stillpending. This advisory lookup requires Zone read permission; permission, network and malformed-response failures appear as warnings rather than silently implying readiness. Re-running a check while eligibility, prices and auto-renew are unchanged keeps the samequoteId(mentioned inmessage) and refreshes the check timestamp, so a previous approval stays valid. - Before submitting, remind the user that the transfer may extend registration by one year and bill for that additional year at Cloudflare as part of the quoted transfer fee, even if auto-renew is off. Registry rules vary, and zero-fee transfers do not incur that charge. Ready quotes return a
confirmationPromptcontaining this reminder, the domain, fee/currency, annual renewal price and auto-renew setting. Ask that question and wait for explicit approval; a request to check or prepare a transfer is not approval. Only then calldomain_manager_transfer_startwithname, the samequoteId,confirm: trueand the code again. The plugin rejects missing/false confirmation and rechecks eligibility and both prices; changed results require a new check and confirmation. A successful paid transfer charges the account's default payment method and is non-refundable. - Approve any registrar emails and call
domain_manager_transfer_statusas needed. Transfers typically take 1–10 days; Namecheap may offer expedited release.action_requiredmeans finish the returned action before checking again;blockedcan require the losing registrar or registry to respond. Results use plain-language stage labels (for examplesubmitted, awaiting approval) plus billing guidance and numbered next steps. Some transfers auto-release after about five days, but registry rules and holds can change that timing. Billing guidance describes the quoted fee, not an independently verified payment; check the Cloudflare dashboard for charges. Expiry extensions depend on registry rules rather than always adding one year. Failures are surfaced and never automatically retried.
Read failures that are safe to retry explain themselves: a Cloudflare 401 names the credential to fix and notes that the plugin reads CLOUDFLARE_API_TOKEN only at startup (restart DSH after editing ~/.dsh/.env); error 9109 points at the token's Client IP Address Filtering; and a 403 for zone.create points at the required Account-scoped "Zone → Edit" policy.
Pending/uncertain transfers survive restarts. The plugin saves submission intent before the API request: on a timeout, HTTP 409 conflict or unknown outcome, check status and the Cloudflare dashboard rather than submitting again. A generic HTTP 404 does not prove that no transfer exists; reconciliation only records a missing workflow when Cloudflare explicitly reports that none was found. After a definitive rejection or confirmed missing workflow, run a new check and obtain explicit approval before retrying. Do not edit the transfer record to bypass these safeguards.
Until Cloudflare reports success and returns an active registration, the portfolio keeps Namecheap and its existing facts. Completion refreshes expiry and auto-renew from Cloudflare, adopts the quoted Cloudflare renewal price and clears Namecheap-only promo/ICANN fees, while preserving purpose/group/notes. Run normal Cloudflare sync to refresh renewal pricing later. Active or uncertain transfers cannot be removed, overwritten by a replace import, or manually assigned another registrar; ordinary sync cannot prematurely move them or revert a completed transfer based on stale Namecheap data. Markdown export is a portfolio view, not a transfer-state backup; keep the JSON store to resume transfers.
Authorization codes are only sent to Cloudflare over HTTPS and are not stored in the portfolio or returned in results; both raw and encoded codes are redacted from request errors. The host may retain tool arguments in conversation history/logs, so handle codes as secrets and avoid sharing them in exported conversations. The plugin does not automate DNS changes, Namecheap unlocking/code requests, contact setup or billing setup.
Data
The store defaults to $DSH_HOME/dsh-domain-manager/domains.json (~/.dsh when DSH_HOME is unset):
{
"version": 1,
"updatedAt": "2026-01-01T00:00:00.000Z",
"domains": [
{
"name": "example.com",
"registrar": "Cloudflare",
"expires": "2027-03-15",
"autoRenew": true,
"renewalPrice": 9.15,
"currency": "USD",
"promoPrice": null,
"icannFee": null,
"purpose": "marketing site",
"group": "brand",
"notes": null,
"source": "cloudflare",
"syncedAt": "2026-01-01T00:00:00.000Z"
}
]
}
Store writes and markdown exports are atomic (exclusive temp file + rename). New files have owner-only permissions (0600), newly created directories use 0700, and temporary files are cleaned up on failure. Existing directory permissions are not changed. A corrupt file fails loudly instead of silently starting empty. The repository ships no portfolio data.
Domains involved in a transfer also have an optional validated transfer object: source/destination, destination account ID, state, quote ID/prices/currency, auto-renew choice, timestamps, eligibility reasons, advisory zone warnings and action/failure messages. It contains no authorization code or API token. Existing version-1 stores without this field remain compatible.
Markdown format
Import accepts any table with Domain and Expires columns, plus optional Registrar, Purpose, Group, Renewal Price, Currency, Auto-renew and Notes. Prices may look like $12.20/year + $0.20 ICANN with an optional *(promo: $9.99)*. An unknown regular price stays unknown even when a promo or fee is present. Escaped pipes and literal asterisks in text cells are preserved. If there is no Registrar column, the closest ## heading (## Cloudflare, ## Namecheap) is used. Tables without Domain/Expires columns (for example a "Domains to Acquire" list) are skipped.
Export writes one ## <Registrar> section per registrar, explicit registrar and currency columns, per-domain ICANN fees, totals, an ICANN-fee/all-in line when fees apply, and an auto-renew cost split. Totals do not perform currency conversion; use a consistent currency for comparable portfolio totals.
Security notes
- The plugin stores no credentials; it only reads them from config or environment variables. In MCP mode the registrar connector owns the OAuth tokens in
~/.mcp-auth/. - Credential-shaped query parameters, JSON fields and bearer tokens are redacted in errors. HTTP and registrar errors also redact known API keys/tokens echoed by upstream services; invalid JSON errors do not echo response content.
- HTTP timeouts cover the complete response, including reading the body. MCP calls have a deadline even if a tool ignores its abort signal.
- Malformed registrar/MCP responses and duplicate sync rows fail explicitly instead of becoming successful empty or partial syncs. Namecheap refuses a partial sync if its page limit is reached. Availability responses must cover every requested domain exactly once with a boolean answer; auto checks can fall back to the next provider and report the failure in
warnings. - XML DTDs and structurally malformed XML are rejected; XML traversal does not depend on JavaScript recursion depth.
- Registrar access is read-only except the explicitly confirmed, billable
domain_manager_transfer_startcall. Transfers never automatically change DNS or unlock the source domain. domain_manager_check_availabilityonly performs DNS/RDAP or registrar read calls; it cannot register or buy anything.
Development
npm test
Tests cover the store, markdown round-trips, XML parsing, both registrar clients (mocked HTTP, no network), RDAP availability, registrar transfer confirmation/pricing/status/restart safety, and the plugin's tool wiring. The test/stubs + --import ./test/register-stubs.js pair stands in for the host-provided @deepseek-ai/dsh-tools and @deepseek-ai/schemastery packages; they must stay out of node_modules so an installed copy resolves the host's real packages.
Load the checkout without installing it into a profile:
# edit dev/cordis.patch.yml first: replace /absolute/path/to/dsh-domain-manager with your clone path
dsh --profile <profile> --patch /absolute/path/to/dsh-domain-manager/dev/cordis.patch.yml
dev/probe.patch.yml additionally loads a startup probe that logs the registered tool schemas and dispatches a real domain_manager_report call — used to verify registration in a real host.
Portfolio data never belongs in this repository: the store defaults to $DSH_HOME/dsh-domain-manager/domains.json, and the dev scratch file dev/domains.json is git-ignored.
Publishing to npm
The Publish to npm workflow publishes the public package @beings-labs/dsh-domain-manager. It accepts stable vX.Y.Z tags only, requires the tag to match package.json, runs the tests and checks the package contents before publishing the exact validated commit. There is no dependency-install step: the tests use the host-package stubs described above.
First publication
The existing v0.1.0 tag predates this workflow, so publishing it requires a manual bootstrap:
- Ensure your npm account can publish packages under the
beings-labsscope. - Create a short-lived granular npm access token with Read and write (publish and stage) access to the
beings-labspackage scope and Bypass two-factor authentication enabled for unattended publishing. Organization-management permissions alone do not grant package-publishing access. - Add it as the repository's Actions secret named
NPM_TOKEN. Never put the token in source files or release notes. - Run Publish to npm from the
mainbranch with thetaginput set tov0.1.0:
gh workflow run publish.yml --repo beings-labs/dsh-domain-manager --ref main -f tag=v0.1.0
The bootstrap publishes the contents of the existing tag, not the current branch. It deliberately omits provenance because a manual run from main has a different workflow source commit from the release tag. It has no OIDC permission and receives the npm token only in the publish step.
Subsequent releases: trusted publishing
After the first publication, configure a trusted publisher in the npm package's settings:
| Setting | Value |
|---|---|
| Provider | GitHub Actions |
| Organization or user | beings-labs |
| Repository | dsh-domain-manager |
| Workflow filename | publish.yml |
| Environment name | Leave empty |
| Allowed actions | Enable direct publishing with npm publish |
Remove the NPM_TOKEN Actions secret and revoke its npm token after bootstrapping. Future tag-push runs use OIDC, not the bootstrap token, and publish with provenance. Restrict token publishing in the npm package settings once trusted publishing is configured. npm requires a newly configured trusted publisher to complete its first successful publish within two days; configure it close to the next release.
For each subsequent release, update package.json, commit and push the version change, then create and push a matching annotated tag:
git tag -a v0.1.1 -m "Release v0.1.1"
git push origin v0.1.1
The tag push triggers publication automatically. Published npm versions and release tags are immutable; do not move v0.1.0 to include this workflow or try to republish an existing npm version.
License
MIT © Beings Labs
星级评价
基本信息
插件信息来自公开 GitHub 仓库与 SkillHub,版权归原作者所有;安装前请自行审查仓库代码。