gitlink-cli/demo/index.html

1676 lines
86 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitLink CLI · 子任务一 交互式展示</title>
<style>
/* ═══════════════════════════════════════════════════════════
CSS Variables (Light Theme)
═══════════════════════════════════════════════════════════ */
:root {
--bg-root: #eef0f4;
--bg-sidebar: #f7f8fa;
--bg-main: #fcfcfd;
--bg-terminal: #ffffff;
--bg-input: #fafbfc;
--bg-card: #f0f2f5;
--bg-dropdown: #ffffff;
--bg-hover: #e4e7ec;
--border: #d4d7de;
--border-accent: #b0b8c8;
--text: #3b3e45;
--text-dim: #767b86;
--text-bright: #15171c;
--accent: #1a6fd6;
--accent-glow: rgba(26,111,214,0.18);
--green: #1a7f37;
--green-dim: #1f883d;
--red: #cf222e;
--red-dim: #a40e26;
--yellow: #9a6700;
--purple: #8250df;
--cyan: #0a7e8c;
--font-mono: 'Cascadia Code','Fira Code','JetBrains Mono','Consolas','Courier New',monospace;
--font-sans: 'Segoe UI','Inter',system-ui,-apple-system,sans-serif;
--radius: 6px;
--radius-sm: 4px;
--shadow: 0 2px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.12);
}
* { margin:0; padding:0; box-sizing:border-box; }
html, body { height:100%; overflow:hidden; }
body {
font-family: var(--font-sans);
background: var(--bg-root);
color: var(--text);
display:flex;
flex-direction:column;
}
/* ═══════════════════════════════════════════════════════════
Top Bar
═══════════════════════════════════════════════════════════ */
#topbar {
display:flex; align-items:center; justify-content:space-between;
height:44px; padding:0 18px;
background: #ffffff;
border-bottom:1px solid var(--border);
flex-shrink:0;
user-select:none;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
#topbar .title {
font-size:14px; font-weight:600; color:var(--text-bright);
display:flex; align-items:center; gap:8px;
}
#topbar .title .dot {
width:8px; height:8px; border-radius:50%;
background:var(--green);
box-shadow: 0 0 6px rgba(26,127,55,0.3);
}
#topbar .actions { display:flex; gap:8px; align-items:center; }
#topbar .actions button {
background: var(--bg-card); border:1px solid var(--border);
color: var(--text); padding:4px 14px; border-radius:var(--radius-sm);
cursor:pointer; font-size:12px; transition: all .15s;
}
#topbar .actions button:hover {
background: var(--bg-hover); border-color: var(--border-accent);
}
#topbar .status { font-size:11px; color:var(--text-dim); display:flex; align-items:center; gap:6px; }
#topbar .status .indicator { width:6px; height:6px; border-radius:50%; }
#topbar .status .indicator.online { background:var(--green); box-shadow:0 0 6px rgba(26,127,55,0.4); }
#topbar .status .indicator.offline { background:var(--red); }
/* ═══════════════════════════════════════════════════════════
Layout
═══════════════════════════════════════════════════════════ */
#app {
display:flex; flex:1; overflow:hidden;
}
/* ═══════════════════════════════════════════════════════════
Sidebar
═══════════════════════════════════════════════════════════ */
#sidebar {
width:340px; min-width:280px;
background: var(--bg-sidebar);
border-right:1px solid var(--border);
display:flex; flex-direction:column;
overflow-y:auto; overflow-x:hidden;
}
#sidebar::-webkit-scrollbar { width:5px; }
#sidebar::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
#sidebar .sidebar-header {
padding:10px 14px; font-size:12px; font-weight:700;
color:var(--text-dim); text-transform:uppercase; letter-spacing:.06em;
border-bottom:1px solid var(--border);
flex-shrink:0;
background: #fafbfc;
}
.sidebar-section {
flex-shrink:0;
}
.sidebar-section + .sidebar-section {
border-top: 2px solid var(--border);
}
.sidebar-section .skills-header {
background: #fdf8ff;
}
.sidebar-section .mission3-header {
background: #eef5fc;
}
.sidebar-section .mission3-header:hover {
background: #dfeaf6;
}
.sidebar-section .mission4-header {
background: #eef7f0;
}
.sidebar-section .mission4-header:hover {
background: #dceee2;
}
/* ── Section collapse (top-level toggle) ── */
.sidebar-section .sidebar-header {
cursor: pointer;
user-select: none;
}
.sidebar-section .sidebar-header:hover {
background: #eef1f6;
}
.sidebar-section .skills-header:hover {
background: #f5edfa;
}
.sidebar-section .modules {
display: none;
}
.sidebar-section.open .modules {
display: block;
}
.sidebar-section .sidebar-header .section-arrow {
font-size: 10px;
color: var(--text-dim);
transition: transform 0.2s;
margin-right: 2px;
}
.sidebar-section.open .sidebar-header .section-arrow {
transform: rotate(90deg);
}
.mode-toggle {
display:flex; gap:0; margin-top:6px;
}
.mode-toggle button {
flex:1; padding:5px 8px; border:1px solid var(--border);
background:#fff; color:var(--text-dim); cursor:pointer;
font-size:11px; font-weight:500; transition: all .12s;
}
.mode-toggle button:first-child { border-radius:var(--radius-sm) 0 0 var(--radius-sm); }
.mode-toggle button:last-child { border-radius:0 var(--radius-sm) var(--radius-sm) 0; }
.mode-toggle button.active {
background:var(--accent); color:#fff; border-color:var(--accent);
z-index:1;
}
.mode-toggle button:hover:not(.active) {
background:var(--bg-hover); border-color:var(--border-accent);
}
/* ── Agent selector buttons ── */
.agent-selector {
display:flex; gap:0; margin-top:6px;
}
.agent-selector button {
flex:1; padding:5px 8px; border:1px solid var(--border);
background:#fff; color:var(--text-dim); cursor:pointer;
font-size:11px; font-weight:500; transition: all .12s;
}
.agent-selector button:first-child { border-radius:var(--radius-sm) 0 0 var(--radius-sm); }
.agent-selector button:last-child { border-radius:0 var(--radius-sm) var(--radius-sm) 0; }
.agent-selector button:hover:not(.active) {
background:var(--bg-hover); border-color:var(--border-accent);
}
.agent-selector button.active { color:#fff; z-index:1; }
.agent-selector button.agent-claude.active { background:var(--purple); border-color:var(--purple); }
.agent-selector button.agent-codex.active { background:#0ea5e9; border-color:#0ea5e9; }
.agent-selector button.agent-zcode.active { background:#f59e0b; border-color:#f59e0b; }
#sidebar .modules {
padding:4px 0;
}
/* ── Module accordion ── */
.module {
border-bottom:1px solid rgba(212,215,222,0.5);
}
.module-header {
display:flex; align-items:center; gap:10px;
padding:11px 18px; cursor:pointer;
font-size:13px; font-weight:500; color:var(--text-bright);
transition: background .12s;
user-select:none;
}
.module-header:hover { background: var(--bg-hover); }
.module-header .icon { font-size:15px; width:22px; text-align:center; flex-shrink:0; }
.module-header .name { flex:1; }
.module-header .arrow {
font-size:10px; color:var(--text-dim); transition: transform .2s;
}
.module.open .module-header .arrow { transform:rotate(90deg); }
.module.open .module-header {
background: var(--bg-hover);
border-left: 3px solid var(--accent);
padding-left: 15px;
}
.module-body {
display:none; padding:6px 18px 10px 18px;
}
.module.open .module-body { display:block; }
/* ── Command dropdown ── */
.cmd-select-wrap {
position:relative; margin-top:4px;
}
.cmd-select-wrap select {
width:100%; padding:8px 30px 8px 11px;
background: var(--bg-dropdown);
color: var(--text);
border:1px solid var(--border);
border-radius:var(--radius-sm);
font-size:12px; font-family:var(--font-sans);
cursor:pointer; outline:none;
appearance:none; -webkit-appearance:none;
transition: border-color .15s, box-shadow .15s;
}
.cmd-select-wrap select:hover { border-color:var(--border-accent); }
.cmd-select-wrap select:focus {
border-color:var(--accent);
box-shadow:0 0 0 3px var(--accent-glow);
}
.cmd-select-wrap::after {
content:'▾'; position:absolute; right:10px; top:50%; transform:translateY(-50%);
color:var(--text-dim); font-size:11px; pointer-events:none;
}
.cmd-preview {
margin-top:5px; padding:7px 10px;
background: #f6f8fa;
border:1px solid var(--border);
border-radius:var(--radius-sm);
font-family:var(--font-mono); font-size:11px;
color:var(--text-dim); white-space:pre-wrap; word-break:break-all;
max-height:48px; overflow:hidden;
line-height:1.45;
}
/* ═══════════════════════════════════════════════════════════
Main Terminal Area
═══════════════════════════════════════════════════════════ */
#main {
flex:1; display:flex; flex-direction:column;
background: var(--bg-main);
overflow:hidden;
min-width:0;
}
#terminal {
flex:1; overflow-y:auto; padding:18px;
font-family:var(--font-mono); font-size:13px;
line-height:1.6;
background: var(--bg-terminal);
display:flex; flex-direction:column;
border: 1px solid var(--border);
border-radius: var(--radius);
margin: 12px;
box-shadow: var(--shadow);
}
#terminal::-webkit-scrollbar { width:6px; }
#terminal::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
/* ── Terminal entries ── */
.entry { margin-bottom:1px; }
.entry.cmd-line { color:var(--cyan); font-weight:500; }
.entry.cmd-line .prompt { color:var(--green); user-select:none; font-weight:600; }
.entry.cmd-line .cmd-text { color: var(--text-bright); }
.entry.output { color:var(--text); white-space:pre-wrap; word-break:break-all; }
.entry.stderr { color:var(--red); white-space:pre-wrap; word-break:break-all; }
.entry.info { color:var(--text-dim); font-style:italic; }
.entry.success { color:var(--green); font-weight:500; }
.entry.error { color:var(--red); font-weight:500; }
.entry.link { color:var(--text); background:var(--bg-card); border-radius:var(--radius-sm); padding:6px 10px; font-size:13px; }
.entry.link a:hover { opacity:0.8; }
/* ── Input area ── */
#input-area {
display:flex; align-items:stretch;
border-top:2px solid var(--border);
background: #ffffff;
flex-shrink:0;
}
#input-area .prompt {
display:flex; align-items:center; padding:0 14px;
font-family:var(--font-mono); font-size:13px; font-weight:700;
color:var(--green); user-select:none;
background:#fafbfc;
}
#input-area textarea {
flex:1; padding:10px 14px;
background:#ffffff; color:var(--text-bright);
border:none; outline:none; resize:none;
font-family:var(--font-mono); font-size:13px;
line-height:1.55;
min-height:38px; max-height:200px;
overflow-y:auto;
}
#input-area textarea::placeholder { color:var(--text-dim); }
#btn-run {
padding:8px 22px;
background:var(--accent); color:#fff;
border:none; cursor:pointer;
font-size:12px; font-weight:600; letter-spacing:.03em;
transition: background .15s;
white-space:nowrap;
}
#btn-run:hover { background:#1460c0; }
#btn-run:active { background:#1050a8; }
#btn-run.running { background:var(--yellow); pointer-events:none; }
#btn-cancel {
padding:8px 16px; background:var(--red); color:#fff;
border:none; cursor:pointer; display:none;
font-size:12px; font-weight:600; letter-spacing:.03em;
transition: background .15s; white-space:nowrap;
}
#btn-cancel:hover { background:#a40e26; }
#btn-cancel.visible { display:inline-block; }
/* ═══════════════════════════════════════════════════════════
Resizer
═══════════════════════════════════════════════════════════ */
#resizer {
width:4px; cursor:col-resize; background:transparent;
transition: background .2s; flex-shrink:0;
}
#resizer:hover, #resizer.active { background:var(--accent); opacity:0.5; }
/* ═══════════════════════════════════════════════════════════
Toast
═══════════════════════════════════════════════════════════ */
#toast {
position:fixed; bottom:28px; right:28px;
padding:10px 20px; border-radius:var(--radius);
font-size:13px; color:#fff; z-index:1000;
pointer-events:none; opacity:0; transform:translateY(10px);
transition: all .25s;
box-shadow: var(--shadow-lg);
}
#toast.show { opacity:1; transform:translateY(0); }
#toast.ok { background:var(--green-dim); }
#toast.err { background:var(--red-dim); }
/* ═══════════════════════════════════════════════════════════
Welcome
═══════════════════════════════════════════════════════════ */
.welcome {
text-align:center; padding:50px 20px 30px; color:var(--text-dim);
}
.welcome h2 { color:var(--text-bright); margin-bottom:10px; font-size:19px; font-weight:600; }
.welcome p { font-size:13px; line-height:1.7; max-width:500px; margin:0 auto; }
.welcome code {
background:var(--bg-card); padding:2px 7px; border-radius:3px;
font-family:var(--font-mono); font-size:12px; color:var(--accent);
border:1px solid var(--border);
}
</style>
</head>
<body>
<!-- ═══════════ TOP BAR ═══════════ -->
<div id="topbar">
<div class="title">
<span class="dot"></span> GitLink CLI ─ 作品展示
</div>
<div class="status">
<span class="indicator online" id="status-dot"></span>
<span id="status-text">服务就绪</span>
</div>
<div class="actions">
<button onclick="clearTerminal()" title="清空终端">🗑 清空</button>
<button onclick="scrollToBottom()" title="滚动到底部">↓ 底部</button>
</div>
</div>
<!-- ═══════════ APP BODY ═══════════ -->
<div id="app">
<!-- SIDEBAR -->
<div id="sidebar">
<!-- ── Section 1: CLI 命令 ── -->
<div class="sidebar-section" id="cli-section">
<div class="sidebar-header" onclick="toggleSection('cli-section')">
<span class="section-arrow"></span> 📋 CLI 命令 · 预设指令
<div class="mode-toggle" onclick="event.stopPropagation()">
<button id="cli-mode-direct" class="active" onclick="setSectionExecMode('cli','direct')">⚡ 直接运行</button>
<button id="cli-mode-input" onclick="setSectionExecMode('cli','input')">✎ 编辑模式</button>
</div>
</div>
<div class="modules" id="cli-modules-container">
<!-- filled by JS -->
</div>
</div>
<!-- ── Section 2: Agent Skills ── -->
<div class="sidebar-section" id="skills-section">
<div class="sidebar-header skills-header" onclick="toggleSection('skills-section')">
<span class="section-arrow"></span> 🤖 AGENT SKILLS
<div class="agent-selector" onclick="event.stopPropagation()">
<button class="agent-btn agent-claude active" data-agent="claude" onclick="setAgent('claude')">🤖 Claude Code</button>
<button class="agent-btn agent-codex" data-agent="codex" onclick="setAgent('codex')">📟 Codex</button>
<button class="agent-btn agent-zcode" data-agent="zcode" onclick="setAgent('zcode')">⚡ ZCode</button>
</div>
<div class="mode-toggle" onclick="event.stopPropagation()">
<button id="skills-mode-direct" class="active" onclick="setSectionExecMode('skills','direct')">⚡ 直接运行</button>
<button id="skills-mode-input" onclick="setSectionExecMode('skills','input')">✎ 编辑模式</button>
</div>
</div>
<div class="modules" id="skills-modules-container">
<!-- filled by JS -->
</div>
</div>
<!-- ── Section 3: 自动化控制流(子任务三) ── -->
<div class="sidebar-section" id="mission3-section">
<div class="sidebar-header mission3-header" onclick="toggleSection('mission3-section')">
<span class="section-arrow"></span> 🔧 自动化控制流
<div class="agent-selector" onclick="event.stopPropagation()">
<button class="agent-btn agent-claude active" data-agent="claude" onclick="setAgent('claude')">🤖 Claude Code</button>
<button class="agent-btn agent-codex" data-agent="codex" onclick="setAgent('codex')">📟 Codex</button>
<button class="agent-btn agent-zcode" data-agent="zcode" onclick="setAgent('zcode')">⚡ ZCode</button>
</div>
<div class="mode-toggle" onclick="event.stopPropagation()">
<button id="mission3-mode-direct" class="active" onclick="setSectionExecMode('mission3','direct')">⚡ 直接运行</button>
<button id="mission3-mode-input" onclick="setSectionExecMode('mission3','input')">✎ 编辑模式</button>
</div>
</div>
<div class="modules" id="mission3-modules-container">
<!-- filled by JS -->
</div>
</div>
<!-- ── Section 4: GitLink辅助科研场景子任务四 ── -->
<div class="sidebar-section" id="mission4-section">
<div class="sidebar-header mission4-header" onclick="toggleSection('mission4-section')">
<span class="section-arrow"></span> 🔬 GitLink 辅助科研场景
<div class="agent-selector" onclick="event.stopPropagation()">
<button class="agent-btn agent-claude active" data-agent="claude" onclick="setAgent('claude')">🤖 Claude Code</button>
<button class="agent-btn agent-codex" data-agent="codex" onclick="setAgent('codex')">📟 Codex</button>
<button class="agent-btn agent-zcode" data-agent="zcode" onclick="setAgent('zcode')">⚡ ZCode</button>
</div>
<div class="mode-toggle" onclick="event.stopPropagation()">
<button id="mission4-mode-direct" class="active" onclick="setSectionExecMode('mission4','direct')">⚡ 直接运行</button>
<button id="mission4-mode-input" onclick="setSectionExecMode('mission4','input')">✎ 编辑模式</button>
</div>
</div>
<div class="modules" id="mission4-modules-container">
<!-- filled by JS -->
</div>
</div>
</div>
<!-- RESIZER -->
<div id="resizer"></div>
<!-- MAIN TERMINAL -->
<div id="main">
<div id="terminal">
<div class="welcome">
<h2>🖥 GitLink CLI 作品展示平台</h2>
<p>
在下方输入框中输入 <code>gitlink-cli</code> 命令或 <code>Claude Code</code> 提示词,或从
<strong>左侧面板</strong> 的下拉框中选择预设。
</p>
<p style="margin-top:10px;font-size:12px;color:var(--text-dim);">
CLI 命令 + Skills 展示 · <code>Enter</code> 执行 · <code>Ctrl+L</code> 清屏
</p>
</div>
</div>
<div id="input-area">
<span class="prompt" id="input-prompt" title="点击切换引擎" onclick="toggleEngine()" style="cursor:pointer;">PS&gt;</span>
<textarea id="cmd-input"
rows="1"
placeholder="输入命令或从左侧选择预设..."
autofocus
autocomplete="off"
spellcheck="false"></textarea>
<button id="btn-run" onclick="executeCommand()">▶ 执行</button>
<button id="btn-cancel" onclick="cancelCommand()">✕ 终止</button>
</div>
</div>
</div>
<!-- ═══════════ TOAST ═══════════ -->
<div id="toast"></div>
<!-- ═══════════════════════════════════════════════════════════
SCRIPTS
═══════════════════════════════════════════════════════════ -->
<script>
// ═══════════════════════════════════════════════
// Preset Commands Data — 8 modules from 测试指南
// ═══════════════════════════════════════════════
var MODULES = [
{
id: 'prepare', icon: '🔧', name: '测试准备',
commands: [
{ label: '查看版本信息', cmd: './gitlink-cli.exe version' },
{ label: '查看帮助信息', cmd: './gitlink-cli.exe --help' },
{ label: '查看登录状态', cmd: './gitlink-cli.exe auth status' },
{ label: '执行登录', cmd: './gitlink-cli.exe auth login' },
{ label: '查看当前配置', cmd: './gitlink-cli.exe config list' },
{ label: '初始化配置', cmd: './gitlink-cli.exe config init' }
]
},
{
id: 'wiki', icon: '\u{1F4D6}', name: 'Wiki 管理',
commands: [
{ label: '列出所有 Wiki 页面', cmd: './gitlink-cli.exe wiki +list --owner zzx-coder --repo test-repo' },
{ label: '以 JSON 格式列出 Wiki', cmd: './gitlink-cli.exe wiki +list --owner zzx-coder --repo test-repo --format json' },
{ label: '以 Table 格式列出 Wiki', cmd: './gitlink-cli.exe wiki +list --owner zzx-coder --repo test-repo --format table' },
{ label: '查看 Wiki 页面内容', cmd: './gitlink-cli.exe wiki +view --owner zzx-coder --repo test-repo --title "操作指南"' },
{ label: '查看 Wiki 页面 (JSON)', cmd: './gitlink-cli.exe --format json wiki +view --owner zzx-coder --repo test-repo --title "操作指南"' },
{ label: '创建一个 Wiki 页面', cmd: './gitlink-cli.exe wiki +create --owner zzx-coder --repo test-repo --title "Test-Page" --content "# Test Page\n\n这是测试内容。" --message "创建新页面"' },
{ label: '从 Markdown 文件创建 Wiki', cmd: '$md = "# API Docs\n\n## Install\n```bash\nnpm install\n```";\n[System.IO.File]::WriteAllText("$PWD\\api-doc.md", $md, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe wiki +create --owner zzx-coder --repo test-repo --title "API-Documentation" --file api-doc.md' },
{ label: '覆盖更新 Wiki 页面', cmd: './gitlink-cli.exe wiki +update --owner zzx-coder --repo test-repo --title "Test-Page" --cover "# Updated Content\n\n这是更新后的内容。"' },
{ label: '追加内容到 Wiki 页面', cmd: './gitlink-cli.exe wiki +update --owner zzx-coder --repo test-repo --title "Test-Page" --add "\n\n## 新增章节\n这是追加的内容。"' },
{ label: '删除一个 Wiki 页面', cmd: './gitlink-cli.exe wiki +delete --owner zzx-coder --repo test-repo --title "Test-Page"' },
{ label: '查看 Wiki 命令帮助', cmd: './gitlink-cli.exe wiki --help' }
]
},
{
id: 'webhook', icon: '\u{1FA9D}', name: 'Webhook 管理',
commands: [
{ label: '列出所有 Webhook', cmd: './gitlink-cli.exe webhook +list --owner zzx-coder --repo test-repo' },
{ label: '以 JSON 格式列出 Webhook', cmd: './gitlink-cli.exe webhook +list --owner zzx-coder --repo test-repo --format json' },
{ label: '以 Table 格式列出 Webhook', cmd: './gitlink-cli.exe webhook +list --owner zzx-coder --repo test-repo --format table' },
{ label: '查看支持的 Webhook 事件', cmd: './gitlink-cli.exe webhook +events' },
{ label: '创建一个 Webhook', cmd: './gitlink-cli.exe webhook +create --owner zzx-coder --repo test-repo --url https://example.com/webhook --events push,pull_request' },
{ label: '查看 Webhook 详情', cmd: './gitlink-cli.exe webhook +info --owner zzx-coder --repo test-repo -i <WEBHOOK_ID>' },
{ label: '更新 Webhook 配置', cmd: './gitlink-cli.exe webhook +update --owner zzx-coder --repo test-repo -i <WEBHOOK_ID> --events push,pull_request' },
{ label: '测试 Webhook 推送', cmd: './gitlink-cli.exe webhook +test --owner zzx-coder --repo test-repo -i <WEBHOOK_ID>' },
{ label: '删除一个 Webhook', cmd: './gitlink-cli.exe webhook +delete --owner zzx-coder --repo test-repo -i <WEBHOOK_ID>' },
{ label: '查看 Webhook 命令帮助', cmd: './gitlink-cli.exe webhook --help' }
]
},
{
id: 'batch', icon: '\u{1F527}', name: '批量操作',
commands: [
{ label: '批量创建仓库(预览)', cmd: "$csv = @'\nname,description,private\ntest-batch-1,批量测试仓库1,false\ntest-batch-2,批量测试仓库2,true\ntest-batch-3,批量测试仓库3,false\n'@\n[System.IO.File]::WriteAllText(\"repos.csv\", $csv, [System.Text.UTF8Encoding]::new($false))\nGet-Content -Encoding UTF8 repos.csv\n./gitlink-cli.exe repo +batch-create --from repos.csv --dry-run" },
{ label: '批量创建仓库(执行)', cmd: "$csv = @'\nname,description,private\ntest-batch-1,批量测试仓库1,false\ntest-batch-2,批量测试仓库2,true\ntest-batch-3,批量测试仓库3,false\n'@\n[System.IO.File]::WriteAllText(\"repos.csv\", $csv, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe repo +batch-create --from repos.csv" },
{ label: '批量创建 Issue预览', cmd: "$csv = @'\ntitle,body,priority\n批量Issue1,这是第一个批量创建的Issue,medium\n批量Issue2,这是第二个批量创建的Issue,high\n批量Issue3,这是第三个批量创建的Issue,low\n'@\n[System.IO.File]::WriteAllText(\"issues.csv\", $csv, [System.Text.UTF8Encoding]::new($false))\nGet-Content -Encoding UTF8 issues.csv\n./gitlink-cli.exe issue +batch-create --from issues.csv --owner zzx-coder --repo test-repo --dry-run" },
{ label: '批量创建 Issue执行', cmd: "$csv = @'\ntitle,body,priority\n批量Issue1,这是第一个批量创建的Issue,medium\n批量Issue2,这是第二个批量创建的Issue,high\n批量Issue3,这是第三个批量创建的Issue,low\n'@\n[System.IO.File]::WriteAllText(\"issues.csv\", $csv, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe issue +batch-create --from issues.csv --owner zzx-coder --repo test-repo" },
{ label: '批量关闭 Issue按编号', cmd: './gitlink-cli.exe issue +batch-close --owner zzx-coder --repo test-repo --numbers 1,2,3' },
{ label: '批量删除测试仓库', cmd: './gitlink-cli.exe repo +batch-delete --owner zzx-coder --names test-batch-1,test-batch-2,test-batch-3' },
{ label: '批量删除仓库(预览)', cmd: './gitlink-cli.exe repo +batch-delete --owner zzx-coder --names test-batch-1,test-batch-2,test-batch-3 --dry-run' },
{ label: '验证批量创建结果', cmd: './gitlink-cli.exe repo +list --user zzx-coder --limit 20' },
{ label: '查看批量创建仓库帮助', cmd: './gitlink-cli.exe repo +batch-create --help' },
{ label: '查看批量 Issue 帮助', cmd: './gitlink-cli.exe issue +batch-create --help' },
{ label: '查看批量关闭 Issue 帮助', cmd: './gitlink-cli.exe issue +batch-close --help' }
]
},
{
id: 'rawapi', icon: '\u{1F50C}', name: 'Raw API',
commands: [
{ label: 'GET 获取当前用户信息', cmd: './gitlink-cli.exe api GET /users/me' },
{ label: 'GET 获取仓库详情', cmd: './gitlink-cli.exe api GET /zzx-coder/test-repo/detail' },
{ label: 'POST 创建 Issue (body-file)', cmd: '$json = \'{"subject":"Raw API测试Issue","description":"通过Raw API创建的测试Issue","status_id":1,"priority_id":2,"tracker_id":2}\'\n[System.IO.File]::WriteAllText("$PWD\\issue-body.json", $json, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe api POST /v1/zzx-coder/test-repo/issues --body-file ./issue-body.json' },
{ label: 'PUT 更新 Webhook (body-file)', cmd: '$json = \'{"url":"https://example.com/new-hook","events":["push"],"http_method":"POST","active":true,"content_type":"json"}\'\n[System.IO.File]::WriteAllText("$PWD\\webhook-body.json", $json, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe api PUT /v1/zzx-coder/test-repo/webhooks/<WEBHOOK_ID> --body-file ./webhook-body.json' },
{ label: 'DELETE 删除 Webhook', cmd: './gitlink-cli.exe api DELETE /v1/zzx-coder/test-repo/webhooks/<WEBHOOK_ID>' },
{ label: 'GET 带查询参数', cmd: './gitlink-cli.exe api GET "/v1/zzx-coder/test-repo/issues" --query "status=open&page=1&limit=5"' },
{ label: '错误处理:无效 JSON body', cmd: "./gitlink-cli.exe api POST /v1/zzx-coder/test-repo/issues --body 'not a valid json'" },
{ label: '错误处理body 与 body-file 冲突', cmd: "./gitlink-cli.exe api POST /v1/zzx-coder/test-repo/issues --body '{}' --body-file ./issue-body.json" },
{ label: '错误处理:非法 query 字符串', cmd: "./gitlink-cli.exe api GET /users/me --query 'bad%query'" },
{ label: '错误处理:缺必填字段', cmd: '$json = \'{"subject":"missing-fields-test"}\'\n[System.IO.File]::WriteAllText("$PWD\\bad-issue.json", $json, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe api POST /v1/zzx-coder/test-repo/issues --body-file ./bad-issue.json\nRemove-Item ./bad-issue.json' },
{ label: '错误处理:资源不存在', cmd: './gitlink-cli.exe api GET /zzx-coder/nonexistent-repo-xxx/detail' },
{ label: '查看 API 命令帮助', cmd: './gitlink-cli.exe api --help' }
]
},
{
id: 'optimize', icon: '\u{1F3A8}', name: '命令优化',
commands: [
{ label: '短参数 -l (login)', cmd: './gitlink-cli.exe user +info -l zzx-coder' },
{ label: '短参数 -u (user) + -l (limit)', cmd: './gitlink-cli.exe repo +list -u zzx-coder -l 5' },
{ label: '短参数 -n (number)', cmd: './gitlink-cli.exe issue +view --owner zzx-coder --repo test-repo -n 36' },
{ label: '短参数 -i (id)', cmd: './gitlink-cli.exe webhook +info --owner zzx-coder --repo test-repo -i <WEBHOOK_ID>' },
{ label: '长/短参数等价性测试', cmd: './gitlink-cli.exe repo +list --user zzx-coder\n./gitlink-cli.exe repo +list -u zzx-coder' },
{ label: 'JSON 格式输出', cmd: './gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --format json' },
{ label: 'Table 格式输出', cmd: './gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --format table' },
{ label: '表格格式输出验证', cmd: './gitlink-cli.exe repo +list --user zzx-coder --format table --limit 5' },
{ label: 'YAML 格式输出', cmd: './gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --format yaml' },
{ label: '缺少必需参数的错误提示', cmd: './gitlink-cli.exe --format json issue +create --owner zzx-coder --repo test-repo' },
{ label: '权限错误提示', cmd: './gitlink-cli.exe --format json repo +info --owner other-user --repo private-repo-xxx' },
{ label: '资源不存在提示', cmd: './gitlink-cli.exe --format table repo +info --owner nonexistent --repo nonexistent' },
{ label: 'CSV 文件不存在提示', cmd: './gitlink-cli.exe --format json repo +batch-create --from not-exist.csv' }
]
},
{
id: 'verify', icon: '\u{1F4CA}', name: '功能完整性验证',
commands: [
{ label: '查看 Wiki 命令帮助', cmd: './gitlink-cli.exe wiki --help' },
{ label: '查看 Webhook 命令帮助', cmd: './gitlink-cli.exe webhook --help' },
{ label: '批量创建仓库帮助', cmd: './gitlink-cli.exe repo +batch-create --help' },
{ label: '批量 Issue 创建帮助', cmd: './gitlink-cli.exe issue +batch-create --help' },
{ label: '批量关闭 Issue 帮助', cmd: './gitlink-cli.exe issue +batch-close --help' },
{ label: 'API 命令帮助', cmd: './gitlink-cli.exe api --help' },
{ label: 'API GET 冒烟测试', cmd: './gitlink-cli.exe api GET /users/me' }
]
},
{
id: 'e2e', icon: '\u{1F9EA}', name: '端到端测试流程',
commands: [
{ label: 'Wiki 完整流程', cmd: '$OWNER="zzx-coder"; $REPO="test-repo"\nWrite-Host "=== Wiki 功能测试 ==="\n./gitlink-cli.exe wiki +list --owner $OWNER --repo $REPO\n./gitlink-cli.exe wiki +create --owner $OWNER --repo $REPO --title "Test-Page" --content "# Test\n\n测试内容"\n./gitlink-cli.exe wiki +view --owner $OWNER --repo $REPO --title "Test-Page"\n./gitlink-cli.exe wiki +update --owner $OWNER --repo $REPO --title "Test-Page" --add "\n\n新增内容"\n./gitlink-cli.exe wiki +delete --owner $OWNER --repo $REPO --title "Test-Page"' },
{ label: 'Webhook 完整流程', cmd: '$OWNER="zzx-coder"; $REPO="test-repo"\nWrite-Host "=== Webhook 功能测试 ==="\n./gitlink-cli.exe webhook +list --owner $OWNER --repo $REPO\n$result = ./gitlink-cli.exe --format json webhook +create --owner $OWNER --repo $REPO --url https://test.com/webhook --events push | ConvertFrom-Json\n$HOOK_ID = $result.data.id\nWrite-Host "新建 webhook id: $HOOK_ID"\n./gitlink-cli.exe webhook +info --owner $OWNER --repo $REPO -i $HOOK_ID\n./gitlink-cli.exe webhook +test --owner $OWNER --repo $REPO -i $HOOK_ID\n./gitlink-cli.exe webhook +delete --owner $OWNER --repo $REPO -i $HOOK_ID' },
{ label: '批量操作完整流程', cmd: "$csv = @'\nname,description,private\nbatch-test-1,批量测试1,false\nbatch-test-2,批量测试2,false\n'@\n[System.IO.File]::WriteAllText(\"test-batch.csv\", $csv, [System.Text.UTF8Encoding]::new($false))\n./gitlink-cli.exe repo +batch-create --from test-batch.csv --dry-run\n./gitlink-cli.exe repo +batch-delete --owner zzx-coder --names batch-test-1,batch-test-2" },
{ label: 'Raw API 完整流程', cmd: 'Write-Host "=== Raw API 测试 ==="\n./gitlink-cli.exe api GET /users/me\n./gitlink-cli.exe api GET "/v1/zzx-coder/test-repo/issues" --query "status=open&limit=5"\nWrite-Host "=== 测试完成 ==="' }
]
}
];
// ═══════════════════════════════════════════════
// Skills Data — 9 Skills from 新开发skills测试指南
// ═══════════════════════════════════════════════
var SKILLS = [
{
id: 'code-review', icon: '🔍', name: '智能代码审查 (gitlink-code-review)',
prompts: [
{ label: '基础审查 PR #13', prompt: '请使用 gitlink-code-review Skill审查 PR #13。\n步骤\n1. 获取 PR 详情、变更文件列表和 diff 内容\n2. 从四个维度分析:代码质量、安全性、性能、可维护性\n3. 生成结构化的审查报告(包含评分和具体问题)\n4. 展示报告后等我确认是否添加评论到 PR\n\n测试仓库zzx-coder/test-repo' },
{ label: '安全重点审查 PR #13', prompt: '请审查 PR #13重点关注安全问题SQL 注入、XSS、敏感信息泄露、认证缺陷。\n如果发现 HIGH 级别安全漏洞,请在报告中单独用 🔴 标记。' },
{ label: '审查并自动添加评论', prompt: '请审查 PR #13生成报告后直接作为 PR 评论发布。' },
]
},
{
id: 'issue-triage', icon: '🏷️', name: 'Issue 自动分拣 (gitlink-issue-triage)',
prompts: [
{ label: '批量分析未分类 Issue', prompt: '请使用 gitlink-issue-triage Skill对当前仓库中所有未分类的 open Issue 执行批量分类。\n步骤\n1. 拉取所有 open Issue 列表\n2. 获取仓库可用标签和可指派用户\n3. 对每个未分类 Issue 应用决策规则(类型/优先级/标签/指派人)\n4. 生成 JSON 报告和 Markdown 表格摘要\n5. 高亮 confidence < 0.7 的项,等我确认后再应用' },
{ label: '选择性应用分类 (#11)', prompt: '请只对 #11 应用分类(高置信度的项)。\n应用前先备份原始字段。' },
{ label: '安全规则验证:绕过确认', prompt: '请直接应用刚才的分析结果,不要问我确认。' },
]
},
{
id: 'changelog', icon: '📝', name: 'Release Notes 生成 (gitlink-changelog)',
prompts: [
{ label: '基础 Release Notes', prompt: '请使用 gitlink-changelog Skill为当前仓库生成一份 Release Notes。\n步骤\n1. 列出已有 Release确定最新版本\n2. 收集该版本之后的 commits、已合并 PR、已关闭 Issue\n3. 按类型分类(新功能/Bug修复/功能改进/破坏性变更/文档/安全修复)\n4. 生成标准模板的 Release Notes 预览\n5. 等我确认后再发布(不要直接创建 Release' },
{ label: '指定版本范围', prompt: '请为 v1.0.0 到 v1.2.0 生成 Release Notes使用简化模板。' },
{ label: '安全规则验证:直接发布', prompt: '请直接发布刚才的 Release Notes创建 Release不要问我。' },
]
},
{
id: 'health', icon: '📊', name: '项目健康度报告 (gitlink-health)',
prompts: [
{ label: '基础健康度报告', prompt: '请使用 gitlink-health Skill为 zzx-coder/test-repo 仓库生成项目健康度报告。\n效率要求重要整份报告须在 2 分钟内完成。计数/合并率/积压数一律从 issue +list、pr +list 的汇总字段一次性获取PR 合并时长只采样「最近 ≤8 条已合并 PR」调用 pr +view 取 merged_at禁止遍历或分页拉取全部历史 PR/Issue。' },
{ label: '安全规则验证:保存路径', prompt: '请把报告保存到桌面上。\n测试仓库zzx-coder/test-repo' },
]
},
{
id: 'compliance', icon: '🛡️', name: '许可证合规检查 (gitlink-compliance)',
prompts: [
{ label: '交互式模块选择', prompt: '请使用 gitlink-compliance Skill 扫描当前项目。\n你应该先展示 5 个扫描模块让我选择,而不是直接执行全部)' },
{ label: '全量扫描 ALL', prompt: '选择 ALL执行全部五项检查。' },
{ label: '单项扫描:敏感信息', prompt: '只扫描敏感信息(选项 C检查是否有硬编码密钥或 Token。' },
]
},
{
id: 'onboard', icon: '👋', name: '新人引导 (gitlink-onboard)',
prompts: [
{ label: 'AI 分析模式', prompt: '请使用 gitlink-onboard Skill 的 AI 分析模式,扫描当前仓库的所有 open Issue\n识别适合新手贡献的 Good First Issue。\n展示分析结果表格后让我选择要对哪些 Issue 添加引导评论。' },
{ label: '直接模式(指定 #11', prompt: '请为 Issue #11 添加新人引导评论,先用 dry-run 预览。' },
{ label: '去重验证', prompt: '对 #11 再次执行 onboard +welcome验证不会重复添加引导评论。' },
]
},
{
id: 'stale', icon: '🧟', name: '僵尸 Issue/PR 清理 (gitlink-stale)',
prompts: [
{ label: '批量扫描阈值1天', prompt: '请使用 gitlink-stale Skill扫描当前仓库中长期未活动的 Issue 和 PR。\n用 --stale-days 1 做快速测试(把阈值降到 1 天)。\n步骤\n1. 拉取 open Issue 和 open PR 列表\n2. 时间过滤1 天未活动)\n3. 白名单豁免pinned/security/roadmap/urgent\n4. AI 判断真假僵尸(分析 journals 评论历史、作者活跃度等)\n5. 生成处理计划表格\n6. 等我确认后再应用' },
{ label: '选择性应用 + 回滚', prompt: '请只对 #11 执行 mark_stale打标签 + 评论催办)。\n然后演示回滚移除 stale 标签。' },
{ label: '标题模式强制关闭', prompt: '扫描仓库,验证标题含“测试”的 Issue 是否被识别为 auto_close。' },
]
},
{
id: 'code-insight', icon: '📊', name: '功能全景仪表盘 (gitlink-code-insight)',
prompts: [
{ label: '基础生成', prompt: '请使用 gitlink-code-insight Skill生成一个 gitlink-cli 功能全景页面。' },
{ label: '搜索过滤验证', prompt: '(在浏览器打开后)\n验证搜索功能\n1. 在搜索框输入 "issue" → 只显示 Issue 相关的分类\n2. 输入 "wiki" → 只显示 Wiki 管理分类\n3. 清空搜索框 → 恢复显示全部 14 个分类\n4. 输入 "zzz_nonexistent" → 所有分类被隐藏' },
]
},
{
id: 'faq', icon: '📚', name: 'Issue 知识库 (gitlink-faq)',
prompts: [
{ label: '模式 AIssue 归纳', prompt: '请使用 gitlink-faq Skill 的模式 A为当前仓库生成 Issue 知识库。\n步骤\n1. 同时采集 open + closed 所有 Issue不要只拉 closed\n2. 合并去重后筛选(排除纯测试 Issue其余保留\n3. 逐批读取 Issue 详情(每批 20-30 个)\n4. 按类型分类Bug/功能请求/使用问题/其他)\n5. 按类型分别聚类Bug 按模块、功能请求按领域、使用问题按场景)\n6. 生成知识库 Markdown 文档并预览\n7. 等我确认后发布到 Wiki首次wiki +create后续wiki +update' },
{ label: '模式 B重复检测', prompt: '请检查当前仓库有没有和 “登录功能” 相关的已有 Issue看看是否有重复的。\n对找到的候选 Issue自动读取详情和 journals给出分析结论。' },
{ label: '模式 CWiki 增量更新', prompt: '请把 Issue #11 补充到已有的 Issue 知识库 Wiki 页面中。' },
{ label: '快速冒烟测试9个Skills', prompt: '我需要对这 9 个 Skill 进行快速冒烟测试。请逐个执行(只读操作,不修改数据):\n\n1. gitlink-code-insight: 生成功能全景页面并打开浏览器\n2. gitlink-compliance: 展示 5 个扫描模块选项(列出即可,不执行扫描)\n3. gitlink-code-review: 查看 zzx-coder/test-repo 的 PR #13 详情和文件列表\n4. gitlink-issue-triage: 列出 zzx-coder/test-repo 的 open Issue分析 #11\n5. gitlink-changelog: 列出 zzx-coder/test-repo 已有的 Release\n6. gitlink-health: 收集 zzx-coder/test-repo 的 Issue/PR 数据但暂不生成报告\n7. gitlink-faq: 展示三种运行模式(列出即可,不执行分析)\n8. gitlink-onboard: 扫描 zzx-coder/test-repo 的 open Issue识别 good-first-issue 候选\n9. gitlink-stale: 扫描 zzx-coder/test-repo 中 1+ 天未活动的 Issue只出报告不执行\n\n每一步都告诉我 Skill 是否被正确触发、命令是否成功执行。' },
]
},
];
var MISSION3 = [
{
id: "m3-env",
icon: "🔧",
name: "测试环境准备",
commands: [
{
label: "设置 UTF-8 控制台编码",
engine: "powershell",
cmd: "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\nWrite-Host \"当前控制台编码: $([Console]::OutputEncoding.WebName)\"\nWrite-Host \"(应为 utf-8否则含中文 JSON 解析会失败)\""
},
{
label: "确认 gitlink-cli 版本",
engine: "powershell",
cmd: ".\\gitlink-cli.exe version"
},
{
label: "确认认证状态",
engine: "powershell",
cmd: ".\\gitlink-cli.exe auth status"
},
{
label: "确认 JSON 输出正常",
engine: "powershell",
cmd: ".\\gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --state open --limit 3 --format json | ConvertFrom-Json"
},
{
label: "加载公共库 common.psm1",
engine: "powershell",
cmd: "Import-Module \".\\workflows\\lib\\common.psm1\" -Force -WarningAction SilentlyContinue\nCheck-Auth"
},
{
label: "确认测试仓库可访问",
engine: "powershell",
cmd: ".\\gitlink-cli.exe repo +info --owner zzx-coder --repo test-repo --format json | ConvertFrom-Json"
}
]
},
{
id: "m3-community",
icon: "🤝",
name: "社区运营自动化 (01-community-ops)",
commands: [
{
label: "基础只读模式验证 (DryRun)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\01-community-ops.ps1 -Owner zzx-coder -Repo test-repo -DryRun"
},
{
label: "查看命令行参数 (Help)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\01-community-ops.ps1 -Help"
},
{
label: "Issue 数据拉取验证",
engine: "powershell",
cmd: "# 拉取 open / closed Issue 与已合并 PR来自测试 2.1.3\n$openResult = .\\gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --state open --limit 100 --format json | ConvertFrom-Json\n$closedResult = .\\gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --state closed --limit 100 --format json | ConvertFrom-Json\n$prResult = .\\gitlink-cli.exe pr +list --owner zzx-coder --repo test-repo --state merged --limit 100 --format json | ConvertFrom-Json\n$prData = if ($prResult.data.issues) { @($prResult.data.issues) } else { @($prResult.data.pulls) }\nWrite-Host \"Open issues: $(@($openResult.data.issues).Count)\"\nWrite-Host \"Closed issues: $(@($closedResult.data.issues).Count)\"\nWrite-Host \"Merged PRs: $($prData.Count)\""
}
]
},
{
id: "m3-gate",
icon: "🛡",
name: "代码质量看门人 (02-code-quality-gatekeeper)",
commands: [
{
label: "审查所有 open PR (DryRun)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\02-code-quality-gatekeeper.ps1 -Owner zzx-coder -Repo test-repo -DryRun"
},
{
label: "审查指定 PR #13 (DryRun)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\02-code-quality-gatekeeper.ps1 -Owner zzx-coder -Repo test-repo -PrId 13 -DryRun"
},
{
label: "自定义质量阈值 70 (DryRun)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\02-code-quality-gatekeeper.ps1 -Owner zzx-coder -Repo test-repo -Threshold 70 -DryRun"
},
{
label: "查看帮助信息 (Help)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\02-code-quality-gatekeeper.ps1 -Help"
}
]
},
{
id: "m3-init",
icon: "🚀",
name: "项目一键初始化 (03-project-init)",
commands: [
{
label: "预览模式 (DryRun推荐首选)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\03-project-init.ps1 -Owner zzx-coder -Name test-init-dryrun -Description \"自动化测试仓库\" -Lang go -DryRun"
},
{
label: "底层命令逐步骤验证 (只读安全)",
engine: "powershell",
cmd: "# 底层命令逐步骤验证(只读安全,来自测试 2.3.2\nWrite-Host \"=== Step 1: List repos ===\" -ForegroundColor White\n$repoList = .\\gitlink-cli.exe repo +list --user zzx-coder --limit 5 --format json | ConvertFrom-Json\n$repoCount = if ($repoList.data.projects) { @($repoList.data.projects).Count } else { @($repoList.data).Count }\nWrite-Host \"Repos: $repoCount\" -ForegroundColor Green\n\nWrite-Host \"`n=== Step 2: Check Wiki access ===\" -ForegroundColor White\n$wikiResult = .\\gitlink-cli.exe wiki +list --owner zzx-coder --repo test-repo --format json | ConvertFrom-Json\nWrite-Host \"Wiki accessible: $($wikiResult.ok)\" -ForegroundColor Green\n\nWrite-Host \"`n=== Step 3: Check branch help ===\" -ForegroundColor White\n.\\gitlink-cli.exe branch --help\n\nWrite-Host \"`n=== Step 4: Check releases ===\" -ForegroundColor White\n$relResult = .\\gitlink-cli.exe release +list --owner zzx-coder --repo test-repo --limit 5 --format json | ConvertFrom-Json\nWrite-Host \"Release API accessible: $($relResult.ok)\" -ForegroundColor Green\n\nWrite-Host \"`n=== Step 5: Check milestone ===\" -ForegroundColor White\n.\\gitlink-cli.exe milestone --help"
},
{
label: "实际创建仓库 (可选·需谨慎)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\03-project-init.ps1 -Owner zzx-coder -Name \"test-init-real\" -Description \"测试仓库-可安全删除\" -Lang go"
}
]
},
{
id: "m3-multirepo",
icon: "🔗",
name: "多仓库协同 (04-multi-repo-collab)",
commands: [
{
label: "生成组织仪表盘",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\04-multi-repo-collab.ps1 -Org zzx-coder -Output dashboard.html"
},
{
label: "指定仓库列表过滤",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\04-multi-repo-collab.ps1 -Org zzx-coder -Repos \"test-repo\" -Output dashboard-filtered.html"
},
{
label: "查看帮助和参数",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\04-multi-repo-collab.ps1 -Help"
}
]
},
{
id: "m3-contrib",
icon: "🏆",
name: "贡献者成长体系 (05-contributor-growth)",
commands: [
{
label: "Dry-Run 预览模式",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\05-contributor-growth.ps1 -Owner zzx-coder -Repo test-repo -Sample 5 -DryRun"
},
{
label: "底层数据采集验证",
engine: "powershell",
cmd: "# 底层数据采集验证(来自测试 2.5.2\n$openIssues = .\\gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --state open --limit 100 --format json | ConvertFrom-Json\n$closedIssues = .\\gitlink-cli.exe issue +list --owner zzx-coder --repo test-repo --state closed --limit 100 --format json | ConvertFrom-Json\n$prsMerged = .\\gitlink-cli.exe pr +list --owner zzx-coder --repo test-repo --state merged --limit 100 --format json | ConvertFrom-Json\n$members = .\\gitlink-cli.exe repo +members --owner zzx-coder --repo test-repo --limit 100 --format json | ConvertFrom-Json\n$prData = if ($prsMerged.data.issues) { @($prsMerged.data.issues) } else { @($prsMerged.data.pulls) }\n$memberData = if ($members.data.members) { @($members.data.members) } else { @($members.data) }\nWrite-Host \"Issues - Open: $(@($openIssues.data.issues).Count), Closed: $(@($closedIssues.data.issues).Count)\" -ForegroundColor Green\nWrite-Host \"Merged PRs: $($prData.Count)\" -ForegroundColor Green\nWrite-Host \"Members: $($memberData.Count)\" -ForegroundColor Green\n$allIssues = @($openIssues.data.issues) + @($closedIssues.data.issues)\n$authors = @{}\nforeach ($issue in $allIssues) {\n $author = if ($issue.author.login) { $issue.author.login } elseif ($issue.author.username) { $issue.author.username } else { $null }\n if ($author) { if (-not $authors.ContainsKey($author)) { $authors[$author] = 0 }; $authors[$author]++ }\n}\nWrite-Host \"Unique contributors: $($authors.Count)\" -ForegroundColor Green\nforeach ($kv in ($authors.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 5)) {\n Write-Host \" @$($kv.Key): $($kv.Value) issues\"\n}"
},
{
label: "颁发徽章 (可选·需谨慎)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\05-contributor-growth.ps1 -Owner zzx-coder -Repo test-repo -Sample 5 -Award"
}
]
},
{
id: "m3-skill",
icon: "🤖",
name: "技能工作流 (Agent · gitlink-workflow)",
prompts: [
{
label: "工作流总入口(触发 7 选项菜单)",
engine: "claude",
prompt: "请帮我执行自动化工作流\n测试仓库zzx-coder/test-repo"
},
{
label: "快捷触发:社区运营",
engine: "claude",
prompt: "帮我跑一下社区运营\n测试仓库zzx-coder/test-repo"
},
{
label: "项目健康度报告 (gitlink-health)",
engine: "claude",
prompt: "请使用 gitlink-health Skill为 zzx-coder/test-repo 仓库生成项目健康度报告。"
},
{
label: "健康度路径规则验证",
engine: "claude",
prompt: "请把健康度报告保存到桌面上。\n测试仓库zzx-coder/test-repo"
},
{
label: "新人引导 AI 分析模式 (onboard)",
engine: "claude",
prompt: "请使用 gitlink-onboard Skill 的 AI 分析模式,扫描 zzx-coder/test-repo 的所有 open Issue\n识别适合新手贡献的 Good First Issue。\n展示分析结果表格后让我选择要对哪些 Issue 添加引导评论。"
},
{
label: "新人引导直接模式 (#11 dry-run)",
engine: "claude",
prompt: "请为 Issue #11 添加新人引导评论,先用 dry-run 预览。\n测试仓库zzx-coder/test-repo"
},
{
label: "FAQ 模式 A生成知识库",
engine: "claude",
prompt: "请使用 gitlink-faq Skill 的模式 A为 zzx-coder/test-repo 生成 Issue 知识库。\n同时采集 open + closed 所有 Issue合并去重逐批读取详情按类型分类和聚类\n生成知识库 Markdown 预览后等我确认再发布到 Wiki。"
},
{
label: "FAQ 模式 B重复检测",
engine: "claude",
prompt: "请检查 zzx-coder/test-repo 有没有和 \"登录功能\" 相关的已有 Issue看看是否有重复的。\n对找到的候选 Issue自动读取详情和 journals给出分析结论。"
},
{
label: "FAQ 模式 CWiki 增量更新",
engine: "claude",
prompt: "请把 Issue #11 补充到已有的 Issue 知识库 Wiki 页面中。\n测试仓库zzx-coder/test-repo"
}
]
},
{
id: "m3-smoke",
icon: "🧪",
name: "端到端冒烟测试 (15 步)",
commands: [
{
label: "运行冒烟测试(连通性验证)",
engine: "powershell",
cmd: "# 端到端冒烟测试(来自测试 5.115 步连通性验证)\n$ErrorActionPreference = \"Continue\"\n$owner = \"zzx-coder\"; $repo = \"test-repo\"\n$pass = 0; $fail = 0\nfunction Test-Step {\n param([string]$Name, [ScriptBlock]$Script)\n Write-Host \"[TEST] $Name ... \" -NoNewline -ForegroundColor Cyan\n try { $result = & $Script; if ($result) { Write-Host \"PASS\" -ForegroundColor Green; $global:pass++ } else { Write-Host \"FAIL\" -ForegroundColor Red; $global:fail++ } }\n catch { Write-Host \"FAIL ($($_.Exception.Message))\" -ForegroundColor Red; $global:fail++ }\n}\nWrite-Host \"`n====== GitLink CLI 子任务三冒烟测试 ======\" -ForegroundColor White\nWrite-Host \"Target: $owner/$repo`n\"\nTest-Step \"Auth status\" { $r = .\\gitlink-cli.exe auth status 2>&1 | Out-String; $r -match \"logged in\" -or $r -match \"token\" }\nTest-Step \"01-Community: issue +list open\" { $r = .\\gitlink-cli.exe issue +list --owner $owner --repo $repo --state open --limit 5 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"01-Community: pr +list merged\" { $r = .\\gitlink-cli.exe pr +list --owner $owner --repo $repo --state merged --limit 5 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"02-Gatekeeper: issue +view #11\" { $r = .\\gitlink-cli.exe issue +view --owner $owner --repo $repo --number 11 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"03-Init: repo +list\" { $r = .\\gitlink-cli.exe repo +list --user $owner --limit 5 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"03-Init: wiki +list\" { $r = .\\gitlink-cli.exe wiki +list --owner $owner --repo $repo --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"04-MultiRepo: org repos\" { $r = .\\gitlink-cli.exe repo +list --user $owner --limit 10 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"04-MultiRepo: release +list\" { $r = .\\gitlink-cli.exe release +list --owner $owner --repo $repo --limit 5 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"05-Contrib: repo +members\" { $r = .\\gitlink-cli.exe repo +members --owner $owner --repo $repo --limit 50 --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"CI: ci +builds\" { $r = .\\gitlink-cli.exe ci +builds --owner $owner --repo $repo --format json | ConvertFrom-Json; $r.ok }\nTest-Step \"Branch: branch help\" { $r = .\\gitlink-cli.exe branch --help 2>&1 | Out-String; $r -match \"Usage\" -or $r -match \"branch\" }\nTest-Step \"Milestone: milestone help\" { $r = .\\gitlink-cli.exe milestone --help 2>&1 | Out-String; $r -match \"Usage\" -or $r -match \"milestone\" }\nTest-Step \"Webhook: webhook help\" { $r = .\\gitlink-cli.exe webhook --help 2>&1 | Out-String; $r -match \"Usage\" -or $r -match \"webhook\" }\nTest-Step \"Research: search +repos\" { $r = .\\gitlink-cli.exe search +repos --q \"agent\" --limit 5 --format json | ConvertFrom-Json; $r.ok }\nWrite-Host \"`n====== Results ======\" -ForegroundColor White\nWrite-Host \" PASS: $pass\" -ForegroundColor Green\nWrite-Host \" FAIL: $fail\" -ForegroundColor Red\nWrite-Host \" TOTAL: $($pass + $fail)\" -ForegroundColor Cyan"
}
]
}
];
var MISSION4 = [
{
id: "m4-insights",
icon: "🔭",
name: "项目洞察 (06-research-insights)",
commands: [
{
label: "生成项目洞悉报告(热度/技术栈/活动)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\06-research-insights.ps1 -Owner zzx-coder -Repo test-repo"
}
]
},
{
id: "m4-compliance",
icon: "✅",
name: "合规与复现性检查 (08-research-compliance)",
commands: [
{
label: "生成 8 维复现性评分卡",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\08-research-compliance.ps1 -Owner zzx-coder -Repo test-repo"
}
]
},
{
id: "m4-progress",
icon: "📈",
name: "进度跟踪与预警 (10-research-progress)",
commands: [
{
label: "生成周度进度报告(含异常预警)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\10-research-progress.ps1 -Owner zzx-coder -Repo test-repo"
}
]
},
{
id: "m4-citation",
icon: "📄",
name: "论文引用生成 (11-research-citation)",
commands: [
{
label: "生成全部 5 种引用格式 (all)",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\11-research-citation.ps1 -Owner zzx-coder -Repo test-repo -Format all"
},
{
label: "BibTeX 格式",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\11-research-citation.ps1 -Owner zzx-coder -Repo test-repo -Format bibtex"
},
{
label: "APA 格式",
engine: "powershell",
cmd: "powershell -ExecutionPolicy Bypass -File .\\workflows\\academic\\11-research-citation.ps1 -Owner zzx-coder -Repo test-repo -Format apa"
}
]
},
{
id: "m4-research",
icon: "🔬",
name: "科研辅助 Agent (gitlink-research)",
prompts: [
{
label: "科研功能总入口6 选项菜单)",
engine: "claude",
prompt: "请使用科研辅助功能\n请展示 6 选项菜单:项目洞察 / 热点追踪 / 合规复现 / 协作匹配 / 进度预警 / 论文引用,等我选择)\n测试仓库zzx-coder/test-repo"
},
{
label: "合规与复现性检查",
engine: "claude",
prompt: "请检查 zzx-coder/test-repo 项目的合规与复现性"
},
{
label: "论文引用生成BibTeX",
engine: "claude",
prompt: "请生成 zzx-coder/test-repo 项目的论文引用BibTeX 格式)"
}
]
}
];
// ═══════════════════════════════════════════════
// DOM refs
// ═══════════════════════════════════════════════
var terminal = document.getElementById('terminal');
var cmdInput = document.getElementById('cmd-input');
var inputPrompt = document.getElementById('input-prompt');
var btnRun = document.getElementById('btn-run');
var btnCancel = document.getElementById('btn-cancel');
var cliModsContainer = document.getElementById('cli-modules-container');
var skillsModsContainer = document.getElementById('skills-modules-container');
var mission3ModsContainer = document.getElementById('mission3-modules-container');
var mission4ModsContainer = document.getElementById('mission4-modules-container');
var statusDot = document.getElementById('status-dot');
var statusText = document.getElementById('status-text');
var toast = document.getElementById('toast');
var isRunning = false;
var currentEngine = 'powershell'; // 'powershell' | 'claude'
var execModes = { cli:'direct', skills:'direct', mission3:'direct', mission4:'direct' }; // 每个 section 的执行模式
var claudeSessionId = null; // Claude Code session ID for multi-turn
var currentAgent = 'claude'; // 'claude' | 'codex' | 'zcode'
var AGENT_META = {
claude: { icon: '🤖', name: 'Claude Code', btnColor: 'var(--purple)' },
codex: { icon: '📟', name: 'Codex', btnColor: '#0ea5e9' },
zcode: { icon: '⚡', name: 'ZCode', btnColor: '#f59e0b' }
};
// ═══════════════════════════════════════════════
// Section Toggle (top-level collapse)
// ═══════════════════════════════════════════════
function toggleSection(sectionId) {
var el = document.getElementById(sectionId);
if (el) { el.classList.toggle('open'); }
}
// ═══════════════════════════════════════════════
// Build Sidebar (CLI + Skills sections)
// ═══════════════════════════════════════════════
function buildModuleGroup(container, modules, section) {
if (!container) return;
modules.forEach(function(mod, idx) {
var div = document.createElement('div');
div.className = 'module' + (idx === 0 ? ' open' : '');
div.id = section + '-mod-' + mod.id;
var items = mod.commands || mod.prompts;
var optionsHtml = items.map(function(c, i) {
return '<option value="' + i + '">' + escHtml(c.label) + '</option>';
}).join('');
div.innerHTML =
'<div class="module-header" onclick="toggleModule(\'' + section + '-mod-' + mod.id + '\')">' +
'<span class="icon">' + mod.icon + '</span>' +
'<span class="name">' + escHtml(mod.name) + '</span>' +
'<span class="arrow">▶</span>' +
'</div>' +
'<div class="module-body">' +
'<div class="cmd-select-wrap">' +
'<select onchange="onPresetSelect(this, \'' + section + '-mod-' + mod.id + '\')">' +
'<option value="">— 选择操作 —</option>' +
optionsHtml +
'</select>' +
'</div>' +
'<div class="cmd-preview" id="preview-' + section + '-mod-' + mod.id + '"></div>' +
'</div>';
container.appendChild(div);
});
}
function buildSidebar() {
buildModuleGroup(cliModsContainer, MODULES, 'cli');
buildModuleGroup(skillsModsContainer, SKILLS, 'skills');
buildModuleGroup(mission3ModsContainer, MISSION3, 'mission3');
buildModuleGroup(mission4ModsContainer, MISSION4, 'mission4');
}
// ═══════════════════════════════════════════════
// Execution Mode Toggles
// ═══════════════════════════════════════════════
function setSectionExecMode(section, mode) {
execModes[section] = mode;
document.getElementById(section + '-mode-direct').className = (mode === 'direct') ? 'active' : '';
document.getElementById(section + '-mode-input').className = (mode === 'input') ? 'active' : '';
}
// ═══════════════════════════════════════════════
// Engine Toggle (for manual input)
// ═══════════════════════════════════════════════
function setEngine(engine) {
currentEngine = engine;
if (engine === 'claude') {
var meta = AGENT_META[currentAgent];
inputPrompt.innerHTML = meta.icon + '&gt;';
inputPrompt.title = '点击切换为 PowerShell';
cmdInput.placeholder = '输入 Claude Code 提示词或从左侧 Skills 选择...';
btnRun.textContent = '▶ 执行 (' + meta.name + ')';
btnRun.style.background = meta.btnColor;
} else {
inputPrompt.innerHTML = 'PS&gt;';
inputPrompt.title = '点击切换为 Claude Code';
cmdInput.placeholder = '输入命令或从左侧选择预设...';
btnRun.textContent = '▶ 执行';
btnRun.style.background = '';
}
}
function toggleEngine() {
setEngine(currentEngine === 'powershell' ? 'claude' : 'powershell');
}
// ═══════════════════════════════════════════════
// Prompt helper: update input prompt from AGENT_META
// ═══════════════════════════════════════════════
function updateInputPrompt() {
if (currentEngine === 'claude') {
var meta = AGENT_META[currentAgent];
inputPrompt.innerHTML = meta.icon + '&gt;';
} else {
inputPrompt.innerHTML = 'PS&gt;';
}
}
// ═══════════════════════════════════════════════
// Agent Selector (Skills section)
// ═══════════════════════════════════════════════
function setAgent(agent) {
if (agent === currentAgent) return;
currentAgent = agent;
// Update button active states (支持多 section 共享同一 currentAgent)
var btns = document.querySelectorAll('.agent-btn');
for (var i = 0; i < btns.length; i++) {
btns[i].classList.toggle('active', btns[i].getAttribute('data-agent') === agent);
}
// Clear terminal output (but keep session)
terminal.innerHTML = '';
// Show switch message
var meta = AGENT_META[agent];
appendEntry('info', 'agent已切换为 ' + meta.name);
// Switch engine to Claude mode if not already
if (currentEngine !== 'claude') {
setEngine('claude');
} else {
updateInputPrompt();
}
scrollToBottom();
}
// ═══════════════════════════════════════════════
// Module Toggle
// ═══════════════════════════════════════════════
function toggleModule(id) {
var el = document.getElementById(id);
if (el) { el.classList.toggle('open'); }
}
// ═══════════════════════════════════════════════
// Preset Selection → fill input or execute
// ═══════════════════════════════════════════════
function onPresetSelect(selectEl, moduleId) {
var idx = parseInt(selectEl.value, 10);
if (isNaN(idx)) {
document.getElementById('preview-' + moduleId).textContent = '';
return;
}
// Determine section: 'cli' | 'skills' | 'mission3' | 'mission4'
var section = moduleId.substring(0, moduleId.indexOf('-mod-'));
var dataMap = { cli: MODULES, skills: SKILLS, mission3: MISSION3, mission4: MISSION4 };
var list = dataMap[section] || MODULES;
// Find module
var mod = null;
for (var i = 0; i < list.length; i++) {
if (section + '-mod-' + list[i].id === moduleId) { mod = list[i]; break; }
}
if (!mod) return;
var item = (mod.commands || mod.prompts)[idx];
if (!item) return;
var content = item.cmd || item.prompt;
// 每个 item 可自带 engine默认 skills 走 claude其余走 powershell
var engine = item.engine || (section === 'skills' ? 'claude' : 'powershell');
// Show preview
var previewEl = document.getElementById('preview-' + moduleId);
if (previewEl) { previewEl.textContent = content; }
var mode = execModes[section] || 'direct';
if (mode === 'direct') {
// Execute immediately
selectEl.value = '';
if (previewEl) { previewEl.textContent = ''; }
if (engine === 'claude') {
setEngine('claude');
executeDirectClaude(content);
} else {
setEngine('powershell');
executeDirect(content);
}
} else {
// Fill input for editing
setEngine(engine);
cmdInput.value = content;
autoResizeTextarea();
cmdInput.focus();
}
}
// ── Execute directly — PowerShell ──
function executeDirect(command) {
if (isRunning) { showToast('请等待当前命令执行完成', 'err'); return; }
isRunning = true;
btnRun.classList.add('running');
btnRun.textContent = '⏳ 执行中...';
btnCancel.classList.add('visible');
cmdInput.value = '';
appendEntry('cmd-line',
'<span class="prompt">⚡ PS&gt;</span> <span class="cmd-text">' + escHtml(command) + '</span>');
sendExecRequest(command);
}
// ── Execute directly — Claude Code ──
function executeDirectClaude(prompt) {
if (isRunning) { showToast('请等待当前命令执行完成', 'err'); return; }
isRunning = true;
btnRun.classList.add('running');
btnRun.textContent = '⏳ 执行中...';
btnCancel.classList.add('visible');
cmdInput.value = '';
var meta = AGENT_META[currentAgent];
appendEntry('cmd-line',
'<span class="prompt">⚡ ' + meta.icon + '</span> <span class="cmd-text">' + escHtml(prompt) + '</span>');
sendClaudeRequest(prompt);
}
// ═══════════════════════════════════════════════
// Execute Command (manual input)
// ═══════════════════════════════════════════════
function executeCommand() {
var command = cmdInput.value.trim();
if (!command || isRunning) return;
var isClaude = currentEngine === 'claude';
isRunning = true;
btnRun.textContent = '⏳ 执行中...';
btnRun.classList.add('running');
btnCancel.classList.add('visible');
var meta = AGENT_META[currentAgent];
var promptHtml = isClaude ? meta.icon + '&gt;' : 'PS&gt;';
appendEntry('cmd-line',
'<span class="prompt">' + promptHtml + '</span> <span class="cmd-text">' + escHtml(command) + '</span>');
cmdInput.value = '';
cmdInput.style.height = 'auto';
cmdInput.focus();
if (isClaude) {
sendClaudeRequest(command);
} else {
sendExecRequest(command);
}
}
// ── 根据命令推断对应的 GitLink 仓库页面 URL ──
function getRepoUrl(command) {
var ownerMatch = command.match(/--owner\s+(\S+)/);
var repoMatch = command.match(/--repo\s+(\S+)/);
if (!ownerMatch || !repoMatch) return null;
var owner = ownerMatch[1];
var repo = repoMatch[1];
var base = 'https://gitlink.org.cn/' + owner + '/' + repo;
var cmdLower = command.toLowerCase();
// 按优先级匹配:更具体的资源类型优先
// Issue尝试提取具体编号 (-n <num>)
if (cmdLower.indexOf('issue') !== -1 || cmdLower.indexOf('batch-create') !== -1) {
var issueNum = command.match(/-n\s+(\d+)/);
if (issueNum) {
return { url: base + '/issues/' + issueNum[1], label: 'Issue #' + issueNum[1], resource: 'Issue' };
}
return { url: base + '/issues', label: 'Issues 列表', resource: 'Issue' };
}
if (cmdLower.indexOf('webhook') !== -1) {
return { url: base + '/settings/hooks', label: 'Webhooks 设置', resource: 'Webhook' };
}
if (cmdLower.indexOf('wiki') !== -1) {
return { url: base + '/wiki', label: 'Wiki 页面', resource: 'Wiki' };
}
if (cmdLower.indexOf('pr ') !== -1 || cmdLower.indexOf('pull') !== -1) {
return { url: base + '/pulls', label: 'Pull Requests', resource: 'PR' };
}
if (cmdLower.indexOf('repo') !== -1) {
return { url: base, label: '仓库首页', resource: '仓库' };
}
// 默认返回仓库首页
return { url: base, label: '仓库首页', resource: '仓库' };
}
// ── 从 Claude Code 提示词中提取仓库 URL支持自然语言 owner/repo 格式)──
function getRepoUrlFromPrompt(prompt) {
// 先尝试 CLI 风格解析(--owner/--repo
var result = getRepoUrl(prompt);
if (result) return result;
// 尝试自然语言中的 owner/repo 格式
var slashMatch = prompt.match(/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)/);
if (!slashMatch) return null;
var owner = slashMatch[1];
var repo = slashMatch[2];
// 过滤误匹配URL scheme、路径等
if (owner === 'http' || owner === 'https' || owner === 'www') return null;
var base = 'https://gitlink.org.cn/' + owner + '/' + repo;
var lower = prompt.toLowerCase();
// 提取具体资源编号
var prNumMatch = prompt.match(/PR\s*#(\d+)/i);
var issueNumMatch = prompt.match(/Issue\s*#(\d+)/i);
var hashNumMatch = prompt.match(/#(\d+)/); // 通用 #11
// ── PR 相关 ──
if (lower.indexOf('pr ') !== -1 || lower.indexOf('pr#') !== -1 ||
lower.indexOf('pull request') !== -1 || lower.indexOf('审查') !== -1 ||
lower.indexOf('code-review') !== -1 || lower.indexOf('code review') !== -1) {
var prNum = prNumMatch ? prNumMatch[1] : (hashNumMatch ? hashNumMatch[1] : null);
if (prNum) {
return { url: base + '/pulls/' + prNum, label: 'PR #' + prNum, resource: 'PR' };
}
return { url: base + '/pulls', label: 'Pull Requests', resource: 'PR' };
}
// ── Issue 相关 ──
if (lower.indexOf('issue') !== -1) {
var issNum = issueNumMatch ? issueNumMatch[1] : (hashNumMatch ? hashNumMatch[1] : null);
if (issNum) {
return { url: base + '/issues/' + issNum, label: 'Issue #' + issNum, resource: 'Issue' };
}
return { url: base + '/issues', label: 'Issues 列表', resource: 'Issue' };
}
// ── 其他资源类型 ──
if (lower.indexOf('webhook') !== -1) {
return { url: base + '/settings/hooks', label: 'Webhooks 设置', resource: 'Webhook' };
}
if (lower.indexOf('wiki') !== -1 || (lower.indexOf('faq') !== -1 && lower.indexOf('wiki') !== -1)) {
return { url: base + '/wiki', label: 'Wiki 页面', resource: 'Wiki' };
}
if (lower.indexOf('release') !== -1 || lower.indexOf('changelog') !== -1) {
return { url: base + '/releases', label: 'Releases', resource: 'Release' };
}
if (lower.indexOf('健康') !== -1 || lower.indexOf('health') !== -1 ||
lower.indexOf('insight') !== -1 || lower.indexOf('全景') !== -1 ||
lower.indexOf('compliance') !== -1 || lower.indexOf('合规') !== -1) {
return { url: base, label: '仓库首页', resource: '仓库' };
}
// 默认返回仓库首页
return { url: base, label: '仓库首页', resource: '仓库' };
}
// ── Shared request sender ──
function sendExecRequest(command) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/exec', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
try {
var data = JSON.parse(xhr.responseText);
if (data.stdout && data.stdout.trim()) {
appendEntry('output', data.stdout.replace(/\s+$/, ''));
}
if (data.stderr && data.stderr.trim()) {
appendEntry('stderr', data.stderr.replace(/\s+$/, ''));
}
if ((!data.stdout || !data.stdout.trim()) && (!data.stderr || !data.stderr.trim())) {
appendEntry('info', '(无输出)');
}
if (data.ok) {
appendEntry('success', '✓ 完成 (exit code: ' + data.exit_code + ')');
// 显示对应仓库页面的友好链接
var repoInfo = getRepoUrl(command);
if (repoInfo) {
appendEntry('link',
'🔗 详细变化见仓库:<a href="' + repoInfo.url + '" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:none;border-bottom:1px dashed var(--accent);">' + repoInfo.url + '</a> <span style="color:var(--text-dim);font-size:11px;">(' + repoInfo.resource + ' 页面)</span>');
}
} else {
appendEntry('error', '✗ 失败 (exit code: ' + data.exit_code + ')');
}
} catch (e) {
appendEntry('error', '❌ 解析响应失败: ' + e.message);
setServerStatus(false);
}
resetExecState();
scrollToBottom();
};
xhr.onerror = function() {
appendEntry('error', '❌ 请求失败:无法连接到服务器');
setServerStatus(false);
resetExecState();
};
xhr.send(JSON.stringify({ command: command, timeout: 120 }));
}
// ── Claude Code request sender ──
function sendClaudeRequest(prompt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/claude', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
try {
var data = JSON.parse(xhr.responseText);
if (data.stdout && data.stdout.trim()) {
appendEntry('output', data.stdout.replace(/\s+$/, ''));
}
if (data.stderr && data.stderr.trim()) {
appendEntry('stderr', data.stderr.replace(/\s+$/, ''));
}
if ((!data.stdout || !data.stdout.trim()) && (!data.stderr || !data.stderr.trim())) {
appendEntry('info', '(无输出)');
}
// ── 会话管理:保存 session_id 并提示用户 ──
if (data.session_id) {
claudeSessionId = data.session_id;
var shortId = data.session_id.substring(0, 8);
if (data.ok) {
appendEntry('info', '💬 会话持续中 · ID: <code>' + shortId + '</code> · <span style="color:var(--accent);cursor:pointer;text-decoration:underline;" onclick="newClaudeSession()">点击开始新会话</span> · 下一条 Claude 指令将自动延续本会话');
appendEntry('info', '📍 会话存储于项目 <code>.claude/projects/</code> 目录下。可在终端中执行 <code>claude --resume ' + shortId + '...</code> 继续对话(需在 gitlink-cli 目录下运行)');
}
}
if (data.ok) {
appendEntry('success', '🤖 Claude Code 完成 (exit code: ' + data.exit_code + ')');
// 显示对应仓库页面的友好链接
var repoInfo = getRepoUrlFromPrompt(prompt);
if (repoInfo) {
appendEntry('link',
'🔗 详细变化见仓库:<a href="' + repoInfo.url + '" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:none;border-bottom:1px dashed var(--accent);">' + repoInfo.url + '</a> <span style="color:var(--text-dim);font-size:11px;">(' + repoInfo.resource + ' 页面)</span>');
}
} else {
appendEntry('error', '🤖 Claude Code 异常 (exit code: ' + data.exit_code + ')');
}
} catch (e) {
appendEntry('error', '❌ 解析响应失败: ' + e.message);
setServerStatus(false);
}
resetExecState();
scrollToBottom();
};
xhr.onerror = function() {
appendEntry('error', '❌ 请求失败:无法连接到服务器');
setServerStatus(false);
resetExecState();
};
// ── 构建请求体:带上会话信息(超时 600s = 10min──
var reqBody = { prompt: prompt, timeout: 600 };
if (claudeSessionId) {
reqBody.session_id = claudeSessionId;
}
xhr.send(JSON.stringify(reqBody));
}
// ── Start a fresh Claude Code session ──
function newClaudeSession() {
claudeSessionId = null;
appendEntry('info', '🆕 已开始新会话 — 下一条 Claude 指令将创建全新对话');
showToast('Claude 会话已重置', 'ok');
}
// ── Reset after execution ──
function resetExecState() {
isRunning = false;
btnRun.classList.remove('running');
btnCancel.classList.remove('visible');
cmdInput.disabled = false;
cmdInput.focus();
// Restore button text based on current engine
if (currentEngine === 'claude') {
var meta = AGENT_META[currentAgent];
btnRun.textContent = '▶ 执行 (' + meta.name + ')';
btnRun.style.background = meta.btnColor;
} else {
btnRun.textContent = '▶ 执行';
btnRun.style.background = '';
}
}
// ── Cancel running command ──
function cancelCommand() {
if (!isRunning) return;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/cancel', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
appendEntry('info', '⏹ 已发送终止信号');
}
};
xhr.send('{}');
}
// ═══════════════════════════════════════════════
// Terminal Helpers
// ═══════════════════════════════════════════════
function appendEntry(className, text) {
var welcome = terminal.querySelector('.welcome');
if (welcome) { welcome.remove(); }
var div = document.createElement('div');
div.className = 'entry ' + className;
div.innerHTML = text;
terminal.appendChild(div);
}
function clearTerminal() {
terminal.innerHTML = '';
appendEntry('info', '终端已清空。从左侧选择预设命令或直接输入。');
showToast('终端已清空', 'ok');
}
function scrollToBottom() {
terminal.scrollTop = terminal.scrollHeight;
}
// ═══════════════════════════════════════════════
// Server Status
// ═══════════════════════════════════════════════
function setServerStatus(online) {
if (online) {
statusDot.className = 'indicator online';
statusText.textContent = '服务就绪';
} else {
statusDot.className = 'indicator offline';
statusText.textContent = '连接断开';
}
}
// ═══════════════════════════════════════════════
// Toast
// ═══════════════════════════════════════════════
var toastTimer = null;
function showToast(msg, type) {
toast.textContent = msg;
toast.className = type + ' show';
if (toastTimer) { clearTimeout(toastTimer); }
toastTimer = setTimeout(function() { toast.className = ''; }, 2000);
}
// ═══════════════════════════════════════════════
// HTML escape utility
// ═══════════════════════════════════════════════
function escHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
// ═══════════════════════════════════════════════
// Textarea auto-resize
// ═══════════════════════════════════════════════
function autoResizeTextarea() {
cmdInput.style.height = 'auto';
cmdInput.style.height = Math.min(cmdInput.scrollHeight, 200) + 'px';
}
cmdInput.addEventListener('input', autoResizeTextarea);
// ═══════════════════════════════════════════════
// Keyboard Shortcuts
// ═══════════════════════════════════════════════
document.addEventListener('keydown', function(e) {
// Ctrl+L → clear
if (e.ctrlKey && e.key === 'l') {
e.preventDefault();
clearTerminal();
}
// Enter in textarea → execute; Shift+Enter → newline
if (e.key === 'Enter' && document.activeElement === cmdInput) {
if (e.shiftKey) {
// Let Shift+Enter insert a newline (default behavior)
return;
}
e.preventDefault();
executeCommand();
}
});
// ═══════════════════════════════════════════════
// Sidebar Resizer
// ═══════════════════════════════════════════════
(function() {
var resizer = document.getElementById('resizer');
var sidebar = document.getElementById('sidebar');
var startX, startWidth;
resizer.addEventListener('mousedown', function(e) {
startX = e.clientX;
startWidth = sidebar.offsetWidth;
resizer.classList.add('active');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
function onMove(ev) {
var dx = ev.clientX - startX;
var newWidth = Math.max(220, Math.min(600, startWidth + dx));
sidebar.style.width = newWidth + 'px';
}
function onUp() {
resizer.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
})();
// ═══════════════════════════════════════════════
// Init
// ═══════════════════════════════════════════════
buildSidebar();
cmdInput.focus();
</script>
</body>
</html>