dscli-agent 0.2.0__tar.gz → 0.4.0__tar.gz

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 (47) hide show
  1. dscli_agent-0.4.0/PKG-INFO +565 -0
  2. dscli_agent-0.4.0/README.md +535 -0
  3. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/__init__.py +1 -1
  4. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/agent.py +246 -49
  5. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/attachments.py +9 -2
  6. dscli_agent-0.4.0/dscli/branding.py +66 -0
  7. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/browser.py +451 -110
  8. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/cli.py +78 -23
  9. dscli_agent-0.4.0/dscli/compaction.py +230 -0
  10. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/config.py +28 -30
  11. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/display.py +29 -3
  12. dscli_agent-0.4.0/dscli/prompts.py +628 -0
  13. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/protocol.py +65 -3
  14. dscli_agent-0.4.0/dscli/providers.py +42 -0
  15. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/scraper.py +45 -9
  16. dscli_agent-0.4.0/dscli/tokens.py +35 -0
  17. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/tools.py +187 -25
  18. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/tui.py +396 -62
  19. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/ui.py +7 -7
  20. dscli_agent-0.4.0/dscli_agent.egg-info/PKG-INFO +565 -0
  21. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli_agent.egg-info/SOURCES.txt +7 -0
  22. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/pyproject.toml +9 -2
  23. dscli_agent-0.4.0/tests/test_agent.py +1125 -0
  24. dscli_agent-0.4.0/tests/test_branding.py +43 -0
  25. dscli_agent-0.4.0/tests/test_browser.py +833 -0
  26. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/tests/test_cli.py +234 -4
  27. dscli_agent-0.4.0/tests/test_compaction.py +302 -0
  28. dscli_agent-0.4.0/tests/test_config.py +104 -0
  29. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/tests/test_display.py +67 -5
  30. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/tests/test_protocol.py +94 -16
  31. dscli_agent-0.4.0/tests/test_tokens.py +49 -0
  32. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/tests/test_tools.py +102 -2
  33. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/tests/test_tui.py +285 -5
  34. dscli_agent-0.2.0/PKG-INFO +0 -427
  35. dscli_agent-0.2.0/README.md +0 -397
  36. dscli_agent-0.2.0/dscli/branding.py +0 -30
  37. dscli_agent-0.2.0/dscli/prompts.py +0 -317
  38. dscli_agent-0.2.0/dscli_agent.egg-info/PKG-INFO +0 -427
  39. dscli_agent-0.2.0/tests/test_agent.py +0 -449
  40. dscli_agent-0.2.0/tests/test_browser.py +0 -359
  41. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/LICENSE +0 -0
  42. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli/__main__.py +0 -0
  43. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli_agent.egg-info/dependency_links.txt +0 -0
  44. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli_agent.egg-info/entry_points.txt +0 -0
  45. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli_agent.egg-info/requires.txt +0 -0
  46. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/dscli_agent.egg-info/top_level.txt +0 -0
  47. {dscli_agent-0.2.0 → dscli_agent-0.4.0}/setup.cfg +0 -0
@@ -0,0 +1,565 @@
1
+ Metadata-Version: 2.4
2
+ Name: dscli-agent
3
+ Version: 0.4.0
4
+ Summary: A local coding agent that uses DeepSeek web chat as its model
5
+ Author: Anish
6
+ License-Expression: MIT
7
+ Keywords: cli,coding-agent,deepseek,playwright,terminal,ai
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
18
+ Classifier: Topic :: Utilities
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: playwright>=1.45
23
+ Requires-Dist: rich>=13.7
24
+ Requires-Dist: prompt-toolkit>=3.0.43
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == "dev"
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Requires-Dist: twine>=5.1; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # dscli
32
+
33
+ A local coding agent that uses a **web chat UI** as its model. No API key: dscli drives
34
+ a real browser session, sends prompts to DeepSeek's chat page, reads the replies, and
35
+ executes file-editing tools on your machine.
36
+
37
+ ```
38
+ you ──> dscli ──> web chat (browser) ──> one JSON envelope per turn
39
+ │ │
40
+ └────── local tools: read / write / patch <──┘
41
+ ```
42
+
43
+ ## Requirements
44
+
45
+ - Python 3.10+ (developed on 3.13)
46
+ - Google Chrome, or Playwright's Chromium
47
+ - A DeepSeek account (the free web chat is enough)
48
+
49
+ ## Install
50
+
51
+ ```powershell
52
+ python -m pip install dscli-agent
53
+ ```
54
+
55
+ This installs the `dscli` command and its Python dependencies. Playwright's browsers are
56
+ not downloaded by pip: dscli drives your installed Google Chrome by default. If Chrome is
57
+ not available, install Playwright's Chromium once:
58
+
59
+ ```powershell
60
+ python -m playwright install chromium
61
+ ```
62
+
63
+ To work from a source checkout instead:
64
+
65
+ ```powershell
66
+ python -m pip install -e ".[dev]"
67
+ ```
68
+
69
+ ## Sign-in
70
+
71
+ If you already use DeepSeek in Chrome, dscli reuses that saved session automatically,
72
+ even while Chrome is open. It reads DeepSeek's login token from Chrome's local storage
73
+ on every start and overwrites any stale copy left in dscli's profile, so the session in
74
+ Chrome always wins. Chrome's cookies are copied as well whenever Chrome is not holding
75
+ its cookie database open. Your normal Chrome profile is never controlled by the
76
+ automation, and you do not need to quit Chrome or sign in again.
77
+
78
+ The isolated profile is persistent and lives under `%APPDATA%\dscli\profile` on Windows
79
+ (`~/Library/Application Support/dscli` on macOS, `~/.config/dscli` elsewhere). Keep this
80
+ directory private because it contains logged-in browser session data.
81
+
82
+ If you are not logged in through Chrome, either log in there first or run:
83
+
84
+ ```powershell
85
+ dscli login
86
+ ```
87
+
88
+ A visible browser window opens for a manual sign-in, including any Cloudflare check.
89
+ dscli closes it as soon as the chat composer appears.
90
+
91
+ Check the page and selectors without sending anything:
92
+
93
+ ```powershell
94
+ dscli doctor
95
+ dscli doctor --send # also do a one-word round-trip test
96
+ dscli doctor --headed # watch it work
97
+ ```
98
+
99
+ ## Interactive chat
100
+
101
+ Start dscli without a task to open an ongoing conversation:
102
+
103
+ ```powershell
104
+ dscli
105
+ ```
106
+
107
+ dscli shows an inline terminal UI inspired by Claude Code: a cleared screen and gradient
108
+ ASCII wordmark on launch, an animated spinner with rotating status verbs, a smoothly
109
+ revealed streaming answer, compact tool cards (`⏺ Read(...)`, `⏺ Update(...)`,
110
+ `⏺ Run(...)`), colored diffs for approvals, and the final answer rendered as Markdown.
111
+ Accents use a muted palette that matches the wordmark: sage for success, dusty rose for
112
+ errors, and soft teal for diffs and highlights. The same muted colors apply to the
113
+ plain and non-TTY output.
114
+ Output stays in normal scrollback after startup; dscli does not take over the full
115
+ screen, and the welcome card shows the workspace, model, active modes, and shortcuts.
116
+ Plans
117
+ render as a compact checklist, and a configured verification command appears as a
118
+ `Verify` card after file edits. `dscli chat --verify CMD` applies the check to each
119
+ message.
120
+
121
+ On launch, the visible terminal is cleared before the wordmark appears. Pass
122
+ `--no-clear` to `chat`, or set `DSCLI_NO_CLEAR=1`, to keep the previous screen contents;
123
+ the logo and welcome card still show. This behavior is TUI-only: `--plain`, non-TTY
124
+ output, `run`, `doctor`, `login`, and `config` keep their existing output.
125
+
126
+ The agent session stays open while you work at the `❯` prompt. The complete JSON
127
+ envelope is still parsed and validated before any tool runs. File approvals,
128
+ auto-approval state, and workspace context remain active across messages.
129
+ `--max-steps` applies separately to each message.
130
+
131
+ DeepThink is always enabled, and the UI streams the model's reasoning live in a
132
+ compact block above the reply:
133
+
134
+ ```text
135
+ Thinking
136
+ │ I should inspect the configuration first.
137
+ │ … +12 rows (Ctrl+O)
138
+ │ charlie line
139
+ │ delta line
140
+ │ echo line
141
+ ```
142
+
143
+ The first sentence stays pinned at the top while the newest three wrapped rows
144
+ autoscroll beneath it, with the collapsed row count in between. Text is revealed
145
+ between provider polls so the block keeps streaming in real time. When the turn ends
146
+ it collapses to the same limited view with a `Ctrl+O` hint; press `Ctrl+O` at the
147
+ prompt to print the full reasoning block (up to 500 rows) into the transcript.
148
+ The streamed block is TUI-only; `--plain` and `run` do not show it.
149
+
150
+ The prompt footer tracks session token usage against a 1,000,000-token budget, and the
151
+ same progress bar renders below the live Thinking and Responding output:
152
+
153
+ ```text
154
+ tokens ███████░░░░░ 612.4k / 1.0M · 61%
155
+ ```
156
+
157
+ Counts are estimated locally from every prompt sent and reply received (one token per
158
+ CJK character, about four characters per token otherwise); they are not reported by
159
+ DeepSeek. Attachment contents are not included. The count resets on `/new` and whenever
160
+ a new chat is opened.
161
+
162
+ ### Conversation compaction
163
+
164
+ When the estimate reaches 1,000,000 tokens, dscli compacts the session instead of
165
+ stopping. Before the next prompt is sent it asks the current chat to write a structured
166
+ compaction summary — an `<analysis>` pass followed by a `<summary>` covering the request,
167
+ technical decisions, files and code, errors and fixes, user messages, pending tasks, and
168
+ the next step, following the same compact guide Claude Code uses. It then writes the
169
+ conversation to a text file, starts a new DeepSeek chat, and sends a resume prompt that
170
+ embeds the compaction and attaches the file. If the summary request fails or returns
171
+ nothing usable, the deterministic activity log is used on its own. The compaction
172
+ contains the model summary (when available), the conversation resume, the current task,
173
+ the remaining (unfinished) plan steps, files changed so far, the verification state,
174
+ the project structure, and instructions for continuing, so the model keeps working
175
+ without redoing finished work.
176
+
177
+ Files are saved under `%APPDATA%\dscli\compactions` on Windows
178
+ (`~/Library/Application Support/dscli/compactions` on macOS,
179
+ `~/.config/dscli/compactions` elsewhere) as `compaction-YYYYmmdd-HHMMSS.txt`. The check
180
+ runs before every model turn, so it also works mid-task; the TUI shows a
181
+ `Compacting conversation` spinner while the summary is written, then a
182
+ `⟳ Compacted 1.0M tokens → … · new chat` notice. `/new` clears the recorded
183
+ conversation as well.
184
+
185
+ ### Deeplanning mode
186
+
187
+ ```powershell
188
+ dscli run "Split the parser module" --deeplanning
189
+ dscli chat --deeplanning
190
+ ```
191
+
192
+ Deeplanning makes the model plan before it acts. While the run has no plan, the harness
193
+ refuses `write_file`, `apply_patch`, `delete_file`, and `run_command` with an error result
194
+ that tells the model to plan first. `read_file` and `list_files` stay available so the
195
+ model can investigate, and `update_plan` unlocks execution as soon as it records a
196
+ non-empty plan. The gate is enforced by dscli, not just requested in the prompt, so a
197
+ model that skips the plan cannot touch the workspace. Plans remain per message: each new
198
+ chat message needs its own plan, and clearing the plan re-arms the gate. Enable it for
199
+ every run with `dscli config --set deeplanning=true`, or turn it off for one run with
200
+ `--no-deeplanning`.
201
+
202
+ ### Prompt shortcuts
203
+
204
+ | Key | Action |
205
+ | --- | --- |
206
+ | `Enter` | Send the message (or accept a `/` completion). |
207
+ | `Esc` then `Enter`, or `Ctrl+J` | Insert a newline for multi-line input. |
208
+ | `Ctrl+C` | While the agent is working: interrupt the current turn, keep the browser session, and return to the prompt. At the prompt: clear the input. |
209
+ | `Ctrl+D` | Exit dscli. |
210
+ | `↑` / `↓` | Browse previous messages. |
211
+ | `Ctrl+O` | Expand the last thinking block in full. |
212
+
213
+ Typing `/` opens an autocomplete menu. History is saved to
214
+ `%APPDATA%\dscli\chat_history` (`~/Library/Application Support/dscli/chat_history` on
215
+ macOS, `~/.config/dscli/chat_history` elsewhere).
216
+
217
+ Slash commands: `/help` lists commands and shortcuts, `/new` starts a fresh
218
+ conversation so the agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
219
+
220
+ If Cloudflare interrupts a headless run, dscli opens the persistent profile in a visible
221
+ Chrome window and resubmits the interrupted prompt. Complete the check in that window.
222
+ dscli keeps the window open until the response actually starts, then minimizes it, keeps
223
+ working, and closes the window and returns to headless when the task finishes. The
224
+ current model turn restarts with the same prompt in a fresh chat, so an answer that was
225
+ already streaming when the check appeared is fetched again. Set
226
+ `dscli config --set auto_human_check=false` to fail fast instead.
227
+
228
+ When dscli asks to write a file or run a command, it shows the title and a compact
229
+ colored diff (additions green, deletions red, hunks cyan) even when `-y` is active.
230
+ Answer `y` to approve once, `n` to deny, `a` to approve everything for the rest of the
231
+ session, or `q` to quit.
232
+
233
+ ### Plain mode
234
+
235
+ Use `--plain` (or set `DSCLI_PLAIN=1`) to keep the earlier line-based interface:
236
+
237
+ ```powershell
238
+ dscli chat --plain
239
+ ```
240
+
241
+ The plain UI prints progress to stderr, accepts one line per message, and uses
242
+ `you> ` as its prompt. It is also selected automatically when stdin or stdout is not
243
+ a terminal (for example when redirecting output), when `TERM=dumb`, or when the UI
244
+ dependencies cannot start. On Windows, Windows Terminal renders the UI best; legacy
245
+ `conhost` windows may show simpler output.
246
+
247
+ You can also use the explicit command with the common agent flags:
248
+
249
+ ```powershell
250
+ dscli chat --headed --workspace C:\projects\app --max-steps 8
251
+ ```
252
+
253
+ ## Run a one-shot task
254
+
255
+ ```powershell
256
+ # Simple task in the current directory
257
+ dscli run "Add input validation to app.py and a test for it"
258
+
259
+ # Attach files (they are uploaded into the chat on the first turn)
260
+ dscli run "Refactor the parser" -f src/parser.py -f tests/test_parser.py
261
+
262
+ # Globs work too; quote them so the shell does not expand them
263
+ dscli run "Fix the bug" -f "src/**/*.py" --workspace C:\projects\app
264
+
265
+ # Read a long task from a file; auto-approve edits and approved shell commands
266
+ dscli run --prompt-file task.md -y
267
+
268
+ # Disable command execution entirely for this run
269
+ dscli run "Review the docs" --no-shell
270
+ ```
271
+
272
+ The final answer is printed to stdout. Progress, diffs, and approval prompts go to
273
+ stderr, so `dscli run ... > result.txt` captures only the answer. `run` never uses the
274
+ interactive UI; its output contract is unchanged.
275
+
276
+ Exit codes: `0` success, `1` error, `2` model-turn budget exhausted or the result was not
277
+ confirmed complete, and `3` verification failed (takes precedence over `2`). Verification
278
+ failures and unconfirmed completions are always logged to stderr, including under `-q`.
279
+
280
+ ### Common flags
281
+
282
+ | Flag | Meaning |
283
+ | --- | --- |
284
+ | `-f, --file PATH` | Attach a file (repeatable, globs supported). |
285
+ | `--workspace PATH` | Root the tools may touch. Defaults to the current directory. |
286
+ | `-y, --yes` | Approve every write/patch/command without asking. |
287
+ | `--no-shell` | Disable `run_command` for this run (commands are otherwise enabled and approved per command). |
288
+ | `--allow-outside` | Let tools read/write outside the workspace. |
289
+ | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
290
+ | `--search` | Turn DeepSeek's Search mode on for the run (DeepThink is always on). |
291
+ | `--deeplanning` | Require a non-empty `update_plan` before writes or commands (alias: `--deep-planning`). |
292
+ | `--max-steps N` | Maximum model turns (default 12). |
293
+ | `--timeout N` | Response timeout per turn in seconds (default 300). |
294
+ | `--verify CMD` | Run CMD automatically after file edits; failures block success. |
295
+ | `--no-verify` | Disable the configured `verify_command` for this run. |
296
+ | `--prompt-file PATH` | Read the task from a UTF-8 file. |
297
+ | `--no-clear` | Keep previous terminal output when the TUI starts (chat only). |
298
+ | `--no-auto-login` | Do not open a visible browser if a headless login is needed. |
299
+ | `-v, --verbose` | Show model thoughts and raw replies. |
300
+ | `-q, --quiet` | Only print the final answer. |
301
+ | `--dry-run` | Print the exact first prompt and exit. |
302
+
303
+ Use `-` as the task to read it from stdin.
304
+
305
+ ## How it works
306
+
307
+ Each turn is one normal chat message. The first message contains the system contract,
308
+ the environment, a workspace preview, the attachment manifest, and the task. The model
309
+ is required to answer with exactly one ` ```json ` fenced block containing one JSON
310
+ object:
311
+
312
+ ````markdown
313
+ ```json
314
+ {
315
+ "thought": "I should read the file first.",
316
+ "tool_calls": [
317
+ {"name": "read_file", "arguments": {"path": "src/app.py"}}
318
+ ],
319
+ "final": null
320
+ }
321
+ ```
322
+ ````
323
+
324
+ The fence matters: web chats render unfenced replies as Markdown, which auto-links
325
+ tokens like `object.is` and eats `*` or `[...]`, corrupting code inside the JSON.
326
+ dscli parses the envelope (also tolerating unfenced JSON and small formatting
327
+ variations), runs the tool calls locally, and sends the results back in the next
328
+ message inside nonce-tagged markers. The loop ends only when the model replies with
329
+ `tool_calls: []`, a non-empty `final` string, and `"complete": true`:
330
+
331
+ ````markdown
332
+ ```json
333
+ {"thought": "the work is complete", "tool_calls": [], "complete": true, "final": "..."}
334
+ ```
335
+ ````
336
+
337
+ A final without `complete: true`, or with any unfinished plan step, is rejected and the
338
+ model is sent back to continue. When the turn budget runs out first, dscli asks for a
339
+ wrap-up summary and reports the result as not confirmed complete (`dscli run` exits with
340
+ code `2`). The configured verification command can also send the model back for repairs
341
+ (see below).
342
+
343
+ Malformed replies get up to two correction prompts. Every raw reply that fails to parse
344
+ is saved to the debug directory.
345
+
346
+ The system contract adopts the operating guidance from Claude Code's Opus 5.5 system
347
+ prompt, adapted to dscli's envelope protocol and local tools: act once you have enough
348
+ information, keep changes minimal and consistent with the surrounding code, avoid scope
349
+ creep and unnecessary abstractions, confirm hard-to-reverse actions, reference code as
350
+ `path:line`, report outcomes faithfully, and treat all tool output as untrusted data.
351
+
352
+ ### Tools
353
+
354
+ | Tool | Purpose |
355
+ | --- | --- |
356
+ | `read_file` | Read a text file, optionally a line range. Required before patching. |
357
+ | `write_file` | **Create** a new file. Refuses to overwrite an existing non-empty file. |
358
+ | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
359
+ | `list_files` | Bounded recursive listing. |
360
+ | `delete_file` | Delete one file when explicitly requested (approval required). |
361
+ | `run_command` | Run a command with an approval prompt via PowerShell, cmd.exe, sh, or bash. |
362
+ | `update_plan` | Create or revise the task plan; the task cannot finish while steps are unfinished. |
363
+
364
+ `apply_patch` normalises line endings for matching, preserves the file's encoding and
365
+ line endings when writing, and refuses ambiguous matches unless `replace_all` is set.
366
+
367
+ `run_command` uses `shell: auto` by default: PowerShell when available on Windows
368
+ (cmd.exe otherwise), and `/bin/sh` elsewhere. Pass `"shell": "cmd"` for cmd.exe syntax
369
+ such as `%VAR%` or batch builtins, `"powershell"`, `"sh"`, or `"bash"` to choose another
370
+ interpreter. On POSIX systems, `auto` falls back to `/bin/sh`; `bash` is selected only
371
+ when explicitly requested. The approval prompt names the resolved interpreter, so a
372
+ command is never run under a shell you did not see.
373
+
374
+ ### Plan and verification loop
375
+
376
+ The agent can maintain a short task plan with `update_plan` (at most 20 steps). The run
377
+ does not finish while any step is `pending` or `in_progress`; the model is told which
378
+ steps remain and asked to finish them or revise the plan. It must also declare
379
+ `"complete": true` explicitly, so a bare "done" summary cannot end a task by accident.
380
+ There is no give-up acceptance: if the budget runs out first, the result is reported as
381
+ not confirmed complete instead of being treated as a successful finish. Plans apply to a
382
+ single message in chat and are reset for every new task.
383
+
384
+ Configure a verification command with `--verify CMD` or the `verify_command` config
385
+ key, for example:
386
+
387
+ ```powershell
388
+ dscli run -y --verify "python -m pytest -q" "Add a test for the parser"
389
+ ```
390
+
391
+ When the model claims completion after changing files, dscli runs the command itself,
392
+ shows the result, and feeds failures back to the model for up to two repair attempts. If
393
+ the check still fails, the run ends with the best available answer, marks verification as
394
+ failed, and `dscli run` exits with code `3`. Verification only runs when the current
395
+ message changed files. A verified result is still only as trustworthy as the command you
396
+ configured.
397
+
398
+ The verification command is supplied by you and is never a model-callable tool. It is
399
+ run without a per-command approval prompt, even when `--no-shell` is set, because you
400
+ configured it explicitly. Treat it like any other command you run from the shell: the
401
+ model can modify the files it checks, so review the final diff.
402
+
403
+ ## Approvals and safety
404
+
405
+ - Tools are confined to the workspace unless `--allow-outside` is passed.
406
+ - `.git` is never writable through the tools.
407
+ - Every write, patch, delete, and command shows a diff/command and asks
408
+ `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
409
+ - Command execution is enabled by default, but every model-requested command still
410
+ requires approval. The prompt names the interpreter (PowerShell, cmd.exe, sh, or
411
+ bash). `--no-shell` disables model-requested commands for a run.
412
+ - The verification command is the one exception to the approval flow: when you configure
413
+ it with `--verify` or `verify_command`, dscli runs it automatically after edits,
414
+ without a prompt and even when `--no-shell` is set. The model can neither call nor
415
+ change it.
416
+ - Attachment contents, file contents, command output, and tool results are treated as
417
+ untrusted data by the system prompt.
418
+ - The built-in file tools do not use the network. Approved commands can run any local
419
+ program, including network clients; dscli itself only talks to the chat provider
420
+ through the browser.
421
+
422
+ This is still an assistant with write access to your files. Use a clean workspace or
423
+ version control, and review diffs before approving them.
424
+
425
+ ## Configuration
426
+
427
+ The config is JSON at:
428
+
429
+ ```powershell
430
+ dscli config --path
431
+ dscli config --show
432
+ dscli config --set headless=false
433
+ dscli config --set stable_seconds=6
434
+ ```
435
+
436
+ Useful keys: `url` (the DeepSeek chat URL; set it only for a custom deployment),
437
+ `headless`, `channel` (`chrome`, `msedge`, or empty for Playwright's Chromium),
438
+ `chrome_user_data_dir` (Chrome's user-data directory; empty disables Chrome session
439
+ import), `chrome_profile` (empty = Chrome's last-used profile, or a name such as
440
+ `Default` / `Profile 1`), `user_agent` (empty = derive a normal Chrome UA from the
441
+ installed browser), `response_timeout_sec`, `first_response_timeout_sec`,
442
+ `stable_seconds`, `fallback_stable_seconds`, `auto_human_check` (true = open a visible
443
+ window to solve a verification check; false = fail fast), `max_steps`, `max_tool_calls`,
444
+ `max_read_chars`, `max_attachment_mb`, `search`, `deeplanning`, `shell_enabled`
445
+ (default true; every command still requires approval), `allow_outside`,
446
+ `verify_command` (empty disables verification),
447
+ `verify_timeout_sec` (clamped to 1–600, default 300).
448
+
449
+ Headless Chrome normally sends a `HeadlessChrome/...` user agent, which DeepSeek's CDN
450
+ rejects with a CloudFront 403. dscli automatically replaces it with the installed
451
+ Chrome's real version when running headless. Set `user_agent` explicitly if you need a
452
+ different string.
453
+
454
+ DeepSeek's default selectors live in `dscli/providers.py`. If its UI changes, override
455
+ the affected CSS selectors in the `selectors` object of the config file (they are merged
456
+ over the defaults):
457
+
458
+ ```json
459
+ {
460
+ "selectors": {
461
+ "composer": "textarea[placeholder=\"Ask anything\"]",
462
+ "send_button": "button.new-send"
463
+ }
464
+ }
465
+ ```
466
+
467
+ Selectors saved by older dscli versions that match DeepSeek's built-in defaults are
468
+ ignored automatically, so built-in selector updates still take effect. Custom overrides
469
+ are preserved.
470
+
471
+ ## Troubleshooting
472
+
473
+ **Cloudflare / human check in headless mode.** When a check interrupts a
474
+ headless run, dscli opens the same persistent profile in a visible Chrome window and
475
+ waits for you to solve it. The window stays visible until the reply starts, then
476
+ minimizes; dscli finishes the task, then closes the window and returns to headless. If
477
+ the window does not appear or the check times out, run `dscli login` (or
478
+ `dscli doctor --headed`) once in a visible browser and retry. Set
479
+ `auto_human_check` to false to keep the fail-fast behavior.
480
+
481
+ **403 ERROR / "Request blocked" from CloudFront.** This is usually the headless browser
482
+ being fingerprinted. dscli tries to avoid it by using a normal Chrome user agent. If it
483
+ still happens, run `dscli login` headed once, then check whether
484
+ `doctor --headed` can reach the composer. You can also set `"channel": ""` or override
485
+ `"user_agent"` in the config.
486
+
487
+ **"Not logged in" or the composer never appears.** Confirm that DeepSeek works in your
488
+ normal Chrome browser. dscli imports the saved login on
489
+ every start, even while Chrome is running; if that login has expired, refresh or sign in
490
+ at `chat.deepseek.com` in Chrome and retry. Chrome keeps its cookie database
491
+ locked while it runs, so dscli keeps using the cookies already saved in its own profile
492
+ and refreshes them from Chrome the next time it starts with Chrome closed. If the
493
+ composer still does not appear, run `dscli doctor --headed`, or
494
+ `dscli login` to sign in directly. To stop importing Chrome sessions,
495
+ set `chrome_user_data_dir` to an empty string in the config.
496
+
497
+ **"The dscli browser profile is already in use."** Close any other dscli/Chrome window
498
+ that uses `%APPDATA%\dscli\profile`, then retry. Do not point two runs at the same
499
+ profile.
500
+
501
+ **The send button never enables.** The attachment may still be uploading, the message
502
+ may exceed the chat's limit, or the selector may have changed. Run `doctor` and check
503
+ the reported counts.
504
+
505
+ **A response is cut off around 12 s after the last text change.** DeepSeek did not
506
+ show its completion toolbar (`Read aloud`), so dscli used the fallback completion
507
+ detector. Increase `fallback_stable_seconds` if replies are still streaming at that
508
+ point.
509
+
510
+ **Chrome will not launch.** Set `"channel": ""` in the config to use Playwright's
511
+ Chromium, after running `python -m playwright install chromium`.
512
+
513
+ Debug screenshots and unparsed replies are written to `%APPDATA%\dscli\debug`.
514
+
515
+ ## Limitations
516
+
517
+ - The DeepSeek web UI is not a stable API. Selectors, rate limits, and
518
+ features can change at any time; `doctor` and the configurable selectors are the
519
+ escape hatch.
520
+ - Chat messages can be length-limited. dscli caps the initial prompt and tool output,
521
+ and long files should be read in ranges rather than pasted.
522
+ - Streaming is approximated by polling the DOM, not by tokens from the model. The
523
+ interactive chat streams a final answer as the page updates only after it declares
524
+ `complete: true`; tool calls still wait for the complete, validated JSON envelope.
525
+ The token bar is likewise a local estimate of sent and received text, not usage
526
+ reported by DeepSeek.
527
+ - Interrupting with Ctrl+C clicks DeepSeek's labelled stop button when dscli can find
528
+ it. If the page does not expose one, generation may continue in the web chat; the
529
+ interrupted answer is discarded, and the next message (or `/new`) re-establishes a
530
+ clean turn.
531
+ - Web-chat attachments accept only certain file types. When a file cannot be attached,
532
+ the agent can still read it through `read_file` because the workspace path is provided
533
+ in the prompt.
534
+ - Using browser automation against a web chat may conflict with DeepSeek's terms.
535
+ Use your own account and judgment.
536
+
537
+ ## Development
538
+
539
+ ```powershell
540
+ python -m compileall -q dscli
541
+ dscli doctor
542
+ ```
543
+
544
+ The package is intentionally small: `browser.py` owns Playwright and selectors,
545
+ `scraper.py` holds the in-page JavaScript, `protocol.py` parses the envelope,
546
+ `tools.py` enforces the sandbox, and `agent.py` runs the turn loop.
547
+
548
+ ### Publishing
549
+
550
+ PyPI uploads are manual and use your own API token:
551
+
552
+ ```powershell
553
+ python -m pip install -e ".[dev]"
554
+ python -m build
555
+ python -m twine check dist/*
556
+ python -m twine upload dist/*
557
+ ```
558
+
559
+ Twine prompts for the username `__token__` and your PyPI API token. Bump
560
+ `__version__` in `dscli/__init__.py` before every upload; PyPI never allows a version
561
+ number to be reused.
562
+
563
+ ## License
564
+
565
+ MIT