Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

### Added

- `DomainHunt` skill with companions (`TldHacks`, `BrokerDetection`, `RegistrarGuide`, `BrandFamilyPairing`) — RDAP-based domain availability workflow, broker-listing detection, brand-family pairing strategy, registrar selection guidance
- `bin/check-domain` — bundled RDAP-based availability checker (bash + curl + python3), source: <https://github.com/N4M3Z/check-domain>
- `DEV-0005 Forge Ecosystem Domain Identity` ADR capturing the decision to anchor on `forgeworld.ai` with `modforge.*` and `handforged.*` sub-brands
- LICENSE (EUPL-1.2), CONTRIBUTING.md, CODEOWNERS, INSTALL.md
- `.gitattributes`, `.gitleaks.toml`, `.gitleaksignore`
- `.pre-commit-config.yaml` + `.githooks/pre-commit` with forge-cli validate.sh fallback
Expand Down
199 changes: 199 additions & 0 deletions bin/check-domain
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#!/bin/bash
# check-domain — Batch domain availability via RDAP

set -euo pipefail

readonly BOOTSTRAP_URL="https://data.iana.org/rdap/dns.json"
readonly CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/check-domain"
readonly BOOTSTRAP_CACHE="$CACHE_DIR/rdap-bootstrap.json"
readonly MAPPING_CACHE="$CACHE_DIR/tld-rdap-map.txt"
readonly BOOTSTRAP_TTL_SECONDS=86400
readonly CURL_TIMEOUT=12

usage() {
local prog
prog=$(basename "$0")
cat <<HELP
Usage: $prog [options] <domain> [<domain>...]
$prog [options] -f <file>

Checks domain availability via RDAP queries against authoritative registries.

Output per domain:
AVAIL Domain is not registered (RDAP 404)
TAKEN Domain is registered (RDAP 200)
HTTP-XXX Registry returned unexpected status
NO-RDAP No public RDAP endpoint known for this TLD

Options:
-h, --help Show this help and exit
-q, --quiet Print only AVAIL results
-f, --file Read domains from file (one per line, # comments allowed)

Examples:
$prog example.com example.dev
$prog forge.{ai,dev,io,build,tools}
$prog -q -f candidates.txt

Cache: $CACHE_DIR (TTL ${BOOTSTRAP_TTL_SECONDS}s)
HELP
}

file_mtime() {
stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null
}

cache_fresh() {
[ -f "$MAPPING_CACHE" ] || return 1
local now mtime
now=$(date +%s)
mtime=$(file_mtime "$MAPPING_CACHE")
[ -n "$mtime" ] || return 1
[ $((now - mtime)) -lt "$BOOTSTRAP_TTL_SECONDS" ]
}

refresh_bootstrap() {
mkdir -p "$CACHE_DIR"
if ! curl -sf --max-time 15 "$BOOTSTRAP_URL" -o "$BOOTSTRAP_CACHE.tmp"; then
echo "warning: could not fetch IANA RDAP bootstrap from $BOOTSTRAP_URL" >&2
rm -f "$BOOTSTRAP_CACHE.tmp"
return 1
fi
mv "$BOOTSTRAP_CACHE.tmp" "$BOOTSTRAP_CACHE"
if ! python3 - "$BOOTSTRAP_CACHE" > "$MAPPING_CACHE.tmp" <<'PY'
import json, sys
with open(sys.argv[1]) as handle:
bootstrap = json.load(handle)
for service in bootstrap.get("services", []):
suffixes, endpoints = service[0], service[1]
if not endpoints:
continue
primary = endpoints[0]
for suffix in suffixes:
print(f"{suffix} {primary}")
PY
then
rm -f "$MAPPING_CACHE.tmp"
echo "error: failed to parse IANA RDAP bootstrap" >&2
return 1
fi
mv "$MAPPING_CACHE.tmp" "$MAPPING_CACHE"
}

ensure_bootstrap() {
if cache_fresh; then
return 0
fi
if refresh_bootstrap; then
return 0
fi
[ -f "$MAPPING_CACHE" ] && return 0
return 1
}

rdap_base_for_tld() {
awk -v tld="$1" '$1 == tld { print $2; exit }' "$MAPPING_CACHE"
}

check_one() {
local domain=$1 quiet=$2
local tld="${domain##*.}"
local base url code
base=$(rdap_base_for_tld "$tld")
if [ -z "$base" ]; then
[ "$quiet" -eq 0 ] && printf "%-30s NO-RDAP\n" "$domain"
return
fi
url="${base%/}/domain/$domain"
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$CURL_TIMEOUT" "$url" 2>/dev/null || echo "000")
case "$code" in
404)
printf "%-30s AVAIL\n" "$domain"
;;
200)
[ "$quiet" -eq 0 ] && printf "%-30s TAKEN\n" "$domain"
;;
*)
[ "$quiet" -eq 0 ] && printf "%-30s HTTP-%s\n" "$domain" "$code"
;;
esac
}

read_domains_from_file() {
local file=$1
[ -r "$file" ] || { echo "error: cannot read file: $file" >&2; exit 2; }
local line
while IFS= read -r line || [ -n "$line" ]; do
line="${line%%#*}"
line="${line//[[:space:]]/}"
[ -n "$line" ] && echo "$line"
done < "$file"
}

main() {
local quiet=0
local -a domains=()
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-q|--quiet)
quiet=1
shift
;;
-f|--file)
shift
[ $# -eq 0 ] && { echo "error: -f requires a file argument" >&2; exit 2; }
while IFS= read -r d; do
domains+=("$d")
done < <(read_domains_from_file "$1")
shift
;;
--)
shift
while [ $# -gt 0 ]; do
domains+=("$1")
shift
done
;;
-*)
echo "error: unknown option: $1" >&2
usage >&2
exit 2
;;
*)
domains+=("$1")
shift
;;
esac
done

if [ ${#domains[@]} -eq 0 ]; then
usage >&2
exit 2
fi

if ! command -v python3 >/dev/null 2>&1; then
echo "error: python3 is required (used to parse IANA RDAP bootstrap)" >&2
exit 1
fi

if ! command -v curl >/dev/null 2>&1; then
echo "error: curl is required" >&2
exit 1
fi

if ! ensure_bootstrap; then
echo "error: no RDAP bootstrap mapping available" >&2
exit 1
fi

for d in "${domains[@]}"; do
check_one "$d" "$quiet" &
done
wait
}

main "$@"
96 changes: 96 additions & 0 deletions docs/decisions/DEV-0005 Forge Ecosystem Domain Identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
title: Forge Ecosystem Domain Identity
description: Choose a brand-family of domains for the forge ecosystem (apex + sub-brands + thematic TLDs) without paying broker premiums or premium-tier traps
type: adr
category: governance
tags:
- governance
- branding
- domains
status: accepted
created: 2026-05-13
updated: 2026-05-13
author: "@N4M3Z"
project: forge-dev
related: []
responsible: ["@N4M3Z"]
accountable: ["@N4M3Z"]
consulted: []
informed: []
upstream: [DomainHunt]
---

# Forge Ecosystem Domain Identity

## Context and Problem Statement

The forge ecosystem (CLI binary, library, ~30 published modules, public documentation) needs a coherent web-property identity. The natural apex `forge.ai` is held by a private 2017 GoDaddy registration with no live site — broker-acquirable but not at base price (typical short `.ai` resale ranges $5k-$50k). Every follow-on candidate explored — `forge.dev`, `forge.io`, `forge.app`, every `forgehq.ai` / `forgekit.ai` / `useforge.ai` modifier — is taken. The technically-available `forge.build` is priced as a premium-tier `.build` registration at $1,500.

The ecosystem needs a pronounceable apex, room for product-line sub-brands, a multi-TLD family totalling under $200/yr, and a trademark posture compatible with public publishing.

## Decision Drivers

- Brand alignment with the ecosystem's purpose (module-building, agent forging, skill authoring)
- Available without paying broker premium ($5k-$50k typical for `.ai` shorts)
- Available without premium-tier TLD traps ($500+ for short names on `.build`, `.gold`, `.cars`)
- Coherent multi-property family supporting product-line differentiation
- No live trademark conflicts in the AI-tooling sector
- Affordable annual renewal across the bundle

## Considered Options

- **Acquire `forge.ai` from broker** — pay $5k-$50k+ for the canonical apex
- **Buy `forge.build`** — premium-tier `.build` at $1,500 first-year
- **Single-modifier `.ai`** (`forgehq.ai`, `forgekit.ai`, `useforge.ai`, `agentforge.ai`) — every variant probed, all taken
- **Latin or mythological alternative** — `caminus.dev` (literal Latin "forge"), `mulciber.ai` (Vulcan's epithet); both available
- **Foreign-language smithy/anvil** — `kovarna.ai` (Czech smithy), `bigorna.ai` (Portuguese anvil), `enclume.ai` (French anvil)
- **Compound on `.ai` with a thematic apex word** — `forgeworld.ai`, `modforge.ai`, `handforged.ai`; all available
- **TLD-hack apex** — `for.ge` was cleanest but held by a Georgian non-profit since 2011

## Decision Outcome

Chosen: **a multi-property brand family centered on `forgeworld.ai` as the public apex, with `modforge.*` and `handforged.*` as sub-brand product lines.**

`forgeworld.ai` is the marketing apex. The Forge World name resonates in two directions — as Games Workshop's specialist miniature studio and as the in-universe Adeptus Mechanicus planet class that houses imperial weapons forges. Both senses imply specialist craft and deliberate construction, which matches an AI tooling ecosystem that builds skills, agents, and modules with care.

`modforge.ai` plus `modforge.dev` plus `modforge.world` carries the module-ecosystem sub-brand. "Mod-forge" directly describes what the forge ecosystem produces: composable modules of skills, agents, and rules. The Minecraft "ModForge" modding loader is a collision but in a fully separate audience.

`handforged.ai` is reserved for hand-curated, premium-quality skill and agent libraries. "Hand-forged" reads artisanal and counterpoints `modforge` (which connotes batch production) with quality framing.

Rejected:

- **Broker acquisition of `forge.ai`** — the price-to-value ratio over `forgeworld.ai` does not justify the broker tax
- **`forge.build` at $1,500** — premium-tier trap; the same brand identity assembles coherently on standard-tier TLDs for under $200/yr total
- **Foreign-language alternatives** (caminus, mulciber, kovarna) — strong individually but lose audience clarity in the predominantly English AI-tooling discourse
- **`for.ge` TLD hack** — owned by an active Georgian non-profit (Union Press); low practical acquisition odds

### Trademark posture

"Forge World" is a registered trademark of Games Workshop Group plc, scoped to miniature wargaming and adjacent merchandise. Practical risk is low: Games Workshop has pursued trademark adjacency in its own sector (the [Space Marine](https://en.wikipedia.org/wiki/Space_Marine_trademark_dispute) dispute being the high-water mark) but has not pursued cross-sector uses of "Forge World" as a phrase. AI tooling is fully outside the trademark's covered sector.

Acceptable risk; documented.

### Acquisition plan

| Domain | Registrar (preferred) | Approx price/yr |
| --------------------- | ------------------------------ | ---------------- |
| `forgeworld.ai` | Cloudflare or Porkbun | ~$80-100 |
| `modforge.ai` | Cloudflare or Porkbun | ~$80-100 |
| `modforge.dev` | Cloudflare | ~$15-20 |
| `modforge.world` | Porkbun or 101domain | ~$25-30 |
| `handforged.ai` | Cloudflare or Porkbun | ~$80-100 |

Total annual: ~$280-350 across the family.

## Consequences

- [+] Coherent multi-domain brand family totalling under $350/yr — affordable defense and product-line space
- [+] Apex plus sub-brand structure supports product and persona differentiation without paying broker premiums
- [+] No broker tax, no premium-tier trap
- [+] Pronounceable, memorable across the family
- [+] Subdomain expansion on each apex (e.g. `ai.forgeworld.ai`, `docs.modforge.dev`) extends the family at zero additional registration cost
- [-] Mild Games Workshop trademark adjacency on `forgeworld.ai` (low practical risk given sector separation)
- [-] `modforge` collides with the Minecraft modding loader of the same name (different audience, low practical confusion)
- [-] Multi-domain family requires DNS discipline — pick a primary registrar and manage uniformly to avoid renewal drift

Future TLDs in this family inherit the same pattern. Decisions to deviate (broker acquisitions, premium-tier purchases, new sub-brand additions) belong in subsequent ADRs.
66 changes: 66 additions & 0 deletions skills/DomainHunt/BrandFamilyPairing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Brand Family Pairing

A single domain rarely solves a brand-identity problem. Pairing creates resilience, sub-brand space, and zero-cost expansion through subdomains.

## The apex + companions pattern

Pick one strong apex on a primary TLD, then add sibling TLDs as namespaced product lines:

| Property | Role | TLD selected for |
| ------------------------------ | ------------------------------------------------- | ------------------------- |
| Apex (e.g. `example.ai`) | Primary marketing site, user-facing identity | Brand strength + AI signal|
| Companion 1 (e.g. `example.dev`) | Developer docs, CLI / SDK download page | Dev signal, cheap |
| Companion 2 (e.g. `example.world`) | Thematic extension, persona sub-brand | Vibe pairing |
| Companion 3 (e.g. `example.md`) | Canonical documentation / specification surface | Markdown signal |

A coherent 3-property bundle on mid-tier TLDs typically totals under $150/yr — cheaper than one premium single domain and reads as a richer brand.

## Subdomain expansion

Subdomains on an apex you already own cost nothing. After registering `example.ai`, you have:

| Subdomain | Common purpose |
| ----------------------- | ---------------------------------------------------- |
| `docs.example.ai` | Documentation |
| `api.example.ai` | REST surface |
| `ai.example.ai` | Themed sub-product — fakes the `ai.example` visual |
| `cli.example.ai` | Download / installation page |
| `blog.example.ai` | Editorial content |
| `<user>.example.ai` | Per-user instance (wildcard DNS) |

A four-part DNS name reads as a real product surface. Use subdomains liberally before buying another TLD.

## Defensive registration

For a brand you intend to publish under, register the obvious neighbors even when you don't plan to use them:

| Defensive target | Why |
| ----------------------------- | ------------------------------------------------------------------------------ |
| Plural / singular | `example.ai` plus `examples.ai` |
| Adjacent dev TLDs | If your apex is `.ai`, also grab `.dev` and `.app` |
| Hyphenated variant | `example-ai.com` for a `.ai` apex |
| Common close-typos | Rarely worth it unless under active impersonation |

Rule of thumb: one apex plus one-to-two defensive registrations is reasonable. Five-plus defensive registrations is bikeshedding unless you're a high-trust brand under active impersonation.

## When to walk away

A brand-family search is failing when:

- Every meaningful TLD is taken, premium-priced, or on a broker
- Adjacent typos are taken by speculators
- The apex name itself triggers a real trademark concern in your sector

Walking away early is cheaper than walking away after registering a partial family. Either rename the brand or scope down to one TLD and lean on subdomains for everything else.

## Trademark posture

Three reasonable defaults when a candidate brand collides with an existing trademark:

| Posture | When to apply |
| --------------------- | ---------------------------------------------------------------------------------------------------------- |
| Avoid entirely | Trademark holder is litigious AND your product overlaps their sector |
| Acknowledge and proceed | Trademark holder operates in a fully unrelated sector and the term is sector-specific within that domain |
| Acquire defensively | The collision is in your sector but the original holder is dormant, willing to sell, or unenforcing |

The most common mistake is treating trademark risk as binary. A small AI tool sharing a name with a niche miniature line is not the same risk profile as a startup branding itself after a Fortune 500 product. Read the trademark filing — most are sector-scoped — before deciding the name is unusable.
Loading
Loading