# Reltio CLI changelog What changed in the `reltio` CLI and the agent skills that ship with it, newest version first. ## 0.10.0 ### Minor Changes - Finding a tenant's API host no longer sends your token to reltio.tools. reltio.tools publishes its list of Reltio environments at [`/api/environments`](https://reltio.tools/api/environments): the API host and the auth server of each, and no credentials. The CLI reads the list and asks only the API hosts of your own auth server, with your token. A service-account sign-in, `reltio use TENANT` and `reltio use` work this way now, so a service-account sign-in reads the list from reltio.tools and sends it nothing. The CLI also sends a token only to an `https` host under `reltio.com`. This applies to a host from the list, from a browser login, or from the session file. `GET /api/checkTenant` and `GET /api/tenants` are removed from reltio.tools. Upgrade the CLI: earlier versions cannot sign in a service account or run `reltio use` without them. - `reltio ui` works on a fresh install. Its apps now come in a skill of their own, `reltio-ui`, which the installer puts in place by default beside `reltio-cli`, so the data model app is there without asking for `reltio-configuration`. The skill also tells an agent how to show tenant data to a person in `reltio ui`, and how to write a new app: one HTML file in `reltio-ui/apps/`, in the list on the next reload. An `app.html` beside another skill's `SKILL.md` is no longer listed. - `reltio ui` opens a browser tab that lists the apps your installed skills carry, and runs any of them against the tenant your session is on. An app reads the tenant live and never sees your token. When the login has expired and cannot be renewed, the tab goes to the sign-in page by itself and comes back to the app, at the view it was on. The first app is the data model, in the `reltio-configuration` skill: a map of entity types and the relations between them, every type's attributes, the relation types, and the sources. Any skill can carry an app — put an `app.html` beside its `SKILL.md`, and it is in the list on the next reload, with nothing rebuilt. - The installer installs one agent skill instead of four. `reltio-cli`, which used to be called `reltio`, tells an agent to learn the CLI from the CLI itself: `reltio --help` for the commands, `reltio get --help` for the endpoints, `reltio get PATH --help` for one endpoint's parameters. It no longer repeats what the help already says, so it is much shorter. The other skills are still published, and you can ask for them by name or install all of them: ```bash curl -fsSL https://reltio.tools/install.sh | RELTIO_SKILLS="reltio-search reltio-profile" sh curl -fsSL https://reltio.tools/install.sh | RELTIO_SKILLS=all sh ``` `RELTIO_NO_SKILLS` still installs no skills at all. If you already have them, the installer removes the old `reltio` skill and leaves `reltio-configuration`, `reltio-search` and `reltio-profile` alone; `uninstall.sh` removes all of them, as before. The skills now have their own version and release notes, separate from the binary's: a change to a skill no longer needs a CLI release. `reltio ui` shows apps only from the skills you have installed. The data model app comes with `reltio-configuration`, and the empty list now says how to install it. ## 0.9.0 ### Minor Changes - `reltio get PATH --help` explains that one endpoint: what it answers with, its path parameters, and every query parameter it takes, with type, default and allowed values. It is written for an agent to build a correct request from it alone, and reads fine to a person too. It needs no session and sends nothing, so it works before `reltio login`. `reltio get --help` with no path now ends with the list of documented endpoints, so there is always a path to ask about. ```text $ reltio get /entities/0001ABC --help GET /entities/{id} Get one entity by its URI This API retrieves full or partial entity objects by URI from the tenant. Usage: reltio get /entities/0001ABC [-q KEY=VALUE]... Path parameters: id: string, required Identifier of entity Query parameters, each passed as -q KEY=VALUE: select: string Comma-separated list of properties from object structure that should be returned in a response. … options: comma-separated list of sendHidden | ovOnly | nonOvOnly | showEndDatedReferenceAttributes … ``` `reltio get PATH --openapi` prints that endpoint's OpenAPI as JSON: a complete document holding just this operation and every schema and response it refers to, ready for `jq` or for an agent that wants the response shape too. The help ends by naming it. A new `reltio` skill ships beside the others and tells an agent where to start: `reltio status` to see whether it is signed in and where, `reltio login` when it is not, and `reltio get PATH --help` for any endpoint before calling it. The installer installs it with the others, and the uninstaller removes it with them. - `reltio use` with no tenant shows every tenant your token reaches, across all environments, and lets you pick one. In a terminal it opens a picker fifteen rows tall, with the tenant you are on at the top and marked. Each row is the tenant's name, its id, its environment and its customer, cut to fit the terminal so nothing wraps. Type to narrow the list by any of them and press Enter: the session switches to that tenant, exactly as `reltio use TENANT` would, and the full API host is printed. Esc leaves the session as it was. ```text tenant (412) | > ● SHAHRUKHMD-WIN:M…h:1744029888121 ytim tst-01 Reltio uitest3gold uitest3gold tst-01 Reltio MDM UI QA autotest tenant #2 regrUI20210323N2 prod-usg Reltio ``` When the output is piped, or there is no terminal to ask in, it prints the list instead, one tenant per line: the customer, the tenant's name, and the tenant's API URL. ```text $ reltio use | cat Reltio MDM UI QA autotest tenant #2 https://eutst-01.reltio.com/reltio/api/regrUI20210323N2 Reltio Demo tenant https://tst-01.reltio.com/reltio/api/demoTenant1 ``` `reltio use | grep -i eutst` then finds a tenant by any field, and `reltio use TENANT` switches as before. Listing needs only a signed-in account, or a service account's credentials in the environment. The list comes from a new `GET /api/tenants` on reltio.tools, which takes the caller's bearer token and answers `[{ tenantName, tenantId, customerName, apiHost }]`: what each Reltio API host's `/reltio/enhancedTenants` says about the token, with the host added. ## 0.8.0 ### Minor Changes - Tab completes `reltio get`. The paths come from the API specifications the binary was built from, one segment at a time and with each endpoint's summary beside it, so `reltio get /ent` reaches `/entities` and `reltio get /entities/0001ABC/` offers `_raw` and `_matches`. `-q` completes the query parameters those endpoints take. Nothing is looked up over the network, so it answers instantly and works before you have logged in. `install.sh` registers it for zsh, bash and fish, so a fresh install has it with nothing to run — and `reltio completions `, which printed a script that knew the subcommands and nothing about a path, is gone. If your login shell changed since you installed, `curl -fsSL https://reltio.tools/install.sh | RELTIO_COMPLETIONS_ONLY=1 sh` registers completion for the shell you are in now and downloads nothing. ## 0.7.0 Release notes are published for the first time, at `https://reltio.tools/changelog.md`. Versions 0.1.0 to 0.6.0 are backfilled from the commits that cut them, so the record starts where the CLI did rather than today. A setting comes from the environment and nowhere else, and `reltio status` is where you read it back. The four flags that duplicated a variable are gone, and `reltio config show` has moved into `status`. Where a request goes is decided when you sign in, written into the session, and read from nowhere else. No argument and no environment variable can move it while a command is running, and `reltio use TENANT` is what moves it on purpose. ### Changes - Every version's notes are readable without an account, served as plain text so a browser shows them instead of downloading a file. - `reltio status` answers what `reltio config show` used to, in the command whose name already said "read-only". - It prints even with no session saved. A service account configured from the environment signs itself in on its first request, so `status` used to say "not signed in" and stop while `reltio get` worked. - `signed in as:` is now `signed in with:`, in `status` and in `login`. The field says how the login was made — `browser` or `client_credentials` — and "as" promised an identity it was never going to print. - `reltio use customerTenant` points the session at another tenant your token already reaches, and finds the host that serves it. Nothing is minted: the session keeps its token, its expiry and its refresh handle, and the tenant and the host change together. - A service-account sign-in finds its own api host, so CI needs one variable fewer. Set `RELTIO_TENANT`, `RELTIO_AUTH_HOST`, `RELTIO_CLIENT_ID` and `RELTIO_CLIENT_SECRET`, and the first `reltio get` does the rest. - `reltio status` leads with where requests actually go, then lists the variables the next sign-in would read. The two can differ, and saying so is the point: a variable no longer changes the command you are running. ### Breaking - `--tenant`, `--client-id` and `--auth-host` are gone. Use `RELTIO_TENANT`, `RELTIO_CLIENT_ID` and `RELTIO_AUTH_HOST`, which they only ever duplicated. Passing one is now a usage error and exits 2 rather than being silently ignored. - `reltio config show` is gone. `reltio status` prints what it printed. - `RELTIO_API_HOST` is gone, with nothing in its place. An api host is not a setting any more — it is the login service's answer about a tenant. Use `reltio use TENANT`, or sign in again. - `RELTIO_API_HOST` was also the only way to reach a tenant in an environment the login service does not list, and there is no replacement for that either. The confirmed environments are `prod-usg` and `tst-01`; a tenant anywhere else needs its environment added rather than its host named. - `RELTIO_TENANT` is read only when you sign in. It no longer changes the tenant an individual `reltio get` uses; `reltio use` does that. - `--client-credentials` is gone. Setting `RELTIO_CLIENT_ID` and `RELTIO_CLIENT_SECRET` selects a service account by itself, with or without a terminal, because having a client and a secret said it already. - `RELTIO_LOGIN_HOST` only accepts a loopback address. Anything else is refused before a request is sent. - `RELTIO_CACHE_DIR` and `RELTIO_CONFIG_DIR` must be absolute paths. ### Under the hood - This repository is private, which is why the notes are on the site and there is no root `CHANGELOG.md`. A changelog nobody outside can open is a changelog that is not published. - `CONTRIBUTING.md` writes the process down: a plan reviewed and merged on its own before the code exists, then an implementation that carries its own release note. Both halves exist as agent skills — `plan` and `build` under `.agents/skills/` — so an agent that is not Cursor does what Cursor's Plan Mode and Build button do. - Releases follow Changesets: an entry declares `bump: major`, `minor` or `patch`, and `version the release` takes the highest in the pool, promotes the entries into one set of notes, bumps the crate and pushes to `main`. Nobody edits a version number, and the judgement about what a change is worth is made in the pull request that makes it. - Only the binary is still published by hand. It has to be linked and signed on a Mac — macOS refuses to run an unsigned arm64 binary — and Bitbucket's hosted runners are Linux, so the four publishing scripts stay there while everything made of text moved into CI. - `scripts/check-contribution.sh` enforces the mechanical half on pull requests: a plan that parses, an entry that names what changed and how far it moves the version, and a published version nobody rewrote. It is its own CI step, so "you forgot the release note" never arrives as the same red tick as "this does not compile". - Writing the checker found two defects it now prevents: a plan file carrying two stacked frontmatter blocks, and plans that reached `main` describing shipped work with every todo still marked pending. - A value that can arrive two ways is a value you have to ask about, which is the whole reason a setting used to carry a source. With one way left there is nothing to record, so `Source`, `Setting`, `Override`, `Overrides` and `pick` are all gone from `config.rs`. - `main` is back to one `Cli::try_parse()`. It kept `ArgMatches` alongside the parsed struct for one purpose: asking clap whether a value came from the command line or the environment. Nothing asks any more. - Every request carries a bearer token, so anything able to name a host is able to send your token to it — and the thing composing the command is increasingly an agent acting on text it read somewhere. `RELTIO_API_HOST=https://… reltio get /entities` was the version of that attack which looks like an ordinary command, and it is the one this closes. One test asserts the whole property: every variable that ever named a destination is pointed at a second mock server, which must receive nothing. - `RELTIO_LOGIN_HOST` was the same hole one step back. It is read on every token renewal rather than only at login, so a plain `reltio get` that happened to refresh would have handed the refresh handle to whatever it named — and that service is what decides the api host a session records. - `RELTIO_CACHE_DIR` was a third. It relocates the session file, and the session is where the host lives. A doubly quoted value in a dotenv file resolved to a relative path once and put a live token in a directory named `'` inside a git repository. - The host probe moved from `/reltio/tenants` to `/reltio/enhancedTenants`, which answers every kind of token; the first refuses a service account with `Security error. This endpoint is forbidden for current user.` It matches on `tenantId` alone, because a row also carries `customerName` and a tenant called `Reltio` would otherwise be claimed by every host that answered. - Nothing reads the tenant list inside a token, and a gate that did was written and then thrown away: a service account's list reads `SuperUser:allTenants.*`, a pattern equal to none of the tenants it stands for, so the check would have refused the most privileged callers everything. - A refresh reply no longer restates the tenant or the api host. It used to, so the CLI would not lose them — and that would have dragged a switched session back to the tenant it signed in at, an hour later, with nothing on screen to say so. - `rustls` moved to 0.23.45 for RUSTSEC's `GHSA-2mjx-qc3c-rqvc`. It was already in the lockfile and already failing `cargo deny check`, so it comes along rather than waiting for a release of its own. - What none of this closes is a process running as you reading `~/.cache/reltio/session.json` and using the token without the CLI. Mode `0600` stops another account, not code running as you. The macOS Keychain is what closes it, and it needs a stable signing identity the ad-hoc signature does not provide, so it is its own piece of work. The claim made here is the narrower true one. ## 0.6.0 The installer ships three agent skills alongside the binary, and login moves to `reltio.tools`. ### Changes - `install.sh` installs `reltio-configuration`, `reltio-search` and `reltio-profile` into `~/.agents/skills`, with symlinks in `~/.claude/skills`. They teach a coding agent to drive this CLI against a tenant: the data model, the filter grammar, and the anatomy of one entity. `RELTIO_NO_SKILLS=1` skips them, and `uninstall.sh` takes them back out. - The login service and the download site are on `reltio.tools`. `cli.reltio.tools` is kept as a second domain, because every installer already saved in a CI image and every binary already installed points at it. ### Under the hood - The skills describe their requests and responses as TypeScript declarations generated from the Reltio specs, not as OpenAPI. The `$ref` closure of one configuration schema is 118 schemas and 148 KB of OpenAPI — around 37K tokens — and 26 KB as TypeScript. The problem was the notation. - A test holds every `reltio get` and every `-q` in the skills' prose against the generated declarations, because prose is where a skill goes wrong, and a skill that teaches a call the API refuses fails in front of a user. ## 0.5.0 `reltio get` no longer takes a whole URL. It was still accepted as a convenience; it is now refused, because every request carries an access token and where that token is sent is the session's decision, not an argument's. ### Changes - Error messages say what to do next rather than what went wrong. A refused URL names the path form to use instead. ### Breaking - `reltio get 'https://…/services/api/…'` fails with an error naming the path form. Use `reltio get /entities/00001ABC`; an environment that is not in the catalog is reached with `--api-host`. ### Under the hood - A bearer token sent to a host an argument named is a credential leak that looks like a typo. Refusing the form outright is the only version of this that cannot be got wrong. ## 0.4.0 `reltio get` takes a path instead of a whole URL. Logging in discovers the tenant's API host and carries it in the session, so there is nothing left for the argument to decide. ### Changes - `reltio get /entities/00001ABC` — the session knows where to send it. Service accounts supply the host through `RELTIO_API_HOST` or `--api-host`. - `reltio status` and `reltio config show` print the API host in use. - Downloads moved to an artifact store and out of the site deploy, behind one `/releases/` prefix. Every version publishes a `manifest.json`, and the release is `latest.json` becoming a copy of one. ### Breaking - `auth-servers.json` is now `environments.json`, with a schema of its own. ### Under the hood - The API host is found by probing `/reltio/tenants` across candidates in parallel and taking the first that answers, aborting the rest. - `install.sh` reads the manifest and downloads what `path` names, so the layout of the download URLs stopped being a contract with every installer already in the wild. The manifest is the contract, and `schema` is how it can change: an installer refuses a version it does not know by name rather than misreading it. - Routing and headers moved into `next.config.ts`, because a JSON config cannot import — the store's origin had been restated beside every rule that used it. ## 0.3.1 `reltio login` now opens a browser and signs you in as a person. Service accounts are unchanged and need no new flag: `--client-credentials` is the old mint, and it is also what happens when there is no terminal. ### Changes - Browser login: a loopback listener on `127.0.0.1`, a handoff to the browser, and a code exchange. The profile gains an `auth` field; absent means `client_credentials`, so existing profiles and their files are untouched. - The login page handles the password, TOTP and SSO branches. - `reltio --version` is stamped at build time with the commit it came from. - The authenticator tries the cache, then a refresh through the login service, then a browser, then an error naming `reltio login`. It can never reach for a client secret it does not have. ### Under the hood - Reltio's auth server has no public client and no `/oauth/authorize`, so a CLI cannot speak RFC 8252 to it: the password grant needs a confidential client's secret. A Next.js app holds that secret and performs the grant, which is the only way to keep it out of a binary anyone can download. This is a stopgap — when the auth server serves `/oauth/authorize` with PKCE, the CLI side is one module and the login service is deleted. - Tokens travel as AES-256-GCM sealed values bound to the auth server that issued them, and the redirect target must be loopback. No credential is ever put in a URL. - The contract was verified against live auth servers rather than assumed. - Releasing refuses a dirty tree. `v0.3.0` was published stamped with a commit that did not contain it, and versioned archives are immutable — there is no fixing one after the fact. ## 0.2.1 Login still works; the compiled catalog no longer guesses auth hosts. It shipped with all 69 environments from Reltio's public list and the auth host guessed as `auth.reltio.com` throughout. That mapping is not public, and one wrong entry sends a credential at the wrong host. ### Changes - The catalog holds only what has been verified by hand: `tst-01` and `prod-usg`. Entries get added as they are confirmed. - `reltio env list` sizes its columns to their contents, the way `profiles` already did. At two rows, a 44-column host field was mostly padding. ### Breaking - `--env` values other than `tst-01` and `prod-usg` are gone. Any other environment is still reachable, but needs `--api-host` and `--auth-host` spelled out. ### Under the hood - The documentation calls `auth.reltio.com` the centralised auth server for customer-facing environments, which is what made the guess look safe. Being wrong once is enough: the failure mode is a client secret posted to a host that should never have seen it. ## 0.2.0 `reltio login` became interactive, and there is now something to install. It asks which environment, which client, and for the secret, then saves the answers as a named profile — so reaching a cluster no longer means knowing which auth service backs it. ### Changes - `reltio login` prompts for the environment, the client and the secret, and saves them as a profile. A request against an environment with no profile builds one first. - `reltio profiles`, `reltio use`, `reltio logout` and `reltio env list` manage the result. - The environment catalog is compiled in, so `--env 361` is enough to reach a cluster. - An installer: `curl -fsSL https://cli.reltio.tools/install.sh | sh` downloads a signed archive, verifies it against its checksum and puts the binary in `~/.local/bin`. ### Breaking - `auth_url` is now `auth_host` and holds a bare origin; `/oauth/token` and `/oauth/checkToken` are constants in the binary. The environment variable is `RELTIO_AUTH_HOST`. A config file still carrying the old key fails loudly rather than being ignored. ### Under the hood - The secret is stored at mode `0600`, and only after the auth server has accepted it — a mistyped secret is not remembered and then silently refused forever after. - The refresh grant is used when the server issues a refresh token, but it is nowhere near load-bearing: redeeming one needs the same client secret a fresh mint does, so it can never remove the need for the secret. ## 0.1.0 The first version does the smallest genuinely useful thing: get an OAuth token, then send an authenticated GET to a Reltio URL. There are no typed per-endpoint commands — `reltio get` covers every endpoint, on a skeleton that already works. ### Changes - `reltio get ` sends an authenticated GET and writes the raw JSON body to stdout, so it composes with `jq`. Every diagnostic goes to stderr. - `reltio login` mints a token with the client credentials grant and caches it until it expires. - `reltio config show` prints the resolved configuration, and `reltio completions` writes a shell completion script. - Configuration resolves from flags, then `RELTIO_*` environment variables, then the config file, in that order. ### Under the hood - Blocking HTTP on purpose. There is no parallel workload yet, and `async` would be a dependency tree bought against a future need. - `reqwest` is built against `rustls` rather than the system OpenSSL, so nothing here needs a native TLS development package to build. - `unwrap()`, `expect()` and `panic!()` are denied outside tests. They are how an error gets silenced instead of modelled.