mnml-agent 0.2.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.
- minimal/__init__.py +16 -0
- minimal/__main__.py +5 -0
- minimal/api.py +26 -0
- minimal/cli.py +213 -0
- minimal/config.py +448 -0
- minimal/core/__init__.py +1 -0
- minimal/core/agent.py +211 -0
- minimal/core/app.py +479 -0
- minimal/core/context.py +177 -0
- minimal/core/permissions.py +98 -0
- minimal/core/prompt.md +13 -0
- minimal/core/session.py +106 -0
- minimal/extensions/__init__.py +1 -0
- minimal/extensions/audit.py +251 -0
- minimal/extensions/facade.py +271 -0
- minimal/extensions/loader.py +409 -0
- minimal/extensions/logo.py +124 -0
- minimal/extensions/marketplace.py +729 -0
- minimal/extensions/metadata.py +101 -0
- minimal/extensions/registry.py +487 -0
- minimal/providers/__init__.py +29 -0
- minimal/providers/anthropic.py +89 -0
- minimal/providers/base.py +135 -0
- minimal/providers/claude.py +38 -0
- minimal/providers/claude_auth.py +236 -0
- minimal/providers/gemini.py +256 -0
- minimal/providers/openai.py +181 -0
- minimal/tools/__init__.py +1 -0
- minimal/tools/builtin.py +23 -0
- minimal/tools/common.py +40 -0
- minimal/tools/files.py +165 -0
- minimal/tools/reload.py +27 -0
- minimal/tools/search.py +226 -0
- minimal/tools/shell.py +131 -0
- minimal/ui/__init__.py +1 -0
- minimal/ui/commands.py +440 -0
- minimal/ui/setup.py +125 -0
- minimal/ui/tui/__init__.py +1 -0
- minimal/ui/tui/app.py +556 -0
- minimal/ui/tui/frontend.py +119 -0
- minimal/ui/tui/screens.py +203 -0
- minimal/ui/tui/theme.py +174 -0
- minimal/ui/tui/widgets.py +353 -0
- mnml_agent-0.2.0.dist-info/METADATA +367 -0
- mnml_agent-0.2.0.dist-info/RECORD +48 -0
- mnml_agent-0.2.0.dist-info/WHEEL +4 -0
- mnml_agent-0.2.0.dist-info/entry_points.txt +2 -0
- mnml_agent-0.2.0.dist-info/licenses/LICENSE +21 -0
minimal/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""minimal: a small, extensible coding-agent harness.
|
|
2
|
+
|
|
3
|
+
Packages, from the bottom up; a package imports only from the ones above it in this list:
|
|
4
|
+
|
|
5
|
+
- :mod:`minimal.config` settings and keys saved in ``~/.minimal`` (used by every layer)
|
|
6
|
+
- :mod:`minimal.providers` model integrations behind one interface
|
|
7
|
+
- :mod:`minimal.extensions` the Registry, extension loading, the /trust scan, the marketplace
|
|
8
|
+
- :mod:`minimal.tools` built-in tools: read, write, edit, bash, grep, glob, reload_extensions
|
|
9
|
+
- :mod:`minimal.core` the engine: agent loop, App (one user turn), sessions, base prompt
|
|
10
|
+
- :mod:`minimal.ui` slash commands, the setup wizard, the Textual UI
|
|
11
|
+
- :mod:`minimal.cli` option parsing, the ``minimal`` entry point
|
|
12
|
+
|
|
13
|
+
Extensions import only from :mod:`minimal.api`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "0.2.0"
|
minimal/__main__.py
ADDED
minimal/api.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""What extensions may import from minimal: these names stay put when internal modules move.
|
|
2
|
+
|
|
3
|
+
``register(minimal)`` receives an :class:`Extension`; tools, commands, events and
|
|
4
|
+
widgets receive a :class:`Context` as ``ctx``. Import anything else an extension
|
|
5
|
+
needs from here::
|
|
6
|
+
|
|
7
|
+
from minimal.api import USER_DENIED, TurnInterrupted, theme
|
|
8
|
+
|
|
9
|
+
Internal code never imports this module.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from minimal import __version__
|
|
13
|
+
from minimal.core.agent import USER_DENIED
|
|
14
|
+
from minimal.core.context import UI, Context
|
|
15
|
+
from minimal.extensions.facade import Extension, ToolCallEvent, ToolResultEvent, TurnEndEvent
|
|
16
|
+
from minimal.extensions.registry import EVENTS, STATUS_SIDES, THEME_COLORS, WIDGET_AREAS, LogoSpec, Tool, TurnInterrupted
|
|
17
|
+
from minimal.providers.base import Provider, ProviderSpec, Reply, Usage, result_text
|
|
18
|
+
from minimal.tools.search import glob_regex
|
|
19
|
+
from minimal.ui.tui import theme
|
|
20
|
+
from minimal.ui.tui.widgets import logo_content
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"EVENTS", "STATUS_SIDES", "THEME_COLORS", "UI", "USER_DENIED", "WIDGET_AREAS", "Context", "Extension", "LogoSpec",
|
|
24
|
+
"Provider", "ProviderSpec", "Reply", "Tool", "ToolCallEvent", "ToolResultEvent", "TurnEndEvent", "TurnInterrupted",
|
|
25
|
+
"Usage", "__version__", "glob_regex", "logo_content", "result_text", "theme",
|
|
26
|
+
] # fmt: skip
|
minimal/cli.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Command-line entry point: parse options, build the :class:`~minimal.core.app.App`
|
|
2
|
+
and open the interactive terminal UI. A turn can only be started from inside it:
|
|
3
|
+
there is no one-shot mode that runs the agent from the shell command line.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from minimal import config
|
|
14
|
+
from minimal.core.app import App
|
|
15
|
+
from minimal.core.session import last_session, new_id, open_session
|
|
16
|
+
from minimal.extensions import marketplace
|
|
17
|
+
from minimal.extensions.registry import Registry
|
|
18
|
+
from minimal.providers import claude_auth, register_builtin_providers
|
|
19
|
+
from minimal.tools.builtin import register_builtin_tools
|
|
20
|
+
from minimal.ui import setup
|
|
21
|
+
|
|
22
|
+
HELP = """minimal — minimal, extensible coding-agent harness
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
minimal [options] interactive terminal UI (runs setup first if no provider is chosen yet)
|
|
26
|
+
minimal --setup choose the default provider, sign in or enter its key, choose the model
|
|
27
|
+
minimal --login sign in with a Claude Pro/Max subscription (browser)
|
|
28
|
+
minimal --logout forget the Claude login
|
|
29
|
+
minimal ext check <path>... validate extension files, package folders or a marketplace checkout
|
|
30
|
+
(exit status 1 if any fails; runs without a terminal or a provider)
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
-c, --continue resume the last session
|
|
34
|
+
-s, --session <id> open (or create) a specific session
|
|
35
|
+
--provider <name> model integration: anthropic, claude, gemini, openai, or one added by an extension
|
|
36
|
+
(default $MINIMAL_PROVIDER, else the settings' provider)
|
|
37
|
+
-m, --model <name> model (default $MINIMAL_MODEL, else the settings' model, else the provider's default)
|
|
38
|
+
-e, --ext <path> load an extension file or folder (repeatable)
|
|
39
|
+
-y, --yes do not ask for confirmation for write/edit/bash
|
|
40
|
+
--trust-project apply this project's extensions and trust-gated settings without /trust
|
|
41
|
+
-h, --help this help
|
|
42
|
+
|
|
43
|
+
Settings:
|
|
44
|
+
~/.minimal/settings.json global: provider, model, theme, prompt, permissions, extensions, marketplaces
|
|
45
|
+
(setup, /theme and /ext save there)
|
|
46
|
+
.minimal/settings.json this project's settings, shared with it
|
|
47
|
+
.minimal/settings.local.json this project's personal settings; both beat the global file
|
|
48
|
+
(/settings shows the files and where each value comes from)
|
|
49
|
+
|
|
50
|
+
Environment:
|
|
51
|
+
ANTHROPIC_API_KEY for --provider anthropic
|
|
52
|
+
GEMINI_API_KEY/GOOGLE_API_KEY for --provider gemini
|
|
53
|
+
OPENAI_API_KEY for --provider openai (OPENAI_BASE_URL points it at OpenRouter, Ollama, LM Studio)
|
|
54
|
+
(a key entered in setup is saved in ~/.minimal/keys.json and wins over these)
|
|
55
|
+
--provider claude uses the login from minimal --login
|
|
56
|
+
MINIMAL_PROVIDER, MINIMAL_MODEL
|
|
57
|
+
MINIMAL_MARKETPLACE git URL or path of the extension marketplace used by the
|
|
58
|
+
model's tools and listed first by /ext
|
|
59
|
+
(default https://github.com/RafaSG13/minimal-marketplace.git)"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main() -> None:
|
|
63
|
+
"""Entry point of the ``minimal`` command."""
|
|
64
|
+
if sys.argv[1:2] == ["ext"]:
|
|
65
|
+
ext_cli(sys.argv[2:])
|
|
66
|
+
opts = parse_args(sys.argv[1:])
|
|
67
|
+
if opts is None:
|
|
68
|
+
return
|
|
69
|
+
try:
|
|
70
|
+
moved = config.migrate_legacy()
|
|
71
|
+
except OSError as e:
|
|
72
|
+
moved = f"Could not move the old settings files into ~/.minimal/settings.json: {e}"
|
|
73
|
+
if moved:
|
|
74
|
+
print(moved, file=sys.stderr)
|
|
75
|
+
if opts.login or opts.logout:
|
|
76
|
+
try:
|
|
77
|
+
if opts.logout:
|
|
78
|
+
claude_auth.logout()
|
|
79
|
+
if opts.login:
|
|
80
|
+
claude_auth.login()
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
print("\nLogin cancelled.", file=sys.stderr)
|
|
83
|
+
sys.exit(130)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(e, file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if not setup.can_run():
|
|
90
|
+
print(
|
|
91
|
+
"minimal needs a terminal: start it there and type your prompt in the interface.",
|
|
92
|
+
file=sys.stderr,
|
|
93
|
+
)
|
|
94
|
+
sys.exit(2)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
session = open_session(
|
|
98
|
+
opts.session or (last_session() if opts.cont else None) or new_id()
|
|
99
|
+
)
|
|
100
|
+
except ValueError as e: # a session id that is not a plain name
|
|
101
|
+
print(e, file=sys.stderr)
|
|
102
|
+
sys.exit(2)
|
|
103
|
+
app = App(
|
|
104
|
+
registry=Registry(),
|
|
105
|
+
session=session,
|
|
106
|
+
cwd=os.getcwd(),
|
|
107
|
+
auto_approve=opts.yes,
|
|
108
|
+
trust_project=opts.trust_project,
|
|
109
|
+
extra_extensions=opts.ext,
|
|
110
|
+
)
|
|
111
|
+
app.reload_extensions()
|
|
112
|
+
try:
|
|
113
|
+
config.apply_keys()
|
|
114
|
+
start_provider(app, opts)
|
|
115
|
+
except KeyboardInterrupt:
|
|
116
|
+
print("\nSetup cancelled.", file=sys.stderr)
|
|
117
|
+
sys.exit(130)
|
|
118
|
+
except (
|
|
119
|
+
Exception
|
|
120
|
+
) as e: # unknown provider, missing credentials, failed login, unreadable config
|
|
121
|
+
print(e, file=sys.stderr)
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
from minimal.ui.tui.app import run_tui
|
|
125
|
+
|
|
126
|
+
run_tui(app)
|
|
127
|
+
|
|
128
|
+
def ext_cli(args: list[str]) -> None:
|
|
129
|
+
"""``minimal ext check <path>...``: print one line per extension checked and exit 0, or 1 if any failed."""
|
|
130
|
+
if len(args) < 2 or args[0] != "check":
|
|
131
|
+
print("usage: minimal ext check <path>...", file=sys.stderr)
|
|
132
|
+
sys.exit(2)
|
|
133
|
+
|
|
134
|
+
def make_registry() -> Registry:
|
|
135
|
+
# The surface App.reload_extensions gives extensions: they may wrap any of it at register time.
|
|
136
|
+
registry = Registry()
|
|
137
|
+
register_builtin_tools(registry)
|
|
138
|
+
register_builtin_providers(registry)
|
|
139
|
+
marketplace.register_marketplace_tools(registry)
|
|
140
|
+
return registry
|
|
141
|
+
|
|
142
|
+
lines, ok = marketplace.check([Path(p) for p in args[1:]], make_registry)
|
|
143
|
+
print("\n".join(lines))
|
|
144
|
+
sys.exit(0 if ok else 1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def start_provider(app: App, opts: argparse.Namespace) -> None:
|
|
149
|
+
"""Start the provider: ``--provider``, else ``$MINIMAL_PROVIDER``, else the settings' provider.
|
|
150
|
+
|
|
151
|
+
Setup runs first with ``--setup``, when no settings file names a provider, or when
|
|
152
|
+
that provider cannot start (for example after ``minimal --logout``).
|
|
153
|
+
Without a terminal, those cases are errors instead.
|
|
154
|
+
"""
|
|
155
|
+
model = opts.model or os.environ.get("MINIMAL_MODEL")
|
|
156
|
+
explicit = opts.provider or os.environ.get("MINIMAL_PROVIDER")
|
|
157
|
+
if explicit and not opts.setup:
|
|
158
|
+
same = app.settings.provider == explicit
|
|
159
|
+
app.use_provider(explicit, model or (app.settings.model if same else None))
|
|
160
|
+
return
|
|
161
|
+
saved = None if opts.setup else app.settings.provider
|
|
162
|
+
if saved is not None:
|
|
163
|
+
try:
|
|
164
|
+
app.use_provider(saved, model or app.settings.model)
|
|
165
|
+
return
|
|
166
|
+
except ValueError as e:
|
|
167
|
+
if not setup.can_run():
|
|
168
|
+
raise
|
|
169
|
+
print(f"{e}\n", file=sys.stderr)
|
|
170
|
+
elif not setup.can_run():
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"No default provider yet: run `minimal --setup` in a terminal, or pass --provider."
|
|
173
|
+
)
|
|
174
|
+
name, saved_model = setup.run_setup(app.registry)
|
|
175
|
+
app.use_provider(name, model or saved_model)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def parse_args(argv: list[str]) -> argparse.Namespace | None:
|
|
179
|
+
"""Parse command-line options.
|
|
180
|
+
|
|
181
|
+
Returns ``None`` after printing the help for ``-h``. Exits with status 2
|
|
182
|
+
after printing the help for unknown or malformed options.
|
|
183
|
+
"""
|
|
184
|
+
parser = argparse.ArgumentParser(prog="minimal", add_help=False)
|
|
185
|
+
parser.add_argument("-c", "--continue", dest="cont", action="store_true")
|
|
186
|
+
parser.add_argument("-s", "--session")
|
|
187
|
+
parser.add_argument("--provider")
|
|
188
|
+
parser.add_argument("-m", "--model")
|
|
189
|
+
parser.add_argument("-e", "--ext", action="append", default=[])
|
|
190
|
+
parser.add_argument("-y", "--yes", action="store_true")
|
|
191
|
+
parser.add_argument("--trust-project", action="store_true")
|
|
192
|
+
parser.add_argument("--setup", action="store_true")
|
|
193
|
+
parser.add_argument("--login", action="store_true")
|
|
194
|
+
parser.add_argument("--logout", action="store_true")
|
|
195
|
+
parser.add_argument("-h", "--help", action="store_true")
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
opts, unknown = parser.parse_known_args(argv)
|
|
199
|
+
except SystemExit: # malformed option, e.g. -s without a value
|
|
200
|
+
print(HELP)
|
|
201
|
+
sys.exit(2)
|
|
202
|
+
if unknown:
|
|
203
|
+
print(f"Unknown option: {unknown[0]}\n", file=sys.stderr)
|
|
204
|
+
print(HELP)
|
|
205
|
+
sys.exit(2)
|
|
206
|
+
if opts.help:
|
|
207
|
+
print(HELP)
|
|
208
|
+
return None
|
|
209
|
+
return opts
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
main()
|