dsh-domain-managerv0.2.0

beings-labs/dsh-domain-manager

模型推理 MIT 最近更新 2026-11-11
0Stars
0Forks
0待解决 Issue
安装命令 dsh plugin --profile web add github:beings-labs/dsh-domain-manager#d0690c24dd04c35b48b7896589960f99565d962c

介绍

A DeepSeek Harness plugin for managing domains.

标签

dsh-plugin dsh deepseek-harness cordis domains domain-portfolio namecheap cloudflare registrar availability

概述

@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: mcp to 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's domains.check, or the Namecheap MCP tool when provider is mcp, 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, with rdap.org kept 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 fieldEnvironment variable
namecheap.apiUserNAMECHEAP_API_USER
namecheap.apiKeyNAMECHEAP_API_KEY
namecheap.userNameNAMECHEAP_USERNAME
namecheap.clientIpNAMECHEAP_CLIENT_IP
cloudflare.apiTokenCLOUDFLARE_API_TOKEN
cloudflare.accountIdCLOUDFLARE_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.namecheapBehaviour
auto (default)Use API credentials when configured, otherwise the connected MCP server
apiRequire the Namecheap XML API with apiUser, apiKey and clientIp; the only mode that refreshes renewalPrice, promoPrice and icannFee from namecheap.users.getPricing
mcpRequire 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 mcp mode only lists domains (availability checks can also use the MCP server) and keeps the renewalPrice, promoPrice and icannFee already in the store. Configure API credentials to refresh them from namecheap.users.getPricing in auto mode, or select api to require API access.
  • With the default auto mode, incomplete API credentials cause Namecheap to use the connected MCP server. If neither integration is configured, a sync fails with setup guidance. Explicit api mode requires complete API credentials and does not use MCP.
  • providers.cloudflare defaults to auto and accepts api/auto (auto is an alias for api); there is no Cloudflare registrar MCP path, so mcp is rejected with a clear error. A browser-login path would require the Cloudflare API MCP server through another connector.

Tools

ToolPurpose
domain_manager_listList domains; filter by registrar, group, autoRenew, dueWithinDays
domain_manager_reportYearly cost (plus ICANN fees and an all-in figure), auto-renew on/off split, per-registrar subtotals, expiring soon
domain_manager_addAdd a domain (name, registrar, expires; optional price, ICANN fee and metadata)
domain_manager_updateChange expiry, price, ICANN fee, auto-renew, registrar, currency, purpose, group, promo, notes
domain_manager_removeRemove a domain from the local portfolio
domain_manager_syncSync 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_availabilityCheck 1–20 domains; provider: auto, mcp, cloudflare, namecheap, rdap; fallback warnings are included in the result
domain_manager_searchCloudflare domain-search candidates for a keyword
domain_manager_transfer_checkCheck 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_startSubmit a paid, non-refundable transfer after explicit user approval (confirm: true and the checked quoteId); recheck eligibility and pricing
domain_manager_transfer_statusRefresh 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_markdownImport portfolio tables from a markdown file (upsert or replace)
domain_manager_export_markdownRender 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.

  1. Sync or add the domain to the portfolio with registrar: Namecheap.
  2. 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.
  3. In Namecheap Domain List → Manage → Sharing & Transfer, unlock the domain and request the EPP/authorization code by email. For .uk namespaces, 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.
  4. Call domain_manager_transfer_check with name and the raw authCode (not base64). Optional autoRenew defaults to the current portfolio setting. The result includes a quoteId, transfer fee, annual renewal price, auto-renew choice and eligibility reasons such as domain_locked, dnssec_enabled or zone_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 still pending. 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 same quoteId (mentioned in message) and refreshes the check timestamp, so a previous approval stays valid.
  5. 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 confirmationPrompt containing 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 call domain_manager_transfer_start with name, the same quoteId, confirm: true and 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.
  6. Approve any registrar emails and call domain_manager_transfer_status as needed. Transfers typically take 1–10 days; Namecheap may offer expedited release. action_required means finish the returned action before checking again; blocked can require the losing registrar or registry to respond. Results use plain-language stage labels (for example submitted, 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_start call. Transfers never automatically change DNS or unlock the source domain.
  • domain_manager_check_availability only 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:

  1. Ensure your npm account can publish packages under the beings-labs scope.
  2. Create a short-lived granular npm access token with Read and write (publish and stage) access to the beings-labs package scope and Bypass two-factor authentication enabled for unattended publishing. Organization-management permissions alone do not grant package-publishing access.
  3. Add it as the repository's Actions secret named NPM_TOKEN. Never put the token in source files or release notes.
  4. Run Publish to npm from the main branch with the tag input set to v0.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:

SettingValue
ProviderGitHub Actions
Organization or userbeings-labs
Repositorydsh-domain-manager
Workflow filenamepublish.yml
Environment nameLeave empty
Allowed actionsEnable 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

星级评价

0.0 按 GitHub Stars 换算的社区热度,非用户打分
Stars0
Forks0
待解决 Issue0
最近更新2026-11-11

基本信息

作者beings-labs
版本0.2.0
开源许可MIT
默认分支main
入库时间2026-11-11

插件信息来自公开 GitHub 仓库与 SkillHub,版权归原作者所有;安装前请自行审查仓库代码。