dscli-agent 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anish
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,379 @@
1
+ Metadata-Version: 2.4
2
+ Name: dscli-agent
3
+ Version: 0.1.0
4
+ Summary: A local coding agent that uses the DeepSeek web chat UI 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 the **DeepSeek web chat UI** as its model. No API key:
34
+ dscli drives a real browser session, sends prompts to `https://chat.deepseek.com/`, reads
35
+ the replies, and executes file-editing tools on your machine.
36
+
37
+ ```
38
+ you ──> dscli ──> DeepSeek 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 saved login token from Chrome's local
73
+ storage and seeds its own isolated browser profile; Chrome's cookies are copied too when
74
+ they are available. Your normal Chrome profile is never controlled by the automation,
75
+ and you do not need to quit Chrome or sign in again.
76
+
77
+ The isolated profile is persistent and lives under `%APPDATA%\dscli\profile` on Windows
78
+ (`~/Library/Application Support/dscli` on macOS, `~/.config/dscli` elsewhere). Keep this
79
+ directory private because it contains logged-in browser session data.
80
+
81
+ If you are not logged in through Chrome, either log in there first or run:
82
+
83
+ ```powershell
84
+ dscli login
85
+ ```
86
+
87
+ A visible browser window opens for a manual DeepSeek login, including any Cloudflare
88
+ check. dscli closes it as soon as the chat composer appears.
89
+
90
+ Check the page and selectors without sending anything:
91
+
92
+ ```powershell
93
+ dscli doctor
94
+ dscli doctor --send # also do a one-word round-trip test
95
+ dscli doctor --headed # watch it work
96
+ ```
97
+
98
+ ## Interactive chat
99
+
100
+ Start dscli without a task to open an ongoing conversation:
101
+
102
+ ```powershell
103
+ dscli
104
+ ```
105
+
106
+ dscli shows an inline terminal UI inspired by Claude Code: an animated spinner with
107
+ rotating status verbs, a smoothly revealed streaming answer, compact tool cards
108
+ (`⏺ Read(...)`, `⏺ Update(...)`, `⏺ Run(...)`), colored diffs for approvals, and the
109
+ final answer rendered as Markdown. Output stays in normal scrollback; dscli does not
110
+ take over the full screen, and startup shows only `Starting agent` instead of browser
111
+ or session internals.
112
+
113
+ The agent session stays open while you work at the `❯` prompt. The complete JSON
114
+ envelope is still parsed and validated before any tool runs. File approvals,
115
+ auto-approval state, and workspace context remain active across messages.
116
+ `--max-steps` applies separately to each message.
117
+
118
+ ### Prompt shortcuts
119
+
120
+ | Key | Action |
121
+ | --- | --- |
122
+ | `Enter` | Send the message (or accept a `/` completion). |
123
+ | `Esc` then `Enter`, or `Ctrl+J` | Insert a newline for multi-line input. |
124
+ | `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. |
125
+ | `Ctrl+D` | Exit dscli. |
126
+ | `↑` / `↓` | Browse previous messages. |
127
+
128
+ Typing `/` opens an autocomplete menu. History is saved to
129
+ `%APPDATA%\dscli\chat_history` (`~/Library/Application Support/dscli/chat_history` on
130
+ macOS, `~/.config/dscli/chat_history` elsewhere).
131
+
132
+ Slash commands: `/help` lists commands and shortcuts, `/new` starts a fresh
133
+ conversation so the agent forgets earlier turns, and `/exit` (or `/quit`) closes dscli.
134
+
135
+ When dscli asks to write a file or run a command, it shows the title and a compact
136
+ colored diff (additions green, deletions red, hunks cyan) even when `-y` is active.
137
+ Answer `y` to approve once, `n` to deny, `a` to approve everything for the rest of the
138
+ session, or `q` to quit.
139
+
140
+ ### Plain mode
141
+
142
+ Use `--plain` (or set `DSCLI_PLAIN=1`) to keep the earlier line-based interface:
143
+
144
+ ```powershell
145
+ dscli chat --plain
146
+ ```
147
+
148
+ The plain UI prints progress to stderr, accepts one line per message, and uses
149
+ `you> ` as its prompt. It is also selected automatically when stdin or stdout is not
150
+ a terminal (for example when redirecting output), when `TERM=dumb`, or when the UI
151
+ dependencies cannot start. On Windows, Windows Terminal renders the UI best; legacy
152
+ `conhost` windows may show simpler output.
153
+
154
+ You can also use the explicit command with the common agent flags:
155
+
156
+ ```powershell
157
+ dscli chat --headed --workspace C:\projects\app --max-steps 8
158
+ ```
159
+
160
+ ## Run a one-shot task
161
+
162
+ ```powershell
163
+ # Simple task in the current directory
164
+ dscli run "Add input validation to app.py and a test for it"
165
+
166
+ # Attach files (they are uploaded into the chat on the first turn)
167
+ dscli run "Refactor the parser" -f src/parser.py -f tests/test_parser.py
168
+
169
+ # Globs work too; quote them so the shell does not expand them
170
+ dscli run "Fix the bug" -f "src/**/*.py" --workspace C:\projects\app
171
+
172
+ # Read a long task from a file; auto-approve edits; allow shell commands
173
+ dscli run --prompt-file task.md -y --allow-shell
174
+ ```
175
+
176
+ The final answer is printed to stdout. Progress, diffs, and approval prompts go to
177
+ stderr, so `dscli run ... > result.txt` captures only the answer. `run` never uses the
178
+ interactive UI; its output contract is unchanged.
179
+
180
+ ### Common flags
181
+
182
+ | Flag | Meaning |
183
+ | --- | --- |
184
+ | `-f, --file PATH` | Attach a file (repeatable, globs supported). |
185
+ | `--workspace PATH` | Root the tools may touch. Defaults to the current directory. |
186
+ | `-y, --yes` | Approve every write/patch/command without asking. |
187
+ | `--allow-shell` | Enable `run_command` (still approved per command). |
188
+ | `--allow-outside` | Let tools read/write outside the workspace. |
189
+ | `--headed` / `--headless` | Show or hide the browser. Headless is the default. |
190
+ | `--deepthink` / `--search` | Turn DeepSeek's modes on for the run. |
191
+ | `--max-steps N` | Maximum model turns (default 12). |
192
+ | `--timeout N` | Response timeout per turn in seconds (default 300). |
193
+ | `--prompt-file PATH` | Read the task from a UTF-8 file. |
194
+ | `--no-auto-login` | Do not open a visible browser if a headless login is needed. |
195
+ | `-v, --verbose` | Show model thoughts and raw replies. |
196
+ | `-q, --quiet` | Only print the final answer. |
197
+ | `--dry-run` | Print the exact first prompt and exit. |
198
+
199
+ Use `-` as the task to read it from stdin.
200
+
201
+ ## How it works
202
+
203
+ Each turn is one normal chat message. The first message contains the system contract,
204
+ the environment, a workspace preview, the attachment manifest, and the task. The model
205
+ is required to answer with exactly one ` ```json ` fenced block containing one JSON
206
+ object:
207
+
208
+ ````markdown
209
+ ```json
210
+ {
211
+ "thought": "I should read the file first.",
212
+ "tool_calls": [
213
+ {"name": "read_file", "arguments": {"path": "src/app.py"}}
214
+ ],
215
+ "final": null
216
+ }
217
+ ```
218
+ ````
219
+
220
+ The fence matters: DeepSeek renders unfenced replies as Markdown, which auto-links
221
+ tokens like `object.is` and eats `*` or `[...]`, corrupting code inside the JSON.
222
+ dscli parses the envelope (also tolerating unfenced JSON and small formatting
223
+ variations), runs the tool calls locally, and sends the results back in the next
224
+ message inside nonce-tagged markers. The loop ends when the model replies with
225
+ `tool_calls: []` and a non-empty `final` string, or when the turn budget is exhausted.
226
+
227
+ Malformed replies get up to two correction prompts. Every raw reply that fails to parse
228
+ is saved to the debug directory.
229
+
230
+ ### Tools
231
+
232
+ | Tool | Purpose |
233
+ | --- | --- |
234
+ | `read_file` | Read a text file, optionally a line range. Required before patching. |
235
+ | `write_file` | **Create** a new file. Refuses to overwrite an existing non-empty file. |
236
+ | `apply_patch` | The only way to edit an existing file: exact `search` → `replace`. |
237
+ | `list_files` | Bounded recursive listing. |
238
+ | `delete_file` | Delete one file when explicitly requested (approval required). |
239
+ | `run_command` | Run a shell command (only with `--allow-shell`, approval required). |
240
+
241
+ `apply_patch` normalises line endings for matching, preserves the file's encoding and
242
+ line endings when writing, and refuses ambiguous matches unless `replace_all` is set.
243
+
244
+ ## Approvals and safety
245
+
246
+ - Tools are confined to the workspace unless `--allow-outside` is passed.
247
+ - `.git` is never writable through the tools.
248
+ - Every write, patch, delete, and command shows a diff/command and asks
249
+ `[y]es / [n]o / [a]ll / [q]uit` before changing anything. `-y` skips the prompts.
250
+ - Shell access is off by default and can be enabled only for a run.
251
+ - Attachment contents, file contents, command output, and tool results are treated as
252
+ untrusted data by the system prompt.
253
+ - No network access is performed by the tools; the only network traffic is the browser
254
+ talking to DeepSeek.
255
+
256
+ This is still an assistant with write access to your files. Use a clean workspace or
257
+ version control, and review diffs before approving them.
258
+
259
+ ## Configuration
260
+
261
+ The config is JSON at:
262
+
263
+ ```powershell
264
+ dscli config --path
265
+ dscli config --show
266
+ dscli config --set headless=false
267
+ dscli config --set stable_seconds=6
268
+ ```
269
+
270
+ Useful keys: `headless`, `channel` (`chrome`, `msedge`, or empty for Playwright's
271
+ Chromium), `chrome_user_data_dir` (Chrome's user-data directory; empty disables Chrome
272
+ session import), `chrome_profile` (empty = Chrome's last-used profile, or a name such as
273
+ `Default` / `Profile 1`), `user_agent` (empty = derive a normal Chrome UA from the
274
+ installed browser), `response_timeout_sec`, `first_response_timeout_sec`,
275
+ `stable_seconds`, `fallback_stable_seconds`, `max_steps`, `max_tool_calls`,
276
+ `max_read_chars`, `max_attachment_mb`, `deepthink`, `search`, `allow_shell`,
277
+ `allow_outside`.
278
+
279
+ Headless Chrome normally sends a `HeadlessChrome/...` user agent, which DeepSeek's CDN
280
+ rejects with a CloudFront 403. dscli automatically replaces it with the installed
281
+ Chrome's real version when running headless. Set `user_agent` explicitly if you need a
282
+ different string.
283
+
284
+ If DeepSeek changes its UI, override the affected CSS selectors in the `selectors`
285
+ object of the config file (they are merged over the defaults):
286
+
287
+ ```json
288
+ {
289
+ "selectors": {
290
+ "composer": "textarea[placeholder=\"Message DeepSeek\"]",
291
+ "assistant_message": ".ds-markdown.ds-assistant-message-main-content",
292
+ "send_button": "div[role=\"button\"].ds-button--primary.ds-button--circle"
293
+ }
294
+ }
295
+ ```
296
+
297
+ ## Troubleshooting
298
+
299
+ **Cloudflare / human check in headless mode.** dscli stops with a clear message. Run
300
+ `dscli login` (or `doctor --headed`) once in a visible browser, complete the
301
+ check, and retry.
302
+
303
+ **403 ERROR / "Request blocked" from CloudFront.** This is usually the headless browser
304
+ being fingerprinted. dscli tries to avoid it by using a normal Chrome user agent. If it
305
+ still happens, run `dscli login` headed once, then check whether
306
+ `doctor --headed` can reach the composer. You can also set `"channel": ""` or override
307
+ `"user_agent"` in the config.
308
+
309
+ **"Not logged in" or the composer never appears.** Confirm that DeepSeek works in your
310
+ normal Chrome browser. dscli imports the saved login even while Chrome is running; if
311
+ that login has expired, refresh or sign in at `chat.deepseek.com` in Chrome and retry.
312
+ If the composer still does not appear, run `dscli doctor --headed`, or
313
+ `dscli login` to sign in directly in the isolated profile. To stop importing
314
+ Chrome sessions, set `chrome_user_data_dir` to an empty string in the config.
315
+
316
+ **"The dscli browser profile is already in use."** Close any other dscli/Chrome window
317
+ that uses `%APPDATA%\dscli\profile`, then retry. Do not point two runs at the same
318
+ profile.
319
+
320
+ **The send button never enables.** The attachment may still be uploading, the message
321
+ may exceed the chat's limit, or the selector may have changed. Run `doctor` and check
322
+ the reported counts.
323
+
324
+ **A response is cut off around 12 s after the last text change.** DeepSeek did not show
325
+ its "Read aloud" toolbar, so dscli used the fallback completion detector. Increase
326
+ `fallback_stable_seconds` if replies are still streaming at that point.
327
+
328
+ **Chrome will not launch.** Set `"channel": ""` in the config to use Playwright's
329
+ Chromium, after running `python -m playwright install chromium`.
330
+
331
+ Debug screenshots and unparsed replies are written to `%APPDATA%\dscli\debug`.
332
+
333
+ ## Limitations
334
+
335
+ - The DeepSeek web UI is not a stable API. Selectors, rate limits, and features can
336
+ change at any time; `doctor` and the configurable selectors are the escape hatch.
337
+ - Chat messages can be length-limited. dscli caps the initial prompt and tool output,
338
+ and long files should be read in ranges rather than pasted.
339
+ - Streaming is approximated by polling the DOM, not by tokens from the model. The
340
+ interactive chat shows the final answer as the page updates, but tool calls still
341
+ wait for the complete, validated JSON envelope.
342
+ - Interrupting with Ctrl+C clicks DeepSeek's stop button when dscli can find it. If the
343
+ page does not expose one, generation may continue on DeepSeek's side; the interrupted
344
+ answer is discarded, and the next message (or `/new`) re-establishes a clean turn.
345
+ - DeepSeek attachments accept only certain file types. When a file cannot be attached,
346
+ the agent can still read it through `read_file` because the workspace path is provided
347
+ in the prompt.
348
+ - Using browser automation against a web chat may conflict with the provider's terms.
349
+ Use your own account and judgment.
350
+
351
+ ## Development
352
+
353
+ ```powershell
354
+ python -m compileall -q dscli
355
+ dscli doctor
356
+ ```
357
+
358
+ The package is intentionally small: `browser.py` owns Playwright and selectors,
359
+ `scraper.py` holds the in-page JavaScript, `protocol.py` parses the envelope,
360
+ `tools.py` enforces the sandbox, and `agent.py` runs the turn loop.
361
+
362
+ ### Publishing
363
+
364
+ PyPI uploads are manual and use your own API token:
365
+
366
+ ```powershell
367
+ python -m pip install -e ".[dev]"
368
+ python -m build
369
+ python -m twine check dist/*
370
+ python -m twine upload dist/*
371
+ ```
372
+
373
+ Twine prompts for the username `__token__` and your PyPI API token. Bump
374
+ `__version__` in `dscli/__init__.py` before every upload; PyPI never allows a version
375
+ number to be reused.
376
+
377
+ ## License
378
+
379
+ MIT