Tool tree
gradeNot graded
get_night_ephemerisAnswer
Use this first when planning a night. Returns the astronomical ephemeris for the night that STARTS on the given evening: sunset and sunrise, civil, nautical and astronomical twilight, the true astronomical darkness window (Sun below -18 degrees), Moon rise and set, illumination and phase, total dark hours and how many of them are Moon-free. Pure astronomy computed in the browser with astronomy-engine; it does NOT include weather, seeing or transparency (use compare_dark_sky_sites for a cloud forecast). Defaults to the night and site currently shown in the app. Times are returned as UTC plus site-local time; for custom coordinates pass site.time_zone or you will only get UTC. Polar and high-latitude cases are explicit in data.darkness.status and data.sun.status.
View tool JSON
{
"name": "get_night_ephemeris",
"kind": "answer",
"impl": "imperative",
"description": "Use this first when planning a night. Returns the astronomical ephemeris for the night that STARTS on the given evening: sunset and sunrise, civil, nautical and astronomical twilight, the true astronomical darkness window (Sun below -18 degrees), Moon rise and set, illumination and phase, total dark hours and how many of them are Moon-free. Pure astronomy computed in the browser with astronomy-engine; it does NOT include weather, seeing or transparency (use compare_dark_sky_sites for a cloud forecast). Defaults to the night and site currently shown in the app. Times are returned as UTC plus site-local time; for custom coordinates pass site.time_zone or you will only get UTC. Polar and high-latitude cases are explicit in data.darkness.status and data.sun.status.",
"inputSchema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Local calendar date of the EVENING the night starts (YYYY-MM-DD). Omit for the night currently selected in the app."
},
"site": {
"type": "object",
"description": "Observing site to compute for. Omit to use the site currently shown in the app. Otherwise name a dark-sky catalog site with id (preferred) or name, OR pass BOTH latitude and longitude, plus time_zone (IANA, e.g. \"Pacific/Honolulu\") if you want local times; without a zone only UTC is returned. An object that does neither comes back as invalid_site with a hint. Answering for a site here does NOT move the app: to change the site the page itself shows, call the tool set_observing_site (the page also carries a declarative form named set_observing_site_form, and both go through the same validation).",
"properties": {
"id": {
"type": "string",
"maxLength": 40,
"description": "Dark-sky catalog id such as roque, mauna-kea, paranal; preferred over coordinates because it brings the exact elevation and IANA zone."
},
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90,
"description": "Decimal degrees, north positive."
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180,
"description": "Decimal degrees, EAST positive (Mauna Kea is -155.47)."
},
"elevation_m": {
"type": "number",
"minimum": -430,
"maximum": 9000
},
"time_zone": {
"type": "string",
"description": "IANA time zone name."
},
"name": {
"type": "string",
"maxLength": 80,
"description": "Catalog site name (\"Mauna Kea\", matched loosely) when you pass no coordinates, or the label for the coordinates when you do."
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
}find_observable_targetsAnswer
Use this to learn what is actually worth observing on a night: Messier objects, planets and the Moon filtered by minimum altitude, darkness window, Moon separation and magnitude, each with its observing window (start, peak, end), peak altitude and airmass, Moon separation, the share of the window with the Moon up and a 0-100 score. Bright stars are included only when you ask for them, with types ["star"] or by naming one in ids or query. Also returns the objects that were REJECTED and why (too low, Moon too close, window too short, no darkness), so you can explain trade-offs to the person. It works on the night, site and filters currently shown in the app unless you override them, and always echoes the values it used in data.filters_used. Pass ids to check specific targets.
View tool JSON
{
"name": "find_observable_targets",
"kind": "answer",
"impl": "imperative",
"description": "Use this to learn what is actually worth observing on a night: Messier objects, planets and the Moon filtered by minimum altitude, darkness window, Moon separation and magnitude, each with its observing window (start, peak, end), peak altitude and airmass, Moon separation, the share of the window with the Moon up and a 0-100 score. Bright stars are included only when you ask for them, with types [\"star\"] or by naming one in ids or query. Also returns the objects that were REJECTED and why (too low, Moon too close, window too short, no darkness), so you can explain trade-offs to the person. It works on the night, site and filters currently shown in the app unless you override them, and always echoes the values it used in data.filters_used. Pass ids to check specific targets.",
"inputSchema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Local calendar date of the EVENING the night starts (YYYY-MM-DD). Omit for the night currently selected in the app."
},
"site": {
"type": "object",
"description": "Observing site to compute for. Omit to use the site currently shown in the app. Otherwise name a dark-sky catalog site with id (preferred) or name, OR pass BOTH latitude and longitude, plus time_zone (IANA, e.g. \"Pacific/Honolulu\") if you want local times; without a zone only UTC is returned. An object that does neither comes back as invalid_site with a hint. Answering for a site here does NOT move the app: to change the site the page itself shows, call the tool set_observing_site (the page also carries a declarative form named set_observing_site_form, and both go through the same validation).",
"properties": {
"id": {
"type": "string",
"maxLength": 40,
"description": "Dark-sky catalog id such as roque, mauna-kea, paranal; preferred over coordinates because it brings the exact elevation and IANA zone."
},
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90,
"description": "Decimal degrees, north positive."
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180,
"description": "Decimal degrees, EAST positive (Mauna Kea is -155.47)."
},
"elevation_m": {
"type": "number",
"minimum": -430,
"maximum": 9000
},
"time_zone": {
"type": "string",
"description": "IANA time zone name."
},
"name": {
"type": "string",
"maxLength": 80,
"description": "Catalog site name (\"Mauna Kea\", matched loosely) when you pass no coordinates, or the label for the coordinates when you do."
}
},
"additionalProperties": false
},
"min_altitude_deg": {
"type": "number",
"minimum": 5,
"maximum": 85,
"description": "Lowest altitude above the horizon that still counts as observable, in degrees. OMIT this to honour the filter the person set in the app; the value actually used comes back as data.filters_used.min_altitude_deg. Pass a number only when the person asked to change the floor."
},
"types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"galaxy",
"open_cluster",
"globular_cluster",
"planetary_nebula",
"diffuse_nebula",
"supernova_remnant",
"other",
"planet",
"moon",
"star"
]
},
"maxItems": 10,
"description": "Keep only these target types. Omit to honour the type filter set in the app (every type except \"star\" by default). Include \"star\" here to add the naked eye stars to the scan."
},
"max_magnitude": {
"type": "number",
"minimum": -30,
"maximum": 20,
"description": "Faintest visual magnitude to keep (larger numbers are fainter); planets and the Moon are judged on their brightness on that night. OMIT this to honour the filter set in the app, which is no limit unless the person set one; the value used comes back as data.filters_used.max_magnitude."
},
"min_moon_separation_deg": {
"type": "number",
"minimum": 0,
"maximum": 180,
"description": "Minimum angular distance from the Moon, in degrees, enforced only while the Moon is above the horizon. OMIT this to honour the filter the person set in the app; the value used comes back as data.filters_used.min_moon_separation_deg."
},
"min_window_minutes": {
"type": "integer",
"minimum": 10,
"maximum": 600,
"default": 45,
"description": "Shortest observing window worth listing, in minutes."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 40,
"default": 12,
"description": "How many candidates to return, best first."
},
"query": {
"type": "string",
"maxLength": 60,
"description": "Free text filter over names and ids, e.g. \"nebula\" or \"Andromeda\"."
},
"ids": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 60,
"description": "Target id or common name; matching ignores case, spaces and punctuation. Accepted: a Messier id in any spelling (\"M31\", \"M 31\", \"messier 31\"), the object common name or NGC designation (\"Ring Nebula\", \"Andromeda Galaxy\", \"Pleiades\", \"NGC 7089\"), a planet (\"Jupiter\", \"Saturn\"), \"Moon\", or a bright star (\"Vega\"). The Sun is not an observing target. A name that does not resolve comes back as unknown_target with the closest matches, so retry with one of those rather than guessing again."
},
"maxItems": 40,
"description": "Check exactly these targets instead of the whole catalog. Names that cannot be resolved come back in rejected."
}
},
"additionalProperties": false
}
}rank_nightsAnswer
Use this to find the BEST ASTRONOMICAL NIGHT in a date range instead of calling get_night_ephemeris in a loop. Scores every night in the range (inclusive, up to 62 nights) by USABLE dark hours: astronomical darkness while the Moon is below the horizon or thinner than 15% illuminated. Astronomy only: darkness and Moon. It does not know the weather; for cloud cover use compare_dark_sky_sites. Returns nights sorted best-first with score (0-100), dark hours, Moon-free hours, Moon illumination and a one-line explanation. Honours cancellation. Read-only: it does not move the app. To put the app on the night you picked, call set_observing_time with { "date": <the night_of you chose> }. from_date and to_date are both required and both name the EVENING a night starts; a two-night range is fine when the person is only choosing between, say, Friday and Saturday ({ "from_date": "2026-09-04", "to_date": "2026-09-05" }).
View tool JSON
{
"name": "rank_nights",
"kind": "answer",
"impl": "imperative",
"description": "Use this to find the BEST ASTRONOMICAL NIGHT in a date range instead of calling get_night_ephemeris in a loop. Scores every night in the range (inclusive, up to 62 nights) by USABLE dark hours: astronomical darkness while the Moon is below the horizon or thinner than 15% illuminated. Astronomy only: darkness and Moon. It does not know the weather; for cloud cover use compare_dark_sky_sites. Returns nights sorted best-first with score (0-100), dark hours, Moon-free hours, Moon illumination and a one-line explanation. Honours cancellation. Read-only: it does not move the app. To put the app on the night you picked, call set_observing_time with { \"date\": <the night_of you chose> }. from_date and to_date are both required and both name the EVENING a night starts; a two-night range is fine when the person is only choosing between, say, Friday and Saturday ({ \"from_date\": \"2026-09-04\", \"to_date\": \"2026-09-05\" }).",
"inputSchema": {
"type": "object",
"properties": {
"from_date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "First evening of the range, YYYY-MM-DD (inclusive)."
},
"to_date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Last evening of the range, YYYY-MM-DD (inclusive). At most 62 nights from from_date."
},
"site": {
"type": "object",
"description": "Observing site to compute for. Omit to use the site currently shown in the app. Otherwise name a dark-sky catalog site with id (preferred) or name, OR pass BOTH latitude and longitude, plus time_zone (IANA, e.g. \"Pacific/Honolulu\") if you want local times; without a zone only UTC is returned. An object that does neither comes back as invalid_site with a hint. Answering for a site here does NOT move the app: to change the site the page itself shows, call the tool set_observing_site (the page also carries a declarative form named set_observing_site_form, and both go through the same validation).",
"properties": {
"id": {
"type": "string",
"maxLength": 40,
"description": "Dark-sky catalog id such as roque, mauna-kea, paranal; preferred over coordinates because it brings the exact elevation and IANA zone."
},
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90,
"description": "Decimal degrees, north positive."
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180,
"description": "Decimal degrees, EAST positive (Mauna Kea is -155.47)."
},
"elevation_m": {
"type": "number",
"minimum": -430,
"maximum": 9000
},
"time_zone": {
"type": "string",
"description": "IANA time zone name."
},
"name": {
"type": "string",
"maxLength": 80,
"description": "Catalog site name (\"Mauna Kea\", matched loosely) when you pass no coordinates, or the label for the coordinates when you do."
}
},
"additionalProperties": false
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 62,
"default": 10,
"description": "How many of the best nights to return in full detail."
}
},
"required": [
"from_date",
"to_date"
],
"additionalProperties": false
}
}point_sky_mapAction
Use this to move the shared sky map the person is looking at: center it on a target (Messier id like "M31", a planet, "Moon" or a bright star) or on an explicit altitude/azimuth, set the zoom as a field of view in degrees (186 = whole sky dome, 60 = a constellation, 10 = a cluster), and optionally highlight objects. Pass at least one of target, altitude_deg with azimuth_deg, fov_deg, highlight or reset: an empty call is refused with invalid_input. Two combinations are refused as well, each with the corrected call in the hint: target together with reset:true (they contradict each other), and altitude_deg without azimuth_deg or the other way round (a direction needs both). The map animates smoothly so the person sees the move and the object you centred on keeps a red agent ring afterwards; call describe_current_view afterwards if you need to know what became visible. Returns where the target is at the map's current time (altitude, azimuth, compass direction, above or below the horizon) and warns when it is below the horizon. Does not change the plan.
View tool JSON
{
"name": "point_sky_map",
"kind": "act",
"impl": "imperative",
"description": "Use this to move the shared sky map the person is looking at: center it on a target (Messier id like \"M31\", a planet, \"Moon\" or a bright star) or on an explicit altitude/azimuth, set the zoom as a field of view in degrees (186 = whole sky dome, 60 = a constellation, 10 = a cluster), and optionally highlight objects. Pass at least one of target, altitude_deg with azimuth_deg, fov_deg, highlight or reset: an empty call is refused with invalid_input. Two combinations are refused as well, each with the corrected call in the hint: target together with reset:true (they contradict each other), and altitude_deg without azimuth_deg or the other way round (a direction needs both). The map animates smoothly so the person sees the move and the object you centred on keeps a red agent ring afterwards; call describe_current_view afterwards if you need to know what became visible. Returns where the target is at the map's current time (altitude, azimuth, compass direction, above or below the horizon) and warns when it is below the horizon. Does not change the plan.",
"inputSchema": {
"type": "object",
"properties": {
"target": {
"type": "string",
"minLength": 1,
"maxLength": 60,
"description": "Target to center on: Messier id (\"M31\"), planet (\"Saturn\"), \"Moon\" or a bright star (\"Vega\"). Wins over altitude_deg and azimuth_deg."
},
"altitude_deg": {
"type": "number",
"minimum": -30,
"maximum": 90,
"description": "Center altitude in degrees above the horizon (90 = zenith). Ignored when target is given."
},
"azimuth_deg": {
"type": "number",
"minimum": 0,
"maximum": 360,
"description": "Center azimuth in degrees clockwise from north (0 = N, 90 = E, 180 = S). Ignored when target is given."
},
"fov_deg": {
"type": "number",
"minimum": 4,
"maximum": 186,
"description": "Field of view in degrees: 186 = the whole sky dome, 60 = a constellation, 10 = a cluster. Defaults to 40 when centering on a target."
},
"highlight": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 60,
"description": "Target id or common name; matching ignores case, spaces and punctuation. Accepted: a Messier id in any spelling (\"M31\", \"M 31\", \"messier 31\"), the object common name or NGC designation (\"Ring Nebula\", \"Andromeda Galaxy\", \"Pleiades\", \"NGC 7089\"), a planet (\"Jupiter\", \"Saturn\"), \"Moon\", or a bright star (\"Vega\"). The Sun is not an observing target. A name that does not resolve comes back as unknown_target with the closest matches, so retry with one of those rather than guessing again."
},
"description": "Replaces the current agent highlights; pass [] to clear."
},
"reset": {
"type": "boolean",
"description": "Return to the whole-sky dome view."
}
},
"additionalProperties": false
}
}set_observing_timeAction
Use this to move the time slider that the sky map and all target positions follow. At least one of time and date is required. Pass time as an ISO 8601 UTC instant between 1900 and 2100 ("2026-09-12T22:30:00Z") or as a keyword for the selected night: "now" (the REAL clock right now; when the selected night does not contain it the app moves to the observing night that does, and says so in caveats), "sunset" (the Sun at the horizon, still far too bright to observe), "darkness_start" (the start of ASTRONOMICAL darkness, Sun 18 degrees below the horizon: this is what "when it gets dark" means for observing, about 1 h 40 min after sunset at the Roque), "midnight" (the middle of astronomical darkness, NOT 00:00 clock time), "darkness_end" (the end of astronomical darkness, Sun back up to 18 degrees below the horizon), "sunrise" (the Sun back at the horizon). Optionally change the selected night with date (YYYY-MM-DD, the evening the night starts), which recomputes everything in the app. An instant that belongs to another night moves the app to that night and says so in caveats. Idempotent.
View tool JSON
{
"name": "set_observing_time",
"kind": "act",
"impl": "imperative",
"description": "Use this to move the time slider that the sky map and all target positions follow. At least one of time and date is required. Pass time as an ISO 8601 UTC instant between 1900 and 2100 (\"2026-09-12T22:30:00Z\") or as a keyword for the selected night: \"now\" (the REAL clock right now; when the selected night does not contain it the app moves to the observing night that does, and says so in caveats), \"sunset\" (the Sun at the horizon, still far too bright to observe), \"darkness_start\" (the start of ASTRONOMICAL darkness, Sun 18 degrees below the horizon: this is what \"when it gets dark\" means for observing, about 1 h 40 min after sunset at the Roque), \"midnight\" (the middle of astronomical darkness, NOT 00:00 clock time), \"darkness_end\" (the end of astronomical darkness, Sun back up to 18 degrees below the horizon), \"sunrise\" (the Sun back at the horizon). Optionally change the selected night with date (YYYY-MM-DD, the evening the night starts), which recomputes everything in the app. An instant that belongs to another night moves the app to that night and says so in caveats. Idempotent.",
"inputSchema": {
"type": "object",
"properties": {
"time": {
"type": "string",
"minLength": 3,
"maxLength": 40,
"description": "ISO 8601 UTC instant between 1900 and 2100 (\"2026-09-12T22:30:00Z\"), or one of the keywords for the selected night: \"now\" (the REAL clock right now; when the selected night does not contain it the app moves to the observing night that does, and says so in caveats), \"sunset\" (the Sun at the horizon, still far too bright to observe), \"darkness_start\" (the start of ASTRONOMICAL darkness, Sun 18 degrees below the horizon: this is what \"when it gets dark\" means for observing, about 1 h 40 min after sunset at the Roque), \"midnight\" (the middle of astronomical darkness, NOT 00:00 clock time), \"darkness_end\" (the end of astronomical darkness, Sun back up to 18 degrees below the horizon), \"sunrise\" (the Sun back at the horizon)."
},
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Local calendar date of the EVENING the night starts (YYYY-MM-DD). Omit for the night currently selected in the app."
}
},
"additionalProperties": false
}
}describe_current_viewAnswer
Use this to see what the person sees BEFORE proposing anything: site, selected night, slider time, sky-map center and field of view, the objects currently on screen above the horizon (planets, Moon, Messier objects with altitude, azimuth and whether they are inside the field of view), the selected object, the person's favorites (objects they tapped on the map), agent highlights, active filters, a summary of the committed plan, pending proposals with the person's accept/reject decisions and reasons, and the last 20 human actions on the page (drags, taps, edits) with timestamps. Read-only and cheap; call it whenever the person refers to "this", "that one", "here" or "what I'm looking at". To change the site the page shows, call the tool set_observing_site (or submit the page form named set_observing_site_form, which shares its validation).
View tool JSON
{
"name": "describe_current_view",
"kind": "answer",
"impl": "imperative",
"description": "Use this to see what the person sees BEFORE proposing anything: site, selected night, slider time, sky-map center and field of view, the objects currently on screen above the horizon (planets, Moon, Messier objects with altitude, azimuth and whether they are inside the field of view), the selected object, the person's favorites (objects they tapped on the map), agent highlights, active filters, a summary of the committed plan, pending proposals with the person's accept/reject decisions and reasons, and the last 20 human actions on the page (drags, taps, edits) with timestamps. Read-only and cheap; call it whenever the person refers to \"this\", \"that one\", \"here\" or \"what I'm looking at\". To change the site the page shows, call the tool set_observing_site (or submit the page form named set_observing_site_form, which shares its validation).",
"inputSchema": {
"type": "object",
"properties": {
"include_visible_objects": {
"type": "boolean",
"default": true,
"description": "Set false to skip the object scan when you only need the view, plan and history."
},
"max_visible_objects": {
"type": "integer",
"minimum": 1,
"maximum": 60,
"default": 20,
"description": "How many objects to list, in-field first and brightest first."
}
},
"additionalProperties": false
}
}propose_planAction
Use this to propose an observing plan WITHOUT applying it. The proposal appears on the person's night timeline as a dotted "proposed by agent" ghost plan they can accept or reject item by item (with a reason you can read back). Give targets by id or name, optionally a duration per target and a rationale; the app schedules each target inside its visibility window during astronomical darkness, avoiding overlaps with the existing plan, and returns the proposal_id, the scheduled items with times (UTC and local) and the targets it could NOT fit with reasons. Nothing changes in the committed plan until commit_proposal is called or the person clicks Accept. This tool has NO date and NO site argument: it always plans the night and the site currently selected in the app, echoed back as data.night_of. To plan a different night, call set_observing_time with { "date": "YYYY-MM-DD" } FIRST, then call this.
View tool JSON
{
"name": "propose_plan",
"kind": "act",
"impl": "imperative",
"description": "Use this to propose an observing plan WITHOUT applying it. The proposal appears on the person's night timeline as a dotted \"proposed by agent\" ghost plan they can accept or reject item by item (with a reason you can read back). Give targets by id or name, optionally a duration per target and a rationale; the app schedules each target inside its visibility window during astronomical darkness, avoiding overlaps with the existing plan, and returns the proposal_id, the scheduled items with times (UTC and local) and the targets it could NOT fit with reasons. Nothing changes in the committed plan until commit_proposal is called or the person clicks Accept. This tool has NO date and NO site argument: it always plans the night and the site currently selected in the app, echoed back as data.night_of. To plan a different night, call set_observing_time with { \"date\": \"YYYY-MM-DD\" } FIRST, then call this.",
"inputSchema": {
"type": "object",
"properties": {
"targets": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"description": "The targets to fit into the night currently selected in the app, in any order; the app schedules them.",
"items": {
"type": "object",
"properties": {
"target": {
"type": "string",
"minLength": 1,
"maxLength": 60,
"description": "Target id or common name; matching ignores case, spaces and punctuation. Accepted: a Messier id in any spelling (\"M31\", \"M 31\", \"messier 31\"), the object common name or NGC designation (\"Ring Nebula\", \"Andromeda Galaxy\", \"Pleiades\", \"NGC 7089\"), a planet (\"Jupiter\", \"Saturn\"), \"Moon\", or a bright star (\"Vega\"). The Sun is not an observing target. A name that does not resolve comes back as unknown_target with the closest matches, so retry with one of those rather than guessing again."
},
"duration_minutes": {
"type": "integer",
"minimum": 10,
"maximum": 300,
"default": 45
},
"note": {
"type": "string",
"maxLength": 200
}
},
"required": [
"target"
],
"additionalProperties": false
}
},
"rationale": {
"type": "string",
"maxLength": 400,
"description": "One or two sentences the person will read next to the ghost plan."
},
"replace_existing": {
"type": "boolean",
"default": false,
"description": "True to propose replacing the committed plan instead of adding to it."
},
"min_altitude_deg": {
"type": "number",
"minimum": 5,
"maximum": 85,
"description": "Altitude floor for the blocks. Defaults to the filter the person set in the app."
}
},
"required": [
"targets"
],
"additionalProperties": false
}
}import_planAction
Use this to bring in another observer's plan and REVALIDATE it for the site and night shown here. Accepts a share URL of this app, an observing-plan.v1 JSON document, or a plain list of target names separated by commas or newlines. Every target is recomputed for THIS sky: targets that remain observable are rescheduled into their local windows; targets that do not work here (never rise at this latitude, below minimum altitude, no darkness, Moon too close) are listed with reasons. Creates a ghost proposal (nothing committed) and returns its proposal_id plus the diff versus the original plan, so you can explain what changed and why. Use commit_proposal to apply.
View tool JSON
{
"name": "import_plan",
"kind": "act",
"impl": "imperative",
"description": "Use this to bring in another observer's plan and REVALIDATE it for the site and night shown here. Accepts a share URL of this app, an observing-plan.v1 JSON document, or a plain list of target names separated by commas or newlines. Every target is recomputed for THIS sky: targets that remain observable are rescheduled into their local windows; targets that do not work here (never rise at this latitude, below minimum altitude, no darkness, Moon too close) are listed with reasons. Creates a ghost proposal (nothing committed) and returns its proposal_id plus the diff versus the original plan, so you can explain what changed and why. Use commit_proposal to apply.",
"inputSchema": {
"type": "object",
"properties": {
"source": {
"type": "string",
"minLength": 1,
"maxLength": 200000,
"description": "A share URL of this app (contains #plan=), the text of an observing-plan.v1 JSON document, or target names separated by commas or newlines (\"M31, M13, Jupiter\")."
},
"min_altitude_deg": {
"type": "number",
"minimum": 5,
"maximum": 85,
"description": "Lowest altitude above the horizon that still counts as observable. Defaults to the minimum altitude currently set in the app."
},
"keep_original_times": {
"type": "boolean",
"default": false,
"description": "Try the original UTC times first when they fall inside this night's darkness and the target is up."
}
},
"required": [
"source"
],
"additionalProperties": false
}
}compare_dark_sky_sitesAction
Use this to compare the world's dark-sky observatories and Starlight reserves for a night: for each site it returns astronomical darkness hours and Moon-free hours, plus (when the network allows) the night's mean cloud cover, humidity and 200 hPa wind (a seeing proxy) from Open-Meteo in ONE request. Ranks sites by usable dark hours and clear-sky fraction. Pass site_ids to restrict, or include_current_site to add the site shown in the app. This is the ONLY tool that calls an external service; if it fails it falls back to a cached forecast and says so in caveats.
View tool JSON
{
"name": "compare_dark_sky_sites",
"kind": "act",
"impl": "imperative",
"description": "Use this to compare the world's dark-sky observatories and Starlight reserves for a night: for each site it returns astronomical darkness hours and Moon-free hours, plus (when the network allows) the night's mean cloud cover, humidity and 200 hPa wind (a seeing proxy) from Open-Meteo in ONE request. Ranks sites by usable dark hours and clear-sky fraction. Pass site_ids to restrict, or include_current_site to add the site shown in the app. This is the ONLY tool that calls an external service; if it fails it falls back to a cached forecast and says so in caveats.",
"inputSchema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Local calendar date of the EVENING the night starts (YYYY-MM-DD). Omit for the night currently selected in the app."
},
"site_ids": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 60
},
"maxItems": 30,
"description": "Restrict the comparison to these dark-sky sites, by id or by name; matching ignores case and punctuation and accepts a name prefix. Known ids: roque, teide, mauna-kea, haleakala, paranal, la-silla, cerro-pachon, cerro-tololo, chajnantor, kitt-peak, mount-graham, mount-lemmon, palomar, dantes-view, jasper, pic-du-midi, calar-alto, montsec, alqueva, kerry, brecon-beacons, westhavelland, sutherland, namibrand, hanle, ali, siding-spring, aoraki-mackenzie. Omit to compare all 28."
},
"include_current_site": {
"type": "boolean",
"default": true,
"description": "Add the observing site currently shown in the app to the comparison."
},
"include_weather": {
"type": "boolean",
"default": true,
"description": "Ask Open-Meteo for the cloud forecast. Set false for pure astronomy with no network call."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 30,
"default": 10,
"description": "How many sites to return, best first."
}
},
"additionalProperties": false
}
}clear_planAction
Use this to delete the whole committed plan. DESTRUCTIVE: requires confirm:true. Without confirm nothing is deleted; instead the app shows the person a confirmation banner and this returns confirmation_required. On success returns an undo_token valid for 5 minutes; call clear_plan again with { undo_token } to restore the plan. confirm:true and undo_token in the same call are opposite intentions and come back as invalid_input. Stays registered when the plan is empty so the undo is always reachable.
View tool JSON
{
"name": "clear_plan",
"kind": "act",
"impl": "imperative",
"description": "Use this to delete the whole committed plan. DESTRUCTIVE: requires confirm:true. Without confirm nothing is deleted; instead the app shows the person a confirmation banner and this returns confirmation_required. On success returns an undo_token valid for 5 minutes; call clear_plan again with { undo_token } to restore the plan. confirm:true and undo_token in the same call are opposite intentions and come back as invalid_input. Stays registered when the plan is empty so the undo is always reachable.",
"inputSchema": {
"type": "object",
"properties": {
"confirm": {
"type": "boolean",
"description": "Must be true to actually delete the plan. Ask the person first; without it nothing is deleted."
},
"undo_token": {
"type": "string",
"minLength": 1,
"maxLength": 80,
"description": "Token returned by a previous clear_plan; restores that plan within 5 minutes."
}
},
"additionalProperties": false
}
}set_observing_siteAction
Use this to move the whole app to another observing site: the night ephemeris, the sky map, the night strip and every altitude in the page recompute for it, and the person sees the move happen. Name a dark-sky catalog site with site_id ("mauna-kea", "paranal", "roque"), or pass latitude and longitude in decimal degrees (longitude EAST positive) with optional elevation_m, time_zone (IANA) and name. Every read-only tool also accepts its own one-off site argument, which answers for that place WITHOUT moving the app: use that to compare places, and use this only when the page itself should move. Idempotent. A committed plan is not deleted by the move: it is marked stale, the person gets a Revalidate banner, and export_plan refuses with plan_stale until they revalidate or keep it anyway.
View tool JSON
{
"name": "set_observing_site",
"kind": "act",
"impl": "imperative",
"description": "Use this to move the whole app to another observing site: the night ephemeris, the sky map, the night strip and every altitude in the page recompute for it, and the person sees the move happen. Name a dark-sky catalog site with site_id (\"mauna-kea\", \"paranal\", \"roque\"), or pass latitude and longitude in decimal degrees (longitude EAST positive) with optional elevation_m, time_zone (IANA) and name. Every read-only tool also accepts its own one-off site argument, which answers for that place WITHOUT moving the app: use that to compare places, and use this only when the page itself should move. Idempotent. A committed plan is not deleted by the move: it is marked stale, the person gets a Revalidate banner, and export_plan refuses with plan_stale until they revalidate or keep it anyway.",
"inputSchema": {
"type": "object",
"description": "Where to move the app. Name a dark-sky catalog site with site_id (or id), OR pass BOTH latitude and longitude, plus time_zone (IANA) if you want local times rather than UTC only.",
"properties": {
"site_id": {
"type": "string",
"maxLength": 40,
"description": "Dark-sky catalog id such as roque, mauna-kea, paranal. Same thing as id, spelled the way a form field is. Preferred over coordinates because it brings the exact elevation and IANA zone."
},
"id": {
"type": "string",
"maxLength": 40,
"description": "Dark-sky catalog id such as roque, mauna-kea, paranal; preferred over coordinates because it brings the exact elevation and IANA zone."
},
"latitude": {
"type": "number",
"minimum": -90,
"maximum": 90,
"description": "Decimal degrees, north positive."
},
"longitude": {
"type": "number",
"minimum": -180,
"maximum": 180,
"description": "Decimal degrees, EAST positive (Mauna Kea is -155.47)."
},
"elevation_m": {
"type": "number",
"minimum": -430,
"maximum": 9000
},
"time_zone": {
"type": "string",
"description": "IANA time zone name."
},
"name": {
"type": "string",
"maxLength": 80,
"description": "Catalog site name (\"Mauna Kea\", matched loosely) when you pass no coordinates, or the label for the coordinates when you do."
}
},
"additionalProperties": false
}
}set_observing_site_formAction
Change the observing site shown in the app. Pick a known dark-sky site by id or enter latitude, longitude (east positive), elevation in metres and an IANA time zone. The whole app (ephemeris, sky map, plan) recomputes for the new site.
View tool JSON
{
"name": "set_observing_site_form",
"kind": "act",
"impl": "declarative",
"description": "Change the observing site shown in the app. Pick a known dark-sky site by id or enter latitude, longitude (east positive), elevation in metres and an IANA time zone. The whole app (ephemeris, sky map, plan) recomputes for the new site.",
"inputSchema": {
"type": "object",
"properties": {
"site_id": {
"type": "string",
"enum": [
"roque",
"teide",
"mauna-kea",
"haleakala",
"paranal",
"la-silla",
"cerro-pachon",
"cerro-tololo",
"chajnantor",
"kitt-peak",
"mount-graham",
"mount-lemmon",
"palomar",
"dantes-view",
"jasper",
"pic-du-midi",
"calar-alto",
"montsec",
"alqueva",
"kerry",
"brecon-beacons",
"westhavelland",
"sutherland",
"namibrand",
"hanle",
"ali",
"siding-spring",
"aoraki-mackenzie",
"custom"
]
},
"latitude": {
"type": "number"
},
"longitude": {
"type": "number"
},
"elevation_m": {
"type": "number"
},
"time_zone": {
"type": "string",
"description": "Atlantic/Canary"
},
"name": {
"type": "string"
}
}
}
}