@openephemeris/mcp-server 3.1.0 → 3.2.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.
Files changed (51) hide show
  1. package/README.md +30 -21
  2. package/dist/index.js +0 -0
  3. package/dist/scripts/dev-allowlist.d.ts +1 -0
  4. package/dist/scripts/dev-allowlist.js +287 -0
  5. package/dist/scripts/pack-audit.d.ts +1 -0
  6. package/dist/scripts/pack-audit.js +45 -0
  7. package/dist/scripts/schema-packs.d.ts +1 -0
  8. package/dist/scripts/schema-packs.js +150 -0
  9. package/dist/scripts/smoke-dev-profile.d.ts +1 -0
  10. package/dist/scripts/smoke-dev-profile.js +25 -0
  11. package/dist/scripts/sync-readme.d.ts +1 -0
  12. package/dist/scripts/sync-readme.js +141 -0
  13. package/dist/src/auth/credentials.d.ts +65 -0
  14. package/dist/src/auth/credentials.js +200 -0
  15. package/dist/src/auth/device-auth.d.ts +56 -0
  16. package/dist/src/auth/device-auth.js +144 -0
  17. package/dist/src/backend/client.d.ts +61 -0
  18. package/dist/src/backend/client.js +335 -0
  19. package/dist/src/index.d.ts +2 -0
  20. package/dist/src/index.js +92 -0
  21. package/dist/src/schema-packs/llm.d.ts +105 -0
  22. package/dist/src/schema-packs/llm.js +429 -0
  23. package/dist/src/tools/auth.d.ts +1 -0
  24. package/dist/src/tools/auth.js +202 -0
  25. package/dist/src/tools/dev.d.ts +1 -0
  26. package/dist/src/tools/dev.js +187 -0
  27. package/dist/src/tools/index.d.ts +25 -0
  28. package/dist/src/tools/index.js +33 -0
  29. package/dist/src/tools/specialized/eclipse.d.ts +1 -0
  30. package/dist/src/tools/specialized/eclipse.js +56 -0
  31. package/dist/src/tools/specialized/electional.d.ts +1 -0
  32. package/dist/src/tools/specialized/electional.js +79 -0
  33. package/dist/src/tools/specialized/human_design.d.ts +1 -0
  34. package/dist/src/tools/specialized/human_design.js +53 -0
  35. package/dist/src/tools/specialized/moon.d.ts +1 -0
  36. package/dist/src/tools/specialized/moon.js +50 -0
  37. package/dist/src/tools/specialized/natal.d.ts +1 -0
  38. package/dist/src/tools/specialized/natal.js +71 -0
  39. package/dist/src/tools/specialized/relocation.d.ts +1 -0
  40. package/dist/src/tools/specialized/relocation.js +71 -0
  41. package/dist/src/tools/specialized/synastry.d.ts +1 -0
  42. package/dist/src/tools/specialized/synastry.js +61 -0
  43. package/dist/src/tools/specialized/transits.d.ts +1 -0
  44. package/dist/src/tools/specialized/transits.js +80 -0
  45. package/dist/test/allowlist-and-tools.test.d.ts +1 -0
  46. package/dist/test/allowlist-and-tools.test.js +96 -0
  47. package/dist/test/backend-client.test.d.ts +1 -0
  48. package/dist/test/backend-client.test.js +286 -0
  49. package/dist/test/credentials.test.d.ts +1 -0
  50. package/dist/test/credentials.test.js +143 -0
  51. package/package.json +21 -18
@@ -0,0 +1,335 @@
1
+ import axios, { AxiosError } from "axios";
2
+ import { CredentialManager } from "../auth/credentials.js";
3
+ import { DeviceAuthFlow } from "../auth/device-auth.js";
4
+ const DEFAULT_TIMEOUT_MS = 45_000;
5
+ const RETRY_DELAYS_MS = [1_000, 2_000, 4_000]; // Three retries with backoff
6
+ const RETRYABLE_STATUSES = new Set([429, 502, 503, 504]);
7
+ const RETRYABLE_CODES = new Set(["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN"]);
8
+ const BINARY_ENDPOINT_PREFIXES = [
9
+ "/visualization/bi-wheel",
10
+ "/visualization/chart-wheel",
11
+ "/comparative/visualization/bi-wheel",
12
+ "/comparative/visualization/chart-wheel",
13
+ ];
14
+ const DASHBOARD_ACCOUNT_URL = "https://openephemeris.com/dashboard?tab=account";
15
+ const LOGIN_SIGNUP_URL = "https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount";
16
+ const UPGRADE_URL = "https://openephemeris.com/pay";
17
+ function sleep(ms) {
18
+ return new Promise((resolve) => setTimeout(resolve, ms));
19
+ }
20
+ export class BackendError extends Error {
21
+ status;
22
+ code;
23
+ retryable;
24
+ upgradeUrl;
25
+ constructor(message, status, code, retryable, upgradeUrl) {
26
+ super(message);
27
+ this.status = status;
28
+ this.code = code;
29
+ this.retryable = retryable;
30
+ this.upgradeUrl = upgradeUrl;
31
+ this.name = "BackendError";
32
+ }
33
+ }
34
+ export class BackendClient {
35
+ client;
36
+ userId;
37
+ jwt;
38
+ serviceKey;
39
+ apiKey;
40
+ credentialManager;
41
+ _pendingAuthFlow = null;
42
+ _authFlowResult = null;
43
+ constructor(config) {
44
+ this.userId = config.userId || "anonymous";
45
+ this.jwt =
46
+ config.jwt ||
47
+ config.authToken ||
48
+ process.env.OPENEPHEMERIS_JWT ||
49
+ process.env.ASTROMCP_JWT ||
50
+ process.env.MERIDIAN_AUTH_TOKEN;
51
+ this.serviceKey =
52
+ config.serviceKey ||
53
+ process.env.OPENEPHEMERIS_SERVICE_KEY ||
54
+ process.env.ASTROMCP_SERVICE_KEY ||
55
+ process.env.MERIDIAN_SERVICE_KEY;
56
+ this.apiKey =
57
+ config.apiKey ||
58
+ process.env.OPENEPHEMERIS_API_KEY ||
59
+ process.env.ASTROMCP_API_KEY ||
60
+ process.env.MERIDIAN_API_KEY;
61
+ this.credentialManager = new CredentialManager();
62
+ this.client = axios.create({
63
+ baseURL: config.baseURL ||
64
+ process.env.OPENEPHEMERIS_BACKEND_URL ||
65
+ process.env.ASTROMCP_BACKEND_URL ||
66
+ process.env.MERIDIAN_BACKEND_URL ||
67
+ "https://api.openephemeris.com",
68
+ timeout: DEFAULT_TIMEOUT_MS,
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ },
72
+ });
73
+ // Add auth interceptor (priority: service key > API key > JWT env > cached JWT)
74
+ this.client.interceptors.request.use(async (req) => {
75
+ req.headers = req.headers ?? {};
76
+ // Explicit per-request headers win.
77
+ const hasAuthHeader = typeof req.headers.Authorization === "string" &&
78
+ req.headers.Authorization.length > 0;
79
+ const hasServiceKeyHeader = typeof req.headers["X-Service-Key"] === "string" &&
80
+ req.headers["X-Service-Key"].length > 0;
81
+ const hasApiKeyHeader = typeof req.headers["X-API-Key"] === "string" &&
82
+ req.headers["X-API-Key"].length > 0;
83
+ if (!hasAuthHeader && !hasServiceKeyHeader && !hasApiKeyHeader) {
84
+ if (this.serviceKey) {
85
+ req.headers["X-Service-Key"] = this.serviceKey;
86
+ }
87
+ else if (this.apiKey) {
88
+ req.headers["X-API-Key"] = this.apiKey;
89
+ }
90
+ else if (this.jwt) {
91
+ req.headers.Authorization = `Bearer ${this.jwt}`;
92
+ }
93
+ else {
94
+ // Fall through to cached credentials from device auth flow
95
+ const cachedToken = await this.credentialManager.getValidToken();
96
+ if (cachedToken) {
97
+ req.headers.Authorization = `Bearer ${cachedToken}`;
98
+ }
99
+ else {
100
+ // No credentials anywhere — auto-start device auth
101
+ await this.autoStartDeviceAuth();
102
+ }
103
+ }
104
+ }
105
+ return req;
106
+ });
107
+ }
108
+ /**
109
+ * Start the device auth flow in the background if not already running.
110
+ * Called automatically when no credentials are found in the interceptor.
111
+ * Idempotent — only starts once per client lifetime.
112
+ */
113
+ async autoStartDeviceAuth() {
114
+ if (this._pendingAuthFlow)
115
+ return; // Already started
116
+ try {
117
+ const flow = new DeviceAuthFlow();
118
+ const startResult = await flow.start();
119
+ this._authFlowResult = {
120
+ verification_uri: startResult.verification_uri,
121
+ user_code: startResult.user_code,
122
+ verification_uri_complete: startResult.verification_uri_complete,
123
+ };
124
+ // Print to stderr so it's visible in MCP host logs
125
+ console.error(`\n🔗 OpenEphemeris: Account connection required.\n` +
126
+ ` Visit: ${startResult.verification_uri}\n` +
127
+ ` Code: ${startResult.user_code}\n` +
128
+ ` Or: ${startResult.verification_uri_complete}\n`);
129
+ // Poll in background (don't await)
130
+ this._pendingAuthFlow = flow
131
+ .poll(startResult.device_code, (attempt) => {
132
+ if (attempt % 12 === 0) {
133
+ console.error(` Still waiting... Enter code ${startResult.user_code} at ${startResult.verification_uri}`);
134
+ }
135
+ })
136
+ .then(() => {
137
+ console.error(`✅ Authenticated! Subsequent requests will use your account.`);
138
+ this._authFlowResult = null;
139
+ })
140
+ .catch((err) => {
141
+ console.error(`❌ Device auth failed: ${err.message}`);
142
+ this._pendingAuthFlow = null; // Allow retry
143
+ });
144
+ }
145
+ catch (err) {
146
+ console.error(`Could not start device auth: ${err.message}`);
147
+ // Don't block — the request will go out unauthenticated and
148
+ // the 401 response will surface the error message to the user.
149
+ }
150
+ }
151
+ expectsBinaryResponse(path, options) {
152
+ const normalizedPath = path.split("?")[0].trim().toLowerCase();
153
+ if (BINARY_ENDPOINT_PREFIXES.some((prefix) => normalizedPath.startsWith(prefix))) {
154
+ return true;
155
+ }
156
+ const format = options?.params?.format;
157
+ if (typeof format === "string") {
158
+ const normalizedFormat = format.trim().toLowerCase();
159
+ if (normalizedFormat === "png" || normalizedFormat === "svg") {
160
+ return true;
161
+ }
162
+ }
163
+ const acceptHeader = Object.entries(options?.headers ?? {}).find(([key]) => key.toLowerCase() === "accept")?.[1];
164
+ if (typeof acceptHeader === "string" && acceptHeader.toLowerCase().includes("image/")) {
165
+ return true;
166
+ }
167
+ return false;
168
+ }
169
+ toBuffer(data) {
170
+ if (data == null)
171
+ return null;
172
+ if (Buffer.isBuffer(data))
173
+ return data;
174
+ if (data instanceof ArrayBuffer)
175
+ return Buffer.from(data);
176
+ if (ArrayBuffer.isView(data)) {
177
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
178
+ }
179
+ if (typeof data === "string")
180
+ return Buffer.from(data);
181
+ return null;
182
+ }
183
+ decodePayload(data) {
184
+ const asBuffer = this.toBuffer(data);
185
+ if (!asBuffer) {
186
+ return data;
187
+ }
188
+ const text = asBuffer.toString("utf8").trim();
189
+ if (!text)
190
+ return "";
191
+ try {
192
+ return JSON.parse(text);
193
+ }
194
+ catch {
195
+ return text;
196
+ }
197
+ }
198
+ extractMessage(data, fallback) {
199
+ if (typeof data === "string") {
200
+ const trimmed = data.trim();
201
+ return trimmed || fallback;
202
+ }
203
+ if (data && typeof data === "object") {
204
+ const obj = data;
205
+ for (const key of ["message", "detail", "title", "error"]) {
206
+ const value = obj[key];
207
+ if (typeof value === "string" && value.trim()) {
208
+ return value.trim();
209
+ }
210
+ }
211
+ }
212
+ return fallback;
213
+ }
214
+ normalizeContentType(value) {
215
+ if (typeof value !== "string")
216
+ return "";
217
+ return value.split(";")[0]?.trim().toLowerCase() || "";
218
+ }
219
+ isBinaryContentType(contentType) {
220
+ return contentType.startsWith("image/") || contentType === "application/octet-stream";
221
+ }
222
+ /** Maps an AxiosError to a structured BackendError. */
223
+ mapError(error) {
224
+ if (error.response) {
225
+ const status = error.response.status;
226
+ const data = this.decodePayload(error.response.data);
227
+ const msg = this.extractMessage(data, error.message);
228
+ if (status === 401) {
229
+ const authInfo = this._authFlowResult;
230
+ if (authInfo) {
231
+ return new BackendError(`🔗 Account connection required. Please visit ${authInfo.verification_uri} ` +
232
+ `and enter code: ${authInfo.user_code}\n\n` +
233
+ `Or open this direct link: ${authInfo.verification_uri_complete}\n\n` +
234
+ `Once you authorize, retry your request — the server is polling in the background. ` +
235
+ `If you don't have an account, you can create one at the link above (free tier, no credit card needed).`, 401, "auth_required", true, authInfo.verification_uri_complete);
236
+ }
237
+ return new BackendError(`Authentication required: ${msg}. Run auth.login to connect your account, ` +
238
+ `or set OPENEPHEMERIS_API_KEY in your MCP server config.`, 401, "auth_required", false, LOGIN_SIGNUP_URL);
239
+ }
240
+ if (status === 402) {
241
+ return new BackendError(`Usage quota exceeded: ${msg}. Review usage and overage settings in ${DASHBOARD_ACCOUNT_URL}, ` +
242
+ `or upgrade at ${UPGRADE_URL}.`, 402, "quota_exceeded", false, UPGRADE_URL);
243
+ }
244
+ if (status === 403) {
245
+ return new BackendError(`Access blocked by plan or policy: ${msg}. If this endpoint is tier-gated, upgrade at ${UPGRADE_URL}. ` +
246
+ `Manage keys and billing at ${DASHBOARD_ACCOUNT_URL}.`, 403, "tier_required", false, UPGRADE_URL);
247
+ }
248
+ if (status === 429) {
249
+ return new BackendError(`Rate limit exceeded. Your plan credits may be exhausted or requests are too frequent. ` +
250
+ `Check usage at ${DASHBOARD_ACCOUNT_URL}, then retry in a moment. (${msg})`, 429, "rate_limited", true);
251
+ }
252
+ if (status === 404)
253
+ return new BackendError(`Resource not found: ${msg}`, 404, "not_found", false);
254
+ if (status === 400)
255
+ return new BackendError(`Invalid request: ${msg}`, 400, "bad_request", false);
256
+ if (status >= 500)
257
+ return new BackendError(`Backend error (${status}): ${msg}`, status, "server_error", true);
258
+ }
259
+ return new BackendError(`Network error: ${error.message}`, 0, "network_error", true);
260
+ }
261
+ async get(path, params, timeoutMs) {
262
+ return this.request("GET", path, { params, timeoutMs });
263
+ }
264
+ async post(path, data, timeoutMs) {
265
+ return this.request("POST", path, { data, timeoutMs });
266
+ }
267
+ async delete(path) {
268
+ return this.request("DELETE", path);
269
+ }
270
+ async request(method, path, options) {
271
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
272
+ const expectsBinary = this.expectsBinaryResponse(path, options);
273
+ let lastError = new Error("Unknown error");
274
+ // Attempt + retries on transient failures (429, 502, 503, 504, network errors)
275
+ for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
276
+ try {
277
+ const response = await this.client.request({
278
+ method,
279
+ url: path,
280
+ params: options?.params,
281
+ data: options?.data,
282
+ headers: options?.headers,
283
+ timeout: timeoutMs,
284
+ responseType: expectsBinary ? "arraybuffer" : "json",
285
+ });
286
+ if (!expectsBinary) {
287
+ return response.data;
288
+ }
289
+ const contentType = this.normalizeContentType(response.headers?.["content-type"]);
290
+ if (!this.isBinaryContentType(contentType)) {
291
+ return this.decodePayload(response.data);
292
+ }
293
+ const bytes = this.toBuffer(response.data) ?? Buffer.alloc(0);
294
+ const payload = {
295
+ content_type: contentType || "application/octet-stream",
296
+ content_length: bytes.length,
297
+ encoding: "base64",
298
+ data_base64: bytes.toString("base64"),
299
+ };
300
+ return payload;
301
+ }
302
+ catch (err) {
303
+ if (err instanceof AxiosError) {
304
+ const isRetryable = (err.response && RETRYABLE_STATUSES.has(err.response.status)) ||
305
+ (!err.response && err.code && RETRYABLE_CODES.has(err.code));
306
+ if (isRetryable && attempt < RETRY_DELAYS_MS.length) {
307
+ const jitter = Math.random() * 500;
308
+ await sleep(RETRY_DELAYS_MS[attempt] + jitter);
309
+ continue;
310
+ }
311
+ lastError = this.mapError(err);
312
+ }
313
+ else {
314
+ lastError = err instanceof Error ? err : new Error(String(err));
315
+ }
316
+ throw lastError;
317
+ }
318
+ }
319
+ throw lastError;
320
+ }
321
+ getUserId() {
322
+ return this.userId;
323
+ }
324
+ }
325
+ // Singleton instance
326
+ export const backendClient = new BackendClient({
327
+ baseURL: process.env.OPENEPHEMERIS_BACKEND_URL ||
328
+ process.env.ASTROMCP_BACKEND_URL ||
329
+ process.env.MERIDIAN_BACKEND_URL ||
330
+ "https://api.openephemeris.com",
331
+ userId: process.env.MCP_USER_ID || "anonymous",
332
+ jwt: process.env.OPENEPHEMERIS_JWT || process.env.ASTROMCP_JWT || process.env.MERIDIAN_AUTH_TOKEN,
333
+ serviceKey: process.env.OPENEPHEMERIS_SERVICE_KEY || process.env.ASTROMCP_SERVICE_KEY || process.env.MERIDIAN_SERVICE_KEY,
334
+ apiKey: process.env.OPENEPHEMERIS_API_KEY || process.env.ASTROMCP_API_KEY || process.env.MERIDIAN_API_KEY,
335
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
+ import { initTools, toolRegistry } from "./tools/index.js";
9
+ function resolveServerVersion() {
10
+ try {
11
+ const here = path.dirname(fileURLToPath(import.meta.url));
12
+ const pkgPath = path.resolve(here, "..", "package.json");
13
+ const raw = fs.readFileSync(pkgPath, "utf-8");
14
+ const parsed = JSON.parse(raw);
15
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
16
+ return parsed.version.trim();
17
+ }
18
+ }
19
+ catch {
20
+ // Ignore and fall back to an explicit unknown version.
21
+ }
22
+ return "0.0.0-unknown";
23
+ }
24
+ const server = new Server({
25
+ name: "openephemeris-mcp",
26
+ version: resolveServerVersion(),
27
+ }, {
28
+ capabilities: {
29
+ tools: {},
30
+ },
31
+ });
32
+ // List available tools
33
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
34
+ return {
35
+ tools: Object.values(toolRegistry).map((tool) => ({
36
+ name: tool.name,
37
+ description: tool.description,
38
+ inputSchema: tool.inputSchema,
39
+ annotations: tool.annotations ?? {
40
+ title: tool.name,
41
+ readOnlyHint: true,
42
+ destructiveHint: false,
43
+ },
44
+ })),
45
+ };
46
+ });
47
+ // Handle tool calls
48
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
49
+ const toolName = request.params.name;
50
+ const tool = toolRegistry[toolName];
51
+ if (!tool) {
52
+ throw new Error(`Unknown tool: ${toolName}`);
53
+ }
54
+ try {
55
+ const result = await tool.handler(request.params.arguments ?? {});
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: JSON.stringify(result, null, 2),
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ catch (error) {
66
+ const errorMessage = error instanceof Error ? error.message : String(error);
67
+ return {
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: JSON.stringify({
72
+ success: false,
73
+ error: "TOOL_EXECUTION_ERROR",
74
+ message: errorMessage,
75
+ }, null, 2),
76
+ },
77
+ ],
78
+ isError: true,
79
+ };
80
+ }
81
+ });
82
+ // Start server
83
+ async function main() {
84
+ await initTools();
85
+ const transport = new StdioServerTransport();
86
+ await server.connect(transport);
87
+ console.error("Open Ephemeris MCP server running on stdio");
88
+ }
89
+ main().catch((error) => {
90
+ console.error("Fatal error:", error);
91
+ process.exit(1);
92
+ });
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+ export declare const LLM_V2_POINTS_SCHEMA: readonly ["id", "kind", "src", "lon", "lat", "spd", "rx", "sg", "deg", "hs", "dec", "oob", "ed", "ed_sc", "ad_sc", "on_cusp"];
3
+ export declare const LLM_V2_ASPECTS_SCHEMA: readonly ["a_i", "b_i", "t", "orb", "orb_pct", "app", "str"];
4
+ export declare const LLM_V2_DICT: {
5
+ readonly sign_id: readonly ["ari", "tau", "gem", "can", "leo", "vir", "lib", "sco", "sag", "cap", "aqu", "pis"];
6
+ readonly aspect_id: readonly ["con", "opp", "tri", "sqr", "sex"];
7
+ readonly aspect_angle: readonly [0, 180, 120, 90, 60];
8
+ readonly kind_id: readonly ["planet", "angle", "node", "lilith", "asteroid", "other"];
9
+ };
10
+ export declare const LlmV2PayloadSchema: z.ZodObject<{
11
+ output_mode: z.ZodOptional<z.ZodLiteral<"llm">>;
12
+ schema_version: z.ZodOptional<z.ZodLiteral<"llm">>;
13
+ chart: z.ZodObject<{
14
+ chart_type: z.ZodEnum<{
15
+ natal: "natal";
16
+ synastry: "synastry";
17
+ }>;
18
+ subject: z.ZodOptional<z.ZodObject<{
19
+ datetime: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
+ jd: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
21
+ lat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
22
+ lon: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
23
+ }, z.core.$strict>>;
24
+ }, z.core.$strict>;
25
+ rules: z.ZodOptional<z.ZodObject<{
26
+ zodiac_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
27
+ house_system: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ coordinate_system: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
+ node_source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ lon_range: z.ZodLiteral<"0_360">;
31
+ aspect_set: z.ZodLiteral<"major_default">;
32
+ orb_profile: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ strength_basis: z.ZodLiteral<"engine">;
34
+ point_order_id: z.ZodLiteral<"astro_point_order_v1">;
35
+ core_set_id: z.ZodLiteral<"core_interp_set_v1">;
36
+ }, z.core.$strict>>;
37
+ reliability: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
+ provenance: z.ZodOptional<z.ZodObject<{
39
+ engine: z.ZodString;
40
+ ephemeris_source: z.ZodString;
41
+ ephemeris_version: z.ZodString;
42
+ validation_profile: z.ZodOptional<z.ZodNullable<z.ZodString>>;
43
+ precision_level: z.ZodOptional<z.ZodNullable<z.ZodString>>;
44
+ checksum: z.ZodOptional<z.ZodNullable<z.ZodString>>;
45
+ }, z.core.$strict>>;
46
+ dict: z.ZodObject<{
47
+ sign_id: z.ZodTuple<any, null>;
48
+ aspect_id: z.ZodTuple<any, null>;
49
+ aspect_angle: z.ZodTuple<any, null>;
50
+ kind_id: z.ZodTuple<any, null>;
51
+ }, z.core.$strict>;
52
+ present: z.ZodObject<{
53
+ point_count: z.ZodNumber;
54
+ kinds: z.ZodRecord<z.ZodString, z.ZodNumber>;
55
+ }, z.core.$strict>;
56
+ points_schema: z.ZodTuple<any, null>;
57
+ points: z.ZodArray<z.ZodTuple<[z.ZodString, z.ZodEnum<{
58
+ [x: string]: any;
59
+ }>, z.ZodNumber, z.ZodNumber, z.ZodNullable<z.ZodNumber>, z.ZodNullable<z.ZodNumber>, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNullable<z.ZodNumber>, z.ZodNullable<z.ZodNumber>, z.ZodNumber, z.ZodEnum<{
60
+ none: "none";
61
+ dom: "dom";
62
+ ex: "ex";
63
+ det: "det";
64
+ fall: "fall";
65
+ }>, z.ZodNullable<z.ZodNumber>, z.ZodNullable<z.ZodNumber>, z.ZodNumber], null>>;
66
+ angles: z.ZodOptional<z.ZodObject<{
67
+ asc: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
68
+ mc: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
69
+ dsc: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
70
+ ic: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
71
+ }, z.core.$strict>>;
72
+ houses: z.ZodOptional<z.ZodObject<{
73
+ system: z.ZodOptional<z.ZodNullable<z.ZodString>>;
74
+ cusps: z.ZodArray<z.ZodNumber>;
75
+ }, z.core.$strict>>;
76
+ aspects_schema: z.ZodTuple<any, null>;
77
+ aspects: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
78
+ patterns: z.ZodOptional<z.ZodObject<{
79
+ stellium: z.ZodArray<z.ZodUnknown>;
80
+ t_square: z.ZodArray<z.ZodUnknown>;
81
+ grand_trine: z.ZodArray<z.ZodUnknown>;
82
+ kite: z.ZodArray<z.ZodUnknown>;
83
+ yod: z.ZodArray<z.ZodUnknown>;
84
+ grand_cross: z.ZodArray<z.ZodUnknown>;
85
+ }, z.core.$strict>>;
86
+ extras: z.ZodOptional<z.ZodObject<{
87
+ fixed_star_conjunctions: z.ZodArray<z.ZodUnknown>;
88
+ lots: z.ZodArray<z.ZodUnknown>;
89
+ }, z.core.$strict>>;
90
+ counts: z.ZodObject<{
91
+ point_count: z.ZodNumber;
92
+ aspect_count: z.ZodNumber;
93
+ aspect_count_unfiltered: z.ZodOptional<z.ZodNumber>;
94
+ midpoint_count: z.ZodOptional<z.ZodNumber>;
95
+ declination_aspect_count: z.ZodOptional<z.ZodNumber>;
96
+ stellium_count: z.ZodOptional<z.ZodNumber>;
97
+ }, z.core.$strict>;
98
+ analysis: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
99
+ human_design: z.ZodOptional<z.ZodObject<{
100
+ gates_schema: z.ZodArray<z.ZodString>;
101
+ gates: z.ZodArray<z.ZodArray<z.ZodAny>>;
102
+ }, z.core.$strict>>;
103
+ }, z.core.$strict>;
104
+ export type LlmV2Payload = z.infer<typeof LlmV2PayloadSchema>;
105
+ export declare const llmV2JsonSchema: Record<string, unknown>;