Imported from salaroglio/MdExplorer (
.github/skills/mde-readme/SKILL.md). Install upstream withnpx skills add salaroglio/MdExplorer --skill mde-readme. Copyright stays with the author.
README with runnable examples — MdExplorer convention
MdExplorer renders fenced bash, sh, powershell, pwsh, cmd blocks with a ▶ Run toolbar.
When the user clicks Run, MdExplorer parses the script for parameters, pops up a dialog so the
user can fill them in, and then executes the script with those values substituted.
For this to work, you must write the example following the convention below.
What every runnable example must contain
- A short prose intro explaining what the script does.
- A
@paramdocumentation header inside the fenced block, one line per parameter. - Placeholder tokens of the form
<param-name>wherever the user must supply a value. - Optionally, a
@descriptionline summarising the block.
Do not hardcode example values directly into the call — always use placeholders. The dialog
defaults to empty (or to a default: you declare); the user fills them in before running.
Parameter declaration syntax
Use comments natural to the shell:
| Shell | Comment prefix |
|---|---|
| bash / sh | # |
| powershell | # |
| cmd / bat | REM or :: |
The grammar for each parameter line is:
<comment-prefix> @param <NAME> [— description] [default: <value>] [secret] [type: file|dir|out-file]
Rules:
<NAME>is[A-Za-z][A-Za-z0-9_-]*(letters, digits, underscore, dash).- The
—(em dash) or a simple-or:separates the name from the description. Anything after the name on the same line is the description. default: <value>(anywhere on the line, in parentheses or after the description) sets the default value pre-filled in the dialog.secret(or the name containingKEY/TOKEN/SECRET/PASSWORD/PWD) renders the field as a password input.type: filerenders a path-picker button that opens MdExplorer's file browser rooted at the project folder. The chosen path is inserted as the parameter value when Run is clicked.type: diris identical totype: filebut lets the user pick a folder instead of a file.type: out-fileis for files the script will generate (output files that don't exist yet). The picker opens in Save-As mode: the user navigates to the destination folder, types the filename, and the full path is composed for the script. Use this whenever the parameter represents an output / destination file. Thedefault:value (if any) pre-fills the filename suggestion in the Save-As input. Synonyms accepted:output-file,save-file.- Path pickers work identically in the browser and in the Electron build — they reuse the MdExplorer-managed file browser, no native OS dialog needed, so they behave consistently on Windows, macOS and Linux.
The same <NAME> is then referenced inside the script body as <name> (lower-cased and with
underscores allowed) — case-insensitive match. Placeholders never quoted by you; the runner
quotes them safely per shell.
This bites hardest in variable assignments, where the quoting habit is strongest. Leave the placeholder bare:
| Correct (bare) | Wrong (quoted) |
|---|---|
$fuseki = <FUSEKI> |
$fuseki = "<FUSEKI>" |
DEST=<target_dir> |
DEST="<target_dir>" |
The runner substitutes <FUSEKI> with an already shell-quoted value (e.g.
'http://localhost:3030'). If you also quote it, the two stack up — "<FUSEKI>" →
"'http://localhost:3030'" — and the variable ends up holding literal quote characters,
producing failures like "Invalid URI: hostname could not be parsed". The only exception is the
legacy export VAR="<x>" form (Example 3), where the runner rewrites the entire right-hand side,
quotes included.
Working directory — every command runs from the PROJECT ROOT
Critical: when the user clicks ▶ Run, MdExplorer executes the script with the working directory set to the project root — the folder the user opened in MdExplorer — not the folder that contains the README. This is true even when the README lives several levels deep in a subfolder.
Therefore every relative path in the command must be written relative to the project root, not relative to the README's own location. This applies to:
- the script/program being invoked (
python main.py,./build.sh,node cli.js), - any helper files, config files, or relative output paths the command references.
The trap: a README documenting a tool naturally describes commands as if you were standing inside
the tool's folder. That instinct produces a broken block. Example — a README at
ai-tools-pli/analyze-pli-programs/README.md whose main.py sits next to it:
| Wrong (relative to the README) | Correct (relative to the project root) |
|---|---|
python main.py <pli_file> |
python ai-tools-pli/analyze-pli-programs/main.py <pli_file> |
./run.sh |
./tools/run.sh |
The wrong form fails with can't open file '...\main.py': [Errno 2] No such file or directory
because Python looks for main.py in the project root, where it does not exist.
Rules:
- Prefix the invoked script with its path from the project root. This is the simplest robust form and is unaffected by how parameter values are resolved.
- Do not assume the README's folder is the cwd. Don't write
python main.pyhoping the runner willcdnext to the README — it won't. - If you genuinely need a different working directory,
cdexplicitly using a root-relative path as the first line of the block (e.g.cd ai-tools-pli/analyze-pli-programs), then call the script. Prefer the path-prefix form above unless the tool truly requires its own cwd. type: file/type: dirpickers are also rooted at the project root, so picked paths share the same anchor as your root-relative command — they stay consistent, no conflict.
Path separator — always /, never \
Critical for cross-platform documents. The same README is run on both Windows and Linux/macOS,
so every path you write inside a runnable block must use the forward slash / as separator —
never the Windows backslash \.
- Forward slash works on all platforms: .NET / Win32 accept
/for filesystem paths on Windows too, and it is the native separator on Linux/macOS. - Backslash works only on Windows. On Linux/macOS
\is a literal filename character, not a separator, so a path likeOntology\ABoxPL1\file.ttlis read as one big filename and the script fails with "No such file or directory".
MdExplorer cannot fix this for you at run time: since \ is a legal character in a Linux filename,
the runner must pass your path through verbatim — rewriting it would corrupt paths that legitimately
contain a backslash. Portability is your responsibility as the author: type /.
Wrong (Windows-only \) |
Correct (portable /) |
|---|---|
$file = "Ontology\ABoxPL1\BS507.ttl" |
$file = "Ontology/ABoxPL1/BS507.ttl" |
dotnet publish .\src\MyApp.csproj |
dotnet publish src/MyApp.csproj |
python tools\foo\main.py |
python tools/foo/main.py |
This holds for every shell — bash, powershell, cmd alike. (PowerShell accepts / on
Windows for file paths, so a single /-form works in pwsh on every OS.) If a PowerShell script
genuinely needs the OS-native separator (e.g. to hand a path to a Windows-only external tool), build
it with Join-Path instead of hardcoding \:
$file = Join-Path "Ontology" "ABoxPL1" "BS507.ttl" # -> '\' on Windows, '/' on Linux
Also never hardcode an absolute root (C:\sviluppo\..., /home/user/...) or a drive letter:
those are machine-specific and break the moment the document moves. Keep paths relative to the
project root (see the previous section) and use /.
Examples to copy when authoring a README
1. Bash — deploy script
# @param ENV — target environment (default: staging)
# @param VERSION — git tag or branch to deploy
# @param API_KEY — deployment API key (secret)
./deploy.sh --env <env> --version <version> --key <api_key>
2. PowerShell — local build
# @param Configuration — Debug or Release (default: Release)
# @param Runtime — RID like win-x64, linux-x64 (default: win-x64)
dotnet publish src/MyApp.csproj -c <Configuration> -r <Runtime> --self-contained
3. Bash with env-export style (also detected, legacy)
Special case: the quotes around "<greeting>" are correct only here — the export VAR=...
form makes the runner rewrite the whole right-hand side. Everywhere else (plain $var = <x>
assignments, command arguments) keep placeholders bare.
# @param GREETING — message to print (default: Hello)
export GREETING="<greeting>"
echo "$GREETING, world!"
4. Cmd / batch
REM @param TARGET — build target (default: all)
REM @param THREADS — parallel build threads (default: 4)
make <target> -j<threads>
5. Bash with path pickers
When a parameter takes a path on disk, mark it type: file (file picker), type: dir
(folder picker), or type: out-file (Save-As picker for files the script generates).
Clicking the parameter chip in MdExplorer opens the project-scoped file browser starting
at the project root.
# @param SOURCE — file to upload (type: file)
# @param TARGET_DIR — destination directory inside the project (type: dir)
rsync <source> <target_dir>/
6. Script that generates an output file
When the parameter is a file the script CREATES, the user cannot select it because it does
not yet exist. Use type: out-file — the picker opens in Save-As mode, so the user picks
the destination folder and types the filename.
# @param INPUT — input Excel file (type: file)
# @param OUTPUT_FILE — generated Markdown file (default: report.md, type: out-file)
python -m tools.excel_to_markdown.main <input> -o <output_file>
7. Tool living in a subfolder (path is relative to the project root)
The README sits in ai-tools-pli/analyze-pli-programs/, and so does main.py. Because the block
runs from the project root, the call must spell out the path to main.py from the root — not
just python main.py.
# @param PLI_FILE — PL/I source to analyse (type: file)
python ai-tools-pli/analyze-pli-programs/main.py <pli_file>
When the AI generates a README
When you are asked to write or update a README that documents a runnable script:
- Start each runnable section with a heading (e.g.
### Deploy,### Run locally). - Above the fenced block, write 1–3 prose lines: what it does, when to use it, any side effects (writes to disk, hits production, etc.).
- Inside the fenced block, put the
@paramdocumentation header first, blank line, then the actual call. - Prefer one parameter per line in the call so the placeholders are visually obvious; long lines are OK if needed.
- One block per scenario — do not stuff multiple unrelated invocations into a single fence; MdExplorer treats each fence as one runnable cell.
- Don't add usage comments after
--help-style lines unless the script also exposes them at runtime; keep documentation in the@paramheader.
What NOT to do
- ❌ Don't write
./deploy.sh --env stagingwith hardcoded values — the user can't change them. - ❌ Don't use bare
$VARreferences without an@paramline above; the runner won't know they exist. - ❌ Don't use angle brackets for anything other than parameter placeholders inside runnable
blocks; the parser treats
<word>as a parameter. - ❌ Don't quote a placeholder in an assignment or argument —
$x = "<param>",--key "<key>". The runner already shell-quotes substituted values, so your quotes stack and inject literal quote characters. Keep them bare:$x = <param>,--key <key>. (Sole exception: the legacyexport VAR="<x>"form.) - ❌ Don't mix shells in a single fence (e.g.
bashfence with PowerShell syntax inside). - ❌ Don't write a script path relative to the README's folder (
python main.pywhenmain.pylives beside the README in a subfolder). The block runs from the project root, so it fails withcan't open file. Write the path from the root:python tools/foo/main.py. - ❌ Don't use backslashes
\in paths ($file = "Ontology\ABoxPL1\file.ttl"). They work only on Windows; on Linux/macOS\is a literal filename character and the script fails withNo such file or directory. Always use forward slashes:"Ontology/ABoxPL1/file.ttl". - ❌ Don't hardcode an absolute root or drive letter (
C:\sviluppo\...,/home/user/...); it is machine-specific. Keep paths relative to the project root.
Quick checklist before committing a README
- Every runnable fence starts with a
@paramheader (or has no parameters at all). - Every placeholder
<name>in the call has a matching@param NAMEline above. - Every relative path (the invoked script, helper/config/output files) is written relative to the project root, not to the README's folder — the block runs from the project root.
- Every path uses forward slashes
/, never backslashes\, and no absolute root / drive letter — so the same document runs on both Windows and Linux/macOS. - Placeholders are bare (
$x = <name>,--key <name>), never self-quoted — the runner quotes them. - Sensitive parameters are marked
secret(or named with a secret-like suffix). - Defaults are provided for non-secret parameters where a sensible default exists.
- Prose above the block explains the side effects.