nakedagent 0.1.0__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 (48) hide show
  1. nakedagent-0.1.0/LICENSE +21 -0
  2. nakedagent-0.1.0/PKG-INFO +169 -0
  3. nakedagent-0.1.0/README.md +140 -0
  4. nakedagent-0.1.0/nakedagent/__init__.py +3 -0
  5. nakedagent-0.1.0/nakedagent/__main__.py +5 -0
  6. nakedagent-0.1.0/nakedagent/cli.py +196 -0
  7. nakedagent-0.1.0/nakedagent/driver.py +171 -0
  8. nakedagent-0.1.0/nakedagent/eventlog.py +275 -0
  9. nakedagent-0.1.0/nakedagent/functional.py +315 -0
  10. nakedagent-0.1.0/nakedagent/llm.py +124 -0
  11. nakedagent-0.1.0/nakedagent/loop.py +254 -0
  12. nakedagent-0.1.0/nakedagent/mcp.py +335 -0
  13. nakedagent-0.1.0/nakedagent/migrate_log.py +187 -0
  14. nakedagent-0.1.0/nakedagent/plugins.py +153 -0
  15. nakedagent-0.1.0/nakedagent/providers_draft.py +302 -0
  16. nakedagent-0.1.0/nakedagent/replay.py +78 -0
  17. nakedagent-0.1.0/nakedagent/sidekick.py +404 -0
  18. nakedagent-0.1.0/nakedagent/toolcall.py +124 -0
  19. nakedagent-0.1.0/nakedagent/tools.py +428 -0
  20. nakedagent-0.1.0/nakedagent.egg-info/PKG-INFO +169 -0
  21. nakedagent-0.1.0/nakedagent.egg-info/SOURCES.txt +46 -0
  22. nakedagent-0.1.0/nakedagent.egg-info/dependency_links.txt +1 -0
  23. nakedagent-0.1.0/nakedagent.egg-info/entry_points.txt +2 -0
  24. nakedagent-0.1.0/nakedagent.egg-info/top_level.txt +1 -0
  25. nakedagent-0.1.0/pyproject.toml +57 -0
  26. nakedagent-0.1.0/setup.cfg +4 -0
  27. nakedagent-0.1.0/tests/test_cli.py +126 -0
  28. nakedagent-0.1.0/tests/test_driver.py +254 -0
  29. nakedagent-0.1.0/tests/test_eventlog.py +112 -0
  30. nakedagent-0.1.0/tests/test_functional.py +137 -0
  31. nakedagent-0.1.0/tests/test_integration.py +94 -0
  32. nakedagent-0.1.0/tests/test_invariants.py +292 -0
  33. nakedagent-0.1.0/tests/test_llm.py +131 -0
  34. nakedagent-0.1.0/tests/test_loop.py +245 -0
  35. nakedagent-0.1.0/tests/test_mcp.py +102 -0
  36. nakedagent-0.1.0/tests/test_mcp_repair.py +201 -0
  37. nakedagent-0.1.0/tests/test_mcp_wire.py +153 -0
  38. nakedagent-0.1.0/tests/test_migrate_log.py +136 -0
  39. nakedagent-0.1.0/tests/test_plugins.py +208 -0
  40. nakedagent-0.1.0/tests/test_pr18_boundaries.py +145 -0
  41. nakedagent-0.1.0/tests/test_pr20_gate.py +154 -0
  42. nakedagent-0.1.0/tests/test_pr20_options.py +28 -0
  43. nakedagent-0.1.0/tests/test_replay_api_compat.py +69 -0
  44. nakedagent-0.1.0/tests/test_resume.py +184 -0
  45. nakedagent-0.1.0/tests/test_sidekick.py +189 -0
  46. nakedagent-0.1.0/tests/test_toolcall.py +291 -0
  47. nakedagent-0.1.0/tests/test_tools.py +369 -0
  48. nakedagent-0.1.0/tests/test_v04_contract.py +115 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reuben Bowlby
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.
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: nakedagent
3
+ Version: 0.1.0
4
+ Summary: A coding agent with zero runtime dependencies. Stdlib-only Python.
5
+ Author-email: Reuben Bowlby <reuben@hummbl.io>
6
+ Maintainer-email: "HUMMBL, LLC" <reuben@hummbl.io>
7
+ License-Expression: MIT
8
+ Project-URL: Repository, https://github.com/hummbl-io/nakedagent
9
+ Project-URL: Homepage, https://github.com/hummbl-io/nakedagent
10
+ Project-URL: Documentation, https://github.com/hummbl-io/nakedagent#readme
11
+ Project-URL: Issues, https://github.com/hummbl-io/nakedagent/issues
12
+ Project-URL: Changelog, https://github.com/hummbl-io/nakedagent/releases
13
+ Keywords: coding-agent,zero-dependencies,stdlib-only,llm,autonomous-agents,software-engineering,replay,mealy-machine,ollama
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Software Development :: Code Generators
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Dynamic: license-file
29
+
30
+ # nakedagent
31
+
32
+ A coding agent with **zero runtime dependencies**. `pip install`-free by
33
+ construction: `python -m nakedagent` runs on a stock Python 3.10+ interpreter,
34
+ nothing else.
35
+
36
+ ```
37
+ $ python -m nakedagent
38
+ nakedagent -- workspace: /home/you/myproject -- model: qwen2.5-coder:7b
39
+ Ctrl-D to exit.
40
+
41
+ > add a .gitignore for a Python project
42
+ ```
43
+
44
+ ## Why
45
+
46
+ Every other agent in this space — gptme, aider, OpenHands, langchain-based
47
+ tools — pulls in 20-40+ packages: HTTP clients, CLI-formatting libraries,
48
+ provider SDKs, telemetry. That's not a criticism of them; those dependencies
49
+ buy real capability (multi-provider abstraction, rich terminal rendering,
50
+ robust malformed-output recovery). But it also means every install inherits
51
+ whatever CVEs are sitting in that dependency tree, and updating any one
52
+ package can break the agent.
53
+
54
+ nakedagent takes the other side of that trade on purpose: `urllib` + `json` +
55
+ `subprocess` + `re`, all from the standard library, are enough to drive a
56
+ local model through a working tool-use loop. No supply chain, because there
57
+ is no supply.
58
+
59
+ ## What you get for that
60
+
61
+ - **Tools**: `shell`, `read`, `write`, `patch` (search/replace edits, aider's
62
+ simplest edit format). That's the whole *foundation* tool surface.
63
+ `shell` is confirmation-first in interactive mode and requires `--allow-shell`
64
+ in non-interactive mode, with optional command filtering via
65
+ `--shell-allowlist`.
66
+ - **Model backend**: [Ollama](https://ollama.com) by default — local-first,
67
+ no API key required to try it. For models too large to run locally,
68
+ `--api openai` speaks the OpenAI-compatible format that hosted APIs,
69
+ vLLM/LM Studio and gateways share (see below).
70
+ - **Tool-call format**: the model writes a fenced code block whose language
71
+ tag is the tool name; nakedagent parses it out of the response text after
72
+ each turn. Works with any model that can write a code fence — no dependency
73
+ on a provider's native function-calling API.
74
+ - **Plugins**: drop a `.py` file in `.nakedagent/plugins/` (repo-local,
75
+ requires `--trust-workspace-plugins`) or `~/.nakedagent/plugins/`
76
+ (user-global, always loaded) that defines a `TOOLS` dict (add or
77
+ override tools) and/or a `DISABLE` list (remove tools entirely — e.g. a
78
+ read-only agent disables `shell` and `write`). Each tool carries a `.usage`
79
+ attribute that controls its prompt example, so a plugin replacing a tool
80
+ replaces its syntax too. No registration, no framework — see
81
+ [`DOCTRINE.md`](DOCTRINE.md) for the omakase framing. Repo-local plugins
82
+ are off by default because they execute with your privileges — only pass
83
+ the flag in workspaces you trust.
84
+
85
+ See [`docs/architecture/architecture.md`](docs/architecture/architecture.md) for how the loop and tool
86
+ parser work, including what was learned from reading gptme's and aider's
87
+ actual source before writing this.
88
+
89
+ ## Quick start
90
+
91
+ ```bash
92
+ git clone https://github.com/hummbl-io/nakedagent.git
93
+ cd nakedagent
94
+ ollama pull qwen2.5-coder:7b # or any model you like
95
+ python -m nakedagent # interactive
96
+ python -m nakedagent "explain what this repo does" # one-shot
97
+ ```
98
+
99
+ ### Larger models
100
+
101
+ Small local models get the loop running; bigger models make it good. Point
102
+ nakedagent at any OpenAI-compatible server. The key comes from an
103
+ environment variable (`--api-key-env`, default `OPENAI_API_KEY`), never a flag:
104
+
105
+ ```bash
106
+ # hosted API
107
+ OPENAI_API_KEY=... python -m nakedagent --api openai --host https://api.openai.com/v1 -m <model>
108
+
109
+ # self-hosted (vLLM, LM Studio) — no key needed
110
+ python -m nakedagent --api openai --host http://localhost:8000/v1 -m <model>
111
+ ```
112
+
113
+ ## Shell safety for automation
114
+
115
+ For one-shot runs and other non-interactive use-cases, the shell tool is denied
116
+ unless explicit shell allowances are provided:
117
+
118
+ ```bash
119
+ python -m nakedagent "run project checks" --allow-shell --shell-allowlist "git status" --shell-allowlist "ls"
120
+ ```
121
+
122
+ `--shell-allowlist` is prefix-based. If you pass `--allow-shell` with an empty
123
+ allowlist, all non-interactive shell commands are blocked.
124
+
125
+ No `pip install` step. That's not an oversight — `nakedagent/` only imports
126
+ the standard library, so running it in place works.
127
+
128
+ ## Provable execution: event log + replay
129
+
130
+ One-shot runs can go through the functional lane — the agent is a pure Mealy
131
+ machine (`functional.agent_reducer`: `(state, event) -> (state, actions)`),
132
+ and a thin driver (`driver.py`) performs the actual model/tool IO while
133
+ appending every event to a JSONL log:
134
+
135
+ ```bash
136
+ python -m nakedagent "add a .gitignore" --event-log run.jsonl
137
+ ```
138
+
139
+ Each logged event carries the Merkle state hash *after* that transition, so
140
+ the trace is a tamper-evident receipt. Replay reconstructs the run with zero
141
+ model calls and verifies the whole chain:
142
+
143
+ ```bash
144
+ python -m nakedagent.replay run.jsonl
145
+ # PASS run.jsonl: verified 5 events (1 tool results); final hash 6d1e0965…
146
+ ```
147
+
148
+ ## Status
149
+
150
+ MVP. Two wire formats (Ollama native + OpenAI-compatible), four foundation
151
+ tools, no streaming. The plugin
152
+ seam is the one extension surface — see [`DOCTRINE.md`](DOCTRINE.md).
153
+ Went through two independent peer reviews (headless GLM-5.2, and a
154
+ separately-running devin session, both 2026-09-09) before this first push —
155
+ between them they found five real bugs, all fixed with regression tests,
156
+ documented in [`docs/architecture/architecture.md`](docs/architecture/architecture.md) alongside what's
157
+ still genuinely not here and why.
158
+
159
+ ## Research & Citation
160
+
161
+ If you use nakedagent in academic research or want to study its architecture, please cite our research paper:
162
+
163
+ > Reuben Bowlby, **"NakedAgent: A Zero-Dependency Coding Agent Architecture via Standard-Library Primitives and Provable Replay"**, HUMMBL Research, Zenodo Preprint, 2026.
164
+
165
+ Preprint PDF and LaTeX sources are available under [`paper/`](paper/). For machine-readable citation metadata, see [`CITATION.cff`](CITATION.cff).
166
+
167
+ ## License
168
+
169
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,140 @@
1
+ # nakedagent
2
+
3
+ A coding agent with **zero runtime dependencies**. `pip install`-free by
4
+ construction: `python -m nakedagent` runs on a stock Python 3.10+ interpreter,
5
+ nothing else.
6
+
7
+ ```
8
+ $ python -m nakedagent
9
+ nakedagent -- workspace: /home/you/myproject -- model: qwen2.5-coder:7b
10
+ Ctrl-D to exit.
11
+
12
+ > add a .gitignore for a Python project
13
+ ```
14
+
15
+ ## Why
16
+
17
+ Every other agent in this space — gptme, aider, OpenHands, langchain-based
18
+ tools — pulls in 20-40+ packages: HTTP clients, CLI-formatting libraries,
19
+ provider SDKs, telemetry. That's not a criticism of them; those dependencies
20
+ buy real capability (multi-provider abstraction, rich terminal rendering,
21
+ robust malformed-output recovery). But it also means every install inherits
22
+ whatever CVEs are sitting in that dependency tree, and updating any one
23
+ package can break the agent.
24
+
25
+ nakedagent takes the other side of that trade on purpose: `urllib` + `json` +
26
+ `subprocess` + `re`, all from the standard library, are enough to drive a
27
+ local model through a working tool-use loop. No supply chain, because there
28
+ is no supply.
29
+
30
+ ## What you get for that
31
+
32
+ - **Tools**: `shell`, `read`, `write`, `patch` (search/replace edits, aider's
33
+ simplest edit format). That's the whole *foundation* tool surface.
34
+ `shell` is confirmation-first in interactive mode and requires `--allow-shell`
35
+ in non-interactive mode, with optional command filtering via
36
+ `--shell-allowlist`.
37
+ - **Model backend**: [Ollama](https://ollama.com) by default — local-first,
38
+ no API key required to try it. For models too large to run locally,
39
+ `--api openai` speaks the OpenAI-compatible format that hosted APIs,
40
+ vLLM/LM Studio and gateways share (see below).
41
+ - **Tool-call format**: the model writes a fenced code block whose language
42
+ tag is the tool name; nakedagent parses it out of the response text after
43
+ each turn. Works with any model that can write a code fence — no dependency
44
+ on a provider's native function-calling API.
45
+ - **Plugins**: drop a `.py` file in `.nakedagent/plugins/` (repo-local,
46
+ requires `--trust-workspace-plugins`) or `~/.nakedagent/plugins/`
47
+ (user-global, always loaded) that defines a `TOOLS` dict (add or
48
+ override tools) and/or a `DISABLE` list (remove tools entirely — e.g. a
49
+ read-only agent disables `shell` and `write`). Each tool carries a `.usage`
50
+ attribute that controls its prompt example, so a plugin replacing a tool
51
+ replaces its syntax too. No registration, no framework — see
52
+ [`DOCTRINE.md`](DOCTRINE.md) for the omakase framing. Repo-local plugins
53
+ are off by default because they execute with your privileges — only pass
54
+ the flag in workspaces you trust.
55
+
56
+ See [`docs/architecture/architecture.md`](docs/architecture/architecture.md) for how the loop and tool
57
+ parser work, including what was learned from reading gptme's and aider's
58
+ actual source before writing this.
59
+
60
+ ## Quick start
61
+
62
+ ```bash
63
+ git clone https://github.com/hummbl-io/nakedagent.git
64
+ cd nakedagent
65
+ ollama pull qwen2.5-coder:7b # or any model you like
66
+ python -m nakedagent # interactive
67
+ python -m nakedagent "explain what this repo does" # one-shot
68
+ ```
69
+
70
+ ### Larger models
71
+
72
+ Small local models get the loop running; bigger models make it good. Point
73
+ nakedagent at any OpenAI-compatible server. The key comes from an
74
+ environment variable (`--api-key-env`, default `OPENAI_API_KEY`), never a flag:
75
+
76
+ ```bash
77
+ # hosted API
78
+ OPENAI_API_KEY=... python -m nakedagent --api openai --host https://api.openai.com/v1 -m <model>
79
+
80
+ # self-hosted (vLLM, LM Studio) — no key needed
81
+ python -m nakedagent --api openai --host http://localhost:8000/v1 -m <model>
82
+ ```
83
+
84
+ ## Shell safety for automation
85
+
86
+ For one-shot runs and other non-interactive use-cases, the shell tool is denied
87
+ unless explicit shell allowances are provided:
88
+
89
+ ```bash
90
+ python -m nakedagent "run project checks" --allow-shell --shell-allowlist "git status" --shell-allowlist "ls"
91
+ ```
92
+
93
+ `--shell-allowlist` is prefix-based. If you pass `--allow-shell` with an empty
94
+ allowlist, all non-interactive shell commands are blocked.
95
+
96
+ No `pip install` step. That's not an oversight — `nakedagent/` only imports
97
+ the standard library, so running it in place works.
98
+
99
+ ## Provable execution: event log + replay
100
+
101
+ One-shot runs can go through the functional lane — the agent is a pure Mealy
102
+ machine (`functional.agent_reducer`: `(state, event) -> (state, actions)`),
103
+ and a thin driver (`driver.py`) performs the actual model/tool IO while
104
+ appending every event to a JSONL log:
105
+
106
+ ```bash
107
+ python -m nakedagent "add a .gitignore" --event-log run.jsonl
108
+ ```
109
+
110
+ Each logged event carries the Merkle state hash *after* that transition, so
111
+ the trace is a tamper-evident receipt. Replay reconstructs the run with zero
112
+ model calls and verifies the whole chain:
113
+
114
+ ```bash
115
+ python -m nakedagent.replay run.jsonl
116
+ # PASS run.jsonl: verified 5 events (1 tool results); final hash 6d1e0965…
117
+ ```
118
+
119
+ ## Status
120
+
121
+ MVP. Two wire formats (Ollama native + OpenAI-compatible), four foundation
122
+ tools, no streaming. The plugin
123
+ seam is the one extension surface — see [`DOCTRINE.md`](DOCTRINE.md).
124
+ Went through two independent peer reviews (headless GLM-5.2, and a
125
+ separately-running devin session, both 2026-09-09) before this first push —
126
+ between them they found five real bugs, all fixed with regression tests,
127
+ documented in [`docs/architecture/architecture.md`](docs/architecture/architecture.md) alongside what's
128
+ still genuinely not here and why.
129
+
130
+ ## Research & Citation
131
+
132
+ If you use nakedagent in academic research or want to study its architecture, please cite our research paper:
133
+
134
+ > Reuben Bowlby, **"NakedAgent: A Zero-Dependency Coding Agent Architecture via Standard-Library Primitives and Provable Replay"**, HUMMBL Research, Zenodo Preprint, 2026.
135
+
136
+ Preprint PDF and LaTeX sources are available under [`paper/`](paper/). For machine-readable citation metadata, see [`CITATION.cff`](CITATION.cff).
137
+
138
+ ## License
139
+
140
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """nakedagent: a coding agent with zero runtime dependencies."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,196 @@
1
+ """nakedagent -- a zero-dependency terminal coding agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from . import __version__
10
+ from .llm import APIS, DEFAULT_API_KEY_ENV, DEFAULT_HOST, LLMError
11
+ from .loop import run, run_interactive
12
+
13
+
14
+ def main(argv: list[str] | None = None) -> int:
15
+ p = argparse.ArgumentParser(prog="nakedagent", description=__doc__)
16
+ p.add_argument("prompt", nargs="?", help="run once with this prompt, then exit")
17
+ p.add_argument("-m", "--model", default="qwen2.5-coder:7b", help="model name or Ollama tag")
18
+ p.add_argument("-w", "--workspace", type=Path, default=Path.cwd())
19
+ p.add_argument(
20
+ "--host",
21
+ default=None,
22
+ help=(
23
+ f"model server URL (default: {DEFAULT_HOST} for ollama; required for "
24
+ "--api openai, including the version path, e.g. https://api.openai.com/v1)"
25
+ ),
26
+ )
27
+ p.add_argument(
28
+ "--api",
29
+ choices=APIS,
30
+ default="ollama",
31
+ help="wire format: ollama (local, default) or openai (any OpenAI-compatible server)",
32
+ )
33
+ p.add_argument(
34
+ "--api-key-env",
35
+ default=DEFAULT_API_KEY_ENV,
36
+ metavar="VAR",
37
+ help=(
38
+ "environment variable holding the API key for --api openai "
39
+ f"(default: {DEFAULT_API_KEY_ENV}); the key itself is never a flag"
40
+ ),
41
+ )
42
+ p.add_argument(
43
+ "--allow-shell",
44
+ action="store_true",
45
+ help="Allow shell tool execution in non-interactive contexts.",
46
+ )
47
+ p.add_argument(
48
+ "--shell-allowlist",
49
+ action="append",
50
+ default=[],
51
+ metavar="PATTERN",
52
+ help=(
53
+ "Allowed shell command prefixes when --allow-shell is set. "
54
+ "Repeatable. Example: --shell-allowlist 'git status' "
55
+ "--shell-allowlist 'ls'"
56
+ ),
57
+ )
58
+ p.add_argument(
59
+ "--shell-timeout",
60
+ type=int,
61
+ default=120,
62
+ help="Timeout in seconds for shell tool execution.",
63
+ )
64
+ p.add_argument(
65
+ "--trust-plugins",
66
+ action="store_true",
67
+ help=(
68
+ "Load <workspace>/.nakedagent/plugins/*.py at startup. "
69
+ "Disabled by default to prevent arbitrary code execution on "
70
+ "untrusted repositories. Alias of --trust-workspace-plugins."
71
+ ),
72
+ )
73
+ p.add_argument(
74
+ "--trust-workspace-plugins",
75
+ action="store_true",
76
+ help=(
77
+ "Load <workspace>/.nakedagent/plugins/*.py at startup. Off by "
78
+ "default: workspace plugins are repo-controlled code running with "
79
+ "your privileges. Only set this in workspaces you trust. "
80
+ "User-global ~/.nakedagent/plugins/ always loads."
81
+ ),
82
+ )
83
+ p.add_argument(
84
+ "--event-log",
85
+ type=Path,
86
+ metavar="PATH",
87
+ help=(
88
+ "Run through the functional driver and append every agent event "
89
+ "to a JSONL log at PATH. One-shot mode only. Verify with "
90
+ "`python -m nakedagent.replay PATH`."
91
+ ),
92
+ )
93
+ p.add_argument(
94
+ "--resume",
95
+ type=Path,
96
+ metavar="PATH",
97
+ help=(
98
+ "Resume a suspended event log at PATH: replay-verify the recorded "
99
+ "prefix, then feed PROMPT as the human verdict that lifts the "
100
+ "suspension. PATH is reused as the event log — the suspended "
101
+ "trailer is replaced, the Merkle chain continues."
102
+ ),
103
+ )
104
+ p.add_argument("--version", action="version", version=__version__)
105
+ args = p.parse_args(argv)
106
+
107
+ workspace = args.workspace.resolve()
108
+ shell_allowlist = tuple(
109
+ pattern.strip() for pattern in args.shell_allowlist if pattern.strip()
110
+ )
111
+ if args.shell_timeout <= 0:
112
+ print("error: --shell-timeout must be greater than 0", file=sys.stderr)
113
+ return 1
114
+ if args.event_log and not args.prompt:
115
+ print("error: --event-log requires a one-shot prompt", file=sys.stderr)
116
+ return 1
117
+ if args.resume and not args.prompt:
118
+ print("error: --resume requires PROMPT as the verdict input", file=sys.stderr)
119
+ return 1
120
+ if args.resume and args.event_log and args.resume != args.event_log:
121
+ print("error: --resume reuses the same path as --event-log; pass only --resume", file=sys.stderr)
122
+ return 1
123
+ host = args.host
124
+ if host is None:
125
+ if args.api != "ollama":
126
+ # No default: silently sending a prompt (and a key) to a server
127
+ # the user didn't name is worse than asking.
128
+ print("error: --api openai needs --host (e.g. https://api.openai.com/v1)", file=sys.stderr)
129
+ return 1
130
+ host = DEFAULT_HOST
131
+ llm_options = {}
132
+ if args.api != "ollama":
133
+ llm_options = {"api": args.api, "api_key_env": args.api_key_env}
134
+ # Plugin trust: two flag spellings, one opt-in. `trust_plugins` always
135
+ # rides along; `trust_workspace_plugins` is forwarded only when a trust
136
+ # flag was actually given -- downstream defaults govern otherwise.
137
+ plugin_trust: dict = {"trust_plugins": args.trust_plugins}
138
+ if args.trust_plugins or args.trust_workspace_plugins:
139
+ plugin_trust["trust_workspace_plugins"] = args.trust_workspace_plugins
140
+ try:
141
+ if args.prompt:
142
+ if args.event_log or args.resume:
143
+ # Deferred import keeps `nakedagent.driver.run_functional`
144
+ # patchable at call time (the CLI tests mock it there).
145
+ from .driver import run_functional
146
+
147
+ run_functional(
148
+ args.prompt,
149
+ args.model,
150
+ workspace,
151
+ host,
152
+ event_log_path=args.resume or args.event_log,
153
+ allow_shell=args.allow_shell,
154
+ shell_allowlist=shell_allowlist,
155
+ shell_timeout=args.shell_timeout,
156
+ llm_options=llm_options,
157
+ resume_from=args.resume,
158
+ **plugin_trust,
159
+ )
160
+ else:
161
+ run(
162
+ args.prompt,
163
+ args.model,
164
+ workspace,
165
+ host,
166
+ allow_shell=args.allow_shell,
167
+ shell_allowlist=shell_allowlist,
168
+ shell_timeout=args.shell_timeout,
169
+ llm_options=llm_options,
170
+ **plugin_trust,
171
+ )
172
+ else:
173
+ run_interactive(
174
+ args.model,
175
+ workspace,
176
+ host,
177
+ allow_shell=args.allow_shell,
178
+ shell_allowlist=shell_allowlist,
179
+ shell_timeout=args.shell_timeout,
180
+ llm_options=llm_options,
181
+ **plugin_trust,
182
+ )
183
+ except LLMError as e:
184
+ print(f"error: {e}", file=sys.stderr)
185
+ return 1
186
+ except (RuntimeError, ValueError) as e:
187
+ # Resume refusals and eventlog integrity failures land here.
188
+ print(f"error: {e}", file=sys.stderr)
189
+ return 1
190
+ except KeyboardInterrupt:
191
+ return 130
192
+ return 0
193
+
194
+
195
+ if __name__ == "__main__":
196
+ sys.exit(main())