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,714 @@
1
+ """CLI commands for the `config` group: inspect the configuration surface.
2
+
3
+ `config show` (aliased as `config list`/`ls`) resolves each option against the
4
+ app credential store (for credentials), the live environment, and `config.toml`,
5
+ reporting the effective value and which source provided it, matching what
6
+ `git config --list` / `aws configure list` users expect. Adding `--verbose`/`--all`
7
+ folds in each option's description and where it can be set (the static catalog).
8
+ `config get <key>` does the same for a single option. `config path` prints the
9
+ on-disk config locations.
10
+
11
+ Secret-flagged options (API keys and other credentials) are never printed by
12
+ value — `config show`/`config get` report only whether they are set and from
13
+ which source, so the output is safe to paste into a bug report.
14
+
15
+ Help rendering for a bare `config` invocation is served by `ui.show_config_help`,
16
+ which does not import this module. The heavy manifest/runtime imports here are
17
+ function-local to the subcommands, so a bare `config`/`config -h` invocation
18
+ never pulls them onto the startup path (`parse_args` does import this module to
19
+ register the subparsers, but only its light top-level imports run then).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import importlib.util
25
+ import logging
26
+ import os
27
+ import sys
28
+ from dataclasses import dataclass, field
29
+ from typing import TYPE_CHECKING, Any, NamedTuple
30
+
31
+ from deepagents_code.output import write_json
32
+
33
+ if TYPE_CHECKING:
34
+ import argparse
35
+ from collections.abc import Callable, Sequence
36
+
37
+ from deepagents_code.config_manifest import ConfigOption
38
+ from deepagents_code.output import OutputFormat
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ def _lazy_ui_help(fn_name: str) -> Callable[[], None]:
44
+ """Return a callable that lazily imports and invokes a `ui` help function."""
45
+
46
+ def _show() -> None:
47
+ from deepagents_code import ui
48
+
49
+ getattr(ui, fn_name)()
50
+
51
+ return _show
52
+
53
+
54
+ def setup_config_parser(
55
+ subparsers: Any, # noqa: ANN401
56
+ *,
57
+ make_help_action: Callable[[Callable[[], None]], type[argparse.Action]],
58
+ add_output_args: Callable[..., None],
59
+ ) -> None:
60
+ """Register the `dcode config` command group.
61
+
62
+ Args:
63
+ subparsers: The `argparse` subparsers object from the top-level CLI
64
+ parser, onto which the `config` command group is attached.
65
+ make_help_action: Factory that wraps a `show_*` callable into an
66
+ `argparse.Action` so `-h/--help` renders the hand-maintained
67
+ help screens from `deepagents_code.ui`.
68
+ add_output_args: Helper that adds the shared `--json` flag.
69
+ """
70
+ config_parser = subparsers.add_parser(
71
+ "config",
72
+ help="Inspect configuration options and their sources",
73
+ add_help=False,
74
+ )
75
+ config_parser.add_argument(
76
+ "-h",
77
+ "--help",
78
+ action=make_help_action(_lazy_ui_help("show_config_help")),
79
+ )
80
+ add_output_args(config_parser)
81
+ config_sub = config_parser.add_subparsers(dest="config_command")
82
+
83
+ def _add_verbose_arg(parser: argparse.ArgumentParser) -> None:
84
+ parser.add_argument(
85
+ "-v",
86
+ "--verbose",
87
+ "--all",
88
+ dest="verbose",
89
+ action="store_true",
90
+ help="Also show each option's description and where to set it",
91
+ )
92
+
93
+ show_parser = config_sub.add_parser(
94
+ "show",
95
+ aliases=["list", "ls"],
96
+ help="Show effective config values and their source",
97
+ add_help=False,
98
+ )
99
+ show_parser.add_argument(
100
+ "-h",
101
+ "--help",
102
+ action=make_help_action(_lazy_ui_help("show_config_help")),
103
+ )
104
+ _add_verbose_arg(show_parser)
105
+ add_output_args(show_parser)
106
+
107
+ get_parser = config_sub.add_parser(
108
+ "get",
109
+ help="Show the effective value and source for one option",
110
+ add_help=False,
111
+ )
112
+ get_parser.add_argument("key", help="Option key (e.g. interpreter.memory_limit_mb)")
113
+ get_parser.add_argument(
114
+ "-h",
115
+ "--help",
116
+ action=make_help_action(_lazy_ui_help("show_config_help")),
117
+ )
118
+ add_output_args(get_parser)
119
+
120
+ path_parser = config_sub.add_parser(
121
+ "path",
122
+ help="Show config file locations",
123
+ add_help=False,
124
+ )
125
+ path_parser.add_argument(
126
+ "-h",
127
+ "--help",
128
+ action=make_help_action(_lazy_ui_help("show_config_help")),
129
+ )
130
+ add_output_args(path_parser)
131
+
132
+
133
+ # --- Resolution -------------------------------------------------------------
134
+
135
+
136
+ @dataclass(frozen=True, slots=True)
137
+ class _StoredCredentialView:
138
+ """Snapshot of the `/auth` credential store for one command invocation.
139
+
140
+ Built once per `config show`/`get` so the store is read and parsed a single
141
+ time rather than once per credential option.
142
+ """
143
+
144
+ keys: dict[str, str] = field(repr=False)
145
+ """Provider/service name to stored API key, for `api_key` entries only.
146
+
147
+ `repr=False` keeps the secret key values out of the dataclass repr, so an
148
+ accidental log/`%r` of the view can't leak them.
149
+ """
150
+
151
+ error: str | None = None
152
+ """Secret-free remediation message when the store was unreadable, else `None`.
153
+
154
+ Never holds the underlying exception text (which can echo file bytes) or any
155
+ key value.
156
+ """
157
+
158
+
159
+ _STORE_UNREADABLE_HINT = (
160
+ "credential store unreadable; showing env/config.toml resolution instead. "
161
+ "Re-add keys via /auth (or delete a corrupt auth.json)."
162
+ )
163
+ """Fixed, secret-free notice surfaced when `auth.json` cannot be read."""
164
+
165
+
166
+ def _load_stored_credentials() -> _StoredCredentialView:
167
+ """Read every `/auth`-stored API key once, degrading a corrupt store to empty.
168
+
169
+ Reading the store a single time (rather than once per credential option)
170
+ keeps `config show` to one `auth.json` parse and one warning. A corrupt
171
+ store is logged once and reported via the returned `error`, so resolution
172
+ degrades to env/`config.toml` instead of failing the command — and the
173
+ corruption stays visible in the output rather than masquerading as an empty
174
+ store.
175
+
176
+ Returns:
177
+ A `_StoredCredentialView` whose `keys` map holds stored `api_key` values
178
+ by provider, and whose `error` is set only when the store was unreadable.
179
+ """
180
+ from deepagents_code import auth_store
181
+
182
+ try:
183
+ creds = auth_store.load_credentials()
184
+ except RuntimeError:
185
+ # Omit the exception text on purpose: it can echo file contents, and the
186
+ # remediation is identical regardless of the specific parse failure.
187
+ logger.warning("Could not read stored credentials; treating as absent")
188
+ return _StoredCredentialView(keys={}, error=_STORE_UNREADABLE_HINT)
189
+ keys = {
190
+ provider: entry["key"]
191
+ for provider, entry in creds.items()
192
+ if entry["type"] == "api_key" and entry["key"]
193
+ }
194
+ return _StoredCredentialView(keys=keys)
195
+
196
+
197
+ def _resolve(
198
+ option: ConfigOption,
199
+ toml_data: dict[str, Any],
200
+ *,
201
+ stored: _StoredCredentialView | None = None,
202
+ ) -> tuple[bool, str, object]:
203
+ """Resolve an option for display, reporting what the runtime actually reads.
204
+
205
+ Credential options follow runtime precedence: a present `DEEPAGENTS_CODE_`
206
+ env override wins (the model factory reads it via `resolve_env_var` even
207
+ after `apply_stored_credentials` bridges a stored key onto the canonical
208
+ var), then a key stored via `/auth`, then the canonical env/`config.toml`.
209
+ Everything else delegates straight to `config_manifest.resolve_scalar`.
210
+
211
+ Args:
212
+ option: The option to resolve.
213
+ toml_data: Parsed `config.toml` contents.
214
+ stored: Pre-loaded credential-store snapshot. When `None`, the store is
215
+ read on demand — fine for one-off calls, but callers resolving many
216
+ options should load it once and pass it so `auth.json` is parsed a
217
+ single time.
218
+
219
+ Returns:
220
+ `(is_set, source, value)`, where `is_set` is `False` when the value
221
+ came from the typed default.
222
+ """
223
+ from deepagents_code.config_manifest import resolve_scalar
224
+ from deepagents_code.model_config import ProviderAuthSource
225
+
226
+ if (
227
+ option.group == "Credentials"
228
+ and option.provider is not None
229
+ and not _has_prefixed_env_override(option)
230
+ ):
231
+ if stored is None:
232
+ stored = _load_stored_credentials()
233
+ key = stored.keys.get(option.provider)
234
+ if key is not None:
235
+ return True, ProviderAuthSource.STORED.value, key
236
+
237
+ value, source = resolve_scalar(option, toml_data=toml_data)
238
+ return source != "default", source, value
239
+
240
+
241
+ def _has_prefixed_env_override(option: ConfigOption) -> bool:
242
+ """Return whether an option's `DEEPAGENTS_CODE_` env var is present."""
243
+ if option.env_var is None:
244
+ return False
245
+ prefix = "DEEPAGENTS_CODE_"
246
+ if option.env_var.startswith(prefix):
247
+ return False
248
+ return f"{prefix}{option.env_var}" in os.environ
249
+
250
+
251
+ def _display_value(option: ConfigOption, *, is_set: bool, value: object) -> str:
252
+ """Render an option value for human output, redacting secrets.
253
+
254
+ Returns:
255
+ `configured`/`not configured` for credential options, otherwise the value
256
+ as text.
257
+ """
258
+ if option.group == "Credentials":
259
+ if value is None:
260
+ return _with_availability(option, "not configured")
261
+ if option.redacted:
262
+ status = "configured" if is_set else "not configured"
263
+ return _with_availability(option, status)
264
+ if value is None:
265
+ return "(unset)"
266
+ if option.key == "display.charset" and value == "auto":
267
+ return _charset_display_value()
268
+ text = str(value)
269
+ if option.group == "Credentials":
270
+ text = _with_availability(option, text)
271
+ max_len = 60
272
+ if len(text) > max_len:
273
+ return text[: max_len - 1] + "\N{HORIZONTAL ELLIPSIS}"
274
+ return text
275
+
276
+
277
+ def _source_label(source: str, *, option: ConfigOption | None = None) -> str:
278
+ """Render the source column for human output.
279
+
280
+ Returns:
281
+ Source label for the value's origin.
282
+ """
283
+ if option is not None and option.group == "Credentials":
284
+ env = _env_source_name(source)
285
+ if env is not None and env.startswith("DEEPAGENTS_CODE_"):
286
+ return f"{source}; session override"
287
+ return source
288
+
289
+
290
+ def _env_source_name(source: str) -> str | None:
291
+ """Return the env var name from an `env (...)` source label, if present."""
292
+ prefix = "env ("
293
+ if not source.startswith(prefix) or not source.endswith(")"):
294
+ return None
295
+ return source[len(prefix) : -1]
296
+
297
+
298
+ def _with_availability(option: ConfigOption, text: str) -> str:
299
+ """Append provider availability to a credential display value when needed.
300
+
301
+ Returns:
302
+ Display text with `, unavailable` appended when the provider integration
303
+ package is missing.
304
+ """
305
+ if _missing_extra_hint(option):
306
+ return f"{text}, unavailable"
307
+ return text
308
+
309
+
310
+ def _charset_display_value() -> str:
311
+ """Return the `display.charset=auto` value with its effective glyph mode."""
312
+ from deepagents_code.config import _detect_charset_mode
313
+
314
+ mode = _detect_charset_mode().value
315
+ label = "Unicode" if mode == "unicode" else "ASCII"
316
+ return f"auto (using {label} glyphs)"
317
+
318
+
319
+ def _missing_extra_hint(option: ConfigOption) -> bool:
320
+ """Return whether a credential option's provider integration is unavailable."""
321
+ if option.group != "Credentials" or option.dependency_module is None:
322
+ return False
323
+ return importlib.util.find_spec(option.dependency_module) is None
324
+
325
+
326
+ class ResolvedOption(NamedTuple):
327
+ """An option paired with its resolved effective value, for display.
328
+
329
+ Bundles the four values that always travel together through the render
330
+ helpers as one named record, so they can't be reordered or misaligned at a
331
+ call site the way a bare positional tuple can.
332
+ """
333
+
334
+ option: ConfigOption
335
+ """The option being described."""
336
+
337
+ is_set: bool
338
+ """`False` when `value` came from the option's typed default."""
339
+
340
+ source: str
341
+ """Where the effective value came from (e.g. `env (...)`, `stored`, `default`)."""
342
+
343
+ value: object
344
+ """The effective value; `None` when unset.
345
+
346
+ Redacted for secrets before display.
347
+ """
348
+
349
+
350
+ # --- Commands ---------------------------------------------------------------
351
+
352
+
353
+ def _show_json_row(
354
+ option: ConfigOption,
355
+ *,
356
+ is_set: bool,
357
+ source: str,
358
+ value: object,
359
+ store_error: str | None,
360
+ include_catalog: bool,
361
+ ) -> dict[str, Any]:
362
+ """Build one show/list --json row, redacting secrets and flagging store errors.
363
+
364
+ Returns:
365
+ A JSON-serializable row. Redacted options report presence only (`value`
366
+ is `None`); a `store_error` key is added to credential rows when
367
+ the `/auth` store was unreadable, so a corrupt store is
368
+ distinguishable from an empty one in the bug-report artifact. When
369
+ `include_catalog` is set the static catalog fields (summary, type,
370
+ default, ...) are folded in so `config list --json` consumers stay
371
+ unbroken.
372
+ """
373
+ row: dict[str, Any] = {
374
+ "key": option.key,
375
+ "group": option.group,
376
+ "source": source,
377
+ "set": is_set,
378
+ "redacted": option.redacted,
379
+ # Redact secret values: report presence only.
380
+ "value": None if option.redacted else value,
381
+ }
382
+ if include_catalog:
383
+ row.update(
384
+ {
385
+ "summary": option.summary,
386
+ "type": option.type,
387
+ "default": option.default,
388
+ "env_var": option.env_var,
389
+ "toml_path": option.toml_path,
390
+ "cli_flag": option.cli_flag,
391
+ }
392
+ )
393
+ if store_error and option.group == "Credentials":
394
+ row["store_error"] = store_error
395
+ return row
396
+
397
+
398
+ def _run_show(output_format: OutputFormat, *, verbose: bool, list_mode: bool) -> int:
399
+ """Resolve every option and print its effective value and source.
400
+
401
+ With `verbose`, each option also lists its description and where it can be
402
+ set (the catalog detail formerly served by `config list`).
403
+
404
+ Args:
405
+ output_format: `text` for the rendered view, `json` for a machine-
406
+ readable payload.
407
+ verbose: Fold each option's description and how-to-set into the output.
408
+ list_mode: `True` when invoked as `config list`/`ls` rather than
409
+ `config show`. It selects the `config list` JSON envelope label and,
410
+ for backward compatibility, includes the static catalog fields in
411
+ `config list --json` even without `verbose`.
412
+
413
+ Returns:
414
+ Process exit code (`0` on success).
415
+ """
416
+ from deepagents_code.config import _ensure_bootstrap
417
+ from deepagents_code.config_manifest import get_config_options, load_config_toml
418
+
419
+ # Load `.env` files into the environment so resolution reflects what the
420
+ # app actually reads, not just shell exports.
421
+ _ensure_bootstrap()
422
+ toml_data = load_config_toml()
423
+ # Read the credential store once; `_resolve` reuses this snapshot rather than
424
+ # re-parsing `auth.json` per credential option.
425
+ stored = _load_stored_credentials()
426
+
427
+ resolved = [
428
+ ResolvedOption(opt, *_resolve(opt, toml_data, stored=stored))
429
+ for opt in get_config_options()
430
+ ]
431
+
432
+ if output_format == "json":
433
+ label = "config list" if list_mode else "config show"
434
+ # `config list --json` was the catalog endpoint; keep its catalog fields
435
+ # so existing consumers stay unbroken (now additive alongside effective
436
+ # values). `config show --json` stays effective-only unless `--verbose`.
437
+ include_catalog = verbose or list_mode
438
+ write_json(
439
+ label,
440
+ [
441
+ _show_json_row(
442
+ row.option,
443
+ is_set=row.is_set,
444
+ source=row.source,
445
+ value=row.value,
446
+ store_error=stored.error,
447
+ include_catalog=include_catalog,
448
+ )
449
+ for row in resolved
450
+ ],
451
+ )
452
+ return 0
453
+
454
+ if verbose:
455
+ _print_show_verbose(resolved, store_error=stored.error)
456
+ else:
457
+ _print_show_table(resolved, store_error=stored.error)
458
+ return 0
459
+
460
+
461
+ def _print_store_warning(store_error: str | None) -> None:
462
+ """Print a warning when the `/auth` credential store was unreadable."""
463
+ if not store_error:
464
+ return
465
+ from rich.markup import escape
466
+
467
+ from deepagents_code.config import console
468
+
469
+ console.print(f"[yellow]Warning:[/yellow] {escape(store_error)}", highlight=False)
470
+ console.print()
471
+
472
+
473
+ def _print_show_table(
474
+ resolved: Sequence[ResolvedOption],
475
+ *,
476
+ store_error: str | None = None,
477
+ ) -> None:
478
+ """Render the compact effective-value table, grouped by section."""
479
+ from rich.table import Table
480
+ from rich.text import Text
481
+
482
+ from deepagents_code.config import console
483
+ from deepagents_code.config_manifest import iter_groups
484
+
485
+ console.print()
486
+ _print_store_warning(store_error)
487
+ for group in iter_groups(row.option for row in resolved):
488
+ console.print(f"[bold]{group}[/bold]")
489
+ table = Table.grid(padding=(0, 2))
490
+ table.add_column()
491
+ table.add_column()
492
+ table.add_column(style="dim")
493
+ for row in resolved:
494
+ if row.option.group != group:
495
+ continue
496
+ display = _display_value(row.option, is_set=row.is_set, value=row.value)
497
+ # `display`/`source` may contain markup from env/TOML; `Text` cells
498
+ # render literally, so values can't break the table.
499
+ table.add_row(
500
+ Text(f" {row.option.key}"),
501
+ Text(display),
502
+ Text(_source_label(row.source, option=row.option)),
503
+ )
504
+ console.print(table, highlight=False)
505
+ console.print()
506
+
507
+
508
+ def _print_show_verbose(
509
+ resolved: Sequence[ResolvedOption],
510
+ *,
511
+ store_error: str | None = None,
512
+ ) -> None:
513
+ """Render the effective value plus description and how-to-set per option."""
514
+ from rich.markup import escape
515
+
516
+ from deepagents_code.config import console
517
+ from deepagents_code.config_manifest import iter_groups
518
+
519
+ console.print()
520
+ _print_store_warning(store_error)
521
+ for group in iter_groups(row.option for row in resolved):
522
+ console.print(f"[bold]{group}[/bold]")
523
+ for row in resolved:
524
+ if row.option.group != group:
525
+ continue
526
+ display = _display_value(row.option, is_set=row.is_set, value=row.value)
527
+ # `display`/`source` may carry markup from env/TOML; escape them.
528
+ console.print(
529
+ f" [cyan]{row.option.key}[/cyan] {escape(display)} "
530
+ f"[dim]{escape(_source_label(row.source, option=row.option))}[/dim]",
531
+ highlight=False,
532
+ )
533
+ console.print(f" {row.option.summary}", highlight=False, style="dim")
534
+ console.print(
535
+ f" {_sources_line(row.option)}", highlight=False, style="dim"
536
+ )
537
+ console.print()
538
+
539
+
540
+ def _run_get(key: str, output_format: OutputFormat) -> int:
541
+ """Resolve and print a single option by key.
542
+
543
+ Returns:
544
+ Process exit code (`0` on success, `1` for an unknown key).
545
+ """
546
+ from deepagents_code.config_manifest import get_option
547
+
548
+ option = get_option(key)
549
+ if option is None:
550
+ if output_format == "json":
551
+ write_json("config get", {"key": key, "error": "unknown option"})
552
+ else:
553
+ print( # noqa: T201
554
+ f"Unknown config option: {key!r}. Run `dcode config list` to "
555
+ "see available keys.",
556
+ file=sys.stderr,
557
+ )
558
+ return 1
559
+
560
+ from deepagents_code.config import _ensure_bootstrap
561
+ from deepagents_code.config_manifest import load_config_toml
562
+
563
+ _ensure_bootstrap()
564
+ toml_data = load_config_toml()
565
+ # Only credential options consult the store, so skip the read (and its
566
+ # warning) for everything else.
567
+ stored = _load_stored_credentials() if option.group == "Credentials" else None
568
+ is_set, source, value = _resolve(option, toml_data, stored=stored)
569
+ store_error = stored.error if stored is not None else None
570
+
571
+ if output_format == "json":
572
+ payload: dict[str, Any] = {
573
+ "key": option.key,
574
+ "source": source,
575
+ "set": is_set,
576
+ "redacted": option.redacted,
577
+ "value": None if option.redacted else value,
578
+ }
579
+ if store_error:
580
+ payload["store_error"] = store_error
581
+ write_json("config get", payload)
582
+ return 0
583
+
584
+ from rich.markup import escape
585
+
586
+ from deepagents_code.config import console
587
+
588
+ display = _display_value(option, is_set=is_set, value=value)
589
+ source_label = _source_label(source, option=option)
590
+ console.print(
591
+ f"{option.key} = {escape(display)} [dim]({escape(source_label)})[/dim]",
592
+ highlight=False,
593
+ )
594
+ if store_error:
595
+ console.print(
596
+ f"[yellow]Warning:[/yellow] {escape(store_error)}", highlight=False
597
+ )
598
+ return 0
599
+
600
+
601
+ def _run_path(output_format: OutputFormat) -> int:
602
+ """Print the on-disk config file locations and whether they exist.
603
+
604
+ Returns:
605
+ Process exit code (`0` on success).
606
+ """
607
+ paths = _config_paths()
608
+
609
+ if output_format == "json":
610
+ write_json(
611
+ "config path",
612
+ [
613
+ {"label": label, "path": str(path), "exists": exists}
614
+ for label, path, exists in paths
615
+ ],
616
+ )
617
+ return 0
618
+
619
+ from deepagents_code.config import console
620
+
621
+ console.print()
622
+ console.print("[bold]Config locations[/bold]")
623
+ for label, path, exists in paths:
624
+ marker = "[green]exists[/green]" if exists else "[dim]missing[/dim]"
625
+ console.print(f" {label:<22} {path} ({marker})", highlight=False)
626
+ console.print()
627
+ return 0
628
+
629
+
630
+ def run_config_command(args: argparse.Namespace) -> int:
631
+ """Dispatch a parsed `config` subcommand.
632
+
633
+ Returns:
634
+ Process exit code from the dispatched subcommand.
635
+ """
636
+ output_format: OutputFormat = getattr(args, "output_format", "text")
637
+ command = getattr(args, "config_command", None)
638
+ verbose: bool = getattr(args, "verbose", False)
639
+
640
+ if command in {"show", "list", "ls"}:
641
+ return _run_show(
642
+ output_format, verbose=verbose, list_mode=command in {"list", "ls"}
643
+ )
644
+ if command == "get":
645
+ return _run_get(args.key, output_format)
646
+ if command == "path":
647
+ return _run_path(output_format)
648
+
649
+ from deepagents_code.ui import show_config_help
650
+
651
+ show_config_help()
652
+ return 0
653
+
654
+
655
+ # --- Helpers ----------------------------------------------------------------
656
+
657
+
658
+ def _sources_line(option: ConfigOption) -> str:
659
+ """Render a compact 'set via' line for the verbose (`--verbose`) view.
660
+
661
+ Returns:
662
+ A human-readable description of where the option can be set.
663
+ """
664
+ parts: list[str] = []
665
+ if option.env_var:
666
+ parts.append(f"env {option.env_var}")
667
+ if option.toml_path:
668
+ parts.append(f"toml {option.toml_path}")
669
+ if option.cli_flag:
670
+ parts.append(f"cli {option.cli_flag}")
671
+ default = f"default {option.default}" if option.default is not None else ""
672
+ set_via = "set via " + ", ".join(parts) if parts else "managed by the app"
673
+ return f"{set_via}{(' | ' + default) if default else ''}"
674
+
675
+
676
+ def _config_paths() -> list[tuple[str, Any, bool]]:
677
+ """Collect known config file locations and whether each exists.
678
+
679
+ Returns:
680
+ A list of `(label, path, exists)` rows in display order.
681
+ """
682
+ from pathlib import Path
683
+
684
+ from deepagents_code.config import _GLOBAL_DOTENV_PATH, _find_dotenv_from_start_path
685
+ from deepagents_code.model_config import (
686
+ DEFAULT_CONFIG_PATH,
687
+ DEFAULT_STATE_DIR,
688
+ RECENT_MODELS_FILENAME,
689
+ )
690
+
691
+ base = DEFAULT_CONFIG_PATH.parent
692
+ project_dotenv = _find_dotenv_from_start_path(Path.cwd())
693
+
694
+ candidates: list[tuple[str, Path | None]] = [
695
+ ("config.toml", DEFAULT_CONFIG_PATH),
696
+ ("project .env", project_dotenv),
697
+ ("global .env", _GLOBAL_DOTENV_PATH),
698
+ ("hooks.json", base / "hooks.json"),
699
+ ("auth.json", DEFAULT_STATE_DIR / "auth.json"),
700
+ ("recent models", DEFAULT_STATE_DIR / RECENT_MODELS_FILENAME),
701
+ ]
702
+
703
+ from deepagents_code._paths import PathState, classify_path
704
+
705
+ rows: list[tuple[str, Any, bool]] = []
706
+ for label, path in candidates:
707
+ if path is None:
708
+ continue
709
+ # `classify_path` logs unreadable paths at debug level. `config path`
710
+ # reports a plain exists/missing bool, so unreadable still collapses to
711
+ # missing here while `doctor` can surface it as a problem.
712
+ exists = classify_path(path) is PathState.EXISTS
713
+ rows.append((label, path, exists))
714
+ return rows