claude-comms 1.0.46 → 2.0.0

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 (54) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +12 -13
  3. package/package.json +6 -10
  4. package/.agents/AGENTS.md +0 -61
  5. package/.agents/repo.md +0 -18
  6. package/.agents/tools/README.md +0 -45
  7. package/.agents/tools/agent-job/README.md +0 -208
  8. package/.agents/tools/agent-job/__pycache__/agent_monitor.cpython-312.pyc +0 -0
  9. package/.agents/tools/agent-job/agent_job.py +0 -936
  10. package/.agents/tools/agent-job/agent_monitor.py +0 -575
  11. package/.agents/tools/agent-job/consultant_template.txt +0 -73
  12. package/.agents/tools/chrome-devtools/README.md +0 -101
  13. package/.agents/tools/chrome-devtools/browsertools.py +0 -839
  14. package/.agents/tools/chrome-devtools/config.json +0 -24
  15. package/.agents/tools/workflow/CONSUMER_SETUP.md +0 -114
  16. package/.agents/tools/workflow/ONBOARD.md +0 -167
  17. package/.agents/tools/workflow/README.md +0 -169
  18. package/.agents/tools/workflow/cli.ts +0 -726
  19. package/.agents/tools/workflow/config.example.json +0 -16
  20. package/.agents/tools/workflow/init.ts +0 -76
  21. package/.agents/tools/workflow/lib/file-tracker.test.ts +0 -81
  22. package/.agents/tools/workflow/lib/file-tracker.ts +0 -387
  23. package/.agents/tools/workflow/lib/harness-executor.ts +0 -649
  24. package/.agents/tools/workflow/lib/prompts.scenario.test.ts +0 -262
  25. package/.agents/tools/workflow/lib/prompts.ts +0 -548
  26. package/.agents/tools/workflow/lib/streams.test.ts +0 -140
  27. package/.agents/tools/workflow/lib/streams.ts +0 -254
  28. package/.agents/tools/workflow/runner.ts +0 -994
  29. package/.agents/tools/workflow/templates/document.md +0 -42
  30. package/.agents/tools/workflow/templates/implement.md +0 -117
  31. package/.agents/tools/workflow/templates/plan.md +0 -54
  32. package/.agents/tools/workflow/templates/pm.md +0 -84
  33. package/.agents/tools/workflow/templates/product-owner.md +0 -211
  34. package/.agents/tools/workflow/templates/review.md +0 -56
  35. package/.agents/tools/workflow/templates/uat.md +0 -71
  36. package/.claude/agents/core/engineer.md +0 -146
  37. package/.claude/commands/action-feedback-item.md +0 -27
  38. package/.claude/commands/install-feedback-widget.md +0 -599
  39. package/.claude/commands/learn.md +0 -32
  40. package/.claude/hooks/comms/__pycache__/register_subagent.cpython-312.pyc +0 -0
  41. package/.claude/hooks/comms/__pycache__/update_subagent_completion.cpython-312.pyc +0 -0
  42. package/.claude/hooks/comms/get_unread_messages.py +0 -71
  43. package/.claude/hooks/comms/register_subagent.py +0 -74
  44. package/.claude/hooks/comms/send_message.py +0 -87
  45. package/.claude/hooks/comms/update_subagent_completion.py +0 -126
  46. package/.claude/hooks/context/session_id.py +0 -35
  47. package/.claude/hooks/observability/send_event.py +0 -110
  48. package/.claude/hooks/session-data/get_session_data.py +0 -261
  49. package/.claude/hooks/session-data/intercept_session_id.py +0 -69
  50. package/.claude/hooks/utils/__pycache__/server_config.cpython-312.pyc +0 -0
  51. package/.claude/hooks/utils/server_config.py +0 -17
  52. package/.claude/settings.json +0 -108
  53. package/.claude/settings.local.json +0 -46
  54. package/CLAUDE.md +0 -240
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env -S uv run --script
2
- # /// script
3
- # requires-python = ">=3.8"
4
- # dependencies = [
5
- # "requests",
6
- # ]
7
- # ///
8
-
9
- import json
10
- import sys
11
- import requests
12
- import os
13
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
14
- from utils.server_config import get_server_url
15
-
16
-
17
- def main():
18
- try:
19
- # Read JSON input from stdin
20
- input_data = json.load(sys.stdin)
21
-
22
- session_id = input_data.get('session_id', 'unknown')
23
-
24
- # Check if this is a Task tool use and register the subagent
25
- tool_name = input_data.get('tool_name', '')
26
- tool_input = input_data.get('tool_input', {})
27
-
28
- if tool_name == 'Task':
29
- description = tool_input.get('description', '')
30
- subagent_type = tool_input.get('subagent_type', '')
31
- initial_prompt = tool_input.get('prompt', '')
32
-
33
- # Extract nickname from description (part before colon)
34
- if ':' in description:
35
- nickname = description.split(':')[0].strip()
36
-
37
- # Register the subagent with the server
38
- try:
39
- response = requests.post(
40
- f'{get_server_url()}/subagents/register',
41
- json={
42
- 'session_id': session_id,
43
- 'name': nickname,
44
- 'subagent_type': subagent_type
45
- },
46
- timeout=2
47
- )
48
- if response.status_code == 200:
49
- print(f"Registered subagent: {nickname} ({subagent_type})", file=sys.stderr)
50
-
51
- # Now update with initial prompt using PATCH endpoint
52
- if initial_prompt:
53
- prompt_response = requests.patch(
54
- f'{get_server_url()}/subagents/{session_id}/{nickname}',
55
- json={'initial_prompt': initial_prompt},
56
- timeout=2
57
- )
58
- if prompt_response.status_code == 200:
59
- print(f"Stored initial prompt for subagent: {nickname}", file=sys.stderr)
60
- except Exception as e:
61
- # Silently fail if server is not available
62
- pass
63
-
64
- sys.exit(0)
65
-
66
- except json.JSONDecodeError:
67
- # Gracefully handle JSON decode errors
68
- sys.exit(0)
69
- except Exception:
70
- # Handle any other errors gracefully
71
- sys.exit(0)
72
-
73
- if __name__ == '__main__':
74
- main()
@@ -1,87 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import json
4
- import sys
5
- import requests
6
- import argparse
7
- import os
8
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
9
- from utils.server_config import get_server_url
10
-
11
- def send_message(sender_name, message):
12
- """
13
- Send a message from a subagent to all other subagents.
14
-
15
- Args:
16
- sender_name: The name/nickname of the sending subagent
17
- message: The message to send (can be string or dict)
18
-
19
- Returns:
20
- True if message was sent successfully, False otherwise
21
- """
22
- try:
23
- # Parse message if it's a JSON string
24
- if isinstance(message, str):
25
- try:
26
- message = json.loads(message)
27
- except json.JSONDecodeError:
28
- # Keep as string if not valid JSON
29
- pass
30
-
31
- response = requests.post(
32
- f'{get_server_url()}/subagents/message',
33
- json={
34
- 'sender': sender_name,
35
- 'message': message
36
- },
37
- timeout=5
38
- )
39
-
40
- if response.status_code == 200:
41
- return True
42
- else:
43
- print(f"Error: Server returned status {response.status_code}", file=sys.stderr)
44
- return False
45
-
46
- except requests.exceptions.ConnectionError:
47
- print(f"Error: Could not connect to observability server at {get_server_url()}", file=sys.stderr)
48
- return False
49
- except requests.exceptions.Timeout:
50
- print("Error: Request timed out", file=sys.stderr)
51
- return False
52
- except Exception as e:
53
- print(f"Error: {str(e)}", file=sys.stderr)
54
- return False
55
-
56
- def main():
57
- parser = argparse.ArgumentParser(description='Send a message from a subagent')
58
- parser.add_argument('--sender', required=True, help='Sender subagent name/nickname')
59
- parser.add_argument('--message', required=True, help='Message to send (string or JSON)')
60
- parser.add_argument('--type', help='Message type (optional)')
61
- parser.add_argument('--data', help='Additional data (JSON format)')
62
-
63
- args = parser.parse_args()
64
-
65
- # Build message structure if type is provided
66
- if args.type:
67
- message = {
68
- 'type': args.type,
69
- 'content': args.message
70
- }
71
- if args.data:
72
- try:
73
- message['data'] = json.loads(args.data)
74
- except json.JSONDecodeError:
75
- message['data'] = args.data
76
- else:
77
- message = args.message
78
-
79
- success = send_message(args.sender, message)
80
-
81
- if success:
82
- print("Message sent successfully")
83
- else:
84
- sys.exit(1)
85
-
86
- if __name__ == '__main__':
87
- main()
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env -S uv run --script
2
- # /// script
3
- # requires-python = ">=3.8"
4
- # dependencies = [
5
- # "requests",
6
- # ]
7
- # ///
8
-
9
- import json
10
- import sys
11
- import requests
12
- import os
13
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
14
- from utils.server_config import get_server_url
15
-
16
-
17
- def main():
18
- try:
19
- # Read JSON input from stdin
20
- input_data = json.load(sys.stdin)
21
-
22
- session_id = input_data.get('session_id', 'unknown')
23
-
24
- # Check if this is a Task tool use completion
25
- tool_name = input_data.get('tool_name', '')
26
- tool_input = input_data.get('tool_input', {})
27
- tool_response = input_data.get('tool_response', {})
28
-
29
- if tool_name == 'Task':
30
- description = tool_input.get('description', '')
31
-
32
- # Extract agent name from description (part before colon)
33
- if ':' in description:
34
- agent_name = description.split(':')[0].strip()
35
-
36
- # Extract completion metadata from tool_response
37
- completion_metadata = {}
38
-
39
- # Get duration, tokens, and tool use count from tool_response
40
- if 'totalDurationMs' in tool_response:
41
- completion_metadata['total_duration_ms'] = tool_response['totalDurationMs']
42
-
43
- if 'totalTokens' in tool_response:
44
- completion_metadata['total_tokens'] = tool_response['totalTokens']
45
-
46
- if 'totalToolUseCount' in tool_response:
47
- completion_metadata['total_tool_use_count'] = tool_response['totalToolUseCount']
48
-
49
- # Get usage details if available
50
- usage = tool_response.get('usage', {})
51
- if usage:
52
- if 'input_tokens' in usage:
53
- completion_metadata['input_tokens'] = usage['input_tokens']
54
-
55
- if 'output_tokens' in usage:
56
- completion_metadata['output_tokens'] = usage['output_tokens']
57
-
58
- if 'cache_creation_input_tokens' in usage:
59
- completion_metadata['cache_creation_input_tokens'] = usage['cache_creation_input_tokens']
60
-
61
- if 'cache_read_input_tokens' in usage:
62
- completion_metadata['cache_read_input_tokens'] = usage['cache_read_input_tokens']
63
-
64
- # Extract the agent's final response content
65
- # The response content is typically in the main tool_response as text
66
- final_response = ''
67
- if 'content' in tool_response:
68
- # If content is a list (standard Claude format)
69
- if isinstance(tool_response['content'], list):
70
- text_blocks = [block.get('text', '') for block in tool_response['content'] if block.get('type') == 'text']
71
- final_response = '\n'.join(text_blocks)
72
- # If content is a string
73
- elif isinstance(tool_response['content'], str):
74
- final_response = tool_response['content']
75
- elif 'text' in tool_response:
76
- # Alternative field name
77
- final_response = tool_response['text']
78
- elif 'response' in tool_response:
79
- # Another possible field name
80
- final_response = tool_response['response']
81
-
82
- # Extract tool calls if available
83
- tool_calls_data = []
84
- if 'tool_calls' in tool_response:
85
- tool_calls_data = tool_response['tool_calls']
86
- elif 'toolCalls' in tool_response:
87
- tool_calls_data = tool_response['toolCalls']
88
-
89
- # Update subagent completion status
90
- try:
91
- response = requests.post(
92
- f'{get_server_url()}/subagents/update-completion',
93
- json={
94
- 'session_id': session_id,
95
- 'name': agent_name,
96
- 'status': 'completed',
97
- 'completed_at': None, # Will be set to current time by server
98
- 'completion_metadata': completion_metadata,
99
- 'final_response': final_response,
100
- 'tool_calls': tool_calls_data
101
- },
102
- timeout=2
103
- )
104
- if response.status_code == 200:
105
- print(f"Updated completion for subagent: {agent_name}", file=sys.stderr)
106
- elif response.status_code == 404:
107
- # Agent not found, silently ignore (might be a direct Task not created by subagent)
108
- pass
109
- else:
110
- # Other error, log but don't fail
111
- print(f"Failed to update completion for {agent_name}: {response.status_code}", file=sys.stderr)
112
- except Exception as e:
113
- # Silently fail if server is not available
114
- pass
115
-
116
- sys.exit(0)
117
-
118
- except json.JSONDecodeError:
119
- # Gracefully handle JSON decode errors
120
- sys.exit(0)
121
- except Exception:
122
- # Handle any other errors gracefully
123
- sys.exit(0)
124
-
125
- if __name__ == '__main__':
126
- main()
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env python3
2
- # UserPromptSubmit hook to inject context into the conversation
3
-
4
- import json
5
- import sys
6
- # NOTE: this ONLY works for userPromptSubmit hook, no other hooks are supported.
7
- def main():
8
- try:
9
- # Read JSON input from stdin
10
- input_data = json.load(sys.stdin)
11
-
12
- # Extract the user's prompt
13
-
14
- session_id = input_data.get('session_id', 'unknown')
15
-
16
- # Create additional context that will be injected
17
- additional_context = "Your session_id is: {session_id}"
18
-
19
-
20
- output = {
21
- "hookSpecificOutput": {
22
- "hookEventName": "UserPromptSubmit",
23
- "additionalContext": additional_context
24
- }
25
- }
26
- print(json.dumps(output))
27
- sys.exit(0)
28
-
29
- except Exception as e:
30
- # On error, show to user but don't block
31
- print(f"Error in UserPromptSubmit hook: {e}", file=sys.stderr)
32
- sys.exit(1)
33
-
34
- if __name__ == '__main__':
35
- main()
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env -S uv run --script
2
- # /// script
3
- # requires-python = ">=3.8"
4
- # ///
5
-
6
- """
7
- Multi-Agent Observability Hook Script
8
- Sends Claude Code hook events to the observability server.
9
- """
10
-
11
- import json
12
- import sys
13
- import os
14
- import argparse
15
- import urllib.request
16
- import urllib.error
17
- from datetime import datetime
18
-
19
- # Add parent directory to path for imports
20
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
21
- from utils.server_config import get_server_url
22
-
23
- def send_event_to_server(event_data, server_url=None):
24
- """Send event data to the observability server."""
25
- if server_url is None:
26
- server_url = f'{get_server_url()}/events'
27
-
28
- try:
29
- # Prepare the request
30
- req = urllib.request.Request(
31
- server_url,
32
- data=json.dumps(event_data).encode('utf-8'),
33
- headers={
34
- 'Content-Type': 'application/json',
35
- 'User-Agent': 'Claude-Code-Hook/1.0'
36
- }
37
- )
38
-
39
- # Send the request
40
- with urllib.request.urlopen(req, timeout=5) as response:
41
- if response.status == 200:
42
- return True
43
- else:
44
- print(f"Server returned status: {response.status}", file=sys.stderr)
45
- return False
46
-
47
- except urllib.error.URLError as e:
48
- print(f"Failed to send event: {e}", file=sys.stderr)
49
- return False
50
- except Exception as e:
51
- print(f"Unexpected error: {e}", file=sys.stderr)
52
- return False
53
-
54
- def main():
55
- # Parse command line arguments
56
- parser = argparse.ArgumentParser(description='Send Claude Code hook events to observability server')
57
- parser.add_argument('--source-app', required=True, help='Source application name')
58
- parser.add_argument('--event-type', required=True, help='Hook event type (PreToolUse, PostToolUse, etc.)')
59
- parser.add_argument('--add-chat', action='store_true', help='Include chat transcript if available')
60
- parser.add_argument('--summarize', action='store_true', help='Generate AI summary of the event')
61
-
62
- args = parser.parse_args()
63
-
64
- try:
65
- # Read hook data from stdin
66
- input_data = json.load(sys.stdin)
67
- except json.JSONDecodeError as e:
68
- print(f"Failed to parse JSON input: {e}", file=sys.stderr)
69
- sys.exit(1)
70
-
71
- # Prepare event data for server
72
- event_data = {
73
- 'source_app': args.source_app,
74
- 'session_id': input_data.get('session_id', 'unknown'),
75
- 'hook_event_type': args.event_type,
76
- 'payload': input_data,
77
- 'timestamp': int(datetime.now().timestamp() * 1000)
78
- }
79
-
80
- # Handle --add-chat option
81
- if args.add_chat and 'transcript_path' in input_data:
82
- transcript_path = input_data['transcript_path']
83
- if os.path.exists(transcript_path):
84
- # Read .jsonl file and convert to JSON array
85
- chat_data = []
86
- try:
87
- with open(transcript_path, 'r') as f:
88
- for line in f:
89
- line = line.strip()
90
- if line:
91
- try:
92
- chat_data.append(json.loads(line))
93
- except json.JSONDecodeError:
94
- pass # Skip invalid lines
95
-
96
- # Add chat to event data
97
- event_data['chat'] = chat_data
98
- except Exception as e:
99
- print(f"Failed to read transcript: {e}", file=sys.stderr)
100
-
101
-
102
- # Send to server
103
- server_url = f'{get_server_url()}/events'
104
- success = send_event_to_server(event_data, server_url)
105
-
106
- # Always exit with 0 to not block Claude Code operations
107
- sys.exit(0)
108
-
109
- if __name__ == '__main__':
110
- main()
@@ -1,261 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Session Data Fetching Script - Token-based pagination for introspect API
4
-
5
- Usage:
6
- python get_session_data.py --session-id <session_id> [--page <page_number>]
7
-
8
- Author: RafaelQuantum
9
- Date: 2025-08-20
10
- """
11
-
12
- import json
13
- import sys
14
- import argparse
15
- import requests
16
- import time
17
- import os
18
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
19
- from utils.server_config import get_server_url
20
-
21
- # Maximum tokens per page (easily configurable)
22
- MAX_TOKENS_PER_PAGE = 6000
23
-
24
- try:
25
- import tiktoken
26
- except ImportError:
27
- print("Error: tiktoken not installed. Run: pip install tiktoken", file=sys.stderr)
28
- sys.exit(1)
29
-
30
-
31
- def count_tokens(json_object, encoding):
32
- """
33
- Count tokens for a JSON object.
34
- Converts object to JSON string and counts tokens.
35
- """
36
- json_str = json.dumps(json_object, separators=(',', ':'))
37
- return len(encoding.encode(json_str))
38
-
39
-
40
- def paginate_timeline(timeline, page, max_tokens=MAX_TOKENS_PER_PAGE):
41
- """
42
- Paginate timeline messages into chunks that fit within max_tokens.
43
- Returns the messages for the requested page or None if out of range.
44
- Uses tiktoken for accurate token counting, including JSON structure overhead.
45
- """
46
- if not timeline:
47
- return timeline if page == 1 else None
48
-
49
- # Get the encoding for token counting (cl100k_base for GPT-3.5/4)
50
- try:
51
- encoding = tiktoken.get_encoding("cl100k_base")
52
- except Exception as e:
53
- print(f"Error initializing tiktoken: {e}", file=sys.stderr)
54
- # Fallback to character-based if tiktoken fails
55
- return paginate_timeline_fallback(timeline, page)
56
-
57
- # Calculate overhead for the JSON structure
58
- # {"sessionId": "...", "timeline": [...], "page": N}
59
- session_id = "8d90abf7-bd33-4367-985c-b5acb886a63a" # Example ID for overhead calculation
60
- overhead_json = {
61
- "sessionId": session_id,
62
- "timeline": [],
63
- "page": 1
64
- }
65
- overhead_tokens = len(encoding.encode(json.dumps(overhead_json, separators=(',', ':'))))
66
-
67
- pages = []
68
- current_page = []
69
- current_tokens = overhead_tokens # Start with JSON structure overhead
70
-
71
- for message in timeline:
72
- message_tokens = count_tokens(message, encoding)
73
- # Add tokens for comma separator if not first message
74
- separator_tokens = 1 if current_page else 0
75
-
76
- # Check if adding this message would exceed the limit
77
- total_with_message = current_tokens + separator_tokens + message_tokens
78
-
79
- if total_with_message > max_tokens:
80
- if current_page:
81
- # Save current page and start new one
82
- pages.append(current_page)
83
- current_page = [message]
84
- current_tokens = overhead_tokens + message_tokens
85
- else:
86
- # Single message exceeds limit, but we need to include it
87
- current_page = [message]
88
- current_tokens = overhead_tokens + message_tokens
89
- else:
90
- # Add to current page (within limit)
91
- current_page.append(message)
92
- current_tokens = total_with_message
93
-
94
- # Add the last page if it has content
95
- if current_page:
96
- pages.append(current_page)
97
-
98
- # Return requested page or None if out of range
99
- if page > 0 and page <= len(pages):
100
- return pages[page - 1]
101
- else:
102
- return None
103
-
104
-
105
- def get_total_pages(timeline, max_tokens=MAX_TOKENS_PER_PAGE):
106
- """
107
- Calculate total number of pages by checking when paginate returns None.
108
- """
109
- if not timeline:
110
- return 1
111
-
112
- page = 1
113
- while paginate_timeline(timeline, page, max_tokens) is not None:
114
- page += 1
115
-
116
- return page - 1
117
-
118
-
119
- def paginate_timeline_fallback(timeline, page, max_chars=30000):
120
- """
121
- Fallback character-based pagination if tiktoken is unavailable.
122
- """
123
- if not timeline:
124
- return timeline if page == 1 else None
125
-
126
- pages = []
127
- current_page = []
128
- current_size = 100
129
-
130
- for message in timeline:
131
- message_json = json.dumps(message, indent=2)
132
- message_size = len(message_json) + 5
133
-
134
- if current_size + message_size > max_chars and current_page:
135
- pages.append(current_page)
136
- current_page = [message]
137
- current_size = 100 + message_size
138
- else:
139
- current_page.append(message)
140
- current_size += message_size
141
-
142
- if current_page:
143
- pages.append(current_page)
144
-
145
- if page > 0 and page <= len(pages):
146
- return pages[page - 1]
147
- else:
148
- return None
149
-
150
-
151
- def main():
152
- """Main entry point - fetch and return paginated JSON."""
153
- parser = argparse.ArgumentParser(
154
- description='Fetch session data from Claude Code observability API with pagination'
155
- )
156
-
157
- parser.add_argument(
158
- '--session-id',
159
- required=True,
160
- help='Session ID to fetch data for'
161
- )
162
-
163
- parser.add_argument(
164
- '--page',
165
- type=int,
166
- default=1,
167
- help='Page number to fetch (default: 1)'
168
- )
169
-
170
- parser.add_argument(
171
- '--total-pages',
172
- action='store_true',
173
- help='Return only the total number of pages instead of page data'
174
- )
175
-
176
- args = parser.parse_args()
177
-
178
- # Wait 100ms per page before hitting the DB (as requested)
179
- if args.page > 1:
180
- time.sleep(args.page * 0.1)
181
-
182
- try:
183
- # Fetch the data from API
184
- api_base = get_server_url()
185
- url = f"{api_base}/api/sessions/{args.session_id}/introspect"
186
- response = requests.get(url, timeout=10)
187
-
188
- if response.status_code == 200:
189
- data = response.json()
190
-
191
- # If --total-pages flag is set, return only the total page count
192
- if args.total_pages:
193
- if 'timeline' in data and data['timeline']:
194
- total = get_total_pages(data['timeline'])
195
- print(total)
196
- else:
197
- print(1)
198
- sys.exit(0)
199
-
200
- # Handle pagination if timeline exists
201
- if 'timeline' in data and data['timeline']:
202
- paginated_timeline = paginate_timeline(data['timeline'], args.page)
203
-
204
- if paginated_timeline is None:
205
- # Page out of range
206
- print("//end")
207
- sys.exit(0)
208
- else:
209
- # Return paginated data
210
- paginated_data = {
211
- 'sessionId': data.get('sessionId'),
212
- 'timeline': paginated_timeline,
213
- 'page': args.page
214
- }
215
- print(json.dumps(paginated_data, indent=2))
216
- sys.exit(0)
217
- else:
218
- # No timeline or empty timeline
219
- if args.page == 1:
220
- print(json.dumps(data, indent=2))
221
- else:
222
- print("//end")
223
- sys.exit(0)
224
- else:
225
- # Error case
226
- error_data = {
227
- 'error': f'API returned status {response.status_code}',
228
- 'status_code': response.status_code,
229
- 'session_id': args.session_id
230
- }
231
- print(json.dumps(error_data, indent=2), file=sys.stderr)
232
- sys.exit(1)
233
-
234
- except requests.exceptions.ConnectionError:
235
- api_base = get_server_url()
236
- error_data = {
237
- 'error': f'Could not connect to API server at {api_base}',
238
- 'status_code': 0
239
- }
240
- print(json.dumps(error_data, indent=2), file=sys.stderr)
241
- sys.exit(1)
242
-
243
- except requests.exceptions.Timeout:
244
- error_data = {
245
- 'error': 'Request timeout',
246
- 'status_code': 0
247
- }
248
- print(json.dumps(error_data, indent=2), file=sys.stderr)
249
- sys.exit(1)
250
-
251
- except Exception as e:
252
- error_data = {
253
- 'error': f'Unexpected error: {str(e)}',
254
- 'status_code': 0
255
- }
256
- print(json.dumps(error_data, indent=2), file=sys.stderr)
257
- sys.exit(1)
258
-
259
-
260
- if __name__ == '__main__':
261
- main()