code-context-control 2.28.0__py3-none-any.whl
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.
- cli/__init__.py +1 -0
- cli/_hook_utils.py +99 -0
- cli/c3.py +6152 -0
- cli/commands/__init__.py +1 -0
- cli/commands/common.py +312 -0
- cli/commands/parser.py +286 -0
- cli/docs.html +3178 -0
- cli/edits.html +878 -0
- cli/hook_auto_snapshot.py +142 -0
- cli/hook_c3_signal.py +61 -0
- cli/hook_c3read.py +116 -0
- cli/hook_edit_ledger.py +213 -0
- cli/hook_edit_unlock.py +170 -0
- cli/hook_filter.py +130 -0
- cli/hook_ghost_files.py +238 -0
- cli/hook_pretool_enforce.py +334 -0
- cli/hook_read.py +200 -0
- cli/hook_session_stats.py +62 -0
- cli/hook_terse_advisor.py +190 -0
- cli/hub.html +3764 -0
- cli/hub_server.py +1619 -0
- cli/mcp_proxy.py +428 -0
- cli/mcp_server.py +660 -0
- cli/server.py +2985 -0
- cli/tools/__init__.py +4 -0
- cli/tools/_helpers.py +65 -0
- cli/tools/agent.py +1165 -0
- cli/tools/compress.py +215 -0
- cli/tools/delegate.py +1184 -0
- cli/tools/edit.py +313 -0
- cli/tools/edits.py +118 -0
- cli/tools/filter.py +285 -0
- cli/tools/impact.py +163 -0
- cli/tools/memory.py +469 -0
- cli/tools/read.py +224 -0
- cli/tools/search.py +337 -0
- cli/tools/session.py +95 -0
- cli/tools/shell.py +193 -0
- cli/tools/status.py +306 -0
- cli/tools/validate.py +310 -0
- cli/ui/api.js +36 -0
- cli/ui/app.js +207 -0
- cli/ui/components/chat.js +758 -0
- cli/ui/components/dashboard.js +689 -0
- cli/ui/components/edits.js +220 -0
- cli/ui/components/instructions.js +481 -0
- cli/ui/components/memory.js +626 -0
- cli/ui/components/sessions.js +606 -0
- cli/ui/components/settings.js +1404 -0
- cli/ui/components/sidebar.js +156 -0
- cli/ui/icons.js +51 -0
- cli/ui/shared.js +119 -0
- cli/ui/theme.js +22 -0
- cli/ui.html +168 -0
- cli/ui_legacy.html +6797 -0
- cli/ui_nano.html +503 -0
- code_context_control-2.28.0.dist-info/METADATA +248 -0
- code_context_control-2.28.0.dist-info/RECORD +150 -0
- code_context_control-2.28.0.dist-info/WHEEL +5 -0
- code_context_control-2.28.0.dist-info/entry_points.txt +4 -0
- code_context_control-2.28.0.dist-info/licenses/LICENSE +201 -0
- code_context_control-2.28.0.dist-info/top_level.txt +5 -0
- core/__init__.py +75 -0
- core/config.py +269 -0
- core/ide.py +188 -0
- oracle/__init__.py +1 -0
- oracle/config.py +75 -0
- oracle/oracle.html +3900 -0
- oracle/oracle_server.py +663 -0
- oracle/services/__init__.py +1 -0
- oracle/services/c3_bridge.py +210 -0
- oracle/services/chat_engine.py +1103 -0
- oracle/services/chat_store.py +155 -0
- oracle/services/cross_memory.py +154 -0
- oracle/services/federated_graph.py +463 -0
- oracle/services/health_checker.py +117 -0
- oracle/services/insight_engine.py +307 -0
- oracle/services/memory_reader.py +106 -0
- oracle/services/memory_writer.py +182 -0
- oracle/services/ollama_bridge.py +332 -0
- oracle/services/project_scanner.py +87 -0
- oracle/services/review_agent.py +206 -0
- services/__init__.py +1 -0
- services/activity_log.py +93 -0
- services/agent_base.py +124 -0
- services/agents.py +1529 -0
- services/auto_memory.py +407 -0
- services/bench/__init__.py +6 -0
- services/bench/external/__init__.py +29 -0
- services/bench/external/aider_polyglot.py +405 -0
- services/bench/external/swe_bench.py +485 -0
- services/benchmark_dashboard.py +596 -0
- services/claude_md.py +785 -0
- services/compressor.py +592 -0
- services/context_snapshot.py +356 -0
- services/conversation_store.py +870 -0
- services/doc_index.py +537 -0
- services/e2e_benchmark.py +2884 -0
- services/e2e_evaluator.py +396 -0
- services/e2e_tasks.py +743 -0
- services/edit_ledger.py +459 -0
- services/embedding_index.py +341 -0
- services/error_reporting.py +123 -0
- services/file_memory.py +734 -0
- services/hub_service.py +585 -0
- services/indexer.py +712 -0
- services/memory.py +318 -0
- services/memory_consolidator.py +538 -0
- services/memory_graph.py +382 -0
- services/memory_grounder.py +304 -0
- services/memory_scorer.py +246 -0
- services/metrics.py +86 -0
- services/notifications.py +209 -0
- services/ollama_client.py +201 -0
- services/output_filter.py +488 -0
- services/parser.py +1238 -0
- services/project_manager.py +579 -0
- services/protocol.py +306 -0
- services/proxy_state.py +152 -0
- services/retrieval_broker.py +129 -0
- services/router.py +414 -0
- services/runtime.py +326 -0
- services/session_benchmark.py +1945 -0
- services/session_manager.py +1026 -0
- services/session_preloader.py +251 -0
- services/text_index.py +90 -0
- services/tool_classifier.py +176 -0
- services/transcript_index.py +340 -0
- services/validation_cache.py +155 -0
- services/vector_store.py +299 -0
- services/version_tracker.py +271 -0
- services/watcher.py +192 -0
- tui/__init__.py +0 -0
- tui/backend.py +59 -0
- tui/main.py +145 -0
- tui/screens/__init__.py +1 -0
- tui/screens/benchmark_view.py +109 -0
- tui/screens/claudemd_view.py +46 -0
- tui/screens/compress_view.py +52 -0
- tui/screens/index_view.py +74 -0
- tui/screens/init_view.py +82 -0
- tui/screens/mcp_view.py +73 -0
- tui/screens/optimize_view.py +41 -0
- tui/screens/pipe_view.py +46 -0
- tui/screens/projects_view.py +355 -0
- tui/screens/search_view.py +55 -0
- tui/screens/session_view.py +143 -0
- tui/screens/stats.py +158 -0
- tui/screens/ui_view.py +54 -0
- tui/theme.tcss +335 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// ─── Sidebar ─────────────────────────────
|
|
2
|
+
function ProjectSwitcher({ registry }) {
|
|
3
|
+
const [open, setOpen] = useState(false);
|
|
4
|
+
const ref = React.useRef(null);
|
|
5
|
+
const myPort = parseInt(window.location.port) || 3333;
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!open) return;
|
|
9
|
+
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
|
|
10
|
+
document.addEventListener("mousedown", handler);
|
|
11
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
12
|
+
}, [open]);
|
|
13
|
+
|
|
14
|
+
const entryName = (e) => {
|
|
15
|
+
if (e.project_name && e.project_name.trim()) return e.project_name.trim();
|
|
16
|
+
if (e.project_path) {
|
|
17
|
+
const parts = e.project_path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
18
|
+
return parts[parts.length - 1] || "Unknown";
|
|
19
|
+
}
|
|
20
|
+
return "Unknown";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const others = (registry || []).filter(e => e.port !== myPort);
|
|
24
|
+
if (others.length === 0) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div ref={ref} style={{ position: "relative" }}>
|
|
28
|
+
<button onClick={() => setOpen(!open)} title="Switch project"
|
|
29
|
+
style={{
|
|
30
|
+
padding: "3px 6px", borderRadius: 4, border: `1px solid ${T.border}`, background: "transparent",
|
|
31
|
+
cursor: "pointer", display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: T.textDim
|
|
32
|
+
}}>
|
|
33
|
+
<I name="shuffle" size={10} color={T.textDim} />
|
|
34
|
+
<span>{others.length}</span>
|
|
35
|
+
</button>
|
|
36
|
+
{open && (
|
|
37
|
+
<div style={{
|
|
38
|
+
position: "absolute", top: "100%", left: 0, marginTop: 4, zIndex: 100,
|
|
39
|
+
background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6,
|
|
40
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)", minWidth: 200, overflow: "hidden"
|
|
41
|
+
}}>
|
|
42
|
+
{others.map(e => (
|
|
43
|
+
<a key={e.port} href={`http://localhost:${e.port}`}
|
|
44
|
+
style={{
|
|
45
|
+
display: "flex", alignItems: "center", gap: 8, padding: "8px 12px",
|
|
46
|
+
color: T.text, textDecoration: "none", fontSize: 12,
|
|
47
|
+
borderBottom: `1px solid ${T.border}20`, transition: "background 0.1s"
|
|
48
|
+
}}
|
|
49
|
+
onMouseEnter={ev => ev.currentTarget.style.background = T.surfaceAlt}
|
|
50
|
+
onMouseLeave={ev => ev.currentTarget.style.background = "transparent"}>
|
|
51
|
+
<GlowDot color={T.accent} size={5} />
|
|
52
|
+
<span style={{ flex: 1 }}>{entryName(e)}</span>
|
|
53
|
+
<span className="mono" style={{ fontSize: 9, color: T.textDim }}>:{e.port}</span>
|
|
54
|
+
</a>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const Sidebar = ({ tab, setTab, tabs, sidebarOpen, sidebarPinned, toggleSidebarPin, setSidebarHover, connected, health, healthChecking, loadHealth, registry }) => (
|
|
63
|
+
<div style={{
|
|
64
|
+
width: sidebarOpen ? 210 : 54, flexShrink: 0, background: T.surface, borderRight: `1px solid ${T.border}`,
|
|
65
|
+
display: "flex", flexDirection: "column", transition: "width 0.25s ease", overflow: "hidden"
|
|
66
|
+
}}
|
|
67
|
+
onMouseEnter={() => setSidebarHover(true)}
|
|
68
|
+
onMouseLeave={() => setSidebarHover(false)}>
|
|
69
|
+
<div style={{
|
|
70
|
+
padding: sidebarOpen ? "16px 14px" : "16px 10px", borderBottom: `1px solid ${T.border}`,
|
|
71
|
+
display: "flex", alignItems: "center", gap: 10
|
|
72
|
+
}}>
|
|
73
|
+
<div style={{
|
|
74
|
+
width: 32, height: 32, borderRadius: 8, background: `${T.accent}18`, border: `1px solid ${T.accent}40`,
|
|
75
|
+
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0
|
|
76
|
+
}}>
|
|
77
|
+
<I name="terminal" size={16} color={T.accent} />
|
|
78
|
+
</div>
|
|
79
|
+
{sidebarOpen && (
|
|
80
|
+
<>
|
|
81
|
+
<div>
|
|
82
|
+
<div style={{ fontSize: 15, fontWeight: 700, color: T.text, letterSpacing: -0.5, lineHeight: 1 }}>
|
|
83
|
+
C<span style={{ color: T.accent }}>3</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div style={{ fontSize: 9, color: T.textMuted, textTransform: "uppercase", letterSpacing: 1.5, marginTop: 2 }}>Context Control</div>
|
|
86
|
+
</div>
|
|
87
|
+
<button onClick={toggleSidebarPin} title={sidebarPinned ? "Unpin sidebar" : "Pin sidebar open"}
|
|
88
|
+
style={{
|
|
89
|
+
marginLeft: "auto", width: 22, height: 22, borderRadius: 4, border: "none",
|
|
90
|
+
background: "transparent", cursor: "pointer", display: "flex", alignItems: "center",
|
|
91
|
+
justifyContent: "center", flexShrink: 0
|
|
92
|
+
}}>
|
|
93
|
+
<I name="pin" size={12} color={sidebarPinned ? T.accent : T.textDim} />
|
|
94
|
+
</button>
|
|
95
|
+
</>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<nav style={{ padding: "8px 6px", flex: 1, display: "flex", flexDirection: "column", gap: 2 }}>
|
|
100
|
+
{tabs.map(t => {
|
|
101
|
+
const active = tab === t.id;
|
|
102
|
+
return (
|
|
103
|
+
<button key={t.id} onClick={() => setTab(t.id)}
|
|
104
|
+
style={{
|
|
105
|
+
display: "flex", alignItems: "center", gap: 10, width: "100%",
|
|
106
|
+
padding: sidebarOpen ? "9px 12px" : "9px 0", justifyContent: sidebarOpen ? "flex-start" : "center",
|
|
107
|
+
borderRadius: 6, border: "none", cursor: "pointer",
|
|
108
|
+
background: active ? T.accentDim : "transparent", color: active ? T.accent : T.textMuted,
|
|
109
|
+
fontSize: 13, fontWeight: active ? 600 : 400, transition: "all 0.15s", position: "relative"
|
|
110
|
+
}}>
|
|
111
|
+
{active && <div style={{ position: "absolute", left: 0, top: "20%", bottom: "20%", width: 3, borderRadius: "0 2px 2px 0", background: T.accent }} />}
|
|
112
|
+
<I name={t.icon} size={16} />
|
|
113
|
+
{sidebarOpen && <span>{t.label}</span>}
|
|
114
|
+
</button>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</nav>
|
|
118
|
+
|
|
119
|
+
{sidebarOpen && (
|
|
120
|
+
<div style={{ padding: "10px 14px", borderTop: `1px solid ${T.border}`, fontSize: 10, color: T.textDim }}>
|
|
121
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
|
|
122
|
+
<GlowDot color={connected ? T.accent : T.error} size={5} />
|
|
123
|
+
<span style={{ color: T.textMuted }}>{connected ? "Connected" : "Disconnected"}</span>
|
|
124
|
+
{health?.session && (
|
|
125
|
+
<span className="mono" style={{ marginLeft: "auto", color: T.textDim, fontSize: 9 }}>
|
|
126
|
+
{health.session.tool_calls} calls
|
|
127
|
+
</span>
|
|
128
|
+
)}
|
|
129
|
+
<button onClick={loadHealth} disabled={healthChecking} title="Check connections"
|
|
130
|
+
style={{
|
|
131
|
+
marginLeft: health?.session ? 0 : "auto", padding: "1px 4px", borderRadius: 3,
|
|
132
|
+
border: `1px solid ${T.border}`, background: "transparent",
|
|
133
|
+
cursor: healthChecking ? "default" : "pointer", display: "flex", alignItems: "center",
|
|
134
|
+
opacity: healthChecking ? 0.5 : 1
|
|
135
|
+
}}>
|
|
136
|
+
<I name="refresh" size={9} color={T.textDim}
|
|
137
|
+
style={healthChecking ? { animation: "spin 0.6s linear infinite" } : {}} />
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
{health?.sources && (
|
|
141
|
+
<div style={{ display: "flex", gap: 4, flexWrap: "wrap", marginBottom: 3 }}>
|
|
142
|
+
{Object.entries(health.sources).map(([name, ok]) => (
|
|
143
|
+
<span key={name} style={{
|
|
144
|
+
padding: "1px 5px", borderRadius: 3, fontSize: 9, fontFamily: "'JetBrains Mono', monospace",
|
|
145
|
+
background: ok ? `${T.accent}18` : `${T.error}18`,
|
|
146
|
+
color: ok ? T.accent : T.error,
|
|
147
|
+
border: `1px solid ${ok ? T.accent : T.error}30`,
|
|
148
|
+
}}>{name}</span>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
<ProjectSwitcher registry={registry} />
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
cli/ui/icons.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// ─── Icons (inline SVG) ──────────────────
|
|
2
|
+
const Icon = ({ d, size = 16, color = "currentColor", style = {} }) => (
|
|
3
|
+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color}
|
|
4
|
+
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={style}>
|
|
5
|
+
<path d={d} />
|
|
6
|
+
</svg>
|
|
7
|
+
);
|
|
8
|
+
const icons = {
|
|
9
|
+
gauge: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM12 6v6l4 2",
|
|
10
|
+
minimize: "M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7",
|
|
11
|
+
search: "M11 3a8 8 0 1 0 0 16 8 8 0 0 0 0-16zM21 21l-4.35-4.35",
|
|
12
|
+
clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM12 6v6l4 2",
|
|
13
|
+
braces: "M7 4a2 2 0 0 0-2 2v3a2 2 0 0 1-2 2 2 2 0 0 1 2 2v3a2 2 0 0 0 2 2M17 4a2 2 0 0 1 2 2v3a2 2 0 0 0 2 2 2 2 0 0 0-2 2v3a2 2 0 0 1-2 2",
|
|
14
|
+
settings: "M12 2a3 3 0 0 0-3 3v1a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.86-.5a3 3 0 0 0-3 5.2l.87.5a2 2 0 0 1 1 1.73v.5a2 2 0 0 1-1 1.73l-.87.5a3 3 0 0 0 3 5.2l.86-.5a2 2 0 0 1 2 0l.43.25A2 2 0 0 1 9 19v1a3 3 0 0 0 6 0v-1a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.86.5a3 3 0 0 0 3-5.2l-.87-.5A2 2 0 0 1 20 10.27v-.5a2 2 0 0 1 1-1.73l.87-.5a3 3 0 0 0-3-5.2l-.86.5a2 2 0 0 1-2 0L15.57 2.5A2 2 0 0 1 15 1V1a3 3 0 0 0-6 0z",
|
|
15
|
+
terminal: "M4 17l6-6-6-6M12 19h8",
|
|
16
|
+
zap: "M13 2L3 14h9l-1 8 10-12h-9l1-8",
|
|
17
|
+
file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6",
|
|
18
|
+
refresh: "M1 4v6h6M23 20v-6h-6M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15",
|
|
19
|
+
copy: "M20 9h-9a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2zM5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1",
|
|
20
|
+
check: "M20 6L9 17l-5-5",
|
|
21
|
+
layers: "M12 2l10 6.5v7L12 22 2 15.5v-7zM12 22v-7M22 8.5l-10 7-10-7",
|
|
22
|
+
brain: "M12 2a7 7 0 0 0-7 7c0 3 2 5.5 5 7v4h4v-4c3-1.5 5-4 5-7a7 7 0 0 0-7-7z",
|
|
23
|
+
chevron: "M9 18l6-6-6-6",
|
|
24
|
+
arrowDown: "M12 5v14M19 12l-7 7-7-7",
|
|
25
|
+
database: "M12 2C6.48 2 2 4.02 2 6.5v11C2 19.98 6.48 22 12 22s10-2.02 10-4.5v-11C22 4.02 17.52 2 12 2z",
|
|
26
|
+
save: "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2zM17 21v-8H7v8M7 3v5h8",
|
|
27
|
+
trash: "M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",
|
|
28
|
+
bookmark: "M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z",
|
|
29
|
+
panelRight: "M3 3h18v18H3zM15 3v18",
|
|
30
|
+
cpu: "M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3M6 5h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zM9 9h6v6H9z",
|
|
31
|
+
shuffle: "M16 3h5v5M4 20L21 3M21 16v5h-5M15 15l6 6M4 4l5 5",
|
|
32
|
+
gitBranch: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 9a9 9 0 0 1-9 9",
|
|
33
|
+
filter: "M22 3H2l8 9.46V19l4 2v-8.54L22 3",
|
|
34
|
+
edit: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z",
|
|
35
|
+
xCircle: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM15 9l-6 6M9 9l6 6",
|
|
36
|
+
external: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3",
|
|
37
|
+
sun: "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z",
|
|
38
|
+
moon: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z",
|
|
39
|
+
"book-open": "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2zM22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z",
|
|
40
|
+
folderOpen: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z",
|
|
41
|
+
pin: "M12 17v5 M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.34V17a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1.66a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3.76z",
|
|
42
|
+
messageSquare: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",
|
|
43
|
+
download: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3",
|
|
44
|
+
wrench: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",
|
|
45
|
+
xSmall: "M18 6L6 18M6 6l12 12",
|
|
46
|
+
fileText: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const I = ({ name, size = 16, color = "currentColor", style = {} }) => (
|
|
50
|
+
<Icon d={icons[name] || ""} size={size} color={color} style={style} />
|
|
51
|
+
);
|
cli/ui/shared.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// ─── Shared Components ────────────────────
|
|
2
|
+
const GlowDot = ({ color = T.accent, size = 6 }) => (
|
|
3
|
+
<span style={{
|
|
4
|
+
display: "inline-block", width: size, height: size, borderRadius: "50%",
|
|
5
|
+
background: color, boxShadow: `0 0 ${size}px ${color}60`, flexShrink: 0
|
|
6
|
+
}} />
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
const Badge = ({ children, color = T.accent }) => (
|
|
10
|
+
<span className="mono" style={{
|
|
11
|
+
display: "inline-flex", alignItems: "center", gap: 4,
|
|
12
|
+
padding: "2px 8px", borderRadius: 4, fontSize: 11, fontWeight: 600,
|
|
13
|
+
background: `${color}15`, color, border: `1px solid ${color}30`, whiteSpace: "nowrap"
|
|
14
|
+
}}>
|
|
15
|
+
{children}
|
|
16
|
+
</span>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const StatBox = ({ label, value, sub, color = T.accent, loading }) => (
|
|
20
|
+
<div style={{
|
|
21
|
+
background: T.surface, border: `1px solid ${T.border}`, borderRadius: 8,
|
|
22
|
+
padding: "16px 18px", flex: 1, minWidth: 140
|
|
23
|
+
}}>
|
|
24
|
+
<div style={{
|
|
25
|
+
color: T.textMuted, fontSize: 11, fontWeight: 600, textTransform: "uppercase",
|
|
26
|
+
letterSpacing: 1, marginBottom: 8
|
|
27
|
+
}}>{label}</div>
|
|
28
|
+
<div className="mono" style={{ fontSize: 26, fontWeight: 700, color, lineHeight: 1.1 }}>
|
|
29
|
+
{loading ? <span style={{ animation: "pulse 1s infinite" }}>—</span> : value}
|
|
30
|
+
</div>
|
|
31
|
+
{sub && <div style={{ color: T.textMuted, fontSize: 11, marginTop: 4 }}>{sub}</div>}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const ProgressBar = ({ value, max, color = T.accent, height = 6 }) => {
|
|
36
|
+
const pct = Math.min(100, (value / max) * 100);
|
|
37
|
+
return (
|
|
38
|
+
<div style={{ flex: 1, height, borderRadius: height, background: T.surfaceAlt, overflow: "hidden" }}>
|
|
39
|
+
<div style={{
|
|
40
|
+
height: "100%", borderRadius: height, width: `${pct}%`,
|
|
41
|
+
background: `linear-gradient(90deg, ${color}90, ${color})`,
|
|
42
|
+
boxShadow: `0 0 8px ${color}40`, transition: "width 0.5s ease"
|
|
43
|
+
}} />
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const Btn = ({ children, color = T.accent, variant = "solid", onClick, disabled, style: s = {} }) => {
|
|
49
|
+
const isSolid = variant === "solid";
|
|
50
|
+
return (
|
|
51
|
+
<button onClick={onClick} disabled={disabled} style={{
|
|
52
|
+
padding: "8px 18px", borderRadius: 6, border: isSolid ? "none" : `1px solid ${T.border}`,
|
|
53
|
+
background: isSolid ? `linear-gradient(135deg, ${color}, ${color}cc)` : "transparent",
|
|
54
|
+
color: isSolid ? T.bg : T.textMuted, fontSize: 12, fontWeight: 700, cursor: disabled ? "default" : "pointer",
|
|
55
|
+
fontFamily: "'JetBrains Mono', monospace", display: "flex", alignItems: "center", gap: 6,
|
|
56
|
+
opacity: disabled ? 0.5 : 1, transition: "all 0.15s", ...s,
|
|
57
|
+
}}>{children}</button>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const Section = ({ label, icon, color, open, onToggle, badge, children }) => (
|
|
62
|
+
<div style={{ background: T.surface, border: `1px solid ${T.border}`, borderRadius: 8, overflow: "hidden" }}>
|
|
63
|
+
<div onClick={onToggle} style={{ display: "flex", alignItems: "center", gap: 8, padding: "12px 16px", cursor: "pointer", userSelect: "none" }}>
|
|
64
|
+
<I name={icon} size={13} color={color || T.textMuted} />
|
|
65
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: T.textMuted, textTransform: "uppercase", letterSpacing: 1, flex: 1 }}>{label}</span>
|
|
66
|
+
{badge}
|
|
67
|
+
<I name="chevron" size={12} color={T.textMuted} style={{ transform: open ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 0.15s" }} />
|
|
68
|
+
</div>
|
|
69
|
+
{open && <div style={{ padding: "0 16px 16px" }}>{children}</div>}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// ─── Shared Constants ─────────────────────
|
|
74
|
+
const typeColors = { tool_call: T.blue, decision: T.purple, file_change: T.accent, fact_stored: T.warn, session_start: "#4ade80", session_save: "#4ade80" };
|
|
75
|
+
const toolColors = { search: T.blue, compress: T.purple, read: T.blue, filter: T.purple, validate: T.accent, session: "#4ade80", memory: T.purple, status: T.warn, delegate: "#e879f9", snapshot: "#4ade80", restore: "#4ade80", transcript: T.blue };
|
|
76
|
+
const getToolColor = (name) => { const n = (name || "").toLowerCase(); const k = Object.keys(toolColors).find(k => n.includes(k)); return k ? toolColors[k] : T.textMuted; };
|
|
77
|
+
|
|
78
|
+
const timeAgo = (iso) => {
|
|
79
|
+
if (!iso) return "";
|
|
80
|
+
const diff = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
81
|
+
if (diff < 60) return `${Math.floor(diff)}s ago`;
|
|
82
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
83
|
+
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
84
|
+
return `${Math.floor(diff / 86400)}d ago`;
|
|
85
|
+
};
|
|
86
|
+
const localTime = (iso) => { if (!iso) return ""; const d = new Date(iso); return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); };
|
|
87
|
+
const localDate = (iso) => { if (!iso) return ""; return new Date(iso).toLocaleDateString(); };
|
|
88
|
+
const formatDuration = (seconds) => {
|
|
89
|
+
if (seconds < 0) seconds = 0;
|
|
90
|
+
const h = Math.floor(seconds / 3600);
|
|
91
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
92
|
+
const s = seconds % 60;
|
|
93
|
+
return h ? `${h}h ${m}m` : `${m}m ${s}s`;
|
|
94
|
+
};
|
|
95
|
+
const useLiveDuration = (startedIso, isLive) => {
|
|
96
|
+
const [dur, setDur] = useState("");
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!startedIso || !isLive) { setDur(""); return; }
|
|
99
|
+
const tick = () => setDur(formatDuration(Math.floor((Date.now() - new Date(startedIso).getTime()) / 1000)));
|
|
100
|
+
tick();
|
|
101
|
+
const iv = setInterval(tick, 1000);
|
|
102
|
+
return () => clearInterval(iv);
|
|
103
|
+
}, [startedIso, isLive]);
|
|
104
|
+
return dur;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const renderBoolToggle = (label, checked, onChange, description) => (
|
|
108
|
+
<label style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, padding: "8px 0" }}>
|
|
109
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
|
110
|
+
<span style={{ color: T.textMuted, fontSize: 12 }}>{label}</span>
|
|
111
|
+
{description && <span style={{ color: T.textDim, fontSize: 11 }}>{description}</span>}
|
|
112
|
+
</div>
|
|
113
|
+
<button type="button" onClick={onChange} style={{
|
|
114
|
+
padding: "4px 10px", borderRadius: 999, border: `1px solid ${T.border}`,
|
|
115
|
+
background: checked ? T.accent + "22" : T.surfaceAlt,
|
|
116
|
+
color: checked ? T.accent : T.textMuted, cursor: "pointer", fontSize: 11, fontWeight: 700, minWidth: 74,
|
|
117
|
+
}}>{checked ? "ON" : "OFF"}</button>
|
|
118
|
+
</label>
|
|
119
|
+
);
|
cli/ui/theme.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ─── Theme ────────────────────────────────
|
|
2
|
+
const DARK = {
|
|
3
|
+
bg: "#0a0e14", surface: "#0f1319", surfaceAlt: "#141a22",
|
|
4
|
+
border: "#1e2733", borderHover: "#2a3544",
|
|
5
|
+
text: "#c5cdd8", textMuted: "#5c6a7a", textDim: "#3a4555",
|
|
6
|
+
accent: "#00e5a0", accentDim: "#00e5a018",
|
|
7
|
+
warn: "#ffb224", warnDim: "#ffb22418",
|
|
8
|
+
error: "#ff6b6b", errorDim: "#ff6b6b18",
|
|
9
|
+
blue: "#4da6ff", blueDim: "#4da6ff18",
|
|
10
|
+
purple: "#b38aff", purpleDim: "#b38aff18",
|
|
11
|
+
};
|
|
12
|
+
const LIGHT = {
|
|
13
|
+
bg: "#f0f2f5", surface: "#ffffff", surfaceAlt: "#e8ebef",
|
|
14
|
+
border: "#dde2e8", borderHover: "#b8c0cb",
|
|
15
|
+
text: "#1a2332", textMuted: "#5a6a7a", textDim: "#9daab8",
|
|
16
|
+
accent: "#009e72", accentDim: "#009e7214",
|
|
17
|
+
warn: "#b87000", warnDim: "#b8700014",
|
|
18
|
+
error: "#c43030", errorDim: "#c4303014",
|
|
19
|
+
blue: "#1a6fc4", blueDim: "#1a6fc414",
|
|
20
|
+
purple: "#6b40c4", purpleDim: "#6b40c414",
|
|
21
|
+
};
|
|
22
|
+
let T = DARK;
|
cli/ui.html
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>C3 — Code Context Control</title>
|
|
8
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
|
|
9
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
|
|
10
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.9/babel.min.js"></script>
|
|
11
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.2/marked.min.js"></script>
|
|
12
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
13
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" id="hljs-theme-dark">
|
|
14
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" id="hljs-theme-light" disabled>
|
|
15
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.30.2/cytoscape.min.js"></script>
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/layout-base@2.0.1/layout-base.js"></script>
|
|
17
|
+
<script src="https://cdn.jsdelivr.net/npm/cose-base@2.2.0/cose-base.js"></script>
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/cytoscape-fcose@2.2.0/cytoscape-fcose.js"></script>
|
|
19
|
+
<style>
|
|
20
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&display=swap');
|
|
21
|
+
|
|
22
|
+
*,
|
|
23
|
+
*::before,
|
|
24
|
+
*::after {
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
html,
|
|
31
|
+
body,
|
|
32
|
+
#root {
|
|
33
|
+
height: 100%;
|
|
34
|
+
width: 100%;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
body {
|
|
39
|
+
background: #0a0e14;
|
|
40
|
+
color: #c5cdd8;
|
|
41
|
+
font-family: 'DM Sans', -apple-system, sans-serif;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
::-webkit-scrollbar {
|
|
45
|
+
width: 5px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
::-webkit-scrollbar-track {
|
|
49
|
+
background: transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
::-webkit-scrollbar-thumb {
|
|
53
|
+
background: #1e2733;
|
|
54
|
+
border-radius: 3px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
::-webkit-scrollbar-thumb:hover {
|
|
58
|
+
background: #2a3544;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes spin {
|
|
62
|
+
from {
|
|
63
|
+
transform: rotate(0deg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
to {
|
|
67
|
+
transform: rotate(360deg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes fadeUp {
|
|
72
|
+
from {
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: translateY(8px);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
to {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
transform: translateY(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@keyframes pulse {
|
|
84
|
+
|
|
85
|
+
0%,
|
|
86
|
+
100% {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
50% {
|
|
91
|
+
opacity: 0.5;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@keyframes fadeIn {
|
|
96
|
+
from {
|
|
97
|
+
opacity: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
to {
|
|
101
|
+
opacity: 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.fade-up {
|
|
106
|
+
animation: fadeUp 0.3s ease forwards;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
input,
|
|
110
|
+
textarea,
|
|
111
|
+
select,
|
|
112
|
+
button {
|
|
113
|
+
font-family: inherit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pre {
|
|
117
|
+
font-family: 'JetBrains Mono', monospace;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.mono {
|
|
121
|
+
font-family: 'JetBrains Mono', monospace;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* ─── Chat markdown rendering ─── */
|
|
125
|
+
.chat-md h1,.chat-md h2,.chat-md h3 { margin:.6em 0 .3em; font-family:'DM Sans',sans-serif; }
|
|
126
|
+
.chat-md h1 { font-size:1.3em; } .chat-md h2 { font-size:1.15em; } .chat-md h3 { font-size:1.05em; }
|
|
127
|
+
.chat-md p { margin:.4em 0; } .chat-md ul,.chat-md ol { margin:.3em 0; padding-left:1.5em; }
|
|
128
|
+
.chat-md li { margin:.15em 0; }
|
|
129
|
+
.chat-md code { font-family:'JetBrains Mono',monospace; font-size:.88em; }
|
|
130
|
+
.chat-md pre { margin:.5em 0; border-radius:6px; overflow-x:auto; }
|
|
131
|
+
.chat-md pre code { display:block; padding:12px 14px; line-height:1.5; }
|
|
132
|
+
.chat-md blockquote { border-left:3px solid; padding-left:12px; margin:.4em 0; opacity:.85; }
|
|
133
|
+
.chat-md table { border-collapse:collapse; margin:.5em 0; width:100%; }
|
|
134
|
+
.chat-md th,.chat-md td { border:1px solid; padding:6px 10px; font-size:12px; text-align:left; }
|
|
135
|
+
.chat-md a { text-decoration:underline; }
|
|
136
|
+
.chat-md hr { border:none; border-top:1px solid; margin:.8em 0; opacity:.3; }
|
|
137
|
+
|
|
138
|
+
.chat-code-block { border-radius:6px; overflow:hidden; margin:.5em 0; }
|
|
139
|
+
.chat-code-header { display:flex; justify-content:space-between; align-items:center; padding:4px 12px; font-size:11px; font-family:'JetBrains Mono',monospace; }
|
|
140
|
+
.chat-code-copy { background:none; border:1px solid; border-radius:4px; padding:2px 8px; cursor:pointer; font-size:10px; font-family:'JetBrains Mono',monospace; transition:opacity .15s; }
|
|
141
|
+
.chat-code-copy:hover { opacity:.7; }
|
|
142
|
+
|
|
143
|
+
[data-theme="dark"] .chat-code-block { background:#0d1117; }
|
|
144
|
+
[data-theme="dark"] .chat-code-header { background:#161b22; color:#8b949e; border-bottom:1px solid #21262d; }
|
|
145
|
+
[data-theme="dark"] .chat-code-copy { color:#8b949e; border-color:#30363d; }
|
|
146
|
+
[data-theme="dark"] .chat-md blockquote { border-color:#30363d; }
|
|
147
|
+
[data-theme="dark"] .chat-md th,[data-theme="dark"] .chat-md td { border-color:#21262d; }
|
|
148
|
+
[data-theme="dark"] .chat-md hr { border-color:#21262d; }
|
|
149
|
+
[data-theme="dark"] .chat-md a { color:#58a6ff; }
|
|
150
|
+
|
|
151
|
+
[data-theme="light"] .chat-code-block { background:#f6f8fa; }
|
|
152
|
+
[data-theme="light"] .chat-code-header { background:#eaecef; color:#57606a; border-bottom:1px solid #d0d7de; }
|
|
153
|
+
[data-theme="light"] .chat-code-copy { color:#57606a; border-color:#d0d7de; }
|
|
154
|
+
[data-theme="light"] .chat-md blockquote { border-color:#d0d7de; }
|
|
155
|
+
[data-theme="light"] .chat-md th,[data-theme="light"] .chat-md td { border-color:#d0d7de; }
|
|
156
|
+
[data-theme="light"] .chat-md hr { border-color:#d0d7de; }
|
|
157
|
+
[data-theme="light"] .chat-md a { color:#0969da; }
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
|
|
161
|
+
<body>
|
|
162
|
+
<div id="root"></div>
|
|
163
|
+
<script type="text/babel">
|
|
164
|
+
/* __C3_UI_SCRIPTS__ */
|
|
165
|
+
</script>
|
|
166
|
+
</body>
|
|
167
|
+
|
|
168
|
+
</html>
|