code-context-control 2.57.0__py3-none-any.whl → 2.57.1__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/c3.py +1 -1
- cli/hub_ui/components/project_card.js +40 -9
- cli/server.py +27 -0
- cli/ui/components/sidebar.js +64 -13
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/METADATA +1 -1
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/RECORD +10 -10
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/WHEEL +0 -0
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/top_level.txt +0 -0
cli/c3.py
CHANGED
|
@@ -374,21 +374,52 @@ function ProjectCard({ p, isChild, rollup, expanded, onToggleExpand, onChanged,
|
|
|
374
374
|
setUpdating(false);
|
|
375
375
|
};
|
|
376
376
|
|
|
377
|
+
// Launch the UI server, then open its tab once the port shows up in the
|
|
378
|
+
// registry. launch_session cannot return the port (the detached child
|
|
379
|
+
// picks it), so open a placeholder tab synchronously — still inside the
|
|
380
|
+
// click gesture, so popup blockers allow it — and navigate it when
|
|
381
|
+
// polling /api/projects finds the port.
|
|
377
382
|
const start = async (e) => {
|
|
378
383
|
e.stopPropagation();
|
|
379
384
|
if (starting) return;
|
|
380
385
|
setStarting(true);
|
|
386
|
+
const win = window.open('', '_blank');
|
|
387
|
+
if (win) {
|
|
388
|
+
try {
|
|
389
|
+
win.document.write(
|
|
390
|
+
`<title>Starting ${p.name}…</title>` +
|
|
391
|
+
'<body style="background:#0d1117;color:#8b949e;font-family:sans-serif;' +
|
|
392
|
+
'display:flex;align-items:center;justify-content:center;height:100vh">' +
|
|
393
|
+
`Starting ${p.name}…</body>`);
|
|
394
|
+
} catch { }
|
|
395
|
+
}
|
|
396
|
+
const fail = (msg) => {
|
|
397
|
+
if (win) win.close();
|
|
398
|
+
notify(msg, 'err');
|
|
399
|
+
setStarting(false);
|
|
400
|
+
};
|
|
381
401
|
try {
|
|
382
402
|
const d = await api.post('/api/sessions/start', { path: p.path });
|
|
383
|
-
if (d.launched) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
403
|
+
if (!d.launched) { fail('Launch failed'); return; }
|
|
404
|
+
notify(`Starting ${p.name}…`);
|
|
405
|
+
setTimeout(onChanged, 1500);
|
|
406
|
+
const poll = async (tries) => {
|
|
407
|
+
let rows = [];
|
|
408
|
+
try { rows = await api.get('/api/projects'); } catch { }
|
|
409
|
+
const row = (Array.isArray(rows) ? rows : []).find(r =>
|
|
410
|
+
(r.path || '').toLowerCase() === (p.path || '').toLowerCase());
|
|
411
|
+
if (row && row.port) {
|
|
412
|
+
const url = 'http://127.0.0.1:' + row.port;
|
|
413
|
+
if (win) { win.location = url; } else { window.open(url, '_blank'); }
|
|
414
|
+
onChanged();
|
|
415
|
+
setStarting(false);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (tries >= 20) { fail(`${p.name}: UI server did not report a port — check .c3/ui.log`); return; }
|
|
419
|
+
setTimeout(() => poll(tries + 1), 1500);
|
|
420
|
+
};
|
|
421
|
+
setTimeout(() => poll(0), 1200);
|
|
422
|
+
} catch (err) { fail('Start: ' + err.message); }
|
|
392
423
|
};
|
|
393
424
|
|
|
394
425
|
const stop = async (e) => {
|
cli/server.py
CHANGED
|
@@ -396,6 +396,33 @@ def api_health():
|
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
# ─── API: Session Registry ───────────────────────────────
|
|
399
|
+
@app.route('/api/registry/active')
|
|
400
|
+
def api_registry_active():
|
|
401
|
+
"""Running UI servers PLUS registered projects with a live agent session
|
|
402
|
+
but no UI (port None) — the project switcher's data. On-demand only:
|
|
403
|
+
get_active_sessions() probes ports, so this must never be polled."""
|
|
404
|
+
try:
|
|
405
|
+
from services.project_manager import ProjectManager
|
|
406
|
+
return jsonify(ProjectManager().get_active_sessions())
|
|
407
|
+
except Exception as e:
|
|
408
|
+
return jsonify({"error": str(e)}), 500
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@app.route('/api/registry/launch', methods=['POST'])
|
|
412
|
+
def api_registry_launch():
|
|
413
|
+
"""Launch another project's UI server so the switcher can jump to it."""
|
|
414
|
+
data = request.get_json(force=True) or {}
|
|
415
|
+
path = (data.get('path') or '').strip()
|
|
416
|
+
if not path:
|
|
417
|
+
return jsonify({"error": "path is required"}), 400
|
|
418
|
+
try:
|
|
419
|
+
from services.project_manager import ProjectManager
|
|
420
|
+
result = ProjectManager().launch_session(path)
|
|
421
|
+
return jsonify(result), (200 if result.get("launched") else 400)
|
|
422
|
+
except Exception as e:
|
|
423
|
+
return jsonify({"error": str(e)}), 500
|
|
424
|
+
|
|
425
|
+
|
|
399
426
|
@app.route('/api/registry')
|
|
400
427
|
def api_registry():
|
|
401
428
|
"""Return all live C3 sessions from the global registry."""
|
cli/ui/components/sidebar.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// ─── Sidebar ─────────────────────────────
|
|
2
|
+
// Switch between ACTIVE projects: other running UI servers (direct jump)
|
|
3
|
+
// plus projects with a live agent session but no UI yet (launch, then
|
|
4
|
+
// jump). The cheap polled /api/registry prop only seeds the count hint;
|
|
5
|
+
// the authoritative list (/api/registry/active — it probes ports) loads
|
|
6
|
+
// on demand when the menu opens, never on a poll.
|
|
2
7
|
function ProjectSwitcher({ registry }) {
|
|
3
8
|
const [open, setOpen] = useState(false);
|
|
9
|
+
const [entries, setEntries] = useState(null); // null = scanning
|
|
10
|
+
const [busyPath, setBusyPath] = useState(null); // path being launched
|
|
4
11
|
const ref = React.useRef(null);
|
|
5
12
|
const myPort = parseInt(window.location.port) || 3333;
|
|
6
13
|
|
|
@@ -20,37 +27,81 @@ function ProjectSwitcher({ registry }) {
|
|
|
20
27
|
return "Unknown";
|
|
21
28
|
};
|
|
22
29
|
|
|
23
|
-
const others = (
|
|
24
|
-
|
|
30
|
+
const others = (list) => (list || []).filter(e => e.port !== myPort);
|
|
31
|
+
|
|
32
|
+
const loadActive = async () => {
|
|
33
|
+
setEntries(null);
|
|
34
|
+
try {
|
|
35
|
+
const r = await api.get('/api/registry/active');
|
|
36
|
+
setEntries(others(Array.isArray(r) ? r : []));
|
|
37
|
+
} catch { setEntries(others(registry)); }
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const toggle = () => { const next = !open; setOpen(next); if (next) loadActive(); };
|
|
41
|
+
|
|
42
|
+
// Portless entry = live agent session without a UI server: launch one,
|
|
43
|
+
// poll until it registers a port, then navigate this tab to it.
|
|
44
|
+
const jump = async (e) => {
|
|
45
|
+
if (busyPath) return;
|
|
46
|
+
setBusyPath(e.project_path);
|
|
47
|
+
try {
|
|
48
|
+
await api.post('/api/registry/launch', { path: e.project_path });
|
|
49
|
+
const poll = async (tries) => {
|
|
50
|
+
let list = [];
|
|
51
|
+
try { list = await api.get('/api/registry/active'); } catch { }
|
|
52
|
+
const row = (Array.isArray(list) ? list : []).find(x =>
|
|
53
|
+
(x.project_path || '').toLowerCase() === (e.project_path || '').toLowerCase() && x.port);
|
|
54
|
+
if (row) { window.location.href = `http://localhost:${row.port}`; return; }
|
|
55
|
+
if (tries >= 15) { setBusyPath(null); return; }
|
|
56
|
+
setTimeout(() => poll(tries + 1), 1500);
|
|
57
|
+
};
|
|
58
|
+
setTimeout(() => poll(0), 1200);
|
|
59
|
+
} catch { setBusyPath(null); }
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const hint = others(registry).length;
|
|
25
63
|
|
|
26
64
|
return (
|
|
27
65
|
<div ref={ref} style={{ position: "relative" }}>
|
|
28
|
-
<button onClick={
|
|
66
|
+
<button onClick={toggle} title="Switch to another active project"
|
|
29
67
|
style={{
|
|
30
|
-
padding: "
|
|
31
|
-
cursor: "pointer", display: "flex", alignItems: "center",
|
|
68
|
+
width: "100%", padding: "4px 6px", borderRadius: 4, border: `1px solid ${T.border}`,
|
|
69
|
+
background: "transparent", cursor: "pointer", display: "flex", alignItems: "center",
|
|
70
|
+
gap: 5, fontSize: 10, color: T.textDim
|
|
32
71
|
}}>
|
|
33
72
|
<I name="shuffle" size={10} color={T.textDim} />
|
|
34
|
-
<span>
|
|
73
|
+
<span>Switch project</span>
|
|
74
|
+
{hint > 0 && <span className="mono" style={{ marginLeft: "auto", color: T.textMuted }}>{hint}</span>}
|
|
35
75
|
</button>
|
|
36
76
|
{open && (
|
|
37
77
|
<div style={{
|
|
38
|
-
position: "absolute",
|
|
78
|
+
position: "absolute", bottom: "100%", left: 0, marginBottom: 4, zIndex: 100,
|
|
39
79
|
background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6,
|
|
40
|
-
boxShadow: "0 4px 12px rgba(0,0,0,0.3)", minWidth:
|
|
80
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)", minWidth: 220, overflow: "hidden"
|
|
41
81
|
}}>
|
|
42
|
-
{
|
|
43
|
-
<
|
|
82
|
+
{entries === null && (
|
|
83
|
+
<div style={{ padding: "8px 12px", fontSize: 11, color: T.textDim }}>Scanning active sessions…</div>
|
|
84
|
+
)}
|
|
85
|
+
{entries !== null && entries.length === 0 && (
|
|
86
|
+
<div style={{ padding: "8px 12px", fontSize: 11, color: T.textDim }}>No other active projects.</div>
|
|
87
|
+
)}
|
|
88
|
+
{(entries || []).map(e => (
|
|
89
|
+
<a key={e.project_path || e.port}
|
|
90
|
+
href={e.port ? `http://localhost:${e.port}` : '#'}
|
|
91
|
+
onClick={(ev) => { if (!e.port) { ev.preventDefault(); jump(e); } }}
|
|
44
92
|
style={{
|
|
45
93
|
display: "flex", alignItems: "center", gap: 8, padding: "8px 12px",
|
|
46
94
|
color: T.text, textDecoration: "none", fontSize: 12,
|
|
47
|
-
borderBottom: `1px solid ${T.border}20`, transition: "background 0.1s"
|
|
95
|
+
borderBottom: `1px solid ${T.border}20`, transition: "background 0.1s",
|
|
96
|
+
opacity: busyPath && busyPath !== e.project_path ? 0.5 : 1
|
|
48
97
|
}}
|
|
49
98
|
onMouseEnter={ev => ev.currentTarget.style.background = T.surfaceAlt}
|
|
50
99
|
onMouseLeave={ev => ev.currentTarget.style.background = "transparent"}>
|
|
51
|
-
<GlowDot color={T.accent} size={5} />
|
|
100
|
+
<GlowDot color={e.port ? T.accent : T.warn} size={5} />
|
|
52
101
|
<span style={{ flex: 1 }}>{entryName(e)}</span>
|
|
53
|
-
<span className="mono" style={{ fontSize: 9, color: T.textDim }}
|
|
102
|
+
<span className="mono" style={{ fontSize: 9, color: T.textDim }}>
|
|
103
|
+
{e.port ? `:${e.port}` : (busyPath === e.project_path ? 'starting…' : 'session')}
|
|
104
|
+
</span>
|
|
54
105
|
</a>
|
|
55
106
|
))}
|
|
56
107
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.57.
|
|
3
|
+
Version: 2.57.1
|
|
4
4
|
Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Copilot, Cursor, Antigravity). Retrieve less, read less, edit safer — version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP), and manage multi-project + sub-project hierarchies from a local hub.
|
|
5
5
|
Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
cli/__init__.py,sha256=ec66drCZGNMRU4V6ov0zVhYZph1us12Vn8OvG_LJyRY,22
|
|
2
2
|
cli/_hook_utils.py,sha256=tJ4ZmXD5Y-JjFWb0uoR7ycYklYFUMnnjF2G7BO-wyBI,12600
|
|
3
|
-
cli/c3.py,sha256=
|
|
3
|
+
cli/c3.py,sha256=8cthpnmU1tBzOEXKHSSGkOdl3YUTMWdjgvP15Weroj8,316619
|
|
4
4
|
cli/docs.html,sha256=bNymSz7LatnWHjxSXL92QSUtGvB3jBzNHbOV-bRpAOo,142507
|
|
5
5
|
cli/edits.html,sha256=UjAhoCmBmQ89cklGvJqzC6eyNP2tc8H6T-e01DVkLvE,43418
|
|
6
6
|
cli/hook_artifact.py,sha256=Se1CNBfoBFyvJQlRmYdNtdRXIkQp9zaF0O6ndRMo7ts,2198
|
|
@@ -23,7 +23,7 @@ cli/hub_ui.html,sha256=nnGqWhWGCLfNisGUnoAlQFcBr6wPNsq-AEOR7uFtVkE,2942
|
|
|
23
23
|
cli/mcp_proxy.py,sha256=92htuT-p0j-cDTbyqlIJpGoQ85_Aw7UuB8L_Toi_u20,17511
|
|
24
24
|
cli/mcp_server.py,sha256=ip_DSWqdu9QgkogtsCLnJzkc0B31SdHHxjDCK8zciuI,42597
|
|
25
25
|
cli/progress.py,sha256=qTkNy0howOD3hXraMo4ok3dYTpBW5yG_tenKfF4UDM0,1470
|
|
26
|
-
cli/server.py,sha256=
|
|
26
|
+
cli/server.py,sha256=BOAjrB0IQCn13mznRhqXSbvC977HwNF5wx6Uicu4Xss,145522
|
|
27
27
|
cli/ui.html,sha256=xcdt74nlFEXx-0Bx6-Okw-WSVZPAXL0iukxU0ytI6CA,5694
|
|
28
28
|
cli/ui_legacy.html,sha256=cI8tC6RKmE2NIJOcsu7CY-zT4VznjcbD6NTjxb_fvUY,378460
|
|
29
29
|
cli/ui_nano.html,sha256=UAwQ6bbTOXAoGq191AZ7slhngR9edJSa3IhqpynveDg,27740
|
|
@@ -50,7 +50,7 @@ cli/hub_ui/components/drill_views.js,sha256=0rGDjecVGOnwmSxKrADZrqSn7r7luKrVTrR8
|
|
|
50
50
|
cli/hub_ui/components/global_search.js,sha256=GPfq1EsZa73vXBNCVn4VhT3a7gYkaUblbcXOFCe7PJQ,11395
|
|
51
51
|
cli/hub_ui/components/mcp_manager.js,sha256=KxA0LlpyNFDgtGxajoqwViJ0pJ0lsa1PqdCMNyyfhCo,17368
|
|
52
52
|
cli/hub_ui/components/modals.js,sha256=3maxEVYIg4McqM-RiOQyr0wz0U08_plGifxVHw2CShQ,52437
|
|
53
|
-
cli/hub_ui/components/project_card.js,sha256=
|
|
53
|
+
cli/hub_ui/components/project_card.js,sha256=PBHtRmh-bocIDkd_qlJmMoxQqQYTcDFFGmjmeSI560E,27219
|
|
54
54
|
cli/hub_ui/components/project_tree.js,sha256=8zs2mfj_RsoxY1gDXmqonPPKKQnm1Ev9dizghg4zpHE,3831
|
|
55
55
|
cli/hub_ui/components/session_drawer.js,sha256=OEBvdF3PqRji9CEntolDMVplXF2NTG2xwmXCqkcb0Nw,7553
|
|
56
56
|
cli/hub_ui/components/settings_modal.js,sha256=KF2gQj7HG568N06k3HjTROEURC1wuBvBi05eJ_URLBk,8929
|
|
@@ -96,9 +96,9 @@ cli/ui/components/jira.js,sha256=dCXkI5bTwroZMiK0RX3Hj8CWmZrlO2kCW7wDSIIjM-k,128
|
|
|
96
96
|
cli/ui/components/memory.js,sha256=v5IsHTxLHpXX4xCsUaZ_UPprZEabdgP4jiWc298iV2U,28818
|
|
97
97
|
cli/ui/components/sessions.js,sha256=FIKtil76B8tCkAmcFV7hlj6GQ_DCJK2jCzvEmdK7NBE,30837
|
|
98
98
|
cli/ui/components/settings.js,sha256=ATbAjBlVIwCNpxq7s191b49a_INQV38iwmySqtJLYwY,79066
|
|
99
|
-
cli/ui/components/sidebar.js,sha256=
|
|
99
|
+
cli/ui/components/sidebar.js,sha256=K2ym2kUgpbyG-EBA_wBIIiqQY8qTatsiBZwzseMWuIQ,9939
|
|
100
100
|
cli/ui/components/tasks.js,sha256=vyKQ3uwoppMwvdEaHlhWXW4oWcAisx4NveqzMhsYqHo,38438
|
|
101
|
-
code_context_control-2.57.
|
|
101
|
+
code_context_control-2.57.1.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
|
|
102
102
|
core/__init__.py,sha256=TSDCEcM4V7gcZVM3w2ykJaqEUch4Dkon-rivV17T73s,2501
|
|
103
103
|
core/config.py,sha256=YmkcZwedz_lfDM0ZuI_f4xpe98ixPLcSQFcTCHgRiRg,19206
|
|
104
104
|
core/ide.py,sha256=V6VVMVsFdmmcsMyxikjQp7z9xa42CWiHKS-ya-MAcG4,6172
|
|
@@ -234,8 +234,8 @@ tui/screens/search_view.py,sha256=MMHjVdlk3HZSuDBSvq8IGrqv_Mh5Us6YqXQ80bcWSMk,19
|
|
|
234
234
|
tui/screens/session_view.py,sha256=eZ1eDwHTvPOck1wCCviixtOaCxIkBT_95ytNNNriGNA,5991
|
|
235
235
|
tui/screens/stats.py,sha256=p81PjzdaIv7hllb8f45-rlVe4lJZwSdIMqu7e86_u5s,6223
|
|
236
236
|
tui/screens/ui_view.py,sha256=1QJCgLh2YfgWIpvzRG1KOGXYEaOYX6ojN61Azjf2oX0,2125
|
|
237
|
-
code_context_control-2.57.
|
|
238
|
-
code_context_control-2.57.
|
|
239
|
-
code_context_control-2.57.
|
|
240
|
-
code_context_control-2.57.
|
|
241
|
-
code_context_control-2.57.
|
|
237
|
+
code_context_control-2.57.1.dist-info/METADATA,sha256=7YW_93WNX2JaRhERJwjTqEz9ur9JvUgObnFuKfKp8mQ,27873
|
|
238
|
+
code_context_control-2.57.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
239
|
+
code_context_control-2.57.1.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
|
|
240
|
+
code_context_control-2.57.1.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
|
|
241
|
+
code_context_control-2.57.1.dist-info/RECORD,,
|
|
File without changes
|
{code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{code_context_control-2.57.0.dist-info → code_context_control-2.57.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|