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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- 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())
|