Changelog
All notable changes to elm are documented in this file.
The format is based on Keep a Changelog.
Unreleased
Removed
-x/--export(export a query as a standalone Python script). It was a never-finished stub carried over from an older project: the handler referenced anelm.flagsattribute that was never set on the context object, usedjinja2.FileSystemLoader/Environmentwithout importing them, and rendered asave_query.py.j2template that does not exist — so the advertised flag raised an exception on any use. The reproduce-a-request need it was meant to cover is already served by-f api(which prints the exact request URL andAuthorizationheader) plus the standalone PyInstaller binary. Removed from_jnja/elm.py.j2,_jnja/engine.py.j2, and the README options list.
Added
tools/elm-host-sdts.sh— lists the SDTs (scheduled downtime) affecting each host in a list (onedisplayNameper line from a file, from stdin via-, or as positional arguments; hosts are de-duplicated preserving first-seen order, and blank lines dropped). Resolves each host to a device id viaDeviceList, then queriesAllSDTListByDeviceId(/device/devices/{id}/sdts), which performs the SDT inheritance join server-side: it returns device, instance, and inherited group SDTs that actually apply to the device — correctly excluding a group SDT scoped to a datasource/instance the device does not have — so no manualdeviceGroupId/dataSourceIdcross-referencing is needed. Output is one aligned table per host with columnsTYPE(the LMsdtType:oneTime/daily/weekly/monthly/monthlyByWeek),ACTIVE(the LMisEffectivefield,yes/no),GROUP/HOST/INSTANCE(only the scope the SDT applies to is filled, others show-; a group SDT shows its full path plus the limiting datasource in brackets when scoped to one),FROM/TO(startDateTimeOnLocal/endDateTimeOnLocal, rendered in the portal’s timezone with abbreviation — not the local machine’s),DURATION(thedurationfield rendered as e.g.5h,1h30m,45m), andCOMMENT. Rows are sorted active-first then byFROMascending.--activelimits to currently-effective SDTs;--exactswitches host matching from contains (displayName~) to exact (displayName:);-p/--profileselects the portal (defaults toconfig). Requireselm,jq, andcolumn.tools/elm-datasource-matrix.py— builds a device-by-datasource usage matrix as a GitHub Flavored Markdown table (each row is a device — device ID then device name — and each datasource is a column; a cell is a tick✓where the datasource is applied, blank otherwise) for every datasource whose name matches a pattern. Pivots oneAssociatedDeviceListByDataSourceId(/setting/datasources/{id}/devices) call per matching datasource, so the cost is one API call per datasource, not one per device. Matching is case-insensitive by default;-s/--case-sensitive(the same flag as ripgrep) narrows it soNTPmatchesNTPv4/Cisco_NTPbut not incidental substrings likeAccessPoi[ntP]erformanceorOverCurre[ntP]rotectors.-x/--regextreats the pattern as a Python regex (anchor with^/$to match only the start/end of the name, e.g.'^NTP|NTP$'); to stay efficient it derives a literal substring from the pattern (e.g.NTP) to push as the server-sidename~filter and refines with the full regex client-side, so the full datasource list (which can be thousands of entries) is never downloaded. Top-level|alternatives OR several patterns in one run (e.g.'NTP|Ping') — since LM ANDs repeated-Ffilters, each branch becomes its own server-sidename~call and the results are unioned. A size guard aborts before the per-datasource calls if more than--max-colsdatasources match (default 20; each is also one API call) and before rendering if more than--max-rowsdevices would be rows (default 1000, LM’s per-request-s0row cap, beyond which the underlying device lists truncate anyway);0disables a limit. GFM cells are padded so the raw Markdown source lines up too. Real devices only: rows are restricted to actual devices (deviceType0 or 1); LM Services / Service Insight, cloud accounts/resources (AWS, Azure) and Kubernetes resources are excluded (not configurable). Drops datasources with no remaining devices (empty columns) and only lists devices using at least one match.--csvemitsid,device,<datasource…>rows with1/0cells for spreadsheets;-p/--profileselects the portal (defaults toconfig). “Applied” is the live device→datasource association, not the dailyauto.activedatasourcesproperty. Documented intools/README.md(Datasource usage matrix).examples/filtering.md— a dedicated-F/--filterreference: operator table (:,~,!:,!~,>,<,>:,<:),~substring-search behaviour (case-insensitive, spaces OK), combining filters with repeated-F(AND), the no-OR limitation, comma-separated string fields, and the quoting/comma-escape gotchas. Linked fromexamples/README.mdandEXAMPLES.md.
Changed
-
Build pipeline (no change to shipped CLI behaviour): PyInstaller now runs at
--log-level WARN, shrinking build logs from ~5,200 lines ofINFOchatter to the handful of real warnings. The bundled binary is ~20% smaller (119M → 96M) thanks to a post-build prune that drops pandas/numpy’s own test suites (*/pandas/tests,*/numpy/*/tests), which elm never imports. The binary target now lists its real rendered inputs (engine.py,elm.py,_cmds/__init__.py) as prerequisites withreqsdemoted to order-only, so a fullmake && make install && make docsbuilds the binary once instead of three times — previously the phonyreqsprerequisite forced a fresh PyInstaller run on every invocation.--exclude-modulewas deliberately not used to drop the test suites: with--collect-allin play it registers each test submodule as a hidden import that the exclude then removes, emitting thousands of spuriousERROR: Hidden import not foundlines. - README hero reworked to lead with use-cases — a “What can I use it for?” bullet list and an aligned table + CSV example block (replacing the single jsonl one-liner), so the front page answers “what can I use this for?” at a glance. The curl/wget bullet was dropped (niche) and “never” emphasised in the read-only note.
examples/alerts.md— added “Find devices in SDT right now” (SDTList -F isEffective:true) and “Find unacknowledged active alerts” (AlertList -F cleared:false,acked:false), backing the new README use-cases (“missing a datasource” was already indatasources.md).elm-notes.yaml— corrected the-f api/curl auth expiry note: the LMv1 signature window is long and inconsistent across environments (measured valid 3h22m on one sandbox, yet under 2h elsewhere), not “within minutes/seconds”.- Standalone helper scripts in
tools/are now documented in a dedicatedtools/README.mdrather than the mainREADME.md. The API speed test and Datasource usage matrix sections moved there, an entry forelm-host-sdts.shwas added, and the main README now carries a short Development → Tools pointer. These scripts are out of scope of the elm program itself — not part of the CLI, and not built or installed bymake— so keeping them out of the main README keeps it focused on elm.
Fixed
-
The LogicMonitor API request (
requests.getin_jnja/engine.py.j2) now setstimeout=(10, 120)— a 10-second connect timeout and a 120-second read (between-bytes) timeout. Previously the call had no timeout, so if LogicMonitor or an intervening proxy stopped responding, elm would hang indefinitely instead of erroring. A timeout raisesrequests.exceptions.Timeout, a subclass ofrequests.RequestException, so the existing request-error handler reports it as elm’s normal redError: request failedmessage with no new error-handling code. The read timeout is per-gap, not a total-request budget, so a large but steadily-streaming result is still tolerated. -
make installfrom a clean tree failed with[ERROR] venv/bin/jinja2 not found. Theinstalltarget’s prerequisite chain ($(bindir)/elm→ PyInstaller binary → rendered sources →JINJA-exists) never raninit, which is the only target that creates the venv and installs jinja2-cli — somake installonly worked after a priormake.installnow runsinitand then re-invokes make ($(MAKE) _render _build _install), the same patternallandbuilduse; the re-invocation is required because from a clean treeCMDTARGETSis computed beforeinitcreates_defs/, so a plain prerequisite chain would also have built a binary with no command modules. The usualmake && make installflow is unchanged (the second invocation finds everything up to date).
[1.8.9] - 2026-06-11
Added
tools/lm-collector-reachability-run-all.ps1— PowerShell runner that checks reachability across every active collector in a collector group at once (any group with more than one collector — auto-balance or manual) and saves each collector’s result as<hostname>.csvfor diffing between collectors. Self-contained: uses only theLogic.Monitormodule (no elm, bash, jq, or jinja2) and a single existing LM session. Select the group with-idor-group; run with no argument to list collector groups that have more than one collector. Discovers group members (preferredCollectorGroupId), builds the protocol matrix fromautoProperties, generates the Groovy inline, submits via Collector Debug, then polls and writes each result as soon as it is ready (CSVs default to a per-run directory under the system temp). When two or more collectors return, it then prints a built-in cross-collector comparison: for every device and protocol it gathers each collector’s result and lists only the rows where collectors disagree (e.g. onepass, anotherFAIL), scaling to any collector count rather than a single A-vs-B diff. For the two-collector case it also prints a ready-to-rundifftcommand (suggested only for exactly two collectors, sincedifftis pairwise).-Candidate ID|NAME(repeatable) tests one or more collectors that are not in the group against the group’s device list — vetting a freshly built collector before moving it in — and prints a per-candidate verdict listing only the device+protocol combinations the candidate fails to reach but an in-group collector does (devices the whole group already cannot reach are not counted against the candidate). A-Candidateis resolved by collector id, exact hostname/description, or — failing that — an unambiguous partial/substring match (sonewedge03resolves an FQDN-named collector likenewedge03.example.com); a value that cannot be resolved to an active, not-yet-in-group collector aborts the run (before device discovery) rather than silently degrading to a group-only test, listing near-matches when the name was ambiguous. The comparison and candidate-verdict output is colour-coded (greenpass, redFAIL, yellowTIMEOUT; disable with-NoColor, theNO_COLORenv var, or when stdout is redirected) and the verdict rows are column-aligned. Comparison columns are ordered deterministically (incumbent collectors first, sorted by hostname; any candidate on the right) rather than in result-arrival order. Skips devices that are themselves collector hosts (matched via a collector’scollectorDeviceId) and warns when such hosts are found in an auto-balance group;-IncludeDeadalso testshostStatus:deaddevices to reveal relocate candidates. Fails clearly when the LM account lacks Collector Debug permission (a read-only token is denied; Collector Debug needs a Manage-level token).
Changed
- Git pre-commit hook (ToC regeneration) is now a tracked file at
.githooks/pre-commitinstead of being emitted as an escapedprintfstring by themake hookstarget.make hooksnow just runsgit config core.hooksPath .githooks, so the hook is normal, reviewable bash. The hook itself now passes--hide-footertogh-md-toc(dropping the post-hocsedthat stripped theAdded by:footer) and selects staged Markdown via NUL-delimitedgit diff --name-only -z -- '*.md'(handles paths with spaces/newlines). Because the footer lives inside the<!--ts-->/<!--te-->block, existing footers are removed automatically the next time a file is staged. ## metaToC-update commands across the 13 documented Markdown files now include--hide-footer, matching the hook so a manual run no longer re-adds the footer.- Git pre-commit hook now also runs a leak scan: it blocks a commit whose staged added lines match a denylist of sensitive customer/portal tokens. The denylist lives in
.githooks/leak-patterns.local(gitignored, stays local so the tokens are never committed);.githooks/leak-patterns.exampledocuments the format. The scan skips cleanly when no local denylist is present, and a genuine false positive can be bypassed once withLEAK_SCAN_SKIP=1 git commit. -F/--filternow gives an actionable error when a filter clause has no operator, instead of a cryptic usage dump. The most common cause is an unescaped comma in a value (commas separate filter clauses), so the message names the offending clause and points at the\,escape — e.g.-F 'name~Smith, Inc'now reportsfilter clause ' Inc' has no operator … to include a literal comma in a value, escape it as '\,'.
Fixed
-
-F/--filtervalues are now escaped before being wrapped in quotes. Previously the raw value was wrapped directly, so a value ending in a backslash escaped elm’s own closing quote (e.g.-F 'hostname~foo\'producedhostname~"foo\") and an embedded double-quote ended the value early — both yielding a malformed filter and a400 Bad Requestfrom the LM API.validate_filter()now escapes\and"in the value, so you type the literal value and elm handles the quoting. Any prior habit of doubling a trailing backslash to work around this is no longer needed (and now means a literal backslash):-F 'name~foo\\'matches two backslashes. tools/lm-collector-reachability-run-all.ps1: detect “not logged in” up front. Previously the only precondition was that theLogic.Monitormodule was loaded, not that a session was active, so running with no connection spilled the module’s multi-line “ensure you are logged in” error mid-listing followed by a misleading0 of 0 total. It now checksGet-LMAccountStatus(a plain string when logged out, a status object when connected) and, like the module-not-loaded case, fails with a single clean red message and exit code 1 rather than a thrownLine | NN |caret block. Also documented that-Candidatetakes several collectors comma-separated (-Candidate id1,id2), each producing its own verdict block.tools/elm-collector-readiness.sh: device membership filter changed fromautoBalancedCollectorGroupIdtopreferredCollectorGroupId.autoBalancedCollectorGroupIdis only populated for devices LM has already auto-placed, so it returned no devices for groups whose members are assigned but not yet balanced (observed: a group with 2 assigned devices reported 0 to test).
[1.8.8] - 2026-05-29
Added
sqliteoutput format (-f sqlite -o file.sqlite): appends query results to a local SQLite database. Table name is derived from the command name (e.g.DeviceList→device_list). Each row gets afetched_atUTC timestamp (ISO8601) as the first column so freshness can be checked at query time. Nested dict/list values are serialised to JSON strings for storage. Repeated runs append new rows — queryWHERE fetched_at = (SELECT MAX(fetched_at) FROM <table>)for the latest snapshot. Requires-o/--filename; errors clearly if stdout is requested. Uses stdlibsqlite3— no new dependencies.
[1.8.7] - 2026-05-27
Added
valuesoutput format (-f values): emits bare field values with no headers or padding. Single field: one value per line — ideal for shell variable assignment (gid=$(elm -f values DeviceGroupList -f id -F name:Linux)). Multiple fields: tab-separated values with no header row — pipes cleanly intocut,awk, orcolumn. Removes jq as a dependency for simple scalar extraction and multi-step command chaining.
Added
tools/elm-collector-readiness.sh— pre-add collector verification tool. Discovers all devices in an LM auto-balance group, detects protocols fromautoProperties(SNMP, SSH, WMI, HTTP/HTTPS) set by LM Active Discovery, and renders a ready-to-paste Groovy reachability test script to stdout. Supports--id GROUP_IDand--name GROUP_NAME; profile defaults toconfig(same as elm). Devices withhostStatus:deadare skipped;dead-collectordevices are kept (collector is down but device may be reachable from new collector).tools/lm-collector-reachability-check.groovy.j2— Jinja2 template for the LM Collector Debug → Script tab. Device list pre-filled byelm-collector-readiness.sh. Tests ping (InetAddress.isReachable), SNMP (raw UDP 161 probe), and TCP connectivity per device in parallel (one thread per device viaExecutorService); outputs CSV withpass/FAIL/TIMEOUT/blank per protocol column.examples/collector-readiness.md— step-by-step documentation for the collector readiness workflow.- Makefile:
testfmtcontenttarget — asserts each of the 21 output formats actually produces that format (e.g. tsv contains a real tab, json is valid and wrapped in the command-name key, jsonl is valid JSON per line and unwrapped, raw is a Python dict repr, txt has no separator line).testfmtsonly checked exit 0; this catches a format silently producing the wrong structure or being aliased to another. Added to thetestaggregate; connects to LM.
Fixed
tools/elm-collector-readiness.sh: was fetching non-existentcategoriesfield for protocol detection; now usesautoProperties(auto.snmp.operational,auto.network.listening_tcp_ports) which reflects what LM Active Discovery actually measured. AddedhostStatusfield to detect and skip dead devices.tools/elm-collector-readiness.sh: fails clearly with a helpful message when jinja2 is not importable by the selected Python, rather than producing a rawModuleNotFoundErrortraceback.tools/lm-collector-reachability-check.groovy.j2: sequential device testing hit the LM debug console timeout with as few as 6 unreachable devices. Now runs all devices in parallel. Ping switched from spawning an OS process toInetAddress.isReachable(). Default timeouts reduced (ping 3000→1500 ms, TCP 2000→1000 ms, SNMP 3000→2000 ms).tools/lm-collector-reachability-check.groovy.j2: Groovy parsedprintln (list).join(",")asprintln(list)(printing the list object) followed by.join(",")on the void return value, throwingNullPointerException: Cannot invoke method join() on null object. Fixed by assigning to a variable before printing.
Changed
tools/lm-collector-reachability-check.groovy.j2: protocol columns now display purpose-based labels (wmi,ssh,http,https) instead of port numbers; column order is ping → snmp → wmi → ssh → http → https (defined order, not alphabetical — alphabetical sort was silently putting https before http); failure list and footer notes use the same labels; footer adds a note thatwmipass only confirms TCP 135, not the dynamic high ports WMI also requires.tools/elm-collector-readiness.sh: bash summary table now shows the same purpose-based labels (wmi,ssh,http,https) in the Protocols column; protocol order in the device matrix corrected to http before https.tools/elm-collector-readiness.sh,tools/lm-collector-reachability-check.groovy.j2: removed Mode A/B/C code paths — script always generates a native Groovy[...]device list (no JSON string embedding); removes credential injection from the rendered script; simplifies the template from ~230 to ~130 lines. Also fixes a Groovy 65535-character string literal limit that crashed with large groups.tools/elm-collector-readiness.sh,tools/lm-collector-reachability-check.groovy.j2: splittcp-135(detected fromauto.network.listening_tcp_ports) andwmi(detected fromauto.wmi.operational) into separate protocol columns — both test TCP 135 but are triggered by different signals.tools/elm-collector-readiness.sh: addedauto.activedatasourcesas fallback for HTTP/HTTPS detection when ports 80/443 are absent from the TCP port scan (handles devices where datasources are applied without an active port listener).tools/lm-collector-reachability-check.groovy.j2: output changed from fixed-width text table to CSV for diff-friendly comparison between collectors.tools/lm-collector-reachability-check.groovy.j2: non-applicable protocol columns now output blank instead of-for cleaner CSV.tools/lm-collector-reachability-check.groovy.j2: added deviceidcolumn; renamedIP/Hostnametohostname; renamedDevicetodevice.tools/lm-collector-reachability-check.groovy.j2: result values lowercased —pass(wasPASS) soFAILandTIMEOUTstand out in the output.tools/lm-collector-reachability-check.groovy.j2: printsTesting N devices from HOSTNAME (parallel)...at start of output, naming the collector host for easy file labelling when comparing runs.elm-speedtest.shmoved totools/elm-speedtest.sh. All README references updated.requirements.txt: grouped into runtime vs build-time dependencies with comments mirroringsetup.py. Removedpackaging(no longer imported by elm; still installed transitively via pyinstaller). Build-time tools (jinja2-cli,Jinja2,pyinstaller) are now clearly separated from the runtime deps that mirrorsetup.py install_requires.PySocksconfirmed as a genuine runtime import (--proxySOCKS5 support), not an optional extra.setup.py: packaging cleanup. Addedpy_modules=['elm', 'engine', '_version']so the top-level entry-point modules are installed (find_packages()alone only picked up_cmds/and silently omitted them). RemovedJinja2andjinja2-clifrominstall_requires(build-time only — used bymake render, pinned inrequirements.txt) andpackaging(no longer imported anywhere). Removedinclude_package_data=True(noMANIFEST.in, so it did nothing). Bumpedpython_requiresfrom>=3.6to>=3.9(pandas 2.3 requires 3.9+). Added comments documenting the build-time-vs-runtime dependency split.elm-notes.yaml: added full entry forImmediateDeviceListByDeviceGroupId— documents shallow/non-recursive fetch behaviour,customPropertiesall-or-nothing constraint, and a pattern for finding the correct group level to query in hierarchical portal structures.elm-notes.yaml,elm-knowledge.md: documented that-s0and-s1000are equivalent (confirmed by live test: both return 97 groups on a portal with 97 groups).ai.md: consolidated principles #11/#12 (both covered SKILLS_USED.md); added uppercase naming convention for AI-created files (CLAUDE.md, SKILLS_USED.md); added principles #12–16 covering instruction precedence, understand-before-modifying, no parallel abstractions, stop-and-ask conditions, and maintainability over speed; added “Verify external APIs” section; strengthened “Working with code” with scope discipline rule..gitignore: addedSKILLS_USED.md(private skills log, not for the repo).- README restructured:
## Developmentis now a top-level section (was### Developmentunder Installation); Quick code testing loop and API speed test moved under Development; AdminById help moved under Usage; Installation now contains only install steps. elm-notes.yaml: expandedCollectorListwithbackupAgentId,enableFailBack,calculatedThreshold,numberOfWebsites,nextUpgradeInfo, correctedstatusandcollectorSizenotes, added gotchas and patterns.elm-notes.yaml: expandedCollectorGroupListwithpropertyForBalancing,mismatchVersion, and a detailedauto_balance_explainedblock covering the device-sideautoBalancedCollectorGroupIdfield, single-collector group intent, and over-capacity limits.elm-notes.yaml: expandedDeviceListwithpreferredCollectorGroupId,preferredCollectorId, and clarifiedautoBalancedCollectorGroupId(0 = pinned, non-zero = in auto-balance pool).elm-notes.yaml: expandedCollectorByIdwithbackupAgentId,enableFailBack,calculatedThreshold, all conf fields (collectorConf,wrapperConf,sbproxyConf,watchdogConf,websiteConf,agentConfFields,confVersion,userChangeOn), and gotchas documenting that all conf fields are read-only in the API, portal UI config pushes do not populate them, andconfVersionis a heartbeat tick not a config-change indicator.examples/collectors.md: added collector health report section (all-collectors overview, DOWN with hosts, no-backup single points of failure, backup pair health); added auto-balance section (explanation, single-collector groups, mismatch groups); restructured build version section.- README: features bullet updated to “more than 20 formats” (avoids hardcoding a count); curl/wget moved out of format list and into a dedicated feature bullet; first-person removed from Development section; collectors example description updated; dead link reference with typo removed.
- Makefile: removed
backtarget and associatedbakdir/TAR/TARFLAGSvariables; superseded by git. - Makefile: copyright year bumped to 2026;
AWK-existsprerequisite check added;-existsrules aligned;# BACKUPsection renamed to# CLEANUP; directory creation rule simplified (removed redundantchown/chmod);helptarget uses$(AWK)variable. _jnja/elm.py.j2: copyright year bumped to 2026.- README: removed
tarfrom pre-requisites; simplified Install in PATH section.
[1.8.6] - 2026-05-21
Added
- Unknown field warning: when
-fincludes a field not returned by the API, a warning is printed listing the missing field(s) with correct singular/plural (Warning: unknown field: foo/Warning: unknown fields: foo, bar). - Both follow-on warnings suppressed when all requested fields are invalid and
output()has already reportedError: no valid fields selected.
Changed
ai.md: added principle #10 — prefer simple, readable code over clever solutions; added matching bullet to “Working with code” behaviour rules.ai.md: verification section now explicitly names hallucinated library APIs as the failure mode that “run the code” is defending against.ai.md: added “Sensitive data in AI sessions” section — covers sanitizing examples before pasting, never pasting credentials, and documenting project-level placeholder conventions.ai.md: added “Security review of AI-generated code” section — covers vulnerability patterns to check (injection, hardcoded secrets, insecure defaults, dependency vetting) and establishes a regular review cadence, not just point-of-generation checks.ai.md: principle 6 (isolated sessions) now names the mechanism — context window degradation — not just the symptoms.ai.md: “Working with code” now includes a note on copyright/IP — avoid reproducing verbatim patterns from known licensed sources.ai.md: “Scope of authorisation” now instructs the AI to explain suggested shell commands before the user runs them and flag anything destructive.- Size limit warning reworded to
Warning: results truncated by size limit. - Unknown total warning reworded to
Warning: total unknown, results may be truncated. elm-notes.yaml: addedappliesTofilter and active/inactive check patterns toDatasourceList; noted that/* */comments are common disable mechanism and Python is more reliable than jq for stripping them.
[1.8.5] - 2026-05-18
Added
--aiflag — prints a quick-start guide for AI assistants and exits. Covers command structure, key flags, filter operators, output formats, and response wrapping; points toelm-notes.yaml,elm-knowledge.md, andexamples/in the repo for deeper reference. Loads without credentials, same as--versionand--list.elm --helpdescription now includes “AI assistants: run ‘elm –ai’ for a quick-start guide” so a cold-start AI running--helpfinds it immediately.
[1.8.4] - 2026-05-15
Added
-has a short form for--helpat the global level.
[1.8.3] - 2026-05-15
Added
curloutput format — prints a ready-to-runcurl -H "Authorization: ..."command for copy-paste use. Makes the API request but outputs the command instead of data.wgetoutput format — prints a ready-to-runwget -O - --header="Authorization: ..."command for copy-paste use.-O -sends output to stdout. Same caveats ascurlandapiformats: HMAC signature is time-limited and contains credentials.
[1.8.2] - 2026-05-15
Added
- Verbose mode (
-v) now showsElapsed time: Xsfor each API request, usingresponse.elapsed(server + transfer time only, excludes elm processing overhead).
Fixed
- Verbose log message capitalisation:
Status code,Elapsed time,Total recordsnow use consistent sentence case.
[1.8.1] - 2026-05-15
Added
elm-speedtest.sh(nowtools/elm-speedtest.sh) — times LM API response per credential profile across configurable endpoints. Runs each endpoint N times and reports averages. Credentials kept in memory only (never written to disk). Automatically skipsconfigif any other profile has identical credentials. Shows short hostname at top for easy sharing. Column widths adapt to endpoint name length. Usage:tools/elm-speedtest.sh(defaults: AdminList, DeviceList, AuditLogList) ortools/elm-speedtest.sh ReportList DeviceGroupList WebsiteList.
Fixed
-C/--totalnow shows>Nwith a warning when the LM API returns a negative sentinel instead of an exact count. Previously printed the raw negative number (e.g.-51). Affected endpoints:AlertList,AuditLogList. All other list endpoints return a real total and are unchanged. Use-c -s0as a workaround to count all fetched records (accurate when total ≤ 1000).- Size-limit warning (“there is data you are not seeing”) now fires correctly
for endpoints that return the LM negative total sentinel. Previously the
obj['total'] > flags['size']check was alwaysFalsefor negative totals, silently suppressing the warning.
[1.8.0] - 2026-05-14
Changed
- Startup time: elm now uses lazy command loading (
LazyGroup). Command modules in_cmds/are imported only when a subcommand is actually invoked, not at startup.--version,--help,--list, and tab-completion all run without loading any subcommand module. Cold-start time dropped from several seconds to ~0.2 s on the compiled binary. - Deferred heavy imports:
pandas,tabulate,htmlmin,pygments, andrequestsare now imported insideengine()andoutput()rather than at module load time. This is the other half of the startup speedup; the imports only happen when an API call is actually made. _cmds/__init__.pyis now generated bymake render(touchin the Makefile). This makes_cmdsa proper Python package so PyInstaller’s--collect-all=_cmdscan enumerate and bundle it correctly.- PyInstaller build flag
--collect-all=_cmdsadded to ensure the compiled binary bundles all lazily-loaded command modules. --profilehelp text now uses the static path~/.config/logicmonitor/credentials/<NAME>.inirather than the runtime- expanded_creds_dir, preventing the real username from leaking into generated documentation.- Default config file path is now shown prominently in the description block
of
elm --help(right after the one-liner), using~rather than the expanded home directory. Removed the epilog, which was buried after the full command list and exposed the real username.
Fixed
make testbasicnow tests every subcommand with--helpto verify the lazy-loading mechanism works for all commands.
[1.7.10] - 2026-05-14
Fixed
- Multiple
-F/--filterflags now all apply correctly. Previously, passing-F field1:val1 -F field2:val2silently dropped all but the last filter — only the last one was sent to the LM API, with no error or warning. Fixed by addingmultiple=Trueto the filter option and handling the resulting tuple invalidate_filter. Comma-separated filters in a single-Fcontinue to work unchanged. Closes #49.
Documentation
- Documented
-i/--access_idand-k/--access_keyglobal flags inelm-notes.yaml. These override the config file values; LM logs the suppliedaccess_idverbatim as theusernamefield inAuditLogList. Confirmed by live test. - Documented LM API behaviour:
AuditLogListentries withusername: "(update)"are not redacted or substituted — LM logs the rawaccess_idas the username field. The string(update)is the literal credential value configured in the integration making those calls. Confirmed by live test. Documented inCLAUDE.md,elm-knowledge.md, andelm-notes.yaml. - Documented LM API bug:
!:(not-equals) and!~(not-contains) filter operators are silently ignored or misapplied on several endpoints (AuditLogList username,AlertList cleared). elm sends the correct URL-encoded filter — the bug is upstream. Workaround: use positive operators and filter client-side with jq. Tracked as upstream bug #48.
[1.7.9] - 2026-05-14
Added
-Vshort form for--version.elm --list/elm -llists available credential profiles from the credentials directory and exits. The active profile is marked with*on the left; inactive profiles are indented to align. Works without valid credentials (eager flag, exits before auth check). Correctly reflects--profile NAMEwhen combined:elm --profile preprod --listmarkspreprod.config.example.iniis excluded from the listing.elm-knowledge.md— team-facing reference covering CLI patterns, common gotchas, alert patterns, portal overview, and time-series data access. Sanitized for public repo use.
Changed
- Credential profile convention documented:
config.iniis the safe default (sandbox/test); non-default environments use explicit names (preprod.ini,prod.ini) and require--profile. elm-notes.yamlgains a_global.flagsblock documenting the five key global options (--list,--profile,--config,--format,--size).CLAUDE.mdupdated with credential profile workflow usingelm --list.
[1.7.8] - 2026-05-14
Fixed
elm-completion.bashused_ELM_COMPLETE=bash_complete(Click 8 style); Click 7.x requires_ELM_COMPLETE=complete. Completion was silently broken.- Click 7’s completion lowercased all command names (
devicelist) but elm commands are CamelCase (DeviceList) — the completed names didn’t work. Fixed with a hybrid completion: static CamelCase list for command name position, dynamic Click completion for flags and values. - Click 7.1.2 template bug generated
_elm_completionetupinstead of_elm_completion_setup. Corrected in the template. - No Makefile target installed the completion file anywhere. Added
make completionwhich installs to$XDG_DATA_HOME/bash-completion/completions/elm(default~/.local/share/bash-completion/completions/elm).make installnow depends onmake completion.
Changed
elm-completion.bashis now a generated file rendered from_jnja/elm-completion.bash.j2bymake render. Removed from git tracking; added to.gitignore.
[1.7.7] - 2026-05-14
Added
tsvoutput format: true tab-separated values (\tdelimiter). Distinct fromtab, which is tabulate’s human-readable aligned table format. Supports-H(hide headers) and-I(show index) likecsv.jsonloutput format: JSON Lines — one JSON object per line, no command-name wrapper. Directly readable by DuckDB, jq, and most analytics tools without preprocessing.
Changed
--formathelp text now usesmetavar='FORMAT'with a readable list instead of the full[csv|html|...]choice string, which was overflowing the terminal line width.
[1.7.6] - 2026-05-13
Added
--fields/-finjected universally on all subcommands (was missing from the LM swagger but accepted by the API). Mirrors the existing--sortinjection.--size/-s,--offset/-o, and--filter/-Fadded to 11 list subcommands where the LM swagger omits them:DeviceEventsourceList,DiagnosticSourcesList,JobMonitorList,LogAlertGroupsList,LogQueryGroupList,LogSourceList,OIDList,RemediationSourcesList,RetentionList,TopologySourceList,TrackedQueryGroupList. Also addsfields/size/offset/filtertoIntegrationList. Tracked as upstream swagger gaps in #47.
Fixed
--sortinjection in_jnja/command.py.j2was always active (comparing a string against a list of dicts is alwaysFalse). Fixed to useopt_names, a proper list of option name strings.
[1.7.5] - 2026-05-13
Added
-oshort form for--offseton all subcommands. Mirrors-sfor--size.
[1.7.4] - 2026-05-13
Changed
--profilesimplified to a pure path resolver: resolvesNAMEto~/.config/logicmonitor/credentials/<NAME>.iniand delegates everything else to the existing--configlogic. No separate existence check or override warning — behaviour is now fully consistent with--config.- Credentials error message changed from “Default config file:” to “Config file:”
and now shows the actual file in use (
_resolved_config_file) rather than the compiled-in default.
[1.7.3] - 2026-05-13
Added
-p/--profile NAMEglobal option: shorthand for--config ~/.config/logicmonitor/credentials/<NAME>.ini. Strips a trailing.iniif supplied.--configoverrides--profileif both are given. Closes #44.
Fixed
--profile foo.inino longer produces a double-extension path (foo.ini.ini); any.inisuffix on the profile name is stripped before resolving.
[1.7.2] - 2026-05-05
Security
- Config directory and file permissions are now enforced on every run.
elm will warn and auto-fix if the credentials directory is not
700or the config file is not600. If the fix fails, elm aborts. Closes #20.
[1.7.1] - 2026-05-04
Fixed
-H/--noheaderflag (renamed from--noheaders) now correctly hides column headers incsv,html,prettyhtml, andlatexformats. The pandasheader=parameter has opposite polarity to the flag, so the value was being passed inverted — headers showed when-Hwas given and were hidden when it was not. Fixed by passingnot noheader. Tabulate-based formats (txt,jira,gfm,md,pipe,rst,tab) were unaffected.
1.7.0 - 2026-05-04
Added
truststoreintegration: system trust store (macOS Keychain, Windows cert store) used automatically for SSL verification, so corporate networks with TLS inspection work without manual certificate configuration--cacert PATHCLI option to specify an explicit CA bundle for SSL verificationmake docstarget: injects liveelm --helpoutput into README.md between marker comments, replacing$HOMEso no personal paths appear in the repoCHANGELOG.md(this file)
Fixed
UnboundLocalErrorwhen a request fails (e.g. SSL error):response.json()was called unconditionally after the try/except block even whenresponsewas never assigned — addedreturnat end of except blocksetup.pyversion pins for lxml, Pygments, and requests were stale and conflicted with the versions bumped in 1.6.0makehanging at parse time on a clean checkout:NONREQTARGETS :=forced immediate evaluation ofREQSOURCES, which rangrepwith no file arguments (hanging on stdin) when_defs/did not yet exist — changed to=(lazy)- Unterminated
$(grep ...)make variable reference ininitrecipe (introduced Oct 2025) causingmaketo hang when parsing the Makefile
1.6.0 - 2026-05-04
Added
make hookstarget to install git pre-commit hook that auto-updates the README table of contents whenREADME.mdis staged
Changed
- Default config directory changed from
~/.elmto~/.config/logicmonitor/credentials
Fixed
REQSOURCESlazy evaluation bug:$$with:=caused closing)to attach to last filename, misclassifyingWidgetListByDashboardIdas not requiring argumentsmake testnow correctly uses the built binary viatestbinvariable
Security
- lxml 5.2.1 → 6.1 — XXE (XML External Entity) injection vulnerability (HIGH). Affected any code parsing untrusted XML.
- Pygments 2.15.0 → 2.20 — ReDoS (Regular Expression Denial of Service) vulnerability (LOW).
- requests 2.32.0 → 2.33 — Insecure temporary file reuse (MEDIUM).
- pandas and pyinstaller bumped for Python 3.14 compatibility.
1.5.0 - 2025-10-17
Added
- Support for
AllLogPartitionscommand - Undocumented API calls via extended swagger spec (
swagger.undocumented.json), includingCompanySetting prettyxml,gfm(GitHub Flavored Markdown), andpipeoutput formats
Changed
- Shell completion file renamed
- Makefile refactored to handle new commands and edge cases
1.4.0 - 2025-03-20
Added
- Support for undocumented LogicMonitor API endpoints via
swagger.undocumented.json
1.3.0 - 2024-11-12
Added
- Colour-coded output in Makefile
Changed
- Improved API error handling with specific HTTP status code messages (400, 401, 403, 404, 429, 500, 503)
- Better debug messages throughout engine
Fixed
- Exit with non-zero status code when HTTP response is not 200
- Switched from deprecated
htmlmintohtmlmin2
1.2.3 - 2024-09-26
Fixed
- Workaround for unescaped pipes bug in tabulate output affecting jira/gfm/pipe formats (python-tabulate#241)
- Compact XML output; escape quotes in swagger description parsing
1.2.2 - 2024-06-20
Changed
- Updated pandas and PyInstaller to latest versions
1.2.1 - 2024-06-17
Security
- requests 2.31.0 → 2.32.0 —
CVE-2024-35195: proxy
credentials leaked via HTTP redirect when using a SOCKS5 proxy (MEDIUM). elm’s
--proxyflag uses SOCKS5, making this directly applicable.
1.2.0 - 2024-04-26
Changed
jinja2-clinow installed and run from within the venv (previously required a global install)
1.1.0 - 2024-04-15
Added
- XML output format
- Moved build system to venv + PyInstaller for self-contained binary distribution
- Censored access_id and access_key in debug output
1.0.6 - 2024-02-07
Added
- API error code documentation in
ERRORS.md
Fixed
- Comma-separated filters now correctly handle escaped commas (issue #36)
1.0.5 - 2024-02-01
Changed
- Updated copyright year and README for 2024
[Older versions]
Versions 1.0.1–1.0.4, 1.0.0, and pre-1.0 (0.9.x) covered initial development: shell completion (#5), jira/markdown/rst/tab output formats (#11, #13, #15), file output (#9), filter validation (#18, #3), HTML output, SOCKS5 proxy support, v2/v3 API support, and the initial release.