Merge pull request #1440 from affaan-m/fix/dashboard-terminal-safety

fix(dashboard): harden terminal launch and maximize behavior
This commit is contained in:
Affaan Mustafa 2026-04-14 20:21:51 -07:00 committed by GitHub
commit cca163c776
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 226 additions and 27 deletions

View File

@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — Agent Instructions
This is a **production-ready AI coding plugin** providing 47 specialized agents, 181 skills, 79 commands, and automated hook workflows for software development.
This is a **production-ready AI coding plugin** providing 48 specialized agents, 183 skills, 79 commands, and automated hook workflows for software development.
**Version:** 1.10.0
@ -145,8 +145,8 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
## Project Structure
```
agents/ — 47 specialized subagents
skills/ — 181 workflow skills and domain knowledge
agents/ — 48 specialized subagents
skills/ — 183 workflow skills and domain knowledge
commands/ — 79 slash commands
hooks/ — Trigger-based automations
rules/ — Always-follow guidelines (common + per-language)

View File

@ -239,7 +239,7 @@ For manual install instructions see the README in the `rules/` folder. When copy
/plugin list ecc@ecc
```
**That's it!** You now have access to 47 agents, 181 skills, and 79 legacy command shims.
**That's it!** You now have access to 48 agents, 183 skills, and 79 legacy command shims.
### Dashboard GUI
@ -1205,9 +1205,9 @@ The configuration is automatically detected from `.opencode/opencode.json`.
| Feature | Claude Code | OpenCode | Status |
|---------|-------------|----------|--------|
| Agents | PASS: 47 agents | PASS: 12 agents | **Claude Code leads** |
| Agents | PASS: 48 agents | PASS: 12 agents | **Claude Code leads** |
| Commands | PASS: 79 commands | PASS: 31 commands | **Claude Code leads** |
| Skills | PASS: 181 skills | PASS: 37 skills | **Claude Code leads** |
| Skills | PASS: 183 skills | PASS: 37 skills | **Claude Code leads** |
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
@ -1314,9 +1314,9 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|---------|------------|------------|-----------|----------|
| **Agents** | 47 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
| **Agents** | 48 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
| **Commands** | 79 | Shared | Instruction-based | 31 |
| **Skills** | 181 | Shared | 10 (native format) | 37 |
| **Skills** | 183 | Shared | 10 (native format) | 37 |
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions |

View File

@ -162,7 +162,7 @@ npx ecc-install typescript
/plugin list ecc@ecc
```
**完成!** 你现在可以使用 47 个代理、181 个技能和 79 个命令。
**完成!** 你现在可以使用 48 个代理、183 个技能和 79 个命令。
### multi-* 命令需要额外配置

View File

@ -1,6 +1,7 @@
---
name: a11y-architect
description: Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI components, establishing design systems, or auditing code for inclusive user experiences.
model: sonnet
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
---

View File

@ -1,6 +1,6 @@
# Everything Claude Code (ECC) — 智能体指令
这是一个**生产就绪的 AI 编码插件**,提供 47 个专业代理、181 项技能、79 条命令以及自动化钩子工作流,用于软件开发。
这是一个**生产就绪的 AI 编码插件**,提供 48 个专业代理、183 项技能、79 条命令以及自动化钩子工作流,用于软件开发。
**版本:** 1.10.0
@ -146,8 +146,8 @@
## 项目结构
```
agents/ — 47 个专业子代理
skills/ — 181 个工作流技能和领域知识
agents/ — 48 个专业子代理
skills/ — 183 个工作流技能和领域知识
commands/ — 79 个斜杠命令
hooks/ — 基于触发的自动化
rules/ — 始终遵循的指导方针(通用 + 每种语言)

View File

@ -209,7 +209,7 @@ npx ecc-install typescript
/plugin list ecc@ecc
```
**搞定!** 你现在可以使用 47 个智能体、181 项技能和 79 个命令了。
**搞定!** 你现在可以使用 48 个智能体、183 项技能和 79 个命令了。
***
@ -1094,9 +1094,9 @@ opencode
| 功能特性 | Claude Code | OpenCode | 状态 |
|---------|-------------|----------|--------|
| 智能体 | PASS: 47 个 | PASS: 12 个 | **Claude Code 领先** |
| 智能体 | PASS: 48 个 | PASS: 12 个 | **Claude Code 领先** |
| 命令 | PASS: 79 个 | PASS: 31 个 | **Claude Code 领先** |
| 技能 | PASS: 181 项 | PASS: 37 项 | **Claude Code 领先** |
| 技能 | PASS: 183 项 | PASS: 37 项 | **Claude Code 领先** |
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
@ -1206,9 +1206,9 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
| 功能特性 | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|---------|------------|------------|-----------|----------|
| **智能体** | 47 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **智能体** | 48 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
| **命令** | 79 | 共享 | 基于指令 | 31 |
| **技能** | 181 | 共享 | 10 (原生格式) | 37 |
| **技能** | 183 | 共享 | 10 (原生格式) | 37 |
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |

View File

@ -8,8 +8,11 @@ import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import os
import json
import subprocess
from typing import Dict, List, Optional
from scripts.lib.ecc_dashboard_runtime import build_terminal_launch, maximize_window
# ============================================================================
# DATA LOADERS - Load ECC data from the project
# ============================================================================
@ -18,6 +21,7 @@ def get_project_path() -> str:
"""Get the ECC project path - assumes this script is run from the project dir"""
return os.path.dirname(os.path.abspath(__file__))
def load_agents(project_path: str) -> List[Dict]:
"""Load agents from AGENTS.md"""
agents_file = os.path.join(project_path, "AGENTS.md")
@ -257,7 +261,7 @@ class ECCDashboard(tk.Tk):
self.project_path = get_project_path()
self.title("ECC Dashboard - Everything Claude Code")
self.state('zoomed')
maximize_window(self)
try:
self.icon_image = tk.PhotoImage(file='assets/images/ecc-logo.png')
@ -789,14 +793,9 @@ Project: github.com/affaan-m/everything-claude-code"""
def open_terminal(self):
"""Open terminal at project path"""
import subprocess
path = self.path_entry.get()
if os.name == 'nt': # Windows
subprocess.Popen(['cmd', '/c', 'start', 'cmd', '/k', f'cd /d "{path}"'])
elif os.uname().sysname == 'Darwin': # macOS
subprocess.Popen(['open', '-a', 'Terminal', path])
else: # Linux
subprocess.Popen(['x-terminal-emulator', '-e', f'cd {path}'])
argv, kwargs = build_terminal_launch(path)
subprocess.Popen(argv, **kwargs)
def open_readme(self):
"""Open README in default browser/reader"""
@ -911,4 +910,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@ -121,7 +121,17 @@ function isChecked(key) {
function sanitizePath(filePath) {
// Strip control chars (including null), bidi overrides, and newlines
return filePath.replace(/[\x00-\x1f\x7f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, ' ').trim().slice(0, 500);
let sanitized = '';
for (const char of String(filePath || '')) {
const code = char.codePointAt(0);
const isAsciiControl = code <= 0x1f || code === 0x7f;
const isBidiOverride =
(code >= 0x200e && code <= 0x200f) ||
(code >= 0x202a && code <= 0x202e) ||
(code >= 0x2066 && code <= 0x2069);
sanitized += isAsciiControl || isBidiOverride ? ' ' : char;
}
return sanitized.trim().slice(0, 500);
}
// --- Gate messages ---

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Runtime helpers for ecc_dashboard.py that do not depend on tkinter.
"""
from __future__ import annotations
import os
import platform
import subprocess
from typing import Optional, Tuple, Dict, List
def maximize_window(window) -> None:
"""Maximize the dashboard window using the safest supported method."""
try:
window.state('zoomed')
return
except Exception:
pass
system_name = platform.system()
if system_name == 'Linux':
try:
window.attributes('-zoomed', True)
except Exception:
pass
elif system_name == 'Darwin':
try:
window.attributes('-fullscreen', True)
except Exception:
pass
def build_terminal_launch(
path: str,
*,
os_name: Optional[str] = None,
system_name: Optional[str] = None,
) -> Tuple[List[str], Dict[str, object]]:
"""Return safe argv/kwargs for opening a terminal rooted at the requested path."""
resolved_os_name = os_name or os.name
resolved_system_name = system_name or platform.system()
if resolved_os_name == 'nt':
creationflags = getattr(subprocess, 'CREATE_NEW_CONSOLE', 0)
return (
['cmd.exe', '/k', 'cd', '/d', path],
{
'cwd': path,
'creationflags': creationflags,
},
)
if resolved_system_name == 'Darwin':
return (['open', '-a', 'Terminal', path], {})
return (
['x-terminal-emulator', '-e', 'bash', '-lc', 'cd -- "$1"; exec bash', 'bash', path],
{},
)

View File

@ -0,0 +1,128 @@
/**
* Behavioral tests for ecc_dashboard.py helper functions.
*/
const assert = require('assert');
const path = require('path');
const { spawnSync } = require('child_process');
const repoRoot = path.join(__dirname, '..', '..');
const runtimeHelpersPath = path.join(repoRoot, 'scripts', 'lib', 'ecc_dashboard_runtime.py');
function test(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (error) {
console.log(`${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
function runPython(source) {
const candidates = process.platform === 'win32' ? ['python', 'python3'] : ['python3', 'python'];
let lastError = null;
for (const command of candidates) {
const result = spawnSync(command, ['-c', source], {
cwd: repoRoot,
encoding: 'utf8',
});
if (result.error && result.error.code === 'ENOENT') {
lastError = result.error;
continue;
}
if (result.status !== 0) {
throw new Error((result.stderr || result.stdout || '').trim() || `${command} exited ${result.status}`);
}
return result.stdout.trim();
}
throw lastError || new Error('No Python interpreter available');
}
function runTests() {
console.log('\n=== Testing ecc_dashboard.py ===\n');
let passed = 0;
let failed = 0;
if (test('build_terminal_launch keeps Linux path separate from shell command text', () => {
const output = runPython(`
import importlib.util, json
spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
argv, kwargs = module.build_terminal_launch('/tmp/proj; rm -rf ~', os_name='posix', system_name='Linux')
print(json.dumps({'argv': argv, 'kwargs': kwargs}))
`);
const parsed = JSON.parse(output);
assert.deepStrictEqual(
parsed.argv,
['x-terminal-emulator', '-e', 'bash', '-lc', 'cd -- "$1"; exec bash', 'bash', '/tmp/proj; rm -rf ~']
);
assert.deepStrictEqual(parsed.kwargs, {});
})) passed++; else failed++;
if (test('build_terminal_launch uses cwd + CREATE_NEW_CONSOLE style launch on Windows', () => {
const output = runPython(`
import importlib.util, json
spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
argv, kwargs = module.build_terminal_launch(r'C:\\\\Users\\\\user\\\\proj & del C:\\\\*', os_name='nt', system_name='Windows')
print(json.dumps({'argv': argv, 'kwargs': kwargs}))
`);
const parsed = JSON.parse(output);
assert.deepStrictEqual(parsed.argv.slice(0, 4), ['cmd.exe', '/k', 'cd', '/d']);
assert.strictEqual(parsed.argv[4], parsed.kwargs.cwd);
assert.ok(parsed.argv[4].includes('proj & del'), 'path should remain a literal argv entry');
assert.ok(parsed.argv[4].includes('C:'), 'windows drive prefix should be preserved');
assert.ok(Object.prototype.hasOwnProperty.call(parsed.kwargs, 'creationflags'));
})) passed++; else failed++;
if (test('maximize_window falls back to Linux zoom attribute when zoomed state is unsupported', () => {
const output = runPython(`
import importlib.util, json
spec = importlib.util.spec_from_file_location("ecc_dashboard_runtime", r"""${runtimeHelpersPath}""")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
class FakeWindow:
def __init__(self):
self.calls = []
def state(self, value):
self.calls.append(['state', value])
raise RuntimeError('bad argument "zoomed"')
def attributes(self, name, value):
self.calls.append(['attributes', name, value])
original = module.platform.system
module.platform.system = lambda: 'Linux'
try:
window = FakeWindow()
module.maximize_window(window)
finally:
module.platform.system = original
print(json.dumps(window.calls))
`);
const parsed = JSON.parse(output);
assert.deepStrictEqual(parsed, [
['state', 'zoomed'],
['attributes', '-zoomed', true],
]);
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
runTests();