ether-to-astro 1.0.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 (138) hide show
  1. package/.env.example +13 -0
  2. package/.github/pull_request_template.md +16 -0
  3. package/.github/workflows/release.yml +35 -0
  4. package/.github/workflows/test.yml +32 -0
  5. package/AGENTS.md +99 -0
  6. package/LICENSE +18 -0
  7. package/NOTICE.md +45 -0
  8. package/README.md +301 -0
  9. package/SETUP.md +70 -0
  10. package/TESTING_SUMMARY.md +238 -0
  11. package/TEST_SUITE_STATUS.md +218 -0
  12. package/biome.json +48 -0
  13. package/dist/astro-service.d.ts +98 -0
  14. package/dist/astro-service.js +496 -0
  15. package/dist/chart-types.d.ts +52 -0
  16. package/dist/chart-types.js +51 -0
  17. package/dist/charts.d.ts +125 -0
  18. package/dist/charts.js +324 -0
  19. package/dist/cli.d.ts +7 -0
  20. package/dist/cli.js +472 -0
  21. package/dist/constants.d.ts +81 -0
  22. package/dist/constants.js +76 -0
  23. package/dist/eclipses.d.ts +85 -0
  24. package/dist/eclipses.js +184 -0
  25. package/dist/ephemeris.d.ts +120 -0
  26. package/dist/ephemeris.js +379 -0
  27. package/dist/formatter.d.ts +2 -0
  28. package/dist/formatter.js +22 -0
  29. package/dist/houses.d.ts +82 -0
  30. package/dist/houses.js +169 -0
  31. package/dist/index.d.ts +14 -0
  32. package/dist/index.js +150 -0
  33. package/dist/loader.d.ts +2 -0
  34. package/dist/loader.js +31 -0
  35. package/dist/logger.d.ts +25 -0
  36. package/dist/logger.js +73 -0
  37. package/dist/profile-store.d.ts +48 -0
  38. package/dist/profile-store.js +156 -0
  39. package/dist/riseset.d.ts +82 -0
  40. package/dist/riseset.js +185 -0
  41. package/dist/storage.d.ts +10 -0
  42. package/dist/storage.js +40 -0
  43. package/dist/time-utils.d.ts +68 -0
  44. package/dist/time-utils.js +136 -0
  45. package/dist/tool-registry.d.ts +35 -0
  46. package/dist/tool-registry.js +307 -0
  47. package/dist/tool-result.d.ts +175 -0
  48. package/dist/tool-result.js +188 -0
  49. package/dist/transits.d.ts +108 -0
  50. package/dist/transits.js +263 -0
  51. package/dist/types.d.ts +450 -0
  52. package/dist/types.js +161 -0
  53. package/example-usage.md +131 -0
  54. package/natal-chart.json +187 -0
  55. package/package.json +61 -0
  56. package/scripts/download-ephemeris.js +115 -0
  57. package/setup.sh +21 -0
  58. package/src/astro-service.ts +710 -0
  59. package/src/chart-types.ts +125 -0
  60. package/src/charts.ts +399 -0
  61. package/src/cli.ts +694 -0
  62. package/src/constants.ts +89 -0
  63. package/src/eclipses.ts +226 -0
  64. package/src/ephemeris.ts +437 -0
  65. package/src/formatter.ts +25 -0
  66. package/src/houses.ts +202 -0
  67. package/src/index.ts +170 -0
  68. package/src/loader.ts +36 -0
  69. package/src/logger.ts +104 -0
  70. package/src/profile-store.ts +285 -0
  71. package/src/riseset.ts +229 -0
  72. package/src/time-utils.ts +167 -0
  73. package/src/tool-registry.ts +357 -0
  74. package/src/tool-result.ts +283 -0
  75. package/src/transits.ts +352 -0
  76. package/src/types.ts +547 -0
  77. package/tests/README.md +173 -0
  78. package/tests/TESTING_STRATEGY.md +178 -0
  79. package/tests/fixtures/bowen-yang-chart.ts +69 -0
  80. package/tests/fixtures/calculate-expected.ts +81 -0
  81. package/tests/fixtures/expected-results.ts +117 -0
  82. package/tests/fixtures/generate-expected-simple.ts +94 -0
  83. package/tests/helpers/date-fixtures.ts +15 -0
  84. package/tests/helpers/ephem.ts +11 -0
  85. package/tests/helpers/temp.ts +9 -0
  86. package/tests/setup.ts +11 -0
  87. package/tests/unit/astro-service.test.ts +323 -0
  88. package/tests/unit/chart-types.test.ts +18 -0
  89. package/tests/unit/charts-errors.test.ts +42 -0
  90. package/tests/unit/charts.test.ts +157 -0
  91. package/tests/unit/cli-commands.test.ts +82 -0
  92. package/tests/unit/cli-profiles.test.ts +128 -0
  93. package/tests/unit/cli.test.ts +191 -0
  94. package/tests/unit/constants.test.ts +26 -0
  95. package/tests/unit/correctness-critical.test.ts +408 -0
  96. package/tests/unit/eclipses.test.ts +108 -0
  97. package/tests/unit/ephemeris.test.ts +213 -0
  98. package/tests/unit/error-handling.test.ts +116 -0
  99. package/tests/unit/formatter.test.ts +29 -0
  100. package/tests/unit/houses-errors.test.ts +27 -0
  101. package/tests/unit/houses-validation.test.ts +164 -0
  102. package/tests/unit/houses.test.ts +205 -0
  103. package/tests/unit/profile-store.test.ts +163 -0
  104. package/tests/unit/real-user-charts.test.ts +148 -0
  105. package/tests/unit/riseset.test.ts +106 -0
  106. package/tests/unit/solver-edges.test.ts +197 -0
  107. package/tests/unit/time-utils-temporal.test.ts +303 -0
  108. package/tests/unit/time-utils.test.ts +173 -0
  109. package/tests/unit/tool-registry.test.ts +222 -0
  110. package/tests/unit/tool-result.test.ts +45 -0
  111. package/tests/unit/transit-correctness.test.ts +78 -0
  112. package/tests/unit/transits.test.ts +238 -0
  113. package/tests/validation/README.md +32 -0
  114. package/tests/validation/adapters/astrolog.ts +306 -0
  115. package/tests/validation/adapters/internal.ts +184 -0
  116. package/tests/validation/compare/eclipses.ts +47 -0
  117. package/tests/validation/compare/houses.ts +76 -0
  118. package/tests/validation/compare/positions.ts +104 -0
  119. package/tests/validation/compare/riseSet.ts +48 -0
  120. package/tests/validation/compare/roots.ts +90 -0
  121. package/tests/validation/compare/transits.ts +69 -0
  122. package/tests/validation/fixtures/astrolog-parity/core.ts +194 -0
  123. package/tests/validation/fixtures/eclipses/core.ts +14 -0
  124. package/tests/validation/fixtures/houses/core.ts +47 -0
  125. package/tests/validation/fixtures/positions/core.ts +159 -0
  126. package/tests/validation/fixtures/rise-set/core.ts +20 -0
  127. package/tests/validation/fixtures/roots/core.ts +47 -0
  128. package/tests/validation/fixtures/transits/core.ts +61 -0
  129. package/tests/validation/fixtures/transits/dst.ts +21 -0
  130. package/tests/validation/oracle.spec.ts +129 -0
  131. package/tests/validation/utils/denseRootOracle.ts +269 -0
  132. package/tests/validation/utils/fixtureTypes.ts +146 -0
  133. package/tests/validation/utils/report.ts +60 -0
  134. package/tests/validation/utils/tolerances.ts +23 -0
  135. package/tests/validation/validation.spec.ts +836 -0
  136. package/tools/color-picker.html +388 -0
  137. package/tsconfig.json +17 -0
  138. package/vitest.config.ts +31 -0
@@ -0,0 +1,307 @@
1
+ export const MCP_TOOL_SPECS = [
2
+ {
3
+ name: 'set_natal_chart',
4
+ description: 'Store natal chart data for transit calculations. Birth time should be LOCAL time at the birth location (not UTC). The server converts to UTC using the timezone parameter. Optional birth_time_disambiguation handles DST overlap/gap edge cases (default: reject).',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: {
8
+ name: { type: 'string', description: 'Name for this chart' },
9
+ year: { type: 'number', description: 'Birth year' },
10
+ month: { type: 'number', description: 'Birth month (1-12)' },
11
+ day: { type: 'number', description: 'Birth day' },
12
+ hour: { type: 'number', description: 'Birth hour (0-23, LOCAL TIME at birth location)' },
13
+ minute: { type: 'number', description: 'Birth minute' },
14
+ latitude: { type: 'number', description: 'Birth location latitude' },
15
+ longitude: { type: 'number', description: 'Birth location longitude' },
16
+ timezone: {
17
+ type: 'string',
18
+ description: 'Timezone (e.g., America/New_York, Europe/London)',
19
+ },
20
+ birth_time_disambiguation: {
21
+ type: 'string',
22
+ enum: ['compatible', 'earlier', 'later', 'reject'],
23
+ description: 'How to handle DST-ambiguous or nonexistent local birth times. Default: reject.',
24
+ },
25
+ house_system: {
26
+ type: 'string',
27
+ description: 'House system preference: P=Placidus (default), W=Whole Sign, K=Koch, E=Equal',
28
+ enum: ['P', 'W', 'K', 'E'],
29
+ },
30
+ },
31
+ required: [
32
+ 'name',
33
+ 'year',
34
+ 'month',
35
+ 'day',
36
+ 'hour',
37
+ 'minute',
38
+ 'latitude',
39
+ 'longitude',
40
+ 'timezone',
41
+ ],
42
+ },
43
+ requiresNatalChart: false,
44
+ execute: (ctx, args) => {
45
+ const result = ctx.service.setNatalChart({
46
+ name: args.name,
47
+ year: args.year,
48
+ month: args.month,
49
+ day: args.day,
50
+ hour: args.hour,
51
+ minute: args.minute,
52
+ latitude: args.latitude,
53
+ longitude: args.longitude,
54
+ timezone: args.timezone,
55
+ house_system: args.house_system,
56
+ birth_time_disambiguation: args.birth_time_disambiguation,
57
+ });
58
+ return { kind: 'state', data: result.data, text: result.text, natalChart: result.chart };
59
+ },
60
+ },
61
+ {
62
+ name: 'get_transits',
63
+ description: 'Get transits (aspects between current/future planets and natal chart). Returns aspects within orb, with exact timing when close. Date defaults to today at local noon in the natal chart timezone.',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ date: {
68
+ type: 'string',
69
+ description: 'Date for transits (ISO format YYYY-MM-DD). Defaults to today.',
70
+ },
71
+ categories: {
72
+ type: 'array',
73
+ items: { type: 'string', enum: ['moon', 'personal', 'outer', 'all'] },
74
+ description: 'Planet categories to include: moon, personal (Sun/Mercury/Venus/Mars), outer (Jupiter/Saturn/Uranus/Neptune/Pluto), or all. Defaults to ["all"].',
75
+ },
76
+ include_mundane: {
77
+ type: 'boolean',
78
+ description: 'Include current planetary positions (not transits to natal chart). Defaults to false.',
79
+ },
80
+ days_ahead: {
81
+ type: 'number',
82
+ description: 'Number of days to look ahead for upcoming transits. 0 = today only. Defaults to 0.',
83
+ default: 0,
84
+ },
85
+ max_orb: {
86
+ type: 'number',
87
+ description: 'Maximum orb in degrees to include. Defaults to 8.',
88
+ default: 8,
89
+ },
90
+ exact_only: {
91
+ type: 'boolean',
92
+ description: 'Only return transits with exact times calculated (within 2° orb). Defaults to false.',
93
+ },
94
+ applying_only: {
95
+ type: 'boolean',
96
+ description: 'Only return applying (tightening) transits. Defaults to false.',
97
+ },
98
+ },
99
+ },
100
+ requiresNatalChart: true,
101
+ execute: (ctx, args) => {
102
+ const result = ctx.service.getTransits(ctx.natalChart, {
103
+ date: args.date,
104
+ categories: args.categories,
105
+ include_mundane: args.include_mundane,
106
+ days_ahead: args.days_ahead,
107
+ max_orb: args.max_orb,
108
+ exact_only: args.exact_only,
109
+ applying_only: args.applying_only,
110
+ });
111
+ return { kind: 'state', data: result.data, text: result.text };
112
+ },
113
+ },
114
+ {
115
+ name: 'get_houses',
116
+ description: 'Calculate house cusps, Ascendant, and Midheaven for the natal chart using the specified house system',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ system: {
121
+ type: 'string',
122
+ enum: ['P', 'K', 'W', 'E'],
123
+ description: 'House system: P=Placidus (default), K=Koch, W=Whole Sign, E=Equal',
124
+ default: 'P',
125
+ },
126
+ },
127
+ },
128
+ requiresNatalChart: true,
129
+ execute: (ctx, args) => {
130
+ const result = ctx.service.getHouses(ctx.natalChart, {
131
+ system: args.system,
132
+ });
133
+ return { kind: 'state', data: result.data, text: result.text };
134
+ },
135
+ },
136
+ {
137
+ name: 'get_retrograde_planets',
138
+ description: 'Show which planets are currently retrograde',
139
+ inputSchema: { type: 'object', properties: {} },
140
+ requiresNatalChart: false,
141
+ execute: (ctx, args) => {
142
+ const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
143
+ const result = ctx.service.getRetrogradePlanets(timezone);
144
+ return { kind: 'state', data: result.data, text: result.text };
145
+ },
146
+ },
147
+ {
148
+ name: 'get_rise_set_times',
149
+ description: 'Get sunrise, sunset, moonrise, moonset times for today',
150
+ inputSchema: { type: 'object', properties: {} },
151
+ requiresNatalChart: true,
152
+ execute: async (ctx) => {
153
+ const result = await ctx.service.getRiseSetTimes(ctx.natalChart);
154
+ return { kind: 'state', data: result.data, text: result.text };
155
+ },
156
+ },
157
+ {
158
+ name: 'get_asteroid_positions',
159
+ description: 'Get positions of major asteroids (Chiron, Ceres, Pallas, Juno, Vesta) and Nodes',
160
+ inputSchema: { type: 'object', properties: {} },
161
+ requiresNatalChart: false,
162
+ execute: (ctx, args) => {
163
+ const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
164
+ const result = ctx.service.getAsteroidPositions(timezone);
165
+ return { kind: 'state', data: result.data, text: result.text };
166
+ },
167
+ },
168
+ {
169
+ name: 'get_next_eclipses',
170
+ description: 'Find the next solar and lunar eclipses',
171
+ inputSchema: { type: 'object', properties: {} },
172
+ requiresNatalChart: false,
173
+ execute: (ctx, args) => {
174
+ const timezone = args.timezone ?? ctx.natalChart?.location.timezone ?? 'UTC';
175
+ const result = ctx.service.getNextEclipses(timezone);
176
+ return { kind: 'state', data: result.data, text: result.text };
177
+ },
178
+ },
179
+ {
180
+ name: 'get_server_status',
181
+ description: 'Inspect the current server state: whether a natal chart is loaded, its name and timezone, and the server version. Call this before making assumptions about loaded context.',
182
+ inputSchema: { type: 'object', properties: {} },
183
+ requiresNatalChart: false,
184
+ execute: (ctx) => {
185
+ const result = ctx.service.getServerStatus(ctx.natalChart);
186
+ return { kind: 'state', data: result.data, text: result.text };
187
+ },
188
+ },
189
+ {
190
+ name: 'generate_natal_chart',
191
+ description: 'Generate a visual natal chart wheel with planets, houses, and aspects. Supports SVG, PNG, and WebP formats. Theme defaults to dark (6pm-6am) or light (6am-6pm) based on current time, but can be overridden with the theme parameter.',
192
+ inputSchema: {
193
+ type: 'object',
194
+ properties: {
195
+ theme: {
196
+ type: 'string',
197
+ enum: ['light', 'dark'],
198
+ description: 'Color theme override. Defaults to time-based: dark (6pm-6am) or light (6am-6pm)',
199
+ },
200
+ format: {
201
+ type: 'string',
202
+ enum: ['svg', 'png', 'webp'],
203
+ description: 'Output format (svg, png, or webp), defaults to svg',
204
+ },
205
+ output_path: {
206
+ type: 'string',
207
+ description: 'Optional absolute file path to save the chart (e.g., /path/to/chart.webp)',
208
+ },
209
+ },
210
+ },
211
+ requiresNatalChart: true,
212
+ execute: async (ctx, args) => {
213
+ const result = await ctx.service.generateNatalChart(ctx.natalChart, {
214
+ theme: args.theme,
215
+ format: args.format,
216
+ output_path: args.output_path,
217
+ });
218
+ if (result.outputPath) {
219
+ return { kind: 'content', content: [{ type: 'text', text: result.text }] };
220
+ }
221
+ if (result.format === 'svg' && result.svg) {
222
+ return {
223
+ kind: 'content',
224
+ content: [
225
+ { type: 'text', text: result.text },
226
+ { type: 'text', text: result.svg },
227
+ ],
228
+ };
229
+ }
230
+ if (result.image) {
231
+ return {
232
+ kind: 'content',
233
+ content: [
234
+ { type: 'text', text: result.text },
235
+ { type: 'image', data: result.image.data, mimeType: result.image.mimeType },
236
+ ],
237
+ };
238
+ }
239
+ throw new Error('Chart generation returned no payload.');
240
+ },
241
+ },
242
+ {
243
+ name: 'generate_transit_chart',
244
+ description: 'Generate a visual transit chart showing current transits overlaid on the natal chart. Date defaults to today at local noon in the natal chart timezone. Supports SVG, PNG, and WebP formats with light or dark themes.',
245
+ inputSchema: {
246
+ type: 'object',
247
+ properties: {
248
+ date: {
249
+ type: 'string',
250
+ description: 'Optional date for transits (ISO format), defaults to today at local noon',
251
+ },
252
+ theme: {
253
+ type: 'string',
254
+ enum: ['light', 'dark'],
255
+ description: 'Color theme override. Defaults to time-based: dark (6pm-6am) or light (6am-6pm)',
256
+ },
257
+ format: {
258
+ type: 'string',
259
+ enum: ['svg', 'png', 'webp'],
260
+ description: 'Output format (svg, png, or webp), defaults to svg',
261
+ },
262
+ output_path: {
263
+ type: 'string',
264
+ description: 'Optional absolute file path to save the chart (e.g., /path/to/chart.webp)',
265
+ },
266
+ },
267
+ },
268
+ requiresNatalChart: true,
269
+ execute: async (ctx, args) => {
270
+ const result = await ctx.service.generateTransitChart(ctx.natalChart, {
271
+ date: args.date,
272
+ theme: args.theme,
273
+ format: args.format,
274
+ output_path: args.output_path,
275
+ });
276
+ if (result.outputPath) {
277
+ return { kind: 'content', content: [{ type: 'text', text: result.text }] };
278
+ }
279
+ if (result.format === 'svg' && result.svg) {
280
+ return {
281
+ kind: 'content',
282
+ content: [
283
+ { type: 'text', text: result.text },
284
+ { type: 'text', text: result.svg },
285
+ ],
286
+ };
287
+ }
288
+ if (result.image) {
289
+ return {
290
+ kind: 'content',
291
+ content: [
292
+ { type: 'text', text: result.text },
293
+ { type: 'image', data: result.image.data, mimeType: result.image.mimeType },
294
+ ],
295
+ };
296
+ }
297
+ throw new Error('Transit chart generation returned no payload.');
298
+ },
299
+ },
300
+ ];
301
+ export function createToolSpecIndex(specs = MCP_TOOL_SPECS) {
302
+ return new Map(specs.map((spec) => [spec.name, spec]));
303
+ }
304
+ const TOOL_INDEX = createToolSpecIndex();
305
+ export function getToolSpec(name) {
306
+ return TOOL_INDEX.get(name);
307
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Structured error handling for MCP tool results
3
+ *
4
+ * @remarks
5
+ * Provides agent-recoverable error codes and suggestions for self-correction.
6
+ * Distinguishes between recoverable domain errors and hard infrastructure failures.
7
+ *
8
+ * Each error code includes whether it's retryable and suggested fixes
9
+ * to help the agent recover from errors automatically.
10
+ */
11
+ /**
12
+ * Error codes for MCP tool operations
13
+ *
14
+ * @remarks
15
+ * Each code represents a specific category of error that can occur
16
+ * during astrological calculations. The codes are designed to be
17
+ * machine-readable while still being descriptive.
18
+ */
19
+ export type ToolIssueCode = 'INVALID_INPUT' | 'UNKNOWN_PLANET' | 'NO_RISE_SET_EVENT' | 'CIRCUMPOLAR_OBJECT' | 'POLAR_LATITUDE_LIMIT' | 'EPHEMERIS_NOT_INITIALIZED' | 'EPHEMERIS_COMPUTE_FAILED' | 'TIMEZONE_ERROR' | 'INVALID_TIMEZONE' | 'MISSING_NATAL_CHART' | 'INVALID_DATE' | 'INVALID_HOUSE_SYSTEM' | 'FILE_WRITE_FAILED' | 'CHART_RENDER_FAILED' | 'INTERNAL_ERROR';
20
+ /**
21
+ * Structured error information for tool operations
22
+ *
23
+ * @remarks
24
+ * Contains the error code, human-readable message, and metadata
25
+ * to help agents understand and recover from errors.
26
+ */
27
+ export interface ToolIssue {
28
+ /** Machine-readable error code */
29
+ code: ToolIssueCode;
30
+ /** Human-readable error description */
31
+ message: string;
32
+ /** Whether the operation can be retried */
33
+ retryable: boolean;
34
+ /** Suggested fix for the agent (optional) */
35
+ suggestedFix?: string;
36
+ /** Additional error context (optional) */
37
+ details?: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Result type for MCP tool operations
41
+ *
42
+ * @remarks
43
+ * Discriminated union that distinguishes between successful
44
+ * and failed operations. Success includes data and optional warnings,
45
+ * while failure includes structured error information.
46
+ */
47
+ export type ToolResult<T> = {
48
+ /** Success flag */
49
+ ok: true;
50
+ /** Result data */
51
+ data: T;
52
+ /** Optional warnings about the operation */
53
+ warnings?: ToolIssue[];
54
+ } | {
55
+ /** Failure flag */
56
+ ok: false;
57
+ /** Error information */
58
+ error: ToolIssue;
59
+ };
60
+ /**
61
+ * Create a successful tool result
62
+ *
63
+ * @param data - The successful result data
64
+ * @param warnings - Optional warnings about the operation
65
+ * @returns Success result with data
66
+ *
67
+ * @remarks
68
+ * Use this to wrap successful operation results. Warnings are
69
+ * optional and can indicate non-fatal issues.
70
+ */
71
+ export declare function success<T>(data: T, warnings?: ToolIssue[]): ToolResult<T>;
72
+ /**
73
+ * Create a failed tool result
74
+ *
75
+ * @param error - Structured error information
76
+ * @returns Failure result with error
77
+ *
78
+ * @remarks
79
+ * Use this to wrap failed operations. The error should include
80
+ * a code, message, and optionally retry/suggestion information.
81
+ */
82
+ export declare function failure(error: ToolIssue): ToolResult<never>;
83
+ /**
84
+ * Schema version for structured responses.
85
+ * Increment on breaking changes to response shapes.
86
+ */
87
+ export declare const SCHEMA_VERSION = "1.0";
88
+ /**
89
+ * Build a successful MCP tool response with structured data + human text.
90
+ *
91
+ * @param data - Structured payload
92
+ * @param humanText - Human-readable summary
93
+ * @param warnings - Optional warnings
94
+ * @returns MCP content array (JSON first, text second)
95
+ */
96
+ export declare function mcpResult<T>(data: T, humanText: string, warnings?: ToolIssue[]): {
97
+ content: {
98
+ type: "text";
99
+ text: string;
100
+ }[];
101
+ };
102
+ /**
103
+ * Build an error MCP tool response with structured error.
104
+ *
105
+ * @param error - Structured error information
106
+ * @returns MCP content array with isError flag
107
+ */
108
+ export declare function mcpError(error: ToolIssue): {
109
+ content: {
110
+ type: "text";
111
+ text: string;
112
+ }[];
113
+ isError: boolean;
114
+ };
115
+ /**
116
+ * Map Swiss Ephemeris errors to structured tool issues
117
+ *
118
+ * @param context - Operation context where error occurred
119
+ * @param err - Raw error from Swiss Ephemeris
120
+ * @param details - Additional error context
121
+ * @returns Structured tool issue with retry information
122
+ *
123
+ * @remarks
124
+ * Converts low-level Swiss Ephemeris errors into structured,
125
+ * agent-recoverable error codes with suggested fixes.
126
+ */
127
+ export declare function mapSweError(context: string, err: unknown, details?: Record<string, unknown>): ToolIssue;
128
+ /**
129
+ * Create a structured error for missing rise/set events
130
+ *
131
+ * @param eventType - Type of event that was missing
132
+ * @param planet - Planet name for context
133
+ * @param details - Additional error context
134
+ * @returns Structured tool issue indicating no event
135
+ *
136
+ * @remarks
137
+ * Used when a planet doesn't rise/set at a location (circumpolar)
138
+ * or when meridian transits don't occur.
139
+ */
140
+ export declare function noRiseSetEvent(eventType: 'rise' | 'set' | 'upper_meridian' | 'lower_meridian', planet: string, details: Record<string, unknown>): ToolIssue;
141
+ /**
142
+ * Create a structured error for circumpolar objects
143
+ *
144
+ * @param planet - Planet name for context
145
+ * @param latitude - Observer latitude where object is circumpolar
146
+ * @param details - Additional error context
147
+ * @returns Structured tool issue for circumpolar object
148
+ *
149
+ * @remarks
150
+ * Used when a planet never rises or sets at extreme latitudes.
151
+ * The object is either always above or always below the horizon.
152
+ */
153
+ export declare function circumpolarObject(planet: string, latitude: number, details?: Record<string, unknown>): ToolIssue;
154
+ /**
155
+ * Create a structured error for missing natal chart
156
+ *
157
+ * @returns Structured tool issue for missing natal chart
158
+ *
159
+ * @remarks
160
+ * Used when transit or chart operations are requested
161
+ * before a natal chart has been set.
162
+ */
163
+ export declare function missingNatalChart(): ToolIssue;
164
+ /**
165
+ * Create a warning for polar latitude limitations
166
+ *
167
+ * @param latitude - Observer latitude in degrees
168
+ * @param houseSystem - House system being used
169
+ * @returns Structured tool issue warning about polar limitations
170
+ *
171
+ * @remarks
172
+ * Some house systems (Placidus, Koch) fail at extreme latitudes.
173
+ * This warns the user and suggests Whole Sign as a fallback.
174
+ */
175
+ export declare function polarLatitudeWarning(latitude: number, houseSystem: string): ToolIssue;
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Structured error handling for MCP tool results
3
+ *
4
+ * @remarks
5
+ * Provides agent-recoverable error codes and suggestions for self-correction.
6
+ * Distinguishes between recoverable domain errors and hard infrastructure failures.
7
+ *
8
+ * Each error code includes whether it's retryable and suggested fixes
9
+ * to help the agent recover from errors automatically.
10
+ */
11
+ /**
12
+ * Create a successful tool result
13
+ *
14
+ * @param data - The successful result data
15
+ * @param warnings - Optional warnings about the operation
16
+ * @returns Success result with data
17
+ *
18
+ * @remarks
19
+ * Use this to wrap successful operation results. Warnings are
20
+ * optional and can indicate non-fatal issues.
21
+ */
22
+ export function success(data, warnings) {
23
+ return warnings ? { ok: true, data, warnings } : { ok: true, data };
24
+ }
25
+ /**
26
+ * Create a failed tool result
27
+ *
28
+ * @param error - Structured error information
29
+ * @returns Failure result with error
30
+ *
31
+ * @remarks
32
+ * Use this to wrap failed operations. The error should include
33
+ * a code, message, and optionally retry/suggestion information.
34
+ */
35
+ export function failure(error) {
36
+ return { ok: false, error };
37
+ }
38
+ /**
39
+ * Schema version for structured responses.
40
+ * Increment on breaking changes to response shapes.
41
+ */
42
+ export const SCHEMA_VERSION = '1.0';
43
+ /**
44
+ * Build a successful MCP tool response with structured data + human text.
45
+ *
46
+ * @param data - Structured payload
47
+ * @param humanText - Human-readable summary
48
+ * @param warnings - Optional warnings
49
+ * @returns MCP content array (JSON first, text second)
50
+ */
51
+ export function mcpResult(data, humanText, warnings) {
52
+ const envelope = warnings
53
+ ? { ok: true, schemaVersion: SCHEMA_VERSION, data, warnings }
54
+ : { ok: true, schemaVersion: SCHEMA_VERSION, data };
55
+ return {
56
+ content: [
57
+ { type: 'text', text: JSON.stringify(envelope, null, 2) },
58
+ { type: 'text', text: humanText },
59
+ ],
60
+ };
61
+ }
62
+ /**
63
+ * Build an error MCP tool response with structured error.
64
+ *
65
+ * @param error - Structured error information
66
+ * @returns MCP content array with isError flag
67
+ */
68
+ export function mcpError(error) {
69
+ return {
70
+ content: [
71
+ {
72
+ type: 'text',
73
+ text: JSON.stringify({ ok: false, schemaVersion: SCHEMA_VERSION, error }, null, 2),
74
+ },
75
+ ],
76
+ isError: true,
77
+ };
78
+ }
79
+ /**
80
+ * Map Swiss Ephemeris errors to structured tool issues
81
+ *
82
+ * @param context - Operation context where error occurred
83
+ * @param err - Raw error from Swiss Ephemeris
84
+ * @param details - Additional error context
85
+ * @returns Structured tool issue with retry information
86
+ *
87
+ * @remarks
88
+ * Converts low-level Swiss Ephemeris errors into structured,
89
+ * agent-recoverable error codes with suggested fixes.
90
+ */
91
+ export function mapSweError(context, err, details) {
92
+ const message = err instanceof Error ? err.message : String(err);
93
+ if (/not initialized/i.test(message)) {
94
+ return {
95
+ code: 'EPHEMERIS_NOT_INITIALIZED',
96
+ message: 'Swiss Ephemeris is not initialized.',
97
+ retryable: false,
98
+ suggestedFix: 'Initialize the ephemeris engine before requesting calculations.',
99
+ details,
100
+ };
101
+ }
102
+ return {
103
+ code: 'EPHEMERIS_COMPUTE_FAILED',
104
+ message: `Swiss Ephemeris failed during ${context}.`,
105
+ retryable: false,
106
+ suggestedFix: 'Check inputs and ephemeris configuration.',
107
+ details: { ...details, rawMessage: message },
108
+ };
109
+ }
110
+ /**
111
+ * Create a structured error for missing rise/set events
112
+ *
113
+ * @param eventType - Type of event that was missing
114
+ * @param planet - Planet name for context
115
+ * @param details - Additional error context
116
+ * @returns Structured tool issue indicating no event
117
+ *
118
+ * @remarks
119
+ * Used when a planet doesn't rise/set at a location (circumpolar)
120
+ * or when meridian transits don't occur.
121
+ */
122
+ export function noRiseSetEvent(eventType, planet, details) {
123
+ return {
124
+ code: 'NO_RISE_SET_EVENT',
125
+ message: `No ${eventType} event for ${planet} at the specified date and location.`,
126
+ retryable: true,
127
+ suggestedFix: 'Try another date, location, or request a different event type. Object may be circumpolar.',
128
+ details,
129
+ };
130
+ }
131
+ /**
132
+ * Create a structured error for circumpolar objects
133
+ *
134
+ * @param planet - Planet name for context
135
+ * @param latitude - Observer latitude where object is circumpolar
136
+ * @param details - Additional error context
137
+ * @returns Structured tool issue for circumpolar object
138
+ *
139
+ * @remarks
140
+ * Used when a planet never rises or sets at extreme latitudes.
141
+ * The object is either always above or always below the horizon.
142
+ */
143
+ export function circumpolarObject(planet, latitude, details) {
144
+ return {
145
+ code: 'CIRCUMPOLAR_OBJECT',
146
+ message: `${planet} is circumpolar at latitude ${latitude.toFixed(1)}° - it does not rise or set.`,
147
+ retryable: true,
148
+ suggestedFix: 'Request meridian transit times instead, or try a different location.',
149
+ details: { planet, latitude, ...details },
150
+ };
151
+ }
152
+ /**
153
+ * Create a structured error for missing natal chart
154
+ *
155
+ * @returns Structured tool issue for missing natal chart
156
+ *
157
+ * @remarks
158
+ * Used when transit or chart operations are requested
159
+ * before a natal chart has been set.
160
+ */
161
+ export function missingNatalChart() {
162
+ return {
163
+ code: 'MISSING_NATAL_CHART',
164
+ message: 'No natal chart found. Please set natal chart first.',
165
+ retryable: true,
166
+ suggestedFix: 'Call set_natal_chart with birth details (date, time, location, timezone) before requesting transits or chart calculations.',
167
+ };
168
+ }
169
+ /**
170
+ * Create a warning for polar latitude limitations
171
+ *
172
+ * @param latitude - Observer latitude in degrees
173
+ * @param houseSystem - House system being used
174
+ * @returns Structured tool issue warning about polar limitations
175
+ *
176
+ * @remarks
177
+ * Some house systems (Placidus, Koch) fail at extreme latitudes.
178
+ * This warns the user and suggests Whole Sign as a fallback.
179
+ */
180
+ export function polarLatitudeWarning(latitude, houseSystem) {
181
+ return {
182
+ code: 'POLAR_LATITUDE_LIMIT',
183
+ message: `${houseSystem} house system may be inaccurate at polar latitudes (${latitude.toFixed(1)}°).`,
184
+ retryable: true,
185
+ suggestedFix: 'Consider using Whole Sign house system for latitudes >66°.',
186
+ details: { latitude, houseSystem },
187
+ };
188
+ }