Imported skill input from langchain
Scanned 9/11/2026
Install to Claude Code
npx -y skills add bitwikiorg/skills.md --skill input --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Input?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/bitwikiorg-input)More formats (shields.io, HTML) on the badges page.
---
description: Imported skill input from langchain
name: input
signature: 114372f130b64ba232cdd67d5f47d885303379e9fa82a6c4f0a267213ebfaf1e
source: /a0/tmp/skills_research/langchain/libs/deepagents-cli/deepagents_cli/input.py
---
"""Input handling, completers, and prompt session for the CLI."""
import asyncio
import os
import re
import time
from collections.abc import Callable
from pathlib import Path
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import (
Completer,
Completion,
PathCompleter,
merge_completers,
)
from prompt_toolkit.document import Document
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.key_binding import KeyBindings
from .config import COLORS, COMMANDS, SessionState, console
from .image_utils import ImageData, get_clipboard_image
# Regex patterns for context-aware completion
AT_MENTION_RE = re.compile(r"@(?P<path>(?:[^\s@]|(?<=\\)\s)*)$")
SLASH_COMMAND_RE = re.compile(r"^/(?P<command>[a-z]*)$")
EXIT_CONFIRM_WINDOW = 3.0
class ImageTracker:
"""Track pasted images in the current conversation."""
def __init__(self) -> None:
self.images: list[ImageData] = []
self.next_id = 1
def add_image(self, image_data: ImageData) -> str:
"""Add an image and return its placeholder text.
Args:
image_data: The image data to track
Returns:
Placeholder string like "[image 1]"
"""
placeholder = f"[image {self.next_id}]"
image_data.placeholder = placeholder
self.images.append(image_data)
self.next_id += 1
return placeholder
def get_images(self) -> list[ImageData]:
"""Get all tracked images."""
return self.images.copy()
def clear(self) -> None:
"""Clear all tracked images and reset counter."""
self.images.clear()
self.next_id = 1
class FilePathCompleter(Completer):
"""Activate filesystem completion only when cursor is after '@'."""
def __init__(self) -> None:
self.path_completer = PathCompleter(
expanduser=True,
min_input_len=0,
only_directories=False,
)
def get_completions(self, document, complete_event):
"""Get file path completions when @ is detected."""
text = document.text_before_cursor
# Use regex to detect @path pattern at end of line
m = AT_MENTION_RE.search(text)
if not m:
return # Not in an @path context
path_fragment = m.group("path")
# Unescape the path for PathCompleter (it doesn't understand escape sequences)
unescaped_fragment = path_fragment.replace("\\ ", " ")
# Strip trailing backslash if present (user is in the process of typing an escape)
unescaped_fragment = unescaped_fragment.removesuffix("\\")
# Create temporary document for the unescaped path fragment
temp_doc = Document(text=unescaped_fragment, cursor_position=len(unescaped_fragment))
# Get completions from PathCompleter and use its start_position
# PathCompleter returns suffix text with start_position=0 (insert at cursor)
for comp in self.path_completer.get_completions(temp_doc, complete_event):
# Add trailing / for directories so users can continue navigating
completed_path = Path(unescaped_fragment + comp.text).expanduser()
# Re-escape spaces in the completion text for the command line
completion_text = comp.text.replace(" ", "\\ ")
if completed_path.is_dir() and not completion_text.endswith("/"):
completion_text += "/"
yield Completion(
text=completion_text,
start_position=comp.start_position, # Use PathCompleter's position (usually 0)
display=comp.display,
display_meta=comp.display_meta,
)
class CommandCompleter(Completer):
"""Activate command completion only when line starts with '/'."""
def get_completions(self, document, _complete_event):
"""Get command completions when / is at the start."""
text = document.text_before_cursor
# Use regex to detect /command pattern at start of line
m = SLASH_COMMAND_RE.match(text)
if not m:
return # Not in a /command context
command_fragment = m.group("command")
# Match commands that start with the fragment (case-insensitive)
for cmd_name, cmd_desc in COMMANDS.items():
if cmd_name.startswith(command_fragment.lower()):
yield Completion(
text=cmd_name,
start_position=-len(command_fragment), # Fixed position for original document
display=cmd_name,
display_meta=cmd_desc,
)
def parse_file_mentions(text: str) -> tuple[str, list[Path]]:
"""Extract @file mentions and return cleaned text with resolved file paths."""
pattern = r"@((?:[^\s@]|(?<=\\)\s)+)" # Match @filename, allowing escaped spaces
matches = re.findall(pattern, text)
files = []
for match in matches:
# Remove escape characters
clean_path = match.replace("\\ ", " ")
path = Path(clean_path).expanduser()
# Try to resolve relative to cwd
if not path.is_absolute():
path = Path.cwd() / path
try:
path = path.resolve()
if path.exists() and path.is_file():
files.append(path)
else:
console.print(f"[yellow]Warning: File not found: {match}[/yellow]")
except Exception as e:
console.print(f"[yellow]Warning: Invalid path {match}: {e}[/yellow]")
return text, files
def parse_image_placeholders(text: str) -> tuple[str, int]:
"""Count image placeholders in text.
Args:
text: Input text potentially containing [image] or [image N] placeholders
Returns:
Tuple of (text, count) where count is the number of image placeholders found
"""
# Match [image] or [image N] patterns
pattern = r"\[image(?:\s+\d+)?\]"
matches = re.findall(pattern, text, re.IGNORECASE)
return text, len(matches)
def get_bottom_toolbar(
session_state: SessionState, session_ref: dict
) -> Callable[[], list[tuple[str, str]]]:
"""Return toolbar function that shows auto-approve status and BASH MODE."""
def toolbar() -> list[tuple[str, str]]:
parts = []
# Check if we're in BASH mode (input starts with !)
try:
session = session_ref.get("session")
if session:
current_text = session.default_buffer.text
if current_text.startswith("!"):
parts.append(("bg:#ff1493 fg:#ffffff bold", " BASH MODE "))
parts.append(("", " | "))
except (AttributeError, TypeError):
# Silently ignore - toolbar is non-critical and called frequently
pass
# Base status message
if session_state.auto_approve:
base_msg = "auto-accept ON (CTRL+T to toggle)"
base_class = "class:toolbar-green"
else:
base_msg = "manual accept (CTRL+T to toggle)"
base_class = "class:toolbar-orange"
parts.append((base_class, base_msg))
# Show exit confirmation hint if active
hint_until = session_state.exit_hint_until
if hint_until is not None:
now = time.monotonic()
if now < hint_until:
parts.append(("", " | "))
parts.append(("class:toolbar-exit", " Ctrl+C again to exit "))
else:
session_state.exit_hint_until = None
return parts
return toolbar
def create_prompt_session(
_assistant_id: str, session_state: SessionState, image_tracker: ImageTracker | None = None
) -> PromptSession:
"""Create a configured PromptSession with all features."""
# Set default editor if not already set
if "EDITOR" not in os.environ:
os.environ["EDITOR"] = "nano"
# Create key bindings
kb = KeyBindings()
@kb.add("c-c")
def _(event) -> None:
"""Require double Ctrl+C within a short window to exit."""
app = event.app
now = time.monotonic()
if session_state.exit_hint_until is not None and now < session_state.exit_hint_until:
handle = session_state.exit_hint_handle
if handle:
handle.cancel()
session_state.exit_hint_handle = None
session_state.exit_hint_until = None
app.invalidate()
app.exit(exception=KeyboardInterrupt())
return
session_state.exit_hint_until = now + EXIT_CONFIRM_WINDOW
handle = session_state.exit_hint_handle
if handle:
handle.cancel()
loop = asyncio.get_running_loop()
app_ref = app
def clear_hint() -> None:
if (
session_state.exit_hint_until is not None
and time.monotonic() >= session_state.exit_hint_until
):
session_state.exit_hint_until = None
session_state.exit_hint_handle = None
app_ref.invalidate()
session_state.exit_hint_handle = loop.call_later(EXIT_CONFIRM_WINDOW, clear_hint)
app.invalidate()
# Bind Ctrl+T to toggle auto-approve
@kb.add("c-t")
def _(event) -> None:
"""Toggle auto-approve mode."""
session_state.toggle_auto_approve()
# Force UI refresh to update toolbar
event.app.invalidate()
# Custom paste handler to detect images
if image_tracker:
from prompt_toolkit.keys import Keys
def _handle_paste_with_image_check(event, pasted_text: str = "") -> None:
"""Check clipboard for image, otherwise insert pasted text."""
# Try to get an image from clipboard
clipboard_image = get_clipboard_image()
if clipboard_image:
# Found an image! Add it to tracker and insert placeholder
placeholder = image_tracker.add_image(clipboard_image)
# Insert placeholder (no confirmation message)
event.current_buffer.insert_text(placeholder)
elif pasted_text:
# No image, insert the pasted text
event.current_buffer.insert_text(pasted_text)
else:
# Fallback: try to get text from prompt_toolkit clipboard
clipboard_data = event.app.clipboard.get_data()
if clipboard_data and clipboard_data.text:
event.current_buffer.insert_text(clipboard_data.text)
@kb.add(Keys.BracketedPaste)
def _(event) -> None:
"""Handle bracketed paste (Cmd+V on macOS) - check for images first."""
# Bracketed paste provides the pasted text in event.data
pasted_text = event.data if hasattr(event, "data") else ""
_handle_paste_with_image_check(event, pasted_text)
@kb.add("c-v")
def _(event) -> None:
"""Handle Ctrl+V paste - check for images first."""
_handle_paste_with_image_check(event)
# Bind regular Enter to submit (intuitive behavior)
@kb.add("enter")
def _(event) -> None:
"""Enter submits the input, unless completion menu is active."""
buffer = event.current_buffer
# If completion menu is showing, apply the current completion
if buffer.complete_state:
# Get the current completion (the highlighted one)
current_completion = buffer.complete_state.current_completion
# If no completion is selected (user hasn't navigated), select and apply the first one
if not current_completion and buffer.complete_state.completions:
# Move to the first completion
buffer.complete_next()
# Now apply it
buffer.apply_completion(buffer.complete_state.current_completion)
elif current_completion:
# Apply the already-selected completion
buffer.apply_completion(current_completion)
else:
# No completions available, close menu
buffer.complete_state = None
# Don't submit if buffer is empty or only whitespace
elif buffer.text.strip():
# Normal submit
buffer.validate_and_handle()
# If empty, do nothing (don't submit)
# Alt+Enter for newlines (press ESC then Enter, or Option+Enter on Mac)
@kb.add("escape", "enter")
def _(event) -> None:
"""Alt+Enter inserts a newline for multi-line input."""
event.current_buffer.insert_text("\n")
# Ctrl+E to open in external editor
@kb.add("c-e")
def _(event) -> None:
"""Open the current input in an external editor (nano by default)."""
event.current_buffer.open_in_editor()
# Backspace handler to retrigger completions and delete image tags as units
@kb.add("backspace")
def _(event) -> None:
"""Handle backspace: delete image tags as single unit, retrigger completion."""
buffer = event.current_buffer
text_before = buffer.document.text_before_cursor
# Check if cursor is right after an image tag like [image 1] or [image 12]
image_tag_pattern = r"\[image \d+\]$"
match = re.search(image_tag_pattern, text_before)
if match and image_tracker:
# Delete the entire tag
tag_length = len(match.group(0))
buffer.delete_before_cursor(count=tag_length)
# Remove the image from tracker and reset counter
tag_text = match.group(0)
image_num_match = re.search(r"\d+", tag_text)
if image_num_match:
image_num = int(image_num_match.group(0))
# Remove image at index (1-based to 0-based)
if 0 < image_num <= len(image_tracker.images):
image_tracker.images.pop(image_num - 1)
# Reset counter to next available number
image_tracker.next_id = len(image_tracker.images) + 1
else:
# Normal backspace
buffer.delete_before_cursor(count=1)
# Check if we're in a completion context (@ or /)
text = buffer.document.text_before_cursor
if AT_MENTION_RE.search(text) or SLASH_COMMAND_RE.match(text):
# Retrigger completion
buffer.start_completion(select_first=False)
from prompt_toolkit.styles import Style
# Define styles for the toolbar with full-width background colors
toolbar_style = Style.from_dict(
{
"bottom-toolbar": "noreverse", # Disable default reverse video
"toolbar-green": "bg:#10b981 #000000", # Green for auto-accept ON
"toolbar-orange": "bg:#f59e0b #000000", # Orange for manual accept
"toolbar-exit": "bg:#2563eb #ffffff", # Blue for exit hint
}
)
# Create session reference dict for toolbar to access session
session_ref = {}
# Create the session
session = PromptSession(
message=HTML(f'<style fg="{COLORS["user"]}">></style> '),
multiline=True, # Keep multiline support but Enter submits
key_bindings=kb,
completer=merge_completers([CommandCompleter(), FilePathCompleter()]),
editing_mode=EditingMode.EMACS,
complete_while_typing=True, # Show completions as you type
complete_in_thread=True, # Async completion prevents menu freezing
mouse_support=False,
enable_open_in_editor=True, # Allow Ctrl+X Ctrl+E to open external editor
bottom_toolbar=get_bottom_toolbar(
session_state, session_ref
), # Persistent status bar at bottom
style=toolbar_style, # Apply toolbar styling
reserve_space_for_menu=7, # Reserve space for completion menu to show 5-6 results
)
# Store session reference for toolbar to access
session_ref["session"] = session
return session
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!