PCLI2

Releases

2.0.1 - 2026-09-06

v2.0.1 Sep 6 2026 at 04:37 UTC

Documentation

  • The command reference and alias tables now cover every command - Every pcli2 ... example in the README and the documentation site was checked against the binary's own help: all commands and flags exist. Twelve commands were missing from the README reference (asset similarity, config get path, config validate, user list, user get, cache clear, doctor and the environment commands), the alias tables lacked a dozen aliases, the feature lists still described resume as downloads-only and omitted the scripting flags and diagnostics, and pcli2 config get (a command group) was shown where config get path was meant. No code changes.

2.0.0 - 2026-09-05

v2.0.0 Sep 5 2026 at 22:44 UTC

Removed

  • The old spellings of the input and output flags - --file (on asset create and config import), --files, --csv-file, --local-path, --file on asset thumbnail, and the bare positional output path on asset download were kept as hidden aliases by 1.27. They are gone. Each is still recognised so a script that uses one stops with exit 64 and a message naming the replacement (--input, or -o/--output), rather than clap's "unexpected argument". The README has the full mapping under "Upgrading to 2.0". This is the only change since 1.29.0, and the reason for the major version.

1.29.0 - 2026-09-05

v1.29.0 Sep 5 2026 at 19:53 UTC

Added

  • Contract tests against Physna's OpenAPI specification - A snapshot of the spec now lives in tests/fixtures/physna-openapi.json. For each of the 22 endpoints the client reads, a response body is generated from the spec's schema (once with only the required properties, once with all of them) and must deserialize into the model type the client uses, so a field Physna renames, drops or retypes fails in CI instead of as a deserialization error in a user's terminal. The asset states, metadata field types, dependency statuses and tenant roles the code hard-codes are compared with the spec's enumerations, the page sizes the client sends are checked against the spec's maximums, and every URL the client builds must exist. A weekly spec-drift workflow fetches the live spec and fails when any endpoint or schema the client depends on has changed, with the diff in the log and instructions for refreshing the snapshot.

Fixed

  • Two things the contract tests found on their first run - A tenant without a display or short name (both optional in the spec) failed the whole tenant listing; both now default to empty. An asset listing without pageData (optional in the spec, absent in cursor mode) was rejected; it now reads as a single page.

1.28.0 - 2026-09-05

v1.28.0 Sep 5 2026 at 18:20 UTC

Added

  • --safe-csv (global, or PCLI2_SAFE_CSV=1) - Guards every CSV cell this process writes against spreadsheet formula injection: a text cell starting with =, +, -, @, tab or carriage return is prefixed with a single quote, which Excel, LibreOffice and Google Sheets show as text. Numbers such as -5 are left alone. Off by default because the quote is visible to other consumers of the file. Excel workbooks were already safe.

Fixed

  • PCLI2_NO_COLOR=0 disabled colour in one place and enabled it in another - The terminal module treated any non-empty value as "disable" while clap parsed it as false. Both now follow one rule for pcli2 boolean variables: empty, 0, false, no and off mean off, anything else means on. NO_COLOR keeps the no-color.org rule (any non-empty value disables). The same rule is pinned on PCLI2_NO_INPUT and PCLI2_SAFE_CSV.
  • --no-color given as a value disabled colour - The terminal module scanned the raw command line, so text-match --text --no-color turned colour off. After parsing, the parsed flag is used; the raw scan only decides how clap colours its own help and error output before parsing.

1.27.0 - 2026-09-05

v1.27.0 Sep 5 2026 at 17:29 UTC

Changed

  • One name per concept: -i/--input and -o/--output - Every command that reads a local file, directory or glob now takes --input: asset create (was --file), asset create-batch (was --files), asset metadata create-batch (was --csv-file), folder upload (was --local-path), config import (was --file). Every command that writes takes -o/--output: asset download (was a bare positional argument), asset thumbnail (was --file), folder download and folder thumbnails (gained the -o short form). The old spellings still work as hidden aliases, and asset download still accepts the positional path, so no script breaks; only the help text and documentation changed.
  • -q is now --quiet, and --force has no short form - -f meant --format on about forty commands but --force on folder delete; -q was --text on text-match while --quiet had no short form. folder delete -f and text-match -q no longer parse; use --force and --text. These are the only two breaking changes in this release.
  • A flag that can change nothing now says so - --pretty with CSV, --headers with JSON or tree, --metadata on asset similarity, and format flags on tenant use used to be accepted in silence. They still are, but with a warning naming the flag and why it has no effect.

1.26.0 - 2026-09-05

v1.26.0 Sep 5 2026 at 16:52 UTC

Added

  • --error-format json (global, or PCLI2_ERROR_FORMAT=json) - Everything on stderr becomes one JSON object per line: errors with their hint and, for API errors, the HTTP status; warnings and log lines; the --stats summary. The final error object carries the process exit code and its class (usage, not_found, auth, api, ...), so a script reads one line instead of parsing prose. Usage errors clap reports before the command runs are JSON too.
  • --no-input (global, or PCLI2_NO_INPUT=1) - Never prompt. A confirmation that would otherwise be asked fails with exit 64 and names --yes; tenant use and env use without --name fail instead of opening a menu; auth login falls back to its missing-argument error. The same now happens without the flag whenever stdin is not a terminal.

Fixed

  • cache clear without a terminal exited 0 as "cancelled" - It read end-of-file as "no" and reported success to the calling script. It now refuses with exit 64 and asks for --yes. folder delete, asset delete and the environment commands already refused; all five prompts now go through one function and give the same message.

1.25.0 - 2026-09-05

v1.25.0 Sep 5 2026 at 16:10 UTC

Added

  • --checkpoint FILE on folder geometric-match, part-match and visual-match - Each asset's completed search is appended to FILE the moment it finishes, so an interrupted multi-hour run is no longer lost. Re-running the same command with the same file reuses the recorded results and searches only the remaining assets; stderr says how many were reused. The file is fingerprinted with the search type, tenant, folders, threshold, --recursive, --exclusive and --limit, and a file from a different run is refused instead of being mixed in. Only successful searches are recorded, so a run that stopped on expired credentials finishes after pcli2 auth login. The file is deleted once the report has been written; it stays if the report fails, so a too-tall Excel workbook can be re-rendered as CSV without searching again.
  • asset create-batch --skip-existing - Skips files whose name is already in the target folder, the same way folder upload --skip-existing does, so an interrupted batch upload can be re-run without creating duplicates.
  • Token renewal is now covered by tests against a mock API and auth server - A 401 renews once and the retry carries the new token; a burst of eight concurrent 401s costs one renewal; a client with credentials but no token authenticates before its first request; a rejected credential reports the auth server's reason and keeps the old token; and without credentials no renewal is attempted.
  • Every formatter is checked for output shape - Each printable type is formatted in every output format it supports and checked for the properties scripts rely on: CSV parses with no ragged rows, --headers adds exactly one line, nothing ends with a line break, compact JSON is one line, and an unsupported format is a clean error. This is what found the three fixes below.

Fixed

  • Four tree outputs printed a trailing empty line - tenant list, tenant metadata list, asset health and asset dependency-diff in --format tree ended with a line break that println! doubled.
  • Four CSV outputs had no final line break at all - auth token --format csv, asset metadata --format csv, asset visual-match --format csv and asset text-match --format csv were printed with print! after the 1.23.2 change made CSV text stop carrying its own line break, so the last line ran into the shell prompt and wc -l under-counted by one.
  • An empty listing in CSV without headers printed one blank line - Every command now prints formatted output through one helper that prints nothing for an empty result, so asset list --folder-path /Empty --format csv | wc -l is 0.
  • Text-match CSV rows had six fields under an eight-column header - The TextMatchPair formatter wrote rows by hand that did not match its own header; the CSV writer rejected the combination outright. Rows now come from the same producer as the header.
  • The credentials file ignored PCLI2_CONFIG_DIR - The documentation has said the variable holds both config.yml and the credentials file; only config.yml followed it, so a second profile shared the default profile's login. Both now live in the directory the variable names. Anyone who set PCLI2_CONFIG_DIR and logged in before this release will need to log in once more.

1.24.0 - 2026-09-04

v1.24.0 Sep 5 2026 at 03:43 UTC

Added

  • pcli2 doctor - One screen with everything support would otherwise ask for: which binary is running and whether others are on the PATH, whether the configuration loads, the active environment and its URLs, where credentials are stored (with the file's permissions), whether a usable token exists and when it expires, the active tenant, cache ages, whether the API and the auth server answer and how fast, and whether a newer release exists. --format json for scripts. Exits 78 for a local problem, 68 when a server is unreachable, 0 otherwise.
  • --stats on every command prints one line on stderr at exit with the number of API requests, retries and token renewals and the elapsed time, so a run that hammers the server is visible to the person running it. The same line always goes to the debug log.

1.23.3 - 2026-09-04

v1.23.3 Sep 5 2026 at 02:37 UTC

Documentation

  • Homebrew 6 requires a one-time brew trust jchultarsky101/pcli2 - Homebrew 6.0 refuses to load formulae from any tap that has not been trusted, so brew install and brew upgrade pcli2 stop with "Refusing to load formula ... from untrusted tap" until the tap is trusted once per machine and user. The README and install guide now say so, including the HOMEBREW_NO_REQUIRE_TAP_TRUST=1 escape hatch for CI. The installers, MSI and pcli2-update are unaffected.

1.23.2 - 2026-09-04

v1.23.2 Sep 4 2026 at 23:59 UTC

Fixed

  • asset list --format csv (and asset, dependency and tenant-status CSV) no longer ends with an empty line - The 1.23.1 fix missed the formatters that name their buffer differently, which included the one every asset listing uses; asset list --folder-path X --format csv | wc -l still over-counted by one. Every CSV and tree formatter now goes through the same trimming, and the two environment commands that printed without a final line break gained one. Checked at the byte level for thirty command and format combinations against a live tenant: each ends in exactly one line break and has no empty lines.

1.23.1 - 2026-09-04

v1.23.1 Sep 4 2026 at 22:53 UTC

Fixed

  • CSV output no longer ends with an empty line - Every CSV-producing command (asset, folder, tenant, user, metadata, environment and configuration listings, match and dependency reports) terminated its output with two line breaks, so asset list --format csv | wc -l reported one row more than there were assets and a spreadsheet opened the file with an empty last row. Verified across every listing command against a live tenant: line counts now equal row counts, and no command prints anything before or after its data.

1.23.0 - 2026-09-04

v1.23.0 Sep 4 2026 at 22:06 UTC

Changed

  • Listings use the API's maximum page size - Folders, folder contents, assets, metadata fields, dependencies and users were fetched 200 at a time (searches 100) although the API accepts 1000 per page; a 10,000-folder tenant is now 10 requests instead of 50, and a folder of 20,000 assets 20 instead of 100. Search pages are 500.
  • --exclusive is filtered by the server - Folder matches with --exclusive sent every search tenant-wide and discarded the out-of-scope rows client-side, which on a small folder in a large tenant meant downloading pages of results only to throw them away. The resolved folder UUIDs are now passed as the search's folderIds filter (which the API applies to the folders and their subfolders); the client-side check still decides the exact set, so output is unchanged.
  • folder delete --force help says what it does - The server deletes a folder recursively; --force skips pcli2's own "not empty" refusal.

1.22.1 - 2026-09-04

v1.22.1 Sep 4 2026 at 21:53 UTC

Fixed

  • A cycle in the server's folder data can no longer hang pcli2 - Every walk over the folder hierarchy (path building, folder list --format tree, subtree filtering) now remembers where it has been, so a folder whose parent chain loops back on itself (or lists itself as its parent) ends the walk with a warning instead of looping forever or overflowing the stack. The "did you mean" suggestions build the path of every folder, so one bad record used to hang every not-found message.

1.22.0 - 2026-09-04

v1.22.0 Sep 4 2026 at 21:15 UTC

Fixed

  • The new-version check no longer delays exit - It ran after the command finished and could hold the prompt for up to three seconds on a stale cache. It now starts before the command and is only reported at exit; a lookup still in flight is abandoned rather than waited for.
  • Match percentages are formatted the same way in every CSV - Single-asset match CSV printed 100.0 where the folder report and the --metadata CSV printed 100.
  • folder visual-match says when an asset hit --limit - The per-asset cap (default 100) truncated silently in folder mode; text match already warned.
  • folder create prints the new folder's UUID - It printed nothing, so a script had to resolve the path it had just created.

Changed

  • Root --help (and the man page) list every environment variable pcli2 reads; the man page previously mentioned only PCLI2_NO_COLOR.
  • Folder match commands document their row order in --help: pair order for CSV and JSON, match percentage for Excel.
  • Help text: "Folder UUID" instead of "Resource's folder UUID"; the --output help on folder geometric-match no longer contradicts itself.

1.21.0 - 2026-09-04

v1.21.0 Sep 4 2026 at 20:44 UTC

Fixed

  • The active tenant follows the environment - One tenant selection was shared by every environment, so env use staging left production's tenant UUID active and every command failed with "Tenant not found" until tenant use was run again. Each environment now remembers its own tenant (older versions still read the top-level value). Folder and metadata caches are keyed by environment too, so a staging tenant cloned from production with the same UUID can no longer be served production's folder tree.

  • Per-asset failure messages survive --progress - During a folder match the reasons for individual failures, and the "stopping after repeated authentication failures" explanation, were written underneath the redrawing progress display and painted over (the same defect fixed for the scan spinner in 1.18.3). They are now printed with the display lifted.

  • env reset, env remove and overwriting with env add ask first - env reset wiped every environment with no prompt; env add silently replaced an existing one. All three honour the global --yes, and refuse rather than assume when no prompt can be shown.

  • env add rejects a URL that is not one - --api-url 'not a url' was saved and surfaced later as an opaque request error.

  • env remove --name nope says the environment does not exist (exit 67) instead of "missing value for property".

  • --format JSON works - The value is matched case-insensitively, as PCLI2_FORMAT already was.

  • asset list --folder-uuid - Every other folder command accepted a UUID; this one only took a path.

  • user list shows its ls alias in help like every other list command.

  • --verbose shows pcli2's own debug output without the HTTP stack's; RUST_LOG still opens everything.

  • Folder trees and lists sort case-insensitively ("alpha" before "Zeta").

  • Configuration and formatting errors show the cause as a sentence rather than a Rust debug dump.

  • asset delete and folder delete work again - The request-path rewrite in this release cycle sent the relative API path as the whole URL for DELETE; caught before release, with a regression test.

  • asset metadata create-batch fetches the field registry once and lists each parent folder once - Both happened per row: a 5,000-row batch into a 10,000-asset folder was about a quarter of a million requests. A registry fetch that fails is now an error rather than "no fields exist", which used to turn every value into a new text field and report the API's rejection as a type conflict in the user's CSV.

  • Cache files are written atomically and carry a schema version - Two pcli2 processes (a script under xargs -P) could read a half-written cache and refetch the whole hierarchy; a cache written by another version is now discarded instead of decoded wrongly. A cache timestamp from the future no longer panics a debug build.

  • Asset listing stops if the server echoes a stale page number - The page loop had no guard against being asked for the same page forever.

  • Listings that hit the safety page cap say so - The truncation of very large user and asset listings was logged at debug level only.

  • --files a,b,c rejects a path that does not exist - It was dropped silently, so a typo uploaded one file fewer. Directories matched by a glob are skipped and counted instead of becoming per-file failures.

  • asset metadata create --type number no longer saturates large values - 1e20 was stored as 9223372036854775807. --type boolean with a value that is not a boolean is sent as-is so the API rejects it, instead of silently writing false.

  • asset metadata get prints {} (or CSV headers) for an asset without metadata - stdout used to be empty for a successful command, and JSON output was wrapped in an internal {"meta": ...} object.

  • folder download --folder-uuid and folder thumbnail --folder-uuid no longer write into a directory named untitled - The folder's name is used.

  • folder upload no longer copies every file through the temp directory - The copy used a non-unique name, so two uploads of files called part.stl from different directories could clobber each other mid-upload.

  • asset create --override treats only "absent" as absent - A network or session failure while checking for the existing asset used to be read as "does not exist".

  • A batch row whose folder does not exist is counted as missing, not as a permission problem.

  • A configured API URL with a trailing slash no longer produces //tenants URLs; user IDs are URL-encoded.

  • tenant use interactive selection no longer panics on a tenant name containing a colon.

  • Excel report sorting parses each cell once instead of once per comparison (tens of millions of parses on a million-row report).

  • --tenant accepts a tenant UUID, as its help text promised - Only the short name worked. A tenant missing from the cached list is also looked up once more from the API before it is reported as not found, so a tenant granted since the cache was written is usable without cache clear.

  • PCLI2_FORMAT is honoured by env list, env get and config get - --format has a default value, so the branch that consulted the variable was unreachable.

  • In-app advice no longer refers to a context command that does not exist - "Run 'pcli2 context set tenant'" is now "Run 'pcli2 tenant use'".

  • Uploads go to the folder that was asked for - The upload endpoint places a file by the path string it is sent and creates any folder it does not know, and three callers sent it the wrong string: folder upload --folder-uuid sent a placeholder / (every file landed at the tenant root), asset create --folder-uuid fell back to the bare folder name when the hierarchy fetch failed (a nested folder A/B/C became a new top-level C), and --folder-path was sent as typed, so /Home/Parts could create a literal Home folder and /parts a second folder beside Parts. The destination is now the folder's canonical path from the hierarchy, whichever way the folder was named.

  • Uploading to the tenant root works - asset create --folder-path /, asset create-batch --folder-path / and folder upload --folder-path / failed with "Folder '/' not found" after fetching the whole hierarchy to suggest alternatives. The root has no UUID and needs none.

  • Typed metadata compares equal to itself in match reports - A numeric or boolean field was rendered as text on the reference side and blanked on the candidate side, so the Excel diff painted every such field as "missing on one side" and CSV rows showed 42 against an empty cell. Both sides now render values the same way (JSON text for non-strings, empty for null).

  • Folder match JSON no longer invents reference asset fields - referenceAsset reported state: "active" (not a Physna state), type: "asset", isAssembly: false and empty timestamps regardless of the real asset. The real values are used.

  • Single-asset text match CSV no longer doubles the asset URL - The stored URL was already an asset URL, and the CSV writer appended /asset/<uuid> to it again.

  • Excel reports stop at the worksheet hyperlink limit - Excel allows 65,530 hyperlinks per sheet; a report between that and the 1,048,574-row cap produced a workbook Excel "repairs" by stripping every link. Comparison URLs past the limit are written as plain text, with one warning.

  • Comma-separated values work where the help text promised them - --name a,b on asset metadata delete and asset metadata inference, and --folder-path a,b on folder geometric-match, part-match, visual-match and asset dependencies, were sent as one value containing a comma; a delete of Material,Weight deleted nothing and exited 0.

  • Metadata batch CSV: the classic header is checked and short rows are reported - A file with columns in another order was applied positionally (paths and names swapped); metadata get --format csv output (NAME,VALUE) was accepted and silently did nothing because every row was too short. The header must now read ASSET_PATH,NAME,VALUE[,TYPE]; rows with fewer than three columns are counted and reported with line numbers; an empty NAME is an error.

  • Downloaded names cannot escape the output directory - A folder or asset named .., or a name containing a path separator, is refused instead of written outside the directory the user chose.

  • One missing field no longer fails a whole page of assets - type, createdAt, updatedAt, state, isAssembly and metadata are optional when deserializing, as are the page index fields.

  • Ambiguous folder paths are reported - Two sibling folders that differ only by case (or not at all) both match a path; the first was used silently. A warning now names both UUIDs.

  • Batch commands exit non-zero when any item failed - asset create-batch logged failed files at debug level only and printed the successes with exit 0; asset metadata create-batch --continue-on-error exited 0 with every row failed; folder upload, folder download and folder thumbnail printed "Operation completed successfully!" whatever had happened, and a missing thumbnail counted as a success. Every failure is now named on stderr and the command exits 69 (temporary failure) with a one-line count. --continue-on-error still decides whether the rest of the batch is attempted; it no longer decides whether the exit code tells the truth.

  • folder download without --continue-on-error stops at the first failure - It used to attempt every remaining asset anyway (each with its own 5-10 second retry sleep) and only then report an error; an expired session on file 1 of 5,000 cost 5,000 failing attempts. Remaining downloads are now cancelled and counted as "not attempted".

  • Concurrent commands share one API client - folder download, folder thumbnail, folder upload and asset create-batch built a fresh client inside every task: one credential-file read, one connection pool and one private token slot per asset, so a token expiring mid-run was renewed once per remaining asset (up to 22,000 auth-server calls in the largest known run) instead of once. Tasks now clone the caller's client, which shares the token, the renewal lock and the connection pool.

  • folder upload lists the destination folder once instead of once per file - Each task listed the entire folder (every page) to check whether its file already existed: 2,000 files into a folder of 10,000 assets was about 100,000 requests before the first upload. A failed listing was also treated as "does not exist", so --skip-existing could re-upload on a transient error; it is now a hard error.

  • folder download and folder thumbnail no longer fetch every subfolder a second time - The subfolder listing already carries the name the walk needed; one request per subfolder is gone.

  • Network and session failures while loading the folder hierarchy are no longer reported as "Folder not found" - The lookup swallowed them and every caller turned the empty answer into a path problem, sending users to check a path that was right. They now surface as what they are, and --recursive no longer suggests cache clear for a failure that clearing the cache cannot fix.

  • asset list and folder list use the folder cache - Both rebuilt the tenant's whole hierarchy from the API on every call (asset list --recursive twice), and folder list --reload refreshed nothing because the listing bypassed the cache. A path missing from the cached hierarchy triggers one refresh before it is reported absent, so a folder created since the cache was written is still found.

  • Downloads are streamed to disk through a temporary file - asset download and folder download held the whole file in memory (ten concurrent multi-gigabyte assemblies meant ten files in RAM), wrote directly to the final name so an interrupted transfer left a truncated file that looked complete, and accepted an empty body as success. The file is now streamed into <name>.part and renamed into place only when whole; an empty response is an error. The blind "sleep and retry any error, including 404" wrappers are gone: the client retries transient failures itself.

  • Folder match runs count every failed task - A task that failed outside the search itself was dropped from the tally, so "Searched X of Y" and the 10% threshold used a smaller denominator. A run stopped by repeated authentication failures now always exits non-zero, even when the stop happened past the 90% mark.

  • Match runs read the configuration once - The UI base URL was loaded and parsed from disk for every match row: 1.29 million times on the largest known report.

  • "No assets to download" is a warning, not an error with exit 0 - asset download-folder and folder download printed a red error and exited 0 for an empty folder.

  • A server outage mid-run no longer produces a "complete" report with exit 0 - Every HTTP status other than 401/403/404 was wrapped in the same Conflict error the search endpoint uses for an asset that is not indexed, so a 500, 503 or 429 during a folder match was counted as "not searchable" - a property of the tenant, excluded from the failure threshold - and the run finished with a short report and a clean exit. Errors now carry their status: only a real 409 is "not searchable", and everything else counts as a failure that can fail the run.

  • Transient failures are retried on every request, not only on DELETE - The API client built its requests on the raw HTTP client and skipped the retry helper entirely, so PCLI2_MAX_RETRIES, the backoff, and Retry-After applied to nothing the user cared about; one 503 in a 5,000-page match run aborted it. Every request now goes through one path with proactive token renewal, transient retry (connection errors, 408/429/502/503/504, Retry-After in seconds or HTTP-date form), and a single renew-and-retry on 401/403. Uploads re-open the file for each attempt; timed-out non-idempotent requests are still never resent.

  • Exit codes follow the documented contract - Authentication failures exit 100, network failures 101, API errors 102, not-found 67, missing input files 66, an incomplete report 69, and usage errors from the argument parser 64 (previously 2). Every one of these used to exit 70 "internal software error" (or 0, see below), which made the case $? examples in the README impossible to hit.

  • Failures that exited 0 now exit non-zero - auth get and auth expiration with no token, env use with no selection, env get with no active or an unknown environment, tenant use with no or an unknown tenant, config validate when validation fails, and config import, which was an unimplemented stub that reported success having done nothing and now says so.

  • Error messages are no longer rewritten by substring - A folder named 401-series was reported as an authentication failure, an unknown environment name was replaced by "Resource not found", a file called connection-bracket.stl became a network error. Messages are now printed as the code produced them, with one hint chosen from what the error actually is (a login hint for authentication failures, a role hint for a 403 that survived token renewal, a connectivity hint for network errors). The OAuth error codes from the auth server keep their explanations, appended rather than substituted.

  • Errors are printed once - Login and token failures were reported with remediation steps and then printed a second time on exit as a bare "Security error"; the incomplete-report error was printed twice with different counts.

  • A 404 no longer triggers a token renewal - Every not-found answer (a deleted asset, a wrong UUID) cost an auth-server call, a credential-file write and a retried request, and with a token-only login was then reported as an expired token. A 404 is now a not-found.

  • Automatic re-authentication failures say why - A rotated secret, a rate-limited auth endpoint and a DNS failure all printed the same "log in again"; the cause is now included.

  • Concurrent 401s collapse into one renewal - The check for "someone else already renewed" compared against the token current at the moment of the check rather than the token the failed request had used, so a task that read the shared slot after another task's renewal renewed again anyway.

  • Timeouts - A stalled connection used to wait out the full 30-minute request timeout even for a small JSON call, and the auth client had no timeout at all while holding the renewal lock, which hung every concurrent task with it. Connections now time out after 15 seconds and a read after 5 minutes of silence; the total timeout (PCLI2_TIMEOUT, still 30 minutes by default) only bounds genuine long transfers.

  • Every request identifies itself - The User-Agent header (now PCLI2/<version>) was only sent on DELETE.

  • Unsupported file types are no longer retried five times by --override - The "Invalid path extension" upload rejection was classified as a conflict, which the override path treats as "the previous asset may still be deleting" and retries with backoff. It is now an invalid-parameter error, reported the same way whether or not the first attempt also hit an expired token.

  • Thumbnail downloads on a Viewer account no longer make three auth-server calls per asset - The per-asset retry helper renewed the token up to three more times on any 403; the client's own renew-and-retry is enough.

Documentation

  • The install guide had pointed at the wrong repository and listed a Rust toolchain as a prerequisite for the binary install; it now covers the shell and PowerShell installers, the Homebrew tap, the MSI, pcli2-update, and says plainly that the crate is not on crates.io. The README no longer claims credentials are kept in the OS keychain: they live in dev_credentials.json under the configuration directory, owner-only on macOS and Linux. pcli2 config environment ... became pcli2 env ... some releases ago and the README and quick start now say so; asset geometric-match-folder is folder geometric-match; the context command is tenant use/get/clear; --path on asset list and folder download is --folder-path; examples use /Home/..., the name Physna shows for the root, instead of /Root/.... Three environment variables (PCLI2_API_BASE_URL, PCLI2_UI_BASE_URL, PCLI2_AUTH_BASE_URL) that the code never read are gone from the docs, and every variable that is read is now listed in one table (README and Cross-Platform Configuration). The exit-code tables drop the code that is never emitted (68) and explain 64 for parser errors and 69 for partial batches. The documented multi-level inference propagation and the config import example are removed with the features that did not exist.

Removed

  • Dead dependencies - bincode, exitcode, url, mime, tokio-util and async-recursion (recursion now uses Box::pin, stable since Rust 1.77); tempfile moves to the test dependencies and tokio-test is dropped. Two integration test files that compiled to no tests (one was a single line of escaped text; the other asserted nothing) are removed and the test README describes what actually exists. The committed mdBook output under docs/book/ and the stale top-level docs/*.md copies are removed; the published site builds from docs/src/. The site no longer advertises cargo install pcli2.

Changed

  • CI runs cargo audit and a minimum-supported-Rust check (1.88, set by transitive dependencies); rust-version is declared in Cargo.toml. Cargo.lock is no longer listed in .gitignore (it was tracked all along).

  • asset metadata inference --recursive - The flag was accepted and never read; the documented multi-level propagation did not exist.

Changed

  • Batch status lines go to stderr - The statistics reports of folder upload, folder download and folder thumbnail, and the per-file "Successfully uploaded" / "Skipping existing asset" lines, no longer share stdout with command output.

1.20.0 - 2026-09-04

v1.20.0 Sep 4 2026 at 19:05 UTC

Fixed

  • asset create --override no longer deletes the existing asset before checking the local file - The existing asset was deleted first and the local file was only validated by the upload that followed, so a mistyped filename (or an unsupported extension, a 413, a dropped connection) destroyed the customer's asset and its metadata with no way back. The local file is now checked before anything destructive happens, and if the re-upload still fails after the delete, the deleted asset's UUID and any metadata captured for --restore-metadata are printed so they can be recovered by hand.
  • folder list --folder-uuid now lists that folder - The argument was accepted by the parser and never read, so the command silently listed the tenant root instead.
  • The banner no longer corrupts command output - It was printed to stdout whenever any argument equalled help, -h or --help, including values: env list --name help or asset text-match --text help emitted ASCII art ahead of their JSON. The banner is now shown only above genuine help output, as decided by the argument parser.
  • --threshold is validated as a percentage - Values outside 0-100 are rejected at parse time on every matching command (--threshold 500 used to go straight to the API; --threshold -5 failed with a misleading "unexpected argument"). A value below 1, almost always 0.85 typed for 85%, is still accepted but produces a warning saying what it actually asks for.
  • --concurrent is validated on every command, so --concurrent 0 no longer hangs - asset create-batch sized its semaphore from the flag with no range check, and zero permits made every upload wait forever with no output. The folder match commands only rejected an out-of-range value after resolving the tenant. All concurrent commands now share one parser that accepts 1-10.
  • The install page links to the right repository - docs/src/installation.md pointed the installer script, the release page, the clone URL and the issues link at github.com/physna/pcli2, which is not this project.

Changed

  • CI is green again on the current stable toolchain - clippy 1.98 flags every Result<_, CliError> as result_large_err because the CSV writer error embedded in the error enums was several hundred bytes wide. That variant is now boxed in both FormattingError and CliActionError; nothing user-visible changes.
  • Dependency lockfile refreshed - Clears the h2 advisory (RUSTSEC-2026-0258, reachable through reqwest in the shipped binary) and the rand and event-listener unsoundness advisories reported by cargo audit.

v1.19.0

v1.19.0 Aug 1 2026 at 07:00 UTC

Changed

  • The new-version hint now also follows a failed command, --version, and a rejected argument - It previously appeared only after a successful run, which is when it matters least. The motivating case is the opposite one: a user on a build predating the flag they are passing sees unexpected argument, concludes the feature is broken, and has nothing pointing at their own install. That is not hypothetical — it is the --recursive report described in the note below, which cost a support round trip and a release spent chasing a defect that user never hit. Argument parsing no longer lets clap exit on pcli2's behalf, so a usage error or a --version check can still be told the binary is out of date, and a failed command prints the hint after the error so it is the last thing read. --help is excluded: its output is long enough that a trailing line would scroll past unread. Every existing restraint still applies — terminal sessions only, at most one check per 24 hours, never in CI, and PCLI2_NO_UPDATE_CHECK still disables it entirely.

Notes

  • Correction to the v1.18.3 entry below. That fix was attributed to a field report of --recursive "not working". The report turned out to have a different cause — an old pcli2 earlier on the user's PATH, in a shell where --recursive did not yet exist — so the silent-fallback defect it describes was not what that user hit. The defect is real and the fix stands: a recursive scan really could degrade to listing only a folder's direct children, with the explanatory warning painted over by the --progress spinner. But it was found by reasoning backwards from the symptom rather than by reproducing it, and there is no evidence it has ever triggered in the field. Worth knowing before treating a low recursive count as that bug: check pcli2 --version in the shell actually being used first.

v1.18.3

v1.18.3 Jul 31 2026 at 19:30 UTC

Fixed

  • --recursive no longer degrades silently into a non-recursive scan - When the folder hierarchy could not be loaded, a recursive folder match fell back to listing only the assets sitting directly in the folder. The warning that was supposed to explain it went to stderr underneath the --progress spinner, which redraws every 100ms and painted straight over it — so the run looked like a success and simply reported far too few assets. Reported from the field as "--recursive is not working": a folder holding 22,378 assets across 511 subfolders reported 1, the count of assets directly inside it. A recursive scan that cannot enumerate subfolders now fails with an explanation and a suggestion to run pcli2 cache clear, rather than quietly answering a different question. The same applies when the folder resolves against the API but is missing from the cached hierarchy, which previously produced a silent empty result. Scan errors also clear the spinner before printing, so the message survives.
  • --continue-on-error now takes precedence over the permission stop - A 403 that survives a token renewal stops a metadata batch, since without per-asset permissions every remaining write would fail the same way. --continue-on-error means what it says, so it now wins: the account-level explanation is given once and the run continues, with the usual terse per-asset skip lines.

v1.18.2

v1.18.2 Jul 31 2026 at 15:44 UTC

Fixed

  • A batch that fails because the account lacks the Author role now says so - Physna has no per-asset permissions: an account is an Author, who may write any asset, or a Viewer, who may write none. A Viewer running asset metadata batch therefore fails on every write, but the run reported an authentication failure and told the user to re-authenticate — a session that was working perfectly. pcli2 now distinguishes authenticated but not permitted from credentials not working: a 403 that survives a token renewal is reported as a role problem, with guidance to ask a tenant administrator for the Author role, and stops the run immediately rather than repeating the same failure for every remaining asset. A 401 that survives renewal still means the credentials themselves are the problem and still asks the user to log in again.

v1.18.1

v1.18.1 Jul 31 2026 at 04:33 UTC

Fixed

  • A transient authentication blip no longer aborts an entire metadata batch (#93) - asset metadata batch stopped the whole run and told the user to re-authenticate a session that was working, so following the advice changed nothing and the next attempt failed identically. A 401/403 causes the client to renew the token and retry; the resulting error text always embeds the original 401/403, and the authentication check searched the whole message — so it matched every retried failure unconditionally, whatever the retry actually returned. A stale-token 403 whose retry then failed for an unrelated reason (the asset not being indexed, a metadata type conflict, a 5xx) was reported as an authentication problem. The check now inspects the retry's own status rather than the original or the response body, and the decision to abandon a run uses a narrower test that fires only when the credential renewal itself failed. Everything else is treated as a per-asset failure: counted, reported with relevant guidance, and skipped under --continue-on-error. This also affects the concurrent folder match commands, where the same check feeds the consecutive-failure counter — three assets in a row failing this way would have tripped a spurious stop. Asset lookups that fail for a reason other than the asset being absent are also no longer reported as "asset not found".

v1.18.0

v1.18.0 Jul 31 2026 at 02:55 UTC

Fixed

  • Token renewal is now shared between concurrent tasks (#86) - The API client is cloned once per task by the concurrent commands, and the access token was a plain field, so a token renewed inside one task was invisible to every other task — each of which still held the original and renewed again for itself. On a folder tree of ~22,400 assets, one expiry produced up to ~22,000 separate token requests where one would do, each an opportunity to fail. The token is now shared by a client and all of its clones, and concurrent renewals are collapsed: the first task to notice renews, and the rest pick up its result instead of queueing to repeat it. Tokens are also renewed shortly before they expire rather than after a request has already been rejected, which avoids the failed-request-then-retry round trip entirely; that check is best-effort and can only avoid work, never fail a request that would otherwise have succeeded.

v1.17.0

v1.17.0 Jul 31 2026 at 02:21 UTC

Fixed

  • Folder match output is now reproducible (#82) - Two identical runs over unchanged data produced reports that differed byte-for-byte. Rows were collected in whatever order the concurrent searches finished, and for a symmetric pair the surviving row was whichever direction arrived first — so which asset appeared as the reference varied between runs. Matches are now deduplicated into an ordered map keyed on the unordered pair, so both the row order and the choice of orientation are properties of the data rather than of the run's scheduling. When a pair is searched from both ends the record whose reference sorts first is kept; a pair searched from only one end keeps that direction, and no record is ever rewritten to look canonical, since the transformation matrix and comparison URL describe one direction and flipping them would corrupt both. Two runs over unchanged data now produce identical bytes, so reports can be diffed and version-controlled.
  • A folder match run now stops after repeated authentication failures, instead of issuing thousands of doomed requests (#78) - When the token expired partway through a long run and automatic re-authentication failed, every remaining asset failed identically: 21,068 of them on one run, each logging its own copy of the same error, taking the full ~25 minutes and ~21,000 pointless API calls to finish. A run now stops after three consecutive authentication failures, and the reason is reported once rather than once per asset. Consecutive, not the first: the client renews the token and retries automatically — which is what lets a long run survive its credentials expiring halfway through — and a failed renewal is reported the same way whether the credential was rejected or the auth endpoint merely hiccuped, so stopping on the first would abandon a long run over something the next request would have recovered from. Any successful search resets the count, so scattered blips are absorbed while a genuinely dead credential trips it almost immediately. Assets that were never searched are counted separately from ones that were tried and failed (Searched 1,259 of 22,378 asset(s): 1 failed, 21,067 not attempted, 51 not searchable).
  • Folder match commands no longer report success when most of their searches failed (#77) - A failed per-asset search contributed nothing and was otherwise indistinguishable from an asset that genuinely had no matches, so nothing counted failures and nothing could report them. A run that lost 21,119 of 22,378 searches to an expired token still exited 0 and printed a normal completion summary, handing over a report missing 94% of its rows. Failures are now counted and reported whenever any occur (Searched 1,259 of 22,378 asset(s): 21,068 failed, 51 not searchable), and the command exits non-zero when operational failures exceed 10% of the assets. Failures are classified: an asset that cannot be searched in its current state (not indexed, no 3D data, indexing failed - reported by the API as a 409) is a property of the tenant and never fails the run, while authentication, network, and 5xx failures do. The check runs immediately after matching, so an incomplete run stops before spending minutes building a report that should not be trusted. Applies to folder geometric-match, folder part-match, and folder visual-match.

v1.16.0

v1.16.0 Jul 31 2026 at 00:53 UTC

Changed

  • folder geometric-match --format xls now refuses a report too tall for a worksheet, instead of failing deep inside the workbook writer - An Excel worksheet holds 1,048,576 rows; this report spends two of them on its header band, leaving 1,048,574 for data. A real run produced 1,293,068 rows, which cannot be represented. The command now fails immediately after matching with the report has 1293068 rows, more than the 1048574 an Excel worksheet can hold - use '--format csv' for the complete report, before building a single row. Truncating to fit was considered and rejected: a workbook silently missing a quarter of its rows looks complete to whoever opens it. Use --format csv, which has no such limit.

Security

  • The stored-credentials file is now owner-only (0600) - With the default dev-keyring feature, the client secret and access token are kept in plain text in dev_credentials.json. Written under the process umask it landed at 0644, readable by every other account and every unprivileged process on the machine. The mode is now set explicitly on every write, and an existing file is tightened the first time it is read, so credentials written by an earlier version are repaired rather than left exposed until the next login. Unix only; Windows has no equivalent mode bits and inherits directory ACLs.

Fixed

  • auth login now prompts when the stored credentials are empty, instead of failing with invalid_client - An empty stored client ID or secret was treated as a usable credential and sent to the auth server, which rejected it with an error pointing at the user's credentials when the actual problem was local state. Empty is now treated as absent, falling through to the interactive prompt — matching what the prompt path itself already did.
  • Report building no longer materializes an intermediate copy of every match - folder geometric-match built a Vec<GeometricMatchPair> before building rows, cloning each reference asset — metadata HashMap included — once per row, only ever to read a path and a UUID back out of it. On a 1.29M-row report that intermediate took seconds to build and a further nine seconds to drop, with the progress display parked at 99% for the whole teardown. Both passes now iterate borrowed data. Output is unchanged.
  • The progress bar no longer flashes 0/0 (0%) when a counted phase starts - The steady-tick thread could redraw in the window between the counter style being applied and the row count being set. Length is now set first.

v1.15.0

v1.15.0 Jul 30 2026 at 19:57 UTC

Changed

  • JSON match reports now stream to stdout too - Completes what v1.14.0 started for CSV. folder geometric-match, folder part-match, and folder visual-match built the entire pretty-printed JSON document as one String before printing it; on a large report that is a second full copy of the data — pretty-printed JSON is bulkier than the rows it came from — and nothing reached the terminal until the last byte was ready. Serialization now goes straight to a buffered, locked stdout. The bytes written are unchanged, trailing newline included. This supersedes the note in the v1.14.0 entry below that JSON would keep buffering: the atomicity trade is real and is harsher here than for CSV, because a truncated JSON document does not parse at all where a truncated CSV still does. A mid-write failure still reports on stderr with a non-zero exit code, which is what separates it from a silently short file. The single-asset asset match visual and asset match text reports are small and still buffer.

v1.14.0

v1.14.0 Jul 30 2026 at 19:32 UTC

Changed

  • CSV match reports now stream to stdout instead of being buffered whole - folder geometric-match, folder part-match, and folder visual-match used to serialize the entire report into a byte buffer, copy that into a String, and print it in one go, holding the report in memory three times over before emitting a single byte. Rows now go straight to a buffered, locked stdout as they are produced: output starts appearing immediately, two of the three full copies are gone, and peak memory no longer scales with a second and third copy of the report. The bytes written are unchanged. Two consequences worth knowing: a failure partway through now leaves a truncated report on stdout rather than printing nothing at all (the error still goes to stderr and the exit code is still non-zero), and closing the pipe early — pcli2 … | head — now ends with a clean error instead of a failed printing to stdout panic. JSON output deliberately still buffers, because half a JSON document is not a document.

Added

  • --progress now covers report building as well as matching - On a large result set, folder match reports spend minutes turning matches into a table and serializing them, and that work happens after the match progress bar has already reached 100% — so the command looked wedged (one run sat silent for several minutes at 22,378 assets). With --progress, each phase now reports itself with a running elapsed time, and the phases whose size is known up front (flattening matches, collecting metadata columns, building rows, formatting CSV, writing worksheet cells) show a percentage bar and an ETA rather than an indeterminate spinner. Serializing JSON, sorting the workbook, sizing its columns, and saving it are named as they happen, so a long wait can be attributed to a specific step. A summary line closes the report (Built report of 1,743,201 row(s) in 2 minutes). Applies to folder geometric-match, folder part-match, and folder visual-match, in every output format including --format xls. All of it goes to stderr, so piped stdout is unaffected; without --progress the phase stays silent.
  • --progress now covers the recursive folder scan - With --recursive, folder match reports spend time walking the folder tree (one API call per folder) before any matching starts, which looked like a hang on a deep tree. When --progress is passed, that phase now reports live (Scanning /Creo Files: 46/312 folders, 1174 assets found) and prints a summary before matching begins (Scanned 1 folder path(s), found 3182 asset(s) to match). All of it goes to stderr, so piped stdout is unaffected; without --progress the scan stays silent.

v1.13.0

v1.13.0 Jul 30 2026 at 18:17 UTC

Added

  • --recursive for folder match reports - folder geometric-match, folder part-match, and folder visual-match accept --recursive (-R) to include the assets in every subfolder, not just those sitting directly in the named folder. The default is unchanged, so existing commands keep their current scope and runtime; recursion is opt-in because it can widen the work dramatically (a folder with a single asset of its own may have thousands underneath it, and each one costs a search).

Fixed

  • Folder match reports no longer look like a broken path on container folders - A folder that holds nothing but subfolders has no assets of its own, so a match report over it found nothing and failed with No assets found in the specified folder(s), whose first suggestion was to verify the folder path. The path had in fact resolved correctly, which sent users hunting for a typo that was never there. The message now reports how many subfolders the folder actually contains and points at --recursive:

    ❌ Error: No assets found directly in the specified folder(s)
    
    🔧 To resolve this issue, try the following:
      1. The folder(s) contain 8 subfolder(s) - pass --recursive to include the assets in them
    

v1.12.0

v1.12.0 Jul 20 2026 at 21:04 UTC

Changed

  • asset text-match no longer silently drops results - Text search ranks name matches above metadata-only matches, so the previous default --limit of 100 could silently omit assets whose only match is in a metadata value. The default limit is now 1000 (the limit remains as a guard against excessive queries), and when the limit does cut the result set short a warning is printed to stderr (⚠️ Result limit reached: showing the first N matching assets; increase --limit to retrieve more). The warning goes to stderr only, so JSON/CSV data piped from stdout is unaffected.

v1.11.0

v1.11.0 Jul 13 2026 at 16:40 UTC

Added

  • tenant metadata list command - Lists every metadata field registered in the tenant along with its data type (text, number, boolean, url). Supports --format json|csv|tree (alias ls). The CSV output uses the same column header as the classic asset metadata create-batch input (ASSET_PATH,NAME,VALUE,TYPE), with NAME and TYPE filled from the registry and ASSET_PATH/VALUE left blank — so a listing can be saved and turned directly into a batch-upload template.
  • Optional TYPE column for asset metadata create-batch - The classic CSV layout accepts an optional fourth TYPE column (text, number, boolean, or url; default text), applied per row. It sets the type used when registering a new metadata field; for a field that already exists, its registered type stays authoritative. The url type is now also accepted by single asset metadata create --type.

Fixed

  • Batch metadata upload no longer fails on non-text fields - asset metadata create-batch sent every CSV value as a string, so number, boolean, and url fields were rejected with a "type mismatch" error. Values are now automatically coerced to each field's registered type (e.g. 18 for a number field, true for a boolean, a URL string for a url field), so existing typed fields populate without any extra configuration. A value that cannot be represented as the field's type (e.g. N/A for a number field) is reported as a type conflict.
  • /Home asset paths in batch uploads resolve to the root - A leading /Home (the name Physna shows for the root folder) in an ASSET_PATH is normalized to the root, so /Home/NX/part.prt and NX/part.prt refer to the same asset. Rows that spell the same asset differently now merge onto one asset instead of splitting into separate lookups.

Changed

  • asset metadata create-batch --continue-on-error also skips metadata failures - Previously the flag skipped only assets whose path could not be resolved; a failed metadata update or delete (such as a type conflict) always terminated the batch. It now skips the offending asset and continues, consistent with how unresolved paths are handled. Authentication failures remain fatal.

v1.10.0

v1.10.0 Jul 3 2026 at 01:35 UTC

Added

  • Homebrew formula published automatically - Releases now push an updated formula to the jchultarsky101/homebrew-pcli2 tap via cargo-dist, so brew install jchultarsky101/pcli2/pcli2 installs the current version (the tap had been stuck at v1.0.0). Requires the HOMEBREW_TAP_TOKEN repository secret.
  • PCLI2_TIMEOUT environment variable - Overrides the request timeout (seconds). The default remains 30 minutes, which large model transfers require; users working with small files can opt into faster failures.

Fixed

  • Redirected diagnostics are free of ANSI escape codes - tracing output (warnings, debug logs) emitted color codes even when stderr was redirected to a file. Colors on stderr now follow the same rules as stdout: disabled when not a terminal, or when NO_COLOR/PCLI2_NO_COLOR/--no-color is set.

Removed

  • update-latest-tag.yml workflow - It triggered on release: published, an event GitHub suppresses for releases created with GITHUB_TOKEN, so it had not run since October 2025. Nothing consumes the latest git tag it maintained (installers use GitHub's releases/latest URLs, which are independent); the stale tag has been deleted.

Changed

  • Timed-out requests are retried only for reads - A network timeout can fire after the server has started processing a request, so retrying a timed-out write (POST/PUT/DELETE) could apply an operation twice. Timeouts now retry only GET requests; connection failures (request never reached the server) and transient status codes (408/429/502/503/504) retry for all methods as before.
  • Failed update checks are not re-attempted until the next daily window - Previously an unreachable GitHub API (offline, firewalled, rate-limited) caused the version check to be re-attempted on every command once its cache went stale, adding up to 3 seconds per command. A failed check now counts as the day's attempt.
  • Man page SYNOPSIS shows the real invocation - Pages now read pcli2 folder delete [OPTIONS] instead of pcli2-folder-delete [OPTIONS], while page names/headers keep the git-style dashed form (pcli2-folder-delete(1)).

v1.9.0

v1.9.0 Jul 3 2026 at 00:34 UTC

Added

  • --dry-run flag for destructive and bulk commands - asset delete, folder delete, asset create, asset create-batch, and folder upload can now report exactly what they would delete or upload and exit without making any changes. For folder upload the check runs before remote folder resolution, so a dry run never creates the target folder.
  • Global --verbose/-v and --quiet flags - Quick verbosity control on every command: --verbose enables debug-level logging, --quiet limits diagnostics to errors. Both take precedence over the PCLI2_LOG_LEVEL/RUST_LOG environment variables. The local --verbose on config validate is replaced by the global flag (same behavior).
  • Automatic retries for transient failures - Network timeouts, connection errors, and HTTP 408/429/502/503/504 responses are now retried with exponential backoff and jitter, honoring the server's Retry-After header. Default is 2 retries; PCLI2_MAX_RETRIES overrides (0 disables).
  • pcli2 man command - Generates Unix man pages for pcli2 and every subcommand (one page per command, e.g. pcli2-folder-delete.1) into a directory given by --output-dir.
  • Update notifications - After a successful command in an interactive terminal session, pcli2 prints a one-line stderr hint when a newer release is available on GitHub. Checked at most once per 24 hours with a 3-second timeout; skipped in CI, for redirected output, and for completions/man; opt out with PCLI2_NO_UPDATE_CHECK.
  • Spinners for quick operations - Single API round-trips (asset get, tenant list, config validate --api) show a spinner on stderr so the CLI never appears hung. Hidden automatically when stderr is not a terminal.
  • Scripting and Automation documentation - New docs page (and README sections) covering exit codes, machine-friendly output, dry-run mode, retries, and a GitHub Actions integration example.

Changed

  • Interactive auth login - When --client-id/--client-secret are omitted and no stored credentials exist, pcli2 now prompts for them interactively (masked input for the secret, keeping it out of shell history) instead of erroring. Non-interactive sessions keep the previous missing-argument error.
  • Colors are TTY-aware and respect NO_COLOR - The banner, help examples, and help styling no longer emit ANSI escape codes when output is piped or redirected, when NO_COLOR/PCLI2_NO_COLOR is set, or when the global --no-color flag (previously defined but inoperative) is passed.
  • Consistent progress bars - All overall progress bars now show ETA and throughput (the batch upload, asset download, and batch create bars were missing one or both), and per-file spinners show elapsed time.

Fixed

  • Warnings are no longer printed twice - Warnings (e.g. skipped rows in asset metadata create-batch --continue-on-error) were emitted both through tracing and a direct stderr print. They now go through tracing only, so their visibility is controlled by --verbose/--quiet, RUST_LOG, or PCLI2_LOG_LEVEL. The tracing subscriber also writes to stderr (previously stdout), so diagnostics never pollute piped command output.
    • Note for script authors: warning lines now use the tracing format (<timestamp> WARN <module>: <message>) instead of the previous ⚠️ Warning: <message> prefix - scripts that matched the old literal prefix on stderr need updating.
    • Note on --quiet: warnings are suppressed under --quiet (errors only). End-of-run summaries (batch statistics and remediation blocks) are still printed.