code-data-ark 2.0.3__tar.gz → 2.0.5__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.
Files changed (35) hide show
  1. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/PKG-INFO +39 -20
  2. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/kernel/pmf_kernel.py +130 -0
  3. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/ui/cli.py +295 -12
  4. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/changelog.md +20 -0
  5. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/pyproject.toml +1 -1
  6. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/readme.md +38 -19
  7. code_data_ark-2.0.5/version +1 -0
  8. code_data_ark-2.0.3/version +0 -1
  9. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/.flake8 +0 -0
  10. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/.github/workflows/ci.yml +0 -0
  11. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/.gitignore +0 -0
  12. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/bin/release.py +0 -0
  13. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/__init__.py +0 -0
  14. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/kernel/__init__.py +0 -0
  15. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/kernel/control_db.py +0 -0
  16. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/kernel/paths.py +0 -0
  17. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/kernel/selfcheck.py +0 -0
  18. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/__init__.py +0 -0
  19. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/embed.py +0 -0
  20. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/extract.py +0 -0
  21. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/ingest.py +0 -0
  22. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/parse_edits.py +0 -0
  23. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/reconstruct.py +0 -0
  24. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/pipeline/watcher.py +0 -0
  25. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/ui/__init__.py +0 -0
  26. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/cda/ui/web.py +0 -0
  27. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/contributing.md +0 -0
  28. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/docs/architecture.md +0 -0
  29. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/docs/examples/usage.md +0 -0
  30. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/docs/pmf_kernel.md +0 -0
  31. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/docs/roadmap.md +0 -0
  32. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/license +0 -0
  33. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/makefile +0 -0
  34. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/tests/test_basic.py +0 -0
  35. {code_data_ark-2.0.3 → code_data_ark-2.0.5}/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
3
+ Version: 2.0.5
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
@@ -121,48 +121,67 @@ make install-dev
121
121
 
122
122
  ## ⚡ Quick Start
123
123
 
124
- 1. **Install**
125
-
126
124
  ```bash
127
125
  pip install code-data-ark
126
+ cda setup
128
127
  ```
129
128
 
130
- 2. **Initialize — create `~/.cda/` and validate your VS Code data path**
129
+ That's it. `cda setup` runs four steps in sequence:
131
130
 
132
- ```bash
133
- cda init
134
- ```
131
+ | Step | What it does |
132
+ |------|-------------|
133
+ | **1. Init** | Creates `~/.cda/` directory tree, validates your VS Code data path |
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 `~/.cda/data/cda.db` |
136
+ | **4. Up** | Starts the watcher daemon and web UI via the PMF kernel, opens browser |
137
+
138
+ After setup, everything is managed by the **PMF kernel**. On every login, `launchd` calls `cda pmf up` which starts the watcher and web UI. No terminal interaction required.
135
139
 
136
- 3. **Ingest all VS Code session data**
140
+ ### Options
137
141
 
138
142
  ```bash
139
- cda sync
143
+ cda setup --skip-sync # Skip initial ingest (run `cda sync` manually later)
144
+ cda setup --no-browser # Don't open browser when the UI starts
140
145
  ```
141
146
 
142
- 4. **Start the live watcher daemon**
147
+ ### After setup
143
148
 
144
149
  ```bash
145
- cda watch start
150
+ cda check # Full system health diagnostic
151
+ cda sync # Re-ingest after significant new session activity
152
+ cda pmf services # View all running services and their status
153
+ cda pmf uninstall # Remove the auto-start LaunchAgent registration
146
154
  ```
147
155
 
148
- 5. **Open the web dashboard**
156
+ ## 🔧 Process Management (PMF)
149
157
 
150
- ```bash
151
- cda serve # → http://127.0.0.1:10001
158
+ All background processes run through the embedded PMF kernel. The LaunchAgent is the entry point — nothing starts directly on the host outside of PMF.
159
+
160
+ ```
161
+ launchd (login)
162
+ └─ cda pmf up
163
+ ├─ PMF kernel → watcher daemon (cda.pipeline.watcher)
164
+ └─ PMF kernel → web UI server (cda.ui.web)
152
165
  ```
153
166
 
154
- 6. **Build semantic intelligence** (optional, requires `sentence-transformers`)
167
+ ### PMF commands
155
168
 
156
169
  ```bash
157
- cda embed build
170
+ cda pmf services # List all services with status and PID
171
+ cda pmf start <service> # Start a service (watcher, ui, sync, reconstruct, embed-build)
172
+ cda pmf stop <service> # Stop a service
173
+ cda pmf restart <service> # Restart a service
174
+ cda pmf logs <service> # Tail the service log
175
+ cda pmf up # Start watcher + UI (opens browser) — same as launchd trigger
176
+ cda pmf install # Register LaunchAgent (done automatically by cda setup)
177
+ cda pmf uninstall # Remove LaunchAgent
158
178
  ```
159
179
 
160
180
  ## 🌐 Web UI
161
181
 
162
- - **Background service**: `cda ui start`
163
- - **Stop service**: `cda ui stop`
164
- - **Service status**: `cda ui status`
165
- - **Foreground mode**: `cda serve`
182
+ - **Background service** (default after setup): managed by PMF, starts on login
183
+ - **Foreground mode**: `cda serve` — runs in the terminal, opens browser, Ctrl+C to stop
184
+ - **Access**: `http://127.0.0.1:10001`
166
185
 
167
186
  The web UI includes:
168
187
 
@@ -1,9 +1,13 @@
1
1
  import json
2
2
  import os
3
+ import shutil
3
4
  import signal
5
+ import socket
4
6
  import subprocess
5
7
  import sys
8
+ import threading
6
9
  import time
10
+ import webbrowser
7
11
  from dataclasses import dataclass
8
12
  from pathlib import Path
9
13
  from typing import Dict, List, Optional
@@ -17,6 +21,132 @@ from cda.kernel.paths import (
17
21
  DEFAULT_HOST = "127.0.0.1"
18
22
  DEFAULT_PORT = 10001
19
23
 
24
+ # ── launchd integration ──────────────────────────────────────────────────────
25
+
26
+ PLIST_LABEL = "com.gocosmix.cda"
27
+
28
+
29
+ def plist_path() -> Path:
30
+ return Path.home() / "Library" / "LaunchAgents" / f"{PLIST_LABEL}.plist"
31
+
32
+
33
+ def generate_plist(cda_bin: str, cda_home: Path) -> str:
34
+ log = cda_home / "logs" / "launchd.log"
35
+ return f"""<?xml version="1.0" encoding="UTF-8"?>
36
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
37
+ <plist version="1.0">
38
+ <dict>
39
+ <key>Label</key>
40
+ <string>{PLIST_LABEL}</string>
41
+ <key>ProgramArguments</key>
42
+ <array>
43
+ <string>{cda_bin}</string>
44
+ <string>pmf</string>
45
+ <string>up</string>
46
+ </array>
47
+ <key>RunAtLoad</key>
48
+ <true/>
49
+ <key>KeepAlive</key>
50
+ <false/>
51
+ <key>StandardOutPath</key>
52
+ <string>{log}</string>
53
+ <key>StandardErrorPath</key>
54
+ <string>{log}</string>
55
+ <key>EnvironmentVariables</key>
56
+ <dict>
57
+ <key>CDA_HOME</key>
58
+ <string>{cda_home}</string>
59
+ <key>PATH</key>
60
+ <string>{os.path.dirname(cda_bin)}:/usr/local/bin:/usr/bin:/bin</string>
61
+ </dict>
62
+ </dict>
63
+ </plist>
64
+ """
65
+
66
+
67
+ def install_launchd(cda_home: Path) -> Path:
68
+ """Write the LaunchAgent plist and load it with launchctl."""
69
+ cda_bin = shutil.which("cda")
70
+ if not cda_bin:
71
+ raise PMFKernelError("cda binary not found on PATH — cannot generate plist")
72
+
73
+ target = plist_path()
74
+ target.parent.mkdir(parents=True, exist_ok=True)
75
+ target.write_text(generate_plist(cda_bin, cda_home))
76
+
77
+ # Unload any stale registration first
78
+ subprocess.run(["launchctl", "unload", str(target)], capture_output=True)
79
+
80
+ result = subprocess.run(
81
+ ["launchctl", "load", str(target)],
82
+ capture_output=True,
83
+ text=True,
84
+ )
85
+ if result.returncode != 0:
86
+ raise PMFKernelError(f"launchctl load failed: {result.stderr.strip()}")
87
+
88
+ return target
89
+
90
+
91
+ def uninstall_launchd() -> None:
92
+ """Unload and remove the LaunchAgent plist."""
93
+ target = plist_path()
94
+ if target.exists():
95
+ subprocess.run(["launchctl", "unload", str(target)], capture_output=True)
96
+ target.unlink(missing_ok=True)
97
+
98
+
99
+ def open_browser_when_ready(
100
+ url: str,
101
+ host: str = DEFAULT_HOST,
102
+ port: int = DEFAULT_PORT,
103
+ timeout: float = 12.0,
104
+ ) -> threading.Thread:
105
+ """
106
+ Spawn a daemon thread that polls host:port and opens a browser when ready.
107
+ For foreground (serve) use: the thread outlives the caller because serve blocks.
108
+ For background (pmf up / ui start): call wait_for_port() instead so we poll
109
+ synchronously before the process exits.
110
+ """
111
+ def _wait_and_open():
112
+ elapsed = 0.0
113
+ while elapsed < timeout:
114
+ try:
115
+ with socket.create_connection((host, port), timeout=0.5):
116
+ webbrowser.open(url)
117
+ return
118
+ except OSError:
119
+ time.sleep(0.25)
120
+ elapsed += 0.25
121
+
122
+ t = threading.Thread(target=_wait_and_open, daemon=True)
123
+ t.start()
124
+ return t
125
+
126
+
127
+ def wait_for_port_and_open_browser(
128
+ url: str,
129
+ host: str = DEFAULT_HOST,
130
+ port: int = DEFAULT_PORT,
131
+ timeout: float = 8.0,
132
+ ) -> bool:
133
+ """
134
+ Block until host:port accepts connections (or timeout), then open browser.
135
+ Use this when the caller process will exit after starting a background service.
136
+ Returns True if port came up, False on timeout.
137
+ """
138
+ elapsed = 0.0
139
+ while elapsed < timeout:
140
+ try:
141
+ with socket.create_connection((host, port), timeout=0.5):
142
+ webbrowser.open(url)
143
+ return True
144
+ except OSError:
145
+ time.sleep(0.25)
146
+ elapsed += 0.25
147
+ return False
148
+
149
+
20
150
  ensure_dirs()
21
151
 
22
152
 
@@ -24,8 +24,12 @@ Commands:
24
24
  cda pmf stop <service> Stop a service
25
25
  cda pmf restart <service> Restart a service
26
26
  cda pmf logs <service> Tail service logs
27
+ cda pmf up Start watcher + web UI (opens browser when ready)
28
+ cda pmf install Register as macOS LaunchAgent (auto-start on login)
29
+ cda pmf uninstall Remove the LaunchAgent registration
27
30
  cda check Run a full self-diagnostic. The system checks itself.
28
31
  cda init First-run setup — create ~/.cda/ and validate environment
32
+ cda setup Full onboarding: init → pmf install → sync → up (browser opens)
29
33
  cda serve Start the local web UI on port 10001
30
34
  cda sync Full re-ingest from disk (rebuilds entire DB)
31
35
  cda reconstruct Re-run reconstruction and FTS rebuild only
@@ -62,7 +66,11 @@ import textwrap
62
66
  import datetime
63
67
  from pathlib import Path
64
68
  from cda.pipeline.reconstruct import decompress_vfs
65
- from cda.kernel.pmf_kernel import PMFKernel, PMFKernelError
69
+ from cda.kernel.pmf_kernel import (
70
+ PMFKernel, PMFKernelError,
71
+ install_launchd, uninstall_launchd, plist_path,
72
+ open_browser_when_ready, wait_for_port_and_open_browser,
73
+ )
66
74
  from cda.kernel.paths import (
67
75
  DB_PATH, PID_FILE, UI_PID_FILE, UI_LOG_FILE,
68
76
  QUEUE_DIR, POLICY_FILE, ensure_dirs,
@@ -73,6 +81,14 @@ import click
73
81
  # Ensure runtime dirs exist on every CLI invocation
74
82
  ensure_dirs()
75
83
 
84
+
85
+ def _pmf_warn_if_not_installed():
86
+ """Emit a one-time advisory if the LaunchAgent is not registered."""
87
+ if not plist_path().exists():
88
+ click.echo(yellow(" Note: LaunchAgent not installed — services won't auto-start on login."))
89
+ click.echo(yellow(" Run `cda setup` to register, or `cda pmf install` to add just the plist."))
90
+
91
+
76
92
  kernel = PMFKernel()
77
93
 
78
94
 
@@ -361,10 +377,11 @@ def status():
361
377
  @cli.command("serve")
362
378
  @click.option("--host", default="127.0.0.1", show_default=True, help="Local host to bind the web UI")
363
379
  @click.option("--port", default=10001, show_default=True, help="Local port for the web UI")
364
- def serve(host, port):
380
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't open browser automatically")
381
+ def serve(host, port, no_browser):
365
382
  """Start the local web UI for Code Data Ark in the foreground."""
366
- click.echo(yellow(f" Starting local web UI at http://{host}:{port}"))
367
- click.echo(yellow(" Use `cda ui start` to launch it as a background service."))
383
+ url = f"http://{host}:{port}"
384
+ click.echo(yellow(f" Starting local web UI at {url}"))
368
385
  try:
369
386
  import importlib
370
387
  import cda.ui.web as web
@@ -373,6 +390,8 @@ def serve(host, port):
373
390
  click.echo(red(" Failed to start web UI. Ensure the package is installed and importable."))
374
391
  click.echo(red(f" Details: {exc}"))
375
392
  return
393
+ if not no_browser:
394
+ open_browser_when_ready(url, host, port)
376
395
  web.start_server(host=host, port=port)
377
396
 
378
397
 
@@ -396,12 +415,18 @@ def _ui_is_running():
396
415
  @ui.command("start")
397
416
  @click.option("--host", default="127.0.0.1", show_default=True, help="Local host to bind the web UI")
398
417
  @click.option("--port", default=10001, show_default=True, help="Local port for the web UI")
399
- def ui_start(host, port):
400
- """Start the web UI as a background service."""
418
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't open browser automatically")
419
+ def ui_start(host, port, no_browser):
420
+ """Start the web UI as a background service (via PMF kernel)."""
421
+ _pmf_warn_if_not_installed()
401
422
  try:
402
423
  result = kernel.start_service("ui", options={"host": host, "port": port})
403
- click.echo(green(f" Web UI started in background at http://{host}:{port} pid={result['pid']}"))
424
+ url = f"http://{host}:{port}"
425
+ click.echo(green(f" Web UI started in background at {url} pid={result['pid']}"))
404
426
  click.echo(yellow(f" Logs: {UI_LOG_FILE}"))
427
+ if not no_browser:
428
+ click.echo(dim(" Opening browser when server is ready..."))
429
+ wait_for_port_and_open_browser(url, host, port)
405
430
  except PMFKernelError as exc:
406
431
  click.echo(red(f" Failed to start UI: {exc}"))
407
432
 
@@ -485,12 +510,17 @@ def pmf_status(service_id):
485
510
  @click.argument("service_id")
486
511
  @click.option("--host", default="127.0.0.1", help="Host override for UI service")
487
512
  @click.option("--port", default=10001, help="Port override for UI service")
488
- def pmf_start(service_id, host, port):
513
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't open browser (UI service only)")
514
+ def pmf_start(service_id, host, port, no_browser):
489
515
  """Start a PMF-managed Ark service."""
490
516
  options = {"host": host, "port": port} if service_id == "ui" else None
491
517
  try:
492
518
  result = kernel.start_service(service_id, options=options)
493
519
  click.echo(green(f" Started {result['label']} pid={result['pid']}"))
520
+ if service_id == "ui" and not no_browser:
521
+ url = f"http://{host}:{port}"
522
+ click.echo(dim(" Opening browser when server is ready..."))
523
+ wait_for_port_and_open_browser(url, host, port)
494
524
  except PMFKernelError as exc:
495
525
  click.echo(red(f" {exc}"))
496
526
 
@@ -529,6 +559,69 @@ def pmf_logs(service_id, tail):
529
559
  click.echo(red(f" {exc}"))
530
560
 
531
561
 
562
+ @pmf.command("up")
563
+ @click.option("--host", default="127.0.0.1", show_default=True, help="Host for web UI")
564
+ @click.option("--port", default=10001, show_default=True, help="Port for web UI")
565
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False, help="Don't open browser when UI is ready")
566
+ def pmf_up(host, port, no_browser):
567
+ """Start all CDA services (watcher + web UI). Called automatically by launchd on login."""
568
+ url = f"http://{host}:{port}"
569
+
570
+ click.echo(bold(" Code Data Ark — starting services"))
571
+ click.echo(hr())
572
+
573
+ try:
574
+ result = kernel.start_service("watcher")
575
+ click.echo(green(f" Watcher started pid={result['pid']}"))
576
+ except PMFKernelError as exc:
577
+ click.echo(yellow(f" Watcher {exc}"))
578
+
579
+ try:
580
+ result = kernel.start_service("ui", options={"host": host, "port": port})
581
+ click.echo(green(f" Web UI started pid={result['pid']} → {url}"))
582
+ if not no_browser:
583
+ click.echo(dim(" Opening browser when server is ready..."))
584
+ wait_for_port_and_open_browser(url, host, port)
585
+ except PMFKernelError as exc:
586
+ click.echo(yellow(f" Web UI {exc}"))
587
+
588
+ click.echo()
589
+
590
+
591
+ @pmf.command("install")
592
+ def pmf_install():
593
+ """Install CDA as a macOS launchd LaunchAgent (auto-start on login)."""
594
+ from cda.kernel.paths import CDA_HOME as _cda_home
595
+ click.echo()
596
+ click.echo(bold(" Installing CDA LaunchAgent"))
597
+ click.echo(hr())
598
+ try:
599
+ target = install_launchd(_cda_home)
600
+ click.echo(green(f" Plist: {target}"))
601
+ click.echo(green(" Label: com.gocosmix.cda"))
602
+ click.echo(green(" Loaded: yes — CDA will start automatically on next login"))
603
+ click.echo()
604
+ click.echo(dim(" To start services now without logging out:"))
605
+ click.echo(dim(" cda pmf up"))
606
+ click.echo()
607
+ except PMFKernelError as exc:
608
+ click.echo(red(f" {exc}"))
609
+ click.echo(yellow(" Make sure `cda` is on PATH: export PATH=\"$HOME/Library/Python/3.9/bin:$PATH\""))
610
+ click.echo()
611
+
612
+
613
+ @pmf.command("uninstall")
614
+ def pmf_uninstall():
615
+ """Remove the CDA launchd LaunchAgent."""
616
+ target = plist_path()
617
+ if not target.exists():
618
+ click.echo(yellow(" No LaunchAgent plist found — nothing to uninstall."))
619
+ return
620
+ uninstall_launchd()
621
+ click.echo(green(f" Removed: {target}"))
622
+ click.echo(green(" CDA will no longer start automatically on login."))
623
+
624
+
532
625
  @cli.group()
533
626
  def embed():
534
627
  """Build and inspect semantic intelligence."""
@@ -705,10 +798,11 @@ def watch():
705
798
 
706
799
  @watch.command("start")
707
800
  def watch_start():
708
- """Start the live sync watcher daemon."""
801
+ """Start the live sync watcher daemon (via PMF kernel)."""
802
+ _pmf_warn_if_not_installed()
709
803
  try:
710
804
  result = kernel.start_service("watcher")
711
- click.echo(green(f" Watcher started pid={result['pid']}"))
805
+ click.echo(green(f" Watcher started pid={result['pid']} (via PMF)"))
712
806
  except PMFKernelError as exc:
713
807
  click.echo(red(f" {exc}"))
714
808
 
@@ -2563,6 +2657,194 @@ def check(as_json, fail_fast):
2563
2657
  sys.exit(0 if passed_all else 1)
2564
2658
 
2565
2659
 
2660
+ # ─────────────────────────────────────────────
2661
+ # SETUP
2662
+ # ─────────────────────────────────────────────
2663
+
2664
+ @cli.command("setup")
2665
+ @click.option("--skip-sync", "skip_sync", is_flag=True, default=False,
2666
+ help="Skip initial data ingest (run `cda sync` manually later)")
2667
+ @click.option("--no-browser", "no_browser", is_flag=True, default=False,
2668
+ help="Don't open browser when the web UI starts")
2669
+ def setup(skip_sync, no_browser):
2670
+ """
2671
+ Full onboarding in four steps: init → pmf install → sync → up.
2672
+
2673
+ \b
2674
+ Run this once after `pip install code-data-ark`:
2675
+
2676
+ cda setup
2677
+
2678
+ \b
2679
+ What each step does:
2680
+ 1. Init — create ~/.cda/ directory tree, validate VS Code data path
2681
+ 2. Install — register a macOS LaunchAgent so CDA starts on every login
2682
+ 3. Sync — ingest all VS Code + Copilot session data into cda.db
2683
+ 4. Up — start the watcher daemon and web UI via PMF, open browser
2684
+
2685
+ All processes are managed by the PMF kernel. The LaunchAgent calls
2686
+ `cda pmf up` on every login — no manual interaction needed after setup.
2687
+ """
2688
+ from cda.kernel.paths import (
2689
+ CDA_HOME, DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR,
2690
+ PMF_DIR, PMF_LOG_DIR, CONFIG_DIR, POLICY_FILE,
2691
+ )
2692
+ import os as _os
2693
+
2694
+ W = 52
2695
+ BAR = "═" * W
2696
+ bar = "─" * W
2697
+ host, port = "127.0.0.1", 10001
2698
+ url = f"http://{host}:{port}"
2699
+
2700
+ click.echo()
2701
+ click.echo(bold(BAR))
2702
+ click.echo(bold(" Code Data Ark — setup"))
2703
+ click.echo(bold(BAR))
2704
+ click.echo()
2705
+ click.echo(dim(" Four steps to a fully operational CDA installation:"))
2706
+ click.echo(dim(f" 1. Init — create {CDA_HOME}"))
2707
+ click.echo(dim(" 2. Install — register macOS LaunchAgent (auto-start on login)"))
2708
+ click.echo(dim(" 3. Sync — first-run data ingest") + (dim(" [skipped]") if skip_sync else ""))
2709
+ click.echo(dim(" 4. Up — start watcher + web UI via PMF, open browser"))
2710
+ click.echo()
2711
+
2712
+ # ── Step 1: Init ─────────────────────────────────────────────
2713
+ click.echo(bold(bar))
2714
+ click.echo(bold(" Step 1/4 — Init"))
2715
+ click.echo(bold(bar))
2716
+ click.echo()
2717
+
2718
+ dirs = [DATA_DIR, RUN_DIR, LOG_DIR, QUEUE_DIR, PMF_DIR, PMF_LOG_DIR, CONFIG_DIR]
2719
+ for d in dirs:
2720
+ d.mkdir(parents=True, exist_ok=True)
2721
+ click.echo(f" {green('✓')} {d}")
2722
+
2723
+ if not POLICY_FILE.exists():
2724
+ POLICY_FILE.write_text("# CDA access policy\n# ALLOW <pattern>\n# DENY <pattern>\n")
2725
+
2726
+ vscode_data = Path(_os.environ.get(
2727
+ "VSCODE_DATA_DIR",
2728
+ Path.home() / "Library/Application Support/Code/User",
2729
+ ))
2730
+ if vscode_data.exists():
2731
+ click.echo(f" {green('✓')} VS Code data dir found")
2732
+ else:
2733
+ click.echo(f" {yellow('⚠')} VS Code data dir not found: {vscode_data}")
2734
+ click.echo(yellow(" Set VSCODE_DATA_DIR if your data is elsewhere."))
2735
+
2736
+ click.echo()
2737
+ click.echo(f" {green('✓')} CDA_HOME: {CDA_HOME}")
2738
+ click.echo()
2739
+
2740
+ # ── Step 2: PMF install ──────────────────────────────────────
2741
+ click.echo(bold(bar))
2742
+ click.echo(bold(" Step 2/4 — PMF install"))
2743
+ click.echo(bold(bar))
2744
+ click.echo()
2745
+ click.echo(dim(" The LaunchAgent registers CDA with macOS launchd. On every login,"))
2746
+ click.echo(dim(" launchd calls `cda pmf up` which starts the watcher daemon and"))
2747
+ click.echo(dim(" web UI via the PMF kernel — no terminal required."))
2748
+ click.echo()
2749
+
2750
+ pmf_ok = False
2751
+ try:
2752
+ target = install_launchd(CDA_HOME)
2753
+ click.echo(f" {green('✓')} LaunchAgent: {target}")
2754
+ click.echo(f" {green('✓')} Loaded — CDA will start automatically on every login")
2755
+ pmf_ok = True
2756
+ except PMFKernelError as exc:
2757
+ click.echo(f" {yellow('⚠')} LaunchAgent registration failed: {exc}")
2758
+ click.echo(yellow(" Ensure `cda` is on PATH, then run `cda pmf install` to retry."))
2759
+ click.echo()
2760
+
2761
+ # ── Step 3: Sync ─────────────────────────────────────────────
2762
+ click.echo(bold(bar))
2763
+ click.echo(bold(" Step 3/4 — Sync"))
2764
+ click.echo(bold(bar))
2765
+ click.echo()
2766
+
2767
+ if skip_sync:
2768
+ click.echo(f" {yellow('↳')} Skipped (--skip-sync). Run `cda sync` when ready.")
2769
+ click.echo()
2770
+ else:
2771
+ click.echo(dim(" Scanning VS Code workspaceStorage and building cda.db."))
2772
+ click.echo(dim(" First run may take several minutes depending on session history."))
2773
+ click.echo()
2774
+
2775
+ sync_failed = False
2776
+ stages = [
2777
+ ("cda.pipeline.ingest", "Ingest — reading VS Code storage"),
2778
+ ("cda.pipeline.reconstruct", "Reconstruct — building exchanges + FTS"),
2779
+ ("cda.pipeline.extract", "Extract — behavioral signals + tokens"),
2780
+ ("cda.pipeline.embed", "Embed — semantic intelligence layer"),
2781
+ ]
2782
+ for module, label in stages:
2783
+ click.echo(yellow(f" → {label}"))
2784
+ result = subprocess.run([sys.executable, "-m", module], capture_output=False)
2785
+ if result.returncode != 0:
2786
+ click.echo(red(" ✗ Failed — sync incomplete"))
2787
+ sync_failed = True
2788
+ break
2789
+ click.echo(green(" ✓ Done"))
2790
+ click.echo()
2791
+
2792
+ if not sync_failed:
2793
+ click.echo(green(" ✓ Sync complete"))
2794
+ click.echo()
2795
+
2796
+ # ── Step 4: Up ───────────────────────────────────────────────
2797
+ click.echo(bold(bar))
2798
+ click.echo(bold(" Step 4/4 — Up"))
2799
+ click.echo(bold(bar))
2800
+ click.echo()
2801
+ click.echo(dim(" Starting all services through the PMF kernel."))
2802
+ click.echo(dim(" Each process is tracked, logged, and restartable via `cda pmf`."))
2803
+ click.echo()
2804
+
2805
+ for svc_id, opts, label in [
2806
+ ("watcher", None, "Watcher daemon"),
2807
+ ("ui", {"host": host, "port": port}, f"Web UI → {url}"),
2808
+ ]:
2809
+ try:
2810
+ result = kernel.start_service(svc_id, options=opts)
2811
+ click.echo(f" {green('✓')} {label} pid={result['pid']}")
2812
+ except PMFKernelError as exc:
2813
+ msg = str(exc)
2814
+ if "already running" in msg.lower():
2815
+ click.echo(f" {green('✓')} {label} (already running)")
2816
+ else:
2817
+ click.echo(f" {yellow('⚠')} {label} {msg}")
2818
+
2819
+ click.echo()
2820
+
2821
+ if not no_browser:
2822
+ click.echo(dim(" Waiting for web UI..."))
2823
+ opened = wait_for_port_and_open_browser(url, host, port)
2824
+ if opened:
2825
+ click.echo(f" {green('✓')} Browser opened: {url}")
2826
+ else:
2827
+ click.echo(f" {yellow('⚠')} Server not responding yet — visit {url} manually")
2828
+ click.echo()
2829
+
2830
+ # ── Done ─────────────────────────────────────────────────────
2831
+ click.echo(bold(BAR))
2832
+ click.echo(bold(" Setup complete."))
2833
+ click.echo(bold(BAR))
2834
+ click.echo()
2835
+ if pmf_ok:
2836
+ click.echo(dim(" CDA will start automatically on every login via launchd."))
2837
+ click.echo(dim(" The watcher daemon stays in sync with your VS Code sessions."))
2838
+ click.echo(dim(f" Visit {url} any time to explore your data."))
2839
+ click.echo()
2840
+ click.echo(dim(" Useful commands:"))
2841
+ click.echo(dim(" cda check — full system health diagnostic"))
2842
+ click.echo(dim(" cda sync — re-ingest after significant new session activity"))
2843
+ click.echo(dim(" cda pmf services — view running services and their status"))
2844
+ click.echo(dim(" cda pmf uninstall — remove auto-start LaunchAgent registration"))
2845
+ click.echo()
2846
+
2847
+
2566
2848
  # ─────────────────────────────────────────────
2567
2849
  # INIT
2568
2850
  # ─────────────────────────────────────────────
@@ -2606,9 +2888,10 @@ def init():
2606
2888
  click.echo(bold(" CDA_HOME: ") + str(CDA_HOME))
2607
2889
  click.echo()
2608
2890
  click.echo(dim(" Next steps:"))
2891
+ click.echo(dim(" cda pmf install — register as a macOS LaunchAgent (auto-start on login)"))
2609
2892
  click.echo(dim(" cda sync — ingest all VS Code session data"))
2610
- click.echo(dim(" cda watch start — start the live watcher daemon"))
2611
- click.echo(dim(" cda serve — open the web dashboard on :10001"))
2893
+ click.echo(dim(" cda pmf up — start watcher + web UI now (opens browser)"))
2894
+ click.echo(dim(" cda serve — run web UI in foreground (opens browser)"))
2612
2895
  click.echo()
2613
2896
 
2614
2897
 
@@ -5,6 +5,26 @@ 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.5] - 2026-05-11
9
+
10
+ ### Added
11
+ - **`cda setup`** — master onboarding command: init → pmf install → sync → up. Replaces the four-step manual process with a single command. Idempotent, safe to re-run. Accepts `--skip-sync` and `--no-browser`.
12
+ - **PMF-first architecture**: all background processes (watcher, web UI) start exclusively through the PMF kernel. `cda watch start` and `cda ui start` now display an advisory if the LaunchAgent is not installed.
13
+ - `_pmf_warn_if_not_installed()` helper — emitted before any background service start when the LaunchAgent plist is absent.
14
+
15
+ ### Changed
16
+ - README quickstart simplified to two commands: `pip install code-data-ark` + `cda setup`
17
+ - Added PMF architecture diagram and process management reference to README
18
+
19
+ ## [2.0.4] - 2026-05-11
20
+
21
+ ### Added
22
+ - **`cda pmf install`** — generates and loads a macOS `~/Library/LaunchAgents/com.gocosmix.cda.plist`; CDA starts automatically on login
23
+ - **`cda pmf uninstall`** — unloads and removes the LaunchAgent plist
24
+ - **`cda pmf up`** — starts watcher + web UI in one command; opens browser when the server is ready; called by launchd on login
25
+ - **Browser auto-open**: `cda serve`, `cda ui start`, and `cda pmf start ui` now open a browser tab when the server is ready (`--no-browser` to disable)
26
+ - `cda.kernel.pmf_kernel`: `install_launchd()`, `uninstall_launchd()`, `generate_plist()`, `plist_path()`, `open_browser_when_ready()`, `wait_for_port_and_open_browser()`
27
+
8
28
  ## [2.0.3] - 2026-05-11
9
29
 
10
30
  ### Fixed
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-data-ark"
7
- version = "2.0.3"
7
+ version = "2.0.5"
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"
@@ -78,48 +78,67 @@ make install-dev
78
78
 
79
79
  ## ⚡ Quick Start
80
80
 
81
- 1. **Install**
82
-
83
81
  ```bash
84
82
  pip install code-data-ark
83
+ cda setup
85
84
  ```
86
85
 
87
- 2. **Initialize — create `~/.cda/` and validate your VS Code data path**
86
+ That's it. `cda setup` runs four steps in sequence:
88
87
 
89
- ```bash
90
- cda init
91
- ```
88
+ | Step | What it does |
89
+ |------|-------------|
90
+ | **1. Init** | Creates `~/.cda/` directory tree, validates your VS Code data path |
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 `~/.cda/data/cda.db` |
93
+ | **4. Up** | Starts the watcher daemon and web UI via the PMF kernel, opens browser |
94
+
95
+ After setup, everything is managed by the **PMF kernel**. On every login, `launchd` calls `cda pmf up` which starts the watcher and web UI. No terminal interaction required.
92
96
 
93
- 3. **Ingest all VS Code session data**
97
+ ### Options
94
98
 
95
99
  ```bash
96
- cda sync
100
+ cda setup --skip-sync # Skip initial ingest (run `cda sync` manually later)
101
+ cda setup --no-browser # Don't open browser when the UI starts
97
102
  ```
98
103
 
99
- 4. **Start the live watcher daemon**
104
+ ### After setup
100
105
 
101
106
  ```bash
102
- cda watch start
107
+ cda check # Full system health diagnostic
108
+ cda sync # Re-ingest after significant new session activity
109
+ cda pmf services # View all running services and their status
110
+ cda pmf uninstall # Remove the auto-start LaunchAgent registration
103
111
  ```
104
112
 
105
- 5. **Open the web dashboard**
113
+ ## 🔧 Process Management (PMF)
106
114
 
107
- ```bash
108
- cda serve # → http://127.0.0.1:10001
115
+ All background processes run through the embedded PMF kernel. The LaunchAgent is the entry point — nothing starts directly on the host outside of PMF.
116
+
117
+ ```
118
+ launchd (login)
119
+ └─ cda pmf up
120
+ ├─ PMF kernel → watcher daemon (cda.pipeline.watcher)
121
+ └─ PMF kernel → web UI server (cda.ui.web)
109
122
  ```
110
123
 
111
- 6. **Build semantic intelligence** (optional, requires `sentence-transformers`)
124
+ ### PMF commands
112
125
 
113
126
  ```bash
114
- cda embed build
127
+ cda pmf services # List all services with status and PID
128
+ cda pmf start <service> # Start a service (watcher, ui, sync, reconstruct, embed-build)
129
+ cda pmf stop <service> # Stop a service
130
+ cda pmf restart <service> # Restart a service
131
+ cda pmf logs <service> # Tail the service log
132
+ cda pmf up # Start watcher + UI (opens browser) — same as launchd trigger
133
+ cda pmf install # Register LaunchAgent (done automatically by cda setup)
134
+ cda pmf uninstall # Remove LaunchAgent
115
135
  ```
116
136
 
117
137
  ## 🌐 Web UI
118
138
 
119
- - **Background service**: `cda ui start`
120
- - **Stop service**: `cda ui stop`
121
- - **Service status**: `cda ui status`
122
- - **Foreground mode**: `cda serve`
139
+ - **Background service** (default after setup): managed by PMF, starts on login
140
+ - **Foreground mode**: `cda serve` — runs in the terminal, opens browser, Ctrl+C to stop
141
+ - **Access**: `http://127.0.0.1:10001`
123
142
 
124
143
  The web UI includes:
125
144
 
@@ -0,0 +1 @@
1
+ 2.0.5
@@ -1 +0,0 @@
1
- 2.0.3
File without changes
File without changes
File without changes
File without changes