← back to CelebritySignatures
scripts/fetch-tm-register.mjs
98 lines
#!/usr/bin/env node
// Fetch USPTO bulk trademark data into tmp_tm_cache/ (local-only, rsync-excluded).
//
// WHY bulk, not an API: USPTO retired TESS (2023); the new tmsearch.uspto.gov has
// NO public real-time name-search API, and the official ODP/TSDR JSON APIs need an
// ID.me-linked key and look up by serial#, not by mark name. The free, $0, no-rate-
// limit path is the published bulk register, which we index locally once.
//
// SOURCE (2026): the legacy host bulkdata.uspto.gov is RETIRED. Trademark bulk
// data now lives on the USPTO Open Data Portal:
// dataset page : https://data.uspto.gov/bulkdata/datasets/trtdxfap
// product : "Trademark Daily XML File (TDXF) applications" (trtdxfap)
// Each file is XML with <case-file> records (mark text, status, Nice class).
//
// REALITY (verified 2026-08-06): the ODP is behind an AWS WAF anti-bot challenge
// AND its Bulk-Search/Download (BSD) API requires an ID.me-linked USPTO API key
// (X-API-KEY). So a plain headless fetch is BLOCKED. Three real ways in:
// (A) --file <path> : ingest a file YOU downloaded in a real browser (or via
// the openclaw/portal-driver real-Chrome flow). $0, no key.
// (B) --odp <fileName>: hit the BSD download API with USPTO_ODP_API_KEY (env/.env)
// — needs Steve's ID.me-linked key. $0 once keyed.
// (C) --url <httpsUrl>: fetch a direct file URL (if you have an unwalled one).
// Files land in tmp_tm_cache/ ; then run build-tm-index.mjs + build-tm-clearance.mjs.
import { mkdir, writeFile, copyFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { join, basename } from 'node:path';
import { readFileSync } from 'node:fs';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
const CACHE = join(ROOT, 'tmp_tm_cache');
const DATASET = 'https://data.uspto.gov/bulkdata/datasets/trtdxfap';
const BSD_DOWNLOAD = 'https://data.uspto.gov/api/v1/datasets/products/files'; // BSD download base (key-gated)
const args = process.argv.slice(2);
const get = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : null; };
function envVal(name) {
if (process.env[name]) return process.env[name];
try { const m = readFileSync(join(ROOT, '.env'), 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
return null;
}
async function main() {
await mkdir(CACHE, { recursive: true });
if (args.includes('--list') || args.length === 0) {
console.log('USPTO trademark bulk register — Open Data Portal (data.uspto.gov):');
console.log(' Dataset : ' + DATASET + ' (product trtdxfap)');
console.log(' NOTE: ODP is behind an AWS WAF + the BSD API needs an ID.me-linked key.');
console.log(' A plain headless fetch is BLOCKED. Choose one:');
console.log(' (A) node scripts/fetch-tm-register.mjs --file /path/to/downloaded-trtdxf.zip # $0, no key');
console.log(' (B) USPTO_ODP_API_KEY=… node scripts/fetch-tm-register.mjs --odp <fileName> # $0, needs key');
console.log(' (C) node scripts/fetch-tm-register.mjs --url <direct-https-url>');
console.log('Then: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
console.log('Until a REAL file is ingested the index stays a SEED SAMPLE (complete:false) and');
console.log('NOTHING is sellable — "only legally clear" holds by default.');
return;
}
// (A) ingest an already-downloaded file (browser / openclaw)
const localFile = get('--file');
if (localFile) {
const out = join(CACHE, basename(localFile));
await copyFile(localFile, out);
console.log('Ingested local register file -> ' + out + ' (cost: $0)');
console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
return;
}
// (B) BSD download API with an ID.me-linked key
const odpFile = get('--odp');
if (odpFile) {
const key = envVal('USPTO_ODP_API_KEY');
if (!key) { console.error('Missing USPTO_ODP_API_KEY (ID.me-linked). See --list option B.'); process.exit(2); }
const target = `${BSD_DOWNLOAD}/${encodeURIComponent(odpFile)}/download`;
console.log('Cost: $0 (keyed ODP). Fetching', target, '...');
const r = await fetch(target, { headers: { 'X-API-KEY': key } });
if (!r.ok) { console.error('ODP fetch failed:', r.status, r.statusText, '(WAF or bad key/filename?)'); process.exit(1); }
const buf = Buffer.from(await r.arrayBuffer());
const out = join(CACHE, basename(odpFile));
await writeFile(out, buf);
console.log(`Saved ${(buf.length / 1e6).toFixed(1)} MB -> ${out}`);
console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
return;
}
// (C) direct URL
const target = get('--url');
if (!target || !/^https:\/\//.test(target)) { console.error('Refusing: --url must be an https URL.'); process.exit(2); }
console.log('Cost: $0. Fetching', target, '...');
const r = await fetch(target);
if (!r.ok) { console.error('fetch failed:', r.status, r.statusText, '(the ODP host is WAF-gated; use --file or --odp instead)'); process.exit(1); }
const buf = Buffer.from(await r.arrayBuffer());
const out = join(CACHE, basename(new URL(target).pathname) || 'register.xml');
await writeFile(out, buf);
console.log(`Saved ${(buf.length / 1e6).toFixed(1)} MB -> ${out}`);
console.log('Next: node scripts/build-tm-index.mjs && node scripts/build-tm-clearance.mjs');
}
main().catch(e => { console.error(e); process.exit(1); });