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 CHANGED
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
92
92
  # Config
93
93
  CONFIG_DIR = ".c3"
94
94
  CONFIG_FILE = ".c3/config.json"
95
- __version__ = "2.57.0"
95
+ __version__ = "2.57.1"
96
96
 
97
97
 
98
98
  def _command_deps() -> CommandDeps:
@@ -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
- notify(`Starting ${p.name}…`);
385
- setTimeout(onChanged, 1500);
386
- setTimeout(onChanged, 4000);
387
- } else {
388
- notify('Launch failed', 'err');
389
- }
390
- } catch (err) { notify('Start: ' + err.message, 'err'); }
391
- setStarting(false);
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."""
@@ -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 = (registry || []).filter(e => e.port !== myPort);
24
- if (others.length === 0) return null;
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={() => setOpen(!open)} title="Switch project"
66
+ <button onClick={toggle} title="Switch to another active project"
29
67
  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
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>{others.length}</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", top: "100%", left: 0, marginTop: 4, zIndex: 100,
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: 200, overflow: "hidden"
80
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)", minWidth: 220, overflow: "hidden"
41
81
  }}>
42
- {others.map(e => (
43
- <a key={e.port} href={`http://localhost:${e.port}`}
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 }}>:{e.port}</span>
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.0
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=km3MPDbELAFjasO7edgNPHsvmk2xkPYg390yIoAJXCg,316619
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=Qfz1BanXv5UNKogrUUGeG3Jf9shG96wCJn6zCbEc8yA,144431
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=UCSa-d894l1EwyTwDbtrwemBvUPDB7wcp3TsX5yoUN0,25766
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=cAY_jwYB-o1X_wWn__VXlG4IegVObuE3NmVsuFWqxtg,7417
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.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
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.0.dist-info/METADATA,sha256=Lg_hqrpQR2nopSUxQN1ybb6t-nddG9OgTq6uL3ej7j0,27873
238
- code_context_control-2.57.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
239
- code_context_control-2.57.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
240
- code_context_control-2.57.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
241
- code_context_control-2.57.0.dist-info/RECORD,,
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,,