@leejungkiin/awkit 1.7.1 → 1.7.4

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 (245) hide show
  1. package/bin/awk.js +576 -84
  2. package/core/CLAUDE.md +1 -1
  3. package/core/GEMINI.md +148 -167
  4. package/core/GEMINI.md.bak +149 -116
  5. package/core/skill-runtime-manifest.json +3 -0
  6. package/docs/Claude Fable 5.md +3826 -0
  7. package/docs/android_kotlin_system_instruction.md +210 -0
  8. package/docs/brainstorm_ponytail_integration.md +146 -0
  9. package/docs/brainstorm_smart_setup.md +113 -0
  10. package/docs/deep-research-report (1).md +293 -0
  11. package/docs/history/GEMINI.v1.md +135 -0
  12. package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
  13. package/docs/history/implementation_plan.v1.md +58 -0
  14. package/package.json +4 -1
  15. package/scripts/artifact-storage.js +130 -0
  16. package/scripts/automation-gate.js +35 -2
  17. package/scripts/claude-plan.js +76 -0
  18. package/scripts/dependency-manager.js +210 -0
  19. package/scripts/exec-rtk.js +11 -5
  20. package/scripts/i18n-helper.js +381 -0
  21. package/scripts/multi-model-pipeline.js +144 -0
  22. package/skill-packs/mobile-ios/pack.json +4 -2
  23. package/skill-packs/reverse-engineering/pack.json +1 -0
  24. package/skills/CATALOG.md +20 -0
  25. package/skills/GEMINI.md +9 -1
  26. package/skills/TRIGGER_INDEX.md +10 -0
  27. package/skills/ai-music/SKILL.md +275 -0
  28. package/skills/android-re-analyzer/SKILL.md +238 -0
  29. package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
  30. package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
  31. package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
  32. package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
  33. package/skills/android-re-analyzer/references/setup-guide.md +221 -0
  34. package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
  35. package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
  36. package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
  37. package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
  38. package/skills/animal-island-ui-style/SKILL.md +1450 -0
  39. package/skills/app-store-review-agent/SKILL.md +164 -0
  40. package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
  41. package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
  42. package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
  43. package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
  44. package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
  45. package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
  46. package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
  47. package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
  48. package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
  49. package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
  50. package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
  51. package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
  52. package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
  53. package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
  54. package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
  55. package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
  56. package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
  57. package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
  58. package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
  59. package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
  60. package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
  61. package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
  62. package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
  63. package/skills/awf-ponytail/SKILL.md +91 -0
  64. package/skills/awf-ponytail-review/SKILL.md +67 -0
  65. package/skills/awf-session-restore/SKILL.md +3 -3
  66. package/skills/brainstorm-agent/SKILL.md +11 -2
  67. package/skills/brainstorm-agent/templates/brief-template.md +8 -0
  68. package/skills/claude-planner/SKILL.md +47 -0
  69. package/skills/code-review/SKILL.md +87 -0
  70. package/skills/expo-game-development/SKILL.md +163 -0
  71. package/skills/flutter/LICENSE.txt +202 -0
  72. package/skills/flutter/SKILL.md +127 -0
  73. package/skills/flutter-project-creater/LICENSE.txt +202 -0
  74. package/skills/flutter-project-creater/SKILL.md +106 -0
  75. package/skills/game-developer/SKILL.md +163 -0
  76. package/skills/game-developer/references/ecs-patterns.md +501 -0
  77. package/skills/game-developer/references/multiplayer-networking.md +475 -0
  78. package/skills/game-developer/references/performance-optimization.md +422 -0
  79. package/skills/game-developer/references/unity-patterns.md +271 -0
  80. package/skills/game-developer/references/unreal-cpp.md +352 -0
  81. package/skills/generate-gui-assets/SKILL.md +305 -0
  82. package/skills/generate-gui-assets/agents/openai.yaml +4 -0
  83. package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
  84. package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
  85. package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
  86. package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  87. package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
  88. package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
  89. package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
  90. package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
  91. package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
  92. package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
  93. package/skills/godot-game-development/SKILL.md +142 -0
  94. package/skills/hatch-pet/LICENSE.txt +201 -0
  95. package/skills/hatch-pet/SKILL.md +420 -0
  96. package/skills/hatch-pet/agents/openai.yaml +4 -0
  97. package/skills/hatch-pet/references/animation-rows.md +29 -0
  98. package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
  99. package/skills/hatch-pet/references/qa-rubric.md +60 -0
  100. package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  101. package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
  102. package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
  103. package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
  104. package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
  105. package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
  106. package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
  107. package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
  108. package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
  109. package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
  110. package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
  111. package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
  112. package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
  113. package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
  114. package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
  115. package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
  116. package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
  117. package/skills/i18n-orchestrator/SKILL.md +37 -0
  118. package/skills/ios-simulator-skill/SKILL.md +390 -0
  119. package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
  120. package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
  121. package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
  122. package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
  123. package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
  124. package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
  125. package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
  126. package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
  127. package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
  128. package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
  129. package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
  130. package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
  131. package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
  132. package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
  133. package/skills/ios-simulator-skill/scripts/container.py +668 -0
  134. package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
  135. package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
  136. package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
  137. package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
  138. package/skills/ios-simulator-skill/scripts/location.py +467 -0
  139. package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
  140. package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
  141. package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
  142. package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
  143. package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
  144. package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
  145. package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
  146. package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
  147. package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
  148. package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
  149. package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
  150. package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
  151. package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
  152. package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
  153. package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
  154. package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
  155. package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
  156. package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
  157. package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
  158. package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
  159. package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
  160. package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
  161. package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
  162. package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
  163. package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
  164. package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
  165. package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
  166. package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
  167. package/skills/orchestrator/SKILL.md +0 -20
  168. package/skills/persistent-storage/SKILL.md +55 -0
  169. package/skills/short-maker/SKILL.md +23 -0
  170. package/skills/short-maker/scripts/effects.js +56 -0
  171. package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
  172. package/skills/short-maker/scripts/videomix.js +601 -0
  173. package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
  174. package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
  175. package/skills/smali-to-kotlin/SKILL.md +128 -0
  176. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  177. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  178. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  179. package/skills/smali-to-kotlin/library-patterns.md +189 -0
  180. package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
  181. package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
  182. package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
  183. package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
  184. package/skills/smali-to-kotlin/phase-3-build.md +248 -0
  185. package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
  186. package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
  187. package/skills/smali-to-kotlin/templates/app-map.md +101 -0
  188. package/skills/smali-to-kotlin/templates/architecture.md +142 -0
  189. package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
  190. package/skills/spec-gate/SKILL.md +6 -2
  191. package/skills/symphony-enforcer/SKILL.md +8 -0
  192. package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
  193. package/skills/symphony-enforcer/examples/three-phase.md +16 -0
  194. package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
  195. package/skills/unity-game-development/SKILL.md +231 -0
  196. package/skills/video-edit/SKILL.md +36 -0
  197. package/skills/video-edit/scripts/video_edit.py +324 -0
  198. package/templates/project-identity/android.json +2 -2
  199. package/templates/project-identity/backend-nestjs.json +2 -2
  200. package/templates/project-identity/expo.json +2 -2
  201. package/templates/project-identity/ios.json +2 -2
  202. package/templates/project-identity/web-nextjs.json +2 -2
  203. package/templates/setup-mapping.json +48 -0
  204. package/templates/specs/design-template.md +161 -71
  205. package/templates/specs/requirements-template.md +65 -133
  206. package/templates/specs/task-spec-template.xml +3 -0
  207. package/workflows/_uncategorized/critic.md +40 -0
  208. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  209. package/workflows/_uncategorized/image-gen.md +118 -0
  210. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  211. package/workflows/_uncategorized/pixel-gen.md +86 -0
  212. package/workflows/_uncategorized/pixel-setup.md +90 -0
  213. package/workflows/_uncategorized/ponytail-review.md +59 -0
  214. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  215. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  216. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  217. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  218. package/workflows/_uncategorized/reverse-android.md +143 -0
  219. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  220. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  221. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  222. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  223. package/workflows/_uncategorized/reverse-ios.md +152 -0
  224. package/workflows/_uncategorized/safety-router.md +34 -0
  225. package/workflows/_uncategorized/teach.md +89 -0
  226. package/workflows/_uncategorized/verify-ui.md +53 -0
  227. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  228. package/workflows/ads/ads-analyst.md +201 -0
  229. package/workflows/ads/ads-audit.md +106 -0
  230. package/workflows/ads/ads-optimize.md +97 -0
  231. package/workflows/ads/ads-targeting.md +241 -0
  232. package/workflows/ads/adsExpert.md +160 -0
  233. package/workflows/ads/smali-ads-config.md +400 -0
  234. package/workflows/ads/smali-ads-flow.md +331 -0
  235. package/workflows/ads/smali-ads-interstitial.md +377 -0
  236. package/workflows/ads/smali-ads-native.md +382 -0
  237. package/workflows/context/teach.md +89 -0
  238. package/workflows/gitnexus.md +8 -8
  239. package/workflows/lifecycle/brainstorm.md +43 -0
  240. package/workflows/lifecycle/code.md +5 -0
  241. package/workflows/lifecycle/init.md +23 -5
  242. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  243. package/workflows/quality/ponytail-review.md +59 -0
  244. package/workflows/roles/critic.md +40 -0
  245. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,493 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ iOS Simulator Log Monitoring and Analysis
4
+
5
+ Real-time log streaming from iOS simulators with intelligent filtering, error detection,
6
+ and token-efficient summarization. Enhanced version of app_state_capture.py's log capture.
7
+
8
+ Features:
9
+ - Real-time log streaming from booted simulators
10
+ - Smart filtering by app bundle ID, subsystem, category, severity
11
+ - Error/warning classification and deduplication
12
+ - Duration-based or continuous follow mode
13
+ - Token-efficient summaries with full logs saved to file
14
+ - Integration with test_recorder and app_state_capture
15
+
16
+ Usage Examples:
17
+ # Monitor app logs in real-time (follow mode)
18
+ python scripts/log_monitor.py --app com.myapp.MyApp --follow
19
+
20
+ # Capture logs for specific duration
21
+ python scripts/log_monitor.py --app com.myapp.MyApp --duration 30s
22
+
23
+ # Extract errors and warnings only from last 5 minutes
24
+ python scripts/log_monitor.py --severity error,warning --last 5m
25
+
26
+ # Save logs to file
27
+ python scripts/log_monitor.py --app com.myapp.MyApp --duration 1m --output logs/
28
+
29
+ # Verbose output with full log lines
30
+ python scripts/log_monitor.py --app com.myapp.MyApp --verbose
31
+ """
32
+
33
+ import argparse
34
+ import json
35
+ import re
36
+ import signal
37
+ import subprocess
38
+ import sys
39
+ from datetime import datetime, timedelta
40
+ from pathlib import Path
41
+
42
+ from common.env_config import env_int
43
+
44
+ LOG_LINE_MAX = env_int("IOS_SIM_LOG_LINE_MAX", 300)
45
+ LOG_TAIL = env_int("IOS_SIM_LOG_TAIL", 200)
46
+ LOG_TEXT_SUMMARY_CAP = env_int("IOS_SIM_LOG_TEXT_SUMMARY", 15)
47
+ LOG_JSON_CAP = env_int("IOS_SIM_LOG_JSON_CAP", 100)
48
+
49
+
50
+ class LogMonitor:
51
+ """Monitor and analyze iOS simulator logs with intelligent filtering."""
52
+
53
+ def __init__(
54
+ self,
55
+ app_bundle_id: str | None = None,
56
+ device_udid: str | None = None,
57
+ severity_filter: list[str] | None = None,
58
+ ):
59
+ """
60
+ Initialize log monitor.
61
+
62
+ Args:
63
+ app_bundle_id: Filter logs by app bundle ID
64
+ device_udid: Device UDID (uses booted if not specified)
65
+ severity_filter: List of severities to include (error, warning, info, debug)
66
+ """
67
+ self.app_bundle_id = app_bundle_id
68
+ self.device_udid = device_udid or "booted"
69
+ self.severity_filter = severity_filter or ["error", "warning", "info", "debug"]
70
+
71
+ # Log storage
72
+ self.log_lines: list[str] = []
73
+ self.errors: list[str] = []
74
+ self.warnings: list[str] = []
75
+ self.info_messages: list[str] = []
76
+
77
+ # Statistics
78
+ self.error_count = 0
79
+ self.warning_count = 0
80
+ self.info_count = 0
81
+ self.debug_count = 0
82
+ self.total_lines = 0
83
+
84
+ # Deduplication
85
+ self.seen_messages: set[str] = set()
86
+
87
+ # Process control
88
+ self.log_process: subprocess.Popen | None = None
89
+ self.interrupted = False
90
+
91
+ def parse_time_duration(self, duration_str: str) -> float:
92
+ """
93
+ Parse duration string to seconds.
94
+
95
+ Args:
96
+ duration_str: Duration like "30s", "5m", "1h"
97
+
98
+ Returns:
99
+ Duration in seconds
100
+ """
101
+ match = re.match(r"(\d+)([smh])", duration_str.lower())
102
+ if not match:
103
+ raise ValueError(
104
+ f"Invalid duration format: {duration_str}. Use format like '30s', '5m', '1h'"
105
+ )
106
+
107
+ value, unit = match.groups()
108
+ value = int(value)
109
+
110
+ if unit == "s":
111
+ return value
112
+ if unit == "m":
113
+ return value * 60
114
+ if unit == "h":
115
+ return value * 3600
116
+
117
+ return 0
118
+
119
+ def classify_log_line(self, line: str) -> str | None:
120
+ """
121
+ Classify log line by severity.
122
+
123
+ Args:
124
+ line: Log line to classify
125
+
126
+ Returns:
127
+ Severity level (error, warning, info, debug) or None
128
+ """
129
+ line_lower = line.lower()
130
+
131
+ # Error patterns
132
+ error_patterns = [
133
+ r"\berror\b",
134
+ r"\bfault\b",
135
+ r"\bfailed\b",
136
+ r"\bexception\b",
137
+ r"\bcrash\b",
138
+ r"❌",
139
+ ]
140
+
141
+ # Warning patterns
142
+ warning_patterns = [r"\bwarning\b", r"\bwarn\b", r"\bdeprecated\b", r"⚠️"]
143
+
144
+ # Info patterns
145
+ info_patterns = [r"\binfo\b", r"\bnotice\b", r"ℹ️"]
146
+
147
+ for pattern in error_patterns:
148
+ if re.search(pattern, line_lower):
149
+ return "error"
150
+
151
+ for pattern in warning_patterns:
152
+ if re.search(pattern, line_lower):
153
+ return "warning"
154
+
155
+ for pattern in info_patterns:
156
+ if re.search(pattern, line_lower):
157
+ return "info"
158
+
159
+ return "debug"
160
+
161
+ def deduplicate_message(self, line: str) -> bool:
162
+ """
163
+ Check if message is duplicate.
164
+
165
+ Args:
166
+ line: Log line
167
+
168
+ Returns:
169
+ True if this is a new message, False if duplicate
170
+ """
171
+ # Create signature by removing timestamps and process IDs
172
+ signature = re.sub(r"\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}", "", line)
173
+ signature = re.sub(r"\[\d+\]", "", signature)
174
+ signature = re.sub(r"\s+", " ", signature).strip()
175
+
176
+ if signature in self.seen_messages:
177
+ return False
178
+
179
+ self.seen_messages.add(signature)
180
+ return True
181
+
182
+ def process_log_line(self, line: str):
183
+ """
184
+ Process a single log line.
185
+
186
+ Args:
187
+ line: Log line to process
188
+ """
189
+ if not line.strip():
190
+ return
191
+
192
+ self.total_lines += 1
193
+ self.log_lines.append(line)
194
+
195
+ # Classify severity
196
+ severity = self.classify_log_line(line)
197
+
198
+ # Skip if not in filter
199
+ if severity not in self.severity_filter:
200
+ return
201
+
202
+ # Deduplicate (for errors and warnings)
203
+ if severity in ["error", "warning"] and not self.deduplicate_message(line):
204
+ return
205
+
206
+ # Store by severity
207
+ if severity == "error":
208
+ self.error_count += 1
209
+ self.errors.append(line)
210
+ elif severity == "warning":
211
+ self.warning_count += 1
212
+ self.warnings.append(line)
213
+ elif severity == "info":
214
+ self.info_count += 1
215
+ if len(self.info_messages) < 20: # Keep only recent info
216
+ self.info_messages.append(line)
217
+ else: # debug
218
+ self.debug_count += 1
219
+
220
+ def stream_logs(
221
+ self,
222
+ follow: bool = False,
223
+ duration: float | None = None,
224
+ last_minutes: float | None = None,
225
+ ) -> bool:
226
+ """
227
+ Stream logs from simulator.
228
+
229
+ Args:
230
+ follow: Follow mode (continuous streaming)
231
+ duration: Capture duration in seconds
232
+ last_minutes: Show logs from last N minutes
233
+
234
+ Returns:
235
+ True if successful
236
+ """
237
+ # Build log stream command
238
+ cmd = ["xcrun", "simctl", "spawn", self.device_udid, "log", "stream"]
239
+
240
+ # Add filters
241
+ if self.app_bundle_id:
242
+ # Filter by process name (extracted from bundle ID)
243
+ app_name = self.app_bundle_id.split(".")[-1]
244
+ cmd.extend(["--predicate", f'processImagePath CONTAINS "{app_name}"'])
245
+
246
+ # Add time filter for historical logs
247
+ if last_minutes:
248
+ start_time = datetime.now() - timedelta(minutes=last_minutes)
249
+ time_str = start_time.strftime("%Y-%m-%d %H:%M:%S")
250
+ cmd.extend(["--start", time_str])
251
+
252
+ # Setup signal handler for graceful interruption
253
+ def signal_handler(sig, frame):
254
+ self.interrupted = True
255
+ if self.log_process:
256
+ self.log_process.terminate()
257
+
258
+ signal.signal(signal.SIGINT, signal_handler)
259
+
260
+ try:
261
+ # Start log streaming process
262
+ self.log_process = subprocess.Popen(
263
+ cmd,
264
+ stdout=subprocess.PIPE,
265
+ stderr=subprocess.PIPE,
266
+ text=True,
267
+ bufsize=1, # Line buffered
268
+ )
269
+
270
+ # Track start time for duration
271
+ start_time = datetime.now()
272
+
273
+ # Process log lines
274
+ for line in iter(self.log_process.stdout.readline, ""):
275
+ if not line:
276
+ break
277
+
278
+ # Process the line
279
+ self.process_log_line(line.rstrip())
280
+
281
+ # Print in follow mode
282
+ if follow:
283
+ severity = self.classify_log_line(line)
284
+ if severity in self.severity_filter:
285
+ print(line.rstrip())
286
+
287
+ # Check duration
288
+ if duration and (datetime.now() - start_time).total_seconds() >= duration:
289
+ break
290
+
291
+ # Check if interrupted
292
+ if self.interrupted:
293
+ break
294
+
295
+ # Wait for process to finish
296
+ self.log_process.wait()
297
+ return True
298
+
299
+ except Exception as e:
300
+ print(f"Error streaming logs: {e}", file=sys.stderr)
301
+ return False
302
+
303
+ finally:
304
+ if self.log_process:
305
+ self.log_process.terminate()
306
+
307
+ def get_summary(self, verbose: bool = False) -> str:
308
+ """
309
+ Get log summary.
310
+
311
+ Args:
312
+ verbose: Include full log details
313
+
314
+ Returns:
315
+ Formatted summary string
316
+ """
317
+ lines = []
318
+
319
+ # Header
320
+ if self.app_bundle_id:
321
+ lines.append(f"Logs for: {self.app_bundle_id}")
322
+ else:
323
+ lines.append("Logs for: All processes")
324
+
325
+ # Statistics
326
+ lines.append(f"Total lines: {self.total_lines}")
327
+ lines.append(
328
+ f"Errors: {self.error_count}, Warnings: {self.warning_count}, Info: {self.info_count}"
329
+ )
330
+
331
+ # Top issues
332
+ if self.errors:
333
+ lines.append(f"\nTop Errors ({len(self.errors)}):")
334
+ for error in self.errors[:LOG_TEXT_SUMMARY_CAP]:
335
+ lines.append(f" ❌ {error[:LOG_LINE_MAX]}")
336
+
337
+ if self.warnings:
338
+ lines.append(f"\nTop Warnings ({len(self.warnings)}):")
339
+ for warning in self.warnings[:LOG_TEXT_SUMMARY_CAP]:
340
+ lines.append(f" ⚠️ {warning[:LOG_LINE_MAX]}")
341
+
342
+ # Verbose output
343
+ if verbose and self.log_lines:
344
+ lines.append("\n=== Recent Log Lines ===")
345
+ for line in self.log_lines[-LOG_TAIL:]:
346
+ lines.append(line)
347
+
348
+ return "\n".join(lines)
349
+
350
+ def get_json_output(self) -> dict:
351
+ """Get log results as JSON."""
352
+ return {
353
+ "app_bundle_id": self.app_bundle_id,
354
+ "device_udid": self.device_udid,
355
+ "statistics": {
356
+ "total_lines": self.total_lines,
357
+ "errors": self.error_count,
358
+ "warnings": self.warning_count,
359
+ "info": self.info_count,
360
+ "debug": self.debug_count,
361
+ },
362
+ "errors": self.errors[:LOG_JSON_CAP],
363
+ "warnings": self.warnings[:LOG_JSON_CAP],
364
+ "sample_logs": self.log_lines[-LOG_TAIL:],
365
+ }
366
+
367
+ def save_logs(self, output_dir: str) -> str:
368
+ """
369
+ Save logs to file.
370
+
371
+ Args:
372
+ output_dir: Directory to save logs
373
+
374
+ Returns:
375
+ Path to saved log file
376
+ """
377
+ # Create output directory
378
+ output_path = Path(output_dir)
379
+ output_path.mkdir(parents=True, exist_ok=True)
380
+
381
+ # Generate filename with timestamp
382
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
383
+ app_name = self.app_bundle_id.split(".")[-1] if self.app_bundle_id else "simulator"
384
+ log_file = output_path / f"{app_name}-{timestamp}.log"
385
+
386
+ # Write all log lines
387
+ with open(log_file, "w") as f:
388
+ f.write("\n".join(self.log_lines))
389
+
390
+ # Also save JSON summary
391
+ json_file = output_path / f"{app_name}-{timestamp}-summary.json"
392
+ with open(json_file, "w") as f:
393
+ json.dump(self.get_json_output(), f, indent=2)
394
+
395
+ return str(log_file)
396
+
397
+
398
+ def main():
399
+ """Main entry point."""
400
+ parser = argparse.ArgumentParser(
401
+ description="Monitor and analyze iOS simulator logs",
402
+ formatter_class=argparse.RawDescriptionHelpFormatter,
403
+ epilog="""
404
+ Examples:
405
+ # Monitor app in real-time
406
+ python scripts/log_monitor.py --app com.myapp.MyApp --follow
407
+
408
+ # Capture logs for 30 seconds
409
+ python scripts/log_monitor.py --app com.myapp.MyApp --duration 30s
410
+
411
+ # Show errors/warnings from last 5 minutes
412
+ python scripts/log_monitor.py --severity error,warning --last 5m
413
+
414
+ # Save logs to file
415
+ python scripts/log_monitor.py --app com.myapp.MyApp --duration 1m --output logs/
416
+ """,
417
+ )
418
+
419
+ # Filtering options
420
+ parser.add_argument(
421
+ "--app", dest="app_bundle_id", help="App bundle ID to filter logs (e.g., com.myapp.MyApp)"
422
+ )
423
+ parser.add_argument("--device-udid", help="Device UDID (uses booted if not specified)")
424
+ parser.add_argument(
425
+ "--severity", help="Comma-separated severity levels (error,warning,info,debug)"
426
+ )
427
+
428
+ # Time options
429
+ time_group = parser.add_mutually_exclusive_group()
430
+ time_group.add_argument(
431
+ "--follow", action="store_true", help="Follow mode (continuous streaming)"
432
+ )
433
+ time_group.add_argument("--duration", help="Capture duration (e.g., 30s, 5m, 1h)")
434
+ time_group.add_argument(
435
+ "--last", dest="last_minutes", help="Show logs from last N minutes (e.g., 5m)"
436
+ )
437
+
438
+ # Output options
439
+ parser.add_argument("--output", help="Save logs to directory")
440
+ parser.add_argument("--verbose", action="store_true", help="Show detailed output")
441
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
442
+
443
+ args = parser.parse_args()
444
+
445
+ # Parse severity filter
446
+ severity_filter = None
447
+ if args.severity:
448
+ severity_filter = [s.strip().lower() for s in args.severity.split(",")]
449
+
450
+ # Initialize monitor
451
+ monitor = LogMonitor(
452
+ app_bundle_id=args.app_bundle_id,
453
+ device_udid=args.device_udid,
454
+ severity_filter=severity_filter,
455
+ )
456
+
457
+ # Parse duration
458
+ duration = None
459
+ if args.duration:
460
+ duration = monitor.parse_time_duration(args.duration)
461
+
462
+ # Parse last minutes
463
+ last_minutes = None
464
+ if args.last_minutes:
465
+ last_minutes = monitor.parse_time_duration(args.last_minutes) / 60
466
+
467
+ # Stream logs
468
+ print("Monitoring logs...", file=sys.stderr)
469
+ if args.app_bundle_id:
470
+ print(f"App: {args.app_bundle_id}", file=sys.stderr)
471
+
472
+ success = monitor.stream_logs(follow=args.follow, duration=duration, last_minutes=last_minutes)
473
+
474
+ if not success:
475
+ sys.exit(1)
476
+
477
+ # Save logs if requested
478
+ if args.output:
479
+ log_file = monitor.save_logs(args.output)
480
+ print(f"\nLogs saved to: {log_file}", file=sys.stderr)
481
+
482
+ # Output results
483
+ if not args.follow: # Don't show summary in follow mode
484
+ if args.json:
485
+ print(json.dumps(monitor.get_json_output(), indent=2))
486
+ else:
487
+ print("\n" + monitor.get_summary(verbose=args.verbose))
488
+
489
+ sys.exit(0)
490
+
491
+
492
+ if __name__ == "__main__":
493
+ main()