euron-code 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
euron_code/ui.py ADDED
@@ -0,0 +1,269 @@
1
+ """Terminal rendering. All output goes through a rich Console so Windows consoles,
2
+ VS Code's terminal and pipes all behave (rich strips ANSI when not a terminal)."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import sys
7
+ import threading
8
+ import time
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from rich.console import Console
12
+ from rich.markup import escape
13
+ from rich.text import Text
14
+
15
+
16
+ class UI:
17
+ """Interactive UI. Subclassed by QuietUI for `euron -p`."""
18
+
19
+ interactive = True
20
+
21
+ def __init__(self, reasoning: str = "collapse", stderr: bool = False):
22
+ self.console = Console(stderr=stderr, highlight=False, soft_wrap=True)
23
+ self.reasoning_mode = reasoning
24
+ self._status = None
25
+ self._think_start: Optional[float] = None
26
+ self._think_chars = 0
27
+ self._content_started = False
28
+ self._reasoning_printed = False
29
+
30
+ # ---------------------------------------------------------- basic messages
31
+ def info(self, msg: str) -> None:
32
+ self.console.print(msg)
33
+
34
+ def dim(self, msg: str) -> None:
35
+ self.console.print(Text(msg, style="dim"))
36
+
37
+ def warn(self, msg: str) -> None:
38
+ self.console.print(Text(msg, style="yellow"))
39
+
40
+ def error(self, msg: str) -> None:
41
+ self.console.print(Text(msg, style="bold red"))
42
+
43
+ def plain(self, msg: str) -> None:
44
+ self.console.print(Text(msg))
45
+
46
+ def exact(self, msg: str) -> None:
47
+ """Print a message byte-for-byte (no wrapping, no markup), e.g. the no-Plus message."""
48
+ stream = sys.stderr if self.console.stderr else sys.stdout
49
+ stream.write(msg + "\n")
50
+ stream.flush()
51
+
52
+ # ---------------------------------------------------------- streaming
53
+ def _status_start(self, msg: str) -> None:
54
+ if self._status is None and self.console.is_terminal:
55
+ self._status = self.console.status(msg, spinner="dots")
56
+ self._status.start()
57
+ elif self._status is not None:
58
+ self._status.update(msg)
59
+
60
+ def _status_stop(self) -> None:
61
+ if self._status is not None:
62
+ self._status.stop()
63
+ self._status = None
64
+
65
+ def begin_response(self) -> None:
66
+ self._think_start = None
67
+ self._think_chars = 0
68
+ self._content_started = False
69
+ self._reasoning_printed = False
70
+ self._status_start("Waiting for Euron Code...")
71
+
72
+ def waiting(self, msg: str) -> None:
73
+ if not self._content_started:
74
+ self._status_start(msg)
75
+
76
+ def reasoning(self, text: str) -> None:
77
+ if self._think_start is None:
78
+ self._think_start = time.monotonic()
79
+ self._think_chars += len(text)
80
+ if self.reasoning_mode == "show":
81
+ self._status_stop()
82
+ self.console.print(Text(text, style="dim italic"), end="")
83
+ self._reasoning_printed = True
84
+ elif self.reasoning_mode == "collapse":
85
+ secs = time.monotonic() - self._think_start
86
+ self._status_start(f"Thinking... {secs:.0f}s (/thinking to show)")
87
+
88
+ def _close_reasoning(self) -> None:
89
+ if self._think_start is not None:
90
+ secs = time.monotonic() - self._think_start
91
+ if self.reasoning_mode == "show" and self._reasoning_printed:
92
+ self.console.print()
93
+ elif self.reasoning_mode == "collapse":
94
+ self.console.print(Text(f"(thought for {secs:.0f}s)", style="dim"))
95
+ self._think_start = None
96
+
97
+ def content(self, text: str) -> None:
98
+ if not self._content_started:
99
+ self._status_stop()
100
+ self._close_reasoning()
101
+ self._content_started = True
102
+ self.console.print(Text(text), end="")
103
+
104
+ def tool_call_pending(self, name: str) -> None:
105
+ if not self._content_started:
106
+ self._status_start(f"Preparing {name}...")
107
+
108
+ def end_response(self) -> None:
109
+ self._status_stop()
110
+ self._close_reasoning()
111
+ if self._content_started:
112
+ self.console.print()
113
+ self._content_started = False
114
+
115
+ # ---------------------------------------------------------- tools
116
+ def tool_start(self, name: str, summary: str) -> None:
117
+ self.console.print(Text.assemble(("> ", "bold cyan"), (name, "bold"), (" " + summary, "")))
118
+
119
+ def tool_result(self, preview: str, is_error: bool = False) -> None:
120
+ style = "red" if is_error else "dim"
121
+ lines = preview.splitlines()[:6]
122
+ if not lines:
123
+ lines = ["(no output)"]
124
+ for ln in lines:
125
+ self.console.print(Text(" " + ln[:200], style=style))
126
+ if len(preview.splitlines()) > 6:
127
+ self.console.print(Text(f" ... ({len(preview.splitlines()) - 6} more lines)", style="dim"))
128
+
129
+ def show_diff(self, diff: str) -> None:
130
+ if not diff.strip():
131
+ self.dim(" (no textual change)")
132
+ return
133
+ shown = diff.splitlines()
134
+ limit = 120
135
+ for ln in shown[:limit]:
136
+ if ln.startswith("+++") or ln.startswith("---"):
137
+ style = "bold"
138
+ elif ln.startswith("+"):
139
+ style = "green"
140
+ elif ln.startswith("-"):
141
+ style = "red"
142
+ elif ln.startswith("@@"):
143
+ style = "cyan"
144
+ else:
145
+ style = "dim"
146
+ self.console.print(Text(" " + ln, style=style))
147
+ if len(shown) > limit:
148
+ self.dim(f" ... diff truncated ({len(shown) - limit} more lines)")
149
+
150
+ def show_todos(self, todos: List[Dict[str, Any]]) -> None:
151
+ if not todos:
152
+ self.dim(" (todo list cleared)")
153
+ return
154
+ for t in todos:
155
+ st = t.get("status", "pending")
156
+ mark = {"completed": "[x]", "in_progress": "[~]"}.get(st, "[ ]")
157
+ style = {"completed": "dim strike", "in_progress": "bold yellow"}.get(st, "")
158
+ self.console.print(Text(f" {mark} {t.get('content', '')}", style=style))
159
+
160
+ # ---------------------------------------------------------- prompts
161
+ def ask_permission(self, title: str, detail: Optional[str], options: List[tuple]) -> str:
162
+ """options: list of (key, label). Returns the chosen key ('n' on EOF)."""
163
+ self._status_stop()
164
+ self.console.print(Text.assemble(("? ", "bold yellow"), (title, "bold")))
165
+ if detail:
166
+ for ln in detail.splitlines()[:40]:
167
+ self.console.print(Text(" " + ln))
168
+ labels = " ".join(f"[{k}] {escape(lbl)}" for k, lbl in options)
169
+ valid = {k for k, _ in options}
170
+ while True:
171
+ self.console.print(Text(" " + labels, style="cyan"))
172
+ try:
173
+ ans = input(" > ").strip().lower()
174
+ except (EOFError, KeyboardInterrupt):
175
+ self.console.print()
176
+ return "n"
177
+ if not ans:
178
+ ans = "n"
179
+ if ans in valid:
180
+ return ans
181
+ if ans in ("yes",):
182
+ return "y"
183
+ if ans in ("no",):
184
+ return "n"
185
+
186
+ def ask_text(self, prompt: str) -> str:
187
+ try:
188
+ return input(prompt)
189
+ except (EOFError, KeyboardInterrupt):
190
+ return ""
191
+
192
+ def countdown(self, seconds: float, message: str, cancel: Optional[threading.Event] = None) -> None:
193
+ """Visible countdown. Raises KeyboardInterrupt if the user presses Ctrl-C."""
194
+ self._status_stop()
195
+ end = time.monotonic() + seconds
196
+ if self.console.is_terminal:
197
+ with self.console.status("") as st:
198
+ while True:
199
+ left = end - time.monotonic()
200
+ if left <= 0:
201
+ break
202
+ if cancel is not None and cancel.is_set():
203
+ break
204
+ st.update(f"{message} retrying in {left:.0f}s (Ctrl-C to stop)")
205
+ time.sleep(min(0.25, left))
206
+ else:
207
+ self.warn(f"{message} retrying in {seconds:.0f}s")
208
+ while time.monotonic() < end:
209
+ if cancel is not None and cancel.is_set():
210
+ break
211
+ time.sleep(min(0.25, max(0.0, end - time.monotonic())))
212
+
213
+
214
+ class QuietUI(UI):
215
+ """Used by `euron -p`: no spinners, progress on stderr, never prompts."""
216
+
217
+ interactive = False
218
+
219
+ def __init__(self, verbose: bool = False):
220
+ super().__init__(reasoning="hide", stderr=True)
221
+ self.verbose = verbose
222
+
223
+ def begin_response(self) -> None:
224
+ pass
225
+
226
+ def waiting(self, msg: str) -> None:
227
+ pass
228
+
229
+ def reasoning(self, text: str) -> None:
230
+ pass
231
+
232
+ def content(self, text: str) -> None:
233
+ if self.verbose:
234
+ self.console.print(Text(text), end="")
235
+
236
+ def tool_call_pending(self, name: str) -> None:
237
+ pass
238
+
239
+ def end_response(self) -> None:
240
+ if self.verbose:
241
+ self.console.print()
242
+
243
+ def tool_start(self, name: str, summary: str) -> None:
244
+ if self.verbose:
245
+ super().tool_start(name, summary)
246
+
247
+ def tool_result(self, preview: str, is_error: bool = False) -> None:
248
+ if self.verbose:
249
+ super().tool_result(preview, is_error)
250
+
251
+ def show_diff(self, diff: str) -> None:
252
+ if self.verbose:
253
+ super().show_diff(diff)
254
+
255
+ def show_todos(self, todos):
256
+ if self.verbose:
257
+ super().show_todos(todos)
258
+
259
+ def ask_permission(self, title, detail, options) -> str:
260
+ self.console.print(Text(f"Denied (non-interactive, use --allowed-tools or --yolo): {title}",
261
+ style="yellow"))
262
+ return "n"
263
+
264
+
265
+ def is_tty() -> bool:
266
+ try:
267
+ return sys.stdin.isatty() and sys.stdout.isatty()
268
+ except (AttributeError, ValueError):
269
+ return False
@@ -0,0 +1,108 @@
1
+ """Rendering for `euron usage`, `/usage`, `whoami` and the status line."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime as _dt
6
+ from typing import Any, Dict, Optional
7
+
8
+ from rich.table import Table
9
+
10
+ from euron_code import api
11
+
12
+
13
+ def fmt_int(n: Any) -> str:
14
+ try:
15
+ return f"{int(n):,}"
16
+ except (TypeError, ValueError):
17
+ return "-"
18
+
19
+
20
+ def short(n: Any) -> str:
21
+ try:
22
+ n = int(n)
23
+ except (TypeError, ValueError):
24
+ return "-"
25
+ if n >= 1_000_000:
26
+ return f"{n / 1_000_000:.1f}M"
27
+ if n >= 10_000:
28
+ return f"{n / 1000:.0f}k"
29
+ if n >= 1000:
30
+ return f"{n / 1000:.1f}k"
31
+ return str(n)
32
+
33
+
34
+ def local_time(iso: Optional[str]) -> str:
35
+ if not iso:
36
+ return "-"
37
+ try:
38
+ s = iso.replace("Z", "+00:00")
39
+ dt = _dt.datetime.fromisoformat(s)
40
+ if dt.tzinfo is None:
41
+ dt = dt.replace(tzinfo=_dt.timezone.utc)
42
+ local = dt.astimezone()
43
+ return local.strftime("%Y-%m-%d %H:%M %Z").strip()
44
+ except ValueError:
45
+ return iso
46
+
47
+
48
+ def render_account(ui, me: Dict[str, Any]) -> None:
49
+ user = me.get("user") or {}
50
+ access = me.get("access") or {}
51
+ quota = me.get("quota") or {}
52
+ totals = me.get("totals") or {}
53
+ ui.info(f"[bold]{user.get('name') or ''}[/bold] <{user.get('email') or ''}>")
54
+ ui.info(f"Plan: {api.plan_label(access)}")
55
+ limit = quota.get("dailyTokenLimit")
56
+ used = quota.get("usedToday")
57
+ ui.info(f"Tokens today: {fmt_int(used)} / {fmt_int(limit)}"
58
+ + (f" ({int(used) * 100 // max(1, int(limit))}%)" if used is not None and limit else ""))
59
+ ui.info(f"Remaining: {fmt_int(quota.get('remainingToday'))}")
60
+ ui.info(f"Resets at: {local_time(quota.get('resetsAt'))}")
61
+ ui.info(f"All time: {fmt_int(totals.get('allTimeTokens'))} tokens, "
62
+ f"{fmt_int(totals.get('allTimeRequests'))} requests")
63
+ if quota.get("requestsPerMinute"):
64
+ ui.dim(f"Limits: {quota.get('requestsPerMinute')} requests/min, "
65
+ f"{quota.get('perUserConcurrency') or 1} at a time. Default model: {me.get('defaultModel') or '-'}")
66
+
67
+
68
+ def render_usage(ui, me: Dict[str, Any], usage: Optional[Dict[str, Any]]) -> None:
69
+ render_account(ui, me)
70
+ if not usage:
71
+ return
72
+ days = usage.get("days") or []
73
+ t = Table(title="Last 30 days", show_edge=False, header_style="bold")
74
+ t.add_column("Day")
75
+ t.add_column("Requests", justify="right")
76
+ t.add_column("Completed", justify="right")
77
+ t.add_column("Prompt", justify="right")
78
+ t.add_column("Completion", justify="right")
79
+ t.add_column("Total", justify="right")
80
+ tot = 0
81
+ for d in sorted(days, key=lambda d: d.get("day", ""), reverse=True):
82
+ p = int(d.get("promptTokens") or 0)
83
+ c = int(d.get("completionTokens") or 0)
84
+ tot += p + c
85
+ t.add_row(str(d.get("day", "")), fmt_int(d.get("requests")), fmt_int(d.get("completed")),
86
+ fmt_int(p), fmt_int(c), fmt_int(p + c))
87
+ ui.info("")
88
+ if days:
89
+ ui.console.print(t)
90
+ ui.dim(f"30-day total: {fmt_int(tot)} tokens")
91
+ else:
92
+ ui.dim("No usage in the last 30 days.")
93
+
94
+
95
+ def status_line(model: str, session_tokens: int, me: Optional[Dict[str, Any]], mode: str,
96
+ context_pct: Optional[int] = None) -> str:
97
+ parts = [model]
98
+ parts.append(f"session {short(session_tokens)} tok")
99
+ if context_pct is not None:
100
+ parts.append(f"context {context_pct}%")
101
+ if me:
102
+ q = me.get("quota") or {}
103
+ rem = q.get("remainingToday")
104
+ if rem is not None:
105
+ parts.append(f"today {short(rem)} left of {short(q.get('dailyTokenLimit'))}")
106
+ if mode != "default":
107
+ parts.append(f"mode: {mode}")
108
+ return " | ".join(parts)
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.5
2
+ Name: euron-code
3
+ Version: 0.1.0
4
+ Summary: Euron Code: an agentic coding assistant in your terminal for Euron Plus and Super 30 students.
5
+ Project-URL: Homepage, https://euron.one/code-agent
6
+ Author-email: Euron <contact@euron.one>
7
+ License: Copyright (c) 2026 Euron. All rights reserved.
8
+
9
+ This software and its source code, documentation and associated files (the
10
+ "Software") are proprietary to Euron and are protected by copyright law.
11
+
12
+ Euron grants you a limited, non-exclusive, non-transferable, revocable licence
13
+ to install and run the Software solely to access the Euron Code service with an
14
+ Euron account that is entitled to it (for example Euron Plus or Super 30).
15
+
16
+ You may not, without the prior written permission of Euron:
17
+ - copy, modify, merge, publish, distribute, sublicense or sell the Software
18
+ or any part of it;
19
+ - reverse engineer, decompile or disassemble the Software, except to the
20
+ extent that applicable law expressly permits it;
21
+ - use the Software to access any service other than Euron Code, or to
22
+ circumvent any access control, quota or rate limit of the service.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
26
+ FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL EURON BE LIABLE
27
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
29
+ THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: ai,assistant,cli,coding agent,euron
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Education
36
+ Classifier: License :: Other/Proprietary License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Topic :: Software Development
45
+ Requires-Python: >=3.9
46
+ Requires-Dist: httpx<1,>=0.24
47
+ Requires-Dist: keyring>=23.0
48
+ Requires-Dist: prompt-toolkit>=3.0.30
49
+ Requires-Dist: rich>=13.0
50
+ Provides-Extra: db
51
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'db'
52
+ Requires-Dist: pymysql>=1.0; extra == 'db'
53
+ Provides-Extra: dev
54
+ Requires-Dist: build>=1.0; extra == 'dev'
55
+ Requires-Dist: pytest>=7; extra == 'dev'
56
+ Provides-Extra: mysql
57
+ Requires-Dist: pymysql>=1.0; extra == 'mysql'
58
+ Provides-Extra: postgres
59
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
60
+ Description-Content-Type: text/markdown
61
+
62
+ # Euron Code
63
+
64
+ An AI coding agent for your terminal, made for Euron students. Ask it to explain a codebase, fix a
65
+ bug, write tests or build a feature: it reads your files, proposes edits, runs commands and checks
66
+ its own work, asking you before it changes anything.
67
+
68
+ Euron Code runs on your computer. Only the conversation goes to Euron's model servers.
69
+
70
+ > **Requires Euron Plus or Super 30.** Euron Code is included with both. Get Plus at
71
+ > https://euron.one/plus
72
+
73
+ ## Install
74
+
75
+ Python 3.9 or newer, on Windows, macOS or Linux.
76
+
77
+ ```bash
78
+ pip install euron-code
79
+ # or, to keep it isolated from your projects:
80
+ pipx install euron-code
81
+ ```
82
+
83
+ Optional database drivers for the database explorer:
84
+
85
+ ```bash
86
+ pip install "euron-code[postgres]" # Postgres
87
+ pip install "euron-code[mysql]" # MySQL / MariaDB
88
+ ```
89
+
90
+ Check the install with `euron --version` and `euron doctor`.
91
+
92
+ ## Log in
93
+
94
+ ```bash
95
+ euron login
96
+ ```
97
+
98
+ Your browser opens a Euron page that shows a code (for example `WXYZ-1234`). Check that it matches
99
+ the code in your terminal and click **Approve**. The terminal finishes by itself. If no browser opens
100
+ (SSH, WSL), copy the link it prints into any browser where you are logged in to euron.one.
101
+
102
+ Your key is saved in the system keychain (Windows Credential Manager, macOS Keychain, Linux Secret
103
+ Service). If there is no keychain it goes in `~/.euron/credentials.json`, readable only by you.
104
+
105
+ ```bash
106
+ euron whoami # who you are logged in as, and your plan
107
+ euron logout # remove the key from this computer
108
+ ```
109
+
110
+ ## Use it
111
+
112
+ Open a terminal **in your project folder** and run:
113
+
114
+ ```bash
115
+ euron
116
+ ```
117
+
118
+ Then type what you want, for example:
119
+
120
+ - `explain how authentication works in this project`
121
+ - `the tests in tests/test_api.py fail, find out why and fix it`
122
+ - `add input validation to the signup form and a test for it`
123
+
124
+ Euron Code works step by step: it looks around, reads files, shows you a diff before each edit, and
125
+ asks before running commands. Answer:
126
+
127
+ | Key | Meaning |
128
+ |---|---|
129
+ | `y` | yes, once |
130
+ | `n` | no (you can then tell it what to do instead) |
131
+ | `a` | always allow this for the rest of the session |
132
+ | `p` | always allow commands starting the same way (e.g. every `npm test ...`) this session |
133
+ | `s` | always, and save the rule to `.euron/settings.local.json` |
134
+
135
+ Press **Esc** or **Ctrl-C** to stop a reply; the session stays open. **Ctrl-D** or `/exit` quits.
136
+ **Esc then Enter** inserts a newline.
137
+
138
+ ### Commands inside a session
139
+
140
+ | Command | What it does |
141
+ |---|---|
142
+ | `/help` | list commands |
143
+ | `/usage` | plan, tokens used today and your daily limit, reset time, last 30 days |
144
+ | `/cost` | tokens used in this session |
145
+ | `/model [id]` | show or switch the model |
146
+ | `/compact [focus]` | summarise the conversation to free context |
147
+ | `/clear` | start a fresh conversation |
148
+ | `/memory`, `/memory edit` | show or edit `EURON.md` memory |
149
+ | `/init` | let Euron Code explore the repo and write `EURON.md` |
150
+ | `/permissions` | show rules; `/permissions mode acceptEdits`, `/permissions allow "Bash(pytest:*)"` |
151
+ | `/thinking show\|collapse\|hide` | how the model's reasoning is displayed |
152
+ | `/todos` | the current task list |
153
+ | `/doctor` | check your setup |
154
+ | `/login`, `/logout`, `/exit` | |
155
+ | `# some note` | save a note to memory (asks: project or user) |
156
+
157
+ The bar at the bottom shows the model, tokens used this session, how full the context is, and how
158
+ many tokens you have left today.
159
+
160
+ ### Continue later
161
+
162
+ Every session is saved under `~/.euron/sessions/`.
163
+
164
+ ```bash
165
+ euron --continue # pick up the latest session in this folder
166
+ euron --resume # choose from a list
167
+ ```
168
+
169
+ ### Usage and limits
170
+
171
+ ```bash
172
+ euron usage
173
+ ```
174
+
175
+ shows your plan (Euron Plus / Super 30), tokens today out of your daily limit, what is left, when it
176
+ resets (in your local time), all-time totals and a day-by-day table for the last 30 days. The daily
177
+ allowance resets at midnight IST.
178
+
179
+ Euron Code sends one request at a time per student. When the servers are busy it waits and retries
180
+ by itself, and tells you.
181
+
182
+ ## Memory: EURON.md
183
+
184
+ Put project instructions in `EURON.md` at the project root (commands to build and test, conventions,
185
+ things to avoid). Euron Code reads it at the start of every session, together with `EURON.md` files in
186
+ parent folders up to the git root and your personal `~/.euron/EURON.md`. Run `/init` to have a first
187
+ version written for you.
188
+
189
+ ## Non-interactive use (scripts, CI)
190
+
191
+ ```bash
192
+ euron -p "summarise what this repo does"
193
+ git diff | euron -p "review this diff"
194
+ euron -p "run the tests and fix failures" --allowed-tools "Edit,Bash(pytest:*)" --max-turns 20
195
+ euron -p "list the API routes" --output-format json
196
+ ```
197
+
198
+ In `-p` mode nothing can ask you, so anything that needs permission is refused unless an allow rule
199
+ covers it (`--allowed-tools`, or settings). `--yolo` skips all prompts: only use it in a throwaway
200
+ folder or container.
201
+
202
+ Exit codes: `0` ok, `1` error, `2` bad usage, `3` no Euron Plus / Super 30 access, `4` not logged in or
203
+ key rejected, `5` daily token allowance used, `6` `--max-turns` reached, `130` interrupted.
204
+
205
+ ## Database explorer
206
+
207
+ Euron Code can look at your app's database while it works. SQLite files inside the project work
208
+ directly (`look at app.db and tell me which tables have no index`). For Postgres or MySQL, name a
209
+ connection in your settings; `${VAR}` reads from the environment so passwords stay out of the file:
210
+
211
+ ```bash
212
+ euron config set --project db.connections.app "postgresql://dev:${PGPASSWORD}@localhost:5432/app"
213
+ ```
214
+
215
+ It is **read-only**: only `SELECT`, `WITH`, `EXPLAIN`, `SHOW`, `PRAGMA` and similar are allowed, and
216
+ the connection itself is opened read-only. To let it write (local dev databases only), run
217
+ `euron config set db.allowWrites true`; every write statement still asks you first.
218
+
219
+ ## Settings
220
+
221
+ `euron config set <key> <value>` writes `~/.euron/settings.json` (add `--project` for
222
+ `.euron/settings.json` in the current folder). `euron config list` shows the merged result.
223
+
224
+ | Key | Default | |
225
+ |---|---|---|
226
+ | `model` | your account default | model id |
227
+ | `reasoning` | `collapse` | `show`, `collapse` or `hide` the model's thinking |
228
+ | `permissions.allow` / `permissions.deny` | `[]` | rules like `Edit`, `Bash(npm test:*)`, `WebFetch(domain:docs.python.org)`, `Read(~/notes/**)` |
229
+ | `permissions.defaultMode` | `default` | `default`, `acceptEdits` (edits inside the project need no prompt) or `yolo` |
230
+ | `bashTimeout` | `120` | seconds before a command is stopped |
231
+ | `shell` | auto | PowerShell on Windows, bash/zsh elsewhere; or `cmd`, `bash`, a path |
232
+ | `autoCompact` | `true` | summarise old turns when the context is 75% full |
233
+ | `webFetch` | `true` | allow the web page tool (it still asks per site) |
234
+ | `db.connections.<name>` | | database DSNs |
235
+
236
+ Environment variables: `EURON_CODE_API` (gateway URL, default
237
+ `https://api.euron.one/api/v1/code-agent`), `EURON_API_KEY` (use a key without logging in, for CI),
238
+ `EURON_HOME` (instead of `~/.euron`), `EURON_NO_BROWSER=1`.
239
+
240
+ ## In VS Code
241
+
242
+ Open your project folder in VS Code, then open the integrated terminal (**Ctrl+`**, or
243
+ **Terminal > New Terminal**) and run `euron`. It works in the folder VS Code opened, and edits show up
244
+ in the editor straight away. Tips:
245
+
246
+ - On Windows the integrated terminal is PowerShell by default; Euron Code runs commands with
247
+ PowerShell too. To use Git Bash instead: `euron config set shell bash`.
248
+ - If `euron` is not found after `pip install`, the Python `Scripts` folder is not on your PATH.
249
+ Use `python -m euron_code`, or install with `pipx` (`pipx ensurepath`), then restart VS Code.
250
+ - Keep your edits saved before asking Euron Code to change the same file.
251
+
252
+ ## Troubleshooting
253
+
254
+ | You see | What to do |
255
+ |---|---|
256
+ | `You don't have Euron Plus access...` | Euron Code needs Euron Plus or an active Super 30 enrolment: https://euron.one/plus |
257
+ | `not logged in` / `key was rejected` | run `euron login` again (keys can be revoked at euron.one/code-agent) |
258
+ | `servers are busy, retrying` | normal at peak times; it retries for you. If it gives up, try again in a few minutes |
259
+ | `Rate limit reached` | too many requests in one minute; it waits and retries |
260
+ | `today's token allowance` | you used today's limit; it resets at the time shown (midnight IST) |
261
+ | `The response stopped part-way` | the connection dropped mid-answer; nothing more was applied, send your message again |
262
+ | `euron: command not found` | see the VS Code tips above, or run `python -m euron_code` |
263
+ | garbled colours on old Windows consoles | use Windows Terminal or VS Code's terminal |
264
+
265
+ `euron doctor` checks Python, the connection, your login and plan, ripgrep (faster search; optional)
266
+ and the keychain.
267
+
268
+ ## Privacy and safety
269
+
270
+ - Your files are only sent as part of the conversation with Euron's model, and only what the agent
271
+ reads. Nothing is uploaded anywhere else. There is no telemetry.
272
+ - Your key is never printed in full and never logged.
273
+ - Edits and commands ask first. Files outside the project folder always ask, even to read.
274
+ - Text inside your files, web pages and command output is treated as data, not instructions.
275
+
276
+ ## Licence
277
+
278
+ Copyright (c) 2026 Euron. All rights reserved. See `LICENSE`.
@@ -0,0 +1,33 @@
1
+ euron_code/__init__.py,sha256=-JSAXxKntRgF38xYMKR_2W6khJ1qD3xaHDLiVx4l2co,87
2
+ euron_code/__main__.py,sha256=m8iDBDA7e9XBlUTpLwMit61zzAtyctk9rySLTMkYU-w,89
3
+ euron_code/agent.py,sha256=aRP2ZGirzIIzX3DUT26J3-gJkq8xyEXscgk84vrao8c,27903
4
+ euron_code/api.py,sha256=LsiRFzxD0DxXDXVt9yBfaEyAJ64eAUIICOhZMs9WoFg,14108
5
+ euron_code/auth.py,sha256=UpRfDkzIokl3hK_zzhjk2SnBJy6BzWG2WpUzr6dLBdU,4290
6
+ euron_code/cli.py,sha256=krvG6kYow7GImIhmLdm8OuSMPKiGKKkGo-tZgBiQaSY,14633
7
+ euron_code/config.py,sha256=5SGxnpSO244PwekWTBpoIySUF_kC59MBreZxBDg_Q9U,6379
8
+ euron_code/context.py,sha256=M3odv5vDwTtPFZUwsT4cnmgFhr-InffgxJy2IvKr9Iw,5184
9
+ euron_code/credentials.py,sha256=gCyLXdkVw0Jzf27BBJEUO-qE_d48bm3KwkuO-7Ec4Ck,5640
10
+ euron_code/doctor.py,sha256=KFB4T4I0aCuTtGs1Kg_fLxY_bZH-ftITbDDg-WS8B9s,3980
11
+ euron_code/keys.py,sha256=Bop0hzB0-Dtz5ag1GY7Zj_JxH-8RfCuZ2qWI4GLvVZ8,3111
12
+ euron_code/memory.py,sha256=hWgeZVpdW9Gki5UW6E9Qdz1MXXv7_HZaZqOo8JYwFvM,2797
13
+ euron_code/permissions.py,sha256=st8YKhKDCKgXyAymwuEJVOWkt6OukG5OtoNwdokqK-0,9126
14
+ euron_code/prompts.py,sha256=V6ZrVh213ADAnM0MMLfvFEZqJNyOxYMt7HPzb4EKlJ8,6806
15
+ euron_code/repl.py,sha256=-pxA64pNQmuZTvCXNjD4UWoh6XPF84wUefp39Ql0GQI,18676
16
+ euron_code/sessions.py,sha256=EcQyRmOZuN16V87aNDJjLIFjF3jJsusdqWokFsFnxXQ,6401
17
+ euron_code/ui.py,sha256=27ccBlSfxrgFVsD838rRmXs-zgVP3Uzk0tbvZS-ldtM,10010
18
+ euron_code/usage_view.py,sha256=oE_dty6Ondzy58coPD3KQNU6787kYgPLO3T6bchH7Vg,3759
19
+ euron_code/tools/__init__.py,sha256=TEY2vc-trh7v01io8Ynui0f8WIEuyHoDexoyKzMoKLw,1185
20
+ euron_code/tools/base.py,sha256=2h6Sk9tsAapI-QTHnol-Se8ff5uRo2opDIR7ZAzBUZk,5341
21
+ euron_code/tools/db.py,sha256=c0KmvI6vYgfoN-vIkJ2jU-MkHpKCUfnSMKMKWm9KQ2c,16199
22
+ euron_code/tools/fs.py,sha256=l1LQB7KYmh2pL9VwV1wiXjM9iSrqfQr-0dbRSIqudFg,16718
23
+ euron_code/tools/ignore.py,sha256=BgUeDjafw5OptbaDMcyOreqvo8SttqRTpJOmA7_cNHk,2564
24
+ euron_code/tools/search.py,sha256=dCPjitKwMP2jJwYQ7G_y5HgcK4l_bORqQNsPL4kFOBg,8145
25
+ euron_code/tools/shell.py,sha256=7Yaz6Te66bn-iSQ1F59BmWMd2peX-QEcKAu8gLgIHvI,9024
26
+ euron_code/tools/task.py,sha256=PjbKTOqdlhveEZOPH0kw6GnYO3yC_UdTF_9VeqObJSI,1796
27
+ euron_code/tools/todo.py,sha256=rAaPBTQtclaYwOCkIrN3nm3bJEeivPjk8L5ytJqtBxs,2395
28
+ euron_code/tools/web.py,sha256=u1YXw8YYAkrYe9aKXFjlX32tylF7EMzn0Ir8o5WHYBk,4412
29
+ euron_code-0.1.0.dist-info/METADATA,sha256=PJY4k8jFaKP-DtMEeWIx1K0DMmTfCViOrXza4Hz5eiQ,12122
30
+ euron_code-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
31
+ euron_code-0.1.0.dist-info/entry_points.txt,sha256=qFTCov7iu3CjrtINB2m-2F6lHJxMG7weDsTwFJgat58,46
32
+ euron_code-0.1.0.dist-info/licenses/LICENSE,sha256=baMUtwGYv0eYJNB0Hs5Hl07el2JuomxzvvOfddNMkNQ,1300
33
+ euron_code-0.1.0.dist-info/RECORD,,