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.
- {euron_code-0.4.0 → euron_code-0.4.2}/CHANGELOG.md +14 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/PKG-INFO +33 -48
- {euron_code-0.4.0 → euron_code-0.4.2}/README.md +32 -47
- {euron_code-0.4.0 → euron_code-0.4.2}/pyproject.toml +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/__init__.py +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/activity.py +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/agent.py +3 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/api.py +3 -2
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/attachments.py +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/config.py +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/doctor.py +88 -88
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/repl.py +8 -6
- euron_code-0.4.2/src/euron_code/textsafe.py +41 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/usage_view.py +1 -1
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_api_shell.py +160 -160
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_permissions.py +91 -91
- euron_code-0.4.2/tests/test_textsafe.py +45 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_tools_fs.py +143 -143
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_activity.py +3 -3
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v4.py +3 -2
- {euron_code-0.4.0 → euron_code-0.4.2}/.gitignore +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/LICENSE +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/__main__.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/auth.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/explain-codebase/SKILL.md +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/review/SKILL.md +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/builtin_skills/test-writer/SKILL.md +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/cache.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/checkpoints.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/cli.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/context.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/LICENSE +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/__init__.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/text.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/contextforge/toolout.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/credentials.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/documents.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/frontmatter.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/hooks.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/keys.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/memory.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/permissions.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/project_setup.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/prompts.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/repomap.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/rules.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/sessions.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/skills.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/__init__.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/background.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/base.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/db.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/document.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/fs.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/ignore.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/reuse.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/search.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/shell.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/skill.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/symbols.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/task.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/todo.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/tools/web.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/trust.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/src/euron_code/ui.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/conftest.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/helpers.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/mock_gateway.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/serve_helper.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_agent_mock.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_cli_e2e.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_context_memory_sessions.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_db.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_powershell_exit_codes.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_repl.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_search.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_rules_skills_hooks.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_tokens_shortcuts.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v2_usage_headers.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v31_security.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_accounts_value_setup.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_attachments.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_context.py +0 -0
- {euron_code-0.4.0 → euron_code-0.4.2}/tests/test_v3_live_repl.py +0 -0
- {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.
|
|
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
|
|
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
|
|
171
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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` |
|
|
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;
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
- **
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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.**
|
|
429
|
-
|
|
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
|
|
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: `
|
|
497
|
-
|
|
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
|
|
535
|
-
|
|
536
|
-
|
|
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
|
|
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
|
|
105
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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` |
|
|
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;
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
- **
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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.**
|
|
363
|
-
|
|
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
|
|
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: `
|
|
431
|
-
|
|
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
|
|
469
|
-
|
|
470
|
-
|
|
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.
|
|
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"
|
|
@@ -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
|
|
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:
|
|
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
|
|
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
|
|
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
|