Skip to content
Back to skills

Tool Widgets

ASecurity

Imported skill tool_widgets from langchain

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 11, 2026
toolsshellbash

Works with

  • cli

Security analysis

A100/100

Scanned September 11, 2026

npx -y skills add bitwikiorg/skills.md --skill tool_widgets --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tool Widgets?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tool Widgets
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/bitwikiorg-tool-widgets/badge)](https://www.skillsdirectory.com/skills/bitwikiorg-tool-widgets)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
description: Imported skill tool_widgets from langchain
name: tool_widgets
signature: 2fd8c41eaff398c59a4da7160e595a40658b5af940b59904651642cc99b63c0e
source: /a0/tmp/skills_research/langchain/libs/deepagents-cli/deepagents_cli/widgets/tool_widgets.py
---

"""Tool-specific approval widgets for HITL display."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from textual.containers import Vertical
from textual.widgets import Markdown, Static

if TYPE_CHECKING:
    from textual.app import ComposeResult

# Constants for display limits
_MAX_VALUE_LEN = 200
_MAX_LINES = 30
_MAX_DIFF_LINES = 50
_MAX_PREVIEW_LINES = 20


def _escape_markup(text: str) -> str:
    """Escape Rich markup characters in text."""
    return text.replace("[", r"\[").replace("]", r"\]")


class ToolApprovalWidget(Vertical):
    """Base class for tool approval widgets."""

    def __init__(self, data: dict[str, Any]) -> None:
        """Initialize the tool approval widget with data."""
        super().__init__(classes="tool-approval-widget")
        self.data = data

    def compose(self) -> ComposeResult:
        """Default compose - override in subclasses."""
        yield Static("Tool details not available", classes="approval-description")


class GenericApprovalWidget(ToolApprovalWidget):
    """Generic approval widget for unknown tools."""

    def compose(self) -> ComposeResult:
        """Compose the generic tool display."""
        for key, value in self.data.items():
            if value is None:
                continue
            value_str = str(value)
            if len(value_str) > _MAX_VALUE_LEN:
                hidden = len(value_str) - _MAX_VALUE_LEN
                value_str = value_str[:_MAX_VALUE_LEN] + f"... ({hidden} more chars)"
            yield Static(f"{key}: {value_str}", markup=False, classes="approval-description")


class WriteFileApprovalWidget(ToolApprovalWidget):
    """Approval widget for write_file - shows file content with syntax highlighting."""

    def compose(self) -> ComposeResult:
        """Compose the file content display with syntax highlighting."""
        file_path = self.data.get("file_path", "")
        content = self.data.get("content", "")
        file_extension = self.data.get("file_extension", "text")

        # File path header
        yield Static(f"File: {file_path}", markup=False, classes="approval-file-path")
        yield Static("")

        # Content with syntax highlighting via Markdown code block
        lines = content.split("\n")
        total_lines = len(lines)

        if total_lines > _MAX_LINES:
            # Truncate for display
            shown_lines = lines[:_MAX_LINES]
            remaining = total_lines - _MAX_LINES
            truncated_content = "\n".join(shown_lines) + f"\n... ({remaining} more lines)"
            yield Markdown(f"```{file_extension}\n{truncated_content}\n```")
        else:
            yield Markdown(f"```{file_extension}\n{content}\n```")


class EditFileApprovalWidget(ToolApprovalWidget):
    """Approval widget for edit_file - shows clean diff with colors."""

    DEFAULT_CSS = """
    EditFileApprovalWidget .diff-removed-line {
        color: #ff6b6b;
        background: #3d1f1f;
    }
    EditFileApprovalWidget .diff-added-line {
        color: #69db7c;
        background: #1f3d1f;
    }
    EditFileApprovalWidget .diff-context-line {
        color: #888888;
    }
    EditFileApprovalWidget .diff-stats {
        color: #888888;
        margin-top: 1;
    }
    """

    def compose(self) -> ComposeResult:
        """Compose the diff display with colored additions and deletions."""
        file_path = self.data.get("file_path", "")
        diff_lines = self.data.get("diff_lines", [])
        old_string = self.data.get("old_string", "")
        new_string = self.data.get("new_string", "")

        # Calculate stats first for header
        additions, deletions = self._count_stats(diff_lines, old_string, new_string)

        # File path header with stats
        stats_str = self._format_stats(additions, deletions)
        yield Static(f"[bold cyan]File:[/bold cyan] {file_path}  {stats_str}")
        yield Static("")

        if not diff_lines and not old_string and not new_string:
            yield Static("No changes to display", classes="approval-description")
            return

        # Render content
        if diff_lines:
            yield from self._render_diff_lines_only(diff_lines)
        else:
            yield from self._render_strings_only(old_string, new_string)

    def _count_stats(
        self, diff_lines: list[str], old_string: str, new_string: str
    ) -> tuple[int, int]:
        """Count additions and deletions from diff data."""
        if diff_lines:
            additions = sum(
                1 for line in diff_lines if line.startswith("+") and not line.startswith("+++")
            )
            deletions = sum(
                1 for line in diff_lines if line.startswith("-") and not line.startswith("---")
            )
        else:
            additions = new_string.count("\n") + 1 if new_string else 0
            deletions = old_string.count("\n") + 1 if old_string else 0
        return additions, deletions

    def _format_stats(self, additions: int, deletions: int) -> str:
        """Format stats as colored string."""
        parts = []
        if additions:
            parts.append(f"[green]+{additions}[/green]")
        if deletions:
            parts.append(f"[red]-{deletions}[/red]")
        return " ".join(parts)

    def _render_diff_lines_only(self, diff_lines: list[str]) -> ComposeResult:
        """Render unified diff lines without returning stats."""
        lines_shown = 0

        for line in diff_lines:
            if lines_shown >= _MAX_DIFF_LINES:
                yield Static(f"[dim]... ({len(diff_lines) - lines_shown} more lines)[/dim]")
                break

            if line.startswith(("@@", "---", "+++")):
                continue

            widget = self._render_diff_line(line)
            if widget:
                yield widget
                lines_shown += 1

    def _render_strings_only(self, old_string: str, new_string: str) -> ComposeResult:
        """Render old/new strings without returning stats."""
        if old_string:
            yield Static("[bold red]Removing:[/bold red]")
            yield from self._render_string_lines(old_string, is_addition=False)
            yield Static("")

        if new_string:
            yield Static("[bold green]Adding:[/bold green]")
            yield from self._render_string_lines(new_string, is_addition=True)

    def _render_diff_line(self, line: str) -> Static | None:
        """Render a single diff line with appropriate styling."""
        content = _escape_markup(line[1:] if len(line) > 1 else "")

        if line.startswith("-"):
            return Static(f"[on #3d1f1f][red]- {content}[/red][/on #3d1f1f]")
        if line.startswith("+"):
            return Static(f"[on #1f3d1f][green]+ {content}[/green][/on #1f3d1f]")
        if line.startswith(" "):
            return Static(f"[dim]  {content}[/dim]")
        if line.strip():
            return Static(line, markup=False)
        return None

    def _render_string_lines(self, text: str, *, is_addition: bool) -> ComposeResult:
        """Render lines from a string with appropriate styling."""
        lines = text.split("\n")
        style = "[on #1f3d1f][green]+" if is_addition else "[on #3d1f1f][red]-"
        end_style = "[/green][/on #1f3d1f]" if is_addition else "[/red][/on #3d1f1f]"

        for line in lines[:_MAX_PREVIEW_LINES]:
            escaped = _escape_markup(line)
            yield Static(f"{style} {escaped}{end_style}")

        if len(lines) > _MAX_PREVIEW_LINES:
            remaining = len(lines) - _MAX_PREVIEW_LINES
            yield Static(f"[dim]... ({remaining} more lines)[/dim]")


class BashApprovalWidget(ToolApprovalWidget):
    """Approval widget for bash/shell commands."""

    def compose(self) -> ComposeResult:
        """Compose the bash command display with syntax highlighting."""
        command = self.data.get("command", "")
        description = self.data.get("description", "")

        if description:
            yield Static(description, markup=False, classes="approval-description")
            yield Static("")

        # Show command with bash syntax highlighting
        yield Markdown(f"```bash\n{command}\n```")

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.