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.
- deepagents_code/__init__.py +42 -0
- deepagents_code/__main__.py +6 -0
- deepagents_code/_ask_user_types.py +90 -0
- deepagents_code/_cli_context.py +96 -0
- deepagents_code/_constants.py +41 -0
- deepagents_code/_debug.py +148 -0
- deepagents_code/_debug_buffer.py +204 -0
- deepagents_code/_env_vars.py +411 -0
- deepagents_code/_fake_models.py +66 -0
- deepagents_code/_git.py +521 -0
- deepagents_code/_paths.py +69 -0
- deepagents_code/_server_config.py +576 -0
- deepagents_code/_session_stats.py +235 -0
- deepagents_code/_startup_error.py +45 -0
- deepagents_code/_testing_models.py +305 -0
- deepagents_code/_textual_patches.py +420 -0
- deepagents_code/_tool_stream.py +694 -0
- deepagents_code/_version.py +46 -0
- deepagents_code/agent.py +1976 -0
- deepagents_code/app.py +19239 -0
- deepagents_code/app.tcss +438 -0
- deepagents_code/approval_mode.py +131 -0
- deepagents_code/ask_user.py +306 -0
- deepagents_code/auth_display.py +185 -0
- deepagents_code/auth_store.py +545 -0
- deepagents_code/built_in_skills/__init__.py +5 -0
- deepagents_code/built_in_skills/remember/SKILL.md +118 -0
- deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
- deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
- deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
- deepagents_code/client/__init__.py +1 -0
- deepagents_code/client/commands/__init__.py +1 -0
- deepagents_code/client/commands/auth.py +541 -0
- deepagents_code/client/commands/config.py +714 -0
- deepagents_code/client/commands/mcp.py +250 -0
- deepagents_code/client/commands/tools.py +416 -0
- deepagents_code/client/launch/__init__.py +1 -0
- deepagents_code/client/launch/server.py +978 -0
- deepagents_code/client/launch/server_manager.py +540 -0
- deepagents_code/client/non_interactive.py +1758 -0
- deepagents_code/client/remote_client.py +794 -0
- deepagents_code/clipboard.py +217 -0
- deepagents_code/command_registry.py +450 -0
- deepagents_code/config.py +4829 -0
- deepagents_code/config_manifest.py +1451 -0
- deepagents_code/configurable_model.py +577 -0
- deepagents_code/default_agent_prompt.md +12 -0
- deepagents_code/doctor.py +559 -0
- deepagents_code/editor.py +142 -0
- deepagents_code/event_bus.py +411 -0
- deepagents_code/extras_info.py +661 -0
- deepagents_code/file_ops.py +576 -0
- deepagents_code/formatting.py +135 -0
- deepagents_code/goal_rubric.py +497 -0
- deepagents_code/goal_tools.py +459 -0
- deepagents_code/hooks.py +348 -0
- deepagents_code/input.py +1041 -0
- deepagents_code/integrations/__init__.py +1 -0
- deepagents_code/integrations/openai_codex.py +551 -0
- deepagents_code/integrations/sandbox_config.py +198 -0
- deepagents_code/integrations/sandbox_factory.py +1124 -0
- deepagents_code/integrations/sandbox_provider.py +137 -0
- deepagents_code/integrations/sandbox_registry.py +350 -0
- deepagents_code/iterm_cursor_guide.py +176 -0
- deepagents_code/local_context.py +926 -0
- deepagents_code/main.py +3985 -0
- deepagents_code/managed_tools.py +642 -0
- deepagents_code/mcp_auth.py +1950 -0
- deepagents_code/mcp_config.py +176 -0
- deepagents_code/mcp_disabled.py +212 -0
- deepagents_code/mcp_login_service.py +281 -0
- deepagents_code/mcp_oauth_ui.py +199 -0
- deepagents_code/mcp_providers/__init__.py +23 -0
- deepagents_code/mcp_providers/_registry.py +39 -0
- deepagents_code/mcp_providers/base.py +133 -0
- deepagents_code/mcp_providers/github.py +102 -0
- deepagents_code/mcp_providers/slack.py +175 -0
- deepagents_code/mcp_tools.py +2427 -0
- deepagents_code/mcp_trust.py +207 -0
- deepagents_code/media_utils.py +826 -0
- deepagents_code/memory_guard.py +474 -0
- deepagents_code/model_config.py +4156 -0
- deepagents_code/notifications.py +247 -0
- deepagents_code/offload.py +402 -0
- deepagents_code/onboarding.py +223 -0
- deepagents_code/output.py +69 -0
- deepagents_code/paste_collapse.py +103 -0
- deepagents_code/project_utils.py +231 -0
- deepagents_code/py.typed +0 -0
- deepagents_code/reasoning_effort.py +641 -0
- deepagents_code/reliable_rubric.py +97 -0
- deepagents_code/resume_state.py +237 -0
- deepagents_code/server_graph.py +310 -0
- deepagents_code/session_end_summary.py +329 -0
- deepagents_code/sessions.py +1716 -0
- deepagents_code/skills/__init__.py +18 -0
- deepagents_code/skills/commands.py +1252 -0
- deepagents_code/skills/invocation.py +112 -0
- deepagents_code/skills/load.py +196 -0
- deepagents_code/skills/trust.py +547 -0
- deepagents_code/state_migration.py +136 -0
- deepagents_code/subagents.py +278 -0
- deepagents_code/system_prompt.md +204 -0
- deepagents_code/terminal_capabilities.py +115 -0
- deepagents_code/terminal_escape.py +287 -0
- deepagents_code/theme.py +891 -0
- deepagents_code/todo_list_prompt.md +12 -0
- deepagents_code/tool_catalog.py +509 -0
- deepagents_code/tool_display.py +367 -0
- deepagents_code/tools.py +516 -0
- deepagents_code/tui/__init__.py +1 -0
- deepagents_code/tui/textual_adapter.py +2553 -0
- deepagents_code/tui/widgets/__init__.py +9 -0
- deepagents_code/tui/widgets/_js_eval_display.py +139 -0
- deepagents_code/tui/widgets/_links.py +261 -0
- deepagents_code/tui/widgets/agent_selector.py +420 -0
- deepagents_code/tui/widgets/approval.py +602 -0
- deepagents_code/tui/widgets/ask_user.py +515 -0
- deepagents_code/tui/widgets/auth.py +1997 -0
- deepagents_code/tui/widgets/autocomplete.py +890 -0
- deepagents_code/tui/widgets/chat_input.py +3181 -0
- deepagents_code/tui/widgets/codex_auth.py +452 -0
- deepagents_code/tui/widgets/cwd_switch.py +242 -0
- deepagents_code/tui/widgets/debug_console.py +868 -0
- deepagents_code/tui/widgets/diff.py +252 -0
- deepagents_code/tui/widgets/effort_selector.py +189 -0
- deepagents_code/tui/widgets/goal_review.py +390 -0
- deepagents_code/tui/widgets/goal_status.py +50 -0
- deepagents_code/tui/widgets/history.py +194 -0
- deepagents_code/tui/widgets/install_confirm.py +248 -0
- deepagents_code/tui/widgets/launch_init.py +482 -0
- deepagents_code/tui/widgets/loading.py +227 -0
- deepagents_code/tui/widgets/mcp_login.py +539 -0
- deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
- deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
- deepagents_code/tui/widgets/message_store.py +999 -0
- deepagents_code/tui/widgets/messages.py +3846 -0
- deepagents_code/tui/widgets/model_selector.py +2343 -0
- deepagents_code/tui/widgets/notification_center.py +456 -0
- deepagents_code/tui/widgets/notification_detail.py +257 -0
- deepagents_code/tui/widgets/notification_settings.py +170 -0
- deepagents_code/tui/widgets/restart_prompt.py +158 -0
- deepagents_code/tui/widgets/skill_trust.py +131 -0
- deepagents_code/tui/widgets/startup_tip.py +91 -0
- deepagents_code/tui/widgets/status.py +781 -0
- deepagents_code/tui/widgets/subagent_panel.py +969 -0
- deepagents_code/tui/widgets/theme_selector.py +401 -0
- deepagents_code/tui/widgets/thread_selector.py +2564 -0
- deepagents_code/tui/widgets/tool_renderers.py +190 -0
- deepagents_code/tui/widgets/tool_widgets.py +304 -0
- deepagents_code/tui/widgets/update_available.py +311 -0
- deepagents_code/tui/widgets/update_confirm.py +184 -0
- deepagents_code/tui/widgets/update_progress.py +327 -0
- deepagents_code/tui/widgets/welcome.py +508 -0
- deepagents_code/turn_end_summary.py +522 -0
- deepagents_code/ui.py +858 -0
- deepagents_code/unicode_security.py +563 -0
- deepagents_code/update_check.py +3124 -0
- zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
- zjcode-0.0.1.dist-info/METADATA +220 -0
- zjcode-0.0.1.dist-info/RECORD +163 -0
- zjcode-0.0.1.dist-info/WHEEL +4 -0
- 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
|