@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.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- 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()
|