Metadata-Version: 2.5
Name: seedagent
Version: 0.8.0
Summary: A coding and document agent on Claude (Azure / Microsoft Foundry): tools, safety rules, memory and context management, usable from the terminal or as a library
Project-URL: Homepage, https://github.com/DemePS/CodeAgent
Project-URL: Source, https://github.com/DemePS/CodeAgent
Project-URL: Issues, https://github.com/DemePS/CodeAgent/issues
Author: DemePS
License-Expression: MIT
License-File: LICENSE
Keywords: agent,anthropic,azure,claude,excel,foundry,pdf
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
Classifier: Topic :: Software Development
Requires-Python: >=3.10
Requires-Dist: anthropic<1,>=0.125.0
Requires-Dist: azure-identity-broker<2,>=1.3.0; sys_platform == 'win32'
Requires-Dist: azure-identity<2,>=1.25.3
Requires-Dist: httpx<1,>=0.28.1
Requires-Dist: openpyxl<4,>=3.1.5
Requires-Dist: pillow>=10
Requires-Dist: pypdf<7,>=6.19.0
Requires-Dist: pypdfium2<5,>=4.30
Requires-Dist: pytesseract<1,>=0.3.10
Requires-Dist: python-dotenv<2,>=1.1.1
Requires-Dist: truststore<1,>=0.10.0
Provides-Extra: browser
Requires-Dist: playwright<2,>=1.63.0; extra == 'browser'
Provides-Extra: excel
Requires-Dist: xlwings<1,>=0.33; extra == 'excel'
Provides-Extra: pdf-image
Description-Content-Type: text/markdown

# CodeAgent

A document and coding agent. It reads and edits files, Excel workbooks and PDFs (scans included) in a project
folder, searches a library of documents, browses the web, writes mail drafts and code, asks before every
change, and remembers what it learned about each project. The model is Claude (Anthropic's API, or your own
Azure subscription through Microsoft Foundry) or DeepSeek. Use it from the terminal, or as a library to build
your own tool on top of it (a desktop app that fills Excel workbooks from invoices, a voice assistant over
a library of PDFs...).

```bash
pip install seedagent
```

Or from a clone of this repository: `uv sync` installs the package and the `coding-agent` command in `.venv` (`uv run coding-agent`), and
`uv sync --all-extras` adds the browser and the Excel backend. An application built on it just runs `uv sync` too: its own `pyproject.toml`
and `uv.lock` say which version of the engine to fetch.

The package `seedagent` installs the `seedagent` Python package and the `coding-agent` command (also available as `seedagent`).
Python 3.10 or later; Windows, macOS and Linux.

## Configure

Environment variables, or a `.env` file in the folder you run from (or `~/.coding-agent/.env`):

| Variable | Meaning |
|---|---|
| `ANTHROPIC_FOUNDRY_ENDPOINT` | `https://<resource>.services.ai.azure.com/anthropic` |
| `ANTHROPIC_FOUNDRY_DEPLOYMENT` | your Claude deployment name |
| `ANTHROPIC_FOUNDRY_API_KEY` | an API key; leave unset to sign in with your Microsoft work account |

Without an API key, the agent signs in with the account of your Windows session, a developer's
`az login`, or else the Microsoft sign-in page (once; the account is remembered).

**Without Azure**, use Anthropic's own API: leave `ANTHROPIC_FOUNDRY_ENDPOINT` unset and set

| Variable | Meaning |
|---|---|
| `ANTHROPIC_API_KEY` | an API key from [console.anthropic.com](https://console.anthropic.com) |
| `ANTHROPIC_MODEL` | a model ID (default `claude-sonnet-5`) |

Models: `claude-sonnet-5` (the default, $2/$10 per million input/output tokens), `claude-sonnet-5-5` (same price), `claude-haiku-4-5`
($1/$5, the cheapest; no effort setting, weaker with tools; good for the two background models `AGENT_MEMORY_MODEL` and
`AGENT_COMPACT_MODEL`), `claude-opus-5-5` ($4/$20), `claude-opus-5` ($5/$25) and `claude-fable-5-1` ($10/$50, its thinking cannot be turned off).

**Thinking and effort** (cost and speed), for the agent's own requests:

| Variable | Meaning |
|---|---|
| `CODEAGENT_THINKING` | thinking before the answer: `off` (the default), `adaptive`, `between_tools` or `disabled` |
| `CODEAGENT_EFFORT` | how much the model thinks and writes: `medium` (the default), `low`, `high`, `xhigh`, `max`, or `default` (nothing is sent: the model's own default applies) |

`off` means the lowest setting the model accepts: `disabled`, `between_tools` on `claude-sonnet-5-5` (it answers 400 to `disabled`), `adaptive` on
`claude-opus-5-5` (it answers 400 to both), and
no thinking field at all on `claude-fable-*` (its thinking cannot be turned off). On Foundry the model is a deployment name, so `off` may guess
wrong: name `between_tools` or `disabled` yourself. `between_tools` does not work with effort `xhigh` or `max`, and `claude-haiku-*` has no effort
setting (none is sent). A value that is not allowed is explained, never replaced. An application can also call `configure(effort=...)`.
`coding-agent --check` shows the thinking and effort it sends. `example.env` lists every variable the agent reads: copy it to `.env`.

A Foundry endpoint always wins: with `ANTHROPIC_FOUNDRY_ENDPOINT` set, the agent uses Foundry even if
`ANTHROPIC_API_KEY` is also set.

### DeepSeek instead of Claude

DeepSeek offers an Anthropic-compatible API (`https://api.deepseek.com/anthropic`), so the same client and the same tools work with a
DeepSeek model. Set `DEEPSEEK_API_KEY` (and nothing else: no `ANTHROPIC_API_KEY`, no Foundry endpoint), or `CODEAGENT_PROVIDER=deepseek` to
choose it over them. `CODEAGENT_PROVIDER=claude` does the opposite: Claude (Anthropic's API or Foundry) is used even when a DeepSeek key is set. The model is `deepseek-flash` by default; `ANTHROPIC_MODEL=deepseek-v4-pro` selects the larger one. Setting
`ANTHROPIC_API_KEY` to a DeepSeek key with `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic` also works and is recognised.
`coding-agent --check` tests the connection.

What DeepSeek does not take, and what the agent does about it: no `web_search` tool (it is left out, `Web search: off`); no `cache_control`
(DeepSeek caches by itself); no PDF `document` blocks, so `read_pdf` sends pages as rendered images instead (pypdfium2) and scans are read
by OCR or by looking at the page; thinking is sent as `disabled` for `CODEAGENT_THINKING=off`. `deepseek-flash` looks at images;
`deepseek-v4-pro` does not (it answers "unsupported image"): the tools that return an image are then not offered and `read_pdf` has no visual
mode (`AGENT_VISION=on|off` overrides the list for a model that is not in it). Set `AGENT_CONTEXT_WINDOW` to the model's real window (the
default is 200,000 tokens). An application can hand the key over without any environment variable: `configure(api_key=..., model=...)`.

## Use it in the terminal

```bash
coding-agent -d path/to/project "Add input validation to the CLI"
coding-agent -d path/to/project            # interactive
coding-agent -d path/to/project -r         # resume the last conversation
coding-agent -d path/to/project --read D:/docs --read //server/share   # also read (never write) other folders
coding-agent --check                      # Claude not answering? test each step of a call
coding-agent --help
```

In the interactive mode, the up arrow at the `You:` prompt recalls earlier questions, also from earlier
runs (kept in `~/.coding-agent/history`; needs `readline`: Linux, macOS, WSL; the Windows console recalls
the questions of the running session by itself).

## Use it as a library

```python
from seedagent import session
from seedagent.ui import TerminalUI  # or your own UI (subclass seedagent.ui.UI)

session.open_project("path/to/folder", ui=TerminalUI(),
                     tools=["read_excel", "edit_excel", "read_pdf", "ask_human"],  # optional subset
                     system_prompt=None)                                           # or your own instructions
ok, message = session.check_connection()   # can Claude be reached?
session.send("Fill costs.xlsx from invoice.pdf")
session.close()
```

`session.open_project(..., excel_first=True)` (for an application that fills workbooks) refuses `read_pdf`
until `read_excel` has run, when the instruction mentions a spreadsheet; it is off by default.

An application that fills one workbook can also set, after `open_project`: `state.excel_writable = {path}` (no other workbook can be changed
or created), `state.excel_protect_formulas = True` (a formula can be neither replaced nor cleared), and `state.excel_edit_sheets[path] = {"Sheet1"}`
(only those sheets). They are checked by the tool, not asked of the model.

`session.stop()` stops the running instruction from another thread (e.g. a Stop button);
`session.add_read_folder(path)` lets the agent read (never write) another folder. Every approval,
question and progress message goes through the UI object, so a web or desktop front end can show them.

### Your own tools

An application adds a tool of its own without changing the package:

```python
import seedagent

def say_hello(who: str, loud: bool = False) -> str:          # called as handler(**arguments)
    return f"Hello {who}{'!' if loud else ''}"

seedagent.register_tool(
    {"name": "say_hello", "description": "Greet someone.",
     "input_schema": {"type": "object", "properties": {"who": {"type": "string"}, "loud": {"type": "boolean"}}, "required": ["who"]}},
    say_hello)
session.open_project(folder, ui=ui, tools=["read_file", "say_hello"])    # a tool is offered only when the session lists it
```

The handler gets one keyword argument per property of the `input_schema` and returns a string or a list of content blocks
(`{"type": "text", ...}`, `{"type": "image", ...}`). To report a failure Claude should see, it raises `seedagent.common.ToolError`; any other
exception also reaches Claude as an error result and does not stop the agent. It runs in the agent's thread and can use `seedagent.state`
(`state.ui`, `state.workspace`). CodeAgent's path checks and confirmations guard only its own tools: what the handler does is the application's
responsibility (to read a file the way the built-in tools do, `seedagent.common.resolve_readable(path)` applies the same rules). A built-in
tool cannot be replaced, registering the same handler twice does nothing, and a second handler under a taken name is an error.

## Tools

45 tools; an application offers the subset it wants (`session.open_project(tools=[...])`).

| Group | Tools |
|---|---|
| Files | `read_file`, `write_file`, `edit_file`, `list_directory`, `change_directory`, `grep`, `copy_path`, `delete_file`, `delete_folder` |
| Documents | `read_pdf`, `search_pdf`, `search_library`, `view_image`, `read_word`, `read_powerpoint` |
| Excel | `read_excel`, `edit_excel`, `view_excel`, `format_excel`, `add_chart`, `add_table`, `add_pivot_table`, `restore_backup`, `run_python_excel` |
| Web | `web_search`, `web_open`, `web_click`, `web_type`, `web_back`, `web_page`, `web_look`, `screenshot_page`, `web_sign_in`, `web_set_token`, `web_close`, `download_file` |
| Mail and SMS | `mail_login`, `mail_draft`, `send_mail`, `send_sms` |
| Code | `run_python` (sandboxed), `git` (read-only), `clone_repo` |
| Other | `load_skill`, `ask_human` |

`read_file` reads UTF-8 and, failing that, Windows-1252 (the usual export of Excel and French accounting software; the result says so).
`grep` cuts a matching line at 300 characters and bounds its whole result, so a minified file cannot flood the conversation.

### Documents, scans and the library

- `read_pdf` reads **text** by default (page ranges). Its **visual** mode (pages rendered as images, at most 20 per call) is allowed once a
  text read happened in the conversation, to check what a scan or a table really shows; names, dates and amounts taken from a scan should be
  checked that way. A model that cannot see images gets no visual mode.
- Scanned pages are read by **OCR** (tesseract and its language packs must be installed); the text is kept in `AGENT_OCR_DIR` so a page is
  read once. `coding-agent --ocr PDF...` does it ahead of time.
- `search_library` searches a library of PDFs, Word and PowerPoint files and text files (the read-only folders and the project) through a full-text index
  (SQLite FTS5; alternatives with `a | b | c`, ranked by the words found). Files that are new or changed are indexed on the way, up to a
  size; bigger ones, and a first indexing with OCR, are done once with `coding-agent --index FOLDER...` (`AGENT_INDEX_DIR` moves the index).
- `read_word` (.docx: paragraphs and tables) and `read_powerpoint` (.pptx: slides and speaker notes) read the text only, with no extra
  dependency; a long document is read in parts with `start`, a slide alone with `slide`. Both are in the search index (a slide is a page; a
  Word file is cut in blocks, located by character). A picture inside is not read.
- `read_pdf`, `read_file`, `search_library` and `grep` also read the folders given with `--read` / `session.add_read_folder` (never write).

### Browsing the web

`web_search` finds pages; `web_open` reads one in a hidden browser that stays open, and lists its text
and its links, buttons and fields with numbers. The agent goes through a site with `web_click 3` and
`web_type 5 "weather paris" submit`, reads long pages with `web_page`, goes back with `web_back`, sees
the page with `web_look` and ends with `web_close`. It needs Playwright and a browser, like
`screenshot_page` (`uv sync --extra browser`, then `playwright install chromium`; `coding-agent
--check-browser` tests it).

What keeps it safe (in the tools, not only in the prompt):

- The first visit to a public site asks you, with the full URL (`[a]ll sites for this session` stops the
  questions until the session ends; `AGENT_WEB_APPROVE=off` never asks, for unattended runs); after that
  the agent can click and type on that site. Links to other sites are refused until the agent opens them with `web_open`,
  which asks you. localhost, private addresses and workspace HTML files need no question.
- It never types into password, sign-in or payment fields. When a site needs you signed in, `web_sign_in` opens
  a visible window where you type the password yourself; the agent never sees it and keeps only the session
  (cookies) of that site, for 30 days (`AGENT_WEB_SESSION_DAYS`), in `~/.coding-agent/web-sessions` (owner-only
  files). `coding-agent --forget-logins` deletes them (`--forget-logins SITE` for one). A saved session is as
  good as a password until it expires: keep it for sites you are comfortable with.
- A form that sends data (POST), by a button or by pressing Enter, shows what it sends and asks first.
- Downloads are blocked, metadata addresses are refused, and nothing is kept on disk (apart from sign-ins you made with `web_sign_in`): cookies and
  history end with the session (`web_close`, or the program ending).
- The page text reaches Claude marked as untrusted content, and the tools say that pages cannot give
  it instructions.

Limits: content inside frames and shadow DOM is not listed, nothing is drawn on a canvas for the text
view (use `web_look`), and a site that blocks automated browsers will block this one too.

### Excel: two backends

Who changes, formats and renders workbooks is set by `AGENT_EXCEL_BACKEND`:

| | `openpyxl` | `xlwings` (`uv sync --extra excel`) |
|---|---|---|
| Runs on | anywhere | Windows or macOS with Excel installed |
| Saving | rewrites the file: charts, pictures, pivot tables can be damaged (you are warned first) | Excel saves: nothing is lost |
| Formulas after an edit | old results until the file is opened in Excel | recalculated at once |
| `view_excel` | a drawing of the cells (needs the `browser` extra); charts listed, not drawn | what Excel prints, charts included |
| A workbook open in Excel | refused (locked) | written in that window, if it has no unsaved changes |
| `add_chart`, `add_table` | written by openpyxl | added by Excel |
| `add_pivot_table` | refused (openpyxl cannot build them) | built by Excel |

`auto` (the default) uses xlwings when it is installed and Excel can be started, else openpyxl.
Reading (`read_excel`) is the same in both: it never changes the file, lists the charts, pictures,
tables and pivot tables of a sheet, and shows a long cell whole when that one cell is read.
`scripts/compare_excel_backends.py` runs the same edits, formatting and views with both backends and
reports what each kept (run it on a PC with Excel: `uv run --extra excel --extra browser python
scripts/compare_excel_backends.py [your.xlsx ...]`).

### Excel scripts (`run_python_excel`)

For what no Excel tool does, the agent has `run_python_excel`: a Python
script run with Excel (xlwings backend only) on a workbook, which it gets as `book`. Excel can do far
more than the `run_python` sandbox can see (run macros, start programs, open and save any file), so:

- the script is checked before it runs, and refused if it uses macros (`Run`, `Evaluate`,
  `VBProject`, `ExecuteExcel4Macro`...), programs or links (`Shell`, `FollowHyperlink`, DDE
  formulas such as `=cmd|...`), other files or workbooks (`save`, `SaveAs`, `Open`, `books`,
  `app`, `Application`, `Parent`), external data (`QueryTables`, `WEBSERVICE`), imports beyond
  `math`, `datetime`, `re`, `collections`... , or ways around the check (`eval`, `exec`, `getattr`
  with a computed name, `_private` attributes);
- you approve the script (unless autonomous mode is on), and a backup of the workbook is kept first
  (`restore_backup` undoes it);
- it runs in its own invisible Excel with macros forced off and events off, inside the `run_python`
  sandbox; macros it adds are refused and the workbook is put back;
- you see what it changed: cells, sheets, charts, tables, pivot tables.

The check reads the script; it cannot prove what Excel will do. An application that passes its own
list of tools (`session.open_project(tools=[...])`) does not get it unless it lists it, and it is
refused in applications that restrict edits (chosen sheets, protected formulas, no formatting).

## Safety

- It writes only inside the project folder. Reading elsewhere: in the terminal the agent asks you the first time it wants to read a folder outside the project (`Allow the agent to read (never change) files in ...?`), and remembers the answer for the session; folders given with `--read` need no question. Keys and settings (`~/.ssh`, `~/.aws`, `~/.azure`, `~/.coding-agent`, `.env` files, `*.pem`, `*.key`...) are never read. An application built on the package keeps reading limited to what it was given.
- Every file or workbook change is shown (a diff, or a cell-by-cell table) and waits for your
  approval; a copy of the previous version of each workbook is kept in `~/.coding-agent/backups/` for 3 days,
  and `restore_backup` puts one back ("undo your changes to costs.xlsx"). Applications can list and
  restore them with `seedagent.backups` (`versions()`, `restore()`).
- Deleting always asks, even in autonomous mode; downloads and clones always ask; the agent cannot
  modify its own files; `run_python` cannot start processes or delete files.
- **No secret reaches code the model wrote.** `run_python` and Excel scripts run with an environment from which every variable that says
  it is a secret (`*KEY*`, `*TOKEN*`, `*SECRET*`, `*PASSWORD*`, `*AUTH*`), the variables of cloud and model services (`ANTHROPIC_*`,
  `DEEPSEEK_*`, `AZURE_*`, `AWS_*`, `GITHUB_*`...) and any copy of an API key are removed, and the output of the script has those values
  hidden. A document that steers the model cannot make it print your keys.
- The agent never reads `~/.coding-agent` (credentials and its own settings), so a knowledge folder must live elsewhere.

## What stays on your machine

Everything is in `~/.coding-agent` (`$HOME`, or your user folder on Windows), cleaned automatically
(`seedagent/cleanup.py`): once per process, at the start of `coding-agent` and the first time an application
opens a project (`session.open_project`). `AGENT_CLEANUP=off` switches that off; an application that runs for
weeks (a server) can call `seedagent.cleanup.run()` itself, daily (a call counts as the run):

| What | Where | Kept |
|---|---|---|
| Copies of workbooks before each change | `backups/` | 3 days (`AGENT_BACKUP_DAYS`) |
| What the agent learned about each project | `memory/<project>-<code>/` | notes capped in size; deleted after 90 days unused (`AGENT_MEMORY_DAYS`) |
| The saved conversation (`--resume`) | `memory/<project>-<code>/conversation.json` | 30 days (`AGENT_CONVERSATION_DAYS`) |

`AGENT_MEMORY=off` stops the agent from writing memory at all.

For developers, to see and delete what the agent remembers:

```bash
coding-agent --memories              # every project's memory: name, last used, size, notes
coding-agent --memories myapp        # one project's notes, in full
coding-agent --forget myapp          # delete one project's memory (asks first)
coding-agent --forget . -d path      # the memory of that project folder
coding-agent --forget all            # delete every project's memory (asks first)
```

## Development

```bash
uv sync --all-extras                      # the exact versions the tests ran with (uv.lock), with the browser and Excel extras
uv run playwright install chromium        # the browser tools and their tests need it
uv run pytest
uv build                                  # dist/seedagent-<version>.tar.gz and .whl
```

The CI (`.github/workflows/package.yml`) runs the same on Linux and Windows, Python 3.10 and 3.14, with every extra and Chromium
installed, then installs the built wheel in a fresh environment and runs the tests there. A test must not depend on a `.env`, on the
order of files or on the repository layout (the wheel's tests run from a copy of `tests/`).

## Releasing (maintainers)

The workflow (`.github/workflows/package.yml`) publishes to PyPI by trusted publishing (no token stored). The project is `seedagent`; its
publisher on pypi.org is owner `DemePS`, repository `CodeAgent`, workflow `package.yml`, environment `pypi` (also an environment `pypi` in
the repository's settings). PyPI refuses a name that equals an existing one once `-`, `_` and `.` are removed (`codeagent` was refused:
`code-agent` exists), so check a new name against the full list before registering it.

To publish a version, tag the commit whose `pyproject.toml` has that version, with CI green, and push the tag:

```bash
git fetch origin
git tag -a v0.7.0 origin/main -m "v0.7.0"
git push origin v0.7.0
```

The tag runs the tests and the build, publishes to PyPI and creates a GitHub Release. A tag whose version differs from `pyproject.toml` is
refused. A published version can never be replaced: fix forward with a new version. Applications then depend on `seedagent==<version>`.

## License

MIT (see LICENSE).
