Skip to content

Commit 41e0137

Browse files
MendixMauclaude
andauthored
Fix mxcli v0.29 compatibility; doctor advises the latest mxcli (#603)
On mxcli v0.29.0, four instruments misread the new output: - property-sets MIXED rows: doctor now classifies them as "mixed", and the guarded repair is no longer rolled back for MIXED alone. - fix hashes on MPR v1: both v1 forms are read, so exec.sh --patch on Mendix < 10.18 is verified again. - SHOW/list modules "Protected AddOn|Solution" in Source (v0.28+): page-scope and the rehost module list keep first-party Protected modules. Also in this change: - wf-add-path-terminators.py refuses below Mendix 10.21. - module-close notes the v0.29 docker check default. - The stale v0.24 pin advice is gone. - New: the normal doctor run advises when mxcli is behind the latest release. It respects MXCLI_VERSION pins, uses curl -q so ~/.curlrc is ignored, downloads nothing and never fails. Golden input was captured from the real v0.28.0/v0.29.0 binaries. Review: MERGE — the latest-version check now ignores ~/.curlrc (curl -q), so it cannot follow the redirect and download ~107 MB; captures re-verified against the real v0.28/v0.29 binaries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LeNWZV2sVH62vkgfYh5u5
1 parent 0ad78dd commit 41e0137

59 files changed

Lines changed: 691 additions & 34 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m
1919

2020
## Unreleased
2121

22+
- fix(doctor, exec, page-scope, rehost, wf-add-path-terminators, module-close): **mxcli v0.29 compatibility, and doctor advises the latest mxcli**: on v0.29.0 (which `--install` fetches by default) four instruments read new output wrong. (1) `fix property-sets` prints `MIXED` rows and exits 1: doctor reported "could not classify" (rc 4, shown as info by sync) — now result `mixed`, a WARN naming the rows and saying the repair cannot clear them; (2) the repair also exits 1 while a MIXED name remains, so `--fix-property-sets` rolled back a repair that had landed — the apply step now passes when only MIXED is left (FAILED still fails); (3) `fix hashes` now verifies MPR v1 with new text, which exec.sh read as an error, so every `--patch` on a Mendix < 10.18 project went unverified — both v1 forms are read; (4) since v0.28 `SHOW MODULES` / `list modules` write `Protected AddOn|Solution` in Source, so a first-party module set to Protected dropped out of page-scope (FAULT, 0 modules) and out of the rehost module list — the export level no longer counts as an origin. `wf-add-path-terminators.py` refuses below Mendix 10.21 (the terminator type does not exist there; written in, the project will not load), module-close notes that v0.29 `docker check` checks the stored project (a CE0463 it now reports is real), and the stale "stay on v0.24" pin advice is replaced. **New:** the normal doctor run compares the project mxcli with the latest release (redirect of the asset URL, 5 s, nothing downloaded): WARN with release link and upgrade path when behind, a note when `MXCLI_VERSION` pins it, one quiet note offline, never a FAIL; `MXTK_NO_LATEST_CHECK=1` skips it. Golden input captured from the real v0.28.0/v0.29.0 binaries on the mxpatch fixture model and mutated copies (CAPTURE files); against master the new cases fail (exec-hash-verify 4, property-sets 7, mxpatch 1, page-scope-protected 3, rehost-list-modules 1, doctor-mxcli-latest 8). Field run: page-scope and doctor against real v0.29.0/v0.28.0 binaries in a cloud session — mxcli v0.29 audit (2026-10-30)
2223
- fix(skills): **skill text no longer tells agents that mxcli v0.25–v0.29 cannot do things it now does**: STOP 21 said `ALTER NAVIGATION` does not exist and restatement is the only write — it now points at `alter navigation`/`alter menu` (v0.28, mendixlabs/mxcli#1522) and in-loop `alter microflow`/`replace body of` (v0.29, ako/mxcli#1693), keeping its describe-first rule; STOP 1b now writes the bare `Editable: <expr>` (v0.25) and limits the quoted-form trap to mxcli < v0.28 (#1567); `learned-mdl-cannot-express.md` marks file upload (`filemanager`, v0.27), tab `Badge:` (v0.29), app title (v0.29), role visibility `Visible: roles(…)` (v0.27) and `sidebartoggle` (v0.28) buildable from their version; four `learned-detection-gaps.md` rows (terminators, `if` without `else` in a handler, `Class` on a snippet call, property sets below 10.24.24) and `platform-link.md` now name the mxcli version that fixed them; three passages no longer assume mxcli auto-creates `<Module>.User` (gone since v0.27, #1474). `mdl-scan` STOP-1 now also catches the bare `Visible:/Editable: <expr>` form inside a customContent column (it matched only `[`), since STOP 1b now recommends that form. Nothing deleted: old-version guidance stays, version-gated, because nothing was re-probed. Baseline 64,193 → 64,252 words. Source: mxcli v0.29 compatibility audit 2026-10-30
2324
- fix(wire-company-brain): **re-wiring a company brain works on macOS, and a rewrite that fails no longer reports "Updated"**: the block was handed to awk as `-v blk=…`, which BSD awk (macOS) refuses when the value holds a newline, so every re-run (a moved brain, a changed block) left the files untouched while printing `Updated: … rewritten in place`; the block now goes through the environment (`ENVIRON["BLK"]`), and a failed rewrite or append prints `FAILED: …`, leaves the file alone and is not counted. Pinned by `tests/wave2/test-company-brain.sh` T2c (a stub awk with BSD's `-v` rule; 4 of its 5 checks fail on 2ba8158) — reported in #598 from a field-run project, macOS 26 / BSD awk 20200816
2425
- fix(doctor, sync-project, speed-setup): **doctor says when the speed fixes never reached a project**: `bin/exec.sh`, `bin/_common.sh` or `bin/lint-gate.sh` without the 2026-10-29 speed code (marker per file) gives a Machine health warning "speed fixes not active", and the Recommended fix leads with the exact `sync-project.sh --upgrade-bin ...` command (exec.sh always with its `_common.sh`; each copy backed up) before any other advice, because every other number measures the old scripts; `sync-project.sh` names the speed fixes on a hand-edited exec.sh / _common.sh that has the exit-code fix but not the speed work; `skills/speed-setup.md` has Claude show each local edit in one line and ask before replacing. Field: an English colleague's ExpenseTracker analysis after the toolkit update still showed a full mxbuild on every change (45.5 min, 57% of all exec time) with 15 drifted scripts in bin/; reproduced here with the real pre-#587 exec.sh / _common.sh / lint-gate.sh — doctor names all three, sync names exec.sh and refreshes the two unedited ones — Maurits (mauritscgvisser)

‎bin/doctor.sh‎

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,18 @@ set -u
8383
# script and the repair inherits exec.sh's snapshot, mxbuild gate and restore.
8484
if [ "${2:-}" = "--property-sets-apply-step" ]; then
8585
{ [ -n "${3:-}" ] && [ -x "$3" ]; } || { echo "--property-sets-apply-step: no runnable mxcli given" >&2; exit 2; }
86-
exec "$3" fix property-sets -p "$1"
86+
# mxcli v0.29.0+ also exits 1 while a MIXED name remains (cmd_fix_property_sets.go: "Exits 1
87+
# when a unit could not be repaired, when a MIXED name remains"), even when every repair it made
88+
# landed. mxcli leaves MIXED units untouched, so that exit is not a failed repair: report 0 and
89+
# let the gate decide, else exec.sh --patch rolls the good repairs back. FAILED still exits 1.
90+
_psa_o="$("$3" fix property-sets -p "$1" 2>&1)"; _psa_r=$?
91+
printf '%s\n' "$_psa_o"
92+
if [ "$_psa_r" -eq 1 ] && printf '%s\n' "$_psa_o" | grep -q '^ MIXED ' \
93+
&& ! printf '%s\n' "$_psa_o" | grep -qE '^ FAILED |could not be normalized'; then
94+
echo "(exit 1 was for the MIXED name(s) above only — mxcli leaves those as they are; the repair stands)"
95+
exit 0
96+
fi
97+
exit "$_psa_r"
8798
fi
8899

89100
TOOLKIT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@@ -201,6 +212,10 @@ probe_runs() {
201212
# What the dry-run prints and how it exits is mxcli's cmd/mxcli/cmd_fix_property_sets.go at
202213
# v0.26.0: exit 0 = nothing to do; exit 1 = a ` NEEDS ` or ` FAILED ` row per unit, OR an
203214
# error such as "project not found" (both exit 1, so the rows decide, never the code alone).
215+
# v0.29.0 adds ` MIXED ` rows (ako/mxcli#1505): a property name some elements of a type carry and
216+
# others lack — Mendix's merge engine cannot compare those revisions, and mxcli leaves them alone
217+
# (no measured value fills them). Exit 1 with only MIXED rows is result "mixed": a merge hazard
218+
# the repair cannot clear, reported, never "could not classify" (captured 2026-10-30).
204219
# Anything else is "could not classify", never clean. Support is read from `mxcli fix --help`
205220
# listing the subcommand: `mxcli fix property-sets --help` exits 0 on v0.24/v0.25 too (cobra
206221
# prints the parent's help for an unknown subcommand), so it cannot be the probe. Golden
@@ -209,7 +224,7 @@ probe_runs() {
209224
# The result is recorded in <project>/.claude/.property-sets-receipt (machine-local, like the
210225
# doctor receipt): line 1 "<date> <time> <result> <detail>", line 2 "mxcli: <--version line>".
211226
# Line 2 is what --if-stale and --quick compare: the check is owed again when mxcli changes.
212-
# Only a settled answer is recorded — clean, unproven, needs, failed, or unsupported (the support probe
227+
# Only a settled answer is recorded — clean, unproven, needs, failed, mixed, or unsupported (the support probe
213228
# ran and the subcommand is absent). An error or a timeout is never written, and --if-stale
214229
# skips the run only on a recorded clean/unproven/unsupported, so a one-off failure (locked model,
215230
# unreadable version, a probe that crashed) is re-checked on the next sync instead of being
@@ -250,19 +265,23 @@ ps_bounded() {
250265
rm -f "$_pb_f"
251266
return "$_pb_rc"
252267
}
253-
# ps_classify <exit> <output> — sets PS_RESULT (clean|needs|failed|error) and PS_DETAIL.
268+
# ps_classify <exit> <output> — sets PS_RESULT (clean|needs|failed|mixed|error) and PS_DETAIL.
254269
ps_classify() {
255270
_psc_checked="$(printf '%s\n' "$2" | grep -m1 -E '^Checked [0-9]+ unit' || true)"
256271
_psc_needs="$(printf '%s\n' "$2" | grep -c '^ NEEDS ' || true)"
257272
_psc_failed="$(printf '%s\n' "$2" | grep -c '^ FAILED ' || true)"
273+
_psc_mixed="$(printf '%s\n' "$2" | grep -c '^ MIXED ' || true)"
274+
_psc_mx=""; [ "$_psc_mixed" -gt 0 ] && _psc_mx=", $_psc_mixed MIXED property name(s) mxcli leaves alone"
258275
if [ "$1" -eq 0 ] && printf '%s\n' "$2" | grep -q 'no readable Mendix version'; then
259276
PS_RESULT=error; PS_DETAIL="mxcli read no Mendix version from the model, so nothing was checked"
260277
elif [ "$1" -eq 0 ]; then
261278
PS_RESULT=clean; PS_DETAIL="${_psc_checked:-exit 0}"
262279
elif [ "$1" -eq 1 ] && [ "$_psc_needs" -gt 0 ]; then
263-
PS_RESULT=needs; PS_DETAIL="$_psc_needs unit(s) need their property set completed, $_psc_failed cannot be normalized"
280+
PS_RESULT=needs; PS_DETAIL="$_psc_needs unit(s) need their property set completed, $_psc_failed cannot be normalized$_psc_mx"
264281
elif [ "$1" -eq 1 ] && [ "$_psc_failed" -gt 0 ]; then
265-
PS_RESULT=failed; PS_DETAIL="$_psc_failed unit(s) hold a value in a key this Mendix version does not declare"
282+
PS_RESULT=failed; PS_DETAIL="$_psc_failed unit(s) hold a value in a key this Mendix version does not declare$_psc_mx"
283+
elif [ "$1" -eq 1 ] && [ "$_psc_mixed" -gt 0 ]; then
284+
PS_RESULT=mixed; PS_DETAIL="$_psc_mixed property name(s) differ between elements of one type (MIXED) — no measured value fills them"
266285
else
267286
PS_RESULT=error
268287
PS_DETAIL="exit $1: $(printf '%s\n' "$2" | grep -v -e '^[[:space:]]*$' -e '^WARNING: This is a' | head -1)"
@@ -289,7 +308,7 @@ ps_unproven_warn() {
289308
note "the proof is Mendix's own: mx diff <base>.mpr <mine>.mpr out.json with this version's mx; exit 0 is clean."
290309
}
291310
ps_write_receipt() {
292-
case "$PS_RESULT" in clean|unproven|needs|failed|unsupported) ;; *) return 0 ;; esac
311+
case "$PS_RESULT" in clean|unproven|needs|failed|mixed|unsupported) ;; *) return 0 ;; esac
293312
[ -n "$PS_VER" ] || return 0
294313
[ -n "$PROJECT_DIR" ] && [ -d "$PROJECT_DIR" ] || return 0
295314
mkdir -p "$PROJECT_DIR/.claude" 2>/dev/null || return 0
@@ -341,14 +360,16 @@ ps_check() {
341360
else
342361
ok "property sets: $PS_DETAIL — every unit has the property names Studio Pro writes"
343362
fi ;;
344-
needs|failed)
363+
needs|failed|mixed)
345364
warn "property sets: $PS_DETAIL — Studio Pro's merge/diff can throw on these"
346-
printf '%s\n' "$PS_OUT" | grep -E '^ (NEEDS|FAILED) ' | head -10 | while IFS= read -r l; do note "$l"; done
347-
_psk_rows="$(printf '%s\n' "$PS_OUT" | grep -cE '^ (NEEDS|FAILED) ' || true)"
365+
printf '%s\n' "$PS_OUT" | grep -E '^ (NEEDS|FAILED|MIXED) ' | head -10 | while IFS= read -r l; do note "$l"; done
366+
_psk_rows="$(printf '%s\n' "$PS_OUT" | grep -cE '^ (NEEDS|FAILED|MIXED) ' || true)"
348367
[ "$_psk_rows" -gt 10 ] && note " … $((_psk_rows - 10)) more"
349368
[ "$PS_RESULT" = needs ] && note "repair (snapshot + mxbuild gate + restore): bin/doctor.sh --fix-property-sets ${PROJECT_DIR:-<project>}"
350369
[ "$(printf '%s\n' "$PS_OUT" | grep -c '^ FAILED ' || true)" -gt 0 ] && \
351-
note "FAILED units are left alone by mxcli and make the guarded repair roll back — resolve them first." ;;
370+
note "FAILED units are left alone by mxcli and make the guarded repair roll back — resolve them first."
371+
[ "$(printf '%s\n' "$PS_OUT" | grep -c '^ MIXED ' || true)" -gt 0 ] && \
372+
note "MIXED names are left alone by mxcli and --fix-property-sets cannot clear them. Report them with your Mendix version (mendixlabs/mxcli asks for exactly that); until a value is measured, a merge touching these units can throw." ;;
352373
*) warn "property sets: could not classify the check — $PS_DETAIL (not recorded; the next run re-checks)"
353374
printf '%s\n' "$PS_OUT" | grep -v '^[[:space:]]*$' | head -5 | while IFS= read -r l; do note " $l"; done ;;
354375
esac
@@ -375,7 +396,7 @@ ps_apply() {
375396
ps_check
376397
}
377398
ps_exit_code() {
378-
case "$PS_RESULT" in clean|unproven) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed) echo 1 ;; *) echo 4 ;; esac
399+
case "$PS_RESULT" in clean|unproven) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed|mixed) echo 1 ;; *) echo 4 ;; esac
379400
}
380401

381402
# --property-sets / --fix-property-sets: only this check, then exit. Stands alone so
@@ -409,7 +430,7 @@ if [ -n "$PS_MODE" ]; then
409430
PS_VER="$(ps_version "$PMXCLI_PROBE")"
410431
if [ -n "$PS_VER" ] && ! ps_stale; then
411432
PS_RESULT="$(sed -n '1s/^[^ ]* [^ ]* \([^ ]*\).*/\1/p' "$(ps_receipt)")"
412-
# Only a settled "nothing to do here" short-circuits; needs/failed re-check every time.
433+
# Only a settled "nothing to do here" short-circuits; needs/failed/mixed re-check every time.
413434
case "$PS_RESULT" in
414435
clean|unsupported)
415436
ok "already checked for $PS_VER: $(sed -n 1p "$(ps_receipt)")"
@@ -425,6 +446,53 @@ if [ -n "$PS_MODE" ]; then
425446
exit "$(ps_exit_code)"
426447
fi
427448

449+
# mxcli_latest_advice <version line> — installed vs the latest mxcli release. Advice only: never a
450+
# FAIL, never blocks, never downloads. A pin ($MXCLI_VERSION) is respected — noted, not nagged.
451+
# Latest is read from the redirect of releases/latest/download/<asset> (one request, not followed,
452+
# 5 s cap); offline or no curl is one quiet note. MXTK_MXCLI_LATEST=vX.Y.Z supplies the
453+
# answer without the network (fixtures, air-gapped machines); MXTK_NO_LATEST_CHECK=1 skips it.
454+
# MXTK_MXCLI_FLOOR is the oldest release this toolkit's instruments are checked against.
455+
MXTK_MXCLI_FLOOR="${MXTK_MXCLI_FLOOR:-v0.28.0}"
456+
mxtk_semver_lt() { # <a> <b> — 0 when vA.B.C < vX.Y.Z; 1 otherwise or unparseable
457+
printf '%s %s\n' "${1#v}" "${2#v}" | awk '{
458+
n = split($1, a, "."); m = split($2, b, ".")
459+
if (n < 3 || m < 3) exit 1
460+
for (i = 1; i <= 3; i++) { if (a[i] + 0 < b[i] + 0) exit 0; if (a[i] + 0 > b[i] + 0) exit 1 }
461+
exit 1 }'
462+
}
463+
mxcli_latest_advice() {
464+
[ "${MXTK_NO_LATEST_CHECK:-0}" = 1 ] && return 0
465+
_ml_have="$(printf '%s\n' "$1" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
466+
[ -n "$_ml_have" ] || return 0
467+
_ml_new="${MXTK_MXCLI_LATEST:-}"
468+
if [ -z "$_ml_new" ] && command -v curl >/dev/null 2>&1; then
469+
# The redirect of the asset URL --install itself uses (not followed: nothing is downloaded;
470+
# -q first, so a ~/.curlrc with `location` cannot make curl follow it and fetch ~100 MB).
471+
# Not releases/latest: a proxy that allows release downloads can refuse that page (403,
472+
# measured in a cloud sandbox 2026-10-30, where this redirect answered v0.29.0).
473+
_ml_new="$(curl -q -sS --max-time 5 -o /dev/null -w '%{redirect_url}' \
474+
https://github.com/mendixlabs/mxcli/releases/latest/download/mxcli-linux-amd64 2>/dev/null \
475+
| sed -n 's|.*/releases/download/\(v[0-9][0-9.]*\)/.*|\1|p')"
476+
fi
477+
if [ -z "$_ml_new" ]; then
478+
note "latest mxcli release: not checked (offline, or no curl) — installed $_ml_have"
479+
return 0
480+
fi
481+
if ! mxtk_semver_lt "$_ml_have" "$_ml_new"; then
482+
ok "mxcli $_ml_have is the latest release"
483+
elif [ -n "${MXCLI_VERSION:-}" ]; then
484+
note "mxcli pinned by MXCLI_VERSION=$MXCLI_VERSION; latest is $_ml_new — move when the team chooses"
485+
else
486+
warn "mxcli $_ml_have is behind the latest release $_ml_new (advice only — nothing is blocked)"
487+
note "what's new: https://github.com/mendixlabs/mxcli/releases/tag/$_ml_new"
488+
note "upgrade: move the old binary aside (mv mxcli mxcli.$_ml_have), then bin/doctor.sh --install ${PROJECT_DIR:-<project>}"
489+
note "to stay on $_ml_have deliberately: set MXCLI_VERSION=$_ml_have and this becomes a note"
490+
fi
491+
mxtk_semver_lt "$_ml_have" "$MXTK_MXCLI_FLOOR" && \
492+
note "this toolkit's instruments are checked against $MXTK_MXCLI_FLOOR and later; $_ml_have output may not parse"
493+
return 0
494+
}
495+
428496
# recommended_fix — ONE block that ends both the normal run and --bench: the first option that works on THIS
429497
# machine, in this order. Read-only, cheap probes; never installs anything and never asks for IT's help.
430498
# 0. MH_STALE (machine_health): the project runs older local copies of the speed scripts -> upgrade them
@@ -1333,10 +1401,10 @@ if [ "$INSTALL" -eq 1 ]; then
13331401
# 2026-08-31): mxcli-{darwin|linux}-{amd64|arm64}, mxcli-windows-amd64.exe. Windows gets
13341402
# the .exe name so Git Bash's transparent foo -> foo.exe mapping serves the project
13351403
# convention ./mxcli unchanged. $MXCLI_VERSION pins a release tag (e.g. v0.16.0) for
1336-
# teams that standardise; default is latest. v0.25.0 froze `mdl 1`: DESCRIBE output and
1337-
# `-c` input changed shape (navigation, DynamicClasses, `show entity` refused). The
1338-
# toolkit's instruments read both shapes, so a project mid-build may stay on its version
1339-
# (MXCLI_VERSION=v0.24.0) and move when it chooses.
1404+
# teams that standardise; default is latest. The toolkit's instruments are checked against
1405+
# $MXTK_MXCLI_FLOOR (v0.28.0) through v0.29.0 (golden captures, 2026-10-30); a project
1406+
# mid-build may stay on its version (MXCLI_VERSION=<its ./mxcli --version>) and move when
1407+
# it chooses. The normal doctor run says when the installed mxcli is behind the latest.
13401408
# Git Bash on Windows ARM64 runs emulated, so `uname -m` says x86_64 there. On Windows the
13411409
# real hardware shows in the `uname -s` suffix (...-ARM64) and in PROCESSOR_ARCHITEW6432
13421410
# (set for emulated processes) / PROCESSOR_ARCHITECTURE; those win over `uname -m`.
@@ -1395,7 +1463,7 @@ if [ "$INSTALL" -eq 1 ]; then
13951463
note " $_STEP. mxcli (~90 MB) -> $PROJECT_DIR/$MXCLI_DEST"
13961464
note " the MDL CLI every session here uses; from $MXCLI_URL"
13971465
[ -z "${MXCLI_VERSION:-}" ] && note " (latest release — set MXCLI_VERSION=vX.Y.Z to pin the team's version;"
1398-
[ -z "${MXCLI_VERSION:-}" ] && note " a project already building on v0.24 can stay there: MXCLI_VERSION=v0.24.0)"
1466+
[ -z "${MXCLI_VERSION:-}" ] && note " a project mid-build can stay on its version: MXCLI_VERSION=<its ./mxcli --version>)"
13991467
fi
14001468
if [ "$NEED_TOOLCHAIN" -eq 1 ]; then
14011469
_STEP=$((_STEP + 1))
@@ -1855,6 +1923,7 @@ if [ -n "$PROJECT_DIR" ]; then
18551923
if [ "$MXCLI_EXIT" -eq 0 ]; then
18561924
ok "mxcli runs ($PROBE_HOW): $(probe_line)"
18571925
note "at: $PMXCLI_PROBE"
1926+
[ "$QUICK" != 1 ] && mxcli_latest_advice "$(probe_line)"
18581927
else
18591928
bad "mxcli exists but cannot run (exit $MXCLI_EXIT): $PMXCLI_PROBE"
18601929
printf '%s\n' "$PROBE_OUT" | grep -v '^[[:space:]]*$' | head -3 | while IFS= read -r l; do note " $l"; done

0 commit comments

Comments
 (0)