@oneciel-ai/ciel-runtime 0.1.1 → 0.2.0

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 (287) hide show
  1. package/README.md +132 -0
  2. package/ciel-runtime-menu.py +56 -6
  3. package/ciel_runtime.py +9379 -35909
  4. package/ciel_runtime_support/advisor_client.py +193 -0
  5. package/ciel_runtime_support/advisor_policy.py +320 -0
  6. package/ciel_runtime_support/advisor_refinement.py +160 -0
  7. package/ciel_runtime_support/advisor_request_builder.py +261 -0
  8. package/ciel_runtime_support/agy_installer.py +169 -0
  9. package/ciel_runtime_support/agy_mcp_restore.py +182 -0
  10. package/ciel_runtime_support/anthropic_model_policy.py +186 -0
  11. package/ciel_runtime_support/anthropic_response_writer.py +255 -0
  12. package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
  13. package/ciel_runtime_support/api_key_cooldown.py +159 -0
  14. package/ciel_runtime_support/architecture.py +488 -1
  15. package/ciel_runtime_support/architecture_budget.py +42 -0
  16. package/ciel_runtime_support/channel_backlog.py +90 -0
  17. package/ciel_runtime_support/channel_cli.py +119 -0
  18. package/ciel_runtime_support/channel_compact_injection.py +82 -0
  19. package/ciel_runtime_support/channel_compact_poll.py +67 -0
  20. package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
  21. package/ciel_runtime_support/channel_config_service.py +281 -0
  22. package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
  23. package/ciel_runtime_support/channel_connection_registry.py +128 -0
  24. package/ciel_runtime_support/channel_connection_worker.py +284 -0
  25. package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
  26. package/ciel_runtime_support/channel_cursor_repository.py +89 -0
  27. package/ciel_runtime_support/channel_cursor_service.py +178 -0
  28. package/ciel_runtime_support/channel_event_identity.py +212 -0
  29. package/ciel_runtime_support/channel_event_projection.py +315 -0
  30. package/ciel_runtime_support/channel_inflight.py +127 -0
  31. package/ciel_runtime_support/channel_injection.py +115 -0
  32. package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
  33. package/ciel_runtime_support/channel_launch_policy.py +180 -0
  34. package/ciel_runtime_support/channel_llm_context.py +156 -0
  35. package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
  36. package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
  37. package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
  38. package/ciel_runtime_support/channel_mcp_tools.py +240 -0
  39. package/ciel_runtime_support/channel_mcp_transport.py +394 -0
  40. package/ciel_runtime_support/channel_message_dedupe.py +65 -0
  41. package/ciel_runtime_support/channel_message_policy.py +256 -0
  42. package/ciel_runtime_support/channel_message_prompt.py +305 -0
  43. package/ciel_runtime_support/channel_message_repository.py +234 -0
  44. package/ciel_runtime_support/channel_notification_projection.py +217 -0
  45. package/ciel_runtime_support/channel_panel.py +162 -0
  46. package/ciel_runtime_support/channel_pending_injection.py +209 -0
  47. package/ciel_runtime_support/channel_pending_poll.py +109 -0
  48. package/ciel_runtime_support/channel_probe_cache.py +433 -0
  49. package/ciel_runtime_support/channel_probe_report.py +101 -0
  50. package/ciel_runtime_support/channel_runtime_environment.py +181 -0
  51. package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
  52. package/ciel_runtime_support/channel_session_repository.py +90 -0
  53. package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
  54. package/ciel_runtime_support/channel_terminal_input.py +277 -0
  55. package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
  56. package/ciel_runtime_support/channel_tool_context.py +166 -0
  57. package/ciel_runtime_support/channel_transcript.py +414 -0
  58. package/ciel_runtime_support/channel_transcript_repository.py +96 -0
  59. package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
  60. package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
  61. package/ciel_runtime_support/chat_files.py +138 -0
  62. package/ciel_runtime_support/chat_http_controller.py +235 -0
  63. package/ciel_runtime_support/claude_environment.py +375 -0
  64. package/ciel_runtime_support/claude_router.py +247 -193
  65. package/ciel_runtime_support/cli_dispatch.py +792 -0
  66. package/ciel_runtime_support/cli_parser.py +165 -0
  67. package/ciel_runtime_support/cli_usage.py +100 -0
  68. package/ciel_runtime_support/codex_app_server.py +20 -5
  69. package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
  70. package/ciel_runtime_support/codex_cli.py +42 -6
  71. package/ciel_runtime_support/codex_config.py +323 -0
  72. package/ciel_runtime_support/codex_launch_configuration.py +240 -0
  73. package/ciel_runtime_support/codex_launch_policy.py +66 -0
  74. package/ciel_runtime_support/codex_mcp_integration.py +195 -0
  75. package/ciel_runtime_support/codex_mcp_restore.py +304 -0
  76. package/ciel_runtime_support/codex_model_catalog.py +133 -0
  77. package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
  78. package/ciel_runtime_support/codex_router.py +147 -1
  79. package/ciel_runtime_support/codex_session_repository.py +115 -0
  80. package/ciel_runtime_support/codex_session_selection.py +114 -0
  81. package/ciel_runtime_support/command_asset_installer.py +103 -0
  82. package/ciel_runtime_support/compatibility_probe.py +295 -0
  83. package/ciel_runtime_support/compatibility_protocol.py +251 -0
  84. package/ciel_runtime_support/compatibility_runtime.py +166 -0
  85. package/ciel_runtime_support/compatibility_test.py +370 -0
  86. package/ciel_runtime_support/config_migrations.py +307 -0
  87. package/ciel_runtime_support/config_repository.py +175 -0
  88. package/ciel_runtime_support/config_value_codec.py +64 -0
  89. package/ciel_runtime_support/configuration_cli.py +374 -0
  90. package/ciel_runtime_support/context_compaction.py +280 -0
  91. package/ciel_runtime_support/context_setup.py +208 -0
  92. package/ciel_runtime_support/context_summary_policy.py +392 -0
  93. package/ciel_runtime_support/credential_cli.py +104 -0
  94. package/ciel_runtime_support/credential_management.py +261 -0
  95. package/ciel_runtime_support/credentials.py +269 -0
  96. package/ciel_runtime_support/executable_discovery.py +141 -0
  97. package/ciel_runtime_support/github_copilot_oauth.py +335 -0
  98. package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
  99. package/ciel_runtime_support/header_forwarding.py +73 -0
  100. package/ciel_runtime_support/headless_config.py +221 -0
  101. package/ciel_runtime_support/http_response.py +129 -0
  102. package/ciel_runtime_support/install_diagnostics.py +149 -0
  103. package/ciel_runtime_support/kimi_identity.py +123 -0
  104. package/ciel_runtime_support/launch_diagnostics.py +204 -0
  105. package/ciel_runtime_support/launch_state.py +127 -0
  106. package/ciel_runtime_support/live_api_key_controller.py +58 -0
  107. package/ciel_runtime_support/llm_config_http.py +148 -0
  108. package/ciel_runtime_support/llm_option_config.py +259 -0
  109. package/ciel_runtime_support/llm_presentation_data.py +447 -0
  110. package/ciel_runtime_support/llm_presets.py +773 -0
  111. package/ciel_runtime_support/lm_studio_runtime.py +401 -0
  112. package/ciel_runtime_support/managed_mcp_config.py +144 -0
  113. package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
  114. package/ciel_runtime_support/managed_service_cleanup.py +89 -0
  115. package/ciel_runtime_support/mcp_config_reader.py +230 -0
  116. package/ciel_runtime_support/mcp_http_proxy.py +607 -0
  117. package/ciel_runtime_support/mcp_inventory.py +59 -0
  118. package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
  119. package/ciel_runtime_support/mcp_probe_codec.py +136 -0
  120. package/ciel_runtime_support/mcp_probe_transport.py +328 -0
  121. package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
  122. package/ciel_runtime_support/mcp_proxy_config.py +107 -0
  123. package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
  124. package/ciel_runtime_support/mcp_proxy_process.py +560 -0
  125. package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
  126. package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
  127. package/ciel_runtime_support/mcp_transport.py +146 -0
  128. package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
  129. package/ciel_runtime_support/model_catalog_projection.py +61 -0
  130. package/ciel_runtime_support/model_context_hints.py +109 -0
  131. package/ciel_runtime_support/model_panel.py +147 -0
  132. package/ciel_runtime_support/model_registry_repository.py +231 -0
  133. package/ciel_runtime_support/npm_runtime.py +191 -0
  134. package/ciel_runtime_support/ollama_catalog.py +462 -0
  135. package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
  136. package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
  137. package/ciel_runtime_support/ollama_context_sync.py +87 -0
  138. package/ciel_runtime_support/ollama_forwarding.py +449 -0
  139. package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
  140. package/ciel_runtime_support/openai_chat_router.py +64 -0
  141. package/ciel_runtime_support/openai_forwarding.py +194 -0
  142. package/ciel_runtime_support/openai_responses_router.py +291 -0
  143. package/ciel_runtime_support/openai_responses_stream.py +135 -0
  144. package/ciel_runtime_support/output_budget.py +89 -0
  145. package/ciel_runtime_support/package_lifecycle.py +217 -0
  146. package/ciel_runtime_support/plan_artifact_controller.py +104 -0
  147. package/ciel_runtime_support/prelaunch.py +959 -0
  148. package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
  149. package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
  150. package/ciel_runtime_support/prelaunch_terminal.py +764 -0
  151. package/ciel_runtime_support/process_control.py +708 -0
  152. package/ciel_runtime_support/prompt_compaction.py +322 -0
  153. package/ciel_runtime_support/prompt_injection.py +176 -0
  154. package/ciel_runtime_support/protocols/__init__.py +24 -0
  155. package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
  156. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
  157. package/ciel_runtime_support/protocols/chat_projection.py +315 -0
  158. package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
  159. package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
  160. package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
  161. package/ciel_runtime_support/protocols/ollama_response.py +231 -0
  162. package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
  163. package/ciel_runtime_support/protocols/openai_responses.py +271 -0
  164. package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
  165. package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
  166. package/ciel_runtime_support/provider_adapters.py +160 -0
  167. package/ciel_runtime_support/provider_catalog_sources.py +316 -0
  168. package/ciel_runtime_support/provider_choice.py +201 -0
  169. package/ciel_runtime_support/provider_compatibility.py +165 -0
  170. package/ciel_runtime_support/provider_config_mutations.py +361 -0
  171. package/ciel_runtime_support/provider_configuration_service.py +162 -0
  172. package/ciel_runtime_support/provider_context.py +308 -0
  173. package/ciel_runtime_support/provider_contract_projection.py +73 -0
  174. package/ciel_runtime_support/provider_descriptor.py +82 -0
  175. package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
  176. package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
  177. package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
  178. package/ciel_runtime_support/provider_limits.py +457 -0
  179. package/ciel_runtime_support/provider_model_identity.py +139 -0
  180. package/ciel_runtime_support/provider_model_selection.py +431 -0
  181. package/ciel_runtime_support/provider_model_specs.py +142 -0
  182. package/ciel_runtime_support/provider_models.py +263 -0
  183. package/ciel_runtime_support/provider_network.py +176 -0
  184. package/ciel_runtime_support/provider_option_cli.py +238 -0
  185. package/ciel_runtime_support/provider_option_panel.py +275 -0
  186. package/ciel_runtime_support/provider_option_status.py +192 -0
  187. package/ciel_runtime_support/provider_policy.py +101 -0
  188. package/ciel_runtime_support/provider_query_policy.py +67 -0
  189. package/ciel_runtime_support/provider_readiness.py +112 -0
  190. package/ciel_runtime_support/provider_request_access.py +131 -0
  191. package/ciel_runtime_support/provider_request_builder.py +250 -0
  192. package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
  193. package/ciel_runtime_support/provider_runtime_info.py +113 -0
  194. package/ciel_runtime_support/provider_runtime_modes.py +150 -0
  195. package/ciel_runtime_support/provider_sampling_policy.py +46 -0
  196. package/ciel_runtime_support/provider_status.py +145 -0
  197. package/ciel_runtime_support/provider_timeout_policy.py +184 -0
  198. package/ciel_runtime_support/provider_tool_policy.py +145 -0
  199. package/ciel_runtime_support/providers/__init__.py +65 -0
  200. package/ciel_runtime_support/providers/anthropic.py +160 -0
  201. package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
  202. package/ciel_runtime_support/providers/base.py +232 -0
  203. package/ciel_runtime_support/providers/catalog.py +326 -0
  204. package/ciel_runtime_support/providers/cloud.py +194 -0
  205. package/ciel_runtime_support/providers/constants.py +54 -0
  206. package/ciel_runtime_support/providers/deepseek.py +115 -0
  207. package/ciel_runtime_support/providers/fireworks.py +158 -0
  208. package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
  209. package/ciel_runtime_support/providers/kimi.py +297 -0
  210. package/ciel_runtime_support/providers/lm_studio.py +76 -0
  211. package/ciel_runtime_support/providers/meta.py +257 -0
  212. package/ciel_runtime_support/providers/native.py +194 -0
  213. package/ciel_runtime_support/providers/nim.py +69 -0
  214. package/ciel_runtime_support/providers/nvidia.py +158 -0
  215. package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
  216. package/ciel_runtime_support/providers/ollama.py +181 -0
  217. package/ciel_runtime_support/providers/ollama_context.py +195 -0
  218. package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
  219. package/ciel_runtime_support/providers/opencode.py +220 -0
  220. package/ciel_runtime_support/providers/opencode_go.py +37 -0
  221. package/ciel_runtime_support/providers/openrouter.py +57 -0
  222. package/ciel_runtime_support/providers/vllm.py +65 -0
  223. package/ciel_runtime_support/providers/zai.py +119 -0
  224. package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
  225. package/ciel_runtime_support/rate_limit_policy.py +117 -0
  226. package/ciel_runtime_support/rate_limit_repository.py +154 -0
  227. package/ciel_runtime_support/registry.py +46 -0
  228. package/ciel_runtime_support/request_shortcuts.py +253 -0
  229. package/ciel_runtime_support/request_trace.py +323 -0
  230. package/ciel_runtime_support/response_collection.py +209 -0
  231. package/ciel_runtime_support/router_access.py +238 -0
  232. package/ciel_runtime_support/router_client_lifecycle.py +366 -0
  233. package/ciel_runtime_support/router_health_policy.py +101 -0
  234. package/ciel_runtime_support/router_http.py +513 -0
  235. package/ciel_runtime_support/router_process_lifecycle.py +401 -0
  236. package/ciel_runtime_support/router_rate_limit_service.py +285 -0
  237. package/ciel_runtime_support/router_server_runtime.py +103 -0
  238. package/ciel_runtime_support/router_shortcuts.py +201 -0
  239. package/ciel_runtime_support/routing_fallback.py +73 -0
  240. package/ciel_runtime_support/runtime_activity_repository.py +143 -0
  241. package/ciel_runtime_support/runtime_adapters.py +104 -0
  242. package/ciel_runtime_support/runtime_command_factory.py +73 -0
  243. package/ciel_runtime_support/runtime_compatibility.py +50 -0
  244. package/ciel_runtime_support/runtime_constants.py +178 -0
  245. package/ciel_runtime_support/runtime_launch.py +1602 -0
  246. package/ciel_runtime_support/runtime_llm_options.py +312 -0
  247. package/ciel_runtime_support/runtime_logging.py +161 -0
  248. package/ciel_runtime_support/runtime_paths.py +157 -0
  249. package/ciel_runtime_support/runtime_restart.py +84 -0
  250. package/ciel_runtime_support/runtime_upgrade.py +149 -0
  251. package/ciel_runtime_support/secure_json_repository.py +55 -0
  252. package/ciel_runtime_support/session_import.py +356 -0
  253. package/ciel_runtime_support/settings_repository.py +8 -0
  254. package/ciel_runtime_support/slash_command_assets.py +211 -0
  255. package/ciel_runtime_support/sse_stream.py +57 -0
  256. package/ciel_runtime_support/sse_trace.py +225 -0
  257. package/ciel_runtime_support/statusline_script.py +593 -0
  258. package/ciel_runtime_support/statusline_settings.py +53 -0
  259. package/ciel_runtime_support/stream_chunk_policy.py +18 -0
  260. package/ciel_runtime_support/streaming_anthropic.py +1955 -0
  261. package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
  262. package/ciel_runtime_support/terminal_platform_io.py +127 -0
  263. package/ciel_runtime_support/timeout_profile.py +196 -0
  264. package/ciel_runtime_support/tool_dialects.py +85 -0
  265. package/ciel_runtime_support/tool_exposure_policy.py +63 -0
  266. package/ciel_runtime_support/tool_guard_hooks.py +218 -0
  267. package/ciel_runtime_support/tool_request_projection.py +96 -0
  268. package/ciel_runtime_support/tool_schema.py +483 -0
  269. package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
  270. package/ciel_runtime_support/ui_text.py +266 -0
  271. package/ciel_runtime_support/upstream_error_policy.py +104 -0
  272. package/ciel_runtime_support/upstream_retry.py +419 -0
  273. package/ciel_runtime_support/upstream_stream_io.py +106 -0
  274. package/ciel_runtime_support/usage_events.py +96 -0
  275. package/ciel_runtime_support/visible_stream_filters.py +130 -0
  276. package/ciel_runtime_support/web_endpoints.py +447 -0
  277. package/ciel_runtime_support/web_ui.py +915 -0
  278. package/ciel_runtime_support/web_ui_controller.py +189 -0
  279. package/ciel_runtime_support/windows_console_input.py +137 -0
  280. package/ciel_runtime_support/windows_console_mode.py +112 -0
  281. package/docs/Architecture.md +54 -0
  282. package/docs/Configuration.md +17 -0
  283. package/docs/Module-Map.md +1093 -26
  284. package/docs/Providers.md +46 -1
  285. package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
  286. package/npm-bin/run-ciel-runtime.js +22 -2
  287. package/package.json +9 -2
@@ -0,0 +1,312 @@
1
+ """Runtime LLM option snapshot, restore, slider, and status controller."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from collections.abc import Callable, Mapping, Sequence
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class RuntimeLlmSettings:
13
+ option_keys: frozenset[str]
14
+ original_key: str
15
+ slider_labels: Mapping[str, str]
16
+ ollama_providers: frozenset[str] = frozenset(("ollama", "ollama-cloud"))
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class RuntimeLlmConfigPorts:
21
+ load: Callable[[], dict[str, Any]]
22
+ save: Callable[[dict[str, Any]], None]
23
+ clear_model_cache: Callable[[], None]
24
+ deep_copy: Callable[[Any], Any]
25
+ current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
26
+ normalize_preset: Callable[[str], str]
27
+ resolve_preset: Callable[[str], str | None]
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class RuntimeLlmPresentationPorts:
32
+ applied_preset: Callable[[str, dict[str, Any]], str]
33
+ slider_presets: Callable[[], Sequence[str]]
34
+ preset_text: Callable[[str, str], tuple[str, str]]
35
+ provider_label: Callable[[str, dict[str, Any]], str]
36
+ context_status: Callable[[str, dict[str, Any]], str]
37
+ timeout_status: Callable[[dict[str, Any], str], str]
38
+ ollama_options: Callable[[dict[str, Any]], dict[str, Any]]
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class RuntimeLlmMutationPorts:
43
+ apply_preset: Callable[..., list[str]]
44
+
45
+
46
+ class RuntimeLlmOptionsController:
47
+ def __init__(
48
+ self,
49
+ settings: RuntimeLlmSettings,
50
+ config: RuntimeLlmConfigPorts,
51
+ presentation: RuntimeLlmPresentationPorts,
52
+ mutation: RuntimeLlmMutationPorts,
53
+ ) -> None:
54
+ self.settings = settings
55
+ self.config = config
56
+ self.presentation = presentation
57
+ self.mutation = mutation
58
+
59
+ def snapshot(self, provider: str, provider_config: dict[str, Any]) -> dict[str, Any]:
60
+ values = {
61
+ key: self.config.deep_copy(provider_config[key])
62
+ for key in sorted(self.settings.option_keys)
63
+ if key in provider_config
64
+ }
65
+ return {
66
+ "version": 1,
67
+ "captured_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
68
+ "provider": provider,
69
+ "model": str(provider_config.get("current_model") or ""),
70
+ "values": values,
71
+ }
72
+
73
+ def ensure_snapshot(self, provider: str, provider_config: dict[str, Any]) -> bool:
74
+ existing = provider_config.get(self.settings.original_key)
75
+ if isinstance(existing, dict) and isinstance(existing.get("values"), dict):
76
+ return False
77
+ provider_config[self.settings.original_key] = self.snapshot(
78
+ provider,
79
+ provider_config,
80
+ )
81
+ return True
82
+
83
+ def restore(self, provider: str) -> list[str]:
84
+ config = self.config.load()
85
+ provider_config = config["providers"][provider]
86
+ snapshot = provider_config.get(self.settings.original_key)
87
+ if not isinstance(snapshot, dict) or not isinstance(snapshot.get("values"), dict):
88
+ return ["No captured live LLM options to restore."]
89
+ values = self.config.deep_copy(snapshot.get("values") or {})
90
+ for key in self.settings.option_keys:
91
+ provider_config.pop(key, None)
92
+ provider_config.update(values)
93
+ provider_config.pop(self.settings.original_key, None)
94
+ self._persist(config)
95
+ return [
96
+ "Restored live LLM options to the values captured before the first runtime preset change.",
97
+ f"Captured provider/model: {snapshot.get('provider') or provider} / "
98
+ f"{snapshot.get('model') or 'unknown'}",
99
+ ]
100
+
101
+ def apply_preset(self, provider: str, preset_id: str) -> list[str]:
102
+ config = self.config.load()
103
+ provider_config = config["providers"][provider]
104
+ captured = self.ensure_snapshot(provider, provider_config)
105
+ lines = self.mutation.apply_preset(
106
+ provider,
107
+ provider_config,
108
+ preset_id,
109
+ config.get("language", "en"),
110
+ )
111
+ if captured:
112
+ lines.insert(0, "Captured current live LLM options for /llm-restore.")
113
+ self._persist(config)
114
+ return lines
115
+
116
+ def slider_line(self, provider: str, provider_config: dict[str, Any]) -> str:
117
+ current = self.presentation.applied_preset(provider, provider_config)
118
+ parts = []
119
+ for preset_id in self.presentation.slider_presets():
120
+ label = self.settings.slider_labels.get(preset_id, preset_id)
121
+ parts.append(f"[{label}]" if preset_id == current else label)
122
+ return "< " + " | ".join(parts) + " >"
123
+
124
+ def apply_slider_delta(self, provider: str, delta: int) -> list[str]:
125
+ config = self.config.load()
126
+ provider_config = config["providers"][provider]
127
+ presets = list(self.presentation.slider_presets())
128
+ current = self.presentation.applied_preset(provider, provider_config)
129
+ try:
130
+ current_index = presets.index(current)
131
+ except ValueError:
132
+ current_index = 0
133
+ next_index = max(0, min(len(presets) - 1, current_index + delta))
134
+ next_preset = presets[next_index]
135
+ language = config.get("language", "en")
136
+ if next_index == current_index:
137
+ label = self.presentation.preset_text(next_preset, language)[0]
138
+ return [
139
+ f"Live LLM preset remains at {label}.",
140
+ f"Slider: {self.slider_line(provider, provider_config)}",
141
+ ]
142
+ captured = self.ensure_snapshot(provider, provider_config)
143
+ lines = self.mutation.apply_preset(
144
+ provider,
145
+ provider_config,
146
+ next_preset,
147
+ language,
148
+ )
149
+ if captured:
150
+ lines.insert(0, "Captured current live LLM options for /llm-restore.")
151
+ self._persist(config)
152
+ label = self.presentation.preset_text(next_preset, language)[0]
153
+ return [f"Live LLM preset moved to {label}."] + lines + [
154
+ f"Slider: {self.slider_line(provider, provider_config)}"
155
+ ]
156
+
157
+ def status_lines(
158
+ self,
159
+ provider: str,
160
+ provider_config: dict[str, Any],
161
+ ) -> list[str]:
162
+ language = self.config.load().get("language", "en")
163
+ applied = self.presentation.applied_preset(provider, provider_config)
164
+ lines = [
165
+ f"Provider: {self.presentation.provider_label(provider, provider_config)}",
166
+ f"Model: {provider_config.get('current_model') or 'unknown'}",
167
+ f"Preset: {applied} ({self.presentation.preset_text(applied, language)[0]})",
168
+ f"Slider: {self.slider_line(provider, provider_config)}",
169
+ f"Context: {self.presentation.context_status(provider, provider_config)}",
170
+ f"Timeout: {self.presentation.timeout_status(provider_config, language)}",
171
+ ]
172
+ if provider in self.settings.ollama_providers:
173
+ options = self.presentation.ollama_options(provider_config)
174
+ lines.append(f"Output tokens: {options.get('num_predict', 'default')}")
175
+ else:
176
+ lines.append(
177
+ f"Output tokens: {provider_config.get('max_output_tokens', 'default')}"
178
+ )
179
+ restore_available = isinstance(
180
+ provider_config.get(self.settings.original_key),
181
+ dict,
182
+ )
183
+ lines.append(f"Restore available: {'yes' if restore_available else 'no'}")
184
+ return lines
185
+
186
+ def preset_list_lines(
187
+ self,
188
+ provider: str,
189
+ provider_config: dict[str, Any],
190
+ ) -> list[str]:
191
+ language = self.config.load().get("language", "en")
192
+ applied = self.presentation.applied_preset(provider, provider_config)
193
+ lines = self.status_lines(provider, provider_config)
194
+ lines.extend(
195
+ (
196
+ "",
197
+ "Use `/llm left` or `/llm right` to move one step, or `/llm <preset-id>` to jump directly.",
198
+ "Preset ids:",
199
+ )
200
+ )
201
+ for preset_id in self.presentation.slider_presets():
202
+ label, description = self.presentation.preset_text(preset_id, language)
203
+ mark = "*" if preset_id == applied else " "
204
+ lines.append(f"{mark} {preset_id} — {label}: {description}")
205
+ lines.extend(
206
+ (
207
+ " /llm-restore restore captured original options",
208
+ " /llm <left|right|preset-id|status|list|restore>",
209
+ )
210
+ )
211
+ return lines
212
+
213
+ def handle_action(
214
+ self,
215
+ action: str = "status",
216
+ preset: str = "",
217
+ ) -> tuple[list[str], bool]:
218
+ config = self.config.load()
219
+ provider, provider_config = self.config.current_provider(config)
220
+ raw_action = str(action or "").strip()
221
+ raw_preset = str(preset or "").strip()
222
+ if not raw_action and raw_preset:
223
+ raw_action = "apply"
224
+ value = raw_preset or raw_action
225
+ normalized = self.config.normalize_preset(value)
226
+ if normalized in {"", "status", "state", "show", "current", "now"}:
227
+ return self.status_lines(provider, provider_config), False
228
+ if normalized in {
229
+ "list", "presets", "preset", "help", "options", "menu", "select"
230
+ }:
231
+ return self.preset_list_lines(provider, provider_config), False
232
+ if normalized in {
233
+ "left", "prev", "previous", "backward", "back", "decrease", "minus"
234
+ }:
235
+ return self._slider_action(provider, -1)
236
+ if normalized in {"right", "next", "forward", "increase", "plus"}:
237
+ return self._slider_action(provider, 1)
238
+ if normalized in {"restore", "original", "reset", "revert", "undo"}:
239
+ had_snapshot = isinstance(
240
+ provider_config.get(self.settings.original_key), dict
241
+ )
242
+ lines = self.restore(provider)
243
+ config_after = self.config.load()
244
+ _provider_after, provider_config_after = self.config.current_provider(
245
+ config_after
246
+ )
247
+ return lines + [""] + self.status_lines(
248
+ provider, provider_config_after
249
+ ), had_snapshot
250
+ preset_id = self.config.resolve_preset(value)
251
+ if not preset_id:
252
+ return [
253
+ f"Unknown live LLM preset/action: {value or raw_action or raw_preset}",
254
+ "Use `/llm-options list` to see available presets, or `/llm-restore` to revert.",
255
+ ], False
256
+ lines = self.apply_preset(provider, preset_id)
257
+ return self._updated_lines(provider, lines), True
258
+
259
+ def _slider_action(self, provider: str, delta: int) -> tuple[list[str], bool]:
260
+ return self._updated_lines(
261
+ provider,
262
+ self.apply_slider_delta(provider, delta),
263
+ ), True
264
+
265
+ def _updated_lines(self, provider: str, lines: list[str]) -> list[str]:
266
+ config_after = self.config.load()
267
+ _provider_after, provider_config_after = self.config.current_provider(
268
+ config_after
269
+ )
270
+ return (
271
+ lines
272
+ + ["", "Updated live LLM options. The next model request uses these settings."]
273
+ + self.status_lines(provider, provider_config_after)
274
+ )
275
+
276
+ def _persist(self, config: dict[str, Any]) -> None:
277
+ self.config.save(config)
278
+ self.config.clear_model_cache()
279
+
280
+
281
+ @dataclass(frozen=True, slots=True)
282
+ class RuntimeLlmOptionsApi:
283
+ """Explicit public adapter for a late-bound runtime options controller."""
284
+
285
+ controller_factory: Callable[[], RuntimeLlmOptionsController]
286
+
287
+ def handle_live_llm_options_action(self, action: str = "status", preset: str = "") -> tuple[list[str], bool]:
288
+ return self.controller_factory().handle_action(action, preset)
289
+
290
+ def snapshot_from_provider(self, provider: str, pcfg: dict[str, Any]) -> dict[str, Any]:
291
+ return self.controller_factory().snapshot(provider, pcfg)
292
+
293
+ def ensure_original_snapshot(self, provider: str, pcfg: dict[str, Any]) -> bool:
294
+ return self.controller_factory().ensure_snapshot(provider, pcfg)
295
+
296
+ def restore_original_options(self, provider: str) -> list[str]:
297
+ return self.controller_factory().restore(provider)
298
+
299
+ def apply_preset_config(self, provider: str, preset_id: str) -> list[str]:
300
+ return self.controller_factory().apply_preset(provider, preset_id)
301
+
302
+ def slider_line(self, provider: str, pcfg: dict[str, Any]) -> str:
303
+ return self.controller_factory().slider_line(provider, pcfg)
304
+
305
+ def apply_slider_delta_config(self, provider: str, delta: int) -> list[str]:
306
+ return self.controller_factory().apply_slider_delta(provider, delta)
307
+
308
+ def status_lines(self, provider: str, pcfg: dict[str, Any]) -> list[str]:
309
+ return self.controller_factory().status_lines(provider, pcfg)
310
+
311
+ def preset_list_lines(self, provider: str, pcfg: dict[str, Any]) -> list[str]:
312
+ return self.controller_factory().preset_list_lines(provider, pcfg)
@@ -0,0 +1,161 @@
1
+ """Runtime log-level repository and rotating file logger."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import time
7
+ from collections.abc import Callable
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
14
+ LOG_LEVEL_NAMES = {value: name for name, value in LOG_LEVELS.items()}
15
+
16
+
17
+ def normalize_log_level(value: str) -> str | None:
18
+ raw = str(value or "").strip()
19
+ if not raw:
20
+ raise ValueError("log level is empty")
21
+ upper = {
22
+ "OFF": "SILENT",
23
+ "NONE": "SILENT",
24
+ "QUIET": "SILENT",
25
+ "WARNING": "WARN",
26
+ "WARNINGS": "WARN",
27
+ }.get(raw.upper(), raw.upper())
28
+ if upper in ("DEFAULT", "RESET", "UNSET", "AUTO"):
29
+ return None
30
+ if upper in LOG_LEVELS:
31
+ return upper
32
+ if upper.isdigit():
33
+ return LOG_LEVEL_NAMES[max(0, min(5, int(upper)))]
34
+ raise ValueError(f"unknown log level: {value}")
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class LogLevelRepository:
39
+ config_dir: Path
40
+ path: Path
41
+ cache: dict[str, Any]
42
+ default_level: int
43
+ environ: dict[str, str]
44
+
45
+ def current(self) -> int:
46
+ now = time.time()
47
+ if self.cache["value"] is not None and now - float(self.cache["checked_at"]) < 1.0:
48
+ return int(self.cache["value"])
49
+ level = self._file_level(now)
50
+ if level is None:
51
+ value = self.environ.get("CIEL_RUNTIME_LOG_LEVEL", "").strip().upper()
52
+ if value in LOG_LEVELS:
53
+ level = LOG_LEVELS[value]
54
+ elif value.isdigit():
55
+ level = max(0, min(5, int(value)))
56
+ if level is None:
57
+ level = self.default_level
58
+ self.cache.update(value=level, checked_at=now)
59
+ return level
60
+
61
+ def _file_level(self, now: float) -> int | None:
62
+ try:
63
+ if not self.path.exists():
64
+ return None
65
+ modified = self.path.stat().st_mtime
66
+ if self.cache["value"] is not None and modified == self.cache["file_mtime"]:
67
+ self.cache["checked_at"] = now
68
+ return int(self.cache["value"])
69
+ text = self.path.read_text(encoding="utf-8").strip().upper()
70
+ self.cache["file_mtime"] = modified
71
+ if text in LOG_LEVELS:
72
+ return LOG_LEVELS[text]
73
+ if text.isdigit():
74
+ return max(0, min(5, int(text)))
75
+ except Exception:
76
+ pass
77
+ return None
78
+
79
+ def reset_cache(self) -> None:
80
+ self.cache.update(value=None, checked_at=0.0, file_mtime=0.0)
81
+
82
+ def name(self, value: int | None = None) -> str:
83
+ effective = self.current() if value is None else value
84
+ return str(LOG_LEVEL_NAMES.get(int(effective), effective))
85
+
86
+ def source(self) -> str:
87
+ if self.path.exists():
88
+ return "file"
89
+ if self.environ.get("CIEL_RUNTIME_LOG_LEVEL", "").strip():
90
+ return "env"
91
+ return "default"
92
+
93
+ def status(self) -> str:
94
+ return f"{self.name()} ({self.source()})"
95
+
96
+ def set(self, value: str) -> list[str]:
97
+ try:
98
+ level = normalize_log_level(value)
99
+ except ValueError as error:
100
+ return [f"{error}. Known levels: {', '.join(LOG_LEVELS)}, DEFAULT."]
101
+ self.config_dir.mkdir(parents=True, exist_ok=True)
102
+ if level is None:
103
+ try:
104
+ self.path.unlink()
105
+ except FileNotFoundError:
106
+ pass
107
+ self.reset_cache()
108
+ return [f"Log level reset to {self.status()}."]
109
+ self.path.write_text(level + "\n", encoding="utf-8")
110
+ try:
111
+ os.chmod(self.path, 0o600)
112
+ except Exception:
113
+ pass
114
+ self.reset_cache()
115
+ return [f"Log level set to {level}."]
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class LogLevelApi:
120
+ """Explicit compatibility adapter for a late-bound log repository."""
121
+
122
+ repository_factory: Callable[[], LogLevelRepository]
123
+
124
+ def current(self) -> int:
125
+ return self.repository_factory().current()
126
+
127
+ def reset_cache(self) -> None:
128
+ self.repository_factory().reset_cache()
129
+
130
+ def name(self, value: int | None = None) -> str:
131
+ return self.repository_factory().name(value)
132
+
133
+ def source(self) -> str:
134
+ return self.repository_factory().source()
135
+
136
+ def status(self) -> str:
137
+ return self.repository_factory().status()
138
+
139
+ def set(self, value: str) -> list[str]:
140
+ return self.repository_factory().set(value)
141
+
142
+
143
+ @dataclass(frozen=True, slots=True)
144
+ class RouterFileLogger:
145
+ config_dir: Path
146
+ path: Path
147
+ max_bytes: int
148
+ levels: LogLevelRepository
149
+
150
+ def write(self, level: str, message: str) -> None:
151
+ threshold = LOG_LEVELS.get(level, 0)
152
+ if threshold <= 0 or threshold > self.levels.current():
153
+ return
154
+ try:
155
+ self.config_dir.mkdir(parents=True, exist_ok=True)
156
+ if self.path.exists() and self.path.stat().st_size > self.max_bytes:
157
+ self.path.replace(self.path.with_suffix(".log.1"))
158
+ with self.path.open("a", encoding="utf-8") as handle:
159
+ handle.write(f"{time.strftime('%Y-%m-%dT%H:%M:%S')} [{level}] {message}\n")
160
+ except Exception:
161
+ pass
@@ -0,0 +1,157 @@
1
+ """Platform-aware filesystem and local router endpoint configuration."""
2
+ from __future__ import annotations
3
+
4
+ import getpass
5
+ import hashlib
6
+ import os
7
+ import sys
8
+ from pathlib import Path, PureWindowsPath
9
+ from typing import Any
10
+
11
+ from .web_endpoints import apply_startup_web_options, load_saved_web_backend
12
+
13
+
14
+ # Web startup flags affect the process-wide router endpoint constants below.
15
+ sys.argv[:] = apply_startup_web_options(sys.argv, os.environ)
16
+
17
+
18
+ HOME = Path.home()
19
+
20
+
21
+ def platform_path(value: str | os.PathLike[str]) -> Any:
22
+ if os.name == "nt" and sys.platform != "win32":
23
+ return PureWindowsPath(value)
24
+ return Path(value)
25
+
26
+
27
+ def windows_appdata_root() -> Path:
28
+ for env_name in ("APPDATA", "LOCALAPPDATA"):
29
+ raw = os.environ.get(env_name)
30
+ if raw:
31
+ return platform_path(raw)
32
+ return HOME / "AppData" / "Roaming"
33
+
34
+
35
+ def windows_local_appdata_root() -> Path:
36
+ raw = os.environ.get("LOCALAPPDATA")
37
+ if raw:
38
+ return platform_path(raw)
39
+ return HOME / "AppData" / "Local"
40
+
41
+
42
+ def platform_config_dir(app_name: str) -> Path:
43
+ if os.name == "nt":
44
+ return windows_appdata_root() / app_name
45
+ return HOME / ".config" / app_name
46
+
47
+
48
+ def ciel_runtime_user_bin_dir() -> Path:
49
+ if os.name == "nt":
50
+ return windows_local_appdata_root() / "ciel-runtime" / "bin"
51
+ return HOME / ".local" / "bin"
52
+
53
+
54
+ def agy_user_bin_dir() -> Path:
55
+ if os.name == "nt":
56
+ return windows_local_appdata_root() / "agy" / "bin"
57
+ return HOME / ".local" / "bin"
58
+
59
+
60
+ def path_with_ciel_runtime_user_dirs(env: dict[str, str]) -> str:
61
+ dirs = [ciel_runtime_user_bin_dir(), agy_user_bin_dir()]
62
+ if os.name == "nt":
63
+ appdata = env.get("APPDATA") or os.environ.get("APPDATA")
64
+ if appdata:
65
+ dirs.append(platform_path(appdata) / "npm")
66
+ local_appdata = env.get("LOCALAPPDATA") or os.environ.get("LOCALAPPDATA")
67
+ if local_appdata:
68
+ dirs.append(platform_path(local_appdata) / "Programs" / "nodejs")
69
+ existing = env.get("PATH", "")
70
+ prefix = os.pathsep.join(str(path) for path in dirs if str(path))
71
+ return prefix + (os.pathsep + existing if existing else "")
72
+
73
+
74
+ def default_router_port(saved_port: int = 0) -> int:
75
+ configured = str(os.environ.get("CIEL_RUNTIME_ROUTER_PORT") or "").strip()
76
+ if configured:
77
+ try:
78
+ port = int(configured)
79
+ if 1 <= port <= 65535:
80
+ return port
81
+ except ValueError:
82
+ pass
83
+ if 1 <= saved_port <= 65535:
84
+ return saved_port
85
+ base = 8799
86
+ getuid = getattr(os, "getuid", None)
87
+ if callable(getuid):
88
+ try:
89
+ return base + (int(getuid()) % 1000)
90
+ except Exception:
91
+ pass
92
+ seed = f"{getpass.getuser()}|{HOME}"
93
+ digest = hashlib.sha256(seed.encode("utf-8", errors="replace")).hexdigest()
94
+ return base + (int(digest[:8], 16) % 1000)
95
+
96
+
97
+ CONFIG_DIR = Path(os.environ.get("CIEL_RUNTIME_CONFIG_DIR") or platform_config_dir("ciel-runtime"))
98
+ CONFIG_PATH = CONFIG_DIR / "config.json"
99
+ LOG_PATH = CONFIG_DIR / "router.log"
100
+ LOG_LEVEL_PATH = CONFIG_DIR / "log-level"
101
+ REQUEST_DUMP_PATH = CONFIG_DIR / "requests.jsonl"
102
+ RESPONSE_DUMP_PATH = CONFIG_DIR / "responses.jsonl"
103
+ USAGE_EVENTS_PATH = CONFIG_DIR / "usage-events.jsonl"
104
+ SSE_TRACE_PATH = CONFIG_DIR / "router-sse-trace.jsonl"
105
+ SSE_LAST_PATH = CONFIG_DIR / "router-last-sse.json"
106
+ TOOL_CALL_LOG_PATH = CONFIG_DIR / "tool-calls.jsonl"
107
+ RATE_LIMIT_STATE_PATH = CONFIG_DIR / "rate-limit-state.json"
108
+ ROUTER_ACTIVITY_PATH = CONFIG_DIR / "router-activity.json"
109
+ CONTEXT_COMPACT_ACTIVITY_PATH = CONFIG_DIR / "context-compact-activity.json"
110
+ CONTEXT_USAGE_PATH = CONFIG_DIR / "context-usage.json"
111
+ OLLAMA_MODEL_CATALOG_PATH = CONFIG_DIR / "ollama-model-catalog.json"
112
+ CHAT_MESSAGES_PATH = CONFIG_DIR / "chat-messages.jsonl"
113
+ CHAT_FILES_DIR = CONFIG_DIR / "chat-files"
114
+ MENU_KEY_DEBUG_PATH = CONFIG_DIR / "ca-key-debug.log"
115
+ PLAN_ARTIFACTS_DIR = CONFIG_DIR / "plan-artifacts"
116
+ PID_PATH = CONFIG_DIR / "router.pid"
117
+ ROUTER_EXTERNAL_TOKEN_PATH = CONFIG_DIR / "router-external-token"
118
+ ROUTER_CLIENTS_DIR = CONFIG_DIR / "router-clients"
119
+ MODEL_LIST_CACHE_PATH = CONFIG_DIR / "model-list-cache.json"
120
+ MODEL_REGISTRY_PATH = CONFIG_DIR / "model-registry.json"
121
+ LAUNCH_STATE_PATH = CONFIG_DIR / "launch-state.json"
122
+ WEB_TOOLS_MCP_CONFIG = CONFIG_DIR / "web-tools-mcp.json"
123
+ DUCKDUCKGO_MCP_CONFIG = CONFIG_DIR / "duckduckgo-mcp.json"
124
+ ZAI_MCP_CONFIG = CONFIG_DIR / "zai-mcp.json"
125
+ CHANNEL_MCP_CONFIG = CONFIG_DIR / "channel-mcp.json"
126
+ NATIVE_MCP_CONFIG = CONFIG_DIR / "native-mcp.json"
127
+ CODEX_MCP_CONFIG = CONFIG_DIR / "codex-mcp.json"
128
+ CODEX_PROCESS_DIR = CONFIG_DIR / "codex-processes"
129
+ CODEX_PROMPTS_DIR_NAME = "prompts"
130
+ CHANNEL_MCP_CURSOR_PATH = CONFIG_DIR / "channel-mcp-cursor.json"
131
+ CHANNEL_LLM_CURSOR_PATH = CONFIG_DIR / "channel-llm-cursor.json"
132
+ CHANNEL_LLM_CLEAR_FLOOR_PATH = CONFIG_DIR / "channel-llm-clear-floor.json"
133
+ CHANNEL_LLM_LAUNCH_GUARD_PATH = CONFIG_DIR / "channel-llm-launch-guard.json"
134
+ CHANNEL_COMPACT_REQUEST_PATH = CONFIG_DIR / "channel-compact-request.json"
135
+ CHANNEL_STDIN_WAKE_CLAIMS_PATH = CONFIG_DIR / "channel-stdin-wake-claims.json"
136
+ CHANNEL_PROBE_CACHE_PATH = CONFIG_DIR / "channel-probe-cache.json"
137
+ MCP_PROXY_CONFIG = CONFIG_DIR / "mcp-proxy.json"
138
+ _SAVED_WEB_BACKEND = load_saved_web_backend(CONFIG_PATH)
139
+ ROUTER_HOST = (
140
+ os.environ.get("CIEL_RUNTIME_ROUTER_CLIENT_HOST", "").strip()
141
+ or _SAVED_WEB_BACKEND.client_host
142
+ )
143
+ ROUTER_PORT = default_router_port(_SAVED_WEB_BACKEND.port)
144
+ ROUTER_BASE = f"http://{ROUTER_HOST}:{ROUTER_PORT}"
145
+ CLAUDE_GATEWAY_CACHE = HOME / ".claude" / "cache" / "gateway-models.json"
146
+ CLAUDE_SETTINGS_PATH = HOME / ".claude" / "settings.json"
147
+ CLAUDE_COMMANDS_DIR = HOME / ".claude" / "commands"
148
+ CIEL_RUNTIME_STATUSLINE_PATH = ciel_runtime_user_bin_dir() / "ciel-runtime-statusline.py"
149
+ NCP_ENV = platform_config_dir("nvd-claude-proxy") / ".env"
150
+ NCP_LOG = platform_config_dir("nvd-claude-proxy") / "proxy.log"
151
+
152
+
153
+ __all__ = [name for name in globals() if name.isupper() or name in {
154
+ "agy_user_bin_dir", "ciel_runtime_user_bin_dir", "default_router_port",
155
+ "path_with_ciel_runtime_user_dirs", "platform_config_dir", "platform_path",
156
+ "windows_appdata_root", "windows_local_appdata_root",
157
+ }]