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/README.md +274 -0
- package/dist/client.d.ts +118 -0
- package/dist/client.js +308 -0
- package/dist/client.js.map +1 -0
- package/dist/convert.d.ts +23 -0
- package/dist/convert.js +192 -0
- package/dist/convert.js.map +1 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +98 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +37 -0
- package/dist/platform.js +72 -0
- package/dist/platform.js.map +1 -0
- package/dist/prompts.d.ts +12 -0
- package/dist/prompts.js +185 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +17 -0
- package/dist/resources.js +305 -0
- package/dist/resources.js.map +1 -0
- package/dist/spatial.d.ts +51 -0
- package/dist/spatial.js +152 -0
- package/dist/spatial.js.map +1 -0
- package/dist/tools.d.ts +19 -0
- package/dist/tools.js +926 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +203 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
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>;
|
package/dist/convert.js
ADDED
|
@@ -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 {};
|