Skip to content

feat(cli): add dynamic integration discovery#131

Merged
khaliqgant merged 1 commit into
mainfrom
codex/dynamic-integration-discovery
May 11, 2026
Merged

feat(cli): add dynamic integration discovery#131
khaliqgant merged 1 commit into
mainfrom
codex/dynamic-integration-discovery

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

Summary

  • add relayfile integration available and relayfile integration search
  • fetch the dynamic Cloud catalog with ?dynamic=true, cache it for one hour, and support --refresh
  • filter locally by --search and --backend so users can discover Nango providers and Composio toolkits

Verification

  • go test ./cmd/relayfile-cli

Note: package-lock.json was already dirty in this worktree and is not part of this PR.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ac9934e4-0dd0-4953-afd5-ecf9b952c30b

📥 Commits

Reviewing files that changed from the base of the PR and between 6af8eb7 and 868088e.

📒 Files selected for processing (3)
  • cmd/relayfile-cli/catalog_test.go
  • cmd/relayfile-cli/main.go
  • cmd/relayfile-cli/main_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/relayfile-cli/main_test.go
  • cmd/relayfile-cli/main.go

📝 Walkthrough

Walkthrough

Adds integration available and integration search commands, expands integration catalog entries with backend/backends/sources/authMode/categories/docs fields, implements dynamic HTTP catalog fetching with optional Bearer auth and cache-keyed on-disk caching, and adds filtering/formatting plus tests.

Changes

Dynamic Integration Catalog & Search

Layer / File(s) Summary
Integration Catalog Entry Schema
cmd/relayfile-cli/main.go
integrationCatalogEntry expanded to include backend/backends, sources, authMode, categories, and docs fields.
CLI Help Text
cmd/relayfile-cli/main.go
printUsage updated with integration available [--search QUERY] [--backend BACKEND] [--json] and integration search QUERY [--backend BACKEND] [--json]; integration description updated.
Integration Command Dispatch
cmd/relayfile-cli/main.go
runIntegration routes available/catalog/providers and search to new handlers.
Cache Pathing
cmd/relayfile-cli/main.go
Cache filenames/read logic become cache-key aware for ?dynamic=true.
Cache Invalidation
cmd/relayfile-cli/main.go
invalidateIntegrationCatalogCache removes both dynamic and regular caches.
Catalog Cache & Loading
cmd/relayfile-cli/main.go
Added dynamicIntegrationCatalogCacheKey and loadAvailableIntegrationCatalog to load cached dynamic catalogs or fetch fresh ones (supports --refresh).
HTTP Catalog Fetching & Caching
cmd/relayfile-cli/main.go
fetchIntegrationCatalogPath issues GET requests (supports dynamic=true), accepts optional Bearer auth, unmarshals JSON, writes successful results to the provided cache key, and falls back to the built-in catalog on failure.
Integration Search & Available Commands
cmd/relayfile-cli/main.go
runIntegrationSearch and runIntegrationAvailable parse flags, load the available catalog, filter by backend/backends and query across id/displayName/configKey/vfsRoot/backend/authMode/docs/backends/sources/categories, and output JSON or tabular results (backends sorted, comma-separated).
Filtering & Formatting Helpers
cmd/relayfile-cli/main.go
Added helpers for backend support checks, multi-field case-insensitive substring query matching, and backend list formatting.
Integration Tests for Dynamic Catalog
cmd/relayfile-cli/main_test.go, cmd/relayfile-cli/catalog_test.go
Added tests: TestIntegrationAvailableSearchesDynamicCatalog, TestIntegrationAvailableFallsBackWhenDynamicCatalogUnavailable, TestIntegrationAvailableUsesDynamicCatalogCache, TestIntegrationCatalogCachesDynamicAndRegularSeparately; updated cache-file existence assertions in catalog tests.

Sequence Diagram

sequenceDiagram
  participant CLI as CLI User
  participant Router as runIntegration
  participant Loader as loadAvailableIntegrationCatalog
  participant Cache as DiskCache
  participant Fetcher as fetchIntegrationCatalogPath
  participant HTTP as RemoteCatalogAPI
  participant Filter as filterIntegrationCatalog

  CLI->>Router: integration search <query>
  Router->>Loader: loadAvailableIntegrationCatalog(refresh=false)
  Loader->>Cache: check dynamic cache key
  Cache-->>Loader: cache miss
  Loader->>Fetcher: fetchIntegrationCatalogPath(dynamic=true)
  Fetcher->>HTTP: GET /api/v1/integrations/catalog?dynamic=true
  HTTP-->>Fetcher: 200 OK + catalog JSON
  Fetcher->>Cache: write dynamic cache key
  Fetcher-->>Loader: providers list
  Loader-->>Filter: filterIntegrationCatalog(backend, query)
  Filter-->>Router: matching entries
  Router-->>CLI: display results (JSON or table)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibbled through the catalog, fresh and bright,
Cached the keys that glimmer in the night,
Queries whispered, backends danced in rows,
Providers lined up tidy, tails aglow,
~🥕 hopped away, a happy rabbit delight

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding dynamic integration discovery functionality to the CLI.
Description check ✅ Passed The description is directly related to the changeset, providing a clear summary of the new commands, caching behavior, filtering options, and verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/dynamic-integration-discovery

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/relayfile-cli/main.go (1)

837-895: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the fallback catalog without also failing the command.

This helper returns fallbackIntegrationCatalog() and a non-nil error on transport/HTTP/JSON failures. Callers like runIntegrationAvailable abort on that error, so integration available/search still hard-fails instead of degrading to the built-in catalog. If fallback is intentional here, return a nil error when the fallback is usable, or split diagnostics from the returned error path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 837 - 895, The helper
fetchIntegrationCatalogPath currently returns fallbackIntegrationCatalog()
together with non-nil errors (e.g., on url.Parse, http.NewRequestWithContext,
http.Client.Do, io.ReadAll, json.Unmarshal, and the non-2xx apiError branch)
which causes callers like runIntegrationAvailable to abort; change those paths
to return the fallback catalog with a nil error when the fallback is usable
instead of propagating the error (i.e., replace occurrences of "return
fallbackIntegrationCatalog(), err" and the non-2xx "return
fallbackIntegrationCatalog(), &apiError{...}" with "return
fallbackIntegrationCatalog(), nil" so callers can degrade gracefully), while
preserving normal error returns where no fallback exists; keep
writeIntegrationCatalogCache and successful json.Unmarshal behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 1410-1415: The wrapper runIntegrationSearch assumes args[0] is the
query which breaks when callers pass flags before the positional (e.g.
--backend); update runIntegrationSearch to normalize/parse flags from args
before pulling the query and then reconstruct the arg list for
runIntegrationAvailable (i.e., parse flags like --backend and --json out of
args, identify the first non-flag token as QUERY, and call
runIntegrationAvailable with append([]string{"--search", QUERY},
remainingArgs...) so flags are preserved and the correct positional query is
used).
- Around line 397-398: Update the help/usage strings to advertise the --refresh
flag: add " [--refresh]" to the top-level usage line that currently shows
"relayfile integration available [--search QUERY] [--backend BACKEND] [--json]"
and also to the "relayfile integration search QUERY [--backend BACKEND]
[--json]" string so both "integration available" and "integration search"
mention --refresh; also apply the same change to the duplicate usage text around
the other occurrence referenced (lines showing the same two usage strings near
1412-1413) so the cache-bypass flag is discoverable everywhere.

---

Outside diff comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 837-895: The helper fetchIntegrationCatalogPath currently returns
fallbackIntegrationCatalog() together with non-nil errors (e.g., on url.Parse,
http.NewRequestWithContext, http.Client.Do, io.ReadAll, json.Unmarshal, and the
non-2xx apiError branch) which causes callers like runIntegrationAvailable to
abort; change those paths to return the fallback catalog with a nil error when
the fallback is usable instead of propagating the error (i.e., replace
occurrences of "return fallbackIntegrationCatalog(), err" and the non-2xx
"return fallbackIntegrationCatalog(), &apiError{...}" with "return
fallbackIntegrationCatalog(), nil" so callers can degrade gracefully), while
preserving normal error returns where no fallback exists; keep
writeIntegrationCatalogCache and successful json.Unmarshal behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: efaea5a0-c96a-4093-9286-738c32af9581

📥 Commits

Reviewing files that changed from the base of the PR and between 964f904 and dffd9c3.

📒 Files selected for processing (2)
  • cmd/relayfile-cli/main.go
  • cmd/relayfile-cli/main_test.go

Comment thread cmd/relayfile-cli/main.go Outdated
Comment thread cmd/relayfile-cli/main.go
@khaliqgant khaliqgant force-pushed the codex/dynamic-integration-discovery branch from dffd9c3 to d371022 Compare May 11, 2026 11:19
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread cmd/relayfile-cli/main.go
Comment on lines +823 to +831
func loadAvailableIntegrationCatalog(cloudAPIURL string, refresh bool) ([]integrationCatalogEntry, error) {
cacheKey := dynamicIntegrationCatalogCacheKey(cloudAPIURL)
if !refresh {
if entry, ok := readIntegrationCatalogCache(); ok && cachedCatalogIsFresh(entry, cacheKey) {
return entry.Providers, nil
}
}
return fetchIntegrationCatalogPath(cloudAPIURL, "", true, cacheKey)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Dynamic and regular catalog caches share a single-entry file, mutually evicting each other

Both loadIntegrationCatalog and loadAvailableIntegrationCatalog read from and write to the same single-entry cache file (catalog-cache.json via readIntegrationCatalogCache/writeIntegrationCatalogCache). They use different APIURL values as cache keys (cloudAPIURL vs cloudAPIURL + "?dynamic=true"), so each write overwrites the other's cached entry. For example: a user runs relayfile integration available (stores dynamic entry) → then relayfile setup calls loadIntegrationCatalog which finds a key mismatch, refetches, and overwrites the dynamic cache. A subsequent integration available call will again miss the cache. The test TestIntegrationAvailableUsesDynamicCatalogCache only verifies two consecutive dynamic calls and never interleaves with loadIntegrationCatalog, so it doesn't catch this mutual eviction.

Prompt for agents
The functions loadIntegrationCatalog and loadAvailableIntegrationCatalog both use the same single-file cache (catalog-cache.json) via readIntegrationCatalogCache/writeIntegrationCatalogCache (cmd/relayfile-cli/main.go:774, 786). They store different APIURL values as discriminators but only one entry can exist in the file at a time, so writes from one function evict the other's cached data.

Possible fixes:
1. Use separate cache files (e.g. catalog-cache.json and catalog-dynamic-cache.json) so each catalog type has its own independent cache slot.
2. Change the cache format to store multiple entries keyed by APIURL, so both entries can coexist.
3. Accept the current behavior as a tradeoff if the alternating usage pattern is considered rare.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
cmd/relayfile-cli/main.go (1)

819-831: 💤 Low value

Consider separate cache storage for dynamic vs non-dynamic catalogs.

The dynamic catalog (?dynamic=true) and the regular catalog share a single cache file but use different cache keys. When integration available writes the dynamic catalog and then setup runs (or vice versa), the cache key mismatch causes a fresh network fetch despite the data being recent.

This isn't incorrect—both paths get valid data—but it means users who alternate between setup and integration available/search will experience unnecessary network requests.

A straightforward fix would be separate cache files (e.g., catalog-cache.json and catalog-cache-dynamic.json), or a multi-entry cache keyed by URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 819 - 831, The cache currently mixes
dynamic and non-dynamic catalogs because loadAvailableIntegrationCatalog uses
dynamicIntegrationCatalogCacheKey but readIntegrationCatalogCache stores/reads a
single cache file; change the cache storage to be keyed by cacheKey (or use two
files) so dynamic and regular catalogs are stored separately: update
readIntegrationCatalogCache and its writer to accept a cacheKey (or to choose
filename like catalog-cache-dynamic.json when
dynamicIntegrationCatalogCacheKey(...) contains "?dynamic=true"), ensure
cachedCatalogIsFresh still checks freshness per cacheKey, and keep
fetchIntegrationCatalogPath calls passing the same cacheKey so reads/writes
reference the same per-key cache entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 819-831: The cache currently mixes dynamic and non-dynamic
catalogs because loadAvailableIntegrationCatalog uses
dynamicIntegrationCatalogCacheKey but readIntegrationCatalogCache stores/reads a
single cache file; change the cache storage to be keyed by cacheKey (or use two
files) so dynamic and regular catalogs are stored separately: update
readIntegrationCatalogCache and its writer to accept a cacheKey (or to choose
filename like catalog-cache-dynamic.json when
dynamicIntegrationCatalogCacheKey(...) contains "?dynamic=true"), ensure
cachedCatalogIsFresh still checks freshness per cacheKey, and keep
fetchIntegrationCatalogPath calls passing the same cacheKey so reads/writes
reference the same per-key cache entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 622efe87-edcf-414a-8fdf-7e425c2feec5

📥 Commits

Reviewing files that changed from the base of the PR and between dffd9c3 and d371022.

📒 Files selected for processing (2)
  • cmd/relayfile-cli/main.go
  • cmd/relayfile-cli/main_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/relayfile-cli/main_test.go

@khaliqgant khaliqgant force-pushed the codex/dynamic-integration-discovery branch 2 times, most recently from 6af8eb7 to 1b3ed4c Compare May 11, 2026 11:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 823-830: loadAvailableIntegrationCatalog currently computes
cacheKey via dynamicIntegrationCatalogCacheKey but still reads/writes a single
on-disk file (catalog-cache.json) so dynamic and static catalogs overwrite each
other; change the persistence to be keyed by cacheKey (either by using one cache
file per cacheKey or by storing a map of cacheKey->entry) and update
readIntegrationCatalogCache and the writer used by fetchIntegrationCatalogPath
to accept/return the cacheKey-scoped entry; ensure cachedCatalogIsFresh
continues to receive the same cacheKey when validating freshness so reads and
writes are isolated per cacheKey.
- Around line 1456-1460: The backend filter is removing all fallback providers
because fallback entries lack backend/backends metadata; after calling
loadAvailableIntegrationCatalog (which may return fallbackIntegrationCatalog
entries), check the returned entries for any non-empty backend or backends
fields and only call filterIntegrationCatalog when at least one entry contains
backend metadata; otherwise skip the backend filtering so fallback entries are
preserved. Locate the entries variable returned by
loadAvailableIntegrationCatalog and modify the logic before calling
filterIntegrationCatalog to perform this metadata presence check and
conditionally apply filterIntegrationCatalog.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cece65f3-a517-4692-b163-4bf67aa6142e

📥 Commits

Reviewing files that changed from the base of the PR and between d371022 and 6af8eb7.

📒 Files selected for processing (2)
  • cmd/relayfile-cli/main.go
  • cmd/relayfile-cli/main_test.go

Comment thread cmd/relayfile-cli/main.go
Comment thread cmd/relayfile-cli/main.go Outdated
@khaliqgant khaliqgant force-pushed the codex/dynamic-integration-discovery branch from 1b3ed4c to 868088e Compare May 11, 2026 11:29
@khaliqgant khaliqgant merged commit 3726864 into main May 11, 2026
7 checks passed
@khaliqgant khaliqgant deleted the codex/dynamic-integration-discovery branch May 11, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant