ether-to-astro 1.0.2 → 1.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.
- package/.github/ISSUE_TEMPLATE/bug-report.yml +87 -0
- package/.github/ISSUE_TEMPLATE/capability-request.yml +117 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/paper-cut.yml +59 -0
- package/.github/pull_request_template.md +1 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/test.yml +2 -2
- package/AGENTS.md +46 -1
- package/DEVELOPER.md +78 -0
- package/README.md +128 -75
- package/SETUP.md +100 -41
- package/dist/astro-service.d.ts +51 -2
- package/dist/astro-service.js +660 -56
- package/dist/cli.js +31 -0
- package/dist/entrypoint.d.ts +13 -0
- package/dist/entrypoint.js +78 -0
- package/dist/ephemeris.d.ts +15 -0
- package/dist/ephemeris.js +33 -0
- package/dist/formatter.d.ts +5 -1
- package/dist/formatter.js +4 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +63 -114
- package/dist/loader.d.ts +1 -1
- package/dist/loader.js +61 -23
- package/dist/mcp-alias.d.ts +2 -0
- package/dist/mcp-alias.js +8 -0
- package/dist/time-utils.d.ts +8 -0
- package/dist/time-utils.js +16 -0
- package/dist/tool-registry.js +111 -5
- package/dist/tool-result.d.ts +8 -0
- package/dist/tool-result.js +39 -0
- package/dist/types.d.ts +79 -1
- package/docs/product/adrs/0001-mcp-vs-skill-boundary.md +96 -0
- package/docs/product/architecture-boundaries.md +223 -0
- package/docs/product/product-tenets.md +174 -0
- package/docs/releases/1.2.0-draft.md +48 -0
- package/package.json +7 -7
- package/skills/.curated/daily-brief/SKILL.md +75 -0
- package/skills/.curated/electional-overlay/SKILL.md +67 -0
- package/skills/.curated/weekly-overview/SKILL.md +73 -0
- package/skills/.system/write-skill/SKILL.md +90 -0
- package/src/astro-service.ts +861 -60
- package/src/cli.ts +84 -0
- package/src/entrypoint.ts +118 -0
- package/src/ephemeris.ts +44 -0
- package/src/formatter.ts +13 -1
- package/src/index.ts +77 -121
- package/src/loader.ts +69 -25
- package/src/mcp-alias.ts +10 -0
- package/src/time-utils.ts +18 -0
- package/src/tool-registry.ts +129 -9
- package/src/tool-result.ts +44 -0
- package/src/types.ts +91 -1
- package/tests/unit/astro-service.test.ts +751 -5
- package/tests/unit/cli-commands.test.ts +13 -0
- package/tests/unit/entrypoint.test.ts +67 -0
- package/tests/unit/error-mapping.test.ts +20 -0
- package/tests/unit/formatter.test.ts +6 -0
- package/tests/unit/tool-registry.test.ts +114 -2
- package/setup.sh +0 -21
package/src/tool-registry.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AstroService } from './astro-service.js';
|
|
2
2
|
import type { Disambiguation } from './time-utils.js';
|
|
3
|
-
import type { HouseSystem, NatalChart } from './types.js';
|
|
3
|
+
import type { ElectionalHouseSystem, HouseSystem, NatalChart } from './types.js';
|
|
4
4
|
|
|
5
5
|
type ToolContent =
|
|
6
6
|
| { type: 'text'; text: string }
|
|
@@ -91,10 +91,117 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
91
91
|
return { kind: 'state', data: result.data, text: result.text, natalChart: result.chart };
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: 'get_rising_sign_windows',
|
|
96
|
+
description:
|
|
97
|
+
'Get local-time windows for which zodiac signs are rising on a given date and location. Returns deterministic intervals only (no ranking or interpretation).',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
date: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: 'Target local date (YYYY-MM-DD)',
|
|
104
|
+
},
|
|
105
|
+
latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
|
|
106
|
+
longitude: {
|
|
107
|
+
type: 'number',
|
|
108
|
+
description: 'Longitude in decimal degrees (-180 to 180)',
|
|
109
|
+
},
|
|
110
|
+
timezone: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'IANA timezone (e.g., America/New_York)',
|
|
113
|
+
},
|
|
114
|
+
mode: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
enum: ['approximate', 'exact'],
|
|
117
|
+
description:
|
|
118
|
+
'Boundary mode. approximate uses coarser stepping; exact refines sign-change boundaries.',
|
|
119
|
+
default: 'approximate',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
required: ['date', 'latitude', 'longitude', 'timezone'],
|
|
123
|
+
},
|
|
124
|
+
requiresNatalChart: false,
|
|
125
|
+
execute: (ctx, args) => {
|
|
126
|
+
const result = ctx.service.getRisingSignWindows({
|
|
127
|
+
date: args.date as string,
|
|
128
|
+
latitude: args.latitude as number,
|
|
129
|
+
longitude: args.longitude as number,
|
|
130
|
+
timezone: args.timezone as string,
|
|
131
|
+
mode: args.mode as 'approximate' | 'exact' | undefined,
|
|
132
|
+
});
|
|
133
|
+
return { kind: 'state', data: result.data, text: result.text };
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'get_electional_context',
|
|
138
|
+
description:
|
|
139
|
+
'Get stateless electional context for a specific local date, time, and location. Returns deterministic timing facts such as ascendant, sect/day-night classification, Moon phase, applying aspects, and optional ascendant-ruler basics. This tool does not require a natal chart and is separate from get_transits.',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {
|
|
143
|
+
date: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: 'Target local date (YYYY-MM-DD)',
|
|
146
|
+
},
|
|
147
|
+
time: {
|
|
148
|
+
type: 'string',
|
|
149
|
+
description:
|
|
150
|
+
'Target local time (HH:mm or HH:mm:ss). DST-ambiguous or nonexistent local times are rejected.',
|
|
151
|
+
},
|
|
152
|
+
timezone: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'IANA timezone (e.g., America/New_York)',
|
|
155
|
+
},
|
|
156
|
+
latitude: { type: 'number', description: 'Latitude in decimal degrees (-90 to 90)' },
|
|
157
|
+
longitude: {
|
|
158
|
+
type: 'number',
|
|
159
|
+
description: 'Longitude in decimal degrees (-180 to 180)',
|
|
160
|
+
},
|
|
161
|
+
house_system: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
enum: ['P', 'K', 'W', 'R'],
|
|
164
|
+
description:
|
|
165
|
+
'House system used for ascendant extraction: P=Placidus (default), K=Koch, W=Whole Sign, R=Regiomontanus.',
|
|
166
|
+
},
|
|
167
|
+
include_ruler_basics: {
|
|
168
|
+
type: 'boolean',
|
|
169
|
+
description:
|
|
170
|
+
'Include ascendant-ruler position, speed, and retrograde flag. Defaults to false.',
|
|
171
|
+
},
|
|
172
|
+
include_planetary_applications: {
|
|
173
|
+
type: 'boolean',
|
|
174
|
+
description: 'Include applying major aspects between current planets. Defaults to true.',
|
|
175
|
+
},
|
|
176
|
+
orb_degrees: {
|
|
177
|
+
type: 'number',
|
|
178
|
+
description:
|
|
179
|
+
'Orb for electional aspect detection in degrees. Defaults to 3 and must be between 0.1 and 10.',
|
|
180
|
+
default: 3,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
required: ['date', 'time', 'timezone', 'latitude', 'longitude'],
|
|
184
|
+
},
|
|
185
|
+
requiresNatalChart: false,
|
|
186
|
+
execute: (ctx, args) => {
|
|
187
|
+
const result = ctx.service.getElectionalContext({
|
|
188
|
+
date: args.date as string,
|
|
189
|
+
time: args.time as string,
|
|
190
|
+
timezone: args.timezone as string,
|
|
191
|
+
latitude: args.latitude as number,
|
|
192
|
+
longitude: args.longitude as number,
|
|
193
|
+
house_system: args.house_system as ElectionalHouseSystem | undefined,
|
|
194
|
+
include_ruler_basics: args.include_ruler_basics as boolean | undefined,
|
|
195
|
+
include_planetary_applications: args.include_planetary_applications as boolean | undefined,
|
|
196
|
+
orb_degrees: args.orb_degrees as number | undefined,
|
|
197
|
+
});
|
|
198
|
+
return { kind: 'state', data: result.data, text: result.text };
|
|
199
|
+
},
|
|
200
|
+
},
|
|
94
201
|
{
|
|
95
202
|
name: 'get_transits',
|
|
96
203
|
description:
|
|
97
|
-
'Get transits (aspects between current/future planets and natal chart).
|
|
204
|
+
'Get transits (aspects between current/future planets and natal chart). Each transit includes additive placement metadata for both sides (sign, degree, house) so clients can render activation context without reconstructing house logic. Supports mode=snapshot (single-day), mode=best_hit (multi-day compressed preview), and mode=forecast (day-grouped output). If mode is omitted, legacy behavior is preserved: days_ahead=0 resolves to snapshot and days_ahead>0 resolves to best_hit.',
|
|
98
205
|
inputSchema: {
|
|
99
206
|
type: 'object',
|
|
100
207
|
properties: {
|
|
@@ -116,9 +223,15 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
116
223
|
days_ahead: {
|
|
117
224
|
type: 'number',
|
|
118
225
|
description:
|
|
119
|
-
'Number of days to look ahead
|
|
226
|
+
'Number of days to look ahead. In snapshot mode only the start day is used. If mode is omitted, legacy behavior is preserved: 0 resolves to snapshot and values > 0 resolve to best_hit.',
|
|
120
227
|
default: 0,
|
|
121
228
|
},
|
|
229
|
+
mode: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
enum: ['snapshot', 'best_hit', 'forecast'],
|
|
232
|
+
description:
|
|
233
|
+
'Transit output mode: snapshot=single-day, best_hit=compressed preview across range, forecast=day-grouped output. If omitted, legacy behavior is preserved.',
|
|
234
|
+
},
|
|
122
235
|
max_orb: {
|
|
123
236
|
type: 'number',
|
|
124
237
|
description: 'Maximum orb in degrees to include. Defaults to 8.',
|
|
@@ -142,6 +255,7 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
142
255
|
categories: args.categories as string[] | undefined,
|
|
143
256
|
include_mundane: args.include_mundane as boolean | undefined,
|
|
144
257
|
days_ahead: args.days_ahead as number | undefined,
|
|
258
|
+
mode: args.mode as 'snapshot' | 'best_hit' | 'forecast' | undefined,
|
|
145
259
|
max_orb: args.max_orb as number | undefined,
|
|
146
260
|
exact_only: args.exact_only as boolean | undefined,
|
|
147
261
|
applying_only: args.applying_only as boolean | undefined,
|
|
@@ -178,8 +292,10 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
178
292
|
inputSchema: { type: 'object', properties: {} },
|
|
179
293
|
requiresNatalChart: false,
|
|
180
294
|
execute: (ctx, args) => {
|
|
181
|
-
const timezone =
|
|
182
|
-
|
|
295
|
+
const timezone = ctx.service.resolveReportingTimezone(
|
|
296
|
+
args.timezone as string | undefined,
|
|
297
|
+
ctx.natalChart?.location?.timezone
|
|
298
|
+
);
|
|
183
299
|
const result = ctx.service.getRetrogradePlanets(timezone);
|
|
184
300
|
return { kind: 'state', data: result.data, text: result.text };
|
|
185
301
|
},
|
|
@@ -200,8 +316,10 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
200
316
|
inputSchema: { type: 'object', properties: {} },
|
|
201
317
|
requiresNatalChart: false,
|
|
202
318
|
execute: (ctx, args) => {
|
|
203
|
-
const timezone =
|
|
204
|
-
|
|
319
|
+
const timezone = ctx.service.resolveReportingTimezone(
|
|
320
|
+
args.timezone as string | undefined,
|
|
321
|
+
ctx.natalChart?.location?.timezone
|
|
322
|
+
);
|
|
205
323
|
const result = ctx.service.getAsteroidPositions(timezone);
|
|
206
324
|
return { kind: 'state', data: result.data, text: result.text };
|
|
207
325
|
},
|
|
@@ -212,8 +330,10 @@ export const MCP_TOOL_SPECS: ToolSpec[] = [
|
|
|
212
330
|
inputSchema: { type: 'object', properties: {} },
|
|
213
331
|
requiresNatalChart: false,
|
|
214
332
|
execute: (ctx, args) => {
|
|
215
|
-
const timezone =
|
|
216
|
-
|
|
333
|
+
const timezone = ctx.service.resolveReportingTimezone(
|
|
334
|
+
args.timezone as string | undefined,
|
|
335
|
+
ctx.natalChart?.location?.timezone
|
|
336
|
+
);
|
|
217
337
|
const result = ctx.service.getNextEclipses(timezone);
|
|
218
338
|
return { kind: 'state', data: result.data, text: result.text };
|
|
219
339
|
},
|
package/src/tool-result.ts
CHANGED
|
@@ -189,6 +189,50 @@ export function mapSweError(
|
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Map generic error messages to structured tool issue codes.
|
|
194
|
+
*
|
|
195
|
+
* @remarks
|
|
196
|
+
* Primarily used at the MCP boundary when unknown errors are caught
|
|
197
|
+
* and need a best-effort classification.
|
|
198
|
+
*/
|
|
199
|
+
export function mapToolErrorMessageToCode(errorMessage: string): ToolIssueCode {
|
|
200
|
+
if (
|
|
201
|
+
errorMessage.includes('Invalid date format') ||
|
|
202
|
+
errorMessage.includes('Invalid calendar date') ||
|
|
203
|
+
errorMessage.includes('Invalid month') ||
|
|
204
|
+
errorMessage.includes('Invalid day') ||
|
|
205
|
+
errorMessage.includes('days_ahead') ||
|
|
206
|
+
errorMessage.includes('max_orb') ||
|
|
207
|
+
errorMessage.includes('missing julianDay') ||
|
|
208
|
+
errorMessage.includes('Invalid mode') ||
|
|
209
|
+
errorMessage.includes('Invalid latitude') ||
|
|
210
|
+
errorMessage.includes('Invalid longitude')
|
|
211
|
+
) {
|
|
212
|
+
return 'INVALID_INPUT';
|
|
213
|
+
}
|
|
214
|
+
if (errorMessage.includes('Invalid timezone') || errorMessage.includes('timezone')) {
|
|
215
|
+
return 'INVALID_TIMEZONE';
|
|
216
|
+
}
|
|
217
|
+
if (errorMessage.includes('Invalid house system')) {
|
|
218
|
+
return 'INVALID_HOUSE_SYSTEM';
|
|
219
|
+
}
|
|
220
|
+
if (errorMessage.includes('Ephemeris') || errorMessage.includes('ephemeris')) {
|
|
221
|
+
return 'EPHEMERIS_COMPUTE_FAILED';
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
errorMessage.includes('write') ||
|
|
225
|
+
errorMessage.includes('ENOENT') ||
|
|
226
|
+
errorMessage.includes('EACCES')
|
|
227
|
+
) {
|
|
228
|
+
return 'FILE_WRITE_FAILED';
|
|
229
|
+
}
|
|
230
|
+
if (errorMessage.includes('render') || errorMessage.includes('chart')) {
|
|
231
|
+
return 'CHART_RENDER_FAILED';
|
|
232
|
+
}
|
|
233
|
+
return 'INTERNAL_ERROR';
|
|
234
|
+
}
|
|
235
|
+
|
|
192
236
|
/**
|
|
193
237
|
* Create a structured error for missing rise/set events
|
|
194
238
|
*
|
package/src/types.ts
CHANGED
|
@@ -86,6 +86,12 @@ export interface NatalChart {
|
|
|
86
86
|
* For polar latitudes (>66°), Whole Sign ('W') may be used as fallback.
|
|
87
87
|
*/
|
|
88
88
|
houseSystem?: HouseSystem;
|
|
89
|
+
/**
|
|
90
|
+
* Explicit house system requested when the natal chart was created
|
|
91
|
+
* @remarks
|
|
92
|
+
* This stays undefined when the caller relied on runtime defaults.
|
|
93
|
+
*/
|
|
94
|
+
requestedHouseSystem?: HouseSystem;
|
|
89
95
|
/**
|
|
90
96
|
* UTC equivalent of birth time
|
|
91
97
|
* @remarks
|
|
@@ -217,6 +223,18 @@ export interface TransitData extends BaseTransit {
|
|
|
217
223
|
* May be undefined if aspect is not within orb or exact time not calculated
|
|
218
224
|
*/
|
|
219
225
|
exactTime?: string; // ISO timestamp
|
|
226
|
+
/** Zodiac sign of the transiting planet */
|
|
227
|
+
transitSign?: string;
|
|
228
|
+
/** Degree of the transiting planet within its sign (0-30) */
|
|
229
|
+
transitDegree?: number;
|
|
230
|
+
/** House occupied by the transiting planet at the transit calculation time */
|
|
231
|
+
transitHouse?: number;
|
|
232
|
+
/** Zodiac sign of the natal planet */
|
|
233
|
+
natalSign?: string;
|
|
234
|
+
/** Degree of the natal planet within its sign (0-30) */
|
|
235
|
+
natalDegree?: number;
|
|
236
|
+
/** House occupied by the natal planet in the natal chart */
|
|
237
|
+
natalHouse?: number;
|
|
220
238
|
}
|
|
221
239
|
|
|
222
240
|
/**
|
|
@@ -229,8 +247,12 @@ export interface TransitData extends BaseTransit {
|
|
|
229
247
|
export interface TransitResponse {
|
|
230
248
|
/** ISO date of the transit calculation (YYYY-MM-DD) */
|
|
231
249
|
date: string;
|
|
232
|
-
/** Timezone used for
|
|
250
|
+
/** Timezone used for reporting and user-facing day labels */
|
|
233
251
|
timezone: string;
|
|
252
|
+
/** Natal/local timezone used for date interpretation and astro calculations */
|
|
253
|
+
calculation_timezone?: string;
|
|
254
|
+
/** Timezone used for reporting and user-facing day labels */
|
|
255
|
+
reporting_timezone?: string;
|
|
234
256
|
/** Array of all active transits for the date */
|
|
235
257
|
transits: TransitData[];
|
|
236
258
|
}
|
|
@@ -250,6 +272,8 @@ export interface PlanetPositionResponse {
|
|
|
250
272
|
positions: PlanetPosition[];
|
|
251
273
|
}
|
|
252
274
|
|
|
275
|
+
export type ElectionalHouseSystem = Extract<HouseSystem, 'P' | 'K' | 'W' | 'R'>;
|
|
276
|
+
|
|
253
277
|
/**
|
|
254
278
|
* Types of astrological aspects
|
|
255
279
|
*
|
|
@@ -259,6 +283,72 @@ export interface PlanetPositionResponse {
|
|
|
259
283
|
*/
|
|
260
284
|
export type AspectType = 'conjunction' | 'opposition' | 'square' | 'trine' | 'sextile';
|
|
261
285
|
|
|
286
|
+
export type ElectionalPhaseName =
|
|
287
|
+
| 'new'
|
|
288
|
+
| 'crescent'
|
|
289
|
+
| 'first_quarter'
|
|
290
|
+
| 'gibbous'
|
|
291
|
+
| 'full'
|
|
292
|
+
| 'disseminating'
|
|
293
|
+
| 'last_quarter'
|
|
294
|
+
| 'balsamic';
|
|
295
|
+
|
|
296
|
+
export interface ElectionalAspect {
|
|
297
|
+
from_body: PlanetName;
|
|
298
|
+
to_body: PlanetName;
|
|
299
|
+
aspect: AspectType;
|
|
300
|
+
orb: number;
|
|
301
|
+
applying: boolean;
|
|
302
|
+
exact_at_utc?: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface ElectionalContextResponse {
|
|
306
|
+
input: {
|
|
307
|
+
date: string;
|
|
308
|
+
time: string;
|
|
309
|
+
timezone: string;
|
|
310
|
+
latitude: number;
|
|
311
|
+
longitude: number;
|
|
312
|
+
house_system: ElectionalHouseSystem;
|
|
313
|
+
instant_utc: string;
|
|
314
|
+
jd_ut: number;
|
|
315
|
+
};
|
|
316
|
+
ascendant: {
|
|
317
|
+
longitude: number;
|
|
318
|
+
sign: string;
|
|
319
|
+
degree_in_sign: number;
|
|
320
|
+
};
|
|
321
|
+
sect: {
|
|
322
|
+
is_day_chart: boolean;
|
|
323
|
+
sun_altitude_degrees: number;
|
|
324
|
+
classification: 'day' | 'night';
|
|
325
|
+
};
|
|
326
|
+
moon: {
|
|
327
|
+
longitude: number;
|
|
328
|
+
sign: string;
|
|
329
|
+
phase_angle: number;
|
|
330
|
+
phase_name: ElectionalPhaseName;
|
|
331
|
+
is_void_of_course: boolean | null;
|
|
332
|
+
applying_aspects?: ElectionalAspect[];
|
|
333
|
+
};
|
|
334
|
+
applying_aspects?: ElectionalAspect[];
|
|
335
|
+
ruler_basics?: {
|
|
336
|
+
asc_sign_ruler: {
|
|
337
|
+
body: PlanetName;
|
|
338
|
+
longitude: number;
|
|
339
|
+
sign: string;
|
|
340
|
+
speed: number;
|
|
341
|
+
is_retrograde: boolean;
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
meta: {
|
|
345
|
+
deterministic: true;
|
|
346
|
+
requires_natal: false;
|
|
347
|
+
warnings: string[];
|
|
348
|
+
deferred_features: string[];
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
262
352
|
/**
|
|
263
353
|
* Aspect definitions with angles and default orbs
|
|
264
354
|
*
|