code-data-ark 2.0.5__tar.gz → 2.0.7__tar.gz
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.
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/PKG-INFO +17 -8
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/__init__.py +1 -1
- code_data_ark-2.0.7/cda/__main__.py +10 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/kernel/paths.py +36 -6
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/kernel/pmf_kernel.py +8 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/ui/cli.py +62 -16
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/ui/web.py +110 -46
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/changelog.md +15 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/pyproject.toml +1 -1
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/readme.md +16 -7
- code_data_ark-2.0.7/version +1 -0
- code_data_ark-2.0.5/version +0 -1
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/.flake8 +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/.github/workflows/ci.yml +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/.gitignore +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/bin/release.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/kernel/__init__.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/kernel/control_db.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/kernel/selfcheck.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/__init__.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/embed.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/extract.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/ingest.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/parse_edits.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/reconstruct.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/pipeline/watcher.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/cda/ui/__init__.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/contributing.md +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/docs/architecture.md +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/docs/examples/usage.md +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/docs/pmf_kernel.md +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/docs/roadmap.md +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/license +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/makefile +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/tests/test_basic.py +0 -0
- {code_data_ark-2.0.5 → code_data_ark-2.0.7}/tests/test_selfcheck.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-data-ark
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.7
|
|
4
4
|
Summary: Code Data Ark — local observability and intelligence platform for VS Code + Copilot Chat sessions
|
|
5
5
|
Project-URL: Homepage, https://github.com/goCosmix/cda
|
|
6
6
|
Project-URL: Repository, https://github.com/goCosmix/cda.git
|
|
@@ -95,10 +95,17 @@ The runtime is managed by an embedded process kernel (PMF) that supervises the w
|
|
|
95
95
|
pip install code-data-ark
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
> **macOS / system Python note**: pip installs the `cda` binary to `~/Library/Python/3.x/bin/` which is not on `PATH` by default. Use the fallback below — `cda setup` will fix PATH for you automatically:
|
|
99
|
+
>
|
|
100
|
+
> ```bash
|
|
101
|
+
> python3 -m cda setup
|
|
102
|
+
> ```
|
|
103
|
+
|
|
98
104
|
### Install with pipx
|
|
99
105
|
|
|
100
106
|
```bash
|
|
101
107
|
pipx install code-data-ark
|
|
108
|
+
# pipx automatically manages PATH — `cda setup` works immediately
|
|
102
109
|
```
|
|
103
110
|
|
|
104
111
|
### Install from source
|
|
@@ -117,25 +124,27 @@ pip install -e ".[dev]"
|
|
|
117
124
|
make install-dev
|
|
118
125
|
```
|
|
119
126
|
|
|
120
|
-
> The `cda` console command is installed into your
|
|
127
|
+
> The `cda` console command is installed into your Python environment's `bin` directory. If it isn't on PATH yet, use `python3 -m cda setup` — setup patches `~/.zprofile` automatically.
|
|
121
128
|
|
|
122
129
|
## ⚡ Quick Start
|
|
123
130
|
|
|
124
131
|
```bash
|
|
125
132
|
pip install code-data-ark
|
|
126
|
-
cda setup
|
|
133
|
+
python3 -m cda setup # use this if `cda` isn't on PATH yet
|
|
127
134
|
```
|
|
128
135
|
|
|
129
|
-
|
|
136
|
+
After the first run, `cda setup` patches `~/.zprofile` so `cda` is on PATH in every new terminal.
|
|
137
|
+
|
|
138
|
+
`cda setup` runs four steps in sequence:
|
|
130
139
|
|
|
131
140
|
| Step | What it does |
|
|
132
141
|
|------|-------------|
|
|
133
|
-
| **1. Init** | Creates
|
|
134
|
-
| **2. PMF install** | Registers a macOS LaunchAgent — CDA starts automatically on every login |
|
|
135
|
-
| **3. Sync** | Ingests all VS Code + Copilot session data into
|
|
142
|
+
| **1. Init** | Creates `~/Library/goCosmix/apps/code-data-ark/` — all app data in one organized namespace. Also patches `~/.zprofile` if `cda` isn't on PATH yet. |
|
|
143
|
+
| **2. PMF install** | Registers a macOS LaunchAgent — CDA starts automatically on every login via `cda pmf up` |
|
|
144
|
+
| **3. Sync** | Ingests all VS Code + Copilot session data into `cda.db` |
|
|
136
145
|
| **4. Up** | Starts the watcher daemon and web UI via the PMF kernel, opens browser |
|
|
137
146
|
|
|
138
|
-
After setup, everything is managed by the **PMF kernel
|
|
147
|
+
All data lives in `~/Library/goCosmix/apps/code-data-ark/`. After setup, everything is managed by the **PMF kernel** — no terminal interaction required.
|
|
139
148
|
|
|
140
149
|
### Options
|
|
141
150
|
|
|
@@ -5,10 +5,16 @@ CDA_HOME is the single root for all runtime state (DB, PID files, logs,
|
|
|
5
5
|
queue, PMF runtime). It is resolved exactly once at import time via:
|
|
6
6
|
|
|
7
7
|
1. CDA_HOME environment variable (absolute path)
|
|
8
|
-
2.
|
|
8
|
+
2. ~/Library/goCosmix/apps/code-data-ark/ (macOS default)
|
|
9
|
+
3. ~/.gocosmix/apps/code-data-ark/ (fallback on non-macOS)
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
All goCosmix apps share the ~/Library/goCosmix/ namespace:
|
|
12
|
+
|
|
13
|
+
~/Library/goCosmix/
|
|
14
|
+
├── apps/
|
|
15
|
+
│ ├── code-data-ark/ ← CDA_HOME
|
|
16
|
+
│ └── ... ← future goCosmix apps
|
|
17
|
+
└── system/ ← shared goCosmix infrastructure
|
|
12
18
|
"""
|
|
13
19
|
|
|
14
20
|
import os
|
|
@@ -17,17 +23,40 @@ from pathlib import Path
|
|
|
17
23
|
# ── home resolution ──────────────────────────────────────────────────────────
|
|
18
24
|
|
|
19
25
|
|
|
26
|
+
def _default_cda_home() -> Path:
|
|
27
|
+
"""Platform-appropriate default for CDA_HOME."""
|
|
28
|
+
library = Path.home() / "Library"
|
|
29
|
+
if library.exists(): # macOS
|
|
30
|
+
return library / "goCosmix" / "apps" / "code-data-ark"
|
|
31
|
+
return Path.home() / ".gocosmix" / "apps" / "code-data-ark"
|
|
32
|
+
|
|
33
|
+
|
|
20
34
|
def get_cda_home() -> Path:
|
|
21
35
|
"""Return the CDA home directory, creating it if it doesn't exist."""
|
|
22
36
|
env = os.environ.get("CDA_HOME")
|
|
23
37
|
if env:
|
|
24
38
|
home = Path(env).expanduser().resolve()
|
|
25
39
|
else:
|
|
26
|
-
home =
|
|
40
|
+
home = _default_cda_home()
|
|
27
41
|
home.mkdir(parents=True, exist_ok=True)
|
|
28
42
|
return home
|
|
29
43
|
|
|
30
44
|
|
|
45
|
+
# ── goCosmix namespace (shared across all goCosmix apps) ────────────────────
|
|
46
|
+
|
|
47
|
+
def get_gocosmix_home() -> Path:
|
|
48
|
+
"""Return ~/Library/goCosmix (macOS) or ~/.gocosmix (other)."""
|
|
49
|
+
library = Path.home() / "Library"
|
|
50
|
+
if library.exists():
|
|
51
|
+
return library / "goCosmix"
|
|
52
|
+
return Path.home() / ".gocosmix"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
GOCOSMIX_HOME = get_gocosmix_home()
|
|
56
|
+
GOCOSMIX_APPS = GOCOSMIX_HOME / "apps"
|
|
57
|
+
GOCOSMIX_SYSTEM = GOCOSMIX_HOME / "system"
|
|
58
|
+
|
|
59
|
+
|
|
31
60
|
# ── canonical paths (module-level constants, computed once) ─────────────────
|
|
32
61
|
|
|
33
62
|
CDA_HOME = get_cda_home()
|
|
@@ -49,6 +78,7 @@ RUNTIME_FILE = PMF_DIR / "runtime.json"
|
|
|
49
78
|
|
|
50
79
|
|
|
51
80
|
def ensure_dirs() -> None:
|
|
52
|
-
"""Create all runtime directories. Safe to call multiple times."""
|
|
53
|
-
for d in (
|
|
81
|
+
"""Create all runtime directories (including goCosmix namespace). Safe to call multiple times."""
|
|
82
|
+
for d in (GOCOSMIX_HOME, GOCOSMIX_APPS, GOCOSMIX_SYSTEM,
|
|
83
|
+
DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR, PMF_DIR, PMF_LOG_DIR, CONFIG_DIR):
|
|
54
84
|
d.mkdir(parents=True, exist_ok=True)
|
|
@@ -421,6 +421,14 @@ class PMFKernel:
|
|
|
421
421
|
pass
|
|
422
422
|
time.sleep(0.25)
|
|
423
423
|
wait_seconds += 0.25
|
|
424
|
+
# Process didn't write its own pid file — write it now using the
|
|
425
|
+
# spawned process's PID so status checks work correctly.
|
|
426
|
+
if self._is_process_alive(proc.pid):
|
|
427
|
+
try:
|
|
428
|
+
spec.pid_file.write_text(str(proc.pid))
|
|
429
|
+
state["pid"] = proc.pid
|
|
430
|
+
except Exception:
|
|
431
|
+
pass
|
|
424
432
|
|
|
425
433
|
if spec.service_type == "daemon":
|
|
426
434
|
state["status"] = "running"
|
|
@@ -28,7 +28,7 @@ Commands:
|
|
|
28
28
|
cda pmf install Register as macOS LaunchAgent (auto-start on login)
|
|
29
29
|
cda pmf uninstall Remove the LaunchAgent registration
|
|
30
30
|
cda check Run a full self-diagnostic. The system checks itself.
|
|
31
|
-
cda init First-run setup — create
|
|
31
|
+
cda init First-run setup — create ~/Library/goCosmix/ and validate environment
|
|
32
32
|
cda setup Full onboarding: init → pmf install → sync → up (browser opens)
|
|
33
33
|
cda serve Start the local web UI on port 10001
|
|
34
34
|
cda sync Full re-ingest from disk (rebuilds entire DB)
|
|
@@ -2671,13 +2671,14 @@ def setup(skip_sync, no_browser):
|
|
|
2671
2671
|
Full onboarding in four steps: init → pmf install → sync → up.
|
|
2672
2672
|
|
|
2673
2673
|
\b
|
|
2674
|
-
Run this once after `pip install code-data-ark
|
|
2674
|
+
Run this once after `pip install code-data-ark`.
|
|
2675
|
+
If `cda` isn't on PATH yet, use the fallback:
|
|
2675
2676
|
|
|
2676
|
-
cda setup
|
|
2677
|
+
python3 -m cda setup
|
|
2677
2678
|
|
|
2678
2679
|
\b
|
|
2679
2680
|
What each step does:
|
|
2680
|
-
1. Init — create
|
|
2681
|
+
1. Init — create ~/Library/goCosmix/apps/code-data-ark/, patch PATH
|
|
2681
2682
|
2. Install — register a macOS LaunchAgent so CDA starts on every login
|
|
2682
2683
|
3. Sync — ingest all VS Code + Copilot session data into cda.db
|
|
2683
2684
|
4. Up — start the watcher daemon and web UI via PMF, open browser
|
|
@@ -2685,11 +2686,13 @@ def setup(skip_sync, no_browser):
|
|
|
2685
2686
|
All processes are managed by the PMF kernel. The LaunchAgent calls
|
|
2686
2687
|
`cda pmf up` on every login — no manual interaction needed after setup.
|
|
2687
2688
|
"""
|
|
2689
|
+
import shutil as _shutil
|
|
2690
|
+
import os as _os
|
|
2688
2691
|
from cda.kernel.paths import (
|
|
2689
2692
|
CDA_HOME, DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR,
|
|
2690
2693
|
PMF_DIR, PMF_LOG_DIR, CONFIG_DIR, POLICY_FILE,
|
|
2694
|
+
GOCOSMIX_HOME, GOCOSMIX_APPS, GOCOSMIX_SYSTEM,
|
|
2691
2695
|
)
|
|
2692
|
-
import os as _os
|
|
2693
2696
|
|
|
2694
2697
|
W = 52
|
|
2695
2698
|
BAR = "═" * W
|
|
@@ -2715,14 +2718,28 @@ def setup(skip_sync, no_browser):
|
|
|
2715
2718
|
click.echo(bold(bar))
|
|
2716
2719
|
click.echo()
|
|
2717
2720
|
|
|
2718
|
-
|
|
2721
|
+
# Create full goCosmix namespace + app dirs
|
|
2722
|
+
dirs = [GOCOSMIX_HOME, GOCOSMIX_APPS, GOCOSMIX_SYSTEM,
|
|
2723
|
+
DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR, PMF_DIR, PMF_LOG_DIR, CONFIG_DIR]
|
|
2719
2724
|
for d in dirs:
|
|
2725
|
+
existed = d.exists()
|
|
2720
2726
|
d.mkdir(parents=True, exist_ok=True)
|
|
2721
|
-
|
|
2727
|
+
if not existed:
|
|
2728
|
+
click.echo(f" {green('+')} {d}")
|
|
2729
|
+
else:
|
|
2730
|
+
click.echo(f" {green('✓')} {d}")
|
|
2722
2731
|
|
|
2723
2732
|
if not POLICY_FILE.exists():
|
|
2724
2733
|
POLICY_FILE.write_text("# CDA access policy\n# ALLOW <pattern>\n# DENY <pattern>\n")
|
|
2725
2734
|
|
|
2735
|
+
# Offer migration from legacy ~/.cda/
|
|
2736
|
+
legacy = Path.home() / ".cda"
|
|
2737
|
+
if legacy.exists() and legacy != CDA_HOME:
|
|
2738
|
+
click.echo()
|
|
2739
|
+
click.echo(yellow(f" ⚠ Legacy data found at {legacy}"))
|
|
2740
|
+
click.echo(yellow(" Run `cda migrate-home` after setup to move it to the new location."))
|
|
2741
|
+
|
|
2742
|
+
# VS Code data dir check
|
|
2726
2743
|
vscode_data = Path(_os.environ.get(
|
|
2727
2744
|
"VSCODE_DATA_DIR",
|
|
2728
2745
|
Path.home() / "Library/Application Support/Code/User",
|
|
@@ -2737,25 +2754,54 @@ def setup(skip_sync, no_browser):
|
|
|
2737
2754
|
click.echo(f" {green('✓')} CDA_HOME: {CDA_HOME}")
|
|
2738
2755
|
click.echo()
|
|
2739
2756
|
|
|
2757
|
+
# ── PATH patch ───────────────────────────────────────────────
|
|
2758
|
+
# Detect where pip placed the `cda` binary and ensure it's on PATH.
|
|
2759
|
+
# Works whether invoked as `cda setup` or `python3 -m cda setup`.
|
|
2760
|
+
cda_bin_dir = None
|
|
2761
|
+
cda_bin = _shutil.which("cda")
|
|
2762
|
+
if cda_bin:
|
|
2763
|
+
cda_bin_dir = str(Path(cda_bin).parent)
|
|
2764
|
+
else:
|
|
2765
|
+
# pip install --user puts scripts next to python executable
|
|
2766
|
+
py_bin_dir = Path(sys.executable).parent
|
|
2767
|
+
candidate = py_bin_dir / "cda"
|
|
2768
|
+
if candidate.exists():
|
|
2769
|
+
cda_bin_dir = str(py_bin_dir)
|
|
2770
|
+
|
|
2771
|
+
if cda_bin_dir and cda_bin_dir not in _os.environ.get("PATH", "").split(":"):
|
|
2772
|
+
export_line = f'export PATH="{cda_bin_dir}:$PATH"'
|
|
2773
|
+
zprofile = Path.home() / ".zprofile"
|
|
2774
|
+
existing = zprofile.read_text() if zprofile.exists() else ""
|
|
2775
|
+
if export_line not in existing:
|
|
2776
|
+
with open(zprofile, "a") as f:
|
|
2777
|
+
f.write(f"\n# goCosmix — added by cda setup\n{export_line}\n")
|
|
2778
|
+
click.echo(f" {green('+')} PATH updated in ~/.zprofile")
|
|
2779
|
+
click.echo(yellow(" Run `source ~/.zprofile` or open a new terminal to activate."))
|
|
2780
|
+
click.echo(f" {green('✓')} cda binary: {cda_bin_dir}/cda")
|
|
2781
|
+
elif cda_bin:
|
|
2782
|
+
click.echo(f" {green('✓')} cda binary on PATH: {cda_bin}")
|
|
2783
|
+
|
|
2784
|
+
click.echo()
|
|
2785
|
+
|
|
2740
2786
|
# ── Step 2: PMF install ──────────────────────────────────────
|
|
2741
2787
|
click.echo(bold(bar))
|
|
2742
2788
|
click.echo(bold(" Step 2/4 — PMF install"))
|
|
2743
2789
|
click.echo(bold(bar))
|
|
2744
2790
|
click.echo()
|
|
2745
2791
|
click.echo(dim(" The LaunchAgent registers CDA with macOS launchd. On every login,"))
|
|
2746
|
-
click.echo(dim(" launchd calls `cda pmf up`
|
|
2747
|
-
click.echo(dim("
|
|
2792
|
+
click.echo(dim(" launchd calls `cda pmf up` — starts watcher + web UI via PMF kernel."))
|
|
2793
|
+
click.echo(dim(" No terminal required after this."))
|
|
2748
2794
|
click.echo()
|
|
2749
2795
|
|
|
2750
2796
|
pmf_ok = False
|
|
2751
2797
|
try:
|
|
2752
2798
|
target = install_launchd(CDA_HOME)
|
|
2753
2799
|
click.echo(f" {green('✓')} LaunchAgent: {target}")
|
|
2754
|
-
click.echo(f" {green('✓')} Loaded — CDA
|
|
2800
|
+
click.echo(f" {green('✓')} Loaded — CDA starts automatically on every login")
|
|
2755
2801
|
pmf_ok = True
|
|
2756
2802
|
except PMFKernelError as exc:
|
|
2757
2803
|
click.echo(f" {yellow('⚠')} LaunchAgent registration failed: {exc}")
|
|
2758
|
-
click.echo(yellow("
|
|
2804
|
+
click.echo(yellow(" Fix PATH then run `cda pmf install` to retry."))
|
|
2759
2805
|
click.echo()
|
|
2760
2806
|
|
|
2761
2807
|
# ── Step 3: Sync ─────────────────────────────────────────────
|
|
@@ -2769,7 +2815,7 @@ def setup(skip_sync, no_browser):
|
|
|
2769
2815
|
click.echo()
|
|
2770
2816
|
else:
|
|
2771
2817
|
click.echo(dim(" Scanning VS Code workspaceStorage and building cda.db."))
|
|
2772
|
-
click.echo(dim(" First run may take
|
|
2818
|
+
click.echo(dim(" First run may take a few minutes depending on session history."))
|
|
2773
2819
|
click.echo()
|
|
2774
2820
|
|
|
2775
2821
|
sync_failed = False
|
|
@@ -2833,15 +2879,15 @@ def setup(skip_sync, no_browser):
|
|
|
2833
2879
|
click.echo(bold(BAR))
|
|
2834
2880
|
click.echo()
|
|
2835
2881
|
if pmf_ok:
|
|
2836
|
-
click.echo(dim(" CDA
|
|
2837
|
-
click.echo(dim(" The watcher daemon
|
|
2882
|
+
click.echo(dim(" CDA starts automatically on every login via launchd."))
|
|
2883
|
+
click.echo(dim(" The watcher daemon keeps your session data in sync automatically."))
|
|
2838
2884
|
click.echo(dim(f" Visit {url} any time to explore your data."))
|
|
2839
2885
|
click.echo()
|
|
2840
2886
|
click.echo(dim(" Useful commands:"))
|
|
2841
2887
|
click.echo(dim(" cda check — full system health diagnostic"))
|
|
2842
2888
|
click.echo(dim(" cda sync — re-ingest after significant new session activity"))
|
|
2843
2889
|
click.echo(dim(" cda pmf services — view running services and their status"))
|
|
2844
|
-
click.echo(dim(" cda pmf uninstall — remove auto-start LaunchAgent
|
|
2890
|
+
click.echo(dim(" cda pmf uninstall — remove the auto-start LaunchAgent"))
|
|
2845
2891
|
click.echo()
|
|
2846
2892
|
|
|
2847
2893
|
|
|
@@ -2851,7 +2897,7 @@ def setup(skip_sync, no_browser):
|
|
|
2851
2897
|
|
|
2852
2898
|
@cli.command("init")
|
|
2853
2899
|
def init():
|
|
2854
|
-
"""First-run setup — create
|
|
2900
|
+
"""First-run setup — create ~/Library/goCosmix/apps/code-data-ark/ directory structure and validate environment."""
|
|
2855
2901
|
from cda.kernel.paths import (
|
|
2856
2902
|
CDA_HOME, DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR,
|
|
2857
2903
|
PMF_DIR, PMF_LOG_DIR, CONFIG_DIR, POLICY_FILE,
|
|
@@ -1008,7 +1008,7 @@ def get_overview():
|
|
|
1008
1008
|
{("(SELECT AVG(heat_score) FROM session_analysis WHERE heat_score IS NOT NULL)" if has_analysis else "0")} as avg_heat,
|
|
1009
1009
|
{("(SELECT COUNT(*) FROM session_analysis WHERE heat_score >= 50)" if has_analysis else "0")} as critical_sessions,
|
|
1010
1010
|
{("(SELECT COUNT(*) FROM anomaly_alerts)" if has_alerts else "0")} as alert_count,
|
|
1011
|
-
(SELECT COUNT(
|
|
1011
|
+
(SELECT COUNT(*) FROM workspaces) as workspace_count,
|
|
1012
1012
|
(SELECT MAX(created_at) FROM sessions) as last_session
|
|
1013
1013
|
""")
|
|
1014
1014
|
|
|
@@ -1041,10 +1041,11 @@ def get_overview():
|
|
|
1041
1041
|
LIMIT 15
|
|
1042
1042
|
""")) if has_signals else []
|
|
1043
1043
|
|
|
1044
|
+
exchange_count_expr = "(SELECT COUNT(*) FROM exchanges WHERE exchanges.session_id = s.session_id)" if has_exchanges else "0"
|
|
1044
1045
|
if has_analysis:
|
|
1045
|
-
recent = safe_rows(query_rows("""
|
|
1046
|
+
recent = safe_rows(query_rows(f"""
|
|
1046
1047
|
SELECT s.session_id as id, s.title, sa.heat_score,
|
|
1047
|
-
{
|
|
1048
|
+
{exchange_count_expr} as exchange_count,
|
|
1048
1049
|
s.created_at
|
|
1049
1050
|
FROM sessions s
|
|
1050
1051
|
LEFT JOIN session_analysis sa ON sa.session_id = s.session_id
|
|
@@ -1052,9 +1053,9 @@ def get_overview():
|
|
|
1052
1053
|
LIMIT 10
|
|
1053
1054
|
"""))
|
|
1054
1055
|
else:
|
|
1055
|
-
recent = safe_rows(query_rows("""
|
|
1056
|
+
recent = safe_rows(query_rows(f"""
|
|
1056
1057
|
SELECT s.session_id as id, s.title, NULL as heat_score,
|
|
1057
|
-
{
|
|
1058
|
+
{exchange_count_expr} as exchange_count,
|
|
1058
1059
|
s.created_at
|
|
1059
1060
|
FROM sessions s
|
|
1060
1061
|
ORDER BY s.created_at DESC
|
|
@@ -1161,7 +1162,7 @@ def get_session_detail(session_id):
|
|
|
1161
1162
|
signals = safe_rows(query_rows("""
|
|
1162
1163
|
SELECT * FROM exchange_signals
|
|
1163
1164
|
WHERE session_id = ?
|
|
1164
|
-
ORDER BY
|
|
1165
|
+
ORDER BY ts DESC
|
|
1165
1166
|
""", (session_id,))) if has_signals else []
|
|
1166
1167
|
|
|
1167
1168
|
signal_summary = safe_rows(query_rows("""
|
|
@@ -1195,18 +1196,20 @@ def get_search_results(query, limit=50):
|
|
|
1195
1196
|
"""Full-text search across exchanges."""
|
|
1196
1197
|
try:
|
|
1197
1198
|
results = query_rows("""
|
|
1198
|
-
SELECT
|
|
1199
|
-
|
|
1199
|
+
SELECT
|
|
1200
|
+
e.session_id,
|
|
1200
1201
|
s.title,
|
|
1201
|
-
|
|
1202
|
+
sa.heat_score,
|
|
1202
1203
|
e.id as exchange_id,
|
|
1203
|
-
e.
|
|
1204
|
-
e.
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
JOIN
|
|
1209
|
-
|
|
1204
|
+
e.exchange_index,
|
|
1205
|
+
e.user_message,
|
|
1206
|
+
e.response_text,
|
|
1207
|
+
e.user_ts
|
|
1208
|
+
FROM fts_exchanges fts
|
|
1209
|
+
JOIN exchanges e ON fts.rowid = e.id
|
|
1210
|
+
JOIN sessions s ON e.session_id = s.session_id
|
|
1211
|
+
LEFT JOIN session_analysis sa ON sa.session_id = e.session_id
|
|
1212
|
+
WHERE fts_exchanges MATCH ?
|
|
1210
1213
|
ORDER BY rank
|
|
1211
1214
|
LIMIT ?
|
|
1212
1215
|
""", (query, limit))
|
|
@@ -1219,13 +1222,11 @@ def get_workspaces():
|
|
|
1219
1222
|
"""List all workspaces with session counts."""
|
|
1220
1223
|
try:
|
|
1221
1224
|
workspaces = query_rows("""
|
|
1222
|
-
SELECT
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
FROM
|
|
1226
|
-
|
|
1227
|
-
GROUP BY workspace_id
|
|
1228
|
-
ORDER BY session_count DESC
|
|
1225
|
+
SELECT w.workspace_id, w.uri, w.name, w.type, w.session_count,
|
|
1226
|
+
(SELECT MAX(s.created_at) FROM sessions s
|
|
1227
|
+
WHERE s.workspace_id = w.workspace_id) as last_session
|
|
1228
|
+
FROM workspaces w
|
|
1229
|
+
ORDER BY w.session_count DESC
|
|
1229
1230
|
""")
|
|
1230
1231
|
return {"workspaces": workspaces}
|
|
1231
1232
|
except Exception as e:
|
|
@@ -1253,9 +1254,9 @@ def get_memory():
|
|
|
1253
1254
|
"""Get all memory files."""
|
|
1254
1255
|
try:
|
|
1255
1256
|
memory = query_rows("""
|
|
1256
|
-
SELECT id,
|
|
1257
|
+
SELECT id, scope, workspace_id, session_id, filename, size_bytes, ingested_at
|
|
1257
1258
|
FROM memory_files
|
|
1258
|
-
ORDER BY
|
|
1259
|
+
ORDER BY ingested_at DESC
|
|
1259
1260
|
""")
|
|
1260
1261
|
return {"memory": memory}
|
|
1261
1262
|
except Exception as e:
|
|
@@ -1267,21 +1268,25 @@ def get_tool_calls(query_str=None, limit=50):
|
|
|
1267
1268
|
try:
|
|
1268
1269
|
if query_str:
|
|
1269
1270
|
results = query_rows("""
|
|
1270
|
-
SELECT tc
|
|
1271
|
+
SELECT tc.id, tc.session_id, tc.exchange_index, tc.request_id,
|
|
1272
|
+
tc.tool_call_id, tc.tool_name, tc.file_path,
|
|
1273
|
+
tc.arguments_json, tc.has_output, tc.ingested_at,
|
|
1274
|
+
s.title as session_title
|
|
1271
1275
|
FROM tool_calls tc
|
|
1272
|
-
JOIN
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
ORDER BY tc.created_at DESC
|
|
1276
|
+
JOIN sessions s ON tc.session_id = s.session_id
|
|
1277
|
+
WHERE tc.tool_name LIKE ? OR tc.arguments_json LIKE ?
|
|
1278
|
+
ORDER BY tc.ingested_at DESC
|
|
1276
1279
|
LIMIT ?
|
|
1277
1280
|
""", (f"%{query_str}%", f"%{query_str}%", limit))
|
|
1278
1281
|
else:
|
|
1279
1282
|
results = query_rows("""
|
|
1280
|
-
SELECT tc
|
|
1283
|
+
SELECT tc.id, tc.session_id, tc.exchange_index, tc.request_id,
|
|
1284
|
+
tc.tool_call_id, tc.tool_name, tc.file_path,
|
|
1285
|
+
tc.arguments_json, tc.has_output, tc.ingested_at,
|
|
1286
|
+
s.title as session_title
|
|
1281
1287
|
FROM tool_calls tc
|
|
1282
|
-
JOIN
|
|
1283
|
-
|
|
1284
|
-
ORDER BY tc.created_at DESC
|
|
1288
|
+
JOIN sessions s ON tc.session_id = s.session_id
|
|
1289
|
+
ORDER BY tc.ingested_at DESC
|
|
1285
1290
|
LIMIT ?
|
|
1286
1291
|
""", (limit,))
|
|
1287
1292
|
return {"tool_calls": results, "query": query_str, "count": len(results)}
|
|
@@ -1293,10 +1298,11 @@ def get_vfs(session_id):
|
|
|
1293
1298
|
"""List VFS files for a session."""
|
|
1294
1299
|
try:
|
|
1295
1300
|
vfs = query_rows("""
|
|
1296
|
-
SELECT id, session_id,
|
|
1301
|
+
SELECT id, session_id, source_type, source_path, filename,
|
|
1302
|
+
content_type, size_bytes, sha256, ingested_at
|
|
1297
1303
|
FROM vfs
|
|
1298
1304
|
WHERE session_id = ?
|
|
1299
|
-
ORDER BY
|
|
1305
|
+
ORDER BY filename
|
|
1300
1306
|
""", (session_id,))
|
|
1301
1307
|
return {"vfs": vfs, "session_id": session_id}
|
|
1302
1308
|
except Exception as e:
|
|
@@ -1354,17 +1360,26 @@ def get_tokens(session_id=None):
|
|
|
1354
1360
|
if session_id:
|
|
1355
1361
|
tokens = query_rows("""
|
|
1356
1362
|
SELECT
|
|
1357
|
-
SUM(
|
|
1358
|
-
|
|
1359
|
-
|
|
1363
|
+
SUM(prompt_tokens) as total_prompt,
|
|
1364
|
+
SUM(completion_tokens) as total_completion,
|
|
1365
|
+
SUM(cached_tokens) as total_cached,
|
|
1366
|
+
SUM(prompt_tokens + completion_tokens) as total_tokens,
|
|
1367
|
+
COUNT(*) as turn_count,
|
|
1368
|
+
GROUP_CONCAT(DISTINCT model_id) as models
|
|
1369
|
+
FROM token_usage
|
|
1360
1370
|
WHERE session_id = ?
|
|
1361
1371
|
""", (session_id,))
|
|
1362
1372
|
else:
|
|
1363
1373
|
tokens = query_rows("""
|
|
1364
1374
|
SELECT
|
|
1365
|
-
SUM(
|
|
1366
|
-
|
|
1367
|
-
|
|
1375
|
+
SUM(prompt_tokens) as total_prompt,
|
|
1376
|
+
SUM(completion_tokens) as total_completion,
|
|
1377
|
+
SUM(cached_tokens) as total_cached,
|
|
1378
|
+
SUM(prompt_tokens + completion_tokens) as total_tokens,
|
|
1379
|
+
COUNT(*) as turn_count,
|
|
1380
|
+
COUNT(DISTINCT session_id) as session_count,
|
|
1381
|
+
GROUP_CONCAT(DISTINCT model_id) as models
|
|
1382
|
+
FROM token_usage
|
|
1368
1383
|
""")
|
|
1369
1384
|
return {"tokens": tokens}
|
|
1370
1385
|
except Exception as e:
|
|
@@ -1726,11 +1741,10 @@ def render_tokens():
|
|
|
1726
1741
|
return """
|
|
1727
1742
|
<div class="page-header">
|
|
1728
1743
|
<div class="page-title">Token Usage</div>
|
|
1729
|
-
<div class="page-subtitle">Token consumption
|
|
1730
|
-
</div>
|
|
1731
|
-
<div class="card">
|
|
1732
|
-
<p>Token usage analysis coming soon.</p>
|
|
1744
|
+
<div class="page-subtitle">Token consumption across all sessions.</div>
|
|
1733
1745
|
</div>
|
|
1746
|
+
<div id="tokens-summary" class="loading"><div class="spinner"></div>Loading...</div>
|
|
1747
|
+
<div id="tokens-table" style="margin-top:16px"></div>
|
|
1734
1748
|
"""
|
|
1735
1749
|
|
|
1736
1750
|
|
|
@@ -1910,6 +1924,9 @@ function initializePage(page) {
|
|
|
1910
1924
|
case 'alerts':
|
|
1911
1925
|
initAlerts();
|
|
1912
1926
|
break;
|
|
1927
|
+
case 'tokens':
|
|
1928
|
+
initTokens();
|
|
1929
|
+
break;
|
|
1913
1930
|
case 'pipeline':
|
|
1914
1931
|
initPipeline();
|
|
1915
1932
|
break;
|
|
@@ -2358,6 +2375,46 @@ function initKeywords() {
|
|
|
2358
2375
|
});
|
|
2359
2376
|
}
|
|
2360
2377
|
|
|
2378
|
+
function initTokens() {
|
|
2379
|
+
const summary = document.getElementById('tokens-summary');
|
|
2380
|
+
const table = document.getElementById('tokens-table');
|
|
2381
|
+
if (!summary) return;
|
|
2382
|
+
summary.innerHTML = '<div class="spinner"></div> Loading...';
|
|
2383
|
+
fetch('/api/tokens').then(r => r.json()).then(data => {
|
|
2384
|
+
const t = (data.tokens || [])[0] || {};
|
|
2385
|
+
const fmt = n => (n || 0).toLocaleString();
|
|
2386
|
+
summary.innerHTML = `
|
|
2387
|
+
<div class="grid-4">
|
|
2388
|
+
<div class="card"><div class="card-header">Total Tokens</div><div class="card-value">${fmt(t.total_tokens)}</div></div>
|
|
2389
|
+
<div class="card"><div class="card-header">Prompt</div><div class="card-value">${fmt(t.total_prompt)}</div></div>
|
|
2390
|
+
<div class="card"><div class="card-header">Completion</div><div class="card-value">${fmt(t.total_completion)}</div></div>
|
|
2391
|
+
<div class="card"><div class="card-header">Cached</div><div class="card-value">${fmt(t.total_cached)}</div></div>
|
|
2392
|
+
<div class="card"><div class="card-header">Sessions</div><div class="card-value">${fmt(t.session_count)}</div></div>
|
|
2393
|
+
<div class="card"><div class="card-header">Turns</div><div class="card-value">${fmt(t.turn_count)}</div></div>
|
|
2394
|
+
</div>
|
|
2395
|
+
<div class="card" style="margin-top:12px"><b>Models:</b> ${t.models || 'n/a'}</div>
|
|
2396
|
+
`;
|
|
2397
|
+
}).catch(() => {
|
|
2398
|
+
summary.innerHTML = '<div class="alert alert-danger">Failed to load token data.</div>';
|
|
2399
|
+
});
|
|
2400
|
+
if (table) {
|
|
2401
|
+
table.innerHTML = '<div class="spinner"></div> Loading sessions...';
|
|
2402
|
+
const sql = 'SELECT s.title, tu.session_id, SUM(tu.prompt_tokens) as prompt, SUM(tu.completion_tokens) as completion, SUM(tu.cached_tokens) as cached, SUM(tu.prompt_tokens + tu.completion_tokens) as total, COUNT(*) as turns, GROUP_CONCAT(DISTINCT tu.model_id) as models FROM token_usage tu JOIN sessions s ON tu.session_id = s.session_id GROUP BY tu.session_id ORDER BY total DESC LIMIT 50';
|
|
2403
|
+
fetch('/api/query', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({sql: sql})})
|
|
2404
|
+
.then(r => r.json()).then(data => {
|
|
2405
|
+
const rows = data.rows || [];
|
|
2406
|
+
if (!rows.length) { table.innerHTML = '<p>No per-session data.</p>'; return; }
|
|
2407
|
+
const fmt = n => (n || 0).toLocaleString();
|
|
2408
|
+
let html = '<div class="card"><div class="card-header">Top Sessions by Token Usage</div><table class="table"><thead><tr><th>Session</th><th>Total</th><th>Prompt</th><th>Completion</th><th>Cached</th><th>Turns</th><th>Models</th></tr></thead><tbody>';
|
|
2409
|
+
rows.forEach(r => {
|
|
2410
|
+
html += '<tr><td class="truncate">' + (r.title || r.session_id) + '</td><td>' + fmt(r.total) + '</td><td>' + fmt(r.prompt) + '</td><td>' + fmt(r.completion) + '</td><td>' + fmt(r.cached) + '</td><td>' + r.turns + '</td><td class="truncate">' + (r.models || '') + '</td></tr>';
|
|
2411
|
+
});
|
|
2412
|
+
html += '</tbody></table></div>';
|
|
2413
|
+
table.innerHTML = html;
|
|
2414
|
+
}).catch(() => { table.innerHTML = '<p>Failed to load session breakdown.</p>'; });
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2361
2418
|
function initWorkspaces() {
|
|
2362
2419
|
const container = document.getElementById('workspaces-content');
|
|
2363
2420
|
if (!container) return;
|
|
@@ -2737,6 +2794,13 @@ def application(environ, start_response):
|
|
|
2737
2794
|
start_response('200 OK', [('Content-Type', 'application/json')])
|
|
2738
2795
|
return [response]
|
|
2739
2796
|
|
|
2797
|
+
elif path == '/api/tokens':
|
|
2798
|
+
session_id = query.get('session_id', [None])[0]
|
|
2799
|
+
data = get_tokens(session_id)
|
|
2800
|
+
response = json.dumps(data).encode('utf-8')
|
|
2801
|
+
start_response('200 OK', [('Content-Type', 'application/json')])
|
|
2802
|
+
return [response]
|
|
2803
|
+
|
|
2740
2804
|
elif path == '/api/alerts':
|
|
2741
2805
|
data = get_alerts()
|
|
2742
2806
|
response = json.dumps(data).encode('utf-8')
|
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.6] - 2026-05-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`~/Library/goCosmix/` home namespace** — all goCosmix apps share a unified `~/Library/goCosmix/` directory (macOS) or `~/.gocosmix/` (other). CDA now installs to `~/Library/goCosmix/apps/code-data-ark/` instead of `~/.cda/`. Provides a clean, organized home for all future goCosmix systems.
|
|
12
|
+
- **`GOCOSMIX_HOME`, `GOCOSMIX_APPS`, `GOCOSMIX_SYSTEM`** constants in `cda.kernel.paths` — shared namespace anchors for future goCosmix apps.
|
|
13
|
+
- **Auto PATH patching in `cda setup`** — detects where pip placed the `cda` binary and patches `~/.zprofile` if the bin dir is missing from PATH. Solves the macOS system Python `command not found` blocker.
|
|
14
|
+
- **`python3 -m cda` entry point** (`cda/__main__.py`) — bootstrapping fallback so users can run `python3 -m cda setup` before PATH is configured.
|
|
15
|
+
- **Legacy migration notice** — `cda setup` detects an existing `~/.cda/` and advises running `cda migrate-home`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `cda.kernel.paths.get_cda_home()` default changed from `~/.cda` to `~/Library/goCosmix/apps/code-data-ark` (macOS) / `~/.gocosmix/apps/code-data-ark` (other). `CDA_HOME` env var still overrides.
|
|
19
|
+
- `ensure_dirs()` now creates the full goCosmix namespace tree in addition to app-level dirs.
|
|
20
|
+
- README quickstart updated: install command is `python3 -m cda setup` as the safe default.
|
|
21
|
+
- `cda setup` Step 1 shows `+` for new dirs and `✓` for existing ones.
|
|
22
|
+
|
|
8
23
|
## [2.0.5] - 2026-05-11
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-data-ark"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.7"
|
|
8
8
|
description = "Code Data Ark — local observability and intelligence platform for VS Code + Copilot Chat sessions"
|
|
9
9
|
readme = "readme.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -52,10 +52,17 @@ The runtime is managed by an embedded process kernel (PMF) that supervises the w
|
|
|
52
52
|
pip install code-data-ark
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
> **macOS / system Python note**: pip installs the `cda` binary to `~/Library/Python/3.x/bin/` which is not on `PATH` by default. Use the fallback below — `cda setup` will fix PATH for you automatically:
|
|
56
|
+
>
|
|
57
|
+
> ```bash
|
|
58
|
+
> python3 -m cda setup
|
|
59
|
+
> ```
|
|
60
|
+
|
|
55
61
|
### Install with pipx
|
|
56
62
|
|
|
57
63
|
```bash
|
|
58
64
|
pipx install code-data-ark
|
|
65
|
+
# pipx automatically manages PATH — `cda setup` works immediately
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
### Install from source
|
|
@@ -74,25 +81,27 @@ pip install -e ".[dev]"
|
|
|
74
81
|
make install-dev
|
|
75
82
|
```
|
|
76
83
|
|
|
77
|
-
> The `cda` console command is installed into your
|
|
84
|
+
> The `cda` console command is installed into your Python environment's `bin` directory. If it isn't on PATH yet, use `python3 -m cda setup` — setup patches `~/.zprofile` automatically.
|
|
78
85
|
|
|
79
86
|
## ⚡ Quick Start
|
|
80
87
|
|
|
81
88
|
```bash
|
|
82
89
|
pip install code-data-ark
|
|
83
|
-
cda setup
|
|
90
|
+
python3 -m cda setup # use this if `cda` isn't on PATH yet
|
|
84
91
|
```
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
After the first run, `cda setup` patches `~/.zprofile` so `cda` is on PATH in every new terminal.
|
|
94
|
+
|
|
95
|
+
`cda setup` runs four steps in sequence:
|
|
87
96
|
|
|
88
97
|
| Step | What it does |
|
|
89
98
|
|------|-------------|
|
|
90
|
-
| **1. Init** | Creates
|
|
91
|
-
| **2. PMF install** | Registers a macOS LaunchAgent — CDA starts automatically on every login |
|
|
92
|
-
| **3. Sync** | Ingests all VS Code + Copilot session data into
|
|
99
|
+
| **1. Init** | Creates `~/Library/goCosmix/apps/code-data-ark/` — all app data in one organized namespace. Also patches `~/.zprofile` if `cda` isn't on PATH yet. |
|
|
100
|
+
| **2. PMF install** | Registers a macOS LaunchAgent — CDA starts automatically on every login via `cda pmf up` |
|
|
101
|
+
| **3. Sync** | Ingests all VS Code + Copilot session data into `cda.db` |
|
|
93
102
|
| **4. Up** | Starts the watcher daemon and web UI via the PMF kernel, opens browser |
|
|
94
103
|
|
|
95
|
-
After setup, everything is managed by the **PMF kernel
|
|
104
|
+
All data lives in `~/Library/goCosmix/apps/code-data-ark/`. After setup, everything is managed by the **PMF kernel** — no terminal interaction required.
|
|
96
105
|
|
|
97
106
|
### Options
|
|
98
107
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.7
|
code_data_ark-2.0.5/version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.0.5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|