okit-cli 0.0.1

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.
Files changed (88) hide show
  1. package/README.md +222 -0
  2. package/dist/commands/check.d.ts +13 -0
  3. package/dist/commands/check.d.ts.map +1 -0
  4. package/dist/commands/check.js +259 -0
  5. package/dist/commands/check.js.map +1 -0
  6. package/dist/commands/claude.d.ts +10 -0
  7. package/dist/commands/claude.d.ts.map +1 -0
  8. package/dist/commands/claude.js +504 -0
  9. package/dist/commands/claude.js.map +1 -0
  10. package/dist/commands/menu.d.ts +3 -0
  11. package/dist/commands/menu.d.ts.map +1 -0
  12. package/dist/commands/menu.js +152 -0
  13. package/dist/commands/menu.js.map +1 -0
  14. package/dist/commands/repo.d.ts +3 -0
  15. package/dist/commands/repo.d.ts.map +1 -0
  16. package/dist/commands/repo.js +266 -0
  17. package/dist/commands/repo.js.map +1 -0
  18. package/dist/commands/uninstall.d.ts +2 -0
  19. package/dist/commands/uninstall.d.ts.map +1 -0
  20. package/dist/commands/uninstall.js +125 -0
  21. package/dist/commands/uninstall.js.map +1 -0
  22. package/dist/commands/upgrade.d.ts +6 -0
  23. package/dist/commands/upgrade.d.ts.map +1 -0
  24. package/dist/commands/upgrade.js +204 -0
  25. package/dist/commands/upgrade.js.map +1 -0
  26. package/dist/config/i18n.d.ts +141 -0
  27. package/dist/config/i18n.d.ts.map +1 -0
  28. package/dist/config/i18n.js +316 -0
  29. package/dist/config/i18n.js.map +1 -0
  30. package/dist/config/registry.d.ts +24 -0
  31. package/dist/config/registry.d.ts.map +1 -0
  32. package/dist/config/registry.js +388 -0
  33. package/dist/config/registry.js.map +1 -0
  34. package/dist/config/user.d.ts +32 -0
  35. package/dist/config/user.d.ts.map +1 -0
  36. package/dist/config/user.js +72 -0
  37. package/dist/config/user.js.map +1 -0
  38. package/dist/executor/deps/BrewDependencyProvider.d.ts +8 -0
  39. package/dist/executor/deps/BrewDependencyProvider.d.ts.map +1 -0
  40. package/dist/executor/deps/BrewDependencyProvider.js +52 -0
  41. package/dist/executor/deps/BrewDependencyProvider.js.map +1 -0
  42. package/dist/executor/deps/DependencyProvider.d.ts +11 -0
  43. package/dist/executor/deps/DependencyProvider.d.ts.map +1 -0
  44. package/dist/executor/deps/DependencyProvider.js +3 -0
  45. package/dist/executor/deps/DependencyProvider.js.map +1 -0
  46. package/dist/executor/deps/NpmDependencyProvider.d.ts +12 -0
  47. package/dist/executor/deps/NpmDependencyProvider.d.ts.map +1 -0
  48. package/dist/executor/deps/NpmDependencyProvider.js +78 -0
  49. package/dist/executor/deps/NpmDependencyProvider.js.map +1 -0
  50. package/dist/executor/deps/PipxDependencyProvider.d.ts +8 -0
  51. package/dist/executor/deps/PipxDependencyProvider.d.ts.map +1 -0
  52. package/dist/executor/deps/PipxDependencyProvider.js +32 -0
  53. package/dist/executor/deps/PipxDependencyProvider.js.map +1 -0
  54. package/dist/executor/deps/UvToolDependencyProvider.d.ts +8 -0
  55. package/dist/executor/deps/UvToolDependencyProvider.d.ts.map +1 -0
  56. package/dist/executor/deps/UvToolDependencyProvider.js +35 -0
  57. package/dist/executor/deps/UvToolDependencyProvider.js.map +1 -0
  58. package/dist/executor/plan/ExecutionPlanner.d.ts +13 -0
  59. package/dist/executor/plan/ExecutionPlanner.d.ts.map +1 -0
  60. package/dist/executor/plan/ExecutionPlanner.js +109 -0
  61. package/dist/executor/plan/ExecutionPlanner.js.map +1 -0
  62. package/dist/executor/plan/TopologicalSorter.d.ts +3 -0
  63. package/dist/executor/plan/TopologicalSorter.d.ts.map +1 -0
  64. package/dist/executor/plan/TopologicalSorter.js +49 -0
  65. package/dist/executor/plan/TopologicalSorter.js.map +1 -0
  66. package/dist/executor/plan/registryDeps.d.ts +3 -0
  67. package/dist/executor/plan/registryDeps.d.ts.map +1 -0
  68. package/dist/executor/plan/registryDeps.js +22 -0
  69. package/dist/executor/plan/registryDeps.js.map +1 -0
  70. package/dist/executor/runner.d.ts +13 -0
  71. package/dist/executor/runner.d.ts.map +1 -0
  72. package/dist/executor/runner.js +430 -0
  73. package/dist/executor/runner.js.map +1 -0
  74. package/dist/main.d.ts +3 -0
  75. package/dist/main.d.ts.map +1 -0
  76. package/dist/main.js +244 -0
  77. package/dist/main.js.map +1 -0
  78. package/dist/utils/sound.d.ts +10 -0
  79. package/dist/utils/sound.d.ts.map +1 -0
  80. package/dist/utils/sound.js +36 -0
  81. package/dist/utils/sound.js.map +1 -0
  82. package/dist/web/api/config.js +73 -0
  83. package/dist/web/api/stats.js +130 -0
  84. package/dist/web/public/app.js +284 -0
  85. package/dist/web/public/index.html +367 -0
  86. package/dist/web/public/styles.css +1004 -0
  87. package/dist/web/server.js +41 -0
  88. package/package.json +61 -0
@@ -0,0 +1,284 @@
1
+ // ═══════════════════════════════════════════
2
+ // OKIT — Dashboard Controller
3
+ // ═══════════════════════════════════════════
4
+
5
+ // State
6
+ let currentPage = 'dashboard';
7
+ let configData = null;
8
+ let refreshTimer = null;
9
+
10
+ // ─── Toast ───
11
+ function showToast(message, type = 'success') {
12
+ const toast = document.getElementById('toast');
13
+ toast.textContent = message;
14
+ toast.className = 'toast ' + type;
15
+ // Trigger reflow for re-animation
16
+ void toast.offsetWidth;
17
+ toast.classList.add('show');
18
+ clearTimeout(toast._timeout);
19
+ toast._timeout = setTimeout(() => {
20
+ toast.classList.remove('show');
21
+ }, 2600);
22
+ }
23
+
24
+ // ─── Router ───
25
+ function navigate(page) {
26
+ if (currentPage === page && document.querySelector(`#page-${page}.active`)) return;
27
+ currentPage = page;
28
+
29
+ // Update nav
30
+ document.querySelectorAll('.nav-links li').forEach(li => {
31
+ li.classList.remove('active');
32
+ });
33
+ document.querySelector(`[data-page="${page}"]`)?.closest('li').classList.add('active');
34
+
35
+ // Update pages
36
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
37
+ const target = document.getElementById(`page-${page}`);
38
+ if (target) {
39
+ target.classList.add('active');
40
+ // Re-trigger stagger animations
41
+ target.querySelectorAll('.stat-card, .stat-pill, .section, .config-section').forEach(el => {
42
+ el.style.animation = 'none';
43
+ void el.offsetWidth;
44
+ el.style.animation = '';
45
+ });
46
+ }
47
+
48
+ // Load data
49
+ if (page === 'dashboard') {
50
+ loadStats();
51
+ } else if (page === 'config') {
52
+ loadConfig();
53
+ }
54
+ }
55
+
56
+ // ─── Stats ───
57
+ async function loadStats() {
58
+ try {
59
+ const response = await fetch('/api/stats');
60
+ const data = await response.json();
61
+
62
+ // Animate value updates
63
+ animateValue('totalTokens', formatNumber(data.totalTokens));
64
+ animateValue('totalCost', '$' + data.totalCost.toFixed(2));
65
+ animateValue('sessionCount', data.sessionCount);
66
+ animateValue('activeTime', formatDuration(data.activeTime));
67
+ animateValue('linesOfCode', formatNumber(data.linesOfCode));
68
+ animateValue('commitCount', data.commitCount);
69
+ animateValue('prCount', data.prCount);
70
+
71
+ // Update sessions table
72
+ const tbody = document.getElementById('sessionsTable');
73
+ if (data.recentSessions.length === 0) {
74
+ tbody.innerHTML = '<tr><td colspan="4" class="loading"><span>暂无会话记录</span></td></tr>';
75
+ } else {
76
+ tbody.innerHTML = data.recentSessions.map((session, i) => `
77
+ <tr style="animation: rowSlide 0.3s ease ${i * 0.03}s both">
78
+ <td>${escapeHtml(session.project)}</td>
79
+ <td><span class="model-tag">${escapeHtml(session.model || '-')}</span></td>
80
+ <td>${formatNumber(session.tokens)}</td>
81
+ <td>${formatDate(session.timestamp)}</td>
82
+ </tr>
83
+ `).join('');
84
+ }
85
+
86
+ setStatus('connected');
87
+ } catch (error) {
88
+ console.error('Failed to load stats:', error);
89
+ setStatus('error');
90
+ }
91
+ }
92
+
93
+ function refreshStats() {
94
+ const btn = document.querySelector('.btn-refresh');
95
+ if (btn) {
96
+ btn.style.pointerEvents = 'none';
97
+ setTimeout(() => { btn.style.pointerEvents = ''; }, 1000);
98
+ }
99
+ loadStats();
100
+ }
101
+
102
+ // ─── Config ───
103
+ async function loadConfig() {
104
+ try {
105
+ const response = await fetch('/api/config');
106
+ const data = await response.json();
107
+ configData = data;
108
+
109
+ const settings = data.settings || {};
110
+
111
+ document.getElementById('configModel').value = settings.model || '';
112
+ document.getElementById('configTeammateMode').value = settings.teammateMode || 'auto';
113
+ document.getElementById('configLanguage').value = settings.language || 'english';
114
+ document.getElementById('configOutputStyle').value = settings.outputStyle || 'Concise';
115
+ document.getElementById('configShowTurnDuration').checked = settings.showTurnDuration || false;
116
+ document.getElementById('configSpinnerTipsEnabled').checked = settings.spinnerTipsEnabled !== false;
117
+
118
+ setStatus('connected');
119
+ } catch (error) {
120
+ console.error('Failed to load config:', error);
121
+ setStatus('error');
122
+ }
123
+ }
124
+
125
+ async function saveConfig() {
126
+ const btn = document.querySelector('.btn-save');
127
+ const originalText = btn.querySelector('span').textContent;
128
+ btn.querySelector('span').textContent = '保存中...';
129
+ btn.style.pointerEvents = 'none';
130
+
131
+ try {
132
+ const response = await fetch('/api/config', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({
136
+ model: document.getElementById('configModel').value || undefined,
137
+ teammateMode: document.getElementById('configTeammateMode').value,
138
+ language: document.getElementById('configLanguage').value,
139
+ outputStyle: document.getElementById('configOutputStyle').value,
140
+ showTurnDuration: document.getElementById('configShowTurnDuration').checked,
141
+ spinnerTipsEnabled: document.getElementById('configSpinnerTipsEnabled').checked,
142
+ }),
143
+ });
144
+
145
+ if (response.ok) {
146
+ showToast('配置已保存', 'success');
147
+ } else {
148
+ showToast('保存失败', 'error');
149
+ }
150
+ } catch (error) {
151
+ console.error('Failed to save config:', error);
152
+ showToast('保存失败', 'error');
153
+ } finally {
154
+ btn.querySelector('span').textContent = originalText;
155
+ btn.style.pointerEvents = '';
156
+ }
157
+ }
158
+
159
+ // ─── Status ───
160
+ function setStatus(status) {
161
+ const statusEl = document.getElementById('status');
162
+ statusEl.className = 'status ' + status;
163
+ const textEl = statusEl.querySelector('.status-text');
164
+
165
+ if (status === 'connected') {
166
+ textEl.textContent = '已连接';
167
+ } else if (status === 'error') {
168
+ textEl.textContent = '连接失败';
169
+ } else {
170
+ textEl.textContent = '连接中...';
171
+ }
172
+ }
173
+
174
+ // ─── Helpers ───
175
+ function formatNumber(num) {
176
+ if (num === undefined || num === null) return '—';
177
+ if (num >= 1000000) {
178
+ return (num / 1000000).toFixed(1) + 'M';
179
+ } else if (num >= 1000) {
180
+ return (num / 1000).toFixed(1) + 'K';
181
+ }
182
+ return num.toString();
183
+ }
184
+
185
+ function formatDuration(seconds) {
186
+ if (seconds === undefined || seconds === null) return '—';
187
+ if (seconds < 60) {
188
+ return seconds + '秒';
189
+ } else if (seconds < 3600) {
190
+ return Math.floor(seconds / 60) + '分钟';
191
+ } else if (seconds < 86400) {
192
+ return Math.floor(seconds / 3600) + '小时';
193
+ }
194
+ return Math.floor(seconds / 86400) + '天';
195
+ }
196
+
197
+ function formatDate(timestamp) {
198
+ const date = new Date(timestamp);
199
+ const now = new Date();
200
+ const diff = now - date;
201
+
202
+ if (diff < 60000) {
203
+ return '刚刚';
204
+ } else if (diff < 3600000) {
205
+ return Math.floor(diff / 60000) + '分钟前';
206
+ } else if (diff < 86400000) {
207
+ return Math.floor(diff / 3600000) + '小时前';
208
+ } else if (diff < 604800000) {
209
+ return Math.floor(diff / 86400000) + '天前';
210
+ }
211
+
212
+ return date.toLocaleDateString('zh-CN');
213
+ }
214
+
215
+ function escapeHtml(text) {
216
+ const div = document.createElement('div');
217
+ div.textContent = text;
218
+ return div.innerHTML;
219
+ }
220
+
221
+ function animateValue(id, newValue) {
222
+ const el = document.getElementById(id);
223
+ if (!el) return;
224
+ const current = el.textContent;
225
+ if (current === String(newValue)) return;
226
+ el.style.transition = 'none';
227
+ el.style.opacity = '0.4';
228
+ el.style.transform = 'translateY(4px)';
229
+ void el.offsetWidth;
230
+ el.textContent = newValue;
231
+ el.style.transition = 'opacity 0.35s ease, transform 0.35s ease';
232
+ el.style.opacity = '1';
233
+ el.style.transform = 'translateY(0)';
234
+ }
235
+
236
+ // ─── Inject dynamic keyframes ───
237
+ (function injectStyles() {
238
+ const style = document.createElement('style');
239
+ style.textContent = `
240
+ @keyframes rowSlide {
241
+ from { opacity: 0; transform: translateX(-8px); }
242
+ to { opacity: 1; transform: translateX(0); }
243
+ }
244
+ .model-tag {
245
+ display: inline-block;
246
+ font-size: 12px;
247
+ padding: 2px 8px;
248
+ border-radius: 4px;
249
+ background: rgba(167, 139, 250, 0.08);
250
+ color: rgba(167, 139, 250, 0.8);
251
+ font-family: 'DM Sans', monospace;
252
+ letter-spacing: 0.2px;
253
+ }
254
+ `;
255
+ document.head.appendChild(style);
256
+ })();
257
+
258
+ // ─── Event Listeners ───
259
+ document.addEventListener('DOMContentLoaded', () => {
260
+ // Handle nav clicks
261
+ document.querySelectorAll('[data-page]').forEach(link => {
262
+ link.addEventListener('click', (e) => {
263
+ e.preventDefault();
264
+ navigate(link.dataset.page);
265
+ });
266
+ });
267
+
268
+ // Handle hash changes
269
+ window.addEventListener('hashchange', () => {
270
+ const hash = window.location.hash.slice(2) || 'dashboard';
271
+ navigate(hash);
272
+ });
273
+
274
+ // Initial navigation
275
+ const initialPage = window.location.hash.slice(2) || 'dashboard';
276
+ navigate(initialPage);
277
+
278
+ // Auto-refresh stats every 30 seconds
279
+ refreshTimer = setInterval(() => {
280
+ if (currentPage === 'dashboard') {
281
+ loadStats();
282
+ }
283
+ }, 30000);
284
+ });
@@ -0,0 +1,367 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OKIT — Claude Code Dashboard</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
10
+ <link rel="stylesheet" href="/styles.css">
11
+ </head>
12
+ <body>
13
+ <!-- Ambient background -->
14
+ <div class="ambient-bg">
15
+ <div class="ambient-orb ambient-orb--1"></div>
16
+ <div class="ambient-orb ambient-orb--2"></div>
17
+ <div class="ambient-orb ambient-orb--3"></div>
18
+ </div>
19
+
20
+ <div id="app">
21
+ <nav class="sidebar">
22
+ <div class="sidebar-header">
23
+ <div class="logo">
24
+ <div class="logo-mark">
25
+ <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
26
+ <rect x="2" y="2" width="24" height="24" rx="6" stroke="url(#logoGrad)" stroke-width="2"/>
27
+ <circle cx="10" cy="11" r="2" fill="url(#logoGrad)"/>
28
+ <circle cx="18" cy="11" r="2" fill="url(#logoGrad)"/>
29
+ <path d="M9 18c1.5 2 4 3 5 3s3.5-1 5-3" stroke="url(#logoGrad)" stroke-width="1.5" stroke-linecap="round"/>
30
+ <defs>
31
+ <linearGradient id="logoGrad" x1="0" y1="0" x2="28" y2="28">
32
+ <stop stop-color="#c084fc"/>
33
+ <stop offset="1" stop-color="#60a5fa"/>
34
+ </linearGradient>
35
+ </defs>
36
+ </svg>
37
+ </div>
38
+ <span class="logo-text">OKIT</span>
39
+ </div>
40
+ </div>
41
+
42
+ <ul class="nav-links">
43
+ <li class="active">
44
+ <a href="#/" data-page="dashboard">
45
+ <span class="nav-icon">
46
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
47
+ <rect x="1" y="1" width="7" height="7" rx="2" stroke="currentColor" stroke-width="1.5"/>
48
+ <rect x="12" y="1" width="7" height="7" rx="2" stroke="currentColor" stroke-width="1.5"/>
49
+ <rect x="1" y="12" width="7" height="7" rx="2" stroke="currentColor" stroke-width="1.5"/>
50
+ <rect x="12" y="12" width="7" height="7" rx="2" stroke="currentColor" stroke-width="1.5"/>
51
+ </svg>
52
+ </span>
53
+ <span class="nav-label">使用量</span>
54
+ </a>
55
+ </li>
56
+ <li>
57
+ <a href="#/config" data-page="config">
58
+ <span class="nav-icon">
59
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
60
+ <circle cx="10" cy="10" r="3" stroke="currentColor" stroke-width="1.5"/>
61
+ <path d="M10 1v3M10 16v3M1 10h3M16 10h3M3.93 3.93l2.12 2.12M13.95 13.95l2.12 2.12M3.93 16.07l2.12-2.12M13.95 6.05l2.12-2.12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
62
+ </svg>
63
+ </span>
64
+ <span class="nav-label">配置</span>
65
+ </a>
66
+ </li>
67
+ </ul>
68
+
69
+ <div class="sidebar-footer">
70
+ <div class="status" id="status">
71
+ <span class="status-dot"></span>
72
+ <span class="status-text">连接中...</span>
73
+ </div>
74
+ <div class="version-tag">v2.0</div>
75
+ </div>
76
+ </nav>
77
+
78
+ <main class="main-content">
79
+ <!-- Dashboard Page -->
80
+ <div id="page-dashboard" class="page active">
81
+ <header class="page-header">
82
+ <div class="page-title-group">
83
+ <h2>使用量统计</h2>
84
+ <p class="page-subtitle">Claude Code 运行数据总览</p>
85
+ </div>
86
+ <button class="btn-refresh" onclick="refreshStats()">
87
+ <svg class="refresh-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
88
+ <path d="M14 8A6 6 0 1 1 8 2c1.66 0 3.14.69 4.22 1.78L14 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
89
+ <path d="M14 2v4h-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
90
+ </svg>
91
+ <span>刷新</span>
92
+ </button>
93
+ </header>
94
+
95
+ <div class="stats-grid">
96
+ <div class="stat-card" data-accent="purple">
97
+ <div class="stat-card-bg"></div>
98
+ <div class="stat-icon-wrap">
99
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
100
+ <path d="M2 16l3-3 4 4 5-8 6 7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
101
+ <circle cx="5" cy="4" r="2" stroke="currentColor" stroke-width="1.5"/>
102
+ <circle cx="17" cy="6" r="2" stroke="currentColor" stroke-width="1.5"/>
103
+ </svg>
104
+ </div>
105
+ <div class="stat-content">
106
+ <div class="stat-label">总 Token 数</div>
107
+ <div class="stat-value" id="totalTokens">—</div>
108
+ </div>
109
+ <div class="stat-glow"></div>
110
+ </div>
111
+
112
+ <div class="stat-card" data-accent="blue">
113
+ <div class="stat-card-bg"></div>
114
+ <div class="stat-icon-wrap">
115
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
116
+ <circle cx="11" cy="11" r="9" stroke="currentColor" stroke-width="1.5"/>
117
+ <path d="M11 6v5.5l3.5 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
118
+ <path d="M7 2l1 2M15 2l-1 2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
119
+ </svg>
120
+ </div>
121
+ <div class="stat-content">
122
+ <div class="stat-label">估算成本</div>
123
+ <div class="stat-value" id="totalCost">—</div>
124
+ </div>
125
+ <div class="stat-glow"></div>
126
+ </div>
127
+
128
+ <div class="stat-card" data-accent="teal">
129
+ <div class="stat-card-bg"></div>
130
+ <div class="stat-icon-wrap">
131
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
132
+ <rect x="3" y="3" width="16" height="16" rx="3" stroke="currentColor" stroke-width="1.5"/>
133
+ <path d="M8 8h6M8 11h6M8 14h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
134
+ </svg>
135
+ </div>
136
+ <div class="stat-content">
137
+ <div class="stat-label">会话数</div>
138
+ <div class="stat-value" id="sessionCount">—</div>
139
+ </div>
140
+ <div class="stat-glow"></div>
141
+ </div>
142
+
143
+ <div class="stat-card" data-accent="amber">
144
+ <div class="stat-card-bg"></div>
145
+ <div class="stat-icon-wrap">
146
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
147
+ <circle cx="11" cy="11" r="9" stroke="currentColor" stroke-width="1.5"/>
148
+ <path d="M11 6v5l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
149
+ </svg>
150
+ </div>
151
+ <div class="stat-content">
152
+ <div class="stat-label">活跃时间</div>
153
+ <div class="stat-value" id="activeTime">—</div>
154
+ </div>
155
+ <div class="stat-glow"></div>
156
+ </div>
157
+ </div>
158
+
159
+ <div class="stats-secondary">
160
+ <div class="stat-pill">
161
+ <div class="stat-pill-icon">
162
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
163
+ <path d="M4 2v12M8 4v10M12 6v6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
164
+ </svg>
165
+ </div>
166
+ <div class="stat-pill-content">
167
+ <span class="stat-pill-label">代码行数</span>
168
+ <span class="stat-pill-value" id="linesOfCode">—</span>
169
+ </div>
170
+ </div>
171
+ <div class="stat-pill">
172
+ <div class="stat-pill-icon">
173
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
174
+ <circle cx="8" cy="8" r="3" stroke="currentColor" stroke-width="1.5"/>
175
+ <path d="M8 1v2M8 13v2M1 8h2M13 8h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
176
+ </svg>
177
+ </div>
178
+ <div class="stat-pill-content">
179
+ <span class="stat-pill-label">提交数</span>
180
+ <span class="stat-pill-value" id="commitCount">—</span>
181
+ </div>
182
+ </div>
183
+ <div class="stat-pill">
184
+ <div class="stat-pill-icon">
185
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
186
+ <path d="M5 1v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2V1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
187
+ <path d="M3 9l5 6 5-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
188
+ </svg>
189
+ </div>
190
+ <div class="stat-pill-content">
191
+ <span class="stat-pill-label">PR 数</span>
192
+ <span class="stat-pill-value" id="prCount">—</span>
193
+ </div>
194
+ </div>
195
+ </div>
196
+
197
+ <div class="section">
198
+ <div class="section-header">
199
+ <h3>最近会话</h3>
200
+ <span class="section-badge">实时</span>
201
+ </div>
202
+ <div class="table-container">
203
+ <table class="data-table">
204
+ <thead>
205
+ <tr>
206
+ <th>项目</th>
207
+ <th>模型</th>
208
+ <th>Token 数</th>
209
+ <th>时间</th>
210
+ </tr>
211
+ </thead>
212
+ <tbody id="sessionsTable">
213
+ <tr>
214
+ <td colspan="4" class="loading">
215
+ <div class="loading-spinner"></div>
216
+ <span>加载中...</span>
217
+ </td>
218
+ </tr>
219
+ </tbody>
220
+ </table>
221
+ </div>
222
+ </div>
223
+ </div>
224
+
225
+ <!-- Config Page -->
226
+ <div id="page-config" class="page">
227
+ <header class="page-header">
228
+ <div class="page-title-group">
229
+ <h2>Claude Code 配置</h2>
230
+ <p class="page-subtitle">管理模型、界面与运行设置</p>
231
+ </div>
232
+ <button class="btn-save" onclick="saveConfig()">
233
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
234
+ <path d="M12.67 2H3.33C2.6 2 2 2.6 2 3.33v9.34C2 13.4 2.6 14 3.33 14h9.34c.73 0 1.33-.6 1.33-1.33V3.33C14 2.6 13.4 2 12.67 2Z" stroke="currentColor" stroke-width="1.3"/>
235
+ <path d="M5 2v4h6V2" stroke="currentColor" stroke-width="1.3"/>
236
+ <rect x="5" y="9" width="6" height="3" rx="0.5" stroke="currentColor" stroke-width="1.3"/>
237
+ </svg>
238
+ <span>保存配置</span>
239
+ </button>
240
+ </header>
241
+
242
+ <div class="config-grid">
243
+ <div class="config-section" data-icon="model">
244
+ <div class="config-section-header">
245
+ <div class="config-section-icon">
246
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
247
+ <path d="M9 1L1 5v8l8 4 8-4V5L9 1z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
248
+ <path d="M1 5l8 4 8-4M9 9v8" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
249
+ </svg>
250
+ </div>
251
+ <h3>模型设置</h3>
252
+ </div>
253
+ <div class="form-group">
254
+ <label for="configModel">模型</label>
255
+ <div class="input-wrap">
256
+ <input type="text" id="configModel" class="input" placeholder="claude-sonnet-4-5-20250929">
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <div class="config-section" data-icon="team">
262
+ <div class="config-section-header">
263
+ <div class="config-section-icon">
264
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
265
+ <circle cx="7" cy="6" r="3" stroke="currentColor" stroke-width="1.3"/>
266
+ <circle cx="13" cy="7" r="2.5" stroke="currentColor" stroke-width="1.3"/>
267
+ <path d="M1 16c0-3.3 2.7-6 6-6 1.3 0 2.5.4 3.5 1.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
268
+ <path d="M10 16c0-2.5 1.3-4 3-4s3 1.5 3 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
269
+ </svg>
270
+ </div>
271
+ <h3>Agent Teams</h3>
272
+ </div>
273
+ <div class="form-group">
274
+ <label for="configTeammateMode">队友显示模式</label>
275
+ <div class="select-wrap">
276
+ <select id="configTeammateMode" class="select">
277
+ <option value="auto">auto</option>
278
+ <option value="in-process">in-process</option>
279
+ <option value="tmux">tmux</option>
280
+ </select>
281
+ <svg class="select-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
282
+ <path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
283
+ </svg>
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <div class="config-section" data-icon="interface">
289
+ <div class="config-section-header">
290
+ <div class="config-section-icon">
291
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
292
+ <rect x="1" y="3" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.3"/>
293
+ <path d="M1 7h16" stroke="currentColor" stroke-width="1.3"/>
294
+ <circle cx="4" cy="5" r="0.8" fill="currentColor"/>
295
+ <circle cx="7" cy="5" r="0.8" fill="currentColor"/>
296
+ <circle cx="10" cy="5" r="0.8" fill="currentColor"/>
297
+ </svg>
298
+ </div>
299
+ <h3>界面设置</h3>
300
+ </div>
301
+ <div class="form-group">
302
+ <label for="configLanguage">语言</label>
303
+ <div class="select-wrap">
304
+ <select id="configLanguage" class="select">
305
+ <option value="english">English</option>
306
+ <option value="japanese">Japanese</option>
307
+ <option value="chinese">Chinese</option>
308
+ </select>
309
+ <svg class="select-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
310
+ <path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
311
+ </svg>
312
+ </div>
313
+ </div>
314
+ <div class="form-group">
315
+ <label for="configOutputStyle">输出风格</label>
316
+ <div class="select-wrap">
317
+ <select id="configOutputStyle" class="select">
318
+ <option value="Concise">Concise</option>
319
+ <option value="Explanatory">Explanatory</option>
320
+ </select>
321
+ <svg class="select-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
322
+ <path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
323
+ </svg>
324
+ </div>
325
+ </div>
326
+ </div>
327
+
328
+ <div class="config-section" data-icon="other">
329
+ <div class="config-section-header">
330
+ <div class="config-section-icon">
331
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
332
+ <circle cx="9" cy="9" r="7" stroke="currentColor" stroke-width="1.3"/>
333
+ <path d="M6 9l2 2 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
334
+ </svg>
335
+ </div>
336
+ <h3>其他设置</h3>
337
+ </div>
338
+ <div class="form-group">
339
+ <label class="toggle-label">
340
+ <span class="toggle-wrap">
341
+ <input type="checkbox" id="configShowTurnDuration" class="toggle-input">
342
+ <span class="toggle-track"><span class="toggle-thumb"></span></span>
343
+ </span>
344
+ <span class="toggle-text">显示轮次耗时</span>
345
+ </label>
346
+ </div>
347
+ <div class="form-group">
348
+ <label class="toggle-label">
349
+ <span class="toggle-wrap">
350
+ <input type="checkbox" id="configSpinnerTipsEnabled" class="toggle-input">
351
+ <span class="toggle-track"><span class="toggle-thumb"></span></span>
352
+ </span>
353
+ <span class="toggle-text">启用加载提示</span>
354
+ </label>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ </main>
360
+ </div>
361
+
362
+ <!-- Toast notification -->
363
+ <div id="toast" class="toast"></div>
364
+
365
+ <script src="/app.js"></script>
366
+ </body>
367
+ </html>