Detailed changes
@@ -17,6 +17,7 @@ import (
"github.com/charmbracelet/crush/internal/filetracker"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/permission"
+ "github.com/charmbracelet/crush/internal/skills"
)
//go:embed view.md
@@ -34,9 +35,19 @@ type ViewPermissionsParams struct {
Limit int `json:"limit"`
}
+type ViewResourceType string
+
+const (
+ ViewResourceUnset ViewResourceType = ""
+ ViewResourceSkill ViewResourceType = "skill"
+)
+
type ViewResponseMetadata struct {
- FilePath string `json:"file_path"`
- Content string `json:"content"`
+ FilePath string `json:"file_path"`
+ Content string `json:"content"`
+ ResourceType ViewResourceType `json:"resource_type,omitempty"`
+ ResourceName string `json:"resource_name,omitempty"`
+ ResourceDescription string `json:"resource_description,omitempty"`
}
const (
@@ -196,12 +207,22 @@ func NewViewTool(
output += "\n</file>\n"
output += getDiagnostics(filePath, lspManager)
filetracker.RecordRead(ctx, sessionID, filePath)
+
+ meta := ViewResponseMetadata{
+ FilePath: filePath,
+ Content: content,
+ }
+ if isSkillFile {
+ if skill, err := skills.Parse(filePath); err == nil {
+ meta.ResourceType = ViewResourceSkill
+ meta.ResourceName = skill.Name
+ meta.ResourceDescription = skill.Description
+ }
+ }
+
return fantasy.WithResponseMetadata(
fantasy.NewTextResponse(output),
- ViewResponseMetadata{
- FilePath: filePath,
- Content: content,
- },
+ meta,
), nil
})
}
@@ -82,6 +82,12 @@ func (v *ViewToolRenderContext) RenderTool(sty *styles.Styles, width int, opts *
content = meta.Content
}
+ // Handle skill content.
+ if meta.ResourceType == tools.ViewResourceSkill {
+ body := toolOutputSkillContent(sty, meta.ResourceName, meta.ResourceDescription)
+ return joinToolParts(header, body)
+ }
+
if content == "" {
return header
}
@@ -625,10 +625,21 @@ func toolOutputImageContent(sty *styles.Styles, data, mediaType string) string {
return sty.Tool.Body.Render(fmt.Sprintf(
"%s %s %s %s",
- sty.Tool.ResourceLoadedText.String(),
- sty.Tool.ResourceLoadedIndicator.String(),
- sty.Base.Render(mediaType),
- sty.Subtle.Render(sizeStr),
+ sty.Tool.ResourceLoadedText.Render("Loaded Image"),
+ sty.Tool.ResourceLoadedIndicator.Render(styles.ArrowRightIcon),
+ sty.Tool.MediaType.Render(mediaType),
+ sty.Tool.ResourceSize.Render(sizeStr),
+ ))
+}
+
+// toolOutputSkillContent renders a skill loaded indicator.
+func toolOutputSkillContent(sty *styles.Styles, name, description string) string {
+ return sty.Tool.Body.Render(fmt.Sprintf(
+ "%s %s %s %s",
+ sty.Tool.ResourceLoadedText.Render("Loaded Skill"),
+ sty.Tool.ResourceLoadedIndicator.Render(styles.ArrowRightIcon),
+ sty.Tool.ResourceName.Render(name),
+ sty.Tool.ResourceSize.Render(description),
))
}
@@ -325,6 +325,7 @@ type Styles struct {
// Images and external resources
ResourceLoadedText lipgloss.Style
ResourceLoadedIndicator lipgloss.Style
+ ResourceName lipgloss.Style
ResourceSize lipgloss.Style
MediaType lipgloss.Style
}
@@ -1175,8 +1176,9 @@ func DefaultStyles() Styles {
s.Tool.MCPArrow = base.Foreground(blue).SetString(ArrowRightIcon)
// Loading indicators for images, skills
- s.Tool.ResourceLoadedText = base.Foreground(green).SetString("Loaded")
- s.Tool.ResourceLoadedIndicator = base.Foreground(greenDark).SetString(ArrowRightIcon)
+ s.Tool.ResourceLoadedText = base.Foreground(green)
+ s.Tool.ResourceLoadedIndicator = base.Foreground(greenDark)
+ s.Tool.ResourceName = base
s.Tool.MediaType = base
s.Tool.ResourceSize = base.Foreground(fgMuted)