@shaohui_jin/git-cockpit-mcp-server 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-563C4WZJ.js → chunk-EGRRNLIR.js} +2 -2
- package/dist/{chunk-FDPM74UE.js → chunk-XZMJUCNP.js} +2 -2
- package/dist/{chunk-FDPM74UE.js.map → chunk-XZMJUCNP.js.map} +1 -1
- package/dist/{chunk-QPNAXPTK.js → chunk-Z2I56YTY.js} +3 -3
- package/dist/cli-entry.js +3 -3
- package/dist/cli.js +3 -3
- package/dist/index.js +3 -3
- package/dist/mcpServer.js +1 -1
- package/dist/web/assets/{index-DFpy0R8H.js → index-Dr08TY5R.js} +1 -1
- package/dist/web/assets/{index-CcJ_rkpc.css → index-Q-8t6Mrp.css} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/webServer.js +2 -2
- package/package.json +2 -2
- /package/dist/{chunk-563C4WZJ.js.map → chunk-EGRRNLIR.js.map} +0 -0
- /package/dist/{chunk-QPNAXPTK.js.map → chunk-Z2I56YTY.js.map} +0 -0
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
summarizeForAgent,
|
|
11
11
|
toolSummaries,
|
|
12
12
|
version
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XZMJUCNP.js";
|
|
14
14
|
|
|
15
15
|
// src/webServer.ts
|
|
16
16
|
import * as path3 from "path";
|
|
@@ -1794,4 +1794,4 @@ export {
|
|
|
1794
1794
|
disposeRuntime,
|
|
1795
1795
|
createWebServer
|
|
1796
1796
|
};
|
|
1797
|
-
//# sourceMappingURL=chunk-
|
|
1797
|
+
//# sourceMappingURL=chunk-EGRRNLIR.js.map
|
|
@@ -7,7 +7,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8
8
|
|
|
9
9
|
// package.json
|
|
10
|
-
var version = "0.1.
|
|
10
|
+
var version = "0.1.26";
|
|
11
11
|
|
|
12
12
|
// src/tools/bindMcp.ts
|
|
13
13
|
import { z } from "zod";
|
|
@@ -730,4 +730,4 @@ export {
|
|
|
730
730
|
startMcpStdio,
|
|
731
731
|
McpHttpHandler
|
|
732
732
|
};
|
|
733
|
-
//# sourceMappingURL=chunk-
|
|
733
|
+
//# sourceMappingURL=chunk-XZMJUCNP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcpServer.ts","../package.json","../src/tools/bindMcp.ts","../src/tools/format.ts","../src/tools/handlers.ts","../src/host.ts","../src/overview.ts","../src/mcpSession.ts","../src/tools/index.ts","../src/tools/prompts.ts","../src/tools/resources.ts","../src/daemonEndpoint.ts"],"sourcesContent":["/**\n * MCP Server 装配:\n * - 基于官方 SDK 的 McpServer(tools 能力),注册全部工具;\n * - 支持 stdio(独立子进程模式)与 Streamable HTTP(daemon 转发模式)两种传输。\n *\n * 所有工具共用 executeCapability 安全链路(权限/dry-run/备份/审计)。\n */\nimport { randomUUID } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { Runtime } from './runtime.ts';\nimport { version } from '../package.json';\nimport { bindMcpTools } from './tools/bindMcp.ts';\nimport { TOOL_DEFS } from './tools/index.ts';\nimport { registerMcpPrompts } from './tools/prompts.ts';\nimport { registerMcpResources } from './tools/resources.ts';\nimport { McpSessionContext } from './mcpSession.ts';\nimport { resolveDaemonEndpoint } from './daemonEndpoint.ts';\n\nexport const MCP_SERVER_INFO = { name: 'git-cockpit-mcp-server', version } as const;\n\n/** 创建一个已注册全部工具的 McpServer(一个传输对应一个实例) */\nexport function createMcpServer(runtime: Runtime, session = new McpSessionContext(runtime)): McpServer {\n const server = new McpServer(\n { name: MCP_SERVER_INFO.name, version: MCP_SERVER_INFO.version },\n {\n capabilities: { tools: {}, prompts: {}, resources: {}, logging: {} },\n instructions: [\n 'Git Cockpit MCP Server:可视化的 Git 操作工具。',\n '使用规则:',\n '1. 先调用 git_status 查看工作区状态(含 operation=merge/rebase/cherry-pick 时走工作区收尾,不要用预演工具冒充);',\n '2. 写操作先 dry_run=true。MCP 真写要等这次预览成功后再用相同参数 dry_run=false;next 不会直接给 dry_run=false。',\n '3. 高风险工具(git_reset_hard/git_clean/git_push_force/git_branch_delete_force/git_rebase)默认禁用,',\n ' 需管理员在配置中开启;执行时系统会自动备份当前状态;',\n '4. 多仓库时先调用 git_repo_select 绑定当前 MCP session;后续未指定 repoId/repoPath 的工具使用该绑定。未绑定时才兼容使用最近打开的仓库;普通工具显式指定仓库只影响单次调用;',\n '5. 合并预演用 git_merge_preview / git_merge_rehearse(merge-tree,不改工作区),',\n ' into=合入目标/线上,from=我的分支;禁止用 git_merge 做预演。',\n ' 冲突行作者用 git_merge_blame(按文件、只读,不是选边)。',\n ' 多分支扫描用 git_merge_survey;建议合入顺序用 git_merge_order。',\n ' 有冲突只把 webUrl 给人在网页选边;Agent 不要选边、不要传 files / resolvedContent。',\n '6. 落盘用 git_apply_resolve(独立 worktree,主区不切换)。仅干净合并。有冲突不要调用。',\n '7. 工作区已经冲突:人先在编辑器或网页解决,再 git_merge_continue / git_rebase_continue / git_cherry_pick_continue(不要带 files)或 abort;不是 apply_resolve。拣选一次一个提交。',\n '8. 更新远程跟踪分支用 git_fetch。两分支分叉对比用 git_branch_graph 并传 into/from。',\n '9. 开 PR/MR:先 git_merge_preview。有冲突不要 git_mr_create(含 dry_run),把 webUrl 给人在网页选边。干净则落盘并推送临时枝,再 git_mr_prepare(看 mergeGate.ok)→ git_mr_create 只 dry_run。方式与 Token 在设置 MR 配置(不进工具参数)。找不到 CLI 时结果含官方安装地址。',\n '10. 大结果默认摘要:git_diff / git_show / git_merge_rehearse 要正文请加 path 或 detail=true。',\n '11. 已打开仓库脉搏用 git_repo_overview。长任务用 git_job_list / git_job_get / git_job_cancel。',\n '12. worktree 列表/添加/删除用 git_worktree_list / git_worktree_add / git_worktree_remove;不要把 linked worktree 当成第二套工作区做 merge。',\n '13. 提交 / 预演落盘 / 工作区收尾 / 开 PR 用 Prompt:safe_commit、merge_preview_apply、workspace_continue、open_mr(正文与 README「Agent 该怎么用」相同)。',\n '14. 只读列表可读 Resource:git-cockpit://repos、git-cockpit://repo/current、git-cockpit://jobs、git-cockpit://jobs/{id}。'\n ].join('\\n')\n }\n );\n\n bindMcpTools(server, runtime, TOOL_DEFS, session);\n\n registerMcpPrompts(server);\n registerMcpResources(server, runtime, session);\n bindMcpNotifications(server, runtime, session);\n return server;\n}\n\n/** 将 daemon 的业务事件映射为 MCP 主动通知;监听器随 server 关闭而移除。 */\nfunction bindMcpNotifications(server: McpServer, runtime: Runtime, session: McpSessionContext): void {\n const onRepoChanged = (payload: unknown) => {\n const repoPath =\n typeof payload === 'object' && payload !== null && 'repoPath' in payload\n ? String((payload as { repoPath?: unknown }).repoPath ?? '')\n : '';\n const binding = session.getBinding();\n if (binding && repoPath && binding.repoPath !== repoPath) return;\n void server.server.sendResourceUpdated({ uri: 'git-cockpit://repo/current' }).catch(() => undefined);\n void server.server\n .sendLoggingMessage({\n level: 'info',\n data: { event: 'repo-changed', repoPath, payload }\n })\n .catch(() => undefined);\n };\n const onJobProgress = (payload: unknown) => {\n void server.server\n .sendLoggingMessage({\n level: 'info',\n data: { event: 'job-progress', payload }\n })\n .catch(() => undefined);\n };\n runtime.eventBus.on('repo-changed', onRepoChanged);\n runtime.eventBus.on('job-progress', onJobProgress);\n server.server.onclose = () => {\n runtime.eventBus.off('repo-changed', onRepoChanged);\n runtime.eventBus.off('job-progress', onJobProgress);\n };\n}\n\n/** stdio 模式:单连接,适合 Claude Desktop / Cursor 直接拉起的独立进程 */\nexport async function startMcpStdio(runtime: Runtime): Promise<void> {\n const server = createMcpServer(runtime, new McpSessionContext(runtime));\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\n// ---------------------------------------------------------------------------\n// Streamable HTTP 模式(daemon 内嵌):stateful 会话管理\n// ---------------------------------------------------------------------------\n\ninterface McpHttpSession {\n id: string;\n server: McpServer;\n transport: StreamableHTTPServerTransport;\n repoContext: McpSessionContext;\n}\n\nfunction isInitializeBody(body: unknown): boolean {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return false;\n return (body as { method?: unknown }).method === 'initialize';\n}\n\n/** IPv6 字面量在 Host 头里必须带方括号(`::1` → `[::1]`) */\nfunction normalizeHostBase(addr: string): string {\n return addr.includes(':') && !addr.startsWith('[') ? `[${addr}]` : addr;\n}\n\n/**\n * SDK 的 DNS rebinding 校验直接用带端口的 Host 头比对 allowedHosts(不剥端口),\n * 所以 `host` 与 `host:port` 两种形态都要列进去,否则真实客户端一律 403。\n *\n * 另外并入本次连接的真实本地端点(socket):\n * - 服务监听在随机端口 / 0.0.0.0 时,配置里的端口未必等于实际端口;\n * - 这不削弱防护 —— 攻击者能控制的只有 Host 头(如 evil.com:3000),\n * 而 socket 端点由服务自身决定,永远不会是攻击者的域名。\n */\nfunction buildAllowedHosts(\n configured: { host: string; port: number },\n socket?: { localAddress?: string; localPort?: number }\n): string[] {\n const bases = new Set<string>();\n for (const raw of [configured.host, '127.0.0.1', 'localhost', '[::1]']) {\n if (raw) bases.add(normalizeHostBase(raw));\n }\n const local = socket?.localAddress;\n if (local) {\n bases.add(normalizeHostBase(local));\n // IPv4-mapped IPv6(::ffff:127.0.0.1)在 Host 头里是裸 IPv4,补一份\n if (local.startsWith('::ffff:')) bases.add(local.slice(7));\n }\n\n const ports = new Set<number>();\n for (const p of [configured.port, socket?.localPort]) {\n if (typeof p === 'number' && Number.isInteger(p) && p > 0) ports.add(p);\n }\n\n const out = new Set<string>();\n for (const base of bases) {\n out.add(base);\n for (const port of ports) out.add(`${base}:${port}`);\n }\n return [...out];\n}\n\n/** 维护 Streamable HTTP 的会话映射(sessionId -> server+transport) */\nexport class McpHttpHandler {\n private readonly sessions = new Map<string, McpHttpSession>();\n constructor(private readonly runtime: Runtime) {}\n\n /** 与 cli.ts、stdio bridge 保持同一套 daemon 地址解析。 */\n private hostPort(): { host: string; port: number } {\n const endpoint = resolveDaemonEndpoint(this.runtime.config);\n return { host: endpoint.host, port: endpoint.port };\n }\n\n async handle(req: IncomingMessage, res: ServerResponse, parsedBody?: unknown): Promise<void> {\n const headerId = req.headers['mcp-session-id'];\n if (typeof headerId === 'string' && headerId) {\n const session = this.sessions.get(headerId);\n if (!session) {\n res.statusCode = 404;\n res.end('MCP session not found');\n return;\n }\n await session.transport.handleRequest(req, res, parsedBody);\n return;\n }\n\n // 没有会话 id 的请求只能是 initialize;预检使用 /api/health,避免制造悬空 transport。\n if (req.method !== 'POST' || !isInitializeBody(parsedBody)) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'MCP session 不存在;请先发送 initialize' }));\n return;\n }\n\n // 新会话:创建 transport + server 并连接\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableDnsRebindingProtection: true,\n allowedHosts: buildAllowedHosts(this.hostPort(), req.socket ?? undefined),\n // JSON 响应模式:POST 直接回一条 application/json(而不是默认的 SSE 长连接)。\n // 本产品的工具是纯请求-响应,没有服务端主动推送,用 SSE 只会让连接悬着:\n // - stdio 桥要等流关闭才能拿到完整正文,curl 调试也要 --no-buffer 才看得到;\n // - 客户端侧(GET /mcp)的 SSE 通道不受影响,仍可用于服务端通知。\n enableJsonResponse: true\n });\n const repoContext = new McpSessionContext(this.runtime);\n const server = createMcpServer(this.runtime, repoContext);\n await server.connect(transport);\n const session: McpHttpSession = { id: '', server, transport, repoContext };\n transport.onclose = () => {\n if (session.id) this.sessions.delete(session.id);\n void server.close().catch(() => undefined);\n };\n await transport.handleRequest(req, res, parsedBody);\n const sid = transport.sessionId;\n if (sid) {\n session.id = sid;\n this.sessions.set(sid, session);\n }\n }\n\n dispose(): void {\n for (const session of this.sessions.values()) {\n session.transport.close().catch(() => undefined);\n }\n this.sessions.clear();\n }\n}\n","{\n \"name\": \"@shaohui_jin/git-cockpit-mcp-server\",\n \"version\": \"0.1.25\",\n \"description\": \"Git Cockpit - 基于 MCP 的 Git 可视化操作工具(MCP Server + Web 服务 + CLI)\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.5.0\"\n },\n \"bin\": {\n \"git-cockpit\": \"./dist/cli-entry.js\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/cli-entry.js start\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\",\n \"prepublishOnly\": \"pnpm run typecheck && pnpm run build && node scripts/copy-web.mjs\"\n },\n \"dependencies\": {\n \"@ai-sdk/openai\": \"^4.0.66\",\n \"@fastify/cors\": \"^10.0.2\",\n \"@fastify/static\": \"^8.0.3\",\n \"@fastify/swagger\": \"^9.8.1\",\n \"@fastify/swagger-ui\": \"^5.2.6\",\n \"@modelcontextprotocol/sdk\": \"^1.12.0\",\n \"@shaohui_jin/git-cockpit-core\": \"workspace:^\",\n \"ai\": \"^7.0.99\",\n \"fastify\": \"^5.2.1\",\n \"zod\": \"^3.24.1\",\n \"zod-to-json-schema\": \"^3.25.2\"\n },\n \"devDependencies\": {\n \"simple-git\": \"^3.27.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.4\"\n }\n}\n","/**\n * 把 Capability 登记成 MCP tool。SDK registerTool 必须 bind(server),\n * 抽成裸函数会丢 this,注册时抛 TypeError 导致握手挂起。\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Capability } from '@shaohui_jin/git-cockpit-core';\nimport type { Runtime } from '../runtime.ts';\nimport { formatResultForMcp } from './format.ts';\nimport { executeTool } from './handlers.ts';\nimport { McpSessionContext } from '../mcpSession.ts';\n\nconst HIDE_FILES_ON_MCP = new Set([\n 'git_apply_resolve',\n 'git_merge_continue',\n 'git_rebase_continue',\n 'git_cherry_pick_continue'\n]);\n\nfunction mcpInputSchema(def: Capability): unknown {\n if (!HIDE_FILES_ON_MCP.has(def.name)) return def.schema;\n const schema = def.schema as { omit?: (mask: { files: true }) => unknown };\n return typeof schema.omit === 'function' ? schema.omit({ files: true }) : def.schema;\n}\n\nfunction mcpAnnotations(def: Capability): {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n openWorldHint: boolean;\n} {\n return {\n readOnlyHint: def.risk === 'readonly',\n destructiveHint: def.risk === 'dangerous',\n openWorldHint: false\n };\n}\n\nconst repoSelectSchema = z.object({\n repoId: z.number().int().positive().optional().describe('已打开仓库的稳定 id'),\n repoPath: z.string().optional().describe('仓库绝对路径'),\n clear: z.boolean().optional().describe('清除当前 MCP session 的仓库绑定')\n});\n\nfunction bindRepoSelect(server: McpServer, runtime: Runtime, session: McpSessionContext): void {\n server.registerTool(\n 'git_repo_select',\n {\n description: '绑定、切换或清除当前 MCP session 的默认仓库。普通工具显式传 repoId/repoPath 只影响单次调用。',\n inputSchema: repoSelectSchema,\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }\n },\n async (args) => {\n try {\n const binding = await session.select(args);\n // 多 Agent 排查时能看出“谁把 session 绑到了哪个仓”,不进审计库\n runtime.eventBus.emit('log', {\n tool: 'git_repo_select',\n result: binding ? `bound:${binding.repoId}` : 'cleared',\n at: new Date().toISOString()\n });\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(binding ? { bound: true, ...binding } : { bound: false }) }\n ]\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: err instanceof Error ? err.message : String(err) }],\n isError: true\n };\n }\n }\n );\n}\n\nexport function bindMcpTools(\n server: McpServer,\n runtime: Runtime,\n capabilities: readonly Capability[],\n session: McpSessionContext = new McpSessionContext(runtime)\n): void {\n bindRepoSelect(server, runtime, session);\n for (const def of capabilities) {\n const register = server.registerTool.bind(server) as unknown as (\n name: string,\n config: { title?: string; description?: string; inputSchema?: unknown; annotations?: unknown },\n cb: (args: unknown, extra: unknown) => Promise<unknown>\n ) => unknown;\n\n register(\n def.name,\n { description: def.description, inputSchema: mcpInputSchema(def), annotations: mcpAnnotations(def) },\n async (args: unknown) => {\n const rawArgs = (args ?? {}) as Record<string, unknown>;\n const binding = session.getBinding();\n const hasExplicitRepo = rawArgs.repoId !== undefined || Boolean(rawArgs.repoPath);\n const exec = await executeTool(def, rawArgs, {\n runtime,\n source: 'mcp',\n repoId: hasExplicitRepo ? undefined : binding?.repoId,\n repoPath: hasExplicitRepo ? undefined : binding?.repoPath\n });\n return {\n content: [{ type: 'text' as const, text: formatResultForMcp(exec) }],\n isError: !exec.success\n };\n }\n );\n }\n}\n","/**\n * MCP 文本输出:默认摘要,正文靠 path / detail。\n * Web REST / executeTool.result 仍是完整对象,不走这里。\n */\nimport { suggestNext } from '@shaohui_jin/git-cockpit-core';\nimport type { ExecutionResult } from '@shaohui_jin/git-cockpit-core';\n\nconst FILE_CONTENT_LIMIT = 64 * 1024;\n\nexport function wantsDetail(args: Record<string, unknown> | undefined): boolean {\n return args?.detail === true;\n}\n\nfunction hasPath(args: Record<string, unknown> | undefined): boolean {\n return typeof args?.path === 'string' && args.path.trim().length > 0;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction stripDiffPatch(diff: unknown): unknown {\n const d = asRecord(diff);\n if (!d || !('rawPatch' in d)) return diff;\n const { rawPatch: _raw, ...rest } = d;\n return { ...rest, detailAvailable: true };\n}\n\nfunction stripRehearseBodies(result: unknown): unknown {\n const r = asRecord(result);\n if (!r) return result;\n const files = Array.isArray(r.conflictFiles) ? r.conflictFiles : [];\n const paths = files.map((f) => {\n const row = asRecord(f);\n return {\n path: row?.path ?? '',\n contentConflict: row?.contentConflict ?? true,\n hunks: []\n };\n });\n return {\n ...r,\n conflictFiles: paths,\n detailAvailable: true\n };\n}\n\nfunction attachNext(tool: string, result: unknown, args: Record<string, unknown>, dryRun: boolean): unknown {\n const rec = asRecord(result);\n if (!rec) return result;\n if (Array.isArray(rec.next) && rec.next.length) return result;\n const next = suggestNext(tool, result, args, { dryRun });\n if (!next.length) return result;\n return { ...rec, next };\n}\n\n/** 把完整工具结果收成 Agent 默认可消化的形状 */\nexport function summarizeForAgent(\n tool: string,\n result: unknown,\n args: Record<string, unknown> = {},\n dryRun = false\n): unknown {\n if (result == null) return result;\n const detail = wantsDetail(args);\n const pathSet = hasPath(args);\n\n let summarized: unknown = result;\n if (tool === 'git_diff' || tool === 'git_stash_show') {\n summarized = detail || pathSet ? result : stripDiffPatch(result);\n } else if (tool === 'git_show') {\n if (detail || pathSet) {\n summarized = result;\n } else {\n const row = asRecord(result);\n summarized = row ? { ...row, diff: stripDiffPatch(row.diff) } : result;\n }\n } else if (tool === 'git_merge_rehearse') {\n summarized = detail || pathSet ? result : stripRehearseBodies(result);\n } else if (tool === 'git_repo_overview') {\n if (!detail) {\n const row = asRecord(result);\n const repos = Array.isArray(row?.repos) ? row.repos : [];\n summarized = {\n ...row,\n repos: repos.map((item) => {\n const rec = asRecord(item);\n if (!rec) return item;\n const { activity: _days, ...rest } = rec;\n return rest;\n })\n };\n }\n } else if (tool === 'git_file_content') {\n if (!detail) {\n const row = asRecord(result);\n const content = typeof row?.content === 'string' ? row.content : '';\n const bytes = Buffer.byteLength(content, 'utf8');\n if (bytes > FILE_CONTENT_LIMIT) {\n summarized = {\n path: args.path,\n truncated: true,\n bytes,\n detailAvailable: true\n };\n }\n }\n }\n\n return attachNext(tool, summarized, args, dryRun);\n}\n\n/** 把干运行/真实结果统一包成 MCP 文本输出 */\nexport function formatResultForMcp(exec: ExecutionResult): string {\n if (!exec.success) {\n const prefix = exec.error?.requiredApproval ? '该工具已禁用' : '操作失败';\n return `${prefix}:${exec.error?.message ?? '未知错误'}`;\n }\n const parts: string[] = [];\n if (exec.dryRun) {\n parts.push('[dry-run 预览] 未实际执行任何操作');\n if (exec.source === 'mcp') {\n parts.push('预览已记下。把命令给人看,等人明确说执行后,再用相同参数 dry_run=false。');\n }\n }\n if (exec.backupCreated) {\n parts.push(\n `[自动备份] 分支 ${exec.backupCreated.branch ?? '(未创建)'}${exec.backupCreated.stashRef ? `,stash ${exec.backupCreated.stashRef}` : ''}`\n );\n }\n const summarized = summarizeForAgent(exec.tool, exec.result, exec.args ?? {}, exec.dryRun);\n const rec = asRecord(summarized);\n const next = Array.isArray(rec?.next) ? rec.next : [];\n if (next.length) {\n const names = next\n .map((step) => asRecord(step)?.tool)\n .filter((n): n is string => typeof n === 'string' && n.length > 0);\n if (names.length) parts.push(`next: ${names.join(' → ')}`);\n }\n const payload = typeof summarized === 'string' ? summarized : JSON.stringify(summarized);\n parts.push(payload);\n return parts.join('\\n\\n');\n}\n","/**\n * 工具执行层:MCP / Web / CLI 共用。安全链路在 core executeCapability。\n */\nimport { executeCapability } from '@shaohui_jin/git-cockpit-core';\nimport type { Capability, ExecutionResult } from '@shaohui_jin/git-cockpit-core';\nimport { hostFromRuntime } from '../host.ts';\nimport type { Runtime } from '../runtime.ts';\n\nexport type ToolDef = Capability;\nexport type ToolExecutionResult = ExecutionResult;\n\nexport interface ToolExecutionContext {\n runtime: Runtime;\n source: 'mcp' | 'web' | 'cli' | 'chat';\n /** 上层已解析的仓库(Web 路由提供 repoId;MCP session 提供 repoId/repoPath 默认值) */\n repoId?: number;\n repoPath?: string;\n}\n\n/**\n * 执行一个工具(含统一安全链路)。返回标准化结果,不抛异常(错误编码进 error 字段)。\n */\nexport async function executeTool(\n def: ToolDef,\n rawArgs: Record<string, unknown>,\n ctx: ToolExecutionContext\n): Promise<ToolExecutionResult> {\n return executeCapability(def, rawArgs, {\n source: ctx.source,\n repoId: ctx.repoId,\n repoPath: ctx.repoPath,\n host: hostFromRuntime(ctx.runtime)\n });\n}\n\nexport { formatResultForMcp, summarizeForAgent } from './format.ts';\n","import { GitOperationError } from '@shaohui_jin/git-cockpit-core';\nimport type { CapabilityHost } from '@shaohui_jin/git-cockpit-core';\nimport { collectRepoOverviews } from './overview.ts';\nimport type { Runtime } from './runtime.ts';\n\n/** 把进程壳 Runtime 收成 core executor 认识的 CapabilityHost */\nexport function hostFromRuntime(runtime: Runtime): CapabilityHost {\n return {\n config: runtime.config,\n permissions: runtime.permissions,\n auditLogger: runtime.auditLogger,\n eventBus: runtime.eventBus,\n jobs: runtime.jobs,\n async resolveRepo({ repoId, repoPath, argsPath }) {\n if (repoId !== undefined) {\n const handle = await runtime.repoManager.getById(repoId);\n if (handle) return { git: handle.service, repoPath: handle.service.repoPath };\n throw new GitOperationError(`仓库不存在: id=${repoId}`, 'REPO_NOT_FOUND');\n }\n if (repoPath) {\n const handle = runtime.repoManager.getByPath(repoPath);\n return { git: handle.service, repoPath: handle.service.repoPath };\n }\n if (argsPath) {\n const handle = await runtime.repoManager.open(argsPath);\n return { git: handle.service, repoPath: handle.service.repoPath };\n }\n const current = await runtime.repoManager.getCurrent();\n if (!current) {\n throw new GitOperationError('尚未打开任何仓库。请先打开仓库或提供 repoPath 参数。', 'NO_ACTIVE_REPO');\n }\n return { git: current.service, repoPath: current.service.repoPath };\n },\n listOverviews: () => collectRepoOverviews(runtime)\n };\n}\n","import { emptyActivity, type RepoOverview } from '@shaohui_jin/git-cockpit-core';\nimport type { Runtime } from './runtime.ts';\n\nconst CONCURRENCY = 4;\n\nexport function unavailableOverview(path: string, extra: Partial<RepoOverview> = {}): RepoOverview {\n const name =\n path\n .replace(/[\\\\/]+$/, '')\n .split(/[\\\\/]/)\n .pop() || path;\n return {\n path,\n name,\n available: false,\n current: '',\n tracking: null,\n ahead: 0,\n behind: 0,\n dirtyCount: 0,\n conflictCount: 0,\n operation: 'none',\n tempMergeBranchCount: 0,\n ...emptyActivity(),\n ...extra\n };\n}\n\nasync function mapPool<T, R>(items: T[], n: number, fn: (t: T) => Promise<R>): Promise<R[]> {\n const out: R[] = new Array(items.length);\n let i = 0;\n async function worker() {\n while (i < items.length) {\n const idx = i++;\n out[idx] = await fn(items[idx]!);\n }\n }\n const workers = Math.min(n, items.length);\n await Promise.all(Array.from({ length: workers }, () => worker()));\n return out;\n}\n\nasync function overviewForRecord(\n runtime: Runtime,\n record: { id: number; path: string; lastOpenedAt: string }\n): Promise<RepoOverview> {\n try {\n const handle = await runtime.repoManager.getById(record.id);\n if (!handle) {\n return unavailableOverview(record.path, { id: record.id, lastOpenedAt: record.lastOpenedAt });\n }\n const o = await handle.service.getOverview();\n return { ...o, id: record.id, lastOpenedAt: record.lastOpenedAt };\n } catch {\n return unavailableOverview(record.path, { id: record.id, lastOpenedAt: record.lastOpenedAt });\n }\n}\n\n/** 单个已打开仓库的脉搏;不在列表中返回 null */\nexport async function collectRepoOverviewForId(runtime: Runtime, id: number): Promise<RepoOverview | null> {\n const record = runtime.repoManager.list().find((r) => r.id === id);\n if (!record) return null;\n return overviewForRecord(runtime, record);\n}\n\nexport async function collectRepoOverviews(runtime: Runtime): Promise<RepoOverview[]> {\n const repos = runtime.repoManager.list();\n if (!repos.length) return [];\n return mapPool(repos, CONCURRENCY, (r) => overviewForRecord(runtime, r));\n}\n","import type { Runtime } from './runtime.ts';\n\nexport interface McpRepoBinding {\n repoId: number;\n repoPath: string;\n boundAt: string;\n generation: number;\n}\n\n/** MCP 连接私有的仓库上下文;不写入 SQLite,也不与其他连接共享。 */\nexport class McpSessionContext {\n private binding: McpRepoBinding | null = null;\n private generation = 0;\n\n constructor(private readonly runtime: Runtime) {}\n\n getBinding(): McpRepoBinding | null {\n return this.binding ? { ...this.binding } : null;\n }\n\n async select(input: { repoId?: number; repoPath?: string; clear?: boolean }): Promise<McpRepoBinding | null> {\n if (input.clear) {\n if (input.repoId !== undefined || input.repoPath) {\n throw new Error('clear=true 不能同时传 repoId 或 repoPath');\n }\n this.binding = null;\n this.generation += 1;\n return null;\n }\n\n if (input.repoId === undefined && !input.repoPath) {\n throw new Error('需要提供 repoId 或 repoPath;如需清除绑定请传 clear=true');\n }\n if (input.repoId !== undefined && input.repoPath) {\n throw new Error('repoId 与 repoPath 只能传一个');\n }\n\n const handle =\n input.repoId !== undefined\n ? await this.runtime.repoManager.getById(input.repoId)\n : await this.runtime.repoManager.open(input.repoPath!);\n if (!handle) {\n throw new Error(`仓库不存在: id=${input.repoId}`);\n }\n\n this.generation += 1;\n this.binding = {\n repoId: handle.record.id,\n repoPath: handle.service.repoPath,\n boundAt: new Date().toISOString(),\n generation: this.generation\n };\n return this.getBinding();\n }\n}\n","/**\n * 工具注册表:定义在 core/capabilities/git/*.ts。\n * 加载本模块时写入 Capability registry。新工具禁止只写在本包。\n */\nimport { getCapabilityRegistry, TOOL_DEFS, TOOL_DEF_MAP, toolSummaries } from '@shaohui_jin/git-cockpit-core';\nimport type { GitService } from '@shaohui_jin/git-cockpit-core';\n\ngetCapabilityRegistry().registerAll(TOOL_DEFS);\n\nexport { TOOL_DEFS, TOOL_DEF_MAP, toolSummaries };\nexport type { GitService };\n","/**\n * MCP Prompt:。正文抄仓库根 README「Agent 该怎么用」,不许另写流程\n */\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nconst PREAMBLE = [\n '不要用终端 git 代替「预演、落盘、开 PR」。',\n '多仓库时先调用 `git_repo_select({ repoId })` 绑定当前 MCP session;显式 repoId/repoPath 只影响单次调用。',\n '默认只给摘要;要看某文件正文时再说路径,或加 `detail=true`。写操作先干跑(`dry_run=true`)。高风险操作默认关着。'\n].join('\\n');\n\nexport const MCP_PROMPTS = [\n {\n name: 'safe_commit',\n title: '提交',\n description: '先看改了什么,再暂存、再提交。不要一上来就提交。',\n text: [\n PREAMBLE,\n '',\n '1. 提交',\n '先看工作区有没有改、改了什么,再暂存,再提交。不要一上来就提交。',\n '',\n '对应工具:`git_status` → `git_diff` → 按需 `git_add` → `git_commit`。'\n ].join('\\n')\n },\n {\n name: 'merge_preview_apply',\n title: '合不合得进去,以及写进仓库',\n description: '只做预演。有冲突给人网页,不要选边。工作区已经卡住时不要走这条。',\n text: [\n PREAMBLE,\n '',\n '2. 合不合得进去,以及写进仓库',\n '先看工作区是不是已经卡在 merge / rebase 里。卡住了走「工作区已经卡在 merge 或 rebase 里」,不要走这条。',\n '',\n '问能不能合进去时,只做预演,不要在当前工作区执行 merge。`into` 是要合进去的目标(线上),`from` 是你的分支。有冲突时把 webUrl 给人打开合并页(`/#/merge?into=&from=&preview=1`),然后停止。不要选边,不要传 files / resolvedContent,不要在冲突时 git_mr_create(含 dry_run)。',\n '',\n '看完要写进仓库:仅干净合并才用独立目录落盘,不要切走你正在干活的分支。人口头说解决了不算:用原来的 into/from 再预演,认这一对的 `merge/<from>-into-<into>`(或 `origin/merge/…`)。别人的 `merge/*` 当噪音。',\n '',\n '对应工具:`git_merge_preview` / `git_merge_rehearse`(冲突行是谁改的用 `git_merge_blame`,只读)。干净才 `git_apply_resolve`。禁止用 `git_merge` 冒充预演。'\n ].join('\\n')\n },\n {\n name: 'workspace_continue',\n title: '工作区已经卡在 merge 或 rebase 里',\n description: '看状态。人先解决,再继续或放弃。不要传 files,不要用预演落盘去收尾。',\n text: [\n PREAMBLE,\n '',\n '3. 工作区已经卡在 merge 或 rebase 里',\n '这是另一回事:冲突已经发生在当前目录了。看状态。人先在编辑器或网页解决,再继续或放弃。不要传 files。不要用「合不合得进去,以及写进仓库」的落盘去收尾。',\n '',\n '对应工具:`git_status`(看 `operation`)→ `git_merge_continue` / `git_rebase_continue`(不要带 `files`)或 abort。禁止用 `git_apply_resolve`。'\n ].join('\\n')\n },\n {\n name: 'open_mr',\n title: '开 Pull Request / Merge Request',\n description: '先预演;冲突则停。干净才落盘开单。Token 在网页设置里,不要塞进工具参数。',\n text: [\n PREAMBLE,\n '',\n '4. 开 Pull Request / Merge Request',\n '先 `git_merge_preview`。有冲突不要 `git_mr_create`(含 dry_run),把 webUrl 给人在网页选边。干净则落盘并推送临时枝,再 `git_mr_prepare`;只有 `mergeGate.ok` 才 `git_mr_create`,且只 dry_run 预览正文,不要把干跑当确认开单。登录用的 Token 写在网页设置里,不要塞进工具参数。',\n '',\n '对应工具:`git_merge_preview` →(冲突则停)`git_apply_resolve` / `git_push` → `git_mr_prepare` → `git_mr_create`(只 dry_run)。'\n ].join('\\n')\n }\n] as const;\n\nexport function registerMcpPrompts(server: McpServer): void {\n const register = server.registerPrompt.bind(server);\n for (const p of MCP_PROMPTS) {\n register(p.name, { title: p.title, description: p.description }, () => ({\n description: p.description,\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: p.text } }]\n }));\n }\n}\n","/**\n * MCP Resources:只读、不进审计。形状与 git_repo_overview / git_job_* 摘要同源。\n */\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Runtime } from '../runtime.ts';\nimport { McpSessionContext } from '../mcpSession.ts';\nimport { collectRepoOverviews } from '../overview.ts';\n\nconst MIME = 'application/json';\n\nfunction jsonContents(uri: string, data: unknown) {\n return {\n contents: [\n {\n uri,\n mimeType: MIME,\n text: JSON.stringify(data)\n }\n ]\n };\n}\n\nfunction overviewWithoutDays(runtime: Runtime) {\n return collectRepoOverviews(runtime).then((repos) =>\n repos.map((row) => {\n const { activity: _days, ...rest } = row;\n return rest;\n })\n );\n}\n\nexport function registerMcpResources(\n server: McpServer,\n runtime: Runtime,\n session: McpSessionContext = new McpSessionContext(runtime)\n): void {\n const register = server.registerResource.bind(server);\n\n register(\n 'repos',\n 'git-cockpit://repos',\n { title: '已打开仓库脉搏', description: '与 git_repo_overview 同摘要,不含每日格子', mimeType: MIME },\n async (uri) => jsonContents(uri.href, { repos: await overviewWithoutDays(runtime) })\n );\n\n register(\n 'repo-current',\n 'git-cockpit://repo/current',\n {\n title: '当前 session 仓',\n description: '当前 MCP session 绑定仓库的工作区摘要;未绑定时兼容最近打开仓库',\n mimeType: MIME\n },\n async (uri) => {\n const binding = session.getBinding();\n const handle = binding\n ? await runtime.repoManager.getById(binding.repoId)\n : await runtime.repoManager.getCurrent();\n if (!handle) {\n return jsonContents(uri.href, { available: false, hint: '没有已打开的仓库' });\n }\n const s = await handle.service.getStatus();\n return jsonContents(uri.href, {\n available: true,\n bound: Boolean(binding),\n scope: binding ? 'session' : 'legacy-current',\n repoId: handle.record.id,\n path: handle.record.path,\n current: s.current,\n tracking: s.tracking,\n ahead: s.ahead,\n behind: s.behind,\n operation: s.operation,\n isClean: s.isClean,\n stagedCount: s.staged.length,\n unstagedCount: s.unstaged.length,\n untrackedCount: s.untracked.length,\n conflictCount: s.conflicted.length,\n conflicted: s.conflicted.slice(0, 20)\n });\n }\n );\n\n register(\n 'jobs',\n 'git-cockpit://jobs',\n { title: '后台任务列表', description: '与 git_job_list 同摘要', mimeType: MIME },\n async (uri) =>\n jsonContents(uri.href, {\n jobs: runtime.jobs.list().map((j) => runtime.jobs.summary(j))\n })\n );\n\n register(\n 'job',\n new ResourceTemplate('git-cockpit://jobs/{id}', {\n list: async () => ({\n resources: runtime.jobs.list().map((j) => ({\n uri: `git-cockpit://jobs/${j.id}`,\n name: j.title,\n description: `${j.kind} ${j.status}`,\n mimeType: MIME\n }))\n })\n }),\n { title: '单条后台任务', description: '状态 + 日志尾 + result 摘要', mimeType: MIME },\n async (uri, variables) => {\n const id = String(variables.id ?? '');\n const job = runtime.jobs.get(id);\n if (!job) {\n return jsonContents(uri.href, { error: '任务不存在', id });\n }\n const summary = runtime.jobs.summary(job);\n return jsonContents(uri.href, {\n ...summary,\n logs: job.logs.slice(-20)\n });\n }\n );\n}\n","import type { GitCockpitConfig } from '@shaohui_jin/git-cockpit-core';\n\nexport interface DaemonEndpoint {\n host: string;\n port: number;\n baseUrl: string;\n healthUrl: string;\n mcpUrl: string;\n}\n\nexport function formatDaemonUrl(host: string, port: number, path = ''): string {\n const authority = `${host.includes(':') && !host.startsWith('[') ? `[${host}]` : host}:${port}`;\n return `http://${authority}${path}`;\n}\n\n/** 统一解析 daemon 地址,start/mcp/bridge 必须使用同一套优先级。 */\nexport function resolveDaemonEndpoint(config: GitCockpitConfig): DaemonEndpoint {\n const port = Number(process.env.GIT_COCKPIT_PORT) || config.server.port;\n const host = process.env.GIT_COCKPIT_HOST ?? config.server.host;\n return {\n host,\n port,\n baseUrl: formatDaemonUrl(host, port),\n healthUrl: formatDaemonUrl(host, port, '/api/health'),\n mcpUrl: formatDaemonUrl(host, port, '/mcp')\n };\n}\n"],"mappings":"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","names":["McpServer","McpServer","session"]}
|
|
1
|
+
{"version":3,"sources":["../src/mcpServer.ts","../package.json","../src/tools/bindMcp.ts","../src/tools/format.ts","../src/tools/handlers.ts","../src/host.ts","../src/overview.ts","../src/mcpSession.ts","../src/tools/index.ts","../src/tools/prompts.ts","../src/tools/resources.ts","../src/daemonEndpoint.ts"],"sourcesContent":["/**\n * MCP Server 装配:\n * - 基于官方 SDK 的 McpServer(tools 能力),注册全部工具;\n * - 支持 stdio(独立子进程模式)与 Streamable HTTP(daemon 转发模式)两种传输。\n *\n * 所有工具共用 executeCapability 安全链路(权限/dry-run/备份/审计)。\n */\nimport { randomUUID } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { Runtime } from './runtime.ts';\nimport { version } from '../package.json';\nimport { bindMcpTools } from './tools/bindMcp.ts';\nimport { TOOL_DEFS } from './tools/index.ts';\nimport { registerMcpPrompts } from './tools/prompts.ts';\nimport { registerMcpResources } from './tools/resources.ts';\nimport { McpSessionContext } from './mcpSession.ts';\nimport { resolveDaemonEndpoint } from './daemonEndpoint.ts';\n\nexport const MCP_SERVER_INFO = { name: 'git-cockpit-mcp-server', version } as const;\n\n/** 创建一个已注册全部工具的 McpServer(一个传输对应一个实例) */\nexport function createMcpServer(runtime: Runtime, session = new McpSessionContext(runtime)): McpServer {\n const server = new McpServer(\n { name: MCP_SERVER_INFO.name, version: MCP_SERVER_INFO.version },\n {\n capabilities: { tools: {}, prompts: {}, resources: {}, logging: {} },\n instructions: [\n 'Git Cockpit MCP Server:可视化的 Git 操作工具。',\n '使用规则:',\n '1. 先调用 git_status 查看工作区状态(含 operation=merge/rebase/cherry-pick 时走工作区收尾,不要用预演工具冒充);',\n '2. 写操作先 dry_run=true。MCP 真写要等这次预览成功后再用相同参数 dry_run=false;next 不会直接给 dry_run=false。',\n '3. 高风险工具(git_reset_hard/git_clean/git_push_force/git_branch_delete_force/git_rebase)默认禁用,',\n ' 需管理员在配置中开启;执行时系统会自动备份当前状态;',\n '4. 多仓库时先调用 git_repo_select 绑定当前 MCP session;后续未指定 repoId/repoPath 的工具使用该绑定。未绑定时才兼容使用最近打开的仓库;普通工具显式指定仓库只影响单次调用;',\n '5. 合并预演用 git_merge_preview / git_merge_rehearse(merge-tree,不改工作区),',\n ' into=合入目标/线上,from=我的分支;禁止用 git_merge 做预演。',\n ' 冲突行作者用 git_merge_blame(按文件、只读,不是选边)。',\n ' 多分支扫描用 git_merge_survey;建议合入顺序用 git_merge_order。',\n ' 有冲突只把 webUrl 给人在网页选边;Agent 不要选边、不要传 files / resolvedContent。',\n '6. 落盘用 git_apply_resolve(独立 worktree,主区不切换)。仅干净合并。有冲突不要调用。',\n '7. 工作区已经冲突:人先在编辑器或网页解决,再 git_merge_continue / git_rebase_continue / git_cherry_pick_continue(不要带 files)或 abort;不是 apply_resolve。拣选一次一个提交。',\n '8. 更新远程跟踪分支用 git_fetch。两分支分叉对比用 git_branch_graph 并传 into/from。',\n '9. 开 PR/MR:先 git_merge_preview。有冲突不要 git_mr_create(含 dry_run),把 webUrl 给人在网页选边。干净则落盘并推送临时枝,再 git_mr_prepare(看 mergeGate.ok)→ git_mr_create 只 dry_run。方式与 Token 在设置 MR 配置(不进工具参数)。找不到 CLI 时结果含官方安装地址。',\n '10. 大结果默认摘要:git_diff / git_show / git_merge_rehearse 要正文请加 path 或 detail=true。',\n '11. 已打开仓库脉搏用 git_repo_overview。长任务用 git_job_list / git_job_get / git_job_cancel。',\n '12. worktree 列表/添加/删除用 git_worktree_list / git_worktree_add / git_worktree_remove;不要把 linked worktree 当成第二套工作区做 merge。',\n '13. 提交 / 预演落盘 / 工作区收尾 / 开 PR 用 Prompt:safe_commit、merge_preview_apply、workspace_continue、open_mr(正文与 README「Agent 该怎么用」相同)。',\n '14. 只读列表可读 Resource:git-cockpit://repos、git-cockpit://repo/current、git-cockpit://jobs、git-cockpit://jobs/{id}。'\n ].join('\\n')\n }\n );\n\n bindMcpTools(server, runtime, TOOL_DEFS, session);\n\n registerMcpPrompts(server);\n registerMcpResources(server, runtime, session);\n bindMcpNotifications(server, runtime, session);\n return server;\n}\n\n/** 将 daemon 的业务事件映射为 MCP 主动通知;监听器随 server 关闭而移除。 */\nfunction bindMcpNotifications(server: McpServer, runtime: Runtime, session: McpSessionContext): void {\n const onRepoChanged = (payload: unknown) => {\n const repoPath =\n typeof payload === 'object' && payload !== null && 'repoPath' in payload\n ? String((payload as { repoPath?: unknown }).repoPath ?? '')\n : '';\n const binding = session.getBinding();\n if (binding && repoPath && binding.repoPath !== repoPath) return;\n void server.server.sendResourceUpdated({ uri: 'git-cockpit://repo/current' }).catch(() => undefined);\n void server.server\n .sendLoggingMessage({\n level: 'info',\n data: { event: 'repo-changed', repoPath, payload }\n })\n .catch(() => undefined);\n };\n const onJobProgress = (payload: unknown) => {\n void server.server\n .sendLoggingMessage({\n level: 'info',\n data: { event: 'job-progress', payload }\n })\n .catch(() => undefined);\n };\n runtime.eventBus.on('repo-changed', onRepoChanged);\n runtime.eventBus.on('job-progress', onJobProgress);\n server.server.onclose = () => {\n runtime.eventBus.off('repo-changed', onRepoChanged);\n runtime.eventBus.off('job-progress', onJobProgress);\n };\n}\n\n/** stdio 模式:单连接,适合 Claude Desktop / Cursor 直接拉起的独立进程 */\nexport async function startMcpStdio(runtime: Runtime): Promise<void> {\n const server = createMcpServer(runtime, new McpSessionContext(runtime));\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\n// ---------------------------------------------------------------------------\n// Streamable HTTP 模式(daemon 内嵌):stateful 会话管理\n// ---------------------------------------------------------------------------\n\ninterface McpHttpSession {\n id: string;\n server: McpServer;\n transport: StreamableHTTPServerTransport;\n repoContext: McpSessionContext;\n}\n\nfunction isInitializeBody(body: unknown): boolean {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return false;\n return (body as { method?: unknown }).method === 'initialize';\n}\n\n/** IPv6 字面量在 Host 头里必须带方括号(`::1` → `[::1]`) */\nfunction normalizeHostBase(addr: string): string {\n return addr.includes(':') && !addr.startsWith('[') ? `[${addr}]` : addr;\n}\n\n/**\n * SDK 的 DNS rebinding 校验直接用带端口的 Host 头比对 allowedHosts(不剥端口),\n * 所以 `host` 与 `host:port` 两种形态都要列进去,否则真实客户端一律 403。\n *\n * 另外并入本次连接的真实本地端点(socket):\n * - 服务监听在随机端口 / 0.0.0.0 时,配置里的端口未必等于实际端口;\n * - 这不削弱防护 —— 攻击者能控制的只有 Host 头(如 evil.com:3000),\n * 而 socket 端点由服务自身决定,永远不会是攻击者的域名。\n */\nfunction buildAllowedHosts(\n configured: { host: string; port: number },\n socket?: { localAddress?: string; localPort?: number }\n): string[] {\n const bases = new Set<string>();\n for (const raw of [configured.host, '127.0.0.1', 'localhost', '[::1]']) {\n if (raw) bases.add(normalizeHostBase(raw));\n }\n const local = socket?.localAddress;\n if (local) {\n bases.add(normalizeHostBase(local));\n // IPv4-mapped IPv6(::ffff:127.0.0.1)在 Host 头里是裸 IPv4,补一份\n if (local.startsWith('::ffff:')) bases.add(local.slice(7));\n }\n\n const ports = new Set<number>();\n for (const p of [configured.port, socket?.localPort]) {\n if (typeof p === 'number' && Number.isInteger(p) && p > 0) ports.add(p);\n }\n\n const out = new Set<string>();\n for (const base of bases) {\n out.add(base);\n for (const port of ports) out.add(`${base}:${port}`);\n }\n return [...out];\n}\n\n/** 维护 Streamable HTTP 的会话映射(sessionId -> server+transport) */\nexport class McpHttpHandler {\n private readonly sessions = new Map<string, McpHttpSession>();\n constructor(private readonly runtime: Runtime) {}\n\n /** 与 cli.ts、stdio bridge 保持同一套 daemon 地址解析。 */\n private hostPort(): { host: string; port: number } {\n const endpoint = resolveDaemonEndpoint(this.runtime.config);\n return { host: endpoint.host, port: endpoint.port };\n }\n\n async handle(req: IncomingMessage, res: ServerResponse, parsedBody?: unknown): Promise<void> {\n const headerId = req.headers['mcp-session-id'];\n if (typeof headerId === 'string' && headerId) {\n const session = this.sessions.get(headerId);\n if (!session) {\n res.statusCode = 404;\n res.end('MCP session not found');\n return;\n }\n await session.transport.handleRequest(req, res, parsedBody);\n return;\n }\n\n // 没有会话 id 的请求只能是 initialize;预检使用 /api/health,避免制造悬空 transport。\n if (req.method !== 'POST' || !isInitializeBody(parsedBody)) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'MCP session 不存在;请先发送 initialize' }));\n return;\n }\n\n // 新会话:创建 transport + server 并连接\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableDnsRebindingProtection: true,\n allowedHosts: buildAllowedHosts(this.hostPort(), req.socket ?? undefined),\n // JSON 响应模式:POST 直接回一条 application/json(而不是默认的 SSE 长连接)。\n // 本产品的工具是纯请求-响应,没有服务端主动推送,用 SSE 只会让连接悬着:\n // - stdio 桥要等流关闭才能拿到完整正文,curl 调试也要 --no-buffer 才看得到;\n // - 客户端侧(GET /mcp)的 SSE 通道不受影响,仍可用于服务端通知。\n enableJsonResponse: true\n });\n const repoContext = new McpSessionContext(this.runtime);\n const server = createMcpServer(this.runtime, repoContext);\n await server.connect(transport);\n const session: McpHttpSession = { id: '', server, transport, repoContext };\n transport.onclose = () => {\n if (session.id) this.sessions.delete(session.id);\n void server.close().catch(() => undefined);\n };\n await transport.handleRequest(req, res, parsedBody);\n const sid = transport.sessionId;\n if (sid) {\n session.id = sid;\n this.sessions.set(sid, session);\n }\n }\n\n dispose(): void {\n for (const session of this.sessions.values()) {\n session.transport.close().catch(() => undefined);\n }\n this.sessions.clear();\n }\n}\n","{\n \"name\": \"@shaohui_jin/git-cockpit-mcp-server\",\n \"version\": \"0.1.26\",\n \"description\": \"Git Cockpit - 基于 MCP 的 Git 可视化操作工具(MCP Server + Web 服务 + CLI)\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"engines\": {\n \"node\": \">=22.5.0\"\n },\n \"bin\": {\n \"git-cockpit\": \"./dist/cli-entry.js\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"start\": \"node dist/cli-entry.js start\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\",\n \"prepublishOnly\": \"pnpm run typecheck && pnpm run build && node scripts/copy-web.mjs\"\n },\n \"dependencies\": {\n \"@ai-sdk/openai\": \"^4.0.66\",\n \"@fastify/cors\": \"^10.0.2\",\n \"@fastify/static\": \"^8.0.3\",\n \"@fastify/swagger\": \"^9.8.1\",\n \"@fastify/swagger-ui\": \"^5.2.6\",\n \"@modelcontextprotocol/sdk\": \"^1.12.0\",\n \"@shaohui_jin/git-cockpit-core\": \"workspace:^\",\n \"ai\": \"^7.0.99\",\n \"fastify\": \"^5.2.1\",\n \"zod\": \"^3.24.1\",\n \"zod-to-json-schema\": \"^3.25.2\"\n },\n \"devDependencies\": {\n \"simple-git\": \"^3.27.0\",\n \"tsup\": \"^8.3.5\",\n \"typescript\": \"^5.7.2\",\n \"vitest\": \"^3.0.4\"\n }\n}\n","/**\n * 把 Capability 登记成 MCP tool。SDK registerTool 必须 bind(server),\n * 抽成裸函数会丢 this,注册时抛 TypeError 导致握手挂起。\n */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Capability } from '@shaohui_jin/git-cockpit-core';\nimport type { Runtime } from '../runtime.ts';\nimport { formatResultForMcp } from './format.ts';\nimport { executeTool } from './handlers.ts';\nimport { McpSessionContext } from '../mcpSession.ts';\n\nconst HIDE_FILES_ON_MCP = new Set([\n 'git_apply_resolve',\n 'git_merge_continue',\n 'git_rebase_continue',\n 'git_cherry_pick_continue'\n]);\n\nfunction mcpInputSchema(def: Capability): unknown {\n if (!HIDE_FILES_ON_MCP.has(def.name)) return def.schema;\n const schema = def.schema as { omit?: (mask: { files: true }) => unknown };\n return typeof schema.omit === 'function' ? schema.omit({ files: true }) : def.schema;\n}\n\nfunction mcpAnnotations(def: Capability): {\n readOnlyHint: boolean;\n destructiveHint: boolean;\n openWorldHint: boolean;\n} {\n return {\n readOnlyHint: def.risk === 'readonly',\n destructiveHint: def.risk === 'dangerous',\n openWorldHint: false\n };\n}\n\nconst repoSelectSchema = z.object({\n repoId: z.number().int().positive().optional().describe('已打开仓库的稳定 id'),\n repoPath: z.string().optional().describe('仓库绝对路径'),\n clear: z.boolean().optional().describe('清除当前 MCP session 的仓库绑定')\n});\n\nfunction bindRepoSelect(server: McpServer, runtime: Runtime, session: McpSessionContext): void {\n server.registerTool(\n 'git_repo_select',\n {\n description: '绑定、切换或清除当前 MCP session 的默认仓库。普通工具显式传 repoId/repoPath 只影响单次调用。',\n inputSchema: repoSelectSchema,\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }\n },\n async (args) => {\n try {\n const binding = await session.select(args);\n // 多 Agent 排查时能看出“谁把 session 绑到了哪个仓”,不进审计库\n runtime.eventBus.emit('log', {\n tool: 'git_repo_select',\n result: binding ? `bound:${binding.repoId}` : 'cleared',\n at: new Date().toISOString()\n });\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify(binding ? { bound: true, ...binding } : { bound: false }) }\n ]\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: err instanceof Error ? err.message : String(err) }],\n isError: true\n };\n }\n }\n );\n}\n\nexport function bindMcpTools(\n server: McpServer,\n runtime: Runtime,\n capabilities: readonly Capability[],\n session: McpSessionContext = new McpSessionContext(runtime)\n): void {\n bindRepoSelect(server, runtime, session);\n for (const def of capabilities) {\n const register = server.registerTool.bind(server) as unknown as (\n name: string,\n config: { title?: string; description?: string; inputSchema?: unknown; annotations?: unknown },\n cb: (args: unknown, extra: unknown) => Promise<unknown>\n ) => unknown;\n\n register(\n def.name,\n { description: def.description, inputSchema: mcpInputSchema(def), annotations: mcpAnnotations(def) },\n async (args: unknown) => {\n const rawArgs = (args ?? {}) as Record<string, unknown>;\n const binding = session.getBinding();\n const hasExplicitRepo = rawArgs.repoId !== undefined || Boolean(rawArgs.repoPath);\n const exec = await executeTool(def, rawArgs, {\n runtime,\n source: 'mcp',\n repoId: hasExplicitRepo ? undefined : binding?.repoId,\n repoPath: hasExplicitRepo ? undefined : binding?.repoPath\n });\n return {\n content: [{ type: 'text' as const, text: formatResultForMcp(exec) }],\n isError: !exec.success\n };\n }\n );\n }\n}\n","/**\n * MCP 文本输出:默认摘要,正文靠 path / detail。\n * Web REST / executeTool.result 仍是完整对象,不走这里。\n */\nimport { suggestNext } from '@shaohui_jin/git-cockpit-core';\nimport type { ExecutionResult } from '@shaohui_jin/git-cockpit-core';\n\nconst FILE_CONTENT_LIMIT = 64 * 1024;\n\nexport function wantsDetail(args: Record<string, unknown> | undefined): boolean {\n return args?.detail === true;\n}\n\nfunction hasPath(args: Record<string, unknown> | undefined): boolean {\n return typeof args?.path === 'string' && args.path.trim().length > 0;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction stripDiffPatch(diff: unknown): unknown {\n const d = asRecord(diff);\n if (!d || !('rawPatch' in d)) return diff;\n const { rawPatch: _raw, ...rest } = d;\n return { ...rest, detailAvailable: true };\n}\n\nfunction stripRehearseBodies(result: unknown): unknown {\n const r = asRecord(result);\n if (!r) return result;\n const files = Array.isArray(r.conflictFiles) ? r.conflictFiles : [];\n const paths = files.map((f) => {\n const row = asRecord(f);\n return {\n path: row?.path ?? '',\n contentConflict: row?.contentConflict ?? true,\n hunks: []\n };\n });\n return {\n ...r,\n conflictFiles: paths,\n detailAvailable: true\n };\n}\n\nfunction attachNext(tool: string, result: unknown, args: Record<string, unknown>, dryRun: boolean): unknown {\n const rec = asRecord(result);\n if (!rec) return result;\n if (Array.isArray(rec.next) && rec.next.length) return result;\n const next = suggestNext(tool, result, args, { dryRun });\n if (!next.length) return result;\n return { ...rec, next };\n}\n\n/** 把完整工具结果收成 Agent 默认可消化的形状 */\nexport function summarizeForAgent(\n tool: string,\n result: unknown,\n args: Record<string, unknown> = {},\n dryRun = false\n): unknown {\n if (result == null) return result;\n const detail = wantsDetail(args);\n const pathSet = hasPath(args);\n\n let summarized: unknown = result;\n if (tool === 'git_diff' || tool === 'git_stash_show') {\n summarized = detail || pathSet ? result : stripDiffPatch(result);\n } else if (tool === 'git_show') {\n if (detail || pathSet) {\n summarized = result;\n } else {\n const row = asRecord(result);\n summarized = row ? { ...row, diff: stripDiffPatch(row.diff) } : result;\n }\n } else if (tool === 'git_merge_rehearse') {\n summarized = detail || pathSet ? result : stripRehearseBodies(result);\n } else if (tool === 'git_repo_overview') {\n if (!detail) {\n const row = asRecord(result);\n const repos = Array.isArray(row?.repos) ? row.repos : [];\n summarized = {\n ...row,\n repos: repos.map((item) => {\n const rec = asRecord(item);\n if (!rec) return item;\n const { activity: _days, ...rest } = rec;\n return rest;\n })\n };\n }\n } else if (tool === 'git_file_content') {\n if (!detail) {\n const row = asRecord(result);\n const content = typeof row?.content === 'string' ? row.content : '';\n const bytes = Buffer.byteLength(content, 'utf8');\n if (bytes > FILE_CONTENT_LIMIT) {\n summarized = {\n path: args.path,\n truncated: true,\n bytes,\n detailAvailable: true\n };\n }\n }\n }\n\n return attachNext(tool, summarized, args, dryRun);\n}\n\n/** 把干运行/真实结果统一包成 MCP 文本输出 */\nexport function formatResultForMcp(exec: ExecutionResult): string {\n if (!exec.success) {\n const prefix = exec.error?.requiredApproval ? '该工具已禁用' : '操作失败';\n return `${prefix}:${exec.error?.message ?? '未知错误'}`;\n }\n const parts: string[] = [];\n if (exec.dryRun) {\n parts.push('[dry-run 预览] 未实际执行任何操作');\n if (exec.source === 'mcp') {\n parts.push('预览已记下。把命令给人看,等人明确说执行后,再用相同参数 dry_run=false。');\n }\n }\n if (exec.backupCreated) {\n parts.push(\n `[自动备份] 分支 ${exec.backupCreated.branch ?? '(未创建)'}${exec.backupCreated.stashRef ? `,stash ${exec.backupCreated.stashRef}` : ''}`\n );\n }\n const summarized = summarizeForAgent(exec.tool, exec.result, exec.args ?? {}, exec.dryRun);\n const rec = asRecord(summarized);\n const next = Array.isArray(rec?.next) ? rec.next : [];\n if (next.length) {\n const names = next\n .map((step) => asRecord(step)?.tool)\n .filter((n): n is string => typeof n === 'string' && n.length > 0);\n if (names.length) parts.push(`next: ${names.join(' → ')}`);\n }\n const payload = typeof summarized === 'string' ? summarized : JSON.stringify(summarized);\n parts.push(payload);\n return parts.join('\\n\\n');\n}\n","/**\n * 工具执行层:MCP / Web / CLI 共用。安全链路在 core executeCapability。\n */\nimport { executeCapability } from '@shaohui_jin/git-cockpit-core';\nimport type { Capability, ExecutionResult } from '@shaohui_jin/git-cockpit-core';\nimport { hostFromRuntime } from '../host.ts';\nimport type { Runtime } from '../runtime.ts';\n\nexport type ToolDef = Capability;\nexport type ToolExecutionResult = ExecutionResult;\n\nexport interface ToolExecutionContext {\n runtime: Runtime;\n source: 'mcp' | 'web' | 'cli' | 'chat';\n /** 上层已解析的仓库(Web 路由提供 repoId;MCP session 提供 repoId/repoPath 默认值) */\n repoId?: number;\n repoPath?: string;\n}\n\n/**\n * 执行一个工具(含统一安全链路)。返回标准化结果,不抛异常(错误编码进 error 字段)。\n */\nexport async function executeTool(\n def: ToolDef,\n rawArgs: Record<string, unknown>,\n ctx: ToolExecutionContext\n): Promise<ToolExecutionResult> {\n return executeCapability(def, rawArgs, {\n source: ctx.source,\n repoId: ctx.repoId,\n repoPath: ctx.repoPath,\n host: hostFromRuntime(ctx.runtime)\n });\n}\n\nexport { formatResultForMcp, summarizeForAgent } from './format.ts';\n","import { GitOperationError } from '@shaohui_jin/git-cockpit-core';\nimport type { CapabilityHost } from '@shaohui_jin/git-cockpit-core';\nimport { collectRepoOverviews } from './overview.ts';\nimport type { Runtime } from './runtime.ts';\n\n/** 把进程壳 Runtime 收成 core executor 认识的 CapabilityHost */\nexport function hostFromRuntime(runtime: Runtime): CapabilityHost {\n return {\n config: runtime.config,\n permissions: runtime.permissions,\n auditLogger: runtime.auditLogger,\n eventBus: runtime.eventBus,\n jobs: runtime.jobs,\n async resolveRepo({ repoId, repoPath, argsPath }) {\n if (repoId !== undefined) {\n const handle = await runtime.repoManager.getById(repoId);\n if (handle) return { git: handle.service, repoPath: handle.service.repoPath };\n throw new GitOperationError(`仓库不存在: id=${repoId}`, 'REPO_NOT_FOUND');\n }\n if (repoPath) {\n const handle = runtime.repoManager.getByPath(repoPath);\n return { git: handle.service, repoPath: handle.service.repoPath };\n }\n if (argsPath) {\n const handle = await runtime.repoManager.open(argsPath);\n return { git: handle.service, repoPath: handle.service.repoPath };\n }\n const current = await runtime.repoManager.getCurrent();\n if (!current) {\n throw new GitOperationError('尚未打开任何仓库。请先打开仓库或提供 repoPath 参数。', 'NO_ACTIVE_REPO');\n }\n return { git: current.service, repoPath: current.service.repoPath };\n },\n listOverviews: () => collectRepoOverviews(runtime)\n };\n}\n","import { emptyActivity, type RepoOverview } from '@shaohui_jin/git-cockpit-core';\nimport type { Runtime } from './runtime.ts';\n\nconst CONCURRENCY = 4;\n\nexport function unavailableOverview(path: string, extra: Partial<RepoOverview> = {}): RepoOverview {\n const name =\n path\n .replace(/[\\\\/]+$/, '')\n .split(/[\\\\/]/)\n .pop() || path;\n return {\n path,\n name,\n available: false,\n current: '',\n tracking: null,\n ahead: 0,\n behind: 0,\n dirtyCount: 0,\n conflictCount: 0,\n operation: 'none',\n tempMergeBranchCount: 0,\n ...emptyActivity(),\n ...extra\n };\n}\n\nasync function mapPool<T, R>(items: T[], n: number, fn: (t: T) => Promise<R>): Promise<R[]> {\n const out: R[] = new Array(items.length);\n let i = 0;\n async function worker() {\n while (i < items.length) {\n const idx = i++;\n out[idx] = await fn(items[idx]!);\n }\n }\n const workers = Math.min(n, items.length);\n await Promise.all(Array.from({ length: workers }, () => worker()));\n return out;\n}\n\nasync function overviewForRecord(\n runtime: Runtime,\n record: { id: number; path: string; lastOpenedAt: string }\n): Promise<RepoOverview> {\n try {\n const handle = await runtime.repoManager.getById(record.id);\n if (!handle) {\n return unavailableOverview(record.path, { id: record.id, lastOpenedAt: record.lastOpenedAt });\n }\n const o = await handle.service.getOverview();\n return { ...o, id: record.id, lastOpenedAt: record.lastOpenedAt };\n } catch {\n return unavailableOverview(record.path, { id: record.id, lastOpenedAt: record.lastOpenedAt });\n }\n}\n\n/** 单个已打开仓库的脉搏;不在列表中返回 null */\nexport async function collectRepoOverviewForId(runtime: Runtime, id: number): Promise<RepoOverview | null> {\n const record = runtime.repoManager.list().find((r) => r.id === id);\n if (!record) return null;\n return overviewForRecord(runtime, record);\n}\n\nexport async function collectRepoOverviews(runtime: Runtime): Promise<RepoOverview[]> {\n const repos = runtime.repoManager.list();\n if (!repos.length) return [];\n return mapPool(repos, CONCURRENCY, (r) => overviewForRecord(runtime, r));\n}\n","import type { Runtime } from './runtime.ts';\n\nexport interface McpRepoBinding {\n repoId: number;\n repoPath: string;\n boundAt: string;\n generation: number;\n}\n\n/** MCP 连接私有的仓库上下文;不写入 SQLite,也不与其他连接共享。 */\nexport class McpSessionContext {\n private binding: McpRepoBinding | null = null;\n private generation = 0;\n\n constructor(private readonly runtime: Runtime) {}\n\n getBinding(): McpRepoBinding | null {\n return this.binding ? { ...this.binding } : null;\n }\n\n async select(input: { repoId?: number; repoPath?: string; clear?: boolean }): Promise<McpRepoBinding | null> {\n if (input.clear) {\n if (input.repoId !== undefined || input.repoPath) {\n throw new Error('clear=true 不能同时传 repoId 或 repoPath');\n }\n this.binding = null;\n this.generation += 1;\n return null;\n }\n\n if (input.repoId === undefined && !input.repoPath) {\n throw new Error('需要提供 repoId 或 repoPath;如需清除绑定请传 clear=true');\n }\n if (input.repoId !== undefined && input.repoPath) {\n throw new Error('repoId 与 repoPath 只能传一个');\n }\n\n const handle =\n input.repoId !== undefined\n ? await this.runtime.repoManager.getById(input.repoId)\n : await this.runtime.repoManager.open(input.repoPath!);\n if (!handle) {\n throw new Error(`仓库不存在: id=${input.repoId}`);\n }\n\n this.generation += 1;\n this.binding = {\n repoId: handle.record.id,\n repoPath: handle.service.repoPath,\n boundAt: new Date().toISOString(),\n generation: this.generation\n };\n return this.getBinding();\n }\n}\n","/**\n * 工具注册表:定义在 core/capabilities/git/*.ts。\n * 加载本模块时写入 Capability registry。新工具禁止只写在本包。\n */\nimport { getCapabilityRegistry, TOOL_DEFS, TOOL_DEF_MAP, toolSummaries } from '@shaohui_jin/git-cockpit-core';\nimport type { GitService } from '@shaohui_jin/git-cockpit-core';\n\ngetCapabilityRegistry().registerAll(TOOL_DEFS);\n\nexport { TOOL_DEFS, TOOL_DEF_MAP, toolSummaries };\nexport type { GitService };\n","/**\n * MCP Prompt:。正文抄仓库根 README「Agent 该怎么用」,不许另写流程\n */\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nconst PREAMBLE = [\n '不要用终端 git 代替「预演、落盘、开 PR」。',\n '多仓库时先调用 `git_repo_select({ repoId })` 绑定当前 MCP session;显式 repoId/repoPath 只影响单次调用。',\n '默认只给摘要;要看某文件正文时再说路径,或加 `detail=true`。写操作先干跑(`dry_run=true`)。高风险操作默认关着。'\n].join('\\n');\n\nexport const MCP_PROMPTS = [\n {\n name: 'safe_commit',\n title: '提交',\n description: '先看改了什么,再暂存、再提交。不要一上来就提交。',\n text: [\n PREAMBLE,\n '',\n '1. 提交',\n '先看工作区有没有改、改了什么,再暂存,再提交。不要一上来就提交。',\n '',\n '对应工具:`git_status` → `git_diff` → 按需 `git_add` → `git_commit`。'\n ].join('\\n')\n },\n {\n name: 'merge_preview_apply',\n title: '合不合得进去,以及写进仓库',\n description: '只做预演。有冲突给人网页,不要选边。工作区已经卡住时不要走这条。',\n text: [\n PREAMBLE,\n '',\n '2. 合不合得进去,以及写进仓库',\n '先看工作区是不是已经卡在 merge / rebase 里。卡住了走「工作区已经卡在 merge 或 rebase 里」,不要走这条。',\n '',\n '问能不能合进去时,只做预演,不要在当前工作区执行 merge。`into` 是要合进去的目标(线上),`from` 是你的分支。有冲突时把 webUrl 给人打开合并页(`/#/merge?into=&from=&preview=1`),然后停止。不要选边,不要传 files / resolvedContent,不要在冲突时 git_mr_create(含 dry_run)。',\n '',\n '看完要写进仓库:仅干净合并才用独立目录落盘,不要切走你正在干活的分支。人口头说解决了不算:用原来的 into/from 再预演,认这一对的 `merge/<from>-into-<into>`(或 `origin/merge/…`)。别人的 `merge/*` 当噪音。',\n '',\n '对应工具:`git_merge_preview` / `git_merge_rehearse`(冲突行是谁改的用 `git_merge_blame`,只读)。干净才 `git_apply_resolve`。禁止用 `git_merge` 冒充预演。'\n ].join('\\n')\n },\n {\n name: 'workspace_continue',\n title: '工作区已经卡在 merge 或 rebase 里',\n description: '看状态。人先解决,再继续或放弃。不要传 files,不要用预演落盘去收尾。',\n text: [\n PREAMBLE,\n '',\n '3. 工作区已经卡在 merge 或 rebase 里',\n '这是另一回事:冲突已经发生在当前目录了。看状态。人先在编辑器或网页解决,再继续或放弃。不要传 files。不要用「合不合得进去,以及写进仓库」的落盘去收尾。',\n '',\n '对应工具:`git_status`(看 `operation`)→ `git_merge_continue` / `git_rebase_continue`(不要带 `files`)或 abort。禁止用 `git_apply_resolve`。'\n ].join('\\n')\n },\n {\n name: 'open_mr',\n title: '开 Pull Request / Merge Request',\n description: '先预演;冲突则停。干净才落盘开单。Token 在网页设置里,不要塞进工具参数。',\n text: [\n PREAMBLE,\n '',\n '4. 开 Pull Request / Merge Request',\n '先 `git_merge_preview`。有冲突不要 `git_mr_create`(含 dry_run),把 webUrl 给人在网页选边。干净则落盘并推送临时枝,再 `git_mr_prepare`;只有 `mergeGate.ok` 才 `git_mr_create`,且只 dry_run 预览正文,不要把干跑当确认开单。登录用的 Token 写在网页设置里,不要塞进工具参数。',\n '',\n '对应工具:`git_merge_preview` →(冲突则停)`git_apply_resolve` / `git_push` → `git_mr_prepare` → `git_mr_create`(只 dry_run)。'\n ].join('\\n')\n }\n] as const;\n\nexport function registerMcpPrompts(server: McpServer): void {\n const register = server.registerPrompt.bind(server);\n for (const p of MCP_PROMPTS) {\n register(p.name, { title: p.title, description: p.description }, () => ({\n description: p.description,\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text: p.text } }]\n }));\n }\n}\n","/**\n * MCP Resources:只读、不进审计。形状与 git_repo_overview / git_job_* 摘要同源。\n */\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { Runtime } from '../runtime.ts';\nimport { McpSessionContext } from '../mcpSession.ts';\nimport { collectRepoOverviews } from '../overview.ts';\n\nconst MIME = 'application/json';\n\nfunction jsonContents(uri: string, data: unknown) {\n return {\n contents: [\n {\n uri,\n mimeType: MIME,\n text: JSON.stringify(data)\n }\n ]\n };\n}\n\nfunction overviewWithoutDays(runtime: Runtime) {\n return collectRepoOverviews(runtime).then((repos) =>\n repos.map((row) => {\n const { activity: _days, ...rest } = row;\n return rest;\n })\n );\n}\n\nexport function registerMcpResources(\n server: McpServer,\n runtime: Runtime,\n session: McpSessionContext = new McpSessionContext(runtime)\n): void {\n const register = server.registerResource.bind(server);\n\n register(\n 'repos',\n 'git-cockpit://repos',\n { title: '已打开仓库脉搏', description: '与 git_repo_overview 同摘要,不含每日格子', mimeType: MIME },\n async (uri) => jsonContents(uri.href, { repos: await overviewWithoutDays(runtime) })\n );\n\n register(\n 'repo-current',\n 'git-cockpit://repo/current',\n {\n title: '当前 session 仓',\n description: '当前 MCP session 绑定仓库的工作区摘要;未绑定时兼容最近打开仓库',\n mimeType: MIME\n },\n async (uri) => {\n const binding = session.getBinding();\n const handle = binding\n ? await runtime.repoManager.getById(binding.repoId)\n : await runtime.repoManager.getCurrent();\n if (!handle) {\n return jsonContents(uri.href, { available: false, hint: '没有已打开的仓库' });\n }\n const s = await handle.service.getStatus();\n return jsonContents(uri.href, {\n available: true,\n bound: Boolean(binding),\n scope: binding ? 'session' : 'legacy-current',\n repoId: handle.record.id,\n path: handle.record.path,\n current: s.current,\n tracking: s.tracking,\n ahead: s.ahead,\n behind: s.behind,\n operation: s.operation,\n isClean: s.isClean,\n stagedCount: s.staged.length,\n unstagedCount: s.unstaged.length,\n untrackedCount: s.untracked.length,\n conflictCount: s.conflicted.length,\n conflicted: s.conflicted.slice(0, 20)\n });\n }\n );\n\n register(\n 'jobs',\n 'git-cockpit://jobs',\n { title: '后台任务列表', description: '与 git_job_list 同摘要', mimeType: MIME },\n async (uri) =>\n jsonContents(uri.href, {\n jobs: runtime.jobs.list().map((j) => runtime.jobs.summary(j))\n })\n );\n\n register(\n 'job',\n new ResourceTemplate('git-cockpit://jobs/{id}', {\n list: async () => ({\n resources: runtime.jobs.list().map((j) => ({\n uri: `git-cockpit://jobs/${j.id}`,\n name: j.title,\n description: `${j.kind} ${j.status}`,\n mimeType: MIME\n }))\n })\n }),\n { title: '单条后台任务', description: '状态 + 日志尾 + result 摘要', mimeType: MIME },\n async (uri, variables) => {\n const id = String(variables.id ?? '');\n const job = runtime.jobs.get(id);\n if (!job) {\n return jsonContents(uri.href, { error: '任务不存在', id });\n }\n const summary = runtime.jobs.summary(job);\n return jsonContents(uri.href, {\n ...summary,\n logs: job.logs.slice(-20)\n });\n }\n );\n}\n","import type { GitCockpitConfig } from '@shaohui_jin/git-cockpit-core';\n\nexport interface DaemonEndpoint {\n host: string;\n port: number;\n baseUrl: string;\n healthUrl: string;\n mcpUrl: string;\n}\n\nexport function formatDaemonUrl(host: string, port: number, path = ''): string {\n const authority = `${host.includes(':') && !host.startsWith('[') ? `[${host}]` : host}:${port}`;\n return `http://${authority}${path}`;\n}\n\n/** 统一解析 daemon 地址,start/mcp/bridge 必须使用同一套优先级。 */\nexport function resolveDaemonEndpoint(config: GitCockpitConfig): DaemonEndpoint {\n const port = Number(process.env.GIT_COCKPIT_PORT) || config.server.port;\n const host = process.env.GIT_COCKPIT_HOST ?? config.server.host;\n return {\n host,\n port,\n baseUrl: formatDaemonUrl(host, port),\n healthUrl: formatDaemonUrl(host, port, '/api/health'),\n mcpUrl: formatDaemonUrl(host, port, '/mcp')\n };\n}\n"],"mappings":"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","names":["McpServer","McpServer","session"]}
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
createRuntime,
|
|
5
5
|
createWebServer,
|
|
6
6
|
disposeRuntime
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EGRRNLIR.js";
|
|
8
8
|
import {
|
|
9
9
|
formatDaemonUrl,
|
|
10
10
|
resolveDaemonEndpoint,
|
|
11
11
|
startMcpStdio,
|
|
12
12
|
version
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XZMJUCNP.js";
|
|
14
14
|
|
|
15
15
|
// src/mcpBridge.ts
|
|
16
16
|
var SECRET_HEADER = "X-Git-Cockpit-Secret";
|
|
@@ -403,4 +403,4 @@ async function main(argv) {
|
|
|
403
403
|
export {
|
|
404
404
|
main
|
|
405
405
|
};
|
|
406
|
-
//# sourceMappingURL=chunk-
|
|
406
|
+
//# sourceMappingURL=chunk-Z2I56YTY.js.map
|
package/dist/cli-entry.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-Z2I56YTY.js";
|
|
5
|
+
import "./chunk-EGRRNLIR.js";
|
|
6
|
+
import "./chunk-XZMJUCNP.js";
|
|
7
7
|
|
|
8
8
|
// src/cli-entry.ts
|
|
9
9
|
main(process.argv).then((code) => {
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Z2I56YTY.js";
|
|
5
5
|
import {
|
|
6
6
|
createRuntime,
|
|
7
7
|
disposeRuntime
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-EGRRNLIR.js";
|
|
9
|
+
import "./chunk-XZMJUCNP.js";
|
|
10
10
|
export {
|
|
11
11
|
createRuntime,
|
|
12
12
|
disposeRuntime,
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
main
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Z2I56YTY.js";
|
|
5
5
|
import {
|
|
6
6
|
ConfigStore,
|
|
7
7
|
RepoManager,
|
|
8
8
|
createRuntime,
|
|
9
9
|
createWebServer,
|
|
10
10
|
disposeRuntime
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EGRRNLIR.js";
|
|
12
12
|
import {
|
|
13
13
|
MCP_PROMPTS,
|
|
14
14
|
MCP_SERVER_INFO,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
resolveDaemonEndpoint,
|
|
24
24
|
startMcpStdio,
|
|
25
25
|
toolSummaries
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-XZMJUCNP.js";
|
|
27
27
|
export {
|
|
28
28
|
ConfigStore,
|
|
29
29
|
MCP_PROMPTS,
|
package/dist/mcpServer.js
CHANGED
|
@@ -407,7 +407,7 @@ ${K}`:Ce},labelFill:oe.onSolid,labelFontSize:11,labelFontWeight:600,labelFontFam
|
|
|
407
407
|
`).replace(/\r\n/g,`
|
|
408
408
|
`).replace(/\r/g,`
|
|
409
409
|
`)});function m(M){return M.error?M.error:M.tail?M.tail.split(`
|
|
410
|
-
`).filter(Boolean).at(-1)??"":""}function y(M){const I=new Date(M);if(Number.isNaN(I.getTime()))return M;const O=Date.now()-I.getTime();return O<6e4?"刚刚":O<36e5?`${Math.floor(O/6e4)} 分钟前`:O<24*36e5?I.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"}):I.toLocaleString(void 0,{month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit"})}async function w(M){const I=e.jobs.find(O=>O.id===M);I&&s.value!=="all"&&I.status!==s.value&&(s.value="all"),i.value=M,t.query.id!==M&&await n.replace({path:"/jobs",query:{id:M}}),a.value=!0;try{await e.loadDetail(M)}catch(O){We.error(O instanceof Error?O.message:String(O))}finally{a.value=!1}}function E(M){if(s.value=M,g.value&&(M==="all"||g.value.status===M))return;const I=p.value[0];I?w(I.id):(i.value=null,t.query.id&&n.replace({path:"/jobs"}))}async function x(M){o.value=M;try{await e.cancel(M),We.success("已请求取消")}catch(I){We.error(I instanceof Error?I.message:String(I))}finally{o.value=null}}function _(M){A.value=!0,u.value=(M.url??"").trim(),c.value=(M.destDir??"").trim(),l.value=!0}async function S(){const M=u.value.trim(),I=c.value.trim();if(!M||!I){We.warning("请填写项目地址和保存路径");return}h.value=!0;try{const{job:O}=await TR(M,I);l.value=!1,u.value="",c.value="",await e.load(),Of({id:O.id,kind:"clone",title:`${M} → ${I}`,router:n}),await w(O.id)}catch(O){We.error(O instanceof Error?O.message:String(O))}finally{h.value=!1}}async function T(){await gu(),d.value&&(d.value.scrollTop=d.value.scrollHeight)}return an(v,()=>void T()),an(()=>t.query.id,M=>{typeof M=="string"&&M&&M!==i.value&&w(M)}),la(async()=>{await e.load();const M=t.query.id;if(typeof M=="string"&&M){await w(M);return}const I=p.value[0];I&&await w(I.id)}),(M,I)=>{const O=Ye("el-button"),P=bh("loading");return H(),J("div",dBe,[I[15]||(I[15]=N("h2",{class:"page-title"},"任务",-1)),!be(e).loading&&!be(e).jobs.length?(H(),J("div",fBe,[...I[10]||(I[10]=[N("strong",null,"还没有后台任务",-1),N("p",null,"克隆、矩阵扫描、抓取远程会排在这里。去工作台克隆一个仓。",-1)])])):Xs((H(),J("div",gBe,[N("aside",pBe,[N("div",vBe,[N("button",{type:"button",class:xt(["chip",{on:s.value==="all"}]),onClick:I[0]||(I[0]=z=>E("all"))}," 全部 "+ae(f.value.all),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="running"}]),onClick:I[1]||(I[1]=z=>E("running"))}," 进行中 "+ae(f.value.running),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="error"}]),onClick:I[2]||(I[2]=z=>E("error"))}," 失败 "+ae(f.value.error),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="ok"}]),onClick:I[3]||(I[3]=z=>E("ok"))}," 成功 "+ae(f.value.ok),3),ee(O,{text:"",type:"primary",loading:be(e).loading,onClick:I[4]||(I[4]=z=>be(e).load())},{default:Y(()=>[...I[11]||(I[11]=[ue("刷新",-1)])]),_:1},8,["loading"])]),p.value.length?ke("",!0):(H(),J("p",mBe,"没有这类任务")),(H(!0),J(at,null,Tt(p.value,z=>(H(),J("button",{key:z.id,type:"button",class:xt(["row",[z.status,{on:z.id===i.value}]]),onClick:re=>w(z.id)},[N("div",wBe,[N("b",{class:xt(z.status)},ae(be(TS)(z.status)),3),N("span",null,ae(be(Yf)(z.kind)),1),N("em",null,ae(y(z.startedAt)),1)]),N("div",{class:"title mono",title:be(zl)(z)},ae(be(zl)(z)),9,bBe),N("div",{class:xt(["tail",{err:!!z.error}])},ae(m(z)),3)],10,yBe))),128))]),Xs((H(),J("section",EBe,[g.value?(H(),J(at,{key:0},[N("header",null,[N("b",{class:xt(g.value.status)},ae(be(TS)(g.value.status)),3),N("span",null,ae(be(Yf)(g.value.kind))+" · "+ae(y(g.value.startedAt)),1),I[14]||(I[14]=N("span",{class:"grow"},null,-1)),g.value.status==="running"?(H(),Ke(O,{key:0,type:"danger",plain:"",loading:o.value===g.value.id,onClick:I[5]||(I[5]=z=>x(g.value.id))},{default:Y(()=>[...I[12]||(I[12]=[ue("取消",-1)])]),_:1},8,["loading"])):ke("",!0),g.value.status==="error"&&g.value.kind==="clone"?(H(),Ke(O,{key:1,type:"primary",plain:"",onClick:I[6]||(I[6]=z=>_(g.value))},{default:Y(()=>[...I[13]||(I[13]=[ue("修改并重试",-1)])]),_:1})):ke("",!0)]),N("p",BBe,ae(be(zl)(g.value)),1),g.value.error?(H(),J("p",xBe,ae(g.value.error),1)):ke("",!0),N("pre",{ref_key:"logEl",ref:d},ae(v.value||"(等待输出)"),513)],64)):(H(),J("p",CBe,"点左侧任务看日志"))])),[[P,a.value]])])),[[P,be(e).loading&&!be(e).jobs.length]]),ee(YR,{visible:l.value,"onUpdate:visible":I[7]||(I[7]=z=>l.value=z),url:u.value,"onUpdate:url":I[8]||(I[8]=z=>u.value=z),dest:c.value,"onUpdate:dest":I[9]||(I[9]=z=>c.value=z),retrying:A.value,loading:h.value,onSubmit:S},null,8,["visible","url","dest","retrying","loading"])])}}}),kBe=vr(SBe,[["__scopeId","data-v-62477110"]]),_Be=aA("logs",{state:()=>({logs:[],loading:!1,error:null,limit:50,toolFilter:""}),actions:{async load(){this.loading=!0,this.error=null;try{const{logs:r}=await $5({limit:this.limit,tool:this.toolFilter||void 0});this.logs=r}catch(r){this.error=r instanceof Error?r.message:String(r)}finally{this.loading=!1}}}}),TBe={class:"page logs-page"},MBe={class:"bar gc-glass"},FBe={key:0,class:"empty gc-glass"},IBe={key:1,class:"table gc-glass"},OBe={class:"tool-cell"},LBe={key:0},NBe=["title"],PBe=["title"],RBe=["title"],UBe=ur({__name:"LogsView",setup(r){const e=_Be(),{revision:t}=Xx(),n=Pe(()=>{const l=new Set;for(const A of e.logs)l.add(A.tool);return[...l].sort()});function i(l){return l.includes("reset")||l.includes("clean")||l.includes("force")||l.includes("rebase")?"high":"write"}function s(l){const A=new Date(l);return Number.isNaN(A.getTime())?l:A.toLocaleString()}function a(l){return l==="mcp"?"MCP":l==="web"?"Web":l==="chat"?"Chat":"CLI"}function o(l){if(l==null)return"";if(typeof l=="string")return l;try{const A=JSON.stringify(l);return A===void 0?"":A}catch{return String(l)}}return an(t,()=>{e.load()}),la(()=>{e.load()}),(l,A)=>{const u=Ye("el-option"),c=Ye("el-select"),h=Ye("el-button"),d=bh("loading");return H(),J("div",TBe,[A[7]||(A[7]=N("h2",{class:"page-title"},"操作日志",-1)),N("div",MBe,[N("label",null,[A[2]||(A[2]=ue(" 工具 ",-1)),ee(c,{modelValue:be(e).toolFilter,"onUpdate:modelValue":A[0]||(A[0]=f=>be(e).toolFilter=f),clearable:"",placeholder:"全部",class:"tool-select",onChange:be(e).load},{default:Y(()=>[(H(!0),J(at,null,Tt(n.value,f=>(H(),Ke(u,{key:f,label:f,value:f},null,8,["label","value"]))),128))]),_:1},8,["modelValue","onChange"])]),ee(c,{modelValue:be(e).limit,"onUpdate:modelValue":A[1]||(A[1]=f=>be(e).limit=f),class:"limit-select",onChange:be(e).load},{default:Y(()=>[ee(u,{label:"最近 20 条",value:20}),ee(u,{label:"最近 50 条",value:50}),ee(u,{label:"最近 100 条",value:100})]),_:1},8,["modelValue","onChange"]),ee(h,{loading:be(e).loading,onClick:be(e).load},{default:Y(()=>[...A[3]||(A[3]=[ue("刷新",-1)])]),_:1},8,["loading","onClick"]),A[4]||(A[4]=N("span",{class:"tip"},"只记写入。来源:Web / MCP / CLI / Chat。参数已脱敏。",-1))]),!be(e).loading&&be(e).logs.length===0?(H(),J("div",FBe,[...A[5]||(A[5]=[N("strong",null,"还没有操作日志",-1),N("p",null,"预览确认后的写操作会记在这里。只读预演不记。",-1)])])):Xs((H(),J("div",IBe,[A[6]||(A[6]=N("div",{class:"th"},[N("span",null,"时间"),N("span",null,"来源"),N("span",null,"工具"),N("span",null,"仓库"),N("span",null,"参数(脱敏)"),N("span",null,"结果"),N("span",null,"耗时")],-1)),(H(!0),J(at,null,Tt(be(e).logs,f=>(H(),J("div",{key:f.id,class:"tr"},[N("span",null,ae(s(f.timestamp)),1),N("span",null,ae(a(f.source)),1),N("span",OBe,[N("code",{class:xt(i(f.tool))},ae(f.tool),3),f.dryRun?(H(),J("em",LBe,"dry-run")):ke("",!0)]),N("span",{class:"mono",title:f.repoPath??""},ae(f.repoPath??"—"),9,NBe),N("span",{class:"mono",title:o(f.params)},ae(o(f.params)||"—"),9,PBe),N("span",{class:xt({bad:!!f.error}),title:f.error||f.result},ae(f.error?`失败 · ${f.error}`:`成功${f.result?" · "+f.result:""}`),11,RBe),N("span",null,ae(f.durationMs)+" ms",1)]))),128))])),[[d,be(e).loading]])])}}}),DBe=vr(UBe,[["__scopeId","data-v-ab7b3739"]]),QBe={class:"tmpl"},HBe={class:"tmpl-head"},$Be={class:"tmpl-head-row"},zBe={key:3,class:"mono muted tmpl-file"},GBe={class:"tmpl-head-row"},VBe={key:0,class:"tmpl-paste-bar"},KBe={class:"tmpl-split"},jBe={class:"pane"},WBe={class:"pane-head"},qBe={class:"pane-body"},YBe={key:0,class:"muted"},XBe={class:"tmpl-field-head"},ZBe={class:"mono muted tmpl-id"},JBe={key:2,class:"tmpl-list"},exe={key:3,class:"tmpl-list"},txe={class:"pane"},nxe={class:"pane-head"},rxe={key:0,class:"pane-body preview-body"},ixe={key:1,class:"pane-body muted"},sxe=ur({__name:"MrTemplateSettings",setup(r){const e=ep(),t=Me(null),n=Me(!1),i=Me(""),s=Me(!1),a=Me(!1),o=Me(""),l=Me(p());let A=null;const u=[{value:"textarea",label:"多行文本"},{value:"select",label:"单选"},{value:"checkboxes",label:"勾选"},{value:"markdown",label:"说明(不填)"}],c=Pe(()=>{var Q;return((Q=e.mr)==null?void 0:Q.template)??null}),h=Pe(()=>l.value.fields.some(Q=>Q.type!=="markdown")),d=Pe(()=>JSON.stringify(g(l.value))!==JSON.stringify(g(c.value))),f=Pe(()=>JSON.stringify(g(l.value)));function p(){return{enabled:!1,agentFill:"allow",filename:"",sourceMd:"",fields:[]}}function g(Q){return Q?JSON.parse(JSON.stringify(h5(Q))):p()}function v(Q){return g(Q)}function m(){l.value=v(c.value)}an(c,()=>{const Q=l.value.fields.length===0&&!l.value.sourceMd&&!l.value.filename;(!d.value||Q)&&m()},{immediate:!0}),an(f,()=>{A&&clearTimeout(A),A=setTimeout(()=>void y(),400)}),wh(()=>{A&&clearTimeout(A)});async function y(){if(l.value.fields.length===0){o.value="";return}a.value=!0;try{const Q=await MR(g(l.value));o.value=Q.preview}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}finally{a.value=!1}}async function w(Q,j){l.value={...Q,enabled:Q.fields.some(ne=>ne.type!=="markdown"),agentFill:l.value.agentFill||Q.agentFill},o.value=j}async function E(Q){var de;const j=Q.target,ne=(de=j.files)==null?void 0:de[0];if(j.value="",!ne)return;const xe=await ne.text();await _(xe,ne.name)}async function x(){if(!i.value.trim()){We.warning("请粘贴 Markdown");return}await _(i.value,"pasted.md")}async function _(Q,j){s.value=!0;try{const ne=await V5(Q,j);await w(ne.template,ne.preview),n.value=!1,We.success(`已识别 ${ne.template.fields.length} 个模块,请校对后保存`)}catch(ne){We.error(ne instanceof Error?ne.message:String(ne))}finally{s.value=!1}}function S(Q,j){Q.type=j,Q.required=j!=="markdown"&&(j==="textarea"||j==="select"),j==="select"&&(!Q.options||Q.options.length<2)&&(Q.options=["低","中","高"]),j==="checkboxes"&&(!Q.items||Q.items.length===0)&&(Q.items=[{id:`${Q.id}_1`,label:"(请填写)",required:!1}]),j==="markdown"&&Q.content==null&&(Q.content="")}function T(Q){Q.options=[...Q.options??[],""]}function M(Q,j){Q.options=(Q.options??[]).filter((ne,xe)=>xe!==j)}function I(Q){const j=Q.items??[];Q.items=[...j,{id:`${Q.id}_${j.length+1}`,label:"",required:!1}]}function O(Q,j){Q.items=(Q.items??[]).filter((ne,xe)=>xe!==j)}function P(Q){l.value.fields.splice(Q,1),h.value||(l.value.enabled=!1)}function z(){const Q=l.value.fields.length+1;l.value.fields.push({id:`custom_${Q}`,type:"textarea",label:"",required:!0,placeholder:"(请填写)"})}async function re(){if(l.value.enabled&&!h.value){We.warning("没有可填写字段时不能启用规范");return}try{await e.save({mr:{template:g(l.value)}}),m(),We.success("MR 正文规范已保存"),await y()}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}}async function ie(){try{await ao.confirm("清除已保存的正文规范?未启用时开单行为与现在相同。","清除模板",{confirmButtonText:"清除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await e.save({mr:{template:p()}}),m(),We.success("已清除")}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}}return(Q,j)=>{var Ce,ge;const ne=Ye("el-tag"),xe=Ye("el-switch"),de=Ye("el-radio"),we=Ye("el-radio-group"),oe=Ye("el-button"),D=Ye("el-input"),R=Ye("el-option"),K=Ye("el-select"),he=Ye("el-checkbox");return H(),J("div",QBe,[N("header",HBe,[N("div",$Be,[(Ce=c.value)!=null&&Ce.enabled?(H(),Ke(ne,{key:0,size:"small",type:"success",effect:"plain"},{default:Y(()=>[...j[5]||(j[5]=[ue("已启用",-1)])]),_:1})):(ge=c.value)!=null&&ge.fields.length?(H(),Ke(ne,{key:1,size:"small",effect:"plain"},{default:Y(()=>[...j[6]||(j[6]=[ue("已导入未启用",-1)])]),_:1})):(H(),Ke(ne,{key:2,size:"small",effect:"plain"},{default:Y(()=>[...j[7]||(j[7]=[ue("未导入",-1)])]),_:1})),ee(xe,{modelValue:l.value.enabled,"onUpdate:modelValue":j[0]||(j[0]=Z=>l.value.enabled=Z),disabled:!h.value},null,8,["modelValue","disabled"]),j[10]||(j[10]=N("span",{class:"tmpl-label"},"启用规范",-1)),ee(we,{modelValue:l.value.agentFill,"onUpdate:modelValue":j[1]||(j[1]=Z=>l.value.agentFill=Z),disabled:!h.value,class:"tmpl-fill"},{default:Y(()=>[ee(de,{value:"allow"},{default:Y(()=>[...j[8]||(j[8]=[ue("允许 AI 填写",-1)])]),_:1}),ee(de,{value:"forbid"},{default:Y(()=>[...j[9]||(j[9]=[ue("须人工填写",-1)])]),_:1})]),_:1},8,["modelValue","disabled"]),l.value.filename?(H(),J("span",zBe,ae(l.value.filename),1)):ke("",!0)]),N("div",GBe,[ee(oe,{loading:s.value,onClick:j[2]||(j[2]=Z=>{var Le;return(Le=t.value)==null?void 0:Le.click()})},{default:Y(()=>[...j[11]||(j[11]=[ue("导入 Markdown",-1)])]),_:1},8,["loading"]),ee(oe,{onClick:j[3]||(j[3]=Z=>n.value=!n.value)},{default:Y(()=>[...j[12]||(j[12]=[ue("粘贴导入",-1)])]),_:1}),ee(oe,{type:"primary",loading:be(e).saving,disabled:!d.value,onClick:re},{default:Y(()=>[...j[13]||(j[13]=[ue("保存规范",-1)])]),_:1},8,["loading","disabled"]),ee(oe,{disabled:!d.value,onClick:m},{default:Y(()=>[...j[14]||(j[14]=[ue("放弃修改",-1)])]),_:1},8,["disabled"]),ee(oe,{disabled:!c.value,onClick:ie},{default:Y(()=>[...j[15]||(j[15]=[ue("清除",-1)])]),_:1},8,["disabled"]),N("input",{ref_key:"fileInput",ref:t,type:"file",accept:".md,.markdown,text/markdown,text/plain",class:"file-hidden",onChange:E},null,544)])]),n.value?(H(),J("div",VBe,[ee(D,{class:"gc-grow-input",modelValue:i.value,"onUpdate:modelValue":j[4]||(j[4]=Z=>i.value=Z),type:"textarea",rows:4,placeholder:"粘贴 MR 模板 Markdown"},null,8,["modelValue"]),ee(oe,{type:"primary",loading:s.value,onClick:x},{default:Y(()=>[...j[16]||(j[16]=[ue("识别",-1)])]),_:1},8,["loading"])])):ke("",!0),N("div",KBe,[N("section",jBe,[N("div",WBe,[j[18]||(j[18]=N("span",null,"结构",-1)),ee(oe,{link:"",type:"primary",onClick:z},{default:Y(()=>[...j[17]||(j[17]=[ue("添加",-1)])]),_:1})]),N("div",qBe,[l.value.fields.length===0?(H(),J("p",YBe,"还没有章节。导入一份 .md,或点右上角添加。")):ke("",!0),(H(!0),J(at,null,Tt(l.value.fields,(Z,Le)=>(H(),J("article",{key:Z.id+Le,class:"tmpl-field"},[N("div",XBe,[ee(K,{"model-value":Z.type,class:"tmpl-type",onChange:fe=>S(Z,fe)},{default:Y(()=>[(H(),J(at,null,Tt(u,fe=>ee(R,{key:fe.value,label:fe.label,value:fe.value},null,8,["label","value"])),64))]),_:1},8,["model-value","onChange"]),ee(D,{modelValue:Z.label,"onUpdate:modelValue":fe=>Z.label=fe,class:"tmpl-title",placeholder:"标题"},null,8,["modelValue","onUpdate:modelValue"]),Z.type!=="markdown"?(H(),Ke(he,{key:0,modelValue:Z.required,"onUpdate:modelValue":fe=>Z.required=fe},{default:Y(()=>[...j[19]||(j[19]=[ue("必填",-1)])]),_:1},8,["modelValue","onUpdate:modelValue"])):ke("",!0),N("span",ZBe,ae(Z.id),1),ee(oe,{link:"",type:"danger",onClick:fe=>P(Le)},{default:Y(()=>[...j[20]||(j[20]=[ue("删除",-1)])]),_:1},8,["onClick"])]),Z.type!=="markdown"?(H(),Ke(D,{key:0,modelValue:Z.help,"onUpdate:modelValue":fe=>Z.help=fe,type:"textarea",rows:2,placeholder:"说明(开单时给填写人看)"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0),Z.type==="textarea"?(H(),Ke(D,{key:1,modelValue:Z.placeholder,"onUpdate:modelValue":fe=>Z.placeholder=fe,placeholder:"占位"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0),Z.type==="select"?(H(),J("div",JBe,[(H(!0),J(at,null,Tt(Z.options,(fe,Te)=>(H(),J("div",{key:Te,class:"tmpl-list-row"},[ee(D,{modelValue:Z.options[Te],"onUpdate:modelValue":nt=>Z.options[Te]=nt,placeholder:"选项"},null,8,["modelValue","onUpdate:modelValue"]),ee(oe,{link:"",onClick:nt=>M(Z,Te)},{default:Y(()=>[...j[21]||(j[21]=[ue("删",-1)])]),_:1},8,["onClick"])]))),128)),ee(oe,{link:"",type:"primary",onClick:fe=>T(Z)},{default:Y(()=>[...j[22]||(j[22]=[ue("添加选项",-1)])]),_:1},8,["onClick"])])):ke("",!0),Z.type==="checkboxes"?(H(),J("div",exe,[(H(!0),J(at,null,Tt(Z.items,(fe,Te)=>(H(),J("div",{key:fe.id,class:"tmpl-list-row"},[ee(D,{modelValue:fe.label,"onUpdate:modelValue":nt=>fe.label=nt,placeholder:"条目"},null,8,["modelValue","onUpdate:modelValue"]),ee(he,{modelValue:fe.required,"onUpdate:modelValue":nt=>fe.required=nt},{default:Y(()=>[...j[23]||(j[23]=[ue("必勾",-1)])]),_:1},8,["modelValue","onUpdate:modelValue"]),ee(oe,{link:"",onClick:nt=>O(Z,Te)},{default:Y(()=>[...j[24]||(j[24]=[ue("删",-1)])]),_:1},8,["onClick"])]))),128)),ee(oe,{link:"",type:"primary",onClick:fe=>I(Z)},{default:Y(()=>[...j[25]||(j[25]=[ue("添加条目",-1)])]),_:1},8,["onClick"])])):ke("",!0),Z.type==="markdown"?(H(),Ke(D,{key:4,modelValue:Z.content,"onUpdate:modelValue":fe=>Z.content=fe,type:"textarea",rows:6,placeholder:"说明正文"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0)]))),128))])]),N("section",txe,[N("div",nxe,[j[27]||(j[27]=N("span",null,"预览",-1)),ee(oe,{link:"",type:"primary",disabled:l.value.fields.length===0,loading:a.value,onClick:y},{default:Y(()=>[...j[26]||(j[26]=[ue(" 刷新 ",-1)])]),_:1},8,["disabled","loading"])]),o.value?(H(),J("pre",rxe,ae(o.value),1)):(H(),J("p",ixe,"导入或修改字段后,这里显示渲染出的 MR 正文。"))])])])}}}),axe=vr(sxe,[["__scopeId","data-v-9166902d"]]),oxe={class:"page settings-page"},lxe={key:0,class:"page-version"},Axe={class:"gc-chip-tabs"},uxe=["onClick"],cxe={class:"stack"},hxe={class:"gc-glass pad"},dxe={class:"remote-bar"},fxe=["title"],gxe={key:1,class:"mr-hint"},pxe={key:0,class:"mr-platform-row"},vxe=["onClick","onKeydown"],mxe={class:"opt-head"},yxe={key:0,class:"mr-stack"},wxe={key:0,class:"mr-kv"},bxe={key:1,class:"mr-kv"},Exe={key:1,class:"mr-stack"},Bxe={key:0,class:"mr-hint"},xxe={class:"mr-field"},Cxe={key:1,class:"mr-hint"},Sxe={class:"mr-hint"},kxe={class:"mr-inline"},_xe={key:2,class:"mr-hint"},Txe={key:0,class:"gc-glass pad"},Mxe={class:"host-list"},Fxe={class:"host-row"},Ixe={class:"mono"},Oxe={class:"template-pane"},Lxe={class:"stack"},Nxe={class:"gc-glass pad"},Pxe={class:"kv"},Rxe={class:"kv"},Uxe={class:"kv"},Dxe={class:"kv"},Qxe={class:"row"},Hxe={class:"git-split"},$xe={class:"gc-glass pad git-side"},zxe={class:"kv"},Gxe={class:"row"},Vxe={key:0,class:"row"},Kxe={class:"gc-glass pad git-main"},jxe={class:"git-pack-h"},Wxe={class:"git-wall"},qxe=["title","onClick"],Yxe="https://cli.github.com/",Xxe="https://gitlab.com/gitlab-org/cli/-/releases",Zxe="https://github.com/settings/tokens/new?scopes=repo&description=Git%20Cockpit",Jxe=ur({__name:"SettingsView",setup(r){const e=ep(),t=Ui(),n=Jg(),i=rl(),s=Pe({get:()=>{const ye=n.query.tab;return ye==="git"||ye==="template"||ye==="llm"?ye:"mr"},set:ye=>{const V=ye==="mr"?{}:{tab:ye};i.replace({path:"/settings",query:V})}}),a=nu({disabledTools:[],dryRunDefault:!1,allowedReposText:""}),o=Me(!1),l=Me("browser"),A=Me("origin"),u=Me(""),c=Me(""),h=Me("gpt-4.1"),d=Me(""),f=Me(""),p=Me(!1),g=Me(!1),v=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.cli.gh.installUrl)||Yxe}),m=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.cli.glab.installUrl)||Xxe}),y=Pe(()=>{const ye=e.permissions;return ye?JSON.stringify([...a.disabledTools].sort())!==JSON.stringify([...ye.disabledTools].sort())||a.dryRunDefault!==ye.dryRunDefault||nt(a.allowedReposText).join(`
|
|
410
|
+
`).filter(Boolean).at(-1)??"":""}function y(M){const I=new Date(M);if(Number.isNaN(I.getTime()))return M;const O=Date.now()-I.getTime();return O<6e4?"刚刚":O<36e5?`${Math.floor(O/6e4)} 分钟前`:O<24*36e5?I.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"}):I.toLocaleString(void 0,{month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit"})}async function w(M){const I=e.jobs.find(O=>O.id===M);I&&s.value!=="all"&&I.status!==s.value&&(s.value="all"),i.value=M,t.query.id!==M&&await n.replace({path:"/jobs",query:{id:M}}),a.value=!0;try{await e.loadDetail(M)}catch(O){We.error(O instanceof Error?O.message:String(O))}finally{a.value=!1}}function E(M){if(s.value=M,g.value&&(M==="all"||g.value.status===M))return;const I=p.value[0];I?w(I.id):(i.value=null,t.query.id&&n.replace({path:"/jobs"}))}async function x(M){o.value=M;try{await e.cancel(M),We.success("已请求取消")}catch(I){We.error(I instanceof Error?I.message:String(I))}finally{o.value=null}}function _(M){A.value=!0,u.value=(M.url??"").trim(),c.value=(M.destDir??"").trim(),l.value=!0}async function S(){const M=u.value.trim(),I=c.value.trim();if(!M||!I){We.warning("请填写项目地址和保存路径");return}h.value=!0;try{const{job:O}=await TR(M,I);l.value=!1,u.value="",c.value="",await e.load(),Of({id:O.id,kind:"clone",title:`${M} → ${I}`,router:n}),await w(O.id)}catch(O){We.error(O instanceof Error?O.message:String(O))}finally{h.value=!1}}async function T(){await gu(),d.value&&(d.value.scrollTop=d.value.scrollHeight)}return an(v,()=>void T()),an(()=>t.query.id,M=>{typeof M=="string"&&M&&M!==i.value&&w(M)}),la(async()=>{await e.load();const M=t.query.id;if(typeof M=="string"&&M){await w(M);return}const I=p.value[0];I&&await w(I.id)}),(M,I)=>{const O=Ye("el-button"),P=bh("loading");return H(),J("div",dBe,[I[15]||(I[15]=N("h2",{class:"page-title"},"任务",-1)),!be(e).loading&&!be(e).jobs.length?(H(),J("div",fBe,[...I[10]||(I[10]=[N("strong",null,"还没有后台任务",-1),N("p",null,"克隆、矩阵扫描、抓取远程会排在这里。去工作台克隆一个仓。",-1)])])):Xs((H(),J("div",gBe,[N("aside",pBe,[N("div",vBe,[N("button",{type:"button",class:xt(["chip",{on:s.value==="all"}]),onClick:I[0]||(I[0]=z=>E("all"))}," 全部 "+ae(f.value.all),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="running"}]),onClick:I[1]||(I[1]=z=>E("running"))}," 进行中 "+ae(f.value.running),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="error"}]),onClick:I[2]||(I[2]=z=>E("error"))}," 失败 "+ae(f.value.error),3),N("button",{type:"button",class:xt(["chip",{on:s.value==="ok"}]),onClick:I[3]||(I[3]=z=>E("ok"))}," 成功 "+ae(f.value.ok),3),ee(O,{text:"",type:"primary",loading:be(e).loading,onClick:I[4]||(I[4]=z=>be(e).load())},{default:Y(()=>[...I[11]||(I[11]=[ue("刷新",-1)])]),_:1},8,["loading"])]),p.value.length?ke("",!0):(H(),J("p",mBe,"没有这类任务")),(H(!0),J(at,null,Tt(p.value,z=>(H(),J("button",{key:z.id,type:"button",class:xt(["row",[z.status,{on:z.id===i.value}]]),onClick:re=>w(z.id)},[N("div",wBe,[N("b",{class:xt(z.status)},ae(be(TS)(z.status)),3),N("span",null,ae(be(Yf)(z.kind)),1),N("em",null,ae(y(z.startedAt)),1)]),N("div",{class:"title mono",title:be(zl)(z)},ae(be(zl)(z)),9,bBe),N("div",{class:xt(["tail",{err:!!z.error}])},ae(m(z)),3)],10,yBe))),128))]),Xs((H(),J("section",EBe,[g.value?(H(),J(at,{key:0},[N("header",null,[N("b",{class:xt(g.value.status)},ae(be(TS)(g.value.status)),3),N("span",null,ae(be(Yf)(g.value.kind))+" · "+ae(y(g.value.startedAt)),1),I[14]||(I[14]=N("span",{class:"grow"},null,-1)),g.value.status==="running"?(H(),Ke(O,{key:0,type:"danger",plain:"",loading:o.value===g.value.id,onClick:I[5]||(I[5]=z=>x(g.value.id))},{default:Y(()=>[...I[12]||(I[12]=[ue("取消",-1)])]),_:1},8,["loading"])):ke("",!0),g.value.status==="error"&&g.value.kind==="clone"?(H(),Ke(O,{key:1,type:"primary",plain:"",onClick:I[6]||(I[6]=z=>_(g.value))},{default:Y(()=>[...I[13]||(I[13]=[ue("修改并重试",-1)])]),_:1})):ke("",!0)]),N("p",BBe,ae(be(zl)(g.value)),1),g.value.error?(H(),J("p",xBe,ae(g.value.error),1)):ke("",!0),N("pre",{ref_key:"logEl",ref:d},ae(v.value||"(等待输出)"),513)],64)):(H(),J("p",CBe,"点左侧任务看日志"))])),[[P,a.value]])])),[[P,be(e).loading&&!be(e).jobs.length]]),ee(YR,{visible:l.value,"onUpdate:visible":I[7]||(I[7]=z=>l.value=z),url:u.value,"onUpdate:url":I[8]||(I[8]=z=>u.value=z),dest:c.value,"onUpdate:dest":I[9]||(I[9]=z=>c.value=z),retrying:A.value,loading:h.value,onSubmit:S},null,8,["visible","url","dest","retrying","loading"])])}}}),kBe=vr(SBe,[["__scopeId","data-v-62477110"]]),_Be=aA("logs",{state:()=>({logs:[],loading:!1,error:null,limit:50,toolFilter:""}),actions:{async load(){this.loading=!0,this.error=null;try{const{logs:r}=await $5({limit:this.limit,tool:this.toolFilter||void 0});this.logs=r}catch(r){this.error=r instanceof Error?r.message:String(r)}finally{this.loading=!1}}}}),TBe={class:"page logs-page"},MBe={class:"bar gc-glass"},FBe={key:0,class:"empty gc-glass"},IBe={key:1,class:"table gc-glass"},OBe={class:"tool-cell"},LBe={key:0},NBe=["title"],PBe=["title"],RBe=["title"],UBe=ur({__name:"LogsView",setup(r){const e=_Be(),{revision:t}=Xx(),n=Pe(()=>{const l=new Set;for(const A of e.logs)l.add(A.tool);return[...l].sort()});function i(l){return l.includes("reset")||l.includes("clean")||l.includes("force")||l.includes("rebase")?"high":"write"}function s(l){const A=new Date(l);return Number.isNaN(A.getTime())?l:A.toLocaleString()}function a(l){return l==="mcp"?"MCP":l==="web"?"Web":l==="chat"?"Chat":"CLI"}function o(l){if(l==null)return"";if(typeof l=="string")return l;try{const A=JSON.stringify(l);return A===void 0?"":A}catch{return String(l)}}return an(t,()=>{e.load()}),la(()=>{e.load()}),(l,A)=>{const u=Ye("el-option"),c=Ye("el-select"),h=Ye("el-button"),d=bh("loading");return H(),J("div",TBe,[A[7]||(A[7]=N("h2",{class:"page-title"},"操作日志",-1)),N("div",MBe,[N("label",null,[A[2]||(A[2]=ue(" 工具 ",-1)),ee(c,{modelValue:be(e).toolFilter,"onUpdate:modelValue":A[0]||(A[0]=f=>be(e).toolFilter=f),clearable:"",placeholder:"全部",class:"tool-select",onChange:be(e).load},{default:Y(()=>[(H(!0),J(at,null,Tt(n.value,f=>(H(),Ke(u,{key:f,label:f,value:f},null,8,["label","value"]))),128))]),_:1},8,["modelValue","onChange"])]),ee(c,{modelValue:be(e).limit,"onUpdate:modelValue":A[1]||(A[1]=f=>be(e).limit=f),class:"limit-select",onChange:be(e).load},{default:Y(()=>[ee(u,{label:"最近 20 条",value:20}),ee(u,{label:"最近 50 条",value:50}),ee(u,{label:"最近 100 条",value:100})]),_:1},8,["modelValue","onChange"]),ee(h,{loading:be(e).loading,onClick:be(e).load},{default:Y(()=>[...A[3]||(A[3]=[ue("刷新",-1)])]),_:1},8,["loading","onClick"]),A[4]||(A[4]=N("span",{class:"tip"},"只记写入。来源:Web / MCP / CLI / Chat。参数已脱敏。",-1))]),!be(e).loading&&be(e).logs.length===0?(H(),J("div",FBe,[...A[5]||(A[5]=[N("strong",null,"还没有操作日志",-1),N("p",null,"预览确认后的写操作会记在这里。只读预演不记。",-1)])])):Xs((H(),J("div",IBe,[A[6]||(A[6]=N("div",{class:"th"},[N("span",null,"时间"),N("span",null,"来源"),N("span",null,"工具"),N("span",null,"仓库"),N("span",null,"参数(脱敏)"),N("span",null,"结果"),N("span",null,"耗时")],-1)),(H(!0),J(at,null,Tt(be(e).logs,f=>(H(),J("div",{key:f.id,class:"tr"},[N("span",null,ae(s(f.timestamp)),1),N("span",null,ae(a(f.source)),1),N("span",OBe,[N("code",{class:xt(i(f.tool))},ae(f.tool),3),f.dryRun?(H(),J("em",LBe,"dry-run")):ke("",!0)]),N("span",{class:"mono",title:f.repoPath??""},ae(f.repoPath??"—"),9,NBe),N("span",{class:"mono",title:o(f.params)},ae(o(f.params)||"—"),9,PBe),N("span",{class:xt({bad:!!f.error}),title:f.error||f.result},ae(f.error?`失败 · ${f.error}`:`成功${f.result?" · "+f.result:""}`),11,RBe),N("span",null,ae(f.durationMs)+" ms",1)]))),128))])),[[d,be(e).loading]])])}}}),DBe=vr(UBe,[["__scopeId","data-v-2b339ef2"]]),QBe={class:"tmpl"},HBe={class:"tmpl-head"},$Be={class:"tmpl-head-row"},zBe={key:3,class:"mono muted tmpl-file"},GBe={class:"tmpl-head-row"},VBe={key:0,class:"tmpl-paste-bar"},KBe={class:"tmpl-split"},jBe={class:"pane"},WBe={class:"pane-head"},qBe={class:"pane-body"},YBe={key:0,class:"muted"},XBe={class:"tmpl-field-head"},ZBe={class:"mono muted tmpl-id"},JBe={key:2,class:"tmpl-list"},exe={key:3,class:"tmpl-list"},txe={class:"pane"},nxe={class:"pane-head"},rxe={key:0,class:"pane-body preview-body"},ixe={key:1,class:"pane-body muted"},sxe=ur({__name:"MrTemplateSettings",setup(r){const e=ep(),t=Me(null),n=Me(!1),i=Me(""),s=Me(!1),a=Me(!1),o=Me(""),l=Me(p());let A=null;const u=[{value:"textarea",label:"多行文本"},{value:"select",label:"单选"},{value:"checkboxes",label:"勾选"},{value:"markdown",label:"说明(不填)"}],c=Pe(()=>{var Q;return((Q=e.mr)==null?void 0:Q.template)??null}),h=Pe(()=>l.value.fields.some(Q=>Q.type!=="markdown")),d=Pe(()=>JSON.stringify(g(l.value))!==JSON.stringify(g(c.value))),f=Pe(()=>JSON.stringify(g(l.value)));function p(){return{enabled:!1,agentFill:"allow",filename:"",sourceMd:"",fields:[]}}function g(Q){return Q?JSON.parse(JSON.stringify(h5(Q))):p()}function v(Q){return g(Q)}function m(){l.value=v(c.value)}an(c,()=>{const Q=l.value.fields.length===0&&!l.value.sourceMd&&!l.value.filename;(!d.value||Q)&&m()},{immediate:!0}),an(f,()=>{A&&clearTimeout(A),A=setTimeout(()=>void y(),400)}),wh(()=>{A&&clearTimeout(A)});async function y(){if(l.value.fields.length===0){o.value="";return}a.value=!0;try{const Q=await MR(g(l.value));o.value=Q.preview}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}finally{a.value=!1}}async function w(Q,j){l.value={...Q,enabled:Q.fields.some(ne=>ne.type!=="markdown"),agentFill:l.value.agentFill||Q.agentFill},o.value=j}async function E(Q){var de;const j=Q.target,ne=(de=j.files)==null?void 0:de[0];if(j.value="",!ne)return;const xe=await ne.text();await _(xe,ne.name)}async function x(){if(!i.value.trim()){We.warning("请粘贴 Markdown");return}await _(i.value,"pasted.md")}async function _(Q,j){s.value=!0;try{const ne=await V5(Q,j);await w(ne.template,ne.preview),n.value=!1,We.success(`已识别 ${ne.template.fields.length} 个模块,请校对后保存`)}catch(ne){We.error(ne instanceof Error?ne.message:String(ne))}finally{s.value=!1}}function S(Q,j){Q.type=j,Q.required=j!=="markdown"&&(j==="textarea"||j==="select"),j==="select"&&(!Q.options||Q.options.length<2)&&(Q.options=["低","中","高"]),j==="checkboxes"&&(!Q.items||Q.items.length===0)&&(Q.items=[{id:`${Q.id}_1`,label:"(请填写)",required:!1}]),j==="markdown"&&Q.content==null&&(Q.content="")}function T(Q){Q.options=[...Q.options??[],""]}function M(Q,j){Q.options=(Q.options??[]).filter((ne,xe)=>xe!==j)}function I(Q){const j=Q.items??[];Q.items=[...j,{id:`${Q.id}_${j.length+1}`,label:"",required:!1}]}function O(Q,j){Q.items=(Q.items??[]).filter((ne,xe)=>xe!==j)}function P(Q){l.value.fields.splice(Q,1),h.value||(l.value.enabled=!1)}function z(){const Q=l.value.fields.length+1;l.value.fields.push({id:`custom_${Q}`,type:"textarea",label:"",required:!0,placeholder:"(请填写)"})}async function re(){if(l.value.enabled&&!h.value){We.warning("没有可填写字段时不能启用规范");return}try{await e.save({mr:{template:g(l.value)}}),m(),We.success("MR 正文规范已保存"),await y()}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}}async function ie(){try{await ao.confirm("清除已保存的正文规范?未启用时开单行为与现在相同。","清除模板",{confirmButtonText:"清除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await e.save({mr:{template:p()}}),m(),We.success("已清除")}catch(Q){We.error(Q instanceof Error?Q.message:String(Q))}}return(Q,j)=>{var Ce,ge;const ne=Ye("el-tag"),xe=Ye("el-switch"),de=Ye("el-radio"),we=Ye("el-radio-group"),oe=Ye("el-button"),D=Ye("el-input"),R=Ye("el-option"),K=Ye("el-select"),he=Ye("el-checkbox");return H(),J("div",QBe,[N("header",HBe,[N("div",$Be,[(Ce=c.value)!=null&&Ce.enabled?(H(),Ke(ne,{key:0,size:"small",type:"success",effect:"plain"},{default:Y(()=>[...j[5]||(j[5]=[ue("已启用",-1)])]),_:1})):(ge=c.value)!=null&&ge.fields.length?(H(),Ke(ne,{key:1,size:"small",effect:"plain"},{default:Y(()=>[...j[6]||(j[6]=[ue("已导入未启用",-1)])]),_:1})):(H(),Ke(ne,{key:2,size:"small",effect:"plain"},{default:Y(()=>[...j[7]||(j[7]=[ue("未导入",-1)])]),_:1})),ee(xe,{modelValue:l.value.enabled,"onUpdate:modelValue":j[0]||(j[0]=Z=>l.value.enabled=Z),disabled:!h.value},null,8,["modelValue","disabled"]),j[10]||(j[10]=N("span",{class:"tmpl-label"},"启用规范",-1)),ee(we,{modelValue:l.value.agentFill,"onUpdate:modelValue":j[1]||(j[1]=Z=>l.value.agentFill=Z),disabled:!h.value,class:"tmpl-fill"},{default:Y(()=>[ee(de,{value:"allow"},{default:Y(()=>[...j[8]||(j[8]=[ue("允许 AI 填写",-1)])]),_:1}),ee(de,{value:"forbid"},{default:Y(()=>[...j[9]||(j[9]=[ue("须人工填写",-1)])]),_:1})]),_:1},8,["modelValue","disabled"]),l.value.filename?(H(),J("span",zBe,ae(l.value.filename),1)):ke("",!0)]),N("div",GBe,[ee(oe,{loading:s.value,onClick:j[2]||(j[2]=Z=>{var Le;return(Le=t.value)==null?void 0:Le.click()})},{default:Y(()=>[...j[11]||(j[11]=[ue("导入 Markdown",-1)])]),_:1},8,["loading"]),ee(oe,{onClick:j[3]||(j[3]=Z=>n.value=!n.value)},{default:Y(()=>[...j[12]||(j[12]=[ue("粘贴导入",-1)])]),_:1}),ee(oe,{type:"primary",loading:be(e).saving,disabled:!d.value,onClick:re},{default:Y(()=>[...j[13]||(j[13]=[ue("保存规范",-1)])]),_:1},8,["loading","disabled"]),ee(oe,{disabled:!d.value,onClick:m},{default:Y(()=>[...j[14]||(j[14]=[ue("放弃修改",-1)])]),_:1},8,["disabled"]),ee(oe,{disabled:!c.value,onClick:ie},{default:Y(()=>[...j[15]||(j[15]=[ue("清除",-1)])]),_:1},8,["disabled"]),N("input",{ref_key:"fileInput",ref:t,type:"file",accept:".md,.markdown,text/markdown,text/plain",class:"file-hidden",onChange:E},null,544)])]),n.value?(H(),J("div",VBe,[ee(D,{class:"gc-grow-input",modelValue:i.value,"onUpdate:modelValue":j[4]||(j[4]=Z=>i.value=Z),type:"textarea",rows:4,placeholder:"粘贴 MR 模板 Markdown"},null,8,["modelValue"]),ee(oe,{type:"primary",loading:s.value,onClick:x},{default:Y(()=>[...j[16]||(j[16]=[ue("识别",-1)])]),_:1},8,["loading"])])):ke("",!0),N("div",KBe,[N("section",jBe,[N("div",WBe,[j[18]||(j[18]=N("span",null,"结构",-1)),ee(oe,{link:"",type:"primary",onClick:z},{default:Y(()=>[...j[17]||(j[17]=[ue("添加",-1)])]),_:1})]),N("div",qBe,[l.value.fields.length===0?(H(),J("p",YBe,"还没有章节。导入一份 .md,或点右上角添加。")):ke("",!0),(H(!0),J(at,null,Tt(l.value.fields,(Z,Le)=>(H(),J("article",{key:Z.id+Le,class:"tmpl-field"},[N("div",XBe,[ee(K,{"model-value":Z.type,class:"tmpl-type",onChange:fe=>S(Z,fe)},{default:Y(()=>[(H(),J(at,null,Tt(u,fe=>ee(R,{key:fe.value,label:fe.label,value:fe.value},null,8,["label","value"])),64))]),_:1},8,["model-value","onChange"]),ee(D,{modelValue:Z.label,"onUpdate:modelValue":fe=>Z.label=fe,class:"tmpl-title",placeholder:"标题"},null,8,["modelValue","onUpdate:modelValue"]),Z.type!=="markdown"?(H(),Ke(he,{key:0,modelValue:Z.required,"onUpdate:modelValue":fe=>Z.required=fe},{default:Y(()=>[...j[19]||(j[19]=[ue("必填",-1)])]),_:1},8,["modelValue","onUpdate:modelValue"])):ke("",!0),N("span",ZBe,ae(Z.id),1),ee(oe,{link:"",type:"danger",onClick:fe=>P(Le)},{default:Y(()=>[...j[20]||(j[20]=[ue("删除",-1)])]),_:1},8,["onClick"])]),Z.type!=="markdown"?(H(),Ke(D,{key:0,modelValue:Z.help,"onUpdate:modelValue":fe=>Z.help=fe,type:"textarea",rows:2,placeholder:"说明(开单时给填写人看)"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0),Z.type==="textarea"?(H(),Ke(D,{key:1,modelValue:Z.placeholder,"onUpdate:modelValue":fe=>Z.placeholder=fe,placeholder:"占位"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0),Z.type==="select"?(H(),J("div",JBe,[(H(!0),J(at,null,Tt(Z.options,(fe,Te)=>(H(),J("div",{key:Te,class:"tmpl-list-row"},[ee(D,{modelValue:Z.options[Te],"onUpdate:modelValue":nt=>Z.options[Te]=nt,placeholder:"选项"},null,8,["modelValue","onUpdate:modelValue"]),ee(oe,{link:"",onClick:nt=>M(Z,Te)},{default:Y(()=>[...j[21]||(j[21]=[ue("删",-1)])]),_:1},8,["onClick"])]))),128)),ee(oe,{link:"",type:"primary",onClick:fe=>T(Z)},{default:Y(()=>[...j[22]||(j[22]=[ue("添加选项",-1)])]),_:1},8,["onClick"])])):ke("",!0),Z.type==="checkboxes"?(H(),J("div",exe,[(H(!0),J(at,null,Tt(Z.items,(fe,Te)=>(H(),J("div",{key:fe.id,class:"tmpl-list-row"},[ee(D,{modelValue:fe.label,"onUpdate:modelValue":nt=>fe.label=nt,placeholder:"条目"},null,8,["modelValue","onUpdate:modelValue"]),ee(he,{modelValue:fe.required,"onUpdate:modelValue":nt=>fe.required=nt},{default:Y(()=>[...j[23]||(j[23]=[ue("必勾",-1)])]),_:1},8,["modelValue","onUpdate:modelValue"]),ee(oe,{link:"",onClick:nt=>O(Z,Te)},{default:Y(()=>[...j[24]||(j[24]=[ue("删",-1)])]),_:1},8,["onClick"])]))),128)),ee(oe,{link:"",type:"primary",onClick:fe=>I(Z)},{default:Y(()=>[...j[25]||(j[25]=[ue("添加条目",-1)])]),_:1},8,["onClick"])])):ke("",!0),Z.type==="markdown"?(H(),Ke(D,{key:4,modelValue:Z.content,"onUpdate:modelValue":fe=>Z.content=fe,type:"textarea",rows:6,placeholder:"说明正文"},null,8,["modelValue","onUpdate:modelValue"])):ke("",!0)]))),128))])]),N("section",txe,[N("div",nxe,[j[27]||(j[27]=N("span",null,"预览",-1)),ee(oe,{link:"",type:"primary",disabled:l.value.fields.length===0,loading:a.value,onClick:y},{default:Y(()=>[...j[26]||(j[26]=[ue(" 刷新 ",-1)])]),_:1},8,["disabled","loading"])]),o.value?(H(),J("pre",rxe,ae(o.value),1)):(H(),J("p",ixe,"导入或修改字段后,这里显示渲染出的 MR 正文。"))])])])}}}),axe=vr(sxe,[["__scopeId","data-v-9166902d"]]),oxe={class:"page settings-page"},lxe={key:0,class:"page-version"},Axe={class:"gc-chip-tabs"},uxe=["onClick"],cxe={class:"stack"},hxe={class:"gc-glass pad"},dxe={class:"remote-bar"},fxe=["title"],gxe={key:1,class:"mr-hint"},pxe={key:0,class:"mr-platform-row"},vxe=["onClick","onKeydown"],mxe={class:"opt-head"},yxe={key:0,class:"mr-stack"},wxe={key:0,class:"mr-kv"},bxe={key:1,class:"mr-kv"},Exe={key:1,class:"mr-stack"},Bxe={key:0,class:"mr-hint"},xxe={class:"mr-field"},Cxe={key:1,class:"mr-hint"},Sxe={class:"mr-hint"},kxe={class:"mr-inline"},_xe={key:2,class:"mr-hint"},Txe={key:0,class:"gc-glass pad"},Mxe={class:"host-list"},Fxe={class:"host-row"},Ixe={class:"mono"},Oxe={class:"template-pane"},Lxe={class:"stack"},Nxe={class:"gc-glass pad"},Pxe={class:"kv"},Rxe={class:"kv"},Uxe={class:"kv"},Dxe={class:"kv"},Qxe={class:"row"},Hxe={class:"git-split"},$xe={class:"gc-glass pad git-side"},zxe={class:"kv"},Gxe={class:"row"},Vxe={key:0,class:"row"},Kxe={class:"gc-glass pad git-main"},jxe={class:"git-pack-h"},Wxe={class:"git-wall"},qxe=["title","onClick"],Yxe="https://cli.github.com/",Xxe="https://gitlab.com/gitlab-org/cli/-/releases",Zxe="https://github.com/settings/tokens/new?scopes=repo&description=Git%20Cockpit",Jxe=ur({__name:"SettingsView",setup(r){const e=ep(),t=Ui(),n=Jg(),i=rl(),s=Pe({get:()=>{const ye=n.query.tab;return ye==="git"||ye==="template"||ye==="llm"?ye:"mr"},set:ye=>{const V=ye==="mr"?{}:{tab:ye};i.replace({path:"/settings",query:V})}}),a=nu({disabledTools:[],dryRunDefault:!1,allowedReposText:""}),o=Me(!1),l=Me("browser"),A=Me("origin"),u=Me(""),c=Me(""),h=Me("gpt-4.1"),d=Me(""),f=Me(""),p=Me(!1),g=Me(!1),v=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.cli.gh.installUrl)||Yxe}),m=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.cli.glab.installUrl)||Xxe}),y=Pe(()=>{const ye=e.permissions;return ye?JSON.stringify([...a.disabledTools].sort())!==JSON.stringify([...ye.disabledTools].sort())||a.dryRunDefault!==ye.dryRunDefault||nt(a.allowedReposText).join(`
|
|
411
411
|
`)!==e.allowedRepos.join(`
|
|
412
412
|
`):!1}),w=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.current)??null}),E=Pe(()=>{var ye;return((ye=e.mr)==null?void 0:ye.remotes)??[]}),x=Pe(()=>E.value.length>0),_=Pe(()=>{var ye;return((ye=w.value)==null?void 0:ye.platform)??"unknown"}),S=Pe(()=>{const ye=A.value;return E.value.find(V=>V.name===ye)??E.value[0]??null}),T=Pe(()=>{var V;const ye=S.value;return((ye==null?void 0:ye.pushUrl)||(ye==null?void 0:ye.fetchUrl)||((V=w.value)==null?void 0:V.remoteUrl)||"").trim()}),M=Pe(()=>{var V,ce;const ye=(V=w.value)==null?void 0:V.host;return!ye||_.value==="unknown"?null:((ce=e.mr)==null?void 0:ce.hosts.find(ze=>ze.host===ye))??null}),I=Pe(()=>!!M.value),O=Pe(()=>{var V,ce;if(!((V=w.value)!=null&&V.host))return u.value.trim().length>0;const ye=((ce=M.value)==null?void 0:ce.apiBaseUrl)??w.value.apiBaseUrl??"";return u.value.trim().length>0||f.value!==ye}),P=Pe(()=>_.value!=="gitlab"),z=Pe(()=>_.value!=="github"),re=Pe(()=>{var V;const ye=(V=e.mr)==null?void 0:V.cli;return ye?_.value==="github"?ye.gh.loggedIn:_.value==="gitlab"?ye.glab.loggedIn:ye.gh.loggedIn||ye.glab.loggedIn:!1}),ie=Pe(()=>{var V;if(!((V=w.value)!=null&&V.tokenSet))return!1;const ye=w.value.tokenStatus;return ye?ye.ok:!0}),Q=Pe(()=>{var ye,V;return g.value?"校验中…":((V=(ye=w.value)==null?void 0:ye.tokenStatus)==null?void 0:V.titleStatus)||""}),j=Pe(()=>{var ye;return g.value||!((ye=w.value)!=null&&ye.tokenStatus)?"muted":w.value.tokenStatus.ok?"ok":"bad"}),ne=Pe(()=>{var V;return`${(((V=w.value)==null?void 0:V.origin)||"").replace(/\/+$/,"")||"https://gitlab.com"}/-/user_settings/personal_access_tokens`}),xe=["查看","分支","写入","同步","合并","变基","MR","任务","Worktree","高风险","其他"];function de(ye){const V=ye.name;return V.startsWith("git_worktree")?"Worktree":V.startsWith("git_job")||V==="git_repo_overview"?"任务":V.startsWith("git_mr_")?"MR":V.startsWith("git_rebase")?"变基":V.startsWith("git_merge")||V==="git_apply_resolve"?"合并":V.startsWith("git_stash")||V==="git_add"||V==="git_unstage"||V==="git_commit"?"写入":V==="git_fetch"||V==="git_pull"||V==="git_push"||V==="git_push_force"?"同步":V==="git_status"||V==="git_log"||V==="git_diff"||V==="git_show"||V==="git_file_content"||V==="git_graph"||V==="git_branch_graph"||V==="git_reflog"||V==="git_backup_list"?"查看":V.startsWith("git_branch")||V.startsWith("git_tag")||V.startsWith("git_remote")||V==="git_checkout"?"分支":ye.riskLevel==="dangerous"?"高风险":"其他"}const we=Pe(()=>{const ye=new Map;for(const V of e.tools){const ce=de(V),ze=ye.get(ce)??[];ze.push(V),ye.set(ce,ze)}return xe.filter(V=>ye.has(V)).map(V=>({name:V,items:ye.get(V)??[]}))});function oe(ye){return ye.replace(/^git_/,"")}function D(ye){return!a.disabledTools.includes(ye.name)}const R=Pe(()=>[{id:"cli",title:Z(),ready:re.value},{id:"token",title:Le(),ready:ie.value},{id:"browser",title:"C. 仅打开浏览器创建页",ready:!0}]),K=Pe(()=>_.value==="github"?"需 repo 权限。Token 不进工具参数。":_.value==="gitlab"?"审核人用户名会解析成数字 id。Token 不进工具参数。":"Token 不进工具参数。"),he=Pe(()=>{var V,ce,ze;const ye=(ce=(V=w.value)==null?void 0:V.tokenPreview)==null?void 0:ce.trim();return(ze=w.value)!=null&&ze.tokenSet&&ye?`已保存 ${ye},输入新 Token 覆盖`:_.value==="github"?"ghp_… 或 github_pat_…":_.value==="gitlab"?"glpat-…":"输入 Token"});function Ce(){return t.currentId}function ge(ye){return ye==="github"?"GitHub":ye==="gitlab"?"GitLab":"未识别"}function Z(){return _.value==="github"?"A. 本机已安装的 gh":_.value==="gitlab"?"A. 本机已安装的 glab":"A. 本机已安装的 gh / glab"}function Le(){var V;const ye=(V=w.value)==null?void 0:V.host;return ye?_.value==="github"?`B. GitHub Token(${ye})`:_.value==="gitlab"?`B. GitLab Token(${ye})`:`B. Token(${ye})`:"B. Token(API)"}function fe(ye){return ye!=null&&ye.found?ye.loggedIn?"已登录":"未登录":"未下载"}function Te(ye){return ye!=null&&ye.tokenStatus?ye.tokenStatus.ok?"ok":"bad":"muted"}function nt(ye){return ye.split(/\r?\n/).map(V=>V.trim()).filter(Boolean)}function Be(){const ye=e.permissions;ye&&(a.disabledTools=[...ye.disabledTools],a.dryRunDefault=ye.dryRunDefault,a.allowedReposText=e.allowedRepos.join(`
|
|
413
413
|
`),o.value=!0)}function At(ye){return ye==="cli"||ye==="token"||ye==="browser"?ye:"browser"}function et(){var ye,V,ce,ze,yt,_t;l.value=At((ye=e.mr)==null?void 0:ye.method),A.value=((V=w.value)==null?void 0:V.remote)||((ce=e.mr)==null?void 0:ce.defaultRemote)||((ze=E.value[0])==null?void 0:ze.name)||"origin",f.value=((yt=M.value)==null?void 0:yt.apiBaseUrl)??((_t=w.value)==null?void 0:_t.apiBaseUrl)??"",u.value=""}async function yn(ye){var V,ce;await e.load(Ce(),{validateToken:(ye==null?void 0:ye.validateToken)??s.value==="mr"}),y.value||Be(),O.value?(l.value=At(((V=e.mr)==null?void 0:V.method)??l.value),A.value=((ce=w.value)==null?void 0:ce.remote)||A.value):et(),Nn()}la(async()=>{await yn(),Be(),et(),Nn()}),an(()=>[e.permissions],()=>{e.permissions&&!y.value&&Be()}),an(()=>e.mr,()=>{var ye;O.value?l.value=At(((ye=e.mr)==null?void 0:ye.method)??l.value):et()}),an(()=>t.currentId,()=>{yn()}),an(s,(ye,V)=>{ye==="mr"&&V!=="mr"&&yn({validateToken:!0})});const kt=[{id:"mr",label:"MR 配置"},{id:"template",label:"正文规范"},{id:"llm",label:"模型"},{id:"git",label:"Git 操作"}];async function Ln(ye,V){V?a.disabledTools=a.disabledTools.filter(ce=>ce!==ye.name):a.disabledTools.includes(ye.name)||a.disabledTools.push(ye.name)}function Yn(ye){Ln(ye,!D(ye))}async function jt(){const{secret:ye}=await m5();if(!ye){We.warning("读不到本机密钥。开发代理会从 config.json 注入,打包页面请用同一来源打开。");return}await navigator.clipboard.writeText(ye),We.success("已复制本机访问密钥")}async function tn(){try{await e.save({permissions:{disabledTools:[...a.disabledTools],requireApprovalFor:[],dryRunDefault:a.dryRunDefault},git:{allowedRepos:nt(a.allowedReposText)}},Ce()),We.success("Git 操作设置已保存")}catch(ye){We.error(ye instanceof Error?ye.message:String(ye))}}async function Tn(ye){var ce;if(!Ce()){We.warning("请先在工作台打开并选择仓库");return}const V=l.value;if(l.value=ye,!(V===ye&&((ce=e.mr)==null?void 0:ce.method)===ye))try{await e.save({mr:{method:ye}},Ce())}catch(ze){l.value=V,We.error(ze instanceof Error?ze.message:String(ze))}}async function $n(ye){var ze,yt;const V=String(ye),ce=((ze=w.value)==null?void 0:ze.remote)||((yt=e.mr)==null?void 0:yt.defaultRemote)||"origin";A.value=V;try{await e.save({mr:{defaultRemote:V}},Ce()),et()}catch(_t){A.value=ce,We.error(_t instanceof Error?_t.message:String(_t))}}async function zn(ye){var ce;const V=(ce=w.value)==null?void 0:ce.host;if(V)try{await e.save({mr:{upsertHost:{host:V,platform:ye}}},Ce())}catch(ze){We.error(ze instanceof Error?ze.message:String(ze))}}async function un(){var ze,yt;const ye=(ze=w.value)==null?void 0:ze.host;if(!ye){We.warning(t.currentId?"当前仓库没有可识别的远程地址":"请先在工作台打开并选择仓库");return}if(_.value==="unknown"){We.warning("请先选择 GitHub 或 GitLab");return}const V=u.value.trim(),ce=((yt=M.value)==null?void 0:yt.apiBaseUrl)??w.value.apiBaseUrl??"";if(!V&&f.value===ce){We.warning("请填写 Token");return}g.value=!0;try{await e.save({mr:{upsertHost:{host:ye,platform:_.value,...V?{token:V}:{},apiBaseUrl:f.value}}},Ce()),u.value="",We.success(V?`已校验并保存 ${ye} 的 Token`:`已保存 ${ye} 的 API 地址`)}catch(_t){We.error(_t instanceof Error?_t.message:String(_t))}finally{g.value=!1}}async function ln(){var ye,V,ce;if((ye=w.value)!=null&&ye.tokenSet){g.value=!0;try{await e.load(Ce(),{validateToken:!0}),O.value||et();const ze=(ce=(V=e.mr)==null?void 0:V.current)==null?void 0:ce.tokenStatus;ze!=null&&ze.ok?We.success(ze.titleStatus):We.warning((ze==null?void 0:ze.titleStatus)||"校验未通过")}catch(ze){We.error(ze instanceof Error?ze.message:String(ze))}finally{g.value=!1}}}async function Zt(ye){try{await ao.confirm(`清除 ${ye} 上保存的 Token 与覆盖项?`,"删除域名配置",{confirmButtonText:"清除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await e.save({mr:{deleteHost:ye}},Ce()),O.value||et(),We.success("已清除")}catch(V){We.error(V instanceof Error?V.message:String(V))}}async function cn(){if(await yn(),e.error){We.error(e.error);return}We.success("已重新检测本机 gh / glab")}function Nn(){var ye,V;h.value=((ye=e.llm)==null?void 0:ye.model)||"gpt-4.1",d.value=((V=e.llm)==null?void 0:V.baseUrl)||"",c.value=""}async function Yr(){try{await e.save({llm:{provider:"openai",model:h.value.trim()||"gpt-4.1",baseUrl:d.value.trim(),...c.value.trim()?{apiKey:c.value.trim()}:{}}},Ce()),c.value="",We.success("已保存模型配置")}catch(ye){We.error(ye instanceof Error?ye.message:String(ye))}}async function Gn(){try{await ao.confirm("清除已保存的模型 API Key?聊天将无法调用模型。","确认",{confirmButtonText:"清除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await e.save({llm:{clearKey:!0}},Ce()),c.value="",We.success("已清除")}catch(ye){We.error(ye instanceof Error?ye.message:String(ye))}}return(ye,V)=>{var L,k,B,b;const ce=Ye("el-alert"),ze=Ye("el-option"),yt=Ye("el-select"),_t=Ye("el-tag"),Wt=Ye("el-button"),kr=Ye("el-link"),Vn=Ye("el-input"),di=Ye("el-form-item"),$i=Ye("el-form"),Dn=Ye("el-switch"),Ti=bh("loading");return H(),J("div",oxe,[V[51]||(V[51]=N("h2",{class:"page-title"},"设置",-1)),be(t).serverVersion?(H(),J("p",lxe,"服务 "+ae(be(t).serverVersion),1)):ke("",!0),be(e).error?(H(),Ke(ce,{key:1,title:be(e).error,type:"error",closable:!1,"show-icon":"",class:"mb"},null,8,["title"])):ke("",!0),N("div",Axe,[(H(),J(at,null,Tt(kt,C=>N("button",{key:C.id,type:"button",class:xt({on:s.value===C.id}),onClick:F=>s.value=C.id},ae(C.label),11,uxe)),64))]),N("div",{class:xt(["settings-body",{fill:s.value==="template"||s.value==="git"}])},[Xs(N("section",cxe,[N("div",hxe,[V[17]||(V[17]=N("p",{class:"gc-eyebrow"},"默认远程",-1)),N("div",dxe,[x.value?(H(),J(at,{key:0},[ee(yt,{modelValue:A.value,"onUpdate:modelValue":V[0]||(V[0]=C=>A.value=C),class:"remote-select",disabled:be(e).saving,onChange:$n},{default:Y(()=>[(H(!0),J(at,null,Tt(E.value,C=>(H(),Ke(ze,{key:C.name,label:C.name,value:C.name},null,8,["label","value"]))),128))]),_:1},8,["modelValue","disabled"]),N("span",{class:"mono muted remote-url",title:T.value},ae(T.value||"—"),9,fxe),ee(_t,{size:"small",effect:"plain"},{default:Y(()=>[ue(ae(ge(_.value)),1)]),_:1})],64)):(H(),J("span",gxe,ae(be(t).currentId?"当前仓库没有 remote":"未选择仓库,请先在工作台打开仓库"),1))]),V[18]||(V[18]=N("p",{class:"mr-hint"},"用于 fetch、MR 短名剥前缀、CLI 未传远程时的默认值。切换后立即保存。",-1)),x.value&&_.value==="unknown"?(H(),J("div",pxe,[V[16]||(V[16]=N("span",{class:"mr-hint"},"未能自动识别远程平台,请手动选择。",-1)),ee(Wt,{disabled:!((L=w.value)!=null&&L.host)||be(e).saving,onClick:V[1]||(V[1]=C=>zn("github"))},{default:Y(()=>[...V[14]||(V[14]=[ue("GitHub",-1)])]),_:1},8,["disabled"]),ee(Wt,{disabled:!((k=w.value)!=null&&k.host)||be(e).saving,onClick:V[2]||(V[2]=C=>zn("gitlab"))},{default:Y(()=>[...V[15]||(V[15]=[ue("GitLab",-1)])]),_:1},8,["disabled"])])):ke("",!0)]),(H(!0),J(at,null,Tt(R.value,C=>{var F,$,G,W,q,ve,Ae,Fe,qe,Se,Ne,it,le,Ge,st;return H(),J("div",{key:C.id,class:xt(["gc-glass opt",{on:l.value===C.id,disabled:be(e).saving}]),role:"button",tabindex:"0",onClick:wt=>Tn(C.id),onKeydown:Ao(si(wt=>Tn(C.id),["prevent"]),["enter"])},[N("header",mxe,[N("strong",null,ae(C.title),1),N("span",{class:xt(C.ready?"ok":"bad")},ae(C.ready?"可用":"未就绪"),3)]),l.value===C.id?(H(),J("div",{key:0,class:"mr-panel",onClick:V[7]||(V[7]=si(()=>{},["stop"]))},[C.id==="cli"?(H(),J("div",yxe,[P.value?(H(),J("div",wxe,[V[20]||(V[20]=N("span",{class:"mono"},"gh",-1)),N("span",null,ae(fe((F=be(e).mr)==null?void 0:F.cli.gh)),1),($=be(e).mr)!=null&&$.cli.gh.found?(G=be(e).mr)!=null&&G.cli.gh.loggedIn&&((W=be(e).mr)!=null&&W.cli.gh.tokenStatus)?(H(),J("span",{key:1,class:xt(["token-title-status",Te((q=be(e).mr)==null?void 0:q.cli.gh)])},ae((ve=be(e).mr)==null?void 0:ve.cli.gh.tokenStatus.titleStatus),3)):ke("",!0):(H(),Ke(kr,{key:0,type:"primary",href:v.value,target:"_blank"},{default:Y(()=>[...V[19]||(V[19]=[ue("去下载",-1)])]),_:1},8,["href"]))])):ke("",!0),z.value?(H(),J("div",bxe,[V[22]||(V[22]=N("span",{class:"mono"},"glab",-1)),N("span",null,ae(fe((Ae=be(e).mr)==null?void 0:Ae.cli.glab)),1),(Fe=be(e).mr)!=null&&Fe.cli.glab.found?(qe=be(e).mr)!=null&&qe.cli.glab.loggedIn&&((Se=be(e).mr)!=null&&Se.cli.glab.tokenStatus)?(H(),J("span",{key:1,class:xt(["token-title-status",Te((Ne=be(e).mr)==null?void 0:Ne.cli.glab)])},ae((it=be(e).mr)==null?void 0:it.cli.glab.tokenStatus.titleStatus),3)):ke("",!0):(H(),Ke(kr,{key:0,type:"primary",href:m.value,target:"_blank"},{default:Y(()=>[...V[21]||(V[21]=[ue("去下载",-1)])]),_:1},8,["href"]))])):ke("",!0),ee(Wt,{onClick:cn},{default:Y(()=>[...V[23]||(V[23]=[ue("重新检测",-1)])]),_:1})])):ke("",!0),C.id==="token"?(H(),J("div",Exe,[(le=w.value)!=null&&le.host?(H(),Ke($i,{key:1,class:"mr-form gc-form-narrow","label-width":"100px","label-position":"left",onSubmit:V[6]||(V[6]=si(()=>{},["prevent"]))},{default:Y(()=>[ee(di,{label:"Token"},{default:Y(()=>[N("div",xxe,[Q.value?(H(),J("p",{key:0,class:xt(["token-title-status",j.value])},ae(Q.value),3)):ke("",!0),ee(Vn,{modelValue:u.value,"onUpdate:modelValue":V[3]||(V[3]=wt=>u.value=wt),type:"password","show-password":"",autocomplete:"new-password",placeholder:he.value,disabled:_.value==="unknown"||g.value},null,8,["modelValue","placeholder","disabled"]),w.value.tokenSet&&w.value.tokenPreview?(H(),J("p",Cxe," 已保存 "+ae(w.value.tokenPreview)+"(明文不回填,输入新 Token 可覆盖) ",1)):ke("",!0),N("p",Sxe,ae(K.value),1)])]),_:1}),ee(di,null,{default:Y(()=>[N("div",kxe,[_.value!=="gitlab"?(H(),Ke(kr,{key:0,type:"primary",href:Zxe,target:"_blank"},{default:Y(()=>[...V[24]||(V[24]=[ue(" 打开 GitHub 创建 Token 页面 ",-1)])]),_:1})):ke("",!0),_.value!=="github"?(H(),Ke(kr,{key:1,type:"primary",href:ne.value,target:"_blank"},{default:Y(()=>[...V[25]||(V[25]=[ue(" 打开 GitLab 创建 Token 页面 ",-1)])]),_:1},8,["href"])):ke("",!0),ee(Wt,{link:"",type:"primary",onClick:V[4]||(V[4]=wt=>p.value=!p.value)},{default:Y(()=>[ue(ae(p.value?"收起自建选项":"自建 / API Base URL"),1)]),_:1})])]),_:1}),p.value?(H(),Ke(di,{key:0,label:"API Base URL"},{default:Y(()=>[ee(Vn,{modelValue:f.value,"onUpdate:modelValue":V[5]||(V[5]=wt=>f.value=wt),placeholder:"空则按域名惯例推断,如 https://git.a.com/api/v4"},null,8,["modelValue"])]),_:1})):ke("",!0),ee(di,null,{default:Y(()=>[ee(Wt,{type:"primary",loading:be(e).saving||g.value,disabled:!O.value||g.value,onClick:un},{default:Y(()=>[...V[26]||(V[26]=[ue(" 保存此远程配置 ",-1)])]),_:1},8,["loading","disabled"]),ee(Wt,{disabled:!w.value.tokenSet||O.value||g.value,loading:g.value&&!O.value,onClick:ln},{default:Y(()=>[...V[27]||(V[27]=[ue(" 重新校验 ",-1)])]),_:1},8,["disabled","loading"]),ee(Wt,{disabled:!O.value,onClick:et},{default:Y(()=>[...V[28]||(V[28]=[ue("放弃修改",-1)])]),_:1},8,["disabled"])]),_:1})]),_:1})):(H(),J("p",Bxe,ae(be(t).currentId?"当前仓库没有可识别的远程地址,无法绑定 Token。":"请先在工作台打开并选择仓库。Token 始终绑定当前远程的域名。"),1))])):ke("",!0),C.id==="browser"?(H(),J("p",_xe,[V[29]||(V[29]=ue(" 确认后只返回浏览器创建页,不调用 Token 或本机 CLI。 ",-1)),(st=(Ge=be(e).mr)==null?void 0:Ge.template)!=null&&st.enabled?(H(),J(at,{key:0},[ue(" 已启用正文规范时,平台创建页不会写入正文,请复制后粘贴,或改用 Token / 本机 CLI。 ")],64)):ke("",!0)])):ke("",!0)])):ke("",!0)],42,vxe)}),128)),I.value&&M.value?(H(),J("div",Txe,[V[31]||(V[31]=N("p",{class:"gc-eyebrow"},"当前远程凭证",-1)),V[32]||(V[32]=N("p",{class:"mr-hint"},"只显示当前选中远程的域名。其它实例上的 Token 不在这里列出。",-1)),N("ul",Mxe,[N("li",Fxe,[N("span",Ixe,ae(M.value.host),1),ee(_t,{size:"small",effect:"plain"},{default:Y(()=>[ue(ae(M.value.platform==="github"?"GitHub":"GitLab"),1)]),_:1}),ee(_t,{size:"small",type:M.value.tokenSet?"success":"info",effect:"plain"},{default:Y(()=>[ue(ae(M.value.tokenSet?M.value.tokenPreview||"已配置 Token":"无 Token"),1)]),_:1},8,["type"]),ee(Wt,{link:"",type:"danger",onClick:V[8]||(V[8]=C=>Zt(M.value.host))},{default:Y(()=>[...V[30]||(V[30]=[ue("清除",-1)])]),_:1})])])])):ke("",!0)],512),[[vc,s.value==="mr"]]),Xs(N("section",Oxe,[ee(axe)],512),[[vc,s.value==="template"]]),Xs(N("section",Lxe,[N("div",Nxe,[V[39]||(V[39]=N("p",{class:"gc-eyebrow"},"聊天模型",-1)),V[40]||(V[40]=N("p",{class:"mr-hint"}," Key 与 MR Token 一样只存在本机设置里,不进 MCP 工具参数,页面不回填明文。浏览器和桌面同一套配置。 ",-1)),N("label",Pxe,[V[33]||(V[33]=ue(" 供应商 ",-1)),ee(yt,{"model-value":"openai",disabled:""},{default:Y(()=>[ee(ze,{label:"OpenAI 兼容",value:"openai"})]),_:1})]),N("label",Rxe,[V[34]||(V[34]=ue(" 模型名 ",-1)),ee(Vn,{modelValue:h.value,"onUpdate:modelValue":V[9]||(V[9]=C=>h.value=C),placeholder:"gpt-4.1"},null,8,["modelValue"])]),N("label",Uxe,[V[35]||(V[35]=ue(" Base URL ",-1)),ee(Vn,{modelValue:d.value,"onUpdate:modelValue":V[10]||(V[10]=C=>d.value=C),placeholder:"空 = https://api.openai.com/v1 ;兼容网关填到 /v1"},null,8,["modelValue"])]),N("label",Dxe,[V[36]||(V[36]=ue(" API Key ",-1)),ee(Vn,{modelValue:c.value,"onUpdate:modelValue":V[11]||(V[11]=C=>c.value=C),type:"password","show-password":"",placeholder:(B=be(e).llm)!=null&&B.tokenSet?`已保存 ${be(e).llm.tokenPreview},输入新 Key 覆盖`:"sk-…"},null,8,["modelValue","placeholder"])]),N("div",Qxe,[ee(Wt,{type:"primary",loading:be(e).saving,onClick:Yr},{default:Y(()=>[...V[37]||(V[37]=[ue("保存",-1)])]),_:1},8,["loading"]),ee(Wt,{disabled:!((b=be(e).llm)!=null&&b.tokenSet)||be(e).saving,onClick:Gn},{default:Y(()=>[...V[38]||(V[38]=[ue("清除 Key",-1)])]),_:1},8,["disabled"])])])],512),[[vc,s.value==="llm"]]),Xs(N("section",Hxe,[N("aside",$xe,[V[45]||(V[45]=N("p",{class:"gc-eyebrow"},"默认行为",-1)),N("label",zxe,[V[41]||(V[41]=ue(" dry-run 默认 ",-1)),ee(Dn,{modelValue:a.dryRunDefault,"onUpdate:modelValue":V[12]||(V[12]=C=>a.dryRunDefault=C)},null,8,["modelValue"])]),V[46]||(V[46]=N("p",{class:"hint"},"开启后写操作(MCP / CLI)默认只生成预览,不真正执行。",-1)),V[47]||(V[47]=N("p",{class:"field-label"},"允许打开的仓库",-1)),ee(Vn,{modelValue:a.allowedReposText,"onUpdate:modelValue":V[13]||(V[13]=C=>a.allowedReposText=C),type:"textarea",rows:6,placeholder:"一行一个本地路径。留空 = 不限制"},null,8,["modelValue"]),V[48]||(V[48]=N("p",{class:"hint"}," 非空时,打开路径必须等于其中一条或位于其下。空名单不限制路径,MCP 带的 repoPath 同样不限制。 ",-1)),N("div",Gxe,[ee(Wt,{onClick:jt},{default:Y(()=>[...V[42]||(V[42]=[ue("复制本机访问密钥",-1)])]),_:1})]),V[49]||(V[49]=N("p",{class:"hint"}," HTTP 的 /api 与 /mcp 要带请求头 X-Git-Cockpit-Secret。探活不校验。能读到 config.json 的本机进程不受这道头限制。 ",-1)),o.value?(H(),J("div",Vxe,[ee(Wt,{type:"primary",loading:be(e).saving,disabled:!y.value,onClick:tn},{default:Y(()=>[...V[43]||(V[43]=[ue("保存",-1)])]),_:1},8,["loading","disabled"]),ee(Wt,{disabled:!y.value,onClick:Be},{default:Y(()=>[...V[44]||(V[44]=[ue("放弃",-1)])]),_:1},8,["disabled"])])):ke("",!0)]),Xs((H(),J("div",Kxe,[V[50]||(V[50]=N("p",{class:"hint git-main-hint"}," 点芯片启用或禁用。高危左边红条,默认关。打开后执行前仍会自动备份。没有审批队列。 ",-1)),(H(!0),J(at,null,Tt(we.value,C=>(H(),J("div",{key:C.name,class:"git-pack"},[N("p",jxe,[ue(ae(C.name)+" ",1),N("span",null,ae(C.items.length),1)]),N("div",Wxe,[(H(!0),J(at,null,Tt(C.items,F=>(H(),J("button",{key:F.name,type:"button",class:xt(["git-chip",{on:D(F),high:F.riskLevel==="dangerous"}]),title:F.description,onClick:$=>Yn(F)},ae(oe(F.name)),11,qxe))),128))])]))),128))])),[[Ti,be(e).loading]])],512),[[vc,s.value==="git"]])],2)])}}}),eCe=vr(Jxe,[["__scopeId","data-v-9b782f2a"]]),i5=d5({history:f5(),routes:[{path:"/",redirect:"/dashboard"},{path:"/dashboard",name:"dashboard",component:mG,meta:{title:"工作台"}},{path:"/jobs",name:"jobs",component:kBe,meta:{title:"任务"}},{path:"/repos",redirect:"/dashboard"},{path:"/status",name:"status",component:vbe,meta:{title:"状态"}},{path:"/merge",name:"merge",component:hBe,meta:{title:"合并"}},{path:"/chat",name:"chat",component:qR,meta:{title:"聊天"}},{path:"/matrix",redirect:{path:"/merge",query:{mode:"matrix"}}},{path:"/logs",name:"logs",component:DBe,meta:{title:"操作日志"}},{path:"/settings",name:"settings",component:eCe,meta:{title:"设置"}}]});i5.afterEach(r=>{const e=r.meta.title;document.title=e?`${e} · Git Cockpit`:"Git Cockpit"});document.documentElement.classList.add("dark");const Vv=g5(Mz);Vv.use(p5());Vv.use(i5);Vv.use(v5,{size:"default"});Vv.mount("#app");
|