opendot 0.0.1__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 (37) hide show
  1. opendot-0.0.1/.github/workflows/ci.yml +26 -0
  2. opendot-0.0.1/.github/workflows/release.yml +46 -0
  3. opendot-0.0.1/.gitignore +39 -0
  4. opendot-0.0.1/CONTRIBUTING.md +55 -0
  5. opendot-0.0.1/LICENSE +21 -0
  6. opendot-0.0.1/PKG-INFO +169 -0
  7. opendot-0.0.1/README.md +137 -0
  8. opendot-0.0.1/SECURITY.md +47 -0
  9. opendot-0.0.1/pyproject.toml +46 -0
  10. opendot-0.0.1/src/opendot/__init__.py +18 -0
  11. opendot-0.0.1/src/opendot/agent/__init__.py +1 -0
  12. opendot-0.0.1/src/opendot/agent/config.py +27 -0
  13. opendot-0.0.1/src/opendot/agent/events.py +34 -0
  14. opendot-0.0.1/src/opendot/agent/explorers.py +93 -0
  15. opendot-0.0.1/src/opendot/agent/loop.py +263 -0
  16. opendot-0.0.1/src/opendot/agent/prompt.py +30 -0
  17. opendot-0.0.1/src/opendot/agent/usage.py +36 -0
  18. opendot-0.0.1/src/opendot/cli.py +364 -0
  19. opendot-0.0.1/src/opendot/mcp/__init__.py +14 -0
  20. opendot-0.0.1/src/opendot/mcp/manager.py +202 -0
  21. opendot-0.0.1/src/opendot/reversibility/__init__.py +2 -0
  22. opendot-0.0.1/src/opendot/reversibility/classifier.py +106 -0
  23. opendot-0.0.1/src/opendot/reversibility/engine.py +82 -0
  24. opendot-0.0.1/src/opendot/reversibility/ledger.py +62 -0
  25. opendot-0.0.1/src/opendot/reversibility/rules.py +64 -0
  26. opendot-0.0.1/src/opendot/reversibility/snapshots.py +220 -0
  27. opendot-0.0.1/src/opendot/tools/__init__.py +1 -0
  28. opendot-0.0.1/src/opendot/tools/local.py +332 -0
  29. opendot-0.0.1/src/opendot/tools/office.py +132 -0
  30. opendot-0.0.1/src/opendot/tui.py +409 -0
  31. opendot-0.0.1/tests/test_classifier.py +78 -0
  32. opendot-0.0.1/tests/test_explorers.py +83 -0
  33. opendot-0.0.1/tests/test_mcp.py +112 -0
  34. opendot-0.0.1/tests/test_office.py +102 -0
  35. opendot-0.0.1/tests/test_rules.py +51 -0
  36. opendot-0.0.1/tests/test_snapshots.py +174 -0
  37. opendot-0.0.1/tests/test_tools.py +76 -0
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install
22
+ run: |
23
+ python -m pip install --upgrade pip
24
+ pip install -e ".[office,dev]"
25
+ - name: Run tests
26
+ run: pytest -q
@@ -0,0 +1,46 @@
1
+ name: Release
2
+
3
+ # Publish to PyPI when you push a version tag, e.g.:
4
+ # git tag v0.1.0 && git push origin v0.1.0
5
+ # Uses PyPI Trusted Publishing (OIDC) — no API token stored in GitHub.
6
+ # One-time setup on PyPI: project → Publishing → add a trusted publisher for
7
+ # this repo + workflow file `release.yml` + environment `pypi`.
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Build sdist + wheel
22
+ run: |
23
+ python -m pip install --upgrade pip build
24
+ python -m build
25
+ - name: Check artifacts
26
+ run: |
27
+ pip install twine
28
+ twine check dist/*
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment: pypi # PyPI trusted-publisher environment
38
+ permissions:
39
+ id-token: write # required for OIDC (Trusted Publishing)
40
+ steps:
41
+ - uses: actions/download-artifact@v4
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+ - name: Publish to PyPI
46
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ *.so
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+ ENV/
16
+
17
+ # Packaging / build
18
+ *.whl
19
+ pip-wheel-metadata/
20
+
21
+ # Test / coverage / type caches
22
+ .pytest_cache/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+ .coverage
26
+ htmlcov/
27
+ .tox/
28
+
29
+ # Editor / OS
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ .DS_Store
34
+
35
+ # opendot local state (snapshot store lives in ~/.opendot; never commit any local copy)
36
+ .opendot/
37
+
38
+ # Environment / secrets
39
+ .env
@@ -0,0 +1,55 @@
1
+ # Contributing to opendot
2
+
3
+ Thanks for your interest! opendot is an interactive terminal AI agent whose
4
+ defining feature is **trustworthy reversibility** — you can see and cleanly undo
5
+ everything it does. Contributions are welcome; please keep that guarantee intact.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ git clone https://github.com/vedaant00/opendot
11
+ cd opendot
12
+ uv venv && uv pip install -e ".[office,dev]" # or: python -m venv .venv && pip install -e ".[office,dev]"
13
+ pytest # should be all green
14
+ ```
15
+
16
+ ## Project layout
17
+
18
+ ```
19
+ src/opendot/
20
+ cli.py entry point (subcommands, one-shot, REPL)
21
+ tui.py the full-screen Textual TUI
22
+ agent/ the model-agnostic ReAct loop (LiteLLM), events, prompt, usage
23
+ tools/ local file/shell tools + office (.xlsx/.pptx)
24
+ reversibility/ THE MOAT: content-addressed snapshots, ledger, undo, classifier
25
+ mcp/ MCP client (connect external MCP servers)
26
+ ```
27
+
28
+ ## The one rule: don't break reversibility
29
+
30
+ `reversibility/` is the reason opendot exists. If you touch it, or add a tool
31
+ that mutates the filesystem:
32
+
33
+ - Every mutating action **must** snapshot before it runs (so `undo` restores
34
+ exact prior bytes).
35
+ - Anything whose effect can escape the workspace (network, `sudo`, deletes
36
+ outside the working dir, external/MCP calls) **must** be routed through the
37
+ confirmation gate and recorded as irreversible in the ledger.
38
+ - The snapshot round-trip must be exact — see `tests/test_snapshots.py`. Add
39
+ tests for any change here; a lying `undo` defeats the whole project.
40
+
41
+ ## Tests
42
+
43
+ Please add tests for changes. Run `pytest` before opening a PR — CI runs it on
44
+ Python 3.10–3.12.
45
+
46
+ ## Style
47
+
48
+ Match the surrounding code: small, clear functions; comments explain *why*, not
49
+ *what*. Keep the tool/UI honest — don't add UI affordances or hints for features
50
+ that don't actually work.
51
+
52
+ ## Opening a PR
53
+
54
+ Describe the change and why. If it's non-trivial, an issue first to align on the
55
+ approach is appreciated.
opendot-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vedaant Singh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
opendot-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: opendot
3
+ Version: 0.0.1
4
+ Summary: An interactive terminal AI agent you can fully undo — any model, acts on your real files, every action reversible.
5
+ Project-URL: Repository, https://github.com/vedaant00/opendot
6
+ Author: Vedaant Singh
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agent,ai,cli,coding-agent,litellm,llm,terminal
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: litellm>=1.50
19
+ Requires-Dist: mcp>=1.2
20
+ Requires-Dist: prompt-toolkit>=3.0
21
+ Requires-Dist: rich>=13.0
22
+ Requires-Dist: textual>=0.60
23
+ Provides-Extra: dev
24
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: python-pptx>=0.6; extra == 'dev'
28
+ Provides-Extra: office
29
+ Requires-Dist: openpyxl>=3.1; extra == 'office'
30
+ Requires-Dist: python-pptx>=0.6; extra == 'office'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <div align="center">
34
+
35
+ ```
36
+ _ _
37
+ ___ _ __ ___ _ __ __| | ___ | |_
38
+ / _ \| '_ \ / _ \ '_ \ / _` |/ _ \| __|
39
+ | (_) | |_) | __/ | | | (_| | (_) | |_
40
+ \___/| .__/ \___|_| |_|\__,_|\___/ \__|
41
+ |_|
42
+ ```
43
+
44
+ **An interactive terminal AI agent you can fully undo.**
45
+
46
+ [![CI](https://github.com/vedaant00/opendot/actions/workflows/ci.yml/badge.svg)](https://github.com/vedaant00/opendot/actions/workflows/ci.yml)
47
+ [![PyPI](https://img.shields.io/pypi/v/opendot.svg)](https://pypi.org/project/opendot/)
48
+ [![Downloads](https://static.pepy.tech/badge/opendot)](https://pepy.tech/project/opendot)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
50
+
51
+ </div>
52
+
53
+
54
+ opendot works directly on your real files and shell — but unlike other terminal
55
+ agents, **every action it takes is snapshotted first**, so you can see exactly
56
+ what it did and cleanly walk it back. Files *and* shell commands, not just
57
+ in-repo edits. Commands whose effects escape your workspace (network, sudo,
58
+ `git push`, deleting outside the working dir) are flagged and confirmed before
59
+ they run, with an honest note about what can't be undone.
60
+
61
+ That's the point of opendot: an agent you can let loose because nothing it does
62
+ is a surprise, and (almost) nothing is irreversible.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ # try it instantly, no install
68
+ uvx opendot
69
+
70
+ # recommended (isolated global CLI)
71
+ uv tool install opendot # or: pipx install opendot
72
+
73
+ # also works
74
+ pip install opendot
75
+ ```
76
+
77
+ ## Use
78
+
79
+ ```bash
80
+ opendot # open an interactive chat
81
+ opendot -p "summarize this project" # one-shot, for scripts / CI
82
+ opendot --model claude-sonnet-4-5 # any model (see below)
83
+
84
+ opendot log # audit: what has the agent done here?
85
+ opendot undo # revert the last action
86
+ opendot undo 000004 # restore the workspace to before action #4
87
+ ```
88
+
89
+ Inside the chat, slash-commands: `/log`, `/undo`, `/clear`, `/compact`,
90
+ `/model`, `/help`.
91
+
92
+ ## Any model
93
+
94
+ opendot uses [LiteLLM](https://docs.litellm.ai), so any model works — cloud,
95
+ local, or Hugging Face. Set the provider's API key in your environment and pass
96
+ `--model`:
97
+
98
+ | Provider | Env var | Example `--model` |
99
+ |----------|---------|-------------------|
100
+ | OpenAI | `OPENAI_API_KEY` | `gpt-4o` |
101
+ | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-5` |
102
+ | Google | `GEMINI_API_KEY` | `gemini/gemini-2.0-flash` |
103
+ | Ollama (local) | — | `ollama/qwen2.5` |
104
+
105
+ Reasoning models stream their thinking live.
106
+
107
+ ## Connect MCP servers
108
+
109
+ opendot is an [MCP](https://modelcontextprotocol.io) client: connect any MCP
110
+ server and its tools become available to the agent alongside the built-in ones.
111
+
112
+ ```bash
113
+ # a stdio server — put its launch command after `--`
114
+ opendot mcp add <name> --env KEY=VALUE -- <command> [args...]
115
+
116
+ # a remote server (http/sse)
117
+ opendot mcp add <name> --url <https url>
118
+
119
+ opendot mcp list # show configured servers
120
+ opendot mcp remove <name> # remove one
121
+ ```
122
+
123
+ Servers are stored in `~/.opendot/mcp.json` and connect automatically on the
124
+ next launch; connected servers appear in the sidebar.
125
+
126
+ Because opendot can't know what an external tool does, **every MCP tool call is
127
+ treated as irreversible** — it's confirmed before running and marked ✗ in the
128
+ ledger. Your built-in file/shell actions stay snapshotted and undoable as usual.
129
+
130
+ ## Project rules — `OPENDOT.md`
131
+
132
+ Drop an `OPENDOT.md` in your project. Its prose is given to the agent as
133
+ context. You can also control what gets snapshotted with an `opendot` block:
134
+
135
+ ````markdown
136
+ ```opendot
137
+ # snapshot these even though they'd normally be skipped:
138
+ snapshot: dist
139
+ # never snapshot these:
140
+ skip: data, *.log
141
+ ```
142
+ ````
143
+
144
+ By default opendot skips `.git`, `node_modules`, virtualenvs, and build caches
145
+ when snapshotting — your rules override those in either direction.
146
+
147
+ ## How the reversibility works
148
+
149
+ - Before every file write or shell command, opendot snapshots the working
150
+ directory into a **content-addressed store** in `~/.opendot` (each unique file
151
+ stored once, so snapshots are cheap).
152
+ - Every action is recorded in an **append-only ledger** you can inspect with
153
+ `opendot log`.
154
+ - `opendot undo` restores the workspace to a chosen point, exactly.
155
+ - A conservative **classifier** decides which shell commands are workspace-
156
+ contained (auto-run, undoable) vs. escaping (confirmed first, marked
157
+ irreversible). When unsure, it asks.
158
+
159
+ Honest boundary: opendot cannot undo effects that leave your machine (a sent
160
+ email, a dropped remote database, a `git push`). It tells you *before* running
161
+ those, rather than pretending otherwise.
162
+
163
+ ## Status
164
+
165
+ Early (alpha). The interactive agent, local tools, and the full reversibility
166
+ engine work and are tested. Streaming, slash-commands, and `OPENDOT.md` rules
167
+ are in. A richer TUI and more tools are coming.
168
+
169
+ MIT licensed.
@@ -0,0 +1,137 @@
1
+ <div align="center">
2
+
3
+ ```
4
+ _ _
5
+ ___ _ __ ___ _ __ __| | ___ | |_
6
+ / _ \| '_ \ / _ \ '_ \ / _` |/ _ \| __|
7
+ | (_) | |_) | __/ | | | (_| | (_) | |_
8
+ \___/| .__/ \___|_| |_|\__,_|\___/ \__|
9
+ |_|
10
+ ```
11
+
12
+ **An interactive terminal AI agent you can fully undo.**
13
+
14
+ [![CI](https://github.com/vedaant00/opendot/actions/workflows/ci.yml/badge.svg)](https://github.com/vedaant00/opendot/actions/workflows/ci.yml)
15
+ [![PyPI](https://img.shields.io/pypi/v/opendot.svg)](https://pypi.org/project/opendot/)
16
+ [![Downloads](https://static.pepy.tech/badge/opendot)](https://pepy.tech/project/opendot)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
18
+
19
+ </div>
20
+
21
+
22
+ opendot works directly on your real files and shell — but unlike other terminal
23
+ agents, **every action it takes is snapshotted first**, so you can see exactly
24
+ what it did and cleanly walk it back. Files *and* shell commands, not just
25
+ in-repo edits. Commands whose effects escape your workspace (network, sudo,
26
+ `git push`, deleting outside the working dir) are flagged and confirmed before
27
+ they run, with an honest note about what can't be undone.
28
+
29
+ That's the point of opendot: an agent you can let loose because nothing it does
30
+ is a surprise, and (almost) nothing is irreversible.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ # try it instantly, no install
36
+ uvx opendot
37
+
38
+ # recommended (isolated global CLI)
39
+ uv tool install opendot # or: pipx install opendot
40
+
41
+ # also works
42
+ pip install opendot
43
+ ```
44
+
45
+ ## Use
46
+
47
+ ```bash
48
+ opendot # open an interactive chat
49
+ opendot -p "summarize this project" # one-shot, for scripts / CI
50
+ opendot --model claude-sonnet-4-5 # any model (see below)
51
+
52
+ opendot log # audit: what has the agent done here?
53
+ opendot undo # revert the last action
54
+ opendot undo 000004 # restore the workspace to before action #4
55
+ ```
56
+
57
+ Inside the chat, slash-commands: `/log`, `/undo`, `/clear`, `/compact`,
58
+ `/model`, `/help`.
59
+
60
+ ## Any model
61
+
62
+ opendot uses [LiteLLM](https://docs.litellm.ai), so any model works — cloud,
63
+ local, or Hugging Face. Set the provider's API key in your environment and pass
64
+ `--model`:
65
+
66
+ | Provider | Env var | Example `--model` |
67
+ |----------|---------|-------------------|
68
+ | OpenAI | `OPENAI_API_KEY` | `gpt-4o` |
69
+ | Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-5` |
70
+ | Google | `GEMINI_API_KEY` | `gemini/gemini-2.0-flash` |
71
+ | Ollama (local) | — | `ollama/qwen2.5` |
72
+
73
+ Reasoning models stream their thinking live.
74
+
75
+ ## Connect MCP servers
76
+
77
+ opendot is an [MCP](https://modelcontextprotocol.io) client: connect any MCP
78
+ server and its tools become available to the agent alongside the built-in ones.
79
+
80
+ ```bash
81
+ # a stdio server — put its launch command after `--`
82
+ opendot mcp add <name> --env KEY=VALUE -- <command> [args...]
83
+
84
+ # a remote server (http/sse)
85
+ opendot mcp add <name> --url <https url>
86
+
87
+ opendot mcp list # show configured servers
88
+ opendot mcp remove <name> # remove one
89
+ ```
90
+
91
+ Servers are stored in `~/.opendot/mcp.json` and connect automatically on the
92
+ next launch; connected servers appear in the sidebar.
93
+
94
+ Because opendot can't know what an external tool does, **every MCP tool call is
95
+ treated as irreversible** — it's confirmed before running and marked ✗ in the
96
+ ledger. Your built-in file/shell actions stay snapshotted and undoable as usual.
97
+
98
+ ## Project rules — `OPENDOT.md`
99
+
100
+ Drop an `OPENDOT.md` in your project. Its prose is given to the agent as
101
+ context. You can also control what gets snapshotted with an `opendot` block:
102
+
103
+ ````markdown
104
+ ```opendot
105
+ # snapshot these even though they'd normally be skipped:
106
+ snapshot: dist
107
+ # never snapshot these:
108
+ skip: data, *.log
109
+ ```
110
+ ````
111
+
112
+ By default opendot skips `.git`, `node_modules`, virtualenvs, and build caches
113
+ when snapshotting — your rules override those in either direction.
114
+
115
+ ## How the reversibility works
116
+
117
+ - Before every file write or shell command, opendot snapshots the working
118
+ directory into a **content-addressed store** in `~/.opendot` (each unique file
119
+ stored once, so snapshots are cheap).
120
+ - Every action is recorded in an **append-only ledger** you can inspect with
121
+ `opendot log`.
122
+ - `opendot undo` restores the workspace to a chosen point, exactly.
123
+ - A conservative **classifier** decides which shell commands are workspace-
124
+ contained (auto-run, undoable) vs. escaping (confirmed first, marked
125
+ irreversible). When unsure, it asks.
126
+
127
+ Honest boundary: opendot cannot undo effects that leave your machine (a sent
128
+ email, a dropped remote database, a `git push`). It tells you *before* running
129
+ those, rather than pretending otherwise.
130
+
131
+ ## Status
132
+
133
+ Early (alpha). The interactive agent, local tools, and the full reversibility
134
+ engine work and are tested. Streaming, slash-commands, and `OPENDOT.md` rules
135
+ are in. A richer TUI and more tools are coming.
136
+
137
+ MIT licensed.
@@ -0,0 +1,47 @@
1
+ # Security
2
+
3
+ ## Threat model
4
+
5
+ ### Overview
6
+
7
+ opendot is an AI-powered agent that runs **locally on your machine**. It gives a
8
+ model access to real tools: shell execution, file read/write/edit, and — if you
9
+ configure them — external MCP servers.
10
+
11
+ ### No sandbox
12
+
13
+ opendot does **not** sandbox the agent. Two features exist to keep you in
14
+ control, but neither is a security boundary:
15
+
16
+ - **Confirmation prompts** ask before running commands opendot judges
17
+ irreversible or workspace-escaping (network, `sudo`, deletes outside the
18
+ working dir, external MCP calls). This is a UX safety net, not isolation.
19
+ - **Reversibility** snapshots the workspace before each action so you can `undo`
20
+ file/shell changes. This lets you *walk back* mistakes made inside the
21
+ workspace — it does **not** prevent an action, and it cannot undo effects that
22
+ leave your machine (a sent request, a dropped remote database, `git push`).
23
+
24
+ If you need true isolation, run opendot inside a Docker container or VM.
25
+
26
+ ### Out of scope
27
+
28
+ | Category | Rationale |
29
+ |----------|-----------|
30
+ | Sandbox escapes | opendot is not a sandbox — the confirm/undo system is a UX layer (see above). |
31
+ | Effects outside the workspace | Actions that leave your machine are flagged as irreversible before running, but cannot be undone; running them is your decision. |
32
+ | LLM provider data handling | Data sent to your configured model provider is governed by their policies. |
33
+ | MCP server behavior | External MCP servers you configure are outside opendot's trust boundary. |
34
+ | Malicious config files | You control your own `~/.opendot` config; modifying it is not an attack vector. |
35
+
36
+ ## Reporting a vulnerability
37
+
38
+ > [!IMPORTANT]
39
+ > We do not accept AI-generated security reports. Please only submit findings you
40
+ > have personally verified.
41
+
42
+ To report a security issue, use the repository's **GitHub Security Advisory
43
+ "Report a Vulnerability"** tab. We'll acknowledge your report and keep you
44
+ informed of progress toward a fix, and may ask for additional detail.
45
+
46
+ We appreciate responsible disclosure and will make every effort to acknowledge
47
+ your contribution.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "opendot"
7
+ version = "0.0.1"
8
+ description = "An interactive terminal AI agent you can fully undo — any model, acts on your real files, every action reversible."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Vedaant Singh" }]
13
+ keywords = ["ai", "agent", "cli", "llm", "litellm", "coding-agent", "terminal"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Software Development",
21
+ "Topic :: Utilities",
22
+ ]
23
+ dependencies = [
24
+ "litellm>=1.50", # any model: cloud, local, HF, through one interface
25
+ "rich>=13.0", # pretty terminal output (plain REPL fallback)
26
+ "textual>=0.60", # full-screen TUI (main pane + live ledger sidebar)
27
+ "prompt_toolkit>=3.0", # plain REPL input (history, editing)
28
+ "mcp>=1.2", # MCP client: connect to external MCP servers' tools
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ office = ["openpyxl>=3.1", "python-pptx>=0.6"] # xlsx / pptx read+edit tools
33
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "openpyxl>=3.1", "python-pptx>=0.6"]
34
+
35
+ [project.scripts]
36
+ opendot = "opendot.cli:main"
37
+
38
+ [project.urls]
39
+ Repository = "https://github.com/vedaant00/opendot"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/opendot"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ asyncio_mode = "auto" # pytest-asyncio runs `async def test_*` without per-test markers
@@ -0,0 +1,18 @@
1
+ """opendot — an interactive terminal AI agent you can fully undo.
2
+
3
+ Public SDK surface (so CLI, and future clients, are thin layers over this):
4
+
5
+ from opendot import Agent, AgentConfig
6
+
7
+ agent = Agent(AgentConfig(model="gpt-4o"))
8
+ async for event in agent.run("list the python files and summarize them"):
9
+ ...
10
+ """
11
+
12
+ from opendot.agent.loop import Agent
13
+ from opendot.agent.config import AgentConfig
14
+ from opendot.agent.events import Event
15
+
16
+ __version__ = "0.0.1"
17
+
18
+ __all__ = ["Agent", "AgentConfig", "Event", "__version__"]
@@ -0,0 +1 @@
1
+ """The opendot agent: the model-agnostic ReAct loop, config, events, prompt."""
@@ -0,0 +1,27 @@
1
+ """Agent configuration.
2
+
3
+ Model IDs are whatever LiteLLM accepts (``gpt-4o``, ``claude-sonnet-4-5``,
4
+ ``ollama/qwen2.5``, ``huggingface/...``, etc.) — opendot is model-agnostic and
5
+ does not maintain its own provider list. API keys are read from the environment
6
+ by LiteLLM in the usual way (OPENAI_API_KEY, ANTHROPIC_API_KEY, ...).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from dataclasses import dataclass, field
13
+
14
+ # A sensible default that works if the user has an OpenAI key; over/ridden via
15
+ # --model, the OPENDOT_MODEL env var, or config.
16
+ DEFAULT_MODEL = os.environ.get("OPENDOT_MODEL", "gpt-4o")
17
+
18
+
19
+ @dataclass
20
+ class AgentConfig:
21
+ """Everything the agent loop needs. Kept minimal for v1."""
22
+
23
+ model: str = DEFAULT_MODEL
24
+ workdir: str = field(default_factory=os.getcwd)
25
+ max_steps: int = 40 # hard bound on tool-calling turns per user message
26
+ temperature: float | None = None
27
+ system_prompt: str | None = None # None => use the built-in default
@@ -0,0 +1,34 @@
1
+ """Events streamed by the agent loop.
2
+
3
+ The loop is an async generator of these. The CLI renders them; a future SDK
4
+ consumer or remote client can consume them just as easily. Keeping this a small,
5
+ stable vocabulary is what lets other surfaces (TUI, web, Slack) be thin adapters.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Literal
12
+
13
+ EventType = Literal[
14
+ "thinking", # a streamed chunk of the model's reasoning (reasoning models)
15
+ "text", # a streamed chunk of the assistant's answer
16
+ "tool_start", # the agent is about to run a tool
17
+ "tool_end", # a tool finished (result attached)
18
+ "final", # the assistant's turn is complete
19
+ "error", # something went wrong
20
+ # parallel read-only explorers
21
+ "explorer_start", # a subagent lane started (text=task, lane=index)
22
+ "explorer_step", # a subagent did something (text=summary, lane=index)
23
+ "explorer_done", # a subagent finished (text=finding summary, lane=index)
24
+ ]
25
+
26
+
27
+ @dataclass
28
+ class Event:
29
+ type: EventType
30
+ text: str = "" # for "text" / "error" / explorer_*
31
+ tool: str = "" # for tool_start / tool_end
32
+ args: dict[str, Any] = field(default_factory=dict) # tool_start
33
+ result: str = "" # tool_end
34
+ lane: int = -1 # explorer lane index (-1 = main agent)