euron-code 0.4.0__tar.gz → 0.4.2__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 (85) hide show
  1. {euron_code-0.4.0 → euron_code-0.4.2}/CHANGELOG.md +14 -0
  2. {euron_code-0.4.0 → euron_code-0.4.2}/PKG-INFO +33 -48
  3. {euron_code-0.4.0 → euron_code-0.4.2}/README.md +32 -47
  4. {euron_code-0.4.0 → euron_code-0.4.2}/pyproject.toml +1 -1
  5. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/__init__.py +1 -1
  6. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/activity.py +1 -1
  7. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/agent.py +3 -1
  8. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/api.py +3 -2
  9. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/attachments.py +1 -1
  10. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/config.py +1 -1
  11. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/doctor.py +88 -88
  12. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/repl.py +8 -6
  13. euron_code-0.4.2/src/euron_code/textsafe.py +41 -0
  14. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/usage_view.py +1 -1
  15. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_api_shell.py +160 -160
  16. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_permissions.py +91 -91
  17. euron_code-0.4.2/tests/test_textsafe.py +45 -0
  18. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_tools_fs.py +143 -143
  19. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_activity.py +3 -3
  20. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v4.py +3 -2
  21. {euron_code-0.4.0 → euron_code-0.4.2}/.gitignore +0 -0
  22. {euron_code-0.4.0 → euron_code-0.4.2}/LICENSE +0 -0
  23. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/__main__.py +0 -0
  24. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/auth.py +0 -0
  25. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/explain-codebase/SKILL.md +0 -0
  26. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/review/SKILL.md +0 -0
  27. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/test-writer/SKILL.md +0 -0
  28. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/cache.py +0 -0
  29. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/checkpoints.py +0 -0
  30. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/cli.py +0 -0
  31. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/context.py +0 -0
  32. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/LICENSE +0 -0
  33. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/__init__.py +0 -0
  34. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/text.py +0 -0
  35. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/toolout.py +0 -0
  36. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/credentials.py +0 -0
  37. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/documents.py +0 -0
  38. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/frontmatter.py +0 -0
  39. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/hooks.py +0 -0
  40. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/keys.py +0 -0
  41. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/memory.py +0 -0
  42. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/permissions.py +0 -0
  43. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/project_setup.py +0 -0
  44. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/prompts.py +0 -0
  45. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/repomap.py +0 -0
  46. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/rules.py +0 -0
  47. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/sessions.py +0 -0
  48. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/skills.py +0 -0
  49. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/__init__.py +0 -0
  50. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/background.py +0 -0
  51. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/base.py +0 -0
  52. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/db.py +0 -0
  53. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/document.py +0 -0
  54. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/fs.py +0 -0
  55. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/ignore.py +0 -0
  56. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/reuse.py +0 -0
  57. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/search.py +0 -0
  58. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/shell.py +0 -0
  59. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/skill.py +0 -0
  60. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/symbols.py +0 -0
  61. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/task.py +0 -0
  62. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/todo.py +0 -0
  63. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/web.py +0 -0
  64. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/trust.py +0 -0
  65. {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/ui.py +0 -0
  66. {euron_code-0.4.0 → euron_code-0.4.2}/tests/conftest.py +0 -0
  67. {euron_code-0.4.0 → euron_code-0.4.2}/tests/helpers.py +0 -0
  68. {euron_code-0.4.0 → euron_code-0.4.2}/tests/mock_gateway.py +0 -0
  69. {euron_code-0.4.0 → euron_code-0.4.2}/tests/serve_helper.py +0 -0
  70. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_agent_mock.py +0 -0
  71. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_cli_e2e.py +0 -0
  72. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_context_memory_sessions.py +0 -0
  73. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_db.py +0 -0
  74. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_powershell_exit_codes.py +0 -0
  75. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_repl.py +0 -0
  76. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_search.py +0 -0
  77. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_rules_skills_hooks.py +0 -0
  78. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_tokens_shortcuts.py +0 -0
  79. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_usage_headers.py +0 -0
  80. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v31_security.py +0 -0
  81. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_accounts_value_setup.py +0 -0
  82. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_attachments.py +0 -0
  83. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_context.py +0 -0
  84. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_live_repl.py +0 -0
  85. {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_single_login.py +0 -0
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2 (2026-11-05)
4
+
5
+ ### Changed
6
+ - Documentation and messages describe what Euron Code does for you, without internal service details.
7
+
8
+ ## 0.4.1 (2026-11-05)
9
+
10
+ ### Fixed
11
+ - Pasting text with emoji into a Windows terminal crashed the prompt ("surrogates not allowed") and
12
+ then failed every request. Text is now repaired before it is saved or sent.
13
+
14
+ ### Changed
15
+ - Clearer privacy wording; the limits section shows the daily allowance and where to track usage.
16
+
3
17
  ## 0.4.0 (2026-11-05)
4
18
 
5
19
  ### Run the app, not just the tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: euron-code
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Euron Code: an agentic coding assistant in your terminal for Euron Plus and Super 30 students.
5
5
  Project-URL: Homepage, https://euron.one/code-agent
6
6
  Author-email: Euron <contact@euron.one>
@@ -105,8 +105,7 @@ That's it. Press **Esc** to stop it, **Ctrl-D** to quit.
105
105
  improve `EURON.md`.
106
106
  - **Context management.** Old tool output is cleared and long conversations are summarised before
107
107
  they fill the context, so it never runs out mid-task. Long answers continue by themselves.
108
- - **Token saving.** Unchanged files are not re-read, big files are read in parts, the prompt is kept
109
- cache-friendly for the servers.
108
+ - **Token saving.** Unchanged files are not re-read and big files are read in parts.
110
109
  - **Local cache.** `.euron/cache/` remembers the repo map and file list, so new sessions start fast.
111
110
  Safe to delete; never holds secrets; git-ignored.
112
111
  - **Runs your app.** It can start your dev server in the background, check it answers
@@ -167,23 +166,21 @@ Shift+Tab cycles default -> acceptEdits -> plan, and reaches bypass only if you
167
166
 
168
167
  ## Where your data goes
169
168
 
170
- - Your prompts and the code the agent reads are processed on GPUs Euron rents from Vast.ai hosts. They
171
- are not logged. The host operator could technically see them, so never paste secrets.
172
- - Images you attach are sent to Google Gemini to be described in text for the coding model; they are not
173
- stored. You are told this the first time you attach one.
169
+ - Your code stays on your computer.
170
+ - Images you attach are turned into a text description by Euron's image understanding and are not stored.
174
171
  - One login at a time: signing in on another computer signs this one out.
172
+ - Don't paste passwords or API keys into prompts.
175
173
 
176
174
  ## Limits and value
177
175
 
178
- Your plan has a token allowance per **day**, **week**, **month** and **year** (Super 30 has a bigger one).
179
- `/usage` (or `euron usage`) shows each with a bar, what is left and when it refills, in your local time.
180
- Days, weeks (Monday), months and years start at midnight IST. If a limit is reached, Euron Code says
181
- which one and when it refills; nothing else breaks.
176
+ **10 million AI tokens. Every day.** Build more. Experiment more. Create without constantly worrying
177
+ about tokens. Included with Euron Plus and Super 30.
182
178
 
183
- It also shows what your usage is worth at a reference price, for example "Used today: 412,300 tokens
184
- ≈ $1.07 (₹94) at Claude Sonnet 5.5 API list prices · included in your plan", and what your monthly
185
- allowance is worth. That is what the same tokens would cost on a paid API. **It is not a bill: you
186
- pay nothing extra.**
179
+ <sub>Up to 10 million tokens per day, subject to fair-use policies, model availability, and plan limits.</sub>
180
+
181
+ Track everything with `/usage` (or `euron usage`) and at https://euron.one/code-agent/usage: what you
182
+ used today, this week, this month and this year, what is left and when it refills, in your local time.
183
+ If a limit is reached, Euron Code says which one and when it refills; nothing else breaks.
187
184
 
188
185
  ## Run and check your app
189
186
 
@@ -221,7 +218,7 @@ running Euron Code in another terminal, you are asked whether to take over (`-p
221
218
  The approval page approves for whichever Euron account is signed in on euron.one in that browser, so
222
219
  to switch accounts sign in there with the other account first (or open the link in a private window).
223
220
  If no browser opens (SSH, WSL), copy the link it prints. The key is kept in the system keychain, or in
224
- `~/.euron/credentials.json` (readable only by you). To revoke a key on the server, delete it at
221
+ `~/.euron/credentials.json` (readable only by you). To revoke a login, remove it at
225
222
  https://euron.one/code-agent.
226
223
 
227
224
  Optional extras: `pip install "euron-code[images]"` (image resizing, clipboard), `"euron-code[postgres]"`,
@@ -244,7 +241,7 @@ Optional extras: `pip install "euron-code[images]"` (image resizing, clipboard),
244
241
  | `/help` | list commands, your own commands and skills, and the shortcuts |
245
242
  | `/status`, `/whoami` | account, key, API, model, mode, session, uptime, quota |
246
243
  | `/usage` (`/users`) | quota per period, refill times, value, sessions, tasks, cache share, last 30 days |
247
- | `/cost`, `/context` | session tokens, server cache, savings, session value; what fills the context |
244
+ | `/cost`, `/context` | what this session used; what fills the context |
248
245
  | `/model [id]`, `/effort` | pick or set the model; reasoning effort |
249
246
  | `/compact [focus]`, `/clear` | summarise to free context; start fresh |
250
247
  | `/resume [id]`, `/rewind`, `/export [file]` | saved sessions; undo the last turn; save as Markdown |
@@ -393,7 +390,7 @@ Hooks are your own shell commands that run at fixed points. Put them in `setting
393
390
  | `Stop` | when the model finishes its answer | stderr goes to the model and it continues (at most twice) |
394
391
 
395
392
  Any other non-zero exit is shown as a warning and blocks nothing. `matcher` is a regular expression
396
- over the tool name; Claude Code names (`Edit`, `Write`, `MultiEdit`, `Read`, `Bash`, `Grep`, `Glob`,
393
+ over the tool name; common names (`Edit`, `Write`, `MultiEdit`, `Read`, `Bash`, `Grep`, `Glob`,
397
394
  `WebFetch`) and Euron Code names (`edit_file`, `bash`, ...) both work. The command runs in the project
398
395
  folder with `cmd.exe` on Windows and `sh` elsewhere, and gets one JSON object on stdin: `event`,
399
396
  `session_id`, `cwd`, `tool_name`, `tool_input`, `tool_output` (PostToolUse) or `prompt`. Environment:
@@ -408,31 +405,22 @@ always run.
408
405
 
409
406
  ## Context and tokens (automatic)
410
407
 
411
- The default model has a 32k-token context and writes at most about 8k tokens per step. Euron Code
412
- manages that the way Claude Code does, with nothing to set up:
413
-
414
- - **Room for the answer.** An output budget (about 8k) is always kept free; "100% full" means the
415
- prompt leaves no more than that. Each request asks for as many output tokens as really fit, and a
416
- request that would not fit is compacted first.
417
- - **Clearing.** From 50% full, tool outputs are replaced with one-line stubs, biggest and oldest first,
418
- until it is under 40%. The latest step is never touched, and each stub says what to re-read (for
419
- example `read_file src/app.py lines 1-200`). The note shows real numbers: "freed 9,400 tokens,
420
- now 38%".
421
- - **Summary.** At 70%, or when the next request would not fit, older messages are summarised: the task,
422
- decisions, files changed, commands and results, the last error and open todos.
423
- - **Long answers and big edits.** An answer cut by the output limit continues by itself. A tool call cut
424
- in the middle (a huge file write) is never run; the model is asked to make the change in smaller
425
- steps. If the model spent its whole budget thinking, it retries with less reasoning.
408
+ Euron Code manages the conversation for you, with nothing to set up:
409
+
410
+ - **Always room to answer.** Each request leaves space for the reply; a conversation that is getting
411
+ full is tidied up first.
412
+ - **Clearing.** Old tool output is replaced with one-line notes that say what to re-read (for example
413
+ `read_file src/app.py lines 1-200`), so the agent keeps going without losing track.
414
+ - **Summary.** Long conversations are summarised: the task, decisions, files changed, commands and
415
+ results, the last error and open todos.
416
+ - **Long answers and big edits.** An answer that gets cut off continues by itself. A huge edit that gets
417
+ cut off is never applied half-way; the change is made in smaller steps instead.
426
418
  - **Reasoning effort** is `medium` by default and `low` for quick follow-ups ("thanks", "continue");
427
419
  change it with `/effort low|medium|high`.
428
- - **Reads.** `read_file` reads 200 lines at a time and stops before about 6k tokens; a file over 2,000
429
- lines returns an outline of its definitions first. Re-reading an unchanged file returns a one-line
430
- note instead of the content.
431
- - **Cache-friendly.** The system prompt and tool list never change during a session, so the servers can
432
- reuse their prefix cache; changing data goes at the end of your message.
420
+ - **Reads.** Files are read in parts; a very large file returns an outline first. Re-reading an
421
+ unchanged file returns a one-line note instead of the content.
433
422
 
434
- `/cost` and `/context` show what was sent, how much the server served from its cache, and how many
435
- tokens these measures kept out of the context.
423
+ `/cost` and `/context` show what this session used and what fills the context.
436
424
 
437
425
  ## Non-interactive use (scripts, CI)
438
426
 
@@ -493,9 +481,8 @@ the connection itself is opened read-only. To let it write (local dev databases
493
481
  | `webFetch` | `true` | allow the web page tool (it still asks per site) |
494
482
  | `db.connections.<name>` | | database DSNs |
495
483
 
496
- Environment variables: `EURON_CODE_API` (gateway URL, default
497
- `https://api.euron.one/api/v1/code-agent`), `EURON_API_KEY` (use a key without logging in, for CI),
498
- `EURON_HOME` (instead of `~/.euron`), `EURON_NO_BROWSER=1`.
484
+ Environment variables: `EURON_API_KEY` (use a key without logging in, for CI), `EURON_HOME` (instead of
485
+ `~/.euron`), `EURON_NO_BROWSER=1`.
499
486
 
500
487
  ## In VS Code
501
488
 
@@ -531,13 +518,11 @@ and the keychain.
531
518
 
532
519
  ## Privacy and safety
533
520
 
534
- - Your files are only sent as part of the conversation with Euron's model (on GPUs Euron rents from
535
- Vast.ai, not logged), and only what the agent reads; attached images go to Google Gemini to be described
536
- and are not stored. There is no telemetry.
521
+ - Your code stays on your computer: only the parts the agent reads are sent, as part of the conversation
522
+ with Euron's model. Attached images are turned into text by Euron's image understanding and are not
523
+ stored.
537
524
  - Your key is never printed in full and never logged.
538
525
  - Edits and commands ask first. Files outside the project folder always ask, even to read.
539
- - Each request carries a random session id and task id so your usage page can count sessions and
540
- tasks. Nothing else about the conversation is stored by the gateway.
541
526
  - Hooks are commands you configure; project hooks only run after you trust them.
542
527
  - Text inside your files, web pages and command output is treated as data, not instructions.
543
528
 
@@ -39,8 +39,7 @@ That's it. Press **Esc** to stop it, **Ctrl-D** to quit.
39
39
  improve `EURON.md`.
40
40
  - **Context management.** Old tool output is cleared and long conversations are summarised before
41
41
  they fill the context, so it never runs out mid-task. Long answers continue by themselves.
42
- - **Token saving.** Unchanged files are not re-read, big files are read in parts, the prompt is kept
43
- cache-friendly for the servers.
42
+ - **Token saving.** Unchanged files are not re-read and big files are read in parts.
44
43
  - **Local cache.** `.euron/cache/` remembers the repo map and file list, so new sessions start fast.
45
44
  Safe to delete; never holds secrets; git-ignored.
46
45
  - **Runs your app.** It can start your dev server in the background, check it answers
@@ -101,23 +100,21 @@ Shift+Tab cycles default -> acceptEdits -> plan, and reaches bypass only if you
101
100
 
102
101
  ## Where your data goes
103
102
 
104
- - Your prompts and the code the agent reads are processed on GPUs Euron rents from Vast.ai hosts. They
105
- are not logged. The host operator could technically see them, so never paste secrets.
106
- - Images you attach are sent to Google Gemini to be described in text for the coding model; they are not
107
- stored. You are told this the first time you attach one.
103
+ - Your code stays on your computer.
104
+ - Images you attach are turned into a text description by Euron's image understanding and are not stored.
108
105
  - One login at a time: signing in on another computer signs this one out.
106
+ - Don't paste passwords or API keys into prompts.
109
107
 
110
108
  ## Limits and value
111
109
 
112
- Your plan has a token allowance per **day**, **week**, **month** and **year** (Super 30 has a bigger one).
113
- `/usage` (or `euron usage`) shows each with a bar, what is left and when it refills, in your local time.
114
- Days, weeks (Monday), months and years start at midnight IST. If a limit is reached, Euron Code says
115
- which one and when it refills; nothing else breaks.
110
+ **10 million AI tokens. Every day.** Build more. Experiment more. Create without constantly worrying
111
+ about tokens. Included with Euron Plus and Super 30.
116
112
 
117
- It also shows what your usage is worth at a reference price, for example "Used today: 412,300 tokens
118
- ≈ $1.07 (₹94) at Claude Sonnet 5.5 API list prices · included in your plan", and what your monthly
119
- allowance is worth. That is what the same tokens would cost on a paid API. **It is not a bill: you
120
- pay nothing extra.**
113
+ <sub>Up to 10 million tokens per day, subject to fair-use policies, model availability, and plan limits.</sub>
114
+
115
+ Track everything with `/usage` (or `euron usage`) and at https://euron.one/code-agent/usage: what you
116
+ used today, this week, this month and this year, what is left and when it refills, in your local time.
117
+ If a limit is reached, Euron Code says which one and when it refills; nothing else breaks.
121
118
 
122
119
  ## Run and check your app
123
120
 
@@ -155,7 +152,7 @@ running Euron Code in another terminal, you are asked whether to take over (`-p
155
152
  The approval page approves for whichever Euron account is signed in on euron.one in that browser, so
156
153
  to switch accounts sign in there with the other account first (or open the link in a private window).
157
154
  If no browser opens (SSH, WSL), copy the link it prints. The key is kept in the system keychain, or in
158
- `~/.euron/credentials.json` (readable only by you). To revoke a key on the server, delete it at
155
+ `~/.euron/credentials.json` (readable only by you). To revoke a login, remove it at
159
156
  https://euron.one/code-agent.
160
157
 
161
158
  Optional extras: `pip install "euron-code[images]"` (image resizing, clipboard), `"euron-code[postgres]"`,
@@ -178,7 +175,7 @@ Optional extras: `pip install "euron-code[images]"` (image resizing, clipboard),
178
175
  | `/help` | list commands, your own commands and skills, and the shortcuts |
179
176
  | `/status`, `/whoami` | account, key, API, model, mode, session, uptime, quota |
180
177
  | `/usage` (`/users`) | quota per period, refill times, value, sessions, tasks, cache share, last 30 days |
181
- | `/cost`, `/context` | session tokens, server cache, savings, session value; what fills the context |
178
+ | `/cost`, `/context` | what this session used; what fills the context |
182
179
  | `/model [id]`, `/effort` | pick or set the model; reasoning effort |
183
180
  | `/compact [focus]`, `/clear` | summarise to free context; start fresh |
184
181
  | `/resume [id]`, `/rewind`, `/export [file]` | saved sessions; undo the last turn; save as Markdown |
@@ -327,7 +324,7 @@ Hooks are your own shell commands that run at fixed points. Put them in `setting
327
324
  | `Stop` | when the model finishes its answer | stderr goes to the model and it continues (at most twice) |
328
325
 
329
326
  Any other non-zero exit is shown as a warning and blocks nothing. `matcher` is a regular expression
330
- over the tool name; Claude Code names (`Edit`, `Write`, `MultiEdit`, `Read`, `Bash`, `Grep`, `Glob`,
327
+ over the tool name; common names (`Edit`, `Write`, `MultiEdit`, `Read`, `Bash`, `Grep`, `Glob`,
331
328
  `WebFetch`) and Euron Code names (`edit_file`, `bash`, ...) both work. The command runs in the project
332
329
  folder with `cmd.exe` on Windows and `sh` elsewhere, and gets one JSON object on stdin: `event`,
333
330
  `session_id`, `cwd`, `tool_name`, `tool_input`, `tool_output` (PostToolUse) or `prompt`. Environment:
@@ -342,31 +339,22 @@ always run.
342
339
 
343
340
  ## Context and tokens (automatic)
344
341
 
345
- The default model has a 32k-token context and writes at most about 8k tokens per step. Euron Code
346
- manages that the way Claude Code does, with nothing to set up:
347
-
348
- - **Room for the answer.** An output budget (about 8k) is always kept free; "100% full" means the
349
- prompt leaves no more than that. Each request asks for as many output tokens as really fit, and a
350
- request that would not fit is compacted first.
351
- - **Clearing.** From 50% full, tool outputs are replaced with one-line stubs, biggest and oldest first,
352
- until it is under 40%. The latest step is never touched, and each stub says what to re-read (for
353
- example `read_file src/app.py lines 1-200`). The note shows real numbers: "freed 9,400 tokens,
354
- now 38%".
355
- - **Summary.** At 70%, or when the next request would not fit, older messages are summarised: the task,
356
- decisions, files changed, commands and results, the last error and open todos.
357
- - **Long answers and big edits.** An answer cut by the output limit continues by itself. A tool call cut
358
- in the middle (a huge file write) is never run; the model is asked to make the change in smaller
359
- steps. If the model spent its whole budget thinking, it retries with less reasoning.
342
+ Euron Code manages the conversation for you, with nothing to set up:
343
+
344
+ - **Always room to answer.** Each request leaves space for the reply; a conversation that is getting
345
+ full is tidied up first.
346
+ - **Clearing.** Old tool output is replaced with one-line notes that say what to re-read (for example
347
+ `read_file src/app.py lines 1-200`), so the agent keeps going without losing track.
348
+ - **Summary.** Long conversations are summarised: the task, decisions, files changed, commands and
349
+ results, the last error and open todos.
350
+ - **Long answers and big edits.** An answer that gets cut off continues by itself. A huge edit that gets
351
+ cut off is never applied half-way; the change is made in smaller steps instead.
360
352
  - **Reasoning effort** is `medium` by default and `low` for quick follow-ups ("thanks", "continue");
361
353
  change it with `/effort low|medium|high`.
362
- - **Reads.** `read_file` reads 200 lines at a time and stops before about 6k tokens; a file over 2,000
363
- lines returns an outline of its definitions first. Re-reading an unchanged file returns a one-line
364
- note instead of the content.
365
- - **Cache-friendly.** The system prompt and tool list never change during a session, so the servers can
366
- reuse their prefix cache; changing data goes at the end of your message.
354
+ - **Reads.** Files are read in parts; a very large file returns an outline first. Re-reading an
355
+ unchanged file returns a one-line note instead of the content.
367
356
 
368
- `/cost` and `/context` show what was sent, how much the server served from its cache, and how many
369
- tokens these measures kept out of the context.
357
+ `/cost` and `/context` show what this session used and what fills the context.
370
358
 
371
359
  ## Non-interactive use (scripts, CI)
372
360
 
@@ -427,9 +415,8 @@ the connection itself is opened read-only. To let it write (local dev databases
427
415
  | `webFetch` | `true` | allow the web page tool (it still asks per site) |
428
416
  | `db.connections.<name>` | | database DSNs |
429
417
 
430
- Environment variables: `EURON_CODE_API` (gateway URL, default
431
- `https://api.euron.one/api/v1/code-agent`), `EURON_API_KEY` (use a key without logging in, for CI),
432
- `EURON_HOME` (instead of `~/.euron`), `EURON_NO_BROWSER=1`.
418
+ Environment variables: `EURON_API_KEY` (use a key without logging in, for CI), `EURON_HOME` (instead of
419
+ `~/.euron`), `EURON_NO_BROWSER=1`.
433
420
 
434
421
  ## In VS Code
435
422
 
@@ -465,13 +452,11 @@ and the keychain.
465
452
 
466
453
  ## Privacy and safety
467
454
 
468
- - Your files are only sent as part of the conversation with Euron's model (on GPUs Euron rents from
469
- Vast.ai, not logged), and only what the agent reads; attached images go to Google Gemini to be described
470
- and are not stored. There is no telemetry.
455
+ - Your code stays on your computer: only the parts the agent reads are sent, as part of the conversation
456
+ with Euron's model. Attached images are turned into text by Euron's image understanding and are not
457
+ stored.
471
458
  - Your key is never printed in full and never logged.
472
459
  - Edits and commands ask first. Files outside the project folder always ask, even to read.
473
- - Each request carries a random session id and task id so your usage page can count sessions and
474
- tasks. Nothing else about the conversation is stored by the gateway.
475
460
  - Hooks are commands you configure; project hooks only run after you trust them.
476
461
  - Text inside your files, web pages and command output is treated as data, not instructions.
477
462
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "euron-code"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Euron Code: an agentic coding assistant in your terminal for Euron Plus and Super 30 students."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """Euron Code: an agentic coding assistant for the terminal."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.2"
@@ -64,7 +64,7 @@ def phase_from_comment(comment: Optional[str]) -> Optional[str]:
64
64
  m = QUEUED.search(comment)
65
65
  if m:
66
66
  n = int(m.group(1))
67
- return "waiting for a GPU slot" + (f" ({n} ahead)" if n else " (next in line)")
67
+ return "waiting for capacity" + (f" ({n} ahead)" if n else " (next in line)")
68
68
  if "previous request" in comment:
69
69
  return "waiting for your previous request to finish"
70
70
  return None
@@ -100,7 +100,7 @@ def repair_json(raw: str):
100
100
 
101
101
 
102
102
  def parse_inline_tool_calls(content: str):
103
- """Fallback for servers without a tool parser: Qwen emits <tool_call>{json}</tool_call>.
103
+ """Fallback for servers without a tool parser: some models emit <tool_call>{json}</tool_call>.
104
104
  Slightly broken JSON is repaired; unparseable calls become an error result, never a crash."""
105
105
  calls = []
106
106
  for m in TOOL_CALL_TAG.finditer(content):
@@ -272,6 +272,8 @@ class Agent:
272
272
 
273
273
  # ---------------------------------------------------------- public
274
274
  def run_turn(self, user_text: str, max_turns: Optional[int] = None) -> TurnResult:
275
+ from euron_code import textsafe
276
+ user_text = textsafe.clean(user_text) # emoji pasted into a Windows console (surrogates)
275
277
  if self.is_subagent:
276
278
  return self._run_turn(user_text, max_turns)
277
279
  self._ui_hook("turn_started")
@@ -19,6 +19,7 @@ from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
19
19
 
20
20
  import httpx
21
21
 
22
+ from euron_code import textsafe
22
23
  from euron_code import __version__, config
23
24
 
24
25
  NO_PLUS_MESSAGE = (
@@ -366,7 +367,7 @@ class Client:
366
367
  def chat(self, payload: Dict[str, Any]) -> Dict[str, Any]:
367
368
  """Non-streaming completion (used for compaction summaries)."""
368
369
  with self._lock:
369
- body = dict(payload)
370
+ body = textsafe.clean_obj(dict(payload))
370
371
  body["stream"] = False
371
372
  data = self._json("POST", "/v1/chat/completions", json=body, headers=self._chat_headers())
372
373
  self.takeover_once = False
@@ -383,7 +384,7 @@ class Client:
383
384
  raise EuronError("Internal error: a model request is already running.")
384
385
  q: "queue.Queue[Tuple[str, Any]]" = queue.Queue()
385
386
  holder: Dict[str, Any] = {}
386
- body = dict(payload)
387
+ body = textsafe.clean_obj(dict(payload))
387
388
  body["stream"] = True
388
389
  body.setdefault("stream_options", {"include_usage": True})
389
390
 
@@ -130,7 +130,7 @@ def load_image(path: Path) -> Tuple[Dict, str]:
130
130
  return {"type": "image_url", "image_url": {"url": url}}, note
131
131
 
132
132
 
133
- IMAGE_PRIVACY = ("Images you attach are sent to Google Gemini to be described in text for the coding model; "
133
+ IMAGE_PRIVACY = ("Images are turned into a text description by Euron's image understanding and are not stored; "
134
134
  "they are not stored. Don't attach images that show secrets.")
135
135
 
136
136
 
@@ -17,7 +17,7 @@ from typing import Any, Dict, List, Optional, Tuple
17
17
 
18
18
  DEFAULT_BASE_URL = "https://api.euron.one/api/v1/code-agent"
19
19
 
20
- #: Context window and output cap of the default model (Qwen3.6-27B, see BENCHMARK_RESULTS.md).
20
+ #: Context window and output cap of the default model.
21
21
  DEFAULT_CONTEXT_WINDOW = 32768
22
22
  DEFAULT_MAX_OUTPUT = 8192
23
23
 
@@ -1,88 +1,88 @@
1
- """`euron doctor`: check the local setup and the connection to Euron Code."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import platform
7
- import sys
8
- from typing import List, Tuple
9
-
10
- import httpx
11
- from rich.markup import escape
12
-
13
- from euron_code import __version__, api, config, credentials
14
- from euron_code.tools.search import ripgrep_path
15
- from euron_code.tools.shell import detect_shell
16
-
17
- OK, WARN, FAIL = "ok", "warn", "fail"
18
-
19
-
20
- def run_checks() -> List[Tuple[str, str, str]]:
21
- checks: List[Tuple[str, str, str]] = []
22
- v = sys.version_info
23
- checks.append(("Python", OK if v >= (3, 9) else FAIL,
24
- f"{platform.python_version()} ({sys.executable})"))
25
- checks.append(("euron-code", OK, __version__))
26
- checks.append(("Platform", OK, f"{platform.system()} {platform.release()} {platform.machine()}"))
27
- url = config.base_url()
28
- src = "EURON_CODE_API" if os.environ.get("EURON_CODE_API") else "default"
29
- checks.append(("API base URL", OK, f"{url} ({src})"))
30
-
31
- # connectivity: any HTTP answer means the gateway is reachable
32
- try:
33
- r = httpx.get(url + "/v1/models", timeout=10.0, headers={"User-Agent": api.user_agent()})
34
- checks.append(("Connectivity", OK, f"reachable (HTTP {r.status_code})"))
35
- reachable = True
36
- except httpx.HTTPError as e:
37
- checks.append(("Connectivity", FAIL, f"cannot reach {url}: {type(e).__name__}"))
38
- reachable = False
39
-
40
- cred = credentials.load(url)
41
- if cred is None:
42
- checks.append(("Login", WARN, "not logged in; run `euron login`"))
43
- else:
44
- checks.append(("Login", OK, f"key {credentials.mask_key(cred.api_key)} in {credentials.where(url)}"))
45
- if reachable:
46
- c = api.Client(api_key=cred.api_key, base_url=url)
47
- try:
48
- me = c.me(use_cache=False)
49
- access = me.get("access") or {}
50
- if access.get("entitled"):
51
- q = me.get("quota") or {}
52
- checks.append(("Entitlement", OK, f"{api.plan_label(access)}; "
53
- f"{q.get('remainingToday', '?'):,} tokens left today"
54
- if isinstance(q.get("remainingToday"), int) else api.plan_label(access)))
55
- else:
56
- checks.append(("Entitlement", FAIL, api.NO_PLUS_MESSAGE))
57
- except api.AuthError:
58
- checks.append(("Entitlement", FAIL, "the saved key was rejected (401); run `euron login`"))
59
- except api.EuronError as e:
60
- checks.append(("Entitlement", WARN, f"could not check: {e.message}"))
61
- finally:
62
- c.close()
63
-
64
- rg = ripgrep_path()
65
- checks.append(("ripgrep", OK if rg else WARN, rg or "not found; grep uses the slower Python fallback"))
66
- checks.append(("Keyring", OK if "unavailable" not in credentials.keyring_backend_name() else WARN,
67
- credentials.keyring_backend_name()))
68
- kind, exe = detect_shell(config.load_user_settings().get("shell"))
69
- checks.append(("Shell", OK, f"{kind} ({exe})"))
70
- term = os.environ.get("TERM_PROGRAM") or os.environ.get("TERM") or ("Windows console" if os.name == "nt" else "?")
71
- checks.append(("Terminal", OK if sys.stdout.isatty() else WARN,
72
- f"{term}{'' if sys.stdout.isatty() else ' (output is not a terminal)'}"))
73
- checks.append(("Config", OK, f"{config.user_settings_path()}"))
74
- return checks
75
-
76
-
77
- def doctor(ui) -> int:
78
- checks = run_checks()
79
- style = {OK: "[green]OK [/green]", WARN: "[yellow]WARN[/yellow]", FAIL: "[red]FAIL[/red]"}
80
- for name, st, detail in checks:
81
- ui.info(f"{style[st]} {name:<14} {escape(detail)}")
82
- fails = [c for c in checks if c[1] == FAIL]
83
- ui.info("")
84
- if fails:
85
- ui.info(f"[red]{len(fails)} problem(s) found.[/red]")
86
- return 1
87
- ui.info("[green]All checks passed.[/green]")
88
- return 0
1
+ """`euron doctor`: check the local setup and the connection to Euron Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import platform
7
+ import sys
8
+ from typing import List, Tuple
9
+
10
+ import httpx
11
+ from rich.markup import escape
12
+
13
+ from euron_code import __version__, api, config, credentials
14
+ from euron_code.tools.search import ripgrep_path
15
+ from euron_code.tools.shell import detect_shell
16
+
17
+ OK, WARN, FAIL = "ok", "warn", "fail"
18
+
19
+
20
+ def run_checks() -> List[Tuple[str, str, str]]:
21
+ checks: List[Tuple[str, str, str]] = []
22
+ v = sys.version_info
23
+ checks.append(("Python", OK if v >= (3, 9) else FAIL,
24
+ f"{platform.python_version()} ({sys.executable})"))
25
+ checks.append(("euron-code", OK, __version__))
26
+ checks.append(("Platform", OK, f"{platform.system()} {platform.release()} {platform.machine()}"))
27
+ url = config.base_url()
28
+ src = "EURON_CODE_API" if os.environ.get("EURON_CODE_API") else "default"
29
+ checks.append(("API base URL", OK, f"{url} ({src})"))
30
+
31
+ # connectivity: any HTTP answer means the gateway is reachable
32
+ try:
33
+ r = httpx.get(url + "/v1/models", timeout=10.0, headers={"User-Agent": api.user_agent()})
34
+ checks.append(("Connectivity", OK, f"reachable (HTTP {r.status_code})"))
35
+ reachable = True
36
+ except httpx.HTTPError as e:
37
+ checks.append(("Connectivity", FAIL, f"cannot reach {url}: {type(e).__name__}"))
38
+ reachable = False
39
+
40
+ cred = credentials.load(url)
41
+ if cred is None:
42
+ checks.append(("Login", WARN, "not logged in; run `euron login`"))
43
+ else:
44
+ checks.append(("Login", OK, f"key {credentials.mask_key(cred.api_key)} in {credentials.where(url)}"))
45
+ if reachable:
46
+ c = api.Client(api_key=cred.api_key, base_url=url)
47
+ try:
48
+ me = c.me(use_cache=False)
49
+ access = me.get("access") or {}
50
+ if access.get("entitled"):
51
+ q = me.get("quota") or {}
52
+ checks.append(("Entitlement", OK, f"{api.plan_label(access)}; "
53
+ f"{q.get('remainingToday', '?'):,} tokens left today"
54
+ if isinstance(q.get("remainingToday"), int) else api.plan_label(access)))
55
+ else:
56
+ checks.append(("Entitlement", FAIL, api.NO_PLUS_MESSAGE))
57
+ except api.AuthError:
58
+ checks.append(("Entitlement", FAIL, "the saved key was rejected (401); run `euron login`"))
59
+ except api.EuronError as e:
60
+ checks.append(("Entitlement", WARN, f"could not check: {e.message}"))
61
+ finally:
62
+ c.close()
63
+
64
+ rg = ripgrep_path()
65
+ checks.append(("ripgrep", OK if rg else WARN, rg or "not found; grep uses the slower Python fallback"))
66
+ checks.append(("Keyring", OK if "unavailable" not in credentials.keyring_backend_name() else WARN,
67
+ credentials.keyring_backend_name()))
68
+ kind, exe = detect_shell(config.load_user_settings().get("shell"))
69
+ checks.append(("Shell", OK, f"{kind} ({exe})"))
70
+ term = os.environ.get("TERM_PROGRAM") or os.environ.get("TERM") or ("Windows console" if os.name == "nt" else "?")
71
+ checks.append(("Terminal", OK if sys.stdout.isatty() else WARN,
72
+ f"{term}{'' if sys.stdout.isatty() else ' (output is not a terminal)'}"))
73
+ checks.append(("Config", OK, f"{config.user_settings_path()}"))
74
+ return checks
75
+
76
+
77
+ def doctor(ui) -> int:
78
+ checks = run_checks()
79
+ style = {OK: "[green]OK [/green]", WARN: "[yellow]WARN[/yellow]", FAIL: "[red]FAIL[/red]"}
80
+ for name, st, detail in checks:
81
+ ui.info(f"{style[st]} {name:<14} {escape(detail)}")
82
+ fails = [c for c in checks if c[1] == FAIL]
83
+ ui.info("")
84
+ if fails:
85
+ ui.info(f"[red]{len(fails)} problem(s) found.[/red]")
86
+ return 1
87
+ ui.info("[green]All checks passed.[/green]")
88
+ return 0