Skip to content

feat(RHIDP-14129): add intent-based CLI commands and backstage-cli pass-through - #156

Merged
kadel merged 25 commits into
redhat-developer:mainfrom
yangcao77:intent-based-cli
Sep 15, 2026
Merged

kadel merged 25 commits into
redhat-developer:mainfrom
yangcao77:intent-based-cli

Conversation

@yangcao77

@yangcao77 yangcao77 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

https://redhat.atlassian.net/browse/RHIDP-14129

Adds intent-based subcommands and backstage-cli pass-through commands to rhdh-cli

Today, interacting with a running RHDH/Backstage instance from the CLI requires backstage-cli actions execute <pluginId>:<actionName> with internal action IDs and raw JSON input. This is very bad experience for human operations.

This change makes rhdh-cli the single entry point:

# Before: two CLIs, raw action IDs                                                                         
backstage-cli auth login --backend-url https://rhdh.example.com                                            
backstage-cli actions execute catalog:query-catalog-entities --query '{"kind":"Component"}' 
backstage-cli actions execute catalog:query-catalog-entities --query '{"kind":"Template"}'   

# After: one CLI, intent-based commands                                                                    
rhdh-cli auth login --backend-url https://rhdh.example.com                                                 
rhdh-cli catalog list --kind Component   
rhdh-cli template list
           

All intent-based commands support --output json for agent consumption and --instance <name> for multi-instance targeting.

The local metadata file is still going to use the config file for backstage-cli , so that existing backstage-cli user can migrate to use rhdh-cli with no extra effort

  • Commands shell out to backstage-cli, no new backend dependencies
  • Large responses use file redirect to work around Node.js pipe buffer limits
  • Human-readable output (entity tables, search results) by default; --output json for agents
  • Existing plugin export and plugin package commands are unchanged

see recording:

rhdh-cli.mov

…tions related will use backstage-cli

Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
@yangcao77 yangcao77 changed the title [RHIDP-14129] Add intent-based CLI commands and backstage-cli pass-through feat(RHIDP-14129) Add intent-based CLI commands and backstage-cli pass-through Aug 5, 2026
@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel @benwilcock @elsony @durandom FYI

@yangcao77 yangcao77 changed the title feat(RHIDP-14129) Add intent-based CLI commands and backstage-cli pass-through feat(RHIDP-14129): add intent-based CLI commands and backstage-cli pass-through Aug 5, 2026
- Statically import command modules in commands/index.ts instead of
  using require(), since the backstage-cli bundler only follows static
  ESM imports/dynamic import() and silently dropped the require()'d
  files from the packed dist, breaking every command once installed
  from npm (Cannot find module './backstage-passthrough').
- Fix TS2352 in intent-errors.ts by adding a safe getStderr() helper
  instead of casting Error directly to Record<string, unknown>.
- Restrict the PATH used to resolve backstage-cli via `which` to
  directories that aren't group/other-writable, addressing the
  SonarCloud S4036 PATH-search security hotspot in lib/client.ts.
- Extract shared runEntityListAction/runRawAction/runSearchAction
  helpers and a registerPassthroughCommand helper to remove the heavy
  code duplication SonarCloud flagged across catalog/api/template/
  search/docs/backstage-passthrough command files.
- Fix pre-existing lint (no-empty, func-names) and prettier issues so
  the Checks job can get past the linter/prettier steps.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/commands/intent-based-actions/client.ts Fixed
…ctory

Move catalog/api/search/docs/template/backstage-passthrough and their
supporting client/format/intent-errors/helpers modules into
src/commands/intent-based-actions/, mirroring the existing
export-dynamic-plugin/ and package-dynamic-plugins/ layout, with a
single registerIntentCommands() entry point.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/commands/intent-based-actions/client.ts Fixed
SonarCloud S4036 still flagged spawnSync('which', ...) even with a restricted PATH env, since it pattern-matches on shelling out to a path-search utility rather than analyzing the PATH value. Replace it with a direct filesystem walk over PATH entries (skipping group/other-writable directories) and an accessSync executability check, avoiding the flagged pattern entirely.

Co-authored-by: Cursor <cursoragent@cursor.com>

@kadel kadel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two concerns about how backstage-cli is resolved and surfaced.

Resolution: findBackstageCliOnPath walks system PATH looking for a backstage-cli binary, but backstage-cli is not something people typically install as a standalone global binary, so the PATH walk is unlikely to find it. This means the npx -y @backstage/cli fallback is effectively the default path. This fallback silently downloads whatever latest is on npm without user confirmation. rhdh-cli is built against 0.36.3, so the downloaded version could behave differently, and -y suppresses the install prompt. This is a supply chain concern for a CLI meant for production use.

@backstage/cli is already a declared dependency of this project at 0.36.3. Could we resolve the binary from the installed dependency instead of walking PATH or downloading via npx?

Leaking backstage-cli identity: The passthrough commands expose backstage-cli's own output directly. Commander intercepts --help before it reaches backstage-cli, so passthrough commands show empty help with no options. But backstage-cli itself has useful help that's being hidden. Compare:

rhdh-cli auth login --help:

Usage: rhdh-cli auth login [options]

Log in to a Backstage/RHDH instance

Options:
  -h, --help  display help for command

backstage-cli auth login --help:

Usage:
  backstage-cli auth login [flags...]

Flags:
      --backend-url <string>        Backend base URL
  -h, --help                        Show help
      --instance <string>           Name for this instance
      --no-browser                  Do not open browser automatically

rhdh-cli actions execute --help:

Usage: rhdh-cli actions execute [options]

Execute an action

Options:
  -h, --help  display help for command

backstage-cli actions execute --help:

Usage:
  backstage-cli actions execute [flags...] <action-id>

Flags:
  -h, --help                     Show help
      --instance <string>        Name of the instance to use

The rhdh-cli versions hide --backend-url, --no-browser, --instance, and the <action-id> positional argument. Running without --help (e.g., rhdh-cli actions execute with no args) does forward to backstage-cli but then shows backstage-cli branding instead of rhdh-cli.

For human users this is confusing. For AI agents discovering the CLI through --help, it's a blocker since they see no options and can't tell which CLI to use.

@yangcao77

yangcao77 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@kadel

Thanks for the review! Fixed both:

Resolution: backstage-cli is now resolved directly from the installed @backstage/cli dependency (via Node module resolution) instead of walking PATH or falling back to npx. No more supply-chain risk, and it works even with an empty PATH.
Help/branding: Passthrough commands now forward -h/--help to backstage-cli so real flags show up (e.g. rhdh-cli auth login --help now shows --backend-url, --instance, --no-browser, etc.), and all output is rebranded to rhdh-cli instead of backstage-cli.
Pushed the changes, ready for another look.

Signed-off-by: Stephanie <yangcao@redhat.com>
@yangcao77
yangcao77 requested a review from kadel August 17, 2026 15:03

@kadel kadel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the intent-based commands (template, catalog, api, search, docs) in this PR, or should they be a follow-up?

Every intent-based command maps 1:1 to actions execute — for example, rhdh-cli template list is just rhdh-cli actions execute catalog:query-catalog-entities --query '{"kind":"Template"}'. The passthrough layer (auth, actions) already gives users and agents full access to the same functionality.

The intent-based commands add ~1,000 lines with no tests, and one of them (template dry-run) already has a bug where it passes an entity ref as templateYaml instead of actual YAML content. The impact is low — the command just fails with an error, it can't cause any damage — but it shows the risk of shipping this much code without test coverage.

If we want to keep those extra commands they need test coverage

Would it make sense to merge just the passthrough commands (auth, actions, actions sources) first — they're solid and already working — and add the intent-based layer in a follow-up with proper test coverage? The repo already has a Jest setup in src/lib/*.test.ts, and most of the new code (formatting, error handling, entity extraction) is pure functions that are straightforward to test.

Comment thread src/commands/intent-based-actions/template.ts Outdated
Signed-off-by: Stephanie <yangcao@redhat.com>
@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel
Thanks for the thorough review, fixed the --template-ref flag to use --templateYaml. now --template-file <path> reads the file and passes its contents as templateYaml.

On whether we need the intent-based commands in this PR: yes, they're the actual point of this story https://redhat.atlassian.net/browse/RHIDP-14129. The goal is that users and agents should never need to know internal action names or the actions execute <plugin>:<action> syntax, that's exactly what the passthrough layer still requires. The intent-based commands (catalog, api, search, docs, template) are the layer that makes rhdh-cli usable without that knowledge, e.g. rhdh-cli template list instead of rhdh-cli actions execute catalog:query-catalog-entities --query '{"kind":"Template"}'. also the intent clis improves UX on the output, backsatge-cli returns json output, whereas the intent clis now outputs in human readable way, more details can be found in the recording attached in the PR description, showing the before/after of using rhdh-cli with the intent-based commands vs raw actions execute.

I agree the test coverage is needed, I initially did not add any as I didn't see any tests coverage for all other cmds in this repo. I've add some unit tests as part of the PR, the integration tests I have created it as a QE item. https://redhat.atlassian.net/browse/RHIDP-14254

@kadel

kadel commented Aug 21, 2026

Copy link
Copy Markdown
Member

The intent-based commands (catalog, api, search, docs, template) are the layer that makes rhdh-cli usable without that knowledge, e.g. rhdh-cli template list instead of rhdh-cli actions execute catalog:query-catalog-entities --query '{"kind":"Template"}'. also the intent clis improves UX on the output, backsatge-cli returns json output, whereas the intent clis now outputs in human readable way, more details can be found in the recording attached in the PR description, showing the before/after of using rhdh-cli with the intent-based commands vs raw actions execute.

I don't think that for agentic use this matters that much. The original backstage cli commands are well designed for AI use.

If the goal for new commands is mainly human usage, then we need to think a little bit harder about how they look like and what arguments they expose.

For example expecting humans to type JSON strings in terminal as arguments for CLI command is bad UX. Agents can deal with it, but people can't.

Even simple tasks like searching only in Components I have to type JSON

rhdh-cli search "rhdh" --filters '{"kind":"Component"}'

something like this would be much better cli experience:

rhdh-cli search "rhdh" --filter kind=Component

In the template execution it can also get quite complicated.

rhdh-cli template execute \
  --template-ref template:default/register-component \
  --values '{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar","componentOwner":"user:default/default","componentType":"service","componentLifecycle":"production"}'

More natural CLI experience should be something like this.

rhdh-cli template execute \
  --template-ref template:default/register-component \
  --set githubHost=github.com \
  --set githubOrganization=foo \
  --set repositoryName=bar \
  --set componentOwner=user:default/default \
  --set componentType=service \
  --set componentLifecycle=production

# or
rhdh-cli template execute \
  --template-ref template:default/register-component \
  --set githubHost=github.com, githubOrganization=foo, repositoryName=bar, componentOwner=user:default/default, componentType=service, componentLifecycle=production

The errors are currently also not presented in a user-friendly way

rhdh-cli template execute --template-ref template:default/register-component \
  --values '{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar"}'
Error: Backend request failed, 400 Bad Request {"errors":[{"path":[],"property":"instance","message":"requires property \"componentOwner\"","schema":{"title":"Provide information about the new component","required":["componentOwner","componentType","componentLifecycle"],"properties":{"componentName":{"title":"Component Name","type":"string","description":"Name of the created component. If leaved empty the name of the repository will be used."},"componentOwner":{"title":"Owner","description":"Select an owner from the list or enter a reference to a Group or a User","type":"string","ui:field":"EntityPicker","ui:options":{"catalogFilter":{"kind":["Group","User"]}}},"componentType":{"title":"Type","type":"string","description":"The type of component. Well-known and common values: service, website, library.","default":"other"},"componentLifecycle":{"title":"Lifecycle","type":"string","description":"The lifecycle state of the component. Well-known and common values: experimental, production, deprecated.","default":"unknown"}}},"instance":{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar"},"name":"required","argument":"componentOwner","stack":"instance requires property \"componentOwner\""},{"path":[],"property":"instance","message":"requires property \"componentType\"","schema":{"title":"Provide information about the new component","required":["componentOwner","componentType","componentLifecycle"],"properties":{"componentName":{"title":"Component Name","type":"string","description":"Name of the created component. If leaved empty the name of the repository will be used."},"componentOwner":{"title":"Owner","description":"Select an owner from the list or enter a reference to a Group or a User","type":"string","ui:field":"EntityPicker","ui:options":{"catalogFilter":{"kind":["Group","User"]}}},"componentType":{"title":"Type","type":"string","description":"The type of component. Well-known and common values: service, website, library.","default":"other"},"componentLifecycle":{"title":"Lifecycle","type":"string","description":"The lifecycle state of the component. Well-known and common values: experimental, production, deprecated.","default":"unknown"}}},"instance":{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar"},"name":"required","argument":"componentType","stack":"instance requires property \"componentType\""},{"path":[],"property":"instance","message":"requires property \"componentLifecycle\"","schema":{"title":"Provide information about the new component","required":["componentOwner","componentType","componentLifecycle"],"properties":{"componentName":{"title":"Component Name","type":"string","description":"Name of the created component. If leaved empty the name of the repository will be used."},"componentOwner":{"title":"Owner","description":"Select an owner from the list or enter a reference to a Group or a User","type":"string","ui:field":"EntityPicker","ui:options":{"catalogFilter":{"kind":["Group","User"]}}},"componentType":{"title":"Type","type":"string","description":"The type of component. Well-known and common values: service, website, library.","default":"other"},"componentLifecycle":{"title":"Lifecycle","type":"string","description":"The lifecycle state of the component. Well-known and common values: experimental, production, deprecated.","default":"unknown"}}},"instance":{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar"},"name":"required","argument":"componentLifecycle","stack":"instance requires property \"componentLifecycle\""}]}

Another usability problem for human use is that there is no way to easily list what parameters a template requires. The only way to do it is using following command. Which forces people to parse JSON. (without pre-filtering it with jq it makes it even harder to read)

bin/rhdh-cli catalog get --name register-component --kind Template --output json | jq '.spec.parameters'
command output
[
  {
    "title": "Provide information about the GitHub location",
    "required": [
      "githubHost",
      "githubOrganization",
      "repositoryName"
    ],
    "properties": {
      "githubHost": {
        "title": "GitHub hostname",
        "type": "string",
        "description": "Use github.com for GitHub Free, Pro, & Team or specify a hostname of your GitHub Enterprise instance.",
        "default": "github.com"
      },
      "githubOrganization": {
        "title": "GitHub Organization",
        "type": "string"
      },
      "repositoryName": {
        "title": "Repository name",
        "type": "string"
      }
    }
  },
  {
    "title": "Provide information about the new component",
    "required": [
      "componentOwner",
      "componentType",
      "componentLifecycle"
    ],
    "properties": {
      "componentName": {
        "title": "Component Name",
        "type": "string",
        "description": "Name of the created component. If leaved empty the name of the repository will be used."
      },
      "componentOwner": {
        "title": "Owner",
        "description": "Select an owner from the list or enter a reference to a Group or a User",
        "type": "string",
        "ui:field": "EntityPicker",
        "ui:options": {
          "catalogFilter": {
            "kind": [
              "Group",
              "User"
            ]
          }
        }
      },
      "componentType": {
        "title": "Type",
        "type": "string",
        "description": "The type of component. Well-known and common values: service, website, library.",
        "default": "other"
      },
      "componentLifecycle": {
        "title": "Lifecycle",
        "type": "string",
        "description": "The lifecycle state of the component. Well-known and common values: experimental, production, deprecated.",
        "default": "unknown"
      }
    }
  }
]

When testing this I also found bug in catalog list command. When using --fields flag in "human" output style it doesn't show extra fields is specified and KIND and TYPE are there empty if I don't specify them in --fields

rhdh-cli catalog list --kind Component --fields '["metadata.name","metadata.description"]'
NAME                                     KIND             NAMESPACE        TYPE
rhdh                                                      default

in json output it is fine

❯ rhdh-cli catalog list --kind Component --fields '["metadata.name","metadata.description"]'  --output json
{
  "items": [
    {
      "metadata": {
        "name": "rhdh",
        "description": "Red Hat Developer Hub is an enterprise-grade Internal Developer Portal based on Backstage."
      }
    }
  ],
  "totalItems": 1,
  "hasMoreEntities": false
}

To summarize this:
I don't disagree that there is a value in new intent based CLI commands, but if they meant to provide good UX they need a lot more work.
Splitting it into multiple PRs would be a better approach.
First, we introduce wrapped actions command. This already provides all that is needed for agentic use. After that we work on commands for humans, where we design it in a way that actually creates nice UX.

@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel that sounds reasonable.
I've created a new PR for wrapping the auth & actions commands: #167

I will leave this branch & PR continue working on the intent clis for UX improvement based on your review comments.

Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel so I pushed a new commit for your suggested UX improvement on the input format, so they no longer require raw JSON:

here is the summary:

Summary

1.template execute --values

Before:

rhdh-cli template execute --template-ref template:default/register-component \
  --values '{"githubHost":"github.com","githubOrganization":"foo","repositoryName":"bar","componentOwner":"user:default/default","componentType":"service","componentLifecycle":"production"}'

Now:

rhdh-cli template execute --template-ref template:default/register-component \
  --value githubHost=github.com \
  --value githubOrganization=foo \
  --value repositoryName=bar \
  --value componentOwner=user:default/default \
  --value componentType=service \
  --value componentLifecycle=production

2. search --filters

Before:

rhdh-cli search "rhdh" --filters '{"kind":"Component"}'

Now:

rhdh-cli search "rhdh" --filter kind=Component

3. catalog list --fields, and also the filtered output

Before:

rhdh-cli catalog list --kind Component --fields '["metadata.name","metadata.description"]'
# human output ignored --fields entirely:
NAME    KIND    NAMESPACE    TYPE
rhdh                         default

After:

rhdh-cli catalog list --kind Component --fields metadata.name,metadata.description
# human output now actually reflects the request:
NAME    DESCRIPTION
rhdh    Developer Hub

4. catalog list --filter, key=value, merged with --kind/--type, JSON kept as --filters

Before:

--filters '{"metadata.namespace":"default", "spec.lifecycle": "production", "kind": "Component"}'

Now:

rhdh-cli catalog list \
  --kind Component \
  --filter spec.lifecycle=production \
  --filter metadata.namespace=default

merged on top of whatever --kind/--type already built, with --filters <json> as the escape hatch for anything not expressible as flat equality. --filter is now consistently the simple form and --filters (plural) the raw-JSON fallback, matching the convention used in search.

5. search --types

Before:

--types '["techdocs","software-catalog"]'

Now:

--types techdocs,software-catalog

6. template execute / dry-run --secret — key=value alongside --secrets

Before:

--secrets '{"token":"abc"}'

Now:

--secret token=abc` (repeatable),

`--secrets <json>` kept as fallback.

7. catalog validate --entity-file <path>

--entity <yaml> still works, but you can now do --entity-file ./catalog-info.yaml and it reads the file instead of requiring --entity "$(cat entity.yaml)".

@yangcao77

yangcao77 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

@kadel Re showing the template params and also the user-friendly error, as long as the bug https://redhat.atlassian.net/browse/RHDHBUGS-3698 you created. I would like to use follow up PR to address those issue.

here is a analysis from claude

Problem A — No easy way to see a template's inputs

Today the only way to discover what --value flags a template needs is:

rhdh-cli catalog get --name register-component --kind Template --output json | jq '.spec.parameters'

i.e. fetch the whole entity and hand-parse nested JSON.

What to build: a new read-only subcommand, e.g. template params (or template show).

  • Data source: wraps catalog:get-catalog-entity (kind Template), reads spec.parameters.
  • Key wrinkle: spec.parameters comes in two shapes — a single object ({required, properties}) or an array of
    "step" objects (register-component has 2 steps/pages). The renderer must normalize both.
  • Human output: one row per input — NAME · REQUIRED · TYPE · DEFAULT · DESCRIPTION, grouped by step title.
    Enum/well-known values shown where present. Directly tells the user which --value key=value flags to pass.
  • JSON output: pass through the normalized parameters.
  • New code: a subcommand in template.ts + a formatTemplateParams() formatter in format.ts (unit-testable — TDD
    lands here).

Decision needed: command name (params / show / describe) and input flag — reuse --template-ref (consistent with
execute) vs --name/--kind (consistent with catalog get). Leaning template show --template-ref ….

Problem B — Validation errors are an unreadable JSON dump

template execute with missing required inputs currently prints the raw scaffolder 400 payload — a multi-hundred-character
{"errors":[…full schema…]} blob.

What to build: parse that payload into a friendly message.

  • Where: the payload is one JSON object embedded after ... 400 Bad Request {…}. Extract the trailing {...},
    JSON.parse, read errors[].

  • Nice bonus: each error object already embeds the field's schema (title + description), so we can render friendly
    labels without a second fetch:

    Error: Template input validation failed
    
    Missing required inputs:
      - componentOwner (Owner) — Select an owner from a Group or User
      - componentType (Type)
      - componentLifecycle (Lifecycle)
    
    Try: rhdh-cli template show --template-ref template:default/register-component
    
  • New code: a helper (shared by execute and dry-run) that detects & formats the {errors:[]} shape, with a
    fallback to the current handleCommandError when parsing fails. Best placed in intent-errors.ts or a small
    scaffolder-errors.ts.

  • Risk: parsing an error string is brittle (depends on backstage-cli output format). Mitigated by: always falling back
    to the raw message, and testing against the real captured payload from the PR comment.

How they connect

They pair naturally: the friendly error in B points the user at the command from A. Build A first so B can reference
it.

Effort / risk / sequencing

Item Effort Risk Notes
A: template show Medium Low Read-only; formatter is TDD-friendly
B: friendly errors Medium Medium Brittle string parsing → needs real-payload tests + fallback

PR recommendation: clean second PR — also satisfies "split into multiple PRs" point

@yangcao77
yangcao77 requested a review from kadel August 27, 2026 18:38
@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel PTAL

@kadel

kadel commented Sep 8, 2026

Copy link
Copy Markdown
Member

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:16 AM UTC · Completed 11:57 AM UTC

Commit: df49483 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.30

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Sep 8, 2026
Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
@yangcao77

yangcao77 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

@kadel I've addressed the consistency concerns you raised. Here's a point-by-point response:


  1. Plugin Dependencies (TechDocs Commands)

Your Concern: Commands like docs list/get/coverage require the optional techdocs-mcp-extras plugin not in default RHDH installations.

Response: This is now documented in README and also CLI reference, I will also document this in the release doc. The techdocs-mcp-extras plugin is RHDH-specific (from rhdh-plugins repository) and provides TechDocs content retrieval capabilities beyond what upstream Backstage offers.

Documentation:

  • Command descriptions explicitly state: "RHDH only, via techdocs-mcp-extras"
  • Error messages guide users: "Use an RHDH instance with techdocs-mcp-extras enabled"
  • README.md and CLI.md both document this requirement

You reported the docs get does not work properl. I have tried, docs get work correctly against the dev cluster

Input:
rhdh-cli docs get --entity-ref aimodelserverapi:default/gmontero-sklearn-iris --output json

Output:

 {
   "entityRef": "aimodelserverapi:default/gmontero-sklearn-iris",
   "name": "gmontero-sklearn-iris",
   "title": "gmontero-sklearn-iris",
   "kind": "aimodelserverapi",
   "namespace": "default",
   "content": "<full HTML/markdown content of the TechDocs page>",
   "pageTitle": "Model Card",
   "path": "index.html",
   "contentType": "text",
   "lastModified": "+058660-09-23T04:37:55.000Z",
   "metadata": {
     "lastUpdated": "+058660-09-23T04:37:55.000Z",
     "buildTimestamp": 1788986579875,
     "siteName": "Model Card",
     "siteDescription": "None"
   }
 }

Verification that actions are available:

$ rhdh-cli actions list | grep techdocs
── techdocs-mcp-extras ─────────────────────────────────────────────────────────
  techdocs-mcp-extras:fetch-techdocs               Fetch TechDoc Entities
  techdocs-mcp-extras:analyze-techdocs-coverage    Analyze TechDocs Coverage
  techdocs-mcp-extras:retrieve-techdocs-content    Retrieve TechDocs Content

All TechDocs commands work correctly on RHDH instances with the plugin installed.

Setup Documentation Added:

The comprehensive CLI documentation (src/commands/intent-based-actions/CLI.md) now includes complete RHDH instance setup instructions:

  1. Enable auth plugin (backstage-plugin-auth)
  2. Enable OAuth2 server endpoints
  3. Enable TechDocs MCP extras plugin (optional)
  4. Register action sources

2. Inconsistent Entity References

Your Concern: Different reference formats across commands:

  • catalog get: --name/--kind/--namespace (3 flags)
  • docs get: --entity-ref (single ref)
  • template execute: --template-ref (single ref)

Response: Not changed - keeping current design.

Rationale:

  • catalog get uses the catalog:get-catalog-entity action, which accepts name, kind, namespace as separate parameters
  • docs get uses techdocs-mcp-extras:retrieve-techdocs-content, which expects entityRef format
  • template execute uses scaffolder:execute-template, which expects templateRef format
  • Each command maps directly to its underlying action's parameter requirements
  • Changing this would require parameter transformation logic and doesn't provide significant UX benefit

3. Flag Inconsistency

Your Concern:

  • catalog list --kind Component vs docs list --entity-type Component
  • catalog list has --filter/--filters but api list and template list don't

Response:FIXED

Changes Made:

a) Standardized flag naming

  • Changed docs list --entity-type to docs list --kind for consistency
  • All list commands now use --kind to filter by entity kind

b) Removed redundant JSON input flags

  • Removed --filters from catalog list
  • Removed --values from template execute and template dry-run
  • Removed --secrets from template execute
  • Only support repeatable key=value flags: --filter, --value, --secret

Rationale: More CLI-idiomatic than JSON strings; reduces redundancy

c) Added --filter to api list and template list

Both commands now support the same filtering as catalog list:

# api list with filters
rhdh-cli api list --type openapi --filter spec.owner=team-a

# template list with filters
rhdh-cli template list --filter metadata.tags=nodejs

Before:

# Inconsistent
catalog list --kind Component --filter spec.lifecycle=production --filters '{...}'
api list --type openapi  # No --filter support
template list  # No --filter support
template execute --value x=y --values '{"a":"b"}'  # Redundant flags

After:

# Consistent
catalog list --kind Component --filter spec.lifecycle=production
api list --type openapi --filter spec.owner=team-a
template list --filter metadata.tags=nodejs
template execute --value x=y --value a=b

4. Missing Functionality

Your Concern:

  • template execute requires --value flags, but some templates accept no parameters
  • api list and template list lack --filter exposure

Response:BOTH FIXED

a) --filter added to both commands

See consistency improvements in section 3c above.

b) Template values are now optional

Changes:

  • template execute no longer requires --value flags
  • template dry-run no longer requires --value flags
  • Templates can now be executed without any parameters

Before:

# ERROR: --value required
rhdh-cli template execute --template-ref template:default/my-template

After:

# Works - values are optional
rhdh-cli template execute --template-ref template:default/my-template

# Also works with values when needed
rhdh-cli template execute \
  --template-ref template:default/my-template \
  --value name=app \
  --value owner=team-a

5. Design Recommendation

Your Concern: "Create design document describing how all commands will look like with all arguments and flags before jumping to implementation."

Response:Design documentation has been created and updated

Documentation Created

CLI documentation (src/commands/intent-based-actions/CLI.md): Comprehensive reference combining setup, commands, and workflows
I'm drafting a doc that will also send to doc team for writing up the release doc. it will include all user guidance on installation and configuration, also including action mapping table which shows every command, its underlying action, and supported flags

Consistent Design Patterns

All commands now follow consistent patterns:

  • All list commands support: --filter (repeatable), --limit, --output, --instance
  • All commands use --kind consistently (not --entity-type)
  • All commands have comprehensive --help documentation
  • All commands support both human-readable and JSON output modes
  • All repeatable flags use key=value format, not JSON strings

Summary of Changes

✅ Implemented (Consistency Improvements)

Change Before After
Flag naming docs list --entity-type Component docs list --kind Component
Filter support api list --type openapi (no filters) api list --type openapi --filter spec.owner=team-a
Filter support template list (no filters) template list --filter metadata.tags=nodejs
Filter flags --filter and --filters on catalog list Only --filter (repeatable) on all commands
Template value flags --value and --values Only --value (repeatable), optional
Template secret flags --secret and --secrets Only --secret (repeatable)
Limit support docs list (missing --limit) docs list --limit 10
Template values required --value required --value optional

✅ Verified Working

  • docs get works correctly with techdocs-mcp-extras plugin (test results above)
  • docs list works correctly and now supports --limit and --kind
  • docs coverage works correctly
  • All actions are available on RHDH instances with the required plugin

❌ Not Changed (By Design)

Item Reason
Entity reference formats Each command maps to its underlying action's parameter format; changing would add unnecessary transformation logic
TechDocs RHDH-only requirement Plugin is RHDH-specific by design; already documented extensively with setup instructions

RHDH Branding Improvements

In addition to consistency fixes, we've improved RHDH branding:

  1. Auth URL flag: auth login now accepts both --rhdh-url and --backend-url
  2. Example URLs: All documentation uses rhdh.example.com instead of backstage.example.com
  3. Error messages: Reference "RHDH instance" instead of "Backstage instance"

Example:

# Both work
rhdh-cli auth login --rhdh-url https://rhdh.example.com
rhdh-cli auth login --backend-url https://rhdh.example.com

Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
@kadel

kadel commented Sep 10, 2026

Copy link
Copy Markdown
Member

You reported the docs get does not work properl. I have tried, docs get work correctly against the dev cluster

I did not say that it doesn't work properly. It works. I made a suggestion that we also need to have a way to trigger TechDocs build from CLI. Otherwise, users or agents won't be able to get docs without going to web ui first.

2. Inconsistent Entity References

Your Concern: Different reference formats across commands:

  • catalog get: --name/--kind/--namespace (3 flags)
  • docs get: --entity-ref (single ref)
  • template execute: --template-ref (single ref)

Response: Not changed - keeping current design.

Rationale:

  • catalog get uses the catalog:get-catalog-entity action, which accepts name, kind, namespace as separate parameters
  • docs get uses techdocs-mcp-extras:retrieve-techdocs-content, which expects entityRef format
  • template execute uses scaffolder:execute-template, which expects templateRef format
  • Each command maps directly to its underlying action's parameter requirements
  • Changing this would require parameter transformation logic and doesn't provide significant UX benefit

It doesn't matter what action it uses. We are exposing completely new commands to users. The whole point of introducing new commands is to provide nice human friendly UX. If you keep it as it is and just pass flags down to actions than what is point of introducing new commands? Users can run those actions directly.

Comment thread docs/Intent-Based-CLI.md
Comment thread src/commands/intent-based-actions/CLI.md Outdated
@kadel

kadel commented Sep 10, 2026

Copy link
Copy Markdown
Member
  • Auth URL flag: auth login now accepts both --rhdh-url and --backend-url

this feels a bit redundant, --backend-url is already generic term, i don't think we need to have duplicated rhdh-url flag

@yangcao77

yangcao77 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

I did not say that it doesn't work properly. It works. I made a suggestion that we also need to have a way to trigger TechDocs build from CLI. Otherwise, users or agents won't be able to get docs without going to web ui first.

You are right on that, I misread the comment.
added docs build, and also added proper instructions on errors when fail to retrieve a doc.

this feels a bit redundant, --backend-url is already generic term, i don't think we need to have duplicated rhdh-url flag

ahh. I somehow thought it was --backstage-url. removed rhdh-url and use --backend-url as before

also updated the entity references as suggested

Replaced --entity-ref flag with positional arguments and added ambiguity detection via catalog queries.

Before:

  # Multiple ways to specify entity, inconsistent across commands
  rhdh-cli catalog get --name my-service --kind component
  rhdh-cli api get-spec --name my-api
  rhdh-cli docs get --entity-ref component:default/my-service

After:

# Consistent positional argument across all commands
rhdh-cli catalog get my-service
rhdh-cli api get-spec my-api
rhdh-cli docs get component:default/my-service

Key Features:

  1. Flexible Reference Format: [kind:][namespace/]name
  • Short name: my-service
  • With namespace: default/my-service
  • Full reference: component:default/my-service
  1. Ambiguity Detection:
  • Queries catalog when short name is provided
  • Exactly 1 match: Uses it automatically
  • 0 matches: Error "not found"
  • Multiple matches: Lists all matches, asks for full reference
  1. Smart Defaults:
  • api get-spec → defaults to kind=api
  • template execute → defaults to kind=template
  • catalog get, docs get/build → require kind or query catalog
  1. Disambiguation Flags:
  • --kind and --namespace flags still available to filter/override
  • Example: rhdh-cli catalog get my-service --kind component --namespace production

Affected Commands:

  • catalog get
  • api get-spec
  • docs get
  • docs build
  • template execute

Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>
Signed-off-by: Stephanie <yangcao@redhat.com>

@kadel kadel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for implementing unified entity references it makes command much easier to use.

docs search without plugin-search-backend-module-techdocs still fails with error 400

❯ ./bin/rhdh-cli docs search "rhdh"
Error: Invalid input to action "search:query"; caused by [

Try:
  rhdh-cli docs search "getting started"

Instead of throwing missleading 400 error it should match docs get/list behavior which correctly suggests that it might be missing plugin.

Error handling in docs get command is weird:

Trying to get docs for non-existing component says that TechDocs were not found, not that entity was not found and it suggests building docs for non-existing entity and on top of that as exit code it returns non-error 0, instead of expected 1

❯ ./bin/rhdh-cli docs get system:default/non-existing
TechDocs content not found for system:default/non-existing
The documentation may not have been built yet.

Trigger build with: rhdh-cli docs build system:default/non-existing
Or visit the TechDocs page in RHDH to trigger a build.

❯ echo $?
0

running it on entity that exists but doesn't have docs built:

❯ ./bin/rhdh-cli docs get system:default/rhdh-local
TechDocs content not found for system:default/rhdh-local
The documentation may not have been built yet.

Trigger build with: rhdh-cli docs build system:default/rhdh-local
Or visit the TechDocs page in RHDH to trigger a build.
❯ echo $?
0

the output is ok, but it returns non-error exit code, i would also expect that this should return error 1, because operation was not successful.

After triggering built it correctly returns docs with non-error exit code (0), this looks OK this is what I would expect.

❯ curl -H "Authorization: Bearer $(./bin/rhdh-cli auth print-token)" http://localhost:7007/api/techdocs/sync/default/system/rhdh-local
<output>

❯ ./bin/rhdh-cli docs get system:default/rhdh-local
<output>


❯ echo $?
0

Comment thread src/commands/intent-based-actions/docs.ts Outdated
@yangcao77

yangcao77 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed these issues:

  • docs search now suggests enabling the search backend module for techdocs, same as docs get did for the techdoc-mcp-extras.
  • docs get validates the entity and exits with code 1 when docs are unavailable.
  • docs build now calls the TechDocs sync endpoint and waits for success.

see below for the output of docs search & docs get

$ ./bin/rhdh-cli docs search --help
Usage: rhdh-cli docs search [options] <term...>

Search TechDocs content (requires search-backend-module-techdocs)

$ ./bin/rhdh-cli docs search "rhdh"
Error: Invalid input to action "search:query"; caused by [

Try:
  Enable search-backend-module-techdocs on the RHDH instance.
# success case

$ ./bin/rhdh-cli docs search "rhdh"
Remote Cluster configuration
  /docs/default/template/codegen/template-remote-cluster/
  Bring your own cluster (remote deployments) ¶ This guide explains how to deploy application workloads to a remote OpenSh...

Prerequisites
  /docs/default/template/codegen/template-remote-cluster/#prerequisites
  Two OpenShift clusters: Host cluster: runs RHDH, Argo CD, Tekton Remote cluster: receives the application deployment CLI...

Prerequisites
  /docs/default/template/codegen/template-prerequisites/
  Template Requirements ¶ This document outlines the plugins and configurations required to use the AI Lab templates effec...

Template Requirements
  /docs/default/template/codegen/template-prerequisites/#template-requirements
  This document outlines the plugins and configurations required to use the AI Lab templates effectively in your Red Hat D...


$ ./bin/rhdh-cli docs get system:default/non-existing
Error: Entity not found: system:default/non-existing
yangcao-mac:rhdh-cli stephanie$ echo $?
1

$ ./bin/rhdh-cli docs get chatbot
TechDocs content not found for Template:default/chatbot
The documentation may not have been built yet.

Trigger build with: rhdh-cli docs build Template:default/chatbot
Or visit the TechDocs page in RHDH to trigger a build.
yangcao-mac:rhdh-cli stephanie$ echo $?
1


$ ./bin/rhdh-cli docs build Template:default/chatbot
✓ TechDocs build completed for Template:default/chatbot


$ ./bin/rhdh-cli docs get chatbot
     Template Documentation    :root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"} \_\_md\_scope=new URL(".",location),\_\_md\_hash=e=>\[...e\].reduce(((e,\_)=>(e<<5)-e+\_.charCodeAt(0)),0),\_\_md\_get=(e,\_=localStorage,t=\_\_md\_scope)=>JSON.parse(\_.getItem(t.pathname+"."+e)),\_\_md\_set=(e,\_,t=localStorage,a=\_\_md\_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(\_))}catch(e){}}  

[Skip to content](#chatbot-software-template-overview)

[](. "Template Documentation")

Template Documentation

Overview

[


... ...

@sonarqubecloud

Copy link
Copy Markdown

@yangcao77

Copy link
Copy Markdown
Contributor Author

@kadel can you help merge this PR? I do not have permission

@kadel
kadel merged commit 7dac197 into redhat-developer:main Sep 15, 2026
36 checks passed
@fullsend-ai-retro

fullsend-ai-retro Bot commented Sep 15, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 2:30 PM UTC · Completed 2:44 PM UTC

Commit: e52a73e · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.43

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #156 — Intent-based CLI commands

Timeline

  • Aug 5: PR opened (+4621 lines, 23 files, 13 new) by yangcao77
  • Aug 13–21: kadel provides three rounds of human review catching supply chain risk (PATH-walk resolution of backstage-cli), UX design flaws (JSON strings as CLI args, inconsistent entity refs, hidden --help), and functional bugs (template dry-run passing entity ref instead of YAML content, broken --fields flag)
  • Aug 27: Author pushes UX improvements (key=value flags, --filter syntax)
  • Sep 8: kadel triggers /fs-review → review agent runs (run 34219748367), 41 min, $8.30, opus/high, posts CHANGES_REQUESTED with 14 findings
  • Sep 9–11: Author addresses both agent and human feedback across 8 commits
  • Sep 14: kadel approves
  • Sep 15: PR merged

Review quality delta

The agent and human reviews are almost entirely complementary with near-zero overlap (1 partial overlap out of 35 combined findings). The agent excelled at static code analysis: runtime crash in runSearchAction, secrets visible in process args, null handling edge cases, shell-escaping defense-in-depth, DRY violations. The human excelled at design judgment: supply chain risk, CLI UX (JSON args hostile to humans, inconsistent entity refs), runtime verification (4 functional bugs found by actually executing commands), and architectural reasoning (plugin dependency handling, missing docs build command).

The biggest quality gap: the agent missed every design-level finding (6 UX issues, 3 architecture issues). It treated the code as "is this implementation correct?" rather than "is this the right design?" — because AGENTS.md encodes architecture and code organization patterns but not CLI UX design principles.

Rework

25 commits over 41 days with 7+ addressing review feedback. The human suggested splitting the PR on Aug 19 (day 14); continuing as a monolith extended the review cycle. The agent review on Sep 8 (day 34) added mechanical findings but the highest-impact issues had already been identified by the human.

Evidence for existing issues

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants