kaax-mcp 0.1.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/dist/client.js ADDED
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Thin HTTP client wrapping the public Kaax v0 API.
3
+ *
4
+ * The MCP server runs locally on the user's machine and talks to Kaax over
5
+ * HTTPS using the API key that lives at `/dashboard/api-manage`. We use
6
+ * `undici.fetch` for the small footprint and Node 18+ compatibility.
7
+ */
8
+ import { fetch } from "undici";
9
+ const DEFAULT_BASE_URL = "https://www.kaax-agritech.com";
10
+ // ─────────────────────────────────────────────────────────────────────────
11
+ // Onboarding handoff — these endpoints do NOT require an apiKey because
12
+ // the MCP calls them BEFORE the user has signed up. They live outside the
13
+ // `KaaxClient` class because the class invariant is "apiKey is non-empty".
14
+ // ─────────────────────────────────────────────────────────────────────────
15
+ /**
16
+ * POST /api/landing/onboarding-token — issue a 15-minute single-use token.
17
+ *
18
+ * The MCP server uses this at the very start of the browser-handoff signup
19
+ * flow. Returns the opaque token + a hint URL the MCP can `openBrowser` on.
20
+ */
21
+ export async function issueOnboardingToken(baseUrl = DEFAULT_BASE_URL, source = "mcp") {
22
+ const url = `${baseUrl.replace(/\/$/, "")}/api/landing/onboarding-token`;
23
+ const res = await fetch(url, {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
26
+ body: JSON.stringify({ source }),
27
+ });
28
+ if (!res.ok) {
29
+ throw new KaaxAPIError(`Token issue failed: ${res.status}`, res.status, await res.text());
30
+ }
31
+ return (await res.json());
32
+ }
33
+ /**
34
+ * GET /api/landing/onboarding-status?token=… — single poll of the binding
35
+ * state. Callers (e.g. `pollOnboardingUntilBound`) loop on this with their
36
+ * own bounded back-off.
37
+ */
38
+ export async function fetchOnboardingStatus(token, baseUrl = DEFAULT_BASE_URL) {
39
+ const url = `${baseUrl.replace(/\/$/, "")}/api/landing/onboarding-status?token=${encodeURIComponent(token)}`;
40
+ const res = await fetch(url, {
41
+ method: "GET",
42
+ headers: { Accept: "application/json" },
43
+ });
44
+ if (!res.ok) {
45
+ throw new KaaxAPIError(`Status check failed: ${res.status}`, res.status, await res.text());
46
+ }
47
+ return (await res.json());
48
+ }
49
+ /**
50
+ * Bounded polling — increasing back-off, hard ceiling on total elapsed time.
51
+ * Returns the first non-`pending` response. Designed so a stuck MCP can
52
+ * never hammer the server: cap of ~120 polls over 15 minutes.
53
+ */
54
+ export async function pollOnboardingUntilBound(token, opts = {}) {
55
+ const baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
56
+ const maxWaitMs = opts.maxWaitMs ?? 15 * 60_000;
57
+ const startedAt = Date.now();
58
+ let delay = opts.initialDelayMs ?? 2_000;
59
+ const maxDelay = opts.maxDelayMs ?? 8_000;
60
+ let attempt = 0;
61
+ while (Date.now() - startedAt < maxWaitMs) {
62
+ attempt += 1;
63
+ const state = await fetchOnboardingStatus(token, baseUrl);
64
+ opts.onTick?.(state, attempt);
65
+ if (state.status !== "pending")
66
+ return state;
67
+ await new Promise((r) => setTimeout(r, delay));
68
+ // Increase delay up to the ceiling — keeps polling gentle.
69
+ delay = Math.min(Math.round(delay * 1.4), maxDelay);
70
+ }
71
+ return { status: "expired" };
72
+ }
73
+ export class KaaxAPIError extends Error {
74
+ status;
75
+ body;
76
+ constructor(message, status, body) {
77
+ super(message);
78
+ this.status = status;
79
+ this.body = body;
80
+ this.name = "KaaxAPIError";
81
+ }
82
+ }
83
+ export class KaaxClient {
84
+ /** May be undefined while the user has not finished onboarding yet. */
85
+ apiKey;
86
+ baseUrl;
87
+ timeoutMs;
88
+ constructor(opts = {}) {
89
+ this.apiKey = opts.apiKey?.trim() || undefined;
90
+ this.baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
91
+ this.timeoutMs = opts.timeoutMs ?? 30_000;
92
+ }
93
+ /** True iff the client has an apiKey and can hit authenticated endpoints. */
94
+ hasApiKey() {
95
+ return typeof this.apiKey === "string" && this.apiKey.length >= 16;
96
+ }
97
+ /**
98
+ * Late-bind an apiKey acquired via the onboarding flow. The new key is
99
+ * used for every subsequent request without restarting the MCP.
100
+ */
101
+ setApiKey(apiKey) {
102
+ if (!apiKey || apiKey.length < 16) {
103
+ throw new Error("Refusing to set an apiKey shorter than 16 characters");
104
+ }
105
+ this.apiKey = apiKey;
106
+ }
107
+ requireKey() {
108
+ if (!this.apiKey) {
109
+ throw new KaaxAPIError("KAAX_API_KEY is not configured. Run kaax_start_onboarding first, or set the env var and restart the MCP.", 401);
110
+ }
111
+ return this.apiKey;
112
+ }
113
+ /**
114
+ * `POST /api/v0/analyses` — the single public endpoint.
115
+ * Filters go in the JSON body; pagination as query params.
116
+ */
117
+ async listAnalyses(filters = {}) {
118
+ const { page, limit, ...body } = filters;
119
+ const url = new URL(`${this.baseUrl}/api/v0/analyses`);
120
+ if (page)
121
+ url.searchParams.set("page", String(page));
122
+ if (limit)
123
+ url.searchParams.set("limit", String(limit));
124
+ return this.request(url.toString(), {
125
+ method: "POST",
126
+ body: Object.keys(body).length ? JSON.stringify(body) : undefined,
127
+ });
128
+ }
129
+ /**
130
+ * Pulls *every* analysis page (up to a safety cap) and concatenates them.
131
+ * Useful for aggregates / heuristics — never on hot UI paths.
132
+ */
133
+ async listAllAnalyses(filters = {}, opts = {}) {
134
+ const pageSize = opts.pageSize ?? 100;
135
+ const maxPages = opts.maxPages ?? 20; // hard cap: 2 000 records
136
+ let page = 1;
137
+ let total = 0;
138
+ const data = [];
139
+ while (page <= maxPages) {
140
+ const resp = await this.listAnalyses({
141
+ ...filters,
142
+ page,
143
+ limit: pageSize,
144
+ });
145
+ total = resp.total ?? data.length;
146
+ data.push(...resp.data);
147
+ if (resp.data.length < pageSize)
148
+ break;
149
+ page += 1;
150
+ }
151
+ return { total, page: 1, limit: data.length, data };
152
+ }
153
+ // ─────────────────────────────────────────────────────────────────────────
154
+ // v2 endpoints — richer per-resource queries, all read-scoped to the
155
+ // caller. Each method swallows 404s (endpoint not deployed yet) and
156
+ // returns `null` so the MCP tool layer can degrade gracefully.
157
+ // ─────────────────────────────────────────────────────────────────────────
158
+ /** GET /api/v2/setup-status — snapshot of fields, models, configs, tags. */
159
+ async setupStatus() {
160
+ return this.tryGet(`/api/v2/setup-status`);
161
+ }
162
+ /** GET /api/v2/fields — paginated, filterable list of the user's fields. */
163
+ async listFields(params = {}) {
164
+ const url = new URL(`${this.baseUrl}/api/v2/fields`);
165
+ if (params.tag)
166
+ url.searchParams.set("tag", params.tag);
167
+ if (params.type)
168
+ url.searchParams.set("type", params.type);
169
+ if (params.q)
170
+ url.searchParams.set("q", params.q);
171
+ if (params.page)
172
+ url.searchParams.set("page", String(params.page));
173
+ if (params.limit)
174
+ url.searchParams.set("limit", String(params.limit));
175
+ return this.tryGet(url.pathname + url.search);
176
+ }
177
+ /** GET /api/v2/models — every detection model owned by the caller. */
178
+ async listModels() {
179
+ return this.tryGet(`/api/v2/models`);
180
+ }
181
+ /** GET /api/v2/tags — tag dictionary with usage counts. */
182
+ async listTagsV2() {
183
+ return this.tryGet(`/api/v2/tags`);
184
+ }
185
+ /** POST /api/v2/tags — append a tag to the user dictionary. */
186
+ async createTag(tag) {
187
+ return this.tryPost(`/api/v2/tags`, { tag });
188
+ }
189
+ /** POST /api/v2/fields/:id/tags — attach tags to a field. */
190
+ async attachFieldTags(fieldId, tags) {
191
+ return this.tryPost(`/api/v2/fields/${encodeURIComponent(fieldId)}/tags`, {
192
+ tags,
193
+ });
194
+ }
195
+ /** GET /api/v2/peer-config-suggestions?type=… — anonymised peer averages. */
196
+ async peerSuggestions(type) {
197
+ return this.tryGet(`/api/v2/peer-config-suggestions?type=${type}`);
198
+ }
199
+ // ─────────────────────────────────────────────────────────────────────────
200
+ // Landing — download permission flow (needs the apiKey but bypasses the
201
+ // Pro-tier subscription gate because it's part of the onboarding funnel).
202
+ // ─────────────────────────────────────────────────────────────────────────
203
+ /** POST /api/landing/request-download — submit the HubSpot form server-side. */
204
+ async requestDownload(body) {
205
+ return this.request(`${this.baseUrl}/api/landing/request-download`, { method: "POST", body: JSON.stringify(body ?? {}) });
206
+ }
207
+ /** GET /api/landing/download-status — returns status + binary URLs on approval. */
208
+ async downloadStatus() {
209
+ return this.request(`${this.baseUrl}/api/landing/download-status`, { method: "GET" });
210
+ }
211
+ // ─────────────────────────────────────────────────────────────────────────
212
+ // Account overview — the ONE endpoint a free-tier user can call. Used by
213
+ // the MCP to decide what tools to enable and to give a clear "you need Pro"
214
+ // explanation BEFORE attempting a paid call that would 403.
215
+ //
216
+ // Cached in-memory for the rest of the process lifetime with a short TTL —
217
+ // checking it on every paid call is fine (the MCP is single-user) but we
218
+ // don't want to spam the endpoint either.
219
+ // ─────────────────────────────────────────────────────────────────────────
220
+ overviewCache = null;
221
+ static OVERVIEW_TTL_MS = 60_000; // 1 minute
222
+ /** GET /api/landing/account-overview — plan + limits + capabilities. */
223
+ async accountOverview(force = false) {
224
+ const now = Date.now();
225
+ if (!force && this.overviewCache && now < this.overviewCache.expiresAt) {
226
+ return this.overviewCache.value;
227
+ }
228
+ const value = await this.request(`${this.baseUrl}/api/landing/account-overview`, { method: "GET" });
229
+ this.overviewCache = {
230
+ value,
231
+ expiresAt: now + KaaxClient.OVERVIEW_TTL_MS,
232
+ };
233
+ return value;
234
+ }
235
+ /** Invalidate the overview cache — call after a successful upgrade. */
236
+ invalidateOverview() {
237
+ this.overviewCache = null;
238
+ }
239
+ /** Helper — GET that returns `null` on 404 / network errors. */
240
+ async tryGet(pathAndQuery) {
241
+ try {
242
+ return await this.request(`${this.baseUrl}${pathAndQuery}`, {
243
+ method: "GET",
244
+ });
245
+ }
246
+ catch (err) {
247
+ if (err instanceof KaaxAPIError && err.status === 404)
248
+ return null;
249
+ throw err;
250
+ }
251
+ }
252
+ /** Helper — POST that returns `null` on 404. */
253
+ async tryPost(pathAndQuery, body) {
254
+ try {
255
+ return await this.request(`${this.baseUrl}${pathAndQuery}`, {
256
+ method: "POST",
257
+ body: JSON.stringify(body),
258
+ });
259
+ }
260
+ catch (err) {
261
+ if (err instanceof KaaxAPIError && err.status === 404)
262
+ return null;
263
+ throw err;
264
+ }
265
+ }
266
+ // ─────────────────────────────────────────────────────────────────────────
267
+ // internals
268
+ // ─────────────────────────────────────────────────────────────────────────
269
+ async request(url, init) {
270
+ const apiKey = this.requireKey();
271
+ const controller = new AbortController();
272
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
273
+ try {
274
+ const res = await fetch(url, {
275
+ method: init.method,
276
+ headers: {
277
+ apiKey,
278
+ "Content-Type": "application/json",
279
+ Accept: "application/json",
280
+ },
281
+ body: init.body,
282
+ signal: controller.signal,
283
+ });
284
+ const text = await res.text();
285
+ if (!res.ok) {
286
+ throw new KaaxAPIError(`Kaax API ${res.status} ${res.statusText}`, res.status, text);
287
+ }
288
+ try {
289
+ return JSON.parse(text);
290
+ }
291
+ catch {
292
+ throw new KaaxAPIError("Kaax API returned non-JSON body", res.status, text);
293
+ }
294
+ }
295
+ catch (err) {
296
+ if (err instanceof KaaxAPIError)
297
+ throw err;
298
+ if (err.name === "AbortError") {
299
+ throw new KaaxAPIError(`Kaax API request timed out after ${this.timeoutMs}ms`);
300
+ }
301
+ throw new KaaxAPIError(err.message);
302
+ }
303
+ finally {
304
+ clearTimeout(timer);
305
+ }
306
+ }
307
+ }
308
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shapefile (.shp + .dbf) → KML converter.
3
+ *
4
+ * Runs entirely in JS — no GDAL/ogr2ogr install required. The MCP process
5
+ * has filesystem access on the user's machine, so it can read the input
6
+ * shapefile bundle and write the converted KML next to it.
7
+ *
8
+ * Kaax expects KML 2.2 with Placemarks containing a single Polygon (or
9
+ * Polygon series for multi-features). Each Placemark gets an `<ExtendedData>`
10
+ * block with the original DBF attributes so nothing is lost.
11
+ */
12
+ export interface ConvertResult {
13
+ outputPath: string;
14
+ featuresWritten: number;
15
+ warnings: string[];
16
+ }
17
+ /**
18
+ * Converts a `.shp` (plus its sibling `.dbf` / `.prj`) to a `.kml` file.
19
+ *
20
+ * @param shpPath absolute path to the `.shp` file
21
+ * @param outputPath optional override; defaults to `<shpPath>.kml`
22
+ */
23
+ export declare function convertShapefileToKml(shpPath: string, outputPath?: string): Promise<ConvertResult>;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Shapefile (.shp + .dbf) → KML converter.
3
+ *
4
+ * Runs entirely in JS — no GDAL/ogr2ogr install required. The MCP process
5
+ * has filesystem access on the user's machine, so it can read the input
6
+ * shapefile bundle and write the converted KML next to it.
7
+ *
8
+ * Kaax expects KML 2.2 with Placemarks containing a single Polygon (or
9
+ * Polygon series for multi-features). Each Placemark gets an `<ExtendedData>`
10
+ * block with the original DBF attributes so nothing is lost.
11
+ */
12
+ import * as fs from "node:fs/promises";
13
+ import * as path from "node:path";
14
+ import { open as shpOpen } from "shapefile";
15
+ import { create } from "xmlbuilder2";
16
+ /**
17
+ * Converts a `.shp` (plus its sibling `.dbf` / `.prj`) to a `.kml` file.
18
+ *
19
+ * @param shpPath absolute path to the `.shp` file
20
+ * @param outputPath optional override; defaults to `<shpPath>.kml`
21
+ */
22
+ export async function convertShapefileToKml(shpPath, outputPath) {
23
+ const absolute = path.resolve(shpPath);
24
+ await assertExists(absolute);
25
+ if (path.extname(absolute).toLowerCase() !== ".shp") {
26
+ throw new Error(`Expected a .shp file, got: ${path.extname(absolute)}`);
27
+ }
28
+ const dbfPath = absolute.replace(/\.shp$/i, ".dbf");
29
+ const hasDbf = await fileExists(dbfPath);
30
+ const out = outputPath
31
+ ? path.resolve(outputPath)
32
+ : absolute.replace(/\.shp$/i, ".kml");
33
+ const warnings = [];
34
+ const features = [];
35
+ const source = await shpOpen(absolute, hasDbf ? dbfPath : undefined, {
36
+ encoding: "utf8",
37
+ });
38
+ while (true) {
39
+ const result = await source.read();
40
+ if (result.done)
41
+ break;
42
+ features.push(result.value);
43
+ }
44
+ if (features.length === 0) {
45
+ throw new Error("Shapefile contains no features.");
46
+ }
47
+ const docName = path.basename(absolute, path.extname(absolute));
48
+ const kml = buildKmlDocument(docName, features, warnings);
49
+ await fs.writeFile(out, kml, "utf8");
50
+ return { outputPath: out, featuresWritten: features.length, warnings };
51
+ }
52
+ // ───────────────────────────────────────────────────────────────────────────
53
+ // internals
54
+ // ───────────────────────────────────────────────────────────────────────────
55
+ async function fileExists(p) {
56
+ try {
57
+ await fs.access(p);
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ async function assertExists(p) {
65
+ if (!(await fileExists(p))) {
66
+ throw new Error(`File not found: ${p}`);
67
+ }
68
+ }
69
+ /** Quote a coordinate triple in KML's "lon,lat,alt" textual format. */
70
+ function coordToKml(c) {
71
+ const lon = Number(c[0]);
72
+ const lat = Number(c[1]);
73
+ const alt = Number.isFinite(c[2]) ? Number(c[2]) : 0;
74
+ return `${lon},${lat},${alt}`;
75
+ }
76
+ function ringToKml(ring) {
77
+ return ring.map(coordToKml).join(" ");
78
+ }
79
+ /**
80
+ * Builds the KML XML document. Uses `xmlbuilder2` so escaping / encoding is
81
+ * handled correctly even when DBF properties contain quotes or `<` / `>`.
82
+ */
83
+ function buildKmlDocument(docName, features, warnings) {
84
+ const root = create({ version: "1.0", encoding: "UTF-8" })
85
+ .ele("kml", { xmlns: "http://www.opengis.net/kml/2.2" })
86
+ .ele("Document")
87
+ .ele("name")
88
+ .txt(docName)
89
+ .up()
90
+ .ele("description")
91
+ .txt(`Converted from ${docName}.shp by @kaax/mcp-server`)
92
+ .up();
93
+ features.forEach((feature, idx) => {
94
+ const placemark = root.ele("Placemark");
95
+ const props = feature.properties ?? {};
96
+ const name = String(props.name ?? props.NAME ?? props.id ?? `Feature ${idx + 1}`);
97
+ placemark.ele("name").txt(name).up();
98
+ // Preserve all DBF attributes as ExtendedData/Data pairs.
99
+ if (props && Object.keys(props).length > 0) {
100
+ const ext = placemark.ele("ExtendedData");
101
+ for (const [k, v] of Object.entries(props)) {
102
+ ext
103
+ .ele("Data", { name: k })
104
+ .ele("value")
105
+ .txt(stringify(v))
106
+ .up()
107
+ .up();
108
+ }
109
+ ext.up();
110
+ }
111
+ const geom = feature.geometry;
112
+ if (!geom) {
113
+ warnings.push(`Feature ${idx} has no geometry — skipped`);
114
+ placemark.up();
115
+ return;
116
+ }
117
+ switch (geom.type) {
118
+ case "Polygon":
119
+ writePolygon(placemark, geom.coordinates);
120
+ break;
121
+ case "MultiPolygon": {
122
+ const multi = placemark.ele("MultiGeometry");
123
+ geom.coordinates.forEach((poly) => writePolygon(multi, poly));
124
+ multi.up();
125
+ break;
126
+ }
127
+ case "Point":
128
+ placemark
129
+ .ele("Point")
130
+ .ele("coordinates")
131
+ .txt(coordToKml(geom.coordinates))
132
+ .up()
133
+ .up();
134
+ break;
135
+ case "MultiPoint": {
136
+ const multi = placemark.ele("MultiGeometry");
137
+ geom.coordinates.forEach((c) => multi.ele("Point").ele("coordinates").txt(coordToKml(c)).up().up());
138
+ multi.up();
139
+ break;
140
+ }
141
+ case "LineString":
142
+ placemark
143
+ .ele("LineString")
144
+ .ele("coordinates")
145
+ .txt(ringToKml(geom.coordinates))
146
+ .up()
147
+ .up();
148
+ break;
149
+ case "MultiLineString": {
150
+ const multi = placemark.ele("MultiGeometry");
151
+ geom.coordinates.forEach((line) => multi.ele("LineString").ele("coordinates").txt(ringToKml(line)).up().up());
152
+ multi.up();
153
+ break;
154
+ }
155
+ default:
156
+ warnings.push(`Feature ${idx}: unsupported geometry type "${geom.type}"`);
157
+ }
158
+ placemark.up();
159
+ });
160
+ return root.end({ prettyPrint: true });
161
+ }
162
+ function writePolygon(parent, rings) {
163
+ const polygon = parent.ele("Polygon");
164
+ const [outer, ...holes] = rings;
165
+ if (outer) {
166
+ polygon
167
+ .ele("outerBoundaryIs")
168
+ .ele("LinearRing")
169
+ .ele("coordinates")
170
+ .txt(ringToKml(outer))
171
+ .up()
172
+ .up()
173
+ .up();
174
+ }
175
+ holes.forEach((hole) => polygon
176
+ .ele("innerBoundaryIs")
177
+ .ele("LinearRing")
178
+ .ele("coordinates")
179
+ .txt(ringToKml(hole))
180
+ .up()
181
+ .up()
182
+ .up());
183
+ polygon.up();
184
+ }
185
+ function stringify(v) {
186
+ if (v === null || v === undefined)
187
+ return "";
188
+ if (typeof v === "object")
189
+ return JSON.stringify(v);
190
+ return String(v);
191
+ }
192
+ //# sourceMappingURL=convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Tiny i18n — user-facing strings that the agent passes through verbatim.
3
+ *
4
+ * The agent (Claude) translates fluently on its own, but messages that
5
+ * users tend to copy/paste (URLs, codes, file paths, "your account is
6
+ * locked" stubs) we localize directly so they read native instead of
7
+ * machine-translated.
8
+ *
9
+ * Selection:
10
+ * 1. `KAAX_LOCALE` env var ("es" | "en"). Defaults to "es" because the
11
+ * Kaax user base is overwhelmingly Spanish-speaking.
12
+ * 2. Falls back to "es" for unknown locales rather than English so we
13
+ * never accidentally surface English to a Spanish-speaking user.
14
+ */
15
+ export type Locale = "es" | "en";
16
+ export declare function getLocale(): Locale;
17
+ declare const dict: {
18
+ proRequired_title: {
19
+ es: string;
20
+ en: string;
21
+ };
22
+ proRequired_body: {
23
+ es: string;
24
+ en: string;
25
+ };
26
+ proRequired_cta: {
27
+ es: string;
28
+ en: string;
29
+ };
30
+ freeAvailable_header: {
31
+ es: string;
32
+ en: string;
33
+ };
34
+ freeAvailable_items: {
35
+ es: string[];
36
+ en: string[];
37
+ };
38
+ needsActivation: {
39
+ es: string;
40
+ en: string;
41
+ };
42
+ missingApiKey: {
43
+ es: string;
44
+ en: string;
45
+ };
46
+ invalidApiKey: {
47
+ es: string;
48
+ en: string;
49
+ };
50
+ rateLimited: {
51
+ es: string;
52
+ en: string;
53
+ };
54
+ network: {
55
+ es: string;
56
+ en: string;
57
+ };
58
+ };
59
+ type DictKey = keyof typeof dict;
60
+ /** Look up a translated phrase, optionally with `{placeholder}` substitution. */
61
+ export declare function t(key: DictKey, vars?: Record<string, string>): string;
62
+ /** Array variant — for bullet lists. */
63
+ export declare function tList(key: DictKey): string[];
64
+ export {};