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 ADDED
@@ -0,0 +1,3 @@
1
+ from .client import Client
2
+
3
+ __all__ = ["Client"]
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()
@@ -0,0 +1,7 @@
1
+ from .pow import PowAPI
2
+ from .chats import ChatsAPI
3
+
4
+ __all__ = [
5
+ "PowAPI",
6
+ "ChatsAPI"
7
+ ]
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)