algo-cli-runtime 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. algo_cli/__init__.py +3 -0
  2. algo_cli/__main__.py +7 -0
  3. algo_cli/_internal/__init__.py +12 -0
  4. algo_cli/_internal/policy_chain.py +259 -0
  5. algo_cli/action_registry.py +1047 -0
  6. algo_cli/agent_blocks.py +550 -0
  7. algo_cli/agent_pipeline.py +1457 -0
  8. algo_cli/agent_threads.py +308 -0
  9. algo_cli/animations.py +316 -0
  10. algo_cli/cache_admission.py +209 -0
  11. algo_cli/capability_mask.py +66 -0
  12. algo_cli/chat_protocol.py +116 -0
  13. algo_cli/chatgpt_auth.py +510 -0
  14. algo_cli/chatgpt_client.py +657 -0
  15. algo_cli/code_rag.py +479 -0
  16. algo_cli/config.py +651 -0
  17. algo_cli/context_budget.py +679 -0
  18. algo_cli/credential_helpers.py +315 -0
  19. algo_cli/deliberation.py +29 -0
  20. algo_cli/display.py +1470 -0
  21. algo_cli/evals/__init__.py +21 -0
  22. algo_cli/evals/algorithm_effectiveness.py +560 -0
  23. algo_cli/evals/competitive_harness_rating.py +702 -0
  24. algo_cli/evals/cot_quality.py +220 -0
  25. algo_cli/evals/harness_retrieval_benchmark.py +401 -0
  26. algo_cli/evals/performance_regression.py +136 -0
  27. algo_cli/evals/scorecard_grading.py +308 -0
  28. algo_cli/evals/session_distribution.py +84 -0
  29. algo_cli/execution_guardrails.py +806 -0
  30. algo_cli/extensions_manifest.py +84 -0
  31. algo_cli/git_evidence.py +227 -0
  32. algo_cli/google_workspace.py +407 -0
  33. algo_cli/google_workspace_auth.py +523 -0
  34. algo_cli/harness.py +2587 -0
  35. algo_cli/identity.py +557 -0
  36. algo_cli/index_compute_lab.py +228 -0
  37. algo_cli/inference_harness.py +70 -0
  38. algo_cli/intelligence/__init__.py +1103 -0
  39. algo_cli/intelligence/acrobat_config.py +307 -0
  40. algo_cli/intelligence/acrobat_manifests.py +338 -0
  41. algo_cli/intelligence/acrobat_models.py +195 -0
  42. algo_cli/intelligence/acrobat_pipeline.py +295 -0
  43. algo_cli/intelligence/acrobat_runtime.py +302 -0
  44. algo_cli/intelligence/acrobat_security.py +261 -0
  45. algo_cli/intelligence/acrobat_workflows.py +226 -0
  46. algo_cli/intelligence/actionability.py +165 -0
  47. algo_cli/intelligence/adversarial_audit.py +136 -0
  48. algo_cli/intelligence/agent_arena.py +92 -0
  49. algo_cli/intelligence/agent_benchmark.py +236 -0
  50. algo_cli/intelligence/agent_runtime.py +171 -0
  51. algo_cli/intelligence/agents_as_tools.py +70 -0
  52. algo_cli/intelligence/artifact_binding.py +80 -0
  53. algo_cli/intelligence/autonomous_engineer.py +1976 -0
  54. algo_cli/intelligence/backpressure.py +99 -0
  55. algo_cli/intelligence/bloom_filter.py +186 -0
  56. algo_cli/intelligence/bonferroni.py +66 -0
  57. algo_cli/intelligence/boundary_compaction.py +98 -0
  58. algo_cli/intelligence/catalog_verifier.py +172 -0
  59. algo_cli/intelligence/cavecrew.py +118 -0
  60. algo_cli/intelligence/changelog.py +176 -0
  61. algo_cli/intelligence/checkpoint_resume.py +92 -0
  62. algo_cli/intelligence/circuit_breaker.py +88 -0
  63. algo_cli/intelligence/clarification_gate.py +101 -0
  64. algo_cli/intelligence/code_graph.py +180 -0
  65. algo_cli/intelligence/coderank.py +97 -0
  66. algo_cli/intelligence/consistent_hash.py +150 -0
  67. algo_cli/intelligence/consortium_synthesis.py +139 -0
  68. algo_cli/intelligence/construction/__init__.py +241 -0
  69. algo_cli/intelligence/construction/common.py +273 -0
  70. algo_cli/intelligence/construction/documents.py +496 -0
  71. algo_cli/intelligence/construction/labor_units.py +1395 -0
  72. algo_cli/intelligence/construction/payments.py +470 -0
  73. algo_cli/intelligence/construction/risk.py +784 -0
  74. algo_cli/intelligence/content_extractor.py +132 -0
  75. algo_cli/intelligence/context_adaptive.py +102 -0
  76. algo_cli/intelligence/context_ops.py +95 -0
  77. algo_cli/intelligence/count_min.py +145 -0
  78. algo_cli/intelligence/cow_state.py +103 -0
  79. algo_cli/intelligence/critic_loop.py +119 -0
  80. algo_cli/intelligence/cross_source.py +113 -0
  81. algo_cli/intelligence/daemon_mode.py +99 -0
  82. algo_cli/intelligence/dag_orchestration.py +151 -0
  83. algo_cli/intelligence/deep_research.py +155 -0
  84. algo_cli/intelligence/degenerate_detector.py +78 -0
  85. algo_cli/intelligence/delta_report.py +92 -0
  86. algo_cli/intelligence/discovery_event_log.py +92 -0
  87. algo_cli/intelligence/document_ingest.py +298 -0
  88. algo_cli/intelligence/dual_layer_validate.py +151 -0
  89. algo_cli/intelligence/echo_fidelity.py +73 -0
  90. algo_cli/intelligence/ema_tuning.py +104 -0
  91. algo_cli/intelligence/event_log.py +92 -0
  92. algo_cli/intelligence/evidence_graph.py +114 -0
  93. algo_cli/intelligence/extension_host.py +162 -0
  94. algo_cli/intelligence/extension_manifest.py +115 -0
  95. algo_cli/intelligence/falsification_suite.py +178 -0
  96. algo_cli/intelligence/finance/__init__.py +169 -0
  97. algo_cli/intelligence/finance/anomalies.py +135 -0
  98. algo_cli/intelligence/finance/ap_ar.py +351 -0
  99. algo_cli/intelligence/finance/cash.py +162 -0
  100. algo_cli/intelligence/finance/close.py +332 -0
  101. algo_cli/intelligence/finance/common.py +244 -0
  102. algo_cli/intelligence/finance/construction.py +135 -0
  103. algo_cli/intelligence/finance/controls.py +172 -0
  104. algo_cli/intelligence/finance/evidence.py +119 -0
  105. algo_cli/intelligence/finance/exceptions.py +157 -0
  106. algo_cli/intelligence/finance/reconciliations.py +254 -0
  107. algo_cli/intelligence/finance/revenue.py +109 -0
  108. algo_cli/intelligence/finance/tax.py +74 -0
  109. algo_cli/intelligence/finance/workpapers.py +111 -0
  110. algo_cli/intelligence/finding_record.py +120 -0
  111. algo_cli/intelligence/flow_dag.py +267 -0
  112. algo_cli/intelligence/gatherer.py +223 -0
  113. algo_cli/intelligence/golden_master.py +98 -0
  114. algo_cli/intelligence/graph_rag.py +195 -0
  115. algo_cli/intelligence/group_chat.py +143 -0
  116. algo_cli/intelligence/hash_dedup.py +145 -0
  117. algo_cli/intelligence/hyperloglog.py +128 -0
  118. algo_cli/intelligence/incremental_index.py +316 -0
  119. algo_cli/intelligence/index_store.py +16 -0
  120. algo_cli/intelligence/iteration_plan.py +133 -0
  121. algo_cli/intelligence/kernel_plugins.py +167 -0
  122. algo_cli/intelligence/lesson_catalog.py +135 -0
  123. algo_cli/intelligence/llm_fallback.py +169 -0
  124. algo_cli/intelligence/log2_histogram.py +267 -0
  125. algo_cli/intelligence/lsp_integration.py +147 -0
  126. algo_cli/intelligence/memory_evolution.py +117 -0
  127. algo_cli/intelligence/minhash_lsh.py +182 -0
  128. algo_cli/intelligence/multi_model_score.py +174 -0
  129. algo_cli/intelligence/multi_tier_grade.py +211 -0
  130. algo_cli/intelligence/negative_controls.py +113 -0
  131. algo_cli/intelligence/numeric_clamp.py +63 -0
  132. algo_cli/intelligence/occ_editor.py +66 -0
  133. algo_cli/intelligence/output_normalize.py +112 -0
  134. algo_cli/intelligence/parallel_delegation.py +98 -0
  135. algo_cli/intelligence/parallel_fanout.py +104 -0
  136. algo_cli/intelligence/permission_modes.py +105 -0
  137. algo_cli/intelligence/pre_push_gate.py +68 -0
  138. algo_cli/intelligence/prefetch.py +171 -0
  139. algo_cli/intelligence/process_framework.py +217 -0
  140. algo_cli/intelligence/project_graph.py +387 -0
  141. algo_cli/intelligence/query_expansion.py +146 -0
  142. algo_cli/intelligence/ralph_loop.py +117 -0
  143. algo_cli/intelligence/rate_limiter.py +153 -0
  144. algo_cli/intelligence/refactor_transaction.py +94 -0
  145. algo_cli/intelligence/research_workspace.py +108 -0
  146. algo_cli/intelligence/retraction_ledger.py +72 -0
  147. algo_cli/intelligence/saga_pattern.py +88 -0
  148. algo_cli/intelligence/session_fork.py +100 -0
  149. algo_cli/intelligence/shadow_editor.py +67 -0
  150. algo_cli/intelligence/shell_session.py +213 -0
  151. algo_cli/intelligence/source_registry.py +143 -0
  152. algo_cli/intelligence/spawn_scales.py +99 -0
  153. algo_cli/intelligence/stat_stability.py +104 -0
  154. algo_cli/intelligence/structural_validator.py +148 -0
  155. algo_cli/intelligence/subagent_spawner.py +111 -0
  156. algo_cli/intelligence/symmetric_verify.py +70 -0
  157. algo_cli/intelligence/task_classifier.py +129 -0
  158. algo_cli/intelligence/team_execution.py +122 -0
  159. algo_cli/intelligence/tiered_access.py +121 -0
  160. algo_cli/intelligence/utility_registry.py +159 -0
  161. algo_cli/intuition_engine.py +560 -0
  162. algo_cli/intuition_injector.py +82 -0
  163. algo_cli/kernels/__init__.py +5 -0
  164. algo_cli/kernels/manifest.py +763 -0
  165. algo_cli/main.py +3903 -0
  166. algo_cli/memory_candidates.py +541 -0
  167. algo_cli/memory_echo_veil.py +394 -0
  168. algo_cli/memory_runtime.py +112 -0
  169. algo_cli/model_info.py +548 -0
  170. algo_cli/model_profile.py +160 -0
  171. algo_cli/model_routing.py +74 -0
  172. algo_cli/oneshot.py +331 -0
  173. algo_cli/perf_telemetry.py +389 -0
  174. algo_cli/plugins.py +245 -0
  175. algo_cli/private_event_store.py +654 -0
  176. algo_cli/quantization/__init__.py +24 -0
  177. algo_cli/quantization/lloyd_max.py +98 -0
  178. algo_cli/quantization/turbo_quant.py +308 -0
  179. algo_cli/reasoning/__init__.py +46 -0
  180. algo_cli/reasoning/combinatorial.py +356 -0
  181. algo_cli/reasoning/graph_of_thought.py +297 -0
  182. algo_cli/reasoning/mcts.py +220 -0
  183. algo_cli/reasoning/neuro_symbolic.py +250 -0
  184. algo_cli/reasoning/react.py +246 -0
  185. algo_cli/reasoning/reflexion.py +225 -0
  186. algo_cli/reasoning/tree_of_thought.py +241 -0
  187. algo_cli/reasoning_bridge.py +150 -0
  188. algo_cli/reconciliation.py +284 -0
  189. algo_cli/reflex.py +385 -0
  190. algo_cli/resources/docs/ALGO.md +13958 -0
  191. algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
  192. algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
  193. algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
  194. algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
  195. algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
  196. algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
  197. algo_cli/resources/docs/main-split-map.md +35 -0
  198. algo_cli/resources/docs/privacy-and-context.md +48 -0
  199. algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
  200. algo_cli/resources/skills/README.md +26 -0
  201. algo_cli/resources/skills/algo-cli.md +59 -0
  202. algo_cli/resources/skills/edit-file-precision.md +49 -0
  203. algo_cli/resources/skills/harness-search-first.md +47 -0
  204. algo_cli/resources/skills/memory-recall-ritual.md +51 -0
  205. algo_cli/resources/skills/qol-algorithms.md +224 -0
  206. algo_cli/resources/skills/smart-error-recovery.md +56 -0
  207. algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
  208. algo_cli/retrieval_algorithms.py +127 -0
  209. algo_cli/runtime_qos.py +236 -0
  210. algo_cli/runtime_services.py +320 -0
  211. algo_cli/session_commands.py +95 -0
  212. algo_cli/session_mode.py +113 -0
  213. algo_cli/skills.py +430 -0
  214. algo_cli/slash_dispatch.py +1265 -0
  215. algo_cli/small_context.py +206 -0
  216. algo_cli/spawn_budget.py +89 -0
  217. algo_cli/task_ledger.py +84 -0
  218. algo_cli/task_router.py +197 -0
  219. algo_cli/tool_context.py +94 -0
  220. algo_cli/tool_contract.py +99 -0
  221. algo_cli/tool_policy.py +357 -0
  222. algo_cli/tool_runtime.py +647 -0
  223. algo_cli/tools.py +3056 -0
  224. algo_cli/url_scheme.py +174 -0
  225. algo_cli/verify.py +154 -0
  226. algo_cli/version_manifest.py +178 -0
  227. algo_cli/vision_screenshot_verify.py +76 -0
  228. algo_cli/workspace_resolver.py +68 -0
  229. algo_cli/x_account.py +209 -0
  230. algo_cli/xai_auth.py +374 -0
  231. algo_cli/xai_client.py +600 -0
  232. algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
  233. algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
  234. algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
  235. algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
  236. algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
  237. ollama_cli/__init__.py +67 -0
@@ -0,0 +1,307 @@
1
+ """B166, B167, B170, B177: Acrobat-derived config/dispatch/theme/telemetry patterns.
2
+
3
+ - B166: Locale-Sharded Resources with Default Fallback
4
+ - B167: Service Endpoint Dispatch Table
5
+ - B170: State-Based Declarative Theming
6
+ - B177: Telemetry Endpoint Config Separation
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+
15
+ # ── B166: Locale-Sharded Resources with Default Fallback ─────────────
16
+
17
+
18
+ @dataclass
19
+ class LocaleResource:
20
+ """A locale-specific resource (B166)."""
21
+ locale: str
22
+ resource_id: str
23
+ content: Any = None
24
+ fallback_used: bool = False
25
+
26
+
27
+ class LocaleResourceStore:
28
+ """Locale-sharded resources with deterministic fallback (B166)."""
29
+
30
+ def __init__(self, default_locale: str = "ENU") -> None:
31
+ self._default = default_locale
32
+ self._resources: dict[tuple[str, str], LocaleResource] = {}
33
+
34
+ def register(self, locale: str, resource_id: str, content: Any) -> None:
35
+ self._resources[(locale, resource_id)] = LocaleResource(
36
+ locale=locale, resource_id=resource_id, content=content,
37
+ )
38
+
39
+ def get(self, locale: str, resource_id: str) -> LocaleResource | None:
40
+ """Get a resource with fallback: exact locale -> language -> default."""
41
+ # Try exact locale
42
+ key = (locale, resource_id)
43
+ if key in self._resources:
44
+ return self._resources[key]
45
+
46
+ # Try language prefix
47
+ lang = locale.split("_")[0].upper()
48
+ key = (lang, resource_id)
49
+ if key in self._resources:
50
+ r = self._resources[key]
51
+ return LocaleResource(
52
+ locale=lang, resource_id=resource_id,
53
+ content=r.content, fallback_used=True,
54
+ )
55
+
56
+ # Try default
57
+ key = (self._default, resource_id)
58
+ if key in self._resources:
59
+ r = self._resources[key]
60
+ return LocaleResource(
61
+ locale=self._default, resource_id=resource_id,
62
+ content=r.content, fallback_used=True,
63
+ )
64
+ return None
65
+
66
+ def available_locales(self) -> list[str]:
67
+ return sorted({loc for loc, _ in self._resources.keys()})
68
+
69
+ def has_resource(self, locale: str, resource_id: str) -> bool:
70
+ return (locale, resource_id) in self._resources
71
+
72
+
73
+ # ── B167: Service Endpoint Dispatch Table ────────────────────────────
74
+
75
+
76
+ @dataclass
77
+ class ServiceEndpoint:
78
+ """A single service endpoint (B167)."""
79
+ operation: str
80
+ url: str
81
+ version: str = "v1"
82
+ scope: str = "GM" # "PR" (pre-release) or "GM" (general release)
83
+
84
+
85
+ @dataclass
86
+ class DispatchTable:
87
+ """A dispatch table for service endpoints (B167)."""
88
+ scope: str = "GM"
89
+ endpoints: dict[str, ServiceEndpoint] = field(default_factory=dict)
90
+ signature: str = ""
91
+
92
+ def add(self, endpoint: ServiceEndpoint) -> None:
93
+ self.endpoints[endpoint.operation] = endpoint
94
+
95
+ def resolve(self, operation: str) -> ServiceEndpoint | None:
96
+ """Resolve an operation to its endpoint."""
97
+ return self.endpoints.get(operation)
98
+
99
+ def operations(self) -> list[str]:
100
+ return list(self.endpoints.keys())
101
+
102
+
103
+ class ServiceDispatchRegistry:
104
+ """Registry of dispatch tables with scope variants (B167)."""
105
+
106
+ def __init__(self) -> None:
107
+ self._tables: dict[str, DispatchTable] = {}
108
+ self._active_scope: str = "GM"
109
+
110
+ def register_table(self, table: DispatchTable) -> None:
111
+ self._tables[table.scope] = table
112
+
113
+ def set_scope(self, scope: str) -> None:
114
+ self._active_scope = scope
115
+
116
+ def resolve(self, operation: str) -> ServiceEndpoint | None:
117
+ """Resolve an operation using the active scope's dispatch table."""
118
+ table = self._tables.get(self._active_scope)
119
+ if not table:
120
+ return None
121
+ return table.resolve(operation)
122
+
123
+ def available_scopes(self) -> list[str]:
124
+ return list(self._tables.keys())
125
+
126
+ def all_operations(self) -> list[str]:
127
+ table = self._tables.get(self._active_scope)
128
+ return table.operations() if table else []
129
+
130
+
131
+ # ── B170: State-Based Declarative Theming ─────────────────────────────
132
+
133
+
134
+ @dataclass
135
+ class ThemeStyle:
136
+ """A style with state variants (B170)."""
137
+ style_id: str
138
+ states: dict[str, str] = field(default_factory=dict) # state -> color
139
+
140
+ def get(self, state: str = "normal") -> str | None:
141
+ """Get the color for a state, falling back to normal."""
142
+ return self.states.get(state, self.states.get("normal"))
143
+
144
+
145
+ @dataclass
146
+ class Theme:
147
+ """A declarative theme (B170)."""
148
+ theme_id: str
149
+ title: str = ""
150
+ icon_set: str = ""
151
+ styles: dict[str, ThemeStyle] = field(default_factory=dict)
152
+
153
+ def get_color(self, style_id: str, state: str = "normal") -> str | None:
154
+ """Resolve a color for a style + state."""
155
+ style = self.styles.get(style_id)
156
+ if not style:
157
+ return None
158
+ return style.get(state)
159
+
160
+
161
+ class ThemeManager:
162
+ """Manages themes with swap support (B170)."""
163
+
164
+ def __init__(self) -> None:
165
+ self._themes: dict[str, Theme] = {}
166
+ self._active: str = ""
167
+
168
+ def register(self, theme: Theme) -> None:
169
+ self._themes[theme.theme_id] = theme
170
+ if not self._active:
171
+ self._active = theme.theme_id
172
+
173
+ def set_active(self, theme_id: str) -> bool:
174
+ if theme_id in self._themes:
175
+ self._active = theme_id
176
+ return True
177
+ return False
178
+
179
+ def get_active(self) -> Theme | None:
180
+ return self._themes.get(self._active)
181
+
182
+ def get_color(self, style_id: str, state: str = "normal") -> str | None:
183
+ theme = self.get_active()
184
+ if not theme:
185
+ return None
186
+ return theme.get_color(style_id, state)
187
+
188
+ def all_themes(self) -> list[str]:
189
+ return list(self._themes.keys())
190
+
191
+
192
+ def default_dark_theme() -> Theme:
193
+ """Create a default dark theme from Acrobat's DarkTheme.acrotheme."""
194
+ return Theme(
195
+ theme_id="DarkTheme",
196
+ title="Dark Theme",
197
+ icon_set="DarkIcons",
198
+ styles={
199
+ "RegularBackground": ThemeStyle("RegularBackground", {"normal": "0x4D4D4D"}),
200
+ "ProminentBackground": ThemeStyle("ProminentBackground", {"normal": "0x424242", "hover": "0x3A3A3A"}),
201
+ "DocumentBackground": ThemeStyle("DocumentBackground", {"normal": "0x282828"}),
202
+ "Border": ThemeStyle("Border", {"normal": "0x3A3A3A"}),
203
+ "Icon": ThemeStyle("Icon", {
204
+ "normal": "0xC2C2C2", "hover": "0x51AAFE",
205
+ "active": "0x2175C8", "checked": "0x51AAFE",
206
+ }),
207
+ "RegularText": ThemeStyle("RegularText", {
208
+ "normal": "0xEAEAEA", "hover": "0x51AAFE",
209
+ "active": "0x2175C8", "checked": "0x51AAFE",
210
+ "disabled": "0x9B9B9B",
211
+ }),
212
+ "Hyperlink": ThemeStyle("Hyperlink", {"normal": "0x70D4FF", "hover": "0x1DBBFF"}),
213
+ },
214
+ )
215
+
216
+
217
+ def default_light_theme() -> Theme:
218
+ """Create a default light theme from Acrobat's LightTheme.acrotheme."""
219
+ return Theme(
220
+ theme_id="LightTheme",
221
+ title="Light Theme",
222
+ icon_set="LightIcons",
223
+ styles={
224
+ "RegularBackground": ThemeStyle("RegularBackground", {"normal": "0xFAFAFA"}),
225
+ "ProminentBackground": ThemeStyle("ProminentBackground", {"normal": "0xEAEAEA", "hover": "0xE1E1E1"}),
226
+ "DocumentBackground": ThemeStyle("DocumentBackground", {"normal": "0x999999"}),
227
+ "Border": ThemeStyle("Border", {"normal": "0xCBCBCB"}),
228
+ "Icon": ThemeStyle("Icon", {
229
+ "normal": "0x6F6F6F", "hover": "0x2175C8",
230
+ "active": "0x0E539B", "checked": "0x2175C8",
231
+ }),
232
+ "RegularText": ThemeStyle("RegularText", {
233
+ "normal": "0x4D4D4D", "hover": "0x2175C8",
234
+ "active": "0x0E539B", "checked": "0x2175C8",
235
+ "disabled": "0x949494",
236
+ }),
237
+ "Hyperlink": ThemeStyle("Hyperlink", {"normal": "0x0252A3"}),
238
+ },
239
+ )
240
+
241
+
242
+ # ── B177: Telemetry Endpoint Config Separation ────────────────────────
243
+
244
+
245
+ @dataclass
246
+ class TelemetryEndpoint:
247
+ """A telemetry endpoint (B177)."""
248
+ name: str
249
+ url: str
250
+ port: int = 443
251
+
252
+
253
+ class TelemetryConfig:
254
+ """Telemetry endpoint configuration loaded from a config file (B177)."""
255
+
256
+ def __init__(self) -> None:
257
+ self._endpoints: dict[str, TelemetryEndpoint] = {}
258
+ self._enabled: bool = True
259
+
260
+ def set_endpoint(self, name: str, url: str, port: int = 443) -> None:
261
+ self._endpoints[name] = TelemetryEndpoint(name=name, url=url, port=port)
262
+
263
+ def get_endpoint(self, name: str) -> TelemetryEndpoint | None:
264
+ if not self._enabled:
265
+ return None
266
+ return self._endpoints.get(name)
267
+
268
+ def load_from_lines(self, lines: list[str]) -> None:
269
+ """Load config from key=value lines."""
270
+ for line in lines:
271
+ line = line.strip()
272
+ if not line or line.startswith("#"):
273
+ continue
274
+ if "=" not in line:
275
+ continue
276
+ key, value = line.split("=", 1)
277
+ key = key.strip()
278
+ value = value.strip()
279
+ if key.endswith("-url"):
280
+ name = key[:-4]
281
+ port = 443
282
+ port_key = f"{name}-port"
283
+ if port_key in self._endpoints:
284
+ port = self._endpoints[port_key].port
285
+ self.set_endpoint(name, value, port)
286
+ elif key.endswith("-port"):
287
+ name = key[:-5]
288
+ if name in self._endpoints:
289
+ self._endpoints[name].port = int(value)
290
+ else:
291
+ self._endpoints[name] = TelemetryEndpoint(name=name, url="", port=int(value))
292
+
293
+ def disable(self) -> None:
294
+ self._enabled = False
295
+
296
+ def enable(self) -> None:
297
+ self._enabled = True
298
+
299
+ def is_enabled(self) -> bool:
300
+ return self._enabled
301
+
302
+ def all_endpoints(self) -> list[str]:
303
+ return list(self._endpoints.keys())
304
+
305
+ def missing_endpoints(self, required: list[str]) -> list[str]:
306
+ """Return names of required endpoints that are missing."""
307
+ return [name for name in required if name not in self._endpoints]
@@ -0,0 +1,338 @@
1
+ """B154-B156, B162, B168, B171: Acrobat-derived manifest/tool/dependency patterns.
2
+
3
+ - B154: Declarative Tool/App Manifest UI
4
+ - B155: Edition / Entitlement Overlay Manifests
5
+ - B156: Plugin Silo + Host Adapter Tree
6
+ - B162: Web Plugin Version Map and Resource Provenance
7
+ - B168: Explicit Dependency Manifest
8
+ - B171: Declarative Task/Filter Registry
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Callable
15
+
16
+
17
+ # ── B154: Declarative Tool/App Manifest UI ─────────────────────────────
18
+
19
+
20
+ @dataclass
21
+ class UIComponent:
22
+ """A single UI component in a declarative tool manifest."""
23
+ type: str
24
+ name: str = ""
25
+ label: str = ""
26
+ children: list["UIComponent"] = field(default_factory=list)
27
+ attributes: dict[str, Any] = field(default_factory=dict)
28
+
29
+
30
+ @dataclass
31
+ class ToolCommand:
32
+ """A command declared in a tool manifest."""
33
+ name: str
34
+ execute_after_layout: bool = False
35
+ prompt_user: bool = False
36
+
37
+
38
+ @dataclass
39
+ class ToolManifest:
40
+ """Declarative tool/app manifest (B154).
41
+
42
+ Defines tools, panels, buttons, commands, and layouts without
43
+ hardcoding UI in Python.
44
+ """
45
+ id: str
46
+ title: str
47
+ version: str = "1.0"
48
+ requires_context: bool = False
49
+ commands: list[ToolCommand] = field(default_factory=list)
50
+ layouts: dict[str, list[UIComponent]] = field(default_factory=dict)
51
+ metadata: dict[str, str] = field(default_factory=dict)
52
+
53
+ def validate(self, command_registry: set[str], component_registry: set[str]) -> list[str]:
54
+ """Validate manifest against registries. Returns list of errors (empty = valid)."""
55
+ errors: list[str] = []
56
+ for cmd in self.commands:
57
+ if cmd.name not in command_registry:
58
+ errors.append(f"unknown command: {cmd.name}")
59
+ for layout_name, components in self.layouts.items():
60
+ for comp in components:
61
+ if comp.name and comp.name not in component_registry:
62
+ errors.append(f"unbound component '{comp.name}' in layout '{layout_name}'")
63
+ return errors
64
+
65
+
66
+ class ToolManifestLoader:
67
+ """Loads and validates tool manifests."""
68
+
69
+ def __init__(self) -> None:
70
+ self._manifests: dict[str, ToolManifest] = {}
71
+ self._command_registry: set[str] = set()
72
+ self._component_registry: set[str] = set()
73
+
74
+ def register_command(self, name: str) -> None:
75
+ self._command_registry.add(name)
76
+
77
+ def register_component(self, name: str) -> None:
78
+ self._component_registry.add(name)
79
+
80
+ def load(self, manifest: ToolManifest) -> list[str]:
81
+ """Load a manifest, returning validation errors. Empty list = success."""
82
+ errors = manifest.validate(self._command_registry, self._component_registry)
83
+ if not errors:
84
+ self._manifests[manifest.id] = manifest
85
+ return errors
86
+
87
+ def get(self, manifest_id: str) -> ToolManifest | None:
88
+ return self._manifests.get(manifest_id)
89
+
90
+ def all_manifests(self) -> list[ToolManifest]:
91
+ return list(self._manifests.values())
92
+
93
+
94
+ # ── B155: Edition / Entitlement Overlay Manifests ──────────────────────
95
+
96
+
97
+ @dataclass
98
+ class EditionOverlay:
99
+ """An edition overlay for a tool (B155)."""
100
+ edition: str # "reader", "pro", "enterprise", "local", "cloud"
101
+ disabled_commands: list[str] = field(default_factory=list)
102
+ hidden_components: list[str] = field(default_factory=list)
103
+ metadata_overrides: dict[str, str] = field(default_factory=dict)
104
+ paywall_message: str = ""
105
+
106
+
107
+ @dataclass
108
+ class ResolvedManifest:
109
+ """The effective manifest after overlay resolution."""
110
+ manifest: ToolManifest
111
+ disabled_commands: set[str] = field(default_factory=set)
112
+ hidden_components: set[str] = field(default_factory=set)
113
+ paywall_message: str = ""
114
+ edition: str = ""
115
+
116
+
117
+ class EditionResolver:
118
+ """Resolves base tool manifest + edition overlay into effective manifest."""
119
+
120
+ def __init__(self) -> None:
121
+ self._overlays: dict[tuple[str, str], EditionOverlay] = {}
122
+
123
+ def register_overlay(self, tool_id: str, overlay: EditionOverlay) -> None:
124
+ self._overlays[(tool_id, overlay.edition)] = overlay
125
+
126
+ def resolve(self, tool_id: str, edition: str, entitled: bool = True) -> ResolvedManifest | None:
127
+ """Resolve a tool manifest with edition overlay."""
128
+ # In a real system, this would look up the base manifest
129
+ # For testing, we accept a simple resolution
130
+ overlay = self._overlays.get((tool_id, edition))
131
+ if overlay is None:
132
+ return None
133
+ if not entitled:
134
+ # Return a paywall-only manifest
135
+ return ResolvedManifest(
136
+ manifest=ToolManifest(id=tool_id, title=""),
137
+ disabled_commands=set(overlay.disabled_commands),
138
+ hidden_components=set(overlay.hidden_components),
139
+ paywall_message=overlay.paywall_message or "Not entitled",
140
+ edition=edition,
141
+ )
142
+ return ResolvedManifest(
143
+ manifest=ToolManifest(id=tool_id, title=""),
144
+ disabled_commands=set(overlay.disabled_commands),
145
+ hidden_components=set(overlay.hidden_components),
146
+ paywall_message="",
147
+ edition=edition,
148
+ )
149
+
150
+
151
+ # ── B156: Plugin Silo + Host Adapter Tree ─────────────────────────────
152
+
153
+
154
+ @dataclass
155
+ class HostAdapter:
156
+ """A host-specific adapter for a plugin (B156)."""
157
+ host: str # "word", "outlook", "autocad", etc.
158
+ version: str # "2019", "365", etc.
159
+ platform: str # "win", "mac", "linux"
160
+ path: str = ""
161
+ available: bool = True
162
+
163
+
164
+ @dataclass
165
+ class PluginSilo:
166
+ """A plugin silo with core capability and host adapters (B156)."""
167
+ id: str
168
+ name: str
169
+ core_api_version: str = "1.0"
170
+ adapters: list[HostAdapter] = field(default_factory=list)
171
+ shared_common: str = "" # path to shared common utilities
172
+
173
+ def select_adapter(self, host: str, version: str, platform: str) -> HostAdapter | None:
174
+ """Select the best adapter for the given host/version/platform."""
175
+ # Try exact match first
176
+ for adapter in self.adapters:
177
+ if (
178
+ adapter.host == host
179
+ and adapter.version == version
180
+ and adapter.platform == platform
181
+ and adapter.available
182
+ ):
183
+ return adapter
184
+ # Try host + platform, any version
185
+ for adapter in self.adapters:
186
+ if adapter.host == host and adapter.platform == platform and adapter.available:
187
+ return adapter
188
+ # Try host only
189
+ for adapter in self.adapters:
190
+ if adapter.host == host and adapter.available:
191
+ return adapter
192
+ return None
193
+
194
+ def unsupported_hosts(self) -> list[str]:
195
+ """List hosts that have no available adapters."""
196
+ hosts_with_adapters = {a.host for a in self.adapters if a.available}
197
+ return sorted(set(a.host for a in self.adapters) - hosts_with_adapters)
198
+
199
+
200
+ class PluginSiloRegistry:
201
+ """Registry of plugin silos."""
202
+
203
+ def __init__(self) -> None:
204
+ self._silos: dict[str, PluginSilo] = {}
205
+
206
+ def register(self, silo: PluginSilo) -> None:
207
+ self._silos[silo.id] = silo
208
+
209
+ def get(self, silo_id: str) -> PluginSilo | None:
210
+ return self._silos.get(silo_id)
211
+
212
+ def all_silos(self) -> list[PluginSilo]:
213
+ return list(self._silos.values())
214
+
215
+
216
+ # ── B162: Web Plugin Version Map and Resource Provenance ───────────────
217
+
218
+
219
+ @dataclass
220
+ class ResourceProvenance:
221
+ """Provenance for a web resource (B162)."""
222
+ plugin_ids: list[str]
223
+ schema_version: int = 1
224
+ git_path: str = ""
225
+ git_commit: str = ""
226
+ git_repo: str = ""
227
+
228
+
229
+ @dataclass
230
+ class PluginGroup:
231
+ """A group of plugins with shared provenance (B162)."""
232
+ name: str
233
+ provenance: ResourceProvenance
234
+
235
+ def resolves(self, plugin_id: str) -> bool:
236
+ return plugin_id in self.provenance.plugin_ids
237
+
238
+
239
+ class WebPluginVersionMap:
240
+ """Maps plugin IDs to code provenance, resource bundles, and versions."""
241
+
242
+ def __init__(self) -> None:
243
+ self._groups: dict[str, PluginGroup] = {}
244
+
245
+ def register(self, group: PluginGroup) -> None:
246
+ self._groups[group.name] = group
247
+
248
+ def resolve(self, plugin_id: str) -> PluginGroup | None:
249
+ """Resolve a plugin ID to its group."""
250
+ matches = [g for g in self._groups.values() if g.resolves(plugin_id)]
251
+ if len(matches) == 1:
252
+ return matches[0]
253
+ if len(matches) > 1:
254
+ # Ambiguous - return first but caller should check
255
+ return matches[0]
256
+ return None
257
+
258
+ def provenance(self, plugin_id: str) -> ResourceProvenance | None:
259
+ group = self.resolve(plugin_id)
260
+ return group.provenance if group else None
261
+
262
+ def all_groups(self) -> list[PluginGroup]:
263
+ return list(self._groups.values())
264
+
265
+
266
+ # ── B168: Explicit Dependency Manifest ─────────────────────────────────
267
+
268
+
269
+ @dataclass
270
+ class DependencyEntry:
271
+ """A single dependency declaration (B168)."""
272
+ name: str
273
+ required: bool = True
274
+ version: str = ""
275
+
276
+
277
+ @dataclass
278
+ class DependencyManifest:
279
+ """Explicit dependency manifest (B168)."""
280
+ identity_name: str
281
+ version: str = "1.0.0"
282
+ arch: str = "amd64"
283
+ files: list[DependencyEntry] = field(default_factory=list)
284
+ external_dependencies: list[str] = field(default_factory=list)
285
+
286
+ def check(self, existing_files: set[str]) -> tuple[list[str], list[str]]:
287
+ """Check dependencies against existing files.
288
+ Returns (missing_required, missing_optional).
289
+ """
290
+ missing_required: list[str] = []
291
+ missing_optional: list[str] = []
292
+ for dep in self.files:
293
+ if dep.name not in existing_files:
294
+ if dep.required:
295
+ missing_required.append(dep.name)
296
+ else:
297
+ missing_optional.append(dep.name)
298
+ return missing_required, missing_optional
299
+
300
+
301
+ # ── B171: Declarative Task/Filter Registry ─────────────────────────────
302
+
303
+
304
+ @dataclass
305
+ class TaskFilter:
306
+ """A task/filter mapping (B171)."""
307
+ menu_item_id: str
308
+ filter_id: str
309
+ handler: Callable[[dict], Any] | None = None
310
+
311
+
312
+ class TaskFilterRegistry:
313
+ """Maps user-facing menu items to format handlers via filter IDs (B171)."""
314
+
315
+ def __init__(self) -> None:
316
+ self._tasks: dict[str, TaskFilter] = {}
317
+ self._handlers: dict[str, Callable[[dict], Any]] = {}
318
+
319
+ def register(self, task: TaskFilter) -> None:
320
+ self._tasks[task.menu_item_id] = task
321
+ if task.handler:
322
+ self._handlers[task.filter_id] = task.handler
323
+
324
+ def register_handler(self, filter_id: str, handler: Callable[[dict], Any]) -> None:
325
+ self._handlers[filter_id] = handler
326
+
327
+ def dispatch(self, menu_item_id: str, context: dict | None = None) -> Any:
328
+ """Dispatch a menu item to its handler."""
329
+ task = self._tasks.get(menu_item_id)
330
+ if not task:
331
+ raise KeyError(f"unknown task: {menu_item_id}")
332
+ handler = self._handlers.get(task.filter_id)
333
+ if not handler:
334
+ raise KeyError(f"no handler for filter: {task.filter_id}")
335
+ return handler(context or {})
336
+
337
+ def available_tasks(self) -> list[str]:
338
+ return list(self._tasks.keys())