@hasna/microservices 0.0.5 → 0.0.6
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/bin/index.js +9 -1
- package/bin/mcp.js +9 -1
- package/dist/index.js +9 -1
- package/microservices/microservice-company/package.json +27 -0
- package/microservices/microservice-company/src/cli/index.ts +1126 -0
- package/microservices/microservice-company/src/db/company.ts +854 -0
- package/microservices/microservice-company/src/db/database.ts +93 -0
- package/microservices/microservice-company/src/db/migrations.ts +214 -0
- package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
- package/microservices/microservice-company/src/index.ts +60 -0
- package/microservices/microservice-company/src/lib/audit.ts +168 -0
- package/microservices/microservice-company/src/lib/finance.ts +299 -0
- package/microservices/microservice-company/src/lib/settings.ts +85 -0
- package/microservices/microservice-company/src/lib/workflows.ts +698 -0
- package/microservices/microservice-company/src/mcp/index.ts +991 -0
- package/microservices/microservice-domains/src/cli/index.ts +420 -0
- package/microservices/microservice-domains/src/lib/brandsight.ts +285 -0
- package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
- package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
- package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
- package/microservices/microservice-domains/src/mcp/index.ts +245 -0
- package/package.json +1 -1
|
@@ -30,6 +30,26 @@ import {
|
|
|
30
30
|
checkAllDomains,
|
|
31
31
|
getDomainByName,
|
|
32
32
|
} from "../db/domains.js";
|
|
33
|
+
import {
|
|
34
|
+
syncToLocalDb,
|
|
35
|
+
renewDomain as namecheapRenew,
|
|
36
|
+
checkAvailability as namecheapCheck,
|
|
37
|
+
} from "../lib/namecheap.js";
|
|
38
|
+
import {
|
|
39
|
+
syncToLocalDb as godaddySyncToLocalDb,
|
|
40
|
+
renewDomain as godaddyRenewDomain,
|
|
41
|
+
} from "../lib/godaddy.js";
|
|
42
|
+
import {
|
|
43
|
+
getAvailableProviders,
|
|
44
|
+
syncAll,
|
|
45
|
+
autoDetectRegistrar,
|
|
46
|
+
getProvider,
|
|
47
|
+
} from "../lib/registrar.js";
|
|
48
|
+
import {
|
|
49
|
+
monitorBrand,
|
|
50
|
+
getSimilarDomains,
|
|
51
|
+
getThreatAssessment,
|
|
52
|
+
} from "../lib/brandsight.js";
|
|
33
53
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
34
54
|
|
|
35
55
|
const program = new Command();
|
|
@@ -688,4 +708,404 @@ alertCmd
|
|
|
688
708
|
}
|
|
689
709
|
});
|
|
690
710
|
|
|
711
|
+
// --- Namecheap Integration ---
|
|
712
|
+
|
|
713
|
+
program
|
|
714
|
+
.command("sync")
|
|
715
|
+
.description("Sync domains from a provider to local DB")
|
|
716
|
+
.requiredOption("--provider <provider>", "Provider name (namecheap, godaddy)")
|
|
717
|
+
.option("--json", "Output as JSON", false)
|
|
718
|
+
.action(async (opts) => {
|
|
719
|
+
const provider = opts.provider.toLowerCase();
|
|
720
|
+
|
|
721
|
+
if (provider === "namecheap") {
|
|
722
|
+
try {
|
|
723
|
+
const result = await syncToLocalDb({
|
|
724
|
+
getDomainByName,
|
|
725
|
+
createDomain,
|
|
726
|
+
updateDomain,
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
if (opts.json) {
|
|
730
|
+
console.log(JSON.stringify(result, null, 2));
|
|
731
|
+
} else {
|
|
732
|
+
console.log(`Synced ${result.synced} domain(s) from Namecheap`);
|
|
733
|
+
for (const d of result.domains) {
|
|
734
|
+
console.log(` ${d}`);
|
|
735
|
+
}
|
|
736
|
+
if (result.errors.length > 0) {
|
|
737
|
+
console.log("Errors:");
|
|
738
|
+
for (const e of result.errors) {
|
|
739
|
+
console.log(` - ${e}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
} catch (error: unknown) {
|
|
744
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
} else if (provider === "godaddy") {
|
|
748
|
+
try {
|
|
749
|
+
const result = await godaddySyncToLocalDb({
|
|
750
|
+
getDomainByName,
|
|
751
|
+
createDomain,
|
|
752
|
+
updateDomain,
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
if (opts.json) {
|
|
756
|
+
console.log(JSON.stringify(result, null, 2));
|
|
757
|
+
} else {
|
|
758
|
+
console.log(`Synced ${result.synced} domain(s) from GoDaddy (created: ${result.created}, updated: ${result.updated})`);
|
|
759
|
+
if (result.errors.length > 0) {
|
|
760
|
+
console.log("Errors:");
|
|
761
|
+
for (const e of result.errors) {
|
|
762
|
+
console.log(` - ${e}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
} catch (error: unknown) {
|
|
767
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
console.error(`Unsupported provider: ${opts.provider}. Supported: namecheap, godaddy`);
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
program
|
|
777
|
+
.command("renew")
|
|
778
|
+
.description("Renew a domain via provider")
|
|
779
|
+
.argument("<name>", "Domain name (e.g. example.com)")
|
|
780
|
+
.requiredOption("--provider <provider>", "Provider name (namecheap, godaddy)")
|
|
781
|
+
.option("--years <n>", "Number of years to renew", "1")
|
|
782
|
+
.option("--json", "Output as JSON", false)
|
|
783
|
+
.action(async (name, opts) => {
|
|
784
|
+
const provider = opts.provider.toLowerCase();
|
|
785
|
+
|
|
786
|
+
if (provider === "namecheap") {
|
|
787
|
+
try {
|
|
788
|
+
const result = await namecheapRenew(name, parseInt(opts.years));
|
|
789
|
+
|
|
790
|
+
if (opts.json) {
|
|
791
|
+
console.log(JSON.stringify(result, null, 2));
|
|
792
|
+
} else {
|
|
793
|
+
console.log(`Renewed ${result.domain} successfully`);
|
|
794
|
+
if (result.chargedAmount) console.log(` Charged: $${result.chargedAmount}`);
|
|
795
|
+
if (result.orderId) console.log(` Order ID: ${result.orderId}`);
|
|
796
|
+
if (result.transactionId) console.log(` Transaction ID: ${result.transactionId}`);
|
|
797
|
+
}
|
|
798
|
+
} catch (error: unknown) {
|
|
799
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
800
|
+
process.exit(1);
|
|
801
|
+
}
|
|
802
|
+
} else if (provider === "godaddy") {
|
|
803
|
+
try {
|
|
804
|
+
const result = await godaddyRenewDomain(name);
|
|
805
|
+
|
|
806
|
+
if (opts.json) {
|
|
807
|
+
console.log(JSON.stringify(result, null, 2));
|
|
808
|
+
} else {
|
|
809
|
+
console.log(`Renewed ${name} successfully via GoDaddy`);
|
|
810
|
+
if (result.orderId) console.log(` Order ID: ${result.orderId}`);
|
|
811
|
+
if (result.total) console.log(` Total: $${(result.total / 100).toFixed(2)}`);
|
|
812
|
+
}
|
|
813
|
+
} catch (error: unknown) {
|
|
814
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
console.error(`Unsupported provider: ${opts.provider}. Supported: namecheap, godaddy`);
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
program
|
|
824
|
+
.command("check")
|
|
825
|
+
.description("Check domain availability")
|
|
826
|
+
.argument("<name>", "Domain name (e.g. example.com)")
|
|
827
|
+
.option("--json", "Output as JSON", false)
|
|
828
|
+
.action(async (name, opts) => {
|
|
829
|
+
try {
|
|
830
|
+
const result = await namecheapCheck(name);
|
|
831
|
+
|
|
832
|
+
if (opts.json) {
|
|
833
|
+
console.log(JSON.stringify(result, null, 2));
|
|
834
|
+
} else {
|
|
835
|
+
if (result.available) {
|
|
836
|
+
console.log(`${result.domain} is AVAILABLE`);
|
|
837
|
+
} else {
|
|
838
|
+
console.log(`${result.domain} is NOT available`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
} catch (error: unknown) {
|
|
842
|
+
console.error(`Availability check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// --- Unified Provider Commands ---
|
|
848
|
+
|
|
849
|
+
program
|
|
850
|
+
.command("providers")
|
|
851
|
+
.description("Show which registrar providers are configured")
|
|
852
|
+
.option("--json", "Output as JSON", false)
|
|
853
|
+
.action((opts) => {
|
|
854
|
+
const providers = getAvailableProviders();
|
|
855
|
+
|
|
856
|
+
if (opts.json) {
|
|
857
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
858
|
+
} else {
|
|
859
|
+
console.log("Registrar Providers:");
|
|
860
|
+
for (const p of providers) {
|
|
861
|
+
const status = p.configured ? "CONFIGURED" : "not configured";
|
|
862
|
+
console.log(` ${p.name}: ${status}`);
|
|
863
|
+
if (!p.configured) {
|
|
864
|
+
console.log(` Missing: ${p.envVars.join(", ")}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// Override sync command to support --all flag
|
|
871
|
+
program.commands = program.commands.filter((c) => c.name() !== "sync");
|
|
872
|
+
|
|
873
|
+
program
|
|
874
|
+
.command("sync")
|
|
875
|
+
.description("Sync domains from a provider to local DB")
|
|
876
|
+
.option("--provider <provider>", "Provider name (namecheap, godaddy)")
|
|
877
|
+
.option("--all", "Sync from all configured providers")
|
|
878
|
+
.option("--json", "Output as JSON", false)
|
|
879
|
+
.action(async (opts) => {
|
|
880
|
+
if (opts.all) {
|
|
881
|
+
try {
|
|
882
|
+
const result = await syncAll({
|
|
883
|
+
getDomainByName,
|
|
884
|
+
createDomain,
|
|
885
|
+
updateDomain,
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
if (opts.json) {
|
|
889
|
+
console.log(JSON.stringify(result, null, 2));
|
|
890
|
+
} else {
|
|
891
|
+
console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
|
|
892
|
+
for (const p of result.providers) {
|
|
893
|
+
console.log(` ${p.name}: ${p.result.synced} synced`);
|
|
894
|
+
}
|
|
895
|
+
if (result.totalErrors.length > 0) {
|
|
896
|
+
console.log("Errors:");
|
|
897
|
+
for (const e of result.totalErrors) {
|
|
898
|
+
console.log(` - ${e}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
} catch (error: unknown) {
|
|
903
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const provider = (opts.provider || "").toLowerCase();
|
|
910
|
+
if (!provider) {
|
|
911
|
+
console.error("Specify --provider <name> or --all");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (provider === "namecheap") {
|
|
916
|
+
try {
|
|
917
|
+
const result = await syncToLocalDb({
|
|
918
|
+
getDomainByName,
|
|
919
|
+
createDomain,
|
|
920
|
+
updateDomain,
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
if (opts.json) {
|
|
924
|
+
console.log(JSON.stringify(result, null, 2));
|
|
925
|
+
} else {
|
|
926
|
+
console.log(`Synced ${result.synced} domain(s) from Namecheap`);
|
|
927
|
+
for (const d of result.domains) {
|
|
928
|
+
console.log(` ${d}`);
|
|
929
|
+
}
|
|
930
|
+
if (result.errors.length > 0) {
|
|
931
|
+
console.log("Errors:");
|
|
932
|
+
for (const e of result.errors) {
|
|
933
|
+
console.log(` - ${e}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} catch (error: unknown) {
|
|
938
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
939
|
+
process.exit(1);
|
|
940
|
+
}
|
|
941
|
+
} else if (provider === "godaddy") {
|
|
942
|
+
try {
|
|
943
|
+
const result = await godaddySyncToLocalDb({
|
|
944
|
+
getDomainByName,
|
|
945
|
+
createDomain,
|
|
946
|
+
updateDomain,
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
if (opts.json) {
|
|
950
|
+
console.log(JSON.stringify(result, null, 2));
|
|
951
|
+
} else {
|
|
952
|
+
console.log(`Synced ${result.synced} domain(s) from GoDaddy (created: ${result.created}, updated: ${result.updated})`);
|
|
953
|
+
if (result.errors.length > 0) {
|
|
954
|
+
console.log("Errors:");
|
|
955
|
+
for (const e of result.errors) {
|
|
956
|
+
console.log(` - ${e}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
} catch (error: unknown) {
|
|
961
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
} else {
|
|
965
|
+
console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// Override renew command to support auto-detect
|
|
971
|
+
program.commands = program.commands.filter((c) => c.name() !== "renew");
|
|
972
|
+
|
|
973
|
+
program
|
|
974
|
+
.command("renew")
|
|
975
|
+
.description("Renew a domain via provider (auto-detects registrar from DB if --provider not given)")
|
|
976
|
+
.argument("<name>", "Domain name (e.g. example.com)")
|
|
977
|
+
.option("--provider <provider>", "Provider name (namecheap, godaddy)")
|
|
978
|
+
.option("--years <n>", "Number of years to renew", "1")
|
|
979
|
+
.option("--json", "Output as JSON", false)
|
|
980
|
+
.action(async (name, opts) => {
|
|
981
|
+
let provider = (opts.provider || "").toLowerCase();
|
|
982
|
+
|
|
983
|
+
if (!provider) {
|
|
984
|
+
const detected = autoDetectRegistrar(name, getDomainByName);
|
|
985
|
+
if (!detected) {
|
|
986
|
+
console.error(`Could not auto-detect registrar for '${name}'. Use --provider.`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
provider = detected;
|
|
990
|
+
console.log(`Auto-detected registrar: ${provider}`);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (provider === "namecheap") {
|
|
994
|
+
try {
|
|
995
|
+
const result = await namecheapRenew(name, parseInt(opts.years));
|
|
996
|
+
if (opts.json) {
|
|
997
|
+
console.log(JSON.stringify(result, null, 2));
|
|
998
|
+
} else {
|
|
999
|
+
console.log(`Renewed ${result.domain} successfully via Namecheap`);
|
|
1000
|
+
if (result.chargedAmount) console.log(` Charged: $${result.chargedAmount}`);
|
|
1001
|
+
if (result.orderId) console.log(` Order ID: ${result.orderId}`);
|
|
1002
|
+
}
|
|
1003
|
+
} catch (error: unknown) {
|
|
1004
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
} else if (provider === "godaddy") {
|
|
1008
|
+
try {
|
|
1009
|
+
const result = await godaddyRenewDomain(name);
|
|
1010
|
+
if (opts.json) {
|
|
1011
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1012
|
+
} else {
|
|
1013
|
+
console.log(`Renewed ${name} successfully via GoDaddy`);
|
|
1014
|
+
if (result.orderId) console.log(` Order ID: ${result.orderId}`);
|
|
1015
|
+
if (result.total) console.log(` Total: $${(result.total / 100).toFixed(2)}`);
|
|
1016
|
+
}
|
|
1017
|
+
} catch (error: unknown) {
|
|
1018
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
} else {
|
|
1022
|
+
console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
|
|
1023
|
+
process.exit(1);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// --- Brandsight Commands ---
|
|
1028
|
+
|
|
1029
|
+
program
|
|
1030
|
+
.command("monitor")
|
|
1031
|
+
.description("Monitor a brand for similar domain registrations (Brandsight)")
|
|
1032
|
+
.argument("<brand>", "Brand name to monitor")
|
|
1033
|
+
.option("--json", "Output as JSON", false)
|
|
1034
|
+
.action(async (brand, opts) => {
|
|
1035
|
+
try {
|
|
1036
|
+
const result = await monitorBrand(brand);
|
|
1037
|
+
if (opts.json) {
|
|
1038
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1039
|
+
} else {
|
|
1040
|
+
if (result.stub) console.log("(stub data — Brandsight API unreachable)");
|
|
1041
|
+
console.log(`Brand monitoring for "${result.brand}":`);
|
|
1042
|
+
if (result.alerts.length === 0) {
|
|
1043
|
+
console.log(" No alerts.");
|
|
1044
|
+
} else {
|
|
1045
|
+
for (const a of result.alerts) {
|
|
1046
|
+
console.log(` [${a.type}] ${a.domain} — registered ${a.registered_at}`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
console.log(`\n${result.alerts.length} alert(s)`);
|
|
1050
|
+
}
|
|
1051
|
+
} catch (error: unknown) {
|
|
1052
|
+
console.error(`Monitor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1053
|
+
process.exit(1);
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
program
|
|
1058
|
+
.command("similar")
|
|
1059
|
+
.description("Find typosquat/competing domains similar to a domain (Brandsight)")
|
|
1060
|
+
.argument("<domain>", "Domain to check")
|
|
1061
|
+
.option("--json", "Output as JSON", false)
|
|
1062
|
+
.action(async (domain, opts) => {
|
|
1063
|
+
try {
|
|
1064
|
+
const result = await getSimilarDomains(domain);
|
|
1065
|
+
if (opts.json) {
|
|
1066
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1067
|
+
} else {
|
|
1068
|
+
if (result.stub) console.log("(stub data — Brandsight API unreachable)");
|
|
1069
|
+
console.log(`Similar domains for ${result.domain}:`);
|
|
1070
|
+
for (const d of result.similar) {
|
|
1071
|
+
console.log(` ${d}`);
|
|
1072
|
+
}
|
|
1073
|
+
console.log(`\n${result.similar.length} similar domain(s)`);
|
|
1074
|
+
}
|
|
1075
|
+
} catch (error: unknown) {
|
|
1076
|
+
console.error(`Similar domains check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1077
|
+
process.exit(1);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
program
|
|
1082
|
+
.command("threats")
|
|
1083
|
+
.description("Get threat assessment for a domain (Brandsight)")
|
|
1084
|
+
.argument("<domain>", "Domain to assess")
|
|
1085
|
+
.option("--json", "Output as JSON", false)
|
|
1086
|
+
.action(async (domain, opts) => {
|
|
1087
|
+
try {
|
|
1088
|
+
const result = await getThreatAssessment(domain);
|
|
1089
|
+
if (opts.json) {
|
|
1090
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1091
|
+
} else {
|
|
1092
|
+
if (result.stub) console.log("(stub data — Brandsight API unreachable)");
|
|
1093
|
+
console.log(`Threat Assessment for ${result.domain}:`);
|
|
1094
|
+
console.log(` Risk Level: ${result.risk_level}`);
|
|
1095
|
+
if (result.threats.length > 0) {
|
|
1096
|
+
console.log(" Threats:");
|
|
1097
|
+
for (const t of result.threats) {
|
|
1098
|
+
console.log(` - ${t}`);
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
console.log(" Threats: none detected");
|
|
1102
|
+
}
|
|
1103
|
+
console.log(` Recommendation: ${result.recommendation}`);
|
|
1104
|
+
}
|
|
1105
|
+
} catch (error: unknown) {
|
|
1106
|
+
console.error(`Threat assessment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1107
|
+
process.exit(1);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
691
1111
|
program.parse(process.argv);
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brandsight API integration for brand monitoring and threat detection
|
|
3
|
+
*
|
|
4
|
+
* Requires environment variable:
|
|
5
|
+
* BRANDSIGHT_API_KEY — API key for Brandsight
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================
|
|
9
|
+
// Types
|
|
10
|
+
// ============================================================
|
|
11
|
+
|
|
12
|
+
export interface BrandsightAlert {
|
|
13
|
+
domain: string;
|
|
14
|
+
type: "typosquat" | "homoglyph" | "keyword" | "tld_variation";
|
|
15
|
+
registered_at: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BrandMonitorResult {
|
|
19
|
+
brand: string;
|
|
20
|
+
alerts: BrandsightAlert[];
|
|
21
|
+
stub: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface WhoisHistoryEntry {
|
|
25
|
+
registrant: string;
|
|
26
|
+
date: string;
|
|
27
|
+
changes: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WhoisHistoryResult {
|
|
31
|
+
domain: string;
|
|
32
|
+
history: WhoisHistoryEntry[];
|
|
33
|
+
stub: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ThreatAssessment {
|
|
37
|
+
domain: string;
|
|
38
|
+
risk_level: "low" | "medium" | "high" | "critical";
|
|
39
|
+
threats: string[];
|
|
40
|
+
recommendation: string;
|
|
41
|
+
stub: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class BrandsightApiError extends Error {
|
|
45
|
+
constructor(
|
|
46
|
+
message: string,
|
|
47
|
+
public statusCode?: number,
|
|
48
|
+
public responseBody?: string
|
|
49
|
+
) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "BrandsightApiError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================
|
|
56
|
+
// Configuration
|
|
57
|
+
// ============================================================
|
|
58
|
+
|
|
59
|
+
const API_BASE = "https://api.brandsight.com/v1";
|
|
60
|
+
|
|
61
|
+
export function getApiKey(): string {
|
|
62
|
+
const key = process.env["BRANDSIGHT_API_KEY"];
|
|
63
|
+
if (!key) {
|
|
64
|
+
throw new BrandsightApiError(
|
|
65
|
+
"BRANDSIGHT_API_KEY environment variable is not set"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return key;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getHeaders(apiKey: string): Record<string, string> {
|
|
72
|
+
return {
|
|
73
|
+
Authorization: `Bearer ${apiKey}`,
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
Accept: "application/json",
|
|
76
|
+
"User-Agent": "microservice-domains/0.0.1",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// Internal fetch helper (allows test injection)
|
|
82
|
+
// ============================================================
|
|
83
|
+
|
|
84
|
+
type FetchFn = typeof globalThis.fetch;
|
|
85
|
+
|
|
86
|
+
let _fetchFn: FetchFn = globalThis.fetch;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Override the fetch implementation (for testing).
|
|
90
|
+
* Pass `null` to restore the default.
|
|
91
|
+
*/
|
|
92
|
+
export function _setFetch(fn: FetchFn | null): void {
|
|
93
|
+
_fetchFn = fn ?? globalThis.fetch;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function apiRequest<T>(path: string, apiKey: string): Promise<{ data: T; stub: false } | { data: null; stub: true }> {
|
|
97
|
+
const url = `${API_BASE}${path}`;
|
|
98
|
+
const headers = getHeaders(apiKey);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const response = await _fetchFn(url, {
|
|
102
|
+
method: "GET",
|
|
103
|
+
headers,
|
|
104
|
+
signal: AbortSignal.timeout(15000),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new BrandsightApiError(
|
|
109
|
+
`Brandsight API GET ${path} failed with status ${response.status}`,
|
|
110
|
+
response.status,
|
|
111
|
+
await response.text()
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const data = (await response.json()) as T;
|
|
116
|
+
return { data, stub: false };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error instanceof BrandsightApiError) throw error;
|
|
119
|
+
// API unreachable — return stub indicator
|
|
120
|
+
return { data: null, stub: true };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================
|
|
125
|
+
// Stub Data Generators
|
|
126
|
+
// ============================================================
|
|
127
|
+
|
|
128
|
+
function generateStubAlerts(brandName: string): BrandsightAlert[] {
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
domain: `${brandName}-deals.com`,
|
|
133
|
+
type: "keyword",
|
|
134
|
+
registered_at: now,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`,
|
|
138
|
+
type: "homoglyph",
|
|
139
|
+
registered_at: now,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
domain: `${brandName}s.com`,
|
|
143
|
+
type: "typosquat",
|
|
144
|
+
registered_at: now,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function generateStubSimilarDomains(domain: string): string[] {
|
|
150
|
+
const base = domain.replace(/\.[^.]+$/, "");
|
|
151
|
+
const tld = domain.slice(base.length);
|
|
152
|
+
return [
|
|
153
|
+
`${base}-online${tld}`,
|
|
154
|
+
`${base}s${tld}`,
|
|
155
|
+
`${base.replace(/a/gi, "4")}${tld}`,
|
|
156
|
+
`${base}-app${tld}`,
|
|
157
|
+
`get${base}${tld}`,
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function generateStubWhoisHistory(domain: string): WhoisHistoryEntry[] {
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
registrant: "Privacy Proxy Service",
|
|
165
|
+
date: "2023-01-15T00:00:00Z",
|
|
166
|
+
changes: ["registrant_changed", "nameserver_changed"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
registrant: "Original Owner LLC",
|
|
170
|
+
date: "2020-06-01T00:00:00Z",
|
|
171
|
+
changes: ["initial_registration"],
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function generateStubThreatAssessment(domain: string): Omit<ThreatAssessment, "stub"> {
|
|
177
|
+
return {
|
|
178
|
+
domain,
|
|
179
|
+
risk_level: "low",
|
|
180
|
+
threats: [],
|
|
181
|
+
recommendation: "No immediate threats detected. Continue routine monitoring.",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================
|
|
186
|
+
// API Functions
|
|
187
|
+
// ============================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Monitor a brand name for new domain registrations that are similar.
|
|
191
|
+
*/
|
|
192
|
+
export async function monitorBrand(brandName: string): Promise<BrandMonitorResult> {
|
|
193
|
+
const apiKey = getApiKey();
|
|
194
|
+
const result = await apiRequest<{ alerts: BrandsightAlert[] }>(
|
|
195
|
+
`/brands/${encodeURIComponent(brandName)}/monitor`,
|
|
196
|
+
apiKey
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (result.stub) {
|
|
200
|
+
return {
|
|
201
|
+
brand: brandName,
|
|
202
|
+
alerts: generateStubAlerts(brandName),
|
|
203
|
+
stub: true,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
brand: brandName,
|
|
209
|
+
alerts: result.data!.alerts,
|
|
210
|
+
stub: false,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Find typosquat/competing domains similar to the given domain.
|
|
216
|
+
*/
|
|
217
|
+
export async function getSimilarDomains(domain: string): Promise<{ domain: string; similar: string[]; stub: boolean }> {
|
|
218
|
+
const apiKey = getApiKey();
|
|
219
|
+
const result = await apiRequest<{ similar: string[] }>(
|
|
220
|
+
`/domains/${encodeURIComponent(domain)}/similar`,
|
|
221
|
+
apiKey
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (result.stub) {
|
|
225
|
+
return {
|
|
226
|
+
domain,
|
|
227
|
+
similar: generateStubSimilarDomains(domain),
|
|
228
|
+
stub: true,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
domain,
|
|
234
|
+
similar: result.data!.similar,
|
|
235
|
+
stub: false,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get historical WHOIS records for a domain.
|
|
241
|
+
*/
|
|
242
|
+
export async function getWhoisHistory(domain: string): Promise<WhoisHistoryResult> {
|
|
243
|
+
const apiKey = getApiKey();
|
|
244
|
+
const result = await apiRequest<{ history: WhoisHistoryEntry[] }>(
|
|
245
|
+
`/domains/${encodeURIComponent(domain)}/whois-history`,
|
|
246
|
+
apiKey
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (result.stub) {
|
|
250
|
+
return {
|
|
251
|
+
domain,
|
|
252
|
+
history: generateStubWhoisHistory(domain),
|
|
253
|
+
stub: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
domain,
|
|
259
|
+
history: result.data!.history,
|
|
260
|
+
stub: false,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get a threat assessment for a domain.
|
|
266
|
+
*/
|
|
267
|
+
export async function getThreatAssessment(domain: string): Promise<ThreatAssessment> {
|
|
268
|
+
const apiKey = getApiKey();
|
|
269
|
+
const result = await apiRequest<Omit<ThreatAssessment, "stub">>(
|
|
270
|
+
`/domains/${encodeURIComponent(domain)}/threats`,
|
|
271
|
+
apiKey
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
if (result.stub) {
|
|
275
|
+
return {
|
|
276
|
+
...generateStubThreatAssessment(domain),
|
|
277
|
+
stub: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
...result.data!,
|
|
283
|
+
stub: false,
|
|
284
|
+
};
|
|
285
|
+
}
|