From 881f1fb8d9a4fefb79cd5754656d5ca8e01a8565 Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 01:23:45 -0700
Subject: [PATCH 1/6] fix(browser): harden bridge and session persistence
---
.../cli_anything/browser/browser_cli.py | 121 ++++--
.../cli_anything/browser/core/fs.py | 32 +-
.../cli_anything/browser/core/page.py | 14 +-
.../cli_anything/browser/core/session.py | 57 ++-
.../browser/utils/domshell_backend.py | 357 +++++++++++++++---
.../cli_anything/browser/utils/tool_result.py | 138 +++++++
6 files changed, 620 insertions(+), 99 deletions(-)
create mode 100644 browser/agent-harness/cli_anything/browser/utils/tool_result.py
diff --git a/browser/agent-harness/cli_anything/browser/browser_cli.py b/browser/agent-harness/cli_anything/browser/browser_cli.py
index 54fbc565c0..3ec64dccf1 100644
--- a/browser/agent-harness/cli_anything/browser/browser_cli.py
+++ b/browser/agent-harness/cli_anything/browser/browser_cli.py
@@ -25,6 +25,7 @@
from cli_anything.browser.core import page as page_mod
from cli_anything.browser.core import fs as fs_mod
from cli_anything.browser.utils import domshell_backend as backend
+from cli_anything.browser.utils.tool_result import tool_result_body_text, tool_result_error_text, tool_result_has_error
# Global state
_session: Optional[Session] = None
@@ -36,22 +37,32 @@
def get_session() -> Session:
global _session
if _session is None:
- _session = Session()
+ if backend.daemon_started():
+ _session = Session.load_persisted()
+ else:
+ _session = Session()
return _session
-def output(data, message: str = ""):
+def output(data, message: str = "", body: Optional[str] = None):
if _json_output:
click.echo(json.dumps(data, indent=2, default=str))
+ return
+
+ if message:
+ click.echo(message)
+
+ if body is not None:
+ if body:
+ click.echo(body)
+ return
+
+ if isinstance(data, dict):
+ _print_dict(data)
+ elif isinstance(data, list):
+ _print_list(data)
else:
- if message:
- click.echo(message)
- if isinstance(data, dict):
- _print_dict(data)
- elif isinstance(data, list):
- _print_list(data)
- else:
- click.echo(str(data))
+ click.echo(str(data))
def _print_dict(d: dict, indent: int = 0):
@@ -77,6 +88,21 @@ def _print_list(items: list, indent: int = 0):
click.echo(f"{prefix}- {item}")
+def _format_page_info(sess: Session) -> str:
+ url = sess.current_url or "(no page loaded)"
+ return f"URL: {url}\nWorking dir: {sess.working_dir}"
+
+
+def _format_session_status(status: dict) -> str:
+ return (
+ f"URL: {status.get('current_url', '(no page loaded)')}\n"
+ f"Working dir: {status.get('working_dir', '/')}\n"
+ f"History: {status.get('history_length', 0)}\n"
+ f"Forward: {status.get('forward_stack_length', 0)}\n"
+ f"Daemon: {'on' if status.get('daemon_mode') else 'off'}"
+ )
+
+
def handle_error(func):
def wrapper(*args, **kwargs):
try:
@@ -136,6 +162,7 @@ def cli(ctx, use_json, use_daemon):
if use_daemon:
try:
backend.start_daemon()
+ _session.persist_state = True
_session.enable_daemon()
if not _json_output:
click.echo("Daemon mode: persistent MCP connection active")
@@ -164,7 +191,10 @@ def page_open(url):
"""Open a URL in Chrome."""
sess = get_session()
result = page_mod.open_page(sess, url)
- output(result, f"Opened: {url}")
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "open failed"), body="")
+ else:
+ output(result, f"Opened: {url}", body="")
@page.command("reload")
@@ -173,7 +203,10 @@ def page_reload():
"""Reload the current page."""
sess = get_session()
result = page_mod.reload_page(sess)
- output(result, "Page reloaded")
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "reload failed"), body="")
+ else:
+ output(result, "Page reloaded", body="")
@page.command("back")
@@ -182,10 +215,10 @@ def page_back():
"""Navigate back in history."""
sess = get_session()
result = page_mod.go_back(sess)
- if "error" in result:
- output(result, result["error"])
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "back failed"), body="")
else:
- output(result, "Navigated back")
+ output(result, "Navigated back", body="")
@page.command("forward")
@@ -194,10 +227,10 @@ def page_forward():
"""Navigate forward in history."""
sess = get_session()
result = page_mod.go_forward(sess)
- if "error" in result:
- output(result, result["error"])
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "forward failed"), body="")
else:
- output(result, "Navigated forward")
+ output(result, "Navigated forward", body="")
@page.command("info")
@@ -206,7 +239,7 @@ def page_info():
"""Show current page information."""
sess = get_session()
result = page_mod.get_page_info(sess)
- output(result)
+ output(result, body=_format_page_info(sess))
# ── Filesystem Commands ──────────────────────────────────────────
@@ -223,7 +256,9 @@ def fs_ls(path):
"""List elements at a path in the accessibility tree."""
sess = get_session()
result = fs_mod.list_elements(sess, path)
- if _json_output:
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "ls failed"), body="")
+ elif _json_output:
output(result)
else:
entries = result.get("entries", [])
@@ -246,10 +281,10 @@ def fs_cd(path):
"""Change directory in the accessibility tree."""
sess = get_session()
result = fs_mod.change_directory(sess, path)
- if "error" in result:
- output(result, result["error"])
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "cd failed"), body="")
else:
- output(result, f"Changed to: {sess.working_dir}")
+ output(result, f"Changed to: {sess.working_dir}", body="")
@fs.command("cat")
@@ -259,7 +294,10 @@ def fs_cat(path):
"""Read element content from the accessibility tree."""
sess = get_session()
result = fs_mod.read_element(sess, path)
- output(result)
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "cat failed"), body="")
+ else:
+ output(result, body=tool_result_body_text(result, "(empty)"))
@fs.command("grep")
@@ -270,7 +308,9 @@ def fs_grep(pattern, path):
"""Search for pattern in the accessibility tree."""
sess = get_session()
result = fs_mod.grep_elements(sess, pattern, path)
- if _json_output:
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "grep failed"), body="")
+ elif _json_output:
output(result)
else:
matches = result.get("matches", [])
@@ -303,9 +343,12 @@ def act():
def act_click(path):
"""Click an element at the given path."""
sess = get_session()
- use_daemon = sess.daemon_mode
+ use_daemon = sess.daemon_mode or backend.daemon_started()
result = backend.click(path, use_daemon=use_daemon)
- output(result, f"Clicked: {path}")
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "click failed"), body="")
+ else:
+ output(result, f"Clicked: {path}", body="")
@act.command("type")
@@ -315,9 +358,12 @@ def act_click(path):
def act_type(path, text):
"""Type text into an input element."""
sess = get_session()
- use_daemon = sess.daemon_mode
+ use_daemon = sess.daemon_mode or backend.daemon_started()
result = backend.type_text(path, text, use_daemon=use_daemon)
- output(result, f"Typed into: {path}")
+ if tool_result_has_error(result):
+ output(result, tool_result_error_text(result, "type failed"), body="")
+ else:
+ output(result, f"Typed into: {path}", body="")
# ── Session Commands ─────────────────────────────────────────────
@@ -333,7 +379,8 @@ def session_status():
"""Show current session status."""
sess = get_session()
status = sess.status()
- output(status)
+ status["daemon_mode"] = status.get("daemon_mode", False) or backend.daemon_started()
+ output(status, body=_format_session_status(status))
@session.command("daemon-start")
@@ -342,10 +389,12 @@ def session_daemon_start():
"""Start persistent daemon mode."""
try:
backend.start_daemon()
- get_session().enable_daemon()
- output({"daemon": "started"}, "Daemon mode started")
+ sess = get_session()
+ sess.persist_state = True
+ sess.enable_daemon()
+ output({"daemon": "started"}, "Daemon mode started", body="")
except RuntimeError as e:
- output({"error": str(e)}, str(e))
+ output({"error": str(e)}, str(e), body="")
@session.command("daemon-stop")
@@ -353,8 +402,10 @@ def session_daemon_start():
def session_daemon_stop():
"""Stop persistent daemon mode."""
backend.stop_daemon()
- get_session().disable_daemon()
- output({"daemon": "stopped"}, "Daemon mode stopped")
+ sess = get_session()
+ sess.persist_state = True
+ sess.disable_daemon()
+ output({"daemon": "stopped"}, "Daemon mode stopped", body="")
# ── REPL ─────────────────────────────────────────────────────────
diff --git a/browser/agent-harness/cli_anything/browser/core/fs.py b/browser/agent-harness/cli_anything/browser/core/fs.py
index 303cc50072..23637ba636 100644
--- a/browser/agent-harness/cli_anything/browser/core/fs.py
+++ b/browser/agent-harness/cli_anything/browser/core/fs.py
@@ -10,6 +10,8 @@
from typing import TYPE_CHECKING
+from cli_anything.browser.utils.tool_result import tool_result_has_error
+
if TYPE_CHECKING:
from cli_anything.browser.core.session import Session
@@ -28,11 +30,23 @@ def list_elements(session: "Session", path: str = "") -> dict:
Example:
>>> list_elements(session, "/main")
- {"path": "/main", "entries": [{"name": "button", "role": "button", ...}]}
+ {"path": "/", "entries": [{"name": "main", "role": "landmark", ...}]}
"""
target_path = path if path else session.working_dir
- use_daemon = session.daemon_mode
- return backend.ls(target_path, use_daemon=use_daemon)
+ use_daemon = session.daemon_mode or backend.daemon_started()
+
+ # DOMShell ls can silently return an empty list for invalid paths. Preflight
+ # the target with cd, then restore the original working directory.
+ if target_path and target_path != "/":
+ cd_result = backend.cd(target_path, use_daemon=use_daemon)
+ if tool_result_has_error(cd_result):
+ return cd_result
+
+ try:
+ return backend.ls(target_path, use_daemon=use_daemon)
+ finally:
+ if target_path and target_path != "/":
+ backend.cd(session.working_dir or "/", use_daemon=use_daemon)
def change_directory(session: "Session", path: str) -> dict:
@@ -68,11 +82,11 @@ def change_directory(session: "Session", path: str) -> dict:
else:
path = session.working_dir.rstrip("/") + "/" + path
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
result = backend.cd(path, use_daemon=use_daemon)
# Only update working_dir if backend succeeded
- if isinstance(result, dict) and "error" not in result:
- new_working_dir = result.get("path", path)
+ if isinstance(result, dict) and not tool_result_has_error(result):
+ new_working_dir = result.get("path") or result.get("working_dir") or path
session.set_working_dir(new_working_dir)
return result
@@ -92,7 +106,7 @@ def read_element(session: "Session", path: str = "") -> dict:
{"name": "Submit", "role": "button", "text": "Submit", ...}
"""
target_path = path if path else session.working_dir
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
return backend.cat(target_path, use_daemon=use_daemon)
@@ -112,13 +126,13 @@ def grep_elements(session: "Session", pattern: str, path: str = "") -> dict:
{"matches": ["/main/button[0]", "/main/link[1]"]}
"""
target_path = path if path else session.working_dir
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
# DOMShell's grep searches from the server-side CWD. To root the search
# at the requested path, cd there first, grep, then restore.
if target_path and target_path != "/":
cd_result = backend.cd(target_path, use_daemon=use_daemon)
- if hasattr(cd_result, 'isError') and cd_result.isError:
+ if tool_result_has_error(cd_result):
return cd_result
try:
diff --git a/browser/agent-harness/cli_anything/browser/core/page.py b/browser/agent-harness/cli_anything/browser/core/page.py
index 92a939eebb..9056299d74 100644
--- a/browser/agent-harness/cli_anything/browser/core/page.py
+++ b/browser/agent-harness/cli_anything/browser/core/page.py
@@ -14,6 +14,7 @@
from cli_anything.browser.utils import domshell_backend as backend
from cli_anything.browser.utils.security import validate_url
+from cli_anything.browser.utils.tool_result import tool_result_has_error
def open_page(session: "Session", url: str) -> dict:
@@ -41,10 +42,11 @@ def open_page(session: "Session", url: str) -> dict:
if not is_valid:
raise ValueError(error_msg)
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
result = backend.open_url(url, use_daemon=use_daemon)
- session.set_url(url)
- session.set_working_dir("/") # Reset to root on new page
+ if not tool_result_has_error(result):
+ session.set_url(url)
+ session.set_working_dir("/") # Reset to root on new page
return result
@@ -61,7 +63,7 @@ def reload_page(session: "Session") -> dict:
>>> reload_page(session)
{"status": "reloaded", "url": "https://example.com"}
"""
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
result = backend.reload(use_daemon=use_daemon)
return result
@@ -79,7 +81,7 @@ def go_back(session: "Session") -> dict:
>>> go_back(session)
{"url": "https://previous.com", "status": "navigated"}
"""
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
result = backend.back(use_daemon=use_daemon)
# Update session state if backend returned a URL
@@ -102,7 +104,7 @@ def go_forward(session: "Session") -> dict:
>>> go_forward(session)
{"url": "https://next.com", "status": "navigated"}
"""
- use_daemon = session.daemon_mode
+ use_daemon = session.daemon_mode or backend.daemon_started()
result = backend.forward(use_daemon=use_daemon)
# Update session state if backend returned a URL
diff --git a/browser/agent-harness/cli_anything/browser/core/session.py b/browser/agent-harness/cli_anything/browser/core/session.py
index 3422f0373b..7514b404de 100644
--- a/browser/agent-harness/cli_anything/browser/core/session.py
+++ b/browser/agent-harness/cli_anything/browser/core/session.py
@@ -7,8 +7,10 @@
- Daemon mode status
"""
-from typing import Optional
+import json
from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Optional
@dataclass
@@ -28,6 +30,53 @@ class Session:
history: list[str] = field(default_factory=list)
forward_stack: list[str] = field(default_factory=list)
daemon_mode: bool = False
+ persist_state: bool = False
+ state_path: Optional[str] = None
+
+ @classmethod
+ def load_persisted(cls, state_path: Optional[str] = None) -> "Session":
+ """Load a persisted session snapshot if present.
+
+ The returned session is marked persistent so future mutations are written
+ back to disk. If no active daemon snapshot exists, returns a fresh session.
+ """
+ session = cls(persist_state=True, state_path=state_path)
+ try:
+ path = session._resolve_state_path()
+ if not path.exists():
+ return session
+ data = json.loads(path.read_text(encoding="utf-8"))
+ except Exception:
+ return session
+
+ session.current_url = data.get("current_url", "")
+ session.working_dir = data.get("working_dir", "/")
+ session.history = list(data.get("history", []))
+ session.forward_stack = list(data.get("forward_stack", []))
+ session.daemon_mode = bool(data.get("daemon_mode", False))
+ return session
+
+ def _resolve_state_path(self) -> Path:
+ if self.state_path:
+ return Path(self.state_path)
+ return Path.home() / ".cli-anything-browser" / "session.json"
+
+ def save_state(self) -> None:
+ """Persist the current session snapshot when enabled."""
+ if not self.persist_state:
+ return
+ path = self._resolve_state_path()
+ path.parent.mkdir(parents=True, exist_ok=True)
+ payload = {
+ "current_url": self.current_url,
+ "working_dir": self.working_dir,
+ "history": self.history,
+ "forward_stack": self.forward_stack,
+ "daemon_mode": self.daemon_mode,
+ }
+ tmp_path = path.with_suffix(path.suffix + ".tmp")
+ tmp_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
+ tmp_path.replace(path)
def set_url(self, url: str, record_history: bool = True) -> None:
"""Set the current URL and update history.
@@ -40,6 +89,7 @@ def set_url(self, url: str, record_history: bool = True) -> None:
self.history.append(self.current_url)
self.forward_stack.clear() # Clear forward stack on new navigation
self.current_url = url
+ self.save_state()
def go_back(self) -> Optional[str]:
"""Navigate back in history.
@@ -52,6 +102,7 @@ def go_back(self) -> Optional[str]:
previous = self.history.pop()
self.forward_stack.append(self.current_url)
self.current_url = previous
+ self.save_state()
return previous
def go_forward(self) -> Optional[str]:
@@ -65,6 +116,7 @@ def go_forward(self) -> Optional[str]:
next_url = self.forward_stack.pop()
self.history.append(self.current_url)
self.current_url = next_url
+ self.save_state()
return next_url
def set_working_dir(self, path: str) -> None:
@@ -74,14 +126,17 @@ def set_working_dir(self, path: str) -> None:
path: New path (e.g., "/main/div[0]")
"""
self.working_dir = path
+ self.save_state()
def enable_daemon(self) -> None:
"""Enable daemon mode for persistent MCP connection."""
self.daemon_mode = True
+ self.save_state()
def disable_daemon(self) -> None:
"""Disable daemon mode."""
self.daemon_mode = False
+ self.save_state()
def status(self) -> dict:
"""Get session status as a dict.
diff --git a/browser/agent-harness/cli_anything/browser/utils/domshell_backend.py b/browser/agent-harness/cli_anything/browser/utils/domshell_backend.py
index eafc25767b..729f1f22c0 100644
--- a/browser/agent-harness/cli_anything/browser/utils/domshell_backend.py
+++ b/browser/agent-harness/cli_anything/browser/utils/domshell_backend.py
@@ -13,13 +13,25 @@
"""
import asyncio
+import json
import os
+import signal
+import socket
import subprocess
import shutil
+import sys
+from pathlib import Path
from typing import Any, Optional
+
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
+from cli_anything.browser.utils.tool_result import (
+ normalize_tool_result as _normalize_tool_result,
+ tool_result_has_error as _tool_result_has_error,
+)
+
+
# DOMShell MCP server command
# The harness connects to a running DOMShell server via domshell-proxy (stdio bridge).
# Configure via environment variables:
@@ -46,10 +58,202 @@ def _build_server_args() -> list[str]:
]
# Daemon mode: persistent MCP connection
+#
+# v1 used an in-process ClientSession only, which meant `session daemon-start`
+# appeared to work but did not survive across CLI invocations. The upgraded
+# design below runs a small background Unix-socket daemon so any later process
+# can reuse the same browser session.
_daemon_session: Optional[ClientSession] = None
_daemon_read: Optional[Any] = None
_daemon_write: Optional[Any] = None
_daemon_client_context: Optional[Any] = None # Store stdio_client context manager
+_daemon_process: Optional[subprocess.Popen] = None
+
+
+def _daemon_runtime_dir() -> Path:
+ return Path.home() / ".cli-anything-browser"
+
+
+def _daemon_socket_path() -> Path:
+ return _daemon_runtime_dir() / "daemon.sock"
+
+
+def _daemon_pid_path() -> Path:
+ return _daemon_runtime_dir() / "daemon.pid"
+
+
+def _ensure_daemon_runtime_dir() -> Path:
+ path = _daemon_runtime_dir()
+ path.mkdir(parents=True, exist_ok=True)
+ return path
+
+
+def _daemon_is_socket_alive() -> bool:
+ """Return True when the Unix-socket daemon is reachable."""
+ sock = _daemon_socket_path()
+ if not sock.exists():
+ return False
+ try:
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
+ client.settimeout(0.5)
+ client.connect(str(sock))
+ client.sendall(b'{"cmd":"ping"}\n')
+ data = b""
+ while not data.endswith(b"\n"):
+ chunk = client.recv(4096)
+ if not chunk:
+ break
+ data += chunk
+ if not data:
+ return False
+ payload = json.loads(data.decode("utf-8", errors="replace"))
+ return payload.get("ok") is True
+ except OSError:
+ return False
+ except json.JSONDecodeError:
+ return False
+
+
+def daemon_started() -> bool:
+ """Check if any daemon process is currently active."""
+ return _daemon_is_socket_alive() or _daemon_session is not None
+
+
+async def _daemon_request(payload: dict, timeout: float = 30.0) -> dict:
+ """Send one JSON request to the background daemon."""
+ sock = _daemon_socket_path()
+ if not sock.exists():
+ raise RuntimeError("Daemon socket not found")
+
+ reader, writer = await asyncio.open_unix_connection(str(sock))
+ try:
+ writer.write((json.dumps(payload) + "\n").encode("utf-8"))
+ await writer.drain()
+
+ raw = await asyncio.wait_for(reader.readline(), timeout=timeout)
+ if not raw:
+ raise RuntimeError("Daemon disconnected before returning a result")
+ response = json.loads(raw.decode("utf-8", errors="replace"))
+ return response
+ finally:
+ writer.close()
+ try:
+ await writer.wait_closed()
+ except Exception:
+ pass
+
+
+async def _daemon_server_main() -> None:
+ """Background daemon process that keeps one DOMShell MCP session alive."""
+ socket_path = _daemon_socket_path()
+ runtime_dir = _ensure_daemon_runtime_dir()
+ pid_path = _daemon_pid_path()
+
+ # Remove stale socket from a prior crash.
+ try:
+ socket_path.unlink()
+ except FileNotFoundError:
+ pass
+
+ server_params = StdioServerParameters(
+ command=DEFAULT_SERVER_CMD,
+ args=_build_server_args(),
+ )
+
+ async with stdio_client(server_params) as (read, write):
+ async with ClientSession(read, write) as session:
+ await session.initialize()
+
+ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
+ try:
+ raw = await reader.readline()
+ if not raw:
+ return
+ request = json.loads(raw.decode("utf-8", errors="replace"))
+ cmd = request.get("cmd")
+
+ if cmd == "ping":
+ response = {"ok": True}
+ elif cmd == "tool":
+ tool_name = request["tool_name"]
+ arguments = request.get("arguments", {})
+ result = await session.call_tool(tool_name, arguments)
+ response = {"ok": True, "result": _normalize_tool_result(result)}
+ elif cmd == "shutdown":
+ response = {"ok": True, "result": "shutdown"}
+ writer.write((json.dumps(response) + "\n").encode("utf-8"))
+ await writer.drain()
+ return
+ else:
+ response = {"ok": False, "error": f"Unknown command: {cmd}"}
+ except Exception as e:
+ response = {"ok": False, "error": str(e)}
+
+ writer.write((json.dumps(response) + "\n").encode("utf-8"))
+ await writer.drain()
+ try:
+ writer.close()
+ await writer.wait_closed()
+ except Exception:
+ pass
+
+ server = await asyncio.start_unix_server(handle_client, path=str(socket_path))
+ pid_path.write_text(str(os.getpid()), encoding="utf-8")
+ try:
+ async with server:
+ await server.serve_forever()
+ finally:
+ try:
+ socket_path.unlink()
+ except FileNotFoundError:
+ pass
+ try:
+ pid_path.unlink()
+ except FileNotFoundError:
+ pass
+
+
+def _spawn_daemon_process() -> subprocess.Popen:
+ """Spawn the background daemon process detached from this CLI."""
+ _ensure_daemon_runtime_dir()
+ cmd = [
+ sys.executable,
+ "-c",
+ "import asyncio; from cli_anything.browser.utils.domshell_backend import _daemon_server_main; asyncio.run(_daemon_server_main())",
+ ]
+ return subprocess.Popen(
+ cmd,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ start_new_session=True,
+ env=os.environ.copy(),
+ )
+
+
+def _kill_pid(pid: int) -> None:
+ try:
+ os.kill(pid, signal.SIGTERM)
+ except ProcessLookupError:
+ return
+ except PermissionError:
+ return
+
+ # Give it a moment to exit, then force-kill if needed.
+ for _ in range(20):
+ try:
+ os.kill(pid, 0)
+ except ProcessLookupError:
+ return
+ except PermissionError:
+ return
+ except OSError:
+ return
+ import time
+ time.sleep(0.1)
+ try:
+ os.kill(pid, signal.SIGKILL)
+ except Exception:
+ pass
def _check_npx() -> bool:
@@ -130,16 +334,27 @@ async def _call_tool(
"""
global _daemon_session, _daemon_read, _daemon_write
+ # 1) Preferred path: external daemon socket, if present.
+ if use_daemon and _daemon_is_socket_alive():
+ response = await _daemon_request({
+ "cmd": "tool",
+ "tool_name": tool_name,
+ "arguments": arguments,
+ })
+ if not response.get("ok"):
+ raise RuntimeError(response.get("error", "Daemon tool call failed"))
+ return response.get("result")
+
+ # 2) Legacy in-process daemon (kept for backwards compatibility inside the
+ # same Python process / REPL session).
if use_daemon and _daemon_session is not None:
- # Use persistent daemon connection
try:
result = await _daemon_session.call_tool(tool_name, arguments)
- return result
- except Exception as e:
- # Daemon died, fall back to spawning new server
+ return _normalize_tool_result(result)
+ except Exception:
await _stop_daemon()
- # Spawn new MCP server process
+ # 3) One-shot spawn (default path).
server_params = StdioServerParameters(
command=DEFAULT_SERVER_CMD,
args=_build_server_args()
@@ -150,7 +365,7 @@ async def _call_tool(
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(tool_name, arguments)
- return result
+ return _normalize_tool_result(result)
except Exception as e:
raise RuntimeError(
f"DOMShell MCP call failed: {e}\n"
@@ -158,11 +373,9 @@ async def _call_tool(
f"Chrome Web Store: https://chromewebstore.google.com/detail/domshell"
) from e
-# NOTE: Known limitation - Daemon mode uses asyncio.run() per tool call (in sync wrappers).
-# Each asyncio.run() creates a new event loop. Async IO objects created in one loop
-# (like the daemon session) may have issues when accessed from subsequent calls that
-# create new loops. This is a documented limitation for v1; future work should use
-# a single long-lived event loop (e.g., background thread + run_coroutine_threadsafe).
+# NOTE: Old v1 daemon mode used in-process state only, which could not survive
+# across separate CLI invocations. The new daemon uses a background Unix-socket
+# process; the in-process client is kept only as a fallback for the REPL.
async def _start_daemon() -> bool:
"""Start persistent daemon mode.
@@ -172,55 +385,87 @@ async def _start_daemon() -> bool:
Raises:
RuntimeError: If daemon fails to start
"""
- global _daemon_session, _daemon_read, _daemon_write, _daemon_client_context
+ global _daemon_session, _daemon_read, _daemon_write, _daemon_client_context, _daemon_process
- if _daemon_session is not None:
- return True # Already running
+ if _daemon_is_socket_alive():
+ return True
- server_params = StdioServerParameters(
- command=DEFAULT_SERVER_CMD,
- args=_build_server_args()
- )
+ # Clean up stale pid/socket from previous runs.
+ try:
+ stale_pid = _daemon_pid_path().read_text(encoding="utf-8").strip()
+ if stale_pid.isdigit():
+ _kill_pid(int(stale_pid))
+ except Exception:
+ pass
+ try:
+ _daemon_socket_path().unlink()
+ except FileNotFoundError:
+ pass
+ try:
+ _daemon_pid_path().unlink()
+ except FileNotFoundError:
+ pass
try:
- # Store the context manager so we can properly clean it up later
- _daemon_client_context = stdio_client(server_params)
- _daemon_read, _daemon_write = await _daemon_client_context.__aenter__()
- _daemon_session = ClientSession(_daemon_read, _daemon_write)
- await _daemon_session.__aenter__()
- await _daemon_session.initialize()
- return True
+ _daemon_process = _spawn_daemon_process()
except Exception as e:
- _daemon_session = None
- _daemon_read = None
- _daemon_write = None
- _daemon_client_context = None
- raise RuntimeError(f"Failed to start DOMShell daemon: {e}") from e
+ raise RuntimeError(f"Failed to spawn DOMShell daemon process: {e}") from e
+
+ # Wait for the socket to become responsive.
+ for _ in range(100):
+ if _daemon_is_socket_alive():
+ return True
+ await asyncio.sleep(0.1)
+
+ raise RuntimeError("Failed to start DOMShell daemon: socket never became ready")
async def _stop_daemon() -> None:
"""Stop persistent daemon mode."""
- global _daemon_session, _daemon_read, _daemon_write, _daemon_client_context
-
- if _daemon_session is None:
- return
+ global _daemon_session, _daemon_read, _daemon_write, _daemon_client_context, _daemon_process
+ # Stop in-process session if present.
+ if _daemon_session is not None:
+ try:
+ await _daemon_session.__aexit__(None, None, None)
+ if _daemon_client_context:
+ await _daemon_client_context.__aexit__(None, None, None)
+ except Exception:
+ pass
+ finally:
+ _daemon_session = None
+ _daemon_read = None
+ _daemon_write = None
+ _daemon_client_context = None
+
+ # Stop external daemon process if present.
+ pid = None
try:
- await _daemon_session.__aexit__(None, None, None)
- if _daemon_client_context:
- await _daemon_client_context.__aexit__(None, None, None)
+ pid_text = _daemon_pid_path().read_text(encoding="utf-8").strip()
+ if pid_text.isdigit():
+ pid = int(pid_text)
except Exception:
- pass # Ignore cleanup errors
- finally:
- _daemon_session = None
- _daemon_read = None
- _daemon_write = None
- _daemon_client_context = None
+ pid = None
+
+ if pid is not None:
+ _kill_pid(pid)
+
+ try:
+ _daemon_socket_path().unlink()
+ except FileNotFoundError:
+ pass
+ try:
+ _daemon_pid_path().unlink()
+ except FileNotFoundError:
+ pass
+
+ _daemon_process = None
def daemon_started() -> bool:
"""Check if daemon mode is active."""
- return _daemon_session is not None
+ return _daemon_is_socket_alive() or _daemon_session is not None
+
# ── Sync wrappers for each DOMShell tool ─────────────────────────────
@@ -392,11 +637,23 @@ def type_text(path: str, text: str, use_daemon: bool = False) -> dict:
Returns:
Dict with action result
"""
+ # Fast preflight: if the target path doesn't exist, return immediately and
+ # do not enter the focus/type flow (which can otherwise hang on bad paths).
+ preflight = cat(path, use_daemon=use_daemon)
+ preflight = _normalize_tool_result(preflight)
+ if _tool_result_has_error(preflight):
+ return preflight
+
async def _focus_and_type():
global _daemon_session
+
if use_daemon and _daemon_session is not None:
- await _daemon_session.call_tool("domshell_focus", {"name": path})
- return await _daemon_session.call_tool("domshell_type", {"text": text})
+ focus_result = await _daemon_session.call_tool("domshell_focus", {"name": path})
+ focus_result = _normalize_tool_result(focus_result)
+ if _tool_result_has_error(focus_result):
+ return focus_result
+ type_result = await _daemon_session.call_tool("domshell_type", {"text": text})
+ return _normalize_tool_result(type_result)
server_params = StdioServerParameters(
command=DEFAULT_SERVER_CMD,
@@ -405,8 +662,12 @@ async def _focus_and_type():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
- await session.call_tool("domshell_focus", {"name": path})
- return await session.call_tool("domshell_type", {"text": text})
+ focus_result = await session.call_tool("domshell_focus", {"name": path})
+ focus_result = _normalize_tool_result(focus_result)
+ if _tool_result_has_error(focus_result):
+ return focus_result
+ type_result = await session.call_tool("domshell_type", {"text": text})
+ return _normalize_tool_result(type_result)
return asyncio.run(_focus_and_type())
diff --git a/browser/agent-harness/cli_anything/browser/utils/tool_result.py b/browser/agent-harness/cli_anything/browser/utils/tool_result.py
new file mode 100644
index 0000000000..41f611d605
--- /dev/null
+++ b/browser/agent-harness/cli_anything/browser/utils/tool_result.py
@@ -0,0 +1,138 @@
+"""Shared helpers for DOMShell/MCP tool results.
+
+The browser harness often receives payloads where `isError=False` but the text
+payload clearly says the operation failed. This module centralizes the best-
+effort classification and message extraction so command handlers can make one
+consistent decision instead of duplicating heuristics.
+"""
+
+from __future__ import annotations
+
+from typing import Any, Iterable
+
+_FAILURE_TOKENS = (
+ "no such",
+ "failed",
+ "could not",
+ "cannot",
+ "invalid",
+ "not found",
+ "denied",
+ "timed out",
+ "timeout",
+ "error occurred",
+ "an error occurred",
+ "error:",
+ "fatal",
+)
+
+_ERROR_STATUS_VALUES = {"error", "failed", "failure"}
+
+
+def normalize_tool_result(result: Any) -> Any:
+ """Convert tool result objects into JSON-friendly Python data."""
+ if hasattr(result, "model_dump"):
+ return result.model_dump()
+ if hasattr(result, "dict"):
+ try:
+ return result.dict()
+ except Exception:
+ pass
+ if isinstance(result, (dict, list, str, int, float, bool)) or result is None:
+ return result
+ return str(result)
+
+
+def _iter_text_payloads(result: Any) -> Iterable[str]:
+ data = normalize_tool_result(result)
+ if isinstance(data, str):
+ yield data
+ return
+ if not isinstance(data, dict):
+ return
+
+ for key in ("error", "message", "detail", "stdout"):
+ value = data.get(key)
+ if isinstance(value, str) and value.strip():
+ yield value
+
+ content = data.get("content")
+ if isinstance(content, list):
+ for item in content:
+ if isinstance(item, dict):
+ text = item.get("text", "")
+ if isinstance(text, str) and text.strip():
+ yield text
+ elif isinstance(item, str) and item.strip():
+ yield item
+
+
+def _looks_like_failure_text(text: str) -> bool:
+ lowered = text.lower().strip()
+ if not lowered:
+ return False
+ return any(token in lowered for token in _FAILURE_TOKENS)
+
+
+def tool_result_has_error(result: Any) -> bool:
+ """Best-effort detection of a tool failure payload."""
+ data = normalize_tool_result(result)
+ if isinstance(data, str):
+ return _looks_like_failure_text(data)
+ if not isinstance(data, dict):
+ return False
+
+ if data.get("error") is not None:
+ return True
+ if data.get("isError") is True:
+ return True
+ status = data.get("status")
+ if isinstance(status, str) and status.lower().strip() in _ERROR_STATUS_VALUES:
+ return True
+
+ for text in _iter_text_payloads(data):
+ if _looks_like_failure_text(text):
+ return True
+
+ return False
+
+
+def tool_result_error_text(result: Any, default: str = "") -> str:
+ """Extract a concise human-readable error text from a tool result."""
+ data = normalize_tool_result(result)
+ if isinstance(data, str):
+ return data.strip() or default
+ if not isinstance(data, dict):
+ return default
+
+ error_value = data.get("error")
+ if error_value is not None:
+ if isinstance(error_value, str) and error_value.strip():
+ return error_value
+ return str(error_value)
+
+ for text in _iter_text_payloads(data):
+ if _looks_like_failure_text(text):
+ return text
+
+ return default
+
+
+def tool_result_body_text(result: Any, default: str = "") -> str:
+ """Extract a concise success payload for human-readable output.
+
+ Joins all textual payload fragments into a single string. If nothing useful
+ is present, returns `default`.
+ """
+ data = normalize_tool_result(result)
+ if isinstance(data, str):
+ text = data.strip()
+ return text or default
+ if not isinstance(data, dict):
+ return default
+
+ lines = [text.strip() for text in _iter_text_payloads(data) if text.strip()]
+ if lines:
+ return "\n".join(lines)
+
+ return default
From d653b77122ec50c4f2097eb391717dd307115357 Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 01:24:10 -0700
Subject: [PATCH 2/6] refactor(repl-skin): sync canonical template across
harnesses
---
README.md | 2 +
.../adguardhome/utils/repl_skin.py | 94 ++--
.../cli_anything/anygen/utils/repl_skin.py | 96 ++--
.../cli_anything/audacity/utils/repl_skin.py | 94 ++--
.../cli_anything/blender/utils/repl_skin.py | 94 ++--
.../cli_anything/browser/tests/test_core.py | 262 +++++++++-
.../cli_anything/browser/utils/repl_skin.py | 116 ++---
.../cli_anything/chromadb/utils/repl_skin.py | 112 ++--
cli-anything-plugin/repl_skin.py | 87 +---
.../cloudanalyzer/utils/repl_skin.py | 87 +---
.../cloudcompare/utils/repl_skin.py | 87 +---
.../dify_workflow/utils/repl_skin.py | 87 +---
.../cli_anything/drawio/utils/repl_skin.py | 96 ++--
.../eth2_quickstart/utils/repl_skin.py | 94 ++--
.../cli_anything/exa/utils/repl_skin.py | 87 +---
.../cli_anything/freecad/utils/repl_skin.py | 89 +---
.../cli_anything/gimp/utils/repl_skin.py | 94 ++--
.../cli_anything/godot/utils/repl_skin.py | 87 +---
.../cli_anything/inkscape/utils/repl_skin.py | 94 ++--
.../intelwatch/utils/repl_skin.py | 94 ++--
.../iterm2_ctl/utils/repl_skin.py | 87 +---
.../cli_anything/kdenlive/utils/repl_skin.py | 94 ++--
.../cli_anything/krita/utils/repl_skin.py | 89 +---
.../libreoffice/utils/repl_skin.py | 94 ++--
.../cli_anything/mermaid/utils/repl_skin.py | 482 ++++++++++++++++--
.../cli_anything/mubu/utils/repl_skin.py | 87 +---
.../cli_anything/musescore/utils/repl_skin.py | 94 ++--
.../cli_anything/n8n/utils/repl_skin.py | 246 +--------
.../cli_anything/novita/utils/repl_skin.py | 208 +++-----
.../obs_studio/utils/repl_skin.py | 94 ++--
.../cli_anything/obsidian/utils/repl_skin.py | 112 ++--
.../cli_anything/ollama/utils/repl_skin.py | 112 ++--
.../openscreen/utils/repl_skin.py | 114 ++---
.../cli_anything/pm2/utils/repl_skin.py | 112 ++--
.../cli_anything/renderdoc/utils/repl_skin.py | 87 +---
.../cli_anything/rms/utils/repl_skin.py | 90 +---
.../cli_anything/seaclip/utils/repl_skin.py | 112 ++--
.../cli_anything/shotcut/utils/repl_skin.py | 94 ++--
.../slay_the_spire_ii/utils/repl_skin.py | 87 +---
.../videocaptioner/utils/repl_skin.py | 112 ++--
.../cli_anything/wiremock/utils/repl_skin.py | 87 +---
.../cli_anything/zoom/utils/repl_skin.py | 96 ++--
.../cli_anything/zotero/utils/repl_skin.py | 87 +---
43 files changed, 1869 insertions(+), 2961 deletions(-)
diff --git a/README.md b/README.md
index 47cdbf52eb..c82ce26098 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ CLI-Anything: Bridging the Gap Between AI Agents and the World's Software
diff --git a/adguardhome/agent-harness/cli_anything/adguardhome/utils/repl_skin.py b/adguardhome/agent-harness/cli_anything/adguardhome/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/adguardhome/agent-harness/cli_anything/adguardhome/utils/repl_skin.py
+++ b/adguardhome/agent-harness/cli_anything/adguardhome/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/anygen/agent-harness/cli_anything/anygen/utils/repl_skin.py b/anygen/agent-harness/cli_anything/anygen/utils/repl_skin.py
index a444e04589..6f918f96a2 100644
--- a/anygen/agent-harness/cli_anything/anygen/utils/repl_skin.py
+++ b/anygen/agent-harness/cli_anything/anygen/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "anygen": "\033[38;5;141m", # soft violet
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -106,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -494,7 +463,6 @@ def toolbar():
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
- "\033[38;5;141m": "#af87ff", # anygen soft violet
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}
diff --git a/audacity/agent-harness/cli_anything/audacity/utils/repl_skin.py b/audacity/agent-harness/cli_anything/audacity/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/audacity/agent-harness/cli_anything/audacity/utils/repl_skin.py
+++ b/audacity/agent-harness/cli_anything/audacity/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/blender/agent-harness/cli_anything/blender/utils/repl_skin.py b/blender/agent-harness/cli_anything/blender/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/blender/agent-harness/cli_anything/blender/utils/repl_skin.py
+++ b/blender/agent-harness/cli_anything/blender/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/browser/agent-harness/cli_anything/browser/tests/test_core.py b/browser/agent-harness/cli_anything/browser/tests/test_core.py
index cb807a283a..66c0b22e64 100644
--- a/browser/agent-harness/cli_anything/browser/tests/test_core.py
+++ b/browser/agent-harness/cli_anything/browser/tests/test_core.py
@@ -6,11 +6,44 @@
python -m pytest cli_anything/browser/tests/test_core.py -v
"""
-import pytest
from unittest.mock import AsyncMock, MagicMock, patch
+import pytest
+
from cli_anything.browser.core.session import Session
from cli_anything.browser.core import page, fs
+from cli_anything.browser.utils.repl_skin import ReplSkin
+
+
+@pytest.fixture(autouse=True)
+def force_non_daemon(monkeypatch):
+ """Keep unit tests isolated from any real daemon running on the machine."""
+ monkeypatch.setattr(fs.backend, "daemon_started", lambda: False)
+
+
+class TestReplSkin:
+ """Test compact REPL prompt/help formatting."""
+
+ def test_prompt_is_plain_text(self):
+ skin = ReplSkin("browser", version="1.0.0")
+ assert skin.prompt(context="https://example.com /") == "browser [https://example.com /] > "
+
+ def test_help_is_compact(self, capsys):
+ skin = ReplSkin("browser", version="1.0.0")
+ skin.help({"page": "open|reload", "fs": "ls|cd|cat|grep|pwd"})
+ out = capsys.readouterr().out
+ assert out == "Commands:\n page open|reload\n fs ls|cd|cat|grep|pwd\n\n"
+
+ def test_banner_and_goodbye_are_compact(self, capsys):
+ skin = ReplSkin("browser", version="1.0.0")
+ skin.print_banner()
+ skin.print_goodbye()
+ out = capsys.readouterr().out
+ assert "Browser v1.0.0" in out
+ assert "Type help for commands, quit to exit" in out
+ assert "Goodbye!" in out
+ assert "╭" not in out
+ assert "◆" not in out
# ── Session Tests ────────────────────────────────────────────────
@@ -120,6 +153,22 @@ def test_status(self):
assert status["forward_stack_length"] == 0
assert not status["daemon_mode"]
+ def test_persisted_session_round_trip(self, tmp_path):
+ """Persistent sessions can round-trip to disk."""
+ state_file = tmp_path / "session.json"
+ sess = Session(persist_state=True, state_path=str(state_file))
+ sess.enable_daemon()
+ sess.set_url("https://example.com")
+ sess.set_working_dir("/main")
+ sess.set_url("https://example.org")
+
+ loaded = Session.load_persisted(str(state_file))
+ assert loaded.daemon_mode is True
+ assert loaded.current_url == "https://example.org"
+ assert loaded.working_dir == "/main"
+ assert loaded.history == ["https://example.com"]
+ assert loaded.forward_stack == []
+
# ── Page Module Tests ────────────────────────────────────────────
@@ -197,6 +246,202 @@ def test_get_page_info(self):
assert result["url"] == "https://example.com"
assert result["working_dir"] == "/main"
+ def test_open_page_rejects_error_payload(self):
+ """Open page should not mutate session on backend error payloads."""
+ sess = Session()
+ sess.set_url("https://first.com")
+ sess.set_working_dir("/main")
+
+ with patch("cli_anything.browser.core.page.backend.open_url") as mock_open:
+ mock_open.return_value = {
+ "content": [{"type": "text", "text": "Could not open"}],
+ "isError": False,
+ }
+
+ result = page.open_page(sess, "https://second.com")
+
+ assert sess.current_url == "https://first.com"
+ assert sess.working_dir == "/main"
+ assert result["isError"] is False
+ mock_open.assert_called_once_with("https://second.com", use_daemon=False)
+
+ def test_act_click_rejects_error_payload(self):
+ """CLI click should not claim success on backend error payloads."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.backend.click") as mock_click:
+ mock_click.return_value = {
+ "content": [{"type": "text", "text": "click: /main/does-not-exist: No such element"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["act", "click", "/main/does-not-exist"])
+ assert result.exit_code == 0
+ assert "Clicked:" not in result.output
+ assert "No such element" in result.output
+
+ def test_act_type_rejects_error_payload(self):
+ """CLI type should not claim success on backend error payloads."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.backend.type_text") as mock_type:
+ mock_type.return_value = {
+ "content": [{"type": "text", "text": "type failed: input not found"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["act", "type", "/main/does-not-exist", "hello"])
+ assert result.exit_code == 0
+ assert "Typed into:" not in result.output
+ assert "input not found" in result.output
+
+ def test_type_text_preflight_blocks_invalid_path(self):
+ """type_text should return fast on invalid paths instead of hanging."""
+ from cli_anything.browser.utils import domshell_backend as backend
+
+ with patch("cli_anything.browser.utils.domshell_backend.cat") as mock_cat, \
+ patch("cli_anything.browser.utils.domshell_backend.asyncio.run") as mock_run:
+ mock_cat.return_value = {
+ "content": [{"type": "text", "text": "cat: /main/does-not-exist: No such file or directory"}],
+ "isError": False,
+ }
+ mock_run.side_effect = AssertionError("asyncio.run should not be called for invalid paths")
+ result = backend.type_text("/main/does-not-exist", "hello", use_daemon=True)
+ assert isinstance(result, dict)
+ assert "No such file or directory" in result["content"][0]["text"]
+
+ def test_page_reload_rejects_error_payload(self):
+ """Reload should not claim success on backend error payloads."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.page_mod.reload_page") as mock_reload:
+ mock_reload.return_value = {
+ "content": [{"type": "text", "text": "reload failed: could not refresh"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["page", "reload"])
+ assert result.exit_code == 0
+ assert "Page reloaded" not in result.output
+ assert "reload failed" in result.output
+ assert "content:" not in result.output
+ assert "isError" not in result.output
+
+ def test_page_back_rejects_error_payload(self):
+ """Back should not claim success on backend error payloads."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.page_mod.go_back") as mock_back:
+ mock_back.return_value = {
+ "content": [{"type": "text", "text": "back failed: No history"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["page", "back"])
+ assert result.exit_code == 0
+ assert "Navigated back" not in result.output
+ assert "No history" in result.output
+ assert "content:" not in result.output
+ assert "isError" not in result.output
+
+ def test_fs_ls_rejects_error_payload(self):
+ """ls should not claim success when backend returns an error payload."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.fs_mod.list_elements") as mock_ls:
+ mock_ls.return_value = {
+ "content": [{"type": "text", "text": "ls failed: No such directory"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["fs", "ls", "/main"])
+ assert result.exit_code == 0
+ assert "No elements at" not in result.output
+ assert "No such directory" in result.output
+ assert "content:" not in result.output
+ assert "isError" not in result.output
+
+ def test_fs_cat_rejects_error_payload(self):
+ """cat should show the backend error text instead of a fake success payload."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.fs_mod.read_element") as mock_cat:
+ mock_cat.return_value = {
+ "content": [{"type": "text", "text": "cat: /main/does-not-exist: No such file or directory"}],
+ "isError": False,
+ }
+ result = runner.invoke(cli, ["fs", "cat", "/main/does-not-exist"])
+ assert result.exit_code == 0
+ assert "No such file or directory" in result.output
+ assert "content:" not in result.output
+ assert "isError" not in result.output
+
+ def test_page_info_is_concise(self):
+ """page info should render a concise summary instead of raw payload."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ result = runner.invoke(cli, ["page", "info"])
+ assert result.exit_code == 0
+ assert "URL:" in result.output
+ assert "Working dir:" in result.output
+ assert "current_url" not in result.output
+ assert "working_dir" not in result.output
+ assert "content:" not in result.output
+
+ def test_session_status_is_concise(self):
+ """session status should render a concise summary instead of raw payload."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ result = runner.invoke(cli, ["session", "status"])
+ assert result.exit_code == 0
+ assert "URL:" in result.output
+ assert "Working dir:" in result.output
+ assert "History:" in result.output
+ assert "Forward:" in result.output
+ assert "Daemon:" in result.output
+ assert "current_url" not in result.output
+ assert "forward_stack_length" not in result.output
+
+ def test_session_daemon_start_is_concise(self):
+ """daemon start should not print the payload dict in normal mode."""
+ from cli_anything.browser.browser_cli import cli
+ from click.testing import CliRunner
+
+ runner = CliRunner()
+ with patch("cli_anything.browser.browser_cli.backend.start_daemon"):
+ result = runner.invoke(cli, ["session", "daemon-start"])
+ assert result.exit_code == 0
+ assert "Daemon mode started" in result.output
+ assert "daemon:" not in result.output
+
+ def test_change_directory_rejects_text_error_payload(self):
+ """cd should not update working_dir when backend returns an error text payload."""
+ sess = Session()
+ sess.set_working_dir("/")
+
+ with patch("cli_anything.browser.core.fs.backend.cd") as mock_cd:
+ mock_cd.return_value = {
+ "content": [{"type": "text", "text": "cd: main: No such directory"}],
+ "isError": False,
+ }
+
+ result = fs.change_directory(sess, "/main")
+
+ assert sess.working_dir == "/"
+ assert result["isError"] is False
+ mock_cd.assert_called_once_with("/main", use_daemon=False)
+
# ── Filesystem Module Tests ───────────────────────────────────────
@@ -208,7 +453,9 @@ def test_list_elements(self):
sess = Session()
sess.set_working_dir("/main")
- with patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ with patch("cli_anything.browser.core.fs.backend.cd") as mock_cd, \
+ patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ mock_cd.return_value = {"path": "/main", "status": "changed"}
mock_ls.return_value = {
"path": "/main",
"entries": [{"name": "button", "role": "button", "path": "/main/button[0]"}]
@@ -216,6 +463,7 @@ def test_list_elements(self):
result = fs.list_elements(sess)
+ mock_cd.assert_any_call("/main", use_daemon=False)
mock_ls.assert_called_once_with("/main", use_daemon=False)
def test_list_elements_with_path(self):
@@ -223,11 +471,14 @@ def test_list_elements_with_path(self):
sess = Session()
sess.set_working_dir("/main")
- with patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ with patch("cli_anything.browser.core.fs.backend.cd") as mock_cd, \
+ patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ mock_cd.return_value = {"path": "/div", "status": "changed"}
mock_ls.return_value = {"path": "/div", "entries": []}
result = fs.list_elements(sess, "/div")
+ mock_cd.assert_any_call("/div", use_daemon=False)
mock_ls.assert_called_once_with("/div", use_daemon=False)
def test_list_elements_empty_path_uses_working_dir(self):
@@ -235,11 +486,14 @@ def test_list_elements_empty_path_uses_working_dir(self):
sess = Session()
sess.set_working_dir("/main")
- with patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ with patch("cli_anything.browser.core.fs.backend.cd") as mock_cd, \
+ patch("cli_anything.browser.core.fs.backend.ls") as mock_ls:
+ mock_cd.return_value = {"path": "/main", "status": "changed"}
mock_ls.return_value = {"path": "/main", "entries": []}
result = fs.list_elements(sess, "")
+ mock_cd.assert_any_call("/main", use_daemon=False)
mock_ls.assert_called_once_with("/main", use_daemon=False)
def test_change_directory_absolute_path(self):
diff --git a/browser/agent-harness/cli_anything/browser/utils/repl_skin.py b/browser/agent-harness/cli_anything/browser/utils/repl_skin.py
index dab3acb5af..6f918f96a2 100644
--- a/browser/agent-harness/cli_anything/browser/utils/repl_skin.py
+++ b/browser/agent-harness/cli_anything/browser/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("browser", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="https://example.com", modified=False)
- skin.success("Page loaded")
- skin.error("Connection failed")
- skin.warning("DOMShell not found")
- skin.info("Navigating...")
- skin.status("URL", "https://example.com")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,8 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
- "browser": "\033[38;5;141m", # lavender (browser harness)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -99,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "browser").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -144,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Browser
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -189,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -221,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -234,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -301,7 +271,7 @@ def section(self, title: str):
print(f" {self._c(self.accent + _BOLD, title)}")
print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}")
- # ── Status display ───────────────────────────────────────────────
+ # ── Status display ────────────────────────────────────────────────
def status(self, label: str, value: str):
"""Print a key-value status line."""
@@ -395,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -495,8 +463,6 @@ def toolbar():
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
- "\033[38;5;141m": "#afafff", # browser lavender
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/chromadb/agent-harness/cli_anything/chromadb/utils/repl_skin.py b/chromadb/agent-harness/cli_anything/chromadb/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/chromadb/agent-harness/cli_anything/chromadb/utils/repl_skin.py
+++ b/chromadb/agent-harness/cli_anything/chromadb/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/cli-anything-plugin/repl_skin.py b/cli-anything-plugin/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/cli-anything-plugin/repl_skin.py
+++ b/cli-anything-plugin/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/cloudanalyzer/agent-harness/cli_anything/cloudanalyzer/utils/repl_skin.py b/cloudanalyzer/agent-harness/cli_anything/cloudanalyzer/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/cloudanalyzer/agent-harness/cli_anything/cloudanalyzer/utils/repl_skin.py
+++ b/cloudanalyzer/agent-harness/cli_anything/cloudanalyzer/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/cloudcompare/agent-harness/cli_anything/cloudcompare/utils/repl_skin.py b/cloudcompare/agent-harness/cli_anything/cloudcompare/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/cloudcompare/agent-harness/cli_anything/cloudcompare/utils/repl_skin.py
+++ b/cloudcompare/agent-harness/cli_anything/cloudcompare/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/dify-workflow/agent-harness/cli_anything/dify_workflow/utils/repl_skin.py b/dify-workflow/agent-harness/cli_anything/dify_workflow/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/dify-workflow/agent-harness/cli_anything/dify_workflow/utils/repl_skin.py
+++ b/dify-workflow/agent-harness/cli_anything/dify_workflow/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/drawio/agent-harness/cli_anything/drawio/utils/repl_skin.py b/drawio/agent-harness/cli_anything/drawio/utils/repl_skin.py
index 93e2f352fd..6f918f96a2 100644
--- a/drawio/agent-harness/cli_anything/drawio/utils/repl_skin.py
+++ b/drawio/agent-harness/cli_anything/drawio/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "drawio": "\033[38;5;202m", # draw.io orange
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -106,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -495,6 +464,5 @@ def toolbar():
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
- "\033[38;5;202m": "#ff5f00", # drawio orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}
diff --git a/eth2-quickstart/agent-harness/cli_anything/eth2_quickstart/utils/repl_skin.py b/eth2-quickstart/agent-harness/cli_anything/eth2_quickstart/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/eth2-quickstart/agent-harness/cli_anything/eth2_quickstart/utils/repl_skin.py
+++ b/eth2-quickstart/agent-harness/cli_anything/eth2_quickstart/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/exa/agent-harness/cli_anything/exa/utils/repl_skin.py b/exa/agent-harness/cli_anything/exa/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/exa/agent-harness/cli_anything/exa/utils/repl_skin.py
+++ b/exa/agent-harness/cli_anything/exa/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/freecad/agent-harness/cli_anything/freecad/utils/repl_skin.py b/freecad/agent-harness/cli_anything/freecad/utils/repl_skin.py
index 962c97bb8d..6f918f96a2 100644
--- a/freecad/agent-harness/cli_anything/freecad/utils/repl_skin.py
+++ b/freecad/agent-harness/cli_anything/freecad/utils/repl_skin.py
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "freecad": "\033[38;5;196m", # FreeCAD red
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -156,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -211,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -243,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -256,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -417,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -518,6 +464,5 @@ def toolbar():
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
- "\033[38;5;196m": "#ff0000", # freecad red
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}
diff --git a/gimp/agent-harness/cli_anything/gimp/utils/repl_skin.py b/gimp/agent-harness/cli_anything/gimp/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/gimp/agent-harness/cli_anything/gimp/utils/repl_skin.py
+++ b/gimp/agent-harness/cli_anything/gimp/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/godot/agent-harness/cli_anything/godot/utils/repl_skin.py b/godot/agent-harness/cli_anything/godot/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/godot/agent-harness/cli_anything/godot/utils/repl_skin.py
+++ b/godot/agent-harness/cli_anything/godot/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/inkscape/agent-harness/cli_anything/inkscape/utils/repl_skin.py b/inkscape/agent-harness/cli_anything/inkscape/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/inkscape/agent-harness/cli_anything/inkscape/utils/repl_skin.py
+++ b/inkscape/agent-harness/cli_anything/inkscape/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/intelwatch/agent-harness/cli_anything/intelwatch/utils/repl_skin.py b/intelwatch/agent-harness/cli_anything/intelwatch/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/intelwatch/agent-harness/cli_anything/intelwatch/utils/repl_skin.py
+++ b/intelwatch/agent-harness/cli_anything/intelwatch/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/iterm2/agent-harness/cli_anything/iterm2_ctl/utils/repl_skin.py b/iterm2/agent-harness/cli_anything/iterm2_ctl/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/iterm2/agent-harness/cli_anything/iterm2_ctl/utils/repl_skin.py
+++ b/iterm2/agent-harness/cli_anything/iterm2_ctl/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/kdenlive/agent-harness/cli_anything/kdenlive/utils/repl_skin.py b/kdenlive/agent-harness/cli_anything/kdenlive/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/kdenlive/agent-harness/cli_anything/kdenlive/utils/repl_skin.py
+++ b/kdenlive/agent-harness/cli_anything/kdenlive/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/krita/agent-harness/cli_anything/krita/utils/repl_skin.py b/krita/agent-harness/cli_anything/krita/utils/repl_skin.py
index c3fd1db2fc..6f918f96a2 100644
--- a/krita/agent-harness/cli_anything/krita/utils/repl_skin.py
+++ b/krita/agent-harness/cli_anything/krita/utils/repl_skin.py
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "krita": "\033[38;5;98m", # purple (Krita brand)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -156,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -211,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -243,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -256,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -417,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -516,7 +462,6 @@ def toolbar():
"\033[38;5;55m": "#5f00af", # obs purple
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
- "\033[38;5;98m": "#875fd7", # krita purple
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
diff --git a/libreoffice/agent-harness/cli_anything/libreoffice/utils/repl_skin.py b/libreoffice/agent-harness/cli_anything/libreoffice/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/libreoffice/agent-harness/cli_anything/libreoffice/utils/repl_skin.py
+++ b/libreoffice/agent-harness/cli_anything/libreoffice/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/mermaid/agent-harness/cli_anything/mermaid/utils/repl_skin.py b/mermaid/agent-harness/cli_anything/mermaid/utils/repl_skin.py
index 5de4ac4204..6f918f96a2 100644
--- a/mermaid/agent-harness/cli_anything/mermaid/utils/repl_skin.py
+++ b/mermaid/agent-harness/cli_anything/mermaid/utils/repl_skin.py
@@ -1,44 +1,468 @@
-"""Minimal REPL skin compatible with CLI-Anything REPL usage."""
+"""cli-anything REPL Skin — Unified terminal interface for all CLI harnesses.
-from __future__ import annotations
+Copy this file into your CLI package at:
+ cli_anything//utils/repl_skin.py
-from prompt_toolkit import PromptSession
-from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
-from prompt_toolkit.history import InMemoryHistory
+Usage:
+ from cli_anything..utils.repl_skin import ReplSkin
+
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
+ skin.table(headers, rows)
+ skin.print_goodbye()
+"""
+
+import os
+import sys
+
+# ── ANSI color codes (no external deps for core styling) ──────────────
+
+_RESET = "\033[0m"
+_BOLD = "\033[1m"
+_DIM = "\033[2m"
+_ITALIC = "\033[3m"
+_UNDERLINE = "\033[4m"
+
+# Brand colors
+_CYAN = "\033[38;5;80m" # cli-anything brand cyan
+_CYAN_BG = "\033[48;5;80m"
+_WHITE = "\033[97m"
+_GRAY = "\033[38;5;245m"
+_DARK_GRAY = "\033[38;5;240m"
+_LIGHT_GRAY = "\033[38;5;250m"
+
+# Software accent colors — each software gets a unique accent
+_ACCENT_COLORS = {
+ "gimp": "\033[38;5;214m", # warm orange
+ "blender": "\033[38;5;208m", # deep orange
+ "inkscape": "\033[38;5;39m", # bright blue
+ "audacity": "\033[38;5;33m", # navy blue
+ "libreoffice": "\033[38;5;40m", # green
+ "obs_studio": "\033[38;5;55m", # purple
+ "kdenlive": "\033[38;5;69m", # slate blue
+ "shotcut": "\033[38;5;35m", # teal green
+}
+_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
+
+# Status colors
+_GREEN = "\033[38;5;78m"
+_YELLOW = "\033[38;5;220m"
+_RED = "\033[38;5;196m"
+_BLUE = "\033[38;5;75m"
+_MAGENTA = "\033[38;5;176m"
+
+# ── Brand icon ────────────────────────────────────────────────────────
+
+# The cli-anything icon: a small colored diamond/chevron mark
+_ICON = f"{_CYAN}{_BOLD}◆{_RESET}"
+_ICON_SMALL = f"{_CYAN}▸{_RESET}"
+
+# ── Box drawing characters ────────────────────────────────────────────
+
+_H_LINE = "─"
+_V_LINE = "│"
+_TL = "╭"
+_TR = "╮"
+_BL = "╰"
+_BR = "╯"
+_T_DOWN = "┬"
+_T_UP = "┴"
+_T_RIGHT = "├"
+_T_LEFT = "┤"
+_CROSS = "┼"
+
+
+def _strip_ansi(text: str) -> str:
+ """Remove ANSI escape codes for length calculation."""
+ import re
+ return re.sub(r"\033\[[^m]*m", "", text)
+
+
+def _visible_len(text: str) -> int:
+ """Get visible length of text (excluding ANSI codes)."""
+ return len(_strip_ansi(text))
class ReplSkin:
- def __init__(self, software: str, version: str = "1.0.0"):
- self.software = software
+ """Unified REPL skin for cli-anything CLIs.
+
+ Provides consistent branding, prompts, and message formatting
+ across all CLI harnesses built with the cli-anything methodology.
+ """
+
+ def __init__(self, software: str, version: str = "1.0.0",
+ history_file: str | None = None, skill_path: str | None = None):
+ """Initialize the REPL skin.
+
+ Args:
+ software: Software name (e.g., "gimp", "shotcut", "blender").
+ version: CLI version string.
+ history_file: Path for persistent command history.
+ Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
+ """
+ self.software = software.lower().replace("-", "_")
+ self.display_name = software.replace("_", " ").title()
self.version = version
- def print_banner(self) -> None:
- print(f"cli-anything-{self.software} v{self.version}")
- print("Type help for commands, quit to exit")
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
+ self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
+
+ # History file
+ if history_file is None:
+ from pathlib import Path
+ hist_dir = Path.home() / f".cli-anything-{self.software}"
+ hist_dir.mkdir(parents=True, exist_ok=True)
+ self.history_file = str(hist_dir / "history")
+ else:
+ self.history_file = history_file
+
+ # Detect terminal capabilities
+ self._color = self._detect_color_support()
+
+ def _detect_color_support(self) -> bool:
+ """Check if terminal supports color."""
+ if os.environ.get("NO_COLOR"):
+ return False
+ if os.environ.get("CLI_ANYTHING_NO_COLOR"):
+ return False
+ if not hasattr(sys.stdout, "isatty"):
+ return False
+ return sys.stdout.isatty()
+
+ def _c(self, code: str, text: str) -> str:
+ """Apply color code if colors are supported."""
+ if not self._color:
+ return text
+ return f"{code}{text}{_RESET}"
+
+ # ── Banner ────────────────────────────────────────────────────────
+
+ def print_banner(self):
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
+ print()
+
+ # ── Prompt ────────────────────────────────────────────────────────
+
+ def prompt(self, project_name: str = "", modified: bool = False,
+ context: str = "") -> str:
+ """Build a plain prompt string for interactive use.
+
+ Args:
+ project_name: Current project name (empty if none open).
+ modified: Whether the project has unsaved changes.
+ context: Optional extra context to show in prompt.
+
+ Returns:
+ Formatted prompt string.
+ """
+ parts = [self.software]
+
+ if project_name or context:
+ ctx = context or project_name
+ mod = "*" if modified else ""
+ parts.append(f"[{ctx}{mod}]")
+
+ parts.append(">")
+ return " ".join(parts) + " "
+
+ def prompt_tokens(self, project_name: str = "", modified: bool = False,
+ context: str = ""):
+ """Build prompt_toolkit formatted text tokens for the prompt.
+
+ Use with prompt_toolkit's FormattedText for proper ANSI handling.
+
+ Returns:
+ list of (style, text) tuples for prompt_toolkit.
+ """
+ tokens = [("class:software", self.software)]
+
+ if project_name or context:
+ ctx = context or project_name
+ mod = "*" if modified else ""
+ tokens.append(("class:bracket", " ["))
+ tokens.append(("class:context", f"{ctx}{mod}"))
+ tokens.append(("class:bracket", "]"))
+
+ tokens.append(("class:arrow", " > "))
+
+ return tokens
+
+ def get_prompt_style(self):
+ """Get a prompt_toolkit Style object matching the skin.
+
+ Returns:
+ prompt_toolkit.styles.Style
+ """
+ try:
+ from prompt_toolkit.styles import Style
+ except ImportError:
+ return None
+
+ accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
+
+ return Style.from_dict({
+ "icon": "#5fdfdf bold", # cyan brand color
+ "software": f"{accent_hex} bold",
+ "bracket": "#585858",
+ "context": "#bcbcbc",
+ "arrow": "#808080",
+ # Completion menu
+ "completion-menu.completion": "bg:#303030 #bcbcbc",
+ "completion-menu.completion.current": f"bg:{accent_hex} #000000",
+ "completion-menu.meta.completion": "bg:#303030 #808080",
+ "completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
+ # Auto-suggest
+ "auto-suggest": "#585858",
+ # Bottom toolbar
+ "bottom-toolbar": "bg:#1c1c1c #808080",
+ "bottom-toolbar.text": "#808080",
+ })
+
+ # ── Messages ──────────────────────────────────────────────────────
+
+ def success(self, message: str):
+ """Print a success message with green checkmark."""
+ icon = self._c(_GREEN + _BOLD, "✓")
+ print(f" {icon} {self._c(_GREEN, message)}")
+
+ def error(self, message: str):
+ """Print an error message with red cross."""
+ icon = self._c(_RED + _BOLD, "✗")
+ print(f" {icon} {self._c(_RED, message)}", file=sys.stderr)
+
+ def warning(self, message: str):
+ """Print a warning message with yellow triangle."""
+ icon = self._c(_YELLOW + _BOLD, "⚠")
+ print(f" {icon} {self._c(_YELLOW, message)}")
+
+ def info(self, message: str):
+ """Print an info message with blue dot."""
+ icon = self._c(_BLUE, "●")
+ print(f" {icon} {self._c(_LIGHT_GRAY, message)}")
+
+ def hint(self, message: str):
+ """Print a subtle hint message."""
+ print(f" {self._c(_DARK_GRAY, message)}")
+
+ def section(self, title: str):
+ """Print a section header."""
+ print()
+ print(f" {self._c(self.accent + _BOLD, title)}")
+ print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}")
+
+ # ── Status display ────────────────────────────────────────────────
+
+ def status(self, label: str, value: str):
+ """Print a key-value status line."""
+ lbl = self._c(_GRAY, f" {label}:")
+ val = self._c(_WHITE, f" {value}")
+ print(f"{lbl}{val}")
+
+ def status_block(self, items: dict[str, str], title: str = ""):
+ """Print a block of status key-value pairs.
+
+ Args:
+ items: Dict of label -> value pairs.
+ title: Optional title for the block.
+ """
+ if title:
+ self.section(title)
+
+ max_key = max(len(k) for k in items) if items else 0
+ for label, value in items.items():
+ lbl = self._c(_GRAY, f" {label:<{max_key}}")
+ val = self._c(_WHITE, f" {value}")
+ print(f"{lbl}{val}")
+
+ def progress(self, current: int, total: int, label: str = ""):
+ """Print a simple progress indicator.
+
+ Args:
+ current: Current step number.
+ total: Total number of steps.
+ label: Optional label for the progress.
+ """
+ pct = int(current / total * 100) if total > 0 else 0
+ bar_width = 20
+ filled = int(bar_width * current / total) if total > 0 else 0
+ bar = "█" * filled + "░" * (bar_width - filled)
+ text = f" {self._c(_CYAN, bar)} {self._c(_GRAY, f'{pct:3d}%')}"
+ if label:
+ text += f" {self._c(_LIGHT_GRAY, label)}"
+ print(text)
+
+ # ── Table display ─────────────────────────────────────────────────
+
+ def table(self, headers: list[str], rows: list[list[str]],
+ max_col_width: int = 40):
+ """Print a formatted table with box-drawing characters.
+
+ Args:
+ headers: Column header strings.
+ rows: List of rows, each a list of cell strings.
+ max_col_width: Maximum column width before truncation.
+ """
+ if not headers:
+ return
+
+ # Calculate column widths
+ col_widths = [min(len(h), max_col_width) for h in headers]
+ for row in rows:
+ for i, cell in enumerate(row):
+ if i < len(col_widths):
+ col_widths[i] = min(
+ max(col_widths[i], len(str(cell))), max_col_width
+ )
+
+ def pad(text: str, width: int) -> str:
+ t = str(text)[:width]
+ return t + " " * (width - len(t))
+
+ # Header
+ header_cells = [
+ self._c(_CYAN + _BOLD, pad(h, col_widths[i]))
+ for i, h in enumerate(headers)
+ ]
+ sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
+ header_line = f" {sep.join(header_cells)}"
+ print(header_line)
+
+ # Separator
+ sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths]
+ sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
+ print(sep_line)
+
+ # Rows
+ for row in rows:
+ cells = []
+ for i, cell in enumerate(row):
+ if i < len(col_widths):
+ cells.append(self._c(_LIGHT_GRAY, pad(str(cell), col_widths[i])))
+ row_sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
+ print(f" {row_sep.join(cells)}")
+
+ # ── Help display ──────────────────────────────────────────────────
+
+ def help(self, commands: dict[str, str]):
+ """Print a compact help listing.
+
+ Args:
+ commands: Dict of command -> description pairs.
+ """
+ print("Commands:")
+ max_cmd = max(len(c) for c in commands) if commands else 0
+ for cmd, desc in commands.items():
+ print(f" {cmd:<{max_cmd}} {desc}")
+ print()
+
+ # ── Goodbye ───────────────────────────────────────────────────────
+
+ def print_goodbye(self):
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
+
+ # ── Prompt toolkit session factory ────────────────────────────────
+
+ def create_prompt_session(self):
+ """Create a prompt_toolkit PromptSession with skin styling.
+
+ Returns:
+ A configured PromptSession, or None if prompt_toolkit unavailable.
+ """
+ try:
+ from prompt_toolkit import PromptSession
+ from prompt_toolkit.history import FileHistory
+ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
+ from prompt_toolkit.formatted_text import FormattedText
+
+ style = self.get_prompt_style()
+
+ session = PromptSession(
+ history=FileHistory(self.history_file),
+ auto_suggest=AutoSuggestFromHistory(),
+ style=style,
+ enable_history_search=True,
+ )
+ return session
+ except ImportError:
+ return None
+
+ def get_input(self, pt_session, project_name: str = "",
+ modified: bool = False, context: str = "") -> str:
+ """Get input from user using prompt_toolkit or fallback.
+
+ Args:
+ pt_session: A prompt_toolkit PromptSession (or None).
+ project_name: Current project name.
+ modified: Whether project has unsaved changes.
+ context: Optional context string.
- def create_prompt_session(self) -> PromptSession:
- return PromptSession(history=InMemoryHistory(), auto_suggest=AutoSuggestFromHistory())
+ Returns:
+ User input string (stripped).
+ """
+ if pt_session is not None:
+ from prompt_toolkit.formatted_text import FormattedText
+ tokens = self.prompt_tokens(project_name, modified, context)
+ return pt_session.prompt(FormattedText(tokens)).strip()
+ else:
+ raw_prompt = self.prompt(project_name, modified, context)
+ return input(raw_prompt).strip()
- def get_input(self, session: PromptSession, project_name: str = "", modified: bool = False) -> str:
- suffix = "*" if modified else ""
- ctx = f"[{project_name}{suffix}]" if project_name else ""
- return session.prompt(f"{self.software}{ctx}> ")
+ # ── Toolbar builder ───────────────────────────────────────────────
- def help(self, commands: dict[str, str]) -> None:
- for command, desc in commands.items():
- print(f"{command}: {desc}")
+ def bottom_toolbar(self, items: dict[str, str]):
+ """Create a bottom toolbar callback for prompt_toolkit.
- def success(self, message: str) -> None:
- print(f"OK {message}")
+ Args:
+ items: Dict of label -> value pairs to show in toolbar.
- def error(self, message: str) -> None:
- print(f"ERROR {message}")
+ Returns:
+ A callable that returns FormattedText for the toolbar.
+ """
+ def toolbar():
+ from prompt_toolkit.formatted_text import FormattedText
+ parts = []
+ for i, (k, v) in enumerate(items.items()):
+ if i > 0:
+ parts.append(("class:bottom-toolbar.text", " │ "))
+ parts.append(("class:bottom-toolbar.text", f" {k}: "))
+ parts.append(("class:bottom-toolbar", v))
+ return FormattedText(parts)
+ return toolbar
- def warning(self, message: str) -> None:
- print(f"WARN {message}")
- def info(self, message: str) -> None:
- print(f"INFO {message}")
+# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ─────────
- def print_goodbye(self) -> None:
- print("Goodbye!")
+_ANSI_256_TO_HEX = {
+ "\033[38;5;33m": "#0087ff", # audacity navy blue
+ "\033[38;5;35m": "#00af5f", # shotcut teal
+ "\033[38;5;39m": "#00afff", # inkscape bright blue
+ "\033[38;5;40m": "#00d700", # libreoffice green
+ "\033[38;5;55m": "#5f00af", # obs purple
+ "\033[38;5;69m": "#5f87ff", # kdenlive slate blue
+ "\033[38;5;75m": "#5fafff", # default sky blue
+ "\033[38;5;80m": "#5fd7d7", # brand cyan
+ "\033[38;5;208m": "#ff8700", # blender deep orange
+ "\033[38;5;214m": "#ffaf00", # gimp warm orange
+}
diff --git a/mubu/agent-harness/cli_anything/mubu/utils/repl_skin.py b/mubu/agent-harness/cli_anything/mubu/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/mubu/agent-harness/cli_anything/mubu/utils/repl_skin.py
+++ b/mubu/agent-harness/cli_anything/mubu/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/musescore/agent-harness/cli_anything/musescore/utils/repl_skin.py b/musescore/agent-harness/cli_anything/musescore/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/musescore/agent-harness/cli_anything/musescore/utils/repl_skin.py
+++ b/musescore/agent-harness/cli_anything/musescore/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py b/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py
index be7e0751ef..6f918f96a2 100644
--- a/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py
+++ b/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py
@@ -6,25 +6,20 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("n8n", version="2.4.7")
+ skin = ReplSkin("shotcut", version="1.0.0")
skin.print_banner() # auto-detects skills/SKILL.md inside the package
- prompt_text = skin.prompt(project_name="my_workflow", modified=True)
- skin.success("Workflow activated")
- skin.error("Connection failed")
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
skin.warning("Unsaved changes")
- skin.info("Processing 24 workflows...")
- skin.status("Status", "Connected")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
-import json
import os
-import shutil
import sys
-from typing import Any
-
-import click
# ── ANSI color codes (no external deps for core styling) ──────────────
@@ -52,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "n8n": "\033[38;5;203m", # n8n coral/red (#EA4B71)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -161,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · N8N
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -216,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
+ parts = [self.software]
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -248,10 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -260,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -362,7 +306,7 @@ def progress(self, current: int, total: int, label: str = ""):
pct = int(current / total * 100) if total > 0 else 0
bar_width = 20
filled = int(bar_width * current / total) if total > 0 else 0
- bar = "\u2588" * filled + "\u2591" * (bar_width - filled)
+ bar = "█" * filled + "░" * (bar_width - filled)
text = f" {self._c(_CYAN, bar)} {self._c(_GRAY, f'{pct:3d}%')}"
if label:
text += f" {self._c(_LIGHT_GRAY, label)}"
@@ -405,7 +349,9 @@ def pad(text: str, width: int) -> str:
print(header_line)
# Separator
- print(self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}"))
+ sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths]
+ sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
+ print(sep_line)
# Rows
for row in rows:
@@ -419,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -450,6 +394,7 @@ def create_prompt_session(self):
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
+ from prompt_toolkit.formatted_text import FormattedText
style = self.get_prompt_style()
@@ -518,141 +463,6 @@ def toolbar():
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
"\033[38;5;75m": "#5fafff", # default sky blue
"\033[38;5;80m": "#5fd7d7", # brand cyan
- "\033[38;5;203m": "#ff5f5f", # n8n coral
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}
-
-
-# ── Module-level convenience wrappers ─────────────────────────────────
-# These delegate to a lazily-initialized ReplSkin singleton so that
-# n8n_cli.py can do `from ...repl_skin import success, error, warn`
-# while still routing through the standard ReplSkin class.
-
-_skin: ReplSkin | None = None
-
-
-def _get_skin() -> ReplSkin:
- global _skin
- if _skin is None:
- # Import VERSION lazily to avoid circular imports
- try:
- from cli_anything.n8n.n8n_cli import VERSION
- except ImportError:
- VERSION = "0.0.0"
- _skin = ReplSkin("n8n", version=VERSION)
- return _skin
-
-
-def print_banner() -> None:
- """Print the cli-anything branded banner."""
- _get_skin().print_banner()
-
-
-def success(msg: str) -> None:
- """Print a success message."""
- _get_skin().success(msg)
-
-
-def error(msg: str) -> None:
- """Print an error message."""
- _get_skin().error(msg)
-
-
-def warn(msg: str) -> None:
- """Print a warning message."""
- _get_skin().warning(msg)
-
-
-# ── n8n-specific output helpers ───────────────────────────────────────
-# These handle --json flag and n8n API response formatting.
-# Not part of the standard ReplSkin because they depend on click and
-# n8n API response structure (data/nextCursor pagination).
-
-def output(data: Any, as_json: bool) -> None:
- """Print data as JSON or human-readable."""
- if as_json:
- click.echo(json.dumps(data, indent=2, default=str))
- return
-
- if isinstance(data, dict):
- if "data" in data and isinstance(data["data"], list):
- _print_table(data["data"])
- if "nextCursor" in data:
- click.secho(f"\n Next cursor: {data['nextCursor']}", fg="bright_black")
- else:
- _print_dict(data)
- elif isinstance(data, list):
- if data and isinstance(data[0], dict):
- _print_table(data)
- else:
- click.echo(json.dumps(data, indent=2, default=str))
- else:
- click.echo(str(data))
-
-
-def _print_dict(d: dict[str, Any], indent: int = 0) -> None:
- prefix = " " * indent
- for k, v in d.items():
- if isinstance(v, dict):
- click.secho(f"{prefix}{k}:", fg="cyan")
- _print_dict(v, indent + 1)
- elif isinstance(v, list) and v and isinstance(v[0], dict):
- click.secho(f"{prefix}{k}:", fg="cyan")
- _print_table(v)
- else:
- click.echo(f"{prefix}{click.style(str(k), fg='cyan')}: {v}")
-
-
-def _print_table(rows: list[dict[str, Any]]) -> None:
- if not rows:
- click.secho(" (empty)", fg="bright_black")
- return
-
- term_width = shutil.get_terminal_size().columns
- keys = list(rows[0].keys())
-
- # Filter out overly complex nested fields for table view
- simple_keys = [k for k in keys if not isinstance(rows[0].get(k), (dict, list))]
- if not simple_keys:
- simple_keys = keys[:5]
-
- col_widths = {k: len(str(k)) for k in simple_keys}
- for row in rows:
- for k in simple_keys:
- val = str(row.get(k, ""))
- col_widths[k] = min(max(col_widths[k], len(val)), 40)
-
- # Truncate columns if they exceed terminal width
- total = sum(col_widths.values()) + (len(simple_keys) - 1) * 3
- if total > term_width:
- max_col = max(10, term_width // len(simple_keys) - 3)
- col_widths = {k: min(v, max_col) for k, v in col_widths.items()}
-
- header = " | ".join(
- click.style(k.ljust(col_widths[k])[:col_widths[k]], fg="cyan")
- for k in simple_keys
- )
- click.echo(header)
- click.echo("-+-".join("-" * col_widths[k] for k in simple_keys))
-
- # Color rules for specific columns
- color_rules = {
- "status": {"success": "green", "error": "red", "running": "bright_yellow", "waiting": "cyan"},
- "active": {"True": "green", "False": "bright_black"},
- }
-
- for row in rows:
- vals = []
- for k in simple_keys:
- v = str(row.get(k, ""))
- w = col_widths[k]
- if len(v) > w and w > 3:
- cell = v[: w - 1] + "\u2026"
- else:
- cell = v.ljust(w)[:w]
- # Apply color if column has a rule
- if k in color_rules and v in color_rules[k]:
- cell = click.style(cell, fg=color_rules[k][v])
- vals.append(cell)
- click.echo(" | ".join(vals))
diff --git a/novita/agent-harness/cli_anything/novita/utils/repl_skin.py b/novita/agent-harness/cli_anything/novita/utils/repl_skin.py
index 68d5a179a7..6f918f96a2 100644
--- a/novita/agent-harness/cli_anything/novita/utils/repl_skin.py
+++ b/novita/agent-harness/cli_anything/novita/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -30,7 +30,7 @@
_UNDERLINE = "\033[4m"
# Brand colors
-_CYAN = "\033[38;5;80m" # cli-anything brand cyan
+_CYAN = "\033[38;5;80m" # cli-anything brand cyan
_CYAN_BG = "\033[48;5;80m"
_WHITE = "\033[97m"
_GRAY = "\033[38;5;245m"
@@ -39,18 +39,16 @@
# Software accent colors — each software gets a unique accent
_ACCENT_COLORS = {
- "gimp": "\033[38;5;214m", # warm orange
- "blender": "\033[38;5;208m", # deep orange
- "inkscape": "\033[38;5;39m", # bright blue
- "audacity": "\033[38;5;33m", # navy blue
- "libreoffice": "\033[38;5;40m", # green
- "obs_studio": "\033[38;5;55m", # purple
- "kdenlive": "\033[38;5;69m", # slate blue
- "shotcut": "\033[38;5;35m", # teal green
- "anygen": "\033[38;5;141m", # soft violet
- "novita": "\033[38;5;81m", # vivid blue (for Novita AI)
+ "gimp": "\033[38;5;214m", # warm orange
+ "blender": "\033[38;5;208m", # deep orange
+ "inkscape": "\033[38;5;39m", # bright blue
+ "audacity": "\033[38;5;33m", # navy blue
+ "libreoffice": "\033[38;5;40m", # green
+ "obs_studio": "\033[38;5;55m", # purple
+ "kdenlive": "\033[38;5;69m", # slate blue
+ "shotcut": "\033[38;5;35m", # teal green
}
-_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
+_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
# Status colors
_GREEN = "\033[38;5;78m"
@@ -83,7 +81,6 @@
def _strip_ansi(text: str) -> str:
"""Remove ANSI escape codes for length calculation."""
import re
-
return re.sub(r"\033\[[^m]*m", "", text)
@@ -99,9 +96,8 @@ class ReplSkin:
across all CLI harnesses built with the cli-anything methodology.
"""
- def __init__(
- self, software: str, version: str = "1.0.0", history_file: str | None = None
- ):
+ def __init__(self, software: str, version: str = "1.0.0",
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -109,16 +105,28 @@ def __init__(
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
if history_file is None:
from pathlib import Path
-
hist_dir = Path.home() / f".cli-anything-{self.software}"
hist_dir.mkdir(parents=True, exist_ok=True)
self.history_file = str(hist_dir / "history")
@@ -147,43 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Novita
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
- def prompt(
- self, project_name: str = "", modified: bool = False, context: str = ""
- ) -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ def prompt(self, project_name: str = "", modified: bool = False,
+ context: str = "") -> str:
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -193,32 +176,18 @@ def prompt(
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
+ parts = [self.software]
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, "]"))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(">")
+ return " ".join(parts) + " "
- return "".join(parts)
-
- def prompt_tokens(
- self, project_name: str = "", modified: bool = False, context: str = ""
- ):
+ def prompt_tokens(self, project_name: str = "", modified: bool = False,
+ context: str = ""):
"""Build prompt_toolkit formatted text tokens for the prompt.
Use with prompt_toolkit's FormattedText for proper ANSI handling.
@@ -226,11 +195,7 @@ def prompt_tokens(
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -239,7 +204,7 @@ def prompt_tokens(
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -256,25 +221,23 @@ def get_prompt_style(self):
accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- return Style.from_dict(
- {
- "icon": "#5fdfdf bold", # cyan brand color
- "software": f"{accent_hex} bold",
- "bracket": "#585858",
- "context": "#bcbcbc",
- "arrow": "#808080",
- # Completion menu
- "completion-menu.completion": "bg:#303030 #bcbcbc",
- "completion-menu.completion.current": f"bg:{accent_hex} #000000",
- "completion-menu.meta.completion": "bg:#303030 #808080",
- "completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
- # Auto-suggest
- "auto-suggest": "#585858",
- # Bottom toolbar
- "bottom-toolbar": "bg:#1c1c1c #808080",
- "bottom-toolbar.text": "#808080",
- }
- )
+ return Style.from_dict({
+ "icon": "#5fdfdf bold", # cyan brand color
+ "software": f"{accent_hex} bold",
+ "bracket": "#585858",
+ "context": "#bcbcbc",
+ "arrow": "#808080",
+ # Completion menu
+ "completion-menu.completion": "bg:#303030 #bcbcbc",
+ "completion-menu.completion.current": f"bg:{accent_hex} #000000",
+ "completion-menu.meta.completion": "bg:#303030 #808080",
+ "completion-menu.meta.completion.current": f"bg:{accent_hex} #000000",
+ # Auto-suggest
+ "auto-suggest": "#585858",
+ # Bottom toolbar
+ "bottom-toolbar": "bg:#1c1c1c #808080",
+ "bottom-toolbar.text": "#808080",
+ })
# ── Messages ──────────────────────────────────────────────────────
@@ -351,7 +314,8 @@ def progress(self, current: int, total: int, label: str = ""):
# ── Table display ─────────────────────────────────────────────────
- def table(self, headers: list[str], rows: list[list[str]], max_col_width: int = 40):
+ def table(self, headers: list[str], rows: list[list[str]],
+ max_col_width: int = 40):
"""Print a formatted table with box-drawing characters.
Args:
@@ -377,7 +341,8 @@ def pad(text: str, width: int) -> str:
# Header
header_cells = [
- self._c(_CYAN + _BOLD, pad(h, col_widths[i])) for i, h in enumerate(headers)
+ self._c(_CYAN + _BOLD, pad(h, col_widths[i]))
+ for i, h in enumerate(headers)
]
sep = self._c(_DARK_GRAY, f" {_V_LINE} ")
header_line = f" {sep.join(header_cells)}"
@@ -385,9 +350,7 @@ def pad(text: str, width: int) -> str:
# Separator
sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths]
- sep_line = self._c(
- _DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}"
- )
+ sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
print(sep_line)
# Rows
@@ -402,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -447,13 +408,8 @@ def create_prompt_session(self):
except ImportError:
return None
- def get_input(
- self,
- pt_session,
- project_name: str = "",
- modified: bool = False,
- context: str = "",
- ) -> str:
+ def get_input(self, pt_session, project_name: str = "",
+ modified: bool = False, context: str = "") -> str:
"""Get input from user using prompt_toolkit or fallback.
Args:
@@ -467,7 +423,6 @@ def get_input(
"""
if pt_session is not None:
from prompt_toolkit.formatted_text import FormattedText
-
tokens = self.prompt_tokens(project_name, modified, context)
return pt_session.prompt(FormattedText(tokens)).strip()
else:
@@ -485,10 +440,8 @@ def bottom_toolbar(self, items: dict[str, str]):
Returns:
A callable that returns FormattedText for the toolbar.
"""
-
def toolbar():
from prompt_toolkit.formatted_text import FormattedText
-
parts = []
for i, (k, v) in enumerate(items.items()):
if i > 0:
@@ -496,23 +449,20 @@ def toolbar():
parts.append(("class:bottom-toolbar.text", f" {k}: "))
parts.append(("class:bottom-toolbar", v))
return FormattedText(parts)
-
return toolbar
# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ─────────
_ANSI_256_TO_HEX = {
- "\033[38;5;33m": "#0087ff", # audacity navy blue
- "\033[38;5;35m": "#00af5f", # shotcut teal
- "\033[38;5;39m": "#00afff", # inkscape bright blue
- "\033[38;5;40m": "#00d700", # libreoffice green
- "\033[38;5;55m": "#5f00af", # obs purple
- "\033[38;5;69m": "#5f87ff", # kdenlive slate blue
- "\033[38;5;75m": "#5fafff", # default sky blue
- "\033[38;5;80m": "#5fd7d7", # brand cyan
- "\033[38;5;81m": "#5fd7ff", # novita vivid blue
- "\033[38;5;141m": "#af87ff", # anygen soft violet
+ "\033[38;5;33m": "#0087ff", # audacity navy blue
+ "\033[38;5;35m": "#00af5f", # shotcut teal
+ "\033[38;5;39m": "#00afff", # inkscape bright blue
+ "\033[38;5;40m": "#00d700", # libreoffice green
+ "\033[38;5;55m": "#5f00af", # obs purple
+ "\033[38;5;69m": "#5f87ff", # kdenlive slate blue
+ "\033[38;5;75m": "#5fafff", # default sky blue
+ "\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
}
diff --git a/obs-studio/agent-harness/cli_anything/obs_studio/utils/repl_skin.py b/obs-studio/agent-harness/cli_anything/obs_studio/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/obs-studio/agent-harness/cli_anything/obs_studio/utils/repl_skin.py
+++ b/obs-studio/agent-harness/cli_anything/obs_studio/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/obsidian/agent-harness/cli_anything/obsidian/utils/repl_skin.py b/obsidian/agent-harness/cli_anything/obsidian/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/obsidian/agent-harness/cli_anything/obsidian/utils/repl_skin.py
+++ b/obsidian/agent-harness/cli_anything/obsidian/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/ollama/agent-harness/cli_anything/ollama/utils/repl_skin.py b/ollama/agent-harness/cli_anything/ollama/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/ollama/agent-harness/cli_anything/ollama/utils/repl_skin.py
+++ b/ollama/agent-harness/cli_anything/ollama/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/openscreen/agent-harness/cli_anything/openscreen/utils/repl_skin.py b/openscreen/agent-harness/cli_anything/openscreen/utils/repl_skin.py
index 2feb59d1b7..6f918f96a2 100644
--- a/openscreen/agent-harness/cli_anything/openscreen/utils/repl_skin.py
+++ b/openscreen/agent-harness/cli_anything/openscreen/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
+ parts = [self.software]
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -459,26 +429,6 @@ def get_input(self, pt_session, project_name: str = "",
raw_prompt = self.prompt(project_name, modified, context)
return input(raw_prompt).strip()
- # ── Sub-prompt input ────────────────────────────────────────────
-
- def sub_input(self, prompt_text: str, pt_session=None) -> str:
- """Get input for a sub-prompt (e.g., parameter entry in add flows).
-
- Uses prompt_toolkit if a session is available, otherwise falls back
- to plain input(). This preserves history and styling consistency.
-
- Args:
- prompt_text: The prompt to display (e.g., " start_ms: ").
- pt_session: An optional prompt_toolkit PromptSession.
-
- Returns:
- User input string (stripped).
- """
- if pt_session is not None:
- return pt_session.prompt(prompt_text).strip()
- else:
- return input(prompt_text).strip()
-
# ── Toolbar builder ───────────────────────────────────────────────
def bottom_toolbar(self, items: dict[str, str]):
diff --git a/pm2/agent-harness/cli_anything/pm2/utils/repl_skin.py b/pm2/agent-harness/cli_anything/pm2/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/pm2/agent-harness/cli_anything/pm2/utils/repl_skin.py
+++ b/pm2/agent-harness/cli_anything/pm2/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/renderdoc/agent-harness/cli_anything/renderdoc/utils/repl_skin.py b/renderdoc/agent-harness/cli_anything/renderdoc/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/renderdoc/agent-harness/cli_anything/renderdoc/utils/repl_skin.py
+++ b/renderdoc/agent-harness/cli_anything/renderdoc/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/rms/agent-harness/cli_anything/rms/utils/repl_skin.py b/rms/agent-harness/cli_anything/rms/utils/repl_skin.py
index 75ac09b1bb..6f918f96a2 100644
--- a/rms/agent-harness/cli_anything/rms/utils/repl_skin.py
+++ b/rms/agent-harness/cli_anything/rms/utils/repl_skin.py
@@ -47,9 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "anygen": "\033[38;5;141m", # soft violet
- "novita": "\033[38;5;81m", # vivid blue (for Novita AI)
- "rms": "\033[38;5;27m", # Teltonika blue
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -158,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -213,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -245,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -258,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -419,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/seaclip/agent-harness/cli_anything/seaclip/utils/repl_skin.py b/seaclip/agent-harness/cli_anything/seaclip/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/seaclip/agent-harness/cli_anything/seaclip/utils/repl_skin.py
+++ b/seaclip/agent-harness/cli_anything/seaclip/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/shotcut/agent-harness/cli_anything/shotcut/utils/repl_skin.py b/shotcut/agent-harness/cli_anything/shotcut/utils/repl_skin.py
index 47260bebd0..6f918f96a2 100644
--- a/shotcut/agent-harness/cli_anything/shotcut/utils/repl_skin.py
+++ b/shotcut/agent-harness/cli_anything/shotcut/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -97,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -105,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -142,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -187,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -219,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -232,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -393,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/utils/repl_skin.py b/slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/utils/repl_skin.py
+++ b/slay_the_spire_ii/agent-harness/cli_anything/slay_the_spire_ii/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/videocaptioner/agent-harness/cli_anything/videocaptioner/utils/repl_skin.py b/videocaptioner/agent-harness/cli_anything/videocaptioner/utils/repl_skin.py
index b356cb8357..6f918f96a2 100644
--- a/videocaptioner/agent-harness/cli_anything/videocaptioner/utils/repl_skin.py
+++ b/videocaptioner/agent-harness/cli_anything/videocaptioner/utils/repl_skin.py
@@ -6,14 +6,14 @@
Usage:
from cli_anything..utils.repl_skin import ReplSkin
- skin = ReplSkin("ollama", version="1.0.0")
- skin.print_banner()
- prompt_text = skin.prompt(project_name="llama3.2", modified=False)
- skin.success("Model pulled")
- skin.error("Connection failed")
- skin.warning("No models loaded")
- skin.info("Generating...")
- skin.status("Model", "llama3.2:latest")
+ skin = ReplSkin("shotcut", version="1.0.0")
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
+ prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
+ skin.success("Project saved")
+ skin.error("File not found")
+ skin.warning("Unsaved changes")
+ skin.info("Processing 24 clips...")
+ skin.status("Track 1", "3 clips, 00:02:30")
skin.table(headers, rows)
skin.print_goodbye()
"""
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "ollama": "\033[38;5;255m", # white (Ollama branding)
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,18 +97,31 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
- software: Software name (e.g., "gimp", "shotcut", "ollama").
+ software: Software name (e.g., "gimp", "shotcut", "blender").
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Ollama
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;255m": "#eeeeee", # ollama white
}
diff --git a/wiremock/agent-harness/cli_anything/wiremock/utils/repl_skin.py b/wiremock/agent-harness/cli_anything/wiremock/utils/repl_skin.py
index c77b64f241..6f918f96a2 100644
--- a/wiremock/agent-harness/cli_anything/wiremock/utils/repl_skin.py
+++ b/wiremock/agent-harness/cli_anything/wiremock/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,10 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -254,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -399,6 +349,7 @@ def pad(text: str, width: int) -> str:
print(header_line)
# Separator
+ sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths]
sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")
print(sep_line)
@@ -414,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
diff --git a/zoom/agent-harness/cli_anything/zoom/utils/repl_skin.py b/zoom/agent-harness/cli_anything/zoom/utils/repl_skin.py
index 1d0213c9f6..6f918f96a2 100644
--- a/zoom/agent-harness/cli_anything/zoom/utils/repl_skin.py
+++ b/zoom/agent-harness/cli_anything/zoom/utils/repl_skin.py
@@ -7,7 +7,7 @@
from cli_anything..utils.repl_skin import ReplSkin
skin = ReplSkin("shotcut", version="1.0.0")
- skin.print_banner()
+ skin.print_banner() # auto-detects skills/SKILL.md inside the package
prompt_text = skin.prompt(project_name="my_video.mlt", modified=True)
skin.success("Project saved")
skin.error("File not found")
@@ -47,7 +47,6 @@
"obs_studio": "\033[38;5;55m", # purple
"kdenlive": "\033[38;5;69m", # slate blue
"shotcut": "\033[38;5;35m", # teal green
- "zoom": "\033[38;5;27m", # zoom blue
}
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
@@ -98,7 +97,7 @@ class ReplSkin:
"""
def __init__(self, software: str, version: str = "1.0.0",
- history_file: str | None = None):
+ history_file: str | None = None, skill_path: str | None = None):
"""Initialize the REPL skin.
Args:
@@ -106,10 +105,23 @@ def __init__(self, software: str, version: str = "1.0.0",
version: CLI version string.
history_file: Path for persistent command history.
Defaults to ~/.cli-anything-/history
+ skill_path: Path to the SKILL.md file for agent discovery.
+ Auto-detected from the package's skills/ directory if not provided.
+ Displayed in banner for AI agents to know where to read skill info.
"""
self.software = software.lower().replace("-", "_")
self.display_name = software.replace("_", " ").title()
self.version = version
+
+ # Auto-detect skill path from package layout:
+ # cli_anything//utils/repl_skin.py (this file)
+ # cli_anything//skills/SKILL.md (target)
+ if skill_path is None:
+ from pathlib import Path
+ _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md"
+ if _auto.is_file():
+ skill_path = str(_auto)
+ self.skill_path = skill_path
self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT)
# History file
@@ -143,42 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -188,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
-
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
+ parts = [self.software]
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
-
- parts.append(self._c(_GRAY, " ❯ "))
+ parts.append(f"[{ctx}{mod}]")
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -220,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -233,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -394,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
@@ -496,5 +465,4 @@ def toolbar():
"\033[38;5;80m": "#5fd7d7", # brand cyan
"\033[38;5;208m": "#ff8700", # blender deep orange
"\033[38;5;214m": "#ffaf00", # gimp warm orange
- "\033[38;5;27m": "#005fff", # zoom blue
}
diff --git a/zotero/agent-harness/cli_anything/zotero/utils/repl_skin.py b/zotero/agent-harness/cli_anything/zotero/utils/repl_skin.py
index c7312348a7..6f918f96a2 100644
--- a/zotero/agent-harness/cli_anything/zotero/utils/repl_skin.py
+++ b/zotero/agent-harness/cli_anything/zotero/utils/repl_skin.py
@@ -155,52 +155,18 @@ def _c(self, code: str, text: str) -> str:
# ── Banner ────────────────────────────────────────────────────────
def print_banner(self):
- """Print the startup banner with branding."""
- inner = 54
-
- def _box_line(content: str) -> str:
- """Wrap content in box drawing, padding to inner width."""
- pad = inner - _visible_len(content)
- vl = self._c(_DARK_GRAY, _V_LINE)
- return f"{vl}{content}{' ' * max(0, pad)}{vl}"
-
- top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}")
- bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}")
-
- # Title: ◆ cli-anything · Shotcut
- icon = self._c(_CYAN + _BOLD, "◆")
- brand = self._c(_CYAN + _BOLD, "cli-anything")
- dot = self._c(_DARK_GRAY, "·")
- name = self._c(self.accent + _BOLD, self.display_name)
- title = f" {icon} {brand} {dot} {name}"
-
- ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}"
- tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}"
- empty = ""
-
- # Skill path for agent discovery
- skill_line = None
- if self.skill_path:
- skill_icon = self._c(_MAGENTA, "◇")
- skill_label = self._c(_DARK_GRAY, " Skill:")
- skill_path_display = self._c(_LIGHT_GRAY, self.skill_path)
- skill_line = f" {skill_icon} {skill_label} {skill_path_display}"
-
- print(top)
- print(_box_line(title))
- print(_box_line(ver))
- if skill_line:
- print(_box_line(skill_line))
- print(_box_line(empty))
- print(_box_line(tip))
- print(bot)
+ """Print a compact startup banner."""
+ title = f"{self.display_name} v{self.version}"
+ tip = "Type help for commands, quit to exit"
+ print(title)
+ print(tip)
print()
# ── Prompt ────────────────────────────────────────────────────────
def prompt(self, project_name: str = "", modified: bool = False,
context: str = "") -> str:
- """Build a styled prompt string for prompt_toolkit or input().
+ """Build a plain prompt string for interactive use.
Args:
project_name: Current project name (empty if none open).
@@ -210,28 +176,15 @@ def prompt(self, project_name: str = "", modified: bool = False,
Returns:
Formatted prompt string.
"""
- parts = []
+ parts = [self.software]
- # Icon
- if self._color:
- parts.append(f"{_CYAN}◆{_RESET} ")
- else:
- parts.append("> ")
-
- # Software name
- parts.append(self._c(self.accent + _BOLD, self.software))
-
- # Project context
if project_name or context:
ctx = context or project_name
mod = "*" if modified else ""
- parts.append(f" {self._c(_DARK_GRAY, '[')}")
- parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}"))
- parts.append(self._c(_DARK_GRAY, ']'))
+ parts.append(f"[{ctx}{mod}]")
- parts.append(self._c(_GRAY, " ❯ "))
-
- return "".join(parts)
+ parts.append(">")
+ return " ".join(parts) + " "
def prompt_tokens(self, project_name: str = "", modified: bool = False,
context: str = ""):
@@ -242,11 +195,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
Returns:
list of (style, text) tuples for prompt_toolkit.
"""
- accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff")
- tokens = []
-
- tokens.append(("class:icon", "◆ "))
- tokens.append(("class:software", self.software))
+ tokens = [("class:software", self.software)]
if project_name or context:
ctx = context or project_name
@@ -255,7 +204,7 @@ def prompt_tokens(self, project_name: str = "", modified: bool = False,
tokens.append(("class:context", f"{ctx}{mod}"))
tokens.append(("class:bracket", "]"))
- tokens.append(("class:arrow", " ❯ "))
+ tokens.append(("class:arrow", " > "))
return tokens
@@ -416,24 +365,22 @@ def pad(text: str, width: int) -> str:
# ── Help display ──────────────────────────────────────────────────
def help(self, commands: dict[str, str]):
- """Print a formatted help listing.
+ """Print a compact help listing.
Args:
commands: Dict of command -> description pairs.
"""
- self.section("Commands")
+ print("Commands:")
max_cmd = max(len(c) for c in commands) if commands else 0
for cmd, desc in commands.items():
- cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}")
- desc_styled = self._c(_GRAY, f" {desc}")
- print(f"{cmd_styled}{desc_styled}")
+ print(f" {cmd:<{max_cmd}} {desc}")
print()
# ── Goodbye ───────────────────────────────────────────────────────
def print_goodbye(self):
- """Print a styled goodbye message."""
- print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n")
+ """Print a compact goodbye message."""
+ print("Goodbye!\n")
# ── Prompt toolkit session factory ────────────────────────────────
From 7df2bcda399a4dc702b8806406b1dc82db9f3c23 Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 01:49:44 -0700
Subject: [PATCH 3/6] ci: add pull request verification workflow
---
.github/workflows/pr-ci.yml | 51 +++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 .github/workflows/pr-ci.yml
diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml
new file mode 100644
index 0000000000..72978119c3
--- /dev/null
+++ b/.github/workflows/pr-ci.yml
@@ -0,0 +1,51 @@
+name: PR CI
+
+on:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - '*/agent-harness/**'
+ - 'browser/**'
+ - 'cli-anything-plugin/repl_skin.py'
+ - 'README.md'
+ - '.github/workflows/pr-ci.yml'
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Install test deps
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install pytest
+
+ - name: Compile repl_skin.py files
+ run: |
+ python - <<'PY'
+ from pathlib import Path
+ import py_compile
+
+ root = Path('.')
+ paths = sorted(root.glob('**/agent-harness/cli_anything/**/utils/repl_skin.py'))
+ paths.append(root / 'cli-anything-plugin' / 'repl_skin.py')
+ seen = []
+ for path in paths:
+ if path.exists() and path not in seen:
+ seen.append(path)
+ for path in seen:
+ py_compile.compile(str(path), doraise=True)
+ print(f'compiled {len(seen)} repl_skin files')
+ PY
+
+ - name: Run browser unit tests
+ working-directory: browser/agent-harness
+ run: python -m pytest -q cli_anything/browser/tests/test_core.py
From e45f6e36f07598c94510af49d3cbd34448c5aafe Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 01:50:16 -0700
Subject: [PATCH 4/6] ci: run verification workflow on fork pushes
---
.github/workflows/pr-ci.yml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml
index 72978119c3..91988650e5 100644
--- a/.github/workflows/pr-ci.yml
+++ b/.github/workflows/pr-ci.yml
@@ -1,6 +1,15 @@
name: PR CI
on:
+ push:
+ branches:
+ - fix/browser-repl-and-template-sync
+ paths:
+ - '*/agent-harness/**'
+ - 'browser/**'
+ - 'cli-anything-plugin/repl_skin.py'
+ - 'README.md'
+ - '.github/workflows/pr-ci.yml'
pull_request:
branches:
- main
From 22a563415195c2c85f5adb222d13fd34703408e5 Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 01:51:19 -0700
Subject: [PATCH 5/6] ci: install browser harness dev dependencies in PR CI
---
.github/workflows/pr-ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml
index 91988650e5..b1a09db381 100644
--- a/.github/workflows/pr-ci.yml
+++ b/.github/workflows/pr-ci.yml
@@ -32,10 +32,10 @@ jobs:
with:
python-version: '3.12'
- - name: Install test deps
+ - name: Install browser harness deps
run: |
python -m pip install --upgrade pip
- python -m pip install pytest
+ python -m pip install -e browser/agent-harness[dev]
- name: Compile repl_skin.py files
run: |
From fd535a2f688c0fa08417079c994809ece573589e Mon Sep 17 00:00:00 2001
From: yangshu
Date: Wed, 15 Apr 2026 02:01:55 -0700
Subject: [PATCH 6/6] ci: remove branch-specific push trigger from PR workflow
---
.github/workflows/pr-ci.yml | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml
index b1a09db381..021041f305 100644
--- a/.github/workflows/pr-ci.yml
+++ b/.github/workflows/pr-ci.yml
@@ -1,15 +1,7 @@
name: PR CI
on:
- push:
- branches:
- - fix/browser-repl-and-template-sync
- paths:
- - '*/agent-harness/**'
- - 'browser/**'
- - 'cli-anything-plugin/repl_skin.py'
- - 'README.md'
- - '.github/workflows/pr-ci.yml'
+ workflow_dispatch:
pull_request:
branches:
- main