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.
Files changed (48) hide show
  1. minimal/__init__.py +16 -0
  2. minimal/__main__.py +5 -0
  3. minimal/api.py +26 -0
  4. minimal/cli.py +213 -0
  5. minimal/config.py +448 -0
  6. minimal/core/__init__.py +1 -0
  7. minimal/core/agent.py +211 -0
  8. minimal/core/app.py +479 -0
  9. minimal/core/context.py +177 -0
  10. minimal/core/permissions.py +98 -0
  11. minimal/core/prompt.md +13 -0
  12. minimal/core/session.py +106 -0
  13. minimal/extensions/__init__.py +1 -0
  14. minimal/extensions/audit.py +251 -0
  15. minimal/extensions/facade.py +271 -0
  16. minimal/extensions/loader.py +409 -0
  17. minimal/extensions/logo.py +124 -0
  18. minimal/extensions/marketplace.py +729 -0
  19. minimal/extensions/metadata.py +101 -0
  20. minimal/extensions/registry.py +487 -0
  21. minimal/providers/__init__.py +29 -0
  22. minimal/providers/anthropic.py +89 -0
  23. minimal/providers/base.py +135 -0
  24. minimal/providers/claude.py +38 -0
  25. minimal/providers/claude_auth.py +236 -0
  26. minimal/providers/gemini.py +256 -0
  27. minimal/providers/openai.py +181 -0
  28. minimal/tools/__init__.py +1 -0
  29. minimal/tools/builtin.py +23 -0
  30. minimal/tools/common.py +40 -0
  31. minimal/tools/files.py +165 -0
  32. minimal/tools/reload.py +27 -0
  33. minimal/tools/search.py +226 -0
  34. minimal/tools/shell.py +131 -0
  35. minimal/ui/__init__.py +1 -0
  36. minimal/ui/commands.py +440 -0
  37. minimal/ui/setup.py +125 -0
  38. minimal/ui/tui/__init__.py +1 -0
  39. minimal/ui/tui/app.py +556 -0
  40. minimal/ui/tui/frontend.py +119 -0
  41. minimal/ui/tui/screens.py +203 -0
  42. minimal/ui/tui/theme.py +174 -0
  43. minimal/ui/tui/widgets.py +353 -0
  44. mnml_agent-0.2.0.dist-info/METADATA +367 -0
  45. mnml_agent-0.2.0.dist-info/RECORD +48 -0
  46. mnml_agent-0.2.0.dist-info/WHEEL +4 -0
  47. mnml_agent-0.2.0.dist-info/entry_points.txt +2 -0
  48. 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
@@ -0,0 +1,5 @@
1
+ """Allow ``python -m minimal``."""
2
+
3
+ from minimal.cli import main
4
+
5
+ main()
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()