zjcode 0.0.1__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 (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. zjcode-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,541 @@
1
+ """CLI commands for the `auth` group: manage stored provider credentials.
2
+
3
+ These subcommands mirror the in-TUI `/auth` modal verbs so credentials can be
4
+ managed non-interactively (dotfile bootstrap, CI, remote boxes) without
5
+ launching the Textual app:
6
+
7
+ - `auth list` — one row per known provider with its resolution status.
8
+ - `auth set <provider>` — store an API key read from stdin (preferred) or,
9
+ with `--from-env VAR`, copied from a process environment variable.
10
+ - `auth remove <provider>` — delete a stored credential (aliases `rm`/`delete`).
11
+ - `auth status <provider>` — print the resolution source (`stored`,
12
+ `env: VAR`, `missing`, ...) for one provider.
13
+ - `auth path` — print the resolved `auth.json` path.
14
+
15
+ Security notes:
16
+
17
+ - The key value is never echoed, logged, or printed back. `set` defaults to
18
+ reading from stdin so the key never lands in shell history or argv, and it
19
+ refuses an interactive TTY (use `--from-env` instead) so an accidental
20
+ invocation cannot hang waiting on input.
21
+ - `set` and `remove` route through `auth_store`, so chmod warnings from the
22
+ store rewrite (the same path the TUI uses) are surfaced on stderr.
23
+
24
+ Help rendering for a bare `auth` invocation is served by `ui.show_auth_help`,
25
+ which does not import this module. The heavy `model_config` imports here are
26
+ function-local so a bare `auth`/`auth -h` invocation stays on the startup fast
27
+ path (`parse_args` imports this module only for its light top-level imports).
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import sys
33
+ from typing import TYPE_CHECKING, Any
34
+
35
+ if TYPE_CHECKING:
36
+ import argparse
37
+ from collections.abc import Callable
38
+
39
+ from deepagents_code.model_config import ProviderAuthStatus
40
+
41
+
42
+ def _lazy_ui_help(fn_name: str) -> Callable[[], None]:
43
+ """Return a callable that lazily imports and invokes a `ui` help function."""
44
+
45
+ def _show() -> None:
46
+ from deepagents_code import ui
47
+
48
+ getattr(ui, fn_name)()
49
+
50
+ return _show
51
+
52
+
53
+ def setup_auth_parser(
54
+ subparsers: Any, # noqa: ANN401
55
+ *,
56
+ make_help_action: Callable[[Callable[[], None]], type[argparse.Action]],
57
+ ) -> None:
58
+ """Register the `dcode auth` command group.
59
+
60
+ Args:
61
+ subparsers: The `argparse` subparsers object from the top-level CLI
62
+ parser, onto which the `auth` command group is attached.
63
+ make_help_action: Factory that wraps a `show_*` callable into an
64
+ `argparse.Action` so `-h/--help` renders the hand-maintained
65
+ help screen from `deepagents_code.ui`.
66
+ """
67
+ auth_parser = subparsers.add_parser(
68
+ "auth",
69
+ help="Manage stored model-provider credentials",
70
+ add_help=False,
71
+ )
72
+ auth_parser.add_argument(
73
+ "-h",
74
+ "--help",
75
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
76
+ )
77
+ auth_sub = auth_parser.add_subparsers(dest="auth_command")
78
+
79
+ list_parser = auth_sub.add_parser(
80
+ "list",
81
+ aliases=["ls"],
82
+ help="List providers and their credential status",
83
+ add_help=False,
84
+ )
85
+ list_parser.add_argument(
86
+ "-h",
87
+ "--help",
88
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
89
+ )
90
+
91
+ set_parser = auth_sub.add_parser(
92
+ "set",
93
+ help="Store an API key for a provider (key read from stdin)",
94
+ add_help=False,
95
+ )
96
+ set_parser.add_argument("provider", help="Provider name (e.g. anthropic)")
97
+ set_parser.add_argument(
98
+ "--from-env",
99
+ dest="from_env",
100
+ metavar="VAR",
101
+ default=None,
102
+ help="Copy the key from this environment variable instead of stdin",
103
+ )
104
+ set_parser.add_argument(
105
+ "--project",
106
+ dest="project",
107
+ metavar="NAME",
108
+ default=None,
109
+ help="With `set langsmith`, set a custom LangSmith project name",
110
+ )
111
+ set_parser.add_argument(
112
+ "--base-url",
113
+ dest="base_url",
114
+ metavar="URL",
115
+ default=None,
116
+ help=(
117
+ "Pair an endpoint with the key. For `set langsmith`, accepts the "
118
+ "`us`/`eu` region shorthands or a full URL"
119
+ ),
120
+ )
121
+ set_parser.add_argument(
122
+ "-h",
123
+ "--help",
124
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
125
+ )
126
+
127
+ remove_parser = auth_sub.add_parser(
128
+ "remove",
129
+ aliases=["rm", "delete"],
130
+ help="Remove a stored credential for a provider",
131
+ add_help=False,
132
+ )
133
+ remove_parser.add_argument("provider", help="Provider name (e.g. anthropic)")
134
+ remove_parser.add_argument(
135
+ "-h",
136
+ "--help",
137
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
138
+ )
139
+
140
+ status_parser = auth_sub.add_parser(
141
+ "status",
142
+ help="Show the credential resolution source for one provider",
143
+ add_help=False,
144
+ )
145
+ status_parser.add_argument(
146
+ "provider",
147
+ nargs="?",
148
+ default=None,
149
+ metavar="provider",
150
+ help="Provider name (e.g. anthropic)",
151
+ )
152
+ status_parser.add_argument(
153
+ "-h",
154
+ "--help",
155
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
156
+ )
157
+
158
+ path_parser = auth_sub.add_parser(
159
+ "path",
160
+ help="Print the resolved auth.json path",
161
+ add_help=False,
162
+ )
163
+ path_parser.add_argument(
164
+ "-h",
165
+ "--help",
166
+ action=make_help_action(_lazy_ui_help("show_auth_help")),
167
+ )
168
+
169
+
170
+ def run_auth_command(args: argparse.Namespace) -> int:
171
+ """Dispatch a parsed `dcode auth` invocation.
172
+
173
+ Returns:
174
+ Process exit code (`0` on success, non-zero on error).
175
+ """
176
+ command = getattr(args, "auth_command", None)
177
+ if command in {"list", "ls"}:
178
+ return _run_list()
179
+ if command == "set":
180
+ return _run_set(
181
+ args.provider,
182
+ from_env=args.from_env,
183
+ project=getattr(args, "project", None),
184
+ base_url=getattr(args, "base_url", None),
185
+ )
186
+ if command in {"remove", "rm", "delete"}:
187
+ return _run_remove(args.provider)
188
+ if command == "status":
189
+ return _run_status(getattr(args, "provider", None))
190
+ if command == "path":
191
+ return _run_path()
192
+ from deepagents_code.ui import show_auth_help
193
+
194
+ show_auth_help()
195
+ return 0
196
+
197
+
198
+ def _resolution_label(status: ProviderAuthStatus) -> str:
199
+ """Render a provider's auth status as a plain-text resolution source.
200
+
201
+ Mirrors the badges the `/auth` modal shows (`stored`, `env: VAR`,
202
+ `missing`) so debugging "why isn't my key picked up?" is one command.
203
+
204
+ Returns:
205
+ A short, terminal-safe label describing where credentials resolve from.
206
+ """
207
+ from deepagents_code.model_config import (
208
+ ProviderAuthSource,
209
+ ProviderAuthState,
210
+ resolved_env_var_name,
211
+ )
212
+
213
+ state = status.state
214
+ if state is ProviderAuthState.CONFIGURED:
215
+ if status.source is ProviderAuthSource.STORED:
216
+ return "stored"
217
+ if status.source is ProviderAuthSource.ENV and status.env_var:
218
+ return f"env: {resolved_env_var_name(status.env_var)}"
219
+ return "configured"
220
+ if state is ProviderAuthState.MISSING:
221
+ return "missing"
222
+ if state is ProviderAuthState.NOT_REQUIRED:
223
+ return status.detail or "no API key required"
224
+ if state is ProviderAuthState.IMPLICIT:
225
+ return status.detail or "implicit auth"
226
+ if state is ProviderAuthState.MANAGED:
227
+ return status.detail or "custom auth"
228
+ # Catch-all for `ProviderAuthState.UNKNOWN` and any state added later, so a
229
+ # new enum member degrades to a readable label rather than raising here.
230
+ return status.detail or "credentials unknown"
231
+
232
+
233
+ def _known_providers() -> tuple[list[str], str | None]:
234
+ """Return the providers shown by `list`/`status` plus a store-warning.
235
+
236
+ Matches the `/auth` manager's set: well-known providers whose integration
237
+ package is installed, plus any provider with a stored credential or an
238
+ `api_key_env` declared in `config.toml` (always shown so stale keys can be
239
+ cleaned up and explicit declarations stay visible).
240
+
241
+ A corrupt store collapses the stored-credential arm to the empty set (so a
242
+ stored-only provider silently drops out of the listing). When that happens
243
+ the corruption message is returned as the second tuple element so the
244
+ caller can surface it on stderr; the provider list alone is not authoritative
245
+ when this is non-`None`. Returning the message as data (rather than relying
246
+ on a sibling `_warn_if_store_unreadable` re-read) reads `auth.json` once,
247
+ removing the double-read and the TOCTOU window between the two reads.
248
+
249
+ Returns:
250
+ A `(providers, warning)` tuple: the sorted provider names, and the
251
+ corrupt-store message when the store is present but unreadable, else
252
+ `None`.
253
+ """
254
+ from deepagents_code import auth_store
255
+ from deepagents_code.model_config import (
256
+ CODEX_PROVIDER,
257
+ PROVIDER_API_KEY_ENV,
258
+ SERVICE_API_KEY_ENV,
259
+ ModelConfig,
260
+ get_available_models,
261
+ )
262
+
263
+ warning: str | None = None
264
+ try:
265
+ stored = set(auth_store.list_configured_providers())
266
+ except RuntimeError as exc:
267
+ warning = str(exc)
268
+ stored = set()
269
+ config = ModelConfig.load()
270
+ config_providers = {
271
+ name for name, cfg in config.providers.items() if cfg.get("api_key_env")
272
+ }
273
+ installed = set(get_available_models().keys())
274
+ well_known_installed = set(PROVIDER_API_KEY_ENV) & installed
275
+ # `openai_codex` uses ChatGPT OAuth through `langchain-openai`, so it has
276
+ # no API-key env var entry. Mirror the TUI auth manager by showing it when
277
+ # the OpenAI integration was discovered.
278
+ codex_installed = {CODEX_PROVIDER} if "openai" in installed else set()
279
+ # Non-model services (Tavily web search, LangSmith tracing) are always
280
+ # shown — they are configurable here regardless of any backing package —
281
+ # so the CLI listing matches the TUI `/auth` manager.
282
+ services = set(SERVICE_API_KEY_ENV)
283
+ providers = sorted(
284
+ well_known_installed | codex_installed | stored | config_providers | services
285
+ )
286
+ return providers, warning
287
+
288
+
289
+ def _print_store_warning(message: str) -> None:
290
+ """Print a corrupt-store diagnostic to stderr."""
291
+ print(f"Warning: {message}", file=sys.stderr) # noqa: T201
292
+
293
+
294
+ def _warn_if_store_unreadable() -> None:
295
+ """Print a stderr warning when the credential store is present but corrupt.
296
+
297
+ Used by `status`, which prints a single requested row and so does not go
298
+ through `_known_providers` (whose return value already carries the
299
+ corruption message for the `list` path).
300
+
301
+ `get_provider_auth_status` (via `model_config._has_stored_credential`)
302
+ swallows a corrupt-store `RuntimeError` and reports `missing`/env-only,
303
+ which would otherwise make `status` silently misreport a provider whose
304
+ stored key cannot be read. The TUI surfaces this with a banner in the
305
+ `/auth` modal; this is the CLI equivalent so the printed row is not taken
306
+ as authoritative when the store is broken.
307
+ """
308
+ from deepagents_code import auth_store
309
+
310
+ try:
311
+ auth_store.list_configured_providers()
312
+ except RuntimeError as exc:
313
+ _print_store_warning(str(exc))
314
+
315
+
316
+ def _print_rows(providers: list[str]) -> None:
317
+ """Print one `<provider> <status>` row per provider, column-aligned."""
318
+ from deepagents_code.model_config import (
319
+ get_provider_auth_status,
320
+ get_service_auth_status,
321
+ is_service,
322
+ )
323
+
324
+ if not providers:
325
+ return
326
+ width = max(len(name) for name in providers)
327
+ for provider in providers:
328
+ status = (
329
+ get_service_auth_status(provider)
330
+ if is_service(provider)
331
+ else get_provider_auth_status(provider)
332
+ )
333
+ label = _resolution_label(status)
334
+ # Plain stdout so rows stay greppable/pipeable, not Rich-styled.
335
+ print(f"{provider.ljust(width)} {label}") # noqa: T201
336
+
337
+
338
+ def _run_list() -> int:
339
+ """Print every known provider and its credential status.
340
+
341
+ Returns:
342
+ Process exit code (`0`).
343
+ """
344
+ providers, warning = _known_providers()
345
+ if warning is not None:
346
+ _print_store_warning(warning)
347
+ if not providers:
348
+ print("No providers found.") # noqa: T201
349
+ return 0
350
+ _print_rows(providers)
351
+ return 0
352
+
353
+
354
+ def _run_status(provider: str | None) -> int:
355
+ """Print the resolution source for one provider.
356
+
357
+ Returns:
358
+ Process exit code (`0` on success, `1` when the provider is omitted).
359
+ """
360
+ if provider is None:
361
+ print( # noqa: T201
362
+ "Error: auth status requires a provider. "
363
+ "Use `dcode auth list` to show all providers.",
364
+ file=sys.stderr,
365
+ )
366
+ return 1
367
+ _warn_if_store_unreadable()
368
+ _print_rows([provider])
369
+ return 0
370
+
371
+
372
+ def _run_set(
373
+ provider: str,
374
+ *,
375
+ from_env: str | None,
376
+ project: str | None,
377
+ base_url: str | None,
378
+ ) -> int:
379
+ """Store an API key for `provider`, reading it from env or stdin.
380
+
381
+ Returns:
382
+ Process exit code (`0` on success, `1` on a recoverable input error).
383
+ """
384
+ from deepagents_code.config import is_http_url, normalize_langsmith_endpoint
385
+ from deepagents_code.model_config import (
386
+ CODEX_PROVIDER,
387
+ LANGSMITH_SERVICE,
388
+ is_langsmith,
389
+ )
390
+
391
+ if provider == CODEX_PROVIDER:
392
+ print( # noqa: T201
393
+ "Error: openai_codex uses ChatGPT OAuth, not API keys. "
394
+ "Run `/auth` and select openai_codex to sign in.",
395
+ file=sys.stderr,
396
+ )
397
+ return 1
398
+
399
+ if project is not None and not is_langsmith(provider):
400
+ print( # noqa: T201
401
+ f"Error: --project is only valid for {LANGSMITH_SERVICE}.",
402
+ file=sys.stderr,
403
+ )
404
+ return 1
405
+
406
+ # Resolve --base-url before reading the store: a malformed value is a clean
407
+ # input error. `None` means "not passed" (preserve the stored endpoint); an
408
+ # explicit empty string clears it. LangSmith accepts the `us`/`eu` region
409
+ # shorthands; any non-empty value must be an http(s) URL so the key is never
410
+ # paired with a non-HTTP endpoint.
411
+ base_url_override: str | None = None
412
+ if base_url is not None:
413
+ base_url_override = (
414
+ normalize_langsmith_endpoint(base_url)
415
+ if is_langsmith(provider)
416
+ else base_url.strip()
417
+ )
418
+ if base_url_override and not is_http_url(base_url_override):
419
+ print( # noqa: T201
420
+ "Error: --base-url must be an http(s) URL.",
421
+ file=sys.stderr,
422
+ )
423
+ return 1
424
+
425
+ import os
426
+
427
+ from deepagents_code import auth_store
428
+
429
+ if from_env is not None:
430
+ key = os.environ.get(from_env)
431
+ if not key or not key.strip():
432
+ print( # noqa: T201
433
+ f"Error: environment variable {from_env} is not set or is empty.",
434
+ file=sys.stderr,
435
+ )
436
+ return 1
437
+ else:
438
+ if sys.stdin.isatty():
439
+ print( # noqa: T201
440
+ "Error: refusing to read an API key from an interactive terminal.\n"
441
+ f"Pipe the key via stdin (e.g. `echo $KEY | dcode auth set {provider}`)"
442
+ " or use --from-env VAR.",
443
+ file=sys.stderr,
444
+ )
445
+ return 1
446
+ key = sys.stdin.read()
447
+ if not key.strip():
448
+ # Mirror the `--from-env` empty-var message so an empty pipe gives a
449
+ # specific, actionable error here rather than the generic
450
+ # "API key cannot be empty" `ValueError` raised later by
451
+ # `set_stored_key`. The key value is never echoed.
452
+ print( # noqa: T201
453
+ "Error: no API key received on stdin.",
454
+ file=sys.stderr,
455
+ )
456
+ return 1
457
+
458
+ try:
459
+ # Preserve a previously stored project/endpoint unless `--project` /
460
+ # `--base-url` overrides it (an explicit empty value clears it). Resolved
461
+ # inside the try so a corrupt store surfaces as a clean error, not a
462
+ # traceback.
463
+ stored_project = (
464
+ project if project is not None else auth_store.get_stored_project(provider)
465
+ )
466
+ stored_base_url = (
467
+ base_url_override
468
+ if base_url is not None
469
+ else auth_store.get_stored_base_url(provider)
470
+ )
471
+ outcome = auth_store.set_stored_key(
472
+ provider,
473
+ key,
474
+ base_url=stored_base_url,
475
+ project=stored_project,
476
+ )
477
+ except (ValueError, RuntimeError) as exc:
478
+ # `auth_store` messages never include the secret value. `ValueError`
479
+ # carries a short fragment, `RuntimeError` a full sentence with a hint;
480
+ # print verbatim so the two stay consistent and free of double periods.
481
+ print(f"Error: {exc}", file=sys.stderr) # noqa: T201
482
+ return 1
483
+
484
+ for warning in outcome.warnings:
485
+ print(f"Warning: {warning}", file=sys.stderr) # noqa: T201
486
+ print(f"Stored credential for {provider}.") # noqa: T201
487
+ return 0
488
+
489
+
490
+ def _run_remove(provider: str) -> int:
491
+ """Delete a stored credential for `provider`.
492
+
493
+ Returns:
494
+ Process exit code (`0`).
495
+ """
496
+ from deepagents_code.model_config import CODEX_PROVIDER
497
+
498
+ if provider == CODEX_PROVIDER:
499
+ from deepagents_code.integrations import openai_codex
500
+
501
+ try:
502
+ removed = openai_codex.logout()
503
+ except OSError as exc:
504
+ print( # noqa: T201
505
+ f"Error: failed to remove stored credential for {provider}: {exc}",
506
+ file=sys.stderr,
507
+ )
508
+ return 1
509
+ if removed:
510
+ print(f"Removed stored credential for {provider}.") # noqa: T201
511
+ else:
512
+ print(f"No stored credential for {provider}.") # noqa: T201
513
+ return 0
514
+
515
+ from deepagents_code import auth_store
516
+
517
+ try:
518
+ result = auth_store.delete_stored_key(provider)
519
+ except RuntimeError as exc:
520
+ print(f"Error: {exc}", file=sys.stderr) # noqa: T201
521
+ return 1
522
+ # Surface chmod warnings on the rewritten store, symmetric with `set`.
523
+ for warning in result.warnings:
524
+ print(f"Warning: {warning}", file=sys.stderr) # noqa: T201
525
+ if result.removed:
526
+ print(f"Removed stored credential for {provider}.") # noqa: T201
527
+ else:
528
+ print(f"No stored credential for {provider}.") # noqa: T201
529
+ return 0
530
+
531
+
532
+ def _run_path() -> int:
533
+ """Print the resolved `auth.json` path.
534
+
535
+ Returns:
536
+ Process exit code (`0`).
537
+ """
538
+ from deepagents_code import auth_store
539
+
540
+ print(auth_store.auth_path()) # noqa: T201
541
+ return 0