netra-zen 1.0.9__py3-none-any.whl → 1.0.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_interface/__init__.py +25 -25
- agent_interface/base_agent.py +350 -350
- {netra_zen-1.0.9.dist-info → netra_zen-1.0.11.dist-info}/METADATA +36 -15
- netra_zen-1.0.11.dist-info/RECORD +30 -0
- {netra_zen-1.0.9.dist-info → netra_zen-1.0.11.dist-info}/licenses/LICENSE.md +1 -1
- scripts/__init__.py +1 -1
- scripts/__main__.py +5 -5
- scripts/agent_cli.py +7179 -6948
- scripts/agent_logs.py +327 -327
- scripts/bump_version.py +137 -137
- scripts/demo_log_collection.py +146 -144
- scripts/embed_release_credentials.py +75 -75
- scripts/test_apex_telemetry_debug.py +221 -0
- scripts/verify_log_transmission.py +140 -140
- token_budget/budget_manager.py +199 -199
- token_budget/models.py +73 -73
- token_budget/visualization.py +21 -21
- token_transparency/__init__.py +19 -19
- token_transparency/claude_pricing_engine.py +326 -326
- zen/__init__.py +7 -7
- zen/__main__.py +11 -11
- zen/telemetry/__init__.py +14 -11
- zen/telemetry/apex_telemetry.py +259 -0
- zen/telemetry/embedded_credentials.py +59 -59
- zen/telemetry/manager.py +249 -249
- zen_orchestrator.py +3058 -3008
- netra_zen-1.0.9.dist-info/RECORD +0 -28
- {netra_zen-1.0.9.dist-info → netra_zen-1.0.11.dist-info}/WHEEL +0 -0
- {netra_zen-1.0.9.dist-info → netra_zen-1.0.11.dist-info}/entry_points.txt +0 -0
- {netra_zen-1.0.9.dist-info → netra_zen-1.0.11.dist-info}/top_level.txt +0 -0
scripts/bump_version.py
CHANGED
@@ -1,138 +1,138 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Version bump utility for Zen Orchestrator.
|
4
|
-
Updates version in all relevant files.
|
5
|
-
|
6
|
-
Usage:
|
7
|
-
python scripts/bump_version.py patch # 1.0.0 -> 1.0.1
|
8
|
-
python scripts/bump_version.py minor # 1.0.0 -> 1.1.0
|
9
|
-
python scripts/bump_version.py major # 1.0.0 -> 2.0.0
|
10
|
-
python scripts/bump_version.py 1.2.3 # Set specific version
|
11
|
-
"""
|
12
|
-
|
13
|
-
import re
|
14
|
-
import sys
|
15
|
-
from pathlib import Path
|
16
|
-
from typing import Tuple
|
17
|
-
|
18
|
-
|
19
|
-
def parse_version(version_str: str) -> Tuple[int, int, int]:
|
20
|
-
"""Parse version string to tuple of integers."""
|
21
|
-
match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', version_str)
|
22
|
-
if not match:
|
23
|
-
raise ValueError(f"Invalid version format: {version_str}")
|
24
|
-
return tuple(map(int, match.groups()))
|
25
|
-
|
26
|
-
|
27
|
-
def format_version(version_tuple: Tuple[int, int, int]) -> str:
|
28
|
-
"""Format version tuple to string."""
|
29
|
-
return '.'.join(map(str, version_tuple))
|
30
|
-
|
31
|
-
|
32
|
-
def get_current_version() -> str:
|
33
|
-
"""Get current version from __init__.py."""
|
34
|
-
init_file = Path(__file__).parent.parent / "__init__.py"
|
35
|
-
content = init_file.read_text()
|
36
|
-
match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
|
37
|
-
if not match:
|
38
|
-
raise ValueError("Could not find version in __init__.py")
|
39
|
-
return match.group(1)
|
40
|
-
|
41
|
-
|
42
|
-
def bump_version(current: str, bump_type: str) -> str:
|
43
|
-
"""Bump version based on type."""
|
44
|
-
if re.match(r'^\d+\.\d+\.\d+$', bump_type):
|
45
|
-
# Specific version provided
|
46
|
-
return bump_type
|
47
|
-
|
48
|
-
major, minor, patch = parse_version(current)
|
49
|
-
|
50
|
-
if bump_type == 'major':
|
51
|
-
return format_version((major + 1, 0, 0))
|
52
|
-
elif bump_type == 'minor':
|
53
|
-
return format_version((major, minor + 1, 0))
|
54
|
-
elif bump_type == 'patch':
|
55
|
-
return format_version((major, minor, patch + 1))
|
56
|
-
else:
|
57
|
-
raise ValueError(f"Invalid bump type: {bump_type}")
|
58
|
-
|
59
|
-
|
60
|
-
def update_file(file_path: Path, old_version: str, new_version: str, patterns: list):
|
61
|
-
"""Update version in a file using specified patterns."""
|
62
|
-
if not file_path.exists():
|
63
|
-
print(f" ⚠️ {file_path} does not exist, skipping...")
|
64
|
-
return
|
65
|
-
|
66
|
-
content = file_path.read_text()
|
67
|
-
original_content = content
|
68
|
-
|
69
|
-
for pattern in patterns:
|
70
|
-
old_pattern = pattern.format(version=old_version)
|
71
|
-
new_pattern = pattern.format(version=new_version)
|
72
|
-
content = content.replace(old_pattern, new_pattern)
|
73
|
-
|
74
|
-
if content != original_content:
|
75
|
-
file_path.write_text(content)
|
76
|
-
print(f" ✅ Updated {file_path}")
|
77
|
-
else:
|
78
|
-
print(f" ℹ️ No changes in {file_path}")
|
79
|
-
|
80
|
-
|
81
|
-
def main():
|
82
|
-
"""Main function."""
|
83
|
-
if len(sys.argv) != 2:
|
84
|
-
print(__doc__)
|
85
|
-
sys.exit(1)
|
86
|
-
|
87
|
-
bump_type = sys.argv[1]
|
88
|
-
|
89
|
-
# Get current version
|
90
|
-
try:
|
91
|
-
current = get_current_version()
|
92
|
-
print(f"Current version: {current}")
|
93
|
-
except Exception as e:
|
94
|
-
print(f"Error getting current version: {e}")
|
95
|
-
sys.exit(1)
|
96
|
-
|
97
|
-
# Calculate new version
|
98
|
-
try:
|
99
|
-
new = bump_version(current, bump_type)
|
100
|
-
print(f"New version: {new}")
|
101
|
-
except Exception as e:
|
102
|
-
print(f"Error calculating new version: {e}")
|
103
|
-
sys.exit(1)
|
104
|
-
|
105
|
-
# Update files
|
106
|
-
base_path = Path(__file__).parent.parent
|
107
|
-
|
108
|
-
files_to_update = [
|
109
|
-
(
|
110
|
-
base_path / "__init__.py",
|
111
|
-
['__version__ = "{version}"']
|
112
|
-
),
|
113
|
-
(
|
114
|
-
base_path / "setup.py",
|
115
|
-
['version="{version}"']
|
116
|
-
),
|
117
|
-
(
|
118
|
-
base_path / "pyproject.toml",
|
119
|
-
['version = "{version}"']
|
120
|
-
),
|
121
|
-
]
|
122
|
-
|
123
|
-
print("\nUpdating files:")
|
124
|
-
for file_path, patterns in files_to_update:
|
125
|
-
update_file(file_path, current, new, patterns)
|
126
|
-
|
127
|
-
print(f"\n✨ Version bumped from {current} to {new}")
|
128
|
-
print("\nNext steps:")
|
129
|
-
print(f" 1. Update CHANGELOG.md with changes for v{new}")
|
130
|
-
print(f" 2. Commit: git commit -am 'Bump version to {new}'")
|
131
|
-
print(f" 3. Tag: git tag -a v{new} -m 'Release version {new}'")
|
132
|
-
print(f" 4. Push: git push origin main --tags")
|
133
|
-
print(f" 5. Build: python -m build")
|
134
|
-
print(f" 6. Upload: python -m twine upload dist/*")
|
135
|
-
|
136
|
-
|
137
|
-
if __name__ == "__main__":
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Version bump utility for Zen Orchestrator.
|
4
|
+
Updates version in all relevant files.
|
5
|
+
|
6
|
+
Usage:
|
7
|
+
python scripts/bump_version.py patch # 1.0.0 -> 1.0.1
|
8
|
+
python scripts/bump_version.py minor # 1.0.0 -> 1.1.0
|
9
|
+
python scripts/bump_version.py major # 1.0.0 -> 2.0.0
|
10
|
+
python scripts/bump_version.py 1.2.3 # Set specific version
|
11
|
+
"""
|
12
|
+
|
13
|
+
import re
|
14
|
+
import sys
|
15
|
+
from pathlib import Path
|
16
|
+
from typing import Tuple
|
17
|
+
|
18
|
+
|
19
|
+
def parse_version(version_str: str) -> Tuple[int, int, int]:
|
20
|
+
"""Parse version string to tuple of integers."""
|
21
|
+
match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', version_str)
|
22
|
+
if not match:
|
23
|
+
raise ValueError(f"Invalid version format: {version_str}")
|
24
|
+
return tuple(map(int, match.groups()))
|
25
|
+
|
26
|
+
|
27
|
+
def format_version(version_tuple: Tuple[int, int, int]) -> str:
|
28
|
+
"""Format version tuple to string."""
|
29
|
+
return '.'.join(map(str, version_tuple))
|
30
|
+
|
31
|
+
|
32
|
+
def get_current_version() -> str:
|
33
|
+
"""Get current version from __init__.py."""
|
34
|
+
init_file = Path(__file__).parent.parent / "__init__.py"
|
35
|
+
content = init_file.read_text()
|
36
|
+
match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
|
37
|
+
if not match:
|
38
|
+
raise ValueError("Could not find version in __init__.py")
|
39
|
+
return match.group(1)
|
40
|
+
|
41
|
+
|
42
|
+
def bump_version(current: str, bump_type: str) -> str:
|
43
|
+
"""Bump version based on type."""
|
44
|
+
if re.match(r'^\d+\.\d+\.\d+$', bump_type):
|
45
|
+
# Specific version provided
|
46
|
+
return bump_type
|
47
|
+
|
48
|
+
major, minor, patch = parse_version(current)
|
49
|
+
|
50
|
+
if bump_type == 'major':
|
51
|
+
return format_version((major + 1, 0, 0))
|
52
|
+
elif bump_type == 'minor':
|
53
|
+
return format_version((major, minor + 1, 0))
|
54
|
+
elif bump_type == 'patch':
|
55
|
+
return format_version((major, minor, patch + 1))
|
56
|
+
else:
|
57
|
+
raise ValueError(f"Invalid bump type: {bump_type}")
|
58
|
+
|
59
|
+
|
60
|
+
def update_file(file_path: Path, old_version: str, new_version: str, patterns: list):
|
61
|
+
"""Update version in a file using specified patterns."""
|
62
|
+
if not file_path.exists():
|
63
|
+
print(f" ⚠️ {file_path} does not exist, skipping...")
|
64
|
+
return
|
65
|
+
|
66
|
+
content = file_path.read_text()
|
67
|
+
original_content = content
|
68
|
+
|
69
|
+
for pattern in patterns:
|
70
|
+
old_pattern = pattern.format(version=old_version)
|
71
|
+
new_pattern = pattern.format(version=new_version)
|
72
|
+
content = content.replace(old_pattern, new_pattern)
|
73
|
+
|
74
|
+
if content != original_content:
|
75
|
+
file_path.write_text(content)
|
76
|
+
print(f" ✅ Updated {file_path}")
|
77
|
+
else:
|
78
|
+
print(f" ℹ️ No changes in {file_path}")
|
79
|
+
|
80
|
+
|
81
|
+
def main():
|
82
|
+
"""Main function."""
|
83
|
+
if len(sys.argv) != 2:
|
84
|
+
print(__doc__)
|
85
|
+
sys.exit(1)
|
86
|
+
|
87
|
+
bump_type = sys.argv[1]
|
88
|
+
|
89
|
+
# Get current version
|
90
|
+
try:
|
91
|
+
current = get_current_version()
|
92
|
+
print(f"Current version: {current}")
|
93
|
+
except Exception as e:
|
94
|
+
print(f"Error getting current version: {e}")
|
95
|
+
sys.exit(1)
|
96
|
+
|
97
|
+
# Calculate new version
|
98
|
+
try:
|
99
|
+
new = bump_version(current, bump_type)
|
100
|
+
print(f"New version: {new}")
|
101
|
+
except Exception as e:
|
102
|
+
print(f"Error calculating new version: {e}")
|
103
|
+
sys.exit(1)
|
104
|
+
|
105
|
+
# Update files
|
106
|
+
base_path = Path(__file__).parent.parent
|
107
|
+
|
108
|
+
files_to_update = [
|
109
|
+
(
|
110
|
+
base_path / "__init__.py",
|
111
|
+
['__version__ = "{version}"']
|
112
|
+
),
|
113
|
+
(
|
114
|
+
base_path / "setup.py",
|
115
|
+
['version="{version}"']
|
116
|
+
),
|
117
|
+
(
|
118
|
+
base_path / "pyproject.toml",
|
119
|
+
['version = "{version}"']
|
120
|
+
),
|
121
|
+
]
|
122
|
+
|
123
|
+
print("\nUpdating files:")
|
124
|
+
for file_path, patterns in files_to_update:
|
125
|
+
update_file(file_path, current, new, patterns)
|
126
|
+
|
127
|
+
print(f"\n✨ Version bumped from {current} to {new}")
|
128
|
+
print("\nNext steps:")
|
129
|
+
print(f" 1. Update CHANGELOG.md with changes for v{new}")
|
130
|
+
print(f" 2. Commit: git commit -am 'Bump version to {new}'")
|
131
|
+
print(f" 3. Tag: git tag -a v{new} -m 'Release version {new}'")
|
132
|
+
print(f" 4. Push: git push origin main --tags")
|
133
|
+
print(f" 5. Build: python -m build")
|
134
|
+
print(f" 6. Upload: python -m twine upload dist/*")
|
135
|
+
|
136
|
+
|
137
|
+
if __name__ == "__main__":
|
138
138
|
main()
|
scripts/demo_log_collection.py
CHANGED
@@ -1,144 +1,146 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Demonstration of log collection from .claude/Projects
|
4
|
-
|
5
|
-
This script shows how the zen --apex --send-logs functionality works
|
6
|
-
"""
|
7
|
-
import sys
|
8
|
-
from pathlib import Path
|
9
|
-
import json
|
10
|
-
|
11
|
-
# Add parent to path for imports
|
12
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
13
|
-
|
14
|
-
from scripts.agent_logs import collect_recent_logs
|
15
|
-
|
16
|
-
|
17
|
-
def demo_log_collection():
|
18
|
-
"""Demonstrate log collection with various scenarios"""
|
19
|
-
|
20
|
-
print("=" * 60)
|
21
|
-
print("Zen Apex Log Collection Demo")
|
22
|
-
print("=" * 60)
|
23
|
-
print()
|
24
|
-
|
25
|
-
# Check if .claude/Projects exists
|
26
|
-
claude_path = Path.home() / ".claude" / "Projects"
|
27
|
-
|
28
|
-
if not claude_path.exists():
|
29
|
-
print("❌ .claude/Projects does not exist")
|
30
|
-
print(f" Expected location: {claude_path}")
|
31
|
-
print()
|
32
|
-
print("Creating test directory...")
|
33
|
-
claude_path.mkdir(parents=True, exist_ok=True)
|
34
|
-
test_project = claude_path / "demo-project"
|
35
|
-
test_project.mkdir(exist_ok=True)
|
36
|
-
|
37
|
-
# Create sample log
|
38
|
-
sample_log = {
|
39
|
-
"type": "demo_event",
|
40
|
-
"timestamp": "2025-01-08T12:00:00",
|
41
|
-
"message": "This is a demo log entry",
|
42
|
-
"data": {"key": "value"}
|
43
|
-
}
|
44
|
-
(test_project / "demo-session.jsonl").write_text(json.dumps(sample_log) + "\n")
|
45
|
-
print(f"✅ Created demo project at {test_project}")
|
46
|
-
print()
|
47
|
-
|
48
|
-
# Scenario 1: Collect with defaults
|
49
|
-
print("Scenario 1: Collect logs with defaults (limit=
|
50
|
-
print("-" * 60)
|
51
|
-
logs = collect_recent_logs(limit=
|
52
|
-
|
53
|
-
if logs:
|
54
|
-
print(f"✅ Collected {len(logs)} log entries")
|
55
|
-
print(f" Total entries: {len(logs)}")
|
56
|
-
print()
|
57
|
-
print(" Sample entry (first):")
|
58
|
-
print(f" {json.dumps(logs[0], indent=4)}")
|
59
|
-
else:
|
60
|
-
print("⚠️ No logs found")
|
61
|
-
print(" Tip: Run Claude Code with some commands to generate logs")
|
62
|
-
print()
|
63
|
-
|
64
|
-
# Scenario 2: List available projects
|
65
|
-
print("Scenario 2: List available projects")
|
66
|
-
print("-" * 60)
|
67
|
-
if claude_path.exists():
|
68
|
-
projects = [p for p in claude_path.iterdir() if p.is_dir()]
|
69
|
-
if projects:
|
70
|
-
print(f"Found {len(projects)} project(s):")
|
71
|
-
for proj in sorted(projects, key=lambda p: p.stat().st_mtime, reverse=True):
|
72
|
-
jsonl_count = len(list(proj.glob("*.jsonl")))
|
73
|
-
mtime = proj.stat().st_mtime
|
74
|
-
from datetime import datetime
|
75
|
-
mtime_str = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
76
|
-
marker = " ← most recent" if proj == projects[0] else ""
|
77
|
-
print(f" • {proj.name}: {jsonl_count} .jsonl files (modified: {mtime_str}){marker}")
|
78
|
-
else:
|
79
|
-
print(" No projects found")
|
80
|
-
print()
|
81
|
-
|
82
|
-
# Scenario 3: Collect from specific project
|
83
|
-
if claude_path.exists():
|
84
|
-
projects = [p for p in claude_path.iterdir() if p.is_dir()]
|
85
|
-
if projects:
|
86
|
-
specific_project = projects[0].name
|
87
|
-
print(f"Scenario 3: Collect from specific project '{specific_project}'")
|
88
|
-
print("-" * 60)
|
89
|
-
logs = collect_recent_logs(limit=3, project_name=specific_project)
|
90
|
-
if logs:
|
91
|
-
print(f"✅ Collected {len(logs)} entries from '{specific_project}'")
|
92
|
-
print(f" Entry types: {[log.get('type', 'unknown') for log in logs[:3]]}")
|
93
|
-
else:
|
94
|
-
print(f"⚠️ No logs in '{specific_project}'")
|
95
|
-
print()
|
96
|
-
|
97
|
-
# Scenario 4: Show what would be sent with --send-logs
|
98
|
-
print("Scenario 4: What gets sent with 'zen --apex --send-logs --message \"..\"'")
|
99
|
-
print("-" * 60)
|
100
|
-
logs = collect_recent_logs(limit=
|
101
|
-
if logs:
|
102
|
-
payload_preview = {
|
103
|
-
"type": "user_message",
|
104
|
-
"payload": {
|
105
|
-
"content": "your message here",
|
106
|
-
"run_id": "cli_20250108_120000_12345",
|
107
|
-
"thread_id": "cli_thread_abc123def456",
|
108
|
-
"timestamp": "2025-01-08T12:00:00",
|
109
|
-
"jsonl_logs": logs # This is what gets attached
|
110
|
-
}
|
111
|
-
}
|
112
|
-
print("Payload structure:")
|
113
|
-
print(json.dumps(payload_preview, indent=2)[:500] + "...")
|
114
|
-
print()
|
115
|
-
print(f"✅ {len(logs)} log entries would be attached to the message")
|
116
|
-
else:
|
117
|
-
print("⚠️ No logs would be attached (none found)")
|
118
|
-
print()
|
119
|
-
|
120
|
-
# Summary
|
121
|
-
print("=" * 60)
|
122
|
-
print("Summary")
|
123
|
-
print("=" * 60)
|
124
|
-
print()
|
125
|
-
print("To use log forwarding with zen --apex:")
|
126
|
-
print()
|
127
|
-
print(" # Basic usage (
|
128
|
-
print(" zen --apex --send-logs --message \"analyze these sessions\"")
|
129
|
-
print()
|
130
|
-
print(" # Custom number of logs")
|
131
|
-
print(" zen --apex --send-logs --
|
132
|
-
print()
|
133
|
-
print("
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
print(" zen --apex --send-logs --logs-project
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Demonstration of log collection from .claude/Projects
|
4
|
+
|
5
|
+
This script shows how the zen --apex --send-logs functionality works
|
6
|
+
"""
|
7
|
+
import sys
|
8
|
+
from pathlib import Path
|
9
|
+
import json
|
10
|
+
|
11
|
+
# Add parent to path for imports
|
12
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
13
|
+
|
14
|
+
from scripts.agent_logs import collect_recent_logs
|
15
|
+
|
16
|
+
|
17
|
+
def demo_log_collection():
|
18
|
+
"""Demonstrate log collection with various scenarios"""
|
19
|
+
|
20
|
+
print("=" * 60)
|
21
|
+
print("Zen Apex Log Collection Demo")
|
22
|
+
print("=" * 60)
|
23
|
+
print()
|
24
|
+
|
25
|
+
# Check if .claude/Projects exists
|
26
|
+
claude_path = Path.home() / ".claude" / "Projects"
|
27
|
+
|
28
|
+
if not claude_path.exists():
|
29
|
+
print("❌ .claude/Projects does not exist")
|
30
|
+
print(f" Expected location: {claude_path}")
|
31
|
+
print()
|
32
|
+
print("Creating test directory...")
|
33
|
+
claude_path.mkdir(parents=True, exist_ok=True)
|
34
|
+
test_project = claude_path / "demo-project"
|
35
|
+
test_project.mkdir(exist_ok=True)
|
36
|
+
|
37
|
+
# Create sample log
|
38
|
+
sample_log = {
|
39
|
+
"type": "demo_event",
|
40
|
+
"timestamp": "2025-01-08T12:00:00",
|
41
|
+
"message": "This is a demo log entry",
|
42
|
+
"data": {"key": "value"}
|
43
|
+
}
|
44
|
+
(test_project / "demo-session.jsonl").write_text(json.dumps(sample_log) + "\n")
|
45
|
+
print(f"✅ Created demo project at {test_project}")
|
46
|
+
print()
|
47
|
+
|
48
|
+
# Scenario 1: Collect with defaults
|
49
|
+
print("Scenario 1: Collect logs with defaults (limit=1, auto-detect project)")
|
50
|
+
print("-" * 60)
|
51
|
+
logs = collect_recent_logs(limit=1)
|
52
|
+
|
53
|
+
if logs:
|
54
|
+
print(f"✅ Collected {len(logs)} log entries")
|
55
|
+
print(f" Total entries: {len(logs)}")
|
56
|
+
print()
|
57
|
+
print(" Sample entry (first):")
|
58
|
+
print(f" {json.dumps(logs[0], indent=4)}")
|
59
|
+
else:
|
60
|
+
print("⚠️ No logs found")
|
61
|
+
print(" Tip: Run Claude Code with some commands to generate logs")
|
62
|
+
print()
|
63
|
+
|
64
|
+
# Scenario 2: List available projects
|
65
|
+
print("Scenario 2: List available projects")
|
66
|
+
print("-" * 60)
|
67
|
+
if claude_path.exists():
|
68
|
+
projects = [p for p in claude_path.iterdir() if p.is_dir()]
|
69
|
+
if projects:
|
70
|
+
print(f"Found {len(projects)} project(s):")
|
71
|
+
for proj in sorted(projects, key=lambda p: p.stat().st_mtime, reverse=True):
|
72
|
+
jsonl_count = len(list(proj.glob("*.jsonl")))
|
73
|
+
mtime = proj.stat().st_mtime
|
74
|
+
from datetime import datetime
|
75
|
+
mtime_str = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
76
|
+
marker = " ← most recent" if proj == projects[0] else ""
|
77
|
+
print(f" • {proj.name}: {jsonl_count} .jsonl files (modified: {mtime_str}){marker}")
|
78
|
+
else:
|
79
|
+
print(" No projects found")
|
80
|
+
print()
|
81
|
+
|
82
|
+
# Scenario 3: Collect from specific project
|
83
|
+
if claude_path.exists():
|
84
|
+
projects = [p for p in claude_path.iterdir() if p.is_dir()]
|
85
|
+
if projects:
|
86
|
+
specific_project = projects[0].name
|
87
|
+
print(f"Scenario 3: Collect from specific project '{specific_project}'")
|
88
|
+
print("-" * 60)
|
89
|
+
logs = collect_recent_logs(limit=3, project_name=specific_project)
|
90
|
+
if logs:
|
91
|
+
print(f"✅ Collected {len(logs)} entries from '{specific_project}'")
|
92
|
+
print(f" Entry types: {[log.get('type', 'unknown') for log in logs[:3]]}")
|
93
|
+
else:
|
94
|
+
print(f"⚠️ No logs in '{specific_project}'")
|
95
|
+
print()
|
96
|
+
|
97
|
+
# Scenario 4: Show what would be sent with --send-logs
|
98
|
+
print("Scenario 4: What gets sent with 'zen --apex --send-logs --message \"..\"'")
|
99
|
+
print("-" * 60)
|
100
|
+
logs = collect_recent_logs(limit=1)
|
101
|
+
if logs:
|
102
|
+
payload_preview = {
|
103
|
+
"type": "user_message",
|
104
|
+
"payload": {
|
105
|
+
"content": "your message here",
|
106
|
+
"run_id": "cli_20250108_120000_12345",
|
107
|
+
"thread_id": "cli_thread_abc123def456",
|
108
|
+
"timestamp": "2025-01-08T12:00:00",
|
109
|
+
"jsonl_logs": logs # This is what gets attached
|
110
|
+
}
|
111
|
+
}
|
112
|
+
print("Payload structure:")
|
113
|
+
print(json.dumps(payload_preview, indent=2)[:500] + "...")
|
114
|
+
print()
|
115
|
+
print(f"✅ {len(logs)} log entries would be attached to the message")
|
116
|
+
else:
|
117
|
+
print("⚠️ No logs would be attached (none found)")
|
118
|
+
print()
|
119
|
+
|
120
|
+
# Summary
|
121
|
+
print("=" * 60)
|
122
|
+
print("Summary")
|
123
|
+
print("=" * 60)
|
124
|
+
print()
|
125
|
+
print("To use log forwarding with zen --apex:")
|
126
|
+
print()
|
127
|
+
print(" # Basic usage (default: 1 log file for best results)")
|
128
|
+
print(" zen --apex --send-logs --message \"analyze these sessions\"")
|
129
|
+
print()
|
130
|
+
print(" # Custom number of logs (default: 1 for best results)")
|
131
|
+
print(" zen --apex --send-logs --message \"review recent log\" (analyzes 1 file)")
|
132
|
+
print(" # Multiple files (use with caution - keep payload under 1MB)")
|
133
|
+
print(" zen --apex --send-logs --logs-count 2 --message \"review last 2\"")
|
134
|
+
print()
|
135
|
+
print(" # Specific project")
|
136
|
+
if claude_path.exists() and list(claude_path.iterdir()):
|
137
|
+
first_project = list(p for p in claude_path.iterdir() if p.is_dir())[0].name
|
138
|
+
print(f" zen --apex --send-logs --logs-project {first_project} --message \"...\"")
|
139
|
+
else:
|
140
|
+
print(" zen --apex --send-logs --logs-project PROJECT_NAME --message \"...\"")
|
141
|
+
print()
|
142
|
+
print("=" * 60)
|
143
|
+
|
144
|
+
|
145
|
+
if __name__ == "__main__":
|
146
|
+
demo_log_collection()
|