illusion-code 0.1.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 (214) hide show
  1. illusion/__init__.py +24 -0
  2. illusion/__main__.py +15 -0
  3. illusion/_frontend/dist/index.mjs +39208 -0
  4. illusion/_frontend/package.json +27 -0
  5. illusion/_frontend/src/App.tsx +624 -0
  6. illusion/_frontend/src/components/CommandPicker.tsx +98 -0
  7. illusion/_frontend/src/components/Composer.tsx +55 -0
  8. illusion/_frontend/src/components/ComposerController.tsx +128 -0
  9. illusion/_frontend/src/components/ConversationView.tsx +750 -0
  10. illusion/_frontend/src/components/Footer.tsx +25 -0
  11. illusion/_frontend/src/components/MarkdownContent.tsx +537 -0
  12. illusion/_frontend/src/components/MarkdownTable.tsx +245 -0
  13. illusion/_frontend/src/components/ModalHost.tsx +425 -0
  14. illusion/_frontend/src/components/MultilineTextInput.tsx +250 -0
  15. illusion/_frontend/src/components/PromptInput.tsx +64 -0
  16. illusion/_frontend/src/components/SelectModal.tsx +78 -0
  17. illusion/_frontend/src/components/SidePanel.tsx +175 -0
  18. illusion/_frontend/src/components/Spinner.tsx +77 -0
  19. illusion/_frontend/src/components/StatusBar.tsx +142 -0
  20. illusion/_frontend/src/components/SwarmPanel.tsx +141 -0
  21. illusion/_frontend/src/components/TodoPanel.tsx +126 -0
  22. illusion/_frontend/src/components/ToolCallDisplay.tsx +202 -0
  23. illusion/_frontend/src/components/TranscriptPane.tsx +79 -0
  24. illusion/_frontend/src/components/WelcomeBanner.tsx +37 -0
  25. illusion/_frontend/src/hooks/useBackendSession.ts +468 -0
  26. illusion/_frontend/src/hooks/useTerminalSize.ts +9 -0
  27. illusion/_frontend/src/i18n.ts +78 -0
  28. illusion/_frontend/src/index.tsx +42 -0
  29. illusion/_frontend/src/theme/ThemeContext.tsx +19 -0
  30. illusion/_frontend/src/theme/builtinThemes.ts +89 -0
  31. illusion/_frontend/src/types.ts +110 -0
  32. illusion/_frontend/src/utils/markdown.ts +33 -0
  33. illusion/_frontend/src/utils/thinking.ts +191 -0
  34. illusion/_frontend/tsconfig.json +13 -0
  35. illusion/_web_dist/assets/index-BseIw-ik.css +10 -0
  36. illusion/_web_dist/assets/index-C_0ZWMuW.js +82 -0
  37. illusion/_web_dist/index.html +16 -0
  38. illusion/api/__init__.py +36 -0
  39. illusion/api/client.py +568 -0
  40. illusion/api/codex_client.py +563 -0
  41. illusion/api/compat.py +138 -0
  42. illusion/api/effort.py +128 -0
  43. illusion/api/errors.py +57 -0
  44. illusion/api/openai_client.py +819 -0
  45. illusion/api/provider.py +148 -0
  46. illusion/api/registry.py +479 -0
  47. illusion/api/usage.py +45 -0
  48. illusion/auth/__init__.py +50 -0
  49. illusion/auth/copilot.py +419 -0
  50. illusion/auth/external.py +612 -0
  51. illusion/auth/flows.py +58 -0
  52. illusion/auth/manager.py +214 -0
  53. illusion/auth/storage.py +372 -0
  54. illusion/bridge/__init__.py +38 -0
  55. illusion/bridge/manager.py +190 -0
  56. illusion/bridge/session_runner.py +84 -0
  57. illusion/bridge/types.py +113 -0
  58. illusion/bridge/work_secret.py +131 -0
  59. illusion/cli.py +1228 -0
  60. illusion/commands/__init__.py +32 -0
  61. illusion/commands/registry.py +1934 -0
  62. illusion/config/__init__.py +39 -0
  63. illusion/config/i18n.py +522 -0
  64. illusion/config/paths.py +259 -0
  65. illusion/config/settings.py +564 -0
  66. illusion/coordinator/__init__.py +41 -0
  67. illusion/coordinator/agent_definitions.py +1093 -0
  68. illusion/coordinator/coordinator_mode.py +127 -0
  69. illusion/engine/__init__.py +95 -0
  70. illusion/engine/cost_tracker.py +55 -0
  71. illusion/engine/messages.py +369 -0
  72. illusion/engine/query.py +632 -0
  73. illusion/engine/query_engine.py +343 -0
  74. illusion/engine/stream_events.py +169 -0
  75. illusion/hooks/__init__.py +67 -0
  76. illusion/hooks/events.py +43 -0
  77. illusion/hooks/executor.py +397 -0
  78. illusion/hooks/hot_reload.py +74 -0
  79. illusion/hooks/loader.py +133 -0
  80. illusion/hooks/schemas.py +121 -0
  81. illusion/hooks/types.py +86 -0
  82. illusion/mcp/__init__.py +104 -0
  83. illusion/mcp/client.py +377 -0
  84. illusion/mcp/config.py +140 -0
  85. illusion/mcp/types.py +175 -0
  86. illusion/memory/__init__.py +36 -0
  87. illusion/memory/manager.py +94 -0
  88. illusion/memory/memdir.py +58 -0
  89. illusion/memory/paths.py +57 -0
  90. illusion/memory/scan.py +120 -0
  91. illusion/memory/search.py +83 -0
  92. illusion/memory/types.py +43 -0
  93. illusion/output_styles/__init__.py +15 -0
  94. illusion/output_styles/loader.py +64 -0
  95. illusion/permissions/__init__.py +39 -0
  96. illusion/permissions/checker.py +174 -0
  97. illusion/permissions/modes.py +38 -0
  98. illusion/platforms.py +148 -0
  99. illusion/plugins/__init__.py +71 -0
  100. illusion/plugins/bundled/__init__.py +0 -0
  101. illusion/plugins/installer.py +59 -0
  102. illusion/plugins/loader.py +301 -0
  103. illusion/plugins/schemas.py +51 -0
  104. illusion/plugins/types.py +56 -0
  105. illusion/prompts/__init__.py +29 -0
  106. illusion/prompts/claudemd.py +74 -0
  107. illusion/prompts/context.py +187 -0
  108. illusion/prompts/environment.py +189 -0
  109. illusion/prompts/system_prompt.py +155 -0
  110. illusion/py.typed +0 -0
  111. illusion/sandbox/__init__.py +29 -0
  112. illusion/sandbox/adapter.py +174 -0
  113. illusion/services/__init__.py +59 -0
  114. illusion/services/compact/__init__.py +1015 -0
  115. illusion/services/cron.py +338 -0
  116. illusion/services/cron_scheduler.py +715 -0
  117. illusion/services/file_history.py +258 -0
  118. illusion/services/lsp/__init__.py +455 -0
  119. illusion/services/session_storage.py +237 -0
  120. illusion/services/token_estimation.py +72 -0
  121. illusion/skills/__init__.py +60 -0
  122. illusion/skills/bundled/__init__.py +110 -0
  123. illusion/skills/bundled/content/batch.md +86 -0
  124. illusion/skills/bundled/content/coding-guidelines.md +70 -0
  125. illusion/skills/bundled/content/debug.md +38 -0
  126. illusion/skills/bundled/content/loop.md +82 -0
  127. illusion/skills/bundled/content/remember.md +105 -0
  128. illusion/skills/bundled/content/simplify.md +53 -0
  129. illusion/skills/bundled/content/skillify.md +113 -0
  130. illusion/skills/bundled/content/stuck.md +54 -0
  131. illusion/skills/bundled/content/update-config.md +329 -0
  132. illusion/skills/bundled/content/verify.md +74 -0
  133. illusion/skills/loader.py +219 -0
  134. illusion/skills/registry.py +40 -0
  135. illusion/skills/types.py +24 -0
  136. illusion/state/__init__.py +18 -0
  137. illusion/state/app_state.py +67 -0
  138. illusion/state/store.py +93 -0
  139. illusion/swarm/__init__.py +71 -0
  140. illusion/swarm/agent_executor.py +857 -0
  141. illusion/swarm/in_process.py +259 -0
  142. illusion/swarm/subprocess_backend.py +136 -0
  143. illusion/swarm/team_helpers.py +123 -0
  144. illusion/swarm/types.py +159 -0
  145. illusion/swarm/worktree.py +347 -0
  146. illusion/tasks/__init__.py +33 -0
  147. illusion/tasks/local_agent_task.py +42 -0
  148. illusion/tasks/local_shell_task.py +27 -0
  149. illusion/tasks/manager.py +377 -0
  150. illusion/tasks/stop_task.py +21 -0
  151. illusion/tasks/types.py +88 -0
  152. illusion/tools/__init__.py +126 -0
  153. illusion/tools/agent_tool.py +388 -0
  154. illusion/tools/ask_user_question_tool.py +186 -0
  155. illusion/tools/base.py +149 -0
  156. illusion/tools/bash_tool.py +413 -0
  157. illusion/tools/config_tool.py +90 -0
  158. illusion/tools/cron_tool.py +473 -0
  159. illusion/tools/enter_plan_mode_tool.py +147 -0
  160. illusion/tools/enter_worktree_tool.py +188 -0
  161. illusion/tools/exit_plan_mode_tool.py +69 -0
  162. illusion/tools/exit_worktree_tool.py +225 -0
  163. illusion/tools/file_edit_tool.py +283 -0
  164. illusion/tools/file_read_tool.py +294 -0
  165. illusion/tools/file_write_tool.py +184 -0
  166. illusion/tools/glob_tool.py +165 -0
  167. illusion/tools/grep_tool.py +190 -0
  168. illusion/tools/list_mcp_resources_tool.py +80 -0
  169. illusion/tools/lsp_tool.py +333 -0
  170. illusion/tools/mcp_auth_tool.py +100 -0
  171. illusion/tools/mcp_tool.py +75 -0
  172. illusion/tools/notebook_edit_tool.py +242 -0
  173. illusion/tools/powershell_tool.py +334 -0
  174. illusion/tools/read_mcp_resource_tool.py +63 -0
  175. illusion/tools/repl_tool.py +100 -0
  176. illusion/tools/send_message_tool.py +112 -0
  177. illusion/tools/shell_common.py +187 -0
  178. illusion/tools/skill_tool.py +86 -0
  179. illusion/tools/sleep_tool.py +62 -0
  180. illusion/tools/structured_output_tool.py +58 -0
  181. illusion/tools/task_create_tool.py +98 -0
  182. illusion/tools/task_get_tool.py +94 -0
  183. illusion/tools/task_list_tool.py +94 -0
  184. illusion/tools/task_output_tool.py +55 -0
  185. illusion/tools/task_stop_tool.py +52 -0
  186. illusion/tools/task_update_tool.py +224 -0
  187. illusion/tools/team_create_tool.py +236 -0
  188. illusion/tools/team_delete_tool.py +104 -0
  189. illusion/tools/todo_write_tool.py +198 -0
  190. illusion/tools/tool_search_tool.py +156 -0
  191. illusion/tools/web_fetch_tool.py +264 -0
  192. illusion/tools/web_search_tool.py +186 -0
  193. illusion/ui/__init__.py +23 -0
  194. illusion/ui/app.py +258 -0
  195. illusion/ui/backend_host.py +1180 -0
  196. illusion/ui/input.py +86 -0
  197. illusion/ui/output.py +363 -0
  198. illusion/ui/permission_dialog.py +47 -0
  199. illusion/ui/permission_store.py +99 -0
  200. illusion/ui/protocol.py +384 -0
  201. illusion/ui/react_launcher.py +280 -0
  202. illusion/ui/runtime.py +787 -0
  203. illusion/ui/textual_app.py +603 -0
  204. illusion/ui/web/__init__.py +10 -0
  205. illusion/ui/web/server.py +87 -0
  206. illusion/ui/web/ws_host.py +1197 -0
  207. illusion/utils/__init__.py +0 -0
  208. illusion/utils/ripgrep.py +299 -0
  209. illusion/utils/shell.py +248 -0
  210. illusion_code-0.1.0.dist-info/METADATA +1159 -0
  211. illusion_code-0.1.0.dist-info/RECORD +214 -0
  212. illusion_code-0.1.0.dist-info/WHEEL +4 -0
  213. illusion_code-0.1.0.dist-info/entry_points.txt +2 -0
  214. illusion_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,214 @@
1
+ """
2
+ 统一认证管理器模块
3
+ ==================
4
+
5
+ 本模块为 IllusionCode 提供统一的认证状态管理功能。
6
+
7
+ 主要功能:
8
+ - 管理环境(env_N)认证状态
9
+ - 切换和配置环境
10
+ - 存储和加载凭据(按 env_N 分组)
11
+
12
+ 类说明:
13
+ - AuthManager: 认证管理器类,负责所有认证相关的操作
14
+
15
+ 使用示例:
16
+ >>> from illusion.auth import AuthManager
17
+ >>> manager = AuthManager()
18
+ >>> status = manager.get_env_credential_statuses()
19
+ >>> print(status)
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ from typing import Any
26
+
27
+ from illusion.auth.storage import (
28
+ clear_env_credentials,
29
+ load_env_credential,
30
+ store_env_credential,
31
+ )
32
+ from illusion.config.i18n import t as _t
33
+
34
+ log = logging.getLogger(__name__)
35
+
36
+
37
+ class AuthManager:
38
+ """认证管理器
39
+
40
+ 通过 :mod:`illusion.auth.storage` 读写凭据,
41
+ 并通过设置跟踪当前活动的环境。
42
+ """
43
+
44
+ def __init__(self, settings: Any | None = None) -> None:
45
+ self._settings = settings
46
+
47
+ @property
48
+ def settings(self) -> Any:
49
+ """获取设置对象(延迟加载)"""
50
+ if self._settings is None:
51
+ from illusion.config import load_settings
52
+ self._settings = load_settings()
53
+ return self._settings
54
+
55
+ def get_active_env_key(self) -> str:
56
+ """获取当前活动的环境键名(如 "env_1")"""
57
+ return self.settings._active_env_key
58
+
59
+ def list_envs(self) -> dict[str, Any]:
60
+ """获取所有环境配置"""
61
+ return self.settings.list_envs()
62
+
63
+ def get_env_credential_statuses(self) -> dict[str, Any]:
64
+ """获取所有环境的凭据状态
65
+
66
+ 返回以 env_key 为键的字典::
67
+
68
+ {
69
+ "env_1": {
70
+ "api_format": "anthropic",
71
+ "base_url": "...",
72
+ "model": "claude-sonnet-4-6",
73
+ "has_credential": True,
74
+ "active": True,
75
+ },
76
+ ...
77
+ }
78
+ """
79
+ active_env_key = self.get_active_env_key()
80
+ envs = self.list_envs()
81
+ result: dict[str, Any] = {}
82
+
83
+ for env_key, env in envs.items():
84
+ models = env.list_models()
85
+ model_name = next(iter(models.values())) if models else "(无模型)"
86
+ has_cred = bool(load_env_credential(env_key, "api_key")) or bool(env.api_key)
87
+ result[env_key] = {
88
+ "api_format": env.api_format,
89
+ "base_url": env.base_url or "",
90
+ "model": model_name,
91
+ "has_credential": has_cred,
92
+ "active": env_key == active_env_key,
93
+ }
94
+
95
+ return result
96
+
97
+ def save_settings(self) -> None:
98
+ """保存内存中的设置到持久化存储"""
99
+ from illusion.config import save_settings
100
+ save_settings(self.settings)
101
+
102
+ def use_env(self, env_key: str) -> None:
103
+ """切换到指定的环境
104
+
105
+ Args:
106
+ env_key: 环境键名(如 "env_1")
107
+
108
+ Raises:
109
+ ValueError: 环境不存在
110
+ """
111
+ env = self.settings.get_env(env_key)
112
+ if env is None:
113
+ raise ValueError(_t("unknown_env", env_key=env_key))
114
+ models = env.list_models()
115
+ if models:
116
+ model_key = next(iter(models.keys()))
117
+ self.settings.model = f"{env_key}.{model_key}"
118
+ else:
119
+ self.settings.model = f"{env_key}.model_1"
120
+ self._settings = self.settings
121
+ self.save_settings()
122
+ log.info("已切换到环境 %s", env_key)
123
+
124
+ def update_env(
125
+ self,
126
+ env_key: str,
127
+ *,
128
+ api_format: str | None = None,
129
+ base_url: str | None = None,
130
+ api_key: str | None = None,
131
+ ) -> None:
132
+ """更新环境配置
133
+
134
+ Args:
135
+ env_key: 环境键名
136
+ api_format: API 格式
137
+ base_url: 基础 URL
138
+ api_key: API 密钥
139
+
140
+ Raises:
141
+ ValueError: 环境不存在
142
+ """
143
+ env = self.settings.get_env(env_key)
144
+ if env is None:
145
+ raise ValueError(_t("unknown_env", env_key=env_key))
146
+ updates: dict[str, Any] = {}
147
+ if api_format is not None:
148
+ updates["api_format"] = api_format
149
+ if base_url is not None:
150
+ updates["base_url"] = base_url
151
+ if api_key is not None:
152
+ updates["api_key"] = api_key
153
+ if updates:
154
+ updated_env = env.model_copy(update=updates)
155
+ setattr(self.settings, env_key, updated_env)
156
+ self._settings = self.settings
157
+ self.save_settings()
158
+
159
+ def remove_env(self, env_key: str) -> None:
160
+ """移除环境配置
161
+
162
+ Args:
163
+ env_key: 环境键名
164
+
165
+ Raises:
166
+ ValueError: 环境不存在或正在使用
167
+ """
168
+ if env_key == self.get_active_env_key():
169
+ raise ValueError(_t("cannot_remove_active_env"))
170
+ envs = self.settings.list_envs()
171
+ if env_key not in envs:
172
+ raise ValueError(_t("unknown_env", env_key=env_key))
173
+ extras = dict(self.settings.model_extra or {})
174
+ if env_key in extras:
175
+ del extras[env_key]
176
+ self._settings = self.settings.model_copy(update=extras)
177
+ self.save_settings()
178
+
179
+ def store_env_api_key(self, env_key: str, api_key: str) -> None:
180
+ """存储环境的 API 密钥到 credentials.json
181
+
182
+ Args:
183
+ env_key: 环境键名(如 "env_1")
184
+ api_key: API 密钥
185
+ """
186
+ store_env_credential(env_key, "api_key", api_key)
187
+
188
+ def clear_env_api_key(self, env_key: str) -> None:
189
+ """删除环境的 API 密钥
190
+
191
+ Args:
192
+ env_key: 环境键名(如 "env_1")
193
+ """
194
+ clear_env_credentials(env_key)
195
+
196
+ def store_credential(self, provider: str, key: str, value: str) -> None:
197
+ """存储凭据(兼容旧接口,按 provider 存储)
198
+
199
+ Args:
200
+ provider: 提供商名称
201
+ key: 键名
202
+ value: 凭据值
203
+ """
204
+ from illusion.auth.storage import store_credential
205
+ store_credential(provider, key, value)
206
+
207
+ def clear_credential(self, provider: str) -> None:
208
+ """删除凭据(兼容旧接口,按 provider 清除)
209
+
210
+ Args:
211
+ provider: 提供商名称
212
+ """
213
+ from illusion.auth.storage import clear_provider_credentials
214
+ clear_provider_credentials(provider)
@@ -0,0 +1,372 @@
1
+ """
2
+ 安全凭据存储模块
3
+ ================
4
+
5
+ 本模块为 IllusionCode 提供安全的凭据存储功能。
6
+
7
+ 默认后端:~/.illusion/credentials.json,权限 600
8
+ 可选后端:系统 keyring(如果安装了 keyring 包)
9
+
10
+ 函数说明:
11
+ - store_credential: 存储凭据
12
+ - load_credential: 加载凭据
13
+ - clear_provider_credentials: 清除提供商凭据
14
+ - store_external_binding/load_external_binding: 外部绑定存储/加载
15
+ - encrypt/decrypt: 轻量级混淆加密
16
+
17
+ 使用示例:
18
+ >>> from illusion.auth.storage import store_credential, load_credential
19
+ >>> store_credential("anthropic", "api_key", "sk-...")
20
+ >>> key = load_credential("anthropic", "api_key")
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import logging
27
+ from dataclasses import asdict, dataclass
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ from illusion.config.paths import get_config_dir
32
+
33
+ # 模块级日志记录器
34
+ log = logging.getLogger(__name__)
35
+
36
+ # 常量定义
37
+ _CREDS_FILE_NAME = "credentials.json" # 凭据文件名
38
+ _KEYRING_SERVICE = "illusion" # keyring 服务名
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ExternalAuthBinding:
43
+ """指向外部 CLI 管理的凭据的指针
44
+
45
+ Attributes:
46
+ provider: 提供商名称
47
+ source_path: 源路径
48
+ source_kind: 源类型
49
+ managed_by: 管理程序
50
+ profile_label: 配置标签
51
+ """
52
+
53
+ provider: str
54
+ source_path: str
55
+ source_kind: str
56
+ managed_by: str
57
+ profile_label: str = ""
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # 文件后端(始终可用)
62
+ # ---------------------------------------------------------------------------
63
+
64
+
65
+ def _creds_path() -> Path:
66
+ """获取凭据文件路径"""
67
+ return get_config_dir() / _CREDS_FILE_NAME
68
+
69
+
70
+ def _load_creds_file() -> dict[str, Any]:
71
+ """加载凭据文件
72
+
73
+ Returns:
74
+ dict[str, Any]: 凭据数据字典
75
+ """
76
+ path = _creds_path()
77
+ if not path.exists():
78
+ return {}
79
+ try:
80
+ return json.loads(path.read_text(encoding="utf-8"))
81
+ except (json.JSONDecodeError, OSError) as exc:
82
+ log.warning("Failed to read credentials file: %s", exc)
83
+ return {}
84
+
85
+
86
+ def _save_creds_file(data: dict[str, Any]) -> None:
87
+ """保存凭据文件
88
+
89
+ Args:
90
+ data: 凭据数据字典
91
+ """
92
+ path = _creds_path()
93
+ path.parent.mkdir(parents=True, exist_ok=True)
94
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
95
+ try:
96
+ path.chmod(0o600)
97
+ except OSError:
98
+ pass
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Keyring 后端(可选)
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ def _keyring_available() -> bool:
107
+ """检查 keyring 是否可用
108
+
109
+ Returns:
110
+ bool: keyring 是否可用
111
+ """
112
+ try:
113
+ import keyring # noqa: F401
114
+
115
+ return True
116
+ except ImportError:
117
+ return False
118
+
119
+
120
+ def _keyring_key(provider: str, key: str) -> str:
121
+ """生成 keyring 键
122
+
123
+ Args:
124
+ provider: 提供商名称
125
+ key: 键名
126
+
127
+ Returns:
128
+ str: 组合键名
129
+ """
130
+ return f"{provider}:{key}"
131
+
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # 公共 API
135
+ # ---------------------------------------------------------------------------
136
+
137
+
138
+ def store_credential(provider: str, key: str, value: str, *, use_keyring: bool | None = None) -> None:
139
+ """持久化 provider 下的凭据
140
+
141
+ 如果未设置 use_keyring,在可用时使用 keyring。
142
+
143
+ Args:
144
+ provider: 提供商名称
145
+ key: 键名
146
+ value: 凭据值
147
+ use_keyring: 是否使用 keyring(可选)
148
+ """
149
+ if use_keyring is None:
150
+ use_keyring = _keyring_available()
151
+
152
+ if use_keyring:
153
+ try:
154
+ import keyring
155
+
156
+ keyring.set_password(_KEYRING_SERVICE, _keyring_key(provider, key), value)
157
+ log.debug("Stored %s/%s in keyring", provider, key)
158
+ return
159
+ except Exception as exc:
160
+ log.warning("Keyring store failed, falling back to file: %s", exc)
161
+
162
+ data = _load_creds_file()
163
+ data.setdefault(provider, {})[key] = value
164
+ _save_creds_file(data)
165
+ log.debug("Stored %s/%s in credentials file", provider, key)
166
+
167
+
168
+ def load_credential(provider: str, key: str, *, use_keyring: bool | None = None) -> str | None:
169
+ """返回存储的凭据,未找到返回 None
170
+
171
+ Args:
172
+ provider: 提供商名称
173
+ key: 键名
174
+ use_keyring: 是否使用 keyring(可选)
175
+
176
+ Returns:
177
+ str | None: 凭据值或 None
178
+ """
179
+ if use_keyring is None:
180
+ use_keyring = _keyring_available()
181
+
182
+ if use_keyring:
183
+ try:
184
+ import keyring
185
+
186
+ value = keyring.get_password(_KEYRING_SERVICE, _keyring_key(provider, key))
187
+ if value is not None:
188
+ return value
189
+ except Exception as exc:
190
+ log.warning("Keyring load failed, falling back to file: %s", exc)
191
+
192
+ data = _load_creds_file()
193
+ return data.get(provider, {}).get(key)
194
+
195
+
196
+ def clear_provider_credentials(provider: str, *, use_keyring: bool | None = None) -> None:
197
+ """删除 provider 的所有存储凭据
198
+
199
+ Args:
200
+ provider: 提供商名称
201
+ use_keyring: 是否使用 keyring(可选)
202
+ """
203
+ if use_keyring is None:
204
+ use_keyring = _keyring_available()
205
+
206
+ if use_keyring:
207
+ try:
208
+ import keyring
209
+ from keyring.errors import PasswordDeleteError
210
+
211
+ # 尝试常见键;静默忽略缺失的
212
+ for key in ("api_key", "token", "github_token"):
213
+ try:
214
+ keyring.delete_password(_KEYRING_SERVICE, _keyring_key(provider, key))
215
+ except (PasswordDeleteError, Exception):
216
+ pass
217
+ except ImportError:
218
+ pass
219
+
220
+ data = _load_creds_file()
221
+ if provider in data:
222
+ del data[provider]
223
+ _save_creds_file(data)
224
+ log.debug("Cleared credentials for provider: %s", provider)
225
+
226
+
227
+ def list_stored_providers() -> list[str]:
228
+ """返回文件中存储了凭据的提供商列表
229
+
230
+ Returns:
231
+ list[str]: 提供商名称列表
232
+ """
233
+ return list(_load_creds_file().keys())
234
+
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # env_N 凭据存储(按环境分组)
238
+ # ---------------------------------------------------------------------------
239
+
240
+
241
+ def store_env_credential(env_key: str, key: str, value: str) -> None:
242
+ """按 env_N 分组存储凭据
243
+
244
+ Args:
245
+ env_key: 环境键名(如 "env_1")
246
+ key: 键名(如 "api_key")
247
+ value: 凭据值
248
+ """
249
+ data = _load_creds_file()
250
+ data.setdefault(env_key, {})[key] = value
251
+ _save_creds_file(data)
252
+ log.debug("Stored %s/%s in credentials file (env)", env_key, key)
253
+
254
+
255
+ def load_env_credential(env_key: str, key: str) -> str | None:
256
+ """按 env_N 读取凭据,未找到返回 None
257
+
258
+ Args:
259
+ env_key: 环境键名(如 "env_1")
260
+ key: 键名(如 "api_key")
261
+
262
+ Returns:
263
+ str | None: 凭据值或 None
264
+ """
265
+ data = _load_creds_file()
266
+ return data.get(env_key, {}).get(key)
267
+
268
+
269
+ def clear_env_credentials(env_key: str) -> None:
270
+ """删除 env_N 的所有存储凭据
271
+
272
+ Args:
273
+ env_key: 环境键名(如 "env_1")
274
+ """
275
+ data = _load_creds_file()
276
+ if env_key in data:
277
+ del data[env_key]
278
+ _save_creds_file(data)
279
+ log.debug("Cleared credentials for env: %s", env_key)
280
+
281
+
282
+ def store_external_binding(binding: ExternalAuthBinding) -> None:
283
+ """持久化描述 provider 外部认证源的元数据
284
+
285
+ Args:
286
+ binding: 外部认证绑定
287
+ """
288
+ data = _load_creds_file()
289
+ entry = data.setdefault(binding.provider, {})
290
+ entry["external_binding"] = asdict(binding)
291
+ _save_creds_file(data)
292
+ log.debug("Stored external auth binding for provider: %s", binding.provider)
293
+
294
+
295
+ def load_external_binding(provider: str) -> ExternalAuthBinding | None:
296
+ """如果存在,加载 provider 的外部认证绑定元数据
297
+
298
+ Args:
299
+ provider: 提供商名称
300
+
301
+ Returns:
302
+ ExternalAuthBinding | None: 外部绑定或 None
303
+ """
304
+ entry = _load_creds_file().get(provider, {})
305
+ if not isinstance(entry, dict):
306
+ return None
307
+ raw = entry.get("external_binding")
308
+ if not isinstance(raw, dict):
309
+ return None
310
+ try:
311
+ return ExternalAuthBinding(
312
+ provider=str(raw["provider"]),
313
+ source_path=str(raw["source_path"]),
314
+ source_kind=str(raw["source_kind"]),
315
+ managed_by=str(raw["managed_by"]),
316
+ profile_label=str(raw.get("profile_label", "") or ""),
317
+ )
318
+ except KeyError:
319
+ log.warning("Ignoring malformed external auth binding for provider: %s", provider)
320
+ return None
321
+
322
+
323
+ # ---------------------------------------------------------------------------
324
+ # 加密/解密辅助函数(轻量级 XOR 混淆,非真正加密)
325
+ # ---------------------------------------------------------------------------
326
+
327
+
328
+ def _obfuscation_key() -> bytes:
329
+ """返回从主目录路径派生的每用户混淆密钥
330
+
331
+ Returns:
332
+ bytes: 32 字节混淆密钥
333
+ """
334
+ seed = str(Path.home()).encode() + b"illusion-v1"
335
+ # 通过 SHA-256 简单重复密钥拉伸到 32 字节以保持确定性
336
+ import hashlib
337
+
338
+ return hashlib.sha256(seed).digest()
339
+
340
+
341
+ def encrypt(plaintext: str) -> str:
342
+ """轻量级混淆 plaintext(base64 编码 XOR)。非加密。
343
+
344
+ Args:
345
+ plaintext: 明文
346
+
347
+ Returns:
348
+ str: 混淆后的字符串
349
+ """
350
+ import base64
351
+
352
+ key = _obfuscation_key()
353
+ data = plaintext.encode("utf-8")
354
+ xored = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
355
+ return base64.urlsafe_b64encode(xored).decode("ascii")
356
+
357
+
358
+ def decrypt(ciphertext: str) -> str:
359
+ """encrypt 的反向操作
360
+
361
+ Args:
362
+ ciphertext: 混淆的字符串
363
+
364
+ Returns:
365
+ str: 明文
366
+ """
367
+ import base64
368
+
369
+ key = _obfuscation_key()
370
+ data = base64.urlsafe_b64decode(ciphertext.encode("ascii"))
371
+ xored = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
372
+ return xored.decode("utf-8")
@@ -0,0 +1,38 @@
1
+ """
2
+ 桥接模块
3
+ ========
4
+
5
+ 本模块提供 IllusionCode 桥接会话管理功能。
6
+
7
+ 主要组件:
8
+ - BridgeSessionManager: 桥接会话管理器
9
+ - BridgeSessionRecord: 桥接会话记录
10
+ - BridgeConfig: 桥接配置
11
+ - SessionHandle: 会话句柄
12
+ - WorkData: 工作数据
13
+ - WorkSecret: 工作密钥
14
+ - get_bridge_manager: 获取桥接管理器
15
+ - spawn_session: 生成会话
16
+
17
+ 使用示例:
18
+ >>> from illusion.bridge import BridgeSessionManager, spawn_session
19
+ """
20
+
21
+ from illusion.bridge.manager import BridgeSessionManager, BridgeSessionRecord, get_bridge_manager
22
+ from illusion.bridge.session_runner import SessionHandle, spawn_session
23
+ from illusion.bridge.types import BridgeConfig, WorkData, WorkSecret
24
+ from illusion.bridge.work_secret import build_sdk_url, decode_work_secret, encode_work_secret
25
+
26
+ __all__ = [
27
+ "BridgeSessionManager",
28
+ "BridgeSessionRecord",
29
+ "BridgeConfig",
30
+ "SessionHandle",
31
+ "WorkData",
32
+ "WorkSecret",
33
+ "build_sdk_url",
34
+ "decode_work_secret",
35
+ "encode_work_secret",
36
+ "get_bridge_manager",
37
+ "spawn_session",
38
+ ]