From 072c61811f53855d687f72aea25bd034073ceb1e Mon Sep 17 00:00:00 2001 From: Sushant Gautam Date: Fri, 26 Jun 2026 12:42:47 +0200 Subject: [PATCH 1/2] feat: add copilot-tool-manager skill Enable, disable, and manage VS Code Copilot agent tools by category. Optimize context window by selectively disabling unused tools. Dynamically discovers tools from VS Code database with pattern-based categorization. --- skills/copilot-tool-manager/SKILL.md | 118 +++++++++++ skills/copilot-tool-manager/copilot-tools.py | 201 +++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 skills/copilot-tool-manager/SKILL.md create mode 100644 skills/copilot-tool-manager/copilot-tools.py diff --git a/skills/copilot-tool-manager/SKILL.md b/skills/copilot-tool-manager/SKILL.md new file mode 100644 index 000000000..7b7830c01 --- /dev/null +++ b/skills/copilot-tool-manager/SKILL.md @@ -0,0 +1,118 @@ +--- +name: copilot-tool-manager +description: > + Enable, disable, and manage VS Code Copilot agent tools by category. + Optimize your context window by selectively disabling unused tools. + Dynamically discovers tools from your VS Code database with pattern-based categorization. +--- + +# Copilot Tool Manager + +Enable, disable, and manage VS Code Copilot agent tools by category. Optimize your context window by selectively disabling unused tools. + +## Triggers +- "disable tools", "enable tools", "toggle tools" +- "show tool status", "list tools" +- "context window optimization", "reduce context usage" +- "disable browser tools", "enable terminal tools" +- "which tools are enabled", "what tools consume most context" + +## Why This Matters + +- Tool definitions consume **~22-25% of the context window** +- Disabling unused tools frees up context for your actual work +- Not linear: browser tools (10 tools) consume more tokens than memory tools (2 tools) combined + +## Fastest Method: Helper Script + +Use the `copilot-tools.py` script in this skill directory: + +```bash +python3 copilot-tools.py status # Show current state +python3 copilot-tools.py disable browser # Disable browser tools +python3 copilot-tools.py enable browser # Enable browser tools +python3 copilot-tools.py disable terminal github # Disable multiple categories +python3 copilot-tools.py list # List all tools with status +python3 copilot-tools.py disable_tool click_element # Disable a single tool +python3 copilot-tools.py enable_tool click_element # Enable a single tool +``` + +Categories: `browser`, `file_ops`, `terminal`, `vscode`, `chat`, `github`, `memory`, `other` + +## Database Details + +### Database Location + +| OS | Path | +|----|------| +| macOS | `~/Library/Application Support/Code/User/globalStorage/state.vscdb` | +| Linux | `~/.config/Code/User/globalStorage/state.vscdb` | +| Windows | `%APPDATA%\Code\User\globalStorage\state.vscdb` | + +### Database Schema + +- **Table:** `ItemTable` +- **Key:** `chat/selectedTools` +- **Value:** JSON with `toolEntries` array of `[tool_name, boolean]` pairs + +## Tool Categories (73 total tools) + +| Category | Count | Token Cost | Safe to Disable? | +|----------|-------|------------|------------------| +| **Browser** (10) | click, drag, hover, navigate, screenshot, etc. | **High** | ✅ Yes (if no web dev) | +| **File Operations** (9) | read, write, search, create files | Medium | ❌ No (core functionality) | +| **VS Code** (8) | rename, find usages, extensions | Medium | ⚠️ Partial | +| **Terminal** (6) | run commands, get output | Medium | ⚠️ Partial | +| **Chat/Agents** (3) | subagent, todo list | Low | ❌ No | +| **GitHub/Web** (3) | fetch page, search repos | Low | ⚠️ Optional | +| **Memory** (2) | persistent notes | Low | ⚠️ Optional | +| **Other** (2) | notebook edit, get errors | Low | ✅ Yes | + +## Tool Name Reference + +### Browser tools (10) +`click_element`, `drag_element`, `handle_dialog`, `hover_element`, `navigate_page`, `open_browser_page`, `read_page`, `run_playwright_code`, `screenshot_page`, `type_in_page` + +### File operations (9) +`copilot_createDirectory`, `copilot_createFile`, `copilot_editFiles`, `copilot_readFile`, `copilot_viewImage`, `copilot_searchCodebase`, `copilot_findFiles`, `copilot_listDirectory`, `copilot_findTextInFiles` + +### VS Code tools (8) +`vscode_renameSymbol`, `vscode_listCodeUsages`, `vscode_askQuestions`, `vscode_searchExtensions_internal`, `copilot_installExtension`, `copilot_createNewWorkspace`, `copilot_runVscodeCommand`, `copilot_getVSCodeAPI` + +### Terminal tools (6) +`get_terminal_output`, `kill_terminal`, `run_in_terminal`, `send_to_terminal`, `terminal_last_command`, `terminal_selection` + +### Chat/Agent tools (3) +`runSubagent`, `execution_subagent`, `manage_todo_list` + +### GitHub/Web tools (3) +`copilot_fetchWebPage`, `copilot_githubRepo`, `copilot_githubTextSearch` + +### Memory tools (2) +`copilot_memory`, `copilot_resolveMemoryFileUri` + +### Other tools (2) +`copilot_editNotebook`, `copilot_getErrors` + +## Usage Patterns + +### When user asks to disable/enable tools: +1. Run the helper script: `python3 copilot-tools.py disable ` +2. Tell user to start a new chat session for changes to take effect + +### When user asks about context window optimization: +1. Check which categories are still enabled +2. Suggest disabling "heavy" categories first (browser → file ops → VS Code) +3. Show expected impact based on category token cost + +### Common presets: +- **Thesis writing**: Disable browser, github, memory, other +- **Web development**: Keep browser, terminal, file_ops; disable memory, other +- **Minimal**: Only keep file_ops + terminal + chat (~20 tools) + +## Safety Notes + +- ⚠️ **Close VS Code** before running commands to avoid database conflicts +- 💡 Start by disabling just `browser` tools (safest, highest impact) +- 🔄 Changes take effect on next Copilot chat session +- 📋 Use `status` command to verify state before/after changes diff --git a/skills/copilot-tool-manager/copilot-tools.py b/skills/copilot-tool-manager/copilot-tools.py new file mode 100644 index 000000000..69e4c96a6 --- /dev/null +++ b/skills/copilot-tool-manager/copilot-tools.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +""" +Copilot Tool Manager - Fast enable/disable of Copilot agent tools. + +Dynamically discovers tools from your VS Code database (no hardcoded names). +Categories are inferred from tool name prefixes/patterns. + +Usage: + ./copilot-tools.py status # Show current state + ./copilot-tools.py disable browser # Disable browser tools + ./copilot-tools.py enable browser # Enable browser tools + ./copilot-tools.py disable terminal github # Disable multiple categories + ./copilot-tools.py list # List all tools with status + ./copilot-tools.py disable_tool click_element # Disable a single tool + ./copilot-tools.py enable_tool click_element # Enable a single tool + ./copilot-tools.py unknown # Show uncategorized tools + +Categories: browser, file_ops, terminal, vscode, chat, github, memory, other +""" +import json, os, sys, sqlite3, platform, re + +VERSION = "1.1.0" + +# Cross-platform database path +system = platform.system() +if system == "Darwin": + DB_PATH = os.path.expanduser("~/Library/Application Support/Code/User/globalStorage/state.vscdb") +elif system == "Linux": + DB_PATH = os.path.expanduser("~/.config/Code/User/globalStorage/state.vscdb") +elif system == "Windows": + DB_PATH = os.path.expanduser("~/AppData/Roaming/Code/User/globalStorage/state.vscdb") +else: + print(f"Unsupported OS: {system}") + sys.exit(1) + +# Category patterns - matched against tool names (regex) +CATEGORY_PATTERNS = { + 'browser': [ + r'click_element', r'drag_element', r'handle_dialog', r'hover_element', + r'navigate_page', r'open_browser', r'read_page', r'run_playwright', + r'screenshot', r'type_in_page' + ], + 'file_ops': [ + r'copilot_create', r'copilot_edit', r'copilot_read', r'copilot_view', + r'copilot_search', r'copilot_find', r'copilot_list' + ], + 'terminal': [ + r'run_in_terminal', r'get_terminal', r'kill_terminal', r'send_to_terminal', + r'terminal_last', r'terminal_selection' + ], + 'vscode': [ + r'vscode_', r'copilot_install', r'copilot_createNew', r'copilot_runVscode', + r'copilot_getVSCode', r'copilot_getErrors', r'copilot_getNotebookSummary', + r'copilot_runNotebookCell' + ], + 'notebook': [ + r'configure_notebook', r'configure_python_environment', r'notebook_install', + r'notebook_list', r'get_python_environment', r'get_python_executable', + r'install_python_packages', r'copilot_runNotebookCell' + ], + 'testing': [ + r'runTests', r'testFailure', r'run_task', r'get_task_output', r'create_and_run_task' + ], + 'mcp': [ + r'mcp_provides_tool_' + ], + 'chat': [ + r'runSubagent', r'execution_subagent', r'manage_todo' + ], + 'github': [ + r'copilot_fetchWeb', r'copilot_github', r'copilot_githubText' + ], + 'memory': [ + r'copilot_memory', r'copilot_resolveMemory' + ], +} + +def categorize_tool(name): + """Dynamically categorize a tool name using pattern matching.""" + for category, patterns in CATEGORY_PATTERNS.items(): + for pattern in patterns: + if re.search(pattern, name, re.IGNORECASE): + return category + return 'other' + +def discover_categories(data): + """Build categories dynamically from actual tools in database.""" + categories = {} + for tool_name, enabled in data['toolEntries']: + cat = categorize_tool(tool_name) + if cat not in categories: + categories[cat] = [] + categories[cat].append(tool_name) + return categories + +def get_data(): + conn = sqlite3.connect(DB_PATH) + cur = conn.cursor() + cur.execute("SELECT value FROM ItemTable WHERE key='chat/selectedTools'") + row = cur.fetchone() + if not row: + print("Error: No tool config found. Have you used Configure Tools UI at least once?") + sys.exit(1) + data = json.loads(row[0]) + conn.close() + return data + +def save_data(data): + conn = sqlite3.connect(DB_PATH) + cur = conn.cursor() + cur.execute("UPDATE ItemTable SET value=? WHERE key='chat/selectedTools'", (json.dumps(data),)) + conn.commit() + conn.close() + +def status(data): + categories = discover_categories(data) + enabled = [k for k,v in data['toolEntries'] if v] + disabled = [k for k,v in data['toolEntries'] if not v] + print(f"Copilot Tool Manager v{VERSION}") + print(f"Enabled: {len(enabled)}, Disabled: {len(disabled)}, Total: {len(data['toolEntries'])}") + print() + for cat in sorted(categories.keys()): + tools = categories[cat] + cat_enabled = sum(1 for t in tools if any(t == e[0] and e[1] for e in data['toolEntries'])) + cat_disabled = len(tools) - cat_enabled + status_icon = "✓" if cat_disabled == 0 else ("✗" if cat_enabled == 0 else "◐") + print(f" {status_icon} {cat}: {cat_enabled}/{len(tools)} enabled") + +def toggle(data, action, *args): + categories = discover_categories(data) + tools_to_toggle = [] + unknown = [] + for arg in args: + if arg in categories: + tools_to_toggle.extend(categories[arg]) + elif arg in CATEGORY_PATTERNS: + tools_to_toggle.extend(categories.get(arg, [])) + else: + # Try to match as individual tool name + found = False + for tool_name, _ in data['toolEntries']: + if tool_name == arg or re.search(arg, tool_name, re.IGNORECASE): + tools_to_toggle.append(tool_name) + found = True + if not found: + unknown.append(arg) + + if unknown: + print(f"Warning: Unknown category/tool: {', '.join(unknown)}") + print(f"Available categories: {', '.join(sorted(categories.keys()))}") + + if not tools_to_toggle: + return + + # Deduplicate + tools_to_toggle = list(set(tools_to_toggle)) + + for entry in data['toolEntries']: + if entry[0] in tools_to_toggle: + entry[1] = (action == 'enable') + + save_data(data) + enabled = [k for k,v in data['toolEntries'] if v] + disabled = [k for k,v in data['toolEntries'] if not v] + action_verb = "Enabled" if action == 'enable' else "Disabled" + print(f"Done. Enabled: {len(enabled)}, Disabled: {len(disabled)}") + print(f" {action_verb}: {len(tools_to_toggle)} tools") + +def list_tools(data): + categories = discover_categories(data) + for cat in sorted(categories.keys()): + tools = categories[cat] + print(f"\n{cat.upper()}:") + for t in sorted(tools): + for entry in data['toolEntries']: + if entry[0] == t: + status_str = "✓ enabled" if entry[1] else "✗ disabled" + print(f" {t}: {status_str}") + break + +if __name__ == '__main__': + if len(sys.argv) < 2: + print(__doc__) + sys.exit(0) + + data = get_data() + cmd = sys.argv[1] + + if cmd == 'status': + status(data) + elif cmd == 'list': + list_tools(data) + elif cmd in ('disable', 'enable'): + toggle(data, cmd, *sys.argv[2:]) + elif cmd == 'disable_tool': + toggle(data, 'disable', sys.argv[2]) + elif cmd == 'enable_tool': + toggle(data, 'enable', sys.argv[2]) + else: + print(f"Unknown command: {cmd}") + print(__doc__) From 5e48ad5024deebaf108400a879cec5acf0afacb4 Mon Sep 17 00:00:00 2001 From: Sushant Gautam Date: Fri, 26 Jun 2026 12:49:28 +0200 Subject: [PATCH 2/2] update: sync latest copilot-tool-manager changes --- skills/copilot-tool-manager/SKILL.md | 2 +- skills/copilot-tool-manager/copilot-tools.py | 167 ++++++++++++------- 2 files changed, 105 insertions(+), 64 deletions(-) diff --git a/skills/copilot-tool-manager/SKILL.md b/skills/copilot-tool-manager/SKILL.md index 7b7830c01..9caed6986 100644 --- a/skills/copilot-tool-manager/SKILL.md +++ b/skills/copilot-tool-manager/SKILL.md @@ -106,8 +106,8 @@ Categories: `browser`, `file_ops`, `terminal`, `vscode`, `chat`, `github`, `memo 3. Show expected impact based on category token cost ### Common presets: -- **Thesis writing**: Disable browser, github, memory, other - **Web development**: Keep browser, terminal, file_ops; disable memory, other +- **Backend/API work**: Disable browser, github, memory; keep terminal, file_ops, vscode - **Minimal**: Only keep file_ops + terminal + chat (~20 tools) ## Safety Notes diff --git a/skills/copilot-tool-manager/copilot-tools.py b/skills/copilot-tool-manager/copilot-tools.py index 69e4c96a6..3b88b07ed 100644 --- a/skills/copilot-tool-manager/copilot-tools.py +++ b/skills/copilot-tool-manager/copilot-tools.py @@ -17,70 +17,113 @@ Categories: browser, file_ops, terminal, vscode, chat, github, memory, other """ -import json, os, sys, sqlite3, platform, re - -VERSION = "1.1.0" - -# Cross-platform database path -system = platform.system() -if system == "Darwin": - DB_PATH = os.path.expanduser("~/Library/Application Support/Code/User/globalStorage/state.vscdb") -elif system == "Linux": - DB_PATH = os.path.expanduser("~/.config/Code/User/globalStorage/state.vscdb") -elif system == "Windows": - DB_PATH = os.path.expanduser("~/AppData/Roaming/Code/User/globalStorage/state.vscdb") -else: - print(f"Unsupported OS: {system}") +import json, os, sys, sqlite3, platform, glob, subprocess + +VERSION = "1.2.0" + + +def get_db_path(): + """Find VS Code's state.vscdb, handling multiple installs (Code, VSCodium, Cursor, etc.).""" + system = platform.system() + + if system == "Darwin": + base = os.path.expanduser("~/Library/Application Support") + elif system == "Linux": + base = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + elif system == "Windows": + base = os.path.expanduser(os.environ.get("APPDATA", "~/AppData/Roaming")) + else: + print(f"Unsupported OS: {system}") + sys.exit(1) + + # Try common VS Code app names in order of likelihood + app_names = ["Code", "VSCodium", "Cursor", "GitHub Codespaces", "Code - OSS"] + + # First check if VSCODE_PID is set (we're running inside VS Code) + vscode_pid = os.environ.get("VSCODE_PID") + if vscode_pid: + # Try to find the app name from the parent process + try: + if system == "Darwin": + cmd = ["ps", "-o", "comm=", "-p", vscode_pid] + else: + cmd = ["ps", "-o", "comm=", "-p", vscode_pid] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=2) + parent = result.stdout.strip().lower() + if "codium" in parent: + app_names = ["VSCodium"] + [a for a in app_names if a != "VSCodium"] + elif "cursor" in parent: + app_names = ["Cursor"] + [a for a in app_names if a != "Cursor"] + except (subprocess.SubprocessError, OSError): + pass + + # Search for the database file + for app in app_names: + db_path = os.path.join(base, app, "User/globalStorage/state.vscdb") + if os.path.exists(db_path): + return db_path + + # Fallback: search all subdirectories + pattern = os.path.join(base, "*/User/globalStorage/state.vscdb") + matches = glob.glob(pattern) + if matches: + return matches[0] + + print(f"Error: Could not find VS Code state.vscdb in {base}") + print("Tried app names: {0}".format(", ".join(app_names))) sys.exit(1) -# Category patterns - matched against tool names (regex) -CATEGORY_PATTERNS = { - 'browser': [ - r'click_element', r'drag_element', r'handle_dialog', r'hover_element', - r'navigate_page', r'open_browser', r'read_page', r'run_playwright', - r'screenshot', r'type_in_page' - ], - 'file_ops': [ - r'copilot_create', r'copilot_edit', r'copilot_read', r'copilot_view', - r'copilot_search', r'copilot_find', r'copilot_list' - ], - 'terminal': [ - r'run_in_terminal', r'get_terminal', r'kill_terminal', r'send_to_terminal', - r'terminal_last', r'terminal_selection' - ], - 'vscode': [ - r'vscode_', r'copilot_install', r'copilot_createNew', r'copilot_runVscode', - r'copilot_getVSCode', r'copilot_getErrors', r'copilot_getNotebookSummary', - r'copilot_runNotebookCell' - ], - 'notebook': [ - r'configure_notebook', r'configure_python_environment', r'notebook_install', - r'notebook_list', r'get_python_environment', r'get_python_executable', - r'install_python_packages', r'copilot_runNotebookCell' - ], - 'testing': [ - r'runTests', r'testFailure', r'run_task', r'get_task_output', r'create_and_run_task' - ], - 'mcp': [ - r'mcp_provides_tool_' - ], - 'chat': [ - r'runSubagent', r'execution_subagent', r'manage_todo' - ], - 'github': [ - r'copilot_fetchWeb', r'copilot_github', r'copilot_githubText' - ], - 'memory': [ - r'copilot_memory', r'copilot_resolveMemory' - ], -} + +DB_PATH = get_db_path() + +# Category keywords — matched against tool names (order matters, first match wins) +# Each entry is (category, [keywords]) — keywords are matched as substrings (case-insensitive) +CATEGORY_KEYWORDS = [ + ('browser', ['click_element', 'drag_element', 'handle_dialog', 'hover_element', + 'navigate_page', 'open_browser', 'read_page', 'run_playwright', + 'screenshot_page', 'type_in_page']), + ('terminal', ['run_in_terminal', 'get_terminal', 'kill_terminal', 'send_to_terminal', + 'terminal_last', 'terminal_selection']), + ('notebook', ['configure_notebook', 'configure_python_environment', 'notebook_install', + 'notebook_list', 'python_environment', 'python_executable', + 'install_python_packages', 'runNotebookCell', 'getNotebookSummary', + 'readNotebookCellOutput']), + ('testing', ['runTests', 'testFailure', 'run_task', 'get_task_output', + 'create_and_run_task']), + ('mcp', ['mcp_provides_tool_', 'container-tools_']), + ('memory', ['copilot_memory', 'resolveMemory']), + ('github', ['copilot_github', 'githubRepo', 'githubTextSearch']), + ('web', ['copilot_fetchWeb']), + ('vscode', ['vscode_', 'copilot_install', 'copilot_createNew', 'copilot_runVscode', + 'copilot_getVSCode', 'copilot_getErrors']), + ('chat', ['runSubagent', 'execution_subagent', 'manage_todo']), + ('file_ops', ['copilot_create', 'copilot_edit', 'copilot_read', 'copilot_view', + 'copilot_search', 'copilot_find', 'copilot_list']), +] + +# Cache: built once per run from actual tools in the database +_CATEGORY_CACHE = {} + +def build_category_map(data): + """Build category->tools mapping dynamically from actual tools in database.""" + categories = {} + for tool_name, enabled in data['toolEntries']: + cat = categorize_tool(tool_name) + categories.setdefault(cat, []).append(tool_name) + return categories def categorize_tool(name): - """Dynamically categorize a tool name using pattern matching.""" - for category, patterns in CATEGORY_PATTERNS.items(): - for pattern in patterns: - if re.search(pattern, name, re.IGNORECASE): + """Categorize a tool by matching against keyword list (first match wins).""" + # Check cache first + if name in _CATEGORY_CACHE: + return _CATEGORY_CACHE[name] + + for category, keywords in CATEGORY_KEYWORDS: + for keyword in keywords: + if keyword.lower() in name.lower(): + _CATEGORY_CACHE[name] = category return category + _CATEGORY_CACHE[name] = 'other' return 'other' def discover_categories(data): @@ -133,13 +176,11 @@ def toggle(data, action, *args): for arg in args: if arg in categories: tools_to_toggle.extend(categories[arg]) - elif arg in CATEGORY_PATTERNS: - tools_to_toggle.extend(categories.get(arg, [])) else: - # Try to match as individual tool name + # Try to match as individual tool name (substring or exact) found = False for tool_name, _ in data['toolEntries']: - if tool_name == arg or re.search(arg, tool_name, re.IGNORECASE): + if tool_name == arg or arg.lower() in tool_name.lower(): tools_to_toggle.append(tool_name) found = True if not found: