diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 0000000..c8017cc
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,1262 @@
+
+
+
+
+
+GitLink CLI · 子任务一 交互式展示
+
+
+
+
+
+
+
+ GitLink CLI ─ 作品展示
+
+
+
+ 服务就绪
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🖥 GitLink CLI 作品展示平台
+
+ 在下方输入框中输入 gitlink-cli 命令或 Claude Code 提示词,或从
+ 左侧面板 的下拉框中选择预设。
+
+
+ CLI 命令 + Skills 展示 · Enter 执行 · Ctrl+L 清屏
+
+
+
+
+ PS>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/server.py b/demo/server.py
new file mode 100644
index 0000000..c4a506e
--- /dev/null
+++ b/demo/server.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+"""
+GitLink CLI Demo Server — 子任务一 交互式展示
+启动后访问 http://127.0.0.1:8765
+"""
+
+import http.server
+import json
+import subprocess
+import os
+import sys
+import tempfile
+import threading
+import shutil
+import uuid
+from pathlib import Path
+
+PORT = 8765
+WORKING_DIR = r"D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli"
+HTML_DIR = Path(__file__).parent
+
+# ── 定位 Claude Code CLI ─────────────────────────
+def _find_claude() -> str | None:
+ """查找 claude 可执行文件,优先返回 .exe 以避开 .cmd 的编码问题"""
+ # 1) 直接找 claude.exe(npm 全局安装路径)
+ candidates = [
+ # npm global on Windows
+ Path(os.environ.get("APPDATA", "")) / r"npm\node_modules\@anthropic-ai\claude-code\bin\claude.exe",
+ # 直接 which(可能返回 .cmd)
+ shutil.which("claude"),
+ shutil.which("claude.exe"),
+ ]
+ for p in candidates:
+ if p and Path(str(p)).is_file():
+ return str(p)
+
+ # 2) 尝试 shutil.which 返回的 .cmd 对应的 .exe
+ cmd = shutil.which("claude.cmd")
+ if cmd:
+ exe = Path(cmd).with_suffix(".exe")
+ if exe.is_file():
+ return str(exe)
+
+ return None
+
+CLAUDE_EXE = _find_claude()
+
+# 全局:当前正在运行的进程(用于取消)
+_proc_lock = threading.Lock()
+_current_proc = None
+
+
+class DemoHandler(http.server.BaseHTTPRequestHandler):
+ """HTTP 请求处理器:静态文件 + API 端点"""
+
+ # ── 日志 ────────────────────────────────────────────
+ def log_message(self, fmt, *args):
+ print(f"[{self.log_date_time_string()}] {args[0]}")
+
+ # ── GET ─────────────────────────────────────────────
+ def do_GET(self):
+ path = self.path.split("?")[0]
+ if path in ("/", "/index.html"):
+ self._serve_file("index.html", "text/html; charset=utf-8")
+ else:
+ self.send_error(404)
+
+ # ── POST ────────────────────────────────────────────
+ def do_POST(self):
+ if self.path == "/api/exec":
+ self._handle_exec()
+ elif self.path == "/api/claude":
+ self._handle_claude()
+ elif self.path == "/api/cancel":
+ self._handle_cancel()
+ else:
+ self.send_error(404)
+
+ # ── OPTIONS (CORS preflight) ────────────────────────
+ def do_OPTIONS(self):
+ self._send_cors_headers()
+ self.send_response(204)
+ self.end_headers()
+
+ # ── 取消当前命令 ────────────────────────────────────
+ def _handle_cancel(self):
+ global _current_proc
+ with _proc_lock:
+ proc = _current_proc
+ if proc is None or proc.poll() is not None:
+ self._send_json({"ok": True, "message": "没有正在运行的命令"})
+ return
+ try:
+ proc.kill()
+ self._send_json({"ok": True, "message": "已发送终止信号"})
+ except Exception as e:
+ self._send_json({"ok": False, "error": str(e)})
+
+ # ── Claude Code 执行 ────────────────────────────────
+ def _handle_claude(self):
+ global _current_proc
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ body = self.rfile.read(content_length)
+ try:
+ data = json.loads(body)
+ except json.JSONDecodeError:
+ self._send_json({"ok": False, "error": "Invalid JSON body"})
+ return
+
+ prompt = data.get("prompt", "").strip()
+ if not prompt:
+ self._send_json({"ok": False, "error": "Empty prompt", "session_id": data.get("session_id", "")})
+ return
+
+ timeout = min(data.get("timeout", 300), 600) # max 10 min for Claude
+
+ if not CLAUDE_EXE:
+ self._send_json({
+ "ok": False, "stdout": "",
+ "stderr": "❌ 找不到 Claude Code CLI(claude.exe)。请确认已通过 npm 安装:npm install -g @anthropic-ai/claude-code",
+ "exit_code": -1,
+ "session_id": "",
+ })
+ return
+
+ # ── 会话管理:支持多轮对话 ──
+ session_id = data.get("session_id", "").strip()
+ new_session = data.get("new_session", False)
+
+ if new_session or not session_id:
+ # 新会话:生成 UUID
+ session_id = str(uuid.uuid4())
+ args = [CLAUDE_EXE, "-p", prompt, "--session-id", session_id, "--permission-mode", "bypassPermissions"]
+ else:
+ # 继续已有会话
+ args = [CLAUDE_EXE, "-p", prompt, "--resume", session_id, "--permission-mode", "bypassPermissions"]
+
+ proc = None
+ try:
+ proc = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ encoding="utf-8",
+ errors="replace",
+ cwd=WORKING_DIR,
+ env={**os.environ, "NO_COLOR": "1"},
+ )
+
+ # 注册为当前进程(允许取消)
+ with _proc_lock:
+ _current_proc = proc
+
+ try:
+ stdout, stderr = proc.communicate(timeout=timeout)
+ exit_code = proc.returncode
+ killed = False
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ stdout, stderr = proc.communicate()
+ exit_code = -1
+ killed = True
+
+ if killed:
+ self._send_json({
+ "ok": False,
+ "stdout": stdout or "",
+ "stderr": f"❌ Claude Code 执行超时(超过 {timeout} 秒)已被终止",
+ "exit_code": -1,
+ "session_id": session_id,
+ })
+ else:
+ self._send_json({
+ "ok": exit_code == 0,
+ "stdout": stdout,
+ "stderr": stderr,
+ "exit_code": exit_code,
+ "session_id": session_id,
+ })
+
+ except FileNotFoundError:
+ self._send_json({
+ "ok": False, "stdout": "",
+ "stderr": "❌ 找不到 Claude Code CLI(claude.exe)。请确认已通过 npm 安装:npm install -g @anthropic-ai/claude-code",
+ "exit_code": -1,
+ "session_id": session_id,
+ })
+ except Exception as e:
+ self._send_json({
+ "ok": False, "stdout": "",
+ "stderr": f"❌ {e}", "exit_code": -1,
+ "session_id": session_id,
+ })
+ finally:
+ with _proc_lock:
+ if _current_proc is proc:
+ _current_proc = None
+
+ # ── 命令执行 ────────────────────────────────────────
+ def _handle_exec(self):
+ global _current_proc
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ body = self.rfile.read(content_length)
+ try:
+ data = json.loads(body)
+ except json.JSONDecodeError:
+ self._send_json({"ok": False, "error": "Invalid JSON body"})
+ return
+
+ command = data.get("command", "").strip()
+ if not command:
+ self._send_json({"ok": False, "error": "Empty command"})
+ return
+
+ timeout = min(data.get("timeout", 120), 300) # max 5 min
+
+ tmp = None
+ proc = None
+ try:
+ tmp = tempfile.NamedTemporaryFile(
+ mode="w", suffix=".ps1", delete=False, encoding="utf-8-sig"
+ )
+ tmp.write(command)
+ tmp.close()
+
+ proc = subprocess.Popen(
+ [
+ "powershell",
+ "-ExecutionPolicy", "Bypass",
+ "-NoProfile",
+ "-File", tmp.name,
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ encoding="utf-8",
+ errors="replace",
+ cwd=WORKING_DIR,
+ env={**os.environ, "NO_COLOR": "1"},
+ )
+
+ # 注册为当前进程(允许取消)
+ with _proc_lock:
+ _current_proc = proc
+
+ try:
+ stdout, stderr = proc.communicate(timeout=timeout)
+ exit_code = proc.returncode
+ killed = False
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ stdout, stderr = proc.communicate()
+ exit_code = -1
+ killed = True
+
+ if killed:
+ self._send_json({
+ "ok": False,
+ "stdout": stdout or "",
+ "stderr": f"❌ 命令执行超时(超过 {timeout} 秒)已被终止",
+ "exit_code": -1,
+ })
+ else:
+ self._send_json({
+ "ok": exit_code == 0,
+ "stdout": stdout,
+ "stderr": stderr,
+ "exit_code": exit_code,
+ })
+
+ except FileNotFoundError:
+ self._send_json({
+ "ok": False, "stdout": "",
+ "stderr": "❌ 找不到 PowerShell,请确认系统已安装 PowerShell",
+ "exit_code": -1,
+ })
+ except Exception as e:
+ self._send_json({
+ "ok": False, "stdout": "",
+ "stderr": f"❌ {e}", "exit_code": -1,
+ })
+ finally:
+ with _proc_lock:
+ if _current_proc is proc:
+ _current_proc = None
+ if tmp:
+ try:
+ os.unlink(tmp.name)
+ except OSError:
+ pass
+
+ # ── 辅助方法 ────────────────────────────────────────
+ def _serve_file(self, filename: str, content_type: str):
+ filepath = HTML_DIR / filename
+ try:
+ with open(filepath, "rb") as f:
+ content = f.read()
+ self.send_response(200)
+ self.send_header("Content-Type", content_type)
+ self.send_header("Content-Length", str(len(content)))
+ self.send_header("Cache-Control", "no-cache")
+ self._send_cors_headers()
+ self.end_headers()
+ self.wfile.write(content)
+ except FileNotFoundError:
+ self.send_error(404)
+
+ def _send_json(self, data: dict):
+ body = json.dumps(data, ensure_ascii=False).encode("utf-8")
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json; charset=utf-8")
+ self.send_header("Content-Length", str(len(body)))
+ self._send_cors_headers()
+ self.end_headers()
+ self.wfile.write(body)
+
+ def _send_cors_headers(self):
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
+
+
+def main():
+ if not os.path.isdir(WORKING_DIR):
+ print(f"⚠️ 警告:工作目录不存在 — {WORKING_DIR}")
+ print(" 请修改 server.py 中的 WORKING_DIR 变量")
+ sys.exit(1)
+
+ server = http.server.ThreadingHTTPServer(("127.0.0.1", PORT), DemoHandler)
+
+ claude_status = f"✅ {CLAUDE_EXE}" if CLAUDE_EXE else "❌ 未找到 claude.exe(Skills 功能不可用)"
+ print(f"""
+╔════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
+║ GitLink CLI 作品展示 · 交互式平台 ║
+╠════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
+║ 打开浏览器访问: http://127.0.0.1:{PORT} ║
+║ 工作目录: {WORKING_DIR} ║
+║ Claude Code: {claude_status:<55} ║
+║ 按 Ctrl+C 停止服务器 ║
+╚════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
+""")
+ try:
+ server.serve_forever()
+ except KeyboardInterrupt:
+ print("\n👋 服务器已停止。")
+
+
+if __name__ == "__main__":
+ main()