-
Notifications
You must be signed in to change notification settings - Fork 7.9k
feat: add catalog discovery CLI commands #2360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
1cbe2d7
69b9f06
e8a8afc
6d86483
a9d403b
1ed25f7
af56f63
dec57ea
618e8bd
8c1e0a2
275570d
5268597
4b67b33
0604b87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1886,6 +1886,13 @@ def get_speckit_version() -> str: | |||||||||||||||||||
| ) | ||||||||||||||||||||
| app.add_typer(integration_app, name="integration") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| integration_catalog_app = typer.Typer( | ||||||||||||||||||||
| name="catalog", | ||||||||||||||||||||
| help="Manage integration catalog sources", | ||||||||||||||||||||
| add_completion=False, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| integration_app.add_typer(integration_catalog_app, name="catalog") | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| INTEGRATION_JSON = ".specify/integration.json" | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -2535,6 +2542,267 @@ def integration_upgrade( | |||||||||||||||||||
| console.print(f"\n[green]✓[/green] Integration '{name}' upgraded successfully") | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| # ===== Integration catalog discovery commands ===== | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # These commands mirror the workflow catalog CLI shape: | ||||||||||||||||||||
| # - `search` / `info` for discovery over the active catalog stack | ||||||||||||||||||||
| # - `catalog list/add/remove` for managing catalog sources | ||||||||||||||||||||
| # | ||||||||||||||||||||
| # They deliberately do NOT add `integration add/remove/enable/disable/ | ||||||||||||||||||||
| # set-priority`: integrations are single-active (install / uninstall / switch), | ||||||||||||||||||||
| # not additive like extensions and presets. | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def _require_specify_project() -> Path: | ||||||||||||||||||||
| """Return the current project root if it is a spec-kit project, else exit.""" | ||||||||||||||||||||
| project_root = Path.cwd() | ||||||||||||||||||||
| if not (project_root / ".specify").exists(): | ||||||||||||||||||||
| console.print("[red]Error:[/red] Not a spec-kit project (no .specify/ directory)") | ||||||||||||||||||||
| console.print("Run this command from a spec-kit project root") | ||||||||||||||||||||
| raise typer.Exit(1) | ||||||||||||||||||||
| return project_root | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| @integration_app.command("search") | ||||||||||||||||||||
| def integration_search( | ||||||||||||||||||||
| query: Optional[str] = typer.Argument(None, help="Search query (optional)"), | ||||||||||||||||||||
| tag: Optional[str] = typer.Option(None, "--tag", help="Filter by tag"), | ||||||||||||||||||||
| author: Optional[str] = typer.Option(None, "--author", help="Filter by author"), | ||||||||||||||||||||
| ): | ||||||||||||||||||||
| """Search for integrations in the active catalog stack.""" | ||||||||||||||||||||
| from .integrations import INTEGRATION_REGISTRY | ||||||||||||||||||||
| from .integrations.catalog import ( | ||||||||||||||||||||
| IntegrationCatalog, | ||||||||||||||||||||
| IntegrationCatalogError, | ||||||||||||||||||||
| IntegrationValidationError, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| project_root = _require_specify_project() | ||||||||||||||||||||
| catalog = IntegrationCatalog(project_root) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| try: | ||||||||||||||||||||
| results = catalog.search(query=query, tag=tag, author=author) | ||||||||||||||||||||
| except IntegrationValidationError as exc: | ||||||||||||||||||||
| console.print(f"[red]Error:[/red] {exc}") | ||||||||||||||||||||
| console.print( | ||||||||||||||||||||
| "\nTip: Check .specify/integration-catalogs.yml for invalid catalog configuration." | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| raise typer.Exit(1) | ||||||||||||||||||||
| except IntegrationCatalogError as exc: | ||||||||||||||||||||
| console.print(f"[red]Error:[/red] {exc}") | ||||||||||||||||||||
| console.print("\nTip: The catalog may be temporarily unavailable. Try again later.") | ||||||||||||||||||||
|
||||||||||||||||||||
| console.print("\nTip: The catalog may be temporarily unavailable. Try again later.") | |
| if os.environ.get("SPECKIT_INTEGRATION_CATALOG_URL"): | |
| console.print( | |
| "\nTip: Check the SPECKIT_INTEGRATION_CATALOG_URL environment variable for an invalid " | |
| "catalog URL, or unset it to use the configured catalog files " | |
| "(.specify/integration-catalogs.yml or ~/.specify/integration-catalogs.yml)." | |
| ) | |
| else: | |
| console.print("\nTip: The catalog may be temporarily unavailable. Try again later.") |
Copilot
AI
Apr 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handler prints a network-oriented hint ("catalog may be temporarily unavailable") for every IntegrationCatalogError, but IntegrationCatalogError is also raised for local config/YAML validation failures (e.g., invalid .specify/integration-catalogs.yml). This can mislead users into retrying instead of fixing the config. Consider catching IntegrationValidationError separately (or narrowing the hint to fetch/URLError cases) and tailoring the guidance accordingly.
Copilot
AI
Apr 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to integration search, this local-config guidance hardcodes the project path .specify/integration-catalogs.yml, but the thrown IntegrationValidationError may be for the user-level config (~/.specify/...). Suggest adjusting the message to reference the failing config path (included in the error) or mention both possible locations.
| "\nCheck .specify/integration-catalogs.yml, " | |
| "\nCheck .specify/integration-catalogs.yml or " | |
| "~/.specify/integration-catalogs.yml, " |
Copilot
AI
Apr 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
integration catalog list currently renders the active catalog stack via get_active_catalogs() (env override → project config → user config → built-ins), but catalog add/remove only operate on the project-level file. If a user has a user-level config (or an env override), catalog list will show indexes that catalog remove <index> cannot act on (it will error with “No catalog config file found.”). Consider either (a) making catalog list show only the project-level config entries (plus a separate built-in section), or (b) explicitly labeling each entry’s source (env/project/user/built-in) and restricting/remapping indexes so remove applies only to removable project entries.
Copilot
AI
Apr 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
integration catalog list prefers get_project_catalog_configs() whenever a project config file exists, which means it will ignore the higher-precedence SPECKIT_INTEGRATION_CATALOG_URL override in that case (even though IntegrationCatalog.get_active_catalogs() will use the env var). This can make the command output disagree with the actual catalog sources used by integration search/info. Consider always showing the active stack (or at least explicitly detecting the env override and noting that it supersedes project config).
Copilot
AI
Apr 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The add command help text says HTTP is only allowed for http://localhost, but _validate_catalog_url also allows http://127.0.0.1 and http://[::1]. Update the help string to match the actual validation rules so users aren't misled.
| help="Catalog URL to add (HTTPS required, except http://localhost for local testing)", | |
| help="Catalog URL to add (HTTPS required, except http://localhost, http://127.0.0.1, or http://[::1] for local testing)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The local-config failure tip hardcodes
.specify/integration-catalogs.yml, butIntegrationCatalog.get_active_catalogs()can also fail on the user-level config at~/.specify/integration-catalogs.yml. When that happens, this guidance points users to the wrong file. Consider wording the tip to reference the path in the exception (already included), or mention both project and user config locations.