@leejungkiin/awkit 1.7.0 → 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 (241) 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 +40 -7
  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/verification-gate/SKILL.md +4 -2
  197. package/skills/video-edit/SKILL.md +36 -0
  198. package/skills/video-edit/scripts/video_edit.py +324 -0
  199. package/templates/setup-mapping.json +48 -0
  200. package/templates/specs/design-template.md +161 -71
  201. package/templates/specs/requirements-template.md +65 -133
  202. package/templates/specs/task-spec-template.xml +3 -0
  203. package/workflows/_uncategorized/critic.md +40 -0
  204. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  205. package/workflows/_uncategorized/image-gen.md +118 -0
  206. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  207. package/workflows/_uncategorized/pixel-gen.md +86 -0
  208. package/workflows/_uncategorized/pixel-setup.md +90 -0
  209. package/workflows/_uncategorized/ponytail-review.md +59 -0
  210. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  211. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  212. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  213. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  214. package/workflows/_uncategorized/reverse-android.md +143 -0
  215. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  216. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  217. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  218. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  219. package/workflows/_uncategorized/reverse-ios.md +152 -0
  220. package/workflows/_uncategorized/safety-router.md +34 -0
  221. package/workflows/_uncategorized/teach.md +89 -0
  222. package/workflows/_uncategorized/verify-ui.md +53 -0
  223. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  224. package/workflows/ads/ads-analyst.md +201 -0
  225. package/workflows/ads/ads-audit.md +106 -0
  226. package/workflows/ads/ads-optimize.md +97 -0
  227. package/workflows/ads/ads-targeting.md +241 -0
  228. package/workflows/ads/adsExpert.md +160 -0
  229. package/workflows/ads/smali-ads-config.md +400 -0
  230. package/workflows/ads/smali-ads-flow.md +331 -0
  231. package/workflows/ads/smali-ads-interstitial.md +377 -0
  232. package/workflows/ads/smali-ads-native.md +382 -0
  233. package/workflows/context/teach.md +89 -0
  234. package/workflows/gitnexus.md +8 -8
  235. package/workflows/lifecycle/brainstorm.md +43 -0
  236. package/workflows/lifecycle/code.md +5 -0
  237. package/workflows/lifecycle/init.md +23 -5
  238. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  239. package/workflows/quality/ponytail-review.md +59 -0
  240. package/workflows/roles/critic.md +40 -0
  241. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Progressive disclosure cache for large outputs.
4
+
5
+ Implements cache system to support progressive disclosure pattern:
6
+ - Return concise summary with cache_id for large outputs
7
+ - User retrieves full details on demand via cache_id
8
+ - Reduces token usage by 96% for common queries
9
+
10
+ Cache directory: ~/.ios-simulator-skill/cache/
11
+ Cache expiration: Configurable per cache type (default 1 hour)
12
+
13
+ Used by:
14
+ - sim_list.py - Simulator listing progressive disclosure
15
+ - Future: build logs, UI trees, etc.
16
+ """
17
+
18
+ import contextlib
19
+ import json
20
+ import time
21
+ from datetime import datetime, timedelta
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from common.env_config import env_int
26
+
27
+ DEFAULT_MAX_AGE_HOURS = env_int("IOS_SIM_CACHE_TTL_HOURS", 1)
28
+ DEFAULT_MAX_ENTRIES = env_int("IOS_SIM_CACHE_MAX_ENTRIES", 500)
29
+
30
+
31
+ class ProgressiveCache:
32
+ """Cache for progressive disclosure pattern.
33
+
34
+ Stores large outputs with timestamped IDs for on-demand retrieval.
35
+ Automatically cleans up expired entries.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ cache_dir: str | None = None,
41
+ max_age_hours: int | None = None,
42
+ max_entries: int | None = None,
43
+ ):
44
+ """Initialize cache system.
45
+
46
+ Args:
47
+ cache_dir: Cache directory path (default: ~/.ios-simulator-skill/cache/)
48
+ max_age_hours: Max age for cache entries before expiration. Defaults to
49
+ ``IOS_SIM_CACHE_TTL_HOURS`` env var, or 1 hour.
50
+ max_entries: Maximum entries retained; oldest are evicted (LRU by mtime).
51
+ Defaults to ``IOS_SIM_CACHE_MAX_ENTRIES`` env var, or 500.
52
+ """
53
+ if cache_dir is None:
54
+ cache_dir = str(Path("~/.ios-simulator-skill/cache").expanduser())
55
+
56
+ self.cache_dir = Path(cache_dir)
57
+ self.max_age_hours = max_age_hours if max_age_hours is not None else DEFAULT_MAX_AGE_HOURS
58
+ self.max_entries = max_entries if max_entries is not None else DEFAULT_MAX_ENTRIES
59
+
60
+ # Create cache directory if needed
61
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
62
+
63
+ def _evict_overflow(self) -> int:
64
+ """Evict oldest entries when count exceeds ``max_entries``. Returns count evicted."""
65
+ files = list(self.cache_dir.glob("*.json"))
66
+ if len(files) <= self.max_entries:
67
+ return 0
68
+ # Sort oldest-first by mtime; delete the head of the overflow
69
+ files.sort(key=lambda p: p.stat().st_mtime)
70
+ to_remove = len(files) - self.max_entries
71
+ for path in files[:to_remove]:
72
+ with contextlib.suppress(OSError):
73
+ path.unlink()
74
+ return to_remove
75
+
76
+ def save(self, data: dict[str, Any], cache_type: str) -> str:
77
+ """Save data to cache and return cache_id.
78
+
79
+ Args:
80
+ data: Dictionary data to cache
81
+ cache_type: Type of cache ('simulator-list', 'build-log', 'ui-tree', etc.)
82
+
83
+ Returns:
84
+ Cache ID like 'sim-20251028-143052' for use in progressive disclosure
85
+
86
+ Example:
87
+ cache_id = cache.save({'devices': [...]}, 'simulator-list')
88
+ # Returns: 'sim-20251028-143052'
89
+ """
90
+ # Generate cache_id with timestamp
91
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
92
+ cache_prefix = cache_type.split("-", maxsplit=1)[0] # e.g., 'sim' from 'simulator-list'
93
+ cache_id = f"{cache_prefix}-{timestamp}"
94
+
95
+ # Save to file
96
+ cache_file = self.cache_dir / f"{cache_id}.json"
97
+ with open(cache_file, "w") as f:
98
+ json.dump(
99
+ {
100
+ "cache_id": cache_id,
101
+ "cache_type": cache_type,
102
+ "created_at": datetime.now().isoformat(),
103
+ "data": data,
104
+ },
105
+ f,
106
+ indent=2,
107
+ )
108
+
109
+ self._evict_overflow()
110
+ return cache_id
111
+
112
+ def get(self, cache_id: str) -> dict[str, Any] | None:
113
+ """Retrieve data from cache by cache_id.
114
+
115
+ Args:
116
+ cache_id: Cache ID from save() or list_entries()
117
+
118
+ Returns:
119
+ Cached data dictionary, or None if not found/expired
120
+
121
+ Example:
122
+ data = cache.get('sim-20251028-143052')
123
+ if data:
124
+ print(f"Found {len(data)} devices")
125
+ """
126
+ cache_file = self.cache_dir / f"{cache_id}.json"
127
+
128
+ if not cache_file.exists():
129
+ return None
130
+
131
+ # Check if expired
132
+ if self._is_expired(cache_file):
133
+ cache_file.unlink() # Delete expired file
134
+ return None
135
+
136
+ try:
137
+ with open(cache_file) as f:
138
+ entry = json.load(f)
139
+ return entry.get("data")
140
+ except (OSError, json.JSONDecodeError):
141
+ return None
142
+
143
+ def list_entries(self, cache_type: str | None = None) -> list[dict[str, Any]]:
144
+ """List available cache entries with metadata.
145
+
146
+ Args:
147
+ cache_type: Filter by type (e.g., 'simulator-list'), or None for all
148
+
149
+ Returns:
150
+ List of cache entries with id, type, created_at, age_seconds
151
+
152
+ Example:
153
+ entries = cache.list_entries('simulator-list')
154
+ for entry in entries:
155
+ print(f"{entry['id']} - {entry['age_seconds']}s old")
156
+ """
157
+ entries = []
158
+
159
+ for cache_file in sorted(self.cache_dir.glob("*.json"), reverse=True):
160
+ # Check if expired
161
+ if self._is_expired(cache_file):
162
+ cache_file.unlink()
163
+ continue
164
+
165
+ try:
166
+ with open(cache_file) as f:
167
+ entry = json.load(f)
168
+
169
+ # Filter by type if specified
170
+ if cache_type and entry.get("cache_type") != cache_type:
171
+ continue
172
+
173
+ created_at = datetime.fromisoformat(entry.get("created_at", ""))
174
+ age_seconds = (datetime.now() - created_at).total_seconds()
175
+
176
+ entries.append(
177
+ {
178
+ "id": entry.get("cache_id"),
179
+ "type": entry.get("cache_type"),
180
+ "created_at": entry.get("created_at"),
181
+ "age_seconds": int(age_seconds),
182
+ }
183
+ )
184
+ except (OSError, json.JSONDecodeError, ValueError):
185
+ continue
186
+
187
+ return entries
188
+
189
+ def cleanup(self, max_age_hours: int | None = None) -> int:
190
+ """Remove expired cache entries.
191
+
192
+ Args:
193
+ max_age_hours: Age threshold (default: uses instance max_age_hours)
194
+
195
+ Returns:
196
+ Number of entries deleted
197
+
198
+ Example:
199
+ deleted = cache.cleanup()
200
+ print(f"Deleted {deleted} expired cache entries")
201
+ """
202
+ if max_age_hours is None:
203
+ max_age_hours = self.max_age_hours
204
+
205
+ deleted = 0
206
+
207
+ for cache_file in self.cache_dir.glob("*.json"):
208
+ if self._is_expired(cache_file, max_age_hours):
209
+ cache_file.unlink()
210
+ deleted += 1
211
+
212
+ return deleted
213
+
214
+ def clear(self, cache_type: str | None = None) -> int:
215
+ """Clear all cache entries of a type.
216
+
217
+ Args:
218
+ cache_type: Type to clear (e.g., 'simulator-list'), or None to clear all
219
+
220
+ Returns:
221
+ Number of entries deleted
222
+
223
+ Example:
224
+ cleared = cache.clear('simulator-list')
225
+ print(f"Cleared {cleared} simulator list entries")
226
+ """
227
+ deleted = 0
228
+
229
+ for cache_file in self.cache_dir.glob("*.json"):
230
+ if cache_type is None:
231
+ # Clear all
232
+ cache_file.unlink()
233
+ deleted += 1
234
+ else:
235
+ # Clear by type
236
+ try:
237
+ with open(cache_file) as f:
238
+ entry = json.load(f)
239
+ if entry.get("cache_type") == cache_type:
240
+ cache_file.unlink()
241
+ deleted += 1
242
+ except (OSError, json.JSONDecodeError):
243
+ pass
244
+
245
+ return deleted
246
+
247
+ def _is_expired(self, cache_file: Path, max_age_hours: int | None = None) -> bool:
248
+ """Check if cache file is expired.
249
+
250
+ Args:
251
+ cache_file: Path to cache file
252
+ max_age_hours: Age threshold (default: uses instance max_age_hours)
253
+
254
+ Returns:
255
+ True if file is older than max_age_hours
256
+ """
257
+ if max_age_hours is None:
258
+ max_age_hours = self.max_age_hours
259
+
260
+ try:
261
+ with open(cache_file) as f:
262
+ entry = json.load(f)
263
+ created_at = datetime.fromisoformat(entry.get("created_at", ""))
264
+ age = datetime.now() - created_at
265
+ return age > timedelta(hours=max_age_hours)
266
+ except (OSError, json.JSONDecodeError, ValueError):
267
+ return True
268
+
269
+
270
+ # Module-level cache instances (lazy-loaded)
271
+ _cache_instances: dict[str, ProgressiveCache] = {}
272
+
273
+
274
+ def get_cache(cache_dir: str | None = None) -> ProgressiveCache:
275
+ """Get or create global cache instance.
276
+
277
+ Args:
278
+ cache_dir: Custom cache directory (uses default if None)
279
+
280
+ Returns:
281
+ ProgressiveCache instance
282
+ """
283
+ # Use cache_dir as key, or 'default' if None
284
+ key = cache_dir or "default"
285
+
286
+ if key not in _cache_instances:
287
+ _cache_instances[key] = ProgressiveCache(cache_dir)
288
+
289
+ return _cache_instances[key]