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.
Files changed (150) hide show
  1. cli/__init__.py +1 -0
  2. cli/_hook_utils.py +99 -0
  3. cli/c3.py +6152 -0
  4. cli/commands/__init__.py +1 -0
  5. cli/commands/common.py +312 -0
  6. cli/commands/parser.py +286 -0
  7. cli/docs.html +3178 -0
  8. cli/edits.html +878 -0
  9. cli/hook_auto_snapshot.py +142 -0
  10. cli/hook_c3_signal.py +61 -0
  11. cli/hook_c3read.py +116 -0
  12. cli/hook_edit_ledger.py +213 -0
  13. cli/hook_edit_unlock.py +170 -0
  14. cli/hook_filter.py +130 -0
  15. cli/hook_ghost_files.py +238 -0
  16. cli/hook_pretool_enforce.py +334 -0
  17. cli/hook_read.py +200 -0
  18. cli/hook_session_stats.py +62 -0
  19. cli/hook_terse_advisor.py +190 -0
  20. cli/hub.html +3764 -0
  21. cli/hub_server.py +1619 -0
  22. cli/mcp_proxy.py +428 -0
  23. cli/mcp_server.py +660 -0
  24. cli/server.py +2985 -0
  25. cli/tools/__init__.py +4 -0
  26. cli/tools/_helpers.py +65 -0
  27. cli/tools/agent.py +1165 -0
  28. cli/tools/compress.py +215 -0
  29. cli/tools/delegate.py +1184 -0
  30. cli/tools/edit.py +313 -0
  31. cli/tools/edits.py +118 -0
  32. cli/tools/filter.py +285 -0
  33. cli/tools/impact.py +163 -0
  34. cli/tools/memory.py +469 -0
  35. cli/tools/read.py +224 -0
  36. cli/tools/search.py +337 -0
  37. cli/tools/session.py +95 -0
  38. cli/tools/shell.py +193 -0
  39. cli/tools/status.py +306 -0
  40. cli/tools/validate.py +310 -0
  41. cli/ui/api.js +36 -0
  42. cli/ui/app.js +207 -0
  43. cli/ui/components/chat.js +758 -0
  44. cli/ui/components/dashboard.js +689 -0
  45. cli/ui/components/edits.js +220 -0
  46. cli/ui/components/instructions.js +481 -0
  47. cli/ui/components/memory.js +626 -0
  48. cli/ui/components/sessions.js +606 -0
  49. cli/ui/components/settings.js +1404 -0
  50. cli/ui/components/sidebar.js +156 -0
  51. cli/ui/icons.js +51 -0
  52. cli/ui/shared.js +119 -0
  53. cli/ui/theme.js +22 -0
  54. cli/ui.html +168 -0
  55. cli/ui_legacy.html +6797 -0
  56. cli/ui_nano.html +503 -0
  57. code_context_control-2.28.0.dist-info/METADATA +248 -0
  58. code_context_control-2.28.0.dist-info/RECORD +150 -0
  59. code_context_control-2.28.0.dist-info/WHEEL +5 -0
  60. code_context_control-2.28.0.dist-info/entry_points.txt +4 -0
  61. code_context_control-2.28.0.dist-info/licenses/LICENSE +201 -0
  62. code_context_control-2.28.0.dist-info/top_level.txt +5 -0
  63. core/__init__.py +75 -0
  64. core/config.py +269 -0
  65. core/ide.py +188 -0
  66. oracle/__init__.py +1 -0
  67. oracle/config.py +75 -0
  68. oracle/oracle.html +3900 -0
  69. oracle/oracle_server.py +663 -0
  70. oracle/services/__init__.py +1 -0
  71. oracle/services/c3_bridge.py +210 -0
  72. oracle/services/chat_engine.py +1103 -0
  73. oracle/services/chat_store.py +155 -0
  74. oracle/services/cross_memory.py +154 -0
  75. oracle/services/federated_graph.py +463 -0
  76. oracle/services/health_checker.py +117 -0
  77. oracle/services/insight_engine.py +307 -0
  78. oracle/services/memory_reader.py +106 -0
  79. oracle/services/memory_writer.py +182 -0
  80. oracle/services/ollama_bridge.py +332 -0
  81. oracle/services/project_scanner.py +87 -0
  82. oracle/services/review_agent.py +206 -0
  83. services/__init__.py +1 -0
  84. services/activity_log.py +93 -0
  85. services/agent_base.py +124 -0
  86. services/agents.py +1529 -0
  87. services/auto_memory.py +407 -0
  88. services/bench/__init__.py +6 -0
  89. services/bench/external/__init__.py +29 -0
  90. services/bench/external/aider_polyglot.py +405 -0
  91. services/bench/external/swe_bench.py +485 -0
  92. services/benchmark_dashboard.py +596 -0
  93. services/claude_md.py +785 -0
  94. services/compressor.py +592 -0
  95. services/context_snapshot.py +356 -0
  96. services/conversation_store.py +870 -0
  97. services/doc_index.py +537 -0
  98. services/e2e_benchmark.py +2884 -0
  99. services/e2e_evaluator.py +396 -0
  100. services/e2e_tasks.py +743 -0
  101. services/edit_ledger.py +459 -0
  102. services/embedding_index.py +341 -0
  103. services/error_reporting.py +123 -0
  104. services/file_memory.py +734 -0
  105. services/hub_service.py +585 -0
  106. services/indexer.py +712 -0
  107. services/memory.py +318 -0
  108. services/memory_consolidator.py +538 -0
  109. services/memory_graph.py +382 -0
  110. services/memory_grounder.py +304 -0
  111. services/memory_scorer.py +246 -0
  112. services/metrics.py +86 -0
  113. services/notifications.py +209 -0
  114. services/ollama_client.py +201 -0
  115. services/output_filter.py +488 -0
  116. services/parser.py +1238 -0
  117. services/project_manager.py +579 -0
  118. services/protocol.py +306 -0
  119. services/proxy_state.py +152 -0
  120. services/retrieval_broker.py +129 -0
  121. services/router.py +414 -0
  122. services/runtime.py +326 -0
  123. services/session_benchmark.py +1945 -0
  124. services/session_manager.py +1026 -0
  125. services/session_preloader.py +251 -0
  126. services/text_index.py +90 -0
  127. services/tool_classifier.py +176 -0
  128. services/transcript_index.py +340 -0
  129. services/validation_cache.py +155 -0
  130. services/vector_store.py +299 -0
  131. services/version_tracker.py +271 -0
  132. services/watcher.py +192 -0
  133. tui/__init__.py +0 -0
  134. tui/backend.py +59 -0
  135. tui/main.py +145 -0
  136. tui/screens/__init__.py +1 -0
  137. tui/screens/benchmark_view.py +109 -0
  138. tui/screens/claudemd_view.py +46 -0
  139. tui/screens/compress_view.py +52 -0
  140. tui/screens/index_view.py +74 -0
  141. tui/screens/init_view.py +82 -0
  142. tui/screens/mcp_view.py +73 -0
  143. tui/screens/optimize_view.py +41 -0
  144. tui/screens/pipe_view.py +46 -0
  145. tui/screens/projects_view.py +355 -0
  146. tui/screens/search_view.py +55 -0
  147. tui/screens/session_view.py +143 -0
  148. tui/screens/stats.py +158 -0
  149. tui/screens/ui_view.py +54 -0
  150. 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" }}>&mdash;</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>