jinzd-ai-cli 0.1.84 → 0.1.85
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/CLAUDE.md +46 -2
- package/dist/{chunk-L2PQET5S.js → chunk-QHZGVP5X.js} +1 -1
- package/dist/{chunk-YHB3S2KS.js → chunk-VR7VECPG.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{run-tests-V2VE7ST5.js → run-tests-IFXCV4UW.js} +1 -1
- package/dist/{server-XWSMOXVH.js → server-2LECXHO6.js} +38 -2
- package/dist/web/client/app.js +132 -2
- package/dist/web/client/index.html +22 -5
- package/dist/web/client/style.css +81 -0
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -352,6 +352,50 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
352
352
|
- [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
|
|
353
353
|
- [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
|
|
354
354
|
|
|
355
|
+
## 本轮开发完成记录(2026-03-16,v0.1.84 → v0.1.85)
|
|
356
|
+
|
|
357
|
+
### Web UI P1 增强:侧边栏 Tools Tab + Diff 语法高亮
|
|
358
|
+
|
|
359
|
+
**P1-1:侧边栏增强 — Sessions/Tools 双 Tab**
|
|
360
|
+
|
|
361
|
+
将原有单一 session 列表侧边栏升级为双 Tab 布局:
|
|
362
|
+
|
|
363
|
+
| 文件 | 变更类型 | 说明 |
|
|
364
|
+
|------|---------|------|
|
|
365
|
+
| `src/web/protocol.ts` | 修改 | 新增 `S2C_ToolsList` 消息类型(builtinTools/mcpServers/skills 三段数据) |
|
|
366
|
+
| `src/web/session-handler.ts` | 修改 | 新增 `/tools` 命令 + `sendToolsList()` 方法(遍历 ToolRegistry + MCP + Skills) |
|
|
367
|
+
| `src/web/client/index.html` | 修改 | 侧边栏重构为 Tabs(📋 Sessions / 🔧 Tools)+ 搜索框 |
|
|
368
|
+
| `src/web/client/style.css` | 修改 | Tab 激活样式 + 工具列表样式(section 标题/工具项/彩色圆点/MCP 折叠) |
|
|
369
|
+
| `src/web/client/app.js` | 修改 | Tab 切换逻辑 + `renderToolsList()` + `renderFilteredTools()` 搜索过滤 |
|
|
370
|
+
|
|
371
|
+
- **Built-in Tools**:显示所有内置工具,带危险级别彩色圆点(蓝=safe, 黄=write, 红=destructive)
|
|
372
|
+
- **MCP Servers**:显示连接状态(绿=connected)+ 工具数量 badge,可折叠展开查看工具列表
|
|
373
|
+
- **Skills**:显示所有可用技能,绿色=active,灰色=inactive
|
|
374
|
+
- **搜索过滤**:实时过滤工具名和描述,空 section 自动隐藏
|
|
375
|
+
- **Session 搜索**:Sessions tab 新增搜索框,按标题过滤
|
|
376
|
+
|
|
377
|
+
**P1-2:Diff 渲染增强 — 语法高亮**
|
|
378
|
+
|
|
379
|
+
工具确认对话框中的 diff 预览从纯文本升级为语法高亮:
|
|
380
|
+
|
|
381
|
+
| 文件 | 变更类型 | 说明 |
|
|
382
|
+
|------|---------|------|
|
|
383
|
+
| `src/web/client/style.css` | 修改 | 新增 `.diff-add`/`.diff-del`/`.diff-hunk`/`.diff-ctx` 四种行样式 |
|
|
384
|
+
| `src/web/client/app.js` | 修改 | 新增 `renderDiffHtml()` 函数,按行首字符(+/-/@@)分类着色 |
|
|
385
|
+
|
|
386
|
+
- `+` 增加行:绿色文字 + 绿色半透明背景
|
|
387
|
+
- `-` 删除行:红色文字 + 红色半透明背景
|
|
388
|
+
- `@@` Hunk header:主题色(紫色),加粗
|
|
389
|
+
- 其他上下文行:降低不透明度
|
|
390
|
+
|
|
391
|
+
### 版本与收尾
|
|
392
|
+
- `src/core/constants.ts`:VERSION `0.1.84` → `0.1.85`
|
|
393
|
+
- `package.json`:version 同步
|
|
394
|
+
- 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
|
|
395
|
+
- Web UI 预览测试:侧边栏 Tab 切换、工具列表渲染、搜索过滤、MCP/Skills 显示 全部通过
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
355
399
|
## 本轮开发完成记录(2026-03-16,v0.1.82 → v0.1.83)
|
|
356
400
|
|
|
357
401
|
### 新增功能:OpenRouter 内置 Provider
|
|
@@ -1932,8 +1976,8 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
1932
1976
|
|
|
1933
1977
|
| # | 功能 | 状态 | 说明 |
|
|
1934
1978
|
|---|------|------|------|
|
|
1935
|
-
| P1-1 | **侧边栏增强** | [x] |
|
|
1936
|
-
| P1-2 | **Diff 渲染增强** | [
|
|
1979
|
+
| P1-1 | **侧边栏增强** | [x] | Sessions/Tools 双 Tab + Built-in Tools(危险级别圆点)+ MCP Servers(连接状态+工具数)+ Skills 列表 + 搜索过滤 |
|
|
1980
|
+
| P1-2 | **Diff 渲染增强** | [x] | confirm 对话框 diff 语法高亮(绿色增行、红色删行、紫色 hunk header、灰色上下文) |
|
|
1937
1981
|
| P1-3 | **更多命令支持** | [~] | `/cost`、`/session list\|load\|delete` 已实现;待增加 `/undo`、`/export [md\|json]`、`/tools`、`/memory` |
|
|
1938
1982
|
| P1-4 | **键盘快捷键** | [ ] | `Ctrl+L` 清屏、`Ctrl+K` 清空输入、`Esc` 停止生成、`↑` 历史消息回溯 |
|
|
1939
1983
|
| P1-5 | **Markdown 导出** | [ ] | 一键导出当前对话为 `.md` 文件下载 |
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
theme,
|
|
36
36
|
truncateOutput,
|
|
37
37
|
undoStack
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-QHZGVP5X.js";
|
|
39
39
|
import {
|
|
40
40
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
41
41
|
AUTHOR,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
REPO_URL,
|
|
56
56
|
SKILLS_DIR_NAME,
|
|
57
57
|
VERSION
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-VR7VECPG.js";
|
|
59
59
|
|
|
60
60
|
// src/index.ts
|
|
61
61
|
import { program } from "commander";
|
|
@@ -1904,7 +1904,7 @@ ${hint}` : "")
|
|
|
1904
1904
|
description: "Run project tests and show structured report",
|
|
1905
1905
|
usage: "/test [command|filter]",
|
|
1906
1906
|
async execute(args, _ctx) {
|
|
1907
|
-
const { executeTests } = await import("./run-tests-
|
|
1907
|
+
const { executeTests } = await import("./run-tests-IFXCV4UW.js");
|
|
1908
1908
|
const argStr = args.join(" ").trim();
|
|
1909
1909
|
let testArgs = {};
|
|
1910
1910
|
if (argStr) {
|
|
@@ -5292,7 +5292,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5292
5292
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5293
5293
|
process.exit(1);
|
|
5294
5294
|
}
|
|
5295
|
-
const { startWebServer } = await import("./server-
|
|
5295
|
+
const { startWebServer } = await import("./server-2LECXHO6.js");
|
|
5296
5296
|
await startWebServer({ port, host: options.host });
|
|
5297
5297
|
});
|
|
5298
5298
|
program.command("sessions").description("List recent conversation sessions").action(async () => {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
setupProxy,
|
|
24
24
|
spawnAgentContext,
|
|
25
25
|
truncateOutput
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-QHZGVP5X.js";
|
|
27
27
|
import {
|
|
28
28
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
29
29
|
CONTEXT_FILE_CANDIDATES,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
SKILLS_DIR_NAME,
|
|
37
37
|
VERSION,
|
|
38
38
|
__require
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-VR7VECPG.js";
|
|
40
40
|
|
|
41
41
|
// src/web/server.ts
|
|
42
42
|
import express from "express";
|
|
@@ -961,6 +961,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
961
961
|
" /session delete <id> \u2014 Delete a session",
|
|
962
962
|
" /status \u2014 Show session info & token usage",
|
|
963
963
|
" /cost \u2014 Show cumulative token usage",
|
|
964
|
+
" /tools \u2014 Show tools, MCP servers & skills in sidebar",
|
|
964
965
|
" /help \u2014 Show this help message",
|
|
965
966
|
"",
|
|
966
967
|
"\u{1F4A1} Tips:",
|
|
@@ -985,6 +986,9 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
985
986
|
});
|
|
986
987
|
break;
|
|
987
988
|
}
|
|
989
|
+
case "tools":
|
|
990
|
+
this.sendToolsList();
|
|
991
|
+
break;
|
|
988
992
|
default:
|
|
989
993
|
this.send({ type: "error", message: `Unknown command: /${name}. Type /help for available commands.` });
|
|
990
994
|
}
|
|
@@ -1036,6 +1040,38 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
1036
1040
|
}))
|
|
1037
1041
|
});
|
|
1038
1042
|
}
|
|
1043
|
+
sendToolsList() {
|
|
1044
|
+
const allDefs = this.toolRegistry.getDefinitions();
|
|
1045
|
+
const builtinTools = allDefs.filter((d) => !d.name.startsWith("mcp__")).map((d) => ({
|
|
1046
|
+
name: d.name,
|
|
1047
|
+
description: d.description,
|
|
1048
|
+
dangerLevel: getDangerLevel(d.name, {})
|
|
1049
|
+
}));
|
|
1050
|
+
const mcpServers = [];
|
|
1051
|
+
if (this.mcpManager) {
|
|
1052
|
+
for (const status of this.mcpManager.getStatus()) {
|
|
1053
|
+
const serverTools = allDefs.filter((d) => d.name.startsWith(`mcp__${status.serverId}__`)).map((d) => d.name.replace(`mcp__${status.serverId}__`, ""));
|
|
1054
|
+
mcpServers.push({
|
|
1055
|
+
serverId: status.serverId,
|
|
1056
|
+
serverName: status.serverName,
|
|
1057
|
+
toolCount: status.toolCount,
|
|
1058
|
+
connected: status.connected,
|
|
1059
|
+
tools: serverTools
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const skills = (this.skillManager?.listSkills() ?? []).map((s) => ({
|
|
1064
|
+
name: s.meta.name,
|
|
1065
|
+
description: s.meta.description,
|
|
1066
|
+
isActive: this.skillManager?.getActive()?.meta.name === s.meta.name
|
|
1067
|
+
}));
|
|
1068
|
+
this.send({
|
|
1069
|
+
type: "tools_list",
|
|
1070
|
+
builtinTools,
|
|
1071
|
+
mcpServers,
|
|
1072
|
+
skills
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1039
1075
|
sendSessionMessages() {
|
|
1040
1076
|
const session = this.sessions.current;
|
|
1041
1077
|
if (!session) return;
|
package/dist/web/client/app.js
CHANGED
|
@@ -34,6 +34,11 @@ const connectionStatus = document.getElementById('connection-status');
|
|
|
34
34
|
const sidebar = document.getElementById('sidebar');
|
|
35
35
|
const sessionListEl = document.getElementById('session-list');
|
|
36
36
|
const btnNewSession = document.getElementById('btn-new-session');
|
|
37
|
+
const toolsListEl = document.getElementById('tools-list');
|
|
38
|
+
const sessionSearchInput = document.getElementById('session-search');
|
|
39
|
+
const toolsSearchInput = document.getElementById('tools-search');
|
|
40
|
+
let cachedSessions = [];
|
|
41
|
+
let cachedToolsData = null;
|
|
37
42
|
|
|
38
43
|
// ── Configure marked.js ────────────────────────────────────────────
|
|
39
44
|
|
|
@@ -107,6 +112,7 @@ function handleServerMessage(msg) {
|
|
|
107
112
|
case 'status': handleStatus(msg); break;
|
|
108
113
|
case 'session_list': renderSessionList(msg.sessions); break;
|
|
109
114
|
case 'session_messages':renderSessionMessages(msg.messages); break;
|
|
115
|
+
case 'tools_list': renderToolsList(msg); switchSidebarTab('tools'); break;
|
|
110
116
|
case 'info': addInfoMessage(msg.message); break;
|
|
111
117
|
case 'error': addErrorMessage(msg.message); setProcessing(false); break;
|
|
112
118
|
case 'round_progress': break;
|
|
@@ -205,7 +211,7 @@ function handleConfirmRequest(msg) {
|
|
|
205
211
|
<span class="badge ${isDestructive ? 'badge-error' : 'badge-warning'} badge-sm">${isDestructive ? '⚠ DESTRUCTIVE' : '✎ Write'}</span>
|
|
206
212
|
<span class="text-sm font-semibold">${escapeHtml(msg.toolName)}</span>
|
|
207
213
|
</div>
|
|
208
|
-
${msg.diff ? `<div class="confirm-diff w-full">${
|
|
214
|
+
${msg.diff ? `<div class="confirm-diff w-full">${renderDiffHtml(msg.diff)}</div>` : ''}
|
|
209
215
|
<div class="flex gap-2 mt-2">
|
|
210
216
|
<button class="btn btn-success btn-sm btn-outline" onclick="respondConfirm('${msg.requestId}', true)">✓ Approve</button>
|
|
211
217
|
<button class="btn btn-error btn-sm btn-outline" onclick="respondConfirm('${msg.requestId}', false)">✗ Deny</button>
|
|
@@ -591,8 +597,16 @@ modelSelect.addEventListener('change', () => {
|
|
|
591
597
|
// ── Session management ──────────────────────────────────────────────
|
|
592
598
|
|
|
593
599
|
function renderSessionList(sessions) {
|
|
600
|
+
cachedSessions = sessions || [];
|
|
601
|
+
renderFilteredSessions(sessionSearchInput?.value || '');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function renderFilteredSessions(filter) {
|
|
605
|
+
const sessions = filter
|
|
606
|
+
? cachedSessions.filter(s => (s.title || '').toLowerCase().includes(filter.toLowerCase()))
|
|
607
|
+
: cachedSessions;
|
|
594
608
|
if (!sessions || sessions.length === 0) {
|
|
595
|
-
sessionListEl.innerHTML =
|
|
609
|
+
sessionListEl.innerHTML = `<div class="text-xs opacity-40 text-center py-4">${filter ? 'No matches' : 'No sessions yet'}</div>`;
|
|
596
610
|
return;
|
|
597
611
|
}
|
|
598
612
|
sessionListEl.innerHTML = sessions.map(s => {
|
|
@@ -656,6 +670,122 @@ btnNewSession.addEventListener('click', () => {
|
|
|
656
670
|
// Request session list on connect
|
|
657
671
|
function requestSessionList() {
|
|
658
672
|
send({ type: 'command', name: 'session', args: ['list'] });
|
|
673
|
+
// Also request tools list for sidebar
|
|
674
|
+
send({ type: 'command', name: 'tools', args: [] });
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Session search filter
|
|
678
|
+
if (sessionSearchInput) {
|
|
679
|
+
sessionSearchInput.addEventListener('input', () => {
|
|
680
|
+
renderFilteredSessions(sessionSearchInput.value);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ── Sidebar tabs ──────────────────────────────────────────────────────
|
|
685
|
+
|
|
686
|
+
function switchSidebarTab(tabName) {
|
|
687
|
+
document.querySelectorAll('.sidebar-tab').forEach(btn => {
|
|
688
|
+
btn.classList.toggle('active', btn.dataset.tab === tabName);
|
|
689
|
+
});
|
|
690
|
+
document.querySelectorAll('.sidebar-tab-content').forEach(el => {
|
|
691
|
+
el.classList.toggle('hidden', el.id !== `tab-${tabName}`);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
document.querySelectorAll('.sidebar-tab').forEach(btn => {
|
|
696
|
+
btn.addEventListener('click', () => switchSidebarTab(btn.dataset.tab));
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// ── Tools list rendering ──────────────────────────────────────────────
|
|
700
|
+
|
|
701
|
+
function renderToolsList(data) {
|
|
702
|
+
cachedToolsData = data;
|
|
703
|
+
renderFilteredTools(toolsSearchInput?.value || '');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function renderFilteredTools(filter) {
|
|
707
|
+
if (!cachedToolsData) {
|
|
708
|
+
toolsListEl.innerHTML = '<div class="text-xs opacity-40 text-center py-4">Type /tools to load</div>';
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const fl = filter.toLowerCase();
|
|
712
|
+
let html = '';
|
|
713
|
+
|
|
714
|
+
// Built-in tools
|
|
715
|
+
const tools = fl
|
|
716
|
+
? cachedToolsData.builtinTools.filter(t => t.name.includes(fl) || t.description.toLowerCase().includes(fl))
|
|
717
|
+
: cachedToolsData.builtinTools;
|
|
718
|
+
if (tools.length > 0) {
|
|
719
|
+
html += '<div class="tools-section-title">Built-in Tools</div>';
|
|
720
|
+
html += tools.map(t =>
|
|
721
|
+
`<div class="tool-item" title="${escapeHtml(t.description)}">
|
|
722
|
+
<span class="tool-dot tool-dot-${t.dangerLevel}"></span>
|
|
723
|
+
<span class="flex-1 truncate">${escapeHtml(t.name)}</span>
|
|
724
|
+
</div>`
|
|
725
|
+
).join('');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// MCP servers
|
|
729
|
+
const servers = fl
|
|
730
|
+
? cachedToolsData.mcpServers.filter(s =>
|
|
731
|
+
s.serverId.includes(fl) || s.serverName.toLowerCase().includes(fl) ||
|
|
732
|
+
s.tools.some(t => t.includes(fl)))
|
|
733
|
+
: cachedToolsData.mcpServers;
|
|
734
|
+
if (servers.length > 0) {
|
|
735
|
+
html += '<div class="tools-section-title mt-2">MCP Servers</div>';
|
|
736
|
+
for (const s of servers) {
|
|
737
|
+
const dotClass = s.connected ? 'tool-dot-connected' : 'tool-dot-disconnected';
|
|
738
|
+
html += `<details class="mcp-server-item">
|
|
739
|
+
<summary class="flex items-center gap-2 cursor-pointer select-none">
|
|
740
|
+
<span class="tool-dot ${dotClass}"></span>
|
|
741
|
+
<span class="flex-1 truncate">${escapeHtml(s.serverId)}</span>
|
|
742
|
+
<span class="text-xs opacity-40">${s.toolCount}</span>
|
|
743
|
+
</summary>
|
|
744
|
+
<div class="mcp-server-tools">
|
|
745
|
+
${s.tools.map(t => `<div class="py-0.5">${escapeHtml(t)}</div>`).join('')}
|
|
746
|
+
</div>
|
|
747
|
+
</details>`;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Skills
|
|
752
|
+
const skills = fl
|
|
753
|
+
? cachedToolsData.skills.filter(s => s.name.includes(fl) || s.description.toLowerCase().includes(fl))
|
|
754
|
+
: cachedToolsData.skills;
|
|
755
|
+
if (skills.length > 0) {
|
|
756
|
+
html += '<div class="tools-section-title mt-2">Skills</div>';
|
|
757
|
+
html += skills.map(s =>
|
|
758
|
+
`<div class="tool-item" title="${escapeHtml(s.description)}">
|
|
759
|
+
<span class="tool-dot ${s.isActive ? 'tool-dot-active' : 'tool-dot-inactive'}"></span>
|
|
760
|
+
<span class="flex-1 truncate">${escapeHtml(s.name)}</span>
|
|
761
|
+
${s.isActive ? '<span class="badge badge-success badge-xs">active</span>' : ''}
|
|
762
|
+
</div>`
|
|
763
|
+
).join('');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (!html) {
|
|
767
|
+
html = '<div class="text-xs opacity-40 text-center py-4">No matches</div>';
|
|
768
|
+
}
|
|
769
|
+
toolsListEl.innerHTML = html;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Tools search filter
|
|
773
|
+
if (toolsSearchInput) {
|
|
774
|
+
toolsSearchInput.addEventListener('input', () => {
|
|
775
|
+
renderFilteredTools(toolsSearchInput.value);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// ── Diff syntax highlighting ──────────────────────────────────────────
|
|
780
|
+
|
|
781
|
+
function renderDiffHtml(diffText) {
|
|
782
|
+
return diffText.split('\n').map(line => {
|
|
783
|
+
const escaped = escapeHtml(line);
|
|
784
|
+
if (line.startsWith('@@')) return `<div class="diff-hunk">${escaped}</div>`;
|
|
785
|
+
if (line.startsWith('+')) return `<div class="diff-add">${escaped}</div>`;
|
|
786
|
+
if (line.startsWith('-')) return `<div class="diff-del">${escaped}</div>`;
|
|
787
|
+
return `<div class="diff-ctx">${escaped}</div>`;
|
|
788
|
+
}).join('');
|
|
659
789
|
}
|
|
660
790
|
|
|
661
791
|
// ── @ File reference autocomplete ───────────────────────────────────
|
|
@@ -50,12 +50,29 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- Sidebar -->
|
|
52
52
|
<aside id="sidebar" class="sidebar bg-base-200 border-r border-base-content/10 flex flex-col w-64 flex-shrink-0 overflow-hidden transition-all duration-200">
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<button
|
|
53
|
+
<!-- Sidebar tabs -->
|
|
54
|
+
<div class="flex border-b border-base-content/10 flex-shrink-0">
|
|
55
|
+
<button class="sidebar-tab active flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="sessions">📋 Sessions</button>
|
|
56
|
+
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
|
|
56
57
|
</div>
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
<!-- Sessions tab -->
|
|
59
|
+
<div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
|
|
60
|
+
<div class="p-2 border-b border-base-content/10 flex items-center justify-between">
|
|
61
|
+
<input id="session-search" type="text" class="input input-xs input-bordered flex-1 mr-2" placeholder="Search sessions...">
|
|
62
|
+
<button id="btn-new-session" class="btn btn-xs btn-primary btn-outline flex-shrink-0" title="New session">+ New</button>
|
|
63
|
+
</div>
|
|
64
|
+
<div id="session-list" class="flex-1 overflow-y-auto p-2 flex flex-col gap-1 text-sm">
|
|
65
|
+
<div class="text-xs opacity-40 text-center py-4">No sessions yet</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<!-- Tools tab -->
|
|
69
|
+
<div id="tab-tools" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
|
|
70
|
+
<div class="p-2 border-b border-base-content/10">
|
|
71
|
+
<input id="tools-search" type="text" class="input input-xs input-bordered w-full" placeholder="Search tools...">
|
|
72
|
+
</div>
|
|
73
|
+
<div id="tools-list" class="flex-1 overflow-y-auto p-2 text-sm">
|
|
74
|
+
<div class="text-xs opacity-40 text-center py-4">Type /tools to load</div>
|
|
75
|
+
</div>
|
|
59
76
|
</div>
|
|
60
77
|
</aside>
|
|
61
78
|
|
|
@@ -239,6 +239,20 @@
|
|
|
239
239
|
.status-connected { color: oklch(var(--su)); }
|
|
240
240
|
.status-disconnected { color: oklch(var(--er)); }
|
|
241
241
|
|
|
242
|
+
/* ── Sidebar tabs ──────────────────────────────────── */
|
|
243
|
+
.sidebar-tab {
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
opacity: 0.5;
|
|
246
|
+
transition: all 0.15s;
|
|
247
|
+
border-bottom: 2px solid transparent;
|
|
248
|
+
}
|
|
249
|
+
.sidebar-tab:hover { opacity: 0.8; }
|
|
250
|
+
.sidebar-tab.active {
|
|
251
|
+
opacity: 1;
|
|
252
|
+
border-bottom-color: oklch(var(--p));
|
|
253
|
+
color: oklch(var(--p));
|
|
254
|
+
}
|
|
255
|
+
|
|
242
256
|
/* ── Sidebar ───────────────────────────────────────── */
|
|
243
257
|
.sidebar .session-item {
|
|
244
258
|
padding: 0.5rem 0.6rem;
|
|
@@ -280,6 +294,73 @@
|
|
|
280
294
|
display: inline-block;
|
|
281
295
|
}
|
|
282
296
|
|
|
297
|
+
/* ── Diff syntax highlighting ─────────────────────── */
|
|
298
|
+
.confirm-diff .diff-add {
|
|
299
|
+
color: oklch(var(--su));
|
|
300
|
+
background: oklch(var(--su) / 0.1);
|
|
301
|
+
}
|
|
302
|
+
.confirm-diff .diff-del {
|
|
303
|
+
color: oklch(var(--er));
|
|
304
|
+
background: oklch(var(--er) / 0.1);
|
|
305
|
+
}
|
|
306
|
+
.confirm-diff .diff-hunk {
|
|
307
|
+
color: oklch(var(--p));
|
|
308
|
+
opacity: 0.8;
|
|
309
|
+
font-weight: 600;
|
|
310
|
+
}
|
|
311
|
+
.confirm-diff .diff-ctx {
|
|
312
|
+
opacity: 0.6;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* ── Tools sidebar list ──────────────────────────── */
|
|
316
|
+
.tools-section-title {
|
|
317
|
+
font-size: 0.7rem;
|
|
318
|
+
font-weight: 700;
|
|
319
|
+
text-transform: uppercase;
|
|
320
|
+
letter-spacing: 0.05em;
|
|
321
|
+
opacity: 0.4;
|
|
322
|
+
padding: 0.5rem 0.25rem 0.25rem;
|
|
323
|
+
}
|
|
324
|
+
.tool-item {
|
|
325
|
+
padding: 0.3rem 0.5rem;
|
|
326
|
+
border-radius: 0.25rem;
|
|
327
|
+
font-size: 0.8rem;
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 0.35rem;
|
|
331
|
+
}
|
|
332
|
+
.tool-item:hover {
|
|
333
|
+
background: oklch(var(--b3));
|
|
334
|
+
}
|
|
335
|
+
.tool-item .tool-dot {
|
|
336
|
+
width: 6px;
|
|
337
|
+
height: 6px;
|
|
338
|
+
border-radius: 50%;
|
|
339
|
+
flex-shrink: 0;
|
|
340
|
+
}
|
|
341
|
+
.tool-dot-safe { background: oklch(var(--in)); }
|
|
342
|
+
.tool-dot-write { background: oklch(var(--wa)); }
|
|
343
|
+
.tool-dot-destructive { background: oklch(var(--er)); }
|
|
344
|
+
.tool-dot-connected { background: oklch(var(--su)); }
|
|
345
|
+
.tool-dot-disconnected { background: oklch(var(--er)); }
|
|
346
|
+
.tool-dot-active { background: oklch(var(--su)); }
|
|
347
|
+
.tool-dot-inactive { background: oklch(var(--bc) / 0.3); }
|
|
348
|
+
|
|
349
|
+
.mcp-server-item {
|
|
350
|
+
padding: 0.3rem 0.5rem;
|
|
351
|
+
border-radius: 0.25rem;
|
|
352
|
+
cursor: pointer;
|
|
353
|
+
font-size: 0.8rem;
|
|
354
|
+
}
|
|
355
|
+
.mcp-server-item:hover {
|
|
356
|
+
background: oklch(var(--b3));
|
|
357
|
+
}
|
|
358
|
+
.mcp-server-tools {
|
|
359
|
+
padding-left: 1.25rem;
|
|
360
|
+
font-size: 0.75rem;
|
|
361
|
+
opacity: 0.6;
|
|
362
|
+
}
|
|
363
|
+
|
|
283
364
|
/* ── Responsive ─────────────────────────────────────── */
|
|
284
365
|
@media (max-width: 768px) {
|
|
285
366
|
.sidebar { width: 0; padding: 0; border: none; }
|