openrunner-sdk 2.20.0__tar.gz → 2.20.2__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.
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/PKG-INFO +1 -1
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/__init__.py +1 -1
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/install_commands.py +167 -54
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/pyproject.toml +1 -1
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/.gitignore +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/=6.0 +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/=8.1 +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/README.md +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/CLAUDE.md +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/api_client.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/artifact.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/buffer.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/cache.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/cli.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/config.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/cost.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/dataset.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/environment.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/evaluation.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/feedback.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/git_info.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/guardrails.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/__init__.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/accelerate.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/anthropic_tracer.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/catboost.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/diffusers.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/fastai.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/forced_alignment.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/gladia.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/gymnasium.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/huggingface.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/hydra.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/ignite.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/jax.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/keras.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/langchain.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/lightgbm.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/lightning.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/llamaindex.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/openai_finetune.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/openai_tracer.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/optuna.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/pytorch.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/sb3.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/sklearn.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/tensorflow.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/trl.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/tts.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/ultralytics.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/voice_agent.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/whisper.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/integration/xgboost.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/launch.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/mcp_server.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/media.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/migrate.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/model.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/offline.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/pii.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/plot.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/prompt.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/query_api.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/redact.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/run.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/scorers.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/sender.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/session.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/settings.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/summary.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/sweep.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/system_metrics.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/tensorboard.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/trace.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/transcript_formatter.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/wal.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/wandb_compat/__init__.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/wandb_compat/_shim.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/openrunner/wer.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/__init__.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/conftest.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_alert.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_aliases.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_api_client.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_artifact.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_buffer.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_cache.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_class_scorers.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_cli.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_config.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_evaluation.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_finish.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_git_info.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_init.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_fastai.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_huggingface.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_keras.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_langchain.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_lightning.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_pytorch.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_sklearn.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_integration_xgboost.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_launch.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_log.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_log_code.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_media.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_migrate.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_offline.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_offline_sync.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_pii.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_plot.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_query_api.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_resume.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_sdk_features.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_sender.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_summary.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_sweep.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_system_metrics.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_trace.py +0 -0
- {openrunner_sdk-2.20.0 → openrunner_sdk-2.20.2}/tests/test_wandb_compat.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrunner-sdk
|
|
3
|
-
Version: 2.20.
|
|
3
|
+
Version: 2.20.2
|
|
4
4
|
Summary: OpenRunner SDK - W&B-compatible ML experiment tracking client
|
|
5
5
|
Project-URL: Homepage, https://github.com/jqueguiner/openrunner
|
|
6
6
|
Project-URL: Repository, https://github.com/jqueguiner/openrunner
|
|
@@ -120,7 +120,7 @@ launch.from_run = _launch_from_run # type: ignore[attr-defined]
|
|
|
120
120
|
# openrunner.trace.patch_openai() syntax
|
|
121
121
|
trace.patch_openai = _patch_openai # type: ignore[attr-defined]
|
|
122
122
|
|
|
123
|
-
__version__ = "2.20.
|
|
123
|
+
__version__ = "2.20.2"
|
|
124
124
|
|
|
125
125
|
logger = logging.getLogger("openrunner")
|
|
126
126
|
|
|
@@ -791,20 +791,19 @@ allowed-tools:
|
|
|
791
791
|
- AskUserQuestion
|
|
792
792
|
---
|
|
793
793
|
<objective>
|
|
794
|
-
Configure which
|
|
795
|
-
|
|
794
|
+
Configure which project, run, and metrics appear in the Claude Code statusline footer.
|
|
795
|
+
Interactive setup: project → run → metrics.
|
|
796
796
|
</objective>
|
|
797
797
|
|
|
798
798
|
<process>
|
|
799
|
-
1.
|
|
800
|
-
2.
|
|
801
|
-
3.
|
|
802
|
-
4.
|
|
803
|
-
5.
|
|
804
|
-
6. Confirm and show preview.
|
|
799
|
+
1. List projects. Ask user which one (propose current if configured).
|
|
800
|
+
2. List recent runs in that project. Ask user which run (propose latest).
|
|
801
|
+
3. List available metrics for that run. Ask user which for sparkline + which extra.
|
|
802
|
+
4. Save to ~/.openrunner/statusline.json.
|
|
803
|
+
5. Confirm and show preview.
|
|
805
804
|
</process>
|
|
806
805
|
|
|
807
|
-
### Step 1:
|
|
806
|
+
### Step 1: List projects and runs
|
|
808
807
|
|
|
809
808
|
```bash
|
|
810
809
|
python3 -c "
|
|
@@ -820,70 +819,130 @@ if not config.get('api_key'):
|
|
|
820
819
|
print('NOT_CONFIGURED'); sys.exit(1)
|
|
821
820
|
|
|
822
821
|
client = APIClient(base_url=config['base_url'], api_key=config['api_key'])
|
|
823
|
-
|
|
822
|
+
|
|
823
|
+
# List projects
|
|
824
|
+
projects = client.list_projects()
|
|
825
|
+
print('PROJECTS:')
|
|
826
|
+
current = config.get('project', '')
|
|
827
|
+
for i, p in enumerate(projects, 1):
|
|
828
|
+
full = '%s/%s' % (p.get('org_name',''), p.get('name',''))
|
|
829
|
+
marker = ' (current)' if full.lower() == current.lower() else ''
|
|
830
|
+
print(' [%d] %s%s' % (i, full, marker))
|
|
831
|
+
|
|
832
|
+
# Show current statusline config
|
|
833
|
+
cfg_path = Path.home() / '.openrunner' / 'statusline.json'
|
|
834
|
+
if cfg_path.exists():
|
|
835
|
+
cfg = json.loads(cfg_path.read_text())
|
|
836
|
+
print('\\nCURRENT_CONFIG: project=%s run=%s sparkline=%s metrics=%s' % (
|
|
837
|
+
cfg.get('project','auto'), cfg.get('run','latest'),
|
|
838
|
+
cfg.get('sparkline','auto'), cfg.get('metrics',[])))
|
|
839
|
+
else:
|
|
840
|
+
print('\\nCURRENT_CONFIG: not configured')
|
|
841
|
+
|
|
842
|
+
client.close()
|
|
843
|
+
"
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### Step 2: Ask project
|
|
847
|
+
|
|
848
|
+
Ask the user: "Which project? (number, or Enter for current)"
|
|
849
|
+
Default = current project.
|
|
850
|
+
|
|
851
|
+
### Step 3: List runs for chosen project
|
|
852
|
+
|
|
853
|
+
```bash
|
|
854
|
+
python3 -c "
|
|
855
|
+
import sys, os, json
|
|
856
|
+
from pathlib import Path
|
|
857
|
+
for p in ([os.path.join(d, 'site-packages') for d in __import__('glob').glob(os.path.expanduser('~/.local/lib/python3.*/')) + __import__('glob').glob(os.path.expanduser('~/Library/Python/3.*/lib/python/'))]):
|
|
858
|
+
if os.path.isdir(p): sys.path.insert(0, p)
|
|
859
|
+
from openrunner.session import get_session_config
|
|
860
|
+
from openrunner.api_client import APIClient
|
|
861
|
+
|
|
862
|
+
config = get_session_config()
|
|
863
|
+
client = APIClient(base_url=config['base_url'], api_key=config['api_key'])
|
|
864
|
+
|
|
865
|
+
# PROJECT_NAME will be replaced by the chosen project
|
|
866
|
+
pname = 'PROJECT_NAME'
|
|
824
867
|
pid = None
|
|
825
868
|
for p in client.list_projects():
|
|
826
|
-
full =
|
|
869
|
+
full = '%s/%s' % (p.get('org_name',''), p.get('name',''))
|
|
827
870
|
if full.lower() == pname.lower() or (p.get('name') or '').lower() == pname.lower():
|
|
828
871
|
pid = p.get('id'); break
|
|
829
872
|
|
|
830
873
|
if not pid:
|
|
831
874
|
print('PROJECT_NOT_FOUND'); client.close(); sys.exit(1)
|
|
832
875
|
|
|
833
|
-
|
|
834
|
-
resp = client._request('get', f'/projects/{{pid}}/runs', params={{'limit': 1, 'order': 'desc'}})
|
|
876
|
+
resp = client._request('get', '/projects/%s/runs' % pid, params={{'limit': 10, 'order': 'desc'}})
|
|
835
877
|
runs_data = resp.json()
|
|
836
878
|
runs = runs_data.get('runs', []) if isinstance(runs_data, dict) else runs_data
|
|
879
|
+
|
|
880
|
+
print('RUNS in %s:' % pname)
|
|
881
|
+
for i, r in enumerate(runs, 1):
|
|
882
|
+
name = r.get('display_name') or r.get('id','?')
|
|
883
|
+
state = r.get('state', '?')
|
|
884
|
+
icon = ' [running]' if state == 'running' else ''
|
|
885
|
+
print(' [%d] %s (%s)%s' % (i, name, r.get('id','?')[:8], icon))
|
|
886
|
+
|
|
837
887
|
if not runs:
|
|
838
|
-
print('
|
|
888
|
+
print(' (no runs)')
|
|
839
889
|
|
|
840
|
-
|
|
841
|
-
|
|
890
|
+
print('\\n [0] latest (auto-follow newest run)')
|
|
891
|
+
client.close()
|
|
892
|
+
"
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Step 4: Ask run
|
|
896
|
+
|
|
897
|
+
Ask: "Which run to track? (number, 0=latest auto-follow)" — propose 0 (latest) as default.
|
|
898
|
+
|
|
899
|
+
### Step 5: List metrics for chosen run
|
|
900
|
+
|
|
901
|
+
```bash
|
|
902
|
+
python3 -c "
|
|
903
|
+
import sys, os, json
|
|
904
|
+
from pathlib import Path
|
|
905
|
+
for p in ([os.path.join(d, 'site-packages') for d in __import__('glob').glob(os.path.expanduser('~/.local/lib/python3.*/')) + __import__('glob').glob(os.path.expanduser('~/Library/Python/3.*/lib/python/'))]):
|
|
906
|
+
if os.path.isdir(p): sys.path.insert(0, p)
|
|
907
|
+
from openrunner.session import get_session_config
|
|
908
|
+
from openrunner.api_client import APIClient
|
|
909
|
+
|
|
910
|
+
config = get_session_config()
|
|
911
|
+
client = APIClient(base_url=config['base_url'], api_key=config['api_key'])
|
|
842
912
|
|
|
843
|
-
#
|
|
844
|
-
|
|
913
|
+
# RUN_ID will be replaced
|
|
914
|
+
rid = 'RUN_ID'
|
|
915
|
+
resp = client._request('get', '/runs/%s/metrics' % rid, params={{'max_points': 10000}})
|
|
845
916
|
data = resp.json()
|
|
846
917
|
metrics = data.get('metrics', data) if isinstance(data, dict) else {{}}
|
|
847
|
-
if 'detail' in metrics:
|
|
848
|
-
metrics = {{}}
|
|
918
|
+
if 'detail' in metrics: metrics = {{}}
|
|
849
919
|
|
|
850
920
|
keys = sorted(metrics.keys())
|
|
851
|
-
|
|
852
|
-
print('NO_METRICS'); client.close(); sys.exit(1)
|
|
853
|
-
|
|
854
|
-
print(f'METRICS_FROM_RUN={{run_name}}')
|
|
921
|
+
print('METRICS (%d available):' % len(keys))
|
|
855
922
|
for i, k in enumerate(keys, 1):
|
|
856
|
-
pts = metrics[
|
|
923
|
+
pts = metrics.get(k, [])
|
|
857
924
|
n = len(pts) if isinstance(pts, list) else 0
|
|
858
|
-
|
|
925
|
+
last_str = ''
|
|
859
926
|
if n > 0:
|
|
860
927
|
lp = pts[-1]
|
|
861
928
|
v = lp.get('value', lp) if isinstance(lp, dict) else lp
|
|
862
929
|
if isinstance(v, float):
|
|
863
|
-
|
|
930
|
+
last_str = ' (%.4f, %d pts)' % (v, n)
|
|
864
931
|
else:
|
|
865
|
-
|
|
866
|
-
print(
|
|
867
|
-
|
|
868
|
-
# Also show current config if exists
|
|
869
|
-
cfg_path = Path.home() / '.openrunner' / 'statusline.json'
|
|
870
|
-
if cfg_path.exists():
|
|
871
|
-
cfg = json.loads(cfg_path.read_text())
|
|
872
|
-
print('\\nCURRENT_CONFIG: sparkline=%s metrics=%s' % (cfg.get('sparkline','auto'), cfg.get('metrics',[])))
|
|
873
|
-
else:
|
|
874
|
-
print('\\nCURRENT_CONFIG: not configured (auto-detect)')
|
|
932
|
+
last_str = ' (%s, %d pts)' % (v, n)
|
|
933
|
+
print(' [%d] %s%s' % (i, k, last_str))
|
|
875
934
|
|
|
876
935
|
client.close()
|
|
877
936
|
"
|
|
878
937
|
```
|
|
879
938
|
|
|
880
|
-
### Step
|
|
939
|
+
### Step 6: Ask metrics
|
|
881
940
|
|
|
882
|
-
|
|
883
|
-
- "Which metric for the sparkline? (number
|
|
884
|
-
- "Which additional metrics
|
|
941
|
+
Ask:
|
|
942
|
+
- "Which metric for the sparkline? (number)" — primary, shown as mini chart
|
|
943
|
+
- "Which additional metrics? (comma-separated numbers, or 'auto')"
|
|
885
944
|
|
|
886
|
-
### Step
|
|
945
|
+
### Step 7: Save config
|
|
887
946
|
|
|
888
947
|
```bash
|
|
889
948
|
python3 -c "
|
|
@@ -891,17 +950,19 @@ import json
|
|
|
891
950
|
from pathlib import Path
|
|
892
951
|
cfg_path = Path.home() / '.openrunner' / 'statusline.json'
|
|
893
952
|
cfg_path.parent.mkdir(parents=True, exist_ok=True)
|
|
894
|
-
cfg = {{'sparkline': 'SPARKLINE_KEY', 'metrics': METRICS_LIST}}
|
|
953
|
+
cfg = {{'project': 'PROJECT_NAME', 'run': 'RUN_VALUE', 'sparkline': 'SPARKLINE_KEY', 'metrics': METRICS_LIST}}
|
|
895
954
|
cfg_path.write_text(json.dumps(cfg, indent=2))
|
|
896
|
-
print(
|
|
897
|
-
print(
|
|
898
|
-
print(
|
|
955
|
+
print('Saved: %s' % cfg_path)
|
|
956
|
+
print(' Project: %s' % cfg['project'])
|
|
957
|
+
print(' Run: %s' % cfg['run'])
|
|
958
|
+
print(' Sparkline: %s' % cfg['sparkline'])
|
|
959
|
+
print(' Metrics: %s' % cfg['metrics'])
|
|
899
960
|
print()
|
|
900
|
-
print('Footer will
|
|
961
|
+
print('Footer will update on next statusline refresh (30s).')
|
|
901
962
|
"
|
|
902
963
|
```
|
|
903
964
|
|
|
904
|
-
Replace
|
|
965
|
+
Replace PROJECT_NAME, RUN_VALUE (run_id or 'latest'), SPARKLINE_KEY, and METRICS_LIST with user's choices.
|
|
905
966
|
"""
|
|
906
967
|
|
|
907
968
|
MCP_CMD = """---
|
|
@@ -977,15 +1038,27 @@ except ImportError:
|
|
|
977
1038
|
cfg = get_session_config()
|
|
978
1039
|
if not cfg.get('api_key'): print("not configured"); sys.exit(0)
|
|
979
1040
|
client = APIClient(base_url=cfg['base_url'], api_key=cfg['api_key'])
|
|
980
|
-
|
|
1041
|
+
# Read statusline config for project/run override
|
|
1042
|
+
import json as _json
|
|
1043
|
+
sl_cfg = {}
|
|
1044
|
+
sl_path = os.path.expanduser('~/.openrunner/statusline.json')
|
|
1045
|
+
if os.path.isfile(sl_path):
|
|
1046
|
+
try: sl_cfg = _json.loads(open(sl_path).read())
|
|
1047
|
+
except: pass
|
|
1048
|
+
pname = sl_cfg.get('project') or cfg.get('project', '')
|
|
981
1049
|
pid = None
|
|
982
1050
|
for p in client.list_projects():
|
|
983
1051
|
full = f"{p.get('org_name','')}/{p.get('name','')}"
|
|
984
1052
|
if full.lower() == pname.lower() or (p.get('name') or '').lower() == pname.lower():
|
|
985
1053
|
pid = p.get('id'); break
|
|
986
1054
|
if not pid: print("?"); client.close(); sys.exit(0)
|
|
987
|
-
|
|
988
|
-
|
|
1055
|
+
# If specific run configured, only show that one
|
|
1056
|
+
configured_run = sl_cfg.get('run', 'latest')
|
|
1057
|
+
if configured_run and configured_run != 'latest':
|
|
1058
|
+
runs = [{'id': configured_run, 'state': 'unknown'}]
|
|
1059
|
+
else:
|
|
1060
|
+
runs = client._request('get', f'/projects/{pid}/runs', params={'limit': 3, 'order': 'desc'}).json()
|
|
1061
|
+
if isinstance(runs, dict): runs = runs.get('runs', [])
|
|
989
1062
|
parts = []
|
|
990
1063
|
for run in runs[:3]:
|
|
991
1064
|
rid = run.get('id', '?'); state = run.get('state', '?')
|
|
@@ -1133,9 +1206,49 @@ When the user asks about papers, research, or sessions:
|
|
|
1133
1206
|
statusline_script.chmod(0o755)
|
|
1134
1207
|
files.append(str(statusline_script))
|
|
1135
1208
|
|
|
1136
|
-
#
|
|
1137
|
-
#
|
|
1138
|
-
|
|
1209
|
+
# Auto-patch any JS statusline to read /tmp/claude-statusline-*.txt plugin files.
|
|
1210
|
+
# Generic: finds the statusline command from settings.json, patches if it's a node script.
|
|
1211
|
+
import json as _json
|
|
1212
|
+
settings_file = Path.home() / ".claude" / "settings.json"
|
|
1213
|
+
if settings_file.exists():
|
|
1214
|
+
try:
|
|
1215
|
+
settings = _json.loads(settings_file.read_text())
|
|
1216
|
+
sl_cmd = settings.get("statusLine", {}).get("command", "")
|
|
1217
|
+
# Extract JS file path from command like 'node "/path/to/script.js"'
|
|
1218
|
+
import shlex
|
|
1219
|
+
parts = shlex.split(sl_cmd) if sl_cmd else []
|
|
1220
|
+
js_file = None
|
|
1221
|
+
for p in parts:
|
|
1222
|
+
if p.endswith(".js") and Path(p).exists():
|
|
1223
|
+
js_file = Path(p)
|
|
1224
|
+
break
|
|
1225
|
+
if js_file:
|
|
1226
|
+
content = js_file.read_text()
|
|
1227
|
+
if "claude-statusline-" not in content and "// Output" in content:
|
|
1228
|
+
patch = """ // Plugin statusline segments: read /tmp/claude-statusline-*.txt
|
|
1229
|
+
let plugins = '';
|
|
1230
|
+
try {
|
|
1231
|
+
const tmpDir = os.tmpdir();
|
|
1232
|
+
const pfiles = fs.readdirSync(tmpDir)
|
|
1233
|
+
.filter(f => f.startsWith('claude-statusline-') && f.endsWith('.txt'));
|
|
1234
|
+
for (const f of pfiles) {
|
|
1235
|
+
try {
|
|
1236
|
+
const fp = path.join(tmpDir, f);
|
|
1237
|
+
const st = fs.statSync(fp);
|
|
1238
|
+
if ((Date.now() - st.mtimeMs) / 1000 > 120) continue;
|
|
1239
|
+
const c = fs.readFileSync(fp, 'utf8').trim();
|
|
1240
|
+
if (c && c.length < 200) plugins += ' \\u2502 ' + c;
|
|
1241
|
+
} catch (e) {}
|
|
1242
|
+
}
|
|
1243
|
+
} catch (e) {}
|
|
1244
|
+
|
|
1245
|
+
"""
|
|
1246
|
+
content = content.replace("// Output", patch + " // Output")
|
|
1247
|
+
content = content.replace("${ctx}`)", "${ctx}${plugins}`)")
|
|
1248
|
+
js_file.write_text(content)
|
|
1249
|
+
files.append(str(js_file))
|
|
1250
|
+
except Exception:
|
|
1251
|
+
pass
|
|
1139
1252
|
|
|
1140
1253
|
return files
|
|
1141
1254
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|