@startanaicompany/dns 1.3.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/lib/client.js ADDED
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SAAC DNS API Client
5
+ * Communicates with the SAAC backend (production: https://start-an-ai-company-3l1xzm.adam.startanaicompany.com)
6
+ * which proxies to NameSilo for domain operations.
7
+ *
8
+ * Auth: set SAAC_USER_API_KEY and SAAC_USER_EMAIL environment variables.
9
+ * SAAC_USER_API_KEY — your personal API key (used as Bearer token and as api_key on registration)
10
+ * SAAC_USER_EMAIL — your account email
11
+ *
12
+ * Override the backend URL with SAAC_API_URL (useful for local development).
13
+ */
14
+
15
+ const BASE_URL = process.env.SAAC_API_URL || 'https://start-an-ai-company-3l1xzm.adam.startanaicompany.com';
16
+
17
+ class AuthenticationError extends Error {
18
+ constructor(msg) { super(msg); this.name = 'AuthenticationError'; this.code = 110; }
19
+ }
20
+ class DomainNotFoundError extends Error {
21
+ constructor(msg) { super(msg); this.name = 'DomainNotFoundError'; this.code = 200; }
22
+ }
23
+ class DomainUnavailableError extends Error {
24
+ constructor(msg) { super(msg); this.name = 'DomainUnavailableError'; this.code = 261; }
25
+ }
26
+ class ValidationError extends Error {
27
+ constructor(msg) { super(msg); this.name = 'ValidationError'; this.code = 108; }
28
+ }
29
+ class InsufficientFundsError extends Error {
30
+ constructor(msg) { super(msg); this.name = 'InsufficientFundsError'; this.code = 280; }
31
+ }
32
+ class SaacDnsError extends Error {
33
+ constructor(msg, code) { super(msg); this.name = 'SaacDnsError'; this.code = code; }
34
+ }
35
+
36
+ function getCredentials() {
37
+ const apiKey = process.env.SAAC_USER_API_KEY;
38
+ const email = process.env.SAAC_USER_EMAIL;
39
+ if (!apiKey) throw new AuthenticationError('SAAC_USER_API_KEY environment variable is required. Get your key at https://start-an-ai-company-3l1xzm.adam.startanaicompany.com');
40
+ if (!email) throw new AuthenticationError('SAAC_USER_EMAIL environment variable is required.');
41
+ return { apiKey, email };
42
+ }
43
+
44
+ async function request(method, path, body) {
45
+ const { apiKey, email } = getCredentials();
46
+
47
+ const headers = {
48
+ 'Content-Type': 'application/json',
49
+ 'Authorization': `Bearer ${apiKey}`,
50
+ 'X-SAAC-Email': email
51
+ };
52
+
53
+ const opts = { method, headers };
54
+ if (body) opts.body = JSON.stringify(body);
55
+
56
+ const res = await fetch(`${BASE_URL}${path}`, opts);
57
+ const contentType = res.headers.get('content-type') || '';
58
+ let data;
59
+ if (contentType.includes('application/json')) {
60
+ data = await res.json();
61
+ } else {
62
+ const text = await res.text();
63
+ if (!res.ok) {
64
+ throw new SaacDnsError(
65
+ `Server error (HTTP ${res.status}): ${text.slice(0, 200)}`,
66
+ res.status
67
+ );
68
+ }
69
+ // Try to parse as JSON anyway
70
+ try {
71
+ data = JSON.parse(text);
72
+ } catch (_) {
73
+ throw new SaacDnsError(`Unexpected non-JSON response: ${text.slice(0, 200)}`, res.status);
74
+ }
75
+ }
76
+
77
+ if (!res.ok) {
78
+ const msg = data.error || `Request failed (${res.status})`;
79
+ const code = data.code;
80
+ switch (code || res.status) {
81
+ case 110: case 401: throw new AuthenticationError(msg);
82
+ case 200: case 404: throw new DomainNotFoundError(msg);
83
+ case 261: throw new DomainUnavailableError(msg);
84
+ case 108: case 400: throw new ValidationError(msg);
85
+ case 280: throw new InsufficientFundsError(msg);
86
+ default: throw new SaacDnsError(msg, code);
87
+ }
88
+ }
89
+
90
+ return data;
91
+ }
92
+
93
+ /**
94
+ * Auto-provision user on first call.
95
+ *
96
+ * Checks if the user already exists via GET /v1/users/me.
97
+ * If authentication fails (user not yet synced), calls POST /v1/auth/login
98
+ * which validates the us_ key against the SAAC platform and upserts the user.
99
+ *
100
+ * This means no manual registration is needed — the agent just sets
101
+ * SAAC_USER_API_KEY and SAAC_USER_EMAIL and everything is provisioned automatically.
102
+ */
103
+ async function ensureRegistered() {
104
+ const { apiKey, email } = getCredentials();
105
+ try {
106
+ await request('GET', '/v1/users/me');
107
+ } catch (err) {
108
+ if (err.name === 'AuthenticationError') {
109
+ // User not synced yet — authenticate with SAAC platform to provision account
110
+ try {
111
+ await fetch(`${BASE_URL}/v1/auth/login`, {
112
+ method: 'POST',
113
+ headers: { 'Content-Type': 'application/json' },
114
+ body: JSON.stringify({ email, api_key: apiKey })
115
+ });
116
+ } catch (_) {
117
+ // Suppress errors; the caller's request() will surface the real issue
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ module.exports = {
124
+ request,
125
+ getCredentials,
126
+ ensureRegistered,
127
+ BASE_URL,
128
+ AuthenticationError,
129
+ DomainNotFoundError,
130
+ DomainUnavailableError,
131
+ ValidationError,
132
+ InsufficientFundsError,
133
+ SaacDnsError
134
+ };