Astro DSO Documentation Generator

SkillDocs & knowledge

Generates a complete, polished HTML documentation page, a processing checklist, an AstroBin post JSON, a PixInsight process icon set (XPSM), AND a ready-to-paste PixInsight project Description field for a deep-sky object (DSO) astrophotography project. Use this skill whenever the user mentions astrophotography, a DSO name (NGC, IC, Messier, Sharpless, etc.), wants to document an imaging session, mentions PixInsight project documentation, wants to create an observation report, or asks to generate a page/document for a nebula, galaxy, cluster, or other deep-sky target. Triggers on phrases like "create doc for NGC XXXX", "generate DSO page", "document my session on", "make a PixInsight doc for", "astro documentation page", "pixinsight project description", "description field pixinsight", "processing checklist", "workflow checklist", "astrobin", "astrobin post", "astrobin upload", "process icons", "xpsm", "pixinsight icons". Always use this skill — not a generic HTML generator — when the subject is a deep-sky object.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Astro DSO Documentation Generator skill

What this skill tells your AI

The instructions your AI receives, as published by jjmartres/ai-coding-agents in shared/.ai-agents/skills/astro-dso-doc/SKILL.md and read by ahel’s review.

Generates five deliverables for a deep-sky object (DSO) astrophotography project:

  1. project.json — a flat JSON file containing the PixInsight project description data (copy the description field value into the Description box of .xosm)
  2. doc/index.html — a rich, self-contained HTML documentation page (path goes into the Documentation field of .xosm), using the Catppuccin flavor palette with a theme switcher.
  3. doc/processing-checklist.html — an interactive step-by-step PixInsight processing checklist adapted to the target's filter set (LRGB, HOO, SHO, RGB-only, etc.), using the same Catppuccin design system.
  4. astrobin.json — a structured JSON file containing all AstroBin image post fields, ready to copy-paste into the AstroBin upload form.
  5. Process Icons - <common_name> - <workflow_type> workflow.xpsm — a PixInsight process icon set tailored to the detected workflow (LRGB, RGB, HOO, SHO), generated from the processing checklist phases.

Workflow

Step 1 — Identify the Target

Extract the DSO name/catalog number from the user's message. If ambiguous or missing, ask for it before proceeding.

Common catalog prefixes: NGC, IC, M (Messier), Sh2 (Sharpless), B (Barnard), vdB, LBN, LDN, Ced, RCW.


Step 2 — Collect Acquisition Data (interactive)

Ask the user the following questions one block at a time (don't dump all at once). Wait for answers before proceeding.

Block A — Instrument:

What instrument did you use?
  (e.g. telescope model, focal length, f-ratio, camera, built-in filter)

Block B — Site:

Where did you image from?
  (location name, lat/lon if known, approximate altitude)

Block C — Session files:

Please provide the path to the folder or list of session files.
  (e.g. /path/to/sessions/ or paste filenames directly)

If the user gives a directory path, scan it with Bash:

ls -1 /path/to/sessions/ | grep -iE '\.(tiff?|fits?|xisf|cr2|nef|dng|raf|jpg|jpeg|png)$' | sort

Present the sorted list for confirmation before proceeding.

If the user pastes filenames directly, accept them as-is.

Block D — Optional extras (ask once, accept "skip" gracefully):

Any additional notes? (sky conditions, Bortle class, total integration time, etc.)

Step 3 — Research the DSO

Use web_search to gather comprehensive data. Run at least 3 searches in parallel or sequence:

  1. {DSO name} {common name} astronomical data distance magnitude type constellation
  2. {DSO name} star forming region physical properties HII nebula galaxy cluster
  3. {DSO name} NASA Hubble ESA observations history discovery
  4. (if nebula) {DSO name} astrophotography imaging narrowband dual-band filter tips

Collect:

  • Coordinates (RA / Dec J2000)
  • Distance (ly and parsecs)
  • Apparent size (arcmin)
  • Visual magnitude
  • Physical diameter
  • Object type (HII region, reflection nebula, galaxy, globular cluster, etc.)
  • Discovery history (who, when, instrument)
  • Physical structure and processes
  • Notable stars or ionizing sources
  • Observation/imaging notes
  • Key references (NASA, ESA, SIMBAD, catalogues)
  • All catalog designations (NGC, IC, Sh2, LBN, etc.)

Step 4 — Generate project.json

Build a flat JSON object (no nesting, no indentation) representing the PixInsight project description. Save it as ./project.json in the current working directory using Bash:

cat > ./project.json << 'EOF'
{"target":"NGC2174","common_name":"Monkey Head Nebula","constellation":"Orion","ra":"06h 09m 42s","dec":"+20° 30' 00\"","object_type":"H II Region","telescope":"Vaonis Vespera 1 (50mm f/4)","camera":"Sony IMX585 (built-in)","filter":"Dual-band built-in (Ha + OIII)","site":"Dark Sky Site","site_coords":"45.0N / 6.0E · ~900m","session_count":6,"sessions":["2026-03-19_19h04.tiff","2026-03-20_18h48.tiff","2026-03-23_20h43.tiff","2026-04-07_20h40.tiff","2026-04-08_19h19.tiff","2026-04-16_19h37.tiff"],"stacking":"Internal stacking by instrument","calibration":"Handled internally by the instrument","subs_integrated":"6 / 6","ref_alignment":"","ref_integration":"","notes":"","created":"2026-05-05","description":"TARGET       : NGC2174 – Monkey Head Nebula\nConstellation: Orion | RA 06h 09m 42s / Dec +20° 30' 00\"\n\n── EQUIPMENT ────────────────────────────────────────────\nTelescope    : Vaonis Vespera 1 (50mm f/4)\nCamera       : Sony IMX585 (built-in)\nFilter       : Dual-band built-in (Ha + OIII)\nSite         : Dark Sky Site (45.0N / 6.0E · ~900m)\n\n── SESSIONS ─────────────────────────────────────────────\n2026-03-19_19h04.tiff\n2026-03-20_18h48.tiff\n2026-03-23_20h43.tiff\n2026-04-07_20h40.tiff\n2026-04-08_19h19.tiff\n2026-04-16_19h37.tiff\n\nTotal        : 6 sessions | Internal stacking by instrument\nCalibration  : Handled internally by the instrument\n\n── INTEGRATION ──────────────────────────────────────────\nSubs integrated  : 6 / 6\nRef. alignment   :\nRef. integration :\n\n────────────────────────────────────────────────────────\nProject created  : 2026-05-05"}
EOF

JSON field rules:

  • No indentation — the entire JSON must be a single line (minified)
  • All string values use \n for newlines within the description field
  • ref_alignment and ref_integration are always empty strings "" — the user fills them in PixInsight
  • calibration: "Handled internally by the instrument" for smart telescopes; "Darks / Flats / Bias" otherwise
  • sessions array: filenames sorted chronologically, no path prefix
  • description field: the full plain-text block (same format as before, using \n escapes) ready to paste into PixInsight's Description box
  • notes: empty string "" if user skipped Block D; otherwise the user's text

After writing the file, confirm the path to the user.


Step 5 — Generate the HTML Documentation Page

Read the full HTML template from references/documentation-template.html.

Fill in all the template placeholders using:

  • Research data (Step 3)
  • Acquisition data (Step 2)
  • Session file list (Step 2C)

Create the doc/ directory if it does not exist, then save the output:

mkdir -p ./doc

Save the file as ./doc/index.html.

Tell the user the full path of the generated file, and remind them:

  • Documentation field of the PixInsight project → path to doc/index.html
  • Description field of the PixInsight project → copy the value of the description key from project.json

Step 5b — Generate the Processing Checklist

Read the full checklist template from references/processing-checklist-template.html.

5b.1 — Detect filter set

Before filling the template, determine the workflow mode from the filter field in project.json:

Filter value containsWorkflow modeActive channels
Luminance + Red + Green + BlueLRGBL, R, G, B
Red + Green + Blue (no Lum)RGBR, G, B
Ha + OIII (no RGB)HOOHα, OIII
Ha + OIII + SIISHOHα, OIII, SII
Ha + RGBHαRGBHα, R, G, B
Ha + OIII + RGBHOO+RGBHα, OIII, R, G, B
Single filter onlyMonoThat channel only
5b.2 — Parse per-channel exposure times

Parse the sessions array from project.json to extract per-channel totals.

Case A — sessions is an array of summary strings (e.g. "Luminance: 3h 00m (36 × 300s)"): Extract directly from the string content.

Case B — sessions is an array of filenames (e.g. CHI-1-CMOS_2023-07-26_NGC253_Blue_300s_ID374118_cal.fits): Count files per filter keyword (Blue, Green, Red, Luminance, Ha, Halpha, OIII, SII) and compute total exposure using the sub duration from the filename or from subs_integrated.

If per-channel data cannot be parsed, use as the placeholder value.

5b.3 — Fill placeholders
PlaceholderValue
{{DSO_ID}}target field from project.json (e.g. NGC 4594)
{{COMMON_NAME}}common_name field
{{CONSTELLATION}}constellation field
{{OBJECT_TYPE}}object_type field, uppercased
{{CAMERA}}camera field
{{INSTRUMENT}}telescope field
{{FILTER}}filter field
{{SITE}}site field
{{TOTAL_EXPOSURE}}total_exposure field (or computed from sessions)
{{TOTAL_SUBS}}numeric portion of subs_integrated (e.g. 120)
{{LUM_TIME}}parsed Luminance total, e.g. 3h 00m (36 × 300s)
{{RED_TIME}}parsed Red total
{{GREEN_TIME}}parsed Green total
{{BLUE_TIME}}parsed Blue total
{{HA_TIME}}parsed Hα total (omit row if channel absent)
{{OIII_TIME}}parsed OIII total (omit row if channel absent)
{{SII_TIME}}parsed SII total (omit row if channel absent)
{{GENERATED_DATE}}today's date YYYY-MM-DD
{{AUTHOR}}user name / location from Block B, or
{{DSO_NOTES}}user notes from Block D — remove entire section 11 if empty

Also replace all occurrences of {{DSO_ID}} inside id="step-*" attributes and the STORAGE_KEY constant with a slug version: spaces replaced by hyphens, lowercased (e.g. ngc-4594).

5b.4 — Adapt sections to workflow mode

Apply the following structural modifications based on the detected workflow mode:

LRGB workflow (default — correct order):

  • Phase 02: Luminance linear (BXT + NXT only — no stretch)
  • Phase 03: RGB linear (SPFC + CC + SPCC + BXT + NXT — no stretch)
  • Phase 04: LRGBCombination on linear masters — combine L + RGB before any stretch
  • Phase 05: Stretch the LRGB combined image + SCNR
  • Phase 06: StarXterminator on stretched LRGB
  • Phase 07: Starless LRGB processing (GHS, LHE, USM, SCC, NXT)
  • Phase 08: Rescreen stars
  • Phase 09: Finalization (Curves, ICC, Export)

RGB workflow (no Luminance):

  • Remove section 04 entirely (Phase 02 — Luminance Processing)
  • Remove section 09 entirely (Phase 07 — LRGB Integration)
  • Update section numbers accordingly (03→03, 05→04, 06→05, 07→06, 08→07, 10→08, 11→09)
  • Change all tag-lrgb tags to tag-rgb in finalization steps
  • Update export filenames from {{DSO_ID}}_LRGB_final.* to {{DSO_ID}}_RGB_final.*
  • Remove Luminance legend item

HOO / SHO narrowband workflow:

  • Remove section 04 (Luminance linear) — keep section 09 if Luminance is present
  • In section 05 (RGB linear): rename to "Narrowband Processing (Linear)"
    • Replace ChannelCombination step with: "PixelMath — HOO / SHO palette assembly"
    • Remove SPCC step; replace with: "BackgroundNeutralization + ColorCalibration"
    • Keep BlurXterminator, NoiseXterminator, stretch steps
    • Replace SCNR step with: "Correct magenta stars — CorrectMagentaStars script"
  • Replace tag-rgb tags with tag-nb throughout narrowband phases
  • Uncomment narrowband legend items (tag-ha, tag-oiii) in the legend section
  • Uncomment narrowband data cells (Hα, OIII, SII) in section 01
  • Update export filenames to {{DSO_ID}}_HOO_final.* or {{DSO_ID}}_SHO_final.*

HαRGB workflow:

  • Keep full LRGB structure
  • Add one step after ChannelCombination in section 05: "PixelMath — Integrate Hα into Red channel (HαRGB blend)"
  • Uncomment Hα legend item and data cell
  • Tag the new step with tag-ha and tag-rgb

Smart telescope / pre-stacked (single session file per filter, stacking = "Internal stacking by instrument"):

  • Replace the entire Phase 01 (section 03) with a single step:

    Import pre-stacked masters
    Copy the stacked output files into the project folder.
    No WBPP run required — calibration and stacking handled by the instrument.
    
  • Remove Blink Comparator steps

5b.5 — Save the file
cat > ./doc/processing-checklist.html << 'EOF'
[filled and adapted template content]
EOF

Tell the user:

  • Processing Checklistdoc/processing-checklist.html

Checklist state (checked steps) persists across browser reloads via localStorage, keyed by DSO slug.


Step 5c — Generate astrobin.json

Build the AstroBin post fields JSON from all previously collected data (Steps 2, 3, 4).

5c.1 — Field mapping rules
AstroBin fieldSourceRules
titlecommon_name + target from project.jsonFormat: "Common Name (DSO_ID)" — e.g. "Sombrero Galaxy (NGC 4594)"
descriptionResearch data (Step 3) + acquisition summary3–5 sentences in English. Scientific summary + total integration + filter set. Plain text, no HTML.
linkTelescope.live dataset URL if applicableSee 5c.2 below
image_fileAlways "" — user uploads the image manually
imaging_telescopestelescope from project.jsonArray of objects — see 5c.3
imaging_camerascamera from project.jsonArray of objects — see 5c.3
mountsEmpty array [] unless user provided mount info in Block A
filtersfilter from project.jsonArray of objects — one per channel — see 5c.4
softwareAlways PixInsightFixed value — see 5c.3
locationsite + site_coords from project.jsonObject — see 5c.3
acquisition_detailssessions array from project.jsonArray of objects — one per filter channel — see 5c.5
first_acquisition_dateEarliest date parsed from sessionsYYYY-MM-DD format
last_acquisition_dateLatest date parsed from sessionsYYYY-MM-DD format
data_source"OWN" or "AMATEUR_HOSTING""AMATEUR_HOSTING" if site contains "telescope.live", "itelescope", "lightbuckets", "slooh"; otherwise "OWN"
remote_sourceRemote hosting label"TELELIVE" if telescope.live; "ITELESCOPE" if iTelescope; "" otherwise
subject_typeDerived from object_type in project.jsonSee 5c.6
5c.2 — Dataset link (telescope.live)

If site or telescope contains "telescope.live" or "Telescope.live":

  • Set link to "https://app.telescope.live/archive" as the default dataset URL.
  • If the user provided a specific dataset URL in Block D, use it instead.
  • Otherwise set link to "".
5c.3 — Equipment objects

imaging_telescopes — one object per telescope:

{
  "name": "Telescope.live Remote Observatory",
  "aperture": null,
  "focal_length": null,
  "type": "REFRACTOR"
}

Parse aperture (mm) and focal length (mm) from the telescope string if present (e.g. "50mm f/4"aperture: 50, focal_length: 200). Type heuristics: contains "Newton" or "Newtonian" → "NEWTONIAN", "Cassegrain" or "SCT" → "SCT", "Refractor" or "APO" or "ED" or "f/" → "REFRACTOR", "Reflector" → "REFLECTOR", otherwise "OTHER".

imaging_cameras — one object:

{
  "name": "QHY 600M",
  "type": "CCD",
  "modified": false
}

Type: if camera name contains "IMX", "CMOS", "ASI", "QHY", "ZWO", "Atik Horizon" → "CCD" (AstroBin uses CCD for all dedicated astro cameras). modified always false unless user stated "modded" or "full spectrum".

software — always:

[{"name": "PixInsight", "version": ""}]
5c.4 — Filters array

Parse the filter field from project.json. Produce one object per distinct channel:

{
  "name": "Luminance",
  "type": "L",
  "bandwidth": null,
  "vendor": "",
  "model": ""
}

Filter type mapping:

Channel keywordAstroBin type
Luminance, Lum, L"L"
Red, R"R"
Green, G"G"
Blue, B"B"
Hα, Ha, H-alpha"Ha"
OIII, O3"OIII"
SII, S2"SII"
Dual-band, dual bandproduce two objects: "Ha" + "OIII"
Tri-band, tri bandproduce three objects: "Ha" + "OIII" + "SII"

If the filter string includes a bandwidth (e.g. "3nm", "6.5nm", "7 nm"), set bandwidth to the numeric value in nm. Extract vendor/model if present (e.g. "Antlia 3nm Ha"vendor: "Antlia", model: "3nm Ha").

5c.5 — Acquisition details (DeepSky sessions)

Produce one object per filter channel. Parse per-channel data from the sessions array (same logic as Step 5b.2).

{
  "filter": "Luminance",
  "filter_type": "L",
  "number": 36,
  "duration": 300,
  "binning": 1,
  "gain": null,
  "sensor_cooling": null,
  "darks": null,
  "flats": null,
  "flat_darks": null,
  "bias": null,
  "bortle": null,
  "mean_sqm": null,
  "mean_fwhm": null,
  "temperature": null,
  "date": "YYYY-MM-DD"
}

Field rules:

  • number — sub count for this filter channel
  • duration — sub duration in seconds (parse from filename or session string, e.g. 300s, 300.00s)
  • binning — always 1 unless user specified otherwise
  • gain — parse from filename if present (e.g. _GAIN100_), otherwise null
  • bortle — parse from Block D notes if user mentioned Bortle class, otherwise null
  • date — use the first date found for this filter in the sessions array; format YYYY-MM-DD
  • All other fields null unless explicitly provided by the user
5c.6 — Subject type mapping
object_type containsAstroBin subject_type
Galaxy, Galaxies"GALAXY"
Nebula, HII, H II, Emission"NEBULA"
Cluster, Open cluster"OPEN_CLUSTER"
Globular"GLOBULAR_CLUSTER"
Planetary nebula"PLANETARY_NEBULA"
Supernova remnant, SNR"SUPERNOVA_REMNANT"
Solar system"SOLAR_SYSTEM_BODY"
Other / unknown"OTHER"
5c.7 — Save the file

Save as ./astrobin.json (pretty-printed, 2-space indent — unlike project.json this one is for human reading):

cat > ./astrobin.json << 'EOF'
{
  "title": "...",
  ...
}
EOF

Example output (M104, LRGB, Telescope.live):

{
  "title": "Sombrero Galaxy (NGC 4594)",
  "description": "The Sombrero Galaxy (NGC 4594) is a spectacular Sa/Sb spiral galaxy in Virgo, located approximately 28 million light-years away. Its iconic silhouette features a brilliant central bulge bisected by a prominent dust lane. Imaged in LRGB from the Telescope.live Chile remote observatory using a QHY 600M camera over 10 hours of total integration (36×300s Luminance, 27×300s Red, 29×300s Green, 28×300s Blue). Processed in PixInsight.",
  "link": "https://app.telescope.live/archive",
  "image_file": "",
  "imaging_telescopes": [
    {
      "name": "Telescope.live Remote Observatory",
      "aperture": null,
      "focal_length": null,
      "type": "REFRACTOR"
    }
  ],
  "imaging_cameras": [
    {
      "name": "QHY 600M",
      "type": "CCD",
      "modified": false
    }
  ],
  "mounts": [],
  "filters": [
    { "name": "Luminance", "type": "L", "bandwidth": null, "vendor": "", "model": "" },
    { "name": "Red",       "type": "R", "bandwidth": null, "vendor": "", "model": "" },
    { "name": "Green",     "type": "G", "bandwidth": null, "vendor": "", "model": "" },
    { "name": "Blue",      "type": "B", "bandwidth": null, "vendor": "", "model": "" }
  ],
  "software": [
    { "name": "PixInsight", "version": "" }
  ],
  "location": {
    "name": "Chile Remote Observatory",
    "coords": "Chile · Southern Hemisphere"
  },
  "subject_type": "GALAXY",
  "data_source": "AMATEUR_HOSTING",
  "remote_source": "TELELIVE",
  "first_acquisition_date": "2023-02-15",
  "last_acquisition_date": "2025-03-06",
  "acquisition_details": [
    {
      "filter": "Luminance", "filter_type": "L",
      "number": 36, "duration": 300, "binning": 1,
      "gain": null, "sensor_cooling": null,
      "darks": null, "flats": null, "flat_darks": null, "bias": null,
      "bortle": null, "mean_sqm": null, "mean_fwhm": null, "temperature": null,
      "date": "2023-02-15"
    },
    {
      "filter": "Red", "filter_type": "R",
      "number": 27, "duration": 300, "binning": 1,
      "gain": null, "sensor_cooling": null,
      "darks": null, "flats": null, "flat_darks": null, "bias": null,
      "bortle": null, "mean_sqm": null, "mean_fwhm": null, "temperature": null,
      "date": "2023-02-15"
    },
    {
      "filter": "Green", "filter_type": "G",
      "number": 29, "duration": 300, "binning": 1,
      "gain": null, "sensor_cooling": null,
      "darks": null, "flats": null, "flat_darks": null, "bias": null,
      "bortle": null, "mean_sqm": null, "mean_fwhm": null, "temperature": null,
      "date": "2023-02-15"
    },
    {
      "filter": "Blue", "filter_type": "B",
      "number": 28, "duration": 300, "binning": 1,
      "gain": null, "sensor_cooling": null,
      "darks": null, "flats": null, "flat_darks": null, "bias": null,
      "bortle": null, "mean_sqm": null, "mean_fwhm": null, "temperature": null,
      "date": "2023-02-15"
    }
  ]
}

Tell the user:

  • AstroBin post fieldsastrobin.json
  • Fields imaging_telescopes, imaging_cameras, filters, acquisition_details map 1:1 to AstroBin's "Equipment" and "Acquisition" sections.
  • image_file must be filled manually when uploading.

Step 5d — Generate Process Icons XPSM

Generate a PixInsight process icon set (.xpsm) tailored to the detected workflow mode (Step 5b.1) and the specific target.

5d.1 — Filename
Process Icons - <common_name> - <workflow_type> workflow.xpsm
  • <common_name>common_name from project.json, spaces preserved (e.g. Sombrero Galaxy)
  • <workflow_type> → detected workflow mode, title-cased: LRGB, RGB, HOO, SHO, HaRGB

Example: Process Icons - Sombrero Galaxy - LRGB workflow.xpsm

Save to the current working directory (not inside doc/).

5d.2 — XPSM file structure

All XPSM files share this skeleton:

<?xml version="1.0" encoding="UTF-8"?>
<!--
  PixInsight XML Process Serialization Module - XPSM 1.0
  Generated: <GENERATED_DATE>
  Target: <DSO_ID> — <COMMON_NAME>
  Workflow: <WORKFLOW_TYPE>
-->
<xpsm version="1.0"
  xmlns="http://www.pixinsight.com/xpsm"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.pixinsight.com/xpsm http://pixinsight.com/xpsm/xpsm-1.0.xsd">

  [instance blocks]

  [icon blocks]

</xpsm>

Rules:

  • Every <instance> block must have a unique id ending in _instance
  • Every <icon> block references its instance via instance="<id>"
  • xpos always 2000, ypos increments of 40 per icon from top
  • workspace always "Workspace01"
  • The <description> element on each instance must state the phase number, step number, and a one-line usage note
5d.3 — LRGB workflow icon set

Generate the following instances and icons in this exact order:

PHASE 02 — Luminance Linear (no stretch)

Icon IDClassKey parametersDescription
STF_AutoStretchScreenTransferFunction4 rows, c0=0.01732, m=0.02099, c1=1.0; interaction=GrayscaleVisual check only — do NOT apply permanently
BXT_CorrectOnly_LumBlurXTerminatorcorrect_only=true, sharpen_stars=0.25, sharpen_nonstellar=0.90, auto_nonstellar_psf=trueFirst pass on Lum linear — PSF correction only
BXT_Full_LumBlurXTerminatorcorrect_only=false, sharpen_stars=0.25, sharpen_nonstellar=0.90, auto_nonstellar_psf=trueSecond pass on Lum linear — full sharpening
NXT_LumNoiseXTerminatordenoise=0.70, enable_color_separation=false, iterations=2, detail=0.15Mono channel — color separation OFF

PHASE 03 — RGB Linear (no stretch)

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
46
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
astro-dso-doc
Source
github.com/jjmartres/ai-coding-agents