feat(extensions): add extension info command for registry metadata (#428)#1445
Conversation
) Add `swamp extension info <name>` to fetch and display full registry metadata for a specific extension. Widens ExtensionInfo to capture all fields the server returns (author, platforms, labels, content types, quality score, pull count, repository verification, yank state). Adds optional authentication to both info and search commands for higher rate limits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
No blocking issues found. This is a clean, well-structured addition that follows existing patterns in the codebase.
What looks good
- Import boundary: CLI command (
extension_info.ts) and renderer (extension_info.ts) both import from../../libswamp/mod.ts— correct. - libswamp-internal imports:
info.tsinsidesrc/libswamp/extensions/imports from../../infrastructure/which is allowed for libswamp-internal code per CLAUDE.md. - DDD alignment: The generator function (
extensionInfo) is a proper Application Service — it orchestrates deps, yields progress events, and decouples the domain data shape (ExtensionInfoData) from the infrastructure type (ExtensionInfo). Dependency injection viaExtensionInfoDepsenables clean testing. - Test coverage: 4 unit tests covering happy path, nullable fields, not-found, and API error — all using injected deps with no network calls.
- Dual output modes: Both
"log"and"json"renderers are implemented as required by CLAUDE.md. - License headers: Present on all new files.
anytype pattern: Thedeno-lint-ignore no-explicit-anyfor Cliffy options matches every other extension command in the codebase.- Security:
getExtensionusesencodeURIComponent(name)for the path parameter, preventing path traversal. The sharedfetch()wrapper provides a 15sAbortSignal.timeout, satisfying the no-fire-and-forget-promises rule.
Suggestions
-
ExtensionInfoDataduplicatesExtensionInfo: The libswamp type is field-for-field identical to the infrastructure type, and the mapping in the generator copies every property individually. This is defensible as a decoupling boundary, but ifExtensionInfogains fields later, both must be updated in lockstep. Consider whether a mapped type or spread would reduce maintenance — though the current explicit approach is also fine for discoverability. -
resolveServerUrlimported frompull.ts: Theinfo.tsmodule importsresolveServerUrlfrom./pull.ts. This works, butpull.tsis a large module (~1100 lines) with its own generator, deps, and types — the coupling is purely for one utility function. If more commands need it, extracting to a sharedserver.tswould be cleaner. Not urgent since both live in the same bounded context.
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
-
extension infoplacement in subcommand list (src/cli/commands/extension.ts): Theinfocommand lands betweenpushandfmt, which are operational commands. It would be more discoverable adjacent tosearchorlist(the other browse/inspect commands). Not blocking — just makes--helpoutput easier to scan. -
Raw ISO timestamps in log mode (
src/presentation/renderers/extension_info.ts,Created:/Updated:/Yanked:lines): Users see2026-01-15T10:30:00.000Zverbatim. A locale-formatted string (e.g.,Jan 15 2026) would be more readable. Other renderers don't format dates either, so this is a consistency-neutral suggestion rather than a regression. -
No progress feedback during network call: The
resolvinghandler is a no-op, so there's a silent pause while the API is contacted.extension pullshows progress via its resolving events. A brieflogger.info\Fetching ${input.extensionName}...`would match the pull pattern. Consistent withextension version` (which is also silent), so not a regression. -
not_foundsearch suggestion in JSON mode (JsonExtensionInfoRenderer): Log mode includesTry: swamp extension search <basename>in the error message; JSON mode omits it. Scripts consuming JSON output won't see the hint. Consider including the suggestion in the JSON error message too (it's still human-readable there and costs nothing).
Verdict
PASS — both output modes are implemented correctly, error messages are clear and actionable, the not_found suggestion (Try: swamp extension search <basename>) is a nice touch, and the field layout in log mode is readable and logically grouped. No issues block merge.
There was a problem hiding this comment.
Adversarial Review
Reviewed all 8 files: the new extension info command, widened ExtensionInfo interface, auth plumbing for search, libswamp generator, renderer, tests, and barrel exports.
Critical / High
None.
Medium
src/serve/open/http.ts:1063-1068— stale comment and unnecessary cast afterExtensionInfowidening. The existing code comments thatExtensionInfo"only declares the minimum fields" and casts toRecord<string, unknown>to forward the server's richer payload. After this PR widensExtensionInfoto include all those fields, the comment is misleading and the cast is no longer necessary. Not a bug — the existing behavior is correct either way — but worth a follow-up cleanup so the next reader doesn't wonder why the cast exists.
Low
-
src/libswamp/extensions/info.ts:115-119— error event dropscause. The catch block constructs aSwampErrorwithcodeandmessagebut omits the optionalcausefield, losing the original exception for stack-trace debugging. This matches the pattern inversion.tsso it's consistent, but theSwampErrortype exists to carrycauseand it would be useful here. Purely a diagnostic quality issue, not a correctness bug. -
src/libswamp/extensions/info.ts:40-56—ExtensionInfoDatais a field-for-field duplicate ofExtensionInfo. The domain type and the infrastructure type have identical shapes. Atype ExtensionInfoData = ExtensionInfoalias would eliminate the duplication risk if fields are added to one but not the other. That said, keeping them separate is a defensible domain-boundary choice.
Verdict
PASS. The code is solid. All event kinds are exhaustively handled (enforced by EventHandlers<E>). The ExtensionInfo widening is backward-compatible — existing callers only read a subset and the new fields are either nullable or already present in server responses. Auth plumbing correctly threads the optional apiKey through closures without changing the ExtensionSearchDeps contract. Network timeouts are covered by the client's default 15-second AbortSignal. encodeURIComponent is correctly applied to path segments. Tests cover success, nullable fields, not-found, and API failure paths.
Summary
swamp extension info <name>command to fetch and display full registry metadata for a specific extensionExtensionInfointerface to capture all fields the server returns (author, namespace, platforms, labels, content types, quality score, pull count, repository verification, yank state)AuthRepository) to bothextension infoandextension searchfor higher rate limitsTry: swamp extension search <basename>Closes swamp-club#428
Test Plan
ExtensionInfo)deno check,deno lint,deno fmtall clean🤖 Generated with Claude Code