deepwrap 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.
- deepwrap/__init__.py +3 -0
- deepwrap/__main__.py +437 -0
- deepwrap/api/__init__.py +7 -0
- deepwrap/api/base.py +31 -0
- deepwrap/api/chat_session.py +303 -0
- deepwrap/api/chats.py +101 -0
- deepwrap/api/pow.py +92 -0
- deepwrap/client.py +136 -0
- deepwrap/config.py +74 -0
- deepwrap/core/__init__.py +4 -0
- deepwrap/core/auth.py +123 -0
- deepwrap/core/session_manager.py +55 -0
- deepwrap/interfaces/__init__.py +1 -0
- deepwrap/interfaces/api.py +373 -0
- deepwrap/interfaces/cli.py +966 -0
- deepwrap/modules/__init__.py +3 -0
- deepwrap/modules/pow_asm.py +305 -0
- deepwrap/py.typed +0 -0
- deepwrap/utils/bearer_token_extractor.py +56 -0
- deepwrap/utils/browser_finder.py +172 -0
- deepwrap/utils/browser_process.py +72 -0
- deepwrap/utils/cdp_client.py +82 -0
- deepwrap/utils/config_store.py +101 -0
- deepwrap/utils/dev_tools_http.py +90 -0
- deepwrap/utils/port_finder.py +17 -0
- deepwrap-0.1.0.dist-info/METADATA +860 -0
- deepwrap-0.1.0.dist-info/RECORD +31 -0
- deepwrap-0.1.0.dist-info/WHEEL +5 -0
- deepwrap-0.1.0.dist-info/entry_points.txt +2 -0
- deepwrap-0.1.0.dist-info/licenses/LICENSE +21 -0
- deepwrap-0.1.0.dist-info/top_level.txt +1 -0
deepwrap/__init__.py
ADDED
deepwrap/__main__.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from deepwrap import Client
|
|
9
|
+
from deepwrap.core import Auth
|
|
10
|
+
from deepwrap.interfaces.cli import main as interactive_main
|
|
11
|
+
from deepwrap.utils.config_store import ConfigStore
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
APP_NAME = "deepwrap"
|
|
15
|
+
VERSION = "0.1.0"
|
|
16
|
+
SUPPORTED_MODELS = ("expert", "default", "vision")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeepWrapCLIError(click.ClickException):
|
|
20
|
+
"""
|
|
21
|
+
User-facing CLI error.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_token(token: str) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Normalize a raw token or Authorization header value.
|
|
28
|
+
|
|
29
|
+
Accepts:
|
|
30
|
+
- raw_token
|
|
31
|
+
- Bearer raw_token
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
token = token.strip()
|
|
35
|
+
|
|
36
|
+
if token.lower().startswith("bearer "):
|
|
37
|
+
token = token[7:].strip()
|
|
38
|
+
|
|
39
|
+
if not token:
|
|
40
|
+
raise DeepWrapCLIError("Token cannot be empty.")
|
|
41
|
+
|
|
42
|
+
return token
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_saved_token() -> Optional[str]:
|
|
46
|
+
"""
|
|
47
|
+
Load token from local DeepWrap config.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
config = ConfigStore().load()
|
|
51
|
+
|
|
52
|
+
if not config.token:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
return normalize_token(config.token)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def save_token(token: str) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Save token to the local DeepWrap config file.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
store = ConfigStore()
|
|
64
|
+
store.update_token(normalize_token(token))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_token(explicit_token: Optional[str] = None) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Resolve token from explicit CLI option or saved config.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if explicit_token:
|
|
73
|
+
return normalize_token(explicit_token)
|
|
74
|
+
|
|
75
|
+
token = load_saved_token()
|
|
76
|
+
|
|
77
|
+
if token:
|
|
78
|
+
return token
|
|
79
|
+
|
|
80
|
+
raise DeepWrapCLIError(
|
|
81
|
+
"No token configured. Run `deepwrap auth` first or pass `--token`."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def echo_success(message: str) -> None:
|
|
86
|
+
click.secho(message, fg="green")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def echo_warning(message: str) -> None:
|
|
90
|
+
click.secho(message, fg="yellow", err=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def echo_error(message: str) -> None:
|
|
94
|
+
click.secho(message, fg="red", err=True)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@click.group(
|
|
98
|
+
invoke_without_command=True,
|
|
99
|
+
context_settings={
|
|
100
|
+
"help_option_names": ["-h", "--help"],
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
@click.version_option(version=VERSION, prog_name=APP_NAME)
|
|
104
|
+
@click.pass_context
|
|
105
|
+
def cli(ctx: click.Context) -> None:
|
|
106
|
+
"""
|
|
107
|
+
DeepWrap command line interface.
|
|
108
|
+
|
|
109
|
+
Run without a command to start the interactive terminal UI.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
if ctx.invoked_subcommand is None:
|
|
113
|
+
interactive_main()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@cli.command("auth")
|
|
117
|
+
@click.option(
|
|
118
|
+
"--manual",
|
|
119
|
+
is_flag=True,
|
|
120
|
+
help="Enter bearer token manually instead of using browser login.",
|
|
121
|
+
)
|
|
122
|
+
@click.option(
|
|
123
|
+
"--token",
|
|
124
|
+
type=str,
|
|
125
|
+
default=None,
|
|
126
|
+
help="Save a bearer token directly.",
|
|
127
|
+
)
|
|
128
|
+
@click.option(
|
|
129
|
+
"--timeout",
|
|
130
|
+
type=int,
|
|
131
|
+
default=None,
|
|
132
|
+
help="Timeout in seconds for browser authentication.",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
"--save/--no-save",
|
|
136
|
+
default=True,
|
|
137
|
+
show_default=True,
|
|
138
|
+
help="Save the token to the DeepWrap config file.",
|
|
139
|
+
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--show-token",
|
|
142
|
+
is_flag=True,
|
|
143
|
+
help="Print the captured token after authentication.",
|
|
144
|
+
)
|
|
145
|
+
def auth_command(
|
|
146
|
+
manual: bool,
|
|
147
|
+
token: Optional[str],
|
|
148
|
+
timeout: Optional[int],
|
|
149
|
+
save: bool,
|
|
150
|
+
show_token: bool,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Authenticate DeepWrap.
|
|
154
|
+
|
|
155
|
+
Default behavior:
|
|
156
|
+
deepwrap auth
|
|
157
|
+
|
|
158
|
+
This opens browser login and captures the bearer token.
|
|
159
|
+
|
|
160
|
+
Manual token:
|
|
161
|
+
deepwrap auth --manual
|
|
162
|
+
|
|
163
|
+
Direct token:
|
|
164
|
+
deepwrap auth --token "..."
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
captured_token: Optional[str] = None
|
|
169
|
+
|
|
170
|
+
if token:
|
|
171
|
+
captured_token = normalize_token(token)
|
|
172
|
+
|
|
173
|
+
elif manual:
|
|
174
|
+
entered = click.prompt(
|
|
175
|
+
"Bearer token",
|
|
176
|
+
hide_input=True,
|
|
177
|
+
confirmation_prompt=False,
|
|
178
|
+
type=str,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
captured_token = normalize_token(entered)
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
click.echo("Starting browser authentication...")
|
|
185
|
+
captured_token = Auth().browser(timeout=timeout)
|
|
186
|
+
|
|
187
|
+
if captured_token:
|
|
188
|
+
captured_token = normalize_token(captured_token)
|
|
189
|
+
|
|
190
|
+
if not captured_token:
|
|
191
|
+
raise DeepWrapCLIError(
|
|
192
|
+
"Authentication failed: bearer token was not captured."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if save:
|
|
196
|
+
store = ConfigStore()
|
|
197
|
+
store.update_token(captured_token)
|
|
198
|
+
echo_success(f"Authentication successful. Token saved to: {store.path}")
|
|
199
|
+
else:
|
|
200
|
+
echo_success("Authentication successful. Token was not saved.")
|
|
201
|
+
|
|
202
|
+
if show_token:
|
|
203
|
+
click.echo(captured_token)
|
|
204
|
+
|
|
205
|
+
except KeyboardInterrupt:
|
|
206
|
+
click.echo()
|
|
207
|
+
raise DeepWrapCLIError("Authentication cancelled.")
|
|
208
|
+
|
|
209
|
+
except DeepWrapCLIError:
|
|
210
|
+
raise
|
|
211
|
+
|
|
212
|
+
except Exception as exc:
|
|
213
|
+
raise DeepWrapCLIError(f"Authentication failed: {exc}") from exc
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@cli.command("logout")
|
|
217
|
+
def logout_command() -> None:
|
|
218
|
+
"""
|
|
219
|
+
Remove the saved DeepWrap token from local config.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
store = ConfigStore()
|
|
224
|
+
config = store.load()
|
|
225
|
+
config.token = None
|
|
226
|
+
store.save(config)
|
|
227
|
+
|
|
228
|
+
echo_success(f"Logged out. Token removed from: {store.path}")
|
|
229
|
+
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
raise DeepWrapCLIError(f"Logout failed: {exc}") from exc
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@cli.command("api")
|
|
235
|
+
@click.option(
|
|
236
|
+
"--host",
|
|
237
|
+
default="127.0.0.1",
|
|
238
|
+
show_default=True,
|
|
239
|
+
help="Host to bind the API server to.",
|
|
240
|
+
)
|
|
241
|
+
@click.option(
|
|
242
|
+
"--port",
|
|
243
|
+
default=8000,
|
|
244
|
+
show_default=True,
|
|
245
|
+
type=int,
|
|
246
|
+
help="Port to bind the API server to.",
|
|
247
|
+
)
|
|
248
|
+
@click.option(
|
|
249
|
+
"--reload",
|
|
250
|
+
is_flag=True,
|
|
251
|
+
help="Enable auto-reload for development.",
|
|
252
|
+
)
|
|
253
|
+
@click.option(
|
|
254
|
+
"--workers",
|
|
255
|
+
default=1,
|
|
256
|
+
show_default=True,
|
|
257
|
+
type=int,
|
|
258
|
+
help="Number of Uvicorn workers. Ignored when --reload is enabled.",
|
|
259
|
+
)
|
|
260
|
+
@click.option(
|
|
261
|
+
"--log-level",
|
|
262
|
+
default="info",
|
|
263
|
+
show_default=True,
|
|
264
|
+
type=click.Choice(["critical", "error", "warning", "info", "debug", "trace"]),
|
|
265
|
+
help="Uvicorn log level.",
|
|
266
|
+
)
|
|
267
|
+
def api_command(
|
|
268
|
+
host: str,
|
|
269
|
+
port: int,
|
|
270
|
+
reload: bool,
|
|
271
|
+
workers: int,
|
|
272
|
+
log_level: str,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""
|
|
275
|
+
Start the DeepWrap FastAPI server.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
import uvicorn
|
|
280
|
+
except ImportError as exc:
|
|
281
|
+
raise DeepWrapCLIError(
|
|
282
|
+
"Missing dependency: uvicorn. Install with: pip install fastapi uvicorn"
|
|
283
|
+
) from exc
|
|
284
|
+
|
|
285
|
+
if workers < 1:
|
|
286
|
+
raise DeepWrapCLIError("--workers must be greater than or equal to 1.")
|
|
287
|
+
|
|
288
|
+
if reload and workers != 1:
|
|
289
|
+
echo_warning("--workers is ignored when --reload is enabled.")
|
|
290
|
+
workers = 1
|
|
291
|
+
|
|
292
|
+
uvicorn.run(
|
|
293
|
+
"deepwrap.interfaces.api:app",
|
|
294
|
+
host=host,
|
|
295
|
+
port=port,
|
|
296
|
+
reload=reload,
|
|
297
|
+
workers=workers,
|
|
298
|
+
log_level=log_level,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@cli.command("chat")
|
|
303
|
+
@click.argument("message", nargs=-1, required=True)
|
|
304
|
+
@click.option(
|
|
305
|
+
"--token",
|
|
306
|
+
type=str,
|
|
307
|
+
default=None,
|
|
308
|
+
help="Bearer token. If omitted, DeepWrap loads the saved config token.",
|
|
309
|
+
)
|
|
310
|
+
@click.option(
|
|
311
|
+
"--model",
|
|
312
|
+
type=click.Choice(SUPPORTED_MODELS),
|
|
313
|
+
default="expert",
|
|
314
|
+
show_default=True,
|
|
315
|
+
)
|
|
316
|
+
@click.option(
|
|
317
|
+
"--thinking/--no-thinking",
|
|
318
|
+
default=True,
|
|
319
|
+
show_default=True,
|
|
320
|
+
help="Enable or disable thinking output.",
|
|
321
|
+
)
|
|
322
|
+
@click.option(
|
|
323
|
+
"--search/--no-search",
|
|
324
|
+
default=True,
|
|
325
|
+
show_default=True,
|
|
326
|
+
help="Enable or disable search.",
|
|
327
|
+
)
|
|
328
|
+
@click.option(
|
|
329
|
+
"--god/--no-god",
|
|
330
|
+
default=False,
|
|
331
|
+
show_default=True,
|
|
332
|
+
help="Enable or disable God Mode for this chat session.",
|
|
333
|
+
)
|
|
334
|
+
@click.option(
|
|
335
|
+
"--stream/--no-stream",
|
|
336
|
+
default=False,
|
|
337
|
+
show_default=True,
|
|
338
|
+
help="Stream response chunks instead of printing after completion.",
|
|
339
|
+
)
|
|
340
|
+
def chat_command(
|
|
341
|
+
message: tuple[str, ...],
|
|
342
|
+
token: Optional[str],
|
|
343
|
+
model: str,
|
|
344
|
+
thinking: bool,
|
|
345
|
+
search: bool,
|
|
346
|
+
god: bool,
|
|
347
|
+
stream: bool,
|
|
348
|
+
) -> None:
|
|
349
|
+
"""
|
|
350
|
+
Send a single non-interactive chat request.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
text = " ".join(message).strip()
|
|
354
|
+
|
|
355
|
+
if not text:
|
|
356
|
+
raise DeepWrapCLIError("Message cannot be empty.")
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
resolved_token = resolve_token(token)
|
|
360
|
+
|
|
361
|
+
client = Client(api_key=resolved_token)
|
|
362
|
+
chat = client.chats.create_session(
|
|
363
|
+
model=model,
|
|
364
|
+
god_mode=god,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
response = chat.respond(
|
|
368
|
+
text,
|
|
369
|
+
thinking=thinking,
|
|
370
|
+
search=search,
|
|
371
|
+
stream=stream,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
if stream:
|
|
375
|
+
for chunk in response:
|
|
376
|
+
click.echo(chunk, nl=False)
|
|
377
|
+
click.echo()
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
click.echo(response)
|
|
381
|
+
|
|
382
|
+
except DeepWrapCLIError:
|
|
383
|
+
raise
|
|
384
|
+
|
|
385
|
+
except KeyboardInterrupt:
|
|
386
|
+
click.echo()
|
|
387
|
+
raise DeepWrapCLIError("Chat cancelled.")
|
|
388
|
+
|
|
389
|
+
except Exception as exc:
|
|
390
|
+
raise DeepWrapCLIError(f"Chat failed: {exc}") from exc
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@cli.command("config")
|
|
394
|
+
def config_command() -> None:
|
|
395
|
+
"""
|
|
396
|
+
Show DeepWrap config location and basic status.
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
store = ConfigStore()
|
|
401
|
+
config = store.load()
|
|
402
|
+
|
|
403
|
+
click.echo(f"Config path: {store.path}")
|
|
404
|
+
click.echo(f"Token configured: {'yes' if config.token else 'no'}")
|
|
405
|
+
|
|
406
|
+
if getattr(config, "model", None):
|
|
407
|
+
click.echo(f"Default model: {config.model}")
|
|
408
|
+
|
|
409
|
+
if hasattr(config, "show_thinking"):
|
|
410
|
+
click.echo(f"Show thinking: {'yes' if config.show_thinking else 'no'}")
|
|
411
|
+
|
|
412
|
+
if hasattr(config, "search_enabled"):
|
|
413
|
+
click.echo(f"Search enabled: {'yes' if config.search_enabled else 'no'}")
|
|
414
|
+
|
|
415
|
+
if hasattr(config, "god_mode"):
|
|
416
|
+
click.echo(f"God Mode: {'yes' if config.god_mode else 'no'}")
|
|
417
|
+
|
|
418
|
+
except Exception as exc:
|
|
419
|
+
raise DeepWrapCLIError(f"Failed to read config: {exc}") from exc
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def main() -> None:
|
|
423
|
+
try:
|
|
424
|
+
cli()
|
|
425
|
+
|
|
426
|
+
except DeepWrapCLIError as exc:
|
|
427
|
+
echo_error(f"Error: {exc.message}")
|
|
428
|
+
sys.exit(exc.exit_code)
|
|
429
|
+
|
|
430
|
+
except KeyboardInterrupt:
|
|
431
|
+
click.echo()
|
|
432
|
+
echo_warning("Cancelled.")
|
|
433
|
+
sys.exit(130)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
main()
|
deepwrap/api/__init__.py
ADDED
deepwrap/api/base.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from deepwrap.client import Client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseAPI:
|
|
8
|
+
"""
|
|
9
|
+
Base class for all API modules.
|
|
10
|
+
|
|
11
|
+
Provides shared access to the root client and its session manager, along
|
|
12
|
+
with convenience wrappers for GET and POST requests.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, client: "Client") -> None:
|
|
16
|
+
self._client = client
|
|
17
|
+
self._session = client.session
|
|
18
|
+
|
|
19
|
+
def _get(self, url: str, **kwargs: Any):
|
|
20
|
+
"""
|
|
21
|
+
Send a GET request through the shared session manager.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
return self._session.get(url, **kwargs)
|
|
25
|
+
|
|
26
|
+
def _post(self, url: str, **kwargs: Any):
|
|
27
|
+
"""
|
|
28
|
+
Send a POST request through the shared session manager.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
return self._session.post(url, **kwargs)
|