bots-garden/mini-mepublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Builtin: fetch content #17

Merged@k33g wants to merge feature/fetch-build-in into main
Merged as 9c6d6b34280c.
45 files changed
added .tickets/issues/0049-recreate-the-readme.yaml +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 49
3+title: Recreate the README
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-28T18:44:44.821Z
9+updatedAt: 2026-09-28T18:44:44.821Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 49
3+title: Recreate the README
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-28T18:44:44.821Z
9+updatedAt: 2026-09-28T18:44:44.821Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
added .tickets/issues/0050-create-a-web-site.yaml +13 -0
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 50
3+title: Create a web site
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-28T18:44:59.825Z
9+updatedAt: 2026-09-28T18:44:59.825Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
new file mode 100644
@@ -0,0 +1,13 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 50
3+title: Create a web site
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-28T18:44:59.825Z
9+updatedAt: 2026-09-28T18:44:59.825Z
10+labels: []
11+body: ""
12+tasks: []
13+comments: []
added .tickets/issues/0051-query-other-models-from-the-prompt.yaml +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 51
3+title: Query other models from the prompt
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-29T04:49:19.839Z
9+updatedAt: 2026-09-29T04:51:59.493Z
10+labels:
11+ - priority::high
12+ - enhancement
13+ - builtin tool
14+body: |
15+ - Add a list of model (and provider) to the agent config file (with an alias / nickname)
16+ - Add a builtin tool "query_model"
17+ - Be able to force a json output
18+tasks: []
19+comments: []
new file mode 100644
@@ -0,0 +1,19 @@
1+# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.
2+id: 51
3+title: Query other models from the prompt
4+state: open
5+author:
6+ name: k33g
7+ email: ph.charriere@gmail.com
8+createdAt: 2026-09-29T04:49:19.839Z
9+updatedAt: 2026-09-29T04:51:59.493Z
10+labels:
11+ - priority::high
12+ - enhancement
13+ - builtin tool
14+body: |
15+ - Add a list of model (and provider) to the agent config file (with an alias / nickname)
16+ - Add a builtin tool "query_model"
17+ - Be able to force a json output
18+tasks: []
19+comments: []
modified README.md +1 -0
@@ -100,3 +100,4 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
100100 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
101101 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
102102 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
103+- `fetchContentTool: true` in `agent.dmr.yaml`: the built-in `fetch_content` tool, which fetches a web page as Markdown (honouring its `robots.txt`) and can save it to a file without showing it — see [Fetch a web page as Markdown](docs/en/how-to/fetch-a-web-page.md).
@@ -100,3 +100,4 @@ Anything that speaks ACP can host `mm -acp` the same way: **JetBrains IDEs** (In
100 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.100 - [Documentation](docs/README.md) — tutorials, how-to guides, reference and explanations, in English and French.
101 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.101 - The `skills/` directory: Markdown procedures the agent can load with its `read_skill` tool.
102 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).102 - `agent.lsp.yaml`: the same agent with a language server behind an `lsp` tool (definition, references, hover, symbols, diagnostics, rename) — see [Navigate and refactor code with a language server](docs/en/how-to/navigate-code-with-lsp.md).
103+- `fetchContentTool: true` in `agent.dmr.yaml`: the built-in `fetch_content` tool, which fetches a web page as Markdown (honouring its `robots.txt`) and can save it to a file without showing it — see [Fetch a web page as Markdown](docs/en/how-to/fetch-a-web-page.md).
modified agent.dmr.yaml +5 -0
@@ -31,6 +31,11 @@ editTools: true
3131 # Set to false to disable plan creation and tracking via the `plan` tool.
3232 planTool: true
3333
34+# The built-in fetch_content tool: fetch a web page and get it as Markdown
35+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
36+# Honours the site's robots.txt. Off by default — it reaches the network.
37+fetchContentTool: true
38+
3439 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
3540 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
3641 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
@@ -31,6 +31,11 @@ editTools: true
31 # Set to false to disable plan creation and tracking via the `plan` tool.31 # Set to false to disable plan creation and tracking via the `plan` tool.
32 planTool: true32 planTool: true
33 33
34+# The built-in fetch_content tool: fetch a web page and get it as Markdown
35+# ("récupère le contenu de https://…", optionally "et sauve le dans page.md").
36+# Honours the site's robots.txt. Off by default — it reaches the network.
37+fetchContentTool: true
38+
34 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).39 # Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename).
35 # The file extension picks the server. A server missing from the PATH is skipped with a warning.40 # The file extension picks the server. A server missing from the PATH is skipped with a warning.
36 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):41 # No server listed = no `lsp` tool. Examples (uncomment what you have installed):
modified demo/agent.dmr.2.yaml +3 -0
@@ -29,6 +29,9 @@ bashTool: true
2929 # The built-in file tools: read_file, write_file, edit_file.
3030 editTools: true
3131
32+# fetch content on the web
33+fetchContentTool: true
34+
3235 # The built-in plan tool for managing multi-step plans.
3336 # Set to false to disable plan creation and tracking via the `plan` tool.
3437 planTool: true
@@ -29,6 +29,9 @@ bashTool: true
29 # The built-in file tools: read_file, write_file, edit_file. 29 # The built-in file tools: read_file, write_file, edit_file.
30 editTools: true30 editTools: true
31 31
32+# fetch content on the web
33+fetchContentTool: true
34+
32 # The built-in plan tool for managing multi-step plans.35 # The built-in plan tool for managing multi-step plans.
33 # Set to false to disable plan creation and tracking via the `plan` tool.36 # Set to false to disable plan creation and tracking via the `plan` tool.
34 planTool: true37 planTool: true
modified docs/diagrams/packages.drawio +2 -2
@@ -1,3 +1,3 @@
1-<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>
1+<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>
22 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry"/></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry"/></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry"/></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file
@@ -1,3 +1,3 @@
1-<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>1+<mxfile host="app.diagrams.net" modified="2026-09-15T00:00:00.000Z" agent="mm docs" version="24.0.0" type="device"><diagram id="packages" name="packages"><mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1400" pageHeight="900"><root><mxCell id="0"/><mxCell id="1" parent="0"/><mxCell id="main" value="&lt;b&gt;main (mm)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;wiring: config, engine, tools, then one front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1"><mxGeometry x="420" y="40" width="200" height="60" as="geometry"/></mxCell><mxCell id="agent" value="&lt;b&gt;internal/agent&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal REPL: /quit /abort /compact /new&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="160" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="acp" value="&lt;b&gt;internal/acp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;editor front end: Agent Client Protocol on stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="700" y="180" width="200" height="60" as="geometry"/></mxCell><mxCell id="session" value="&lt;b&gt;internal/session&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;what a conversation starts from; the /new reset&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="compact" value="&lt;b&gt;internal/compact&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;history compression into a model-written summary&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="40" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="engine" value="&lt;b&gt;internal/engine&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LLM connection + generation, one Provider per server&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="700" y="320" width="200" height="60" as="geometry"/></mxCell><mxCell id="tools" value="&lt;b&gt;internal/tools&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;built-in tools: bash, read_skill, file tools, lsp, fetch_content&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="460" width="200" height="60" as="geometry"/></mxCell><mxCell id="detector" value="&lt;b&gt;internal/detector&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;repeated-action (loop) detection&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="40" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="config" value="&lt;b&gt;internal/config&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;settings with YAML overrides (agent.yaml)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="260" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="spinner" value="&lt;b&gt;internal/spinner&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal spinner and styling&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="480" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="ui" value="&lt;b&gt;internal/ui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;human output destination + shared event sink&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="700" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="fileedit" value="&lt;b&gt;internal/fileedit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;exact-replacement file editing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="920" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="skills" value="&lt;b&gt;internal/skills&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;markdown skills catalogue (./skills)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="600" width="200" height="60" as="geometry"/></mxCell><mxCell id="genkit" value="&lt;b&gt;github.com/firebase/genkit&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;ai.Message, tools, model calls&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="160" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="acpsdk" value="&lt;b&gt;github.com/coder/acp-go-sdk&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;JSON-RPC transport for ACP&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="700" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="openai" value="&lt;b&gt;github.com/openai/openai-go&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;OpenAI-compatible HTTP client&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="yaml" value="&lt;b&gt;gopkg.in/yaml.v3&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;YAML parsing&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="400" y="760" width="200" height="60" as="geometry"/></mxCell><mxCell id="e0" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="agent"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="version" value="&lt;b&gt;internal/version&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;which mm this is: build stamp, else the commit&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1380" y="600" width="200" height="60" as="geometry"/></mxCell>
2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry"/></mxCell>2 <mxCell id="e61" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="version"><mxGeometry relative="1" as="geometry"/></mxCell>
3- <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
3+ <mxCell id="e1" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="acp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e2" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e3" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e4" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e5" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e6" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e7" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tools"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e8" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e9" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e10" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e11" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e12" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="acpsdk"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e13" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="acp" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e14" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e15" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e16" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e17" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e18" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e19" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e20" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="agent" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e21" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="compact" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e22" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="compact" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e23" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="config" target="yaml"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e24" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e25" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e26" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="engine" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e27" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e28" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="engine" target="openai"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e29" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e30" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e31" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="fileedit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e32" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="skills"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e33" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="spinner"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e34" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e35" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="tools" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e36" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="session" target="genkit"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="mention" value="&lt;b&gt;internal/mention&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the @path notation of a question&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="400" width="200" height="60" as="geometry"/></mxCell><mxCell id="e37" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e38" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="store" value="&lt;b&gt;internal/store&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;per-project session files (.mm/sessions/&lt;id&gt;.json)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="480" width="200" height="60" as="geometry"/></mxCell><mxCell id="e39" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e40" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="tui" value="&lt;b&gt;internal/tui&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;window front end: one screen on tcell (mm -tui)&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1"><mxGeometry x="430" y="160" width="200" height="60" as="geometry"/></mxCell><mxCell id="command" value="&lt;b&gt;internal/command&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;the one slash-command table, per front end&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1"><mxGeometry x="430" y="560" width="200" height="60" as="geometry"/></mxCell><mxCell id="tcell" value="&lt;b&gt;github.com/gdamore/tcell/v2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;terminal screen, keys, mouse&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="920" y="840" width="200" height="60" as="geometry"/></mxCell><mxCell id="e41" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="tui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e42" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="ui"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e43" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="engine"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e44" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="compact"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e45" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="store"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e46" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="mention"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e47" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e48" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="detector"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e49" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e50" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e51" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="tcell"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e52" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e53" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="acp" target="command"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e54" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="command" target="session"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="clipboard" value="&lt;b&gt;internal/clipboard&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;system clipboard: native tool, then OSC 52&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1140" y="680" width="200" height="60" as="geometry"/></mxCell><mxCell id="e55" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="agent" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e56" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tui" target="clipboard"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="lsp" value="&lt;b&gt;internal/lsp&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;language-server client: definition, references, hover, symbols, diagnostics, rename&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1160" y="600" width="220" height="60" as="geometry"/></mxCell><mxCell id="lspdev" value="&lt;b&gt;go.lsp.dev/protocol + jsonrpc2&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;LSP types and JSON-RPC 2.0 over stdio&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="1160" y="700" width="220" height="60" as="geometry"/></mxCell><mxCell id="e57" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e58" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="lsp"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e59" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="lsp" target="config"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e60" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;dashed=1;" edge="1" parent="1" source="lsp" target="lspdev"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="legend" value="&lt;b&gt;mm — package dependencies&lt;/b&gt;&lt;br/&gt;Arrow: &quot;imports&quot;. Yellow: entry point. Blue: front ends. Green: shared logic. Grey: leaf packages. Dashed purple: third-party modules.&lt;br/&gt;Regenerate the edge list from: go list -f '{{.ImportPath}}: {{join .Imports &quot; &quot;}}' ./..." style="text;html=1;whiteSpace=wrap;align=left;verticalAlign=top;fillColor=#ffffff;strokeColor=#999999;" vertex="1" parent="1"><mxGeometry x="900" y="40" width="440" height="90" as="geometry"/></mxCell><mxCell id="webmd" value="&lt;b&gt;internal/webmd&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;web page → Markdown: fetch (robots.txt, charset → UTF-8), clean, convert&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1"><mxGeometry x="1400" y="460" width="220" height="60" as="geometry"/></mxCell><mxCell id="xnet" value="&lt;b&gt;golang.org/x/net/html (+ x/text)&lt;/b&gt;&lt;br/&gt;&lt;font style=&quot;font-size:10px&quot;&gt;HTML5 parser, charset lookup&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;dashed=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1"><mxGeometry x="1400" y="760" width="220" height="60" as="geometry"/></mxCell><mxCell id="e_main_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="main" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_tools_webmd" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="tools" target="webmd"><mxGeometry relative="1" as="geometry"/></mxCell><mxCell id="e_webmd_xnet" style="endArrow=block;html=1;rounded=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="webmd" target="xnet"><mxGeometry relative="1" as="geometry"/></mxCell></root></mxGraphModel></diagram></mxfile>
\ No newline at end of file\ No newline at end of file
modified docs/en/README.md +4 -2
@@ -1,6 +1,6 @@
11 # mini-me — documentation (English)
22
3-`mm` is a coding agent that runs in your terminal — line by line, or in a single window with `mm -tui` — or inside an editor through the Agent Client Protocol. It talks to a local model server (Docker Model Runner or llama.cpp's `llama-server`) and works with a handful of built-in tools: `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`.
3+`mm` is a coding agent that runs in your terminal — line by line, or in a single window with `mm -tui` — or inside an editor through the Agent Client Protocol. It talks to a local model server (Docker Model Runner or llama.cpp's `llama-server`) and works with a handful of built-in tools: `bash`, `read_skill`, `read_file`, `write_file`, `edit_file` — plus, when turned on, `lsp` and `fetch_content`.
44
55 This documentation is organised along the four [Diátaxis](https://diataxis.fr) quadrants. Pick the one that matches what you need right now.
66
@@ -23,6 +23,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
2323 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)
2424 - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md)
2525 - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md)
26+- [Fetch a web page as Markdown (`fetch_content`)](how-to/fetch-a-web-page.md) — read a page, or save it to a file without showing it
2627
2728 ### MCP (Model Context Protocol) How-to
2829 - [Use Context7 for documentation](how-to/mcp-use-context7.md)
@@ -34,7 +35,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
3435 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
3536 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
3637 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
37-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`
38+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`
3839 - [Skill file format](reference/skill-format.md)
3940 - [Agent Client Protocol surface](reference/acp.md)
4041
@@ -50,6 +51,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
5051 - [Context compression](explanation/context-compression.md)
5152 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
5253 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
54+- [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits
5355 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive
5456 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
5557
@@ -1,6 +1,6 @@
1 # mini-me — documentation (English)1 # mini-me — documentation (English)
2 2
3-`mm` is a coding agent that runs in your terminal — line by line, or in a single window with `mm -tui` — or inside an editor through the Agent Client Protocol. It talks to a local model server (Docker Model Runner or llama.cpp's `llama-server`) and works with a handful of built-in tools: `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`.3+`mm` is a coding agent that runs in your terminal — line by line, or in a single window with `mm -tui` — or inside an editor through the Agent Client Protocol. It talks to a local model server (Docker Model Runner or llama.cpp's `llama-server`) and works with a handful of built-in tools: `bash`, `read_skill`, `read_file`, `write_file`, `edit_file` — plus, when turned on, `lsp` and `fetch_content`.
4 4
5 This documentation is organised along the four [Diátaxis](https://diataxis.fr) quadrants. Pick the one that matches what you need right now.5 This documentation is organised along the four [Diátaxis](https://diataxis.fr) quadrants. Pick the one that matches what you need right now.
6 6
@@ -23,6 +23,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
23 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)23 - [Use the window mode (`mm -tui`)](how-to/use-the-window-mode.md)
24 - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md)24 - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md)
25 - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md)25 - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md)
26+- [Fetch a web page as Markdown (`fetch_content`)](how-to/fetch-a-web-page.md) — read a page, or save it to a file without showing it
26 27
27 ### MCP (Model Context Protocol) How-to28 ### MCP (Model Context Protocol) How-to
28 - [Use Context7 for documentation](how-to/mcp-use-context7.md)29 - [Use Context7 for documentation](how-to/mcp-use-context7.md)
@@ -34,7 +35,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
34 - [Command line](reference/cli.md) — flags, environment variables, start-up banner35 - [Command line](reference/cli.md) — flags, environment variables, start-up banner
35 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys36 - [Slash commands](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, keyboard shortcuts, window keys
36 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule37 - [Configuration file](reference/configuration.md) — every YAML key, its default and its validation rule
37-- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`38+- [Built-in tools](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`
38 - [Skill file format](reference/skill-format.md)39 - [Skill file format](reference/skill-format.md)
39 - [Agent Client Protocol surface](reference/acp.md)40 - [Agent Client Protocol surface](reference/acp.md)
40 41
@@ -50,6 +51,7 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr)
50 - [Context compression](explanation/context-compression.md)51 - [Context compression](explanation/context-compression.md)
51 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`52 - [Persistent sessions](explanation/persistent-sessions.md) — files per project, why a start is a new session, `session/load` versus docker agent's `session/resume`
52 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission53 - [The `lsp` tool](explanation/lsp-tool.md) — one tool with an `action` field, a `symbol` field instead of columns, files synced from disk, rename gated by permission
54+- [The `fetch_content` tool](explanation/fetch-content-tool.md) — why a built-in tool beside MCP `fetch`, a home-made converter, robots.txt, errors as numbered text, known limits
53 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive55 - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive
54 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)56 - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
55 57
added docs/en/explanation/fetch-content-tool.md +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+# The `fetch_content` tool — explanation
2+
3+## What is this about?
4+
5+`fetch_content` gives the model one thing it could only do clumsily before: read a web page. With `bash` alone, a model runs `curl`, gets hundreds of kilobytes of HTML — scripts, styles, menus, tracking code — and either drowns its context window in it or summarises a page it never really saw. `fetch_content` downloads the page, throws away what is not content, and hands back Markdown: the format a language model reads best, and the one a user can keep.
6+
7+The work is split in two. `internal/webmd` is a plain Go package with three stages — `Fetcher.Fetch`, `Clean`, `Convert` — that knows nothing about models. `internal/tools/fetchcontent.go` is the thin tool around it: arguments, display, permission, and the text the model gets back.
8+
9+## Why a built-in tool, and not the MCP `fetch` server
10+
11+An MCP `fetch` server can be wired into `mm` already. The built-in tool exists for what such a server does not guarantee:
12+
13+- **It works with nothing else installed.** No Node or Python runtime, no gateway: the conversion is in the binary.
14+- **It can save without returning.** "Save it to hello.txt, don't show it" is common, and a tool that always returns the page makes the model pay for the whole page in its context just to copy it into a file — which a small local model does badly anyway. `output` writes the file directly and returns one line.
15+- **Its conversion rules are ours.** The specification it follows (see the reference) decides headings, lists, tables, escaping; the tests pin them.
16+
17+The name is `fetch_content`, not `fetch`, so it never collides with an MCP tool of that name.
18+
19+## Why the conversion is written here
20+
21+A library such as html-to-markdown was considered. The specification asks for behaviour that such libraries either do not have or make awkward: complex tables kept as raw HTML *or* flattened on demand, a clean-up pass driven by roles and class words with a guard for layout wrappers, links resolved against the final URL, a strict "one trailing newline, no preamble" output. Bending a general-purpose converter to all of it would have cost about as much as writing the few hundred lines, and would have added a dependency whose defaults change under us. The parsing itself is not ours: `golang.org/x/net/html` is the HTML5 parser, so broken HTML is repaired exactly the way a browser repairs it.
22+
23+## Why robots.txt is honoured
24+
25+`mm` fetches on behalf of a person, one page at a time — closer to a browser than to a crawler. It still honours `robots.txt`, because the request said so and because an agent that ignores a site's stated wishes is a poor citizen however few requests it sends. It identifies itself as `mm/<version>`, so a site can address it by name.
26+
27+The rules follow RFC 9309, including its cautious corners: a site whose `robots.txt` answers with a server error, or cannot be reached, is treated as closed — the RFC's choice, and the only safe reading of "we could not ask". Every redirection is checked too, so a redirect cannot lead into a forbidden path.
28+
29+## Why failures are text with a number
30+
31+Every `mm` tool returns failures as text, so the model can read them and react. `fetch_content` adds the error code of its specification (1 conversion, 2 input, 3 usage) because the three call for different reactions: fix the call (3), give up or try another page (2), or tell the user the thing is not a web page (1). A model that sees `error 2 (input) — … robots.txt does not allow "mm"` has no reason to try `curl` next.
32+
33+## Choices that trade exactness for safety
34+
35+- **Off by default.** It is the only built-in tool that reaches the network; an operator turns it on knowingly.
36+- **Limits.** 30 seconds, 5 MiB, 10 redirections: a tool call must not hang a turn or fill memory.
37+- **Encoding.** The declared encoding wins (BOM, then HTTP header, then `<meta>`); when nothing is declared, valid UTF-8 is taken as UTF-8 and anything else as windows-1252 — the web's historical default.
38+- **Images as `data:` URIs** are reduced to their alt text: kilobytes of base64 are no use to a reader, human or model.
39+
40+## Known limits
41+
42+- The clean-up is heuristic. It removes navigation, ads and banners that are marked as such; an ad without a telling class or role stays, and an unusual class name can, rarely, remove content. Anything wrapping a `main`, an `article` or an `h1` is protected.
43+- Inline elements that a page separates only with CSS come out glued together: tags rendered as `<a>go</a><a>tui</a>` read `gotui`.
44+- Pages built by JavaScript in the browser have little content in their HTML; `fetch_content` does not run scripts.
45+- Only HTML is converted: a PDF or a JSON document is refused with `error 1`.
46+
47+## How it relates to the rest
48+
49+`fetch_content` follows the same contract as the other tools (see [architecture](architecture.md)): one display line in the terminal, an event with a kind (`fetch`) for ACP and the window, a permission request only when it writes a file, and a record in the loop detector. Its output goes through the same `maxOutput` truncation as `bash`.
new file mode 100644
@@ -0,0 +1,49 @@
1+# The `fetch_content` tool — explanation
2+
3+## What is this about?
4+
5+`fetch_content` gives the model one thing it could only do clumsily before: read a web page. With `bash` alone, a model runs `curl`, gets hundreds of kilobytes of HTML — scripts, styles, menus, tracking code — and either drowns its context window in it or summarises a page it never really saw. `fetch_content` downloads the page, throws away what is not content, and hands back Markdown: the format a language model reads best, and the one a user can keep.
6+
7+The work is split in two. `internal/webmd` is a plain Go package with three stages — `Fetcher.Fetch`, `Clean`, `Convert` — that knows nothing about models. `internal/tools/fetchcontent.go` is the thin tool around it: arguments, display, permission, and the text the model gets back.
8+
9+## Why a built-in tool, and not the MCP `fetch` server
10+
11+An MCP `fetch` server can be wired into `mm` already. The built-in tool exists for what such a server does not guarantee:
12+
13+- **It works with nothing else installed.** No Node or Python runtime, no gateway: the conversion is in the binary.
14+- **It can save without returning.** "Save it to hello.txt, don't show it" is common, and a tool that always returns the page makes the model pay for the whole page in its context just to copy it into a file — which a small local model does badly anyway. `output` writes the file directly and returns one line.
15+- **Its conversion rules are ours.** The specification it follows (see the reference) decides headings, lists, tables, escaping; the tests pin them.
16+
17+The name is `fetch_content`, not `fetch`, so it never collides with an MCP tool of that name.
18+
19+## Why the conversion is written here
20+
21+A library such as html-to-markdown was considered. The specification asks for behaviour that such libraries either do not have or make awkward: complex tables kept as raw HTML *or* flattened on demand, a clean-up pass driven by roles and class words with a guard for layout wrappers, links resolved against the final URL, a strict "one trailing newline, no preamble" output. Bending a general-purpose converter to all of it would have cost about as much as writing the few hundred lines, and would have added a dependency whose defaults change under us. The parsing itself is not ours: `golang.org/x/net/html` is the HTML5 parser, so broken HTML is repaired exactly the way a browser repairs it.
22+
23+## Why robots.txt is honoured
24+
25+`mm` fetches on behalf of a person, one page at a time — closer to a browser than to a crawler. It still honours `robots.txt`, because the request said so and because an agent that ignores a site's stated wishes is a poor citizen however few requests it sends. It identifies itself as `mm/<version>`, so a site can address it by name.
26+
27+The rules follow RFC 9309, including its cautious corners: a site whose `robots.txt` answers with a server error, or cannot be reached, is treated as closed — the RFC's choice, and the only safe reading of "we could not ask". Every redirection is checked too, so a redirect cannot lead into a forbidden path.
28+
29+## Why failures are text with a number
30+
31+Every `mm` tool returns failures as text, so the model can read them and react. `fetch_content` adds the error code of its specification (1 conversion, 2 input, 3 usage) because the three call for different reactions: fix the call (3), give up or try another page (2), or tell the user the thing is not a web page (1). A model that sees `error 2 (input) — … robots.txt does not allow "mm"` has no reason to try `curl` next.
32+
33+## Choices that trade exactness for safety
34+
35+- **Off by default.** It is the only built-in tool that reaches the network; an operator turns it on knowingly.
36+- **Limits.** 30 seconds, 5 MiB, 10 redirections: a tool call must not hang a turn or fill memory.
37+- **Encoding.** The declared encoding wins (BOM, then HTTP header, then `<meta>`); when nothing is declared, valid UTF-8 is taken as UTF-8 and anything else as windows-1252 — the web's historical default.
38+- **Images as `data:` URIs** are reduced to their alt text: kilobytes of base64 are no use to a reader, human or model.
39+
40+## Known limits
41+
42+- The clean-up is heuristic. It removes navigation, ads and banners that are marked as such; an ad without a telling class or role stays, and an unusual class name can, rarely, remove content. Anything wrapping a `main`, an `article` or an `h1` is protected.
43+- Inline elements that a page separates only with CSS come out glued together: tags rendered as `<a>go</a><a>tui</a>` read `gotui`.
44+- Pages built by JavaScript in the browser have little content in their HTML; `fetch_content` does not run scripts.
45+- Only HTML is converted: a PDF or a JSON document is refused with `error 1`.
46+
47+## How it relates to the rest
48+
49+`fetch_content` follows the same contract as the other tools (see [architecture](architecture.md)): one display line in the terminal, an event with a kind (`fetch`) for ACP and the window, a permission request only when it writes a file, and a record in the loop detector. Its output goes through the same `maxOutput` truncation as `bash`.
added docs/en/how-to/fetch-a-web-page.md +65 -0
new file mode 100644
@@ -0,0 +1,65 @@
1+# How to fetch a web page as Markdown
2+
3+This guide shows how to let the model read a web page, or save it to a file, with the built-in `fetch_content` tool. It assumes you already run `mm` with a configuration file.
4+
5+## Steps
6+
7+1. Turn the tool on in your configuration file (it is off by default):
8+
9+ ```yaml
10+ fetchContentTool: true
11+ ```
12+
13+2. Start `mm` and check that the start-up banner lists `fetch_content` among the tools.
14+
15+3. Ask for the page in plain words, in any language:
16+
17+ ```text
18+ fetch the content of https://k33g.org
19+ ```
20+
21+ ```text
22+ récupère le contenu de https://k33g.org
23+ ```
24+
25+ The terminal shows `🌐 fetch_content: https://k33g.org`, then the first lines of the Markdown, and the model answers from it.
26+
27+## Save the page to a file instead of showing it
28+
29+Name the file and say not to display it:
30+
31+```text
32+récupère le contenu de https://hello.com mais ne l'affiche pas et sauve le dans hello.txt
33+```
34+
35+The model passes `output: hello.txt`. The Markdown is written to `hello.txt` (relative to the directory `mm` runs in, or to the project opened in the editor) and the tool returns only one line:
36+
37+```text
38+[fetch_content: saved 9 lines of Markdown (421 bytes) from https://hello.com to hello.txt — the content is not shown]
39+```
40+
41+The page does not enter the conversation, so a long page does not use up the context window.
42+
43+## Variants
44+
45+- **A URL typed carelessly.** `k33g.org` and `https//k33g.org` both work: a missing scheme becomes `https://`, a missing colon is added back.
46+- **Complex tables.** Tables with merged cells are kept as raw HTML by default. Ask for flattened tables ("…as Markdown tables, flatten them") and the model passes `tables: flatten`.
47+- **Strikethrough and task lists.** Ask for them explicitly ("keep the struck-through text", "keep the checkboxes") and the model passes `strikethrough: true` or `taskList: true`.
48+- **Links relative to another site.** If the page is a mirror, ask to resolve links against the original; the model passes `baseUrl`.
49+- **In an editor (ACP) or with `permissions: ask`.** Reading a page needs no permission; saving it to a file asks first, like `write_file`.
50+
51+## When it fails
52+
53+The model receives `[fetch_content: error N (…) — reason]` and usually tells you why:
54+
55+- `error 2 (input)`: the site is unreachable, returned an HTTP error, or its `robots.txt` forbids robots to read that page. `mm` does not bypass `robots.txt`.
56+- `error 1 (conversion)`: the URL is not an HTML page (a PDF, JSON, an image) or is larger than 5 MiB.
57+- `error 3 (usage)`: the call itself is wrong, for example an `ftp://` URL.
58+
59+If the model uses `curl` through `bash` instead, the tool is probably not declared: check `fetchContentTool: true` and the banner.
60+
61+## See also
62+
63+- Every field, rule and error code: [built-in tools reference](../reference/tools.md#fetch_content)
64+- The configuration key: [configuration reference](../reference/configuration.md)
65+- Why the tool works this way: [the `fetch_content` tool](../explanation/fetch-content-tool.md)
new file mode 100644
@@ -0,0 +1,65 @@
1+# How to fetch a web page as Markdown
2+
3+This guide shows how to let the model read a web page, or save it to a file, with the built-in `fetch_content` tool. It assumes you already run `mm` with a configuration file.
4+
5+## Steps
6+
7+1. Turn the tool on in your configuration file (it is off by default):
8+
9+ ```yaml
10+ fetchContentTool: true
11+ ```
12+
13+2. Start `mm` and check that the start-up banner lists `fetch_content` among the tools.
14+
15+3. Ask for the page in plain words, in any language:
16+
17+ ```text
18+ fetch the content of https://k33g.org
19+ ```
20+
21+ ```text
22+ récupère le contenu de https://k33g.org
23+ ```
24+
25+ The terminal shows `🌐 fetch_content: https://k33g.org`, then the first lines of the Markdown, and the model answers from it.
26+
27+## Save the page to a file instead of showing it
28+
29+Name the file and say not to display it:
30+
31+```text
32+récupère le contenu de https://hello.com mais ne l'affiche pas et sauve le dans hello.txt
33+```
34+
35+The model passes `output: hello.txt`. The Markdown is written to `hello.txt` (relative to the directory `mm` runs in, or to the project opened in the editor) and the tool returns only one line:
36+
37+```text
38+[fetch_content: saved 9 lines of Markdown (421 bytes) from https://hello.com to hello.txt — the content is not shown]
39+```
40+
41+The page does not enter the conversation, so a long page does not use up the context window.
42+
43+## Variants
44+
45+- **A URL typed carelessly.** `k33g.org` and `https//k33g.org` both work: a missing scheme becomes `https://`, a missing colon is added back.
46+- **Complex tables.** Tables with merged cells are kept as raw HTML by default. Ask for flattened tables ("…as Markdown tables, flatten them") and the model passes `tables: flatten`.
47+- **Strikethrough and task lists.** Ask for them explicitly ("keep the struck-through text", "keep the checkboxes") and the model passes `strikethrough: true` or `taskList: true`.
48+- **Links relative to another site.** If the page is a mirror, ask to resolve links against the original; the model passes `baseUrl`.
49+- **In an editor (ACP) or with `permissions: ask`.** Reading a page needs no permission; saving it to a file asks first, like `write_file`.
50+
51+## When it fails
52+
53+The model receives `[fetch_content: error N (…) — reason]` and usually tells you why:
54+
55+- `error 2 (input)`: the site is unreachable, returned an HTTP error, or its `robots.txt` forbids robots to read that page. `mm` does not bypass `robots.txt`.
56+- `error 1 (conversion)`: the URL is not an HTML page (a PDF, JSON, an image) or is larger than 5 MiB.
57+- `error 3 (usage)`: the call itself is wrong, for example an `ftp://` URL.
58+
59+If the model uses `curl` through `bash` instead, the tool is probably not declared: check `fetchContentTool: true` and the banner.
60+
61+## See also
62+
63+- Every field, rule and error code: [built-in tools reference](../reference/tools.md#fetch_content)
64+- The configuration key: [configuration reference](../reference/configuration.md)
65+- Why the tool works this way: [the `fetch_content` tool](../explanation/fetch-content-tool.md)
modified docs/en/reference/acp.md +1 -0
@@ -128,6 +128,7 @@ A transport error or a `cancelled` outcome counts as a rejection.
128128 | `execute` | `execute` | `bash` (and any unknown kind) |
129129 | `read` | `read` | `read_skill`, `read_file` |
130130 | `edit` | `edit` | `write_file`, `edit_file` |
131+| `fetch` | `fetch` | `fetch_content` |
131132
132133 ## Tool call identifiers
133134
@@ -128,6 +128,7 @@ A transport error or a `cancelled` outcome counts as a rejection.
128 | `execute` | `execute` | `bash` (and any unknown kind) |128 | `execute` | `execute` | `bash` (and any unknown kind) |
129 | `read` | `read` | `read_skill`, `read_file` |129 | `read` | `read` | `read_skill`, `read_file` |
130 | `edit` | `edit` | `write_file`, `edit_file` |130 | `edit` | `edit` | `write_file`, `edit_file` |
131+| `fetch` | `fetch` | `fetch_content` |
131 132
132 ## Tool call identifiers133 ## Tool call identifiers
133 134
modified docs/en/reference/configuration.md +1 -0
@@ -21,6 +21,7 @@
2121 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |
2222 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
2323 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
24+| `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |
2425 | `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
2526 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
2627 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |
@@ -21,6 +21,7 @@
21 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |21 | `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. |
22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |22 | `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. |
23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |23 | `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. |
24+| `fetchContentTool` | bool | `false` | Declare `fetch_content`, which fetches web pages as Markdown. Off by default: it is the only built-in tool that reaches the network. |
24 | `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |25 | `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. `max_tokens` also bounds one tool call: a `write_file` of a long document (or a thinking model's reasoning plus the call) can hit it, and the call is then cut off. mm hands it back to the model twice, asking for smaller pieces, then stops with `the model hit max_tokens (N) while writing a tool call`; raise the value if that happens. With streaming, mm also asks the server for token counts (`stream_options.include_usage`). |
25 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |26 | `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. |
26 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |27 | `firstTokenTimeout` | duration | `3m` | Silence tolerated before a model call's first token, while the server reads the prompt (every round trip of a turn has one). Never shorter than `watchdogTimeout`; must be >= 0. With a llama.cpp server (`dmr`, `llamacpp`) mm asks for `return_progress`: each progress report restarts this clock, and the spinner shows `Reading the prompt 12.0k/25.3k tokens`. On a timeout the watchdog says `no first token from the server after …` (before the first token) or `connection hang detected after …` (after). |
modified docs/en/reference/tools.md +39 -0
@@ -159,6 +159,45 @@ Declared when `lsp.servers` lists at least one server whose command is on the `P
159159 | ACP / window | Kind `read` for navigation, no permission; `rename` is kind `edit` and asks for permission like `edit_file` (`[rename rejected by the user]` when refused). |
160160 | Counter | `🔎 N lsp call(s)` in the recap line (see below). |
161161
162+## `fetch_content`
163+
164+Declared only with `fetchContentTool: true`. Downloads a web page and returns it as Markdown, or saves the Markdown to a file. Package `internal/webmd`.
165+
166+| Field | Type | Default | Description |
167+|-------|------|---------|-------------|
168+| `url` | string | — | The page. A missing scheme means `https://` (`k33g.org`); a scheme typed without its colon is repaired (`https//k33g.org`). Only `http` and `https`. |
169+| `output` | string | `""` | File to save the Markdown to, relative to the working directory (the session `cwd` in ACP); parent directories are created. When set, the Markdown is not returned. |
170+| `baseUrl` | string | page URL | Absolute URL against which relative links and image sources are resolved. Default: the page's final URL, after redirections. |
171+| `tables` | string | `html` | Tables with `rowspan`/`colspan` or a nested table: `html` keeps them as raw HTML, `flatten` makes a GFM table and copies a spanned cell into every slot it covers. |
172+| `strikethrough` | bool | `false` | `<del>`, `<s>`, `<strike>` → `~~text~~`; otherwise the plain text. |
173+| `taskList` | bool | `false` | A list item starting with a checkbox → `- [ ] …` / `- [x] …`; otherwise the checkbox is dropped. |
174+
175+| Aspect | Behaviour |
176+|--------|-----------|
177+| Request | `GET` with `User-Agent: mm/<version>`; at most 10 redirections; 30 s for the whole fetch; pages above 5 MiB refused. |
178+| robots.txt | Read (and cached 24 h per origin) before the page and before every redirection target, for the product token `mm`, else the `*` group. `Allow`/`Disallow`, longest match wins, `Allow` wins a tie, `*` and a final `$` supported (RFC 9309). A 4xx robots.txt allows everything; a 5xx or an unreachable one forbids everything. |
179+| Accepted types | `text/html`, `application/xhtml+xml`; no `Content-Type` → sniffed. |
180+| Encoding | In order: byte-order mark, `charset` of the `Content-Type` header, `<meta charset>` or `<meta http-equiv="Content-Type">` in the first 4 KiB, UTF-8 if the bytes are valid UTF-8, else `windows-1252`. An unknown name is skipped. The content is converted to UTF-8. |
181+| Removed | `script`, `style`, `noscript`, `template`, `meta`, `link`, `base`, `title`, `svg`, `canvas`, `iframe`, `object`, `embed`, `nav`, `aside`, `dialog`, `button`, `select`, `textarea`, inputs other than checkboxes, a `footer` outside `article`/`main`, HTML comments, ARIA roles `navigation` `banner` `complementary` `search` `contentinfo` `dialog`, `aria-hidden="true"`, `hidden`, class/id words `ad` `ads` `advert` `sponsor` `promo` `cookie` `consent` `gdpr` `newsletter` `subscribe` `popup` `modal` `share` `social` `breadcrumb` `nav` `navbar` `navigation` `menu` `sidebar` `skiplink` (and a few variants), classes `sr-only` `visually-hidden` `screen-reader-text`. An element matched by class/id is kept when it contains `main`, `article` or `h1`. |
182+| Attributes | All removed except `a[href]`, `img[src,alt]`, `td/th[colspan,rowspan]`, `ol[start]`, `input[type,checked]`, and the `language-*`/`lang-*` words of a `pre`/`code` class. |
183+| Conversion | `h1`–`h6` → `#`–`######`; `p` → paragraph, blank line between blocks; `strong`/`b` → `**…**`; `em`/`i` → `*…*`; inline `code`/`kbd`/`samp`/`tt` → backtick span; `pre` → fenced block (info string from `language-x`), fence longer than any backtick run inside; `br` → hard break `\`; `hr` → `---`; `blockquote` → `> `; `a` → `[text](url)`, the URL when there is no text, text only for `javascript:`; `img` → `![alt](src)`, `![](src)` without alt, alt only for a `data:` URI; `ul`/`ol` → `- ` / `N. ` (from `start`), nested lists indented by the parent marker's width; simple tables → GFM, first row as header, `|` escaped, `<br>` for line breaks in a cell, `<caption>` as a paragraph above. |
184+| Unknown element | Tags dropped, text kept; one that contains a block element is treated as a container. |
185+| Empty element | Nothing, no blank line. |
186+| Invalid HTML | Repaired by the HTML5 parser (`golang.org/x/net/html`), then converted. |
187+| Escaping | Backslash, `*`, backtick, `[`, `]`, `<`, and a `_` not inside a word get a backslash; so does a line starting with `#`, `>`, `-`, `+`, `=`, `\|` or `N.`. |
188+| Result | The Markdown, ending with exactly one newline; no title, front matter or preamble. Truncated to `maxOutput` characters. |
189+| With `output` | `[fetch_content: saved N lines of Markdown (B bytes) from <url> to <file> — the content is not shown]`. |
190+| Page with no content left | `[fetch_content: <url> has no text content once scripts, navigation and ads are removed — nothing saved]`; no file written. |
191+| Failure | `[fetch_content: error N (<class>) — <reason>]`, see the codes below. |
192+| Terminal display | `🌐 fetch_content: <url>` (`→ <file>` with `output`), spinner `Fetching`, then the result echoed like a command's output (only the confirmation line with `output`). |
193+| ACP / window | Kind `fetch`. No permission without `output`; with `output`, permission requested like `write_file` (`[fetch_content rejected by the user]` when refused), and the file's absolute path as location. |
194+
195+| Code | Class | Cause |
196+|------|-------|-------|
197+| `1` | `conversion` | Not an HTML page, larger than 5 MiB, undecodable, or the output file cannot be written. |
198+| `2` | `input` | Host unreachable, timeout, HTTP status ≥ 300 after redirections, more than 10 redirections, robots.txt forbids the page or cannot be read. |
199+| `3` | `usage` | Empty or unparsable `url`, a scheme other than `http`/`https`, unknown `tables` value, `baseUrl` not absolute. |
200+
162201 ## Loop detection
163202
164203 Every tool records `(tool, input, output)` in the loop detector after each call.
@@ -159,6 +159,45 @@ Declared when `lsp.servers` lists at least one server whose command is on the `P
159 | ACP / window | Kind `read` for navigation, no permission; `rename` is kind `edit` and asks for permission like `edit_file` (`[rename rejected by the user]` when refused). |159 | ACP / window | Kind `read` for navigation, no permission; `rename` is kind `edit` and asks for permission like `edit_file` (`[rename rejected by the user]` when refused). |
160 | Counter | `🔎 N lsp call(s)` in the recap line (see below). |160 | Counter | `🔎 N lsp call(s)` in the recap line (see below). |
161 161
162+## `fetch_content`
163+
164+Declared only with `fetchContentTool: true`. Downloads a web page and returns it as Markdown, or saves the Markdown to a file. Package `internal/webmd`.
165+
166+| Field | Type | Default | Description |
167+|-------|------|---------|-------------|
168+| `url` | string | — | The page. A missing scheme means `https://` (`k33g.org`); a scheme typed without its colon is repaired (`https//k33g.org`). Only `http` and `https`. |
169+| `output` | string | `""` | File to save the Markdown to, relative to the working directory (the session `cwd` in ACP); parent directories are created. When set, the Markdown is not returned. |
170+| `baseUrl` | string | page URL | Absolute URL against which relative links and image sources are resolved. Default: the page's final URL, after redirections. |
171+| `tables` | string | `html` | Tables with `rowspan`/`colspan` or a nested table: `html` keeps them as raw HTML, `flatten` makes a GFM table and copies a spanned cell into every slot it covers. |
172+| `strikethrough` | bool | `false` | `<del>`, `<s>`, `<strike>` → `~~text~~`; otherwise the plain text. |
173+| `taskList` | bool | `false` | A list item starting with a checkbox → `- [ ] …` / `- [x] …`; otherwise the checkbox is dropped. |
174+
175+| Aspect | Behaviour |
176+|--------|-----------|
177+| Request | `GET` with `User-Agent: mm/<version>`; at most 10 redirections; 30 s for the whole fetch; pages above 5 MiB refused. |
178+| robots.txt | Read (and cached 24 h per origin) before the page and before every redirection target, for the product token `mm`, else the `*` group. `Allow`/`Disallow`, longest match wins, `Allow` wins a tie, `*` and a final `$` supported (RFC 9309). A 4xx robots.txt allows everything; a 5xx or an unreachable one forbids everything. |
179+| Accepted types | `text/html`, `application/xhtml+xml`; no `Content-Type` → sniffed. |
180+| Encoding | In order: byte-order mark, `charset` of the `Content-Type` header, `<meta charset>` or `<meta http-equiv="Content-Type">` in the first 4 KiB, UTF-8 if the bytes are valid UTF-8, else `windows-1252`. An unknown name is skipped. The content is converted to UTF-8. |
181+| Removed | `script`, `style`, `noscript`, `template`, `meta`, `link`, `base`, `title`, `svg`, `canvas`, `iframe`, `object`, `embed`, `nav`, `aside`, `dialog`, `button`, `select`, `textarea`, inputs other than checkboxes, a `footer` outside `article`/`main`, HTML comments, ARIA roles `navigation` `banner` `complementary` `search` `contentinfo` `dialog`, `aria-hidden="true"`, `hidden`, class/id words `ad` `ads` `advert` `sponsor` `promo` `cookie` `consent` `gdpr` `newsletter` `subscribe` `popup` `modal` `share` `social` `breadcrumb` `nav` `navbar` `navigation` `menu` `sidebar` `skiplink` (and a few variants), classes `sr-only` `visually-hidden` `screen-reader-text`. An element matched by class/id is kept when it contains `main`, `article` or `h1`. |
182+| Attributes | All removed except `a[href]`, `img[src,alt]`, `td/th[colspan,rowspan]`, `ol[start]`, `input[type,checked]`, and the `language-*`/`lang-*` words of a `pre`/`code` class. |
183+| Conversion | `h1`–`h6` → `#`–`######`; `p` → paragraph, blank line between blocks; `strong`/`b` → `**…**`; `em`/`i` → `*…*`; inline `code`/`kbd`/`samp`/`tt` → backtick span; `pre` → fenced block (info string from `language-x`), fence longer than any backtick run inside; `br` → hard break `\`; `hr` → `---`; `blockquote` → `> `; `a` → `[text](url)`, the URL when there is no text, text only for `javascript:`; `img` → `![alt](src)`, `![](src)` without alt, alt only for a `data:` URI; `ul`/`ol` → `- ` / `N. ` (from `start`), nested lists indented by the parent marker's width; simple tables → GFM, first row as header, `|` escaped, `<br>` for line breaks in a cell, `<caption>` as a paragraph above. |
184+| Unknown element | Tags dropped, text kept; one that contains a block element is treated as a container. |
185+| Empty element | Nothing, no blank line. |
186+| Invalid HTML | Repaired by the HTML5 parser (`golang.org/x/net/html`), then converted. |
187+| Escaping | Backslash, `*`, backtick, `[`, `]`, `<`, and a `_` not inside a word get a backslash; so does a line starting with `#`, `>`, `-`, `+`, `=`, `\|` or `N.`. |
188+| Result | The Markdown, ending with exactly one newline; no title, front matter or preamble. Truncated to `maxOutput` characters. |
189+| With `output` | `[fetch_content: saved N lines of Markdown (B bytes) from <url> to <file> — the content is not shown]`. |
190+| Page with no content left | `[fetch_content: <url> has no text content once scripts, navigation and ads are removed — nothing saved]`; no file written. |
191+| Failure | `[fetch_content: error N (<class>) — <reason>]`, see the codes below. |
192+| Terminal display | `🌐 fetch_content: <url>` (`→ <file>` with `output`), spinner `Fetching`, then the result echoed like a command's output (only the confirmation line with `output`). |
193+| ACP / window | Kind `fetch`. No permission without `output`; with `output`, permission requested like `write_file` (`[fetch_content rejected by the user]` when refused), and the file's absolute path as location. |
194+
195+| Code | Class | Cause |
196+|------|-------|-------|
197+| `1` | `conversion` | Not an HTML page, larger than 5 MiB, undecodable, or the output file cannot be written. |
198+| `2` | `input` | Host unreachable, timeout, HTTP status ≥ 300 after redirections, more than 10 redirections, robots.txt forbids the page or cannot be read. |
199+| `3` | `usage` | Empty or unparsable `url`, a scheme other than `http`/`https`, unknown `tables` value, `baseUrl` not absolute. |
200+
162 ## Loop detection201 ## Loop detection
163 202
164 Every tool records `(tool, input, output)` in the loop detector after each call.203 Every tool records `(tool, input, output)` in the loop detector after each call.
modified docs/fr/README.md +4 -2
@@ -1,6 +1,6 @@
11 # mini-me — documentation (français)
22
3-`mm` est un agent de code qui tourne dans votre terminal — ligne à ligne, ou dans une fenêtre unique avec `mm -tui` — ou dans un éditeur via l'Agent Client Protocol. Il dialogue avec un serveur de modèle local (Docker Model Runner ou `llama-server` de llama.cpp) et travaille avec une poignée d'outils intégrés : `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`.
3+`mm` est un agent de code qui tourne dans votre terminal — ligne à ligne, ou dans une fenêtre unique avec `mm -tui` — ou dans un éditeur via l'Agent Client Protocol. Il dialogue avec un serveur de modèle local (Docker Model Runner ou `llama-server` de llama.cpp) et travaille avec une poignée d'outils intégrés : `bash`, `read_skill`, `read_file`, `write_file`, `edit_file` — et, quand ils sont activés, `lsp` et `fetch_content`.
44
55 Cette documentation est organisée selon les quatre quadrants [Diátaxis](https://diataxis.fr). Choisissez celui qui correspond à votre besoin du moment.
66
@@ -23,6 +23,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
2323 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)
2424 - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md)
2525 - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md)
26+- [Récupérer une page web en Markdown (`fetch_content`)](how-to/fetch-a-web-page.md) — lire une page, ou l'enregistrer dans un fichier sans l'afficher
2627
2728 ### Guides pratiques MCP (Model Context Protocol)
2829 - [Utiliser Context7 pour la documentation](how-to/mcp-utiliser-context7.md)
@@ -34,7 +35,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
3435 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage
3536 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre
3637 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation
37-- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`
38+- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`
3839 - [Format d'un fichier de skill](reference/skill-format.md)
3940 - [Surface Agent Client Protocol](reference/acp.md)
4041
@@ -50,6 +51,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
5051 - [Compression du contexte](explanation/context-compression.md)
5152 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent
5253 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
54+- [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues
5355 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie
5456 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
5557
@@ -1,6 +1,6 @@
1 # mini-me — documentation (français)1 # mini-me — documentation (français)
2 2
3-`mm` est un agent de code qui tourne dans votre terminal — ligne à ligne, ou dans une fenêtre unique avec `mm -tui` — ou dans un éditeur via l'Agent Client Protocol. Il dialogue avec un serveur de modèle local (Docker Model Runner ou `llama-server` de llama.cpp) et travaille avec une poignée d'outils intégrés : `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`.3+`mm` est un agent de code qui tourne dans votre terminal — ligne à ligne, ou dans une fenêtre unique avec `mm -tui` — ou dans un éditeur via l'Agent Client Protocol. Il dialogue avec un serveur de modèle local (Docker Model Runner ou `llama-server` de llama.cpp) et travaille avec une poignée d'outils intégrés : `bash`, `read_skill`, `read_file`, `write_file`, `edit_file` — et, quand ils sont activés, `lsp` et `fetch_content`.
4 4
5 Cette documentation est organisée selon les quatre quadrants [Diátaxis](https://diataxis.fr). Choisissez celui qui correspond à votre besoin du moment.5 Cette documentation est organisée selon les quatre quadrants [Diátaxis](https://diataxis.fr). Choisissez celui qui correspond à votre besoin du moment.
6 6
@@ -23,6 +23,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
23 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)23 - [Utiliser le mode fenêtre (`mm -tui`)](how-to/use-the-window-mode.md)
24 - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md)24 - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md)
25 - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md)25 - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md)
26+- [Récupérer une page web en Markdown (`fetch_content`)](how-to/fetch-a-web-page.md) — lire une page, ou l'enregistrer dans un fichier sans l'afficher
26 27
27 ### Guides pratiques MCP (Model Context Protocol)28 ### Guides pratiques MCP (Model Context Protocol)
28 - [Utiliser Context7 pour la documentation](how-to/mcp-utiliser-context7.md)29 - [Utiliser Context7 pour la documentation](how-to/mcp-utiliser-context7.md)
@@ -34,7 +35,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
34 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage35 - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage
35 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre36 - [Commandes slash](reference/slash-commands.md) — `/quit`, `/abort`, `/compact`, `/new`, `/sessions`, `/resume`, `/help`, raccourcis clavier, touches de la fenêtre
36 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation37 - [Fichier de configuration](reference/configuration.md) — chaque clé YAML, sa valeur par défaut et sa règle de validation
37-- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`38+- [Outils intégrés](reference/tools.md) — `bash`, `read_skill`, `read_file`, `write_file`, `edit_file`, `lsp`, `fetch_content`
38 - [Format d'un fichier de skill](reference/skill-format.md)39 - [Format d'un fichier de skill](reference/skill-format.md)
39 - [Surface Agent Client Protocol](reference/acp.md)40 - [Surface Agent Client Protocol](reference/acp.md)
40 41
@@ -50,6 +51,7 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https:
50 - [Compression du contexte](explanation/context-compression.md)51 - [Compression du contexte](explanation/context-compression.md)
51 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent52 - [Les sessions persistantes](explanation/persistent-sessions.md) — des fichiers par projet, pourquoi un lancement est une nouvelle session, `session/load` face au `session/resume` de docker agent
52 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission53 - [L'outil `lsp`](explanation/lsp-tool.md) — un seul outil avec un champ `action`, un champ `symbol` plutôt que des colonnes, des fichiers synchronisés depuis le disque, un renommage soumis à permission
54+- [L'outil `fetch_content`](explanation/fetch-content-tool.md) — pourquoi un outil intégré à côté du `fetch` MCP, un convertisseur maison, le robots.txt, des erreurs numérotées en texte, les limites connues
53 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie55 - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie
54 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)56 - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio)
55 57
added docs/fr/explanation/fetch-content-tool.md +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+# L'outil `fetch_content` — explication
2+
3+## De quoi s'agit-il ?
4+
5+`fetch_content` donne au modèle une capacité qu'il n'exerçait jusqu'ici que maladroitement : lire une page web. Avec `bash` seul, un modèle lance `curl`, reçoit des centaines de kilo-octets de HTML (scripts, styles, menus, code de suivi) et soit y noie sa fenêtre de contexte, soit résume une page qu'il n'a jamais vraiment vue. `fetch_content` télécharge la page, jette ce qui n'est pas du contenu et rend du Markdown : le format qu'un modèle de langage lit le mieux, et celui qu'un utilisateur peut conserver.
6+
7+Le travail est coupé en deux. `internal/webmd` est un paquet Go ordinaire en trois étapes — `Fetcher.Fetch`, `Clean`, `Convert` — qui ne sait rien des modèles. `internal/tools/fetchcontent.go` est la fine couche d'outil autour : arguments, affichage, permission, et le texte que reçoit le modèle.
8+
9+## Pourquoi un outil intégré, et pas le serveur MCP `fetch`
10+
11+On peut déjà brancher un serveur MCP `fetch` sur `mm`. L'outil intégré existe pour ce qu'un tel serveur ne garantit pas :
12+
13+- **Il fonctionne sans rien installer d'autre.** Ni Node, ni Python, ni passerelle : la conversion est dans le binaire.
14+- **Il sait enregistrer sans renvoyer.** « Sauve-le dans hello.txt, ne l'affiche pas » est une demande courante, et un outil qui renvoie toujours la page fait payer au modèle toute la page dans son contexte rien que pour la recopier dans un fichier — ce qu'un petit modèle local fait mal de toute façon. `output` écrit le fichier directement et ne renvoie qu'une ligne.
15+- **Ses règles de conversion sont les nôtres.** La spécification qu'il suit (voir la référence) fixe titres, listes, tableaux, échappement ; les tests les verrouillent.
16+
17+Il s'appelle `fetch_content`, pas `fetch`, pour ne jamais entrer en collision avec un outil MCP de ce nom.
18+
19+## Pourquoi la conversion est écrite ici
20+
21+Une bibliothèque comme html-to-markdown a été envisagée. La spécification demande des comportements que ces bibliothèques n'ont pas ou rendent malaisés : tableaux complexes gardés en HTML brut *ou* aplatis à la demande, un nettoyage guidé par les rôles et les mots de classe avec une protection pour les conteneurs de mise en page, des liens résolus contre l'URL finale, une sortie stricte « un seul retour à la ligne final, pas de préambule ». Plier un convertisseur généraliste à tout cela aurait coûté à peu près autant que d'écrire ces quelques centaines de lignes, en ajoutant une dépendance dont les comportements par défaut peuvent changer sous nos pieds. L'analyse elle-même n'est pas la nôtre : `golang.org/x/net/html` est le parseur HTML5, donc un HTML cassé est réparé exactement comme un navigateur le répare.
22+
23+## Pourquoi le robots.txt est respecté
24+
25+`mm` récupère pour le compte d'une personne, une page à la fois — plus proche d'un navigateur que d'un robot d'indexation. Il respecte pourtant le `robots.txt`, parce que la demande l'exigeait et parce qu'un agent qui ignore les souhaits déclarés d'un site se conduit mal, si peu de requêtes qu'il envoie. Il s'annonce comme `mm/<version>`, pour qu'un site puisse le désigner par son nom.
26+
27+Les règles suivent la RFC 9309, y compris ses recoins prudents : un site dont le `robots.txt` répond par une erreur serveur, ou est injoignable, est considéré comme fermé — le choix de la RFC, et la seule lecture sûre de « nous n'avons pas pu demander ». Chaque redirection est vérifiée aussi, pour qu'une redirection ne mène pas vers un chemin interdit.
28+
29+## Pourquoi un échec est un texte avec un numéro
30+
31+Tous les outils de `mm` renvoient leurs échecs en texte, pour que le modèle les lise et réagisse. `fetch_content` y ajoute le code d'erreur de sa spécification (1 conversion, 2 entrée, 3 usage), parce que les trois appellent des réactions différentes : corriger l'appel (3), abandonner ou essayer une autre page (2), ou dire à l'utilisateur que ce n'est pas une page web (1). Un modèle qui lit `error 2 (input) — … robots.txt does not allow "mm"` n'a aucune raison d'essayer `curl` ensuite.
32+
33+## Des choix qui échangent de l'exactitude contre de la sûreté
34+
35+- **Désactivé par défaut.** C'est le seul outil intégré qui accède au réseau ; un opérateur l'active en connaissance de cause.
36+- **Limites.** 30 secondes, 5 Mio, 10 redirections : un appel d'outil ne doit ni bloquer un tour ni remplir la mémoire.
37+- **Encodage.** L'encodage déclaré l'emporte (BOM, puis en-tête HTTP, puis `<meta>`) ; quand rien n'est déclaré, de l'UTF-8 valide est lu comme de l'UTF-8 et tout le reste comme du windows-1252, le défaut historique du web.
38+- **Les images en URI `data:`** sont réduites à leur texte alternatif : des kilo-octets de base64 ne servent à aucun lecteur, humain ou modèle.
39+
40+## Limites connues
41+
42+- Le nettoyage est heuristique. Il retire la navigation, les publicités et les bannières signalées comme telles ; une publicité sans classe ni rôle révélateur reste, et un nom de classe inhabituel peut, rarement, retirer du contenu. Tout ce qui enveloppe un `main`, un `article` ou un `h1` est protégé.
43+- Les éléments en ligne qu'une page ne sépare que par du CSS ressortent collés : des étiquettes rendues par `<a>go</a><a>tui</a>` donnent `gotui`.
44+- Les pages construites par JavaScript dans le navigateur ont peu de contenu dans leur HTML ; `fetch_content` n'exécute pas de script.
45+- Seul le HTML est converti : un PDF ou un document JSON est refusé avec `error 1`.
46+
47+## Comment il s'insère dans le reste
48+
49+`fetch_content` suit le même contrat que les autres outils (voir [l'architecture](architecture.md)) : une ligne d'affichage dans le terminal, un événement avec un genre (`fetch`) pour ACP et la fenêtre, une demande de permission seulement quand il écrit un fichier, et un enregistrement dans le détecteur de boucle. Sa sortie passe par la même troncature `maxOutput` que `bash`.
new file mode 100644
@@ -0,0 +1,49 @@
1+# L'outil `fetch_content` — explication
2+
3+## De quoi s'agit-il ?
4+
5+`fetch_content` donne au modèle une capacité qu'il n'exerçait jusqu'ici que maladroitement : lire une page web. Avec `bash` seul, un modèle lance `curl`, reçoit des centaines de kilo-octets de HTML (scripts, styles, menus, code de suivi) et soit y noie sa fenêtre de contexte, soit résume une page qu'il n'a jamais vraiment vue. `fetch_content` télécharge la page, jette ce qui n'est pas du contenu et rend du Markdown : le format qu'un modèle de langage lit le mieux, et celui qu'un utilisateur peut conserver.
6+
7+Le travail est coupé en deux. `internal/webmd` est un paquet Go ordinaire en trois étapes — `Fetcher.Fetch`, `Clean`, `Convert` — qui ne sait rien des modèles. `internal/tools/fetchcontent.go` est la fine couche d'outil autour : arguments, affichage, permission, et le texte que reçoit le modèle.
8+
9+## Pourquoi un outil intégré, et pas le serveur MCP `fetch`
10+
11+On peut déjà brancher un serveur MCP `fetch` sur `mm`. L'outil intégré existe pour ce qu'un tel serveur ne garantit pas :
12+
13+- **Il fonctionne sans rien installer d'autre.** Ni Node, ni Python, ni passerelle : la conversion est dans le binaire.
14+- **Il sait enregistrer sans renvoyer.** « Sauve-le dans hello.txt, ne l'affiche pas » est une demande courante, et un outil qui renvoie toujours la page fait payer au modèle toute la page dans son contexte rien que pour la recopier dans un fichier — ce qu'un petit modèle local fait mal de toute façon. `output` écrit le fichier directement et ne renvoie qu'une ligne.
15+- **Ses règles de conversion sont les nôtres.** La spécification qu'il suit (voir la référence) fixe titres, listes, tableaux, échappement ; les tests les verrouillent.
16+
17+Il s'appelle `fetch_content`, pas `fetch`, pour ne jamais entrer en collision avec un outil MCP de ce nom.
18+
19+## Pourquoi la conversion est écrite ici
20+
21+Une bibliothèque comme html-to-markdown a été envisagée. La spécification demande des comportements que ces bibliothèques n'ont pas ou rendent malaisés : tableaux complexes gardés en HTML brut *ou* aplatis à la demande, un nettoyage guidé par les rôles et les mots de classe avec une protection pour les conteneurs de mise en page, des liens résolus contre l'URL finale, une sortie stricte « un seul retour à la ligne final, pas de préambule ». Plier un convertisseur généraliste à tout cela aurait coûté à peu près autant que d'écrire ces quelques centaines de lignes, en ajoutant une dépendance dont les comportements par défaut peuvent changer sous nos pieds. L'analyse elle-même n'est pas la nôtre : `golang.org/x/net/html` est le parseur HTML5, donc un HTML cassé est réparé exactement comme un navigateur le répare.
22+
23+## Pourquoi le robots.txt est respecté
24+
25+`mm` récupère pour le compte d'une personne, une page à la fois — plus proche d'un navigateur que d'un robot d'indexation. Il respecte pourtant le `robots.txt`, parce que la demande l'exigeait et parce qu'un agent qui ignore les souhaits déclarés d'un site se conduit mal, si peu de requêtes qu'il envoie. Il s'annonce comme `mm/<version>`, pour qu'un site puisse le désigner par son nom.
26+
27+Les règles suivent la RFC 9309, y compris ses recoins prudents : un site dont le `robots.txt` répond par une erreur serveur, ou est injoignable, est considéré comme fermé — le choix de la RFC, et la seule lecture sûre de « nous n'avons pas pu demander ». Chaque redirection est vérifiée aussi, pour qu'une redirection ne mène pas vers un chemin interdit.
28+
29+## Pourquoi un échec est un texte avec un numéro
30+
31+Tous les outils de `mm` renvoient leurs échecs en texte, pour que le modèle les lise et réagisse. `fetch_content` y ajoute le code d'erreur de sa spécification (1 conversion, 2 entrée, 3 usage), parce que les trois appellent des réactions différentes : corriger l'appel (3), abandonner ou essayer une autre page (2), ou dire à l'utilisateur que ce n'est pas une page web (1). Un modèle qui lit `error 2 (input) — … robots.txt does not allow "mm"` n'a aucune raison d'essayer `curl` ensuite.
32+
33+## Des choix qui échangent de l'exactitude contre de la sûreté
34+
35+- **Désactivé par défaut.** C'est le seul outil intégré qui accède au réseau ; un opérateur l'active en connaissance de cause.
36+- **Limites.** 30 secondes, 5 Mio, 10 redirections : un appel d'outil ne doit ni bloquer un tour ni remplir la mémoire.
37+- **Encodage.** L'encodage déclaré l'emporte (BOM, puis en-tête HTTP, puis `<meta>`) ; quand rien n'est déclaré, de l'UTF-8 valide est lu comme de l'UTF-8 et tout le reste comme du windows-1252, le défaut historique du web.
38+- **Les images en URI `data:`** sont réduites à leur texte alternatif : des kilo-octets de base64 ne servent à aucun lecteur, humain ou modèle.
39+
40+## Limites connues
41+
42+- Le nettoyage est heuristique. Il retire la navigation, les publicités et les bannières signalées comme telles ; une publicité sans classe ni rôle révélateur reste, et un nom de classe inhabituel peut, rarement, retirer du contenu. Tout ce qui enveloppe un `main`, un `article` ou un `h1` est protégé.
43+- Les éléments en ligne qu'une page ne sépare que par du CSS ressortent collés : des étiquettes rendues par `<a>go</a><a>tui</a>` donnent `gotui`.
44+- Les pages construites par JavaScript dans le navigateur ont peu de contenu dans leur HTML ; `fetch_content` n'exécute pas de script.
45+- Seul le HTML est converti : un PDF ou un document JSON est refusé avec `error 1`.
46+
47+## Comment il s'insère dans le reste
48+
49+`fetch_content` suit le même contrat que les autres outils (voir [l'architecture](architecture.md)) : une ligne d'affichage dans le terminal, un événement avec un genre (`fetch`) pour ACP et la fenêtre, une demande de permission seulement quand il écrit un fichier, et un enregistrement dans le détecteur de boucle. Sa sortie passe par la même troncature `maxOutput` que `bash`.
added docs/fr/how-to/fetch-a-web-page.md +65 -0
new file mode 100644
@@ -0,0 +1,65 @@
1+# Comment récupérer une page web en Markdown
2+
3+Ce guide montre comment faire lire une page web au modèle, ou la faire enregistrer dans un fichier, avec l'outil intégré `fetch_content`. Il suppose que vous lancez déjà `mm` avec un fichier de configuration.
4+
5+## Étapes
6+
7+1. Activez l'outil dans votre fichier de configuration (il est désactivé par défaut) :
8+
9+ ```yaml
10+ fetchContentTool: true
11+ ```
12+
13+2. Lancez `mm` et vérifiez que la bannière de démarrage cite `fetch_content` parmi les outils.
14+
15+3. Demandez la page avec vos mots, dans la langue de votre choix :
16+
17+ ```text
18+ récupère le contenu de https://k33g.org
19+ ```
20+
21+ ```text
22+ fetch the content of https://k33g.org
23+ ```
24+
25+ Le terminal affiche `🌐 fetch_content: https://k33g.org`, puis les premières lignes du Markdown, et le modèle répond à partir de ce contenu.
26+
27+## Enregistrer la page dans un fichier sans l'afficher
28+
29+Nommez le fichier et précisez de ne pas l'afficher :
30+
31+```text
32+récupère le contenu de https://hello.com mais ne l'affiche pas et sauve le dans hello.txt
33+```
34+
35+Le modèle passe `output: hello.txt`. Le Markdown est écrit dans `hello.txt` (relatif au répertoire où tourne `mm`, ou au projet ouvert dans l'éditeur) et l'outil ne renvoie qu'une ligne :
36+
37+```text
38+[fetch_content: saved 9 lines of Markdown (421 bytes) from https://hello.com to hello.txt — the content is not shown]
39+```
40+
41+La page n'entre pas dans la conversation : une page longue n'épuise donc pas la fenêtre de contexte.
42+
43+## Variantes
44+
45+- **Une URL tapée à la va-vite.** `k33g.org` et `https//k33g.org` fonctionnent : un schéma manquant devient `https://`, les deux-points oubliés sont remis.
46+- **Tableaux complexes.** Les tableaux à cellules fusionnées sont gardés en HTML brut par défaut. Demandez des tableaux aplatis (« …en tableaux Markdown, aplatis-les ») et le modèle passe `tables: flatten`.
47+- **Texte barré et listes de tâches.** Demandez-les explicitement (« garde le texte barré », « garde les cases à cocher ») et le modèle passe `strikethrough: true` ou `taskList: true`.
48+- **Liens relatifs à un autre site.** Si la page est un miroir, demandez de résoudre les liens par rapport à l'original ; le modèle passe `baseUrl`.
49+- **Dans un éditeur (ACP) ou avec `permissions: ask`.** Lire une page ne demande aucune permission ; l'enregistrer dans un fichier la demande d'abord, comme `write_file`.
50+
51+## En cas d'échec
52+
53+Le modèle reçoit `[fetch_content: error N (…) — raison]` et vous explique en général pourquoi :
54+
55+- `error 2 (input)` : le site est injoignable, a renvoyé une erreur HTTP, ou son `robots.txt` interdit aux robots de lire cette page. `mm` ne contourne pas le `robots.txt`.
56+- `error 1 (conversion)` : l'URL n'est pas une page HTML (un PDF, du JSON, une image) ou dépasse 5 Mio.
57+- `error 3 (usage)` : l'appel lui-même est faux, par exemple une URL `ftp://`.
58+
59+Si le modèle utilise `curl` via `bash` à la place, l'outil n'est probablement pas déclaré : vérifiez `fetchContentTool: true` et la bannière.
60+
61+## Voir aussi
62+
63+- Chaque champ, règle et code d'erreur : [référence des outils intégrés](../reference/tools.md#fetch_content)
64+- La clé de configuration : [référence de la configuration](../reference/configuration.md)
65+- Pourquoi l'outil fonctionne ainsi : [l'outil `fetch_content`](../explanation/fetch-content-tool.md)
new file mode 100644
@@ -0,0 +1,65 @@
1+# Comment récupérer une page web en Markdown
2+
3+Ce guide montre comment faire lire une page web au modèle, ou la faire enregistrer dans un fichier, avec l'outil intégré `fetch_content`. Il suppose que vous lancez déjà `mm` avec un fichier de configuration.
4+
5+## Étapes
6+
7+1. Activez l'outil dans votre fichier de configuration (il est désactivé par défaut) :
8+
9+ ```yaml
10+ fetchContentTool: true
11+ ```
12+
13+2. Lancez `mm` et vérifiez que la bannière de démarrage cite `fetch_content` parmi les outils.
14+
15+3. Demandez la page avec vos mots, dans la langue de votre choix :
16+
17+ ```text
18+ récupère le contenu de https://k33g.org
19+ ```
20+
21+ ```text
22+ fetch the content of https://k33g.org
23+ ```
24+
25+ Le terminal affiche `🌐 fetch_content: https://k33g.org`, puis les premières lignes du Markdown, et le modèle répond à partir de ce contenu.
26+
27+## Enregistrer la page dans un fichier sans l'afficher
28+
29+Nommez le fichier et précisez de ne pas l'afficher :
30+
31+```text
32+récupère le contenu de https://hello.com mais ne l'affiche pas et sauve le dans hello.txt
33+```
34+
35+Le modèle passe `output: hello.txt`. Le Markdown est écrit dans `hello.txt` (relatif au répertoire où tourne `mm`, ou au projet ouvert dans l'éditeur) et l'outil ne renvoie qu'une ligne :
36+
37+```text
38+[fetch_content: saved 9 lines of Markdown (421 bytes) from https://hello.com to hello.txt — the content is not shown]
39+```
40+
41+La page n'entre pas dans la conversation : une page longue n'épuise donc pas la fenêtre de contexte.
42+
43+## Variantes
44+
45+- **Une URL tapée à la va-vite.** `k33g.org` et `https//k33g.org` fonctionnent : un schéma manquant devient `https://`, les deux-points oubliés sont remis.
46+- **Tableaux complexes.** Les tableaux à cellules fusionnées sont gardés en HTML brut par défaut. Demandez des tableaux aplatis (« …en tableaux Markdown, aplatis-les ») et le modèle passe `tables: flatten`.
47+- **Texte barré et listes de tâches.** Demandez-les explicitement (« garde le texte barré », « garde les cases à cocher ») et le modèle passe `strikethrough: true` ou `taskList: true`.
48+- **Liens relatifs à un autre site.** Si la page est un miroir, demandez de résoudre les liens par rapport à l'original ; le modèle passe `baseUrl`.
49+- **Dans un éditeur (ACP) ou avec `permissions: ask`.** Lire une page ne demande aucune permission ; l'enregistrer dans un fichier la demande d'abord, comme `write_file`.
50+
51+## En cas d'échec
52+
53+Le modèle reçoit `[fetch_content: error N (…) — raison]` et vous explique en général pourquoi :
54+
55+- `error 2 (input)` : le site est injoignable, a renvoyé une erreur HTTP, ou son `robots.txt` interdit aux robots de lire cette page. `mm` ne contourne pas le `robots.txt`.
56+- `error 1 (conversion)` : l'URL n'est pas une page HTML (un PDF, du JSON, une image) ou dépasse 5 Mio.
57+- `error 3 (usage)` : l'appel lui-même est faux, par exemple une URL `ftp://`.
58+
59+Si le modèle utilise `curl` via `bash` à la place, l'outil n'est probablement pas déclaré : vérifiez `fetchContentTool: true` et la bannière.
60+
61+## Voir aussi
62+
63+- Chaque champ, règle et code d'erreur : [référence des outils intégrés](../reference/tools.md#fetch_content)
64+- La clé de configuration : [référence de la configuration](../reference/configuration.md)
65+- Pourquoi l'outil fonctionne ainsi : [l'outil `fetch_content`](../explanation/fetch-content-tool.md)
modified docs/fr/reference/acp.md +1 -0
@@ -128,6 +128,7 @@ Une erreur de transport ou un résultat `cancelled` compte comme un refus.
128128 | `execute` | `execute` | `bash` (et tout genre inconnu) |
129129 | `read` | `read` | `read_skill`, `read_file` |
130130 | `edit` | `edit` | `write_file`, `edit_file` |
131+| `fetch` | `fetch` | `fetch_content` |
131132
132133 ## Identifiants d'appel d'outil
133134
@@ -128,6 +128,7 @@ Une erreur de transport ou un résultat `cancelled` compte comme un refus.
128 | `execute` | `execute` | `bash` (et tout genre inconnu) |128 | `execute` | `execute` | `bash` (et tout genre inconnu) |
129 | `read` | `read` | `read_skill`, `read_file` |129 | `read` | `read` | `read_skill`, `read_file` |
130 | `edit` | `edit` | `write_file`, `edit_file` |130 | `edit` | `edit` | `write_file`, `edit_file` |
131+| `fetch` | `fetch` | `fetch_content` |
131 132
132 ## Identifiants d'appel d'outil133 ## Identifiants d'appel d'outil
133 134
modified docs/fr/reference/configuration.md +1 -0
@@ -21,6 +21,7 @@
2121 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |
2222 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |
2323 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
24+| `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. |
2425 | `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |
2526 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |
2627 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |
@@ -21,6 +21,7 @@
21 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |21 | `skillsDir` | chaîne | `skills` | Répertoire des skills, sous forme de fichiers `<nom>.md` ou de répertoires `<nom>/SKILL.md`. Un chemin relatif est résolu depuis le répertoire de lancement de `mm` (mode terminal, ou mode ACP sans fichier de configuration), ou à côté du fichier de configuration (mode ACP avec fichier) ; la bannière avertit avec le chemin absolu quand rien n'y est trouvé. |
22 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |22 | `bashTool` | booléen | `true` | Déclare `bash`. `false` le retire : le modèle peut encore lire et modifier des fichiers (`editTools`) et charger des skills, mais n'exécute jamais de commande. Reformulez un prompt système qui annonce encore un outil bash. |
23 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |23 | `editTools` | booléen | `true` | Déclare `read_file`, `write_file`, `edit_file`. `false` ne laisse que `bash` et `read_skill`. |
24+| `fetchContentTool` | booléen | `false` | Déclare `fetch_content`, qui récupère des pages web en Markdown. Désactivé par défaut : c'est le seul outil intégré qui accède au réseau. |
24 | `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |25 | `sampling` | dictionnaire | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Paramètres de génération, noms de l'API OpenAI, transmis au serveur. L'`agent.yaml` livré ajoute `parallel_tool_calls: false`. `max_tokens` borne aussi un appel d'outil : un `write_file` de long document (ou le raisonnement d'un modèle « thinking » plus l'appel) peut l'atteindre, et l'appel est alors coupé. mm le rend deux fois au modèle en lui demandant de procéder par morceaux, puis s'arrête avec `the model hit max_tokens (N) while writing a tool call` ; augmentez la valeur si cela arrive. En streaming, mm demande aussi au serveur le compte des tokens (`stream_options.include_usage`). |
25 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |26 | `watchdogTimeout` | durée | `20s` | Silence toléré entre deux tokens diffusés avant de déclarer la connexion bloquée. Doublé pour les requêtes de résumé. |
26 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |27 | `firstTokenTimeout` | durée | `3m` | Silence toléré avant le premier token d'un appel au modèle, pendant que le serveur lit le prompt (chaque aller-retour d'un tour en a un). Jamais plus court que `watchdogTimeout` ; doit être >= 0. Avec un serveur llama.cpp (`dmr`, `llamacpp`), mm demande `return_progress` : chaque rapport d'avancement relance ce délai, et le spinner affiche `Reading the prompt 12.0k/25.3k tokens`. À l'expiration, le watchdog dit `no first token from the server after …` (avant le premier token) ou `connection hang detected after …` (après). |
modified docs/fr/reference/tools.md +39 -0
@@ -159,6 +159,45 @@ Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur
159159 | ACP / fenêtre | Sorte `read` pour la navigation, sans permission ; `rename` est de sorte `edit` et demande la permission comme `edit_file` (`[rename rejected by the user]` en cas de refus). |
160160 | Compteur | `🔎 N lsp call(s)` dans la ligne de récapitulatif (voir plus bas). |
161161
162+## `fetch_content`
163+
164+Déclaré seulement avec `fetchContentTool: true`. Télécharge une page web et la renvoie en Markdown, ou enregistre ce Markdown dans un fichier. Paquet `internal/webmd`.
165+
166+| Champ | Type | Défaut | Description |
167+|-------|------|--------|-------------|
168+| `url` | chaîne | — | La page. Sans schéma, `https://` est ajouté (`k33g.org`) ; un schéma tapé sans ses deux-points est réparé (`https//k33g.org`). Seuls `http` et `https`. |
169+| `output` | chaîne | `""` | Fichier où enregistrer le Markdown, relatif au répertoire de travail (le `cwd` de la session en ACP) ; les répertoires parents sont créés. Renseigné, le Markdown n'est pas renvoyé. |
170+| `baseUrl` | chaîne | URL de la page | URL absolue qui sert à résoudre les liens et images relatifs. Par défaut : l'URL finale de la page, après redirections. |
171+| `tables` | chaîne | `html` | Tableaux avec `rowspan`/`colspan` ou un tableau imbriqué : `html` les garde en HTML brut, `flatten` produit un tableau GFM en recopiant une cellule fusionnée dans chaque case qu'elle couvre. |
172+| `strikethrough` | booléen | `false` | `<del>`, `<s>`, `<strike>` → `~~texte~~` ; sinon le texte seul. |
173+| `taskList` | booléen | `false` | Un élément de liste qui commence par une case à cocher → `- [ ] …` / `- [x] …` ; sinon la case est retirée. |
174+
175+| Aspect | Comportement |
176+|--------|--------------|
177+| Requête | `GET` avec `User-Agent: mm/<version>` ; 10 redirections au plus ; 30 s pour toute la récupération ; pages de plus de 5 Mio refusées. |
178+| robots.txt | Lu (et gardé 24 h par origine) avant la page et avant chaque cible de redirection, pour le jeton `mm`, sinon le groupe `*`. `Allow`/`Disallow`, la correspondance la plus longue l'emporte, `Allow` gagne en cas d'égalité, `*` et `$` final pris en charge (RFC 9309). Un robots.txt en 4xx autorise tout ; en 5xx ou injoignable, il interdit tout. |
179+| Types acceptés | `text/html`, `application/xhtml+xml` ; sans `Content-Type`, le type est deviné. |
180+| Encodage | Dans l'ordre : marque d'ordre des octets, `charset` de l'en-tête `Content-Type`, `<meta charset>` ou `<meta http-equiv="Content-Type">` dans les 4 premiers Kio, UTF-8 si les octets sont de l'UTF-8 valide, sinon `windows-1252`. Un nom inconnu est ignoré. Le contenu est converti en UTF-8. |
181+| Retiré | `script`, `style`, `noscript`, `template`, `meta`, `link`, `base`, `title`, `svg`, `canvas`, `iframe`, `object`, `embed`, `nav`, `aside`, `dialog`, `button`, `select`, `textarea`, les champs autres que les cases à cocher, un `footer` hors de `article`/`main`, les commentaires HTML, les rôles ARIA `navigation` `banner` `complementary` `search` `contentinfo` `dialog`, `aria-hidden="true"`, `hidden`, les mots de class/id `ad` `ads` `advert` `sponsor` `promo` `cookie` `consent` `gdpr` `newsletter` `subscribe` `popup` `modal` `share` `social` `breadcrumb` `nav` `navbar` `navigation` `menu` `sidebar` `skiplink` (et quelques variantes), les classes `sr-only` `visually-hidden` `screen-reader-text`. Un élément repéré par class/id est gardé s'il contient `main`, `article` ou `h1`. |
182+| Attributs | Tous retirés sauf `a[href]`, `img[src,alt]`, `td/th[colspan,rowspan]`, `ol[start]`, `input[type,checked]`, et les mots `language-*`/`lang-*` de la classe d'un `pre`/`code`. |
183+| Conversion | `h1`–`h6` → `#`–`######` ; `p` → paragraphe, ligne vide entre les blocs ; `strong`/`b` → `**…**` ; `em`/`i` → `*…*` ; `code`/`kbd`/`samp`/`tt` en ligne → accents graves ; `pre` → bloc clôturé (langage tiré de `language-x`), clôture plus longue que toute suite d'accents graves du code ; `br` → saut de ligne forcé `\` ; `hr` → `---` ; `blockquote` → `> ` ; `a` → `[texte](url)`, l'URL faute de texte, le texte seul pour `javascript:` ; `img` → `![alt](src)`, `![](src)` sans alt, l'alt seul pour une URI `data:` ; `ul`/`ol` → `- ` / `N. ` (depuis `start`), listes imbriquées indentées de la largeur du marqueur parent ; tableaux simples → GFM, première ligne comme en-tête, `|` échappé, `<br>` pour un saut de ligne dans une cellule, `<caption>` en paragraphe au-dessus. |
184+| Élément inconnu | Balises retirées, texte conservé ; s'il contient un bloc, il est traité comme un conteneur. |
185+| Élément vide | Rien, pas de ligne vide. |
186+| HTML invalide | Réparé par le parseur HTML5 (`golang.org/x/net/html`), puis converti. |
187+| Échappement | La barre oblique inverse, `*`, l'accent grave, `[`, `]`, `<` et un `_` hors d'un mot reçoivent une barre oblique inverse ; de même une ligne qui commence par `#`, `>`, `-`, `+`, `=`, `\|` ou `N.`. |
188+| Résultat | Le Markdown, terminé par un unique retour à la ligne ; ni titre, ni front matter, ni préambule. Tronqué à `maxOutput` caractères. |
189+| Avec `output` | `[fetch_content: saved N lines of Markdown (B bytes) from <url> to <fichier> — the content is not shown]`. |
190+| Page vide une fois nettoyée | `[fetch_content: <url> has no text content once scripts, navigation and ads are removed — nothing saved]` ; aucun fichier écrit. |
191+| Échec | `[fetch_content: error N (<classe>) — <raison>]`, voir les codes ci-dessous. |
192+| Affichage terminal | `🌐 fetch_content: <url>` (`→ <fichier>` avec `output`), indicateur `Fetching`, puis le résultat affiché comme la sortie d'une commande (seulement la ligne de confirmation avec `output`). |
193+| ACP / fenêtre | Genre `fetch`. Pas de permission sans `output` ; avec `output`, permission demandée comme pour `write_file` (`[fetch_content rejected by the user]` en cas de refus), et le chemin absolu du fichier comme emplacement. |
194+
195+| Code | Classe | Cause |
196+|------|--------|-------|
197+| `1` | `conversion` | Pas une page HTML, plus de 5 Mio, impossible à décoder, ou fichier de sortie impossible à écrire. |
198+| `2` | `input` | Hôte injoignable, délai dépassé, statut HTTP ≥ 300 après redirections, plus de 10 redirections, page interdite par le robots.txt ou robots.txt illisible. |
199+| `3` | `usage` | `url` vide ou illisible, schéma autre que `http`/`https`, valeur de `tables` inconnue, `baseUrl` non absolue. |
200+
162201 ## Détection de boucle
163202
164203 Chaque outil enregistre `(outil, entrée, sortie)` dans le détecteur de boucle après chaque appel.
@@ -159,6 +159,45 @@ Déclaré quand `lsp.servers` liste au moins un serveur dont la commande est sur
159 | ACP / fenêtre | Sorte `read` pour la navigation, sans permission ; `rename` est de sorte `edit` et demande la permission comme `edit_file` (`[rename rejected by the user]` en cas de refus). |159 | ACP / fenêtre | Sorte `read` pour la navigation, sans permission ; `rename` est de sorte `edit` et demande la permission comme `edit_file` (`[rename rejected by the user]` en cas de refus). |
160 | Compteur | `🔎 N lsp call(s)` dans la ligne de récapitulatif (voir plus bas). |160 | Compteur | `🔎 N lsp call(s)` dans la ligne de récapitulatif (voir plus bas). |
161 161
162+## `fetch_content`
163+
164+Déclaré seulement avec `fetchContentTool: true`. Télécharge une page web et la renvoie en Markdown, ou enregistre ce Markdown dans un fichier. Paquet `internal/webmd`.
165+
166+| Champ | Type | Défaut | Description |
167+|-------|------|--------|-------------|
168+| `url` | chaîne | — | La page. Sans schéma, `https://` est ajouté (`k33g.org`) ; un schéma tapé sans ses deux-points est réparé (`https//k33g.org`). Seuls `http` et `https`. |
169+| `output` | chaîne | `""` | Fichier où enregistrer le Markdown, relatif au répertoire de travail (le `cwd` de la session en ACP) ; les répertoires parents sont créés. Renseigné, le Markdown n'est pas renvoyé. |
170+| `baseUrl` | chaîne | URL de la page | URL absolue qui sert à résoudre les liens et images relatifs. Par défaut : l'URL finale de la page, après redirections. |
171+| `tables` | chaîne | `html` | Tableaux avec `rowspan`/`colspan` ou un tableau imbriqué : `html` les garde en HTML brut, `flatten` produit un tableau GFM en recopiant une cellule fusionnée dans chaque case qu'elle couvre. |
172+| `strikethrough` | booléen | `false` | `<del>`, `<s>`, `<strike>` → `~~texte~~` ; sinon le texte seul. |
173+| `taskList` | booléen | `false` | Un élément de liste qui commence par une case à cocher → `- [ ] …` / `- [x] …` ; sinon la case est retirée. |
174+
175+| Aspect | Comportement |
176+|--------|--------------|
177+| Requête | `GET` avec `User-Agent: mm/<version>` ; 10 redirections au plus ; 30 s pour toute la récupération ; pages de plus de 5 Mio refusées. |
178+| robots.txt | Lu (et gardé 24 h par origine) avant la page et avant chaque cible de redirection, pour le jeton `mm`, sinon le groupe `*`. `Allow`/`Disallow`, la correspondance la plus longue l'emporte, `Allow` gagne en cas d'égalité, `*` et `$` final pris en charge (RFC 9309). Un robots.txt en 4xx autorise tout ; en 5xx ou injoignable, il interdit tout. |
179+| Types acceptés | `text/html`, `application/xhtml+xml` ; sans `Content-Type`, le type est deviné. |
180+| Encodage | Dans l'ordre : marque d'ordre des octets, `charset` de l'en-tête `Content-Type`, `<meta charset>` ou `<meta http-equiv="Content-Type">` dans les 4 premiers Kio, UTF-8 si les octets sont de l'UTF-8 valide, sinon `windows-1252`. Un nom inconnu est ignoré. Le contenu est converti en UTF-8. |
181+| Retiré | `script`, `style`, `noscript`, `template`, `meta`, `link`, `base`, `title`, `svg`, `canvas`, `iframe`, `object`, `embed`, `nav`, `aside`, `dialog`, `button`, `select`, `textarea`, les champs autres que les cases à cocher, un `footer` hors de `article`/`main`, les commentaires HTML, les rôles ARIA `navigation` `banner` `complementary` `search` `contentinfo` `dialog`, `aria-hidden="true"`, `hidden`, les mots de class/id `ad` `ads` `advert` `sponsor` `promo` `cookie` `consent` `gdpr` `newsletter` `subscribe` `popup` `modal` `share` `social` `breadcrumb` `nav` `navbar` `navigation` `menu` `sidebar` `skiplink` (et quelques variantes), les classes `sr-only` `visually-hidden` `screen-reader-text`. Un élément repéré par class/id est gardé s'il contient `main`, `article` ou `h1`. |
182+| Attributs | Tous retirés sauf `a[href]`, `img[src,alt]`, `td/th[colspan,rowspan]`, `ol[start]`, `input[type,checked]`, et les mots `language-*`/`lang-*` de la classe d'un `pre`/`code`. |
183+| Conversion | `h1`–`h6` → `#`–`######` ; `p` → paragraphe, ligne vide entre les blocs ; `strong`/`b` → `**…**` ; `em`/`i` → `*…*` ; `code`/`kbd`/`samp`/`tt` en ligne → accents graves ; `pre` → bloc clôturé (langage tiré de `language-x`), clôture plus longue que toute suite d'accents graves du code ; `br` → saut de ligne forcé `\` ; `hr` → `---` ; `blockquote` → `> ` ; `a` → `[texte](url)`, l'URL faute de texte, le texte seul pour `javascript:` ; `img` → `![alt](src)`, `![](src)` sans alt, l'alt seul pour une URI `data:` ; `ul`/`ol` → `- ` / `N. ` (depuis `start`), listes imbriquées indentées de la largeur du marqueur parent ; tableaux simples → GFM, première ligne comme en-tête, `|` échappé, `<br>` pour un saut de ligne dans une cellule, `<caption>` en paragraphe au-dessus. |
184+| Élément inconnu | Balises retirées, texte conservé ; s'il contient un bloc, il est traité comme un conteneur. |
185+| Élément vide | Rien, pas de ligne vide. |
186+| HTML invalide | Réparé par le parseur HTML5 (`golang.org/x/net/html`), puis converti. |
187+| Échappement | La barre oblique inverse, `*`, l'accent grave, `[`, `]`, `<` et un `_` hors d'un mot reçoivent une barre oblique inverse ; de même une ligne qui commence par `#`, `>`, `-`, `+`, `=`, `\|` ou `N.`. |
188+| Résultat | Le Markdown, terminé par un unique retour à la ligne ; ni titre, ni front matter, ni préambule. Tronqué à `maxOutput` caractères. |
189+| Avec `output` | `[fetch_content: saved N lines of Markdown (B bytes) from <url> to <fichier> — the content is not shown]`. |
190+| Page vide une fois nettoyée | `[fetch_content: <url> has no text content once scripts, navigation and ads are removed — nothing saved]` ; aucun fichier écrit. |
191+| Échec | `[fetch_content: error N (<classe>) — <raison>]`, voir les codes ci-dessous. |
192+| Affichage terminal | `🌐 fetch_content: <url>` (`→ <fichier>` avec `output`), indicateur `Fetching`, puis le résultat affiché comme la sortie d'une commande (seulement la ligne de confirmation avec `output`). |
193+| ACP / fenêtre | Genre `fetch`. Pas de permission sans `output` ; avec `output`, permission demandée comme pour `write_file` (`[fetch_content rejected by the user]` en cas de refus), et le chemin absolu du fichier comme emplacement. |
194+
195+| Code | Classe | Cause |
196+|------|--------|-------|
197+| `1` | `conversion` | Pas une page HTML, plus de 5 Mio, impossible à décoder, ou fichier de sortie impossible à écrire. |
198+| `2` | `input` | Hôte injoignable, délai dépassé, statut HTTP ≥ 300 après redirections, plus de 10 redirections, page interdite par le robots.txt ou robots.txt illisible. |
199+| `3` | `usage` | `url` vide ou illisible, schéma autre que `http`/`https`, valeur de `tables` inconnue, `baseUrl` non absolue. |
200+
162 ## Détection de boucle201 ## Détection de boucle
163 202
164 Chaque outil enregistre `(outil, entrée, sortie)` dans le détecteur de boucle après chaque appel.203 Chaque outil enregistre `(outil, entrée, sortie)` dans le détecteur de boucle après chaque appel.
modified go.mod +6 -5
@@ -1,14 +1,17 @@
11 module mm
22
3-go 1.26
3+go 1.26.0
44
55 require (
66 github.com/firebase/genkit/go v1.10.0
77 github.com/gdamore/tcell/v2 v2.13.10
88 github.com/openai/openai-go v1.8.2
9+ github.com/sashabaranov/go-openai v1.42.1
910 go.lsp.dev/jsonrpc2 v1.0.1
1011 go.lsp.dev/protocol v1.0.1
1112 go.lsp.dev/uri v1.0.1
13+ golang.org/x/net v0.59.0
14+ golang.org/x/text v0.42.0
1215 gopkg.in/yaml.v3 v3.0.1
1316 )
1417
@@ -17,9 +20,7 @@ require (
1720 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect
1821 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
1922 github.com/rivo/uniseg v0.4.7 // indirect
20- github.com/sashabaranov/go-openai v1.42.1 // indirect
21- golang.org/x/term v0.39.0 // indirect
22- golang.org/x/text v0.36.0 // indirect
23+ golang.org/x/term v0.46.0 // indirect
2324 )
2425
2526 require (
@@ -49,5 +50,5 @@ require (
4950 go.opentelemetry.io/otel/metric v1.36.0 // indirect
5051 go.opentelemetry.io/otel/sdk v1.36.0 // indirect
5152 go.opentelemetry.io/otel/trace v1.36.0 // indirect
52- golang.org/x/sys v0.45.0 // indirect
53+ golang.org/x/sys v0.48.0 // indirect
5354 )
@@ -1,14 +1,17 @@
1 module mm1 module mm
2 2
3-go 1.263+go 1.26.0
4 4
5 require (5 require (
6 github.com/firebase/genkit/go v1.10.06 github.com/firebase/genkit/go v1.10.0
7 github.com/gdamore/tcell/v2 v2.13.107 github.com/gdamore/tcell/v2 v2.13.10
8 github.com/openai/openai-go v1.8.28 github.com/openai/openai-go v1.8.2
9+ github.com/sashabaranov/go-openai v1.42.1
9 go.lsp.dev/jsonrpc2 v1.0.110 go.lsp.dev/jsonrpc2 v1.0.1
10 go.lsp.dev/protocol v1.0.111 go.lsp.dev/protocol v1.0.1
11 go.lsp.dev/uri v1.0.112 go.lsp.dev/uri v1.0.1
13+ golang.org/x/net v0.59.0
14+ golang.org/x/text v0.42.0
12 gopkg.in/yaml.v3 v3.0.115 gopkg.in/yaml.v3 v3.0.1
13 )16 )
14 17
@@ -17,9 +20,7 @@ require (
17 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect20 github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect
18 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect21 github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
19 github.com/rivo/uniseg v0.4.7 // indirect22 github.com/rivo/uniseg v0.4.7 // indirect
20- github.com/sashabaranov/go-openai v1.42.1 // indirect23+ golang.org/x/term v0.46.0 // indirect
21- golang.org/x/term v0.39.0 // indirect
22- golang.org/x/text v0.36.0 // indirect
23 )24 )
24 25
25 require (26 require (
@@ -49,5 +50,5 @@ require (
49 go.opentelemetry.io/otel/metric v1.36.0 // indirect50 go.opentelemetry.io/otel/metric v1.36.0 // indirect
50 go.opentelemetry.io/otel/sdk v1.36.0 // indirect51 go.opentelemetry.io/otel/sdk v1.36.0 // indirect
51 go.opentelemetry.io/otel/trace v1.36.0 // indirect52 go.opentelemetry.io/otel/trace v1.36.0 // indirect
52- golang.org/x/sys v0.45.0 // indirect53+ golang.org/x/sys v0.48.0 // indirect
53 )54 )
modified go.sum +8 -6
@@ -104,6 +104,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
104104 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
105105 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
106106 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
107+golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
108+golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
107109 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
108110 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
109111 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -113,20 +115,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
113115 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114116 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115117 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116-golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
117-golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
118+golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
119+golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
118120 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
119121 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
120122 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
121-golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
122-golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
123+golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
124+golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
123125 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
124126 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
125127 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
126128 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
127129 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
128-golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
129-golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
130+golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
131+golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
130132 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
131133 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
132134 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -104,6 +104,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
104 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=104 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
105 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=105 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
106 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=106 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
107+golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
108+golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
107 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=109 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
108 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=110 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
109 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=111 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -113,20 +115,20 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
113 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=115 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=116 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=117 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116-golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=118+golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
117-golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=119+golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
118 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=120 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
119 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=121 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
120 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=122 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
121-golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=123+golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
122-golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=124+golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
123 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=125 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
124 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=126 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
125 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=127 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
126 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=128 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
127 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=129 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
128-golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=130+golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
129-golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=131+golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
130 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=132 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
131 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=133 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
132 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=134 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
modified internal/acp/acp_test.go +1 -0
@@ -40,6 +40,7 @@ func TestToolKindDefaultsToExecute(t *testing.T) {
4040 cases := map[string]sdk.ToolKind{
4141 "read": sdk.ToolKindRead,
4242 "edit": sdk.ToolKindEdit,
43+ "fetch": sdk.ToolKindFetch,
4344 "execute": sdk.ToolKindExecute,
4445 "": sdk.ToolKindExecute,
4546 "exotic": sdk.ToolKindExecute,
@@ -40,6 +40,7 @@ func TestToolKindDefaultsToExecute(t *testing.T) {
40 cases := map[string]sdk.ToolKind{40 cases := map[string]sdk.ToolKind{
41 "read": sdk.ToolKindRead,41 "read": sdk.ToolKindRead,
42 "edit": sdk.ToolKindEdit,42 "edit": sdk.ToolKindEdit,
43+ "fetch": sdk.ToolKindFetch,
43 "execute": sdk.ToolKindExecute,44 "execute": sdk.ToolKindExecute,
44 "": sdk.ToolKindExecute,45 "": sdk.ToolKindExecute,
45 "exotic": sdk.ToolKindExecute,46 "exotic": sdk.ToolKindExecute,
modified internal/acp/sink.go +2 -0
@@ -149,6 +149,8 @@ func toolKind(kind string) sdk.ToolKind {
149149 return sdk.ToolKindRead
150150 case "edit":
151151 return sdk.ToolKindEdit
152+ case "fetch":
153+ return sdk.ToolKindFetch
152154 default:
153155 return sdk.ToolKindExecute
154156 }
@@ -149,6 +149,8 @@ func toolKind(kind string) sdk.ToolKind {
149 return sdk.ToolKindRead149 return sdk.ToolKindRead
150 case "edit":150 case "edit":
151 return sdk.ToolKindEdit151 return sdk.ToolKindEdit
152+ case "fetch":
153+ return sdk.ToolKindFetch
152 default:154 default:
153 return sdk.ToolKindExecute155 return sdk.ToolKindExecute
154 }156 }
modified internal/config/config.go +7 -0
@@ -100,6 +100,12 @@ type Config struct {
100100 // On by default. Off to disable plan creation and tracking via the `plan` tool.
101101 PlanTool bool `yaml:"planTool"`
102102
103+ // FetchContentTool: declare the built-in `fetch_content` tool, which
104+ // downloads a web page and returns it as Markdown. OFF by default: it is
105+ // the one built-in tool that reaches the network, so an operator turns it
106+ // on knowingly (`fetchContentTool: true`).
107+ FetchContentTool bool `yaml:"fetchContentTool"`
108+
103109 // Sampling: generation settings (OpenAI API keys; the plugin converts them.)
104110 Sampling map[string]any `yaml:"sampling"`
105111
@@ -414,6 +420,7 @@ var Cfg = Config{
414420 BashTool: true,
415421 EditTools: true,
416422 PlanTool: true,
423+ // FetchContentTool stays false (the zero value): see its field comment.
417424 System: `You are a coding agent working in a terminal.
418425 You have a "bash" tool to run shell commands.
419426 Use it to explore files, run tests, inspect the repository, etc.
@@ -100,6 +100,12 @@ type Config struct {
100 // On by default. Off to disable plan creation and tracking via the `plan` tool.100 // On by default. Off to disable plan creation and tracking via the `plan` tool.
101 PlanTool bool `yaml:"planTool"`101 PlanTool bool `yaml:"planTool"`
102 102
103+ // FetchContentTool: declare the built-in `fetch_content` tool, which
104+ // downloads a web page and returns it as Markdown. OFF by default: it is
105+ // the one built-in tool that reaches the network, so an operator turns it
106+ // on knowingly (`fetchContentTool: true`).
107+ FetchContentTool bool `yaml:"fetchContentTool"`
108+
103 // Sampling: generation settings (OpenAI API keys; the plugin converts them.)109 // Sampling: generation settings (OpenAI API keys; the plugin converts them.)
104 Sampling map[string]any `yaml:"sampling"`110 Sampling map[string]any `yaml:"sampling"`
105 111
@@ -414,6 +420,7 @@ var Cfg = Config{
414 BashTool: true,420 BashTool: true,
415 EditTools: true,421 EditTools: true,
416 PlanTool: true,422 PlanTool: true,
423+ // FetchContentTool stays false (the zero value): see its field comment.
417 System: `You are a coding agent working in a terminal.424 System: `You are a coding agent working in a terminal.
418 You have a "bash" tool to run shell commands.425 You have a "bash" tool to run shell commands.
419 Use it to explore files, run tests, inspect the repository, etc.426 Use it to explore files, run tests, inspect the repository, etc.
modified internal/config/config_test.go +21 -0
@@ -196,3 +196,24 @@ func TestACPConfig(t *testing.T) {
196196 t.Errorf("Load(bad.yaml) = %v, want an acp.name validation error", err)
197197 }
198198 }
199+
200+// fetch_content reaches the network, so it is off unless the YAML asks for it.
201+func TestFetchContentToolConfig(t *testing.T) {
202+ defaults := Cfg
203+ t.Cleanup(func() { Cfg = defaults })
204+
205+ if Cfg.FetchContentTool {
206+ t.Fatal("fetchContentTool is on by default, want off")
207+ }
208+
209+ on := filepath.Join(t.TempDir(), "on.yaml")
210+ if err := os.WriteFile(on, []byte("fetchContentTool: true\n"), 0o644); err != nil {
211+ t.Fatal(err)
212+ }
213+ if _, err := Load(on); err != nil {
214+ t.Fatal(err)
215+ }
216+ if !Cfg.FetchContentTool {
217+ t.Error("fetchContentTool: true was not read")
218+ }
219+}
@@ -196,3 +196,24 @@ func TestACPConfig(t *testing.T) {
196 t.Errorf("Load(bad.yaml) = %v, want an acp.name validation error", err)196 t.Errorf("Load(bad.yaml) = %v, want an acp.name validation error", err)
197 }197 }
198 }198 }
199+
200+// fetch_content reaches the network, so it is off unless the YAML asks for it.
201+func TestFetchContentToolConfig(t *testing.T) {
202+ defaults := Cfg
203+ t.Cleanup(func() { Cfg = defaults })
204+
205+ if Cfg.FetchContentTool {
206+ t.Fatal("fetchContentTool is on by default, want off")
207+ }
208+
209+ on := filepath.Join(t.TempDir(), "on.yaml")
210+ if err := os.WriteFile(on, []byte("fetchContentTool: true\n"), 0o644); err != nil {
211+ t.Fatal(err)
212+ }
213+ if _, err := Load(on); err != nil {
214+ t.Fatal(err)
215+ }
216+ if !Cfg.FetchContentTool {
217+ t.Error("fetchContentTool: true was not read")
218+ }
219+}
added internal/tools/fetchcontent.go +200 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/tools/fetchcontent_test.go +191 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/blocks.go +86 -0
new file mode 100644
@@ -0,0 +1,86 @@
1+package webmd
2+
3+import (
4+ "strings"
5+
6+ "golang.org/x/net/html"
7+ "golang.org/x/net/html/atom"
8+)
9+
10+// Renderers of the block elements that need more than their children.
11+
12+func heading(level int, text string) string {
13+ text = strings.TrimSpace(strings.ReplaceAll(text, "\n", " "))
14+ if text == "" {
15+ return ""
16+ }
17+ return strings.Repeat("#", level) + " " + text
18+}
19+
20+// codeBlock renders <pre> as a fenced block, its text untouched. The fence
21+// is longer than any backtick run inside, and its info string is the
22+// language of a "language-x" / "lang-x" class on the <pre> or its <code>.
23+func codeBlock(pre *html.Node) string {
24+ code := strings.TrimRight(strings.TrimPrefix(textContent(pre), "\n"), "\n")
25+ if strings.TrimSpace(code) == "" {
26+ return ""
27+ }
28+ fence := strings.Repeat("`", max(3, longestRun(code, '`')+1))
29+ return fence + language(pre) + "\n" + code + "\n" + fence
30+}
31+
32+func language(pre *html.Node) string {
33+ nodes := []*html.Node{pre}
34+ for ch := pre.FirstChild; ch != nil; ch = ch.NextSibling {
35+ if ch.DataAtom == atom.Code {
36+ nodes = append(nodes, ch)
37+ }
38+ }
39+ for _, n := range nodes {
40+ for _, word := range strings.Fields(attr(n, "class")) {
41+ if lang, ok := strings.CutPrefix(word, "language-"); ok {
42+ return lang
43+ }
44+ if lang, ok := strings.CutPrefix(word, "lang-"); ok {
45+ return lang
46+ }
47+ }
48+ }
49+ return ""
50+}
51+
52+// quote prefixes every line of md with "> ".
53+func quote(md string) string {
54+ if md == "" {
55+ return ""
56+ }
57+ lines := strings.Split(md, "\n")
58+ for i, line := range lines {
59+ if line == "" {
60+ lines[i] = ">"
61+ } else {
62+ lines[i] = "> " + line
63+ }
64+ }
65+ return strings.Join(lines, "\n")
66+}
67+
68+// textContent is the raw text under n — no escaping, no whitespace
69+// collapsing — with <br> as a newline. It is what code keeps.
70+func textContent(n *html.Node) string {
71+ var b strings.Builder
72+ var walk func(*html.Node)
73+ walk = func(n *html.Node) {
74+ switch {
75+ case n.Type == html.TextNode:
76+ b.WriteString(n.Data)
77+ case n.DataAtom == atom.Br:
78+ b.WriteString("\n")
79+ }
80+ for ch := n.FirstChild; ch != nil; ch = ch.NextSibling {
81+ walk(ch)
82+ }
83+ }
84+ walk(n)
85+ return b.String()
86+}
new file mode 100644
@@ -0,0 +1,86 @@
1+package webmd
2+
3+import (
4+ "strings"
5+
6+ "golang.org/x/net/html"
7+ "golang.org/x/net/html/atom"
8+)
9+
10+// Renderers of the block elements that need more than their children.
11+
12+func heading(level int, text string) string {
13+ text = strings.TrimSpace(strings.ReplaceAll(text, "\n", " "))
14+ if text == "" {
15+ return ""
16+ }
17+ return strings.Repeat("#", level) + " " + text
18+}
19+
20+// codeBlock renders <pre> as a fenced block, its text untouched. The fence
21+// is longer than any backtick run inside, and its info string is the
22+// language of a "language-x" / "lang-x" class on the <pre> or its <code>.
23+func codeBlock(pre *html.Node) string {
24+ code := strings.TrimRight(strings.TrimPrefix(textContent(pre), "\n"), "\n")
25+ if strings.TrimSpace(code) == "" {
26+ return ""
27+ }
28+ fence := strings.Repeat("`", max(3, longestRun(code, '`')+1))
29+ return fence + language(pre) + "\n" + code + "\n" + fence
30+}
31+
32+func language(pre *html.Node) string {
33+ nodes := []*html.Node{pre}
34+ for ch := pre.FirstChild; ch != nil; ch = ch.NextSibling {
35+ if ch.DataAtom == atom.Code {
36+ nodes = append(nodes, ch)
37+ }
38+ }
39+ for _, n := range nodes {
40+ for _, word := range strings.Fields(attr(n, "class")) {
41+ if lang, ok := strings.CutPrefix(word, "language-"); ok {
42+ return lang
43+ }
44+ if lang, ok := strings.CutPrefix(word, "lang-"); ok {
45+ return lang
46+ }
47+ }
48+ }
49+ return ""
50+}
51+
52+// quote prefixes every line of md with "> ".
53+func quote(md string) string {
54+ if md == "" {
55+ return ""
56+ }
57+ lines := strings.Split(md, "\n")
58+ for i, line := range lines {
59+ if line == "" {
60+ lines[i] = ">"
61+ } else {
62+ lines[i] = "> " + line
63+ }
64+ }
65+ return strings.Join(lines, "\n")
66+}
67+
68+// textContent is the raw text under n — no escaping, no whitespace
69+// collapsing — with <br> as a newline. It is what code keeps.
70+func textContent(n *html.Node) string {
71+ var b strings.Builder
72+ var walk func(*html.Node)
73+ walk = func(n *html.Node) {
74+ switch {
75+ case n.Type == html.TextNode:
76+ b.WriteString(n.Data)
77+ case n.DataAtom == atom.Br:
78+ b.WriteString("\n")
79+ }
80+ for ch := n.FirstChild; ch != nil; ch = ch.NextSibling {
81+ walk(ch)
82+ }
83+ }
84+ walk(n)
85+ return b.String()
86+}
added internal/webmd/charset.go +131 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/chrome.go +143 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/clean.go +107 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/convert.go +187 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/convert_test.go +168 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/errors.go +77 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/fetch.go +325 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/fetch_test.go +237 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/grid.go +89 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/inline.go +95 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/lists.go +108 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/robots.go +125 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/robots_test.go +70 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/tables.go +110 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/text.go +149 -0
Large diff collapsed to keep the page fast — load it when you need it.
added internal/webmd/webmd.go +73 -0
Large diff collapsed to keep the page fast — load it when you need it.
modified main.go +10 -2
@@ -49,6 +49,7 @@ import (
4949 "mm/internal/tui"
5050 "mm/internal/ui"
5151 "mm/internal/version"
52+ "mm/internal/webmd"
5253
5354 "github.com/firebase/genkit/go/ai"
5455 )
@@ -256,8 +257,9 @@ type toolSet struct {
256257 // description carries the catalogue, so declaring it empty would advertise a
257258 // tool that can do nothing); the file tools behind `editTools` — the same
258259 // binary must run as part 09 did (bash + the `edit` CLI) and as this part
259-// (built-in tools), so the two can be compared on the same prompts; and
260-// `lsp` when `lsp.servers` names at least one server whose command is on
260+// (built-in tools), so the two can be compared on the same prompts;
261+// `fetch_content` behind `fetchContentTool`, off by default because it is the
262+// one built-in tool that reaches the network; and `lsp` when `lsp.servers` names at least one server whose command is on
261263 // the PATH — a missing server is a warning, never a refusal to start.
262264 func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet {
263265 set := toolSet{close: func() {}}
@@ -287,6 +289,12 @@ func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet {
287289 set.tools = append(set.tools, tools.PlanTool(e.G, d))
288290 set.names = append(set.names, "plan")
289291 }
292+ if config.Cfg.FetchContentTool {
293+ // The product token "mm" is what the sites' robots.txt are read for.
294+ fetcher := webmd.NewFetcher("mm/" + version.Short())
295+ set.tools = append(set.tools, tools.FetchContent(e.G, d, fetcher))
296+ set.names = append(set.names, "fetch_content")
297+ }
290298 assembleLSP(e, d, &set)
291299 assembleMCP(e, d, &set)
292300 return set
@@ -49,6 +49,7 @@ import (
49 "mm/internal/tui"49 "mm/internal/tui"
50 "mm/internal/ui"50 "mm/internal/ui"
51 "mm/internal/version"51 "mm/internal/version"
52+ "mm/internal/webmd"
52 53
53 "github.com/firebase/genkit/go/ai"54 "github.com/firebase/genkit/go/ai"
54 )55 )
@@ -256,8 +257,9 @@ type toolSet struct {
256 // description carries the catalogue, so declaring it empty would advertise a257 // description carries the catalogue, so declaring it empty would advertise a
257 // tool that can do nothing); the file tools behind `editTools` — the same258 // tool that can do nothing); the file tools behind `editTools` — the same
258 // binary must run as part 09 did (bash + the `edit` CLI) and as this part259 // binary must run as part 09 did (bash + the `edit` CLI) and as this part
259-// (built-in tools), so the two can be compared on the same prompts; and260+// (built-in tools), so the two can be compared on the same prompts;
260-// `lsp` when `lsp.servers` names at least one server whose command is on261+// `fetch_content` behind `fetchContentTool`, off by default because it is the
262+// one built-in tool that reaches the network; and `lsp` when `lsp.servers` names at least one server whose command is on
261 // the PATH — a missing server is a warning, never a refusal to start.263 // the PATH — a missing server is a warning, never a refusal to start.
262 func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet {264 func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet {
263 set := toolSet{close: func() {}}265 set := toolSet{close: func() {}}
@@ -287,6 +289,12 @@ func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet {
287 set.tools = append(set.tools, tools.PlanTool(e.G, d))289 set.tools = append(set.tools, tools.PlanTool(e.G, d))
288 set.names = append(set.names, "plan")290 set.names = append(set.names, "plan")
289 }291 }
292+ if config.Cfg.FetchContentTool {
293+ // The product token "mm" is what the sites' robots.txt are read for.
294+ fetcher := webmd.NewFetcher("mm/" + version.Short())
295+ set.tools = append(set.tools, tools.FetchContent(e.G, d, fetcher))
296+ set.names = append(set.names, "fetch_content")
297+ }
290 assembleLSP(e, d, &set)298 assembleLSP(e, d, &set)
291 assembleMCP(e, d, &set)299 assembleMCP(e, d, &set)
292 return set300 return set