imprint-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: search_namecheap_domains
|
|
5
|
+
* Site: namecheap-domains
|
|
6
|
+
* Intent: Search Namecheap for available domains matching a query, returning the exact-match availability + pricing, suggested domain picks across many TLDs, the TLD catalog, and Spaceship/brandstore aftermarket marketplace listings.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/namecheap-domains/search_namecheap_domains/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "search_namecheap_domains",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Search Namecheap for available domains matching a query, returning the exact-match availability + pricing, suggested domain picks across many TLDs, the TLD catalog, and Spaceship/brandstore aftermarket marketplace listings.",
|
|
23
|
+
"userSaid": "i searched for imprint on namecheap. njow there are a lot of filters, by default i am on \"Popular\". there were about 30 filters. i cycled through all of them"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "query",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The SLD (second-level-domain) or search term to look up (e.g. 'imprint').",
|
|
30
|
+
"default": "imprint"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "category",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Optional TLD category filter to narrow returned suggestions. One of the CategoryName values exposed by the TLD catalog: 'popular', 'international', 'new', '088domains'. Leave empty for unfiltered results.",
|
|
36
|
+
"default": ""
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"requests": [
|
|
40
|
+
{
|
|
41
|
+
"method": "GET",
|
|
42
|
+
"url": "https://www.namecheap.com/domains/tlds.ashx",
|
|
43
|
+
"headers": {
|
|
44
|
+
"Accept": "application/json, text/plain, */*",
|
|
45
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
46
|
+
"Referer": "https://www.namecheap.com/domains/registration/results/?domain=${param.query}",
|
|
47
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
48
|
+
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
|
49
|
+
"sec-ch-ua-mobile": "?0",
|
|
50
|
+
"sec-ch-ua-platform": "\"macOS\""
|
|
51
|
+
},
|
|
52
|
+
"effect": "safe"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"method": "GET",
|
|
56
|
+
"url": "https://rtb.namecheapapi.com/api/search/${param.query}?session_id=1000000000000&search=false",
|
|
57
|
+
"headers": {
|
|
58
|
+
"Accept": "application/json, text/plain, */*",
|
|
59
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
60
|
+
"Origin": "https://www.namecheap.com",
|
|
61
|
+
"Referer": "https://www.namecheap.com/",
|
|
62
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
63
|
+
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
|
64
|
+
"sec-ch-ua-mobile": "?0",
|
|
65
|
+
"sec-ch-ua-platform": "\"macOS\""
|
|
66
|
+
},
|
|
67
|
+
"effect": "safe"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"method": "GET",
|
|
71
|
+
"url": "https://rtb.namecheapapi.com/api/picks/${param.query}?session_id=1000000000000",
|
|
72
|
+
"headers": {
|
|
73
|
+
"Accept": "application/json, text/plain, */*",
|
|
74
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
75
|
+
"Origin": "https://www.namecheap.com",
|
|
76
|
+
"Referer": "https://www.namecheap.com/",
|
|
77
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
78
|
+
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
|
79
|
+
"sec-ch-ua-mobile": "?0",
|
|
80
|
+
"sec-ch-ua-platform": "\"macOS\""
|
|
81
|
+
},
|
|
82
|
+
"effect": "safe"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"method": "POST",
|
|
86
|
+
"url": "https://www.namecheap.com/api/v1/ncpl/domainsearchgateway/domain/readBySld",
|
|
87
|
+
"headers": {
|
|
88
|
+
"Accept": "application/json, text/plain, */*",
|
|
89
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
90
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
91
|
+
"Origin": "https://www.namecheap.com",
|
|
92
|
+
"Referer": "https://www.namecheap.com/domains/registration/results/?domain=${param.query}",
|
|
93
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
94
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
95
|
+
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
|
96
|
+
"sec-ch-ua-mobile": "?0",
|
|
97
|
+
"sec-ch-ua-platform": "\"macOS\""
|
|
98
|
+
},
|
|
99
|
+
"body": "{\"request\":{\"sld\":\"${param.query}\",\"sources\":[\"sellerhub\",\"brandstore\"]}}",
|
|
100
|
+
"effect": "safe"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"method": "GET",
|
|
104
|
+
"url": "https://domains.revved.com/v1/domainStatus?whois=true&domains=__PLACEHOLDER__",
|
|
105
|
+
"headers": {
|
|
106
|
+
"Accept": "application/json, text/plain, */*",
|
|
107
|
+
"Referer": "https://www.namecheap.com/"
|
|
108
|
+
},
|
|
109
|
+
"effect": "safe"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"site": "namecheap-domains",
|
|
113
|
+
"parserModule": "./parser.ts",
|
|
114
|
+
"requestTransformModule": "./request-transform.ts"
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export interface SearchNamecheapDomainsInput {
|
|
118
|
+
/** The SLD (second-level-domain) or search term to look up (e.g. 'imprint'). */
|
|
119
|
+
query?: string;
|
|
120
|
+
/** Optional TLD category filter to narrow returned suggestions. One of the CategoryName values exposed by the TLD catalog: 'popular', 'international', 'new', '088domains'. Leave empty for unfiltered results. */
|
|
121
|
+
category?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function searchNamecheapDomains(
|
|
125
|
+
input: SearchNamecheapDomainsInput,
|
|
126
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
127
|
+
): Promise<ToolResult> {
|
|
128
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
129
|
+
const params: Record<string, string | number | boolean> = {
|
|
130
|
+
query: input.query ?? "imprint",
|
|
131
|
+
category: input.category ?? "",
|
|
132
|
+
|
|
133
|
+
};
|
|
134
|
+
return executeWorkflow({
|
|
135
|
+
workflow: WORKFLOW,
|
|
136
|
+
params,
|
|
137
|
+
credentials: opts.credentials,
|
|
138
|
+
fetchImpl: opts.fetchImpl,
|
|
139
|
+
initialState: opts.initialState,
|
|
140
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for the Namecheap domain-search workflow.
|
|
3
|
+
*
|
|
4
|
+
* Input (context.responses):
|
|
5
|
+
* [0] tlds.ashx → TLD catalog with category & pricing metadata
|
|
6
|
+
* [1] /api/search/{sld} → exact_match + first 5 picks + 25 ranks
|
|
7
|
+
* [2] /api/picks/{sld} → expanded picks with aftermarket pricing
|
|
8
|
+
* [3] /readBySld → marketplace listings (Items[]) from Spaceship
|
|
9
|
+
* [4] /v1/domainStatus → availability + whois for all suggested domains
|
|
10
|
+
*
|
|
11
|
+
* Returns a clean object that merges all four sources into agent-usable shape.
|
|
12
|
+
* If `params.category` is provided, picks/ranks are filtered to TLDs that
|
|
13
|
+
* carry that CategoryName in the TLD catalog.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface TldCategory {
|
|
17
|
+
CategoryName: string;
|
|
18
|
+
SeqNoOfProduct?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TldPricing {
|
|
22
|
+
Price?: number;
|
|
23
|
+
Regular?: number;
|
|
24
|
+
Renewal?: number;
|
|
25
|
+
Hint?: string;
|
|
26
|
+
Duration?: number;
|
|
27
|
+
DurationType?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface TldEntry {
|
|
31
|
+
Name: string;
|
|
32
|
+
Type?: string;
|
|
33
|
+
Categories?: TldCategory[] | null;
|
|
34
|
+
Pricing?: TldPricing;
|
|
35
|
+
TldsState?: string;
|
|
36
|
+
WhoisguardCompatibile?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SearchAftermarket {
|
|
40
|
+
domain?: string;
|
|
41
|
+
fast_transfer?: boolean;
|
|
42
|
+
price?: number;
|
|
43
|
+
status?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
username?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SearchStatus {
|
|
49
|
+
available?: boolean;
|
|
50
|
+
lookupType?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
premium?: boolean;
|
|
53
|
+
reason?: string;
|
|
54
|
+
whois?: { createdYear?: number };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface SearchPick {
|
|
58
|
+
domain: string;
|
|
59
|
+
tld: string;
|
|
60
|
+
type?: string;
|
|
61
|
+
priority?: number;
|
|
62
|
+
info?: string;
|
|
63
|
+
enable_cart_verification?: boolean;
|
|
64
|
+
aftermarket?: SearchAftermarket;
|
|
65
|
+
status?: SearchStatus;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface SearchResponse {
|
|
69
|
+
exact_match?: {
|
|
70
|
+
domain?: string;
|
|
71
|
+
tld?: string;
|
|
72
|
+
is_supported?: boolean;
|
|
73
|
+
enable_cart_verification?: boolean;
|
|
74
|
+
campaignType?: string | null;
|
|
75
|
+
status?: SearchStatus;
|
|
76
|
+
};
|
|
77
|
+
picks?: SearchPick[];
|
|
78
|
+
ranks?: Array<{ domain: string; tld: string; enable_cart_verification?: boolean }>;
|
|
79
|
+
hasNextPage?: boolean;
|
|
80
|
+
type?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface PicksResponse {
|
|
84
|
+
type?: string;
|
|
85
|
+
picks?: SearchPick[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface MarketplaceMoney {
|
|
89
|
+
OriginalAmount?: number;
|
|
90
|
+
Amount?: number;
|
|
91
|
+
Currency?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface MarketplaceSource {
|
|
95
|
+
EntryPoints?: Record<string, string | null>;
|
|
96
|
+
DomainDisplayName?: string;
|
|
97
|
+
LogoUrl?: string | null;
|
|
98
|
+
BuyItNow?: MarketplaceMoney | null;
|
|
99
|
+
MinOffer?: MarketplaceMoney | null;
|
|
100
|
+
LeaseToOwn?: {
|
|
101
|
+
DownPayment?: MarketplaceMoney | null;
|
|
102
|
+
InstallmentPayment?: { Amount?: MarketplaceMoney; Cycles?: number } | null;
|
|
103
|
+
FinalPayment?: MarketplaceMoney | null;
|
|
104
|
+
Total?: MarketplaceMoney | null;
|
|
105
|
+
} | null;
|
|
106
|
+
Description?: string | null;
|
|
107
|
+
Keywords?: string[] | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface MarketplaceItem {
|
|
111
|
+
DomainName: string;
|
|
112
|
+
DomainDisplayName?: string;
|
|
113
|
+
Tld?: string;
|
|
114
|
+
Sld?: string;
|
|
115
|
+
DomainHasSalePageEnabled?: boolean;
|
|
116
|
+
ResponseStatus?: string;
|
|
117
|
+
EntryPoints?: Record<string, string | null>;
|
|
118
|
+
Source?: { brandStore?: MarketplaceSource | null; sellerHub?: MarketplaceSource | null };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface MarketplaceResponse {
|
|
122
|
+
Items?: MarketplaceItem[];
|
|
123
|
+
TotalCount?: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildTldIndex(tlds: TldEntry[]): Map<string, TldEntry> {
|
|
127
|
+
const map = new Map<string, TldEntry>();
|
|
128
|
+
for (const t of tlds) {
|
|
129
|
+
if (t && typeof t.Name === 'string') map.set(t.Name.toLowerCase(), t);
|
|
130
|
+
}
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function enrichDomain(
|
|
135
|
+
pick: SearchPick | { domain: string; tld: string; enable_cart_verification?: boolean },
|
|
136
|
+
tldIndex: Map<string, TldEntry>,
|
|
137
|
+
statusByDomain: Map<string, { available: boolean; reason?: string; createdYear?: number; registrar?: string }>,
|
|
138
|
+
) {
|
|
139
|
+
const tldKey = (pick.tld || '').toLowerCase();
|
|
140
|
+
const tldMeta = tldIndex.get(tldKey);
|
|
141
|
+
const aftermarket = (pick as SearchPick).aftermarket;
|
|
142
|
+
const aftermarketActive = !!(aftermarket && aftermarket.status && aftermarket.domain);
|
|
143
|
+
const domainStatus = statusByDomain.get((pick.domain || '').toLowerCase());
|
|
144
|
+
|
|
145
|
+
const available = domainStatus?.available ?? null;
|
|
146
|
+
const isPremium = aftermarketActive && (aftermarket?.price ?? 0) > 0;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
domain: pick.domain,
|
|
150
|
+
tld: pick.tld,
|
|
151
|
+
available,
|
|
152
|
+
is_premium: isPremium,
|
|
153
|
+
registered_year: domainStatus?.createdYear ?? null,
|
|
154
|
+
registrar: domainStatus?.registrar ?? null,
|
|
155
|
+
status_reason: domainStatus?.reason ?? null,
|
|
156
|
+
aftermarket: aftermarketActive
|
|
157
|
+
? {
|
|
158
|
+
domain: aftermarket?.domain,
|
|
159
|
+
price: aftermarket?.price,
|
|
160
|
+
status: aftermarket?.status,
|
|
161
|
+
type: aftermarket?.type,
|
|
162
|
+
fast_transfer: aftermarket?.fast_transfer,
|
|
163
|
+
}
|
|
164
|
+
: null,
|
|
165
|
+
registration_price: available !== false && tldMeta?.Pricing
|
|
166
|
+
? {
|
|
167
|
+
price: tldMeta.Pricing.Price ?? null,
|
|
168
|
+
regular: tldMeta.Pricing.Regular ?? null,
|
|
169
|
+
renewal: tldMeta.Pricing.Renewal ?? null,
|
|
170
|
+
hint: tldMeta.Pricing.Hint ?? null,
|
|
171
|
+
}
|
|
172
|
+
: null,
|
|
173
|
+
categories: (tldMeta?.Categories ?? []).map((c) => c.CategoryName),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function pickHasCategory(tld: string, category: string, tldIndex: Map<string, TldEntry>): boolean {
|
|
178
|
+
const meta = tldIndex.get(tld.toLowerCase());
|
|
179
|
+
if (!meta || !Array.isArray(meta.Categories)) return false;
|
|
180
|
+
const target = category.toLowerCase();
|
|
181
|
+
return meta.Categories.some((c) => (c.CategoryName || '').toLowerCase() === target);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function dedupeByDomain<T extends { domain: string }>(items: T[]): T[] {
|
|
185
|
+
const seen = new Set<string>();
|
|
186
|
+
const out: T[] = [];
|
|
187
|
+
for (const item of items) {
|
|
188
|
+
const key = (item.domain || '').toLowerCase();
|
|
189
|
+
if (!key || seen.has(key)) continue;
|
|
190
|
+
seen.add(key);
|
|
191
|
+
out.push(item);
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mergePicks(
|
|
197
|
+
searchPicks: SearchPick[],
|
|
198
|
+
picksDetail: SearchPick[],
|
|
199
|
+
): SearchPick[] {
|
|
200
|
+
// Index detail picks by domain so we can prefer the richer record.
|
|
201
|
+
const detailIndex = new Map<string, SearchPick>();
|
|
202
|
+
for (const p of picksDetail) {
|
|
203
|
+
if (p && p.type === 'domain' && p.domain) detailIndex.set(p.domain.toLowerCase(), p);
|
|
204
|
+
}
|
|
205
|
+
const merged: SearchPick[] = [];
|
|
206
|
+
const seen = new Set<string>();
|
|
207
|
+
for (const p of searchPicks) {
|
|
208
|
+
if (!p || !p.domain) continue;
|
|
209
|
+
const key = p.domain.toLowerCase();
|
|
210
|
+
seen.add(key);
|
|
211
|
+
const detail = detailIndex.get(key);
|
|
212
|
+
merged.push(detail ? { ...p, ...detail } : p);
|
|
213
|
+
}
|
|
214
|
+
// Add any picksDetail entries not already represented (e.g. from /api/picks).
|
|
215
|
+
for (const [key, p] of detailIndex.entries()) {
|
|
216
|
+
if (!seen.has(key)) merged.push(p);
|
|
217
|
+
}
|
|
218
|
+
return merged;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function summariseMarketplace(item: MarketplaceItem) {
|
|
222
|
+
const sources: Array<{
|
|
223
|
+
name: string;
|
|
224
|
+
entry_points: Record<string, string | null>;
|
|
225
|
+
buy_it_now: number | null;
|
|
226
|
+
min_offer: number | null;
|
|
227
|
+
currency: string | null;
|
|
228
|
+
description: string | null;
|
|
229
|
+
keywords: string[] | null;
|
|
230
|
+
lease_to_own: {
|
|
231
|
+
down_payment: number | null;
|
|
232
|
+
installment: { amount: number | null; cycles: number | null } | null;
|
|
233
|
+
total: number | null;
|
|
234
|
+
} | null;
|
|
235
|
+
}> = [];
|
|
236
|
+
const src = item.Source ?? {};
|
|
237
|
+
for (const [name, info] of Object.entries(src)) {
|
|
238
|
+
if (!info) continue;
|
|
239
|
+
const lto = info.LeaseToOwn ?? null;
|
|
240
|
+
sources.push({
|
|
241
|
+
name,
|
|
242
|
+
entry_points: info.EntryPoints ?? {},
|
|
243
|
+
buy_it_now: info.BuyItNow?.Amount ?? null,
|
|
244
|
+
min_offer: info.MinOffer?.Amount ?? null,
|
|
245
|
+
currency:
|
|
246
|
+
info.BuyItNow?.Currency ??
|
|
247
|
+
info.MinOffer?.Currency ??
|
|
248
|
+
info.LeaseToOwn?.Total?.Currency ??
|
|
249
|
+
null,
|
|
250
|
+
description: info.Description ?? null,
|
|
251
|
+
keywords: info.Keywords ?? null,
|
|
252
|
+
lease_to_own: lto
|
|
253
|
+
? {
|
|
254
|
+
down_payment: lto.DownPayment?.Amount ?? null,
|
|
255
|
+
installment: lto.InstallmentPayment
|
|
256
|
+
? {
|
|
257
|
+
amount: lto.InstallmentPayment.Amount?.Amount ?? null,
|
|
258
|
+
cycles: lto.InstallmentPayment.Cycles ?? null,
|
|
259
|
+
}
|
|
260
|
+
: null,
|
|
261
|
+
total: lto.Total?.Amount ?? null,
|
|
262
|
+
}
|
|
263
|
+
: null,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
domain: item.DomainName,
|
|
268
|
+
tld: item.Tld,
|
|
269
|
+
sld: item.Sld,
|
|
270
|
+
sale_page_enabled: !!item.DomainHasSalePageEnabled,
|
|
271
|
+
entry_points: item.EntryPoints ?? {},
|
|
272
|
+
sources,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function extract(
|
|
277
|
+
rawResponse: unknown,
|
|
278
|
+
context?: {
|
|
279
|
+
params: Record<string, string | number | boolean>;
|
|
280
|
+
responses: unknown[];
|
|
281
|
+
},
|
|
282
|
+
): unknown {
|
|
283
|
+
const responses = context?.responses ?? [rawResponse];
|
|
284
|
+
const params = context?.params ?? {};
|
|
285
|
+
const query = String(params.query ?? '').trim();
|
|
286
|
+
const category = String(params.category ?? '').trim().toLowerCase();
|
|
287
|
+
|
|
288
|
+
const tldsRaw = responses[0];
|
|
289
|
+
const searchRaw = responses[1] as SearchResponse | undefined;
|
|
290
|
+
const picksRaw = responses[2] as PicksResponse | undefined;
|
|
291
|
+
const marketplaceRaw = responses[3] as MarketplaceResponse | undefined;
|
|
292
|
+
const domainStatusRaw = responses[4] as {
|
|
293
|
+
status?: Array<{
|
|
294
|
+
name: string;
|
|
295
|
+
available: boolean;
|
|
296
|
+
lookupType?: string;
|
|
297
|
+
reason?: string;
|
|
298
|
+
whois?: { createdYear?: number };
|
|
299
|
+
extra?: { createdYear?: number; registrar?: string; extensionsTaken?: number; ns?: string[] };
|
|
300
|
+
}>;
|
|
301
|
+
} | undefined;
|
|
302
|
+
|
|
303
|
+
const statusByDomain = new Map<string, { available: boolean; reason?: string; createdYear?: number; registrar?: string }>();
|
|
304
|
+
for (const s of domainStatusRaw?.status ?? []) {
|
|
305
|
+
statusByDomain.set(s.name.toLowerCase(), {
|
|
306
|
+
available: s.available,
|
|
307
|
+
reason: s.reason,
|
|
308
|
+
createdYear: s.whois?.createdYear ?? s.extra?.createdYear,
|
|
309
|
+
registrar: s.extra?.registrar,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const tldList: TldEntry[] = Array.isArray(tldsRaw) ? (tldsRaw as TldEntry[]) : [];
|
|
314
|
+
const tldIndex = buildTldIndex(tldList);
|
|
315
|
+
|
|
316
|
+
const exactMatchRaw = searchRaw?.exact_match ?? null;
|
|
317
|
+
const exactMatch = exactMatchRaw
|
|
318
|
+
? (() => {
|
|
319
|
+
const ds = statusByDomain.get((exactMatchRaw.domain || '').toLowerCase());
|
|
320
|
+
const meta = tldIndex.get((exactMatchRaw.tld || '').toLowerCase());
|
|
321
|
+
return {
|
|
322
|
+
domain: exactMatchRaw.domain,
|
|
323
|
+
tld: exactMatchRaw.tld,
|
|
324
|
+
available: ds?.available ?? null,
|
|
325
|
+
registered_year: ds?.createdYear ?? null,
|
|
326
|
+
registrar: ds?.registrar ?? null,
|
|
327
|
+
status_reason: ds?.reason ?? null,
|
|
328
|
+
registration_price: ds?.available !== false && meta?.Pricing
|
|
329
|
+
? {
|
|
330
|
+
price: meta.Pricing.Price ?? null,
|
|
331
|
+
regular: meta.Pricing.Regular ?? null,
|
|
332
|
+
renewal: meta.Pricing.Renewal ?? null,
|
|
333
|
+
hint: meta.Pricing.Hint ?? null,
|
|
334
|
+
}
|
|
335
|
+
: null,
|
|
336
|
+
};
|
|
337
|
+
})()
|
|
338
|
+
: null;
|
|
339
|
+
|
|
340
|
+
const detailPicks = (picksRaw?.picks ?? []).filter(
|
|
341
|
+
(p) => p && (p.type === 'domain' || !p.type) && p.domain,
|
|
342
|
+
);
|
|
343
|
+
const searchPicks = (searchRaw?.picks ?? []).filter((p) => p && p.domain);
|
|
344
|
+
const merged = mergePicks(searchPicks, detailPicks);
|
|
345
|
+
let picks = merged.map((p) => enrichDomain(p, tldIndex, statusByDomain));
|
|
346
|
+
|
|
347
|
+
let ranks = (searchRaw?.ranks ?? []).map((r) => enrichDomain(r, tldIndex, statusByDomain));
|
|
348
|
+
|
|
349
|
+
if (category) {
|
|
350
|
+
picks = picks.filter((p) => pickHasCategory(p.tld, category, tldIndex));
|
|
351
|
+
ranks = ranks.filter((r) => pickHasCategory(r.tld, category, tldIndex));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
picks = dedupeByDomain(picks);
|
|
355
|
+
ranks = dedupeByDomain(ranks);
|
|
356
|
+
|
|
357
|
+
const marketplace = (marketplaceRaw?.Items ?? []).map(summariseMarketplace);
|
|
358
|
+
|
|
359
|
+
// Compose a flat list of TLDs that match the requested category, useful when
|
|
360
|
+
// the agent wants to know what other TLDs are filed under a filter button.
|
|
361
|
+
const categoryTlds: string[] = category
|
|
362
|
+
? tldList
|
|
363
|
+
.filter((t) =>
|
|
364
|
+
(t.Categories ?? []).some((c) => (c.CategoryName || '').toLowerCase() === category),
|
|
365
|
+
)
|
|
366
|
+
.map((t) => t.Name)
|
|
367
|
+
: [];
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
query,
|
|
371
|
+
category: category || null,
|
|
372
|
+
exact_match: exactMatch,
|
|
373
|
+
picks,
|
|
374
|
+
ranks,
|
|
375
|
+
marketplace,
|
|
376
|
+
marketplace_count: marketplaceRaw?.TotalCount ?? marketplace.length,
|
|
377
|
+
category_tlds: categoryTlds,
|
|
378
|
+
tld_catalog_count: tldList.length,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
toolName: search_namecheap_domains
|
|
2
|
+
summary: Search Namecheap for available domains matching a query, optionally filtering by TLD category.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: query
|
|
5
|
+
type: string
|
|
6
|
+
description: The SLD or search term to look up (e.g. 'imprint')
|
|
7
|
+
- name: category
|
|
8
|
+
type: string
|
|
9
|
+
description: "Optional filter category slug to narrow TLD suggestions (e.g. 'international', 'finance', 'technology', 'short', 'new', 'orless', 'businesses', 'professional', 'organizations', 'shoppingsales', 'mediamusic', 'fun', 'sportshobbies', 'transport', 'personal', 'sociallifestyle', 'fooddrink', 'cities', 'beauty', 'travel', 'healthfitness', 'environment', 'colors', 'tradesconstruction', 'nonenglish', 'religion', 'academiceducation', 'audiovideo', 'artsculture', 'marketing', 'products', 'services', 'adult', 'realestate', 'politics'). Omit or leave empty for the default Popular view."
|
|
10
|
+
default: ""
|
|
11
|
+
- name: include_marketplace
|
|
12
|
+
type: boolean
|
|
13
|
+
description: Whether to also wait for aftermarket/marketplace results (default true). Currently informational; the page fetches marketplace data automatically.
|
|
14
|
+
default: true
|
|
15
|
+
steps:
|
|
16
|
+
- action: navigate
|
|
17
|
+
url: https://www.namecheap.com/
|
|
18
|
+
wait_for: networkidle
|
|
19
|
+
- action: type
|
|
20
|
+
locators:
|
|
21
|
+
- by: id
|
|
22
|
+
value: static-domain-search-domain-search-input
|
|
23
|
+
- by: css
|
|
24
|
+
value: "input[name=\"domain\"][type=\"search\"]"
|
|
25
|
+
value: ${query}
|
|
26
|
+
- action: click
|
|
27
|
+
locators:
|
|
28
|
+
- by: aria_label
|
|
29
|
+
value: Search
|
|
30
|
+
- by: role
|
|
31
|
+
value: button
|
|
32
|
+
name: Search
|
|
33
|
+
- by: css
|
|
34
|
+
value: button.search-domain-btn.nchp-search-form__submit
|
|
35
|
+
wait_for:
|
|
36
|
+
xhr: /api/search/${query}
|
|
37
|
+
- action: wait
|
|
38
|
+
wait_for: networkidle
|
|
39
|
+
- action: click
|
|
40
|
+
locators:
|
|
41
|
+
- by: css
|
|
42
|
+
value: "main.results.filtering ul.filters li.filter-${category} > button"
|
|
43
|
+
wait_for:
|
|
44
|
+
xhr: /api/search/${query}
|
|
45
|
+
result:
|
|
46
|
+
source: xhr
|
|
47
|
+
url_pattern: /api/search/${query}
|
|
48
|
+
extract: picks[].domain
|
|
49
|
+
return_as: picks
|
|
50
|
+
notes: "The final filter-click step is only meaningful when ${category} is supplied; with an empty category the playbook should skip it (default Popular view is already loaded by the search submit). Each filter click triggers the same /api/search/{query} XHR; the load-bearing response shape is {exact_match, picks[], ranks[], hasNextPage, type}. Marketplace/aftermarket data is returned by a separate POST to /api/v1/ncpl/domainsearchgateway/domain/readBySld which fires automatically on the results page. Session bootstrap (SessionHandler.ashx, refid) happens via the cookies/page load — no explicit auth step required."
|