superqode 0.1.5__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 (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. superqode-0.1.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,477 @@
1
+ """
2
+ Session Sharing - Collaborative Session Features.
3
+
4
+ Enables sharing and forking of sessions between users:
5
+ - Export sessions for sharing
6
+ - Import shared sessions
7
+ - Fork sessions to create branches
8
+ - Session links for collaboration
9
+ - Adapted for SuperQode's multi-agent QE workflow
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import gzip
16
+ import hashlib
17
+ import json
18
+ import secrets
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime, timedelta
21
+ from enum import Enum
22
+ from pathlib import Path
23
+ from typing import Any, Dict, List, Optional
24
+ import urllib.parse
25
+
26
+ from .persistence import Session, SessionStore
27
+
28
+
29
+ class ShareVisibility(Enum):
30
+ """Visibility of a shared session."""
31
+
32
+ PRIVATE = "private" # Only accessible with link
33
+ UNLISTED = "unlisted" # Not discoverable, but accessible
34
+ PUBLIC = "public" # Discoverable and accessible
35
+
36
+
37
+ @dataclass
38
+ class ShareConfig:
39
+ """Configuration for session sharing."""
40
+
41
+ visibility: ShareVisibility = ShareVisibility.PRIVATE
42
+ expires_in: Optional[timedelta] = None
43
+ allow_fork: bool = True
44
+ allow_view_history: bool = True
45
+ password: Optional[str] = None # Optional password protection
46
+
47
+
48
+ @dataclass
49
+ class SharedSession:
50
+ """A shared session with access controls."""
51
+
52
+ id: str
53
+ session_id: str
54
+ share_token: str
55
+ visibility: ShareVisibility
56
+ created_at: datetime = field(default_factory=datetime.now)
57
+ expires_at: Optional[datetime] = None
58
+ access_count: int = 0
59
+ fork_count: int = 0
60
+ allow_fork: bool = True
61
+ allow_view_history: bool = True
62
+ password_hash: Optional[str] = None
63
+ created_by: str = ""
64
+
65
+ @property
66
+ def is_expired(self) -> bool:
67
+ """Check if share has expired."""
68
+ if self.expires_at is None:
69
+ return False
70
+ return datetime.now() > self.expires_at
71
+
72
+ @property
73
+ def share_url(self) -> str:
74
+ """Get the share URL."""
75
+ return f"/share/{self.share_token}"
76
+
77
+ def to_dict(self) -> dict:
78
+ return {
79
+ "id": self.id,
80
+ "session_id": self.session_id,
81
+ "share_token": self.share_token,
82
+ "visibility": self.visibility.value,
83
+ "created_at": self.created_at.isoformat(),
84
+ "expires_at": self.expires_at.isoformat() if self.expires_at else None,
85
+ "access_count": self.access_count,
86
+ "fork_count": self.fork_count,
87
+ "allow_fork": self.allow_fork,
88
+ "allow_view_history": self.allow_view_history,
89
+ "password_hash": self.password_hash,
90
+ "created_by": self.created_by,
91
+ }
92
+
93
+ @classmethod
94
+ def from_dict(cls, data: dict) -> "SharedSession":
95
+ return cls(
96
+ id=data["id"],
97
+ session_id=data["session_id"],
98
+ share_token=data["share_token"],
99
+ visibility=ShareVisibility(data["visibility"]),
100
+ created_at=datetime.fromisoformat(data["created_at"]),
101
+ expires_at=datetime.fromisoformat(data["expires_at"])
102
+ if data.get("expires_at")
103
+ else None,
104
+ access_count=data.get("access_count", 0),
105
+ fork_count=data.get("fork_count", 0),
106
+ allow_fork=data.get("allow_fork", True),
107
+ allow_view_history=data.get("allow_view_history", True),
108
+ password_hash=data.get("password_hash"),
109
+ created_by=data.get("created_by", ""),
110
+ )
111
+
112
+
113
+ @dataclass
114
+ class ExportedSession:
115
+ """A session exported for sharing."""
116
+
117
+ session_data: dict
118
+ export_format: str = "superqode-session-v1"
119
+ exported_at: datetime = field(default_factory=datetime.now)
120
+ checksum: str = ""
121
+
122
+ def to_json(self) -> str:
123
+ """Export to JSON string."""
124
+ data = {
125
+ "format": self.export_format,
126
+ "exported_at": self.exported_at.isoformat(),
127
+ "session": self.session_data,
128
+ "checksum": self.checksum,
129
+ }
130
+ return json.dumps(data, indent=2)
131
+
132
+ def to_compressed(self) -> bytes:
133
+ """Export to compressed bytes."""
134
+ json_data = self.to_json().encode("utf-8")
135
+ return gzip.compress(json_data)
136
+
137
+ def to_base64(self) -> str:
138
+ """Export to base64 string for URLs."""
139
+ compressed = self.to_compressed()
140
+ return base64.urlsafe_b64encode(compressed).decode("ascii")
141
+
142
+ @classmethod
143
+ def from_json(cls, json_str: str) -> "ExportedSession":
144
+ """Import from JSON string."""
145
+ data = json.loads(json_str)
146
+
147
+ if data.get("format") != "superqode-session-v1":
148
+ raise ValueError(f"Unknown export format: {data.get('format')}")
149
+
150
+ return cls(
151
+ session_data=data["session"],
152
+ export_format=data["format"],
153
+ exported_at=datetime.fromisoformat(data["exported_at"]),
154
+ checksum=data.get("checksum", ""),
155
+ )
156
+
157
+ @classmethod
158
+ def from_compressed(cls, data: bytes) -> "ExportedSession":
159
+ """Import from compressed bytes."""
160
+ json_data = gzip.decompress(data).decode("utf-8")
161
+ return cls.from_json(json_data)
162
+
163
+ @classmethod
164
+ def from_base64(cls, b64_str: str) -> "ExportedSession":
165
+ """Import from base64 string."""
166
+ compressed = base64.urlsafe_b64decode(b64_str)
167
+ return cls.from_compressed(compressed)
168
+
169
+
170
+ class SessionSharingManager:
171
+ """
172
+ Manages session sharing and forking.
173
+
174
+ Usage:
175
+ store = SessionStore()
176
+ sharing = SessionSharingManager(store)
177
+
178
+ # Share a session
179
+ share = sharing.create_share("session-123", ShareConfig())
180
+ print(f"Share URL: {share.share_url}")
181
+
182
+ # Fork a shared session
183
+ forked = await sharing.fork_session(share.share_token, "My Fork")
184
+
185
+ # Export for offline sharing
186
+ exported = sharing.export_session("session-123")
187
+ with open("session.json", "w") as f:
188
+ f.write(exported.to_json())
189
+ """
190
+
191
+ def __init__(
192
+ self,
193
+ session_store: SessionStore,
194
+ shares_dir: Optional[Path] = None,
195
+ ):
196
+ self.session_store = session_store
197
+ self.shares_dir = shares_dir or (session_store.storage_dir / "shares")
198
+ self.shares_dir.mkdir(parents=True, exist_ok=True)
199
+
200
+ self._shares: Dict[str, SharedSession] = {}
201
+ self._load_shares()
202
+
203
+ def _load_shares(self) -> None:
204
+ """Load shares from disk."""
205
+ index_file = self.shares_dir / "index.json"
206
+ if index_file.exists():
207
+ try:
208
+ data = json.loads(index_file.read_text())
209
+ for share_data in data.get("shares", []):
210
+ share = SharedSession.from_dict(share_data)
211
+ if not share.is_expired:
212
+ self._shares[share.share_token] = share
213
+ except (json.JSONDecodeError, KeyError):
214
+ pass
215
+
216
+ def _save_shares(self) -> None:
217
+ """Save shares to disk."""
218
+ index_file = self.shares_dir / "index.json"
219
+
220
+ # Remove expired shares
221
+ self._shares = {k: v for k, v in self._shares.items() if not v.is_expired}
222
+
223
+ data = {
224
+ "shares": [s.to_dict() for s in self._shares.values()],
225
+ }
226
+ index_file.write_text(json.dumps(data, indent=2))
227
+
228
+ def _generate_token(self) -> str:
229
+ """Generate a unique share token."""
230
+ return secrets.token_urlsafe(16)
231
+
232
+ def _hash_password(self, password: str) -> str:
233
+ """Hash a password for storage."""
234
+ return hashlib.sha256(password.encode()).hexdigest()
235
+
236
+ def _verify_password(self, password: str, hash_value: str) -> bool:
237
+ """Verify a password against its hash."""
238
+ return self._hash_password(password) == hash_value
239
+
240
+ def create_share(
241
+ self,
242
+ session_id: str,
243
+ config: ShareConfig,
244
+ created_by: str = "",
245
+ ) -> Optional[SharedSession]:
246
+ """Create a share for a session."""
247
+ # Verify session exists
248
+ session = self.session_store.load(session_id)
249
+ if not session:
250
+ return None
251
+
252
+ share_token = self._generate_token()
253
+ share_id = f"share-{int(datetime.now().timestamp())}"
254
+
255
+ expires_at = None
256
+ if config.expires_in:
257
+ expires_at = datetime.now() + config.expires_in
258
+
259
+ password_hash = None
260
+ if config.password:
261
+ password_hash = self._hash_password(config.password)
262
+
263
+ share = SharedSession(
264
+ id=share_id,
265
+ session_id=session_id,
266
+ share_token=share_token,
267
+ visibility=config.visibility,
268
+ expires_at=expires_at,
269
+ allow_fork=config.allow_fork,
270
+ allow_view_history=config.allow_view_history,
271
+ password_hash=password_hash,
272
+ created_by=created_by,
273
+ )
274
+
275
+ self._shares[share_token] = share
276
+ self._save_shares()
277
+
278
+ return share
279
+
280
+ def get_share(
281
+ self,
282
+ share_token: str,
283
+ password: Optional[str] = None,
284
+ ) -> Optional[SharedSession]:
285
+ """Get a share by token."""
286
+ share = self._shares.get(share_token)
287
+
288
+ if not share:
289
+ return None
290
+
291
+ if share.is_expired:
292
+ del self._shares[share_token]
293
+ self._save_shares()
294
+ return None
295
+
296
+ # Check password if required
297
+ if share.password_hash:
298
+ if not password or not self._verify_password(password, share.password_hash):
299
+ return None
300
+
301
+ # Increment access count
302
+ share.access_count += 1
303
+ self._save_shares()
304
+
305
+ return share
306
+
307
+ def get_session_for_share(
308
+ self,
309
+ share_token: str,
310
+ password: Optional[str] = None,
311
+ ) -> Optional[Session]:
312
+ """Get the session for a share."""
313
+ share = self.get_share(share_token, password)
314
+ if not share:
315
+ return None
316
+
317
+ return self.session_store.load(share.session_id)
318
+
319
+ def fork_session(
320
+ self,
321
+ share_token: str,
322
+ new_title: str,
323
+ password: Optional[str] = None,
324
+ ) -> Optional[Session]:
325
+ """Fork a shared session."""
326
+ share = self.get_share(share_token, password)
327
+
328
+ if not share or not share.allow_fork:
329
+ return None
330
+
331
+ session = self.session_store.load(share.session_id)
332
+ if not session:
333
+ return None
334
+
335
+ # Create fork
336
+ forked = session.fork(new_title)
337
+
338
+ # Add fork metadata
339
+ forked.metadata["forked_from"] = {
340
+ "session_id": session.id,
341
+ "share_token": share_token,
342
+ "forked_at": datetime.now().isoformat(),
343
+ }
344
+
345
+ # Save forked session
346
+ self.session_store.save(forked)
347
+
348
+ # Update fork count
349
+ share.fork_count += 1
350
+ self._save_shares()
351
+
352
+ return forked
353
+
354
+ def revoke_share(self, share_token: str) -> bool:
355
+ """Revoke a share."""
356
+ if share_token in self._shares:
357
+ del self._shares[share_token]
358
+ self._save_shares()
359
+ return True
360
+ return False
361
+
362
+ def list_shares(self, session_id: Optional[str] = None) -> List[SharedSession]:
363
+ """List all shares, optionally filtered by session."""
364
+ shares = list(self._shares.values())
365
+
366
+ if session_id:
367
+ shares = [s for s in shares if s.session_id == session_id]
368
+
369
+ return sorted(shares, key=lambda s: s.created_at, reverse=True)
370
+
371
+ def export_session(
372
+ self,
373
+ session_id: str,
374
+ include_history: bool = True,
375
+ ) -> Optional[ExportedSession]:
376
+ """Export a session for sharing."""
377
+ session = self.session_store.load(session_id)
378
+ if not session:
379
+ return None
380
+
381
+ session_data = session.to_dict()
382
+
383
+ # Optionally strip history
384
+ if not include_history:
385
+ session_data["messages"] = []
386
+ session_data["tool_executions"] = []
387
+
388
+ # Calculate checksum
389
+ json_str = json.dumps(session_data, sort_keys=True)
390
+ checksum = hashlib.sha256(json_str.encode()).hexdigest()[:16]
391
+
392
+ return ExportedSession(
393
+ session_data=session_data,
394
+ checksum=checksum,
395
+ )
396
+
397
+ def import_session(
398
+ self,
399
+ exported: ExportedSession,
400
+ new_title: Optional[str] = None,
401
+ ) -> Session:
402
+ """Import an exported session."""
403
+ # Verify checksum if present
404
+ if exported.checksum:
405
+ json_str = json.dumps(exported.session_data, sort_keys=True)
406
+ expected = hashlib.sha256(json_str.encode()).hexdigest()[:16]
407
+ if expected != exported.checksum:
408
+ raise ValueError("Session data checksum mismatch")
409
+
410
+ # Create session from data
411
+ session = Session.from_dict(exported.session_data)
412
+
413
+ # Generate new ID for imported session
414
+ session.id = f"session-{int(datetime.now().timestamp())}-import"
415
+
416
+ if new_title:
417
+ session.title = new_title
418
+
419
+ # Add import metadata
420
+ session.metadata["imported"] = {
421
+ "imported_at": datetime.now().isoformat(),
422
+ "original_id": exported.session_data.get("id"),
423
+ "export_format": exported.export_format,
424
+ }
425
+
426
+ # Save session
427
+ self.session_store.save(session)
428
+
429
+ return session
430
+
431
+ def generate_share_link(
432
+ self,
433
+ share_token: str,
434
+ base_url: str = "https://superqode.dev",
435
+ ) -> str:
436
+ """Generate a shareable link."""
437
+ return f"{base_url}/share/{share_token}"
438
+
439
+ def generate_export_link(
440
+ self,
441
+ session_id: str,
442
+ base_url: str = "https://superqode.dev",
443
+ ) -> Optional[str]:
444
+ """Generate a self-contained export link."""
445
+ exported = self.export_session(session_id, include_history=False)
446
+ if not exported:
447
+ return None
448
+
449
+ encoded = exported.to_base64()
450
+
451
+ # URL encode the data
452
+ params = urllib.parse.urlencode({"data": encoded})
453
+ return f"{base_url}/import?{params}"
454
+
455
+
456
+ def create_quick_share(
457
+ session_id: str,
458
+ store: SessionStore,
459
+ expires_hours: int = 24,
460
+ ) -> Optional[str]:
461
+ """Quick function to create a share link.
462
+
463
+ Returns the share URL or None if session not found.
464
+ """
465
+ manager = SessionSharingManager(store)
466
+
467
+ config = ShareConfig(
468
+ visibility=ShareVisibility.PRIVATE,
469
+ expires_in=timedelta(hours=expires_hours),
470
+ allow_fork=True,
471
+ )
472
+
473
+ share = manager.create_share(session_id, config)
474
+ if share:
475
+ return manager.generate_share_link(share.share_token)
476
+
477
+ return None