-
Notifications
You must be signed in to change notification settings - Fork 0
feat(004): add skillrig show for a human-friendly full skill view
#19
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 all commits
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 |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # `skillrig show <skill>` — read one skill's full details (Query) | ||
|
|
||
| > The **human** counterpart to [search](search.md): drill into ONE skill and print its whole | ||
| > record — most importantly the **complete, untruncated description** that the `search` table | ||
| > clips to ~80 chars. `info` is an alias. Needs an origin (run [init](init.md) first). | ||
|
|
||
| `show` resolves the active origin and reads its catalog (`index.json`) through the **same** | ||
| path `search` uses, then prints a single named skill's full record: name, version, namespace, | ||
| the full description, topics, path, and backing-tool requirements. Reach for it when `search` | ||
| showed a truncated one-liner and a human wants to read the whole thing (an agent can instead | ||
| pipe `search <name> --json` to `jq`). | ||
|
|
||
| ``` | ||
| skillrig show terraform-plan-review # full human-readable record | ||
| skillrig info terraform-plan-review # identical (alias) | ||
| skillrig show terraform-plan-review --json # complete record for an agent / jq | ||
| ``` | ||
|
|
||
| ## Lookup is exact (a point lookup, not a filter) | ||
|
|
||
| The skill name is matched **exactly** — the same canonical name `add` vendors by — not the | ||
| fuzzy, case-insensitive substring match `search` uses. So `show` is for a name you already | ||
| know (typically one you saw in `search`). A name the origin does not publish is an **error** | ||
| (exit 1), pointing you back at `search` — deliberately unlike `search`, where an empty result | ||
| is a clean exit 0. | ||
|
|
||
| ## Freshness & origin | ||
|
|
||
| Like `search`, `show` fetches the origin's `index.json` **per call** (no local cache), resolves | ||
| the active origin through the shared resolver (`SKILLRIG_ORIGIN` > project `.skillrig/config.toml` | ||
| > global), and checks the origin's convention version before reading. A **remote** origin is | ||
| fetched over `git` (a private one uses the auto-resolved read-only token); a **local-path** | ||
| origin is read with no network. | ||
|
|
||
| ## Output | ||
|
|
||
| - **Human (default)** — a labelled block: a `name version (namespace)` header, the `path`, | ||
| `topics`, and `requires` lines, then the **full description** as the body, and a footer hint | ||
| pointing at `add`. | ||
| - **`--json`** — `{ "origin": ..., "skill": { ...full catalog entry... } }`, untruncated and | ||
| pipeable: `skillrig show terraform-plan-review --json | jq '.skill.requires'`. | ||
|
|
||
| | Flag | Purpose | | ||
| |------|---------| | ||
| | `--json` | Emit the complete record (origin + the whole catalog entry) on stdout | | ||
| | `--verbose` | Show the raw underlying cause behind a summary or error | | ||
|
|
||
| ## Exit codes | ||
|
|
||
| | Code | When | | ||
| |------|------| | ||
| | `0` | Success — the named skill was found and printed | | ||
| | `1` | Usage/config: no origin configured, the named skill is **not in the origin**, unreachable/auth/incompatible-convention fetching the catalog, bad args | | ||
|
|
||
| `show` never emits `2`/`3` (those are reserved for verification/prerequisite gates). | ||
|
|
||
| ## Error handling | ||
|
|
||
| | Symptom (stderr) | Cause | Fix | | ||
| |------------------|-------|-----| | ||
| | `skill "<name>" not found in origin ...` | no skill by that exact name in the catalog | run `skillrig search` to list the real names, then `show` one | | ||
| | `no origin configured` | no resolvable origin | `skillrig init --origin OWNER/REPO`, or set `SKILLRIG_ORIGIN` | | ||
| | `... is unreachable` / `authentication ... failed` / `... not found` / `convention version N` | catalog fetch/gate failures (shared with `search`) | see [search.md](search.md) — same typed errors and fixes | | ||
|
|
||
| All failures state what/why/fix and exit `1`; `--verbose` shows the raw cause. Errors to | ||
| stderr, data to stdout (so `skillrig show <name> --json 2>/dev/null | jq .` stays clean). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,6 +167,78 @@ func renderSearchResult(w io.Writer, origin string, matches []skillcore.CatalogE | |
| return err | ||
| } | ||
|
|
||
| // showResultJSON is the complete, untruncated --json view of a show: the | ||
| // resolved origin and the single matched skill with every field add needs. It | ||
| // reuses the skillcore.CatalogEntry JSON tags, so the record is byte-for-byte the | ||
| // same shape search emits per entry — one entry, named `skill`. | ||
| type showResultJSON struct { | ||
| Origin string `json:"origin"` | ||
| Skill skillcore.CatalogEntry `json:"skill"` | ||
| } | ||
|
|
||
| // showFooterPrefix is the next-step footer for a human show — the skill name is | ||
| // appended so the hint is a runnable command (cli.md Principle 3). | ||
| const showFooterPrefix = "→ vendor it: skillrig add " | ||
|
|
||
| // renderShowResult writes a single skill's full record to w. With jsonOut it | ||
| // emits one complete JSON object (origin + the whole catalog entry, all fields | ||
| // present); otherwise a human-friendly labelled block whose defining feature is | ||
| // the COMPLETE, untruncated description — the gap issue #17 closes, since search | ||
| // clips it to ~80 chars. The block is a fixed handful of header/field lines plus | ||
| // the description body and a footer hint. Data goes to stdout (the caller passes | ||
| // cmd.OutOrStdout()). | ||
| func renderShowResult(w io.Writer, origin string, e skillcore.CatalogEntry, jsonOut bool) error { | ||
| if jsonOut { | ||
| enc := json.NewEncoder(w) | ||
| enc.SetEscapeHTML(false) | ||
|
|
||
| return enc.Encode(showResultJSON{Origin: origin, Skill: e}) | ||
| } | ||
|
|
||
| var b strings.Builder | ||
|
|
||
| fmt.Fprintf(&b, "%s %s (%s)\n", e.Name, e.Version, e.Namespace) | ||
| fmt.Fprintf(&b, "path: %s\n", e.Path) | ||
|
|
||
| if len(e.Topics) > 0 { | ||
| fmt.Fprintf(&b, "topics: %s\n", strings.Join(e.Topics, ", ")) | ||
| } | ||
|
|
||
| if len(e.Requires) > 0 { | ||
| fmt.Fprintf(&b, "requires: %s\n", joinRequires(e.Requires)) | ||
| } | ||
|
|
||
| // The whole point of show: the complete description, untruncated, set off by a | ||
| // blank line so it reads as the body of the record. | ||
| if desc := strings.TrimSpace(e.Description); desc != "" { | ||
| fmt.Fprintf(&b, "\n%s\n", desc) | ||
| } | ||
|
|
||
| fmt.Fprintf(&b, "\n%s%s\n", showFooterPrefix, e.Name) | ||
|
|
||
|
Comment on lines
+179
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Footer hint breaks for names renderShowResult prints a supposedly runnable skillrig add <name> footer using the raw catalog skill name, but valid skill names can start with - or include spaces, making the suggested command fail (flag parsing / shell splitting). This violates the stated contract that the footer is a runnable next-step command. Agent Prompt
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify agenskills.io specification on allowed characters in skill frontmatter field "name" - gh cli parsing logic of this metadata and if this is a real concern |
||
| _, err := io.WriteString(w, b.String()) | ||
|
|
||
| return err | ||
| } | ||
|
|
||
| // joinRequires renders a skill's backing-tool requirements as a compact human | ||
| // summary — "tool (version)" joined by commas, or a bare tool when no version | ||
| // constraint is recorded — mirroring search's requires summary. | ||
| func joinRequires(reqs []skillcore.Require) string { | ||
| parts := make([]string, 0, len(reqs)) | ||
|
|
||
| for _, r := range reqs { | ||
| part := r.Tool | ||
| if r.Version != "" { | ||
| part += " (" + r.Version + ")" | ||
| } | ||
|
|
||
| parts = append(parts, part) | ||
| } | ||
|
|
||
| return strings.Join(parts, ", ") | ||
| } | ||
|
|
||
| // searchEmptyFooter is the next-step hint for an empty search result (still exit 0). | ||
| const searchEmptyFooter = "→ broaden the query, or run skillrig search with no filter to list all" | ||
|
|
||
|
|
||
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.
2. rendershowresult prints full description
📘 Rule violation⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools